From 361f100f1caeddcdf2b6e485c973cc8dd43b8f6c Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 1 Sep 2026 23:09:55 +0200 Subject: [PATCH 1/6] DNSBL iauth module: rewrite with non-blocking lookups and new configuration --- doc/iauth.conf.example | 8 +- iauth/a_conf.c | 74 +- iauth/a_conf_def.h | 11 + iauth/mod_dnsbl.c | 1453 +++++++++++++++++++++++++++++++++------- support/Makefile.in | 3 +- 5 files changed, 1295 insertions(+), 254 deletions(-) diff --git a/doc/iauth.conf.example b/doc/iauth.conf.example index 1998ab58..15f2cefa 100644 --- a/doc/iauth.conf.example +++ b/doc/iauth.conf.example @@ -46,10 +46,12 @@ module rfc931 # option = reject,careful # reason = Denied access (insecure proxy found) -# Reject clients based on DNS BL. #module dnsbl -# option = log,reject,servers=bl1.example.org,bl2.example.org -# reason = Denied access (DNSBL) +# option = log,reject,skip_if_ident,skip_if_sasl +# reason = Denied access (DNSBL) +# entry = torexit.dan.me.uk|Tor Exit Node +# entry = dnsbl.dronebl.org|You are listed in DroneBL - https://dronebl.org/lookup?ip={ip} + # Check clients using an external scanning service. #module external diff --git a/iauth/a_conf.c b/iauth/a_conf.c index 0a99b51f..0517815e 100644 --- a/iauth/a_conf.c +++ b/iauth/a_conf.c @@ -31,11 +31,39 @@ static const volatile char rcsid[] = "@(#)$Id: a_conf.c,v 1.39 2004/11/13 16:46: static aModule *Mlist[16]; #define DEFAULT_TIMEOUT 30 +#define CONF_BUFSIZE 1024 u_int debuglevel = 0; AnInstance *instances = NULL; + +static void conf_kv_add(AnInstance *inst, const char *key, const char *value) +{ + aConfKV **last = &inst->module_kv; + + while (*last) + last = &(*last)->next; + *last = (aConfKV *) malloc(sizeof(aConfKV)); + (*last)->key = mystrdup((char *) key); + (*last)->value = mystrdup((char *) value); + (*last)->next = NULL; +} + +static char *conf_trim(char *s) +{ + char *e; + + while (*s == ' ' || *s == ' ') + s++; + if (*s == '\0') + return s; + e = s + strlen(s); + while (e > s && (e[-1] == ' ' || e[-1] == ' ')) + *--e = '\0'; + return s; +} + static void conf_err(u_int nb, char *msg, char *chk) { if (chk) @@ -56,7 +84,7 @@ char *conf_read(char *cfile) u_int timeout = DEFAULT_TIMEOUT, totto = 0; u_int lnnb = 0, i; u_char icount = 0, Mcnt = 0; - char buffer[160], *ch; + char buffer[CONF_BUFSIZE], *ch; AnInstance **last = &instances, *itmp; FILE *cfh; @@ -79,7 +107,7 @@ char *conf_read(char *cfile) } else { - while (fgets(buffer, 160, cfh)) + while (fgets(buffer, sizeof(buffer), cfh)) { if ((ch = index(buffer, '\n'))) { @@ -90,7 +118,7 @@ char *conf_read(char *cfile) conf_err(lnnb, "line too long, ignoring.", cfile); /* now skip what's left */ - while (fgets(buffer, 160, cfh)) + while (fgets(buffer, sizeof(buffer), cfh)) { if (index(buffer, '\n')) { @@ -227,7 +255,7 @@ char *conf_read(char *cfile) } if (Mlist[i] == &Module_rfc931 && ident) { - conf_err(lnnb, + conf_err(lnnb, "This module can only be loaded once.", cfile); continue; @@ -247,12 +275,13 @@ char *conf_read(char *cfile) (*last)->skip_if_sasl = 0; (*last)->wait_for_ident = 0; (*last)->skip_if_ident = 0; + (*last)->module_kv = NULL; (*last)->delayed = o_del; (*last)->port = 0; if (Mlist[i] == &Module_rfc931) ident = *last; - while (fgets(buffer, 160, cfh)) + while (fgets(buffer, sizeof(buffer), cfh)) { aTarget **ttmp; int inverse = 0; @@ -267,7 +296,7 @@ char *conf_read(char *cfile) "line too long, ignoring.", cfile); /* now skip what's left */ - while (fgets(buffer, 160, cfh)) + while (fgets(buffer, sizeof(buffer), cfh)) if (index(buffer,'\n')) break; continue; @@ -444,8 +473,18 @@ char *conf_read(char *cfile) } else { - conf_err(lnnb, "Invalid keyword.", - cfile); + char *prop = buffer + 1; + char *eq = strstr(prop, " = "); + + if (!eq) + { + conf_err(lnnb, "Invalid keyword.", + cfile); + continue; + } + + *eq = '\0'; + conf_kv_add(*last, conf_trim(prop), conf_trim(eq + 3)); continue; } if (Mlist[i] == &Module_rfc931) @@ -471,16 +510,25 @@ char *conf_read(char *cfile) if (ident == NULL) { - ident = *last = (AnInstance *) malloc(sizeof(AnInstance)); + ident = *last = (AnInstance *) calloc(1, sizeof(AnInstance)); + if (ident == NULL) + return NULL; + (*last)->nexti = NULL; - (*last)->opt = NULL; + (*last)->in = icount; (*last)->mod = &Module_rfc931; + (*last)->opt = NULL; + (*last)->popt = NULL; + (*last)->data = NULL; (*last)->hostname = NULL; (*last)->address = NULL; (*last)->timeout = DEFAULT_TIMEOUT; - (*last)->in = icount; - (*last)->popt = NULL; - (*last)->address = NULL; + (*last)->wait_for_reg = 0; + (*last)->skip_if_sasl = 0; + (*last)->wait_for_ident = 0; + (*last)->skip_if_ident = 0; + (*last)->reason = NULL; + (*last)->module_kv = NULL; (*last)->delayed = o_del; (*last)->port = 0; } diff --git a/iauth/a_conf_def.h b/iauth/a_conf_def.h index ad41392d..74083d81 100644 --- a/iauth/a_conf_def.h +++ b/iauth/a_conf_def.h @@ -21,6 +21,16 @@ typedef struct Module aModule; typedef struct Instance AnInstance; typedef struct Target aTarget; +typedef struct ConfKV aConfKV; + +struct ConfKV +{ + char *key; + char *value; + aConfKV *next; +}; + + struct Module { char *name; /* module name */ @@ -58,6 +68,7 @@ struct Instance u_char wait_for_ident; /* wait until ident lookup completes */ u_char skip_if_ident; /* skip module if we got an ident reply */ char *reason; /* reject reason */ + aConfKV *module_kv; /* module-specific key/value pairs */ u_char delayed; /* delayed execution mode */ }; diff --git a/iauth/mod_dnsbl.c b/iauth/mod_dnsbl.c index f8ad5d5c..7f8fb721 100644 --- a/iauth/mod_dnsbl.c +++ b/iauth/mod_dnsbl.c @@ -20,11 +20,10 @@ */ #ifndef lint -static const volatile char rcsid[] = "@(#)$Id: mod_dnsbl.c,v 1.1 2024/10/1 15:35:00 patrick Exp $"; +static const volatile char rcsid[] = "@(#)$Id: mod_dnsbl.c,v 1.3 2026/04/04 00:00:00 ai Exp $"; #endif // clang-format off -// "os.h" must be included before "a_defines.h" #include "os.h" #include "a_defines.h" // clang-format on @@ -32,14 +31,23 @@ static const volatile char rcsid[] = "@(#)$Id: mod_dnsbl.c,v 1.1 2024/10/1 15:35 #include "a_externs.h" #undef MOD_DNSBL_C -/****************************** PRIVATE *************************************/ +#include "../ircd/resolv_def.h" +#include "../ircd/res_init_ext.h" +#include "../ircd/res_comp_ext.h" +#include "../ircd/res_mkquery_ext.h" /* Cache time in minutes */ #define CACHETIME 30 +#define DNSBL_LOOKUPLEN 512 +#define DNSBL_MAXPACKET 512 +#define DNSBL_GWORK_BUDGET 64 +#define DNSBL_MAX_ACTIVE_QUERIES 128 struct hostlog { struct hostlog *next; char ip[HOSTLEN + 1]; + char listname[DNSBL_LOOKUPLEN]; + unsigned char answer[INADDRSZ]; u_char state; /* 0 = not found, 1 = found, 2 = timeout */ time_t expire; }; @@ -52,11 +60,38 @@ struct hostlog { #define DNSBL_FOUND 1 #define DNSBL_FAILED 2 +#define DNSBL_PENDING 0 +#define DNSBL_DONE_FOUND 1 +#define DNSBL_DONE_CLEAN 2 +#define DNSBL_DONE_FAILED 3 + +#define DNSBL_PARSE_IGNORE 0 +#define DNSBL_PARSE_FOUND 1 +#define DNSBL_PARSE_NEXT_HOST 2 +#define DNSBL_PARSE_RETRY 3 + struct dnsbl_list { char *host; + char *reason; struct dnsbl_list *next; }; +typedef struct dnsbl_pending { + int active; + int wake_r; + int wake_w; + int final_state; + u_short qid; + u_int ns_index; + u_int tries; + time_t deadline; + struct dnsbl_list *current; + char lookup[DNSBL_LOOKUPLEN]; + char listname[DNSBL_LOOKUPLEN]; + unsigned char answer[INADDRSZ]; + u_int queue_seq; +} DnsblPending; + struct dnsbl_private { struct hostlog *cache; char *reason; @@ -66,21 +101,423 @@ struct dnsbl_private { u_int chitc, chito, chitn, cmiss, cnow, cmax; u_int found, failed, good, total, rejects; struct dnsbl_list *host_list; + char *desc; + int gfd; + u_int qid_seq; + u_int queue_seq; + DnsblPending pend[MAXCONNECTIONS]; }; -/* - * dnsbl_succeed - * - * Found a host in DNSBL. Deal with it. - */ -static void dnsbl_succeed(u_int cl, char *listname, char *result) +static int dnsbl_resolver_ready = 0; + +static void dnsbl_close_client_rfd(u_int cl) +{ + if (cldata[cl].rfd > 0) + { + close(cldata[cl].rfd); + cldata[cl].rfd = 0; + } + cldata[cl].wfd = 0; + cldata[cl].buflen = 0; +} + +static void dnsbl_reset_pending(DnsblPending *p) +{ + if (p->wake_w > 0) + { + close(p->wake_w); + p->wake_w = 0; + } + p->wake_r = 0; + bzero((char *) p, sizeof(*p)); +} + +static int dnsbl_make_wakeup(u_int cl, int *rfd_out, int *wfd_out) +{ + int sp[2]; + int flags; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) < 0) + { + DebugLog((ALOG_DNSBL, 0, "dnsbl_make_wakeup(%d): socketpair() failed: %s", + cl, strerror(errno))); + return -1; + } + + flags = fcntl(sp[0], F_GETFL, 0); + if (flags >= 0) + (void) fcntl(sp[0], F_SETFL, flags | O_NONBLOCK); + flags = fcntl(sp[1], F_GETFL, 0); + if (flags >= 0) + (void) fcntl(sp[1], F_SETFL, flags | O_NONBLOCK); + + cldata[cl].rfd = sp[0]; + *rfd_out = sp[0]; + *wfd_out = sp[1]; + return 0; +} + +static int dnsbl_ns_count(void) +{ + return MAX(1, ircd_res.nscount); +} + +static int dnsbl_max_sends(void) +{ + return MAX(1, dnsbl_ns_count() * MAX(1, ircd_res.retry)); +} + +static int dnsbl_max_active_queries(void) +{ + return (DNSBL_MAX_ACTIVE_QUERIES < MAXCONNECTIONS) ? + DNSBL_MAX_ACTIVE_QUERIES : MAXCONNECTIONS; +} + +static int dnsbl_count_inflight(struct dnsbl_private *mydata) +{ + u_int i; + int n = 0; + + for (i = 0; i < MAXCONNECTIONS; i++) + { + DnsblPending *p = &mydata->pend[i]; + + if (p->active && p->final_state == DNSBL_PENDING && p->qid != 0) + n++; + } + return n; +} + +static void dnsbl_dispatch_queued(struct dnsbl_private *mydata); + +static int dnsbl_opt_eq(const char *token, const char *name) +{ + return strcmp(token, name) == 0; +} + +static int dnsbl_opt_startswith(const char *token, const char *name) +{ + size_t n = strlen(name); + return strncmp(token, name, n) == 0; +} + +static int dnsbl_appendf(char **buf, size_t *size, size_t *used, const char *fmt, ...) +{ + va_list ap; + int needed; + char *nbuf; + size_t nsize; + + if (!buf || !size || !used || !fmt) + return -1; + + va_start(ap, fmt); + needed = vsnprintf((*buf) ? (*buf + *used) : NULL, + (*buf && *size > *used) ? (*size - *used) : 0, + fmt, ap); + va_end(ap); + if (needed < 0) + return -1; + if (*buf && (size_t) needed < (*size - *used)) + { + *used += (size_t) needed; + return 0; + } + + nsize = (*size != 0) ? *size : 128; + while (nsize <= *used + (size_t) needed) + nsize *= 2; + nbuf = (char *) realloc(*buf, nsize); + if (!nbuf) + return -1; + *buf = nbuf; + *size = nsize; + + va_start(ap, fmt); + needed = vsnprintf(*buf + *used, *size - *used, fmt, ap); + va_end(ap); + if (needed < 0) + return -1; + *used += (size_t) needed; + return 0; +} + + +static int dnsbl_same_ns(const struct SOCKADDR_IN *a, const struct SOCKADDR_IN *b) +{ + return a->SIN_FAMILY == b->SIN_FAMILY && + a->SIN_PORT == b->SIN_PORT && + memcmp(a->SIN_ADDR.S_ADDR, b->SIN_ADDR.S_ADDR, + sizeof(a->SIN_ADDR.S_ADDR)) == 0; +} + +static u_short dnsbl_new_qid(struct dnsbl_private *mydata) +{ + u_int n; + + for (n = 0; n < 1024; n++) + { + u_short qid = (u_short) (ircd_res_randomid() ^ (++mydata->qid_seq)); + u_int i; + int used = 0; + + if (qid == 0) + qid = 1; + for (i = 0; i < MAXCONNECTIONS; i++) + { + if (mydata->pend[i].active && mydata->pend[i].qid == qid) + { + used = 1; + break; + } + } + if (!used) + return qid; + } + return 0; +} + +static int dnsbl_find_by_wake_r(struct dnsbl_private *mydata, int wake_r) +{ + u_int i; + + if (wake_r <= 0) + return -1; + + for (i = 0; i < MAXCONNECTIONS; i++) + { + if ((mydata->pend[i].active || mydata->pend[i].wake_w > 0) && + mydata->pend[i].wake_r == wake_r) + return (int) i; + } + return -1; +} + +static DnsblPending *dnsbl_pending_for_cl(struct dnsbl_private *mydata, u_int cl, u_int *owner_out) +{ + int owner = -1; + + if (cldata[cl].rfd > 0) + owner = dnsbl_find_by_wake_r(mydata, cldata[cl].rfd); + if (owner < 0 && (mydata->pend[cl].active || mydata->pend[cl].wake_w > 0)) + owner = (int) cl; + if (owner < 0) + return NULL; + if (owner_out) + *owner_out = (u_int) owner; + return &mydata->pend[owner]; +} + +static int dnsbl_build_lookup(u_int cl, struct dnsbl_list *l, char *lookup, + size_t lookup_len) +{ + struct addrinfo hints, *addr_res; + int rc; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + + rc = getaddrinfo(cldata[cl].itsip, NULL, &hints, &addr_res); + if (rc) + return -1; + + if (addr_res->ai_family == AF_INET) + { + const struct sockaddr_in *v4 = (const struct sockaddr_in *) addr_res->ai_addr; + const uint8_t *b = (const uint8_t *) &v4->sin_addr.s_addr; + + snprintf(lookup, lookup_len, "%u.%u.%u.%u.%s", + (unsigned int) (b[3]), (unsigned int) (b[2]), + (unsigned int) (b[1]), (unsigned int) (b[0]), + l->host); + } + else if (addr_res->ai_family == AF_INET6) + { + const struct sockaddr_in6 *v6 = (const struct sockaddr_in6 *) addr_res->ai_addr; + const uint8_t *b = (const uint8_t *) &v6->sin6_addr.s6_addr; + + snprintf(lookup, lookup_len, + "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." + "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%s", + (unsigned int) (b[15] & 0xF), (unsigned int) (b[15] >> 4), + (unsigned int) (b[14] & 0xF), (unsigned int) (b[14] >> 4), + (unsigned int) (b[13] & 0xF), (unsigned int) (b[13] >> 4), + (unsigned int) (b[12] & 0xF), (unsigned int) (b[12] >> 4), + (unsigned int) (b[11] & 0xF), (unsigned int) (b[11] >> 4), + (unsigned int) (b[10] & 0xF), (unsigned int) (b[10] >> 4), + (unsigned int) (b[9] & 0xF), (unsigned int) (b[9] >> 4), + (unsigned int) (b[8] & 0xF), (unsigned int) (b[8] >> 4), + (unsigned int) (b[7] & 0xF), (unsigned int) (b[7] >> 4), + (unsigned int) (b[6] & 0xF), (unsigned int) (b[6] >> 4), + (unsigned int) (b[5] & 0xF), (unsigned int) (b[5] >> 4), + (unsigned int) (b[4] & 0xF), (unsigned int) (b[4] >> 4), + (unsigned int) (b[3] & 0xF), (unsigned int) (b[3] >> 4), + (unsigned int) (b[2] & 0xF), (unsigned int) (b[2] >> 4), + (unsigned int) (b[1] & 0xF), (unsigned int) (b[1] >> 4), + (unsigned int) (b[0] & 0xF), (unsigned int) (b[0] >> 4), + l->host); + } + else + { + freeaddrinfo(addr_res); + return -1; + } + + freeaddrinfo(addr_res); + return 0; +} + +static void dnsbl_free_cache(struct dnsbl_private *mydata) +{ + struct hostlog *pl, *next; + + for (pl = mydata->cache; pl; pl = next) + { + next = pl->next; + free(pl); + } + mydata->cache = NULL; + mydata->cnow = 0; +} + +static void dnsbl_free_host_list(struct dnsbl_private *mydata) +{ + struct dnsbl_list *l, *n; + + for (l = mydata->host_list; l; l = n) + { + free(l->host); + if (l->reason) + free(l->reason); + n = l->next; + free(l); + } + mydata->host_list = NULL; +} + +static void dnsbl_free_private(struct dnsbl_private *mydata) +{ + if (!mydata) + return; + + dnsbl_free_cache(mydata); + dnsbl_free_host_list(mydata); + if (mydata->reason) + free(mydata->reason); + if (mydata->desc) + free(mydata->desc); + free(mydata); +} + +static struct dnsbl_list *dnsbl_add_host(struct dnsbl_private *mydata, const char *host, const char *reason) +{ + struct dnsbl_list *l, **tail; + + if (!mydata || !host || !*host) + return NULL; + + l = (struct dnsbl_list *) malloc(sizeof(struct dnsbl_list)); + if (!l) + return NULL; + l->host = mystrdup((char *) host); + l->reason = (reason && *reason) ? mystrdup((char *) reason) : NULL; + l->next = NULL; + + tail = &mydata->host_list; + while (*tail) + tail = &(*tail)->next; + *tail = l; + return l; +} + +static const char *dnsbl_reason_template(struct dnsbl_private *mydata, const char *listname) +{ + struct dnsbl_list *l; + + for (l = mydata->host_list; l; l = l->next) + { + if (!strcasecmp(l->host, (char *) listname)) + return l->reason ? l->reason : mydata->reason; + } + return mydata->reason; +} + +static char *dnsbl_expand_reason(u_int cl, const char *tmpl, const char *domain) +{ + const char *ip = cldata[cl].itsip ? cldata[cl].itsip : ""; + const char *host = cldata[cl].host ? cldata[cl].host : ""; + size_t len = 0; + const char *p; + char *out, *w; + + if (!tmpl) + return mystrdup(""); + + for (p = tmpl; *p; ) + { + if (!strncmp(p, "{ip}", 4)) + { + len += strlen(ip); + p += 4; + } + else if (!strncmp(p, "{domain}", 8)) + { + len += strlen(domain ? domain : ""); + p += 8; + } + else if (!strncmp(p, "{host}", 6)) + { + len += strlen(host); + p += 6; + } + else + { + len++; + p++; + } + } + + out = (char *) malloc(len + 1); + for (p = tmpl, w = out; *p; ) + { + if (!strncmp(p, "{ip}", 4)) + { + strcpy(w, ip); + w += strlen(ip); + p += 4; + } + else if (!strncmp(p, "{domain}", 8)) + { + const char *d = domain ? domain : ""; + strcpy(w, d); + w += strlen(d); + p += 8; + } + else if (!strncmp(p, "{host}", 6)) + { + strcpy(w, host); + w += strlen(host); + p += 6; + } + else + { + *w++ = *p++; + } + } + *w = '\0'; + return out; +} + +static void dnsbl_succeed(u_int cl, const char *listname, const unsigned char *result) { struct dnsbl_private *mydata = cldata[cl].instance->data; - char *reason = mydata->reason; + const char *tmpl = dnsbl_reason_template(mydata, listname); + char *reason = dnsbl_expand_reason(cl, tmpl, listname); if (mydata->options & OPT_PARANOID || (mydata->options & OPT_DENY && - result[0] == '\177' && result[1] == '\0' && - result[2] == '\0' /* && result[3] == '\2' */)) + result[0] == '\177' && result[1] == '\0' && + result[2] == '\0')) { cldata[cl].state |= A_DENY; sendto_ircd("k %d %s %u #dnsbl :%s", cl, @@ -91,54 +528,80 @@ static void dnsbl_succeed(u_int cl, char *listname, char *result) if (mydata->options & OPT_LOG) sendto_log(ALOG_FLOG | ALOG_IRCD | ALOG_DNSBL, LOG_INFO, "%s: found: %s[%s]", listname, cldata[cl].host, cldata[cl].itsip); + if (reason) + free(reason); } - -/* - * dnsbl_add_cache - * - * Add an entry to the cache. - */ -static void dnsbl_add_cache(u_int cl, u_int state) +static void dnsbl_add_cache(u_int cl, u_int state, const char *listname, + const unsigned char *answer) { struct dnsbl_private *mydata = cldata[cl].instance->data; - struct hostlog *next; + struct hostlog *pl; + time_t expire; if (state == DNSBL_FOUND) mydata->found++; else if (state == DNSBL_FAILED) mydata->failed++; - else /* state == OK */ + else mydata->good++; - if (mydata->lifetime == 0) + if (mydata->lifetime == 0 || state == DNSBL_FAILED) return; + expire = time(NULL) + mydata->lifetime; + for (pl = mydata->cache; pl; pl = pl->next) + { + if (!strcasecmp(pl->ip, cldata[cl].itsip)) + { + pl->expire = expire; + pl->state = state; + if (listname) + { + strncpy(pl->listname, listname, sizeof(pl->listname) - 1); + pl->listname[sizeof(pl->listname) - 1] = '\0'; + } + else + pl->listname[0] = '\0'; + if (answer) + memcpy(pl->answer, answer, sizeof(pl->answer)); + else + bzero(pl->answer, sizeof(pl->answer)); + DebugLog((ALOG_DNSBLC, 0, + "dnsbl_add_cache(%d): refreshed cache %s, result=%d", + cl, pl->ip, state)); + return; + } + } + + pl = (struct hostlog *) malloc(sizeof(struct hostlog)); + if (!pl) + return; + bzero((char *) pl, sizeof(*pl)); + pl->expire = expire; + strcpy(pl->ip, cldata[cl].itsip); + pl->state = state; + if (listname) + { + strncpy(pl->listname, listname, sizeof(pl->listname) - 1); + pl->listname[sizeof(pl->listname) - 1] = '\0'; + } + if (answer) + memcpy(pl->answer, answer, sizeof(pl->answer)); + pl->next = mydata->cache; + mydata->cache = pl; mydata->cnow++; if (mydata->cnow > mydata->cmax) mydata->cmax = mydata->cnow; - - next = mydata->cache; - mydata->cache = (struct hostlog *) malloc(sizeof(struct hostlog)); - mydata->cache->expire = time(NULL) + mydata->lifetime; - strcpy(mydata->cache->ip, cldata[cl].itsip); - mydata->cache->state = state; - mydata->cache->next = next; DebugLog((ALOG_DNSBLC, 0, "dnsbl_add_cache(%d): new cache %s, result=%d", cl, mydata->cache->ip, state)); } -/* - * dnsbl_check_cache - * - * Check cache for an entry. - */ static int dnsbl_check_cache(u_int cl) { struct dnsbl_private *mydata = cldata[cl].instance->data; struct hostlog **last, *pl; time_t now = time(NULL); - if (!mydata || mydata->lifetime == 0) return 0; @@ -154,8 +617,8 @@ static int dnsbl_check_cache(u_int cl) if (pl->expire < now) { DebugLog((ALOG_DNSBLC, 0, - "dnsbl_check_cache(%d): free %s (%d < %d)", - cl, pl->ip, pl->expire, now)); + "dnsbl_check_cache(%d): free %s (%ld < %ld)", + cl, pl->ip, (long) pl->expire, (long) now)); *last = pl->next; free(pl); mydata->cnow--; @@ -166,13 +629,15 @@ static int dnsbl_check_cache(u_int cl) DebugLog((ALOG_DNSBLC, 0, "dnsbl_check_cache(%d): match (%u)", cl, pl->state)); - pl->expire = now + mydata->lifetime; /* dubious */ - if (pl->state == 1) + pl->expire = now + mydata->lifetime; + if (pl->state == DNSBL_FOUND) { - dnsbl_succeed(cl, "cached", ""); + dnsbl_succeed(cl, + pl->listname[0] ? pl->listname : "dnsbl", + pl->answer); mydata->chito++; } - else if (pl->state == 0) + else if (pl->state == OK) mydata->chitn++; else mydata->chitc++; @@ -184,21 +649,400 @@ static int dnsbl_check_cache(u_int cl) return 0; } -/******************************** PUBLIC ************************************/ +static int dnsbl_ginit(AnInstance *self) +{ + struct dnsbl_private *mydata = self->data; + int flags; + + if (!dnsbl_resolver_ready) + { + if (ircd_res_init() == 0) + dnsbl_resolver_ready = 1; + else + return -1; + } + + if (mydata->gfd > 0) + return 0; + + mydata->gfd = socket(AFINET, SOCK_DGRAM, 0); + if (mydata->gfd < 0) + { + DebugLog((ALOG_DNSBL, 0, "dnsbl_ginit: socket() failed: %s", + strerror(errno))); + return -1; + } + flags = fcntl(mydata->gfd, F_GETFL, 0); + if (flags >= 0) + (void) fcntl(mydata->gfd, F_SETFL, flags | O_NONBLOCK); + + if (io_register_gfd(self, mydata->gfd, 0) < 0) + { + DebugLog((ALOG_DNSBL, 0, "dnsbl_ginit: io_register_gfd failed")); + close(mydata->gfd); + mydata->gfd = 0; + return -1; + } + + return 0; +} + +static void dnsbl_grelease(AnInstance *self) +{ + struct dnsbl_private *mydata = self->data; + u_int i; + + if (!mydata) + return; + + for (i = 0; i < MAXCONNECTIONS; i++) + { + if (cldata[i].instance == self && (cldata[i].rfd > 0 || cldata[i].wfd > 0)) + dnsbl_close_client_rfd(i); + } + + for (i = 0; i < MAXCONNECTIONS; i++) + { + if (mydata->pend[i].active || mydata->pend[i].wake_w > 0) + dnsbl_reset_pending(&mydata->pend[i]); + } + + if (mydata->gfd > 0) + { + io_unregister_gfd(self); + close(mydata->gfd); + mydata->gfd = 0; + } +} + +static void dnsbl_complete(struct dnsbl_private *mydata, u_int cl, int final_state) +{ + DnsblPending *p = &mydata->pend[cl]; + ssize_t wr; + + if (!p->active) + return; + + p->qid = 0; + p->deadline = 0; + p->final_state = final_state; + if (p->wake_w > 0) + { + wr = write(p->wake_w, "D", 1); + if (wr < 0 && errno != EWOULDBLOCK && errno != EAGAIN) + DebugLog((ALOG_DNSBL, 0, + "dnsbl_complete(%u): wake write failed: %s", + cl, strerror(errno))); + } + + dnsbl_dispatch_queued(mydata); +} + +static int dnsbl_send_request(u_int cl, struct dnsbl_private *mydata) +{ + DnsblPending *p = &mydata->pend[cl]; + char packet[DNSBL_MAXPACKET]; + HEADER *hptr = (HEADER *) packet; + int packet_len; + int send_len; + + if (mydata->gfd <= 0 && dnsbl_ginit(cldata[cl].instance) < 0) + return -1; + + while (p->current) + { + if (p->tries == 0) + { + if (dnsbl_build_lookup(cl, p->current, p->lookup, sizeof(p->lookup)) != 0) + { + DebugLog((ALOG_DNSBL, 0, + "dnsbl_send_request(%d): invalid lookup host=%s, skipping", + cl, p->current ? p->current->host : "?")); + p->current = p->current->next; + p->tries = 0; + p->qid = 0; + p->deadline = 0; + p->lookup[0] = '\0'; + continue; + } + } + + if (p->tries >= (u_int) dnsbl_max_sends()) + { + p->current = p->current->next; + p->tries = 0; + p->lookup[0] = '\0'; + continue; + } + + p->ns_index = p->tries % dnsbl_ns_count(); + p->qid = dnsbl_new_qid(mydata); + if (p->qid == 0) + return -1; + + bzero(packet, sizeof(packet)); + packet_len = ircd_res_mkquery(QUERY, p->lookup, C_IN, T_A, + NULL, 0, NULL, (u_char *) packet, sizeof(packet)); + if (packet_len <= 0) + { + DebugLog((ALOG_DNSBL, 0, + "dnsbl_send_request(%d): mkquery failed host=%s name=%s, skipping", + cl, p->current ? p->current->host : "?", p->lookup)); + p->current = p->current->next; + p->tries = 0; + p->qid = 0; + p->deadline = 0; + p->lookup[0] = '\0'; + continue; + } + hptr->id = htons(p->qid); + + send_len = sendto(mydata->gfd, packet, packet_len, 0, + (struct sockaddr *) &ircd_res.nsaddr_list[p->ns_index], + sizeof(ircd_res.nsaddr_list[p->ns_index])); + p->tries++; + if (send_len == packet_len) + { + p->deadline = time(NULL) + MAX(1, ircd_res.retrans); + if (cldata[cl].timeout == 0 && cldata[cl].instance) + cldata[cl].timeout = time(NULL) + cldata[cl].instance->timeout; + DebugLog((ALOG_DNSBL, 0, + "dnsbl_send_request(%d): qid=%u ns=%u try=%u host=%s name=%s", + cl, p->qid, p->ns_index, p->tries, + p->current ? p->current->host : "?", p->lookup)); + return 0; + } + + DebugLog((ALOG_DNSBL, 0, + "dnsbl_send_request(%d): sendto failed on ns=%u: %s", + cl, p->ns_index, strerror(errno))); + p->qid = 0; + } + + return -1; +} + +static void dnsbl_dispatch_queued(struct dnsbl_private *mydata) +{ + int inflight; + + if (!mydata || mydata->gfd <= 0) + return; + + inflight = dnsbl_count_inflight(mydata); + while (inflight < dnsbl_max_active_queries()) + { + u_int i; + int best = -1; + u_int best_seq = 0; + + for (i = 0; i < MAXCONNECTIONS; i++) + { + DnsblPending *p = &mydata->pend[i]; + + if (!p->active || p->final_state != DNSBL_PENDING || p->qid != 0) + continue; + if (best < 0 || p->queue_seq < best_seq) + { + best = (int) i; + best_seq = p->queue_seq; + } + } + + if (best < 0) + break; + + if (dnsbl_send_request((u_int) best, mydata) == 0) + { + DebugLog((ALOG_DNSBL, 0, + "dnsbl_dispatch_queued: activated cl=%d inflight=%d/%d", + best, inflight + 1, dnsbl_max_active_queries())); + inflight++; + continue; + } + + { + DnsblPending *p = &mydata->pend[best]; + ssize_t wr = -1; + + DebugLog((ALOG_DNSBL, 0, + "dnsbl_dispatch_queued: activation failed for cl=%d", best)); + p->qid = 0; + p->deadline = 0; + p->final_state = DNSBL_DONE_FAILED; + if (p->wake_w > 0) + { + wr = write(p->wake_w, "D", 1); + if (wr < 0 && errno != EWOULDBLOCK && errno != EAGAIN) + DebugLog((ALOG_DNSBL, 0, + "dnsbl_dispatch_queued(%d): wake write failed: %s", + best, strerror(errno))); + } + } + } +} + +static int dnsbl_parse_reply(DnsblPending *p, const u_char *buf, size_t len, + const struct SOCKADDR_IN *src, + unsigned char *answer) +{ + const HEADER *hptr = (const HEADER *) buf; + const u_char *cp = buf + HFIXEDSZ; + const u_char *eob = buf + len; + char qname[DNSBL_LOOKUPLEN]; + char rrname[DNSBL_LOOKUPLEN]; + u_int16_t id, qdcount, ancount, qtype, qclass; + int n; + + if (len < HFIXEDSZ) + return DNSBL_PARSE_IGNORE; + + id = ntohs(hptr->id); + qdcount = ntohs(hptr->qdcount); + ancount = ntohs(hptr->ancount); + if (!hptr->qr || id != p->qid) + return DNSBL_PARSE_IGNORE; + if (!dnsbl_same_ns(src, &ircd_res.nsaddr_list[p->ns_index])) + return DNSBL_PARSE_IGNORE; + + if (qdcount == 0) + return DNSBL_PARSE_RETRY; + + n = ircd_dn_expand(buf, eob, cp, qname, sizeof(qname)); + if (n <= 0) + return DNSBL_PARSE_RETRY; + cp += n; + if (cp + QFIXEDSZ > eob) + return DNSBL_PARSE_RETRY; + qtype = ircd_getshort(cp); + cp += INT16SZ; + qclass = ircd_getshort(cp); + cp += INT16SZ; + if (qtype != T_A || qclass != C_IN || strcasecmp(qname, p->lookup) != 0) + return DNSBL_PARSE_IGNORE; + + while (--qdcount > 0) + { + n = __ircd_dn_skipname(cp, eob); + if (n < 0) + return DNSBL_PARSE_RETRY; + cp += n; + if (cp + QFIXEDSZ > eob) + return DNSBL_PARSE_RETRY; + cp += QFIXEDSZ; + } + + if (hptr->tc) + return DNSBL_PARSE_RETRY; + if (hptr->rcode == NXDOMAIN) + return DNSBL_PARSE_NEXT_HOST; + if (hptr->rcode != NOERROR) + return DNSBL_PARSE_RETRY; + if (ancount == 0) + return DNSBL_PARSE_NEXT_HOST; + + while (ancount-- > 0 && cp < eob) + { + u_int16_t type, class, dlen; + + n = ircd_dn_expand(buf, eob, cp, rrname, sizeof(rrname)); + if (n <= 0) + return DNSBL_PARSE_RETRY; + cp += n; + if (cp + RRFIXEDSZ > eob) + return DNSBL_PARSE_RETRY; + type = ircd_getshort(cp); + cp += INT16SZ; + class = ircd_getshort(cp); + cp += INT16SZ; + cp += INT32SZ; + dlen = ircd_getshort(cp); + cp += INT16SZ; + if (cp + dlen > eob) + return DNSBL_PARSE_RETRY; + if (type == T_A && class == C_IN && dlen == INADDRSZ) + { + memcpy(answer, cp, INADDRSZ); + return DNSBL_PARSE_FOUND; + } + cp += dlen; + } + + return DNSBL_PARSE_NEXT_HOST; +} + +static int dnsbl_find_by_qid(struct dnsbl_private *mydata, u_short qid) +{ + u_int i; + + for (i = 0; i < MAXCONNECTIONS; i++) + { + if (mydata->pend[i].active && mydata->pend[i].qid == qid) + return (int) i; + } + return -1; +} + +static void dnsbl_gwork_one(struct dnsbl_private *mydata, const u_char *buf, + size_t len, const struct SOCKADDR_IN *src) +{ + const HEADER *hptr = (const HEADER *) buf; + unsigned char answer[INADDRSZ]; + int cl; + int action; + DnsblPending *p; + + if (len < HFIXEDSZ) + return; + + cl = dnsbl_find_by_qid(mydata, ntohs(hptr->id)); + if (cl < 0) + return; + + p = &mydata->pend[cl]; + bzero(answer, sizeof(answer)); + action = dnsbl_parse_reply(p, buf, len, src, answer); + if (action == DNSBL_PARSE_IGNORE) + return; + + if (action == DNSBL_PARSE_FOUND) + { + memcpy(p->answer, answer, sizeof(p->answer)); + if (p->current && p->current->host) + { + strncpy(p->listname, p->current->host, sizeof(p->listname) - 1); + p->listname[sizeof(p->listname) - 1] = '\0'; + } + dnsbl_complete(mydata, (u_int) cl, DNSBL_DONE_FOUND); + return; + } + + p->qid = 0; + p->deadline = 0; + if (action == DNSBL_PARSE_NEXT_HOST) + { + p->current = p->current ? p->current->next : NULL; + p->tries = 0; + p->lookup[0] = '\0'; + if (dnsbl_send_request((u_int) cl, mydata) == 0) + return; + dnsbl_complete(mydata, (u_int) cl, DNSBL_DONE_CLEAN); + return; + } + + if (dnsbl_send_request((u_int) cl, mydata) == 0) + return; + dnsbl_complete(mydata, (u_int) cl, DNSBL_DONE_FAILED); +} -/* - * dnsbl_init - * - * This procedure is called when a particular module is loaded. - * Returns NULL if everything went fine, - * an error message otherwise. - */ static char *dnsbl_init(AnInstance *self) { struct dnsbl_private *mydata; struct dnsbl_list *l; - char tmpbuf[255], cbuf[32], *s; - static char txtbuf[255]; + char *tmpbuf = NULL, *s; + char *txtbuf = NULL; + size_t tmpbuf_size = 0, tmpbuf_used = 0; + size_t txtbuf_size = 0, txtbuf_used = 0; if (self->opt == NULL) return "Aie! no option(s): nothing to be done!"; @@ -209,124 +1053,207 @@ static char *dnsbl_init(AnInstance *self) mydata->cache = NULL; mydata->host_list = NULL; mydata->lifetime = CACHETIME; + mydata->gfd = 0; - tmpbuf[0] = txtbuf[0] = '\0'; - if (strstr(self->opt, "log")) - { - mydata->options |= OPT_LOG; - strcat(tmpbuf, ",log"); - strcat(txtbuf, ", Log"); - } - if (strstr(self->opt, "reject")) + if (dnsbl_appendf(&tmpbuf, &tmpbuf_size, &tmpbuf_used, "") < 0 || + dnsbl_appendf(&txtbuf, &txtbuf_size, &txtbuf_used, "") < 0) { - mydata->options |= OPT_DENY; - strcat(tmpbuf, ",reject"); - strcat(txtbuf, ", Reject"); + dnsbl_free_private(mydata); + self->data = NULL; + return "Aie! out of memory"; } - if (strstr(self->opt, "paranoid")) + if (self->opt) { - mydata->options |= OPT_PARANOID; - strcat(tmpbuf, ",paranoid"); - strcat(txtbuf, ", Paranoid"); + char *opts = mystrdup(self->opt); + char *last = NULL; + char *tok; + + if (!opts) + { + dnsbl_free_private(mydata); + self->data = NULL; + return "Aie! out of memory"; + } + for (tok = strtoken(&last, opts, ","); tok; + tok = strtoken(&last, NULL, ",")) + { + char *eq; + char *name; + char *value = NULL; + + while (*tok && isspace((unsigned char) *tok)) + tok++; + for (s = tok + strlen(tok); s > tok && isspace((unsigned char) s[-1]); ) + *--s = '\0'; + if (!*tok) + continue; + + eq = strchr(tok, '='); + if (eq) + { + *eq++ = '\0'; + value = eq; + while (*value && isspace((unsigned char) *value)) + value++; + } + name = tok; + + if (dnsbl_opt_eq(name, "log")) + { + mydata->options |= OPT_LOG; + dnsbl_appendf(&tmpbuf, &tmpbuf_size, &tmpbuf_used, ",log"); + dnsbl_appendf(&txtbuf, &txtbuf_size, &txtbuf_used, ", Log"); + } + else if (dnsbl_opt_eq(name, "reject")) + { + mydata->options |= OPT_DENY; + dnsbl_appendf(&tmpbuf, &tmpbuf_size, &tmpbuf_used, ",reject"); + dnsbl_appendf(&txtbuf, &txtbuf_size, &txtbuf_used, ", Reject"); + } + else if (dnsbl_opt_eq(name, "paranoid")) + { + mydata->options |= OPT_PARANOID; + dnsbl_appendf(&tmpbuf, &tmpbuf_size, &tmpbuf_used, ",paranoid"); + dnsbl_appendf(&txtbuf, &txtbuf_size, &txtbuf_used, ", Paranoid"); + } + else if (dnsbl_opt_startswith(name, "servers") && value) + { + char *servers = mystrdup(value); + char *slast = NULL; + char *name2; + + if (!servers) + continue; + for (name2 = strtoken(&slast, servers, ","); name2; + name2 = strtoken(&slast, NULL, ",")) + { + while (*name2 && isspace((unsigned char) *name2)) + name2++; + for (s = name2 + strlen(name2); + s > name2 && isspace((unsigned char) s[-1]); ) + *--s = '\0'; + if (*name2) + { + dnsbl_add_host(mydata, name2, NULL); + sendto_log(ALOG_DNSBL, LOG_NOTICE, + "dnsbl_init: Added %s as dnsbl", name2); + } + } + free(servers); + } + else if (dnsbl_opt_startswith(name, "cache") && value) + { + char *endp = NULL; + unsigned long v; + + errno = 0; + v = strtoul(value, &endp, 10); + if (errno == 0 && endp && *endp == '\0' && v <= (unsigned long) (UINT_MAX / 60)) + mydata->lifetime = (u_int) v; + } + } + free(opts); } if (mydata->options == 0) { DebugLog((ALOG_DNSBL, 0, "dnsbl_init: Aie! unknown option(s): nothing to be done!")); + free(tmpbuf); + free(txtbuf); + dnsbl_free_private(mydata); + self->data = NULL; return "Aie! unknown option(s): nothing to be done!"; } - if ((s = strstr(self->opt, "servers"))) - { - char *ch = index(s, '='); - if (++ch) + if (self->module_kv) + { + aConfKV *kv = self->module_kv; + while (kv) { - char *name, *last = NULL; - for (name = strtoken(&last, ch, ","); name; - name = strtoken(&last, NULL, ",")) + if (!strcasecmp(kv->key, "entry")) { - while (name && *name && isspace(*name)) - name++; - if (!name || !*name) - continue; - l = (struct dnsbl_list *) - malloc(sizeof(struct dnsbl_list)); - l->host = strdup(name); - sendto_log(ALOG_DNSBL, LOG_NOTICE, - "dnsbl_init: Added %s as dnsbl", name); - l->next = mydata->host_list; - mydata->host_list = l; + char *tmp = mystrdup(kv->value); + char *sep = strchr(tmp, '|'); + char *host = tmp; + char *rsn = NULL; + char *e; + + while (*host && isspace(*host)) + host++; + if (sep) + { + *sep++ = '\0'; + rsn = sep; + while (*rsn && isspace(*rsn)) + rsn++; + for (e = rsn + strlen(rsn); e > rsn && isspace((unsigned char)e[-1]); ) + *--e = '\0'; + } + for (e = host + strlen(host); e > host && isspace((unsigned char)e[-1]); ) + *--e = '\0'; + if (*host) + { + dnsbl_add_host(mydata, host, rsn); + sendto_log(ALOG_DNSBL, LOG_NOTICE, + "dnsbl_init: Added %s as dnsbl%s", host, + (rsn && *rsn) ? " with custom reason" : ""); + } + free(tmp); } + kv = kv->next; } } if (mydata->host_list == NULL) { DebugLog((ALOG_DNSBL, 0, "dnsbl_init: Aie! No DNSBL host: nothing to be done!")); + free(tmpbuf); + free(txtbuf); + dnsbl_free_private(mydata); + self->data = NULL; return "Aie! No DNSBL host: nothing to be done!"; } - if (strstr(self->opt, "cache")) - { - char *ch = index(self->opt, '='); - - if (ch) - mydata->lifetime = atoi(++ch); - } - sprintf(cbuf, ",cache=%d", mydata->lifetime); - strcat(tmpbuf, cbuf); - sprintf(cbuf, ", Cache %d (min)", mydata->lifetime); - strcat(txtbuf, cbuf); + dnsbl_appendf(&tmpbuf, &tmpbuf_size, &tmpbuf_used, ",cache=%u", mydata->lifetime); + dnsbl_appendf(&txtbuf, &txtbuf_size, &txtbuf_used, ", Cache %u (min)", mydata->lifetime); mydata->lifetime *= 60; - strcat(tmpbuf, ",list="); - strcat(txtbuf, ", List(s): "); + dnsbl_appendf(&tmpbuf, &tmpbuf_size, &tmpbuf_used, ",list="); + dnsbl_appendf(&txtbuf, &txtbuf_size, &txtbuf_used, ", List(s): "); l = mydata->host_list; - strcat(tmpbuf, l->host); - strcat(txtbuf, l->host); - - for (l = l->next; l; l = l->next) + if (l) { - strcat(tmpbuf, ","); - strcat(txtbuf, ", "); - strcat(tmpbuf, l->host); - strcat(txtbuf, l->host); + dnsbl_appendf(&tmpbuf, &tmpbuf_size, &tmpbuf_used, "%s", l->host); + dnsbl_appendf(&txtbuf, &txtbuf_size, &txtbuf_used, "%s", l->host); + for (l = l->next; l; l = l->next) + { + dnsbl_appendf(&tmpbuf, &tmpbuf_size, &tmpbuf_used, ",%s", l->host); + dnsbl_appendf(&txtbuf, &txtbuf_size, &txtbuf_used, ", %s", l->host); + } } if (self->reason) - { mydata->reason = mystrdup(self->reason); - } - self->popt = strdup(tmpbuf + 1); - return txtbuf + 2; + mydata->desc = txtbuf; + self->popt = (tmpbuf && tmpbuf[0]) ? mystrdup(tmpbuf + 1) : mystrdup(""); + if (!self->popt) + self->popt = mystrdup(""); + if (!mydata->desc) + mydata->desc = mystrdup(""); + return (mydata->desc && mydata->desc[0]) ? mydata->desc + 2 : mydata->desc; } -/* - * dnsbl_release - * - * This procedure is called when a particular module is unloaded. - */ void dnsbl_release(AnInstance *self) { struct dnsbl_private *mydata = self->data; - struct dnsbl_list *l, *n; - for (l = mydata->host_list; l; l = n) - { - free(l->host); - n = l->next; - free(l); - } - - free(mydata); + dnsbl_grelease(self); + dnsbl_free_private(mydata); + self->data = NULL; free(self->popt); + self->popt = NULL; } -/* - * dnsbl_stats - * - * This procedure is called regularly to update statistics sent to ircd. - */ static void dnsbl_stats(AnInstance *self) { struct dnsbl_private *mydata = self->data; @@ -335,25 +1262,10 @@ static void dnsbl_stats(AnInstance *self) mydata->total, mydata->rejects); } -/* - * dnsbl_start - * - * This procedure is called to start the host check procedure. - * Returns 0 if everything went fine, - * -1 otherwise (nothing to be done, or failure) - * - * It is responsible for sending error messages where appropriate. - * In case of failure, it's responsible for cleaning up (e.g. dnsbl_clean - * will NOT be called) - * - * IPv4/IPv6 conversion has been taken from HOPM https://github.com/ircd-hybrid/hopm - */ static int dnsbl_start(u_int cl) { - char lookup[128]; struct dnsbl_private *mydata = cldata[cl].instance->data; - struct dnsbl_list *l, *m; - struct hostent *he = NULL; + DnsblPending *p = &mydata->pend[cl]; struct addrinfo hints, *addr_res; memset(&hints, 0, sizeof(hints)); @@ -365,13 +1277,12 @@ static int dnsbl_start(u_int cl) DebugLog((ALOG_DNSBL, 0, "dnsbl_start(%d): invalid address '%s', skipping ", cl, cldata[cl].itsip)); - return -1; } + freeaddrinfo(addr_res); if (cldata[cl].state & A_DENY) { - /* no point of doing anything */ DebugLog((ALOG_DNSBL, 0, "dnsbl_start(%d): A_DENY already set ", cl)); return -1; } @@ -379,118 +1290,186 @@ static int dnsbl_start(u_int cl) if (dnsbl_check_cache(cl)) return -1; - DebugLog((ALOG_DNSBL, 0, "dnsbl_start(%d): checking %s", cl, cldata[cl].itsip)); + if (mydata->gfd <= 0 && dnsbl_ginit(cldata[cl].instance) < 0) + return -1; - for (l = mydata->host_list; l && !he; l = l->next) - { - if (addr_res->ai_family == AF_INET) - { - const struct sockaddr_in *v4 = (const struct sockaddr_in *) addr_res->ai_addr; - const uint8_t *b = (const uint8_t *) &v4->sin_addr.s_addr; + if (p->active || p->wake_w > 0) + dnsbl_reset_pending(p); + if (cldata[cl].rfd > 0) + dnsbl_close_client_rfd(cl); - snprintf(lookup, sizeof(lookup), "%u.%u.%u.%u.%s", - (unsigned int) (b[3]), (unsigned int) (b[2]), - (unsigned int) (b[1]), (unsigned int) (b[0]), - l->host); - } - else if (addr_res->ai_family == AF_INET6) - { - const struct sockaddr_in6 *v6 = (const struct sockaddr_in6 *) addr_res->ai_addr; - const uint8_t *b = (const uint8_t *) &v6->sin6_addr.s6_addr; - snprintf(lookup, sizeof(lookup), - "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." - "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%s", - (unsigned int) (b[15] & 0xF), (unsigned int) (b[15] >> 4), - (unsigned int) (b[14] & 0xF), (unsigned int) (b[14] >> 4), - (unsigned int) (b[13] & 0xF), (unsigned int) (b[13] >> 4), - (unsigned int) (b[12] & 0xF), (unsigned int) (b[12] >> 4), - (unsigned int) (b[11] & 0xF), (unsigned int) (b[11] >> 4), - (unsigned int) (b[10] & 0xF), (unsigned int) (b[10] >> 4), - (unsigned int) (b[9] & 0xF), (unsigned int) (b[9] >> 4), - (unsigned int) (b[8] & 0xF), (unsigned int) (b[8] >> 4), - (unsigned int) (b[7] & 0xF), (unsigned int) (b[7] >> 4), - (unsigned int) (b[6] & 0xF), (unsigned int) (b[6] >> 4), - (unsigned int) (b[5] & 0xF), (unsigned int) (b[5] >> 4), - (unsigned int) (b[4] & 0xF), (unsigned int) (b[4] >> 4), - (unsigned int) (b[3] & 0xF), (unsigned int) (b[3] >> 4), - (unsigned int) (b[2] & 0xF), (unsigned int) (b[2] >> 4), - (unsigned int) (b[1] & 0xF), (unsigned int) (b[1] >> 4), - (unsigned int) (b[0] & 0xF), (unsigned int) (b[0] >> 4), - l->host); - } - else - continue; + if (dnsbl_make_wakeup(cl, &p->wake_r, &p->wake_w) < 0) + return -1; - DebugLog((ALOG_DNSBL, 0, - "dnsbl_start(%d): gethostbyname() for %s", - cl, lookup)); - he = gethostbyname(lookup); - m = l; - } + p->active = 1; + p->final_state = DNSBL_PENDING; + p->current = mydata->host_list; + p->tries = 0; + p->qid = 0; + p->deadline = 0; + p->lookup[0] = '\0'; + p->listname[0] = '\0'; + p->queue_seq = ++mydata->queue_seq; + bzero(p->answer, sizeof(p->answer)); mydata->total++; - if (he) - dnsbl_succeed(cl, m->host, he->h_addr_list[0]); - else + if (dnsbl_count_inflight(mydata) >= dnsbl_max_active_queries()) { DebugLog((ALOG_DNSBL, 0, - "dnsbl_start(%d): gethostbyname() reported %s", - cl, hstrerror(h_errno))); - dnsbl_add_cache(cl, DNSBL_FAILED); + "dnsbl_start(%d): queued (inflight=%d limit=%d)", + cl, dnsbl_count_inflight(mydata), dnsbl_max_active_queries())); } - return -1; + dnsbl_dispatch_queued(mydata); + if (p->final_state != DNSBL_PENDING) + return 0; + if (p->qid != 0) + return 0; + + // Keep the module timeout running even while the lookup is still queued. + DebugLog((ALOG_DNSBL, 0, + "dnsbl_start(%d): waiting in queue (timeout_at=%ld)", + cl, (long) cldata[cl].timeout)); + return 0; } -/* - * dnsbl_work - * - * This procedure is called whenever there's new data in the buffer. - * Returns 0 if everything went fine, and there is more work to be done, - * Returns -1 if the module has finished its work (and cleaned up). - * - * It is responsible for sending error messages where appropriate. - */ static int dnsbl_work(u_int cl) { - /* - ** There' nothing to do here - */ - DebugLog((ALOG_DNSBL, 0, - "dnsbl_work(%d) invoked but why?", cl)); - return 0; + struct dnsbl_private *mydata = cldata[cl].instance->data; + u_int owner; + DnsblPending *p = dnsbl_pending_for_cl(mydata, cl, &owner); + + if (!p || !p->active) + { + dnsbl_close_client_rfd(cl); + return -1; + } + + if (p->final_state == DNSBL_PENDING) + return 0; + + if (p->final_state == DNSBL_DONE_FOUND) + { + dnsbl_add_cache(cl, DNSBL_FOUND, p->listname, p->answer); + dnsbl_succeed(cl, p->listname[0] ? p->listname : "dnsbl", + (char *) p->answer); + } + else if (p->final_state == DNSBL_DONE_CLEAN) + { + dnsbl_add_cache(cl, OK, NULL, NULL); + } + else + { + dnsbl_add_cache(cl, DNSBL_FAILED, NULL, NULL); + } + + cldata[cl].timeout = 0; + dnsbl_close_client_rfd(cl); + dnsbl_reset_pending(&mydata->pend[owner]); + return -1; } -/* - * dnsbl_clean - * - * This procedure is called whenever the module should interrupt its work. - * It is responsible for cleaning up any allocated data, and in particular - * closing file descriptors. - */ static void dnsbl_clean(u_int cl) { + struct dnsbl_private *mydata = cldata[cl].instance->data; + u_int owner; + DnsblPending *p = dnsbl_pending_for_cl(mydata, cl, &owner); + DebugLog((ALOG_DNSBL, 0, "dnsbl_clean(%d): cleaning up", cl)); - /* - * only one of rfd and wfd may be set at the same time, - * in any case, they would be the same fd, so only close() once - */ + cldata[cl].timeout = 0; + dnsbl_close_client_rfd(cl); + if (p && (p->active || p->wake_w > 0)) + { + dnsbl_reset_pending(&mydata->pend[owner]); + dnsbl_dispatch_queued(mydata); + } } -/* - * dnsbl_timeout - * - * This procedure is called whenever the timeout set by the module is - * reached. - * - * Returns 0 if things are okay, -1 if check was aborted. - */ static int dnsbl_timeout(u_int cl) { - DebugLog((ALOG_DNSBL, 0, "dnsbl_timeout(%d): calling dnsbl_clean ", cl)); - dnsbl_clean(cl); - return -1; + struct dnsbl_private *mydata = cldata[cl].instance->data; + u_int owner; + DnsblPending *p = dnsbl_pending_for_cl(mydata, cl, &owner); + + DebugLog((ALOG_DNSBL, 0, "dnsbl_timeout(%d)", cl)); + if (!p || !p->active) + return -1; + + p->final_state = DNSBL_DONE_FAILED; + return dnsbl_work(cl); } -aModule Module_dnsbl = { "dnsbl", dnsbl_init, dnsbl_release, dnsbl_stats, - dnsbl_start, dnsbl_work, dnsbl_timeout, dnsbl_clean }; \ No newline at end of file +static int dnsbl_gwork(AnInstance *self) +{ + struct dnsbl_private *mydata = self->data; + u_char buf[DNSBL_MAXPACKET]; + struct SOCKADDR_IN src; + socklen_t slen; + ssize_t n; + int budget; + + if (!mydata || mydata->gfd <= 0) + return 0; + + for (budget = DNSBL_GWORK_BUDGET; budget > 0; budget--) + { + slen = sizeof(src); + n = recvfrom(mydata->gfd, buf, sizeof(buf), 0, + (struct sockaddr *) &src, &slen); + if (n < 0) + { + if (errno == EWOULDBLOCK || errno == EAGAIN) + break; + DebugLog((ALOG_DNSBL, 0, "dnsbl_gwork: recvfrom failed: %s", + strerror(errno))); + break; + } + if (n == 0) + break; + + dnsbl_gwork_one(mydata, buf, (size_t) n, &src); + } + if (budget == 0) + DebugLog((ALOG_DNSBL, 0, + "dnsbl_gwork: budget exhausted, deferring remaining packets")); + return 0; +} + +static void dnsbl_gtick(AnInstance *self) +{ + struct dnsbl_private *mydata = self->data; + time_t now = time(NULL); + u_int cl; + + if (!mydata) + return; + if (mydata->gfd <= 0) + (void) dnsbl_ginit(self); + + for (cl = 0; cl < MAXCONNECTIONS; cl++) + { + DnsblPending *p = &mydata->pend[cl]; + + if (!p->active || p->final_state != DNSBL_PENDING || p->qid == 0) + continue; + if (p->deadline > now) + continue; + + DebugLog((ALOG_DNSBL, 0, + "dnsbl_gtick: timeout cl=%u qid=%u host=%s try=%u", + cl, p->qid, p->current ? p->current->host : "?", p->tries)); + p->qid = 0; + p->deadline = 0; + if (dnsbl_send_request(cl, mydata) == 0) + continue; + dnsbl_complete(mydata, cl, DNSBL_DONE_FAILED); + } + + dnsbl_dispatch_queued(mydata); +} + +aModule Module_dnsbl = { + "dnsbl", dnsbl_init, dnsbl_release, dnsbl_stats, + dnsbl_start, dnsbl_work, dnsbl_timeout, dnsbl_clean, + dnsbl_ginit, dnsbl_gtick, dnsbl_gwork, dnsbl_grelease +}; diff --git a/support/Makefile.in b/support/Makefile.in index bfe195ae..41caa2c5 100644 --- a/support/Makefile.in +++ b/support/Makefile.in @@ -153,7 +153,8 @@ IRCD_OBJS = channel.o class.o hash.o ircd.o list.o res.o s_auth.o \ IAUTH_COMMON_OBJS = clsupport.o clmatch.o # This is a little evil IAUTH_OBJS = iauth.o a_conf.o a_io.o a_log.o \ mod_pipe.o mod_rfc931.o mod_socks.o \ - mod_webproxy.o mod_dnsbl.o mod_external.o + mod_webproxy.o mod_dnsbl.o mod_external.o \ + res_init.o res_comp.o res_mkquery.o CHKCONF_COMMON_OBJS = match.o clsupport.o CHKCONF_OBJS = chkconf.o From 8535fb7d7abe9ec9ef6aad9c83f433debd09979c Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 30 Aug 2026 21:24:57 +0200 Subject: [PATCH 2/6] DNSBL iauth module: removed "paranoid" option; updated logging --- doc/iauth.conf.5 | 9 ++++--- iauth/mod_dnsbl.c | 69 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/doc/iauth.conf.5 b/doc/iauth.conf.5 index dacf0090..d1ecdf63 100644 --- a/doc/iauth.conf.5 +++ b/doc/iauth.conf.5 @@ -158,13 +158,14 @@ Apache+PHP configurations). .B DNSBL This module performs a DNS query to check if an IP address is blacklisted. -This module understands 3 options: +This module only accepts 127.0.0.x A responses as valid DNSBL hits. +Other A responses are ignored and logged as unexpected DNSBL responses. + +This module understands the following options: .B log to log the IP which is listed on the DNSBL .B reject to reject the connection which is listed on the DNSBL -.B paranoid -Double check that the DNSBL is actually responding a 127.0.0.x IP .B servers A comma-separated list of DNSBL domains which are checked. This iauth module cannot check which DNSBL type is used so only use for DNSBLs suitable where all @@ -199,7 +200,7 @@ module webproxy port = 3128 module dnsbl - option = log,reject,paranoid,servers=bl1.example.org,bl2.example.org + option = log,reject,servers=bl1.example.org,bl2.example.org reason = Denied access (DNSBL) .fi .RE diff --git a/iauth/mod_dnsbl.c b/iauth/mod_dnsbl.c index 7f8fb721..f1442e37 100644 --- a/iauth/mod_dnsbl.c +++ b/iauth/mod_dnsbl.c @@ -54,7 +54,6 @@ struct hostlog { #define OPT_LOG 0x001 #define OPT_DENY 0x002 -#define OPT_PARANOID 0x004 #define OK 0 #define DNSBL_FOUND 1 @@ -69,6 +68,7 @@ struct hostlog { #define DNSBL_PARSE_FOUND 1 #define DNSBL_PARSE_NEXT_HOST 2 #define DNSBL_PARSE_RETRY 3 +#define DNSBL_PARSE_INVALID 4 struct dnsbl_list { char *host; @@ -509,15 +509,21 @@ static char *dnsbl_expand_reason(u_int cl, const char *tmpl, const char *domain) return out; } -static void dnsbl_succeed(u_int cl, const char *listname, const unsigned char *result) +static const char *dnsbl_log_value(const char *value) +{ + return (value && *value) ? value : "*"; +} + +static void dnsbl_succeed(u_int cl, const char *listname, + const unsigned char *answer, const char *source) { struct dnsbl_private *mydata = cldata[cl].instance->data; const char *tmpl = dnsbl_reason_template(mydata, listname); + const char *ident = (cldata[cl].state & A_GOTIDENT) && cldata[cl].authuser[0] ? + cldata[cl].authuser : ""; char *reason = dnsbl_expand_reason(cl, tmpl, listname); - if (mydata->options & OPT_PARANOID || (mydata->options & OPT_DENY && - result[0] == '\177' && result[1] == '\0' && - result[2] == '\0')) + if (mydata->options & OPT_DENY) { cldata[cl].state |= A_DENY; sendto_ircd("k %d %s %u #dnsbl :%s", cl, @@ -526,8 +532,28 @@ static void dnsbl_succeed(u_int cl, const char *listname, const unsigned char *r mydata->rejects++; } if (mydata->options & OPT_LOG) - sendto_log(ALOG_FLOG | ALOG_IRCD | ALOG_DNSBL, LOG_INFO, "%s: found: %s[%s]", - listname, cldata[cl].host, cldata[cl].itsip); + { + if (mydata->options & OPT_DENY) + sendto_log(ALOG_FLOG | ALOG_IRCD | ALOG_DNSBL, LOG_INFO, + "Denied connection from %s[%s@%s] (DNSBL %s: %s; %u.%u.%u.%u via %s)", + dnsbl_log_value(cldata[cl].nick), ident, cldata[cl].itsip, + listname, reason ? reason : "", + answer ? (u_int) answer[0] : 0, + answer ? (u_int) answer[1] : 0, + answer ? (u_int) answer[2] : 0, + answer ? (u_int) answer[3] : 0, + dnsbl_log_value(source)); + else + sendto_log(ALOG_FLOG | ALOG_IRCD | ALOG_DNSBL, LOG_INFO, + "DNSBL match for %s[%s@%s] on %s (%s; %u.%u.%u.%u via %s)", + dnsbl_log_value(cldata[cl].nick), ident, cldata[cl].itsip, + listname, reason ? reason : "", + answer ? (u_int) answer[0] : 0, + answer ? (u_int) answer[1] : 0, + answer ? (u_int) answer[2] : 0, + answer ? (u_int) answer[3] : 0, + dnsbl_log_value(source)); + } if (reason) free(reason); } @@ -634,7 +660,7 @@ static int dnsbl_check_cache(u_int cl) { dnsbl_succeed(cl, pl->listname[0] ? pl->listname : "dnsbl", - pl->answer); + pl->answer, "cache"); mydata->chito++; } else if (pl->state == OK) @@ -893,6 +919,7 @@ static int dnsbl_parse_reply(DnsblPending *p, const u_char *buf, size_t len, char rrname[DNSBL_LOOKUPLEN]; u_int16_t id, qdcount, ancount, qtype, qclass; int n; + int saw_invalid_a = 0; if (len < HFIXEDSZ) return DNSBL_PARSE_IGNORE; @@ -963,12 +990,14 @@ static int dnsbl_parse_reply(DnsblPending *p, const u_char *buf, size_t len, if (type == T_A && class == C_IN && dlen == INADDRSZ) { memcpy(answer, cp, INADDRSZ); - return DNSBL_PARSE_FOUND; + if (answer[0] == 127 && answer[1] == 0 && answer[2] == 0) + return DNSBL_PARSE_FOUND; + saw_invalid_a = 1; } cp += dlen; } - return DNSBL_PARSE_NEXT_HOST; + return saw_invalid_a ? DNSBL_PARSE_INVALID : DNSBL_PARSE_NEXT_HOST; } static int dnsbl_find_by_qid(struct dnsbl_private *mydata, u_short qid) @@ -1017,9 +1046,19 @@ static void dnsbl_gwork_one(struct dnsbl_private *mydata, const u_char *buf, return; } + if (action == DNSBL_PARSE_INVALID) + { + sendto_log(ALOG_DNSBL, LOG_WARNING, + "%s: ignoring unexpected DNSBL response %u.%u.%u.%u for %s[%s]", + p->current && p->current->host ? p->current->host : "?", + (u_int) answer[0], (u_int) answer[1], + (u_int) answer[2], (u_int) answer[3], + cldata[cl].host, cldata[cl].itsip); + } + p->qid = 0; p->deadline = 0; - if (action == DNSBL_PARSE_NEXT_HOST) + if (action == DNSBL_PARSE_NEXT_HOST || action == DNSBL_PARSE_INVALID) { p->current = p->current ? p->current->next : NULL; p->tries = 0; @@ -1110,12 +1149,6 @@ static char *dnsbl_init(AnInstance *self) dnsbl_appendf(&tmpbuf, &tmpbuf_size, &tmpbuf_used, ",reject"); dnsbl_appendf(&txtbuf, &txtbuf_size, &txtbuf_used, ", Reject"); } - else if (dnsbl_opt_eq(name, "paranoid")) - { - mydata->options |= OPT_PARANOID; - dnsbl_appendf(&tmpbuf, &tmpbuf_size, &tmpbuf_used, ",paranoid"); - dnsbl_appendf(&txtbuf, &txtbuf_size, &txtbuf_used, ", Paranoid"); - } else if (dnsbl_opt_startswith(name, "servers") && value) { char *servers = mystrdup(value); @@ -1352,7 +1385,7 @@ static int dnsbl_work(u_int cl) { dnsbl_add_cache(cl, DNSBL_FOUND, p->listname, p->answer); dnsbl_succeed(cl, p->listname[0] ? p->listname : "dnsbl", - (char *) p->answer); + p->answer, "dns"); } else if (p->final_state == DNSBL_DONE_CLEAN) { From 4f64da3221e7a71220837b01fd5f25d6aa2a8491 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 30 Aug 2026 22:07:44 +0200 Subject: [PATCH 3/6] DNSBL iauth module: removed obsolete "servers" option --- doc/iauth.conf.5 | 4 +++- iauth/mod_dnsbl.c | 25 ------------------------- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/doc/iauth.conf.5 b/doc/iauth.conf.5 index d1ecdf63..57177701 100644 --- a/doc/iauth.conf.5 +++ b/doc/iauth.conf.5 @@ -200,8 +200,10 @@ module webproxy port = 3128 module dnsbl - option = log,reject,servers=bl1.example.org,bl2.example.org + option = log,reject reason = Denied access (DNSBL) + entry = bl1.example.org + entry = bl2.example.org .fi .RE .SH CAVEATS diff --git a/iauth/mod_dnsbl.c b/iauth/mod_dnsbl.c index f1442e37..93f15bd4 100644 --- a/iauth/mod_dnsbl.c +++ b/iauth/mod_dnsbl.c @@ -1149,31 +1149,6 @@ static char *dnsbl_init(AnInstance *self) dnsbl_appendf(&tmpbuf, &tmpbuf_size, &tmpbuf_used, ",reject"); dnsbl_appendf(&txtbuf, &txtbuf_size, &txtbuf_used, ", Reject"); } - else if (dnsbl_opt_startswith(name, "servers") && value) - { - char *servers = mystrdup(value); - char *slast = NULL; - char *name2; - - if (!servers) - continue; - for (name2 = strtoken(&slast, servers, ","); name2; - name2 = strtoken(&slast, NULL, ",")) - { - while (*name2 && isspace((unsigned char) *name2)) - name2++; - for (s = name2 + strlen(name2); - s > name2 && isspace((unsigned char) s[-1]); ) - *--s = '\0'; - if (*name2) - { - dnsbl_add_host(mydata, name2, NULL); - sendto_log(ALOG_DNSBL, LOG_NOTICE, - "dnsbl_init: Added %s as dnsbl", name2); - } - } - free(servers); - } else if (dnsbl_opt_startswith(name, "cache") && value) { char *endp = NULL; From 1892c3638f18faa64eec4f31c5ff8b2ce91189c0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 30 Aug 2026 22:16:42 +0200 Subject: [PATCH 4/6] DNSBL iauth module: parse IP only once --- iauth/mod_dnsbl.c | 69 +++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/iauth/mod_dnsbl.c b/iauth/mod_dnsbl.c index 93f15bd4..b10bab70 100644 --- a/iauth/mod_dnsbl.c +++ b/iauth/mod_dnsbl.c @@ -76,11 +76,18 @@ struct dnsbl_list { struct dnsbl_list *next; }; +typedef union dnsbl_addr { + struct in_addr v4; + struct in6_addr v6; +} DnsblAddr; + typedef struct dnsbl_pending { int active; int wake_r; int wake_w; int final_state; + int addr_family; + DnsblAddr addr; u_short qid; u_int ns_index; u_int tries; @@ -308,35 +315,45 @@ static DnsblPending *dnsbl_pending_for_cl(struct dnsbl_private *mydata, u_int cl return &mydata->pend[owner]; } -static int dnsbl_build_lookup(u_int cl, struct dnsbl_list *l, char *lookup, - size_t lookup_len) +static int dnsbl_parse_client_ip(const char *ip, int *family, DnsblAddr *addr) { - struct addrinfo hints, *addr_res; - int rc; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_flags = AI_NUMERICHOST; + int af; - rc = getaddrinfo(cldata[cl].itsip, NULL, &hints, &addr_res); - if (rc) + if (!ip || !*ip || !family || !addr) return -1; - if (addr_res->ai_family == AF_INET) + /* A numeric IPv6 address always contains ':'. Select the family first + * so the textual client address is parsed exactly once. */ + af = strchr(ip, ':') ? AF_INET6 : AF_INET; + bzero((char *) addr, sizeof(*addr)); + if (af == AF_INET) { - const struct sockaddr_in *v4 = (const struct sockaddr_in *) addr_res->ai_addr; - const uint8_t *b = (const uint8_t *) &v4->sin_addr.s_addr; + if (inetpton(AF_INET, ip, &addr->v4) != 1) + return -1; + } + else if (inetpton(AF_INET6, ip, &addr->v6) != 1) + return -1; + + *family = af; + return 0; +} +static int dnsbl_build_lookup(const DnsblPending *p, struct dnsbl_list *l, + char *lookup, size_t lookup_len) +{ + const uint8_t *b; + + if (p->addr_family == AF_INET) + { + b = (const uint8_t *) &p->addr.v4.s_addr; snprintf(lookup, lookup_len, "%u.%u.%u.%u.%s", (unsigned int) (b[3]), (unsigned int) (b[2]), (unsigned int) (b[1]), (unsigned int) (b[0]), l->host); } - else if (addr_res->ai_family == AF_INET6) + else if (p->addr_family == AF_INET6) { - const struct sockaddr_in6 *v6 = (const struct sockaddr_in6 *) addr_res->ai_addr; - const uint8_t *b = (const uint8_t *) &v6->sin6_addr.s6_addr; - + b = (const uint8_t *) p->addr.v6.s6_addr; snprintf(lookup, lookup_len, "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%s", @@ -359,12 +376,8 @@ static int dnsbl_build_lookup(u_int cl, struct dnsbl_list *l, char *lookup, l->host); } else - { - freeaddrinfo(addr_res); return -1; - } - freeaddrinfo(addr_res); return 0; } @@ -779,7 +792,7 @@ static int dnsbl_send_request(u_int cl, struct dnsbl_private *mydata) { if (p->tries == 0) { - if (dnsbl_build_lookup(cl, p->current, p->lookup, sizeof(p->lookup)) != 0) + if (dnsbl_build_lookup(p, p->current, p->lookup, sizeof(p->lookup)) != 0) { DebugLog((ALOG_DNSBL, 0, "dnsbl_send_request(%d): invalid lookup host=%s, skipping", @@ -1274,20 +1287,16 @@ static int dnsbl_start(u_int cl) { struct dnsbl_private *mydata = cldata[cl].instance->data; DnsblPending *p = &mydata->pend[cl]; - struct addrinfo hints, *addr_res; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_flags = AI_NUMERICHOST; + int addr_family; + DnsblAddr addr; - if (getaddrinfo(cldata[cl].itsip, NULL, &hints, &addr_res)) + if (dnsbl_parse_client_ip(cldata[cl].itsip, &addr_family, &addr) != 0) { DebugLog((ALOG_DNSBL, 0, "dnsbl_start(%d): invalid address '%s', skipping ", cl, cldata[cl].itsip)); return -1; } - freeaddrinfo(addr_res); if (cldata[cl].state & A_DENY) { @@ -1311,6 +1320,8 @@ static int dnsbl_start(u_int cl) p->active = 1; p->final_state = DNSBL_PENDING; + p->addr_family = addr_family; + memcpy(&p->addr, &addr, sizeof(p->addr)); p->current = mydata->host_list; p->tries = 0; p->qid = 0; From e5409fe5e26d622f716a3affd29c7da04ec61621 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 30 Aug 2026 22:34:24 +0200 Subject: [PATCH 5/6] iauth: start DNSBL timeout on first DNS query; require an exact match for the cache option name. --- iauth/mod_dnsbl.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/iauth/mod_dnsbl.c b/iauth/mod_dnsbl.c index b10bab70..c4ca31f9 100644 --- a/iauth/mod_dnsbl.c +++ b/iauth/mod_dnsbl.c @@ -1162,7 +1162,7 @@ static char *dnsbl_init(AnInstance *self) dnsbl_appendf(&tmpbuf, &tmpbuf_size, &tmpbuf_used, ",reject"); dnsbl_appendf(&txtbuf, &txtbuf_size, &txtbuf_used, ", Reject"); } - else if (dnsbl_opt_startswith(name, "cache") && value) + else if (dnsbl_opt_eq(name, "cache") && value) { char *endp = NULL; unsigned long v; @@ -1318,6 +1318,9 @@ static int dnsbl_start(u_int cl) if (dnsbl_make_wakeup(cl, &p->wake_r, &p->wake_w) < 0) return -1; + /* Start timeout only once the DNS query is sent. */ + cldata[cl].timeout = 0; + p->active = 1; p->final_state = DNSBL_PENDING; p->addr_family = addr_family; @@ -1345,10 +1348,9 @@ static int dnsbl_start(u_int cl) if (p->qid != 0) return 0; - // Keep the module timeout running even while the lookup is still queued. DebugLog((ALOG_DNSBL, 0, - "dnsbl_start(%d): waiting in queue (timeout_at=%ld)", - cl, (long) cldata[cl].timeout)); + "dnsbl_start(%d): waiting in queue (module timeout paused)", + cl)); return 0; } From 8789e8fad59e948d1ff398721be56934d5a3e4a9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 30 Aug 2026 23:36:38 +0200 Subject: [PATCH 6/6] iauth: pass ircd name and sid to auth modules, updated mod_dnsbl and mod_external --- doc/iauth.conf.example | 2 +- iauth/a_io.c | 21 +++++++++++++++++++-- iauth/a_io_ext.h | 2 ++ iauth/mod_dnsbl.c | 7 +++++++ iauth/mod_external.c | 15 ++++----------- ircd/s_auth.c | 2 +- 6 files changed, 34 insertions(+), 15 deletions(-) diff --git a/doc/iauth.conf.example b/doc/iauth.conf.example index 15f2cefa..638fdcf9 100644 --- a/doc/iauth.conf.example +++ b/doc/iauth.conf.example @@ -55,4 +55,4 @@ module rfc931 # Check clients using an external scanning service. #module external -# option = sid=000A,host=127.0.0.1,port=9009,srcip=,timeout=20,allow_on_timeout=yes,skip_if_sasl,skip_if_ident +# option = host=127.0.0.1,port=9009,srcip=,timeout=20,allow_on_timeout=yes,skip_if_sasl,skip_if_ident diff --git a/iauth/a_io.c b/iauth/a_io.c index b06e760f..3aef86d2 100644 --- a/iauth/a_io.c +++ b/iauth/a_io.c @@ -38,6 +38,9 @@ static char iobuf[IOBUFSIZE+1]; static char rbuf[IOBUFSIZE+1]; /* incoming ircd stream */ static int iob_len = 0, rb_len = 0; +static char ircd_name[HOSTLEN + 1]; +static char ircd_sid[SIDLEN + 1]; + typedef struct { int fd; int want_write; @@ -214,6 +217,16 @@ void sendto_ircd(char *pattern, ...) va_end(va); } +const char *iauth_ircd_name(void) +{ + return ircd_name[0] ? ircd_name : "*"; +} + +const char *iauth_ircd_sid(void) +{ + return ircd_sid[0] ? ircd_sid : "*"; +} + static char *iauth_skip_ws(char *p) { while (*p == ' ' || *p == '\t') @@ -794,8 +807,12 @@ static void parse_ircd(void) "Error from ircd: %s", chp); break; case 'M': - /* RPL_HELLO to be exact, but who cares. */ - strConnLen = sprintf(strConn, ":%s 020 * :", chp+2); + /* M */ + p = chp + 1; + p = iauth_read_token(p, ircd_name, sizeof(ircd_name)); + (void) iauth_read_token(p, ircd_sid, sizeof(ircd_sid)); + strConnLen = snprintf(strConn, sizeof(strConn), + ":%s 020 * :", iauth_ircd_name()); break; case 'S': /* SASL authentication */ cldata[cl].state |= A_SASL; diff --git a/iauth/a_io_ext.h b/iauth/a_io_ext.h index 6ce736c1..081512eb 100644 --- a/iauth/a_io_ext.h +++ b/iauth/a_io_ext.h @@ -38,6 +38,8 @@ extern anAuthData cldata[MAXCONNECTIONS]; EXTERN void io_init(void); EXTERN void vsendto_ircd (char *, va_list); EXTERN void sendto_ircd (char *, ...); +EXTERN const char *iauth_ircd_name(void); +EXTERN const char *iauth_ircd_sid(void); EXTERN void init_io (void); EXTERN void loop_io (void); EXTERN int tcp_connect (char *, char *, u_short, char **); diff --git a/iauth/mod_dnsbl.c b/iauth/mod_dnsbl.c index c4ca31f9..88ad0d2f 100644 --- a/iauth/mod_dnsbl.c +++ b/iauth/mod_dnsbl.c @@ -538,7 +538,14 @@ static void dnsbl_succeed(u_int cl, const char *listname, if (mydata->options & OPT_DENY) { + const char *nick = cldata[cl].nick[0] ? cldata[cl].nick : "*"; + cldata[cl].state |= A_DENY; + sendto_ircd("R %u %s %u ::%s 465 %s " + ":You (*@%s) are banned from this server: %s", + cl, cldata[cl].itsip, cldata[cl].itsport, + iauth_ircd_name(), nick, cldata[cl].itsip, + reason ? reason : ""); sendto_ircd("k %d %s %u #dnsbl :%s", cl, cldata[cl].itsip, cldata[cl].itsport, reason ? reason : ""); diff --git a/iauth/mod_external.c b/iauth/mod_external.c index f40c88cb..30a81eae 100644 --- a/iauth/mod_external.c +++ b/iauth/mod_external.c @@ -87,7 +87,6 @@ struct external_conf { u_int timeout; /* seconds per client */ u_char allow_on_timeout; /* 0=deny, 1=allow */ char reason[128]; - char sid[128]; /* server identifier in CONN */ char srcip[64]; /* optional bind() source IP */ }; @@ -482,7 +481,6 @@ static char *external_init(AnInstance *self) S->cfg.allow_on_timeout = 0; external_safe_copy(S->cfg.reason, sizeof(S->cfg.reason), "Denied access (policy)"); - external_safe_copy(S->cfg.sid, sizeof(S->cfg.sid), "-"); S->gfd = 0; S->qh = S->qt = 0; S->ibuf_len = 0; @@ -523,11 +521,6 @@ static char *external_init(AnInstance *self) ? 1 : 0; } - else if (!strncasecmp(tok, "sid=", 4)) - { - external_safe_copy(S->cfg.sid, sizeof(S->cfg.sid), tok + 4); - external_sanitize_ascii(S->cfg.sid, sizeof(S->cfg.sid)); - } else if (!strncasecmp(tok, "srcip=", 6)) { external_safe_copy(S->cfg.srcip, sizeof(S->cfg.srcip), tok + 6); @@ -543,9 +536,9 @@ static char *external_init(AnInstance *self) DebugLog((ALOG_DMISC, 0, "external_init: host=%s port=%u timeout=%u allow_on_timeout=%u " - "sid=[%s] reason=[%s]", + "reason=[%s]", S->cfg.host, S->cfg.port, S->cfg.timeout, S->cfg.allow_on_timeout, - S->cfg.sid, S->cfg.reason)); + S->cfg.reason)); return NULL; } @@ -944,7 +937,7 @@ static int external_start(u_int cl) *cldata[cl].user3 && *cldata[cl].realname) { snprintf(line, sizeof(line), "CONN %s %u %s %s %s %s %s %s %s %s :%s\r\n", - S->cfg.sid, cl, cldata[cl].itsip, + iauth_ircd_sid(), cl, cldata[cl].itsip, external_nonempty_or_asterisk(cldata[cl].host), external_nonempty_or_asterisk(cldata[cl].authuser), external_nonempty_or_asterisk(cldata[cl].sasl_user), @@ -954,7 +947,7 @@ static int external_start(u_int cl) else { snprintf(line, sizeof(line), "CONN %s %u %s %s %s %s\r\n", - S->cfg.sid, cl, cldata[cl].itsip, + iauth_ircd_sid(), cl, cldata[cl].itsip, external_nonempty_or_asterisk(cldata[cl].host), external_nonempty_or_asterisk(cldata[cl].authuser), external_nonempty_or_asterisk(cldata[cl].sasl_user)); diff --git a/ircd/s_auth.c b/ircd/s_auth.c index 49e15317..942f9b26 100644 --- a/ircd/s_auth.c +++ b/ircd/s_auth.c @@ -234,7 +234,7 @@ void read_iauth(void) sendto_flag(SCH_AUTH, "iauth version %s running.", iauth_version); start = end; - sendto_iauth("0 M %s", me.name); + sendto_iauth("0 M %s %s", me.name, me.serv->sid); continue; } if (*start == 'a')