+static void
+do_expandos(char *output, unsigned int out_len, const char *input, ...)
+{
+ va_list args;
+ const char *key;
+ const char *datum;
+ char *found;
+ unsigned int klen;
+ unsigned int dlen;
+ unsigned int rlen;
+
+ safestrncpy(output, input, out_len);
+ va_start(args, input);
+ while ((key = va_arg(args, const char*)) != NULL) {
+ datum = va_arg(args, const char *);
+ klen = strlen(key);
+ dlen = strlen(datum);
+ for (found = output; (found = strstr(output, key)) != NULL; found += dlen) {
+ rlen = strlen(found + klen);
+ if ((dlen > klen) && (found + dlen + rlen - output > out_len))
+ rlen = output + out_len - found - dlen;
+ memmove(found + dlen, found + klen, rlen);
+ memcpy(found, datum, dlen + 1);
+ }
+ }
+ va_end(args);
+}
+
+static void
+dnsbl_hit(struct sar_request *req, struct dns_header *hdr, struct dns_rr *rr, unsigned char *raw, unsigned int raw_size)
+{
+ struct dnsbl_data *data;
+ struct dnsbl_zone *zone;
+ const char *message;
+ char *txt;
+ unsigned int mask;
+ unsigned int pos;
+ unsigned int len;
+ unsigned int ii;
+ char reason[MAXLEN];
+ char target[IRC_NTOP_MAX_SIZE + 2];
+
+ /* Get the DNSBL zone (to make sure it has not disappeared in a rehash). */
+ data = (struct dnsbl_data*)(req + 1);
+ zone = dict_find(blacklist_zones, data->zone_name, NULL);
+ if (!zone)
+ return;
+
+ /* Scan the results. */
+ for (mask = 0, ii = 0, txt = NULL; ii < hdr->ancount; ++ii) {
+ pos = rr[ii].rd_start;
+ switch (rr[ii].type) {
+ case REQ_TYPE_A:
+ if (rr[ii].rdlength != 4)
+ break;
+ if (pos + 3 < raw_size)
+ mask |= (1 << raw[pos + 3]);
+ break;
+ case REQ_TYPE_TXT:
+ len = raw[pos];
+ txt = malloc(len + 1);
+ memcpy(txt, raw + pos + 1, len);
+ txt[len] = '\0';
+ break;
+ }
+ }
+
+ /* Do we care about one of the masks we found? */
+ if (mask & zone->mask) {
+ /* See if a per-result message was provided. */
+ for (ii = 0, message = NULL; mask && (ii < zone->reasons.used); ++ii, mask >>= 1) {
+ if (0 == (mask & 1))
+ continue;
+ if (NULL != (message = zone->reasons.list[ii]))
+ break;
+ }
+
+ /* If not, use a standard fallback. */
+ if (message == NULL) {
+ message = zone->reason;
+ if (message == NULL)
+ message = "client is blacklisted";
+ }
+
+ /* Expand elements of the message as necessary. */
+ do_expandos(reason, sizeof(reason), message, "%txt%", (txt ? txt : "(no-txt)"), "%ip%", data->client_ip, NULL);
+
+ /* Now generate the G-line. */
+ target[0] = '*';
+ target[1] = '@';
+ strcpy(target + 2, data->client_ip);
+ gline_add(self->name, target, zone->duration, reason, now, now, 1);
+ }
+ free(txt);
+}
+