Add support for multiple forms of blacklist queries using matches.

It supports both literal and last octet matches from the dnsbl.
If matches is not present, the old behaviour is used.
This commit is contained in:
Elizabeth Myers 2013-04-20 16:17:29 -05:00
parent 543a10c338
commit 3c93d380e0
5 changed files with 219 additions and 21 deletions

View File

@ -388,6 +388,14 @@ serverhide {
* IPv4 is currently the default as few blacklists support IPv6 operation
* as of this writing.
*
* As of charybdis 3.5, a matches parameter is allowed; if omitted, any result
* is considered a match. If included, a comma-separated list of *quoted*
* strings is allowed to match queries. They may be of the format "0" to "255"
* to match the final octet (e.g. 127.0.0.1) or "127.x.y.z" to explicitly match
* an A record. The blacklist is only applied if it matches anything in the
* list. You may freely mix full IP's and final octets. Consult your blacklist
* provider for the meaning of these parameters.
*
* Note: AHBL (the providers of the below *.ahbl.org BLs) request that they be
* contacted, via email, at admins@2mbit.com before using these BLs.
* See <http://www.ahbl.org/services.php> for more information.
@ -405,9 +413,10 @@ blacklist {
# type = ipv4;
# reject_reason = "${nick}, your IP (${ip}) is listed as a TOR exit node. In order to protect ${network-name} from tor-based abuse, we are not allowing TOR exit nodes to connect to our network.";
#
/* Example of a blacklist that supports both IPv4 and IPv6 */
/* Example of a blacklist that supports both IPv4 and IPv6 and using matches */
# host = "foobl.blacklist.invalid";
# type = ipv4, ipv6;
# matches = "4", "6", "127.0.0.10";
# reject_reason = "${nick}, your IP (${ip}) is listed in ${dnsbl-host} for some reason. In order to protect ${network-name} from abuse, we are not allowing connections listed in ${dnsbl-host} to connect";
};

View File

@ -843,6 +843,14 @@ serverhide {
* IPv4 is currently the default as few blacklists support IPv6 operation
* as of this writing.
*
* As of charybdis 3.5, a matches parameter is allowed; if omitted, any result
* is considered a match. If included, a comma-separated list of *quoted*
* strings is allowed to match queries. They may be of the format "0" to "255"
* to match the final octet (e.g. 127.0.0.1) or "127.x.y.z" to explicitly match
* an A record. The blacklist is only applied if it matches anything in the
* list. You may freely mix full IP's and final octets. Consult your blacklist
* provider for the meaning of these parameters.
*
* Note: AHBL (the providers of the below *.ahbl.org BLs) request that they be
* contacted, via email, at admins@2mbit.com before using these BLs.
* See <http://www.ahbl.org/services.php> for more information.
@ -860,8 +868,9 @@ blacklist {
# type = ipv4;
# reject_reason = "${nick}, your IP (${ip}) is listed as a TOR exit node. In order to protect ${network-name} from tor-based abuse, we are not allowing TOR exit nodes to connect to our network.";
#
/* Example of a blacklist that supports both IPv4 and IPv6 */
/* Example of a blacklist that supports both IPv4 and IPv6 and using matches */
# host = "foobl.blacklist.invalid";
# matches = "4", "6", "127.0.0.10";
# type = ipv4, ipv6;
# reject_reason = "${nick}, your IP (${ip}) is listed in ${dnsbl-host} for some reason. In order to protect ${network-name} from abuse, we are not allowing connections listed in ${dnsbl-host} to connect";
};

View File

@ -24,6 +24,11 @@
#ifndef _BLACKLIST_H_
#define _BLACKLIST_H_
#include "stdinc.h"
#define BLACKLIST_FILTER_ALL 1
#define BLACKLIST_FILTER_LAST 2
/* A configured DNSBL */
struct Blacklist {
unsigned int status; /* If CONF_ILLEGAL, delete when no clients */
@ -31,6 +36,7 @@ struct Blacklist {
int ipv4; /* Does this blacklist support IPv4 lookups? */
int ipv6; /* Does this blacklist support IPv6 lookups? */
char host[IRCD_RES_HOSTLEN + 1];
rb_dlink_list filters; /* Filters for queries */
char reject_reason[IRCD_BUFSIZE];
unsigned int hits;
time_t lastwarning;
@ -44,8 +50,15 @@ struct BlacklistClient {
rb_dlink_node node;
};
/* A blacklist filter */
struct BlacklistFilter {
int type; /* Type of filter */
char filterstr[HOSTIPLEN]; /* The filter itself */
rb_dlink_node node;
};
/* public interfaces */
struct Blacklist *new_blacklist(char *host, char *reject_reason, int ipv4, int ipv6);
struct Blacklist *new_blacklist(char *host, char *reject_reason, int ipv4, int ipv6, rb_dlink_list *filters);
void lookup_blacklists(struct Client *client_p);
void abort_blacklist_queries(struct Client *client_p);
void unref_blacklist(struct Blacklist *blptr);

View File

@ -59,6 +59,64 @@ static struct Blacklist *find_blacklist(char *name)
return NULL;
}
static inline int blacklist_check_reply(struct BlacklistClient *blcptr, struct rb_sockaddr_storage *addr)
{
struct Blacklist *blptr = blcptr->blacklist;
char ipaddr[HOSTIPLEN];
char *lastoctet;
rb_dlink_node *ptr;
/* XXX the below two checks might want to change at some point
* e.g. if IPv6 blacklists don't use 127.x.y.z or A records anymore
* --Elizabeth
*/
if (addr->ss_family != AF_INET ||
memcmp(&((struct sockaddr_in *)addr)->sin_addr, "\177", 1))
goto blwarn;
/* No filters and entry found - thus positive match */
if (!rb_dlink_list_length(&blptr->filters))
return 1;
rb_inet_ntop_sock((struct sockaddr *)addr, ipaddr, sizeof(ipaddr));
/* Below will prolly have to change too if the above changes */
if ((lastoctet = strrchr(ipaddr, '.')) == NULL || *(++lastoctet) == '\0')
goto blwarn;
RB_DLINK_FOREACH(ptr, blcptr->blacklist->filters.head)
{
struct BlacklistFilter *filter = ptr->data;
char *cmpstr;
if (filter->type == BLACKLIST_FILTER_ALL)
cmpstr = ipaddr;
else if (filter->type == BLACKLIST_FILTER_LAST)
cmpstr = lastoctet;
else
{
sendto_realops_snomask(SNO_GENERAL, L_ALL,
"blacklist_check_reply(): Unknown filtertype (BUG!)");
continue;
}
if (strcmp(cmpstr, filter->filterstr) == 0)
/* Match! */
return 1;
}
return 0;
blwarn:
if (blcptr->blacklist->lastwarning + 3600 < rb_current_time())
{
sendto_realops_snomask(SNO_GENERAL, L_ALL,
"Garbage reply from blacklist %s",
blcptr->blacklist->host);
blcptr->blacklist->lastwarning = rb_current_time();
}
return 0;
}
static void blacklist_dns_callback(void *vptr, struct DNSReply *reply)
{
struct BlacklistClient *blcptr = (struct BlacklistClient *) vptr;
@ -77,17 +135,8 @@ static void blacklist_dns_callback(void *vptr, struct DNSReply *reply)
if (reply != NULL)
{
/* only accept 127.x.y.z as a listing */
if (reply->addr.ss_family == AF_INET &&
!memcmp(&((struct sockaddr_in *)&reply->addr)->sin_addr, "\177", 1))
if (blacklist_check_reply(blcptr, &reply->addr))
listed = TRUE;
else if (blcptr->blacklist->lastwarning + 3600 < rb_current_time())
{
sendto_realops_snomask(SNO_GENERAL, L_ALL,
"Garbage reply from blacklist %s",
blcptr->blacklist->host);
blcptr->blacklist->lastwarning = rb_current_time();
}
}
/* they have a blacklist entry for this client */
@ -180,7 +229,7 @@ static void initiate_blacklist_dnsquery(struct Blacklist *blptr, struct Client *
}
/* public interfaces */
struct Blacklist *new_blacklist(char *name, char *reject_reason, int ipv4, int ipv6)
struct Blacklist *new_blacklist(char *name, char *reject_reason, int ipv4, int ipv6, rb_dlink_list *filters)
{
struct Blacklist *blptr;
@ -195,10 +244,14 @@ struct Blacklist *new_blacklist(char *name, char *reject_reason, int ipv4, int i
}
else
blptr->status &= ~CONF_ILLEGAL;
rb_strlcpy(blptr->host, name, IRCD_RES_HOSTLEN + 1);
rb_strlcpy(blptr->reject_reason, reject_reason, IRCD_BUFSIZE);
blptr->ipv4 = ipv4;
blptr->ipv6 = ipv6;
rb_dlinkMoveList(filters, &blptr->filters);
blptr->lastwarning = 0;
return blptr;
@ -206,9 +259,17 @@ struct Blacklist *new_blacklist(char *name, char *reject_reason, int ipv4, int i
void unref_blacklist(struct Blacklist *blptr)
{
rb_dlink_node *ptr, *next_ptr;
blptr->refcount--;
if (blptr->status & CONF_ILLEGAL && blptr->refcount <= 0)
{
RB_DLINK_FOREACH_SAFE(ptr, next_ptr, blptr->filters.head)
{
rb_free(ptr);
rb_dlinkDelete(ptr, &blptr->filters);
}
rb_dlinkFindDestroy(blptr, &blacklist_list);
rb_free(blptr);
}

View File

@ -58,6 +58,7 @@ static char *yy_blacklist_host = NULL;
static char *yy_blacklist_reason = NULL;
static int yy_blacklist_ipv4 = 1;
static int yy_blacklist_ipv6 = 0;
static rb_dlink_list yy_blacklist_filters;
static char *yy_privset_extends = NULL;
@ -1779,9 +1780,24 @@ conf_set_alias_target(void *data)
yy_alias->target = rb_strdup(data);
}
/* XXX for below */
static void conf_set_blacklist_reason(void *data);
static void
conf_set_blacklist_host(void *data)
{
if (yy_blacklist_host)
{
conf_report_error("blacklist::host %s overlaps existing host %s",
(char *)data, yy_blacklist_host);
/* Cleanup */
conf_set_blacklist_reason(NULL);
return;
}
yy_blacklist_ipv4 = 1;
yy_blacklist_ipv6 = 0;
yy_blacklist_host = rb_strdup(data);
}
@ -1813,10 +1829,90 @@ conf_set_blacklist_type(void *data)
}
}
static void
conf_set_blacklist_matches(void *data)
{
conf_parm_t *args = data;
for (; args; args = args->next)
{
struct BlacklistFilter *filter;
char *str = args->v.string;
char *p;
int type = BLACKLIST_FILTER_LAST;
if (CF_TYPE(args->type) != CF_QSTRING)
{
conf_report_error("blacklist::matches -- must be quoted string");
continue;
}
if (str == NULL)
{
conf_report_error("blacklist::matches -- invalid entry");
continue;
}
if (strlen(str) > HOSTIPLEN)
{
conf_report_error("blacklist::matches has an entry too long: %s",
str);
continue;
}
for (p = str; *p != '\0'; p++)
{
/* Check for validity */
if (*p == '.')
type = BLACKLIST_FILTER_ALL;
else if (!isalnum(*p))
{
conf_report_error("blacklist::matches has invalid IP match entry %s",
str);
type = 0;
break;
}
}
if (type == BLACKLIST_FILTER_ALL)
{
/* Basic IP sanity check */
struct rb_sockaddr_storage tmp;
if (rb_inet_pton(AF_INET, str, &tmp) <= 0)
{
conf_report_error("blacklist::matches has invalid IP match entry %s",
str);
continue;
}
}
else if (type == BLACKLIST_FILTER_LAST)
{
/* Verify it's the correct length */
if (strlen(str) > 3)
{
conf_report_error("blacklist::matches has invalid octet match entry %s",
str);
continue;
}
}
else
{
continue; /* Invalid entry */
}
filter = rb_malloc(sizeof(struct BlacklistFilter));
filter->type = type;
rb_strlcpy(filter->filterstr, str, sizeof(filter->filterstr));
rb_dlinkAdd(filter, &filter->node, &yy_blacklist_filters);
}
}
static void
conf_set_blacklist_reason(void *data)
{
yy_blacklist_reason = rb_strdup(data);
rb_dlink_node *ptr, *nptr;
if (yy_blacklist_host && yy_blacklist_reason)
{
@ -1842,16 +1938,25 @@ conf_set_blacklist_reason(void *data)
}
}
new_blacklist(yy_blacklist_host, yy_blacklist_reason, yy_blacklist_ipv4, yy_blacklist_ipv6);
new_blacklist(yy_blacklist_host, yy_blacklist_reason, yy_blacklist_ipv4, yy_blacklist_ipv6,
&yy_blacklist_filters);
}
cleanup_bl:
rb_free(yy_blacklist_host);
rb_free(yy_blacklist_reason);
yy_blacklist_host = NULL;
yy_blacklist_reason = NULL;
yy_blacklist_ipv4 = 1;
yy_blacklist_ipv6 = 0;
RB_DLINK_FOREACH_SAFE(ptr, nptr, yy_blacklist_filters.head)
{
if (data == NULL)
rb_free(ptr);
rb_dlinkDelete(ptr, &yy_blacklist_filters);
}
rb_free(yy_blacklist_host);
rb_free(yy_blacklist_reason);
yy_blacklist_host = NULL;
yy_blacklist_reason = NULL;
yy_blacklist_ipv4 = 1;
yy_blacklist_ipv6 = 0;
}
/* public functions */
@ -2354,5 +2459,6 @@ newconf_init()
add_top_conf("blacklist", NULL, NULL, NULL);
add_conf_item("blacklist", "host", CF_QSTRING, conf_set_blacklist_host);
add_conf_item("blacklist", "type", CF_STRING | CF_FLIST, conf_set_blacklist_type);
add_conf_item("blacklist", "matches", CF_QSTRING | CF_FLIST, conf_set_blacklist_matches);
add_conf_item("blacklist", "reject_reason", CF_QSTRING, conf_set_blacklist_reason);
}