rbldnsd-0.997a/dns_ptodn.c0000664000175000017500000000327012120257742013643 0ustar mjtmjt/* dns_ptodn() parses external textual dot-separated format * into a domain name */ #include "dns.h" #include unsigned dns_ptodn(const char *name, unsigned char *dn, unsigned dnsiz) { unsigned char *d; /* current position in dn (len byte first) */ unsigned char *label; /* start of last label */ unsigned char *m; /* max byte can be filled up */ unsigned l; /* length of current label */ unsigned c; /* next input character */ d = dn + 1; label = d; m = dn + (dnsiz > DNS_MAXDN ? DNS_MAXDN : dnsiz) - 1; while((c = (unsigned char)*name++) != 0) { if (c == '.') { if ((l = d - label) != 0) { /* if there was a non-empty label */ if (l > DNS_MAXLABEL) { errno = EMSGSIZE; return 0; } label[-1] = (char)l; /* update len of last label */ label = ++d; /* start new label, label[-1] will be len of it */ } continue; } if (c == '\\') { /* handle escapes */ if (!(c = (unsigned char)*name++)) break; if (c >= '0' && c <= '9') { /* dec number: will be in c */ c -= '0'; if (*name >= '0' && *name <= '9') { /* 2digits */ c = (c * 10) + (*name++ - '0'); if (*name >= '0' && *name <= '9') { /* 3digits */ c = (c * 10) + (*name++ - '0'); if (c > 255) { errno = EINVAL; return 0; } } } } } if (d >= m) { /* too long? */ errno = EMSGSIZE; return 0; } *d++ = (char)c; /* place next out byte */ } if ((l = d - label) > DNS_MAXLABEL) { errno = EMSGSIZE; return 0; } if ((label[-1] = (char)l) != 0) *d++ = 0; return d - dn; } rbldnsd-0.997a/dns_dntop.c0000664000175000017500000000216712120257742013647 0ustar mjtmjt/* dns_dntop() produces printable ascii representation (external form) * of a domain name */ #include "dns.h" unsigned dns_dntop(const unsigned char *dn, char *dst, unsigned dstsiz) { char *d = dst; char *m = dst + dstsiz - 1; unsigned n; unsigned char c; if (!(n = *dn++)) { if (dstsiz < 2) return 0; *d++ = '.'; *d++ = '\0'; return 1; } do { if (d >= m) return 0; if (dst != d) *d++ = '.'; do { switch((c = *dn++)) { case '"': case '.': case ';': case '\\': /* Special modifiers in zone files. */ case '@': case '$': if (d + 1 >= m) return 0; *d++ = '\\'; *d++ = c; break; default: if (c <= 0x20 || c >= 0x7f) { if (d + 3 >= m) return 0; *d++ = '\\'; *d++ = '0' + (c / 100); *d++ = '0' + ((c % 100) / 10); *d++ = '0' + (c % 10); } else { if (d >= m) return 0; *d++ = c; } } } while(--n); } while((n = *dn++) != 0); *d = '\0'; return d - dst; } rbldnsd-0.997a/dns_dntol.c0000664000175000017500000000051112120257742013632 0ustar mjtmjt/* dns_dntol() lowercases a domain name */ #include "dns.h" unsigned dns_dntol(const unsigned char *srcdn, unsigned char *dstdn) { unsigned c; const unsigned char *s = srcdn; while((c = (*dstdn++ = *s++)) != 0) { do { *dstdn++ = dns_dnlc(*s); ++s; } while(--c); } return (unsigned)(s - srcdn); } rbldnsd-0.997a/dns_dnlen.c0000664000175000017500000000044412120257742013617 0ustar mjtmjt/* dns_dnlen(): return length of a domain name (with trailing * zero, as it is an integral part of DN) */ #include "dns.h" unsigned dns_dnlen(register const unsigned char *dn) { register unsigned c; const unsigned char *d = dn; while((c = *d++) != 0) d += c; return d - dn; } rbldnsd-0.997a/dns_dnlabels.c0000664000175000017500000000032412120257742014300 0ustar mjtmjt/* dns_dnlabels() returns number of individual labels in a domain name */ #include "dns.h" unsigned dns_dnlabels(const unsigned char *dn) { unsigned l = 0; while(*dn) ++l, dn += 1 + *dn; return l; } rbldnsd-0.997a/dns_dnequ.c0000664000175000017500000000060712120257742013634 0ustar mjtmjt/* dns_dneq(): compare to domain names and return true if equal * (case-unsensitive) */ #include "dns.h" int dns_dnequ(const unsigned char *dn1, const unsigned char *dn2) { unsigned c; for(;;) { if ((c = *dn1++) != *dn2++) return 0; if (!c) return 1; while(c--) { if (dns_dnlc(*dn1) != dns_dnlc(*dn2)) return 0; ++dn1; ++dn2; } } } rbldnsd-0.997a/dns_dnreverse.c0000664000175000017500000000133712120257742014516 0ustar mjtmjt/* dns_dnreverse() produces reverse of a domain name */ #include #include "dns.h" /* reverse labels of the dn, return dns_dnlen() */ unsigned dns_dnreverse(register const unsigned char *dn, register unsigned char *rdn, register unsigned len) { register unsigned c; /* length of a current label */ if (!len) /* if no length given, compute it */ len = dns_dnlen(dn); rdn += len; /* start from the very end */ *--rdn = '\0'; /* and null-terminate the dn */ while((c = *dn) != 0) { /* process each label */ ++c; /* include length byte */ rdn -= c; /* this is where this label will be in rdn - back it's len */ memcpy(rdn, dn, c); dn += c; } return len; } rbldnsd-0.997a/dns_findname.c0000664000175000017500000000113112120257742014272 0ustar mjtmjt/* dns_findname() is an auxilary routine to find given name in a * given dns_nameval table. */ #include "dns.h" #include const struct dns_nameval * dns_findname(const struct dns_nameval *nv, const char *name) { char nm[60]; /* all names are less than 60 chars anyway */ char *p = nm; while(*name) { if (*name >= 'a' && *name <= 'z') *p++ = *name++ - 'a' + 'A'; else *p++ = *name++; if (p == nm + sizeof(nm) - 1) return NULL; } *p = '\0'; while(nv->name) if (strcmp(nv->name, nm) == 0) return nv; else ++nv; return NULL; } rbldnsd-0.997a/ip4parse.c0000664000175000017500000001323412120257742013403 0ustar mjtmjt/* IP4 address parsing routines */ #include "ip4addr.h" #define digit(c) ((c) >= '0' && (c) <= '9') #define d2n(c) ((c) - '0') /* cret(): returns v if last byte is \0, or else * either sets *np to point to the last byte and * returns v, or return -1 */ #define cret(v, np, s) ((np) ? *(np) = (char*)(s), (v) : *(s) ? -1 : (v)) /* eret(): returns -1 and sets *np if np is non-NULL */ #define eret(np, s) ((np) ? *(np) = (char*)(s), -1 : -1) static int ip4mbits(const char *s, char **np) { /* helper routine for ip4cidr() and ip4range() */ int bits; if (!digit(*s)) return eret(np, s - 1); bits = d2n(*s++); while(digit(*s)) if ((bits = bits * 10 + d2n(*s++)) > 32) return eret(np, s - 1); /*if (!bits) return eret(np, s - 1); allow /0 mask too */ return cret(bits, np, s); } /* parse a prefix, return # of bits (8,16,24 or 32) * or <0 on error. Can't return 0. */ int ip4prefix(const char *s, ip4addr_t *ap, char **np) { ip4addr_t o; *ap = 0; #define ip4oct(bits) \ if (!digit(*s)) \ return eret(np, s); \ o = d2n(*s++); \ while(digit(*s)) \ if ((o = o * 10 + d2n(*s++)) > 255) \ return eret(np, s - 1); \ *ap |= o << bits ip4oct(24); if (*s != '.') return cret(8, np, s); ++s; ip4oct(16); if (*s != '.') return cret(16, np, s); ++s; ip4oct(8); if (*s != '.') return cret(24, np, s); ++s; ip4oct(0); return cret(32, np, s); #undef ip4oct } /* Parse ip4 CIDR range in `s', store base * in *ap and return number of bits (may be 0) * or <0 on error. * To zero hostpart: * *ap &= ip4mask(bits) * where bits is the return value */ int ip4cidr(const char *s, ip4addr_t *ap, char **np) { int bits = ip4prefix(s, ap, (char**)&s); if (bits < 0) /* error */ return eret(np, s); else if (*s == '/') /* probably /bits */ return ip4mbits(s + 1, np); else if (bits == 8) /* disallow bare numbers */ return eret(np, s); else return cret(bits, np, s); } /* Parse ip4 range or CIDR in `s', * store start in *ap and end in *bp, * return * bits (may be 0) if that was CIDR or prefix, * 32 if plain range, * <0 on error. * *ap may have non-zero hostpart on * return and should be adjusted as * *ap &= ip4mask(bits) * where bits is the return value. */ int ip4range(const char *s, ip4addr_t *ap, ip4addr_t *bp, char **np) { int bits = ip4prefix(s, ap, (char**)&s); if (bits < 0) return eret(np, s); else if (*s == '-') { /* a-z */ int bbits = ip4prefix(s + 1, bp, (char**)&s); if (bbits < 0) return eret(np, s); if (bbits == 8) { /* treat 127.0.0.1-2 as 127.0.0.1-127.0.0.2 */ *bp = (*bp >> (bits - 8)) | (*ap & ip4mask(bits - 8)); bbits = bits; } else if (bbits != bits) /* disallow weird stuff like 1.2-1.2.3.4 */ return eret(np, s); if (bbits != 32) /* complete last octets */ *bp |= ~ip4mask(bbits); if (*ap > *bp) return eret(np, s); return cret(32, np, s); } else { if (*s == '/') { /* /bits */ bits = ip4mbits(s + 1, (char**)&s); if (bits < 0) return eret(np, s); } else if (bits == 8) /* disallow bare numbers - use /8 */ return eret(np, s); *bp = *ap | ~ip4mask(bits); return cret(bits, np, s); } } /* traditional inet_aton/inet_addr. Werid: * 127.1 = 127.0.0.1 * 1 = 0.0.0.1!!! - but here we go... * return #bits in _prefix_ (8,16,24 or 32), or <0 on error. */ int ip4addr(const char *s, ip4addr_t *ap, char **np) { int bits = ip4prefix(s, ap, np); switch(bits) { case 8: /* what a werid case! */ *ap >>= 24; break; case 16: *ap = (*ap & 0xff000000u) | ((*ap >> 16) & 255); break; case 24: *ap = (*ap & 0xffff0000u) | ((*ap >> 8) & 255); break; } return bits; } #ifdef TEST #include int main(int argc, char **argv) { int i; ip4addr_t a, b; int bits; char *np; #define octets(x) (x)>>24,((x)>>16)&255,((x)>>8)&255,(x)&255 #define IPFMT "%u.%u.%u.%u" for(i = 1; i < argc; ++i) { char *s = argv[i]; printf("%s:\n", s); #define PFX " pfx : bits=%d " #define ADDR " addr : bits=%d " #define CIDR " cidr : bits=%d " #define RANGE " range: bits=%d " #define HP(a,bits) (a & ~ip4mask(bits) ? " (non-zero hostpart)" : "") bits = ip4prefix(s, &a, NULL); if (bits < 0) printf(" pfx : err\n"); else printf(" pfx : bits=%d " IPFMT "\n", bits, octets(a)); bits = ip4prefix(s, &a, &np); if (bits < 0) printf(" pfx : err tail=`%s'\n", np); else printf(" pfx : bits=%d " IPFMT " tail=`%s'\n", bits, octets(a), np); bits = ip4addr(s, &a, NULL); if (bits < 0) printf(" addr : err\n"); else printf(" addr : bits=%d " IPFMT "\n", bits, octets(a)); bits = ip4addr(s, &a, &np); if (bits < 0) printf(" addr : err tail=`%s'\n", np); else printf(" addr : bits=%d " IPFMT " tail=`%s'\n", bits, octets(a), np); bits = ip4cidr(s, &a, NULL); if (bits < 0) printf(" cidr : err\n"); else printf(" cidr : bits=%d " IPFMT "%s\n", bits, octets(a), HP(a,bits)); bits = ip4cidr(argv[i], &a, &np); if (bits < 0) printf(" cidr : err tail=`%s'\n", np); else printf(" cidr : bits=%d " IPFMT " " IPFMT " tail=`%s'%s\n", bits, octets(a), octets(a&ip4mask(bits)), np, HP(a,bits)); bits = ip4range(s, &a, &b, NULL); if (bits < 0) printf(" range: err\n"); else printf(" range: bits=%d " IPFMT "-" IPFMT "%s\n", bits, octets(a), octets(b), HP(a,bits)); bits = ip4range(s, &a, &b, &np); if (bits < 0) printf(" range: err tail=`%s'\n", np); else printf(" range: bits=%d " IPFMT "-" IPFMT " tail=`%s'%s\n", bits, octets(a), octets(b), np, HP(a,bits)); } return 0; } #endif rbldnsd-0.997a/ip4atos.c0000664000175000017500000000122512120257742013234 0ustar mjtmjt/* ip4atos() converts binary IP4 address into textual printable * dotted-quad form. */ #include "ip4addr.h" /* helper routine for ip4atos() */ static char *oct(char *s, unsigned char o, char e) { if (o >= 100) { *s++ = o / 100 + '0', o %= 100; *s++ = o / 10 + '0', o %= 10; } else if (o >= 10) *s++ = o / 10 + '0', o %= 10; *s++ = o + '0'; *s++ = e; return s; } /* return printable representation of ip4addr like inet_ntoa() */ const char *ip4atos(ip4addr_t a) { static char buf[16]; oct(oct(oct(oct(buf, (a >> 24) & 0xff, '.'), (a >> 16) & 0xff, '.'), (a >> 8) & 0xff, '.'), a & 0xff, '\0'); return buf; } rbldnsd-0.997a/ip4mask.c0000664000175000017500000000123312120257742013220 0ustar mjtmjt/* ip4mask(), given number of significant bits in network * prefix, return IP4 network mask. */ #include "ip4addr.h" /* network mask for a number of bits in network part. * Use ~mask for host part */ const ip4addr_t ip4addr_cidr_netmasks[33] = { 0, 0x80000000u,0xc0000000u,0xe0000000u,0xf0000000u, 0xf8000000u,0xfc000000u,0xfe000000u,0xff000000u, 0xff800000u,0xffc00000u,0xffe00000u,0xfff00000u, 0xfff80000u,0xfffc0000u,0xfffe0000u,0xffff0000u, 0xffff8000u,0xffffc000u,0xffffe000u,0xfffff000u, 0xfffff800u,0xfffffc00u,0xfffffe00u,0xffffff00u, 0xffffff80u,0xffffffc0u,0xffffffe0u,0xfffffff0u, 0xfffffff8u,0xfffffffcu,0xfffffffeu,0xffffffffu }; rbldnsd-0.997a/ip6addr.c0000664000175000017500000001510012130046505013171 0ustar mjtmjt/* IPv6 address-related routines */ #include "ip6addr.h" #include #include #define digit(x) ((x)>='0'&&(x)<='9') #define d2n(x) ((x)-'0') /* parse address (prefix) in a form ffff:ffff:ffff:ffff:... * granularity (bits) is 16, so the routine will return * 16, 32, 48, 64, ... _bits_ on output or <0 on error. * "an" is the max amount of bytes (not bits!) to store in *ap, * should be even (2, 4, 6, 8, ...) and should be at least 2. * The routine does support shortcuts like ffff::ffff. */ int ip6prefix(const char *s, ip6oct_t ap[IP6ADDR_FULL], char **np) { static signed char hex_table[] = { /* map ascii to hex nibble value */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; unsigned bytes = 0; /* number of bytes we filled in ap so far */ int zstart = -1; /* location of "::", if any */ int ret = -1; /* memset is better be done here instead of zeroing the tail, * since it's faster to zerofill full words. * We may even change that to a by-word loop, since all * addresses are aligned properly anyway */ memset(ap, 0, IP6ADDR_FULL); if (s[0] == ':' && s[1] == ':') { zstart = 0; s += 2; } /* loop by semicolon-separated 2-byte (4 hex digits) fields */ for(;;) { unsigned v = 0; /* 2-byte (4 hex digit) field */ const char *ss = s; /* save `s' value */ while ((*s & '\x80') == 0) { int nibble = hex_table[(unsigned)*s]; if (nibble < 0) break; /* not a hex digit */ v = (v << 4) + nibble; if (v > 0xffff) /* a field can't be > 0xffff */ break; ++s; } if (ss == s || v > 0xffff) /* if no field has been consumed */ break; ap[bytes++] = v >> 8; ap[bytes++] = v & 0xff; if (*s != ':' || bytes + 2 > IP6ADDR_FULL) { ret = bytes * 8; break; } ++s; if (zstart < 0 && *s == ':') { zstart = bytes; ++s; } } if (zstart >= 0) { /* expand the "::" */ unsigned nzeros = IP6ADDR_FULL - bytes; if (nzeros == 0) ret = -1; /* illegal - no space for zeros */ else { memmove(&ap[zstart + nzeros], &ap[zstart], bytes - zstart); memset(&ap[zstart], 0, nzeros); ret = 8 * IP6ADDR_FULL; } } if (np) *np = (char*)s; else if (*s) /* no success return if no 'tail' (np) pointer is given * and the tail isn't empty */ ret = -1; return ret; } /* Parse ip6 CIDR range in `s', store base * in *ap (of size an bytes) and return number * of _bits_ (may be 0) or <0 on error. */ int ip6cidr(const char *s, ip6oct_t ap[IP6ADDR_FULL], char **np) { int bits = ip6prefix(s, ap, (char**)&s); if (bits >= 0 && *s == '/') { /* parse /bits CIDR range */ ++s; if (!digit(*s)) bits = -1; else { bits = d2n(*s++); while(digit(*s)) if ((bits = bits * 10 + d2n(*s++)) > 128) { bits = -1; break; } } } else if (bits == 16) bits = -1; /* disallow bare number */ if (np) *np = (char*)s; else if (*s) bits = -1; return bits; } int ip6mask(const ip6oct_t *ap, ip6oct_t *bp, unsigned n, unsigned bits) { unsigned i; int r = 0; i = bits / 8; bits %= 8; /* copy head */ if (bp && bp != ap) memcpy(bp, ap, i < n ? i : n); /* check the middle byte */ if (i < n && bits) { if (ap[i] & (0xff >> bits)) r = 1; if (bp) bp[i] = ap[i] & (0xff << (8 - bits)); ++i; } /* check-n-zero tail */ while(i < n) { if (ap[i]) r = 1; if (bp) bp[i] = 0; ++i; } return r; } const char *ip6atos(const ip6oct_t *ap, unsigned an) { static char buf[(4+1)*8+1]; unsigned awords = an / 2; char *bp = buf; unsigned nzeros = 0, zstart = 0, i; if (awords > IP6ADDR_FULL / 2) awords = IP6ADDR_FULL / 2; /* find longest string of repeated zero words */ for (i = 0; i + nzeros < IP6ADDR_FULL / 2; i++) { unsigned nz; /* count zero words */ for (nz = 0; i + nz < awords; nz++) if (ap[2 * (i + nz)] != 0 || ap[2 * (i + nz) + 1] != 0) break; if (i + nz == awords) nz += IP6ADDR_FULL / 2 - awords; if (nz > 1 && nz > nzeros) { nzeros = nz; zstart = i; } } for (i = 0; i < zstart; i++) bp += sprintf(bp, ":%x", (((unsigned)ap[2*i]) << 8) + ap[2*i+1]); if (nzeros) { *bp++ = ':'; if (zstart == 0) *bp++ = ':'; /* leading "::" */ if (zstart + nzeros == IP6ADDR_FULL / 2) *bp++ = ':'; /* trailing "::" */ } for (i += nzeros; i < awords; i++) bp += sprintf(bp, ":%x", (((unsigned)ap[2*i]) << 8) + ap[2*i+1]); for (; i < IP6ADDR_FULL / 2; i++) { *bp++ = ':'; *bp++ = '0'; } *bp = '\0'; return buf + 1; } #ifdef TEST #include /* our own version of assert() * * (mostly so that checks still happen even when compiled with * -DNDEBUG) */ #define CHECK(expr) ((expr) ? (void) 0 : report_failure(#expr, __LINE__)) static void report_failure(const char *expr, unsigned line) { printf("%s:%u: check `%s' failed\n", __FILE__, line, expr); abort(); } int main(int argc, char **argv) { int i; ip6oct_t a[IP6ADDR_FULL]; int bits; char *np; #define ip6tos(a,bits) (bits < 0 ? "err" : ip6atos(a,sizeof(a))) for(i = 1; i < argc; ++i) { char *s = argv[i]; printf("%s:\n", s); bits = ip6prefix(s, a, NULL); printf(" pfx: %s/%d\n", ip6tos(a, bits), bits); bits = ip6prefix(s, a, &np); printf(" pfx: %s/%d tail=`%s'\n", ip6tos(a, bits), bits, np); bits = ip6cidr(s, a, NULL); printf("cidr: %s/%d\n", ip6tos(a, bits), bits); bits = ip6cidr(s, a, &np); printf("cidr: %s/%d tail=`%s'\n", ip6tos(a, bits), bits, np); if (bits >= 0) { bits = ip6mask(a, a, IP6ADDR_FULL, bits); printf("mask: %s (host=%d)\n", ip6atos(a, sizeof(a)), bits); } } CHECK(ip6prefix("::1", a, NULL) == 128); CHECK(strcmp(ip6atos(a, 128), "::1") == 0); CHECK(ip6prefix("1::", a, NULL) == 128); CHECK(strcmp(ip6atos(a, 128), "1::") == 0); CHECK(ip6prefix("1::4:0:0:0:8", a, NULL) == 128); CHECK(strcmp(ip6atos(a, 128), "1:0:0:4::8") == 0); return 0; } #endif rbldnsd-0.997a/mempool.c0000664000175000017500000000735312130046505013323 0ustar mjtmjt/* memory pool implementation */ #include #include #include "mempool.h" /* A pool of constant (in size) memory blocks which will be * freed all at once. We allocate memory in (relatively) * large chunks (MEMPOOL_CHUNKSIZE) and keep list of chunks * with some free space within them and list of chunks without * sufficient free space. Mempool is optimized to allocate * blocks of approximate equal size (determined dynamically), * so free chunks are moved to "used" list when free space * becomes less than an average allocated block size. */ #define alignto sizeof(void*) #define alignmask (alignto-1) void *emalloc(unsigned size); #define MEMPOOL_CHUNKSIZE (65536-sizeof(unsigned)*4) struct mempool_chunk { char buf[MEMPOOL_CHUNKSIZE+alignto]; struct mempool_chunk *next; unsigned size; }; struct mempool_cfull { /* pseudo-chunk: one entry into full list */ struct mempool_chunk *next; char buf[1]; }; void mp_init(struct mempool *mp) { mp->mp_chunk = mp->mp_fullc = NULL; mp->mp_nallocs = mp->mp_datasz = 0; mp->mp_lastbuf = NULL; mp->mp_lastlen = 0; } void *mp_alloc(struct mempool *mp, unsigned size, int align) { if (size >= MEMPOOL_CHUNKSIZE / 2) { /* for large blocks, allocate separate "full" chunk */ struct mempool_cfull *c = (struct mempool_cfull*)emalloc(sizeof(*c)+size-1); if (!c) return NULL; c->next = mp->mp_fullc; mp->mp_fullc = (struct mempool_chunk*)c; return c->buf; } else { struct mempool_chunk *c; struct mempool_chunk *best; /* "best fit" chunk */ unsigned avg; /* average data size: total size / numallocs */ ++mp->mp_nallocs; mp->mp_datasz += size; avg = mp->mp_datasz / mp->mp_nallocs; /* round size up to a multiple of alignto */ if (align) size = (size + alignmask) & ~alignmask; for(c = mp->mp_chunk, best = NULL; c; c = c->next) if (c->size >= size && (!best || best->size > c->size)) { best = c; if (c->size - size < avg) break; } if (best != NULL) { /* found a free chunk */ char *b; if (align) best->size &= ~alignmask; b = best->buf + MEMPOOL_CHUNKSIZE - best->size; best->size -= size; if (best->size < avg) { struct mempool_chunk **cp = &mp->mp_chunk; while(*cp != best) cp = &(*cp)->next; *cp = best->next; best->next = mp->mp_fullc; mp->mp_fullc = best; } return b; } else { /* no sutable chunks -> allocate new one */ c = (struct mempool_chunk *)emalloc(sizeof(*c)); if (!c) return NULL; c->next = mp->mp_chunk; mp->mp_chunk = c; c->size = MEMPOOL_CHUNKSIZE - size; return c->buf; } } } void mp_free(struct mempool *mp) { struct mempool_chunk *c; while((c = mp->mp_chunk) != NULL) { mp->mp_chunk = c->next; free(c); } while((c = mp->mp_fullc) != NULL) { mp->mp_fullc = c->next; free(c); } mp_init(mp); } void *mp_memdup(struct mempool *mp, const void *buf, unsigned len) { void *b = mp_alloc(mp, len, 0); if (b) memcpy(b, buf, len); return b; } char *mp_strdup(struct mempool *mp, const char *str) { return (char*)mp_memdup(mp, str, strlen(str) + 1); } /* string pool: a pool of _constant_ strings, with minimal * elimination of dups (only last request is checked) */ const void *mp_dmemdup(struct mempool *mp, const void *buf, unsigned len) { if (mp->mp_lastlen == len && memcmp(mp->mp_lastbuf, buf, len) == 0) return mp->mp_lastbuf; else if ((buf = mp_memdup(mp, buf, len)) != NULL) mp->mp_lastbuf = buf, mp->mp_lastlen = len; return buf; } const char *mp_dstrdup(struct mempool *mp, const char *str) { return (const char*)mp_dmemdup(mp, str, strlen(str) + 1); } rbldnsd-0.997a/istream.c0000664000175000017500000002320312120257742013315 0ustar mjtmjt/* Simple and efficient (especially line-oriented) read (input) * stream implementation a-la stdio. */ #include #include #include #include "config.h" #ifndef NO_ZLIB #include #include #endif #include "istream.h" #ifndef EPROTO # define EPROTO ENOEXEC #endif #if !defined(__GNUC__) && !defined(__attribute__) # define __attribute__(x) #endif #ifndef UNUSED # define UNUSED __attribute__((unused)) #endif /* An attempt of efficient copy-less way to read lines from a file. * We fill in a buffer, and try to find next newline character. * If found, advance 'readp' pointer past it, replace it with * null byte and return previous value of 'readp' (which points * to the beginning of the line). When there's no newline found * in the data which is already read into buffer, there are several * possibilities: * o the amount of data in buffer exceeds BUFSIZE/2 - line is too long, * return whatever we have already. * o we have at least BUFSIZE/2 free bytes at the end of the buffer - * read next BUFSIZE/2 chunk from file (actual read may return less * data) * o we don't have BUFSIZE/2 free bytes at the end of the buffer - * move some data to the beginning, to "defragment" the buffer, * and start over. * We read in chunks of BUFSIZE/2, and the buffer size is BUFSIZE to * be able to avoid moving data in the buffer as much as possible, * AND to be able to read chunks of exactly BUFSIZE/2 size from the file, * to make i/o more efficient. */ int istream_getline(struct istream *sp, char **linep, char delim) { unsigned char *x, *s; int r; s = sp->readp; *linep = (char*)s; for (;;) { /* check if we already have complete line in the buffer */ if (sp->readp < sp->endp && (x = memchr(sp->readp, delim, sp->endp - sp->readp))) /* yes we have, just return it */ return (sp->readp = x + 1) - s; sp->readp = sp->endp; /* check if the line is too long, and return it as well if it is */ if ((unsigned)(sp->endp - s) >= ISTREAM_BUFSIZE/2) return sp->endp - s; /* if we've a 'gap' at the beginning, close it */ if (s != sp->buf) { if (!(sp->endp - s)) { s = sp->readp = sp->endp = sp->buf; *linep = (char*)s; } else if (sp->endp > sp->buf + ISTREAM_BUFSIZE/2) { /* if too few bytes free */ memmove(sp->buf, s, sp->endp - s); sp->endp -= s - sp->buf; sp->readp = sp->endp; s = sp->buf; *linep = (char*)s; } } /* read the next chunk. read 2buf if at the beginning of buf */ r = sp->readfn(sp, sp->endp, ISTREAM_BUFSIZE - (sp->endp - sp->buf), sp->endp == sp->buf ? ISTREAM_BUFSIZE : ISTREAM_BUFSIZE/2); if (r <= 0) return r < 0 ? r : sp->readp - s; sp->endp += r; } } /* try to fill in a buffer if it contains less than BUFSIZE/2 bytes */ int istream_fillbuf(struct istream *sp) { if ((unsigned)(sp->endp - sp->readp) < ISTREAM_BUFSIZE/2) { int r; /* if we've a 'gap' at the beginning, close it */ if (sp->readp != sp->buf) { if (!(sp->endp - sp->readp)) sp->readp = sp->endp = sp->buf; else if (sp->endp > sp->buf + ISTREAM_BUFSIZE/2) { /* if too few bytes free */ memmove(sp->buf, sp->readp, sp->endp - sp->readp); sp->endp -= sp->readp - sp->buf; sp->readp = sp->buf; } } r = sp->readfn(sp, sp->endp, ISTREAM_BUFSIZE - (sp->endp - sp->buf), sp->endp == sp->buf ? ISTREAM_BUFSIZE : ISTREAM_BUFSIZE/2); if (r <= 0) return r; sp->endp += r; } return sp->endp - sp->readp; } /* Try to read at least nbytes (BUFSIZE/2 max) from the file. * return nbytes (>0) if ok, 0 if less than nbytes read (EOF), * or <0 on error. */ int istream_ensurebytes(struct istream *sp, int nbytes) { int r; if (nbytes > ISTREAM_BUFSIZE/2) nbytes = ISTREAM_BUFSIZE/2; while (sp->endp - sp->readp < nbytes) if ((r = istream_fillbuf(sp)) <= 0) return r; return nbytes; } static int istream_readfn(struct istream *sp, unsigned char *buf, int UNUSED size, int szhint) { return read((int)(long)sp->cookie, buf, szhint); } void istream_init(struct istream *sp, int (*readfn)(struct istream*,unsigned char*,int,int), void (*freefn)(struct istream*), void *cookie) { sp->cookie = cookie; sp->readfn = readfn; sp->freefn = freefn; sp->readp = sp->endp = sp->buf; } void istream_destroy(struct istream *sp) { if (sp->freefn) sp->freefn(sp); memset(sp, 0, sizeof(*sp)); } void istream_init_fd(struct istream *sp, int fd) { istream_init(sp, istream_readfn, NULL, (void*)(long)fd); } /* check for gzip magic (2 bytes) */ int istream_compressed(struct istream *sp) { if (istream_ensurebytes(sp, 2) <= 0) return 0; if (sp->readp[0] != 0x1f || sp->readp[1] != 0x8b) return 0; return 1; } #ifndef NO_ZLIB struct zistream { struct istream is; z_stream zs; unsigned crc32; unsigned bytes; }; #define HEAD_CRC 0x02 /* bit 1 set: header CRC present */ #define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ #define ORIG_NAME 0x08 /* bit 3 set: original file name present */ #define COMMENT 0x10 /* bit 4 set: file comment present */ #define RESERVED 0xE0 /* bits 5..7: reserved */ /* always return end-of-file */ static int istream_eof(struct istream UNUSED *sp, unsigned char UNUSED *buf, int UNUSED size, int UNUSED szhint) { return 0; } static int zistream_readfn(struct istream *sp, unsigned char *buf, int size, int UNUSED szhint) { struct zistream *zsp = sp->cookie; int r = Z_OK; unsigned char *p; zsp->zs.next_out = buf; zsp->zs.avail_out = size; while(r == Z_OK && zsp->zs.avail_out != 0) { if (zsp->is.readp == zsp->is.endp) { r = istream_fillbuf(&zsp->is); if (r <= 0) { if (size != (int)zsp->zs.avail_out) r = Z_OK; else { if (!r) errno = EPROTO; r = Z_ERRNO; } break; } } zsp->zs.next_in = zsp->is.readp; zsp->zs.avail_in = zsp->is.endp - zsp->is.readp; r = inflate(&zsp->zs, Z_NO_FLUSH); zsp->is.readp = zsp->zs.next_in; } size -= zsp->zs.avail_out; zsp->bytes += size; zsp->crc32 = crc32(zsp->crc32, buf, size); switch(r) { case Z_STREAM_END: break; case Z_OK: return size; case Z_MEM_ERROR: errno = ENOMEM; return -1; default: errno = EPROTO; case Z_ERRNO: return -1; } inflateEnd(&zsp->zs); sp->readfn = istream_eof; r = istream_ensurebytes(&zsp->is, 8); /* 8 bytes trailer */ if (r <= 0) { if (!r) errno = EPROTO; return -1; } p = zsp->is.readp; zsp->is.readp += 8; if ((((unsigned)p[0] << 0) | ((unsigned)p[1] << 8) | ((unsigned)p[2] << 16) | ((unsigned)p[3] << 24)) != zsp->crc32 || (((unsigned)p[4] << 0) | ((unsigned)p[5] << 8) | ((unsigned)p[6] << 16) | ((unsigned)p[7] << 24)) != (zsp->bytes & 0xffffffffu)) { errno = EPROTO; return -1; } return size; } static void zistream_freefn(struct istream *sp) { struct zistream *zsp = sp->cookie; inflateEnd(&zsp->zs); istream_destroy(&zsp->is); free(zsp); } int istream_uncompress_setup(struct istream *sp) { int r, x, flags; struct zistream *zsp; if (!istream_compressed(sp)) return 0; sp->readp += 2; errno = EPROTO; if (istream_ensurebytes(sp, 8) <= 0) return -1; if (sp->readp[0] != Z_DEFLATED || (flags = sp->readp[1]) & RESERVED) return -1; sp->readp += 8; if (flags & EXTRA_FIELD) { if (istream_ensurebytes(sp, 2) <= 0) return -1; x = sp->readp[0] | ((unsigned)sp->readp[1] << 8); sp->readp += 2; while(sp->endp - sp->readp < x) { x -= sp->endp - sp->readp; sp->readp = sp->endp; if (istream_fillbuf(sp) <= 0) return -1; } sp->readp += x; } x = ((flags & ORIG_NAME) ? 1 : 0) + ((flags & COMMENT) ? 1 : 0); while(x--) { char *p; do if ((r = istream_getline(sp, &p, '\0')) <= 0) return -1; while (p[r-1] != '\0'); } if (flags & HEAD_CRC) { if (istream_ensurebytes(sp, 2) <= 0) return -1; sp->readp += 2; } zsp = malloc(sizeof(*zsp)); if (!zsp) { errno = ENOMEM; return -1; } zsp->is.cookie = sp->cookie; zsp->is.readfn = sp->readfn; zsp->is.freefn = sp->freefn; x = sp->endp - sp->readp; memcpy(zsp->is.buf, sp->readp, x); zsp->is.readp = zsp->is.buf; zsp->is.endp = zsp->is.buf + x; zsp->zs.zalloc = NULL; zsp->zs.zfree = NULL; zsp->zs.opaque = NULL; zsp->bytes = 0; zsp->crc32 = crc32(0, NULL, 0); zsp->zs.next_in = zsp->is.readp; zsp->zs.avail_in = x; r = inflateInit2(&zsp->zs, -MAX_WBITS); switch(r) { case Z_OK: zsp->is.readp = zsp->zs.next_in; istream_init(sp, zistream_readfn, zistream_freefn, zsp); return 1; case Z_MEM_ERROR: errno = ENOMEM; break; case Z_ERRNO: break; default: errno = EPROTO; break; } inflateEnd(&zsp->zs); free(zsp); return -1; } #else /* !ZLIB */ int istream_uncompress_setup(struct istream *sp) { if (!istream_compressed(sp)) return 0; errno = ENOSYS; return -1; } #endif #ifdef TEST #include int main() { struct istream is; char *l; int r; istream_init_fd(&is, 0); if (zistream_setup(&is) < 0) { perror("zistream_setup"); return 1; } while((r = istream_getline(&is, &l, '\n')) > 0) { #if 0 printf("%d ", r); fwrite(l, r, 1, stdout); if (l[r-1] != '\n') printf(" (incomplete)\n"); #endif #if 0 write(1, l, r); #endif #if 1 fwrite(l, r, 1, stdout); #endif } if (r < 0) perror("read"); return 0; } #endif rbldnsd-0.997a/btrie.c0000664000175000017500000024057112137237054012771 0ustar mjtmjt/* Level-Compressed Tree Bitmap (LC-TBM) Trie implementation * * Contributed by Geoffrey T. Dairiki * * This file is released under a "Three-clause BSD License". * * Copyright (c) 2013, Geoffrey T. Dairiki * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * * Neither the name of Geoffrey T. Dairiki nor the names of other * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GEOFFREY * T. DAIRIKI BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ /***************************************************************** * * This code implements a routing table conceptually based on a binary * trie structure. Internally, the trie is represented by two types * of compound nodes: "multibit nodes", which contain the top few * levels of an entire binary subtree; and "level compression" (LC) * nodes which represent a (potentially long) chain of out-degree one * (single child) binary nodes (possibly ending at a terminal node). * * The multibit nodes are represented using a "Tree Bitmap" structure * (more on this below), which is very efficient --- both in terms of * memory usage and lookup speed --- at representing densely branching * parts of the trie. The LC nodes can efficiently represent long * non-branching chains of binary trie nodes. Using both node types * together results in efficient representation of both the sparse and * dense parts of a binary trie. * * Graphically, here's the rough idea: * * ........ * .LC o . * . / . LC nodes can * . o . <= represent long chains * . \ . of (non-branching) binary * . o . trie nodes * . / . * . o . * ......../..... * .TBM o . * . / \ . TBM nodes can represent * . o * . <= several levels of densely * . / \ . branching binary trie nodes * . o o . * ......./.....\....... * .TBM o .. o LC. * . / \ .. \ . * . o o .. o . * . / / \ .. \ . * . * o *.. o . * ...../....... / . * . o LC. . o . * . \ . .....\...... * . * . . o TBM. * ........ . / \ . * . o o . * . / \ \ . * .* * *. * ........... * * Terminology * ----------- * * node * Usually, in the comments below, "node" will be used to refer to * a compound node: either a multibit (TBM) node or an LC node. * * "internal node" or "prefix" * The terms "prefix" or "internal node" are used to refer to * a node in the binary trie which is internal to a multibit (TBM) * node. * * ---------------------------------------------------------------- * * Internal Representation of the Nodes * ==================================== * * Multibit (TBM) Nodes * ~~~~~~~~~~~~~~~~~~~~ * * The multibit nodes are represented using a "Tree Bitmap" (TBM) * structure as described by Eatherton, Dittia and Varghese[1]. See * the paper referenced below for basic details. * * A multibit node, represents several levels of a binary trie. * For example, here is a multibit node of stride 2 (which represent * two levels of a binary trie. * * +------- | ------+ * | multi o | * | bit / \ | * | node / \ | * | o * | * +--- / \ - / \ --+ * O * * Note that, for a multibit node of stride S, there are 2^S - 1 internal * nodes, each of which may have data (or not) associated with them, and * 2^S "external paths" leading to other (possibly compound nodes). * (In the diagram above, one of three internal node (the one denoted by "*") * has data, and one of four extending paths leads to an external node * (denoted by the 'O').) * * The TBM structure can represent these bitmaps in a very memory-efficient * manner. * * Each TBM node consists of two bitmaps --- the "internal bitmap" and the * "extending paths bitmap" --- and a pointer which points to an array * which contains both the extending path ("child") nodes and any * internal prefix data for the TBM node. * * +--------+--------+ * TBM | ext bm | int bm | * Node +--------+--------+ * | pointer |----+ * +-----------------+ | * | * | * +-----------------+ | * | extending path | | * | node[N-1] | | * +-----------------+ | * / ... / | * / ... / | * +-----------------+ | * | extending path | | * | node[0] | | * +-----------------+<---+ * | int. data[M-1] | * +-----------------+ * / ... / * +-----------------+ * | int. data[0] | * +-----------------+ * * The extending paths bitmap (or "ext bitmap") has one bit for each * possible "extending path" from the bottom of the multibit node. To * check if a particular extending path is present, one checks to see if * the corresponding bit is set in the ext bitmap. The index into the * array of children for that path can be found by counting the number * of set bits to the left of that bit. * * Similary, the internal bitmap has one bit for each binary node * which is internal to the multibit node. To determine whether there * is data stored for an internal prefix, one checks the corresponding * bit in the internal bitmap. As for extending paths, the index into * the array of internal data is found by counting the number of set * bits to the left of that bit. * * To save space in the node structure, the node data array is stored * contiguously with the node extending path array. The single * ("children") pointer in the TBM structure points to the beginning * of the array of extending path nodes and to (one past) the end of * the the internal data array. * * The multibit stride is chosen so that the entire TBM node structure fits * in the space of two pointers. On 32 bit machines this means the stride * is four (each of the two bitmaps is 16 bits); on 32 bit machines the * stride is five. * * Note that there are only 2^stride - 1 internal prefixes in a TBM * node. That means there is one unused bit in the internal bitmap. * We require that that bit must always be clear for a TBM node. (If * set, it indicates that the structure represents, instead, an LC * node. See below.) * * ---------------------------------------------------------------- * * Level Compression (LC) Nodes * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * LC nodes are used to represent a chain of out-degree-one (single * child) prefixes in the binary trie. The are represented by a bit * string (the "relative prefix") along with its length and a pointer * to the extending path (the next node past the LC node.) * * * Non-Terminal LC Node: * * +------------------+-------+ * | relative prefix |1|0|len| * +------------------+-------+ * | ptr.child |--+ * +--------------------------+ | * | * | * +--------------------------+ | * | Next node - | | * | either LC or TBM | | * | | | * +--------------------------+<-+ * * The Relative Prefix * ------------------- * * The maximum relative prefix per LC node is selected so that (again) * the entire node structure fits in the space of two pointers. On 32 bit * machines, the maximum relative prefix is 24 bits; on 62 bit machines * the limit is 56 bits. * * In the LC node structure, the relative prefix is stored as an array * of bytes. To avoid some bit-shifting during tree searches, these * bytes are byte-aligned with the global prefix. In other words, in * general there are (pos % 8) "pad" bits at the beginning of the * relative prefix --- where pos "starting bit" (or depth in the * binary tree) of the LC node --- which really belong to the parent * node(s) of the LC node. For efficiency (so that we don't have to * mask them out when matching) we require that these pad bits be * correct --- they must match the path which leads to the LC node. * * The relative prefix length stored in the LC node structure does not * count the pad bits. * * Terminal Node Compression * ------------------------- * * For memory efficiency, we also support "terminal LC" nodes. When * the extension path from an LC node consists a single terminal node, * we store that terminal nodes data directly in the parent LC node. * * Instead of this: * * +------------------+-------+ * | relative prefix |1|0|len| * +------------------+-------+ * | ptr.child |--+ * +--------------------------+ | * | * +--------------------------+ | * | Terminal Node (TBM node, | | * | empty except for the | | * +--| root internal node.) | | * | +--------------------------+<-+ * | * +->+--------------------------+ * | terminal node data | * +--------------------------+ * * We can do this: * * +------------------+-------+ * | relative prefix |1|1|len| * +------------------+-------+ * | terminal node data | * +--------------------------+ * * Terminal LC nodes are differentiated from non-terminal LC nodes * by the setting of the is_terminal flag. * * Node Structure Packing Details * ------------------------------ * * The LC and TBM node structures are carefully packed so that the * "is_lc" flag (which indicates that a node is an LC node) * corresponds to the one unused bit in the internal bitmap of the TBM * node structure (which we require to be zero for TBM nodes). * * ---------------------------------------------------------------- * * References * ========== * * [1] Will Eatherton, George Varghese, and Zubin Dittia. 2004. Tree * bitmap: hardware/software IP lookups with incremental * updates. SIGCOMM Comput. Commun. Rev. 34, 2 (April 2004), * 97-122. DOI=10.1145/997150.997160 * http://doi.acm.org/10.1145/997150.997160 * http://comnet.kaist.ac.kr/yhlee/CN_2008_Spring/readings/Eath-04-tree_bitmap.pdf * ****************************************************************/ #include #include #include #include #if defined(TEST) && defined(NDEBUG) # warning undefining NDEBUG for TEST build # undef NDEBUG #endif #include #include "btrie.h" #include "mempool.h" #if __SIZEOF_POINTER__ == 4 # define TBM_STRIDE 4 #elif __SIZEOF_POINTER__ == 8 # define TBM_STRIDE 5 #else # error "Unsupported word size" #endif #ifndef NO_STDINT_H # if TBM_STRIDE == 4 typedef uint16_t tbm_bitmap_t; # else typedef uint32_t tbm_bitmap_t; # endif #else /* NO_STDINT_H */ # if TBM_STRIDE == 4 # if SIZEOF_SHORT == 2 typedef short unsigned tbm_bitmap_t; # else # error "can not determine type for 16 bit unsigned int" # endif # else /* TBM_STRIDE == 5 */ # if SIZEOF_INT == 4 typedef unsigned tbm_bitmap_t; # elif SIZEOF_LONG == 4 typedef long unsigned tbm_bitmap_t; # else # error "can not determine type for 32 bit unsigned int" # endif # endif #endif #define TBM_FANOUT (1U << TBM_STRIDE) #define LC_BYTES_PER_NODE (__SIZEOF_POINTER__ - 1) typedef union node_u node_t; /* The tbm_node and lc_node structs must be packed so that the the * high bit (LC_FLAGS_IS_LC) of lc_flags in the the lc_node struct * coincides with bit zero (the most significant bit) of tbm_node's * int_bm. (This bit is how we differentiate between the two node * types. It is always clear for a tbm_node and always set for an * lc_node.) */ struct tbm_node { #ifdef WORDS_BIGENDIAN tbm_bitmap_t int_bm; /* the internal bitmap */ tbm_bitmap_t ext_bm; /* extending path ("external") bitmap */ #else tbm_bitmap_t ext_bm; /* extending path ("external") bitmap */ tbm_bitmap_t int_bm; /* the internal bitmap */ #endif union { node_t *children; /* pointer to array of children */ const void **data_end; /* one past end of internal prefix data array */ } ptr; }; struct lc_node { /* lc_flags contains the LC prefix length and a couple of bit flags * (apparently char-sized bit fields are a gcc extension) */ # define LC_FLAGS_IS_LC 0x80 # define LC_FLAGS_IS_TERMINAL 0x40 # define LC_FLAGS_LEN_MASK 0x3f #ifdef WORDS_BIGENDIAN btrie_oct_t lc_flags; btrie_oct_t prefix[LC_BYTES_PER_NODE]; #else btrie_oct_t prefix[LC_BYTES_PER_NODE]; btrie_oct_t lc_flags; #endif union { node_t *child; /* pointer to child (if !is_terminal) */ const void *data; /* the prefix data (if is_terminal) */ } ptr; }; union node_u { struct tbm_node tbm_node; struct lc_node lc_node; }; struct free_hunk { struct free_hunk *next; }; #define MAX_CHILD_ARRAY_LEN (TBM_FANOUT + TBM_FANOUT / 2) struct btrie { node_t root; struct mempool *mp; struct free_hunk *free_list[MAX_CHILD_ARRAY_LEN]; jmp_buf exception; /* mem mgmt stats */ size_t alloc_total; /* total bytes allocated from mempool */ size_t alloc_data; /* bytes allocated for TBM node int. prefix data */ size_t alloc_waste; /* bytes wasted by rounding of data array size */ #ifdef BTRIE_DEBUG_ALLOC size_t alloc_hist[MAX_CHILD_ARRAY_LEN * 2]; /* histogram of alloc sizes */ #endif /* trie stats */ size_t n_entries; /* number of entries */ size_t n_tbm_nodes; /* total number of TBM nodes in tree */ size_t n_lc_nodes; /* total number of LC nodes in tree */ }; /**************************************************************** * * Memory management * * We will need to frequently resize child/data arrays. The current * mempool implementation does not support resizing/freeing, so here * we roll our own. */ static inline void _free_hunk(struct btrie *btrie, void *buf, unsigned n_nodes) { struct free_hunk *hunk = buf; hunk->next = btrie->free_list[n_nodes - 1]; btrie->free_list[n_nodes - 1] = hunk; } static inline void * _get_hunk(struct btrie *btrie, unsigned n_nodes) { struct free_hunk *hunk = btrie->free_list[n_nodes - 1]; if (hunk != NULL) btrie->free_list[n_nodes - 1] = hunk->next; return hunk; } /* Get pointer to uninitialized child/data array. * * Allocates memory for an array of NDATA (void *)s followed by an * array of NCHILDREN (node_t)s. The returned pointer points to to * beginning of the children array (i.e. it points to (one past) the * end of the data array.) */ static node_t * alloc_nodes(struct btrie *btrie, unsigned nchildren, unsigned ndata) { size_t n_nodes = nchildren + (ndata + 1) / 2; node_t *hunk; assert(n_nodes > 0 && n_nodes <= MAX_CHILD_ARRAY_LEN); hunk = _get_hunk(btrie, n_nodes); if (hunk == NULL) { /* Do not have free hunk of exactly the requested size, look for a * larger hunk. (The funny order in which we scan the buckets is * heuristically selected in an attempt to minimize unnecessary * creation of small fragments) */ size_t n, skip = n_nodes > 4 ? 4 : n_nodes; for (n = n_nodes + skip; n <= MAX_CHILD_ARRAY_LEN; n++) { if ((hunk = _get_hunk(btrie, n)) != NULL) { _free_hunk(btrie, hunk + n_nodes, n - n_nodes); goto DONE; } } for (n = n_nodes + 1; n < n_nodes + skip && n <= MAX_CHILD_ARRAY_LEN; n++) { if ((hunk = _get_hunk(btrie, n)) != NULL) { _free_hunk(btrie, hunk + n_nodes, n - n_nodes); goto DONE; } } /* failed to find free hunk, allocate a fresh one */ hunk = mp_alloc(btrie->mp, n_nodes * sizeof(node_t), 1); if (hunk == NULL) longjmp(btrie->exception, BTRIE_ALLOC_FAILED); btrie->alloc_total += n_nodes * sizeof(node_t); } DONE: btrie->alloc_data += ndata * sizeof(void *); btrie->alloc_waste += (ndata % 2) * sizeof(void *); #ifdef BTRIE_DEBUG_ALLOC btrie->alloc_hist[2 * nchildren + ndata]++; #endif /* adjust pointer to allow room for data array before child array */ return hunk + (ndata + 1) / 2; } /* Free memory allocated by alloc_nodes */ static void free_nodes(struct btrie *btrie, node_t *buf, unsigned nchildren, unsigned ndata) { size_t n_nodes = nchildren + (ndata + 1) / 2; assert(n_nodes > 0 && n_nodes <= MAX_CHILD_ARRAY_LEN); _free_hunk(btrie, buf - (ndata + 1) / 2, n_nodes); btrie->alloc_data -= ndata * sizeof(void *); btrie->alloc_waste -= (ndata % 2) * sizeof(void *); #ifdef BTRIE_DEBUG_ALLOC btrie->alloc_hist[2 * nchildren + ndata]--; #endif } /* Debugging/development only: */ #ifdef BTRIE_DEBUG_ALLOC static void dump_alloc_hist(const struct btrie *btrie) { unsigned bin; size_t total_alloc = 0; size_t total_free = 0; size_t total_bytes = 0; size_t total_waste = 0; size_t total_free_bytes = 0; puts("hunk alloc free alloc wasted free"); puts("size hunks hunks bytes bytes bytes"); puts("==== ====== ====== ======== ======== ========"); for (bin = 1; bin < 2 * MAX_CHILD_ARRAY_LEN; bin++) { size_t n_alloc = btrie->alloc_hist[bin]; size_t bytes = n_alloc * bin * sizeof(void *); size_t waste_bytes = (bin % 2) * n_alloc * sizeof(void *); size_t n_free = 0, free_bytes; if (bin % 2 == 0) { const struct free_hunk *hunk; for (hunk = btrie->free_list[bin / 2 - 1]; hunk; hunk = hunk->next) n_free++; } free_bytes = n_free * bin * sizeof(void *); printf("%3zu: %6zu %6zu %8zu %8zu %8zu\n", bin * sizeof(void *), n_alloc, n_free, bytes, waste_bytes, free_bytes); total_alloc += n_alloc; total_free += n_free; total_bytes += bytes; total_waste += waste_bytes; total_free_bytes += free_bytes; } puts("---- ------ ------ -------- -------- --------"); printf("SUM: %6zu %6zu %8zu %8zu %8zu\n", total_alloc, total_free, total_bytes, total_waste, total_free_bytes); } #endif /**************************************************************** * * Bit twiddling * */ static inline tbm_bitmap_t bit(unsigned b) { return 1U << ((1 << TBM_STRIDE) - 1 - b); } /* count the number of set bits in bitmap * * algorithm from * http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel */ static inline unsigned count_bits(tbm_bitmap_t v) { /* Count set bits in parallel. */ /* v = (v & 0x5555...) + ((v >> 1) & 0x5555...); */ v -= (v >> 1) & (tbm_bitmap_t)~0UL/3; /* v = (v & 0x3333...) + ((v >> 2) & 0x3333...); */ v = (v & (tbm_bitmap_t)~0UL/5) + ((v >> 2) & (tbm_bitmap_t)~0UL/5); /* v = (v & 0x0f0f...) + ((v >> 4) & 0x0f0f...); */ v = (v + (v >> 4)) & (tbm_bitmap_t)~0UL/17; /* v = v % 255; */ #if TBM_STRIDE == 4 /* tbm_bitmap_t is uint16_t, avoid the multiply */ return (v + (v >> 8)) & 0x0ff; #else return (v * (tbm_bitmap_t)(~0UL/255)) >> ((sizeof(tbm_bitmap_t) - 1) * 8); #endif } static inline unsigned count_bits_before(tbm_bitmap_t bm, int b) { return b ? count_bits(bm >> ((1 << TBM_STRIDE) - b)) : 0; } static inline unsigned count_bits_from(tbm_bitmap_t bm, int b) { return count_bits(bm << b); } /* extracts a few bits from bitstring, returning them as an integer */ static inline btrie_oct_t extract_bits(const btrie_oct_t *prefix, unsigned pos, unsigned nbits) { if (nbits == 0) return 0; else { unsigned v = (prefix[pos / 8] << 8) + prefix[pos / 8 + 1]; return (v >> (16 - nbits - pos % 8)) & ((1U << nbits) - 1); } } static inline unsigned extract_bit(const btrie_oct_t *prefix, int pos) { return (prefix[pos / 8] >> (7 - pos % 8)) & 0x01; } /* get mask for high n bits of a byte */ static inline btrie_oct_t high_bits(unsigned n) { return (btrie_oct_t) -(1U << (8 - n)); } /* determine whether two prefixes are equal */ static inline int prefixes_equal(const btrie_oct_t *pfx1, const btrie_oct_t *pfx2, unsigned len) { return (memcmp(pfx1, pfx2, len / 8) == 0 && ((pfx1[len / 8] ^ pfx2[len / 8]) & high_bits(len % 8)) == 0); } /* determine length of longest common subprefix */ static inline unsigned common_prefix(const btrie_oct_t *pfx1, const btrie_oct_t *pfx2, unsigned len) { /* algorithm adapted from * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup */ static btrie_oct_t leading_zeros[] = { 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; unsigned nb; for (nb = 0; nb < len / 8; nb++) { unsigned diff = *pfx1++ ^ *pfx2++; if (diff != 0) return 8 * nb + leading_zeros[diff]; } if (len % 8) { unsigned n = leading_zeros[*pfx1 ^ *pfx2]; if (n < len % 8) return 8 * nb + n; } return len; } /**************************************************************** */ static inline int is_empty_node(const node_t *node) { return node->tbm_node.ext_bm == 0 && node->tbm_node.int_bm == 0; } static inline int is_lc_node(const node_t *node) { return (node->lc_node.lc_flags & LC_FLAGS_IS_LC) != 0; } static inline int is_tbm_node(const node_t *node) { return !is_lc_node(node); } /* is node a TBM node with internal data? */ static inline int has_data(const node_t *node) { return is_tbm_node(node) && node->tbm_node.int_bm != 0; } static inline unsigned base_index(unsigned pfx, unsigned plen) { assert(plen < TBM_STRIDE); assert(pfx < (1U << plen)); return pfx | (1U << plen); } /* initialize node to an empty TBM node */ static inline void init_empty_node(struct btrie *btrie, node_t *node) { memset(node, 0, sizeof(*node)); btrie->n_tbm_nodes++; } /* get pointer to TBM internal prefix data */ static inline const void ** tbm_data_p(const struct tbm_node *node, unsigned pfx, unsigned plen) { unsigned bi = base_index(pfx, plen); if ((node->int_bm & bit(bi)) == 0) return NULL; /* no data */ else { return &node->ptr.data_end[ -(int)count_bits_from(node->int_bm, bi) ]; } } /* add an element to the internal data array */ static void tbm_insert_data(struct btrie *btrie, struct tbm_node *node, unsigned pfx, unsigned plen, const void *data) { /* XXX: don't realloc if already big enough? */ unsigned bi = base_index(pfx, plen); unsigned nchildren = count_bits(node->ext_bm); int ndata = count_bits(node->int_bm); unsigned di = count_bits_before(node->int_bm, bi); node_t *old_children = node->ptr.children; const void **old_data_beg = node->ptr.data_end - ndata; const void **data_beg; assert((node->int_bm & bit(bi)) == 0); node->ptr.children = alloc_nodes(btrie, nchildren, ndata + 1); data_beg = node->ptr.data_end - (ndata + 1); data_beg[di] = data; node->int_bm |= bit(bi); if (nchildren != 0 || ndata != 0) { memcpy(data_beg, old_data_beg, di * sizeof(data_beg[0])); memcpy(&data_beg[di + 1], &old_data_beg[di], (ndata - di) * sizeof(data_beg[0]) + nchildren * sizeof(node_t)); free_nodes(btrie, old_children, nchildren, ndata); } } /* determine whether TBM has internal prefix data for pfx/plen or ancestors */ static inline int has_internal_data(const struct tbm_node *node, unsigned pfx, unsigned plen) { # define BIT(n) (1U << ((1 << TBM_STRIDE) - 1 - (n))) # define B0() BIT(1) /* the bit for 0/0 */ # define B1(n) (BIT((n) + 2) | B0()) /* the bits for n/1 and its ancestors */ # define B2(n) (BIT((n) + 4) | B1(n >> 1)) /* the bits for n/2 and ancestors */ # define B3(n) (BIT((n) + 8) | B2(n >> 1)) /* the bits for n/3 and ancestors */ # define B4(n) (BIT((n) + 16) | B3(n >> 1)) /* the bits for n/4 and ancestors */ static tbm_bitmap_t ancestors[] = { 0, B0(), B1(0), B1(1), B2(0), B2(1), B2(2), B2(3), B3(0), B3(1), B3(2), B3(3), B3(4), B3(5), B3(6), B3(7), # if TBM_STRIDE == 5 B4(0), B4(1), B4(2), B4(3), B4(4), B4(5), B4(6), B4(7), B4(8), B4(9), B4(10), B4(11), B4(12), B4(13), B4(14), B4(15), # elif TBM_STRIDE != 4 # error "unsupported TBM_STRIDE" # endif }; # undef B4 # undef B3 # undef B2 # undef B1 # undef B0 # undef BIT return (node->int_bm & ancestors[base_index(pfx, plen)]) != 0; } /* get pointer to TBM extending path */ static inline node_t * tbm_ext_path(const struct tbm_node *node, unsigned pfx) { if ((node->ext_bm & bit(pfx)) == 0) return NULL; else return &node->ptr.children[count_bits_before(node->ext_bm, pfx)]; } /* resize TBM node child array to make space for new child node */ static node_t * tbm_insert_ext_path(struct btrie *btrie, struct tbm_node *node, unsigned pfx) { unsigned nchildren = count_bits(node->ext_bm); unsigned ci = count_bits_before(node->ext_bm, pfx); int ndata = count_bits(node->int_bm); node_t *old_children = node->ptr.children; const void **old_data_beg = node->ptr.data_end - ndata; assert ((node->ext_bm & bit(pfx)) == 0); node->ptr.children = alloc_nodes(btrie, nchildren + 1, ndata); init_empty_node(btrie, &node->ptr.children[ci]); node->ext_bm |= bit(pfx); if (nchildren != 0 || ndata != 0) { const void **data_beg = node->ptr.data_end - ndata; memcpy(data_beg, old_data_beg, ndata * sizeof(data_beg[0]) + ci * sizeof(node_t)); memcpy(&node->ptr.children[ci + 1], &old_children[ci], (nchildren - ci) * sizeof(old_children[0])); free_nodes(btrie, old_children, nchildren, ndata); } return &node->ptr.children[ci]; } static inline int lc_is_terminal(const struct lc_node *node) { return (node->lc_flags & LC_FLAGS_IS_TERMINAL) != 0; } static inline unsigned lc_len(const struct lc_node *node) { return node->lc_flags & LC_FLAGS_LEN_MASK; } static inline void lc_init_flags(struct lc_node *node, int is_terminal, unsigned len) { assert((len & ~LC_FLAGS_LEN_MASK) == 0); node->lc_flags = LC_FLAGS_IS_LC | len; if (is_terminal) node->lc_flags |= LC_FLAGS_IS_TERMINAL; } static inline void lc_add_to_len(struct lc_node *node, int increment) { unsigned new_len = lc_len(node) + increment; assert((new_len & ~LC_FLAGS_LEN_MASK) == 0); node->lc_flags = (node->lc_flags & ~LC_FLAGS_LEN_MASK) | new_len; } static inline unsigned lc_shift(unsigned pos) { return pos / 8; } static inline unsigned lc_base(unsigned pos) { return 8 * lc_shift(pos); } static inline unsigned lc_bits(const struct lc_node *node, unsigned pos) { return pos % 8 + lc_len(node); } static inline unsigned lc_bytes(const struct lc_node *node, unsigned pos) { return (lc_bits(node, pos) + 7) / 8; } static inline unsigned lc_leading_bits(const struct lc_node *node, unsigned pos, unsigned nbits) { return extract_bits(node->prefix, pos % 8, nbits); } /* Initialize a new terminal LC node * * If prefix is too long to fit in a single LC node, then a chain * of LC nodes will be created. */ static void init_terminal_node(struct btrie *btrie, node_t *dst, unsigned pos, const btrie_oct_t *prefix, unsigned len, const void *data) { struct lc_node *node = &dst->lc_node; unsigned nbytes = (len + 7) / 8; while (nbytes - lc_shift(pos) > LC_BYTES_PER_NODE) { memcpy(node->prefix, prefix + lc_shift(pos), LC_BYTES_PER_NODE); lc_init_flags(node, 0, 8 * LC_BYTES_PER_NODE - pos % 8); node->ptr.child = alloc_nodes(btrie, 1, 0); pos += lc_len(node); node = &node->ptr.child->lc_node; btrie->n_lc_nodes++; } memcpy(node->prefix, prefix + lc_shift(pos), nbytes - lc_shift(pos)); lc_init_flags(node, 1, len - pos); node->ptr.data = data; btrie->n_lc_nodes++; } /* merge chains of multiple LC nodes into a single LC node, if possible. * * also ensure that the leading nodes in the LC chain have maximum length. */ static void coalesce_lc_node(struct btrie *btrie, struct lc_node *node, unsigned pos) { while (! lc_is_terminal(node) && lc_bits(node, pos) < 8 * LC_BYTES_PER_NODE && is_lc_node(node->ptr.child)) { struct lc_node *child = &node->ptr.child->lc_node; unsigned spare_bits = 8 * LC_BYTES_PER_NODE - lc_bits(node, pos); unsigned end = pos + lc_len(node); unsigned shift = lc_shift(end) - lc_shift(pos); if (lc_len(child) <= spare_bits) { /* node plus child will fit in single node - merge */ memcpy(node->prefix + shift, child->prefix, lc_bytes(child, end)); lc_init_flags(node, lc_is_terminal(child), lc_len(node) + lc_len(child)); node->ptr = child->ptr; free_nodes(btrie, (node_t *)child, 1, 0); btrie->n_lc_nodes--; } else { /* can't merge, but can take some of childs bits */ unsigned cshift = lc_shift(end + spare_bits) - lc_shift(end); memcpy(node->prefix + shift, child->prefix, LC_BYTES_PER_NODE - shift); lc_add_to_len(node, spare_bits); if (cshift) memmove(child->prefix, child->prefix + cshift, lc_bytes(child, end) - cshift); assert(lc_len(child) > spare_bits); lc_add_to_len(child, -spare_bits); pos += lc_len(node); node = child; } } } static void init_tbm_node(struct btrie *btrie, node_t *node, unsigned pos, const btrie_oct_t pbyte, const void **root_data_p, node_t *left, node_t *right); /* given an LC node at orig_pos, create a new (shorter) node at pos */ static void shorten_lc_node(struct btrie *btrie, node_t *dst, unsigned pos, struct lc_node *src, unsigned orig_pos) { assert(orig_pos < pos); assert(lc_len(src) >= pos - orig_pos); assert(dst != (node_t *)src); if (lc_len(src) == pos - orig_pos && !lc_is_terminal(src)) { /* just steal the child */ node_t *child = src->ptr.child; *dst = *child; free_nodes(btrie, child, 1, 0); btrie->n_lc_nodes--; } else { struct lc_node *node = &dst->lc_node; unsigned shift = lc_shift(pos) - lc_shift(orig_pos); if (shift) { memmove(node->prefix, src->prefix + shift, lc_bytes(src, orig_pos) - shift); node->lc_flags = src->lc_flags; node->ptr = src->ptr; } else { *node = *src; } lc_add_to_len(node, -(pos - orig_pos)); coalesce_lc_node(btrie, node, pos); } } /* convert LC node to non-terminal LC node of length len *in place* * * on entry, node must have length at least len */ static void split_lc_node(struct btrie *btrie, struct lc_node *node, unsigned pos, unsigned len) { node_t *child = alloc_nodes(btrie, 1, 0); assert(lc_len(node) >= len); shorten_lc_node(btrie, child, pos + len, node, pos); lc_init_flags(node, 0, len); node->ptr.child = child; btrie->n_lc_nodes++; } /* convert non-terminal LC node of length one to a TBM node *in place* */ static void convert_lc_node_1(struct btrie *btrie, struct lc_node *node, unsigned pos) { btrie_oct_t pbyte = node->prefix[0]; node_t *child = node->ptr.child; node_t *left, *right; assert(lc_len(node) == 1); assert(!lc_is_terminal(node)); if (extract_bit(node->prefix, pos % 8)) left = NULL, right = child; else left = child, right = NULL; init_tbm_node(btrie, (node_t *)node, pos, pbyte, NULL, left, right); free_nodes(btrie, child, 1, 0); btrie->n_lc_nodes--; } /* convert an LC node to TBM node *in place* */ static void convert_lc_node(struct btrie *btrie, struct lc_node *node, unsigned pos) { unsigned len = lc_len(node); if (len >= TBM_STRIDE) { unsigned pfx = lc_leading_bits(node, pos, TBM_STRIDE); struct tbm_node *result = (struct tbm_node *)node; /* split to LC of len TBM_STRIDE followed by child (extending path) */ split_lc_node(btrie, node, pos, TBM_STRIDE); /* then convert leading LC node to TBM node */ result->int_bm = 0; result->ext_bm = bit(pfx); btrie->n_lc_nodes--; btrie->n_tbm_nodes++; } else if (lc_is_terminal(node)) { /* convert short terminal LC to TBM (with internal data) */ unsigned pfx = lc_leading_bits(node, pos, len); const void *data = node->ptr.data; node_t *result = (node_t *)node; init_empty_node(btrie, result); tbm_insert_data(btrie, &result->tbm_node, pfx, len, data); btrie->n_lc_nodes--; } else { assert(len > 0); for (; len > 1; len--) { split_lc_node(btrie, node, pos, len - 1); convert_lc_node_1(btrie, &node->ptr.child->lc_node, pos + len - 1); } convert_lc_node_1(btrie, node, pos); } } static void insert_lc_node(struct btrie *btrie, node_t *dst, unsigned pos, btrie_oct_t pbyte, unsigned last_bit, node_t *tail) { struct lc_node *node = &dst->lc_node; btrie_oct_t mask = 1 << (7 - (pos % 8)); btrie_oct_t bit = last_bit ? mask : 0; if (mask != 0x01 && is_lc_node(tail)) { /* optimization: LC tail has room for the extra bit (without shifting) */ assert((tail->lc_node.prefix[0] & mask) == bit); *node = tail->lc_node; lc_add_to_len(node, 1); return; } /* add new leading LC node of len 1 */ node->prefix[0] = pbyte | bit; lc_init_flags(node, 0, 1); node->ptr.child = alloc_nodes(btrie, 1, 0); node->ptr.child[0] = *tail; btrie->n_lc_nodes++; if (is_lc_node(tail)) coalesce_lc_node(btrie, node, pos); } /* given: * pbyte: the bits in the prefix between lc_base(pos) and pos * pfx: the next TBM_STRIDE bits in the prefix starting at pos * returns: * the bits in the prefix between lc_base(pos + plen) and pos + plen */ static inline btrie_oct_t next_pbyte(btrie_oct_t pbyte, unsigned pos, unsigned pfx) { unsigned end = pos + TBM_STRIDE; if (end % 8 != 0) { btrie_oct_t nbyte = (btrie_oct_t)pfx << (8 - end % 8); if (end % 8 > TBM_STRIDE) nbyte |= pbyte & high_bits(pos % 8); return nbyte; } return 0; } /* construct a new TBM node, given the data and children of the * root prefix of the new node. */ static void init_tbm_node(struct btrie *btrie, node_t *dst, unsigned pos, const btrie_oct_t pbyte, const void **root_data_p, node_t *left, node_t *right) { struct tbm_node *node = &dst->tbm_node; unsigned nchildren = 0; unsigned ndata = 0; node_t children[TBM_FANOUT]; const void *data[TBM_FANOUT - 1]; tbm_bitmap_t ext_bm = 0; tbm_bitmap_t int_bm = 0; unsigned i, d, pfx_base; if (left && is_lc_node(left) && lc_len(&left->lc_node) < TBM_STRIDE) convert_lc_node(btrie, &left->lc_node, pos + 1); if (right && is_lc_node(right) && lc_len(&right->lc_node) < TBM_STRIDE) convert_lc_node(btrie, &right->lc_node, pos + 1); /* set internal data for root prefix */ if (root_data_p) { data[ndata++] = *root_data_p; int_bm |= bit(base_index(0, 0)); } /* copy internal data from children */ for (d = 0; d < TBM_STRIDE - 1; d++) { if (left && has_data(left)) { for (i = 0; i < 1U << d; i++) { const void **data_p = tbm_data_p(&left->tbm_node, i, d); if (data_p) { data[ndata++] = *data_p; int_bm |= bit(base_index(i, d + 1)); } } } if (right && has_data(right)) { for (i = 0; i < 1U << d; i++) { const void **data_p = tbm_data_p(&right->tbm_node, i, d); if (data_p) { data[ndata++] = *data_p; int_bm |= bit(base_index(i + (1 << d), d + 1)); } } } } /* copy extending paths */ for (pfx_base = 0; pfx_base < TBM_FANOUT; pfx_base += TBM_FANOUT / 2) { node_t *child = pfx_base ? right : left; if (child == NULL) { continue; } else if (is_lc_node(child)) { unsigned pfx = pfx_base + lc_leading_bits(&child->lc_node, pos + 1, TBM_STRIDE - 1); /* child is LC node, just shorten it by TBM_STRIDE - 1 */ shorten_lc_node(btrie, &children[nchildren++], pos + TBM_STRIDE, &child->lc_node, pos + 1); ext_bm |= bit(pfx); } else if (!is_empty_node(child)) { /* convert deepest internal prefixes of child to extending paths * of the new node */ for (i = 0; i < TBM_FANOUT / 2; i++) { const void **data_p = tbm_data_p(&child->tbm_node, i, TBM_STRIDE - 1); node_t *left_ext = tbm_ext_path(&child->tbm_node, 2 * i); node_t *right_ext = tbm_ext_path(&child->tbm_node, 2 * i + 1); if (data_p || left_ext || right_ext) { node_t *ext_path = &children[nchildren++]; unsigned pfx = pfx_base + i; btrie_oct_t npbyte = next_pbyte(pbyte, pos, pfx); ext_bm |= bit(pfx); if (left_ext == NULL && right_ext == NULL) { /* only have data - set ext_path to zero-length terminal LC node */ lc_init_flags(&ext_path->lc_node, 1, 0); ext_path->lc_node.prefix[0] = npbyte; ext_path->lc_node.ptr.data = *data_p; btrie->n_lc_nodes++; } else if (data_p || (left_ext && right_ext)) { /* have at least two of data, left_ext, right_ext * ext_path must be a full TBM node */ init_tbm_node(btrie, ext_path, pos + TBM_STRIDE, npbyte, data_p, left_ext, right_ext); } else if (left_ext) { /* have only left_ext, insert length-one LC node */ insert_lc_node(btrie, ext_path, pos + TBM_STRIDE, npbyte, 0, left_ext); } else { /* have only right_ext, insert length-one LC node */ insert_lc_node(btrie, ext_path, pos + TBM_STRIDE, npbyte, 1, right_ext); } } } btrie->n_tbm_nodes--; free_nodes(btrie, child->tbm_node.ptr.children, count_bits(child->tbm_node.ext_bm), count_bits(child->tbm_node.int_bm)); } } assert(count_bits(int_bm) == ndata); assert(count_bits(ext_bm) == nchildren); node->ptr.children = alloc_nodes(btrie, nchildren, ndata); memcpy(node->ptr.data_end - (int)ndata, data, ndata * sizeof(data[0])); memcpy(node->ptr.children, children, nchildren * sizeof(children[0])); node->ext_bm = ext_bm; node->int_bm = int_bm; btrie->n_tbm_nodes++; } static enum btrie_result add_to_trie(struct btrie *btrie, node_t *node, unsigned pos, const btrie_oct_t *prefix, unsigned len, const void *data) { for (;;) { if (is_lc_node(node)) { struct lc_node *lc_node = &node->lc_node; unsigned end = pos + lc_len(lc_node); unsigned cbits = common_prefix(prefix + lc_shift(pos), lc_node->prefix, (len < end ? len : end) - lc_base(pos)); unsigned clen = lc_base(pos) + cbits; /* position of first mismatch */ if (clen == end && !lc_is_terminal(lc_node)) { /* matched entire prefix of LC node, proceed to child */ assert(lc_len(lc_node) > 0); node = lc_node->ptr.child; pos = end; } else if (clen == end && len == end && lc_is_terminal(lc_node)) { /* exact match for terminal node - already have data for prefix */ return BTRIE_DUPLICATE_PREFIX; } else { assert(clen < end || (lc_is_terminal(lc_node) && len > end)); /* Need to insert new TBM node at clen */ if (clen > pos) { split_lc_node(btrie, lc_node, pos, clen - pos); node = lc_node->ptr.child; assert(is_lc_node(node)); pos = clen; } convert_lc_node(btrie, &node->lc_node, pos); } } else if (is_empty_node(node)) { /* at empty TBM node - just replace with terminal LC node */ init_terminal_node(btrie, node, pos, prefix, len, data); btrie->n_entries++; btrie->n_tbm_nodes--; return BTRIE_OKAY; } else { struct tbm_node *tbm_node = &node->tbm_node; unsigned end = pos + TBM_STRIDE; if (len < end) { unsigned plen = len - pos; unsigned pfx = extract_bits(prefix, pos, plen); if (tbm_data_p(tbm_node, pfx, plen) != NULL) return BTRIE_DUPLICATE_PREFIX; /* prefix already has data */ else { tbm_insert_data(btrie, tbm_node, pfx, plen, data); btrie->n_entries++; return BTRIE_OKAY; } } else { unsigned pfx = extract_bits(prefix, pos, TBM_STRIDE); /* follow extending path */ node = tbm_ext_path(tbm_node, pfx); if (node == NULL) node = tbm_insert_ext_path(btrie, tbm_node, pfx); pos = end; } } } } static const void * search_trie(const node_t *node, unsigned pos, const btrie_oct_t *prefix, unsigned len) { /* remember last TBM node seen with internal data */ const struct tbm_node *int_node = 0; unsigned int_pfx = 0, int_plen = 0; while (node) { if (is_lc_node(node)) { const struct lc_node *lc_node = &node->lc_node; unsigned end = pos + lc_len(lc_node); if (len < end) break; if (!prefixes_equal(prefix + lc_shift(pos), lc_node->prefix, end - lc_base(pos))) break; if (lc_is_terminal(lc_node)) return lc_node->ptr.data; /* found terminal node */ pos = end; node = lc_node->ptr.child; } else { const struct tbm_node *tbm_node = &node->tbm_node; unsigned end = pos + TBM_STRIDE; if (len < end) { unsigned plen = len - pos; unsigned pfx = extract_bits(prefix, pos, plen); if (has_internal_data(tbm_node, pfx, plen)) { int_node = tbm_node; int_pfx = pfx; int_plen = plen; } break; } else { unsigned pfx = extract_bits(prefix, pos, TBM_STRIDE); if (has_internal_data(tbm_node, pfx >> 1, TBM_STRIDE - 1)) { int_node = tbm_node; int_pfx = pfx >> 1; int_plen = TBM_STRIDE - 1; } pos = end; node = tbm_ext_path(tbm_node, pfx); } } } if (int_node) { const void **data_p = tbm_data_p(int_node, int_pfx, int_plen); while (data_p == NULL) { assert(int_plen > 0); int_pfx >>= 1; int_plen--; data_p = tbm_data_p(int_node, int_pfx, int_plen); } return *data_p; } return NULL; } struct btrie * btrie_init(struct mempool *mp) { struct btrie *btrie; if (!(btrie = mp_alloc(mp, sizeof(*btrie), 1))) return NULL; memset(btrie, 0, sizeof(*btrie)); btrie->mp = mp; btrie->alloc_total = sizeof(*btrie); /* count the empty root node */ btrie->n_tbm_nodes = 1; return btrie; } enum btrie_result btrie_add_prefix(struct btrie *btrie, const btrie_oct_t *prefix, unsigned len, const void *data) { enum btrie_result rv; if ((rv = setjmp(btrie->exception)) != 0) return rv; /* out of memory */ return add_to_trie(btrie, &btrie->root, 0, prefix, len, data); } const void * btrie_lookup(const struct btrie *btrie, const btrie_oct_t *prefix, unsigned len) { return search_trie(&btrie->root, 0, prefix, len); } /**************************************************************** * * btrie_stats() - statistics reporting */ #ifdef BTRIE_EXTENDED_STATS /* Define BTRIE_EXTENDED_STATS to get extra statistics (including * trie depth). This statistics require a traversal of the entire trie * to compute, and so are disabled by default. */ struct stats { size_t max_depth; size_t total_depth; #ifndef NDEBUG size_t n_lc_nodes; size_t n_tbm_nodes; size_t n_entries; size_t alloc_data; size_t alloc_waste; #endif }; static void node_stats(const node_t *node, size_t depth, struct stats *stats) { if (depth > stats->max_depth) stats->max_depth = depth; stats->total_depth += depth; if (is_lc_node(node)) { #ifndef NDEBUG stats->n_lc_nodes++; #endif if (!lc_is_terminal(&node->lc_node)) node_stats(node->lc_node.ptr.child, depth + 1, stats); #ifndef NDEBUG else stats->n_entries++; #endif } else { unsigned i; unsigned nchildren = count_bits(node->tbm_node.ext_bm); #ifndef NDEBUG unsigned ndata = count_bits(node->tbm_node.int_bm); stats->n_tbm_nodes++; stats->n_entries += ndata; stats->alloc_data += ndata * sizeof(void *); stats->alloc_waste += (ndata % 2) * sizeof(void *); #endif for (i = 0; i < nchildren; i++) node_stats(&node->tbm_node.ptr.children[i], depth + 1, stats); } } #endif /* BTRIE_EXTENDED_STATS */ #ifndef NDEBUG static size_t count_free(const struct btrie *btrie) { size_t total = 0; unsigned sz; for (sz = 1; sz <= MAX_CHILD_ARRAY_LEN; sz++) { const struct free_hunk *free = btrie->free_list[sz - 1]; size_t n; for (n = 0; free; n++) free = free->next; total += sz * n; } return total * sizeof(node_t); } #endif /* not NDEBUG */ const char * btrie_stats(const struct btrie *btrie) { static char buf[128]; size_t n_nodes = btrie->n_lc_nodes + btrie->n_tbm_nodes; size_t alloc_free = (btrie->alloc_total + sizeof(node_t) /* do not double-count the root node */ - n_nodes * sizeof(node_t) - btrie->alloc_data - btrie->alloc_waste - sizeof(*btrie)); #ifdef BTRIE_EXTENDED_STATS struct stats stats; double average_depth; memset(&stats, 0, sizeof(stats)); node_stats(&btrie->root, 0, &stats); average_depth = (double)stats.total_depth / n_nodes; #ifndef NDEBUG /* check the node counts */ assert(stats.n_lc_nodes == btrie->n_lc_nodes); assert(stats.n_tbm_nodes == btrie->n_tbm_nodes); assert(stats.n_entries == btrie->n_entries); assert(stats.alloc_data == btrie->alloc_data); assert(stats.alloc_waste == btrie->alloc_waste); #endif /* not NDEBUG */ #endif /* BTRIE_EXTENDED_STATS */ #ifndef NDEBUG /* check that we haven't lost any memory */ assert(alloc_free == count_free(btrie)); #endif #ifdef BTRIE_DEBUG_ALLOC dump_alloc_hist(btrie); #endif snprintf(buf, sizeof(buf), "ents=%lu tbm=%lu lc=%lu mem=%.0fk free=%lu waste=%lu" #ifdef BTRIE_EXTENDED_STATS " depth=%.1f/%lu" #endif , (long unsigned)btrie->n_entries, (long unsigned)btrie->n_tbm_nodes, (long unsigned)btrie->n_lc_nodes, (double)btrie->alloc_total / 1024, (long unsigned)alloc_free, (long unsigned)btrie->alloc_waste #ifdef BTRIE_EXTENDED_STATS , average_depth, (long unsigned)stats.max_depth #endif ); buf[sizeof(buf) - 1] = '\0'; return buf; } /****************************************************************/ #ifndef NO_MASTER_DUMP struct walk_context { btrie_walk_cb_t *callback; void *user_data; btrie_oct_t prefix[(BTRIE_MAX_PREFIX + 7) / 8]; }; static void walk_node(const node_t *node, unsigned pos, struct walk_context *ctx); static void walk_tbm_node(const struct tbm_node *node, unsigned pos, unsigned pfx, unsigned plen, struct walk_context *ctx) { btrie_oct_t *prefix = ctx->prefix; int pbyte = pos / 8; btrie_oct_t pbit = 0x80 >> (pos % 8); const void **data_p = tbm_data_p(node, pfx, plen); if (pos >= BTRIE_MAX_PREFIX) { /* This can/should not happen, but don't overwrite buffers if it does. */ return; } if (data_p) ctx->callback(prefix, pos, *data_p, 0, ctx->user_data); /* walk children */ if (plen < TBM_STRIDE - 1) { /* children are internal prefixes in same node */ walk_tbm_node(node, pos + 1, pfx << 1, plen + 1, ctx); prefix[pbyte] |= pbit; walk_tbm_node(node, pos + 1, (pfx << 1) + 1, plen + 1, ctx); prefix[pbyte] &= ~pbit; } else { /* children are extending paths */ const node_t *ext_path; if ((ext_path = tbm_ext_path(node, pfx << 1)) != NULL) walk_node(ext_path, pos + 1, ctx); if ((ext_path = tbm_ext_path(node, (pfx << 1) + 1)) != NULL) { prefix[pbyte] |= pbit; walk_node(ext_path, pos + 1, ctx); prefix[pbyte] &= ~pbit; } } if (data_p) ctx->callback(prefix, pos, *data_p, 1, ctx->user_data); } static void walk_lc_node(const struct lc_node *node, unsigned pos, struct walk_context *ctx) { btrie_oct_t *prefix = ctx->prefix; unsigned end = pos + lc_len(node); btrie_oct_t save_prefix = prefix[lc_shift(pos)]; if (end > BTRIE_MAX_PREFIX) { /* This can/should not happen, but don't overwrite buffers if it does. */ return; } /* construct full prefix to node */ memcpy(&prefix[lc_shift(pos)], node->prefix, lc_bytes(node, pos)); if (end % 8) prefix[end / 8] &= high_bits(end % 8); if (lc_is_terminal(node)) { ctx->callback(prefix, end, node->ptr.data, 0, ctx->user_data); ctx->callback(prefix, end, node->ptr.data, 1, ctx->user_data); } else walk_node(node->ptr.child, end, ctx); prefix[lc_shift(pos)] = save_prefix; /* restore parents prefix */ if (lc_bytes(node, pos) > 1) memset(&prefix[lc_shift(pos) + 1], 0, lc_bytes(node, pos) - 1); } static void walk_node(const node_t *node, unsigned pos, struct walk_context *ctx) { if (is_lc_node(node)) walk_lc_node(&node->lc_node, pos, ctx); else walk_tbm_node(&node->tbm_node, pos, 0, 0, ctx); } /* walk trie in lexicographical order * * calls callback twice (once preorder, once postorder) at each prefix */ void btrie_walk(const struct btrie *btrie, btrie_walk_cb_t *callback, void *user_data) { struct walk_context ctx; memset(&ctx, 0, sizeof(ctx)); ctx.callback = callback; ctx.user_data = user_data; walk_node(&btrie->root, 0, &ctx); } #endif /* not NO_MASTER_DUMP */ #ifdef TEST /***************************************************************** * * Unit tests * */ #include #ifndef UNUSED # define UNUSED __attribute__((unused)) #endif /* bogus replacements mp_alloc for running self-tests */ void * mp_alloc(UNUSED struct mempool *mp, unsigned sz, UNUSED int align) { return malloc(sz); } #if 0 # define PASS(name) puts("OK " name) #else # define PASS(name) fputs(".", stdout); fflush(stdout) #endif const char * pgm_name = "???"; static void test_struct_node_packing() { node_t node; assert(sizeof(struct tbm_node) == 2 * sizeof(void *)); assert(sizeof(struct lc_node) == 2 * sizeof(void *)); assert(sizeof(node_t) == 2 * sizeof(void *)); /* The lc_node bit must be an alias for bit zero of int_bm, since * that is the only unused bit in the TBM node structure. */ memset(&node, 0, sizeof(node)); assert(node.tbm_node.int_bm == 0); lc_init_flags(&node.lc_node, 0, 0); assert(node.tbm_node.int_bm == bit(0)); PASS("test_struct_node_packing"); } static void test_bit() { tbm_bitmap_t ones = ~(tbm_bitmap_t)0; tbm_bitmap_t high_bit = ones ^ (ones >> 1); assert(bit(0) == high_bit); assert(bit(1) == high_bit >> 1); assert(bit(8 * sizeof(tbm_bitmap_t) - 1) == 1); PASS("test_bit"); } static void test_count_bits() { unsigned max_bits = sizeof(tbm_bitmap_t) * 8; tbm_bitmap_t ones = ~(tbm_bitmap_t)0; assert(count_bits(0) == 0); assert(count_bits(1) == 1); assert(count_bits(2) == 1); assert(count_bits(3) == 2); assert(count_bits(ones) == max_bits); assert(count_bits(~1) == max_bits - 1); /* count_bits(0x5555....) */ assert(count_bits(ones / 3) == max_bits / 2); /* count_bits(0x3333...) */ assert(count_bits(ones / 5) == max_bits / 2); /* count_bits(0x0f0f...) */ assert(count_bits(ones / 17) == max_bits / 2); /* count_bits(0x1010...) */ assert(count_bits(ones / 255) == max_bits / 8); PASS("test_count_bits"); } static void test_count_bits_before() { unsigned max_bits = sizeof(tbm_bitmap_t) * 8; tbm_bitmap_t ones = ~(tbm_bitmap_t)0; unsigned i; for (i = 0; i < max_bits; i++) { assert(count_bits_before(0, i) == 0); assert(count_bits_before(ones, i) == i); } PASS("test_count_bits_before"); } static void test_count_bits_from() { unsigned max_bits = sizeof(tbm_bitmap_t) * 8; tbm_bitmap_t ones = ~(tbm_bitmap_t)0; unsigned i; for (i = 0; i < max_bits; i++) { assert(count_bits_from(0, i) == 0); assert(count_bits_from(ones, i) == max_bits - i); } PASS("test_count_bits_from"); } static void test_extract_bits() { static btrie_oct_t prefix[] = { 0xff, 0x55, 0xaa, 0x00 }; unsigned i; for (i = 0; i < 32; i++) assert(extract_bits(prefix, i, 0) == 0); for (i = 0; i < 8; i++) assert(extract_bits(prefix, i, 1) == 1); for (i = 8; i < 16; i++) assert(extract_bits(prefix, i, 1) == i % 2); for (i = 16; i < 24; i++) assert(extract_bits(prefix, i, 1) == (i + 1) % 2); for (i = 24; i < 32; i++) assert(extract_bits(prefix, i, 1) == 0); assert(extract_bits(prefix, 2, 6) == 0x3f); assert(extract_bits(prefix, 3, 6) == 0x3e); assert(extract_bits(prefix, 4, 6) == 0x3d); assert(extract_bits(prefix, 5, 6) == 0x3a); assert(extract_bits(prefix, 6, 6) == 0x35); assert(extract_bits(prefix, 7, 6) == 0x2a); assert(extract_bits(prefix, 8, 6) == 0x15); PASS("test_extract_bits"); } static void test_high_bits() { assert(high_bits(0) == 0x00); assert(high_bits(1) == 0x80); assert(high_bits(2) == 0xc0); assert(high_bits(3) == 0xe0); assert(high_bits(4) == 0xf0); assert(high_bits(5) == 0xf8); assert(high_bits(6) == 0xfc); assert(high_bits(7) == 0xfe); assert(high_bits(8) == 0xff); PASS("test_high_bits"); } static void test_prefixes_equal() { btrie_oct_t prefix1[LC_BYTES_PER_NODE]; btrie_oct_t prefix2[LC_BYTES_PER_NODE]; unsigned i; memset(prefix1, 0xaa, LC_BYTES_PER_NODE); memset(prefix2, 0xaa, LC_BYTES_PER_NODE); for (i = 0; i < 8 * LC_BYTES_PER_NODE; i++) { assert(prefixes_equal(prefix1, prefix2, i)); prefix1[i / 8] ^= 1 << (7 - i % 8); assert(!prefixes_equal(prefix1, prefix2, 8 * LC_BYTES_PER_NODE)); assert(prefixes_equal(prefix1, prefix2, i)); if (i + 1 < 8 * LC_BYTES_PER_NODE) assert(!prefixes_equal(prefix1, prefix2, i + 1)); prefix1[i / 8] ^= 1 << (7 - i % 8); } PASS("test_prefixes_equal"); } static void test_common_prefix() { btrie_oct_t prefix1[LC_BYTES_PER_NODE]; btrie_oct_t prefix2[LC_BYTES_PER_NODE]; unsigned i; memset(prefix1, 0x55, LC_BYTES_PER_NODE); memset(prefix2, 0x55, LC_BYTES_PER_NODE); for (i = 0; i < 8 * LC_BYTES_PER_NODE; i++) { assert(common_prefix(prefix1, prefix2, i) == i); prefix1[i / 8] ^= 1 << (7 - i % 8); assert(common_prefix(prefix1, prefix2, 8 * LC_BYTES_PER_NODE) == i); if (i + 1 < 8 * LC_BYTES_PER_NODE) assert(common_prefix(prefix1, prefix2, i+1) == i); prefix1[i / 8] ^= 1 << (7 - i % 8); } PASS("test_common_prefix"); } static void test_base_index() { assert(base_index(0,0) == 1); assert(base_index(0,1) == 2); assert(base_index(1,1) == 3); assert(base_index(0,2) == 4); assert(base_index(1,2) == 5); assert(base_index(2,2) == 6); assert(base_index(3,2) == 7); PASS("test_base_index"); } static void test_has_internal_data() { struct tbm_node node; unsigned plen, pfx, bi; for (plen = 0; plen < TBM_STRIDE; plen++) { for (pfx = 0; pfx < 1U << plen; pfx++) { tbm_bitmap_t ancestor_mask = 0; for (bi = base_index(pfx, plen); bi; bi >>= 1) { node.int_bm = bit(bi); ancestor_mask |= bit(bi); assert(has_internal_data(&node, pfx, plen)); } node.int_bm = ~ancestor_mask; assert(!has_internal_data(&node, pfx, plen)); } } PASS("test_has_internal_data"); } /****************************************************************/ static const btrie_oct_t numbered_bytes[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, }; static void check_non_terminal_lc_node(struct lc_node *node, unsigned len) { assert(is_lc_node((node_t *)node)); assert(!lc_is_terminal(node)); assert(lc_len(node) == len); } static void check_terminal_lc_node(struct lc_node *node, unsigned len, const void *data) { assert(is_lc_node((node_t *)node)); assert(lc_is_terminal(node)); assert(lc_len(node) == len); assert(node->ptr.data == data); } static void test_init_terminal_node() { struct btrie *btrie = btrie_init(NULL); const void *data = (void *)0xdeadbeef; node_t node; struct lc_node *head = &node.lc_node; init_terminal_node(btrie, &node, 0, numbered_bytes, 8 * LC_BYTES_PER_NODE, data); check_terminal_lc_node(head, 8 * LC_BYTES_PER_NODE, data); assert(memcmp(head->prefix, numbered_bytes, LC_BYTES_PER_NODE) == 0); init_terminal_node(btrie, &node, 7, numbered_bytes, 8 * LC_BYTES_PER_NODE, data); check_terminal_lc_node(head, 8 * LC_BYTES_PER_NODE - 7, data); assert(memcmp(head->prefix, numbered_bytes, LC_BYTES_PER_NODE) == 0); init_terminal_node(btrie, &node, 0, numbered_bytes, 2 * 8 * LC_BYTES_PER_NODE, data); check_non_terminal_lc_node(head, 8 * LC_BYTES_PER_NODE); assert(memcmp(head->prefix, numbered_bytes, LC_BYTES_PER_NODE) == 0); { struct lc_node *child = &head->ptr.child->lc_node; check_terminal_lc_node(child, 8 * LC_BYTES_PER_NODE, data); assert(memcmp(child->prefix, &numbered_bytes[LC_BYTES_PER_NODE], LC_BYTES_PER_NODE) == 0); } init_terminal_node(btrie, &node, 15, numbered_bytes, 8 * LC_BYTES_PER_NODE + 15, data); check_non_terminal_lc_node(head, 8 * LC_BYTES_PER_NODE - 7); assert(memcmp(head->prefix, &numbered_bytes[1], LC_BYTES_PER_NODE) == 0); { struct lc_node *child = &head->ptr.child->lc_node; check_terminal_lc_node(child, 7, data); assert(child->prefix[0] == numbered_bytes[LC_BYTES_PER_NODE + 1]); } PASS("test_init_terminal_node"); } static void test_coalesce_lc_node() { struct btrie *btrie = btrie_init(NULL); const void *data = (void *)0xdeadbeef; node_t node; struct lc_node *head = &node.lc_node; /* test merging */ init_terminal_node(btrie, &node, 0, numbered_bytes, 8 * (LC_BYTES_PER_NODE + 1), data); check_non_terminal_lc_node(head, LC_BYTES_PER_NODE * 8); lc_add_to_len(head, -8); coalesce_lc_node(btrie, head, 8); check_terminal_lc_node(head, LC_BYTES_PER_NODE * 8, data); assert(head->prefix[LC_BYTES_PER_NODE - 1] == numbered_bytes[LC_BYTES_PER_NODE]); /* test bit stealing */ init_terminal_node(btrie, &node, 0, numbered_bytes, 8 * (2 * LC_BYTES_PER_NODE), data); check_non_terminal_lc_node(head, LC_BYTES_PER_NODE * 8); lc_add_to_len(head, -15); coalesce_lc_node(btrie, head, 15); check_non_terminal_lc_node(head, LC_BYTES_PER_NODE * 8 - 7); assert(memcmp(head->prefix, numbered_bytes, LC_BYTES_PER_NODE - 1) == 0); assert(head->prefix[LC_BYTES_PER_NODE - 1] == numbered_bytes[LC_BYTES_PER_NODE]); { struct lc_node *child = &head->ptr.child->lc_node; check_terminal_lc_node(child, 8 * (LC_BYTES_PER_NODE - 1), data); assert(memcmp(child->prefix, &numbered_bytes[LC_BYTES_PER_NODE + 1], LC_BYTES_PER_NODE - 1) == 0); } PASS("test_coalesce_lc_node"); } static void test_shorten_lc_node() { struct btrie *btrie = btrie_init(NULL); const void *data = (void *)0xdeadbeef; node_t node, shorter; /* test shorten without shift */ init_terminal_node(btrie, &node, 0, numbered_bytes, 8 * LC_BYTES_PER_NODE, data); memset(shorter.lc_node.prefix, 0xff, LC_BYTES_PER_NODE); shorten_lc_node(btrie, &shorter, 7, &node.lc_node, 0); check_terminal_lc_node(&shorter.lc_node, LC_BYTES_PER_NODE * 8 - 7, data); assert(memcmp(shorter.lc_node.prefix, numbered_bytes, LC_BYTES_PER_NODE) == 0); /* test shorten with shift */ init_terminal_node(btrie, &node, 7, numbered_bytes, 8 * LC_BYTES_PER_NODE, data); memset(shorter.lc_node.prefix, 0xff, LC_BYTES_PER_NODE); shorten_lc_node(btrie, &shorter, 9, &node.lc_node, 7); check_terminal_lc_node(&shorter.lc_node, LC_BYTES_PER_NODE * 8 - 9, data); assert(memcmp(shorter.lc_node.prefix, &numbered_bytes[1], LC_BYTES_PER_NODE - 1) == 0); { /* test child stealing */ struct lc_node head; node_t tail, shorter; lc_init_flags(&head, 0, 7); head.ptr.child = &tail; init_empty_node(btrie, &tail); shorten_lc_node(btrie, &shorter, 7, &head, 0); assert(is_empty_node(&shorter)); } PASS("test_shorten_lc_node"); } static void test_split_lc_node() { struct btrie *btrie = btrie_init(NULL); const void *data = (void *)0xdeadbeef; struct lc_node node; init_terminal_node(btrie, (node_t *)&node, 1, numbered_bytes, 25, data); split_lc_node(btrie, &node, 1, 8); check_non_terminal_lc_node(&node, 8); check_terminal_lc_node(&node.ptr.child->lc_node, 16, data); /* test conversion of terminal to non-terminal */ init_terminal_node(btrie, (node_t *)&node, 7, numbered_bytes, 10, data); split_lc_node(btrie, &node, 7, 3); check_non_terminal_lc_node(&node, 3); check_terminal_lc_node(&node.ptr.child->lc_node, 0, data); PASS("test_split_lc_node"); } static void test_convert_lc_node_1() { struct btrie *btrie = btrie_init(NULL); const void *data = (void *)0xdeadbeef; struct lc_node head; /* test tail is left */ lc_init_flags(&head, 0, 1); head.prefix[0] = 0; head.ptr.child = alloc_nodes(btrie, 1, 0); init_terminal_node(btrie, head.ptr.child, 1, numbered_bytes, 1, data); convert_lc_node_1(btrie, &head, 0); { node_t *result = (node_t *)&head; assert(is_tbm_node(result)); assert(result->tbm_node.ext_bm == 0); assert(result->tbm_node.int_bm == bit(base_index(0, 1))); assert(*tbm_data_p(&result->tbm_node, 0, 1) == data); } /* test tail is right */ lc_init_flags(&head, 0, 1); head.prefix[0] = 1; head.ptr.child = alloc_nodes(btrie, 1, 0); init_terminal_node(btrie, head.ptr.child, 8, numbered_bytes, 10, data); convert_lc_node_1(btrie, &head, 7); { node_t *result = (node_t *)&head; assert(is_tbm_node(result)); assert(result->tbm_node.ext_bm == 0); assert(result->tbm_node.int_bm == bit(base_index(4, 3))); assert(*tbm_data_p(&result->tbm_node, 4, 3) == data); } PASS("test_convert_lc_node_1"); } static void test_convert_lc_node() { struct btrie *btrie = btrie_init(NULL); const void *data = (void *)0xdeadbeef; node_t node; /* if (len >= TBM_STRIDE) */ init_terminal_node(btrie, &node, 7, numbered_bytes, TBM_STRIDE + 7, data); convert_lc_node(btrie, &node.lc_node, 7); assert(is_tbm_node(&node)); assert(node.tbm_node.ext_bm == bit(0)); assert(node.tbm_node.int_bm == 0); check_terminal_lc_node(&tbm_ext_path(&node.tbm_node, 0)->lc_node, 0, data); /* if (lc_is_terminal(node)) */ init_terminal_node(btrie, &node, 0, numbered_bytes, 0, data); convert_lc_node(btrie, &node.lc_node, 0); assert(is_tbm_node(&node)); assert(node.tbm_node.ext_bm == 0); assert(node.tbm_node.int_bm == bit(base_index(0, 0))); assert(*tbm_data_p(&node.tbm_node, 0, 0) == data); /* else */ lc_init_flags(&node.lc_node, 0, TBM_STRIDE - 1); node.lc_node.prefix[0] = 0; node.lc_node.ptr.child = alloc_nodes(btrie, 1, 0); init_empty_node(btrie, node.lc_node.ptr.child); tbm_insert_data(btrie, &node.lc_node.ptr.child->tbm_node, 0, 0, data); convert_lc_node(btrie, &node.lc_node, 0); assert(is_tbm_node(&node)); assert(node.tbm_node.ext_bm == 0); assert(node.tbm_node.int_bm == bit(base_index(0, TBM_STRIDE - 1))); assert(*tbm_data_p(&node.tbm_node, 0, TBM_STRIDE - 1) == data); PASS("test_convert_lc_node"); } static void test_insert_lc_node() { struct btrie *btrie = btrie_init(NULL); const void *data = (void *)0xdeadbeef; node_t node, tail; /* test optimized case, last_bit == 0 */ init_terminal_node(btrie, &tail, 9, numbered_bytes, 17, data); insert_lc_node(btrie, &node, 8, 0, 0, &tail); check_terminal_lc_node(&node.lc_node, 9, data); assert(memcmp(node.lc_node.prefix, &numbered_bytes[1], 2) == 0); /* test optimized case, last_bit == 1 */ init_terminal_node(btrie, &tail, 7, &numbered_bytes[0x12], 15, data); insert_lc_node(btrie, &node, 6, 0x10, 1, &tail); check_terminal_lc_node(&node.lc_node, 9, data); assert(node.lc_node.prefix[0] == 0x12); assert(node.lc_node.prefix[1] == 0x13); /* test with shift */ init_terminal_node(btrie, &tail, 0, numbered_bytes, 8, data); insert_lc_node(btrie, &node, 7, 0x40, 1, &tail); check_terminal_lc_node(&node.lc_node, 9, data); assert(node.lc_node.prefix[0] == 0x41); assert(node.lc_node.prefix[1] == numbered_bytes[0]); /* test with TBM node */ init_empty_node(btrie, &tail); insert_lc_node(btrie, &node, 6, 0x40, 0, &tail); check_non_terminal_lc_node(&node.lc_node, 1); assert(is_tbm_node(node.lc_node.ptr.child)); PASS("test_insert_lc_node"); } static void test_next_pbyte() { assert(next_pbyte(0xff, 0, 1) == 0x80 >> (TBM_STRIDE - 1)); assert(next_pbyte(0xff, 1, 1) == (0x80 | (0x80 >> TBM_STRIDE))); assert(next_pbyte(0xff, 2, 1) == (0xc0 | (0x80 >> (TBM_STRIDE + 1)))); assert(next_pbyte(0xff, 8 - TBM_STRIDE, 1) == 0); assert(next_pbyte(0xff, 9 - TBM_STRIDE, 1) == 0x80); PASS("test_next_pbyte"); } static void test_init_tbm_node() { struct btrie *btrie = btrie_init(NULL); const void *data = (void *)0xdeadbeef; unsigned lr; node_t node; /* test root data */ init_tbm_node(btrie, &node, 0, 0, &data, NULL, NULL); assert(is_tbm_node(&node)); assert(node.tbm_node.ext_bm == 0); assert(node.tbm_node.int_bm == bit(base_index(0, 0))); assert(*tbm_data_p(&node.tbm_node, 0, 0) == data); for (lr = 0; lr < 2; lr++) { node_t child; node_t *left = lr ? NULL : &child; node_t *right = lr ? &child : NULL; unsigned base = lr ? (1U << (TBM_STRIDE - 1)) : 0; unsigned pfx; /* test with long LC node child */ init_terminal_node(btrie, &child, 1, numbered_bytes, TBM_STRIDE + 1, data); init_tbm_node(btrie, &node, 0, 0, NULL, left, right); assert(is_tbm_node(&node)); assert(node.tbm_node.ext_bm == bit(base)); assert(node.tbm_node.int_bm == 0); check_terminal_lc_node(&tbm_ext_path(&node.tbm_node, base)->lc_node, 1, data); /* test with short LC node children */ init_terminal_node(btrie, &child, 1, numbered_bytes, TBM_STRIDE - 1, data); init_tbm_node(btrie, &node, 0, 0, NULL, left, right); assert(is_tbm_node(&node)); assert(node.tbm_node.ext_bm == 0); assert(node.tbm_node.int_bm == bit(base_index(base >> 1, TBM_STRIDE-1))); assert(*tbm_data_p(&node.tbm_node, base >> 1, TBM_STRIDE-1) == data); /* construct TBM node with all eight combinations of having data, * left_ext and/or right_ext in its extending paths */ init_empty_node(btrie, &child); for (pfx = 0; pfx < 8; pfx++) { if (pfx & 1) tbm_insert_data(btrie, &child.tbm_node, pfx, TBM_STRIDE - 1, data); if (pfx & 2) { btrie_oct_t prefix0 = 0; init_terminal_node(btrie, tbm_insert_ext_path(btrie, &child.tbm_node, 2*pfx), TBM_STRIDE + 1, &prefix0, TBM_STRIDE + 2, data); } if (pfx & 4) { btrie_oct_t prefix0 = 0x80 >> TBM_STRIDE; init_terminal_node(btrie, tbm_insert_ext_path(btrie, &child.tbm_node, 2*pfx+1), TBM_STRIDE + 1, &prefix0, TBM_STRIDE + 3, data); } } init_tbm_node(btrie, &node, 0, 0, NULL, left, right); for (pfx = 0; pfx < 8; pfx++) { unsigned base = lr ? (1U << (TBM_STRIDE - 1)) : 0; node_t *ext_path = tbm_ext_path(&node.tbm_node, base + pfx); if (pfx == 0) assert(ext_path == NULL); else if (pfx == 1) check_terminal_lc_node(&ext_path->lc_node, 0, data); else if (pfx == 2) { check_terminal_lc_node(&ext_path->lc_node, 2, data); assert(ext_path->lc_node.prefix[0] == 0); } else if (pfx == 4) { check_terminal_lc_node(&ext_path->lc_node, 3, data); assert(ext_path->lc_node.prefix[0] == (0x80 >> TBM_STRIDE)); } else { tbm_bitmap_t int_bm = 0; assert(is_tbm_node(ext_path)); if (pfx & 1) { int_bm |= bit(base_index(0, 0)); assert(*tbm_data_p(&ext_path->tbm_node, 0, 0) == data); } if (pfx & 2) { int_bm |= bit(base_index(0, 2)); assert(*tbm_data_p(&ext_path->tbm_node, 0, 2) == data); } if (pfx & 4) { int_bm |= bit(base_index(4, 3)); assert(*tbm_data_p(&ext_path->tbm_node, 4, 3) == data); } assert(ext_path->tbm_node.int_bm == int_bm); } } } PASS("test_init_tbm_node"); } static void test_add_to_trie() { struct btrie *btrie = btrie_init(NULL); const void *data = (void *)0xdeadbeef; enum btrie_result result; unsigned pfx, plen; node_t root; /* test initial insertion */ init_empty_node(btrie, &root); result = add_to_trie(btrie, &root, 0, numbered_bytes, 8 * 2 * LC_BYTES_PER_NODE, data); assert(result == BTRIE_OKAY); check_non_terminal_lc_node(&root.lc_node, 8 * LC_BYTES_PER_NODE); check_terminal_lc_node(&root.lc_node.ptr.child->lc_node, 8 * LC_BYTES_PER_NODE, data); /* test can follow LC node to tail, and then detect duplicate prefix */ result = add_to_trie(btrie, &root, 0, numbered_bytes, 8 * 2 * LC_BYTES_PER_NODE, data); assert(result == BTRIE_DUPLICATE_PREFIX); /* test can insert new TBM node within existing LC node */ result = add_to_trie(btrie, &root, 0, &numbered_bytes[1], 16, data); assert(result == BTRIE_OKAY); check_non_terminal_lc_node(&root.lc_node, 7); assert(is_tbm_node(root.lc_node.ptr.child)); /* test can convert terminal LC node to TBM node */ init_terminal_node(btrie, &root, 0, numbered_bytes, 12, data); result = add_to_trie(btrie, &root, 0, numbered_bytes, 24, data); assert(result == BTRIE_OKAY); check_non_terminal_lc_node(&root.lc_node, 12); assert(is_tbm_node(root.lc_node.ptr.child)); /* test can insert internal prefix data in TBM node */ for (plen = 0; plen < TBM_STRIDE; plen++) { for (pfx = 0; pfx < (1U << plen); pfx++) { btrie_oct_t prefix0 = plen ? pfx << (8 - plen) : 0; init_empty_node(btrie, &root); init_terminal_node(btrie, tbm_insert_ext_path(btrie, &root.tbm_node, 0), TBM_STRIDE, numbered_bytes, 8, data); result = add_to_trie(btrie, &root, 0, &prefix0, plen, data); assert(result == BTRIE_OKAY); assert(is_tbm_node(&root)); assert(root.tbm_node.ext_bm == bit(0)); assert(root.tbm_node.int_bm == bit(base_index(pfx, plen))); assert(*tbm_data_p(&root.tbm_node, pfx, plen) == data); result = add_to_trie(btrie, &root, 0, &prefix0, plen, data); assert(result == BTRIE_DUPLICATE_PREFIX); } } /* test can add extending paths to TBM node */ for (pfx = 0; pfx < (1U << TBM_STRIDE); pfx++) { btrie_oct_t prefix0 = pfx << (8 - TBM_STRIDE); init_empty_node(btrie, &root); tbm_insert_data(btrie, &root.tbm_node, 0, 0, data); result = add_to_trie(btrie, &root, 0, &prefix0, 8, data); assert(result == BTRIE_OKAY); assert(is_tbm_node(&root)); assert(root.tbm_node.ext_bm == bit(pfx)); assert(root.tbm_node.int_bm == bit(base_index(0, 0))); check_terminal_lc_node(&tbm_ext_path(&root.tbm_node, pfx)->lc_node, 8 - TBM_STRIDE, data); result = add_to_trie(btrie, &root, 0, &prefix0, 8, data); assert(result == BTRIE_DUPLICATE_PREFIX); } /* test can follow extending path */ init_empty_node(btrie, &root); init_terminal_node(btrie, tbm_insert_ext_path(btrie, &root.tbm_node, 0), TBM_STRIDE, numbered_bytes, 8, data); result = add_to_trie(btrie, &root, 0, numbered_bytes, 7, data); assert(result == BTRIE_OKAY); assert(root.tbm_node.ext_bm == bit(0)); assert(root.tbm_node.int_bm == 0); check_non_terminal_lc_node(&root.tbm_node.ptr.children[0].lc_node, 7 - TBM_STRIDE); PASS("test_add_to_trie"); } static void test_search_trie() { struct btrie *btrie = btrie_init(NULL); const void *data01 = (void *)0xdead0001; const void *data11 = (void *)0xdead0101; const void *data = (void *)0xdeadbeef; unsigned plen, pfx; node_t root; /* test can follow chain of LC nodes to an exact match */ init_empty_node(btrie, &root); add_to_trie(btrie, &root, 0, numbered_bytes, 8 * 2 * LC_BYTES_PER_NODE, data); assert(search_trie(&root, 0, numbered_bytes, 8 * 2 * LC_BYTES_PER_NODE) == data); assert(search_trie(&root, 0, numbered_bytes, 8 * 2 * LC_BYTES_PER_NODE + 1) == data); assert(search_trie(&root, 0, numbered_bytes, 8 * 2 * LC_BYTES_PER_NODE - 1) == NULL); assert(search_trie(&root, 0, &numbered_bytes[1], 8 * 2 * LC_BYTES_PER_NODE) == NULL); /* test can follow extending path to an exact match */ for (pfx = 0; pfx < (1U << TBM_STRIDE); pfx++) { btrie_oct_t prefix0 = pfx << (8 - TBM_STRIDE); init_empty_node(btrie, &root); tbm_insert_data(btrie, &root.tbm_node, 0, 1, data01); tbm_insert_data(btrie, &root.tbm_node, 1, 1, data11); add_to_trie(btrie, &root, 0, &prefix0, 8, data); assert(search_trie(&root, 0, &prefix0, 8) == data); /* test that last matching TBM internal prefix gets picked up */ if (prefix0 & 0x80) assert(search_trie(&root, 0, &prefix0, 7) == data11); else assert(search_trie(&root, 0, &prefix0, 7) == data01); prefix0 ^= 1 << (8 - TBM_STRIDE); if (prefix0 & 0x80) assert(search_trie(&root, 0, &prefix0, 8) == data11); else assert(search_trie(&root, 0, &prefix0, 8) == data01); } /* test finding of TBM internal prefixes */ init_empty_node(btrie, &root); tbm_insert_data(btrie, &root.tbm_node, 0, 1, data01); tbm_insert_data(btrie, &root.tbm_node, 1, 1, data11); assert(search_trie(&root, 0, numbered_bytes, 0) == NULL); for (plen = 1; plen < TBM_STRIDE; plen++) { for (pfx = 0; pfx < (1U << TBM_STRIDE); pfx++) { btrie_oct_t prefix0 = pfx << (8 - plen); if (prefix0 & 0x80) assert(search_trie(&root, 0, &prefix0, plen) == data11); else assert(search_trie(&root, 0, &prefix0, plen) == data01); } } PASS("test_search_trie"); } static int unit_tests() { test_struct_node_packing(); test_bit(); test_count_bits(); test_count_bits_before(); test_count_bits_from(); test_extract_bits(); test_high_bits(); test_prefixes_equal(); test_common_prefix(); test_base_index(); test_has_internal_data(); test_init_terminal_node(); test_coalesce_lc_node(); test_shorten_lc_node(); test_split_lc_node(); test_convert_lc_node_1(); test_convert_lc_node(); test_insert_lc_node(); test_next_pbyte(); test_init_tbm_node(); test_add_to_trie(); test_search_trie(); puts("\nOK"); return 0; } /***************************************************************** * * btrie_dump: print out the trie structure (for testing) * */ #define INDENT_FILL "....:....|....:....|....:....|....:....|" static void dump_node(const node_t *node, unsigned pos, btrie_oct_t *prefix, int indent); static void dump_prefix(btrie_oct_t *prefix, unsigned len, int indent, const char *tail) { unsigned i; printf("%*.*s0x", indent, indent, INDENT_FILL); for (i = 0; i < len / 8; i++) printf("%02x", prefix[i]); if (len % 8) printf("%02x", prefix[len / 8] & high_bits(len % 8)); printf("/%u%s", len, tail); } /* the opposite of extract_bits, sets a short string of bits from integer */ static void insert_bits(btrie_oct_t *prefix, unsigned pos, btrie_oct_t pfx, unsigned nbits) { if (nbits != 0) { unsigned v = (prefix[pos / 8] << 8) + prefix[pos / 8 + 1]; unsigned mask = (1U << nbits) - 1; unsigned shift = 16 - (pos % 8) - nbits; v = (v & ~(mask << shift)) | (pfx << shift); prefix[pos / 8] = v >> 8; prefix[pos / 8 + 1] = (btrie_oct_t)v; } } static void dump_tbm_node(const struct tbm_node *node, unsigned pos, btrie_oct_t *prefix, int indent) { unsigned pfx = 0, plen = 0; dump_prefix(prefix, pos, indent, " [tbm]\n"); for (;;) { if (plen < TBM_STRIDE) { const void **data_p = tbm_data_p(node, pfx, plen); if (data_p) { insert_bits(prefix, pos, pfx, plen); dump_prefix(prefix, pos + plen, indent, ""); printf(" [%u/%u] (%s)\n", pfx, plen, (const char *)*data_p); } plen++; pfx <<= 1; } else { const node_t *ext_path = tbm_ext_path(node, pfx); if (ext_path) { insert_bits(prefix, pos, pfx, TBM_STRIDE); dump_node(ext_path, pos + TBM_STRIDE, prefix, indent + 1); } while (pfx & 1) { if (--plen == 0) return; pfx >>= 1; } pfx++; } } } static void dump_lc_node(const struct lc_node *node, unsigned pos, btrie_oct_t *prefix, int indent) { unsigned end = pos + lc_len(node); btrie_oct_t save_prefix = prefix[lc_shift(pos)]; memcpy(&prefix[lc_shift(pos)], node->prefix, lc_bytes(node, pos)); if (lc_is_terminal(node)) { dump_prefix(prefix, end, indent, ""); printf(" (%s)\n", (const char *)node->ptr.data); } else { dump_prefix(prefix, end, indent, "\n"); dump_node(node->ptr.child, end, prefix, indent + 1); } prefix[lc_shift(pos)] = save_prefix; if (lc_bytes(node, pos) > 1) memset(&prefix[lc_shift(pos) + 1], 0, lc_bytes(node, pos) - 1); } static void dump_node(const node_t *node, unsigned pos, btrie_oct_t *prefix, int indent) { if (is_lc_node(node)) dump_lc_node(&node->lc_node, pos, prefix, indent); else dump_tbm_node(&node->tbm_node, pos, prefix, indent); } static void btrie_dump(struct btrie *btrie) { btrie_oct_t prefix[(BTRIE_MAX_PREFIX + 7) / 8]; memset(prefix, 0, sizeof(prefix)); dump_node(&btrie->root, 0, prefix, 0); puts(btrie_stats(btrie)); } /**************************************************************** * * test program - just enough to construct a trie and preform a lookup * */ #include static int parse_prefix(const char *arg, btrie_oct_t prefix[16], unsigned *len) { char addrbuf[128]; return sscanf(arg, "%127[0-9a-fA-F:]/%u", addrbuf, len) == 2 && inet_pton(AF_INET6, addrbuf, prefix) == 1; } static int test_btrie(int argc, char *argv[]) { struct btrie *btrie = btrie_init(NULL); int i; btrie_oct_t prefix[16]; unsigned len; for (i = 1; i < argc-1; i++) { if (!parse_prefix(argv[i], prefix, &len)) { fprintf(stderr, "Can not parse arg '%s'\n", argv[i]); return 1; } btrie_add_prefix(btrie, prefix, len, argv[i]); } btrie_dump(btrie); if (argc > 1) { const void *data; if (!parse_prefix(argv[argc-1], prefix, &len)) { fprintf(stderr, "Can not parse arg '%s'\n", argv[argc-1]); return 1; } data = btrie_lookup(btrie, prefix, 128); printf("lookup(%s) => %s\n", argv[argc-1], (const char *)data); } return 0; } int main(int argc, char *argv[]) { if ((pgm_name = strrchr(argv[0], '/')) != NULL) pgm_name++; else pgm_name = argv[0]; if (argc > 1) return test_btrie(argc, argv); else return unit_tests(); } #endif /* TEST */ rbldnsd-0.997a/rbldnsd.c0000664000175000017500000007310212130046505013276 0ustar mjtmjt/* rbldnsd: main program */ #define _LARGEFILE64_SOURCE /* to define O_LARGEFILE if supported */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* some systems can't include time.h and sys/time.h */ #include #include #include "rbldnsd.h" #ifndef NO_SELECT_H # include #endif #ifndef NO_POLL # include #endif #ifndef NO_MEMINFO # include #endif #ifndef NO_TIMES # include #endif #ifndef NO_STDINT_H /* if system have stdint.h, assume it have inttypes.h too */ # include #endif #ifndef NO_STATS # ifndef NO_IOVEC # include # define STATS_IPC_IOVEC 1 # endif #endif #ifndef NO_DSO # include #endif #ifndef NI_MAXHOST # define NI_MAXHOST 1025 #endif #ifndef NI_MAXSERV # define NI_MAXSERV 32 #endif #ifndef O_LARGEFILE # define O_LARGEFILE 0 #endif const char *version = VERSION; const char *show_version = "rbldnsd " VERSION; /* version to show in version.bind CH TXT reply */ char *progname; /* limited to 32 chars */ int logto; void error(int errnum, const char *fmt, ...) { char buf[256]; int l, pl; va_list ap; l = pl = ssprintf(buf, sizeof(buf), "%.30s: ", progname); va_start(ap, fmt); l += vssprintf(buf + l, sizeof(buf) - l, fmt, ap); if (errnum) l += ssprintf(buf + l, sizeof(buf) - l, ": %.50s", strerror(errnum)); if (logto & LOGTO_SYSLOG) { fmt = buf + pl; syslog(LOG_ERR, strchr(fmt, '%') ? "%s" : fmt, fmt); } buf[l++] = '\n'; write(2, buf, l); _exit(1); } static unsigned recheck = 60; /* interval between checks for reload */ static int initialized; /* 1 when initialized */ static char *logfile; /* log file name */ #ifndef NO_STATS static char *statsfile; /* statistics file */ static int stats_relative; /* dump relative, not absolute, stats */ #endif int accept_in_cidr; /* accept 127.0.0.1/8-"style" CIDRs */ int nouncompress; /* disable on-the-fly decompression */ unsigned def_ttl = 35*60; /* default record TTL 35m */ unsigned min_ttl, max_ttl; /* TTL constraints */ const char def_rr[5] = "\177\0\0\2\0"; /* default A RR */ #define MAXSOCK 20 /* maximum # of supported sockets */ static int sock[MAXSOCK]; /* array of active sockets */ static int numsock; /* number of active sockets in sock[] */ static FILE *flog; /* log file */ static int flushlog; /* flush log after each line */ static struct zone *zonelist; /* list of zones we're authoritative for */ static int numzones; /* number of zones in zonelist */ int lazy; /* don't return AUTH section by default */ static int fork_on_reload; /* >0 - perform fork on reloads, <0 - this is a child of reloading parent */ #if STATS_IPC_IOVEC static struct iovec *stats_iov; #endif #ifndef NO_DSO int (*hook_reload_check)(), (*hook_reload)(); int (*hook_query_access)(), (*hook_query_result)(); #endif /* a list of zonetypes. */ const struct dstype *ds_types[] = { dstype(ip4set), dstype(ip4tset), dstype(ip4trie), dstype(ip6tset), dstype(ip6trie), dstype(dnset), #ifdef DNHASH dstype(dnhash), #endif dstype(combined), dstype(generic), dstype(acl), NULL }; static int do_reload(int do_fork); static int satoi(const char *s) { int n = 0; if (*s < '0' || *s > '9') return -1; do n = n * 10 + (*s++ - '0'); while (*s >= '0' && *s <= '9'); return *s ? -1 : n; } static void NORETURN usage(int exitcode) { const struct dstype **dstp; printf( "%s: rbl dns daemon version %s\n" "Usage is: %s options zonespec...\n" "where options are:\n" " -u user[:group] - run as this user:group (rbldns)\n" " -r rootdir - chroot to this directory\n" " -w workdir - working directory with zone files\n" " -b address[/port] - bind to (listen on) this address (required)\n" #ifndef NO_IPv6 " -4 - use IPv4 socket type\n" " -6 - use IPv6 socket type\n" #endif " -t ttl - default TTL value to set in answers (35m)\n" " -v - hide version information in replies to version.bind CH TXT\n" " (second -v makes rbldnsd to refuse such requests completely)\n" " -e - enable CIDR ranges where prefix is not on the range boundary\n" " (by default ranges such 127.0.0.1/8 will be rejected)\n" " -c check - time interval to check for data file updates (1m)\n" " -p pidfile - write pid to specified file\n" " -n - do not become a daemon\n" " -f - fork a child process while reloading zones, to process requests\n" " during reload (may double memory requiriments)\n" " -q - quickstart, load zones after backgrounding\n" " -l [+]logfile - log queries and answers to this file (+ for unbuffered)\n" #ifndef NO_STATS " -s [+]statsfile - write a line with short statistics summary into this\n" " file every `check' (-c) secounds, for rrdtool-like applications\n" " (+ to log relative, not absolute, statistics counters)\n" #endif " -a - omit AUTH section from regular replies, do not return list of\n" " nameservers, but only return NS info when explicitly asked.\n" " This is an equivalent of bind9 \"minimal-answers\" setting.\n" " In future versions this mode will be the default.\n" " -A - put AUTH section in every reply.\n" #ifndef NO_ZLIB " -C - disable on-the-fly decompression of dataset files\n" #endif #ifndef NO_DZO " -x extension - load given extension module (.so file)\n" " -X extarg - pass extarg to extension init routine\n" #endif " -d - dump all zones in BIND format to standard output and exit\n" "each zone specified using `name:type:file,file...'\n" "syntax, repeated names constitute the same zone.\n" "Available dataset types:\n" , progname, version, progname); for(dstp = ds_types; *dstp; ++dstp) printf(" %s - %s\n", (*dstp)->dst_name, (*dstp)->dst_descr); exit(exitcode); } static volatile int signalled; #define SIGNALLED_RELOAD 0x01 #define SIGNALLED_RELOG 0x02 #define SIGNALLED_LSTATS 0x04 #define SIGNALLED_SSTATS 0x08 #define SIGNALLED_ZSTATS 0x10 #define SIGNALLED_TERM 0x20 #ifdef NO_IPv6 static void newsocket(struct sockaddr_in *sin) { int fd; const char *host = ip4atos(ntohl(sin->sin_addr.s_addr)); if (numsock >= MAXSOCK) error(0, "too many listening sockets (%d max)", MAXSOCK); fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (fd < 0) error(errno, "unable to create socket"); if (bind(fd, (struct sockaddr *)sin, sizeof(*sin)) < 0) error(errno, "unable to bind to %s/%d", host, ntohs(sin->sin_port)); dslog(LOG_INFO, 0, "listening on %s/%d", host, ntohs(sin->sin_port)); sock[numsock++] = fd; } #else static int newsocket(struct addrinfo *ai) { int fd; char host[NI_MAXHOST], serv[NI_MAXSERV]; if (numsock >= MAXSOCK) error(0, "too many listening sockets (%d max)", MAXSOCK); fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (fd < 0) { if (errno == EAFNOSUPPORT) return 0; error(errno, "unable to create socket"); } getnameinfo(ai->ai_addr, ai->ai_addrlen, host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST|NI_NUMERICSERV); if (bind(fd, ai->ai_addr, ai->ai_addrlen) < 0) error(errno, "unable to bind to %s/%s", host, serv); dslog(LOG_INFO, 0, "listening on %s/%s", host, serv); sock[numsock++] = fd; return 1; } #endif static void initsockets(const char *bindaddr[MAXSOCK], int nba, int UNUSED family) { int i, x; char *host, *serv; const char *ba; #ifdef NO_IPv6 struct sockaddr_in sin; ip4addr_t sinaddr; int port; struct servent *se; struct hostent *he; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; if (!(se = getservbyname("domain", "udp"))) port = htons(DNS_PORT); else port = se->s_port; #else struct addrinfo hints, *aires, *ai; memset(&hints, 0, sizeof(hints)); hints.ai_family = family; hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_PASSIVE; #endif for (i = 0; i < nba; ++i) { ba = bindaddr[i]; host = estrdup(ba); serv = strchr(host, '/'); if (serv) { *serv++ = '\0'; if (!*host) error(0, "missing host part in bind address `%.60s'", ba); } #ifdef NO_IPv6 if (!serv || !*serv) sin.sin_port = port; else if ((x = satoi(serv)) > 0 && x <= 0xffff) sin.sin_port = htons(x); else if (!(se = getservbyname(serv, "udp"))) error(0, "unknown service in `%.60s'", ba); else sin.sin_port = se->s_port; if (ip4addr(host, &sinaddr, NULL) > 0) { sin.sin_addr.s_addr = htonl(sinaddr); newsocket(&sin); } else if (!(he = gethostbyname(host)) || he->h_addrtype != AF_INET || he->h_length != 4 || !he->h_addr_list[0]) error(0, "unknown host in `%.60s'", ba); else { for(x = 0; he->h_addr_list[x]; ++x) { memcpy(&sin.sin_addr, he->h_addr_list[x], 4); newsocket(&sin); } } #else if (!serv || !*serv) serv = "domain"; x = getaddrinfo(host, serv, &hints, &aires); if (x != 0) error(0, "%.60s: %s", ba, gai_strerror(x)); for(ai = aires, x = 0; ai; ai = ai->ai_next) if (newsocket(ai)) ++x; if (!x) error(0, "%.60s: no available protocols", ba); freeaddrinfo(aires); #endif free(host); } endservent(); endhostent(); for (i = 0; i < numsock; ++i) { x = 65536; do if (setsockopt(sock[i], SOL_SOCKET, SO_RCVBUF, (void*)&x, sizeof x) == 0) break; while ((x -= (x >> 5)) >= 1024); } } static void init(int argc, char **argv) { int c; char *p; const char *user = NULL; const char *rootdir = NULL, *workdir = NULL, *pidfile = NULL; const char *bindaddr[MAXSOCK]; int nba = 0; uid_t uid = 0; gid_t gid = 0; int nodaemon = 0, quickstart = 0, dump = 0, nover = 0, forkon = 0; int family = AF_UNSPEC; int cfd = -1; const struct zone *z; #ifndef NO_DSO char *ext = NULL, *extarg = NULL; int (*extinit)(const char *arg, struct zone *zonelist) = NULL; #endif if ((progname = strrchr(argv[0], '/')) != NULL) argv[0] = ++progname; else progname = argv[0]; if (argc <= 1) usage(1); while((c = getopt(argc, argv, "u:r:b:w:t:c:p:nel:qs:h46dvaAfCx:X:")) != EOF) switch(c) { case 'u': user = optarg; break; case 'r': rootdir = optarg; break; case 'b': if (nba >= MAXSOCK) error(0, "too many addresses to listen on (%d max)", MAXSOCK); bindaddr[nba++] = optarg; break; #ifndef NO_IPv6 case '4': family = AF_INET; break; case '6': family = AF_INET6; break; #else case '4': break; case '6': error(0, "IPv6 support isn't compiled in"); #endif case 'w': workdir = optarg; break; case 'p': pidfile = optarg; break; case 't': p = optarg; if (*p == ':') ++p; else { if (!(p = parse_time(p, &def_ttl)) || !def_ttl || (*p && *p++ != ':')) error(0, "invalid ttl (-t) value `%.50s'", optarg); } if (*p == ':') ++p; else if (*p) { if (!(p = parse_time(p, &min_ttl)) || (*p && *p++ != ':')) error(0, "invalid minttl (-t) value `%.50s'", optarg); } if (*p == ':') ++p; else if (*p) { if (!(p = parse_time(p, &max_ttl)) || (*p && *p++ != ':')) error(0, "invalid maxttl (-t) value `%.50s'", optarg); } if (*p) error(0, "invalid value for -t (ttl) option: `%.50s'", optarg); if ((min_ttl && max_ttl && min_ttl > max_ttl) || (min_ttl && def_ttl < min_ttl) || (max_ttl && def_ttl > max_ttl)) error(0, "inconsistent def:min:max ttl: %u:%u:%u", def_ttl, min_ttl, max_ttl); break; case 'c': if (!(p = parse_time(optarg, &recheck)) || *p) error(0, "invalid check interval (-c) value `%.50s'", optarg); break; case 'n': nodaemon = 1; break; case 'e': accept_in_cidr = 1; break; case 'l': logfile = optarg; if (*logfile != '+') flushlog = 0; else ++logfile, flushlog = 1; if (!*logfile) logfile = NULL, flushlog = 0; else if (logfile[0] == '-' && logfile[1] == '\0') logfile = NULL, flog = stdout; break; break; case 's': #ifdef NO_STATS fprintf(stderr, "%s: warning: no statistics counters support is compiled in\n", progname); #else statsfile = optarg; if (*statsfile != '+') stats_relative = 0; else ++statsfile, stats_relative = 1; if (!*statsfile) statsfile = NULL; #endif break; case 'q': quickstart = 1; break; case 'd': #ifdef NO_MASTER_DUMP error(0, "master-format dump option (-d) isn't compiled in"); #endif dump = 1; break; case 'v': show_version = nover++ ? NULL : "rbldnsd"; break; case 'a': lazy = 1; break; case 'A': lazy = 0; break; case 'f': forkon = 1; break; case 'C': nouncompress = 1; break; #ifndef NO_DSO case 'x': ext = optarg; break; case 'X': extarg = optarg; break; #else case 'x': case 'X': error(0, "extension support is not compiled in"); #endif case 'h': usage(0); default: error(0, "type `%.50s -h' for help", progname); } if (!(argc -= optind)) error(0, "no zone(s) to service specified (-h for help)"); argv += optind; #ifndef NO_MASTER_DUMP if (dump) { time_t now; logto = LOGTO_STDERR; for(c = 0; c < argc; ++c) zonelist = addzone(zonelist, argv[c]); init_zones_caches(zonelist); if (rootdir && (chdir(rootdir) < 0 || chroot(rootdir) < 0)) error(errno, "unable to chroot to %.50s", rootdir); if (workdir && chdir(workdir) < 0) error(errno, "unable to chdir to %.50s", workdir); if (!do_reload(0)) error(0, "zone loading errors, aborting"); now = time(NULL); printf("; zone dump made %s", ctime(&now)); printf("; rbldnsd version %s\n", version); for (z = zonelist; z; z = z->z_next) dumpzone(z, stdout); fflush(stdout); exit(ferror(stdout) ? 1 : 0); } #endif if (!nba) error(0, "no address to listen on (-b option) specified"); tzset(); if (nodaemon) logto = LOGTO_STDOUT|LOGTO_STDERR; else { /* fork early so that logging will be from right pid */ int pfd[2]; if (pipe(pfd) < 0) error(errno, "pipe() failed"); c = fork(); if (c < 0) error(errno, "fork() failed"); if (c > 0) { close(pfd[1]); if (read(pfd[0], &c, 1) < 1) exit(1); else exit(0); } cfd = pfd[1]; close(pfd[0]); openlog(progname, LOG_PID|LOG_NDELAY, LOG_DAEMON); logto = LOGTO_STDERR|LOGTO_SYSLOG; if (!quickstart && !flog) logto |= LOGTO_STDOUT; } initsockets(bindaddr, nba, family); #ifndef NO_DSO if (ext) { void *handle = dlopen(ext, RTLD_NOW); if (!handle) error(0, "unable to load extension `%s': %s", ext, dlerror()); extinit = dlsym(handle, "rbldnsd_extension_init"); if (!extinit) error(0, "unable to find extension init routine in `%s'", ext); } #endif if (!user && !(uid = getuid())) user = "rbldns"; if (!user) p = NULL; else { if ((p = strchr(user, ':')) != NULL) *p++ = '\0'; if ((c = satoi(user)) >= 0) uid = c, gid = c; else { struct passwd *pw = getpwnam(user); if (!pw) error(0, "unknown user `%s'", user); uid = pw->pw_uid; gid = pw->pw_gid; endpwent(); } } if (!uid) error(0, "daemon should not run as root, specify -u option"); if (p) { if ((c = satoi(p)) >= 0) gid = c; else { struct group *gr = getgrnam(p); if (!gr) error(0, "unknown group `%s'", p); gid = gr->gr_gid; endgrent(); } p[-1] = ':'; } if (pidfile) { int fdpid; char buf[40]; c = sprintf(buf, "%ld\n", (long)getpid()); fdpid = open(pidfile, O_CREAT|O_WRONLY|O_TRUNC, 0644); if (fdpid < 0 || write(fdpid, buf, c) < c) error(errno, "unable to write pidfile"); close(fdpid); } if (rootdir && (chdir(rootdir) < 0 || chroot(rootdir) < 0)) error(errno, "unable to chroot to %.50s", rootdir); if (workdir && chdir(workdir) < 0) error(errno, "unable to chdir to %.50s", workdir); if (user) if (setgroups(1, &gid) < 0 || setgid(gid) < 0 || setuid(uid) < 0) error(errno, "unable to setuid(%d:%d)", (int)uid, (int)gid); for(c = 0; c < argc; ++c) zonelist = addzone(zonelist, argv[c]); init_zones_caches(zonelist); #ifndef NO_DSO if (extinit && extinit(extarg, zonelist) != 0) error(0, "unable to iniitialize extension `%s'", ext); #endif if (!quickstart && !do_reload(0)) error(0, "zone loading errors, aborting"); /* count number of zones */ for(c = 0, z = zonelist; z; z = z->z_next) ++c; numzones = c; #if STATS_IPC_IOVEC stats_iov = (struct iovec *)emalloc(numzones * sizeof(struct iovec)); for(c = 0, z = zonelist; z; z = z->z_next, ++c) { stats_iov[c].iov_base = (char*)&z->z_stats; stats_iov[c].iov_len = sizeof(z->z_stats); } #endif dslog(LOG_INFO, 0, "rbldnsd version %s started (%d socket(s), %d zone(s))", version, numsock, numzones); initialized = 1; if (cfd >= 0) { write(cfd, "", 1); close(cfd); close(0); close(2); if (!flog) close(1); setsid(); logto = LOGTO_SYSLOG; } if (quickstart) do_reload(0); /* only set "main" fork_on_reload after first reload */ fork_on_reload = forkon; } static void sighandler(int sig) { switch(sig) { case SIGHUP: signalled |= SIGNALLED_RELOG|SIGNALLED_RELOAD; break; case SIGALRM: #ifndef HAVE_SETITIMER alarm(recheck); #endif signalled |= SIGNALLED_RELOAD|SIGNALLED_SSTATS; break; #ifndef NO_STATS case SIGUSR1: signalled |= SIGNALLED_LSTATS|SIGNALLED_SSTATS; break; case SIGUSR2: signalled |= SIGNALLED_LSTATS|SIGNALLED_SSTATS|SIGNALLED_ZSTATS; break; #endif case SIGTERM: case SIGINT: signalled |= SIGNALLED_TERM; break; } } static sigset_t ssblock; /* signals to block during zone reload */ static sigset_t ssempty; /* empty set */ static void setup_signals(void) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = sighandler; sigemptyset(&ssblock); sigemptyset(&ssempty); sigaction(SIGHUP, &sa, NULL); sigaddset(&ssblock, SIGHUP); sigaction(SIGALRM, &sa, NULL); sigaddset(&ssblock, SIGALRM); #ifndef NO_STATS sigaction(SIGUSR1, &sa, NULL); sigaddset(&ssblock, SIGUSR1); sigaction(SIGUSR2, &sa, NULL); sigaddset(&ssblock, SIGUSR2); #endif sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); signal(SIGPIPE, SIG_IGN); /* in case logfile is FIFO */ } #ifndef NO_STATS struct dnsstats gstats; static struct dnsstats gptot; static time_t stats_time; static void dumpstats(void) { struct dnsstats tot; char name[DNS_MAXDOMAIN+1]; FILE *f; struct zone *z; f = fopen(statsfile, "a"); if (f) fprintf(f, "%ld", (long)time(NULL)); #define C ":%" PRI_DNSCNT tot = gstats; for(z = zonelist; z; z = z->z_next) { #define add(x) tot.x += z->z_stats.x add(b_in); add(b_out); add(q_ok); add(q_nxd); add(q_err); #undef add if (f) { dns_dntop(z->z_dn, name, sizeof(name)); #define delta(x) z->z_stats.x - z->z_pstats.x fprintf(f, " %s" C C C C C, name, delta(q_ok) + delta(q_nxd) + delta(q_err), delta(q_ok), delta(q_nxd), delta(b_in), delta(b_out)); #undef delta } if (stats_relative) z->z_pstats = z->z_stats; } if (f) { #define delta(x) tot.x - gptot.x fprintf(f, " *" C C C C C "\n", delta(q_ok) + delta(q_nxd) + delta(q_err), delta(q_ok), delta(q_nxd), delta(b_in), delta(b_out)); #undef delta fclose(f); } if (stats_relative) gptot = tot; #undef C } static void dumpstats_z(void) { FILE *f = fopen(statsfile, "a"); if (f) { fprintf(f, "%ld\n", (long)time(NULL)); fclose(f); } } static void logstats(int reset) { time_t t = time(NULL); time_t d = t - stats_time; struct dnsstats tot = gstats; char name[DNS_MAXDOMAIN+1]; struct zone *z; #define C(x) " " #x "=%" PRI_DNSCNT for(z = zonelist; z; z = z->z_next) { #define add(x) tot.x += z->z_stats.x add(b_in); add(b_out); add(q_ok); add(q_nxd); add(q_err); #undef add dns_dntop(z->z_dn, name, sizeof(name)); dslog(LOG_INFO, 0, "stats for %ldsecs zone %.60s:" C(tot) C(ok) C(nxd) C(err) C(in) C(out), (long)d, name, z->z_stats.q_ok + z->z_stats.q_nxd + z->z_stats.q_err, z->z_stats.q_ok, z->z_stats.q_nxd, z->z_stats.q_err, z->z_stats.b_in, z->z_stats.b_out); } dslog(LOG_INFO, 0, "stats for %ldsec:" C(tot) C(ok) C(nxd) C(err) C(in) C(out), (long)d, tot.q_ok + tot.q_nxd + tot.q_err, tot.q_ok, tot.q_nxd, tot.q_err, tot.b_in, tot.b_out); #undef C if (reset) { for(z = zonelist; z; z = z->z_next) { memset(&z->z_stats, 0, sizeof(z->z_stats)); memset(&z->z_pstats, 0, sizeof(z->z_pstats)); } memset(&gstats, 0, sizeof(gstats)); memset(&gptot, 0, sizeof(gptot)); stats_time = t; } } #if STATS_IPC_IOVEC # define ipc_read_stats(fd) readv(fd, stats_iov, numzones) # define ipc_write_stats(fd) writev(fd, stats_iov, numzones) #else static void ipc_read_stats(int fd) { struct zone *z; for(z = zonelist; z; z = z->z_next) if (read(fd, &z->z_stats, sizeof(z->z_stats)) <= 0) break; } static void ipc_write_stats(int fd) { const struct zone *z; for(z = zonelist; z; z = z->z_next) if (write(fd, &z->z_stats, sizeof(z->z_stats)) <= 0) break; } #endif #else # define ipc_read_stats(fd) # define ipc_write_stats(fd) #endif static void reopenlog(void) { if (logfile) { int fd; if (flog) fclose(flog); fd = open(logfile, O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK|O_LARGEFILE, 0644); if (fd < 0 || (flog = fdopen(fd, "a")) == NULL) { dslog(LOG_WARNING, 0, "error (re)opening logfile `%.50s': %s", logfile, strerror(errno)); if (fd >= 0) close(fd); flog = NULL; } } else if (flog && !flushlog) { /* log to stdout */ clearerr(flog); fflush(flog); } } static void check_expires(void) { struct zone *zone; time_t now = time(NULL); for (zone = zonelist; zone; zone = zone->z_next) { if (!zone->z_stamp) continue; if (zone->z_expires && zone->z_expires < now) { zlog(LOG_WARNING, zone, "zone data expired, zone will not be serviced"); zone->z_stamp = 0; } } } static int do_reload(int do_fork) { int r; char ibuf[150]; int ip; struct dataset *ds; struct zone *zone; pid_t cpid = 0; /* child pid; =0 to make gcc happy */ int cfd = 0; /* child stats fd; =0 to make gcc happy */ #ifndef NO_TIMES struct tms tms; clock_t utm, etm; #ifndef HZ static clock_t HZ; #endif #endif /* NO_TIMES */ ds = nextdataset2reload(NULL); if (!ds && call_hook(reload_check, (zonelist)) == 0) { check_expires(); return 1; /* nothing to reload */ } if (do_fork) { int pfd[2]; if (flog && !flushlog) fflush(flog); /* forking reload. if anything fails, just do a non-forking one */ if (pipe(pfd) < 0) do_fork = 0; else if ((cpid = fork()) < 0) { /* fork failed, close the pipe */ close(pfd[0]); close(pfd[1]); do_fork = 0; } else if (!cpid) { /* child, continue answering queries */ signal(SIGALRM, SIG_IGN); signal(SIGHUP, SIG_IGN); #ifndef NO_STATS signal(SIGUSR1, SIG_IGN); signal(SIGUSR2, SIG_IGN); #endif close(pfd[0]); /* set up the fd#1 to write stats later on SIGTERM */ if (pfd[1] != 1) { dup2(pfd[1], 1); close(pfd[1]); } fork_on_reload = -1; return 1; } else { close(pfd[1]); cfd = pfd[0]; } } #ifndef NO_TIMES #ifndef HZ if (!HZ) HZ = sysconf(_SC_CLK_TCK); #endif etm = times(&tms); utm = tms.tms_utime; #endif /* NO_TIMES */ r = 1; while(ds) { if (!loaddataset(ds)) r = 0; ds = nextdataset2reload(ds); } for (zone = zonelist; zone; zone = zone->z_next) { time_t stamp = 0; time_t expires = 0; const struct dssoa *dssoa = NULL; const struct dsns *dsns = NULL; unsigned nsttl = 0; struct dslist *dsl; for(dsl = zone->z_dsl; dsl; dsl = dsl->dsl_next) { const struct dataset *ds = dsl->dsl_ds; if (!ds->ds_stamp) { stamp = 0; break; } if (stamp < ds->ds_stamp) stamp = ds->ds_stamp; if (ds->ds_expires && (!expires || expires > ds->ds_expires)) expires = ds->ds_expires; if (!dssoa) dssoa = ds->ds_dssoa; if (!dsns) dsns = ds->ds_dsns, nsttl = ds->ds_nsttl; } zone->z_expires = expires; zone->z_stamp = stamp; if (!stamp) { zlog(LOG_WARNING, zone, "not all datasets are loaded, zone will not be serviced"); r = 0; } else if (!update_zone_soa(zone, dssoa) || !update_zone_ns(zone, dsns, nsttl, zonelist)) zlog(LOG_WARNING, zone, "NS or SOA RRs are too long, will be ignored"); } if (call_hook(reload, (zonelist)) != 0) r = 0; ip = ssprintf(ibuf, sizeof(ibuf), "zones reloaded"); #ifndef NO_TIMES etm = times(&tms) - etm; utm = tms.tms_utime - utm; # define sec(tm) (unsigned long)(tm/HZ), (unsigned long)((tm*100/HZ)%100) ip += ssprintf(ibuf + ip, sizeof(ibuf) - ip, ", time %lu.%lue/%lu.%luu sec", sec(etm), sec(utm)); # undef sec #endif /* NO_TIMES */ #ifndef NO_MEMINFO { struct mallinfo mi = mallinfo(); # define kb(x) ((mi.x + 512)>>10) ip += ssprintf(ibuf + ip, sizeof(ibuf) - ip, ", mem arena=%d free=%d mmap=%d Kb", kb(arena), kb(fordblks), kb(hblkhd)); # undef kb } #endif /* NO_MEMINFO */ dslog(LOG_INFO, 0, ibuf); check_expires(); /* ok, (something) loaded. */ if (do_fork) { /* here we should notify query-answering child (send SIGTERM to it), * and wait for it to complete. * Unfortunately at least on linux, the SIGTERM sometimes gets ignored * by the child process, so we're trying several times here, in a loop. */ int s, n; fd_set fds; struct timeval tv; for(n = 1; ++n;) { if (kill(cpid, SIGTERM) != 0) dslog(LOG_WARNING, 0, "kill(qchild): %s", strerror(errno)); FD_ZERO(&fds); FD_SET(cfd, &fds); tv.tv_sec = 0; tv.tv_usec = 500000; s = select(cfd+1, &fds, NULL, NULL, &tv); if (s > 0) break; dslog(LOG_WARNING, 0, "waiting for qchild process: %s, retrying", s ? strerror(errno) : "timeout"); } ipc_read_stats(cfd); close(cfd); wait(&s); } return r; } static void do_signalled(void) { sigprocmask(SIG_SETMASK, &ssblock, NULL); if (signalled & SIGNALLED_TERM) { if (fork_on_reload < 0) { /* this is a temp child; dump stats and exit */ ipc_write_stats(1); if (flog && !flushlog) fflush(flog); _exit(0); } dslog(LOG_INFO, 0, "terminating"); #ifndef NO_STATS if (statsfile) dumpstats(); logstats(0); if (statsfile) dumpstats_z(); #endif exit(0); } #ifndef NO_STATS if (signalled & SIGNALLED_SSTATS && statsfile) dumpstats(); if (signalled & SIGNALLED_LSTATS) { logstats(signalled & SIGNALLED_ZSTATS); if (signalled & SIGNALLED_ZSTATS && statsfile) dumpstats_z(); } #endif if (signalled & SIGNALLED_RELOG) reopenlog(); if (signalled & SIGNALLED_RELOAD) do_reload(fork_on_reload); signalled = 0; sigprocmask(SIG_SETMASK, &ssempty, NULL); } #ifndef NO_IPv6 static struct sockaddr_storage peer_sa; #else static struct sockaddr_in peer_sa; #endif static struct dnspacket pkt; static void request(int fd) { int q, r; socklen_t salen = sizeof(peer_sa); q = recvfrom(fd, (void*)pkt.p_buf, sizeof(pkt.p_buf), 0, (struct sockaddr *)&peer_sa, &salen); if (q <= 0) /* interrupted? */ return; pkt.p_peerlen = salen; r = replypacket(&pkt, q, zonelist); if (!r) return; if (flog) logreply(&pkt, flog, flushlog); /* finally, send a reply */ while(sendto(fd, (void*)pkt.p_buf, r, 0, (struct sockaddr *)&peer_sa, salen) < 0) if (errno != EINTR) break; } int main(int argc, char **argv) { init(argc, argv); setup_signals(); reopenlog(); #ifdef HAVE_SETITIMER if (recheck) { struct itimerval itv; itv.it_interval.tv_sec = itv.it_value.tv_sec = recheck; itv.it_interval.tv_usec = itv.it_value.tv_usec = 0; if (setitimer(ITIMER_REAL, &itv, NULL) < 0) error(errno, "unable to setitimer()"); } #else alarm(recheck); #endif #ifndef NO_STATS stats_time = time(NULL); if (statsfile) dumpstats_z(); #endif pkt.p_peer = (struct sockaddr *)&peer_sa; if (numsock == 1) { /* optimized case for only one socket */ int fd = sock[0]; for(;;) { if (signalled) do_signalled(); request(fd); } } else { /* several sockets, do select/poll loop */ #ifdef NO_POLL fd_set rfds; int maxfd = 0; int *fdi, *fde = sock + numsock; FD_ZERO(&rfds); for (fdi = sock; fdi < fde; ++fdi) { FD_SET(*fdi, &rfds); if (*fdi > maxfd) maxfd = *fdi; } ++maxfd; for(;;) { fd_set rfd = rfds; if (signalled) do_signalled(); if (select(maxfd, &rfd, NULL, NULL, NULL) <= 0) continue; for(fdi = sock; fdi < fde; ++fdi) { if (FD_ISSET(*fdi, &rfd)) request(*fdi); } } #else /* !NO_POLL */ struct pollfd pfda[MAXSOCK]; struct pollfd *pfdi, *pfde = pfda + numsock; int r; for(r = 0; r < numsock; ++r) { pfda[r].fd = sock[r]; pfda[r].events = POLLIN; } for(;;) { if (signalled) do_signalled(); r = poll(pfda, numsock, -1); if (r <= 0) continue; for(pfdi = pfda; pfdi < pfde; ++pfdi) { if (!(pfdi->revents & POLLIN)) continue; request(pfdi->fd); if (!--r) break; } } #endif /* NO_POLL */ } } void oom(void) { if (initialized) dslog(LOG_ERR, 0, "out of memory loading dataset"); else error(0, "out of memory"); } rbldnsd-0.997a/rbldnsd_zones.c0000664000175000017500000003524312120257742014526 0ustar mjtmjt/* Nameserver zones: structures and routines */ #include #include #include #include #include #include #include #include #include #include #include "rbldnsd.h" #include "istream.h" static struct dataset *ds_list; struct dataset *g_dsacl; static struct dataset *newdataset(char *spec) { /* type:file,file,file... */ struct dataset *ds, **dsp; char *f; struct dsfile **dsfp, *dsf; static const char *const delims = ",:"; const struct dstype **dstp; f = strchr(spec, ':'); if (!f) error(0, "invalid zone data specification `%.60s'", spec); *f++ = '\0'; for(dsp = &ds_list; (ds = *dsp) != NULL; dsp = &ds->ds_next) if (strcmp(ds->ds_type->dst_name, spec) == 0 && strcmp(ds->ds_spec, f) == 0) return ds; dstp = ds_types; while(strcmp(spec, (*dstp)->dst_name)) if (!*++dstp) error(0, "unknown dataset type `%.60s'", spec); ds = (struct dataset*)ezalloc(sizeof(struct dataset) + sizeof(struct mempool) + (*dstp)->dst_size); ds->ds_type = *dstp; ds->ds_mp = (struct mempool*)(ds + 1); ds->ds_dsd = (struct dsdata*)(ds->ds_mp + 1); ds->ds_spec = estrdup(f); ds->ds_next = NULL; *dsp = ds; dsfp = &ds->ds_dsf; for (f = strtok(f, delims); f; f = strtok(NULL, delims)) { dsf = tmalloc(struct dsfile); dsf->dsf_stamp = 0; dsf->dsf_name = estrdup(f); *dsfp = dsf; dsfp = &dsf->dsf_next; } *dsfp = NULL; if (!ds->ds_dsf) error(0, "missing filenames for %s", spec); return ds; } struct zone *newzone(struct zone **zonelist, unsigned char *dn, unsigned dnlen, struct mempool *mp) { struct zone *zone, **zonep, **lastzonep; zonep = zonelist; lastzonep = NULL; for (;;) { if (!(zone = *zonep)) { if (mp) zone = mp_talloc(mp, struct zone); else zone = tmalloc(struct zone); if (!zone) return NULL; memset(zone, 0, sizeof(*zone)); if (lastzonep) { zone->z_next = *lastzonep; *lastzonep = zone; } else *zonep = zone; memcpy(zone->z_dn, dn, dnlen); zone->z_dnlen = dnlen; zone->z_dnlab = dns_dnlabels(dn); zone->z_dslp = &zone->z_dsl; break; } else if (zone->z_dnlen == dnlen && memcmp(zone->z_dn, dn, dnlen) == 0) break; else { if (!lastzonep && zone->z_dnlen < dnlen && memcmp(dn + dnlen - zone->z_dnlen, zone->z_dn, zone->z_dnlen) == 0) lastzonep = zonep; zonep = &zone->z_next; } } return zone; } void connectdataset(struct zone *zone, struct dataset *ds, struct dslist *dsl) { dsl->dsl_next = NULL; *zone->z_dslp = dsl; zone->z_dslp = &dsl->dsl_next; dsl->dsl_ds = ds; dsl->dsl_queryfn = ds->ds_type->dst_queryfn; zone->z_dstflags |= ds->ds_type->dst_flags; } struct zone *addzone(struct zone *zonelist, const char *spec) { struct zone *zone; char *p; char name[DNS_MAXDOMAIN]; unsigned char dn[DNS_MAXDN]; unsigned dnlen; struct dataset *ds; p = strchr(spec, ':'); if (!p || p - spec >= DNS_MAXDOMAIN) error(0, "invalid zone spec `%.60s'", spec); memcpy(name, spec, p - spec); name[p - spec] = '\0'; dnlen = dns_ptodn(name, dn, sizeof(dn)); if (!dnlen) error(0, "invalid domain name `%.80s'", name); dns_dntol(dn, dn); p = estrdup(p+1); ds = newdataset(p); if (!dn[0]) { if (!isdstype(ds->ds_type, acl)) error(0, "missing domain name in `%.60s'", spec); if (g_dsacl) error(0, "global acl specified more than once"); g_dsacl = ds; } else { zone = newzone(&zonelist, dn, dnlen, NULL); if (isdstype(ds->ds_type, acl)) { if (zone->z_dsacl) error(0, "repeated ACL definition for zone `%.60s'", name); zone->z_dsacl = ds; } else connectdataset(zone, ds, tmalloc(struct dslist)); } free(p); return zonelist; } /* parse $SPECIAL construct */ static int ds_special(struct dataset *ds, char *line, struct dsctx *dsc) { char *w; if ((w = firstword_lc(line, "soa"))) { /* SOA record */ struct dssoa dssoa; unsigned char odn[DNS_MAXDN], pdn[DNS_MAXDN]; unsigned odnlen, pdnlen; if (isdstype(ds->ds_type, acl)) return 0; /* don't allow SOA for ACLs */ if (ds->ds_dssoa) return 1; /* ignore if already set */ if (!(w = parse_ttl(w, &dssoa.dssoa_ttl, ds->ds_ttl))) return 0; if (!(w = parse_dn(w, odn, &odnlen))) return 0; if (!(w = parse_dn(w, pdn, &pdnlen))) return 0; if (!(w = parse_uint32(w, &dssoa.dssoa_serial))) return 0; if (!(w = parse_time_nb(w, dssoa.dssoa_n+0))) return 0; if (!(w = parse_time_nb(w, dssoa.dssoa_n+4))) return 0; if (!(w = parse_time_nb(w, dssoa.dssoa_n+8))) return 0; if (!(w = parse_time_nb(w, dssoa.dssoa_n+12))) return 0; if (*w) return 0; dssoa.dssoa_odn = mp_memdup(ds->ds_mp, odn, odnlen); dssoa.dssoa_pdn = mp_memdup(ds->ds_mp, pdn, pdnlen); if (!dssoa.dssoa_odn || !dssoa.dssoa_pdn) return -1; ds->ds_dssoa = mp_talloc(ds->ds_mp, struct dssoa); if (!ds->ds_dssoa) return -1; *ds->ds_dssoa = dssoa; return 1; } if ((w = firstword_lc(line, "ns")) || (w = firstword_lc(line, "nameserver"))) { /* NS records */ unsigned char dn[DNS_MAXDN]; unsigned dnlen; struct dsns *dsns, **dsnslp; unsigned ttl; #ifndef INCOMPAT_0_99 #ifdef __GNUC__ /* some compilers don't understand #warning directive */ #warning NS record compatibility mode: remove for 1.0 final #endif struct dsns *dsns_first = 0; unsigned cnt; int newformat = 0; #endif if (isdstype(ds->ds_type, acl)) return 0; /* don't allow NSes for ACLs */ #ifndef INCOMPAT_0_99 if (ds->ds_nsflags & DSF_NEWNS) return 1; if (ds->ds_dsns) { dsns = ds->ds_dsns; while(dsns->dsns_next) dsns = dsns->dsns_next; dsnslp = &dsns->dsns_next; } else dsnslp = &ds->ds_dsns; cnt = 0; #else if (ds->ds_dsns) return 1; /* ignore 2nd nameserver line */ dsnslp = &ds->ds_dsns; #endif /*XXX parse options (AndrewSN suggested `-bloat') here */ if (!(w = parse_ttl(w, &ttl, ds->ds_ttl))) return 0; do { if (*w == '-') { /* skip nameservers that start with `-' aka 'commented-out' */ do ++w; while (*w && !ISSPACE(*w)); SKIPSPACE(w); #ifndef INCOMPAT_0_99 newformat = 1; #endif continue; } if (!(w = parse_dn(w, dn, &dnlen))) return 0; dsns = (struct dsns*) mp_alloc(ds->ds_mp, sizeof(struct dsns) + dnlen - 1, 1); if (!dsns) return -1; memcpy(dsns->dsns_dn, dn, dnlen); *dsnslp = dsns; dsnslp = &dsns->dsns_next; *dsnslp = NULL; #ifndef INCOMPAT_0_99 if (!cnt++) dsns_first = dsns; #endif } while(*w); #ifndef INCOMPAT_0_99 if (cnt > 1 || newformat) { ds->ds_nsflags |= DSF_NEWNS; ds->ds_dsns = dsns_first; /* throw away all NS recs */ } else if (dsns_first != ds->ds_dsns && !(ds->ds_nsflags & DSF_NSWARN)) { dswarn(dsc, "compatibility mode: specify all NS records in ONE line"); ds->ds_nsflags |= DSF_NSWARN; } if (!ds->ds_nsttl || ds->ds_nsttl > ttl) ds->ds_nsttl = ttl; #else ds->ds_nsttl = ttl; #endif return 1; } if ((w = firstword_lc(line, "ttl"))) { unsigned ttl; if (!(w = parse_ttl(w, &ttl, def_ttl))) return 0; if (*w) return 0; if (dsc->dsc_subset) dsc->dsc_subset->ds_ttl = ttl; else ds->ds_ttl = ttl; return 1; } if ((w = firstword_lc(line, "maxrange4"))) { unsigned r; int cidr; if (*w == '/') cidr = 1, ++w; else cidr = 0; if (!(w = parse_uint32(w, &r)) || *w || !r) return 0; if (cidr) { if (r > 32) return 0; r = ~ip4mask(r) + 1; } if (dsc->dsc_ip4maxrange && dsc->dsc_ip4maxrange < r) dswarn(dsc, "ignoring attempt to increase $MAXRANGE4 from %u to %u", dsc->dsc_ip4maxrange, r); else dsc->dsc_ip4maxrange = r; return 1; } if (((*(w = line) >= '0' && *w <= '9') || *w == '=') && ISSPACE(w[1])) { /* substitution vars */ unsigned n = w[0] == '=' ? SUBST_BASE_TEMPLATE : w[0] - '0'; if (dsc->dsc_subset) ds = dsc->dsc_subset; if (ds->ds_subst[n]) return 1; /* ignore second assignment */ w += 2; SKIPSPACE(w); if (!*w) return 0; if (!(ds->ds_subst[n] = mp_strdup(ds->ds_mp, w))) return 0; return 1; } if ((w = firstword_lc(line, "dataset"))) { if (!isdstype(ds->ds_type, combined)) return 0; /* $dataset is only allowed for combined dataset */ return ds_combined_newset(ds, w, dsc); } if ((w = firstword_lc(line, "timestamp"))) { time_t stamp, expires; if (!(w = parse_timestamp(w, &stamp))) return 0; if (!*w) expires = 0; else if (*w == '+') { /* relative */ unsigned n; if (!(w = parse_time(w + 1, &n)) || *w) return 0; if (!stamp || !n) return 0; expires = stamp + n; if (expires < 0 || expires - (time_t)n != stamp) return 0; } else { if (!(w = parse_timestamp(w, &expires)) || *w) return 0; } if (stamp) { time_t now = time(NULL); if (stamp > now) { dslog(LOG_ERR, dsc, "data timestamp is %u sec in the future, aborting loading", (unsigned)(stamp - now)); return -1; } } if (expires && (!ds->ds_expires || ds->ds_expires > expires)) ds->ds_expires = expires; return 1; } return 0; } static int readdslines(struct istream *sp, struct dataset *ds, struct dsctx *dsc) { char *line, *eol; int r; int noeol = 0; struct dataset *dscur = ds; ds_linefn_t *linefn = dscur->ds_type->dst_linefn; while((r = istream_getline(sp, &line, '\n')) > 0) { eol = line + r - 1; if (noeol) { if (*eol == '\n') noeol = 0; continue; } ++dsc->dsc_lineno; if (*eol == '\n') --eol; else { dswarn(dsc, "long line (truncated)"); noeol = 1; /* mark it to be read above */ } SKIPSPACE(line); while(eol >= line && ISSPACE(*eol)) --eol; eol[1] = '\0'; if (line[0] == '$' || ((ISCOMMENT(line[0]) || line[0] == ':') && line[1] == '$')) { int r = ds_special(ds, line[0] == '$' ? line + 1 : line + 2, dsc); if (!r) dswarn(dsc, "invalid or unrecognized special entry"); else if (r < 0) return 0; dscur = dsc->dsc_subset ? dsc->dsc_subset : ds; linefn = dscur->ds_type->dst_linefn; continue; } if (line[0] && !ISCOMMENT(line[0])) if (!linefn(dscur, line, dsc)) return 0; } if (r < 0) return -1; if (noeol) dslog(LOG_WARNING, dsc, "incomplete last line (ignored)"); return 1; } static void freedataset(struct dataset *ds) { ds->ds_type->dst_resetfn(ds->ds_dsd, 0); mp_free(ds->ds_mp); ds->ds_dssoa = NULL; ds->ds_ttl = def_ttl; ds->ds_dsns = NULL; ds->ds_nsttl = 0; ds->ds_expires = 0; #ifndef INCOMPAT_0_99 ds->ds_nsflags = 0; #endif memset(ds->ds_subst, 0, sizeof(ds->ds_subst)); } int loaddataset(struct dataset *ds) { struct dsfile *dsf; time_t stamp = 0; struct istream is; int fd; int r; struct stat st0, st1; struct dsctx dsc; freedataset(ds); memset(&dsc, 0, sizeof(dsc)); dsc.dsc_ds = ds; for(dsf = ds->ds_dsf; dsf; dsf = dsf->dsf_next) { dsc.dsc_fname = dsf->dsf_name; fd = open(dsf->dsf_name, O_RDONLY); if (fd < 0 || fstat(fd, &st0) < 0) { dslog(LOG_ERR, &dsc, "unable to open file: %s", strerror(errno)); if (fd >= 0) close(fd); goto fail; } ds->ds_type->dst_startfn(ds); istream_init_fd(&is, fd); if (istream_compressed(&is)) { if (nouncompress) { dslog(LOG_ERR, &dsc, "file is compressed, decompression disabled"); r = 0; } else { #ifdef NO_ZLIB dslog(LOG_ERR, &dsc, "file is compressed, decompression is not compiled in"); r = 0; #else r = istream_uncompress_setup(&is); /* either 1 or -1 but not 0 */ #endif } } else r = 1; if (r > 0) r = readdslines(&is, ds, &dsc); if (r > 0) r = fstat(fd, &st1) < 0 ? -1 : 1; dsc.dsc_lineno = 0; istream_destroy(&is); close(fd); if (!r) goto fail; if (r < 0) { dslog(LOG_ERR, &dsc, "error reading file: %s", strerror(errno)); goto fail; } if (st0.st_mtime != st1.st_mtime || st0.st_size != st1.st_size) { dslog(LOG_ERR, &dsc, "file changed while we where reading it, data load aborted"); dslog(LOG_ERR, &dsc, "do not write data files directly, " "use temp file and rename(2) instead"); goto fail; } dsf->dsf_stamp = st0.st_mtime; dsf->dsf_size = st0.st_size; if (dsf->dsf_stamp > stamp) stamp = dsf->dsf_stamp; } ds->ds_stamp = stamp; dsc.dsc_fname = NULL; ds->ds_type->dst_finishfn(ds, &dsc); return 1; fail: freedataset(ds); for (dsf = ds->ds_dsf; dsf; dsf = dsf->dsf_next) dsf->dsf_stamp = 0; ds->ds_stamp = 0; return 0; } /* find next dataset which needs reloading */ struct dataset *nextdataset2reload(struct dataset *ds) { struct dsfile *dsf; for (ds = ds ? ds->ds_next : ds_list; ds; ds = ds->ds_next) for(dsf = ds->ds_dsf; dsf; dsf = dsf->dsf_next) { struct stat st; if (stat(dsf->dsf_name, &st) < 0) return ds; if (dsf->dsf_stamp != st.st_mtime || dsf->dsf_size != st.st_size) return ds; } return NULL; } #ifndef NO_MASTER_DUMP void dumpzone(const struct zone *z, FILE *f) { const struct dslist *dsl; { /* zone header */ char name[DNS_MAXDOMAIN+1]; const unsigned char *const *nsdna = z->z_nsdna; const struct dssoa *dssoa = z->z_dssoa; unsigned nns = z->z_nns; unsigned n; dns_dntop(z->z_dn, name, sizeof(name)); fprintf(f, "$ORIGIN\t%s.\n", name); if (z->z_dssoa) { fprintf(f, "@\t%u\tSOA", dssoa->dssoa_ttl); dns_dntop(dssoa->dssoa_odn, name, sizeof(name)); fprintf(f, "\t%s.", name); dns_dntop(dssoa->dssoa_pdn, name, sizeof(name)); fprintf(f, "\t%s.", name); fprintf(f, "\t(%u %u %u %u %u)\n", dssoa->dssoa_serial ? dssoa->dssoa_serial : z->z_stamp, unpack32(dssoa->dssoa_n+0), unpack32(dssoa->dssoa_n+4), unpack32(dssoa->dssoa_n+8), unpack32(dssoa->dssoa_n+12)); } for(n = 0; n < nns; ++n) { dns_dntop(nsdna[n], name, sizeof(name)); fprintf(f, "\t%u\tNS\t%s.\n", z->z_nsttl, name); } } for (dsl = z->z_dsl; dsl; dsl = dsl->dsl_next) { fprintf(f, "$TTL %u\n", dsl->dsl_ds->ds_ttl); dsl->dsl_ds->ds_type->dst_dumpfn(dsl->dsl_ds, z->z_dn, f); } } #endif rbldnsd-0.997a/rbldnsd_packet.c0000664000175000017500000007563212130046505014637 0ustar mjtmjt/* DNS packet handling routines for rbldnsd */ #include #include #include #include #include #include #include #include #include #include "rbldnsd.h" #ifndef NO_IPv6 # ifndef NI_MAXHOST # define IPSIZE 1025 # else # define IPSIZE NI_MAXHOST # endif #else # define IPSIZE 16 #endif #define MAX_GLUE (MAX_NS*2) static int addrr_soa(struct dnspacket *pkt, const struct zone *zone, int auth); static int addrr_ns(struct dnspacket *pkt, const struct zone *zone, int auth); static int version_req(struct dnspacket *pkt, const struct dnsquery *qry); /* DNS packet: * bytes comment */ /* 0:1 identifier (client supplied) */ #define p_id1 0 #define p_id2 1 /* 2 flags1 */ #define p_f1 2 #define pf1_qr 0x80 /* query response flag */ #define pf1_opcode 0x78 /* opcode, 0 = query */ #define pf1_aa 0x04 /* auth answer */ #define pf1_tc 0x02 /* truncation flag */ #define pf1_rd 0x01 /* recursion desired (may be set in query) */ /* 3 flags2 */ #define p_f2 3 #define pf2_ra 0x80 /* recursion available */ #define pf2_z 0x70 /* reserved */ #define pf2_rcode 0x0f /* response code */ /* 0 ok, 1 format error, 2 servfail, 3 nxdomain, 4 notimpl, 5 refused */ /* 4:5 qdcount (numqueries) */ #define p_qdcnt1 4 #define p_qdcnt2 5 /* 6:7 ancount (numanswers) */ #define p_ancnt1 6 #define p_ancnt2 7 /* 8:9 nscount (numauthority) */ #define p_nscnt1 8 #define p_nscnt2 9 /* 10:11 arcount (numadditional) */ #define p_arcnt1 10 #define p_arcnt2 11 #define p_hdrsize 12 /* size of packet header */ /* next is a DN name, a series of labels with first byte is label's length, * terminated by zero-length label (i.e. at least one zero byte is here) * next two bytes are query type (A, SOA etc) * next two bytes are query class (IN, HESIOD etc) */ static int parsequery(struct dnspacket *pkt, unsigned qlen, struct dnsquery *qry) { /* parsing incoming query. Untrusted data read directly from the network. * pkt->p_buf is a buffer - data that was read (DNS_EDNS0_MAXPACKET max). * qlen is number of bytes actually read (packet length) * first p_hdrsize bytes is header, next is query DN, * next are QTYPE and QCLASS (2x2 bytes). * If NSCNT==0 && ARCNT==1, and an OPT record comes after the query, * EDNS0 packet size gets extracted from the OPT record. * Rest of data is ignored. * Returns true on success, 0 on failure. * Upon successeful return, pkt->p_sans = pkt->p_cur points to the end of * the query section (where our answers will be placed), and * pkt->p_endp is initialized to point to the real end of answers. * Real end of answers is: * for non-EDNS0-aware clients it's pkt->p_buf+DNS_MAXPACKET, and * if a vaild EDNS0 UDPsize is given, it will be pkt->p_buf+UDPsize-11, * with the 11 bytes needed for a minimal OPT record. * In replypacket() we check whenever all our answers fits in standard * UDP buffer size (DNS_MAXPACKET), and if not (which means we're replying * to EDNS0-aware client due to the above rules), we just add proper OPT * record at the end. */ register unsigned const char *q = pkt->p_buf; register unsigned const char *x, *e; register unsigned char *d; unsigned qlab; /* number of labels in qDN */ x = q + qlen - 5; /* last possible qDN zero terminator position */ /* qlen isn't needed anymore, it'll be used as length of qDN below */ if (q + p_hdrsize > x) /* short packet (header isn't here) */ return 0; else if (q + p_hdrsize + DNS_MAXDN <= x) x = q + p_hdrsize + DNS_MAXDN - 1; /* constrain query DN to DNS_MAXDN */ if (q[p_f1] & pf1_qr) /* response packet?! */ return 0; if (q[p_qdcnt1] || q[p_qdcnt2] != 1) /* qdcount should be == 1 */ return 0; /* parse and lowercase query DN, count and init labels */ qlab = 0; /* number of labels so far */ q += p_hdrsize; /* start of qDN */ d = qry->q_dn; /* destination lowercased DN */ while((*d = *q) != 0) { /* loop by DN lables */ qry->q_lptr[qlab++] = d++; /* another label */ e = q + *q + 1; /* end of this label */ if (*q > DNS_MAXLABEL /* too long label? */ || e > x) /* or it ends past packet? */ return 0; /* lowercase it */ ++q; /* length */ do *d++ = dns_dnlc(*q); /* lowercase each char */ while(++q < e); /* until end of label */ } /* d points to qDN terminator now */ qry->q_dnlen = d - qry->q_dn + 1; qry->q_dnlab = qlab; /* q is end of qDN. decode qtype and qclass, and prepare for an answer */ ++q; qry->q_type = ((unsigned)(q[0]) << 8) | q[1]; qry->q_class = ((unsigned)(q[2]) << 8) | q[3]; q += 4; pkt->p_sans = (unsigned char *)q; /* answers will start here */ pkt->p_cur = (unsigned char *)q; /* and current answer pointer is here */ d = pkt->p_buf; if (q < x && d[p_nscnt1] == 0 && d[p_nscnt2] == 0 && d[p_arcnt1] == 0 && d[p_arcnt2] == 1 && q[0] == 0 /* empty DN */ && q[1] == (DNS_T_OPT>>8) && q[2] == (DNS_T_OPT&255)) { qlen = (((unsigned)q[3]) << 8) | q[4]; /* 11 bytes are needed to encode minimal EDNS0 OPT record */ if (qlen < DNS_MAXPACKET + 11) qlen = DNS_MAXPACKET; else if (qlen > sizeof(pkt->p_buf) - 11) qlen = sizeof(pkt->p_buf) - 11; else qlen -= 11; pkt->p_endp = d + qlen; } else pkt->p_endp = d + DNS_MAXPACKET; return 1; } #define digit(c) ((c) >= '0' && (c) <= '9') #define d2n(c) ((unsigned)((c) - '0')) static int dntoip4addr(const unsigned char *q, ip4addr_t *ap) { unsigned o, a = 0; #define oct(q,o) \ switch(*q) { \ case 1: \ if (!digit(q[1])) \ return 0; \ o = d2n(q[1]); \ break; \ case 2: \ if (!digit(q[1]) || !digit(q[2])) \ return 0; \ o = d2n(q[1]) * 10 + d2n(q[2]); \ break; \ case 3: \ if (!digit(q[1]) || !digit(q[2]) || !digit(q[3])) \ return 0; \ o = d2n(q[1]) * 100 + d2n(q[2]) * 10 + d2n(q[3]); \ if (o > 255) return 0; \ break; \ default: return 0; \ } oct(q,o); a |= o; q += *q + 1; oct(q,o); a |= o << 8; q += *q + 1; oct(q,o); a |= o << 16; q += *q + 1; oct(q,o); a |= o << 24; *ap = a; return 1; #undef oct } static int dntoip6addr(const unsigned char *q, ip6oct_t ap[IP6ADDR_FULL]) { unsigned o1, o2, c; for(c = IP6ADDR_FULL; c; ) { if (*q++ != 1) return 0; if (digit(*q)) o2 = d2n(*q++); else if (*q >= 'a' && *q <= 'f') o2 = *q++ - 'a' + 10; else return 0; if (*q++ != 1) return 0; if (digit(*q)) o1 = d2n(*q++); else if (*q >= 'a' && *q <= 'f') o1 = *q++ - 'a' + 10; else return 0; ap[--c] = (o1 << 4) | o2; } return 1; } static const ip6oct_t ip6mapped_pfx[12] = "\0\0\0\0\0\0\0\0" "\377\377\377\377"; /* parse DN (as in 4.3.2.1.in-addr.arpa) to ip4addr_t (4 octets 0..255). * parse DN (as in 0.1.2.3.4.5...f.base.dn) to ip6 address (32 nibbles 0..f) */ static void dntoip(struct dnsqinfo *qi, int flags) { const unsigned char *q = qi->qi_dn; unsigned qlab = qi->qi_dnlab; qi->qi_ip4valid = qlab == 4 && dntoip4addr(q, &qi->qi_ip4); if (qi->qi_ip4valid) { if (flags & DSTF_IP6REV) { /* construct IP4MAPPED address */ memcpy(qi->qi_ip6, ip6mapped_pfx, sizeof(ip6mapped_pfx)); PACK32(qi->qi_ip6 + sizeof(ip6mapped_pfx), qi->qi_ip4); qi->qi_ip6valid = 1; } } else { qi->qi_ip6valid = qlab == 32 && qi->qi_dnlen0 == 64 && dntoip6addr(q, qi->qi_ip6); if (flags & DSTF_IP4REV && memcmp(qi->qi_ip6, ip6mapped_pfx, sizeof(ip6mapped_pfx)) == 0) { /* construct IP4 from IP4MAPPED */ qi->qi_ip4 = unpack32(qi->qi_ip6 + sizeof(ip6mapped_pfx)); qi->qi_ip4valid = 1; } } } const struct zone * findqzone(const struct zone *zone, unsigned dnlen, unsigned dnlab, unsigned char *const *const dnlptr, struct dnsqinfo *qi) { const unsigned char *q; for(;; zone = zone->z_next) { if (!zone) return NULL; if (zone->z_dnlab > dnlab) continue; q = dnlptr[dnlab - zone->z_dnlab]; if (memcmp(zone->z_dn, q, zone->z_dnlen - 1)) continue; break; } qi->qi_dn = dnlptr[0]; qi->qi_dnlptr = dnlptr; qi->qi_dnlab = dnlab - zone->z_dnlab; qi->qi_dnlen0 = dnlen - zone->z_dnlen; if (zone->z_dstflags & (DSTF_IP4REV|DSTF_IP6REV)) /* IP address */ dntoip(qi, zone->z_dstflags); return zone; } #ifdef NO_STATS # define do_stats(x) #else # define do_stats(x) x #endif /* construct reply to a query. */ int replypacket(struct dnspacket *pkt, unsigned qlen, struct zone *zone) { struct dnsquery qry; /* query structure */ struct dnsqinfo qi; /* query info structure */ unsigned char *h = pkt->p_buf; /* packet's header */ const struct dslist *dsl; int found; extern int lazy; /*XXX hack*/ pkt->p_substrr = 0; /* check global ACL */ if (g_dsacl && g_dsacl->ds_stamp) { found = ds_acl_query(g_dsacl, pkt); if (found & NSQUERY_IGNORE) { do_stats(gstats.q_err += 1; gstats.b_in += qlen); return 0; } } else found = 0; if (!parsequery(pkt, qlen, &qry)) { do_stats(gstats.q_err += 1; gstats.b_in += qlen); return 0; } /* from now on, we see (almost?) valid dns query, should reply */ #define setnonauth(h) (h[p_f1] &= ~pf1_aa) #define _refuse(code,lab) \ do { setnonauth(h); h[p_f2] = (code); goto lab; } while(0) #define refuse(code) _refuse(code, err_nz) #define rlen() pkt->p_cur - h /* construct reply packet */ /* identifier already in place */ /* flags will be set up later */ /* qdcount already set up in query */ /* all counts (qd,an,ns,ar) are <= 255 due to size limit */ h[p_ancnt1] = h[p_ancnt2] = 0; h[p_nscnt1] = h[p_nscnt2] = 0; h[p_arcnt1] = h[p_arcnt2] = 0; if (h[p_f1] & (pf1_opcode | pf1_aa | pf1_tc | pf1_qr)) { h[p_f1] = pf1_qr; refuse(DNS_R_NOTIMPL); } h[p_f1] |= pf1_qr; if (qry.q_class == DNS_C_IN) h[p_f1] |= pf1_aa; else if (qry.q_class != DNS_C_ANY) { if (version_req(pkt, &qry)) { do_stats(gstats.q_ok += 1; gstats.b_in += qlen; gstats.b_out += rlen()); return rlen(); } else refuse(DNS_R_REFUSED); } switch(qry.q_type) { case DNS_T_ANY: qi.qi_tflag = NSQUERY_ANY; break; case DNS_T_A: qi.qi_tflag = NSQUERY_A; break; case DNS_T_TXT: qi.qi_tflag = NSQUERY_TXT; break; case DNS_T_NS: qi.qi_tflag = NSQUERY_NS; break; case DNS_T_SOA: qi.qi_tflag = NSQUERY_SOA; break; case DNS_T_MX: qi.qi_tflag = NSQUERY_MX; break; default: if (qry.q_type >= DNS_T_TSIG) refuse(DNS_R_NOTIMPL); qi.qi_tflag = NSQUERY_OTHER; } qi.qi_tflag |= found; h[p_f2] = DNS_R_NOERROR; /* find matching zone */ zone = (struct zone*) findqzone(zone, qry.q_dnlen, qry.q_dnlab, qry.q_lptr, &qi); if (!zone) /* not authoritative */ refuse(DNS_R_REFUSED); /* found matching zone */ #undef refuse #define refuse(code) _refuse(code, err_z) do_stats(zone->z_stats.b_in += qlen); if (zone->z_dsacl && zone->z_dsacl->ds_stamp) { qi.qi_tflag |= ds_acl_query(zone->z_dsacl, pkt); if (qi.qi_tflag & NSQUERY_IGNORE) { do_stats(gstats.q_err += 1); return 0; } } if (!zone->z_stamp) /* do not answer if not loaded */ refuse(DNS_R_SERVFAIL); if (qi.qi_tflag & NSQUERY_REFUSE) refuse(DNS_R_REFUSED); if ((found = call_hook(query_access, (pkt->p_peer, zone, &qi)))) { if (found < 0) return 0; refuse(DNS_R_REFUSED); } if (qi.qi_dnlab == 0) { /* query to base zone: SOA and NS */ found = NSQUERY_FOUND; /* NS and SOA with auth=0 will only touch answer section */ if ((qi.qi_tflag & NSQUERY_SOA) && !addrr_soa(pkt, zone, 0)) found = 0; else if ((qi.qi_tflag & NSQUERY_NS) && !addrr_ns(pkt, zone, 0)) found = 0; if (!found) { pkt->p_cur = pkt->p_sans; h[p_ancnt2] = h[p_nscnt2] = 0; refuse(DNS_R_REFUSED); } } else /* not to zone base DN */ found = 0; /* search the datasets */ for(dsl = zone->z_dsl; dsl; dsl = dsl->dsl_next) found |= dsl->dsl_queryfn(dsl->dsl_ds, &qi, pkt); if (found & NSQUERY_ADDPEER) { #ifdef NO_IPv6 addrr_a_txt(pkt, qi.qi_tflag, pkt->p_substrr, inet_ntoa(((struct sockaddr_in*)pkt->p_peer)->sin_addr), pkt->p_substds); #else char subst[IPSIZE]; if (getnameinfo(pkt->p_peer, pkt->p_peerlen, subst, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) != 0) subst[0] = '\0'; addrr_a_txt(pkt, qi.qi_tflag, pkt->p_substrr, subst, pkt->p_substds); #endif } /* now complete the reply: add AUTH etc sections */ /* addrr_ns(auth=1) should be called last as it fills in * both AUTH and ADDITIONAL sections */ if (!found) { /* negative result */ addrr_soa(pkt, zone, 1); /* add SOA if any to AUTHORITY */ h[p_f2] = DNS_R_NXDOMAIN; do_stats(zone->z_stats.q_nxd += 1); } else { if (!h[p_ancnt2]) { /* positive reply, no answers */ addrr_soa(pkt, zone, 1); /* add SOA if any to AUTHORITY */ } else if (zone->z_nns && /* (!(qi.qi_tflag & NSQUERY_NS) || qi.qi_dnlab) && */ !lazy) addrr_ns(pkt, zone, 1); /* add nameserver records to positive reply */ do_stats(zone->z_stats.q_ok += 1); } (void)call_hook(query_result, (pkt->p_peer, zone, &qi, found)); if (rlen() > DNS_MAXPACKET) { /* add OPT record for long replies */ /* as per parsequery(), we always have 11 bytes for minimal OPT record at * the end of our reply packet, OR rlen() does not exceed DNS_MAXPACKET */ h[p_arcnt2] += 1; /* arcnt is limited to 254 records */ h = pkt->p_cur; *h++ = 0; /* empty (root) DN */ PACK16S(h, DNS_T_OPT); PACK16S(h, DNS_EDNS0_MAXPACKET); *h++ = 0; *h++ = 0; /* RCODE and version */ *h++ = 0; *h++ = 0; /* rest of the TTL field */ *h++ = 0; *h++ = 0; /* RDLEN */ pkt->p_cur = h; h = pkt->p_buf; /* restore for rlen() to work */ } do_stats(zone->z_stats.b_out += rlen()); return rlen(); err_nz: do_stats(gstats.q_err += 1; gstats.b_in += qlen; gstats.b_out += rlen()); return rlen(); err_z: do_stats(zone->z_stats.q_err += 1; zone->z_stats.b_out += rlen()); return rlen(); } #define fit(pkt, c, bytes) ((c) + (bytes) <= (pkt)->p_endp) /* DN compression pointers/structures */ /* We store pre-computed RRs for NS and SOA records in special * cache buffers referenced to by zone structure. * * The precomputed RRs consists of ready-to-be-sent data (with * all record types/classes, TTLs, and data in place), modulo * compressed DN backreferences. When this cached data will be * copied into answer packet, we'll need to adjust "jumps" in * DN name compressions to reflect actual position of the data * in answer packet. * * Cache data is calculated as if it where inside some packet, * where it's start is exactly at the beginning of cached/precomputed * record, and with zone's base DN (with class and type) being in * question section immediately BEFORE the data (i.e. before our * "virtual packet"). So some DN compression offsets (pointers) * will be negative (referring to the query section of actual answer, * as zone base DN will be present in answer anyway), and some will * be positive (referring to this very record in actual answer). */ struct dnjump { /* one DN "jump": */ unsigned char *pos; /* position in precomputed packet where the jump is */ int off; /* jump offset relative to beginning of the RRs */ }; struct dnptr { /* domain pointer for DN compression */ const unsigned char *dn; /* actual (complete) domain name */ int off; /* jump offset relative to start of RRs */ }; struct dncompr { /* DN compression structure */ struct dnptr ptr[256]; /* array of all known domain names */ struct dnptr *lptr; /* last unused slot in ptr[] */ unsigned char *buf; /* buffer for the cached RRs */ unsigned char *bend; /* pointer to past the end of buf */ struct dnjump *jump; /* current jump ptr (array of jumps) */ }; #define CACHEBUF_SIZE (DNS_MAXPACKET-p_hdrsize-4) /* maxpacket minus header minus (class+type) */ /* initialize compression/cache structures */ static unsigned char * dnc_init(struct dncompr *compr, unsigned char *buf, unsigned bufsize, struct dnjump *jump, const unsigned char *dn) { struct dnptr *ptr; unsigned char *cpos; compr->buf = buf; compr->bend = buf + bufsize; compr->jump = jump; cpos = buf - dns_dnlen(dn) - 4; /* current position: qDN BEFORE the RRs */ ptr = compr->ptr; while(*dn) { ptr->dn = dn; ptr->off = cpos - buf; ++ptr; cpos += *dn + 1; dn += *dn + 1; } compr->lptr = ptr; return cpos + 5; } /* add one DN into cache, adjust compression pointers and current pointer */ static unsigned char * dnc_add(struct dncompr *compr, unsigned char *cpos, const unsigned char *dn) { struct dnptr *ptr; while(*dn) { /* lookup DN in already stored names */ for(ptr = compr->ptr; ptr < compr->lptr; ++ptr) { if (!dns_dnequ(ptr->dn, dn)) continue; /* found one, make a jump to it */ if (cpos + 2 >= compr->bend) return NULL; compr->jump->pos = cpos; compr->jump->off = ptr->off; ++compr->jump; return cpos + 2; } /* not found, add it to the list of known DNs... */ if (cpos + *dn + 1 >= compr->bend) return NULL; /* does not fit */ if (ptr < compr->ptr + sizeof(compr->ptr) / sizeof(compr->ptr[0])) { ptr->dn = dn; ptr->off = cpos - compr->buf; ++compr->lptr; } /* ...and add one label into the "packet" */ memcpy(cpos, dn, *dn + 1); cpos += *dn + 1; dn += *dn + 1; } if (cpos + 1 >= compr->bend) return NULL; *cpos++ = '\0'; return cpos; } /* finalize RRs: remember it's size and number of jumps */ static void dnc_finish(struct dncompr *compr, unsigned char *cptr, unsigned *sizep, struct dnjump **jendp) { *sizep = cptr - compr->buf; *jendp = compr->jump; } /* place pre-cached RRs into the packet, adjusting jumps */ static int dnc_final(struct dnspacket *pkt, const unsigned char *data, unsigned dsize, const struct dnjump *jump, const struct dnjump *jend) { const unsigned qoff = (pkt->p_sans - pkt->p_buf) + 0xc000; const unsigned coff = (pkt->p_cur - pkt->p_buf) + 0xc000; unsigned pos; if (!fit(pkt, pkt->p_cur, dsize)) return 0; /* first, adjust offsets - in cached data anyway */ while(jump < jend) { /* jump to either query section or this very RRs */ pos = jump->off + (jump->off < 0 ? qoff : coff); PACK16(jump->pos, pos); ++jump; } /* and finally, copy the RRs into answer packet */ memcpy(pkt->p_cur, data, dsize); pkt->p_cur += dsize; return 1; } struct zonesoa { /* cached SOA RR */ unsigned size; /* size of the RR */ unsigned ttloff; /* offset of the TTL field */ const unsigned char *minttl; /* pointer to minttl in data */ struct dnjump jump[3]; /* jumps to fix: 3 max (qdn, odn, pdn) */ struct dnjump *jend; /* last jump */ unsigned char data[CACHEBUF_SIZE]; }; struct zonens { /* cached NS RRs */ unsigned nssize; /* size of all NS RRs */ unsigned tsize; /* size of NS+glue recs */ struct dnjump jump[MAX_NS*2+MAX_GLUE];/* jumps: for qDNs and for NSes */ struct dnjump *nsjend; /* last NS jump */ struct dnjump *tjend; /* last glue jump */ unsigned char data[CACHEBUF_SIZE]; }; void init_zones_caches(struct zone *zonelist) { while(zonelist) { if (!zonelist->z_dsl) { char name[DNS_MAXDOMAIN]; dns_dntop(zonelist->z_dn, name, sizeof(name)); error(0, "missing data for zone `%s'", name); } zonelist->z_zsoa = tmalloc(struct zonesoa); /* for NS RRs, we allocate MAX_NS caches: * each stores one variant of NS rotation */ zonelist->z_zns = (struct zonens *)emalloc(sizeof(struct zonens) * MAX_NS); zonelist = zonelist->z_next; } } /* update SOA RR cache */ int update_zone_soa(struct zone *zone, const struct dssoa *dssoa) { struct zonesoa *zsoa; unsigned char *cpos; struct dncompr compr; unsigned t; unsigned char *sizep; zsoa = zone->z_zsoa; zsoa->size = 0; if (!(zone->z_dssoa = dssoa)) return 1; cpos = dnc_init(&compr, zsoa->data, sizeof(zsoa->data), zsoa->jump, zone->z_dn); cpos = dnc_add(&compr, cpos, zone->z_dn); PACK16S(cpos, DNS_T_SOA); PACK16S(cpos, DNS_C_IN); zsoa->ttloff = cpos - compr.buf; PACK32S(cpos, dssoa->dssoa_ttl); sizep = cpos; cpos += 2; cpos = dnc_add(&compr, cpos, dssoa->dssoa_odn); if (!cpos) return 0; cpos = dnc_add(&compr, cpos, dssoa->dssoa_pdn); if (!cpos) return 0; t = dssoa->dssoa_serial ? dssoa->dssoa_serial : zone->z_stamp; PACK32S(cpos, t); memcpy(cpos, dssoa->dssoa_n, 16); cpos += 16; zsoa->minttl = cpos - 4; t = cpos - sizep - 2; PACK16(sizep, t); dnc_finish(&compr, cpos, &zsoa->size, &zsoa->jend); return 1; } static int addrr_soa(struct dnspacket *pkt, const struct zone *zone, int auth) { const struct zonesoa *zsoa = zone->z_zsoa; unsigned char *c = pkt->p_cur; if (!zone->z_dssoa || !zsoa->size) { if (!auth) setnonauth(pkt->p_buf); return 0; } if (!dnc_final(pkt, zsoa->data, zsoa->size, zsoa->jump, zsoa->jend)) { if (!auth) setnonauth(pkt->p_buf); /* non-auth answer as we can't fit the record */ return 0; } /* for AUTHORITY section for NXDOMAIN etc replies, use minttl as TTL */ if (auth) memcpy(c + zsoa->ttloff, zsoa->minttl, 4); pkt->p_buf[auth ? p_nscnt2 : p_ancnt2]++; return 1; } static unsigned char * find_glue(struct zone *zone, const unsigned char *nsdn, struct dnspacket *pkt, const struct zone *zonelist) { struct dnsqinfo qi; unsigned lab; unsigned char dnbuf[DNS_MAXDN], *dp; unsigned char *dnlptr[DNS_MAXLABELS]; const struct dslist *dsl; const struct zone *qzone; /* lowercase the nsdn and find label pointers */ lab = 0; dp = dnbuf; while((*dp = *nsdn)) { const unsigned char *e = nsdn + *nsdn + 1; dnlptr[lab++] = dp++; while(++nsdn < e) *dp++ = dns_dnlc(*nsdn); } qzone = findqzone(zonelist, dp - dnbuf + 1, lab, dnlptr, &qi); if (!qzone) return NULL; /* pefrorm fake query */ qi.qi_tflag = NSQUERY_A/*|NSQUERY_AAAA*/; dp = pkt->p_cur; for(dsl = qzone->z_dsl; dsl; dsl = dsl->dsl_next) dsl->dsl_queryfn(dsl->dsl_ds, &qi, pkt); if (dp == pkt->p_cur) { char name[DNS_MAXDOMAIN]; dns_dntop(qi.qi_dn, name, sizeof(name)); zlog(LOG_WARNING, zone, "no glue record(s) for %.60s NS found", name); return NULL; } return dp; } int update_zone_ns(struct zone *zone, const struct dsns *dsns, unsigned ttl, const struct zone *zonelist) { struct zonens *zns; unsigned char *cpos, *sizep; struct dncompr compr; unsigned size, i, ns, nns; const unsigned char *nsdna[MAX_NS]; const unsigned char *dn; unsigned char *nsrrs[MAX_NS], *nsrre[MAX_NS]; unsigned nglue; struct dnspacket pkt; memset(&pkt, 0, sizeof(pkt)); pkt.p_sans = pkt.p_cur = pkt.p_buf + p_hdrsize; pkt.p_endp = pkt.p_buf + CACHEBUF_SIZE + p_hdrsize; for(nns = 0; dsns; dsns = dsns->dsns_next) { i = 0; while(i < nns && !dns_dnequ(nsdna[i], dsns->dsns_dn)) ++i; if (i < nns) continue; if (nns >= MAX_NS) { zlog(LOG_WARNING, zone, "too many NS records specified, only first %d will be used", MAX_NS); break; } nsdna[nns] = dsns->dsns_dn; if ((nsrrs[nns] = find_glue(zone, dsns->dsns_dn, &pkt, zonelist))) nsrre[nns] = pkt.p_cur; ++nns; } /* number of additional records must not exceed 254: (room for EDNS0 OPT) */ nglue = pkt.p_buf[p_ancnt2]; if (pkt.p_buf[p_ancnt1] || nglue > 254) /* too many glue recs */ return 0; /* check if we have enouth dnjump slots */ if (nns * 2 + nglue > sizeof(zns->jump)/sizeof(zns->jump[0])) return 0; memcpy(zone->z_nsdna, nsdna, nns * sizeof(nsdna[0])); memset(nsdna + nns, 0, (MAX_NS - nns) * sizeof(nsdna[0])); zone->z_nns = 0; /* for now, in case of error return */ zone->z_nsttl = ttl; /* fill up nns variants of NS RRs ordering: * zns is actually an array, not single structure */ ns = 0; zns = zone->z_zns; for(;;) { cpos = dnc_init(&compr, zns->data, sizeof(zns->data), zns->jump, zone->z_dn); for(i = 0; i < nns; ++i) { cpos = dnc_add(&compr, cpos, zone->z_dn); if (!cpos || cpos + 10 > compr.bend) return 0; PACK16S(cpos, DNS_T_NS); PACK16S(cpos, DNS_C_IN); PACK32S(cpos, ttl); sizep = cpos; cpos += 2; cpos = dnc_add(&compr, cpos, nsdna[i]); if (!cpos) return 0; size = cpos - sizep - 2; PACK16(sizep, size); } dnc_finish(&compr, cpos, &zns->nssize, &zns->nsjend); if (nglue) for(i = 0; i < nns; ++i) for(dn = nsrrs[i]; dn && dn < nsrre[i]; ) { /* pack the glue record. dnjump, type+class, ttl, size (= 4 or 16) */ dn += 2; size = 10 + dn[2+2+4+1]; cpos = dnc_add(&compr, cpos, zone->z_nsdna[i]); if (!cpos || cpos + size > compr.bend) return 0; memcpy(cpos, dn, size); dn += size; cpos += size; } dnc_finish(&compr, cpos, &zns->tsize, &zns->tjend); if (++ns >= nns) break; /* rotate list of NSes */ dn = nsdna[0]; memmove(nsdna, nsdna + 1, (nns - 1) * sizeof(nsdna[0])); nsdna[nns - 1] = dn; ++zns; } zone->z_nns = nns; zone->z_nglue = nglue; return 1; } static int addrr_ns(struct dnspacket *pkt, const struct zone *zone, int auth) { unsigned cns = zone->z_cns; const struct zonens *zns = zone->z_zns + cns; if (!zone->z_nns) return 0; /* if auth=1, we're adding last records (except maybe EDNS0 OPT), * so it's ok to fill in both AUTH and ADDITIONAL sections. */ /* If we can't fit both NS and glue recs, try NS only, omitting glue. * For auth=0, don't add glue records at all. */ if (auth && dnc_final(pkt, zns->data, zns->tsize, zns->jump, zns->tjend)) { pkt->p_buf[p_nscnt2] += zone->z_nns; pkt->p_buf[p_arcnt2] += zone->z_nglue; } else if (!dnc_final(pkt, zns->data, zns->nssize, zns->jump, zns->nsjend)) return 0; else /* we can't overflow p_ancnt2 (255 max) because addrr_ns(auth=0) * is called before all other answers will be collected, * and MAX_NS (zone->z_nns) is definitely less than 255 */ pkt->p_buf[auth ? p_nscnt2 : p_ancnt2] += zone->z_nns; /* pick up next variation of NS ordering */ ++cns; if (cns >= zone->z_nns) cns = 0; ((struct zone *)zone)->z_cns = cns; return 1; } static unsigned checkrr_present(register unsigned char *c, register unsigned char *e, unsigned dtp, const void *data, unsigned dsz, unsigned ttl) { /* check whenever we already have this (type of) RR in reply, * ensure that all RRs of the same type has the same TTL */ const unsigned char dtp1 = dtp >> 8, dtp2 = dtp & 255; unsigned t; #define nextRR(c) ((c) + 12 + (c)[11]) #define hasRR(c,e) ((c) < (e)) #define sameRRT(c,dtp1,dtp2) ((c)[2] == (dtp1) && (c)[3] == (dtp2)) #define sameDATA(c,dsz,data) \ ((c)[11] == (dsz) && memcmp((c)+12, (data), (dsz)) == 0) #define rrTTL(c) ((c)+6) for(;;) { if (!hasRR(c,e)) return ttl; if (sameRRT(c,dtp1,dtp2)) break; c = nextRR(c); } /* found at least one RR with the same type as new */ if (ttl >= (t = unpack32(rrTTL(c)))) { /* new ttl is either larger or the same as ttl of one of existing RRs */ /* if we already have the same record, do nothing */ if (sameDATA(c,dsz,data)) return 0; /* check other records too */ for(c = nextRR(c); hasRR(c,e); c = nextRR(c)) if (sameRRT(c,dtp1,dtp2) && sameDATA(c,dsz,data)) /* already has exactly the same data */ return 0; return t; /* use existing, smaller TTL for new RR */ } else { /* change TTLs of existing RRs to new, smaller one */ int same = sameDATA(c,dsz,data); unsigned char *ttlnb = rrTTL(c); PACK32(ttlnb, ttl); for(c = nextRR(c); hasRR(c,e); c = nextRR(c)) if (sameRRT(c,dtp1,dtp2)) { memcpy(rrTTL(c), ttlnb, 4); if (sameDATA(c,dsz,data)) same = 1; } return same ? 0 : ttl; } #undef nextRR #undef hasRR #undef sameRRT #undef sameDATA #undef rrTTL } /* add a new record into answer, check for dups. * We just ignore any data that exceeds packet size */ void addrr_any(struct dnspacket *pkt, unsigned dtp, const void *data, unsigned dsz, unsigned ttl) { register unsigned char *c = pkt->p_cur; ttl = checkrr_present(pkt->p_sans, c, dtp, data, dsz, ttl); if (!ttl) return; /* if RR is already present, do nothing */ if (!fit(pkt, c, 12 + dsz) || pkt->p_buf[p_ancnt2] == 255) { setnonauth(pkt->p_buf); /* non-auth answer as we can't fit the record */ return; } *c++ = 192; *c++ = p_hdrsize; /* jump after header: query DN */ PACK16S(c, dtp); PACK16S(c, DNS_C_IN); PACK32S(c, ttl); PACK16S(c, dsz); memcpy(c, data, dsz); pkt->p_cur = c + dsz; pkt->p_buf[p_ancnt2] += 1; /* increment numanswers */ } void addrr_a_txt(struct dnspacket *pkt, unsigned qtflag, const char *rr, const char *subst, const struct dataset *ds) { if (qtflag & NSQUERY_A) addrr_any(pkt, DNS_T_A, rr, 4, ds->ds_ttl); if (qtflag & NSQUERY_TXT) { char sb[TXTBUFSIZ+1]; unsigned sl = txtsubst(sb + 1, rr + 4, subst, ds); if (sl) { sb[0] = sl; addrr_any(pkt, DNS_T_TXT, sb, sl + 1, ds->ds_ttl); } } } static int version_req(struct dnspacket *pkt, const struct dnsquery *qry) { register unsigned char *c; unsigned dsz; if (!show_version) return 0; if (qry->q_class != DNS_C_CH || qry->q_type != DNS_T_TXT) return 0; if ((qry->q_dnlen != 16 || memcmp(qry->q_dn, "\7version\6server", 16)) && (qry->q_dnlen != 14 || memcmp(qry->q_dn, "\7version\4bind", 14))) return 0; c = pkt->p_cur; *c++ = 192; *c++ = p_hdrsize; /* jump after header: query DN */ *c++ = DNS_T_TXT>>8; *c++ = DNS_T_TXT; *c++ = DNS_C_CH>>8; *c++ = DNS_C_CH; *c++ = 0; *c++ = 0; *c++ = 0; *c++ = 0; /* ttl */ dsz = strlen(show_version) + 1; PACK16(c, dsz); c += 2; /* dsize */ *c++ = --dsz; memcpy(c, show_version, dsz); pkt->p_cur = c + dsz; pkt->p_buf[p_ancnt2] += 1; /* increment numanswers */ return 1; } void logreply(const struct dnspacket *pkt, FILE *flog, int flushlog) { char cbuf[DNS_MAXDOMAIN + IPSIZE + 50]; char *cp = cbuf; const unsigned char *const q = pkt->p_sans - 4; cp += sprintf(cp, "%lu ", (unsigned long)time(NULL)); #ifndef NO_IPv6 if (getnameinfo(pkt->p_peer, pkt->p_peerlen, cp, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0) cp += strlen(cp); else *cp++ = '?'; #else strcpy(cp, inet_ntoa(((struct sockaddr_in*)pkt->p_peer)->sin_addr)); cp += strlen(cp); #endif *cp++ = ' '; cp += dns_dntop(pkt->p_buf + p_hdrsize, cp, DNS_MAXDOMAIN); cp += sprintf(cp, " %s %s: %s/%u/%d\n", dns_typename(((unsigned)q[0]<<8)|q[1]), dns_classname(((unsigned)q[2]<<8)|q[3]), dns_rcodename(pkt->p_buf[p_f2] & pf2_rcode), pkt->p_buf[p_ancnt2], (int)(pkt->p_cur - pkt->p_buf)); if (flushlog) write(fileno(flog), cbuf, cp - cbuf); else fwrite(cbuf, cp - cbuf, 1, flog); } rbldnsd-0.997a/rbldnsd_ip4set.c0000664000175000017500000003215312120257742014575 0ustar mjtmjt/* ip4set dataset type: IP4 addresses (ranges), with A and TXT * values for every individual entry. */ #include #include #include #include "rbldnsd.h" struct entry { ip4addr_t addr; /* key: IP address */ const char *rr; /* A and TXT RRs */ }; struct dsdata { unsigned n[4]; /* counts */ unsigned a[4]; /* allocated (only for loading) */ unsigned h[4]; /* hint, how much to allocate next time */ struct entry *e[4]; /* entries */ const char *def_rr; /* default A and TXT RRs */ }; /* indexes */ #define E32 0 #define E24 1 #define E16 2 #define E08 3 /* ..and masks, "network" and "host" parts */ #define M32 0xffffffffu #define H32 0x00000000u #define M24 0xffffff00u #define H24 0x000000ffu #define M16 0xffff0000u #define H16 0x0000ffffu #define M08 0xff000000u #define H08 0x00ffffffu definedstype(ip4set, DSTF_IP4REV, "set of (ip4 range, value) pairs"); static void ds_ip4set_reset(struct dsdata *dsd, int UNUSED unused_freeall) { unsigned r; for (r = 0; r < 4; ++r) { if (!dsd->e[r]) continue; free(dsd->e[r]); dsd->e[r] = NULL; dsd->n[r] = dsd->a[r] = 0; } dsd->def_rr = NULL; } static int ds_ip4set_addent(struct dsdata *dsd, unsigned idx, ip4addr_t a, unsigned count, const char *rr) { struct entry *e = dsd->e[idx]; ip4addr_t step = 1 << (idx << 3); if (dsd->n[idx] + count > dsd->a[idx]) { if (!dsd->a[idx]) dsd->a[idx] = dsd->h[idx] ? dsd->h[idx] : 64; while(dsd->n[idx] + count > dsd->a[idx]) dsd->a[idx] <<= 1; e = trealloc(struct entry, e, dsd->a[idx]); if (!e) return 0; dsd->e[idx] = e; } e += dsd->n[idx]; dsd->n[idx] += count; for(; count--; a += step, ++e) { e->addr = a; e->rr = rr; } return 1; } static void ds_ip4set_start(struct dataset *ds) { ds->ds_dsd->def_rr = def_rr; } static int ds_ip4set_line(struct dataset *ds, char *s, struct dsctx *dsc) { struct dsdata *dsd = ds->ds_dsd; ip4addr_t a, b; const char *rr; unsigned rrl; int not; int bits; if (*s == ':') { if (!(rrl = parse_a_txt(s, &rr, def_rr, dsc))) return 1; if (!(dsd->def_rr = mp_dmemdup(ds->ds_mp, rr, rrl))) return 0; return 1; } if (*s == '!') { not = 1; ++s; SKIPSPACE(s); } else not = 0; if ((bits = ip4range(s, &a, &b, &s)) <= 0 || (*s && !ISSPACE(*s) && !ISCOMMENT(*s) && *s != ':')) { dswarn(dsc, "invalid address"); return 1; } if (accept_in_cidr) a &= ip4mask(bits); else if (a & ~ip4mask(bits)) { dswarn(dsc, "invalid range (non-zero host part)"); return 1; } if (dsc->dsc_ip4maxrange && dsc->dsc_ip4maxrange <= (b - a)) { dswarn(dsc, "too large range (%u) ignored (%u max)", b - a + 1, dsc->dsc_ip4maxrange); return 1; } if (not) rr = NULL; else { SKIPSPACE(s); if (!*s || ISCOMMENT(*s)) rr = dsd->def_rr; else if (!(rrl = parse_a_txt(s, &rr, dsd->def_rr, dsc))) return 1; else if (!(rr = mp_dmemdup(ds->ds_mp, rr, rrl))) return 0; } /*XXX some comments about funny ip4range_expand et al */ #define fn(idx,start,count) ds_ip4set_addent(dsd, idx, start, count, rr) /* helper macro for ip4range_expand: * deal with last octet, shifting a and b when done */ #define ip4range_expand_octet(bits) \ if ((a | 255u) >= b) { \ if (b - a == 255u) \ return fn((bits>>3)+1, a<>3, a<>3, a<> 8) + 1; \ } \ else \ a >>= 8; \ if ((b & 255u) != 255u) { \ if (!fn((bits>>3), (b & ~255u)<> 8) - 1; \ } \ else \ b >>= 8 ip4range_expand_octet(0); ip4range_expand_octet(8); ip4range_expand_octet(16); return fn(3, a << 24, b - a + 1); } static void ds_ip4set_finish(struct dataset *ds, struct dsctx *dsc) { struct dsdata *dsd = ds->ds_dsd; unsigned r; for(r = 0; r < 4; ++r) { if (!dsd->n[r]) { dsd->h[r] = 0; continue; } dsd->h[r] = dsd->a[r]; while((dsd->h[r] >> 1) >= dsd->n[r]) dsd->h[r] >>= 1; # define QSORT_TYPE struct entry # define QSORT_BASE dsd->e[r] # define QSORT_NELT dsd->n[r] # define QSORT_LT(a,b) \ a->addr < b->addr ? 1 : \ a->addr > b->addr ? 0 : \ a->rr < b->rr # include "qsort.c" #define ip4set_eeq(a,b) a.addr == b.addr && rrs_equal(a,b) REMOVE_DUPS(struct entry, dsd->e[r], dsd->n[r], ip4set_eeq); SHRINK_ARRAY(struct entry, dsd->e[r], dsd->n[r], dsd->a[r]); } dsloaded(dsc, "e32/24/16/8=%u/%u/%u/%u", dsd->n[E32], dsd->n[E24], dsd->n[E16], dsd->n[E08]); } static const struct entry * ds_ip4set_find(const struct entry *e, int b, ip4addr_t q) { int a = 0, m; --b; while(a <= b) { if (e[(m = (a + b) >> 1)].addr == q) { const struct entry *p = e + m - 1; while(p >= e && p->addr == q) --p; return p + 1; } else if (e[m].addr < q) a = m + 1; else b = m - 1; } return NULL; } static int ds_ip4set_query(const struct dataset *ds, const struct dnsqinfo *qi, struct dnspacket *pkt) { const struct dsdata *dsd = ds->ds_dsd; ip4addr_t q = qi->qi_ip4; ip4addr_t f; const struct entry *e, *t; const char *ipsubst; if (!qi->qi_ip4valid) return 0; check_query_overwrites(qi); #define try(i,mask) \ (dsd->n[i] && \ (t = dsd->e[i] + dsd->n[i], \ e = ds_ip4set_find(dsd->e[i], dsd->n[i], (f = q & mask))) != NULL) if (!try(E32, M32) && !try(E24, M24) && !try(E16, M16) && !try(E08, M08)) return 0; if (!e->rr) return 0; /* exclusion */ ipsubst = (qi->qi_tflag & NSQUERY_TXT) ? ip4atos(q) : NULL; do addrr_a_txt(pkt, qi->qi_tflag, e->rr, ipsubst, ds); while(++e < t && e->addr == f); return NSQUERY_FOUND; } #ifndef NO_MASTER_DUMP /* dump the data as master-format file. * Having two entries: * 127.0.0.0/8 "A" * 127.0.0.2 "B" * we have to generate the following stuff to make bind return what we need: * *.127 "A" * *.0.127 "A" * *.0.0.127 "A" * 2.0.0.127 "B" * If we have two (or more) /8 entries, each should be repeated for /16 and /24. * The same is when we have /16 and /32 (no /24), or /8 and /24 (no /16). * * The algorithm is as follows. We enumerating entries in /8 array * (ds_ip4set_dump08()), emitting all "previous" entries in /16 (indicating * there's no parent entry), when all entries in lower levels that are * covered by our /16 (indicating there IS a parent this time), our own /16 * group -- all entries with the same address. ds_ip4set_dump16() accepts * 'last' parameter telling it at which address to stop). ds_ip4set_dump16() * does the same with /16s and /24s as ds_ip4set_dump08() does with /8s and * /16s. Similarily, ds_ip4set_dump24() deals with /24s and /32s, but at this * point, it should pay attention to the case when there's a /8 but no /16 * covering the range in question. Ditto for ds_ip4set_dump32(), which also * should handle the case when there's no upper /24 but either /16 or /8 exists. */ struct dumpdata { /* state. */ const struct entry *e[4]; /* current entry we're looking at in each arr */ const struct entry *t[4]; /* end pointers for arrays */ const struct dataset *ds; /* the dataset in question */ FILE *f; /* file to dump data to */ }; /* dump a group of entries with the same IP address. * idx shows how many octets we want to print -- * used only with E08, E16 or E24, not with E32. * e is where to start, and t is the end of the array. * Returns pointer to the next element after the group. */ static const struct entry * ds_ip4set_dump_group(const struct dumpdata *dd, ip4addr_t saddr, ip4addr_t hmask, const struct entry *e, const struct entry *t) { ip4addr_t addr = e->addr; do dump_ip4range(saddr, saddr | hmask, e->rr, dd->ds, dd->f); while(++e < t && e->addr == addr); return e; } /* dump all /32s up to addr <= last. * u08, u16 and u24 is what's on upper levels. */ static void ds_ip4set_dump32(struct dumpdata *dd, ip4addr_t last, const struct entry *u08, const struct entry *u16, const struct entry *u24) { const struct entry *e = dd->e[E32], *t = dd->t[E32]; ip4addr_t m16 = 1, m24 = 1; /* up_rr is true if there's anything non-excluded that is on upper level. */ int up_rr = (u24 ? u24->rr : u16 ? u16->rr : u08 ? u08->rr : NULL) != NULL; while(e < t && e->addr <= last) { if (!e->rr && !up_rr) { /* skip entry if nothing listed on upper level */ ++e; continue; } if (!u24 && m24 != (e->addr & M24) && (u08 || u16)) { /* if there's no "parent" /24 entry, AND * we just advanced to next /24, AND * there's something on even-upper levels, * we have to repeat something from upper-upper level * in mid-level. */ m24 = e->addr & M24; /* remember parent /24 mask we're in */ if (!u16 && m16 != (m24 & M16) && u08) { /* if there's no parent /16, but there is parent /8: * repeat that /8 in current /16, but only once per /16. */ m16 = m24 & M16; ds_ip4set_dump_group(dd, m16, H16, u08, dd->t[E08]); } /* several cases: u16!=0 and isn't exclusion: dump it in upper /24. u16!=0 and it IS exclusion: do nothing. u08!=0 - dump it. */ if (!u16) /* u08 is here as per condition above */ ds_ip4set_dump_group(dd, m24, H24, u08, dd->t[E08]); else if (u16->rr) ds_ip4set_dump_group(dd, m24, H24, u16, dd->t[E16]); /* else nothing: the upper-upper /16 is an exclusion anyway */ } dump_ip4(e->addr, e->rr, dd->ds, dd->f); ++e; } dd->e[E32] = e; } /* dump all /24s and lower-levels up to addr <= last. * u08 and u16 is what's on upper levels. */ static void ds_ip4set_dump24(struct dumpdata *dd, ip4addr_t last, const struct entry *u08, const struct entry *u16) { const struct entry *e = dd->e[E24], *t = dd->t[E24]; ip4addr_t m16 = 1, a; /* up_rr is true if there's a non-excluded upper-level entry present */ int up_rr = (u16 ? u16->rr : u08 ? u08->rr : NULL) != NULL; while(e < t && (a = e->addr) <= last) { if (!e->rr && !up_rr) { /* ignore exclusions if there's nothing listed in upper levels */ ++e; continue; } if (a) /* dump all preceeding lower-level entries */ ds_ip4set_dump32(dd, a - 1, u08, u16, 0); /* and this is where the fun is. */ if (!u16 && m16 != (a & M16) && u08) { /* if there's no "parent" /16 entry, AND * we just advanced to next /16, AND * there's a /8 entry, * repeat that /8 in this new /16. * This produces *.x.y entry from y/8 and y.x.z/24. */ m16 = a & M16; ds_ip4set_dump_group(dd, m16, H16, u08, dd->t[E08]); } /* dump all lower-level entries covering by our group */ ds_ip4set_dump32(dd, a | H24, u08, u16, e); /* dump our group */ e = ds_ip4set_dump_group(dd, a, H24, e, t); } /* and finally, dump the rest in lower-level groups up to last */ ds_ip4set_dump32(dd, last, u08, u16, 0); dd->e[E24] = e; /* save loop counter */ } /* dump all /16s and lower-levels up to addr <= last. * u08 is what's on upper levels. */ static void ds_ip4set_dump16(struct dumpdata *dd, ip4addr_t last, const struct entry *u08) { const struct entry *e = dd->e[E16], *t = dd->t[E16]; while(e < t && e->addr <= last) { if (!e->rr && !u08) { /* skip exclusion only if there's no upper-level entry */ ++e; continue; } if (e->addr) /* dump all preceeding lower-level entries if any */ ds_ip4set_dump24(dd, e->addr - 1, u08, 0); /* dump all lower-level entries covering by this group */ ds_ip4set_dump24(dd, e->addr | H16, u08, e); /* dump the group itself */ e = ds_ip4set_dump_group(dd, e->addr, H16, e, t); } /* and finally, dump the rest in lower levels, up to last */ ds_ip4set_dump24(dd, last, u08, 0); dd->e[E16] = e; /* update loop variable */ } /* ok, the simplest case, dump all /8s in turn, unconditionally */ static void ds_ip4set_dump08(struct dumpdata *dd) { const struct entry *e = dd->e[E08], *t = dd->t[E08]; while(e < t) { if (!e->rr) { /* just skip all excludes here */ ++e; continue; } if (e->addr) /* dump any preceeding lower-level entries if any */ ds_ip4set_dump16(dd, e->addr - 1, 0); /* dump all entries covered by our group */ ds_ip4set_dump16(dd, e->addr | H08, e); /* dump our own group too */ e = ds_ip4set_dump_group(dd, e->addr, H08, e, t); } /* and finally, dump the rest */ ds_ip4set_dump16(dd, M32, 0); dd->e[E08] = e; /* just in case ;) */ } static void ds_ip4set_dump(const struct dataset *ds, const unsigned char UNUSED *unused_odn, FILE *f) { struct dumpdata dd; const struct dsdata *dsd = ds->ds_dsd; unsigned i; for(i = 0; i < 4; ++i) dd.t[i] = (dd.e[i] = dsd->e[i]) + dsd->n[i]; dd.ds = ds; dd.f = f; ds_ip4set_dump08(&dd); } #endif /* NO_MASTER_DUMP */ rbldnsd-0.997a/rbldnsd_ip4tset.c0000664000175000017500000000622612120257742014763 0ustar mjtmjt/* ip4tset dataset type: IP4 addresses (ranges), with A and TXT * values for every individual entry. */ #include #include #include #include "rbldnsd.h" struct dsdata { unsigned n; /* count */ unsigned a; /* allocated (only for loading) */ unsigned h; /* hint: how much to allocate next time */ ip4addr_t *e; /* array of entries */ const char *def_rr; /* default A and TXT RRs */ }; definedstype(ip4tset, DSTF_IP4REV, "(trivial) set of ip4 addresses"); static void ds_ip4tset_reset(struct dsdata *dsd, int UNUSED unused_freeall) { if (dsd->e) { free(dsd->e); dsd->e = NULL; dsd->n = dsd->a = 0; } dsd->def_rr = NULL; } static void ds_ip4tset_start(struct dataset UNUSED *unused_ds) { } static int ds_ip4tset_line(struct dataset *ds, char *s, struct dsctx *dsc) { struct dsdata *dsd = ds->ds_dsd; ip4addr_t a; if (*s == ':') { if (!dsd->def_rr) { unsigned rrl; const char *rr; if (!(rrl = parse_a_txt(s, &rr, def_rr, dsc))) return 1; if (!(dsd->def_rr = mp_dmemdup(ds->ds_mp, rr, rrl))) return 0; } return 1; } if (ip4prefix(s, &a, &s) != 32 || (*s && !ISSPACE(*s) && !ISCOMMENT(*s) && *s != ':')) { dswarn(dsc, "invalid address"); return 1; } if (dsd->n >= dsd->a) { ip4addr_t *e = dsd->e; if (!dsd->a) dsd->a = dsd->h ? dsd->h : 64; else dsd->a <<= 1; e = trealloc(ip4addr_t, e, dsd->a); if (!e) return 0; dsd->e = e; } dsd->e[dsd->n++] = a; return 1; } static void ds_ip4tset_finish(struct dataset *ds, struct dsctx *dsc) { struct dsdata *dsd = ds->ds_dsd; ip4addr_t *e = dsd->e; unsigned n = dsd->n; if (!n) dsd->h = 0; else { dsd->h = dsd->a; while((dsd->h >> 1) >= n) dsd->h >>= 1; # define QSORT_TYPE ip4addr_t # define QSORT_BASE e # define QSORT_NELT n # define QSORT_LT(a,b) *a < *b # include "qsort.c" #define ip4tset_eeq(a,b) a == b REMOVE_DUPS(ip4addr_t, e, n, ip4tset_eeq); SHRINK_ARRAY(ip4addr_t, e, n, dsd->a); dsd->e = e; dsd->n = n; } if (!dsd->def_rr) dsd->def_rr = def_rr; dsloaded(dsc, "cnt=%u", n); } static int ds_ip4tset_find(const ip4addr_t *e, int b, ip4addr_t q) { int a = 0, m; --b; while(a <= b) { if (e[(m = (a + b) >> 1)] == q) return 1; else if (e[m] < q) a = m + 1; else b = m - 1; } return 0; } static int ds_ip4tset_query(const struct dataset *ds, const struct dnsqinfo *qi, struct dnspacket *pkt) { const struct dsdata *dsd = ds->ds_dsd; const char *ipsubst; if (!qi->qi_ip4valid) return 0; check_query_overwrites(qi); if (!dsd->n || !ds_ip4tset_find(dsd->e, dsd->n, qi->qi_ip4)) return 0; ipsubst = (qi->qi_tflag & NSQUERY_TXT) ? ip4atos(qi->qi_ip4) : NULL; addrr_a_txt(pkt, qi->qi_tflag, dsd->def_rr, ipsubst, ds); return NSQUERY_FOUND; } #ifndef NO_MASTER_DUMP static void ds_ip4tset_dump(const struct dataset *ds, const unsigned char UNUSED *unused_odn, FILE *f) { const struct dsdata *dsd = ds->ds_dsd; const ip4addr_t *e = dsd->e, *t = e + dsd->n; while(e < t) dump_ip4(*e++, dsd->def_rr, ds, f); } #endif rbldnsd-0.997a/rbldnsd_ip4trie.c0000664000175000017500000001227612130046505014743 0ustar mjtmjt/* ip4trie dataset type: IP4 CIDR ranges with A and TXT values. * Only one value per range allowed. */ #include #include #include #include "rbldnsd.h" #include "btrie.h" struct dsdata { struct btrie *btrie; const char *def_rr; /* default RR */ }; definedstype(ip4trie, DSTF_IP4REV, "set of (ip4cidr, value) pairs"); static void ds_ip4trie_reset(struct dsdata *dsd, int UNUSED unused_freeall) { memset(dsd, 0, sizeof(*dsd)); } static void ds_ip4trie_start(struct dataset *ds) { struct dsdata *dsd = ds->ds_dsd; dsd->def_rr = def_rr; if (!dsd->btrie) dsd->btrie = btrie_init(ds->ds_mp); } static int ds_ip4trie_line(struct dataset *ds, char *s, struct dsctx *dsc) { struct dsdata *dsd = ds->ds_dsd; ip4addr_t a; btrie_oct_t addr_bytes[4]; int bits; const char *rr; unsigned rrl; int not; if (*s == ':') { if (!(rrl = parse_a_txt(s, &rr, def_rr, dsc))) return 1; if (!(dsd->def_rr = mp_dmemdup(ds->ds_mp, rr, rrl))) return 0; return 1; } if (*s == '!') { not = 1; ++s; SKIPSPACE(s); } else not = 0; if ((bits = ip4cidr(s, &a, &s)) < 0 || (*s && !ISSPACE(*s) && !ISCOMMENT(*s) && *s != ':')) { dswarn(dsc, "invalid address"); return 1; } if (accept_in_cidr) a &= ip4mask(bits); else if (a & ~ip4mask(bits)) { dswarn(dsc, "invalid range (non-zero host part)"); return 1; } if (dsc->dsc_ip4maxrange && dsc->dsc_ip4maxrange <= ~ip4mask(bits)) { dswarn(dsc, "too large range (%u) ignored (%u max)", ~ip4mask(bits) + 1, dsc->dsc_ip4maxrange); return 1; } if (not) rr = NULL; else { SKIPSPACE(s); if (!*s || ISCOMMENT(*s)) rr = dsd->def_rr; else if (!(rrl = parse_a_txt(s, &rr, dsd->def_rr, dsc))) return 1; else if (!(rr = mp_dmemdup(ds->ds_mp, rr, rrl))) return 0; } ip4unpack(addr_bytes, a); switch(btrie_add_prefix(dsd->btrie, addr_bytes, bits, rr)) { case BTRIE_OKAY: return 1; case BTRIE_DUPLICATE_PREFIX: dswarn(dsc, "duplicated entry for %s/%d", ip4atos(a), bits); return 1; case BTRIE_ALLOC_FAILED: default: return 0; /* oom */ } } static void ds_ip4trie_finish(struct dataset *ds, struct dsctx *dsc) { dsloaded(dsc, "%s", btrie_stats(ds->ds_dsd->btrie)); } static int ds_ip4trie_query(const struct dataset *ds, const struct dnsqinfo *qi, struct dnspacket *pkt) { const char *rr; btrie_oct_t addr_bytes[4]; if (!qi->qi_ip4valid) return 0; check_query_overwrites(qi); ip4unpack(addr_bytes, qi->qi_ip4); rr = btrie_lookup(ds->ds_dsd->btrie, addr_bytes, 32); if (!rr) return 0; addrr_a_txt(pkt, qi->qi_tflag, rr, qi->qi_tflag & NSQUERY_TXT ? ip4atos(qi->qi_ip4) : NULL, ds); return NSQUERY_FOUND; } #ifndef NO_MASTER_DUMP static inline int increment_bit(ip4addr_t *addr, int bit) { ip4addr_t mask = (ip4addr_t)1 << (31 - bit); if (*addr & mask) { *addr &= ~mask; return 1; } else { *addr |= mask; return 0; } } struct dump_context { const struct dataset *ds; FILE *f; ip4addr_t prev_addr; const char *prev_rr; /* Keep stack of data inherited from parent prefixes */ const void *parent_data[33]; unsigned depth; }; static void dump_cb(const btrie_oct_t *prefix, unsigned len, const void *data, int post, void *user_data) { struct dump_context *ctx = user_data; ip4addr_t addr; if (len > 32) return; /* paranoia */ addr = (prefix[0] << 24) + (prefix[1] << 16) + (prefix[2] << 8) + prefix[3]; addr &= len ? -((ip4addr_t)1 << (32 - len)) : 0; if (post == 0) { /* pre order visit (before child nodes are visited) */ /* push the inherited data stack down to our level */ for (; ctx->depth < len; ctx->depth++) ctx->parent_data[ctx->depth + 1] = ctx->parent_data[ctx->depth]; ctx->parent_data[len] = data; } else { /* post order - restore RR at end of prefix */ unsigned carry_bits; /* increment address to one past the end of the current prefix */ for (carry_bits = 0; carry_bits < len; carry_bits++) if (increment_bit(&addr, len - 1 - carry_bits) == 0) break; /* no carry */ if (carry_bits == len) return; /* wrapped - all done */ /* look up the stack one level for each bit of carry to get * the inherited data value at the incremented address */ ctx->depth = len - 1 - carry_bits; data = ctx->parent_data[ctx->depth]; } if (data != ctx->prev_rr) { if (addr != ctx->prev_addr) { if (ctx->prev_rr) dump_ip4range(ctx->prev_addr, addr - 1, ctx->prev_rr, ctx->ds, ctx->f); ctx->prev_addr = addr; } /* else addr unchanged => zero-length range, ignore */ ctx->prev_rr = data; } /* else rr unchanged => merge current range with previous */ } static void ds_ip4trie_dump(const struct dataset *ds, const unsigned char UNUSED *unused_odn, FILE *f) { struct dump_context ctx; memset(&ctx, 0, sizeof(ctx)); ctx.ds = ds; ctx.f = f; btrie_walk(ds->ds_dsd->btrie, dump_cb, &ctx); /* flush final range */ if (ctx.prev_rr) dump_ip4range(ctx.prev_addr, ip4mask(32), ctx.prev_rr, ds, f); } #endif rbldnsd-0.997a/rbldnsd_ip6tset.c0000664000175000017500000001300612130046505014751 0ustar mjtmjt/* ip6tset dataset type: IP6 half-addresses (/64s), * with the same A and TXT values for every individual entry. * Exclusions as /64 or /128 are recognized. */ #include #include #include #include "rbldnsd.h" #include "ip6addr.h" struct ip6half { ip6oct_t a[IP6ADDR_HALF]; }; struct ip6full { ip6oct_t a[IP6ADDR_FULL]; }; struct dsdata { unsigned a_cnt, e_cnt; /* count */ unsigned a_alc, e_alc; /* allocated (only for loading) */ unsigned a_hnt, e_hnt; /* hint: how much to allocate next time */ struct ip6half *a; /* array of entries */ struct ip6full *e; /* array of exclusions */ const char *def_rr; /* default A and TXT RRs */ }; definedstype(ip6tset, DSTF_IP6REV, "(trivial) set of ip6 addresses"); static void ds_ip6tset_reset(struct dsdata *dsd, int UNUSED unused_freeall) { free(dsd->a); dsd->a = NULL; free(dsd->e); dsd->e = NULL; dsd->a_alc = dsd->e_alc = 0; dsd->a_cnt = dsd->e_cnt = 0; dsd->def_rr = NULL; } static void ds_ip6tset_start(struct dataset UNUSED *unused_ds) { } static int ds_ip6tset_line(struct dataset *ds, char *s, struct dsctx *dsc) { struct dsdata *dsd = ds->ds_dsd; int bits, excl; ip6oct_t addr[IP6ADDR_FULL]; if (*s == ':') { if (!dsd->def_rr) { unsigned rrl; const char *rr; if (!(rrl = parse_a_txt(s, &rr, def_rr, dsc))) return 1; if (!(dsd->def_rr = mp_dmemdup(ds->ds_mp, rr, rrl))) return 0; } return 1; } excl = *s == '!'; if (excl) { ++s; SKIPSPACE(s); } bits = ip6prefix(s, addr, &s); if (bits < 0 || (*s && !ISSPACE(*s) && !ISCOMMENT(*s) && *s != ':')) { dswarn(dsc, "invalid address"); return 1; } if (bits != (excl ? 128 : 64)) { dswarn(dsc, "invalid address for %s (should be %d bits)", excl ? "exclusion" : "regular entry", excl ? 128 : 64); return 1; } if (excl) { if (dsd->e_cnt >= dsd->e_alc) { struct ip6full *e = dsd->e; unsigned alc = dsd->e_alc ? dsd->e_alc << 1 : dsd->e_hnt ? dsd->e_hnt : 64; e = trealloc(struct ip6full, e, alc); if (!e) return 0; dsd->e = e; dsd->e_alc = alc; } memcpy(&(dsd->e[dsd->e_cnt++]), addr, sizeof(*dsd->e)); } else { if (dsd->a_cnt >= dsd->a_alc) { struct ip6half *a = dsd->a; unsigned alc = dsd->a_alc ? dsd->a_alc << 1 : dsd->a_hnt ? dsd->a_hnt : 64; a = trealloc(struct ip6half, a, alc); if (!a) return 0; dsd->a = a; dsd->a_alc = alc; } memcpy(&(dsd->a[dsd->a_cnt++]), addr, sizeof(*dsd->a)); } return 1; } static void ds_ip6tset_finish(struct dataset *ds, struct dsctx *dsc) { struct dsdata *dsd = ds->ds_dsd; unsigned n; #define ip6tset_eeq(a,b) memcmp(&a, &b, sizeof(a)) == 0 #define QSORT_LT(a,b) (memcmp(a, b, sizeof(*a)) < 0) /* regular entries, ip6halves */ n = dsd->a_cnt; if (!n) dsd->a_hnt = 0; else { struct ip6half *a = dsd->a; dsd->a_hnt = dsd->a_alc; while((dsd->a_hnt >> 1) >= n) dsd->a_hnt >>= 1; # define QSORT_TYPE struct ip6half # define QSORT_BASE a # define QSORT_NELT n # include "qsort.c" # undef QSORT_NELT # undef QSORT_BASE # undef QSORT_TYPE REMOVE_DUPS(struct ip6half, a, n, ip6tset_eeq); SHRINK_ARRAY(struct ip6half, a, n, dsd->a_alc); dsd->a = a; dsd->a_cnt = n; } /* exclusions, ip6fulls */ n = dsd->e_cnt; if (!n) dsd->e_hnt = 0; else { struct ip6full *e = dsd->e; dsd->e_hnt = dsd->e_alc; while((dsd->e_hnt >> 1) >= n) dsd->e_hnt >>= 1; # define QSORT_TYPE struct ip6full # define QSORT_BASE e # define QSORT_NELT n # include "qsort.c" # undef QSORT_NELT # undef QSORT_BASE # undef QSORT_TYPE REMOVE_DUPS(struct ip6full, e, n, ip6tset_eeq); SHRINK_ARRAY(struct ip6full, e, n, dsd->a_alc); dsd->e = e; dsd->e_cnt = n; } if (!dsd->def_rr) dsd->def_rr = def_rr; dsloaded(dsc, "cnt=%u exl=%u", dsd->a_cnt, dsd->e_cnt); } static int ds_ip6tset_find(const struct ip6half *arr, int b, const ip6oct_t *q) { int a = 0; --b; while(a <= b) { int m = (a + b) >> 1; int r = memcmp(arr[m].a, q, sizeof(*arr)); if (r == 0) return 1; else if (r < 0) a = m + 1; else b = m - 1; } return 0; } static int ds_ip6tset_find_excl(const struct ip6full *arr, int b, const ip6oct_t *q) { int a = 0; --b; while(a <= b) { int m = (a + b) >> 1; int r = memcmp(arr[m].a, q, sizeof(*arr)); if (r == 0) return 1; else if (r < 0) a = m + 1; else b = m - 1; } return 0; } static int ds_ip6tset_query(const struct dataset *ds, const struct dnsqinfo *qi, struct dnspacket *pkt) { const struct dsdata *dsd = ds->ds_dsd; const char *ipsubst; if (!qi->qi_ip6valid) return 0; check_query_overwrites(qi); if (!ds_ip6tset_find(dsd->a, dsd->a_cnt, qi->qi_ip6)) return 0; if (dsd->e_cnt && ds_ip6tset_find_excl(dsd->e, dsd->e_cnt, qi->qi_ip6)) return 0; ipsubst = (qi->qi_tflag & NSQUERY_TXT) ? ip6atos(qi->qi_ip6, IP6ADDR_FULL) : NULL; addrr_a_txt(pkt, qi->qi_tflag, dsd->def_rr, ipsubst, ds); return NSQUERY_FOUND; } #ifndef NO_MASTER_DUMP static void ds_ip6tset_dump(const struct dataset *ds, const unsigned char UNUSED *unused_odn, FILE *f) { const struct dsdata *dsd = ds->ds_dsd; { const struct ip6half *a = dsd->a, *at = a + dsd->a_cnt; while(a < at) { dump_ip6((a++)->a, 16, dsd->def_rr, ds, f); } } { const struct ip6full *e = dsd->e, *et = e + dsd->e_cnt; while(e < et) { dump_ip6((e++)->a, 0, NULL, ds, f); } } } #endif rbldnsd-0.997a/rbldnsd_ip6trie.c0000664000175000017500000001247312130046505014744 0ustar mjtmjt/* ip6trie dataset type: IP6 CIDR ranges with A and TXT values. * Only one value per range allowed. */ #include #include #include #include "rbldnsd.h" #include "btrie.h" struct dsdata { struct btrie *btrie; const char *def_rr; /* default RR */ }; definedstype(ip6trie, DSTF_IP6REV, "set of (ip6cidr, value) pairs"); static void ds_ip6trie_reset(struct dsdata *dsd, int UNUSED unused_freeall) { memset(dsd, 0, sizeof(*dsd)); } static void ds_ip6trie_start(struct dataset *ds) { struct dsdata *dsd = ds->ds_dsd; dsd->def_rr = def_rr; if (!dsd->btrie) dsd->btrie = btrie_init(ds->ds_mp); } static int ds_ip6trie_line(struct dataset *ds, char *s, struct dsctx *dsc) { struct dsdata *dsd = ds->ds_dsd; const char *rr; unsigned rrl; int bits, excl, non_zero_host; ip6oct_t addr[IP6ADDR_FULL]; /* "::" can not be a valid start to a default RR setting ("invalid A * RR") but it can be a valid beginning to an ip6 address * (e.g. "::1") */ if (*s == ':' && s[1] != ':') { if (!(rrl = parse_a_txt(s, &rr, def_rr, dsc))) return 1; if (!(dsd->def_rr = mp_dmemdup(ds->ds_mp, rr, rrl))) return 0; return 1; } excl = *s == '!'; if (excl) { ++s; SKIPSPACE(s); } bits = ip6cidr(s, addr, &s); if (bits < 0 || (*s && !ISSPACE(*s) && !ISCOMMENT(*s) && *s != ':')) { dswarn(dsc, "invalid address"); return 1; } non_zero_host = ip6mask(addr, addr, IP6ADDR_FULL, bits); if (non_zero_host && !accept_in_cidr) { dswarn(dsc, "invalid range (non-zero host part)"); return 1; } SKIPSPACE(s); if (excl) rr = NULL; else if (!*s || ISCOMMENT(*s)) rr = dsd->def_rr; else if (!(rrl = parse_a_txt(s, &rr, dsd->def_rr, dsc))) return 1; else if (!(rr = mp_dmemdup(ds->ds_mp, rr, rrl))) return 0; switch(btrie_add_prefix(dsd->btrie, addr, bits, rr)) { case BTRIE_OKAY: return 1; case BTRIE_DUPLICATE_PREFIX: dswarn(dsc, "duplicated entry for %s/%d", ip6atos(addr, IP6ADDR_FULL), bits); return 1; case BTRIE_ALLOC_FAILED: default: return 0; /* oom */ } } static void ds_ip6trie_finish(struct dataset *ds, struct dsctx *dsc) { dsloaded(dsc, "%s", btrie_stats(ds->ds_dsd->btrie)); } static int ds_ip6trie_query(const struct dataset *ds, const struct dnsqinfo *qi, struct dnspacket *pkt) { const char *subst = NULL; const char *rr; if (!qi->qi_ip6valid) return 0; check_query_overwrites(qi); rr = btrie_lookup(ds->ds_dsd->btrie, qi->qi_ip6, 8 * IP6ADDR_FULL); if (!rr) return 0; if (qi->qi_tflag & NSQUERY_TXT) subst = ip6atos(qi->qi_ip6, IP6ADDR_FULL); addrr_a_txt(pkt, qi->qi_tflag, rr, subst, ds); return NSQUERY_FOUND; } #ifndef NO_MASTER_DUMP /* routines to increment individual bits in ip6 addr: returns carry */ static inline int increment_bit(ip6oct_t *addr, int bit) { ip6oct_t mask = 1 << (7 - bit % 8); if (addr[bit / 8] & mask) { addr[bit / 8] &= ~mask; return 1; } else { addr[bit / 8] |= mask; return 0; } } struct dump_context { const struct dataset *ds; FILE *f; ip6oct_t prev_addr[IP6ADDR_FULL]; const char *prev_rr; /* Keep stack of data inherited from parent prefixes */ const void *parent_data[IP6ADDR_FULL * 8 + 1]; unsigned depth; }; static void dump_cb(const btrie_oct_t *prefix, unsigned len, const void *data, int post, void *user_data) { struct dump_context *ctx = user_data; unsigned nb = (len + 7) / 8; ip6oct_t addr[IP6ADDR_FULL]; if (nb > IP6ADDR_FULL) return; /* paranoia */ /* pad prefix to full ip6 length */ memcpy(addr, prefix, nb); memset(addr + nb, 0, IP6ADDR_FULL - nb); if (post == 0) { /* pre order visit (before child nodes are visited) */ /* push the inherited data stack down to our level */ for (; ctx->depth < len; ctx->depth++) ctx->parent_data[ctx->depth + 1] = ctx->parent_data[ctx->depth]; ctx->parent_data[len] = data; } else { /* post order - restore RR at end of prefix */ unsigned carry_bits; /* increment address to one past the end of the current prefix */ for (carry_bits = 0; carry_bits < len; carry_bits++) if (increment_bit(addr, len - 1 - carry_bits) == 0) break; /* no carry */ if (carry_bits == len) return; /* wrapped - all done */ /* look up the stack one level for each bit of carry to get * the inherited data value at the incremented address */ ctx->depth = len - 1 - carry_bits; data = ctx->parent_data[ctx->depth]; } if (data != ctx->prev_rr) { if (memcmp(addr, ctx->prev_addr, IP6ADDR_FULL) != 0) { if (ctx->prev_rr) dump_ip6range(ctx->prev_addr, addr, ctx->prev_rr, ctx->ds, ctx->f); memcpy(ctx->prev_addr, addr, IP6ADDR_FULL); } /* else addr unchanged => zero-length range, ignore */ ctx->prev_rr = data; } /* else rr unchanged => merge current range with previous */ } static void ds_ip6trie_dump(const struct dataset *ds, const unsigned char UNUSED *unused_odn, FILE *f) { struct dump_context ctx; memset(&ctx, 0, sizeof(ctx)); ctx.ds = ds; ctx.f = f; btrie_walk(ds->ds_dsd->btrie, dump_cb, &ctx); /* flush final range */ if (ctx.prev_rr) dump_ip6range(ctx.prev_addr, NULL, ctx.prev_rr, ds, f); } #endif rbldnsd-0.997a/rbldnsd_dnset.c0000664000175000017500000001763112120257742014506 0ustar mjtmjt/* Dataset type which consists of a set of (possible wildcarded) * domain names together with (A,TXT) result for each. */ #include #include #include #include "rbldnsd.h" struct entry { const unsigned char *ldn; /* DN key, mp-allocated, length byte first */ const char *rr; /* A and TXT RRs */ }; /* * We store all domain names in a sorted array, using binary * search to find an entry. */ struct dnarr { unsigned n; /* number of entries */ unsigned a; /* entries allocated so far */ unsigned h; /* hint: number of ent to alloc next time */ struct entry *e; /* (sorted) array of entries */ unsigned minlab, maxlab; /* min and max no. of labels in array */ }; /* There are two similar arrays - * for plain entries and for wildcard entries. */ struct dsdata { struct dnarr p; /* plain entries */ struct dnarr w; /* wildcard entries */ const char *def_rr; /* default A and TXT RRs */ }; definedstype(dnset, 0, "set of (domain name, value) pairs"); static void ds_dnset_reset(struct dsdata *dsd, int UNUSED unused_freeall) { unsigned hp = dsd->p.h, hw = dsd->w.h; if (dsd->p.e) free(dsd->p.e); if (dsd->w.e) free(dsd->w.e); memset(dsd, 0, sizeof(*dsd)); dsd->p.minlab = dsd->w.minlab = DNS_MAXDN; dsd->p.h = hp; dsd->w.h = hw; } static void ds_dnset_start(struct dataset *ds) { ds->ds_dsd->def_rr = def_rr; } static int ds_dnset_addent(struct dnarr *arr, const unsigned char *ldn, const char *rr, unsigned dnlab) { struct entry *e; e = arr->e; if (arr->n >= arr->a) { /* expand array */ arr->a = arr->a ? arr->a << 1 : arr->h ? arr->h : 64; e = trealloc(struct entry, e, arr->a); if (!e) return 0; arr->e = e; } /* fill up an entry */ e += arr->n++; e->ldn = ldn; e->rr = rr; /* adjust min/max #labels */ if (arr->maxlab < dnlab) arr->maxlab = dnlab; if (arr->minlab > dnlab) arr->minlab = dnlab; return 1; } static int ds_dnset_line(struct dataset *ds, char *s, struct dsctx *dsc) { struct dsdata *dsd = ds->ds_dsd; unsigned char dn[DNS_MAXDN]; const char *rr; unsigned char *ldn; unsigned dnlen, size; int not, iswild, isplain; if (*s == ':') { /* default entry */ if (!(size = parse_a_txt(s, &rr, def_rr, dsc))) return 1; if (!(dsd->def_rr = mp_dmemdup(ds->ds_mp, rr, size))) return 0; return 1; } /* check negation */ if (*s == '!') { not = 1; ++s; SKIPSPACE(s); } else not = 0; /* check for wildcard: .xxx or *.xxx */ if (*s == '.') { iswild = 1; isplain = 1; ++s; } else if (s[0] == '*' && s[1] == '.') { iswild = 1; isplain = 0; s += 2; } else { iswild = 0; isplain = 1; } /* disallow emptry DN to be listed (i.e. "all"?) */ if (!(s = parse_dn(s, dn, &dnlen)) || dnlen == 1) { dswarn(dsc, "invalid domain name"); return 1; } dns_dntol(dn, dn); /* lowercase */ if (not) rr = NULL; /* negation entry */ else { /* else parse rest */ SKIPSPACE(s); if (!*s || ISCOMMENT(*s)) /* use default if none given */ rr = dsd->def_rr; else if (!(size = parse_a_txt(s, &rr, dsd->def_rr, dsc))) return 1; else if (!(rr = mp_dmemdup(ds->ds_mp, rr, size))) return 0; } ldn = (unsigned char*)mp_alloc(ds->ds_mp, dnlen + 1, 0); if (!ldn) return 0; ldn[0] = (unsigned char)(dnlen - 1); memcpy(ldn + 1, dn, dnlen); dnlen = dns_dnlabels(dn); if (isplain && !ds_dnset_addent(&dsd->p, ldn, rr, dnlen)) return 0; if (iswild && !ds_dnset_addent(&dsd->w, ldn, rr, dnlen)) return 0; return 1; } static int ds_dnset_lt(const struct entry *a, const struct entry *b) { int r; if (a->ldn[0] < b->ldn[0]) return 1; if (a->ldn[0] > b->ldn[0]) return 0; r = memcmp(a->ldn + 1, b->ldn + 1, a->ldn[0]); return r < 0 ? 1 : r > 0 ? 0 : a->rr < b->rr; } static void ds_dnset_finish_arr(struct dnarr *arr) { if (!arr->n) { arr->h = 0; return; } arr->h = arr->a; while((arr->h >> 1) >= arr->n) arr->h >>= 1; # define QSORT_TYPE struct entry # define QSORT_BASE arr->e # define QSORT_NELT arr->n # define QSORT_LT(a,b) ds_dnset_lt(a,b) # include "qsort.c" /* we make all the same DNs point to one string for faster searches */ { register struct entry *e, *t; for(e = arr->e, t = e + arr->n - 1; e < t; ++e) if (memcmp(e[0].ldn, e[1].ldn, e[0].ldn[0] + 1) == 0) e[1].ldn = e[0].ldn; } #define dnset_eeq(a,b) a.ldn == b.ldn && rrs_equal(a,b) REMOVE_DUPS(struct entry, arr->e, arr->n, dnset_eeq); SHRINK_ARRAY(struct entry, arr->e, arr->n, arr->a); } static void ds_dnset_finish(struct dataset *ds, struct dsctx *dsc) { struct dsdata *dsd = ds->ds_dsd; ds_dnset_finish_arr(&dsd->p); ds_dnset_finish_arr(&dsd->w); dsloaded(dsc, "e/w=%u/%u", dsd->p.n, dsd->w.n); } static const struct entry * ds_dnset_find(const struct entry *e, int n, const unsigned char *dn, unsigned dnlen0) { int a = 0, b = n - 1, m, r; /* binary search */ while(a <= b) { /* middle entry */ const struct entry *t = e + (m = (a + b) >> 1); if (t->ldn[0] < dnlen0) /* middle entry < dn */ a = m + 1; /* look in last half */ else if (t->ldn[0] > dnlen0) /* middle entry > dn */ b = m - 1; /* look in first half */ /* lengths match, compare the DN itself */ else if ((r = memcmp(t->ldn + 1, dn, dnlen0)) == 0) { /* found exact match, seek back to * first entry with this domain name */ dn = (t--)->ldn; while(t >= e && t->ldn == dn) --t; return t + 1; } else if (r < 0) /* middle entry < dn */ a = m + 1; /* look in last half */ else /* middle entry > dn */ b = m - 1; /* look in first half */ } return NULL; /* not found */ } static int ds_dnset_query(const struct dataset *ds, const struct dnsqinfo *qi, struct dnspacket *pkt) { const struct dsdata *dsd = ds->ds_dsd; const unsigned char *dn = qi->qi_dn; unsigned qlen0 = qi->qi_dnlen0; unsigned qlab = qi->qi_dnlab; const struct entry *e, *t; char name[DNS_MAXDOMAIN+1]; if (!qlab) return 0; /* do not match empty dn */ check_query_overwrites(qi); if (qlab > dsd->p.maxlab /* if we have less labels, search unnec. */ || qlab < dsd->p.minlab /* ditto for more */ || !(e = ds_dnset_find(dsd->p.e, dsd->p.n, dn, qlen0))) { /* try wildcard */ /* remove labels until number of labels in query is greather * than we have in wildcard array, but remove at least 1 label * for wildcard itself. */ do --qlab, qlen0 -= *dn + 1, dn += *dn + 1; while(qlab > dsd->w.maxlab); /* now, lookup every so long dn in wildcard array */ for(;;) { if (qlab < dsd->w.minlab) /* oh, number of labels in query become less than * minimum we have listed. Nothing to search anymore */ return 0; if ((e = ds_dnset_find(dsd->w.e, dsd->w.n, dn, qlen0))) break; /* found, listed */ /* remove next label at the end of rdn */ qlen0 -= *dn + 1; dn += *dn + 1; --qlab; } t = dsd->w.e + dsd->w.n; } else t = dsd->p.e + dsd->p.n; if (!e->rr) return 0; /* exclusion */ dn = e->ldn; if (qi->qi_tflag & NSQUERY_TXT) dns_dntop(e->ldn + 1, name, sizeof(name)); do addrr_a_txt(pkt, qi->qi_tflag, e->rr, name, ds); while(++e < t && e->ldn == dn); return NSQUERY_FOUND; } #ifndef NO_MASTER_DUMP static void ds_dnset_dump(const struct dataset *ds, const unsigned char UNUSED *unused_odn, FILE *f) { const struct dsdata *dsd = ds->ds_dsd; const struct entry *e, *t; char name[DNS_MAXDOMAIN+4]; for (e = dsd->p.e, t = e + dsd->p.n; e < t; ++e) { dns_dntop(e->ldn + 1, name, sizeof(name)); dump_a_txt(name, e->rr, name, ds, f); } name[0] = '*'; name[1] = '.'; for (e = dsd->w.e, t = e + dsd->w.n; e < t; ++e) { dns_dntop(e->ldn + 1, name + 2, sizeof(name) - 2); dump_a_txt(name, e->rr, name + 2, ds, f); } } #endif rbldnsd-0.997a/rbldnsd_generic.c0000664000175000017500000002155212120257742015002 0ustar mjtmjt/* generic dataset, simplified bind format. */ #include #include #include #include "rbldnsd.h" struct entry { const unsigned char *ldn; /* DN, first byte is length, w/o EON */ unsigned dtyp; /* data (query) type (NSQUERY_XX) */ unsigned ttl; /* time-to-live */ unsigned char *data; /* data, mp-allocated (size depends on dtyp) */ }; struct dsdata { unsigned n; /* number of entries */ unsigned a; /* entries allocated (only when loading) */ struct entry *e; /* entries */ unsigned maxlab; /* max level of labels */ unsigned minlab; /* min level of labels */ }; definedstype(generic, 0, "generic simplified bind-format"); static void ds_generic_reset(struct dsdata *dsd, int UNUSED unused_freeall) { if (dsd->e) free(dsd->e); memset(dsd, 0, sizeof(*dsd)); dsd->minlab = DNS_MAXDN; } static void ds_generic_start(struct dataset UNUSED *unused_ds) { } static int ds_generic_parseany(struct dataset *ds, char *s, struct dsctx *dsc) { struct dsdata *dsd = ds->ds_dsd; struct entry *e; char *t; unsigned dtyp, dsiz, dnlab; unsigned char data[DNS_MAXDN*2+20]; unsigned char *dp; /* allocate new entry */ e = dsd->e; if (dsd->n >= dsd->a) { dsd->a = dsd->a ? dsd->a << 1 : 8; e = trealloc(struct entry, e, dsd->a); if (!e) return 0; dsd->e = e; } e += dsd->n; /* dn */ if (s[0] == '@' && ISSPACE(s[1])) { data[1] = '\0'; dsiz = 1; s += 2; SKIPSPACE(s); } else if (!(s = parse_dn(s, data + 1, &dsiz)) || dsiz == 1) return -1; else dns_dntol(data + 1, data + 1); dnlab = dns_dnlabels(data + 1); data[0] = (unsigned char)(dsiz - 1); if (!(e->ldn = mp_dmemdup(ds->ds_mp, data, dsiz))) return 0; SKIPSPACE(s); if (*s >= '0' && *s <= '9') { /* ttl */ if (!(s = parse_ttl(s, &e->ttl, ds->ds_ttl))) return 0; SKIPSPACE(s); } else e->ttl = ds->ds_ttl; dp = data; /* type */ if ((s[0] == 'i' || s[0] == 'I') && (s[1] == 'n' || s[1] == 'N') && ISSPACE(s[2])) { /* skip IN class name */ s += 2; SKIPSPACE(s); } t = s; while(!ISSPACE(*s)) if (!*s) return -1; else { *s = dns_dnlc(*s); ++s; } *s++ = '\0'; SKIPSPACE(s); if (strcmp(t, "a") == 0) { ip4addr_t a; dtyp = NSQUERY_A; if (ip4addr(s, &a, &s) <= 0) return -1; PACK32(dp, a); dsiz = 4; } else if (strcmp(t, "txt") == 0) { dtyp = NSQUERY_TXT; dsiz = strlen(s); if (dsiz >= 2 && s[0] == '"' && s[dsiz-1] == '"') ++s, dsiz -= 2; if (dsiz > 255) { dswarn(dsc, "TXT RR truncated to 255 bytes"); dsiz = 255; } dp[0] = (char)dsiz; memcpy(dp+1, s, dsiz); dsiz += 1; } else if (strcmp(t, "mx") == 0) { dtyp = NSQUERY_MX; if (!(s = parse_uint32_nb(s, dp)) || dp[0] || dp[1]) return -1; dp[1] = dp[2]; dp[2] = dp[3]; if (!(s = parse_dn(s, dp + 3, &dsiz))) return 0; if (*s) return 0; dp[0] = (unsigned char)dsiz; dsiz += 3; } else return -1; e->dtyp = dtyp; dsiz += 4; if (!(e->data = mp_alloc(ds->ds_mp, dsiz, 0))) return 0; memcpy(e->data, data, dsiz); ++dsd->n; if (dsd->maxlab < dnlab) dsd->maxlab = dnlab; if (dsd->minlab > dnlab) dsd->minlab = dnlab; return 1; } static int ds_generic_line(struct dataset *ds, char *s, struct dsctx *dsc) { int r = ds_generic_parseany(ds, s, dsc); if (r < 0) { dswarn(dsc, "invalid/unrecognized entry"); return 1; } else if (!r) return 0; else return 1; } #define min(a,b) ((a)<(b)?(a):(b)) /* comparision of first MINlen bytes of two DNs is sufficient * due to the nature of domain name representation */ static int ds_generic_lt(const struct entry *a, const struct entry *b) { int r = memcmp(a->ldn, b->ldn, a->ldn[0] + 1); if (r < 0) return 1; else if (r > 0) return 0; else return a->dtyp < b->dtyp; } static void ds_generic_finish(struct dataset *ds, struct dsctx *dsc) { struct dsdata *dsd = ds->ds_dsd; if (dsd->n) { # define QSORT_TYPE struct entry # define QSORT_BASE dsd->e # define QSORT_NELT dsd->n # define QSORT_LT(a,b) ds_generic_lt(a,b) # include "qsort.c" /* collect all equal DNs to point to the same place */ { struct entry *e, *t; for(e = dsd->e, t = e + dsd->n - 1; e < t; ++e) if (memcmp(e[0].ldn, e[1].ldn, e[0].ldn[0] + 1) == 0) e[1].ldn = e[0].ldn; } SHRINK_ARRAY(struct entry, dsd->e, dsd->n, dsd->a); } dsloaded(dsc, "e=%u", dsd->n); } static const struct entry * ds_generic_find(const struct entry *e, int b, const unsigned char *dn, unsigned qlen0) { int a = 0, m, r; const struct entry *t; --b; for(;;) { if (a > b) return 0; t = e + (m = (a + b) >> 1); if (t->ldn[0] < qlen0) a = m + 1; else if (t->ldn[0] > qlen0) b = m - 1; else if (!(r = memcmp(t->ldn + 1, dn, qlen0))) return t; else if (r < 0) a = m + 1; else b = m - 1; } } static void ds_generic_add_rr(struct dnspacket *pkt, const struct entry *e) { const unsigned char *d = e->data; switch(e->dtyp) { case NSQUERY_A: addrr_any(pkt, DNS_T_A, d, 4, e->ttl); break; case NSQUERY_TXT: addrr_any(pkt, DNS_T_TXT, d, (unsigned)(d[0]) + 1, e->ttl); break; case NSQUERY_MX: addrr_any(pkt, DNS_T_MX, d + 1, (unsigned)(d[0]) + 2, e->ttl); break; } } static void ds_generic_add_rrs(struct dnspacket *pkt, const struct entry *e, const struct entry *l) { /* this routine should randomize order of the RRs when placing them * into the resulting packet. Currently, we use plain "dumb" round-robin, * that is, given N RRs, we chose some M in between, based on a single * sequence nn, and will return M..N-1 records first, and 0..M-1 records * second. Dumb, dumb, I know, but this is very simple to implement!.. ;) */ static unsigned nn; const struct entry *m = (l - e > 1) ? e + nn++ % (l - e) : e; const struct entry *t; for(t = m; t < l; ++t) ds_generic_add_rr(pkt, t); for(t = e; t < m; ++t) ds_generic_add_rr(pkt, t); } static int ds_generic_query(const struct dataset *ds, const struct dnsqinfo *qi, struct dnspacket *pkt) { const struct dsdata *dsd = ds->ds_dsd; const unsigned char *dn = qi->qi_dn; const struct entry *e, *t, *l; unsigned qt = qi->qi_tflag; if (qi->qi_dnlab > dsd->maxlab || qi->qi_dnlab < dsd->minlab) return 0; e = dsd->e; t = ds_generic_find(e, dsd->n, qi->qi_dn, qi->qi_dnlen0); if (!t) return 0; /* find first and last entries with the DN and qtype in question */ dn = t->ldn; if (qt == NSQUERY_ANY) { /* ANY query, we want all records regardless of type; * but "randomize" each type anyway */ do --t; while(t >= e && t->ldn == dn); l = e + dsd->n; e = t + 1; t = e + 1; qt = e->dtyp; for(;;) { if (t >= l || t->ldn != dn) { ds_generic_add_rrs(pkt, e, t); break; } else if (t->dtyp != qt) { qt = t->dtyp; ds_generic_add_rrs(pkt, e, t); e = t; } ++t; } } else if (qt == NSQUERY_OTHER) return 1; /* we have nothing of this type */ else if (t->dtyp > qt) { /* search backward */ do if (--t < e || t->ldn != dn || t->dtyp < qt) return 1; while (t->dtyp > qt); l = t + 1; do --t; while(t >= e && t->ldn == dn && t->dtyp == qt); ds_generic_add_rrs(pkt, t + 1, l); } else if (t->dtyp < qt) { /* search forward */ l = e + dsd->n; do if (++t >= l || t->ldn != dn || t->dtyp > qt) return 1; while(t->dtyp < qt); e = t; do ++t; while(t < l && t->ldn == dn && t->dtyp == qt); ds_generic_add_rrs(pkt, e, t); } else { /* we're here, find boundaries */ l = t; do --t; while(t >= e && t->ldn == dn && t->dtyp == qt); e = t + 1; t = dsd->e + dsd->n; do ++l; while(l < t && l->ldn == dn && l->dtyp == qt); ds_generic_add_rrs(pkt, e, l); } return NSQUERY_FOUND; } #ifndef NO_MASTER_DUMP static void ds_generic_dump(const struct dataset *ds, const unsigned char UNUSED *unused_odn, FILE *f) { const struct dsdata *dsd = ds->ds_dsd; const struct entry *e, *t; unsigned char dn[DNS_MAXDN]; char name[DNS_MAXDOMAIN+1]; const unsigned char *ldn = NULL; const unsigned char *d; for (e = dsd->e, t = e + dsd->n; e < t; ++e) { if (ldn != e->ldn) { ldn = e->ldn; if (ldn[0] > 1) { memcpy(dn, ldn + 1, ldn[0]); dn[ldn[0]] = '\0'; dns_dntop(dn, name, sizeof(name)); } else strcpy(name, "@"); } else name[0] = '\0'; fprintf(f, "%s\t%u\t", name, e->ttl); d = e->data; switch(e->dtyp) { case NSQUERY_A: fprintf(f, "A\t%u.%u.%u.%u\n", d[0], d[1], d[2], d[3]); break; case NSQUERY_TXT: fprintf(f, "TXT\t\"%.*s\"\n", *d, d + 1); /*XXX quotes */ break; case NSQUERY_MX: dns_dntop(d + 3, name, sizeof(name)); fprintf(f, "MX\t%u\t%s.\n", ((unsigned)d[1] << 8) | d[2], name); break; } } } #endif rbldnsd-0.997a/rbldnsd_combined.c0000664000175000017500000001464512120257742015153 0ustar mjtmjt/* combined dataset, one file is a collection of various datasets * and subzones. Special case. */ #include #include #include #include "rbldnsd.h" /* Special "dataset", which does NOT contain any data itself, * but contains several datasets of other types instead. * Operations: * we have a list of datasets in dslist, that contains * our data (subdataset) * when loading, we have current dataset in dssub, which * will be called to parse a line * we have a list of subzones in zlist for query */ struct dsdata { struct dataset *dslist; /* list of subzone datasets */ struct dataset **dslastp; /* where to connect next dataset */ unsigned nds; /* number of datasets in dslist */ struct dataset *sdslist; /* saved datasets list */ struct zone *zlist; /* list of subzones */ }; definedstype(combined, DSTF_SPECIAL, "several datasets/subzones combined"); static void ds_combined_reset(struct dsdata *dsd, int freeall) { struct dataset *dslist = dsd->dslist; while(dslist) { struct dataset *ds = dslist; dslist = dslist->ds_next; ds->ds_type->dst_resetfn(ds->ds_dsd, freeall); if (freeall) free(ds); } dslist = dsd->sdslist; while(dslist) { struct dataset *ds = dslist; dslist = dslist->ds_next; ds->ds_type->dst_resetfn(ds->ds_dsd, 1); free(ds); } dslist = dsd->dslist; memset(dsd, 0, sizeof(*dsd)); if (!freeall) dsd->sdslist = dslist; dsd->dslastp = &dsd->dslist; } static int ds_combined_line(struct dataset UNUSED *unused_ds, char UNUSED *unused_s, struct dsctx *dsc) { dslog(LOG_ERR, dsc, "invalid/unrecognized entry - specify $DATASET line"); return 0; } static void ds_combined_finishlast(struct dsctx *dsc) { struct dataset *dssub = dsc->dsc_subset; if (dssub) { const char *fname = dsc->dsc_fname; dsc->dsc_fname = NULL; dssub->ds_type->dst_finishfn(dssub, dsc); dsc->dsc_subset = NULL; dsc->dsc_fname = fname; } } static void ds_combined_start(struct dataset UNUSED *ds) { } static void ds_combined_finish(struct dataset *ds, struct dsctx *dsc) { struct dsdata *dsd = ds->ds_dsd; struct zone *zone; unsigned nzones; ds_combined_finishlast(dsc); for(nzones = 0, zone = dsd->zlist; zone; zone = zone->z_next) ++nzones; dsloaded(dsc, "subzones=%u datasets=%u", nzones, dsd->nds); } int ds_combined_newset(struct dataset *ds, char *line, struct dsctx *dsc) { char *p; const char *const space = " \t"; struct dsdata *dsd = ds->ds_dsd; struct dataset *dssub; struct dslist *dsl; struct zone *zone; unsigned char dn[DNS_MAXDN]; unsigned dnlen; char *name; ds_combined_finishlast(dsc); /* remove comment. only recognize # after a space. */ for (p = line; *p; ++p) if (ISCOMMENT(*p) && (p == line || ISSPACE(p[-1]))) { *p = '\0'; break; } p = strtok(line, space); /* dataset type */ if (!p) return 0; if ((name = strchr(p, ':')) != NULL) *name++ = '\0'; for(;;) { /* search appropriate dataset */ if (!(dssub = dsd->sdslist)) { /* end of the saved list, allocate new dataset */ const struct dstype **dstp = ds_types, *dst; dstp = ds_types; while(strcmp(p, (*dstp)->dst_name)) if (!*++dstp) { dslog(LOG_ERR, dsc, "unknown dataset type `%.60s'", p); return -1; } dst = *dstp; if (dst->dst_flags & DSTF_SPECIAL) { dslog(LOG_ERR, dsc, "dataset type `%s' cannot be used inside `combined'", dst->dst_name); return -1; } dssub = (struct dataset *) ezalloc(sizeof(struct dataset) + dst->dst_size); if (!dssub) return -1; dssub->ds_type = dst; dssub->ds_dsd = (struct dsdata *)(dssub + 1); dssub->ds_mp = ds->ds_mp; /* use parent memory pool */ break; } else if (strcmp(dssub->ds_type->dst_name, p) == 0) { /* reuse existing one */ dsd->sdslist = dssub->ds_next; break; } else { /* entry is of different type, free it and try next one */ dsd->sdslist = dssub->ds_next; dssub->ds_type->dst_resetfn(dssub->ds_dsd, 1); free(dssub); } } dssub->ds_next = NULL; *dsd->dslastp = dssub; dsd->dslastp = &dssub->ds_next; if (name && *name) { if (strlen(name) > 20) name[20] = '\0'; if (!(dssub->ds_spec = mp_strdup(ds->ds_mp, name))) return -1; } if (!(p = strtok(NULL, space))) dswarn(dsc, "no subzone(s) specified for dataset, data will be ignored"); else do { if (p[0] == '@' && p[1] == '\0') { dn[0] = '\0'; dnlen = 1; } else if (!(dnlen = dns_ptodn(p, dn, sizeof(dn)))) { dswarn(dsc, "invalid domain name `%.60s'", p); continue; } else dns_dntol(dn, dn); zone = newzone(&dsd->zlist, dn, dnlen, ds->ds_mp); dsl = mp_talloc(ds->ds_mp, struct dslist); if (!zone || !dsl) return -1; connectdataset(zone, dssub, dsl); } while((p = strtok(NULL, space)) != NULL); ++dsd->nds; dsc->dsc_subset = dssub; dssub->ds_type->dst_resetfn(dssub->ds_dsd, 0); dssub->ds_ttl = ds->ds_ttl; memcpy(dssub->ds_subst, ds->ds_subst, sizeof(ds->ds_subst)); dssub->ds_type->dst_startfn(dssub); return 1; } static int ds_combined_query(const struct dataset *ds, const struct dnsqinfo *qi, struct dnspacket *pkt) { struct dnsqinfo sqi; const struct dslist *dsl; int found = 0; const struct zone *zone = findqzone(ds->ds_dsd->zlist, qi->qi_dnlen0 + 1, qi->qi_dnlab, qi->qi_dnlptr, &sqi); if (!zone) return 0; sqi.qi_tflag = qi->qi_tflag; for (dsl = zone->z_dsl; dsl; dsl = dsl->dsl_next) found |= dsl->dsl_queryfn(dsl->dsl_ds, &sqi, pkt); /* if it was a query for our base subzone, always return `found' */ return found | (sqi.qi_dnlab ? 0 : NSQUERY_FOUND); } #ifndef NO_MASTER_DUMP static void ds_combined_dump(const struct dataset *ds, const unsigned char *odn, FILE *f) { char bname[DNS_MAXDOMAIN+1], sname[DNS_MAXDOMAIN+1]; const struct zone *zone; const struct dslist *dsl; dns_dntop(odn, bname, DNS_MAXDOMAIN + 1); for(zone = ds->ds_dsd->zlist; zone; zone = zone->z_next) { if (zone->z_dnlen == 1) fprintf(f, "$ORIGIN %s.\n", bname); else { dns_dntop(zone->z_dn, sname, DNS_MAXDOMAIN + 1); fprintf(f, "$ORIGIN %s.%s.\n", sname, bname); } for(dsl = zone->z_dsl; dsl; dsl = dsl->dsl_next) dsl->dsl_ds->ds_type->dst_dumpfn(dsl->dsl_ds, NULL/*XXX*/, f); } } #endif rbldnsd-0.997a/rbldnsd_acl.c0000664000175000017500000001362712130046505014123 0ustar mjtmjt/* ACL (Access Control Lists) implementation for rbldnsd. */ #include #include #include #include #include #include #include #include "rbldnsd.h" #include "btrie.h" struct dsdata { struct btrie *ip4_trie; #ifndef NO_IPv6 struct btrie *ip6_trie; #endif const char *def_rr; const char *def_action; }; /* special cases for pseudo-RRs */ static const struct { const char *name; unsigned long rr; } keywords[] = { /* ignore (don't answer) queries from this IP */ #define RR_IGNORE 1 { "ignore", RR_IGNORE }, { "blackhole", RR_IGNORE }, /* refuse *data* queries from this IP (but not metadata) */ #define RR_REFUSE 2 { "refuse", RR_REFUSE }, /* pretend the zone is completely empty */ #define RR_EMPTY 3 { "empty", RR_EMPTY }, /* a 'whitelist' entry: pretend this netrange isn't here */ #define RR_PASS 4 { "pass", RR_PASS }, }; static void ds_acl_reset(struct dsdata *dsd, int UNUSED unused_freeall) { memset(dsd, 0, sizeof(*dsd)); } static void ds_acl_start(struct dataset *ds) { struct dsdata *dsd = ds->ds_dsd; dsd->def_rr = def_rr; dsd->def_action = (char*)RR_IGNORE; if (!dsd->ip4_trie) { dsd->ip4_trie = btrie_init(ds->ds_mp); #ifndef NO_IPv6 dsd->ip6_trie = btrie_init(ds->ds_mp); #endif } } static const char *keyword(const char *s) { const char *k, *p; unsigned i; if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z'))) return NULL; for (i = 0; i < sizeof(keywords)/sizeof(keywords[0]); ++i) for (k = keywords[i].name, p = s;;) if ((*p >= 'A' && *p <= 'Z' ? *p - 'A' + 'a' : *p) != *k++) break; else if (!*++p || *p == ':' || ISSPACE(*p) || ISCOMMENT(*p)) return (const char *)(keywords[i].rr); return NULL; } static int ds_acl_parse_val(char *s, const char **rr_p, struct dsdata *dsd, struct dsctx *dsc) { int r; if (*s == '=') { if ((*rr_p = keyword(s+1))) return 0; dswarn(dsc, "invalid keyword"); return -1; } if (*s == ':' && (*rr_p = keyword(s+1))) return 0; r = parse_a_txt(s, rr_p, dsd->def_rr, dsc); return r ? r : -1; } #define VALID_TAIL(c) ((c) == '\0' || ISSPACE(c) || ISCOMMENT(c) || (c) == ':') static int ds_acl_line(struct dataset *ds, char *s, struct dsctx *dsc) { struct dsdata *dsd = ds->ds_dsd; char *tail; ip4addr_t ip4addr; ip6oct_t addr[IP6ADDR_FULL]; const char *ipstring; struct btrie *trie; int bits; const char *rr; int rrl; /* "::" can not be a valid start to a default RR setting ("invalid A * RR") but it can be a valid beginning to an ip6 address * (e.g. "::1") */ if ((*s == ':' && s[1] != ':') || *s == '=') { if ((rrl = ds_acl_parse_val(s, &rr, dsd, dsc)) < 0) return 1; else if (!rrl) dsd->def_action = rr; else if (!(rr = mp_dmemdup(ds->ds_mp, rr, rrl))) return 0; dsd->def_rr = dsd->def_action = rr; return 1; } if ((bits = ip4cidr(s, &ip4addr, &tail)) >= 0 && VALID_TAIL(tail[0])) { if (accept_in_cidr) ip4addr &= ip4mask(bits); else if (ip4addr & ~ip4mask(bits)) { dswarn(dsc, "invalid range (non-zero host part)"); return 1; } if (dsc->dsc_ip4maxrange && dsc->dsc_ip4maxrange <= ~ip4mask(bits)) { dswarn(dsc, "too large range (%u) ignored (%u max)", ~ip4mask(bits) + 1, dsc->dsc_ip4maxrange); return 1; } trie = dsd->ip4_trie; ip4unpack(addr, ip4addr); ipstring = ip4atos(ip4addr); s = tail; } #ifndef NO_IPv6 else if ((bits = ip6cidr(s, addr, &tail)) >= 0 && VALID_TAIL(tail[0])) { int non_zero_host = ip6mask(addr, addr, IP6ADDR_FULL, bits); if (non_zero_host && !accept_in_cidr) { dswarn(dsc, "invalid range (non-zero host part)"); return 1; } trie = dsd->ip6_trie; ipstring = ip6atos(addr, IP6ADDR_FULL); s = tail; } #endif else { dswarn(dsc, "invalid address"); return 1; } SKIPSPACE(s); if (!*s || ISCOMMENT(*s)) rr = dsd->def_action; else if ((rrl = ds_acl_parse_val(s, &rr, dsd, dsc)) < 0) return 1; else if (rrl && !(rr = mp_dmemdup(ds->ds_mp, rr, rrl))) return 0; switch(btrie_add_prefix(trie, addr, bits, rr)) { case BTRIE_OKAY: return 1; case BTRIE_DUPLICATE_PREFIX: dswarn(dsc, "duplicated entry for %s/%d", ipstring, bits); return 1; case BTRIE_ALLOC_FAILED: default: return 0; } } static void ds_acl_finish(struct dataset *ds, struct dsctx *dsc) { dsloaded(dsc, "loaded"); dslog(LOG_INFO, dsc, "ip4 trie: %s", btrie_stats(ds->ds_dsd->ip4_trie)); #ifndef NO_IPv6 dslog(LOG_INFO, dsc, "ip6 trie: %s", btrie_stats(ds->ds_dsd->ip6_trie)); #endif } int ds_acl_query(const struct dataset *ds, struct dnspacket *pkt) { const struct sockaddr *sa = pkt->p_peer; const char *rr; if (sa->sa_family == AF_INET) { const struct sockaddr_in *sin = (const struct sockaddr_in *)pkt->p_peer; if (pkt->p_peerlen < sizeof(*sin)) return 0; rr = btrie_lookup(ds->ds_dsd->ip4_trie, (const btrie_oct_t *)&sin->sin_addr.s_addr, 32); } #ifndef NO_IPv6 else if (sa->sa_family == AF_INET6) { const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)pkt->p_peer; if (pkt->p_peerlen < sizeof(*sin6)) return 0; rr = btrie_lookup(ds->ds_dsd->ip6_trie, sin6->sin6_addr.s6_addr, 8 * IP6ADDR_FULL); } #endif else { return 0; } switch((unsigned long)rr) { case 0: return 0; case RR_IGNORE: return NSQUERY_IGNORE; case RR_REFUSE: return NSQUERY_REFUSE; case RR_EMPTY: return NSQUERY_EMPTY; case RR_PASS: return 0; } if (!pkt->p_substrr) { pkt->p_substrr = rr; pkt->p_substds = ds; } return NSQUERY_ALWAYS; } /*definedstype(acl, DSTF_SPECIAL, "Access Control List dataset");*/ const struct dstype dataset_acl_type = { "acl", DSTF_SPECIAL, sizeof(struct dsdata), ds_acl_reset, ds_acl_start, ds_acl_line, ds_acl_finish, NULL, NULL, "Access Control List dataset" }; rbldnsd-0.997a/rbldnsd_util.c0000664000175000017500000003744712130046505014347 0ustar mjtmjt/* Common utility routines for rbldnsd. */ #include #include #include #include #include #include #include #include "rbldnsd.h" #define digit(c) ((c) >= '0' && (c) <= '9') #define d2n(c) ((unsigned)((c) - '0')) static char *parse_uint32_s(char *s, unsigned *np) { unsigned char *t = (unsigned char*)s; unsigned n = 0; if (!digit(*t)) return NULL; do { if (n > 0xffffffffu / 10) return 0; if (n * 10 > 0xffffffffu - d2n(*t)) return 0; n = n * 10 + d2n(*t++); } while(digit(*t)); *np = n; return (char*)t; } char *parse_uint32(char *s, unsigned *np) { if (!(s = parse_uint32_s(s, np))) return NULL; if (*s) { if (!ISSPACE(*s)) return NULL; ++s; SKIPSPACE(s); } return s; } char *parse_uint32_nb(char *s, unsigned char nb[4]) { unsigned n; if (!(s = parse_uint32(s, &n))) return NULL; PACK32(nb, n); return s; } char *parse_time(char *s, unsigned *tp) { unsigned m = 1; if (!(s = parse_uint32_s(s, tp))) return NULL; switch(*s) { case 'w': case 'W': m *= 7; /* week */ case 'd': case 'D': m *= 24; /* day */ case 'h': case 'H': m *= 60; /* hours */ case 'm': case 'M': m *= 60; /* minues */ if (0xffffffffu / m < *tp) return NULL; *tp *= m; case 's': case 'S': /* secounds */ ++s; break; } if (*s && *s != ':') { if (!ISSPACE(*s)) return NULL; ++s; SKIPSPACE(s); } return s; } char *parse_time_nb(char *s, unsigned char nb[4]) { unsigned t; if (!(s = parse_time(s, &t))) return NULL; PACK32(nb, t); return s; } char *parse_ttl(char *s, unsigned *ttlp, unsigned defttl) { s = parse_time(s, ttlp); if (*ttlp == 0) *ttlp = defttl; else if (min_ttl && *ttlp < min_ttl) *ttlp = min_ttl; else if (max_ttl && *ttlp > max_ttl) *ttlp = max_ttl; return s; } static const unsigned char mday[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; #define isleap(year) \ ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0)) static char * parse_tsp(char *s, unsigned *np, unsigned min, unsigned max, unsigned w) { unsigned n = 0; if (!digit(*s)) return NULL; do n = n * 10 + d2n(*s++); while(digit(*s) && --w); if (n < min || n > max) return NULL; if (*s == ':' || *s == '-') ++s; *np = n; return s; } char *parse_timestamp(char *s, time_t *tsp) { unsigned year, mon, day, hour, min, sec; if ((*s == '0' || *s == '-' || *s == ':') && (ISSPACE(s[1]) || !s[1])) { *tsp = 0; ++s; SKIPSPACE(s); return s; } if (!(s = parse_tsp(s, &year, 1970, 2038, 4))) return NULL; if (!(s = parse_tsp(s, &mon, 1, 12, 2))) return NULL; mon -= 1; day = mon == 1 && isleap(year) ? 29 : mday[mon]; if (!(s = parse_tsp(s, &day, 1, day, 2))) return NULL; hour = min = sec = 0; if (*s && !ISSPACE(*s)) if (!(s = parse_tsp(s, &hour, 0, 23, 2))) return NULL; if (*s && !ISSPACE(*s)) if (!(s = parse_tsp(s, &min, 0, 59, 2))) return NULL; if (*s && !ISSPACE(*s)) if (!(s = parse_tsp(s, &sec, 0, 59, 2))) return NULL; if (*s) { if (!ISSPACE(*s)) return NULL; ++s; SKIPSPACE(s); } { unsigned y4 = (year / 4) - !(year & 3); unsigned y100 = y4 / 25; unsigned y400 = y100 / 4; day = 365 * (year - 1970) + (y4 - 492) - (y100 - 19) + (y400 - 4) + day - 1; if (isleap(year) && mon > 1) ++day; while(mon) day += mday[--mon]; *tsp = ((day * 24 + hour) * 60 + min) * 60 + sec; } return s; } char *parse_dn(char *s, unsigned char *dn, unsigned *dnlenp) { char *n = s; unsigned l; while(*n && !ISSPACE(*n)) ++n; if (*n) *n++ = '\0'; if (!*s) return NULL; if ((l = dns_ptodn(s, dn, DNS_MAXDN)) == 0) return NULL; if (dnlenp) *dnlenp = l; SKIPSPACE(n); return n; } int parse_a_txt(char *str, const char **rrp, const char *def_rr, struct dsctx *dsc) { char *rr; static char rrbuf[4+256]; /*XXX static buffer */ if (*str == ':') { ip4addr_t a; int bits = ip4addr(str + 1, &a, &str); if (!a || bits <= 0) { dswarn(dsc, "invalid A RR"); return 0; } if (bits == 8) a |= IP4A_LOOPBACK; /* only last digit in 127.0.0.x */ SKIPSPACE(str); if (*str == ':') { /* A+TXT */ ++str; SKIPSPACE(str); rr = str - 4; PACK32(rr, a); } else if (*str) { dswarn(dsc, "unrecognized value for an entry"); return 0; } else { /* only A - take TXT from default entry */ unsigned tlen = strlen(def_rr+4); /* tlen is <= 255 */ rr = rrbuf; PACK32(rr, a); memcpy(rr+4, def_rr+4, tlen+1); *rrp = rr; return tlen + 5; } } else { rr = str - 4; memcpy(rr, def_rr, 4); } if (*str) { unsigned len = strlen(str); if (len > 255) { dswarn(dsc, "TXT RR truncated to 255 bytes"); str += 255; } else str += len; *str = '\0'; } *rrp = rr; return 1 + (str - rr); } unsigned unpack32(const unsigned char p[4]) { unsigned n = p[0]; n = (n << 8) | p[1]; n = (n << 8) | p[2]; n = (n << 8) | p[3]; return n; } /* return pointer to the next word in line if first word is the same * as word_lc (ignoring case), or NULL if not. */ char *firstword_lc(char *line, const char *word_lc) { while(*word_lc) if (dns_dnlc(*line) != *word_lc) return NULL; else ++word_lc, ++line; if (!ISSPACE(*line)) return NULL; SKIPSPACE(line); return line; } /* implement TXT substitutions. * `sb' is a buffer where the result will be stored - * at least 255 + 3 characters long */ int txtsubst(char sb[TXTBUFSIZ], const char *txt, const char *s0, const struct dataset *ds) { char *const *sn = ds->ds_subst; unsigned sl; char *const e = sb + 254; char *lp = sb; const char *s, *si, *sx; if (txt[0] == '=') sx = ++txt; else if (sn[SUBST_BASE_TEMPLATE] && *sn[SUBST_BASE_TEMPLATE]) { if (*txt) s0 = txt; sx = s0; txt = sn[SUBST_BASE_TEMPLATE]; } else sx = txt; while(lp < e) { if ((s = strchr(txt, '$')) == NULL) s = (char*)txt + strlen(txt); sl = s - txt; if (lp + sl > e) sl = e - lp; memcpy(lp, txt, sl); lp += sl; if (!*s++) break; if (*s == '$') { si = s++; sl = 1; } else if (*s >= '0' && *s <= '9') { /* $n var */ si = sn[*s - '0']; if (!si) { si = s - 1; sl = 2; } else sl = strlen(si); ++s; } else if (*s == '=') { si = sx; sl = strlen(si); ++s; } else sl = strlen(si = s0); if (lp + sl > e) /* silently truncate TXT RR >255 bytes */ sl = e - lp; memcpy(lp, si, sl); lp += sl; txt = s; } sl = lp - sb; if (sl > 254) sl = 254; return sl; } #ifndef NO_MASTER_DUMP void dump_ip4(ip4addr_t a, const char *rr, const struct dataset *ds, FILE *f) { char name[sizeof("255.255.254.255")]; sprintf(name, "%u.%u.%u.%u", a&255, (a>>8)&255, (a>>16)&255, (a>>24)); dump_a_txt(name, rr, ip4atos(a), ds, f); } static void dump_ip4octets(FILE *f, unsigned idx, ip4addr_t a, unsigned cnt, const char *rr, const struct dataset *ds) { char name[16]; static const char * const fmt[4] = { "%u.%u.%u.%u", "*.%u.%u.%u", "*.%u.%u", "*.%u" }; const unsigned bits = 8 * idx; for(;;) { sprintf(name, fmt[idx], a&255, (a>>8)&255, (a>>16)&255, (a>>24)); dump_a_txt(name, rr, ip4atos(a<= b) { \ if (b - a == 255u) \ fn((bits>>3)+1, a>>8, 1); \ else \ fn(bits>>3, a, b - a + 1); \ return; \ } \ if (a & 255u) { \ fn(bits>>3, a, 256u - (a & 255u)); \ a = (a >> 8) + 1; \ } \ else \ a >>= 8; \ if ((b & 255u) != 255u) { \ fn((bits>>3), (b & ~255u), (b&255u)+1); \ b = (b >> 8) - 1; \ } \ else \ b >>= 8 ip4range_expand_octet(0); ip4range_expand_octet(8); ip4range_expand_octet(16); fn(3, a, b - a + 1); #undef fn #undef ip4range_expand_octet } static inline unsigned ip6nibble(const ip6oct_t addr[IP6ADDR_FULL], unsigned i) { ip6oct_t byte = addr[i / 2]; return (i % 2) ? (byte & 0xf) : (byte >> 4); } /* format DNS name for ip6 address (with some nibbles possibly wild-carded) */ static const char * ip6name(const ip6oct_t *addr, unsigned wild_nibbles) { static char hexdigits[] = "0123456789abcdef"; static char name[IP6ADDR_FULL * 4 + 2] = "*"; char *np = name + 1; unsigned n = 32 - wild_nibbles; /* don't write past end of buffer, even if passed invalid args */ if (n > 32) n = 32; while (n-- > 0) { *np++ = '.'; *np++ = hexdigits[ip6nibble(addr, n)]; } *np = '\0'; return wild_nibbles ? name : name + 2; } /* dump an ip6 address, with some nibbles possible wild-carded */ void dump_ip6(const ip6oct_t *addr, unsigned wild_nibbles, const char *rr, const struct dataset *ds, FILE *f) { const char *dns_name = ip6name(addr, wild_nibbles); const char *ipsubst = NULL; if (rr) { /* careful: addr may point to a short array (e.g. IP6ADDR_HALF) */ ipsubst = ip6atos(addr, IP6ADDR_FULL - wild_nibbles / 2); } dump_a_txt(dns_name, rr, ipsubst, ds, f); } /* dump an ip6 address range. * * BEG is the first address in the range, END is one past the last * address included in the range. END = NULL means no end limit. * * NB: The semantics of END are different than for dump_ip4range! */ void dump_ip6range(const ip6oct_t *beg, const ip6oct_t *end, const char *rr, const struct dataset *ds, FILE *f) { ip6oct_t addr[IP6ADDR_FULL]; memcpy(addr, beg, IP6ADDR_FULL); while (1) { unsigned nwild, i; unsigned maxwild = 32; if (end) { /* find first nibble of end which is greater than addr */ for (i = 0; i < 32; i++) { if (ip6nibble(end, i) != ip6nibble(addr, i)) break; } if (i == 32 || ip6nibble(end, i) < ip6nibble(addr, i)) return; /* end <= addr */ /* we can only wildcard after this nibble */ maxwild = 31 - i; } /* can only wildcard nibbles where we're starting from zero */ for (nwild = 0; nwild < maxwild; nwild++) if (ip6nibble(addr, 31 - nwild) != 0) break; dump_ip6(addr, nwild, rr, ds, f); /* advance address to one past end of wildcarded range */ /* Increment right-most non-wildcarded nibble */ i = 15 - nwild / 2; addr[i] += (nwild % 2) ? 0x10 : 0x01; while (addr[i] == 0) { /* propagate carry */ if (i == 0) return; /* wrapped */ addr[--i]++; } } } void dump_a_txt(const char *name, const char *rr, const char *subst, const struct dataset *ds, FILE *f) { if (!rr) fprintf(f, "%s\tCNAME\texcluded\n", name); else { const unsigned char *a = (const unsigned char*)rr; char sb[TXTBUFSIZ]; unsigned sl = txtsubst(sb, rr + 4, subst, ds); fprintf(f, "%s\tA\t%u.%u.%u.%u\n", name, a[0], a[1], a[2], a[3]); if (sl) { char *p, *n; sb[sl] = '\0'; fprintf(f, "\tTXT\t\""); for(p = sb; (n = strchr(p, '"')) != NULL; p = n + 1) { fwrite(p, 1, n - p, f); putc('\\', f); putc('"', f); } fprintf(f, "%s\"\n", p); } } } #endif char *emalloc(unsigned size) { void *ptr = malloc(size); if (!ptr) oom(); return ptr; } char *ezalloc(unsigned size) { void *ptr = calloc(1, size); if (!ptr) oom(); return ptr; } char *erealloc(void *ptr, unsigned size) { void *nptr = realloc(ptr, size); if (!nptr) oom(); return nptr; } char *ememdup(const void *buf, unsigned len) { char *b = emalloc(len); if (b) memcpy(b, buf, len); return b; } char *estrdup(const char *str) { return ememdup(str, strlen(str) + 1); } /* what a mess... this routine is to work around various snprintf * implementations. It never return <1 or value greather than * size of buffer: i.e. it returns number of chars _actually written_ * to a buffer. * Maybe replace this with an alternative (simplistic) implementation, * only %d/%u/%s, with additional %S to print untrusted data replacing * control chars with something sane, and to print `...' for arguments * that aren't fit (e.g. "%.5s", "1234567" will print `12...') ? */ int vssprintf(char *buf, int bufsz, const char *fmt, va_list ap) { int r = vsnprintf(buf, bufsz, fmt, ap); return r < 0 ? 0 : r >= bufsz ? buf[bufsz-1] = '\0', bufsz - 1 : r; } int ssprintf(char *buf, int bufsz, const char *fmt, ...) { va_list ap; va_start(ap, fmt); bufsz = vssprintf(buf, bufsz, fmt, ap); va_end(ap); return bufsz; } /* logging */ static void vdslog(int level, struct dsctx *dsc, const char *fmt, va_list ap) { char buf[1024]; int pl, l; if ((logto & LOGTO_STDOUT) || (level <= LOG_WARNING && (logto & LOGTO_STDERR))) l = pl = ssprintf(buf, sizeof(buf), "%.30s: ", progname); else if (logto & LOGTO_SYSLOG) l = pl = 0; else return; if (dsc) { if (dsc->dsc_fname) { l += ssprintf(buf + l, sizeof(buf) - l, "file %.60s", dsc->dsc_fname); l += ssprintf(buf + l, sizeof(buf) - l, dsc->dsc_lineno ? "(%d): " : ": ", dsc->dsc_lineno); } else { l += ssprintf(buf + l, sizeof(buf) - l, "%s:%.60s:", dsc->dsc_ds->ds_type->dst_name, dsc->dsc_ds->ds_spec); if (dsc->dsc_subset) { l += ssprintf(buf + l, sizeof(buf) - l, "%s:", dsc->dsc_subset->ds_type->dst_name); if (dsc->dsc_subset->ds_spec) l += ssprintf(buf + l, sizeof(buf) - l, "%s:", dsc->dsc_subset->ds_spec); } l += ssprintf(buf + l, sizeof(buf) - l, " "); } } l += vssprintf(buf + l, sizeof(buf) - l, fmt, ap); if (logto & LOGTO_SYSLOG) { fmt = buf + pl; syslog(level, strchr(fmt, '%') ? "%s" : fmt, fmt); } buf[l++] = '\n'; if (level <= LOG_WARNING) { if (logto & (LOGTO_STDERR|LOGTO_STDOUT)) write(2, buf, l); } else if (logto & LOGTO_STDOUT) write(1, buf, l); } void dslog(int level, struct dsctx *dsc, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vdslog(level, dsc, fmt, ap); va_end(ap); } #define MAXWARN 5 void dswarn(struct dsctx *dsc, const char *fmt, ...) { if (++dsc->dsc_warns <= MAXWARN) { /* prevent syslog flood */ va_list ap; va_start(ap, fmt); vdslog(LOG_WARNING, dsc, fmt, ap); va_end(ap); } } void dsloaded(struct dsctx *dsc, const char *fmt, ...) { va_list ap; if (dsc->dsc_warns > MAXWARN) dslog(LOG_WARNING, dsc, "%d more warnings suppressed", dsc->dsc_warns - MAXWARN); va_start(ap, fmt); if (dsc->dsc_subset) vdslog(LOG_INFO, dsc, fmt, ap); else { struct tm *tm = gmtime(&dsc->dsc_ds->ds_stamp); char buf[128]; vssprintf(buf, sizeof(buf), fmt, ap); dslog(LOG_INFO, dsc, "%04d%02d%02d %02d%02d%02d: %s", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, buf); } va_end(ap); } void zlog(int level, const struct zone *zone, const char *fmt, ...) { va_list ap; char buf[128]; char name[DNS_MAXDOMAIN+1]; va_start(ap, fmt); vssprintf(buf, sizeof(buf), fmt, ap); va_end(ap); dns_dntop(zone->z_dn, name, sizeof(name)); dslog(level, 0, "zone %.70s: %s", name, buf); } rbldnsd-0.997a/dns.h0000664000175000017500000001175312120257742012451 0ustar mjtmjt/* common #include file for dns library. */ #ifndef DNS_PORT #define DNS_PORT 53 /* default DNS port */ #define DNS_MAXPACKET 512 /* max size of UDP packet */ #define DNS_EDNS0_MAXPACKET 2048 /* max size of EDNS0 UDP packet */ #define DNS_MAXDN 255 /* max length of DN */ #define DNS_MAXLABEL 63 /* max length of one DN label */ #define DNS_MAXLABELS (DNS_MAXDN/2) /* max # of labels in a DN */ #define DNS_MAXDOMAIN 1024 /* max length of asciiz DN */ enum dns_class { DNS_C_INVALID = 0, /* invalid class */ DNS_C_IN = 1, /* Internet */ DNS_C_CH = 3, /* CHAOS */ DNS_C_HS = 4, /* HESIOD */ DNS_C_ANY = 255 /* wildcard */ }; enum dns_type { DNS_T_INVALID = 0, /* Cookie. */ DNS_T_A = 1, /* Host address. */ DNS_T_NS = 2, /* Authoritative server. */ DNS_T_MD = 3, /* Mail destination. */ DNS_T_MF = 4, /* Mail forwarder. */ DNS_T_CNAME = 5, /* Canonical name. */ DNS_T_SOA = 6, /* Start of authority zone. */ DNS_T_MB = 7, /* Mailbox domain name. */ DNS_T_MG = 8, /* Mail group member. */ DNS_T_MR = 9, /* Mail rename name. */ DNS_T_NULL = 10, /* Null resource record. */ DNS_T_WKS = 11, /* Well known service. */ DNS_T_PTR = 12, /* Domain name pointer. */ DNS_T_HINFO = 13, /* Host information. */ DNS_T_MINFO = 14, /* Mailbox information. */ DNS_T_MX = 15, /* Mail routing information. */ DNS_T_TXT = 16, /* Text strings. */ DNS_T_RP = 17, /* Responsible person. */ DNS_T_AFSDB = 18, /* AFS cell database. */ DNS_T_X25 = 19, /* X_25 calling address. */ DNS_T_ISDN = 20, /* ISDN calling address. */ DNS_T_RT = 21, /* Router. */ DNS_T_NSAP = 22, /* NSAP address. */ DNS_T_NSAP_PTR = 23, /* Reverse NSAP lookup (deprecated). */ DNS_T_SIG = 24, /* Security signature. */ DNS_T_KEY = 25, /* Security key. */ DNS_T_PX = 26, /* X.400 mail mapping. */ DNS_T_GPOS = 27, /* Geographical position (withdrawn). */ DNS_T_AAAA = 28, /* Ip6 Address. */ DNS_T_LOC = 29, /* Location Information. */ DNS_T_NXT = 30, /* Next domain (security). */ DNS_T_EID = 31, /* Endpoint identifier. */ DNS_T_NIMLOC = 32, /* Nimrod Locator. */ DNS_T_SRV = 33, /* Server Selection. */ DNS_T_ATMA = 34, /* ATM Address */ DNS_T_NAPTR = 35, /* Naming Authority PoinTeR */ DNS_T_KX = 36, /* Key Exchange */ DNS_T_CERT = 37, /* Certification record */ DNS_T_A6 = 38, /* IPv6 address (deprecates AAAA) */ DNS_T_DNAME = 39, /* Non-terminal DNAME (for IPv6) */ DNS_T_SINK = 40, /* Kitchen sink (experimentatl) */ DNS_T_OPT = 41, /* EDNS0 option (meta-RR) */ DNS_T_TSIG = 250, /* Transaction signature. */ DNS_T_IXFR = 251, /* Incremental zone transfer. */ DNS_T_AXFR = 252, /* Transfer zone of authority. */ DNS_T_MAILB = 253, /* Transfer mailbox records. */ DNS_T_MAILA = 254, /* Transfer mail agent records. */ DNS_T_ANY = 255, /* Wildcard match. */ DNS_T_ZXFR = 256, /* BIND-specific, nonstandard. */ DNS_T_MAX = 65536 }; enum dns_rcode { /* reply code */ DNS_R_NOERROR = 0, /* ok, no error */ DNS_R_FORMERR = 1, /* format error */ DNS_R_SERVFAIL = 2, /* server failed */ DNS_R_NXDOMAIN = 3, /* domain does not exists */ DNS_R_NOTIMPL = 4, /* not implemented */ DNS_R_REFUSED = 5, /* query refused */ /* these are for BIND_UPDATE */ DNS_R_YXDOMAIN = 6, /* Name exists */ DNS_R_YXRRSET = 7, /* RRset exists */ DNS_R_NXRRSET = 8, /* RRset does not exist */ DNS_R_NOTAUTH = 9, /* Not authoritative for zone */ DNS_R_NOTZONE = 10, /* Zone of record different from zone section */ /*ns_r_max = 11,*/ /* The following are TSIG extended errors */ DNS_R_BADSIG = 16, DNS_R_BADKEY = 17, DNS_R_BADTIME = 18 }; struct dns_nameval { int val; const char *name; }; extern const struct dns_nameval dns_classtab[]; extern const struct dns_nameval dns_typetab[]; extern const struct dns_nameval dns_rcodetab[]; const struct dns_nameval * dns_findname(const struct dns_nameval *nv, const char *name); #define dns_findclassname(class) dns_findname(dns_classtab, (class)) #define dns_findtypename(type) dns_findname(dns_typetab, (type)) #define dns_findrcodename(rcode) dns_findname(dns_rcodetab, (rcode)) const char *dns_classname(enum dns_class class); const char *dns_typename(enum dns_type type); const char *dns_rcodename(enum dns_rcode rcode); unsigned dns_ptodn(const char *name, unsigned char *dn, unsigned dnsiz); /* convert asciiz string `name' to the DN format, return length or 0 */ unsigned dns_dntop(const unsigned char *dn, char *dst, unsigned dstsiz); unsigned dns_dntol(const unsigned char *srcdn, unsigned char *dstdn); #define dns_dnlc(c) ((c) >= 'A' && (c) <= 'Z' ? (c) - 'A' + 'a' : (c)) unsigned dns_dnlen(const unsigned char *dn); unsigned dns_dnlabels(const unsigned char *dn); /* return number of labels in a dn */ int dns_dnequ(const unsigned char *dn1, const unsigned char *dn2); unsigned dns_dnreverse(const unsigned char *dn, unsigned char *rdn, unsigned len); /* reverse order of labels in a dn; len, if non-zero, should be length * of dn (to save some CPU cycles) */ #endif rbldnsd-0.997a/ip4addr.h0000664000175000017500000000427712130046505013211 0ustar mjtmjt/* common #include header for various helper routines to * manipulate IP4 addreses */ #ifndef _IP4ADDR_H_INCLUDED #define _IP4ADDR_H_INCLUDED #include "config.h" #if !defined(NO_STDINT_H) # include typedef uint32_t ip4addr_t; /* host byte order */ #elif SIZEOF_SHORT == 4 typedef unsigned short ip4addr_t; #else typedef unsigned ip4addr_t; #endif /* parse string to ip4addr_t (if np specified, return * pointer to the next characted in it)...: */ /* ..single address, like inet_aton/inet_addr, * return #bits in _prefix_ (32,24,16 or 8) or <0 on error */ int ip4addr(const char *s, ip4addr_t *ap, char **np); /* ..prefix, 1.2.3.4 or 1.2.3 or 1.2, return number of bits or <0 */ int ip4prefix(const char *s, ip4addr_t *ap, char **np); /* ..CIDR range, return number of bits or <0 if error. * does NOT zerofill hostpart of *ap - &= ip4mask(bits) for this */ int ip4cidr(const char *s, ip4addr_t *ap, char **np); /* ..range of addresses (inclusive) or CIDR, return #bits if * that was CIDR or prefix or 32 if plain range, <0 on error. * does NOT zerofill hostpart of *ap - &= ip4mask(bits) for this. * *bp will be real end of range regardless of netmask */ int ip4range(const char *s, ip4addr_t *ap, ip4addr_t *bp, char **np); /* inet_ntoa() */ const char *ip4atos(ip4addr_t a); /* convert #bits into mask */ /* note: works for bits < 32 only! */ extern const ip4addr_t ip4addr_cidr_netmasks[33]; #define ip4mask(bits) ip4addr_cidr_netmasks[bits] #define IP4A_LOOPBACK 0x7f000000 /* ip4unpack(bytes, a) * * Unpack ip4addr_t to an array of (four) bytes */ #ifndef inline /* compiler supports 'inline' */ static inline void ip4unpack(unsigned char bytes[4], ip4addr_t a) { bytes[0] = (unsigned char)(a >> 24); bytes[1] = (unsigned char)(a >> 16); bytes[2] = (unsigned char)(a >> 8); bytes[3] = (unsigned char)a; } #else /* inline macro defined - compiler may not support 'inline' */ # define ip4unpack(bytes, a) ( bytes[0] = (unsigned char)(a >> 24), \ bytes[1] = (unsigned char)(a >> 16), \ bytes[2] = (unsigned char)(a >> 8), \ bytes[3] = (unsigned char)a ) #endif #endif rbldnsd-0.997a/ip6addr.h0000664000175000017500000000253612130046505013207 0ustar mjtmjt/* common #include header for various helper routines to * manipulate IP6 addreses. */ #ifndef _IP6ADDR_H_INCLUDED #define _IP6ADDR_H_INCLUDED /* IPv6 address is just 16 bytes. Sometimes only part of * all 16 bytes can be used, most commonly it's 64 bits (8 bytes). * All routines accepts pointer to ip6oct_t array of size IP6ADDR_FULL * bytes. */ typedef unsigned char ip6oct_t; #define IP6ADDR_FULL 16 #define IP6ADDR_HALF 8 /* parse string to ip6oct_t (if np specified, return * pointer to the next characted in the input buffer)...: */ /* ..prefix, ffff:ffff:ffff... * Return number of _bits_ (16,32,48,...) or <0 on error/ */ int ip6prefix(const char *s, ip6oct_t ap[IP6ADDR_FULL], char **np); #define ip6addr(s,ap,np) ip6prefix((s),(ap),(np)) /* ..CIDR range, return number of bits or <0 if error. * does NOT zerofill hostpart */ int ip6cidr(const char *s, ip6oct_t ap[IP6ADDR_FULL], char **np); /* applies a /bits mask to v6 address in ap * and optionally stores result into bp (if non-NULL). * Both ap and bp are of size n bytes. * return >1 if any of host bits in a are non-zero * or 0 if all host bits are zero. */ int ip6mask(const ip6oct_t *ap, ip6oct_t *bp, unsigned n, unsigned bits); /* inet_ntoa(). * This routine accepts `an' as size of ap buffer, in bytes. */ const char *ip6atos(const ip6oct_t *ap, unsigned an); #endif rbldnsd-0.997a/mempool.h0000664000175000017500000000204112120257742013323 0ustar mjtmjt/* memory pool #include file */ #ifndef _MEMPOOL_H_INCLUDED #define _MEMPOOL_H_INCLUDED struct mempool_chunk; struct mempool { /* free-once memory pool. All members are private */ struct mempool_chunk *mp_chunk; /* list of chunks with free space */ struct mempool_chunk *mp_fullc; /* list of full chunks */ unsigned mp_nallocs; /* number of allocs so far */ unsigned mp_datasz; /* size of allocated data */ const char *mp_lastbuf; /* last allocated string */ unsigned mp_lastlen; /* length of lastbuf */ }; void mp_init(struct mempool *mp); void *mp_alloc(struct mempool *mp, unsigned size, int align); #define mp_talloc(mp, type) ((type*)mp_alloc((mp), sizeof(type), 1)) void mp_free(struct mempool *mp); char *mp_strdup(struct mempool *mp, const char *str); void *mp_memdup(struct mempool *mp, const void *buf, unsigned len); const char *mp_dstrdup(struct mempool *mp, const char *str); const void *mp_dmemdup(struct mempool *mp, const void *buf, unsigned len); /* dstrdup, dmemdup trying to pack repeated strings together */ #endif rbldnsd-0.997a/istream.h0000664000175000017500000000254612120257742013331 0ustar mjtmjt/* simple read (input) stream (header file) */ #ifndef ISTREAM_BUFSIZE #define ISTREAM_BUFSIZE 65536 /* max line size is ISTREAM_BUFSIZE/2 */ #define ISTREAM_PAD 4 /* extra safety bytes */ struct istream { unsigned char *endp; /* end-of-data pointer (data read so far) in buf */ unsigned char *readp; /* current read pointer within buf */ unsigned char pad1[ISTREAM_PAD]; unsigned char buf[ISTREAM_BUFSIZE]; /* the data pointer */ unsigned char pad2[ISTREAM_PAD]; void *cookie; /* cookie for readfn routine */ int (*readfn)(struct istream *sp, unsigned char *buf, int size, int szhint); void (*freefn)(struct istream *sp); }; #define istream_buf(sp) ((sp)->buf+ISTREAM_EXTRA) int istream_fillbuf(struct istream *sp); int istream_ensurebytes(struct istream *sp, int nbytes); int istream_getline(struct istream *sp, char **linep, char delim); void istream_init(struct istream *sp, int (*readfn)(struct istream*,unsigned char*,int,int), void (*freefn)(struct istream*), void *cookie); void istream_init_fd(struct istream *sp, int fd); void istream_destroy(struct istream *sp); /* checks whenever the given stream is in gzip format */ int istream_compressed(struct istream *sp); /* setup istream to automatically uncompress input if compressed */ int istream_uncompress_setup(struct istream *sp); #endif /* include guard */ rbldnsd-0.997a/btrie.h0000664000175000017500000000607112130046505012761 0ustar mjtmjt/* Level-Compressed Tree Bitmap (LC-TBM) Trie implementation * * Contributed by Geoffrey T. Dairiki * * This file is released under a "Three-clause BSD License". * * Copyright (c) 2013, Geoffrey T. Dairiki * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * * Neither the name of Geoffrey T. Dairiki nor the names of other * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GEOFFREY * T. DAIRIKI BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ #ifndef _BTRIE_H_INCLUDED #define _BTRIE_H_INCLUDED #include "config.h" #ifndef NO_STDINT_H # include typedef uint8_t btrie_oct_t; #else typedef unsigned char btrie_oct_t; #endif /* maximum length of bit string btrie_walk() can handle * * note: this limit is necessitated by the use of fixed length buffers * in btrie_walk() --- btrie_add_prefix() and btrie_lookup() impose no * limit on the length of bitstrings */ #define BTRIE_MAX_PREFIX 128 struct btrie; struct mempool; struct btrie * btrie_init(struct mempool *mp); enum btrie_result { BTRIE_OKAY = 0, BTRIE_ALLOC_FAILED = -1, BTRIE_DUPLICATE_PREFIX = 1 }; enum btrie_result btrie_add_prefix(struct btrie *btrie, const btrie_oct_t *prefix, unsigned len, const void *data); const void *btrie_lookup(const struct btrie *btrie, const btrie_oct_t *pfx, unsigned len); const char *btrie_stats(const struct btrie *btrie); #ifndef NO_MASTER_DUMP typedef void btrie_walk_cb_t(const btrie_oct_t *prefix, unsigned len, const void *data, int post, void *user_data); void btrie_walk(const struct btrie *btrie, btrie_walk_cb_t *callback, void *user_data); #endif /* not NO_MASTER_DUMP */ #endif /* _BTRIE_H_INCLUDED */ rbldnsd-0.997a/rbldnsd.h0000664000175000017500000003733412130046505013312 0ustar mjtmjt/* common rbldnsd #include header */ #include #include #include #include "config.h" #include "ip4addr.h" #include "ip6addr.h" #include "dns.h" #include "mempool.h" #if !defined(__GNUC__) && !defined(__attribute__) # define __attribute__(x) #endif #ifndef PRINTFLIKE # define PRINTFLIKE(fmtp, ap) __attribute__((format(printf,fmtp,ap))) #endif #ifndef UNUSED # define UNUSED __attribute__((unused)) #endif #ifndef NORETURN # define NORETURN __attribute__((noreturn)) #endif extern char *progname; /* limited to 32 chars */ extern int logto; #define LOGTO_STDOUT 0x01 #define LOGTO_STDERR 0x02 #define LOGTO_SYSLOG 0x04 void PRINTFLIKE(2,3) NORETURN error(int errnum, const char *fmt, ...); struct zone; struct dataset; struct dsdata; struct dsctx; struct sockaddr; struct dnspacket { /* private structure */ unsigned char p_buf[DNS_EDNS0_MAXPACKET]; /* packet buffer */ unsigned char *p_endp; /* end of packet buffer */ unsigned char *p_cur; /* current pointer */ unsigned char *p_sans; /* start of answers */ const char *p_substrr; /* for always-listed queries */ const struct dataset *p_substds; const struct sockaddr *p_peer;/* address of the requesting client */ unsigned p_peerlen; }; struct dnsquery { /* q */ unsigned q_type; /* query RR type */ unsigned q_class; /* query class */ unsigned char q_dn[DNS_MAXDN]; /* original query DN, lowercased */ unsigned q_dnlen; /* length of q_dn */ unsigned q_dnlab; /* number of labels in q_dn */ unsigned char *q_lptr[DNS_MAXLABELS]; /* pointers to labels */ }; struct dnsqinfo { /* qi */ unsigned char *const *qi_dnlptr; const unsigned char *qi_dn; /* cached query DN */ unsigned qi_tflag; /* query RR type flag (NSQUERY_XX) */ unsigned qi_dnlen0; /* length of qi_dn - 1 */ unsigned qi_dnlab; /* number of labels in q_dn */ int qi_ip4valid; /* true if qi_ip4 is valid */ int qi_ip6valid; /* true if qi_ip6 is valid */ ip4addr_t qi_ip4; /* parsed IP4 address */ ip6oct_t qi_ip6[IP6ADDR_FULL]; /* parsed IP6 address */ }; #define PACK32(b,n) ((b)[0]=(n)>>24,(b)[1]=(n)>>16,(b)[2]=(n)>>8,(b)[3]=(n)) #define PACK32S(b,n) (*b++=(n)>>24,*b++=(n)>>16,*b++=(n)>>8,*b++=n) #define PACK16(b,n) ((b)[0]=(n)>>8,(b)[1]=(unsigned char)(n)) #define PACK16S(b,n) (*b++=(n)>>8,*b++=(unsigned char)(n)) unsigned unpack32(const unsigned char nb[4]); #define ISSPACE(c) ((c) == ' ' || (c) == '\t') #define ISCOMMENT(c) ((c) == '#' || (c) == ';') #define SKIPSPACE(s) while(ISSPACE(*s)) ++s char *parse_uint32(char *s, unsigned *np); char *parse_uint32_nb(char *s, unsigned char nb[4]); char *parse_time(char *s, unsigned *tp); char *parse_time_nb(char *s, unsigned char nb[4]); char *parse_ttl(char *s, unsigned *ttlp, unsigned defttl); char *parse_timestamp(char *s, time_t *tsp); char *parse_dn(char *s, unsigned char *dn, unsigned *dnlenp); /* parse line in form :ip:text into rr * where first 4 bytes is ip in network byte order. * Note this routine uses 4 bytes BEFORE str (it's safe to call it after * readdslines() */ int parse_a_txt(char *str, const char **rrp, const char *def_rr, struct dsctx *dsc); char *firstword_lc(char *line, const char *word_lc); typedef void ds_startfn_t(struct dataset *ds); typedef int ds_linefn_t(struct dataset *ds, char *line, struct dsctx *dsc); typedef void ds_finishfn_t(struct dataset *ds, struct dsctx *dsc); typedef void ds_resetfn_t(struct dsdata *dsd, int freeall); typedef int ds_queryfn_t(const struct dataset *ds, const struct dnsqinfo *qi, struct dnspacket *pkt); #ifdef NO_MASTER_DUMP typedef void ds_dumpfn_t(void); #define _master_dump_body(n) static void n(void) {} #else typedef void ds_dumpfn_t(const struct dataset *ds, const unsigned char *odn, FILE *f); #define _master_dump_body(n) #endif #define NSQUERY_OTHER 0 #define NSQUERY_SOA (1u<<0) #define NSQUERY_NS (1u<<1) #define NSQUERY_A (1u<<2) #define NSQUERY_MX (1u<<3) #define NSQUERY_TXT (1u<<4) #define NSQUERY_ANY 0xffffu /* special cases for ACLs */ #define NSQUERY_IGNORE 0x010000u #define NSQUERY_REFUSE 0x020000u #define NSQUERY_EMPTY 0x040000u #define NSQUERY_ALWAYS 0x080000u /* result flags from dataset queryfn */ #define NSQUERY_FOUND 0x01 #define NSQUERY_ADDPEER 0x02 struct dstype { /* dst */ const char *dst_name; /* name of the type */ unsigned dst_flags; /* how to pass arguments to queryfn */ unsigned dst_size; /* size of struct dataset */ ds_resetfn_t *dst_resetfn; /* routine to release ds internal data */ ds_startfn_t *dst_startfn; /* routine called at start of every file */ ds_linefn_t *dst_linefn; /* routine to parse input line */ ds_finishfn_t *dst_finishfn; /* finish loading */ ds_queryfn_t *dst_queryfn; /* routine to perform query */ ds_dumpfn_t *dst_dumpfn; /* dump zone in BIND format */ const char *dst_descr; /* short description of a ds type */ }; /* dst_flags */ #define DSTF_IP4REV 0x01 /* ip4 set */ #define DSTF_IP6REV 0x02 /* ip6 set */ #define DSTF_SPECIAL 0x08 /* special ds: non-recursive */ #define declaredstype(t) extern const struct dstype dataset_##t##_type #define definedstype(t, flags, descr) \ static ds_resetfn_t ds_##t##_reset; \ static ds_startfn_t ds_##t##_start; \ static ds_linefn_t ds_##t##_line; \ static ds_finishfn_t ds_##t##_finish; \ static ds_queryfn_t ds_##t##_query; \ static ds_dumpfn_t ds_##t##_dump; \ _master_dump_body(ds_##t##_dump) \ const struct dstype dataset_##t##_type = { \ #t /* name */, flags, sizeof(struct dsdata), \ ds_##t##_reset, ds_##t##_start, ds_##t##_line, ds_##t##_finish, \ ds_##t##_query, ds_##t##_dump, \ descr } declaredstype(ip4set); declaredstype(ip4tset); declaredstype(ip4trie); declaredstype(ip6tset); declaredstype(ip6trie); declaredstype(dnset); declaredstype(dnhash); declaredstype(generic); declaredstype(combined); declaredstype(acl); #define dstype(type) (&dataset_##type##_type) #define isdstype(dst, type) ((dst) == dstype(type)) extern const struct dstype *ds_types[]; /* * Each zone is composed of a set of datasets. * There is a global list of datasets, each * with a timestamp etc. * Each zonedata is composed of a list of files. */ struct dsfile { /* dsf */ time_t dsf_stamp; /* last timestamp of this file */ off_t dsf_size; /* last size of this file */ struct dsfile *dsf_next; /* next file in list */ const char *dsf_name; /* name of this file */ }; struct dssoa { /* dssoa */ unsigned dssoa_ttl; /* TTL value */ const unsigned char *dssoa_odn; /* origin DN */ const unsigned char *dssoa_pdn; /* person DN */ unsigned dssoa_serial; /* SOA serial # */ unsigned char dssoa_n[16]; /* refresh, retry, expire, minttl */ }; struct dsns { /* dsns, nameserver */ struct dsns *dsns_next; /* next nameserver in list */ unsigned char dsns_dn[1]; /* nameserver DN, varlen */ }; struct dataset { /* ds */ const struct dstype *ds_type; /* type of this data */ struct dsdata *ds_dsd; /* type-specific data */ time_t ds_stamp; /* timestamp */ time_t ds_expires; /* when the dataset expires if any */ const char *ds_spec; /* original specification */ struct dsfile *ds_dsf; /* list of files for this data */ struct dssoa *ds_dssoa; /* SOA record */ struct dsns *ds_dsns; /* list of nameservers */ unsigned ds_nsttl; /* TTL for NS records */ #ifndef INCOMPAT_0_99 int ds_nsflags; #define DSF_NEWNS 0x01 /* new-style NS on one line */ #define DSF_NSWARN 0x02 /* warned about new-style NS */ #endif unsigned ds_ttl; /* default ttl for a dataset */ char *ds_subst[11]; /* substitution variables */ #define SUBST_BASE_TEMPLATE 10 struct mempool *ds_mp; /* memory pool for data */ struct dataset *ds_next; /* next in global list */ }; struct dslist { /* dsl */ struct dataset *dsl_ds; ds_queryfn_t *dsl_queryfn; /* cached dsl_ds->ds_type->dst_queryfn */ struct dslist *dsl_next; }; struct zonesoa; struct zonens; #ifndef NO_STATS #if !defined(NO_STDINT_H) typedef uint64_t dnscnt_t; #define PRI_DNSCNT PRIu64 #elif SIZEOF_LONG < 8 && defined(SIZEOF_LONG_LONG) typedef unsigned long long dnscnt_t; #define PRI_DNSCNT "llu" #else typedef unsigned long dnscnt_t; #define PRI_DNSCNT "lu" #endif struct dnsstats { dnscnt_t b_in, b_out; /* number of bytes: in, out */ dnscnt_t q_ok, q_nxd, q_err; /* number of requests: OK, NXDOMAIN, ERROR */ }; extern struct dnsstats gstats; /* global statistics counters */ #endif /* NO_STATS */ #define MAX_NS 32 struct zone { /* zone, list of zones */ unsigned z_stamp; /* timestamp, 0 if not loaded */ time_t z_expires; /* when the zone expires if any */ unsigned char z_dn[DNS_MAXDN+1]; /* zone domain name */ unsigned z_dnlen; /* length of z_dn */ unsigned z_dnlab; /* number of dn labels */ unsigned z_dstflags; /* flags of all datasets */ struct dslist *z_dsl; /* list of datasets */ struct dslist **z_dslp; /* last z_dsl in list */ struct dataset *z_dsacl; /* zone ACL */ /* SOA record */ const struct dssoa *z_dssoa; /* original SOA from a dataset */ struct zonesoa *z_zsoa; /* pre-packed SOA record */ const unsigned char *z_nsdna[MAX_NS]; /* array of nameserver DNs */ unsigned z_nns; /* number of NSes in z_dsnsa[] */ unsigned z_nsttl; /* ttl for NS records */ unsigned z_cns; /* current NS in rotation */ unsigned z_nglue; /* number of glue records */ struct zonens *z_zns; /* pre-packed NS records */ #ifndef NO_STATS struct dnsstats z_stats; /* statistic counters */ struct dnsstats z_pstats; /* for stats monitoring: prev values */ #endif #ifndef NO_DSO void *z_hookdata; /* data ptr for hooks */ #endif struct zone *z_next; /* next in list */ }; void init_zones_caches(struct zone *zonelist); int update_zone_soa(struct zone *zone, const struct dssoa *dssoa); int update_zone_ns(struct zone *zone, const struct dsns *dsns, unsigned ttl, const struct zone *zonelist); /* parse query and construct a reply to it, return len of answer or 0 */ int replypacket(struct dnspacket *p, unsigned qlen, struct zone *zone); const struct zone * findqzone(const struct zone *zonelist, unsigned dnlen, unsigned dnlab, unsigned char *const *const dnlptr, struct dnsqinfo *qi); /* log a reply */ void logreply(const struct dnspacket *pkt, FILE *flog, int flushlog); /* details of DNS packet structure are in rbldnsd_packet.c */ /* add a record into answer section */ void addrr_a_txt(struct dnspacket *pkt, unsigned qtflag, const char *rr, const char *subst, const struct dataset *ds); void addrr_any(struct dnspacket *pkt, unsigned dtp, const void *data, unsigned dsz, unsigned ttl); #define check_query_overwrites(qi) \ if ((qi)->qi_tflag & NSQUERY_EMPTY) return 0; \ if ((qi)->qi_tflag & NSQUERY_ALWAYS) return NSQUERY_ADDPEER int ds_acl_query(const struct dataset *ds, struct dnspacket *pkt); #ifndef NO_MASTER_DUMP void dump_a_txt(const char *name, const char *rr, const char *subst, const struct dataset *ds, FILE *f); void dump_ip4range(ip4addr_t a, ip4addr_t b, const char *rr, const struct dataset *ds, FILE *f); void dump_ip4(ip4addr_t a, const char *rr, const struct dataset *ds, FILE *f); void dumpzone(const struct zone *z, FILE *f); void dump_ip6(const ip6oct_t *addr, unsigned wild_nibbles, const char *rr, const struct dataset *ds, FILE *f); void dump_ip6range(const ip6oct_t *beg, const ip6oct_t *end, const char *rr, const struct dataset *ds, FILE *f); #endif #define TXTBUFSIZ 260 int txtsubst(char txtbuf[TXTBUFSIZ], const char *template, const char *sub0, const struct dataset *ds); struct zone *addzone(struct zone *zonelist, const char *spec); void connectdataset(struct zone *zone, struct dataset *ds, struct dslist *dsl); struct zone *newzone(struct zone **zonelist, unsigned char *dn, unsigned dnlen, struct mempool *mp); struct dataset *nextdataset2reload(struct dataset *ds); int loaddataset(struct dataset *ds); struct dsctx { struct dataset *dsc_ds; /* currently loading dataset */ struct dataset *dsc_subset; /* currently loading subset (combined) */ const char *dsc_fname; /* currently loading file name */ int dsc_lineno; /* current line number */ int dsc_warns; /* number of warnings so far */ unsigned dsc_ip4maxrange; /* max IP4 range allowed */ }; void PRINTFLIKE(3,4) dslog(int level, struct dsctx *dsc, const char *fmt, ...); void PRINTFLIKE(2,3) dswarn(struct dsctx *dsc, const char *fmt, ...); void PRINTFLIKE(2,3) dsloaded(struct dsctx *dsc, const char *fmt, ...); void PRINTFLIKE(3,4) zlog(int level, const struct zone *zone, const char *fmt, ...); /* from rbldnsd_combined.c, special routine used inside ds_special() */ int ds_combined_newset(struct dataset *ds, char *line, struct dsctx *dsc); extern unsigned def_ttl, min_ttl, max_ttl; extern const char def_rr[5]; extern int accept_in_cidr; extern int nouncompress; extern struct dataset *g_dsacl; /* global acl */ extern const char *show_version; /* version.bind CH TXT */ void oom(void); char *emalloc(unsigned size); char *ezalloc(unsigned size); /* zero-fill */ char *erealloc(void *ptr, unsigned size); char *estrdup(const char *str); char *ememdup(const void *buf, unsigned size); #define tmalloc(type) ((type*)emalloc(sizeof(type))) #define tzalloc(type) ((type*)ezalloc(sizeof(type))) #define trealloc(type,ptr,n) ((type*)erealloc((ptr),(n)*(sizeof(type)))) int vssprintf(char *buf, int bufsz, const char *fmt, va_list ap); int PRINTFLIKE(3, 4) ssprintf(char *buf, int bufsz, const char *fmt, ...); /* a helper to shrink an array */ /* some malloc implementations may return NULL on realloc() * even if newsize is smaller than current size, so deal with * this here. Note that if realloc() fails, we're under memory * pressure here, so next reload will probably hit ENOMEM anyway... */ #define SHRINK_ARRAY(type, arr, needed, allocated) \ if ((allocated) > (needed)) { \ type *_temp = trealloc(type, (arr), (needed)); \ if (_temp) { \ (arr) = _temp; (allocated) = (needed); \ } \ } /* a helper macro to remove dups from a sorted array */ #define REMOVE_DUPS(type, arr, num, eq) \ { register type *_p, *_e, *_t; \ _p = arr; _t = _p + num - 1; \ while(_p < _t) \ if (!(eq((_p[0]), (_p[1])))) ++_p; \ else { \ ++_t; _e = _p + 1; \ do \ if (eq((*_p), (*_e))) ++_e; \ else *++_p = *_e++; \ while (_e < _t); \ num = _p + 1 - arr; \ break; \ } \ } /* helper macro to test whenever two given RRs (A and TXT) are equal, * provided that arr is zero, this is an exclusion entry. * if arr is zero, we're treating them equal. * else compare pointers * else memcmp first 4 (A) bytes and strcmp rest (TXT) */ #define rrs_equal(a, b) \ (!(a).rr \ || (a).rr == (b).rr \ || (memcmp((a).rr, (b).rr, 4) == 0 \ && strcmp((a).rr + 4, (b).rr + 4) == 0 \ ) \ ) /* hooks from a DSO extensions */ #ifndef NO_DSO /* prototype for init routine */ int rbldnsd_extension_init(char *arg, struct zone *zonelist); /* return true/false depending whenever hook needs to be reloaded */ extern int (*hook_reload_check)(const struct zone *zonelist); /* perform actual reload, after all zones has been reloaded. */ extern int (*hook_reload)(struct zone *zonelist); /* check whenever this query is allowed for this client: * * 0 = ok, <0 = drop the packet, >0 = refuse */ extern int (*hook_query_access) (const struct sockaddr *requestor, const struct zone *zone, const struct dnsqinfo *qinfo); /* notice result of the OK query */ extern int (*hook_query_result) (const struct sockaddr *requestor, const struct zone *zone, const struct dnsqinfo *qinfo, int positive); #define call_hook(name, args) \ (hook_##name ? hook_##name args : 0) #else /* dummy "functions" */ #define call_hook(name, args) 0 #endif rbldnsd-0.997a/configure0000775000175000017500000001731312173527606013430 0ustar mjtmjt#! /bin/sh # autoconf-style configuration script # set -e name=rbldnsd if [ -f rbldnsd.h -a -f rbldnsd_util.c -a -f debian/changelog ] ; then : else echo "configure: error: sources not found at `pwd`" >&2 exit 1 fi options="ipv6 stats master_dump zlib dso asserts" for opt in $options; do eval enable_$opt= done if [ -f config.status ]; then . ./config.status fi enable() { opt=`echo "$1" | sed 's/^--[^-]*-//'` case "$opt" in ipv6|stats|master_dump|zlib|dso|asserts) ;; master-dump) opt=master_dump ;; *) echo "configure: unrecognized option \`$1'" >&2; exit 1;; esac eval enable_$opt=$2 } while [ $# -gt 0 ]; do case "$1" in --disable-*|--without-*|--no-*) enable "$1" n;; --enable-*|--with-*) enable "$1" y;; --help | --hel | --he | --h | -help | -hel | -he | -h ) cat <&2; exit 1 ;; esac shift done . ./configure.lib rm -f confdef.h cat >confdef.h <>confdef.h if ac_yesno "sizes of standard integer types" \ ac_compile_run < int main() { printf("#define SIZEOF_SHORT %d\n", sizeof(short)); printf("#define SIZEOF_INT %d\n", sizeof(int)); printf("#define SIZEOF_LONG %d\n", sizeof(long)); return 0; } EOF then cat conftest.out >> confdef.h else ac_fatal "cannot determine sizes of standard types" fi if ac_yesno "for long long" \ ac_compile_run < int main() { long long x; printf("#define SIZEOF_LONG_LONG %d\n", sizeof(long long)); return 0; } EOF then cat conftest.out >>confdef.h else echo "#define NO_LONG_LONG" >>confdef.h fi fi if ac_compile_run_v "whether C compiler defines __SIZEOF_POINTER__" < int main() { #ifdef __SIZEOF_POINTER__ return 0; #else printf("#define __SIZEOF_POINTER__ %d\n", sizeof(int *)); return 1; #endif } EOF then : else cat conftest.out >>confdef.h fi if ac_verbose "byte order" "big-endian" "little-endian" \ ac_compile_run <>confdef.h fi has_inline= for c in inline __inline; do if ac_compile_v "for $c" <>confdef.h fi if ac_compile_v "for socklen_t" < #include int foo() { socklen_t len; len = 0; return len; } EOF then : else echo "#define socklen_t int" >>confdef.h fi if ac_library_find_v 'connect()' "" "-lsocket -lnsl" <>confdef.h else if ac_link_v "for IPv6" < #include #include #include int main() { char h[200]; char s[200]; struct sockaddr_in6 sa; sa.sin6_family = AF_INET6; getnameinfo((struct sockaddr*)&sa, sizeof(sa), h, sizeof(h), s, sizeof(s), 0); return 0; } EOF then : else if [ "$enable_ipv6" ]; then ac_fatal "IPv6 is requested but not available" fi echo "#define NO_IPv6 1" >>confdef.h fi fi # enable_ipv6? if ac_link_v "for mallinfo()" < #include #include int main() { struct mallinfo mi = mallinfo(); return 0; } EOF then : else echo "#define NO_MEMINFO 1" >>confdef.h fi if ac_link_v "for poll()" < #include int main() { struct pollfd pfd[2]; return poll(pfd, 2, 10); } EOF then : else echo "#define NO_POLL 1" >>confdef.h if ac_header_check_v "sys/select.h" then : else echo "#define NO_SELECT_H 1" >>confdefs.h fi fi if ac_link_v "for vsnprintf()" < #include #include int test(char *fmt, ...) { va_list ap; char buf[40]; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); return 0; } int main() { test("test%d", 40); return 0; } EOF then : else ac_fatal "$name requires working vsnprintf() routine" fi if ac_link_v "for writev()/readv()" < #include #include int main() { struct iovec iov; return writev(1, &iov, 1) && readv(1, &iov, 1); } EOF then : else echo "#define NO_IOVEC 1" >>confdef.h fi if ac_link_v "for setitimer()" < #include int main() { struct itimerval itv; itv.it_interval.tv_sec = itv.it_value.tv_sec = 10; itv.it_interval.tv_usec = itv.it_value.tv_usec = 20; setitimer(ITIMER_REAL, &itv, 0); return 0; } EOF then echo "#define HAVE_SETITIMER 1" >>confdef.h fi if [ n = "$enable_zlib" ]; then echo "#define NO_ZLIB 1 /* option disabled */" >>confdef.h elif ac_link_v "for zlib support" -lz < #include #include int main() { z_stream z; int r; r = inflateInit2(&z, 0); r = inflate(&z, Z_NO_FLUSH); r = inflateEnd(&z); return 0; } EOF then LIBS="$LIBS -lz" elif [ "$enable_zlib" ]; then ac_fatal "zlib support is requested but not found/available" else echo "#define NO_ZLIB" >>confdef.h fi if [ -z "$enable_dso" ]; then echo "#define NO_DSO 1 /* disabled by default */" >> confdef.h elif [ n = "$enable_dso" ]; then echo "#define NO_DSO 1 /* option disabled */" >>confdef.h elif ac_link_v "for dlopen() in -dl with -rdynamic" -ldl -rdynamic < int main() { void *handle, *func; handle = dlopen("testfile", RTLD_NOW); func = dlsym(handle, "function"); return 0; } EOF then LIBS="$LIBS -ldl -rdynamic" elif [ "$enable_dso" ]; then ac_fatal "dso support requires dlopen() in -ldl" else echo "#define NO_DSO 1 /* not available */" >> confdef.h fi if [ n = "$enable_stats" ]; then echo "#define NO_STATS 1 /* option disabled */" >>confdef.h fi if [ n = "$enable_master_dump" ]; then echo "#define NO_MASTER_DUMP 1 /* option disabled */" >>confdef.h fi if [ y != "$enable_asserts" ]; then echo "#define NDEBUG 1 /* option disabled */" >>confdef.h fi ac_output Makefile ac_msg "creating config.h" mv -f confdef.h config.h ac_result ok ac_msg "creating config.status" rm -f config.status { echo "# automatically generated by configure to hold command-line options" echo found= for opt in $options; do eval val=\$enable_$opt if [ -n "$val" ]; then echo enable_$opt=$val found=y fi done if [ ! "$found" ]; then echo "# (no options encountered)" fi } > config.status ac_result ok ac_result "all done." exit 0 rbldnsd-0.997a/configure.lib0000664000175000017500000001261012120257742014156 0ustar mjtmjt# configure.lib # a library of shell routines for simple autoconf system # set -e ac_substitutes= rm -f conftest* config.log exec 5>config.log cat <&5 This file contains any messages produced by compilers etc while running configure, to aid debugging if configure script makes a mistake. EOF case `echo "a\c"` in *c*) ac_en=-n ac_ec= ;; *) ac_en= ac_ec='\c' ;; esac ##### Messages ac_msg() { echo $ac_en "$*... $ac_ec" echo ">>> $*" >&5 } ac_checking() { echo $ac_en "checking $*... $ac_ec" echo ">>> checking $*" >&5 } ac_result() { echo "$1" echo "=== $1" >&5 } ac_fatal() { echo "configure: fatal: $*" >&2 echo "=== FATAL: $*" >&5 exit 1 } ac_warning() { echo "configure: warning: $*" >&2 echo "=== WARNING: $*" >&5 } # ac_run command... # captures output in conftest.out ac_run() { # apparently UnixWare (for one) /bin/sh optimizes the following "if" # "away", by checking if there's such a command BEFORE redirecting # output. So error message (like "gcc: command not found") goes # to stderr instead of to conftest.out, and `cat conftest.out' below # fails. if "$@" >conftest.out 2>&1; then return 0 else echo "==== Command invocation failed. Command line was:" >&5 echo "$*" >&5 echo "==== compiler input was:" >&5 cat conftest.c >&5 echo "==== output was:" >&5 cat conftest.out >&5 echo "====" >&5 return 1 fi } # ac_verbose "feature" "ok" "bad" command... ac_verbose() { ac_checking "$1" ok=$2 bad=$3; shift 3 if "$@"; then ac_result $ok return 0 else ac_result $bad return 1 fi } # common case for ac_verbose: yes/no result ac_yesno() { ac_checking "$1" shift if "$@"; then ac_result yes return 0 else ac_result no return 1 fi } ac_subst() { ac_substitutes="$ac_substitutes $*" } ac_define() { if [ -f confdefs.h ]; then echo "#define $1 ${2:-1}" >>confdefs.h else CDEFS="$CDEFS -D$1=${2:-1}" fi } ##### Compiling, linking # run a compiler ac_run_compiler() { rm -f conftest*; cat >conftest.c ac_run $CC $CFLAGS "$@" conftest.c } ac_compile() { ac_run_compiler -c } ac_compile_v() { what="$1"; shift ac_yesno "$what" ac_compile "$@" } ac_link() { ac_run_compiler -o conftest $LIBS "$@" } ac_link_v() { what="$1"; shift ac_yesno "$what" ac_link "$@" } ac_cpp() { ac_run_compiler -E "$@" } ac_cpp_v() { what="$1"; shift ac_yesno "$what" ac_cpp "$@" } ### check for C compiler. Set $CC, $CFLAGS etc ac_prog_c_compiler() { ac_checking "for C compiler" rm -f conftest* echo 'int main(int argc, char **argv) { return 0; }' >conftest.c if [ -n "$CC" ]; then if ac_run $CC -o conftest conftest.c && ac_run ./conftest; then ac_result "\$CC ($CC)" else ac_result no ac_fatal "\$CC ($CC) is not a working compiler" fi else for cc in gcc cc ; do if ac_run $cc -o conftest conftest.c && ac_run ./conftest; then ac_result "$cc" CC=$cc break fi done if [ -z "$CC" ]; then ac_result no ac_fatal "no working C compiler found in \$PATH. please set \$CC variable" fi fi if [ -z "$CFLAGS" ]; then if ac_grep_cpp_v "whether C compiler ($CC) is GNU CC" yEs < EOF } ac_library_find_v() { ac_checking "for libraries needed for $1" shift fond= rm -f conftest*; cat >conftest.c for lib in "$@"; do if ac_run $CC $CFLAGS $LDFLAGS conftest.c -o conftest $LIBS $lib; then found=y break fi done if [ ! "$found" ]; then ac_result "not found" return 1 fi if [ -z "$lib" ]; then ac_result "ok (none needed)" else ac_result "ok ($lib)" LIBS="$LIBS $lib" fi } ac_compile_run() { ac_link "$@" && ac_run ./conftest } ac_compile_run_v() { what="$1"; shift ac_yesno "$what" ac_compile_run "$@" } ac_grep_cpp() { pattern="$1"; shift ac_cpp "$@" && grep "$pattern" conftest.out >/dev/null } ac_grep_cpp_v() { ac_yesno "$1" ac_grep_cpp "$2" } ac_output() { for var in $ac_substitutes; do eval echo "\"s|@$var@|\$$var|\"" done >conftest.sed for file in "$@"; do ac_msg "creating $file" if [ -f $file.in ]; then sed -f conftest.sed $file.in > $file.tmp mv -f $file.tmp $file ac_result ok else ac_result failed ac_fatal "$file.in not found" fi done rm -f conftest* } rbldnsd-0.997a/rbldnsd.80000664000175000017500000013511412163515623013235 0ustar mjtmjt.\" rbldnsd manpage .\" .TH rbldnsd 8 "Jun 2013" .SH NAME rbldnsd \- DNS daemon suitable for running DNS\-based blocklists .SH SYNOPSIS .B rbldnsd options .IR zone : dataset ... .SH DESCRIPTION .PP .B rbldnsd is a small DNS\-protocol daemon which is designed to handle queries to DNS\-based IP\-listing or NAME\-listing services. Such services are a simple way to share/publish a list of IP addresses or (domain) names which are "listed" for for some reason, for example in order to be able to refuse a service to a client which is "listed" in some blocklist. .PP .B rbldnsd is not a general\-purpose nameserver. It will answer to A and TXT (and SOA and NS if such RRs are specified) queries, and has limited ability to answer to some other types of queries. .PP .B rbldnsd tries to handle data from two different perspectives: given a set (or several) of "listed entries" (e.g. IP address ranges or domain names), it builds and serves a DNS zone. Note the two are not the same: list of spammer's IPs is NOT a DNS zone, but may be represented and used as such, provided that some additional information necessary to build complete DNS zone (e.g. NS and SOA records, maybe A records necessary for http to work) is available. In this context, \fBrbldnsd\fR is very different from other general\-purpose nameservers such as BIND or NSD: \fBrbldnsd\fR operates with \fIdatasets\fR (sets of entries \- IP addresses or domain names, logically grouped together), while other general\-purpose nameservers operates with zones. The way how \fBrbldnsd\fR operates may be somewhat confusing to BIND experts. .PP For \fBrbldnsd\fR, a building block is a dataset: e.g., set of insecure/abuseable hosts (IP addresses), set of network ranges that belongs to various spam operations (IP ranges), domain names that belong to spammers (RHSBL) and so on. Usually, different kind of information is placed into separate file, for easy maintenance. From a number of such datasets, \fBrbldnsd\fR constructs a number of DNS zones as specified on command line. A single dataset may be used for several zones, and a single zone may be constructed from several datasets. .PP .B rbldnsd will answer queries to DNS zones specified on the command line as a set of zone specifications. Each zone specification consists of zone basename, dataset type and a comma\-separated list of files that forms a given dataset: .IR zone : type : file , file ,... .PP Several zones may be specified in command line, so that .B rbldnsd will answer queries to any of them. Also, a single zone may be specified several times with different datasets, so it is possible to form a zone from a combination of several different dataset. The same dataset may be reused for several zones too (and in this case, it will be read into memory only once). .PP There are several dataset formats available, each is suitable and optimized (in terms of memory, speed and ease of use) for a specific task. Available dataset types may be grouped into the following categories: .IP lists of IP addresses. When a query is done to a zone with such data, query is interpreted as an IP address in a reverse form (similar to in\-addr.arpa zone). If the address is found in dataset data, .B rbldnsd will return A and TXT records specified in data for that IP. This is a classical IP\-based blocklist. .IP lists of domain names. Similar to list of IP addresses, but with generic domain names instead of IPs (wildcards allowed). This type of data may be used to form a blocklist of e.g. sender domain names. .IP generic list of various types of records, as an auxilary data to form a complete nameserver. This format is similar to bind\-style datafiles, but very simplified. One may specify A, TXT, NS and MX records here. .IP combined set, different datasets from the list above combined in the single (set of) source files, for easy maintenance. .IP acl, or Access Control List. This is a pseudo dataset, that works by overweriting query results based on the requestor (peer) IP address. .SH OPTIONS .PP The following options may be specified: .IP "\fB\-u\fR \fIuser\fR[:\fIgroup\fR]" \fBrbldnsd\fR will change its userid to the specified \fIuser\fR, which defaults to \fBrbldns\fR, and \fIgroup\fR, which by default is the primary group of a \fIuser\fR. \fBrbldnsd\fR will refuse to run as the root user, since this is insecure. .IP "\fB\-r\fR \fIrootdir\fR" \fBrbldnsd\fR will chroot to \fIrootdir\fR if specified. Data files should be available inside \fIrootdir\fR. .IP "\fB\-w\fR \fIworkdir\fR" \fBrbldnsd\fR will change its working directory to \fIworkdir\fR (after chrooting to \fIrootdir\fR if \fB\-r\fR option is also specified). May be used to shorten filename paths. .IP "\fB\-b\fR \fIaddress\fR/\fIport\fR" This option is \fIrequired\fR. \fBrbldnsd\fR will bind to specified \fIaddress\fR and \fIport\fR (port defaults to port 53, domain). Either numeric IP address or a hostname may be specified, and either port number or service name is accepted. It is possible to specify several addresses to listen on this way, by repeating \fB\-b\fR option. Additionally, if there are several addresses listed for a hostname, \fBrbldnsd\fR will listen on all of them. Note that \fBrbldnsd\fR will work slightly faster if only one listening address is specified. Note the delimiter between host and port is a slash (/), not a colon, to be able to correctly handle IPv6 addresses. .IP \fB\-4\fR Use IPv4 listening socket/transport, do not attempt to use IPv6 (ignored if \fBrbldnsd\fR was built without IPv6 support). .IP \fB\-6\fR Use IPv6 listening socket/transport, do not attempt to use IPv4 (this option will be reported as error if IPv6 support was not compiled in). .IP "\fB\-t\fR \fIdefttl\fR:\fIminttl\fR:\fImaxttl\fR" Set default reply time\-to\-live (TTL) value to be \fIdefttl\fR, and set constraints for TTL to \fIminttl\fR and \fImaxttl\fR. Default applies when there's no TTL defined in a given scope (in data file), and constraints are applied when such value provided in data. Any of the values may be omitted, including trailing colon (:) characters, e.g. "\fB\-t\fR\ 30" set default TTL to be 30 secound, and "\fB\-t\fR\ ::120" or "\fB\-t\fR\ ::2m" sets maximum allowed TTL to 2 minutes. All 3 values are in time units, with optional suffix: \fBs\fR (secounds, default), \fBm\fR (minutes), \fBh\fR (hours), \fBd\fR (days) or \fBw\fR (weeks). Zero \fIminttl\fR or \fRmaxttl\fR means no corresponding constraint will be enforced. Default \fIdefttl\fR is 35m. .IP "\fB\-c\fR \fIcheck\fR" Set interval between checking for zone file changes to be \fIcheck\fR, default is 1m (one minute). \fBrbldnsd\fR will check zone file's last modification time every so often, and if it detects a change, zone will be automatically reloaded. Setting this value to 0 disables automatic zone change detection. This procedure may also be triggered by sending a SIGHUP signal to \fBrbldnsd\fR (see SIGNALS section below). .IP \fB\-e\fR Allow non\-network addresses to be used in CIDR ranges. Normally, \fBrbldnsd\fR rejects addresses such as 127.2.3.4/24, where prefix is not within the network mask derived from bit number (in this case, correct form is 127.2.3.0/24, note the trailing zero in prefix). This is done in order to catch possible typos in zones (\fBrbldnsd\fR will warn about a problem and will ignore such entry). This option disables checking whether the CIDR prefix fits within the network mask. .IP \fB\-q\fR Quick and quiet start. Normally, .B rbldnsd does socket initialization and zone load in foreground, writing progress and statistic to standard output, and aborts in case of any errors. With this flag, nothing will be printed and first zone load will be done in background (unless \fB\-n\fR option is also given), and zone load errors will be non\-fatal. .IP "\fB\-p\fR \fIpidfile\fR" Write rbldnsd's pid to the specified \fIpidfile\fR, so it will be easily findable. This file gets written before entering a chroot jail (if specified) and before changing userid, so it's ok to specify e.g. /var/run/rbldnsd.pid here. .IP "\fB\-l\fR \fIlogfile\fR" Specifies a file to which log all requests made. This file is created after entering a chroot jail and becoming a user. Logfiles may be quite large, esp. on busy sites (\fBrbldnsd\fR will log \fIevery\fR recognized request if this option is specified). This option is mainly intended for debugging purposes. Upon receiption of SIGHUP signal, \fBrbldnsd\fR reopens its logfile. If \fIlogfile\fR prefixed with a plus sign (+), logging will not be buffered (i.e. each line will be flushed to disk); by default, logging is buffered to reduce system load. Specify a single hyphen (\-) as a filename to log to standard output (filedescriptor 1), either buffered by default, or line-buffered if specified as `+\-' (standard output will not be "reopened" upon receiving SIGHUP signal, but will be flushed in case logging is buffered). .IP "\fB\-s\fR \fIstatsfile\fR" Specifies a file where \fBrbldnsd\fR will write a line with short statistic summary of queries made per zone, every check (\fB\-c\fR) interval. Format of each line is: .nf \fItimestamp\fR \fIzone\fR:\fIqtot\fR:\fIqok\fR:\fIqnxd\fR:\fIbin\fR:\fIbout\fR \fIzone\fR:... .fi where \fItimestamp\fR is unix time (secounds since epoch), \fIzone\fR is the name of the base zone, \fIqtot\fR is the total number of queries received, \fIqok\fR \- number of positive replies, \fIqnxd\fR \- number of NXDOMAIN replies, \fIbin\fR is the total number of bytes read from network (excluding IP/UDP overhead and dropped packets), \fIbout\fR is the total number of bytes written to network. Ther are as many such tuples as there are zones, and one extra, total typle at the end, with \fIzone\fR being "*", like: .nf 1234 bl1.ex:10:5:4:311:432 bl2.ex:24:13:7:248:375 *:98:35:12:820:987 .fi Note the total values may be larger than the sum of per-zone values, due to queries made against unlisted zones, or bad/broken packets. \fBRbldnsd\fR will write bare timestamp to \fIstatsfile\fR when it is starting up, shutting down or when statistic counters are being reset after receiving SIGUSR2 signal (see below), to indicate the points where the counters are starting back from zero. By default, \fBrbldnsd\fR writes absolute counter values into \fIstatsfile\fR (number of packets (bytes) since startup or last reset). \fIstatsfile\fR may be prefixed with plus sign (+), in which case \fBrbldnsd\fR will write delta values, that is, number of packets or bytes since last write, or number of packets (bytes) per unit of time ("incremental" mode, hence the "+" sign). .IP \fB\-n\fR Do not become a daemon. Normally, \fBrbldnsd\fR will fork and go to the background after successful initialization. This option disables this behaviour. .IP \fB\-f\fR Request \fBrbldnsd\fR to continue processing requests during data reloads. \fBRbldnsd\fR forks a child process to handle requests while parent reloads the data. This ensures smooth operations, but requires more memory, since two copies of data is keept in memory during reload process. .IP \fB\-d\fR Dump all zones to stdout in BIND format and exit. This may be suitable to convert easily editable rbldnsd-style data into BIND zone. \fBrbldnsd\fR dumps all zones as one stream, so one may want to specify only one zone with \fB\-d\fR. Zone file will have appropriate $ORIGIN tags. Note that data generated may be really huge (as BIND format isn't appropriate for this sort of things), and some entries may not be really the same in BIND as in \fBrbldnsd\fR (e.g., IP netblocks of large size will be represented as wildcard entries \- 10.0.0.0/8 will become *.10; excluded entries will be represented by a CNAME to `excluded' name, so such name should not be present in a data set). In this mode, \fBrbldnsd\fR ignores \fB\-r\fR (root directory) option. .IP \fB\-v\fR Do not show exact version information in response to version.bind CH TXT queries (by default \fBrbldnsd\fR responds to such queries since version 0.98). With single \fB\-v\fR, \fBrbldnsd\fR will only return "rbldnsd" to the caller, without the version number. Second \fB\-v\fR disables providing any information in response to such requests, i.e. \fBrbldnsd\fR will return REFUSE code. .IP \fB\-C\fR Disable automatic on\-the\-fly uncompression of data files if this feature is compiled in (see below). .IP \fB\-A\fR .IP \fB\-a\fR Controls "laziness" of \fBrbldnsd\fR when constructing replies. With \fB\-a\fR specified, \fBrbldnsd\fR does not add AUTH section (containing NS records) to replies unless explicitly asked for NS records. It is equivalent to BIND9 "minimal\-answers" configuration setting. While with \fB\-A\fR specified, \fBrbldnsd\fR will always fill in AUTH section, increasing size of replies dramatically but allowing (caching) resolver clients to react faster to changes in nameserver lists. Currently (as of 0.996 version), non\-lazy (as with \fB\-A\fR) mode is the default, but it will change in future release. .IP "\fB\-x\fR \fIextension\fR" Load the given \fIextension\fR file (a dynamically-linked library, usually with ".so" suffix). This allows to gather custom statistics or perform other custom tasks. See separate document for details about building and using extensions. This feature is not available on all platforms, and can be disabled at compile time. .IP "\fB\-X\fR \fIextarg\fR" Pass the given argument, \fIextarg\fR, to the extension loaded with \fB\-x\fR. .SH "DATASET TYPES AND FORMATS" .PP Dataset files are text files which are interpreted depending on type specified in command line. Empty lines and lines starting with hash character (#) or semicolon (;) are ignored, except for a special case outlined below in section titled "Special Entries". .PP A (comma\-separated) list of files in dataset specification (in \fItype\fR:\fIfile\fR,\fIfile\fR,...) is interpreted as if all files where logically combined into one single file. .PP When compiled with zlib support, .B rbldnsd is able to read gzip\-compressed data files. So, every \fIfile\fR in dataset specification can be compressed with \fBgzip\fR(1), and .B rbldnsd will read such a file decompressing it on\-the\-fly. This feature may be turned off by specifying \fB\-C\fR option. .PP .B rbldnsd is designed to service a DNSBL, where each entry have single A record and optional TXT record assotiated with it. .B rbldnsd allows to specify A value and TXT \fItemplate\fR either for each entry individually, or to use default A value and TXT template pair for a group of entries. See section "Resulting A values and TXT templates" below for a way to specify them. .SS "Special Entries" .PP If a line starts with a dollar sign ($), hash character and a dollar sign (#$), semicolon and dollar sign (;#) or colon and a dollar sign (:$), it is interpreted in a special way, regardless of dataset type (this is one exception where a line starting with hash character is not ignored \- to be able to use zone files for both \fBrbldnsd\fR and for DJB's rbldns). The following keywords, following a dollar sign, are recognized: .IP "\fB$SOA\fR \fIttl origindn persondn serial refresh retry expire minttl" Specifies SOA (Start Of Authority) record for all zones using this dataset. Only first SOA record is interpreted. This is the only way to specify SOA \- by default, \fBrbldnsd\fR will not add any SOA record into answers, and will REFUSE to answer to certain queries (notably, SOA query to zone's base domain name). It is recommended, but not mandatory to specify SOA record for every zone. If no SOA is given, negative replies will not be cacheable by caching nameservers. Only one, first $SOA line is recognized in every dataset (all subsequent $SOA lines encountered in the same dataset are silently ignored). When constructing a zone, SOA will be taken from \fIfirst\fR dataset where $SOA line is found, in an order as specified in command line, subsequent $SOA lines, if any, are ignored. This way, one may overwrite $SOA found in 3rd party data by \fIprepending\fR small local file to the dataset in question, listing it before any other files. .IP If \fIserial\fR value specified is zero, timestamp of most recent modified file will be substituted as \fIserial\fR. .IP If \fIttl\fR field is zero, default ttl (\fB\-t\fR option or last \fB$TTL\fR value, see below) will be used. .IP All time fields (ttl, refresh, retry, expire, minttl) may be specified in time units. See \fB\-t\fR option for details. .IP "\fB$NS\fR \fIttl\fR \fInameserverdn\fR \fInameserverdn\fI..." Specifies NS (Name Server) records for all zones using this dataset. Only first $NS line in a dataset is recognized, all subsequent lines are silently ignored. When constructing a zone from several datasets, rbldnsd uses nameservers from $NS line in only first dataset where $NS line is given, in command-line order, just like for $SOA record. Only first 32 namservers are recognized. Individual nameserver(s) may be prefixed with a minus sign (\-), which means this single nameserver will be ignored by \fBrbldnsd\fR. This is useful to temporary comment out one nameserver entry without removing it from the list. If \fIttl\fR is zero, default ttl will be used. The list of NS records, just like $SOA value, are taken from the \fIfirst\fR data file in a dataset where the $NS line is found, subsequent $NS lines, if any, are ignored. .IP "\fB$TTL\fR \fItime-to-live\fR" Specifies TTL (time-to-live) value for all records in current dataset. See also \fB\-t\fR option. \fB$TTL\fR special overrides \fB\-t\fR value on a per-dataset basis. .IP "\fB$TIMESTAMP\fR \fIdstamp\fR [\fIexpires\fR]" (experimental) Specifies the data timestamp \fIdstamp\fR when the data has been generated, and optionally when it will expire. The timestamps are in form \fIyyyy\fR:\fImm\fR:\fIdd\fR[:\fIhh\fR[:\fImi\fR[:\fIss\fR]]], where \fIyyyy\fR is the year like 2005, \fImm\fR is the month number (01..12), \fIdd\fR is the month day number (01..31), \fIhh\fR is hour (00..23), \fImi\fR and \fIss\fR are minutes and secounds (00.59); hours, minutes and secounds are optional and defaults to 0; the delimiters (either colon or dash may be used) are optional too, but are allowed for readability. Also, single zero (0) or dash (\-) may be used as \fIdstamp\fR and/or \fIexpires\fR, indicating the value is not given. \fIexpires\fR may also be specified as \fB+\fIrel\fR, where \fIrel\fR is a time specification (probably with suffix like s, m, h, d) as an offset to \fIdstamp\fR. .B rbldnsd compares \fIdstamp\fR with current timestamp and refuses to load the file if \fIdstamp\fR specifies time in the future. And if \fIexpires\fR is specified, .B rbldnsd will refuse to service requests for that data if current time is greather than the value specified in \fIexpires\fR field. .IP Note that .B rbldnsd will check the data expiry time every time it checks for data file updates (when receiving SIGHUP signal or every \fB\-c\fR interval). If automatic data reload timer (\fB\-c\fR option) is disabled, zones will not be exipired automatically. .IP "\fB$MAXRANGE4\fR \fIrange-size\fR" Specifies maximum size of IPv4 range allowed for IPv4\-based datasets. If an entry covers more IP addresses than \fIrange-size\fR, it will be ignored (and a warning will be logged). \fIrange-size\fR may be specified as a number of hosts, like 256, or as network prefix lenght, like /24 (the two are the same): .nf $MAXRANGE4 /24 $MAXRANGE4 256 .fi This constraint is active for a dataset it is specified in, and can be owerwritten (by subsequent $MAXRANGE statement) by a smaller value, but can not be increased. .IP "\fB$\fIn\fR \fItext\fR" (\fIn\fR is a single digit). Specifies a \fIsubstitution variable\fR for use as $\fIn\fR placeholders (the \fB$\fIn\fR entries are ignored in generic daaset). See section "Resulting A values and TXT templates" below for description and usage examples. .IP "\fB$=\fR \fItext\fR" Set the base template for all individual TXT records. See section "Resulting A values and TXT templates" below for more information. .SS "ip4set Dataset" .PP A set of IP addresses or CIDR address ranges, together with A and TXT resulting values. IP addresses are specified one per line, by an IP address prefix (initial octets), complete IP address, CIDR range, or IP prefix range (two IP prefixes or complete addresses delimited by a dash, inclusive). Examples, to specify 127.0.0.0/24: .nf 127.0.0.0/24 127.0.0 127/24 127\-127.0.0 127.0.0.0\-127.0.0.255 127.0.0.1\-255 .fi to specify 127.16.0.0\-127.31.255.255: .nf 127.16.0.0\-127.31.255.255 127.16.0\-127.31.255 127.16\-127.31 127.16\-31 127.16.0.0/12 127.16.0/12 127.16/12 .fi Note that in prefix range, last boundary is completed with all\-ones (255), not all\-zeros line with first boundary and a prefix alone. In prefix ranges, if last boundary is only one octet (127.16\-31), it is treated as "suffix", as value of last \fIspecified\fR octet of the first boundary prefix (127.16.0\-31 is treated as 127.16.0.0\-127.16.31.255, i.e. 127.16.0.0/19). .PP After an IP address range, A and TXT values for a given entry may be specified. If none given, default values in current scope (see below) applies. If a value starts with a colon, it is interpreted as a pair of A record and TXT template, delimited by colon (:127.0.0.2:This entry is listed). If a value does not start with a colon, it is interpreted as TXT template only, with A record defaulting to the default A value in current scope. .PP IP address range may be followed by a comment char (either hash character (#) or semicolon (;)), e.g.: .nf 127/8 ; loopback network .fi In this case all characters up to the end of line are ignored, and default A and TXT values will be used for this IP range. .PP Every IP address that fits within any of specified ranges is "listed", and .B rbldnsd will respond to reverse queries against it within specified zone with positive results. In contrast, if an entry starts with an exclamation sign (!), this is an .I exclusion entry, i.e. corresponding address range is excluded from being listed (and any value for this record is ignored). This may be used to specify large range except some individual addresses, in a compact form. .PP If a line starts with a colon (:), this line specifies the default A value and TXT template to return (see below) for all subsequent entries up to end of current file. If no default entry specified, and no value specified for a given record, \fBrbldnsd\fR will return 127.0.0.2 for matching A queries and no record for matching TXT queries. If TXT record template is specified and contains occurences of of dollar sign ($), every such occurence is replaced with an IP address in question, so singe TXT template may be used to e.g. refer to a webpage for an additional information for a specific IP address. .SS "ip4trie Dataset" .PP Set of IP4 CIDR ranges with corresponding (A, TXT) values. This dataset is similar to ip4set, but uses a different internal representation. It accepts CIDR ranges only (not a.b.c.d\-e.f.g.h), and allows for the specification of A/TXT values on a per CIDR range basis. (If multiple CIDR ranges match a query, the value for longest matching prefix is returned.) Exclusions are supported too. .PP This dataset is not particularly memory-efficient for storing many single IP addresses \(em it uses about 50% more memory than the ip4set dataset in that case. The ip4trie dataset is better adapted, however, for listing CIDR ranges (whose lengths are not a multiple of 8 bits.) .SS "ip4tset Dataset" .PP "trivial" ip4set: a set of single IP addresses (one per line), with the same A+TXT template. This dataset type is more efficient than ip4set (in both memory usage and access times), but have obvious limitation. It is intended for DNSBLs like DSBL.org, ORDB.org and similar, where each entry uses the same default A+TXT template. This dataset uses only half a memory for the same list of IP addresses compared to \fBip4set\fR. .SS "ip6trie Dataset" .PP Set of IP6 CIDR ranges. This is the IP6 equivalent of the ip4trie dataset. It allows the sepecification of individual A/TXT values for each CIDR range and supports exclusions. Compressed ("::") ip6 notation is supported. .PP Example zone data: .nf # Default A and TXT template valuse :127.0.1.2: Listed, see http://example.com/lookup?$ # A listing, note that trailing :0s can be omitted 2001:21ab:c000/36 # /64 range with non-default A and TXT values 2001:21ab:def7:4242 :127.0.1.3: This one smells funny # compressed notation 2605:6001:42::/52 ::1 # localhost !2605:6001:42::bead # exclusion .fi .SS "ip6tset Dataset" .PP "Trivial" ip6 dataset: a set of /64 IP6 CIDR ranges (one per line), all sharing a single A+TXT template. Exclusions of single IP6 (/128) addresses are also supported. This dataset type is quite memory-efficient \(em it uses about 40% of the memory that the ip6trie dataset would use \(em but has obvious limitations. .PP This dataset wants the /64s listed as four ip6 words, for example: .nf 2001:20fe:23:41ed abac:adab:ad00:42f .fi Exclusions are denoted with a leading exclamation mark. You may also use compressed "::" notation for excluded addresses. E.g.: .nf !abac:adab:ad00:42f:face:0f:a:beef !abac:adab:ad00:42f::2 .fi .SS "dnset Dataset" .PP Set of (possible wildcarded) domain names with associated A and TXT values. Similar to \fBip4set\fR, but instead of IP addresses, data consists of domain names (\fInot\fR in reverse form). One domain name per line, possible starting with wildcard (either with star\-dot (*.) or just a dot). Entry starting with exclamation sign is exclusion. Default value for all subsequent lines may be specified by a line starting with a colon. .PP Wildcards are interpreted as follows: .IP example.com only example.com domain is listed, not subdomains thereof. Not a wildcard entry. .IP *.example.com all subdomains of example.com are listed, but not example.com itself. .IP .example.com all subdomains of example.com \fIand\fR example.com itself are listed. This is a shortcut: to list a domain name itself and all it's subdomains, one may either specify two lines (example.com and *.example.com), or one line (.example.com). .PP This dataset type may be used instead of \fBip4set\fR, provided all CIDR ranges are expanded and reversed (but in this case, TXT template will be expanded differently). .SS "generic Dataset" .PP Generic type, simplified bind\-style format. Every record should be on one line (line continuations are not supported), and should be specified completely (i.e. all domain names in values should be fully\-qualified, entry name may not be omitted). No wildcards are accepted. Only A, TXT, and MX records are recognized. TTL value may be specified before record type. Examples: .IP .nf # bl.ex.com # specify some values for current zone $NS 0 ns1.ex.com ns2.ex.com # record with TTL www 3000 A 127.0.0.1 about TXT "ex.com combined blocklist" .fi .SS "combined Dataset" .PP This is a special dataset that stores no data by itself but acts like a container for several other datasets of any type except of combined type itself. The data file contains an optional common section, where various specials are recognized like $NS, $SOA, $TTL (see above), and a series of sections, each of which defines one (nested) dataset and several subzones of the base zone, for which this dataset should be consulted. New (nested) dataset starts with a line .nf $DATASET \fItype\fR[:\fIname\fR] \fIsubzone\fR \fIsubzone\fR... .fi and all subsequent lines up to the end of current file or to next $DATASET line are interpreted as a part of dataset of type \fItype\fR, with optional \fIname\fR (name is used for logging purposes only, and the whole ":\fIname\fR" (without quotes or square brackets) part is optional). Note that combined datasets cannot be nested. Every \fIsubzone\fR will always be relative to the base zone name specified on command line. If \fIsubzone\fR specified as single character "@", dataset will be connected to the base zone itself. .PP This dataset type aims to simplify subzone maintenance, in order to be able to include several subzones in one file for easy data transfer, atomic operations and to be able to modify list of subzones on remote secondary nameservers. .PP Example of a complete dataset that contains subzone `proxies' with a list of open proxies, subzone `relays' with a list of open relays, subzone `multihop' with output IPs of multihop open relays, and the base zone itself includes proxies and relays but not multihops: .nf # common section $NS 1w ns1.ex.com ns2.ex.com $SOA 1w ns1.ex.com admin.ex.com 0 2h 2h 1w 1h # list of open proxies, # in `proxies' subzone and in base zone $DATASET ip4set:proxy proxies @ :2:Open proxy, see http://bl.ex.com/proxy/$ 127.0.0.2 127.0.0.10 # list of open relays, # in `relays' subzone and in base zone $DATASET ip4set:relay relays @ :3:Open relay, see http://bl.ex.com/relay/$ 127.0.0.2 127.0.2.10 # list of optputs of multistage relays, # in `multihop' subzone only $DATASET ip4set:multihop-relay multihop :4:Multihop open relay, see http://bl.ex.com/relay/$ 127.0.0.2 127.0.9.12 # for the base zone and all subzones, # include several additional records $DATASET generic:common proxies relays multihop @ @ A 127.0.0.8 www A 127.0.0.8 @ MX 10 mx.ex.com # the above results in having the following records # (provided that the base zone specified is bl.ex.com): # proxies.bl.ex.com A 127.0.0.8 # www.proxies.bl.ex.com 127.0.0.8 # relays.bl.ex.com A 127.0.0.8 # www.relays.bl.ex.com 127.0.0.8 # multihop.bl.ex.com A 127.0.0.8 # www.multihop.bl.ex.com 127.0.0.8 # bl.ex.com A 127.0.0.8 # www.bl.ex.com 127.0.0.8 .fi .PP Note that $NS and $SOA values applies to the base zone \fIonly\fR, regardless of the placement in the file. Unlike the $TTL values and $\fIn\fR substitutions, which may be both global and local for a given (sub\-)dataset. .SS "Resulting A values and TXT templates" .PP In all zone file types except generic, A values and TXT templates are specified as following: .nf :127.0.0.2:Blacklisted: http://example.com/bl?$ .fi If a line starts with a colon, it specifies default A and TXT for all subsequent entries in this dataset. Similar format is used to specify values for individual records, with the A value (enclosed by colons) being optional: .nf 127.0.0.2 :127.0.0.2:Blacklisted: http://example.com/bl?$ .fi or, without specific A value: .nf 127.0.0.2 Blacklisted: http://example.com/bl?$ .fi .PP Two parts of a line, delimited by second colon, specifies A and TXT record values. Both are optional. By default (either if no default line specified, or no IP address within that line), \fBrbldnsd\fR will return 127.0.0.2 as A record. 127.0.0 prefix for A value may be omitted, so the above example may be simplified to: .nf :2:Blacklisted: http://example.com/bl?$ .fi There is no default TXT value, so .B rbldnsd will not return anything for TXT queries it TXT isn't specified. .PP When A value is specified for a given entry, but TXT template is omitted, there may be two cases interpreted differently, namely, whenever there's a second semicolon (:) after the A value. If there's no second semicolon, default TXT value for this scope will be used. In contrast, when second semicolon is present, no TXT template will be generated at all. All possible cases are outlined in the following example: .PP .nf # default A value and TXT template :127.0.0.2:IP address $ is listed # 127.0.0.4 will use default A and TXT 127.0.0.4 # 127.0.0.5 will use specific A and default TXT 127.0.0.5 :5 # 127.0.0.6 will use specific a and \fIno\fR TXT 127.0.0.6 :6: # 127.0.0.7 will use default A and specific TXT 127.0.0.7 IP address $ running an open relay .fi .PP In a TXT template, references to substitution variables are replaced with values of that variables. In particular, single dollar sign ($) is replaced by a listed entry (an IP address in question for IP\-based datasets and the domain name for domain\-based datasets). \fB$\fIn\fR\-style constructs, where \fIn\fR is a single digit, are replaced by a substitution variable $\fIn\fR defined for this dataset in current scope (see section "Special Entries" above). To specify a dollar sign as\-is, use \fB$$\fR. .PP For example, the following lines: .nf $1 See http://www.example.com/bl $2 for details 127.0.0.2 $1/spammer/$ $2 127.0.0.3 $1/relay/$ $2 127.0.0.4 This spammer wants some $$$$. $1/$ .fi will result in the following text to be generated: .nf See http://www.example.com/bl/spammer/127.0.0.2 for details See http://www.example.com/bl/relay/127.0.0.3 for details This spammer wants some $$. See http://www.example.com/bl/127.0.0.4 .fi .PP If the "base template" (\fB$=\fR variable) is defined, this template is used for expansion, instead of the one specified for an entry being queried. Inside the base template, \fB$=\fR construct is substituted with the text given for individual entries. In order to stop usage of base template \fB$=\fR for a single record, start it with \fB=\fR (which will be omitted from the resulting TXT value). For example, .nf $0 See http://www.example.com/bl?$= ($) for details 127.0.0.2 r123 127.0.0.3 127.0.0.4 =See other blocklists for details about $ .fi produces the following TXT records: .nf See http://www.example.com/bl?r123 (127.0.0.2) for details See http://www.example.com/bl?127.0.0.3 (127.0.0.3) for details See other blocklists for details about 127.0.0.4 .fi .SS "acl Dataset" .PP This is not a real dataset, while the syntax and usage is the same as with other datasets. Instead of defining which records exists in a given zone and which do not, the .B acl dataset specifies which client hosts (peers) are allowed to query the given zone. The dataset specifies a set of IPv4 and/or IPv6 CIDR ranges (with the syntax exactly the same as understood by the .B ip4trie and .B ip6trie datasets), together with action specifiers. When a query is made from an IP address listed (not .I for the IP address), the specified action changes rules used to construct the reply. Possible actions and their meanings are: .IP :\fBignore\fR ignore all queries from this IP address altogether. .B rbldnsd acts like there was no query received at all. This is the default action. .IP :\fBrefuse\fR refuse all queries from the IP in question. .B rbldnsd will always return REFUSED DNS response code. .IP :\fBempty\fR pretend there's no data in all other datasets for the given zone. This means that all the clients in question will always receive reply from .B rbldnsd telling that the requested IP address or domain name is not listed in a given DNSBL. .B rbldnsd still replies to metadata queries (SOA and NS records, and to all queries satisfied by generic dataset if specified for the given zone) as usual. .IP :\fBpass\fR process the request as usual. This may be used to add a "whitelisting" entry for a network/host bloked by another (larger) ACL entry. .IP \fIa_txt_template\fR usual A+TXT template as used by other datasets. This means that .B rbldnsd will reply to any valid DNSBL query with "it is listed" answer, so that the client in question will see every IP address or domain name is listed in a given DNSBL. TXT record used in the reply is taken from the acl dataset instead of real datasets. Again, just like with \fBempty\fR case, \fBrbldnsd\fR will continue replying to metadata queries (including generic datasets if any) as usual. .PP Only one ACL dataset can be specified for a given zone, and each zone must have at least one non\-acl dataset. It is also possible to specify one global ACL dataset, by specifying empty zone name (which is not allowed for other dataset types), like .br .nf \fBrbldnsd\fR ... :\fBacl\fR:\fIfilename\fR... .fi .br In this case the ACL defined in \fIfilename\fR applies to all zones. If there are both global ACL and local zone-specific ACL specified, both will be consulted and actions taken in the order specified above, ie, if either ACL returns \fBignore\fR for this IP, the request will be ignored, else if either ACL returns \fBrefuse\fR, the query will be refused, and so on. If both ACLs specifies "always listed" A+TXT template, the reply will contain A+TXT from global ACL. .PP For this dataset type, only a few $-style specials are recognized. In particular, $SOA and $NS keywords are not allowed. When .B rbldnsd performs \fB$\fR substitution in the TXT template returned from ACL dataset, it will use client IP address to substitute for a single $ character, instead of the IP address or domain name found in the original query. .SH SIGNALS .B Rbldnsd handles the following signals: .IP \fBSIGHUP\fR recheck zone files and reload any outdated ones. This is done automatically if enabled, see \fB\-c\fR option. Additionally, .B rbldnsd will reopen logfile upon receiving SIGHUP, if specified (\fB\-l\fR option). .IP "\fBSIGTERM\fR, \fBSIGINT\fR" Terminate process. .IP \fBSIGUSR1\fR Log current statistic counters into syslog. .B Rbldnsd collects how many packets it handled, how many bytes was received, sent, how many OK requests/replies (and how many answer records) was received/sent, how many NXDOMAIN answers was sent, and how many errors/refusals/etc was sent, in a period of time. .IP \fBSIGUSR2\fR The same as SIGUSR1, but reset all counters and start new sample period. .SH NOTES .PP Some unsorted usage notes follows. .SS "Generating and transferring data files" .PP When creating a data file for \fBrbldnsd\fR (and for anything else, it is a general advise), it is a good idea to create the data in temporary file and rename the temp file when all is done. \fINever\fR try to write to the main file directly, it is possible that at the same time, \fBrbldnsd\fR will try to read it and will get incomplete data as the result. The same applies to copying data using \fBcp\fR(1) utility and similar (including \fBscp\fR(1)), that performs copying over existing data. Even if you're sure noone is reading the data while you're copying or generating it, imagine what will happen if you will not be able to complete the process for whatever reason (interrupt, filesystem full, endless number of other reasons...). In most cases is better to keep older but correct data instead of leaving incomplete/corrupt data in place. .PP Right: .nf scp remote:data target.tmp && mv target.tmp target .fi \fIWrong\fR: .nf scp remote:data target .fi Right: .nf ./generate.pl > target.tmp && mv target.tmp target .fi \fIWrong\fR: .nf ./generate.pl > target .fi .PP From this point of view, \fBrsync\fR(1) command seems to be safe, as it \fIalways\fR creates temporary file and renames it to the destination only when all is ok (but note the \-\-partial option, which is good for downloading something but may be wrong to transfer data files \-\- usually you don't want partial files to be loaded). In contrast, \fBscp\fR(1) command is \fInot\fR safe, as it performs direct copying. You may still use \fBscp\fR(1) in a safe manner, as shown in the example above. .PP Also try to eliminate a case when two (or more) processes performs data copying/generation at the same time to the same destination. When your data is generated by a cron job, use file locking (create separate lock file (which should never be removed) and flock/fcntl it in exclusive mode without waiting, exiting if lock fails) before attempting to do other file manipulation. .SS "Absolute vs relative domain names" .PP All \fIkeys\fR specified in dataset files are always relative to the zone base DN. In contrast, all the \fIvalues\fR (NS and SOA records, MX records in generic dataset) are absolute. This is different from BIND behaviour, where trailing dot indicates whenever this is an absolute or relative DN. Trailing dots in domain names are ignored by \fBrbldnsd\fR. .SS "Aggregating datasets" .PP Several zones may be served by \fBrbldnsd\fR, every zone may consist of several datasets. There are numerous ways to combine several data files into several zones. For example, suppose you have a list of dialup ranges in file named `dialups', and a list of spammer's ip addresses in file named `spammers', and want to serve 3 zones with \fBrbldnsd\fR: dialups.bl.ex.com, spam.bl.ex.com and bl.ex.com which is a combination of the two. There are two ways to do this: .PP .nf rbldnsd \fIoptions...\fR \\ dialups.bl.ex.com:ip4set:dialups \\ spam.bl.ex.com:ip4set:spammers \\ bl.ex.com:ip4set:dialups,spammers .fi .PP or: .PP .nf rbldnsd \fIoptions...\fR \\ dialups.bl.ex.com:ip4set:dialups \\ spam.bl.ex.com:ip4set:spammers \\ bl.ex.com:ip4set:dialups \\ bl.ex.com:ip4set:spammers .fi .PP (note you should specify combined bl.ex.com zone .I after all its subzones in a command line, or else subzones will not be consulted at all). .PP In the first form, there will be 3 independent data sets, and every record will be stored 2 times in memory, but only one search in internal data structures will be needed to resolve queries for aggregate bl.ex.com. In second form, there will be only 2 data sets, every record will be stored only once (both datasets will be reused), but 2 searches will be performed by \fBrbldnsd\fR to answer queries against aggregate zone (but difference in speed is almost unnoticeable). Note that when aggregating several data files into one dataset, an exclusion entry in one file becomes exclusion entry in the whole dataset (which may be a problem when aggregating dialups, where exclusions are common, with open relays/proxies, where exclusions are rare if at all used). .PP Similar effect may be achieved by using \fBcombined\fR dataset type, sometimes more easily. \fBcombined\fR dataset results in every nested dataset to be used independantly, like in second form above. .PP \fBcombined\fR dataset requires \fBrbldnsd\fR to be the authoritative nameserver for the whole base zone. Most important, one may specify SOA and NS records for the base zone \fIonly\fR. So, some DNSBLs which does not use a common subzone for the data, cannot use this dataset. An example being DSBL.org DNSBL, where each of list.dsbl.org, multihop.dsbl.org and unconfirmed.dsbl.org zones are separate, independant zones with different set of nameservers. But for DSBL.org, where each dataset is really independant and used only once (there's no (sub)zone that is as a combinations of other zones), \fBcombined\fR dataset isn't necessary. In contrast, SORBS.net zones, where several subzones used and main zone is a combination of several subzones, \fBcombined\fR dataset is a way to go. .SS "All authoritative nameservers should be set up similarily" .PP When you have several nameservers for your zone, set them all in a similar way. Namely, if one is set up using \fBcombined\fR dataset, all the rest should be too, or else DNS meta\-data will be broken. This is because metadata (SOA and NS) records returned by nameservers using \fBcombined\fR and other datasets will have different origin. With \fRcombined\fR dataset, \fBrbldnsd\fR return NS and SOA records for the base zone, not for any subzone defined inside the dataset. Given the above example with dialups.bl.ex.com, spammers.bl.ex.com and aggregate bl.ex.com zones, and two nameservers, first is set up in any ways described above (using individual datasets for every of the 3 zones), and second is set up for the whole bl.ex.com zone using \fBcombined\fR dataset. In this case, for queries against dialups.bl.ex.com, first nameserver will return NS records like .nf dialups.bl.ex.com. IN NS a.ns.ex.com. .fi while second will always use base zone, and NS records will look like .nf bl.ex.com. IN NS a.ns.ex.com. .fi All authoritative nameservers for a zone must have consistent metadata records. The only way to achieve this is to use similar configuration (combined or not) on all nameservers. Have this in mind when using other software for a nameserver. .SS "Generic dataset usage" .PP .B generic dataset type is very rudimentary. It's purpose is to complement all the other type to form complete nameserver that may answer to A, TXT and MX queries. This is useful mostly to define A records for HTTP access (relays.bl.example.com A, www.bl.example.com A just in case), and maybe descriptive texts as a TXT record. .PP Since \fBrbldnsd\fR only searches \fIone\fR, most closely matching (sub)zone for every request, one cannot specify a single e.g. \fBgeneric\fR dataset in form .nf proxies TXT list of open proxies www.proxies A 127.0.0.8 relays TXT list of open relays www.relays A 127.0.0.9 .fi for several (sub)zones, each of which are represented as a zone too (either in command line or as \fBcombined\fR dataset). Instead, several \fBgeneric\fR datasets should be specified, separate one for every (sub)zone. If the data for every subzone is the same, the same, single dataset may be used, but it should be specified for every zone it should apply to (see \fBcombined\fR dataset usage example above). .SH BUGS .PP Most of the bugs outlined in this section aren't really bugs, but present due to non-standartized and thus unknown expected behaviour of a nameserver that serves a DNSBL zone. .B rbldnsd matches BIND runtime behaviour where appropriate, but not always. .PP .B rbldnsd lowercases some domain names (the ones that are lookup keys, e.g. in `generic' and `dnset' datasets) when loading, to speed up lookup operations. This isn't a problem in most cases. .PP There is no TCP mode. If a resource record does not fit in UDP packet (512 bytes), it will be silently ignored. For most usages, this isn't a problem, because there should be only a few RRs in an answer, and because one record is usually sufficient to decide whenever a given entry is "listed" or not. .B rbldnsd isn't a full\-featured nameserver, after all. .PP .B rbldnsd will not always return a list of nameserver records in the AUTHORITY section of every positive answer: NS records will be provided (if given) only if there's a room for them in single UDP packet. If records does not fit, AUTHORITY section will be empty. .PP .B rbldnsd does not allow AXFR operations. For DNSBLs, AXFR is the stupidiest yet common thing to do \- use rsync for zone transfers instead. This isn't a bug in .B rbldnsd itself, but in common practice of using AXFR and the like to transfer huge zones in a format which isn't suitable for such a task. Perhaps in the future, if there will be some real demand, I'll implement AXFR "server" support (so that .B rbldnsd will be able to act as master for BIND nameservers, but not as secondary), but the note remains: use rsync. .PP .B rbldnsd truncates all TXT records to be at most 255 bytes. DNS specs allows longer TXTs, but long TXTs is something that should be avoided as much as possible \- TXT record is used as SMTP rejection string. Note that DNS UDP packet is limited to 512 bytes. .B rbldnsd will log a warning when such truncation occurs. .SH VERSION This manpage corresponds to \fBrbldnsd\fR version \fB0.997\fR. .SH AUTHOR The \fBrbldnsd\fR daemon written by Michael Tokarev , based on ideas by Dan Bernstein and his djbdns package, with excellent contributions by Geoffrey T. Dairiki . .SH LICENCE Mostly GPL, with some code licensed under 3-clause BSD license. rbldnsd-0.997a/qsort.c0000664000175000017500000001767012120257742013034 0ustar mjtmjt/* Adopted from GNU glibc by Mjt. * See stdlib/qsort.c in glibc */ /* Copyright (C) 1991, 1992, 1996, 1997, 1999 Free Software Foundation, Inc. This file is part of the GNU C Library. Written by Douglas C. Schmidt (schmidt@ics.uci.edu). The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ /* Usage: * first, define the following: * QSORT_TYPE - type of array elements * QSORT_BASE - pointer to array * QSORT_NELT - number of elements in the array (must not be 0) * QSORT_LT - QSORT_LT(a,b) should return true if *a < *b * and second, just #include this file into the place you want it. * Some C code will be inserted into that place, to sort array defined * by QSORT_TYPE, QSORT_BASE, QSORT_NELT and comparison routine QSORT_LT. */ /* Swap two items pointed to by A and B using temporary buffer t. */ #define _QSORT_SWAP(a, b, t) ((void)((t = *a), (*a = *b), (*b = t))) /* Discontinue quicksort algorithm when partition gets below this size. This particular magic number was chosen to work best on a Sun 4/260. */ #define _QSORT_MAX_THRESH 4 /* Stack node declarations used to store unfulfilled partition obligations * (inlined in QSORT). typedef struct { TYPE *_lo, *_hi; } qsort_stack_node; */ /* The next 4 #defines implement a very fast in-line stack abstraction. */ /* The stack needs log (total_elements) entries (we could even subtract log(MAX_THRESH)). Since total_elements has type unsigned, we get as upper bound for log (total_elements): bits per byte (CHAR_BIT) * sizeof(unsigned). */ #define _QSORT_STACK_SIZE (8 * sizeof(unsigned)) #define _QSORT_PUSH(top, low, high) \ (((top->_lo = (low)), (top->_hi = (high)), ++top)) #define _QSORT_POP(low, high, top) \ ((--top, (low = top->_lo), (high = top->_hi))) #define _QSORT_STACK_NOT_EMPTY (_stack < _top) /* Order size using quicksort. This implementation incorporates four optimizations discussed in Sedgewick: 1. Non-recursive, using an explicit stack of pointer that store the next array partition to sort. To save time, this maximum amount of space required to store an array of SIZE_MAX is allocated on the stack. Assuming a 32-bit (64 bit) integer for size_t, this needs only 32 * sizeof(stack_node) == 256 bytes (for 64 bit: 1024 bytes). Pretty cheap, actually. 2. Chose the pivot element using a median-of-three decision tree. This reduces the probability of selecting a bad pivot value and eliminates certain extraneous comparisons. 3. Only quicksorts TOTAL_ELEMS / MAX_THRESH partitions, leaving insertion sort to order the MAX_THRESH items within each partition. This is a big win, since insertion sort is faster for small, mostly sorted array segments. 4. The larger of the two sub-partitions is always pushed onto the stack first, with the algorithm then concentrating on the smaller partition. This *guarantees* no more than log (total_elems) stack size is needed (actually O(1) in this case)! */ { QSORT_TYPE *const _base = (QSORT_BASE); const unsigned _elems = (QSORT_NELT); QSORT_TYPE _hold; if (_elems > _QSORT_MAX_THRESH) { QSORT_TYPE *_lo = _base; QSORT_TYPE *_hi = _lo + _elems - 1; struct { QSORT_TYPE *_hi, *_lo; } _stack[_QSORT_STACK_SIZE], *_top = _stack + 1; while (_QSORT_STACK_NOT_EMPTY) { QSORT_TYPE *_left_ptr, *_right_ptr; /* Select median value from among LO, MID, and HI. Rearrange LO and HI so the three values are sorted. This lowers the probability of picking a pathological pivot value and skips a comparison for both the LEFT_PTR and RIGHT_PTR in the while loops. */ QSORT_TYPE *_mid = _lo + ((_hi - _lo) >> 1); if (QSORT_LT (_mid, _lo)) _QSORT_SWAP (_mid, _lo, _hold); if (QSORT_LT (_hi, _mid)) { _QSORT_SWAP (_mid, _hi, _hold); if (QSORT_LT (_mid, _lo)) _QSORT_SWAP (_mid, _lo, _hold); } _left_ptr = _lo + 1; _right_ptr = _hi - 1; /* Here's the famous ``collapse the walls'' section of quicksort. Gotta like those tight inner loops! They are the main reason that this algorithm runs much faster than others. */ do { while (QSORT_LT (_left_ptr, _mid)) ++_left_ptr; while (QSORT_LT (_mid, _right_ptr)) --_right_ptr; if (_left_ptr < _right_ptr) { _QSORT_SWAP (_left_ptr, _right_ptr, _hold); if (_mid == _left_ptr) _mid = _right_ptr; else if (_mid == _right_ptr) _mid = _left_ptr; ++_left_ptr; --_right_ptr; } else if (_left_ptr == _right_ptr) { ++_left_ptr; --_right_ptr; break; } } while (_left_ptr <= _right_ptr); /* Set up pointers for next iteration. First determine whether left and right partitions are below the threshold size. If so, ignore one or both. Otherwise, push the larger partition's bounds on the stack and continue sorting the smaller one. */ if (_right_ptr - _lo <= _QSORT_MAX_THRESH) { if (_hi - _left_ptr <= _QSORT_MAX_THRESH) /* Ignore both small partitions. */ _QSORT_POP (_lo, _hi, _top); else /* Ignore small left partition. */ _lo = _left_ptr; } else if (_hi - _left_ptr <= _QSORT_MAX_THRESH) /* Ignore small right partition. */ _hi = _right_ptr; else if (_right_ptr - _lo > _hi - _left_ptr) { /* Push larger left partition indices. */ _QSORT_PUSH (_top, _lo, _right_ptr); _lo = _left_ptr; } else { /* Push larger right partition indices. */ _QSORT_PUSH (_top, _left_ptr, _hi); _hi = _right_ptr; } } } /* Once the BASE array is partially sorted by quicksort the rest is completely sorted using insertion sort, since this is efficient for partitions below MAX_THRESH size. BASE points to the beginning of the array to sort, and END_PTR points at the very last element in the array (*not* one beyond it!). */ { QSORT_TYPE *const _end_ptr = _base + _elems - 1; QSORT_TYPE *_tmp_ptr = _base; register QSORT_TYPE *_run_ptr; QSORT_TYPE *_thresh; _thresh = _base + _QSORT_MAX_THRESH; if (_thresh > _end_ptr) _thresh = _end_ptr; /* Find smallest element in first threshold and place it at the array's beginning. This is the smallest array element, and the operation speeds up insertion sort's inner loop. */ for (_run_ptr = _tmp_ptr + 1; _run_ptr <= _thresh; ++_run_ptr) if (QSORT_LT (_run_ptr, _tmp_ptr)) _tmp_ptr = _run_ptr; if (_tmp_ptr != _base) _QSORT_SWAP (_tmp_ptr, _base, _hold); /* Insertion sort, running from left-hand-side * up to right-hand-side. */ _run_ptr = _base + 1; while (++_run_ptr <= _end_ptr) { _tmp_ptr = _run_ptr - 1; while (QSORT_LT (_run_ptr, _tmp_ptr)) --_tmp_ptr; ++_tmp_ptr; if (_tmp_ptr != _run_ptr) { QSORT_TYPE *_trav = _run_ptr + 1; while (--_trav >= _run_ptr) { QSORT_TYPE *_hi, *_lo; _hold = *_trav; for (_hi = _lo = _trav; --_lo >= _tmp_ptr; _hi = _lo) *_hi = *_lo; *_hi = _hold; } } } } } rbldnsd-0.997a/Makefile.in0000664000175000017500000001436212163540666013567 0ustar mjtmjt#! /usr/bin/make -rf # # Makefile for rbldnsd SHELL = /bin/sh CC = @CC@ CFLAGS = @CFLAGS@ LD = @LD@ LDFLAGS = @LDFLAGS@ AR = @AR@ ARFLAGS = @ARFLAGS@ RANLIB = @RANLIB@ AWK = @AWK@ PYTHON = python GNUTAR = tar # Disable statistic counters #DEFS = -DNO_STATS # Disable printing zone (re)load time using utimes() #DEFS = -DNO_TIMES # Disable memory info logging (mallinfo) #DEFS = -DNO_MEMINFO # If you don't want/have IPv6 support (transport only) #DEFS = -DNO_IPv6 # To turn on recognision of ipv6-mapped ipv4 queries (silly idea?) #DEFS = -DRECOGNIZE_IP4IN6 # To use select() instead of poll() #DEFS = -DNO_POLL # To disable master-format (named) dump (-d option) #DEFS = -DNO_MASTER_DUMP # To disable usage of zlib (also LIBS - for zlib, -lz is needed) #DEFS = -DNO_ZLIB # To disable asserts #DEFS = -DNDEBUG # DEFS = LIBS = @LIBS@ NAME = rbldnsd # taken from debian/changelog, by ./configure VERSION = @VERSION@ VERSION_DATE = @VERSION_DATE@ LIBDNS_SRCS = dns_ptodn.c dns_dntop.c dns_dntol.c dns_dnlen.c dns_dnlabels.c \ dns_dnequ.c dns_dnreverse.c dns_findname.c LIBDNS_GSRC = dns_nametab.c LIBDNS_HDRS = dns.h LIBDNS_OBJS = $(LIBDNS_SRCS:.c=.o) $(LIBDNS_GSRC:.c=.o) LIBIP_SRCS = ip4parse.c ip4atos.c ip4mask.c ip6addr.c LIBIP_GSRC = LIBIP_HDRS = ip4addr.h ip6addr.h LIBIP_OBJS = $(LIBIP_SRCS:.c=.o) LIB_SRCS = $(LIBDNS_SRCS) $(LIBIP_SRCS) mempool.c istream.c btrie.c LIB_HDRS = $(LIBDNS_HDRS) $(LIBIP_HDRS) mempool.h istream.h btrie.h LIB_OBJS = $(LIBDNS_OBJS) $(LIBIP_OBJS) mempool.o istream.o btrie.o LIB_GSRC = $(LIBDNS_GSRC) $(LIBIP_GSRC) RBLDNSD_SRCS = rbldnsd.c rbldnsd_zones.c rbldnsd_packet.c \ rbldnsd_ip4set.c rbldnsd_ip4tset.c rbldnsd_ip4trie.c \ rbldnsd_ip6tset.c rbldnsd_ip6trie.c rbldnsd_dnset.c \ rbldnsd_generic.c rbldnsd_combined.c rbldnsd_acl.c \ rbldnsd_util.c RBLDNSD_HDRS = rbldnsd.h RBLDNSD_OBJS = $(RBLDNSD_SRCS:.c=.o) lib$(NAME).a MISC = configure configure.lib \ $(NAME).8 qsort.c Makefile.in dns_maketab.awk $(NAME).spec \ NEWS TODO CHANGES-0.81 README.user \ rbldnsd.py TESTS = tests.py $(wildcard test_*.py) DEBFILES = debian/changelog debian/copyright debian/rules debian/control \ debian/postinst debian/$(NAME).default debian/$(NAME).init SRCS = $(LIB_SRCS) $(RBLDNSD_SRCS) GSRC = $(LIB_GSRC) HDRS = $(LIB_HDRS) $(RBLDNSD_HDRS) DISTFILES = $(SRCS) $(HDRS) $(MISC) $(TESTS) SELF_TESTS = btrie.test all: $(NAME) $(NAME): $(RBLDNSD_OBJS) $(LD) $(LDFLAGS) -o $@ $(RBLDNSD_OBJS) $(LIBS) lib$(NAME).a: $(LIB_OBJS) -rm -f $@ $(AR) $(ARFLAGS) $@ $(LIB_OBJS) $(RANLIB) $@ .SUFFIXES: .c .o COMPILE = $(CC) $(CFLAGS) $(DEFS) -c $< .c.o: $(COMPILE) dns_nametab.c: dns.h dns_maketab.awk $(AWK) -f dns_maketab.awk dns.h > $@.tmp mv -f $@.tmp $@ rbldnsd.o: rbldnsd.c NEWS @echo @echo \ $(NAME) VERSION="\"$(VERSION) ($(VERSION_DATE))\"" @echo $(COMPILE) -DVERSION="\"$(VERSION) ($(VERSION_DATE))\"" clean: -rm -f $(RBLDNSD_OBJS) $(LIB_OBJS) lib$(NAME).a $(GSRC) config.log -rm -f $(SELF_TESTS) distclean: clean -rm -f $(NAME) config.h Makefile config.status *.py[co] spec: @sed "s/^Version:.*/Version: $(VERSION)/" $(NAME).spec \ > $(NAME).spec.tmp @set -e; \ if cmp $(NAME).spec $(NAME).spec.tmp ; then \ rm -f $(NAME).spec.tmp; \ else \ echo "Updating $(NAME).spec ($(VERSION))" ; \ mv -f $(NAME).spec.tmp $(NAME).spec ; \ fi dist: $(NAME)-$(VERSION).tar.gz $(NAME)-$(VERSION).tar.gz: $(DISTFILES) $(GNUTAR) -czf $@ --transform='s|^|$(NAME)-$(VERSION)/|' \ $(DISTFILES) $(DEBFILES) depend dep deps: $(SRCS) $(GSRC) @echo Generating deps for: @echo \ $(SRCS) $(GSRC) @sed '/^# depend/q' Makefile.in > Makefile.tmp @$(CC) $(CFLAGS) -MM $(SRCS) $(GSRC) | \ sed 's/^\(btrie\).o:/\1.o \1.test:/' >> Makefile.tmp @set -e; \ if cmp Makefile.tmp Makefile.in ; then \ echo Makefile.in unchanged; \ rm -f Makefile.tmp; \ else \ echo Updating Makfile.in; \ mv -f Makefile.tmp Makefile.in ; \ fi config.h Makefile: configure configure.lib Makefile.in NEWS ./configure @echo @echo Please rerun make >&2 @exit 1 # tests .PHONY: check check-python-tests check-selftests check: check-selftests check-python-tests check-selftests: $(SELF_TESTS) @set -e; for t in $(SELF_TESTS); do \ echo =============================================================; \ echo Running $$t; \ ./$$t; \ done check-python-tests: $(NAME) @echo ============================================================= @echo Running tests.py @$(PYTHON) tests.py .SUFFIXES: .test .c.test: $(CC) $(CFLAGS) $(DEFS) -DTEST -o $@ $< # depend dns_ptodn.o: dns_ptodn.c dns.h dns_dntop.o: dns_dntop.c dns.h dns_dntol.o: dns_dntol.c dns.h dns_dnlen.o: dns_dnlen.c dns.h dns_dnlabels.o: dns_dnlabels.c dns.h dns_dnequ.o: dns_dnequ.c dns.h dns_dnreverse.o: dns_dnreverse.c dns.h dns_findname.o: dns_findname.c dns.h ip4parse.o: ip4parse.c ip4addr.h config.h ip4atos.o: ip4atos.c ip4addr.h config.h ip4mask.o: ip4mask.c ip4addr.h config.h ip6addr.o: ip6addr.c ip6addr.h mempool.o: mempool.c mempool.h istream.o: istream.c config.h istream.h btrie.o btrie.test: btrie.c btrie.h config.h mempool.h rbldnsd.o: rbldnsd.c rbldnsd.h config.h ip4addr.h ip6addr.h dns.h \ mempool.h rbldnsd_zones.o: rbldnsd_zones.c rbldnsd.h config.h ip4addr.h ip6addr.h \ dns.h mempool.h istream.h rbldnsd_packet.o: rbldnsd_packet.c rbldnsd.h config.h ip4addr.h ip6addr.h \ dns.h mempool.h rbldnsd_ip4set.o: rbldnsd_ip4set.c rbldnsd.h config.h ip4addr.h ip6addr.h \ dns.h mempool.h qsort.c rbldnsd_ip4tset.o: rbldnsd_ip4tset.c rbldnsd.h config.h ip4addr.h \ ip6addr.h dns.h mempool.h qsort.c rbldnsd_ip4trie.o: rbldnsd_ip4trie.c rbldnsd.h config.h ip4addr.h \ ip6addr.h dns.h mempool.h btrie.h rbldnsd_ip6tset.o: rbldnsd_ip6tset.c rbldnsd.h config.h ip4addr.h \ ip6addr.h dns.h mempool.h qsort.c rbldnsd_ip6trie.o: rbldnsd_ip6trie.c rbldnsd.h config.h ip4addr.h \ ip6addr.h dns.h mempool.h btrie.h rbldnsd_dnset.o: rbldnsd_dnset.c rbldnsd.h config.h ip4addr.h ip6addr.h \ dns.h mempool.h qsort.c rbldnsd_generic.o: rbldnsd_generic.c rbldnsd.h config.h ip4addr.h \ ip6addr.h dns.h mempool.h qsort.c rbldnsd_combined.o: rbldnsd_combined.c rbldnsd.h config.h ip4addr.h \ ip6addr.h dns.h mempool.h rbldnsd_acl.o: rbldnsd_acl.c rbldnsd.h config.h ip4addr.h ip6addr.h dns.h \ mempool.h btrie.h rbldnsd_util.o: rbldnsd_util.c rbldnsd.h config.h ip4addr.h ip6addr.h \ dns.h mempool.h dns_nametab.o: dns_nametab.c dns.h rbldnsd-0.997a/dns_maketab.awk0000664000175000017500000000153412120257742014464 0ustar mjtmjt#! /usr/bin/awk -f # A script to generate dns_nametab.c from dns.h # (various name tables like rtype etc) BEGIN { n = "" s = "" print "/* file automatically generated */" print "#include \"dns.h\"" print "#include " } /^enum dns_/ { n = substr($2,5) #print "\n#ifdef gen_" n "tab" print "\nconst struct dns_nameval dns_" n "tab[] = {" i = 0 next } n != "" && /^[ ]+DNS_[A-Z]_[A-Z0-9_]+[ ]+=/ { print " {"$1",\"" substr($1,7) "\"}," s = s "\n case "$1": return dns_" n "tab["i"].name;" ++i next } n != "" && /^}/ { print " {0,0}" print "};\n" print "const char *dns_" n "name(enum dns_" n " code) {" print " static char buf[20];" print " switch(code) {" s print " }" print " sprintf(buf, \"" n "%d\", code);" print " return buf;" print "}" #print "#endif /* " n "tab */" s = "" n = "" next } rbldnsd-0.997a/rbldnsd.spec0000664000175000017500000000276212173530061014014 0ustar mjtmjt# RPM spec file for rbldnsd Summary: Small fast daemon to serve DNSBLs Name: rbldnsd Version: 0.997a Release: 1 License: GPL Group: System Environment/Daemons BuildRoot: %_tmppath/%name-%version PreReq: /sbin/chkconfig, /sbin/nologin, shadow-utils Source: http://www.corpit.ru/mjt/%name/%{name}_%version.tar.gz %define home /var/lib/rbldns %description Rbldnsd is a small authoritate-only DNS nameserver designed to serve DNS-based blocklists (DNSBLs). It may handle IP-based and name-based blocklists. %prep %setup -q -n %name-%version %build CFLAGS="$RPM_OPT_FLAGS" CC="${CC:-%__cc}" ./configure make %install rm -rf $RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT{%_sbindir,%_mandir/man8,/etc/init.d,/etc/sysconfig} cp rbldnsd $RPM_BUILD_ROOT%_sbindir/ cp -p rbldnsd.8 $RPM_BUILD_ROOT%_mandir/man8/ cp -p debian/rbldnsd.default $RPM_BUILD_ROOT/etc/sysconfig/rbldnsd cp -p debian/rbldnsd.init $RPM_BUILD_ROOT/etc/init.d/rbldnsd chmod +x $RPM_BUILD_ROOT/etc/init.d/rbldnsd %clean rm -rf $RPM_BUILD_ROOT %post if ! getent passwd rbldns ; then mkdir -p -m 0755 %home # ensure it is owned by root useradd -r -d %home -M -c "rbldns Daemon" -s /sbin/nologin rbldns fi /sbin/chkconfig --add rbldnsd /etc/init.d/rbldnsd restart %preun if [ $1 -eq 0 ]; then /etc/init.d/rbldnsd stop || : /sbin/chkconfig --del rbldnsd fi %files %defattr (-,root,root) %doc README.user NEWS TODO debian/changelog CHANGES-0.81 %_sbindir/rbldnsd %_mandir/man8/rbldnsd.8* %config(noreplace) /etc/sysconfig/rbldnsd /etc/init.d/rbldnsd rbldnsd-0.997a/NEWS0000664000175000017500000007272012173527776012233 0ustar mjtmjtThis file describes user-visible changes in rbldnsd. Newer news is at the top. 0.997a (23 Jul 2013) - minor fixes/changes in packaging, no code changes. In particular, fixes a build failure on *BSD introduced in 0.997 0.997 (29 Jun 2013) - main feature of this version is ipv6 support. Many thanks to Geoffrey T. Dairiki for the implementation of btrie (btrie.c) which is far superior to old ip4trie code and handles both v4 and v6 - feature: ip6trie - new dataset supports listing of arbitrary length ip6 CIDRs, along with individual A/TXT values for each prefix - feature: ip6tset - new dataset supports listing of ip6 /64 subnets and the exclusion of /128 subnets; only supports a single A/TXT value for the entire dataset - optimization: ip4trie - using new trie implementation (developed for the ip6trie dataset) decreases memory consumption by roughly a factor of three - feature: acl dataset - ip6 addresses are now supported in ACLs - feature: added --enable-asserts configure option to enable compilation of debugging assertions; assertion checking is disabled by default - featurette: zero-length "wildcard" IP4 CIDR prefixes are now allowed in ip4trie and acl datasets. 0.996b (29 Mar 2008) - most stuff is very minor, while preparing for larger changes for 0.997 and 1.0. - several (mostly minor) bug and portability fixes. - feature: ability to specify "base template" for a dataset, using $= variable. See manpage for details, "Resulting A values and TXT templates" section. - incompat: due to the above change ($= base template), in TXT entries which starts with equal sign (=), the first character (which is `=', obviously) is removed. $= is now treated specially too. - feature: (experimental) support for dynamically-loaded extensions (DSOs), in place of (previously unfinished) compile-time "hooks" support. New options -x/-X, to load an extension and to specify argument for it. Usage document has to be written still. 0.996a (27 Jul 2006) (The "34-Birthday Release") - -a/-A (-A is new, currently a noop) option clarification/addition. Don't mark -a as experimental anymore, and note that -a mode ("lazy", minimal-answers, w/o AUTH section in every reply) will be the default in future versions. -A tells rbldnsd to go to "non-lazy" mode. Document options in the manpage. - bugfix: fix configure script breakages: portability: for f; do => for f in "$@"; do fix broken GNU C (mis)detection - bugfix: fix dataset "un-expiration". Previously, once a dataset has expired, it never "un-expires" again even if new expire time is in future. Due to missing reset of a dataset structure field. - portability: apparently at least one (broken) linux distribution includes kernel modification which leads to losing SIGALRM interrupts at times. So use setitimer() instead of alarm(), if available. - minor code cleanups here and there (fixing (real) GCC-4.1 warnings). - Debian-specific: adopt to more recent Debian packaging requiriments, and ensure that Debian package builds with zlib support 0.996 (19 Feb 2006) - experimental feature: data expiration support, in form $TIMESTAMP created expires see manpage for details. - feature: recognize new 'pass' entry in ACL "dataset", to allow whitelisting of a particular network/host covered by another ACL entry. - bugfix (sort of): deal with possible null-pointer dereferences on some systems such as FreeBSD where realloc(smaller_amount) may actually return NULL. Note that this particular malloc implementation (where realloc() may return NULL if requested to reallocate to a smaller amount of memory) perform very badly with rbldnsd in the first place: rbldnsd tries to free some unused memory at the end of data load process, and realloc() forces a copy so there will be extra copy of a huge data and bad memory fragmentation, so on next reload rbldnsd will most likely just run out of memory. I think it's best to experiment with alternative malloc implementation on such systems, eg dmalloc. - feature: rbldnsd is now able to read gzip-compressed data files, doing transparent on-the-fly decompression, if built with zlib support (if built w/o zlib support, it still checks whenever datafile is compressed and refuses to load it if it is). Use -C option to turn this feature off. - due to zlib support, this version introduces rewritten data-reading loop (previously with fgets()) - on some systems this results in noticeable (re)load speed improvement on large datasets. - number of max nameservers (MAX_NS in rbldnsd.h) increased from 20 to 32, per request from Spamhaus. - feature: configure script now accepts command-line options (--enable-xx and --disable-xx) to turn optional features on/off (including stats, ipv6, master-dump and zlib), and saves such options into config.status so that automatic re-making will pick up the right options again. 0.995 (28 Apr 2005) - feature: allow glue records for nameservers (IPv4 only for now, as there's no AAAA record support yet). Generic dataset it the most appropriate place to specify actual A records for the NSes. - when replying to NS or ANY query to the base zone, rbldnsd now returns set of nameservers in both ANSWER and AUTHORITY section. - feature: (initial, experimental) ACL support. It is possble to force certain kinds of replies to be sent to certain clients (based on the client IP address), regardless of the query the client performs. Read rbldnsd(8) manpage for the details. This feature is experimental. Basic idea will remain but details are likely to change in the future, as requiriments will be understood better. - feature: ENDS0 support for UDPsize, allowing replies larger than 512 bytes for clients claiming EDNS0 support (appropriate OPT record in additional section in query). Not really user-visible change per se, but may be quite visible for clients especially when our replies are large. 0.994b (16 Apr 2005) - bugfix: use of uninitialized pointer in ip4set and ip4trie datasets when input data file (A+TXT template for a given entry) is invalid, instead of rejecting the line. This can lead to "random" crashes. 0.994a (10 Mar 2005) - bugfix: for queries for base subzone in combined dataset, rbldnsd improperly returned NXDOMAIN instead of NODATA -- eg a query for sub.bl.example.com where sub is a subzone of a combined dataset "rooted" at bl.example.com resulted in NXDOMAIN while the name obviously does exists. Fixed (one-liner). 0.994 (18 Dec 2004) - bugfix: fix a memory leak when $n-style substitutions are being used: each $n definition resulted in a leak of the substitution text on every reload (used estrdup() but should be using mp_strdup()) - feature, sort of: allow to omit support for -d option, thus eliminating some bloat: DEFS = -DNO_MASTER_DUMP - bugfix: fixed master-format dump (-d) for ip4trie - some ranges weren't expanding properly, resulting in missing entries - bugfix: fixed master-format dump (-d) for ip4set: when we have two entries in input: 127.0.0.0/8 a 127.0.0.2 b for master-format dump there should be 4 lines, not 2 as before: 2.0.0.127 b (was ok before) *.0.0.127 a (was missing) *.0.127 a (was missing) *.127 a (was ok before) Without the two intermediate lines, named returns NXDOMAIN for eg 3.0.0.127 or x.1.0.127. Quite an.. interesting case... 0.993.1 (29 Jul 2004) - only minor, mostly (Debian) package-specific, stuff (see debian/changes for details) 0.993 (01 Jul 2004) - bugfix: fix 0.0.0.0 A value being used instead of the specified real IP address in a case like ":127.0.0.2" (use specific A and default TXT) (noted by njabl) - feature: allow (optional) names for subdatasets in combined dataset, for better logging. Specify :name after dataset type in $DATASET line, like $DATASET ip4set:http proxies @ $DATASET ip4set:relays relays @ - feature, safety: implement and enforce $MAXRANGE4 special like this: $MAXRANGE /24 $MAXRANGE 256 the maximum "size" of a single entry, in number of IPv4 addresses it covers. If an entry covers more addresses, it is ignored (and warning is logged). The constraint may be decreased by the following $MAXRANGE special, but can not be increased. Global per dataset. - feature, safety: ignore incomplete last lines (lines w/o end-of-line terminator) in data files (to prevent mis-interpreting of incomplete data) - feature, safety: check for data file changes during reloads (while reading data), and abort loading (and mark all zones to return SERVFAIL until next reload) if a change is detected. - safety: do not treat bare numbers as /8 ranges. 10 -- wrong from now on 10/8 -- ok 10-11 -- ok - safety: require equal number of octets for x-y style ranges: 1.2.3-2.3.4.5 -- wrong 1.2.3.0-2.3.4.5 -- ok 1.2.3.4-2.3.4 -- wrong 1.2.3.4-2.3.4.5 -- ok and the "repeat-last-octet" variant is still ok too, obviously: 1.2-3 -- ok 1.2.3-4 -- ok 1.2.3.4-5 -- ok - safety: only accept complete, 4-octet IPv4 addresses in ip4tset, do not allow weird stuff like inet_aton() allows: 10 = 0.0.0.10 -- wrong 10.1 = 10.0.0.1 -- wrong - bugfix: several more small fixes for IP4 address parser - refine logging a bit, make it less verbose (esp. when logging problems) - bugfix: query logging (-l) with background reloading: the file was not flushed properly (resulted in double logging) - bugfix: dump (-d) of MX record (generic dataset) was incorrect - bugfix: wrong subzone in $ORIGIN when dumping (-d) combined dataset - bugfix: incorect (opposite) evaluation of maxttl 0.992 (07 Mar 2004) - feature: allow easy turning on/off individual NS records in $NS line, by prefixing unused nameservers with minus sign (-) - bugfix: fix -d (master-format dump) for generic dataset - bugfix: remove usage of NI_WITHSCOPEID (it was used for unknown reason anyway and broke on latest solaris) - #define _LARGEFILE64_SOURCE and use O_LARGEFILE if defined in rbldnsd.c to be able to write larger logfiles. Dunno whenever it will actually help, but it at least works on linux. - old -s option (log reload times/memusage) is gone, it is now turned on all the time, but produces slightly less verbose output. - new -s option: write short statistic summaries into given file, to help obtaining data for tools like RRD. - format of statistic logging changed slightly, it is a bit less verbose now too (and less confusing) - feature: continue processing queries during reloads. For this, rbldnds forks off the child process that process queries while parent performs the reload. Requires 2x more memory (changed datasets will be doubled during reloads). -f option (not enabled by default). - feature: new dataset, ip4tset, very simplified ip4set. Only accepts bare IP addresses, no netranges, no exceptions, but requires 2x less memory and is faster. - feature: extended -t option, allow minttl and maxttl to be specified (to set constraints for TTLs found in data files). New syntax is -t defttl:minttl:maxttl, with everything optional (so -t defttl works too, as well as -t ::1d). - feature/expectation_fix: add an ability to specify A but inherit default TXT value for an entry: entry :addr: - specific A, no TXT entry :addr - specific A, default TXT - cleanup: remove redundrant CNAMEs from master-file dump in ip4set 0.991 (30 Nov 2003) - in order to be able to overrite both SOA and NS records in data downloaded from 3rd party blocklist to use in local environment, $NS record handling changed. From now on, rbldnsd expects all nameservers to be specified on one single $NS line. Compatibility with previous releases preserved for now, but will be removed in the future: if several domain names are specified in $NS line, all other $NS lines are ignored; but when only one nameserver is specified, rbldnsd still collects all such single-ns lines as in previous releases. - when the query matches several RRs with different TTLs (e.g. from different datasets), rbldnsd now sets smallest TTL in ALL RRs of this type. - when several RRs of the same type exists in generic dataset, we now trying to return them in "random" order. The "randomization" is very dumb for now. - implemented master format dump for ip4trie dataset 0.99 (16 Sep 2003) - autoconf-style configuration. Run ./configure before make. -DSTATS_LL gone; NOSTDINT_H, NOIPv6, NOMEMINFO, NOPOLL are set automatically (hopefully). I dont use GNU autoconf just because it is too huge, but my own "mini-autoconf" may be not as portable/tested, obviously. Great thanks to Christian Krackowizer (ckrackowiz at std.schuler-ag.com) for testing this stuff on numerous platforms. - remove EasynetDynablock and relays.osirusoft.com conversion scripts - bugfix: Fixed range parsing. E.g., 24.217.64-191 did not work (and any range like this where last two bits where xored into 255). Spotted by easynet.nl folks, thanks. This bug occurs only when last 2 numbers, when xored together, gives 255, like 124-131, 120-135, 127-128, 65-190, 64-191, ... The listing will never be matched, so bug does no harm (i.e. no extra, incorrect listings). - feature: allow logging to standard output (-l - or -l +-). See manpage for details. Idea by Klaus Alexander Seistrup @magnetic-ink.dk. 0.98 (17 Aug 2003) - incompatible change: bind address (-b option) is now mandatory. Too many problems with INADDR_ANY, multihomed hosts and wrong source address on replies. - feature: allow listening on multiple addresses. Needed e.g. on hosts where both IPv4 and IPv6 addresses are in use. Having multiple listening addresses means rbldnsd now uses select/poll (but it works exactly as before if only one listening address specified). If your system does not provide working poll() system call, specify -DNOPOLL at compile time. - feature: recognize host/port syntax in argument for -b option (bind address) to be able to bind to different ports. -P option is gone again. Note that delimiter is slash (/), not colon (:), to be able to work with IPv6 addresses correctly. - feature, and incompatibility change in dnset DN interpretation. *.example.com is now NOT the same as .example.com. Specify *.example.com to include all subdomains of example.com, and specify .example.com to include all subdomains AND example.com itself - instead of specifying 2 lines, only one is now needed. - bugfix: memleak in combined dataset: NS and SOA caches was allocated for subzones of combined dataset (NS/SOA are never used here). - feature: respond to version.bind CH TXT requests (and version.server). Use -v to hide version info from reply, or two -v's to disable this feature completely. - reply with REFUSED instead of FORMERR for unknown query class - warn about truncated TXT records. DNS spec allows TXT record to be more than 255 bytes long (by using a series of STRINGs in one RR, each 255 bytes max), but there's no point using TXTs longer than 255 bytes for a DNSBL (think of SMTP rejection message) - feature: new dataset, ip4trie, to store IP4 CIDR ranges. Unlike ip4set, ip4trie can only hold one value per CIDR range and returns only closest matching entry. Experimental. 0.97b (6 Aug 2003) - bugfix: there was an error in per-zone statistics counting code introduced in 0.97. This bug may be triggered remotely by *first* DNS query since rbldnsd startup, provided the query is against a zone for which rbldnsd is not authoritative. If such out-of-zone query will be first, it will result in instant crash of a server. Subsequent out-of-zone queries will not result in a crash, just wrong counters (for previously queried zone) will be incremented. Impact of this bug is low, since it is difficult to trigger the bug and made rbldnsd crash. Thanks Marco D'Itri (md at linux.it) for pointing this out to me. 0.97a (1 Aug 2003) - bugfix: ip4parse_range(): invalid addresses was not marked as such, which may result in various crashes when parsing bogus datafiles. Note this is remotely exploitable bug: if you grab data from a remote system, invalid data may crash you server. DNS operations (query handling etc) aren't affected by this bug, it is in dataset parsing code. Please note that this fix also restores previously non-working detection of non-zero host part in ranges like 1.2.3.4/24 (proper form is 1.2.3.0/24). If you want to process such address ranges, specify -e command-line option. - feature: recognize and ignore "IN" classname in `generic' dataset, so it is now possible to have @ IN A 127.0.0.1 0.97 (13 Jul 2003) - feature: added per-basezone statistic counters - osirusoft2rbldnsd.pl: sample script to convert relays.osirusoft.com bind zone into rbldnsd `combined' dataset - bugfix: in some rare cases, dnset missed one RR for a DN with multiple RRs. Spotted by Matthew Sullivan, SORBS.net - bugfix: rbldnsd didn't return NS records for base DN query if qtype=ANY. Also, SOA now will be first in reply, not last. - optimization for `combined' dataset: try to not remove stats (possible collected by previous loads) for subzones on reloads (i.e. ip4set keeps approx. number of records in a set to avoid many malloc() calls) - new compile-time define: -DSTATS_LL, to keep statistic counters (if not disabled with -DNOSTATS) in variables of type `unsigned long long', not `unsigned long' - on 32-bit machines, this may be 64-bit integers. 0.96 (29 May 2003) - fixed alignment bug in mempool.c that caused allocation slip - pre-compress SOA and NS records for faster access - return NS records in AUTHORITY section of positive answers if available and there's a room for them. - restore broken MX record functionality. Note that MX domain names aren't compressed anymore - do not lowercase domain names specified in NS, SOA and MX records 0.95 (27 May 2003) - new dataset: combined: a container for other datasets. See manpage for details. - reorder zones given in command line (and in combined dataset) to move superzone after all it's subzones. The order is still important - place most commonly referenced zones first - but it's not a problem anymore to specify superzone first. 0.94 (26 May 2003) - implemented -d option (dump zone data in BIND format to stdout) - data loading warnings goes to stderr instead of stdout - Makefile portability tweaks for Solaris - recognize ';' as comment char in addition to '#'; also, officially recognize comments after an entry (IP address or domain name) in ip4set and dnset 0.93 (18 May 2003) - reverse change made in 0.91: SOA TTL, when SOA is in AUTHORITY section, should be from SOA's MINTTL (negative cache TTL). 0.92 (17 May 2003) - bugfix: fixed SOA screwup introduced in 0.91 0.91 (15 May 2003) - rotate nameserver records (simple cyclic rotation) - understand time units - 1w = 7d = 168h = 10080m = 604800s - allow compilation without IPv6 transport support (-DNOIPv6) - bugfix: fixed default A RR to be 127.0.0.2, not 2.0.0.127 - added (preliminary) RPM .spec file (rpmbuild -tb to build from tarball) 0.90 (10 May 2003) - IPv6 transport support. Specify -4 or -6 to use particular transport, default is to use first available. - -b (bindaddr) now does not accept port specification, only host address. Use new option -P to specify listening port. - acl (-a) and log filter (-L) - per-IP filters - are gone for now, as I should figure out how to do that with IPv6. 0.89p4 (8 May 2003) - since bind9 returns NXDOMAIN for b.example.com even if a.b.example.com exists, all the NXDOMAIN elimination code has been removed. So much useless work. Now rbldnsd is small again. 0.89p3 (8 May 2003) Incompatible changes: - ip4vset and dnvset are gone. A trivial idea allowed me to merge functionality into ip4set and dnset. This means, in particular, that default A/TXT values may be specified at any place in data files, and applies to all subsequent records up to end of file (defaults gets reset at file boundary), and negative (exclusion) entries works - all in uniform way. - $NS special in every dataset instead of NS record in generic dataset. Up to 20 per zone may be specified. Rbldnsd still does not add NS RRs into normal answers, and perhaps will never do; also it never fills up ADDITIONAL section (e.g. with NS A RRs). - rbldnsd will now refuse ANY, SOA and NS queries for zone's base DN if SOA and/or NS records (as specials) aren't specified. - Support for NS and SOA record types removed from generic dataset. Use dataset specials ($SOA and $NS) for this. - $SOA and $NS specials requires TTL as a first word, so SOA become 8-field instead of 7-field, and NS become 2-field instead of one-field. Changes: - Allow to specify TTL per dataset (as $TTL special), and for every record in generic dataset (optional field before record type) - substitution variables $0,$1,$2...$9 implemented for TXT templates, so it is now possible to use less space and less typing. I don't know whenever this is useful or not. 0.89p2 (6 May 2003) Incompatible changes: - rbldnsd now substitutes listed DN in TXT template, instead of query DN, e.g. if some.spammer.example.com is queried, and *.spammer.example.com listed, `spammer.example.com' will be used for $ substitution. For domain-based lists (dn[v]set) only, IP-based always substitutes an IP. - for name-based lists, empty domain names disallowed. Changes: - completed NXDOMAIN vs subdomains handling for domain-based lists (generic, dn[v]set). Rbldnsd now very close to BIND behaviour with all it's dataset types. - correctly handle zero bytes in DN names ewerywhere. Before, rbldnsd was incorrect in this area. - allow logging to be done to FIFO (ignore SIGPIPE and open with NODELAY) - control whenever logging is buffered or not (place `+' in front of logfile (-l option) to make it non-buffered) - log (-l) creation errors are now logged to syslog as warnings - -q option - quick/quiet start, load zones after backgrounding (so load errors are not fatal) - as usual, some more code cleanups etc all over the place. 0.89p1 (4 May 2003) many changes. "Expirience" release... Incompatible changes: - generic zone does not understand SOA records anymore - SOA now may be specified in every zone data file as $SOA. - rbldnsd now matches BIND's runtime behaviour as close as possible. In particular, rbldnsd now replies to any query type (except of AXFR and the like), giving positive reply if requested name exists. Also, it now will reply to queries like 0.0.127.bl.example.com (note partial IP) positively with zero answers (certainly, such domain does exists if e.g. 127.0.0.2 is listed). Additionally, rbldnsd now inserts SOA record (if available) to every answer that contains no answer section (this way, it is possible to specify negative caching ttl for example). - order of zones in command line is now important again. Rbldnsd will stop searching at first matching zone found, so if a superzone specified before some of it's subzone, subzone will never be consulted. This may change again in the future. Changes: - much improved manual page, including new "bugs" section and usage of proper (I hope) terms (in particular, "zone" changed to "dataset" where appropriate) - default values for ip4vset and dnvset may be specified in any line of data file, and applies to all subsequent entries - major code cleanups and some redesigns, to follow BIND's behaviour - generic dataset may now handle MX records too. - proper domain name compression implemented (SOA, NS, MX values) - SOA serial value may be set to be dataset's modification timestamp (just specify serial to be 0 and rbldnsd will set it automatically) 0.84 (not released): - return positive result with zero records to AAAA, PTR and CNAME queries. Hack for now, but this way rbldnsd may finally be used together with sendmail and bind... - rewrote query parsing routine to be much more accurate and a bit faster. 0.83 (released 2003-04-19) - critical security fix in query parsing code - that check was here initially, in version 0.1, but was removed when I optimized that code. Ugh!.. - portability: 4.4 FreeBSD does not have mallinfo() and stdint.h (use appropriate -Ddefines, Makefile) - access control and filtering logging by IP - inlined qsort routine, speed up loading significantly. - removed some cruft from the code 0.82 (released 2003-04-05) - recognize another variation of IP address range, for easy use: 127.0.0.1-2 is now treated as 127.0.0.1-127.0.0.2 127.0-200 is now treated as 127.0.0.0-127.200.255.255 - debianized 0.81 (released 2003-04-03) - rbldnsd now recognizes IP address ranges in additional to IP prefixes and CIDR ranges, e.g. 127.0.0.2-127.0.1.5 now works with ip4[v]set zonetypes (range is inclusive). May be disabled at compile time by adding -DNOIP4RANGES to $(DEFS). - new option, -e, to enable usage of "non-conforming" CIDR ranges, where prefix does not fit within given netmask. - -v option is gone, new option -l to specify a logfile (it was a bad idea to log every request via syslog). - when constructing a dataset from several files, A and TXT records are now taken from _first_ file for ip4set and dnset (ignoring those in other files), and for ip4vset and dnvset, defaults are in effect for a single file only. - implemented removal of duplicate entries on zone data reloads. May be disabled at compile time by adding -DNOREMOVEDUPS to $(DEFS). - various code cleanups 0.80 (released 2003-04-02) Incompatible changes: - command-line zone syntax has changed. Consult the manpage for examples. Basically, instead of type:file-zone-name rbldnsd now expects zone-name:type:file-name thus eliminating requiriment that zone name should be in file named after zone. Also, a LIST (comma-separated) of filenames may be specified instead of a single file. Note that all 3 fields are required. Resulting command line may look somewhat ugly (and it may be long), but the effect is much improved flexibility. - logging has changed. Data set may be reused for several zones, so "zone xxx loaded" message is now replaced by "dataset loaded", without any reference to zone(s) which uses that data set. - rbldnsd will abort it's startup if it will encounter any error during initial zone loading (missing file, out of memory etc). After initialization, all errors are not fatal, but partially loaded zones will NOT be serviced (rbldnsd will return REFUSED in this case, as if it does not service this zone at all). If, on subsequent reload, problematic zone will be back available, it will be included in servicing list automatically. Other changes: - rbldnsd now recognizes and answers to NS and SOA records. For this to work, one need to specify such records, and for this, new data type was introduced, named `generic' (simplified bind-style format, see manpage for more info). If no `generic' type dataset is specified for a domain, rbldnsd will refuse NS and SOA queries as before. - due to changed command line format, it is now possible to construct one zone from several data sets (by repeating the same zone name with different data sets), and to construct one data set from several files (of the same type). Either way and any combinations works (see NOTES section in the manpage for examples). - logging of queries is implemented. Give -v option to turn it on, but expect large amount of data to be logged on a busy site (every query will be logged via syslog). This feature is mainly for debugging purposes, and later may be replaced with more advanced logging to a file. 0.74 (newer released) Incompatible changes over 0.73: - In ip4vset and most notable in dnvset types, it is now possible to specify exclusion of an entry (useful to specify large block and exclude a single entry from it). This is done by prefixing an entry with an exclamation sign (!). So, exclamation sign at start of line is now treated specially (it wasn't valid for ip4vset, but it was treated as a part of domain name in dnvset). - If no TXT record is available for an entry, rbldnsd will now not return NXDOMAIN but will return zero-entry successeful answer. This is how BIND works. Something like "valid name but now data of requested type". Other changes in 0.74: - reorganized storage for TXT records, to speed up loading of zones with non-repeatable TXT values. With this change, relays.osirusoft zones now requires somewhat more memory (since no hard work for TXT duplication elimination is now taking place), but overall case (where TXTs aren't repeated frequently) is now much faster, in particular, Wirehub's permblockIP.txt now loads in an acceptable time. Rbldnsd still recognizes and packs adjanced duplicates. Worst case will be with randomized osirusoft data (it has very many dups, but most are adjanced to each other). - reviewed logging, should be ok for buffer-overflow things. Also, prevent log flooding in case input file contains many errors (only first 5 is logged) rbldnsd-0.997a/TODO0000664000175000017500000000330112120257742012172 0ustar mjtmjtTODO list for rbldnsd, in no particular order. * implement TCP mode (in separate process, to reduce number of syscalls) probably never. * implement AXFR query - stupid idea but AXFR is widely used. probably never. * control socket for reloads (so that reload may be triggered by writing something to a socket). This may be done in a separate process which will listen for requests on control socket and send signal to it's child. This will also give us an ability to monitor and restart failed child (ala supervise) * add `fnmatch' (or dnpat) type, to handle shell-style wildcards (no regexps please). * It would be nice if queries to name-based datasets will be resolved in one lookup instead of several (by query dn labels) as now. How? * two last TODO entries may be done by implementing sort of lex-like thing (DFA/NFA), in runtime. Probably too much for such simple (?) program, unless someone will write good multi-regex-matching library (many applications will benefit from it). I have no expirience with all those DFA/NFA things - at all. * document plugins/extensions internals (-x option). For now, the only "reference" is a stub plugin in hooks.c. * implement 'noip' "dataset", to match queries made for IP-based blocklist (which expects an IP number as a query) which are NOT of IP numbers, ie, to detect wrong usage (as RHSBL, or just stupid) of a DNSBL. Too many systems uses DNSBLs in various incorrect ways (improperly written software, querying for rDNS name in an IP-based list, wrong setup like using some additional suffixes and so on), generating extra unnecessary traffic and never noticying their queries does not help. rbldnsd-0.997a/CHANGES-0.810000664000175000017500000001445512120257742013075 0ustar mjtmjt2003-04-03 0.81 - IP4RANGES is now the default (but old behaviour may be selected by defining -DNOIP4RANGES). Updated manpage etc. 2003-04-03 0.81pre2 - -v option is gone, logging is now done with -l logfile option - reorganized source: moved various parts into separate files, cleanups - ip4addr library modified: new and improved/completed "parsers" (ip4addr, ip4range, ip4prefix, ip4cidr - ip4parse.c) - preparations to handle net ranges. Feature is implemented but not yet tested well. NOT compiled in by default (specify DEFS=-DIP4RANGES if you want to try this out). To test what it looks like, make ip4rangetest and execute it with netranges in command line. Famous ip4range_expand macro in rbldnsd.h... 2003-04-03 0.81pre1 - revisited default A/TXT values handling when reading multiple files. Now, ip4set and dnset will get A/TXT from the FIRST file, :A:TXT in subsequent files will be ignored; ip4vset and dnvset will not assign A/TXT from previous file if a given file has no :A:TXT line. - corrected a typo in ip4cidr.c: reversed error return. Does not affect rbldnsd (happened only when ip4cidr was called with zero last argument) - added code to remove duplicates from zones. Enabled by default, may be disabled by adding DEFS=-DNOREMOVEDUPS when compiling (maybe made this runtime-configurable? But not per-zone, please!..;). Tested on concatenation of list.dsbl.org (192796 entries) and unconfirmed.dsbl.org (209198 entries, list.dsbl.org is completely included into unconfirmed): On PII-233 machine, using ip4vset, load time is 0m5.910s when not removing dups, and 0m6.030s when removing dups so the difference is almost unnotiseable. 2003-04-02 0.80 - many changes. Reworked zone handling code significantly. See NEWS file for details. 2003-04-01 0.74pre4 - reorganized zone loading to allow loading from several files (split xx_load into xx_alloc, xx_load and xx_finish). - new zonetype: generic, simplified bind-alike format. 2003-03-31 0.74pre3 - not change but a note: 0.74pre1 change introduced a question. What dns server should return in case of e.g. TXT query for listed IP but no TXT value is available? Two choices: return NXDOMAIN, or return no RRs at all. Pre-0.74 rbldnsd returned NXDOMAIN, now it returns zero RRs in this case. DJB's rbldns always adds fake, zero-length TXT in this case. BIND returns no records. For now, I'll follow BIND's behaviour, i.e. no records in successeful answer will be returned. - use one mempool for dnvset again. - reviewed logging, should be ok for buffer-overflow things. Also, prevent log flooding in case input file contains many errors (only first 5 is logged) - some more source reorganizations and simplifications. 2003-03-30 0.74pre2 - removed sstr completely, in flavour of mempool. For *different* data (like in wirehub.net's permblockIP.txt - many number of *different* strings), sstr is very inefficient. Switch to (enhanced) mempool allows much faster loads of non-repeated values, but uses much more memory on _randomized_ relays.osirusoft.com zone (but it is still good on original relays.osirus zone, where equal entries follows each other most of the time) - modified mempool (and added strpool: "subclass" of mempool). Much faster, uses larger chunksize (64K vs 8K), and moves chunks with only a few free space (less than average data size) to "full" chunk list. - little optimization in dn[v]set: add lower number of dn labels limit, do not try to search DNs with less labels than minlab. - stop searching zone list at first applicable zone. For now. Should add sorting of zones (longer first), or else, if relays.osirusoft.com specified before dialups.relays.osirusoft.com, first will catch all queries to the second and second will never be considered. Currently, order is the same as was in command line. Hmm... but user may know better which zones will be accessed more frequently. Probably should document this instead. - fixed another bug introduced in 0.74pre1 - uninitialized A RR for dnvset zone files 2003-03-29 0.74pre1 - added exclusions to ip4vset and dnvset zones - entry starting with an exclamation sign `!' treated as _excluded_ from a set. Useful to include large networks but exclude some hosts. - fixed potential memory leak when memory is unsufficient to extend an array of entries (may this even happen?), where erealloc() routine is used - added pidfile (-p) writing option. Thanks to Bruce Gingery @gtcs.com. - reorganized source - use common routine to read zone data files, and handle long lines in zones more accurately - allow first line of zonedata to start with a comment, so default RR values may be specified in first _non-comment_ line. 2003-02-12 0.73 - bugfix: ip4vset, when no A RR was given for a particular IP, returned strange A RR instead of default one. Noticied by Karl A. Krueger @whoi.edu. - bugfix: rbldnsd segfaulted when given A/TXT RR in a (incorrect) form :Some text instead of :1.2.3.4:Some text 2003-01-09 0.72 - yet another bug in ip4vset: not all records where returned for listings >= /24. Thanks Furio Ercolessi for finding this. 2003-01-08 0.71 - fixed a bug: in ip4vset, rbldnsd returned only one TXT record when several entries are present - fixed a bug with incorrect sorting of dnset record (resulted in returning NXDOMAIN for almost every query to such zones) - archive now unpacks to rbldnsd-NN.NN directory 2002-12-05 0.7 - added an initial manual page 2002-11-30 0.6 - speed up significantly (about 3 times) zone loading by reorganizing sstr pool to be a hash table. Restrict length of text records to 254 bytes. - implemented proper signal blocking during zone reloads - cleaned up signal handling (use POSIX signal primitives) - added some comments to the code - do not use *log() routines with user-controllable data, changed logging and added length checks for command-line parameters - all to avoid possible buffer overruns after user (not network!)-supplied data 2002-11-28 0.5 - fixed a bug with incorrect formatting of IP address - allow last digit of 127.0.0.x A result in lists to save space - first release with a few changes (too much changes was before) rbldnsd-0.997a/README.user0000664000175000017500000000322212120257742013341 0ustar mjtmjtBy default (if no -u option is specified), rbldnsd runs as user rbldns. At a time the rbldnsd package is installed, such a user is created on the target system, with home directory /var/lib/rbldns (note the directory is owned by root user, see below). When the package is removed, user rbldns is NOT removed, so that files owned by that user, if any, will not be "orphaned" (package removal scripts don't check whether there are any such files still exists). If you're sure no files owned by that user exists after removing the package, it's ok to remove the user (and /var/lib/rbldns directory) too. Note again the rbldns home directory, /var/lib/rbldns, is owned by root when the package is installed, not by rbldns user. Usually you will put zone data files into that directory, and use it as a chroot directory for rbldnsd (-r option). Feel free to chown the /var/lib/rbldns directory to whatever user you like, to be able to put data files into it. If you're paranoid about security (every system administrator should be, right?), create yet another account that will own data files and set up data updating process to run as that account. If you want rbldnsd query and statistic logs (-l and -s options), you may create files for that in this directory (or a subdirectory of it) and set owner/permissions of that files in such a way so rbldnsd will be able to write to them. The two files -- query and statistics logs -- are the ONLY files where rbldnsd should be able to write to, the rest of files, including zone data, are never opened for writing by rbldnsd in normal circumstances, and generally (from security perspective) should not be writable by the daemon. rbldnsd-0.997a/rbldnsd.py0000664000175000017500000001171312130202277013504 0ustar mjtmjt""" Run an rbldnsd and send it DNS queries. """ from itertools import count import subprocess from tempfile import NamedTemporaryFile, TemporaryFile import time import unittest try: import DNS except ImportError: raise RuntimeError("The pydns library is not installed") DUMMY_ZONE_HEADER = """ $SOA 0 example.org. hostmaster.example.com. 0 1h 1h 2d 1h $NS 1d ns0.example.org """ class ZoneFile(object): def __init__(self, lines=None, no_header=False): self._file = NamedTemporaryFile() if not no_header: self._file.write(DUMMY_ZONE_HEADER) if lines is not None: self.writelines(lines) self._file.flush() @property def name(self): return self._file.name def write(self, str): self._file.write(str) self._file.flush() def writelines(self, lines): self._file.writelines("%s\n" % line for line in lines) self._file.flush() class DaemonError(Exception): """ Various errors having to do with the execution of the daemon. """ class QueryRefused(Exception): """ Query to rbldnsd was REFUSED. """ class Rbldnsd(object): def __init__(self, datasets=None, daemon_addr='localhost', daemon_port=5300, daemon_bin='./rbldnsd', stderr=None): self._daemon = None self.datasets = [] self.daemon_addr = daemon_addr self.daemon_port = daemon_port self.daemon_bin = daemon_bin self.stderr = stderr def add_dataset(self, ds_type, file, soa='example.com'): self.datasets.append((soa, ds_type, file)) def __enter__(self): self._start_daemon() return self def __exit__(self, exc_type, exc_value, exc_tb): self._stop_daemon() def __del__(self): if self._daemon: self._stop_daemon() def query(self, name, qtype='TXT'): if not self._daemon: raise DaemonError("daemon not running") elif self._daemon.poll() is not None: raise DaemonError("daemon has died with code %d" % self._daemon.returncode) req = DNS.Request(name=name, qtype=qtype, rd=0) resp = req.req(server=self.daemon_addr, port=self.daemon_port) status = resp.header['status'] if status == 'REFUSED': raise QueryRefused("REFUSED") elif status == 'NXDOMAIN': return None else: assert status == 'NOERROR' assert len(resp.answers) == 1 assert len(resp.answers[0]['data']) == 1 return resp.answers[0]['data'][0] def _start_daemon(self): if len(self.datasets) == 0: raise ValueError("no datasets defined") cmd = [ self.daemon_bin, '-n', '-b', '%s/%u' % (self.daemon_addr, self.daemon_port), ] for zone, ds_type, file in self.datasets: if isinstance(file, basestring): filename = file else: filename = file.name cmd.append("%s:%s:%s" % (zone, ds_type, filename)) self._stdout = TemporaryFile() self._daemon = daemon = subprocess.Popen(cmd, stdout=self._stdout, stderr=self.stderr) # wait for rbldnsd to start responding time.sleep(0.1) for retry in count(): if daemon.poll() is not None: raise DaemonError( "rbldsnd exited unexpectedly with return code %d" % daemon.returncode) try: self.query('dummy.nonexisting.zone') break except QueryRefused: break except DNS.DNSError as ex: if str(ex) != 'no working nameservers found': raise elif retries > 10: raise DaemonError( "rbldnsd does not seem to be responding") def _stop_daemon(self): daemon = self._daemon if daemon.poll() is None: daemon.terminate() retries = count() while daemon.poll() is None: retry = next(retries) if retry == 30: daemon.kill() elif retry == 50: raise DaemonError("can not kill stop rbldnsd") time.sleep(0.1) self._daemon = None if daemon.returncode != 0: raise DaemonError("rbldnsd exited with code %d" % daemon.returncode) class TestRbldnsd(unittest.TestCase): def test(self): rbldnsd = Rbldnsd() test_zone = ZoneFile(lines=["1.2.3.4 :1: Success"]) rbldnsd.add_dataset('ip4set', test_zone) with rbldnsd: self.assertEqual(rbldnsd.query('4.3.2.1.example.com'), 'Success') self.assertEqual(rbldnsd.query('5.3.2.1.example.com'), None) if __name__ == '__main__': unittest.main() rbldnsd-0.997a/tests.py0000664000175000017500000000055412130202277013217 0ustar mjtmjt""" Convenience script to run all python test cases in one go. """ import sys import unittest try: import DNS except ImportError: print "TESTS SKIPPED: the pydns library is not installed" sys.exit(0) from test_btrie import * from test_ip6trie import * from test_ip4trie import * from test_acl import * if __name__ == '__main__': unittest.main() rbldnsd-0.997a/test_acl.py0000664000175000017500000000273612130202277013657 0ustar mjtmjt""" Tests for the acl dataset """ from tempfile import NamedTemporaryFile import unittest from rbldnsd import ZoneFile, Rbldnsd, QueryRefused __all__ = [ 'TestAclDataset', ] def daemon(acl, addr='localhost'): """ Create an Rbldnsd instance with given ACL """ acl_zone = NamedTemporaryFile() acl_zone.writelines("%s\n" % line for line in acl) acl_zone.flush() dnsd = Rbldnsd(daemon_addr=addr) dnsd.add_dataset('acl', acl_zone) dnsd.add_dataset('generic', ZoneFile(['test TXT "Success"'])) return dnsd class TestAclDataset(unittest.TestCase): def test_refuse_ipv4(self): with daemon(acl=["127.0.0.1 :refuse"], addr='127.0.0.1') as dnsd: self.assertRaises(QueryRefused, dnsd.query, 'test.example.com') def test_pass_ipv4(self): with daemon(acl=[ "0.0.0.0/0 :refuse", "127.0.0.1 :pass" ], addr='127.0.0.1') as dnsd: self.assertEqual(dnsd.query('test.example.com'), 'Success') def test_refuse_ipv6(self): with daemon(acl=["::1 :refuse"], addr='::1') as dnsd: self.assertRaises(QueryRefused, dnsd.query, 'test.example.com') def test_pass_ipv6(self): with daemon(acl=[ "0/0 :refuse", "0::1 :pass" ], addr='::1') as dnsd: self.assertEqual(dnsd.query('test.example.com'), 'Success') if __name__ == '__main__': unittest.main() rbldnsd-0.997a/test_btrie.py0000664000175000017500000003657512130202277014235 0ustar mjtmjt""" Tests for btrie.c """ import os import re from subprocess import Popen, PIPE from tempfile import TemporaryFile, NamedTemporaryFile import unittest from rbldnsd import Rbldnsd, ZoneFile __all__ = [ 'Test_coalesce_lc_node', 'Test_shorten_lc_node', 'Test_convert_lc_node_1', 'Test_convert_lc_node', 'Test_insert_lc_node', 'Test_init_tbm_node', 'Test_add_to_trie', 'Test_search_trie', ] def deduce_pointer_size(makefile='./Makefile'): """ Deduce the pointer size (in the current compilation environment) """ with file(makefile) as f: make_vars = dict( m.groups() for m in (re.match(r'\s*(\w+)\s*=\s*(.*?)\s*\Z', line) for line in f) if m is not None) cc = make_vars['CC'] cflags = make_vars['CFLAGS'] test_c = NamedTemporaryFile(suffix=".c") test_c.write(r''' #include #ifndef __SIZEOF_POINTER__ # define __SIZEOF_POINTER__ sizeof(void *) #endif int main () { printf("%u\n", (unsigned)__SIZEOF_POINTER__); return 0; } ''') test_c.flush() src = test_c.name try: proc = Popen("%(cc)s %(cflags)s -o %(src)s.bin %(src)s && %(src)s.bin" % locals(), shell=True, stdout=PIPE) output = proc.stdout.read() if proc.wait() != 0: raise RuntimeError("test prog exited with code %d" % proc.returncode) return int(output) finally: try: os.unlink(src + '.bin') except: pass try: sizeof_pointer = deduce_pointer_size() except Exception: print "Can not deduce size of pointer. Assuming pointer size of 8." sizeof_pointer = 8 if sizeof_pointer == 8: STRIDE = 5 LC_BYTES_PER_NODE = 7 elif sizeof_pointer == 4: STRIDE = 4 LC_BYTES_PER_NODE = 3 else: raise RuntimeError("Unsupported pointer size (%d)" % sizeof_pointer) def pad_prefix(prefix, plen): """Pad prefix on the right with zeros to a full 128 bits """ if not isinstance(prefix, (int, long)): raise TypeError("prefix must be an integer") if not 0 <= int(plen) <= 128: raise ValueError("plen out of range") if not 0 <= prefix < (1 << plen): raise ValueError("prefix out of range") return prefix << (128 - plen) class BTrie(object): """ A class to construct and perform lookups on a btrie. Since we do not have python bindings for btrie, we do this in a roundabout way by running rbldnsd with a single ip6trie dataset, and then querying the rbldnsd to perform the lookup. """ def __init__(self, prefixes, **kwargs): self.rbldnsd = Rbldnsd(**kwargs) zonedata = (self._zone_entry(*prefix) for prefix in prefixes) self.rbldnsd.add_dataset('ip6trie', ZoneFile(zonedata)) def __enter__(self): self.rbldnsd.__enter__() return self def __exit__(self, exc_type, exc_value, exc_tb): return self.rbldnsd.__exit__(exc_type, exc_value, exc_tb) def lookup(self, prefix, plen): prefix = pad_prefix(prefix, plen) nibbles = '.'.join("%x" % ((prefix >> n) & 0x0f) for n in range(0, 128, 4)) return self.rbldnsd.query(nibbles + '.example.com') def _zone_entry(self, prefix, plen, data): prefix = pad_prefix(prefix, plen) ip6addr = ':'.join("%x" % ((prefix >> n) & 0xffff) for n in range(112, -16, -16)) return "%s/%u :1:%s" % (ip6addr, plen, data) class CaptureOutput(object): def __init__(self): self._file = TemporaryFile() def fileno(self): return self._file.fileno() def __contains__(self, substr): return substr in str(self) def __str__(self): self._file.seek(0, 0) return self._file.read() class Test_coalesce_lc_node(unittest.TestCase): def test_merge(self): # test coverage of coalesce_lc_node prefixes = [ # this prefix is too long for a single LC node # but after we stick the TBM node at 0/0 it should # just fit into a single LC extension path (0, 8 * (LC_BYTES_PER_NODE + 1), "term"), # Add a TBM node to shorten the above LC node (0, (8 - STRIDE), "root"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0, 8), "term") self.assertEqual(btrie.lookup(1, 8), "root") def test_steal_bits(self): # test coverage of coalesce_lc_node prefixes = [ # This prefix is too long for a single LC node. After we # stick the TBM node at 0/0 it should still be too long # for a single LC node, but the upper LC node should steal # bits from the terminal LC node. (0, 8 * (LC_BYTES_PER_NODE + 1) + 1, "term"), (0, (8 - STRIDE), "root"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0, 0), "term") self.assertEqual(btrie.lookup(1, 8), "root") class Test_shorten_lc_node(unittest.TestCase): def test_steal_child(self): # test coverage of coalesce_lc_node prefixes = [ # this prefix is too long for a single LC node # but after we stick the TBM node at 0/0 it should # just fit into a single LC extension path (0, 9, "tbm root"), (0, 10, "term"), # Add a TBM node to shorten the above LC node (0, 9 - STRIDE, "root"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0, 8), "term") self.assertEqual(btrie.lookup(1, 8), "root") class Test_convert_lc_node_1(unittest.TestCase): def test_left_child(self): # test coverage of coalesce_lc_node prefixes = [ # create TBM node at depth 1 (0, 2, "term"), (0, 1, "tbm node"), # promote to depth 0 (0, 0, "root"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0, 0), "term") self.assertEqual(btrie.lookup(1, 2), "tbm node") self.assertEqual(btrie.lookup(1, 1), "root") def test_right_child(self): # test coverage of coalesce_lc_node prefixes = [ (3, 2, "term"), (1, 1, "tbm node"), (0, 0, "root"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(3, 2), "term") self.assertEqual(btrie.lookup(2, 2), "tbm node") self.assertEqual(btrie.lookup(0, 1), "root") class Test_convert_lc_node(unittest.TestCase): def test_left_child(self): # test coverage of coalesce_lc_node prefixes = [ # create TBM node at depth STRIDE - 1 (0, STRIDE, "term"), (0, STRIDE - 1, "tbm node"), # promote to depth 0 (0, 0, "root"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0, STRIDE), "term") self.assertEqual(btrie.lookup(1, STRIDE), "tbm node") self.assertEqual(btrie.lookup(1, 1), "root") class Test_insert_lc_node(unittest.TestCase): def test_insert_lc_len_1(self): prefixes = [ # create TBM node at depth 1 with TBM extending path (0, STRIDE + 2, "term"), (0, STRIDE + 1, "tbm ext path"), (0, 1, "tbm node"), # promote to depth 0 (0, 0, "root"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0, 0), "term") self.assertEqual(btrie.lookup(1, STRIDE + 2), "tbm ext path") self.assertEqual(btrie.lookup(1, 2), "tbm node") self.assertEqual(btrie.lookup(1, 1), "root") def test_extend_lc_tail_optimization(self): prefixes = [ # create TBM node at depth 1 with LC extending path (1, STRIDE + 2, "term"), (0, 1, "tbm node"), # promote to depth 0 (0, 0, "root"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(1, STRIDE + 2), "term") self.assertEqual(btrie.lookup(0, 0), "tbm node") self.assertEqual(btrie.lookup(1, 1), "root") def test_coalesce_lc_tail(self): prefixes = [ # create TBM node with LC extending path which starts # at a byte boundary. (0, 10, "term"), (0, 8 - STRIDE, "tbm node"), # promote one level (0, 7 - STRIDE, "promoted"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0, 0), "term") self.assertEqual(btrie.lookup(1, 9 - STRIDE), "tbm node") self.assertEqual(btrie.lookup(1, 8 - STRIDE), "promoted") class Test_init_tbm_node(unittest.TestCase): def test_short_lc_children(self): # this exercises the convert_lc_node calls in init_tbm_node() prefixes = [ # create TBM node at depth 1, with two LC extending paths # from a deep internal node (0, 1, "tbm"), (0, STRIDE + 2, "term0"), (2, STRIDE + 2, "term1"), # promote one level (0, 0, "root"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0, 0), "term0") self.assertEqual(btrie.lookup(1, STRIDE + 1), "term1") self.assertEqual(btrie.lookup(1, 2), "tbm") self.assertEqual(btrie.lookup(1, 1), "root") def test_long_lc_children(self): # this exercises the shorten_lc_node calls in init_tbm_node() prefixes = [ # create TBM node at depth 1, with two LC extending paths # from a deep internal node (0, 1, "tbm"), (0, STRIDE + 9, "term0"), (0x100, STRIDE + 9, "term1"), # promote one level (0, 0, "root"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0, 0), "term0") self.assertEqual(btrie.lookup(1, STRIDE + 1), "term1") self.assertEqual(btrie.lookup(1, 2), "tbm") self.assertEqual(btrie.lookup(1, 1), "root") def test_set_internal_data_for_root_prefix(self): # this exercises the "set internal data for root prefix" code prefixes = [ # create TBM node at depth 1, with internal prefix data # and an extending path on a deep internal node (0, 1, "tbm"), (0, STRIDE, "int data"), (0, STRIDE + 1, "ext path"), # promote one level (0, 0, "root"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0, 0), "ext path") self.assertEqual(btrie.lookup(1, STRIDE + 1), "int data") self.assertEqual(btrie.lookup(1, 2), "tbm") self.assertEqual(btrie.lookup(1, 1), "root") def test_set_right_ext_path(self): # this exercises the insert_lc_node(right_ext) call in init_tbm_node() # this also exercises next_pbyte with (pos + TBM_STRIDE) % 8 == 0 prefixes = [ # create TBM node at depth (9 - STRIDE) with a right TBM # extending path on a deep internal node (0, 9 - STRIDE, "tbm"), (1, 9, "ext path"), (2, 10, "term"), # promote one level to depth (8 - STRIDE) (0, 8 - STRIDE, "top"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0, 0), "tbm") self.assertEqual(btrie.lookup(2, 10), "term") self.assertEqual(btrie.lookup(3, 10), "ext path") self.assertEqual(btrie.lookup(1, 9 - STRIDE), "top") class Test_add_to_trie(unittest.TestCase): def test_duplicate_terminal_lc(self): prefixes = [ (0, 1, "term"), (0, 1, "term"), ] stderr = CaptureOutput() with BTrie(prefixes, stderr=stderr) as btrie: self.assertEqual(btrie.lookup(0, 0), "term") self.assertTrue("duplicated entry for" in stderr, "No duplicated entry error message in stderr: %r" % str(stderr)) def test_duplicate_internal_data(self): prefixes = [ (0, 0, "root"), (2, 3, "term"), (2, 3, "term"), ] stderr = CaptureOutput() with BTrie(prefixes, stderr=stderr) as btrie: self.assertEqual(btrie.lookup(4, 4), "term") self.assertEqual(btrie.lookup(0, 0), "root") self.assertTrue("duplicated entry for" in stderr, "No duplicated entry error message in stderr: %r" % str(stderr)) def test_split_first_byte_of_lc_prefix(self): # this is for coverage of common_prefix() prefixes = [ (0x1234, 16, "long"), (0x1000, 16, "splitter"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0x1234, 16), "long") self.assertEqual(btrie.lookup(0x1000, 16), "splitter") def test_split_last_byte_of_lc_prefix(self): # this is for coverage of common_prefix() prefixes = [ (0x1234, 15, "long"), (0x1238, 15, "splitter"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0x1234, 15), "long") self.assertEqual(btrie.lookup(0x1238, 15), "splitter") class Test_search_trie(unittest.TestCase): def test_tbm_root_data(self): # test access to root internal node in a TBM node prefixes = [(0, 127, "tbm root"), (1, 128, "int data")] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0, 0), "tbm root") def test_tbm_internal_data(self): # test access to each (non-root) internal node in a TBM node for plen in range(1, STRIDE): # TBM node prefixes = [(0, 128 - plen, "tbm root")] prefixes.extend((pfx, 128, "%u/%u" % (pfx, plen)) for pfx in range(1 << plen)) with BTrie(prefixes) as btrie: for pfx in range(1 << plen): self.assertEqual(btrie.lookup(pfx, 128), "%u/%u" % (pfx, plen)) def test_tbm_extending_paths(self): # test access to each extended path of a TBM node prefixes = [(0,0,"root")] # make sure to create top-level TBM node prefixes.extend((pfx, STRIDE, str(pfx)) for pfx in range(1 << STRIDE)) with BTrie(prefixes) as btrie: for pfx in range(1 << STRIDE): self.assertEqual(btrie.lookup(pfx, STRIDE), str(pfx)) def test_no_match(self): prefixes = [ (1, 2, "term"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0,0), None) def test_follow_lc(self): prefixes = [ (0, 2 * STRIDE, "term"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0,0), "term") def test_parents_internal_data(self): prefixes = [ (0, 0, "root"), (2, 2, "int data"), (0x200, 10, "term"), ] with BTrie(prefixes) as btrie: self.assertEqual(btrie.lookup(0x201, 10), "int data") if __name__ == '__main__': unittest.main() rbldnsd-0.997a/test_ip4trie.py0000664000175000017500000000232112130202277014466 0ustar mjtmjt""" (Very) basic ip4trie dataset tests """ import unittest from rbldnsd import Rbldnsd, ZoneFile __all__ = [ 'TestIp4TrieDataset', ] def ip4trie(zone_data): """ Run rbldnsd with an ip6trie dataset """ dnsd = Rbldnsd() dnsd.add_dataset('ip4trie', ZoneFile(zone_data)) return dnsd def reversed_ip(ip4addr, domain='example.com'): revip = '.'.join(reversed(ip4addr.split('.'))) return "%s.%s" % (revip, domain) class TestIp4TrieDataset(unittest.TestCase): def test_exclusion(self): with ip4trie(["1.2.3.0/24 listed", "!1.2.3.4"]) as dnsd: self.assertEqual(dnsd.query(reversed_ip("1.2.3.4")), None) self.assertEqual(dnsd.query(reversed_ip("1.2.3.3")), "listed") self.assertEqual(dnsd.query(reversed_ip("1.2.3.5")), "listed") def test_wildcard_prefix(self): with ip4trie(["0/0 wild", "127.0.0.1 localhost"]) as dnsd: self.assertEqual(dnsd.query(reversed_ip("127.0.0.1")), "localhost") self.assertEqual(dnsd.query(reversed_ip("0.0.0.0")), "wild") self.assertEqual(dnsd.query(reversed_ip("127.0.0.2")), "wild") if __name__ == '__main__': unittest.main() rbldnsd-0.997a/test_ip6trie.py0000664000175000017500000000173712130202277014502 0ustar mjtmjt""" Basic ip6trie dataset Tests """ import unittest from rbldnsd import Rbldnsd, ZoneFile __all__ = [ 'TestIp6TrieDataset', ] def ip6trie(zone_data): """ Run rbldnsd with an ip6trie dataset """ dnsd = Rbldnsd() dnsd.add_dataset('ip6trie', ZoneFile(zone_data)) return dnsd def rfc3152(ip6addr, domain='example.com'): from socket import inet_pton, AF_INET6 from struct import unpack bytes = unpack("16B", inet_pton(AF_INET6, ip6addr)) nibbles = '.'.join("%x.%x" % (byte & 0xf, (byte >> 4) & 0xf) for byte in reversed(bytes)) return "%s.%s" % (nibbles, domain) class TestIp6TrieDataset(unittest.TestCase): def test_exclusion(self): with ip6trie(["dead::/16 listed", "!dead::beef"]) as dnsd: self.assertEqual(dnsd.query(rfc3152("dead::beef")), None) self.assertEqual(dnsd.query(rfc3152("dead::beee")), "listed") if __name__ == '__main__': unittest.main() rbldnsd-0.997a/debian/changelog0000664000175000017500000010041312173530043014574 0ustar mjtmjtrbldnsd (0.997a) unstable; urgency=low * change the way how `make dist' behaves (no need for a temporary subdir anymore) * fixed sed expression to determine version number, needed for *BSD -- Michael Tokarev Tue, 23 Jul 2013 20:32:28 +0400 rbldnsd (0.997) unstable; urgency=low [ Jeff Dairiki ] * configure: add --enable-asserts option to enable compilation of assertions; assertion checking is disabled by default * tests/test_acl.py, tests/test_ip4trie.py: new, very basic, tests for the acl and ip4trie datasets * rbldnsd_acl.c, rbldnsd_ip4trie.c: allow zero-length "wildcard" IP4 CIDR prefixes * rbldnsd_acl.c: add support for ip6 addresses in ACLs * rbldnsd_util.c: delete the (now unused) ip4trie code * rbldnsd_acl.c: use new LC-TBM trie implementation * rbldnsd_ip4trie.c: use new LC-TBM trie implementation instead of the previous ip4trie; this decreased the memory consumption of this dataset by roughly a factor of three * rbldnsd_ip6tset.c: use new dump_ip6() * Makefile.in: add 'check' target to run btrie.c self-tests and new python-driven tests * feature: ip6trie - new dataset supports listing of arbitrary length ip6 CIDRs, along with individual A/TXT values for each prefix * rbldnsd_util.c(dump_ip6, dump_ip6range): new functions to support zone dump of ip6 zones * btrie.c: LC-TBM trie implementation; supports arbitrary length prefixes in a memory- and cpu- efficient manner * configure: add test for __SIZEOF_POINTER__ - define if the compiler does not already do so * configure: add test for byte sex - define WORDS_BIGENDIAN in config.h on big-endian architectures * ip6addr.c: add support for compressed ("::") ip6 address notation * mempool.c: bug fix for a theoretically possible buffer overrun [ Michael Tokarev ] * feature: ip6tset - new dataset supports listing of ip6 /64 subnets and the exclusion of /128 subnets; only supports a single A/TXT value for the entire dataset -- Michael Tokarev Sat, 29 Jun 2013 12:02:54 +0400 rbldnsd (0.996b) unstable; urgency=low * cleanup: #ifdefed dump_a_txt() properly so it will not be compiled if --disable-master-dump was specified. * implement "base template" ($=) feature. * feature: (experimental) support for dynamically-loaded extensions (DSOs) (disabled by default, use --enable-dso configure option) * portability and readability fixes from Victor Duchovni * added configure test for inline and __inline keywords, and only use #warning keyword if __GNUC__ is defined (more portability fixes from Victor Duchovni) * misc type conversions here and there, and change alignment in mempool.c to be sizeof(void*) instead of sizeof(int), to help 64bit platforms. Thanks to Mike Quintero for an excellent bugreport. * bugfix: combined dataset - improper return of query() routine in some cases * internal code reorg: - move firstword[_lc]() to _util.c - use two structs instead of a set of 2-element arrays in dnset * bugfix: lowercase base zone names given on command line and in `combined' dataset, or else they wont be recognized in queries * added an lsb info to Debian initscript (Closes: #468886) -- Michael Tokarev Sat, 29 Mar 2008 17:38:49 +0300 rbldnsd (0.996a) unstable; urgency=low * the "34-Birthday Release" * use setitimer() instead of alarm() on systems which supports it (configure & rbldnsd.c). This works around some broken linux kernel behaviour where we sometimes lose a signal (SIGALRM in this case) and rbldnsd stops checking for updates. * cosmetic code cleanups: - use void* instead of char* in mempool.[ch] where appropriate. - wrong type in check_expires(): unsigned instead of time_t. - wrong type (signedness) for domain name in ds_dnset_dump() - use appropriate char/uchar for domain names in ds_generic_dump() - istream signedness (warning) fixes * debian: - Build-Depends on debhelper>>4, and set DH_COMPAT to 4 - Build-Depends on zlib1g-dev, and pass --enable-zlib to ./configure - bump Standards-Version to 3.7.2 - add Depends: on adduser (Closes: #398560) * bugfix: fix dataset "un-expiration" time. Previously, once a dataset has expired, it never "un-expires" again even if new expire time is in future. Due to missing reset of ds->ds_expire field. * bugfix: fix configure breakages: - portability: for f; do => for f in "$@"; do - fix broken GNU C (mis)detection * don't treat -a as experimental and mention it will be the default. Add -A option. -- Michael Tokarev Thu, 27 Jul 2006 13:36:30 +0400 rbldnsd (0.996) unstable; urgency=low * 0.996 release * portability fix in istream.c: EPROTO=>ENOEXEC if EPROTO isn't defined -- Michael Tokarev Sun, 19 Feb 2006 16:41:16 +0300 rbldnsd (0.995.99) unstable; urgency=low * add a 'pass' entry processing into ACL "dataset" * (internal) reorganize ds_special() (no user-visible changes) * (internal) reorganized zone reloading code to eliminate start_loading()+longjump() hack * data expiration support, in form $TIMESTAMP created expires -- Michael Tokarev Sun, 29 Jan 2006 00:43:51 +0000 rbldnsd (0.995.98) unstable; urgency=low * deal with realloc(smaller_size) returning NULL * add next-reload-size hint to dnset too * (cosmetic code) unify several structure member names across different *sets * up MAX_NS from 20 to 32, per request from Spamhaus * fix a nasty typo in vssprintf() (bufsiz => bufsz). DO #undefine constants which aren't needed anymore! * rearrange code a bit: move readslines() from rbldnsd_util.c to rbldnsd_zones.c, make some functions static and remove declarations from rbldnsd.h. No code changes. * cosmetic again: NOSTATS, NOSTDINT_H etc => NO_STATS, NO_STDINT_H etc. Note if you had automated building procedure you have to change your #defines. * new configure script, using shell functions in configure.lib - much more manageable. * configure script now accepts some --disable- and --enable- options (--disable-ipv6) * istream.[ch] - helper module, light buffering input somewhat similar to stdio, much faster, supports line oriented input, and is stackable (one istream can read from another or something else). Reading speed improved significantly. * support of automatic on-the-fly de-compression of gzip-compressed data files (with a help of zlib). -- Michael Tokarev Tue, 20 Dec 2005 00:15:56 +0300 rbldnsd (0.995) unstable; urgency=low * released 0.995 * ensure we do not return more than 255 answers (which can happen when EDNS0 size extensions are enabled), as the code isn't prepared to handle 2-byte numanswers field in DNS packet header. -- Michael Tokarev Thu, 28 Apr 2005 23:57:32 +0400 rbldnsd (0.994.94) unstable; urgency=low * fixed EDNS0 (it now really works) * fixed NS+glue records - if there's no room for glue but auth section can be added, add auth w/o glue * remove p_..cnt #defines (to be p_..cnt2), as they're confusing -- Michael Tokarev Thu, 28 Apr 2005 15:24:10 +0400 rbldnsd (0.994.93) unstable; urgency=low * glue records for NSes, finally. * when queried for NS or ANY to the base zone, return NS recs in both answer and authority sections. * in acl "dataset", default action is now "ignore", not "always-listed" -- Michael Tokarev Wed, 27 Apr 2005 19:04:56 +0400 rbldnsd (0.994.92) unstable; urgency=low * finally: EDNS0 support. Patch was here for a long time, now applied. * ACL (initial, experimental) support, with quite some changes all over to make it possible/easier: - add peer address info pointers to struct dnspacket (and made both struct packet and peer sockaddr to be static in rbldnsd.c); remove peer address parameters from logreply(). - add DSTF_SPECIAL flag, to indicate this dataset type can't be nested; modify rbldnsd_combined.c accordingly - remove (unused) DSTF_ZERODN flag - define two new helper macros, dstype(name) and isdstype(dst,name), to refer to types of datasets, and use the macros in the code - add ACL-specific RR info into struct dnspacket - dataset query types now return bitflags, not true/false: NSQUERY_FOUND (1) - found a matching record NSQUERY_ADDPEER (2) - for "always listed" acl, we should add the "always listed" ACL RR into the reply packet. return NSQUERY_FOUND instead of generic `1' in all dataset->queryfns. - extend qi_tflag to also include ACL-specific flags: NSQUERY_IGNORE, NSQUERY_REFUSE, NSQUERY_EMPTY, NSQUERY_ALWAYS - add check_query_overwrites() macro to test the above flags and return NSQUERY_ADDPEER or other bits in dataset->queryfn routines, and use this macro in all non-metadata datasets - when constructing reply, collect flags from queryfns, not boolean. - add g_dsacl and zone->z_dsacl pointers (global and zone-specific datasets) - modify rbldnsd_zones.c:addzone() to recognize ACLs and to disallow empty base zone domain name. - disallow $NS and $SOA for ACL-type datasets in ds_special() - new dataset file: rbldnsd_acl.c. Add acl dataset into global dataset type list. - actually call ACL-specific routine -- ds_acl_query() -- when constructing reply to a query - when at the end of constructing answer section of the reply we notice "always-listed" ACL has been triggered, add ACL-specific A+TXT records into the reply -- Michael Tokarev Sat, 16 Apr 2005 17:43:43 +0400 rbldnsd (0.994b) unstable; urgency=low * use of uninitialized pointer in ip4set and ip4trie datasets when input data file (A+TXT template for a given entry) is invalid, instead of rejecting the line. This can lead to "random" crashes. -- Michael Tokarev Thu, 10 Mar 2005 01:57:14 +0300 rbldnsd (0.994a) unstable; urgency=low * fixed wrong NXDOMAIN return for a query for base subzone in combined dataset (should be NODATA at least, not NXDOMAIN) -- Michael Tokarev Thu, 10 Mar 2005 01:43:22 +0300 rbldnsd (0.994) unstable; urgency=low * released 0.994 -- Michael Tokarev Sat, 18 Dec 2004 17:07:24 +0300 rbldnsd (0.993.9) unstable; urgency=low * fixed wrong return in ds_ip4trie_dump_octets(), which prevented some ranges from being expanded properly when creating master-format dump (-d) file. * generalized and moved ds_ip4trie_dump_range() (renamed to dump_ip4range()) to rbldnsd_utils.c to made it generally useful (to be used in rbldnsd_ip4set.c too), and made it void (cf the wrong return above) * made txtsubst() public, and move it and dump_a_txt() into rbldnsd_utils.c where the stuff belongs to, out from rbldnsd_packets.c which is too large and has nothing to do with that stuff. * new helper dump_ip4(), analogous to dump_ip4range() * use dump_ip4() in ip4tset * rewrite ds_ip4set_dump(), to handle that problrmatic case with entries 127/8 and 127.0.0.2: should also emit *.0.127 and *.0.0.127 * recognize NO_MASTER_DUMP #define (disable -d option) * fix memleak when loading $n substitutions (mp_strdup vs estrdup) -- Michael Tokarev Mon, 13 Dec 2004 04:40:27 +0300 rbldnsd (0.993.1) unstable; urgency=low * don't remove rbldns user in debian/postrm (Closes: bug#258012). * create /var/lib/rbldns root-owned, to stop encouraging usage of rbldns userid to store datafiles. * add README.user explaining rbldns userid and /var/lib/rbldns dir usage * #include in rbldnsd.c to get struct timeval definition (needed e.g. on older glibc) -- Michael Tokarev Thu, 29 Jul 2004 17:46:51 +0400 rbldnsd (0.993) unstable; urgency=low * 0.993 release, finally * cleaned up debian/rules a bit per recommendations by Santiago Vila * fixed a bug when rbldnsd was using 0.0.0.0 as A value instead of a real IP address (when construct like ":127.0.0.2" is specified), introduced in 0.992 * manpage: described various :A:TXT cases; reformatted to use .SS instead of .IP in some cases. * remove a note about ip4trie and -d option: ip4trie is dumpable now. -- Michael Tokarev Thu, 1 Jul 2004 20:23:01 +0400 rbldnsd (0.993p2) unstable; urgency=low * warn when $DATASET specifies no subzones * recognize # in $DATASET line only after a space, so that one may have names with embedded #s. * allow names for subdatasets in combined dataset, for better logging. Specify :name after type in $DATASET line, like $DATASET ip4set:http proxies @ $DATASET ip4set:relays relays @ -- Michael Tokarev Thu, 1 Jul 2004 15:27:30 +0400 rbldnsd (0.993p1) unstable; urgency=low * implement and enforce $MAXRANGE4 special * refine logging a bit, make it less verbose * (internal) reorganize loading process a bit to have dsctx parameter (load context) to be passed as an argument, instead of global ds_loading. (internal) moved ip4parse_*() into the only places where that stuff was used. (internal) minor cleanups in combined dataset * ignore incomplete last lines (lines w/o end-of-line terminator) in data files * check for data file changes during reloads (while reading data), and abort loading if a change is detected * another parsing error in ip4parse.c (ip4addr() and ip4prefix(), unlike claimed, was able to return 0) * more cleanups and clarifications in ip4parse.c. Do not treat bare numbers as /8s anymore. Do not allow ranges like 1.2-2 or 1.2-1.2.3 - number of octets on both sides should match (with the exception of 1.2.3.4-5 - last part may have only one octet) * really require complete IPv4 addresses (not prefixes) in ip4tset. * fixed query logging (-l) with background reloading (missing fflush() before fork() and exit()) * use writev/readv to transfer stats in 2-process reload (may be a bit faster) * cleanup NOSTATS #defines/usage a bit * bugfix: wrong MX pref in dump (-d) in generic dataset. Fix by Andreas Pommer, apommer at cosy.sbg.ac.at * bugfix: wrong subzone in $ORIGIN when dumping combined dataset. Fix by Andreas Pommer, apommer at cosy.sbg.ac.at * bugfix: incorect (opposite) evaluation of maxttl. Fix by Bradley Baetz bradley.baetz at optusnet.com.au * some hooks, to build custom versions (rbldnsd_hooks.[ch]) -- Michael Tokarev Thu, 10 Jun 2004 02:46:57 +0400 rbldnsd (0.992) unstable; urgency=low * add an ability to specify A value but inherit default TXT value: entry :addr: - specific A, no TXT entry :addr - specific A, default TXT * remove redundrant CNAMEs from master-file dump in ip4set * fix syslog statistics logging mess introduced in pre4 -- Michael Tokarev Sun, 7 Mar 2004 01:35:46 +0300 rbldnsd (0.992pre4) unstable; urgency=low * reviewed statistics again: - use different structure (smaller and simpler anyway) - moved all stats updating to rbldnsd_packet.c - different syslogging (simpler) - different statslogging (more details) * extended -t (TTL) option: -t defttl:minttl:maxttl. * remove some more mess from memstats logging * changed rbldnsd.init to not rely on readlink and be less strict * rework 2-process reload, add workarounds for lost signals -- Michael Tokarev Wed, 3 Mar 2004 17:43:38 +0300 rbldnsd (0.992pre3) unstable; urgency=low * added ip4tset ("trivial" set of IPv4 addresses) - faster and smaller but limited version of ip4set * continue processing queries during reloads (-f option) -- Michael Tokarev Mon, 1 Mar 2004 19:36:15 +0300 rbldnsd (0.992pre2) unstable; urgency=low * cleaned up statistics printing * removed -s option (log memory usage and (re)load times) - turned (reworked) equivalent always on * reworked memusage/times logging * added -s back, to mean something different: log short stats line on every check (-c) invocation, to help collecting data for RRD-like applications * remove some bash-isms from rbldnsd.init * lazy mode is still experimental... -- Michael Tokarev Sat, 28 Feb 2004 04:57:36 +0300 rbldnsd (0.992pre1) unstable; urgency=low * add `lazy' mode (-a, experimental) to stop returing AUTH section by default, return it only when asked explicitly * recognize `-' as single-NS-comment char in $NS line * use O_LARGEFILE and #define _LARGEFILE64_SOURCE in rbldnsd.c to be able to write larger logfiles * removed usage of NI_WITHSCOPEID (was used for unknwon reason anyway and it have problems with new Solaris) * fix ds_generic_dump() that was broken for quite some time * fix example dataset in manpage (missing preference for MX) -- Michael Tokarev Wed, 4 Feb 2004 15:55:08 +0300 rbldnsd (0.991) unstable; urgency=low * implemented master format dump for ip4trie dataset * removed all references to osirusoft.com * switch to integer TTL everywhere * PACK32S() and PACK16S() aka stpcpy() (incrementing destination) * $NS records change: accept all NS in single line or in multiple lines * increase dataset reading line length from 512 bytes to 8k to allow large list of nameservers in one $NS record * use ns records from FIRST dataset (with non-empty $NS) only * add new dataset to the end of a list, not to top (cosmetic change, specifies loading order and thus logging order only) * disallow zero TTL in command line * reworked duplicate RR detection and choosing of minTTL. Fix TTL even when we have exactly the same RR in packet already. * simplified zone NS records handling * new routine: zlog(loglevel, zone, fmt, ...) * log number of zones at startup -- Michael Tokarev Sun, 30 Nov 2003 18:34:04 +0300 rbldnsd (0.990pre0) unstable; urgency=low * ensure we return all RRs of the same type (if there are several of them) with the same (smallest) TTL. Problem spotted by Victor Duchovny, MorganStanley. * "randomize" order of RRs of the same type in `generic' dataset. The randomization is dumb, but it is better than nothing anyway. Suggested by Furio Ercolessi. -- Michael Tokarev Sun, 30 Nov 2003 14:28:52 +0300 rbldnsd (0.99) unstable; urgency=medium * autoconf-style configuration. Run ./configure before make. - #include "config.h" - uint32_t - STATS_LL => uint64_t/long long/long, PRI_DNSCNT: PRIu64/llu/lu Thanks to Christian Krackowizer for testing and patience. * change ip4addr parsing routines to return -1 in case of error, instead of 0, and treat 0 to be valid return value too (0/0) * fix utimes printing (time rbldnsd spent when loading zones) * move VERSION[_DATE] extraction from Makefile to configure, made Makefile depend on debian/changelog. * debian/rules: do not attempt to make distclean if there's no Makefile * another config test: (NOSELECT_H) * some mods to ip4trie * cast [ug]id_t to int for printf * config test for vsnprintf(), fatal if not found for now (OSF lacks it) * remove EasynetDynablock2rbldnsd.pl since easynet.nl provides dynablock in rbldnsd format now. * remove osirusoft2rbldns.pl since relays.osirusoft.com is dead. * made zonelist really global in rbldnsd.c, reorder some globals to be in one place, add some more comments * Fixed range parsing. E.g., 24.217.64-191 did not work (and any range like this where last two bits where xored into 255) * allow logging to standard output (-l - or -l +-) * move logfile handling a bit earlier in the init process, and do not send initialization messages to stdout in case we're logging queries there (-l -) -- Michael Tokarev Tue, 16 Sep 2003 21:33:35 +0400 rbldnsd (0.98) unstable; urgency=low * use memmove() instead of memcpy() for overlapping regions, even if dstaddr is less than srcaddr (current memcpy() can deal with that, but this is out of spec and future versions may do some more optimizations that'll break things) * made init_zone_caches() to work for a list of zones, not just a single zone, and move a call to this routine from newzone() to init(). Fixes a memory leak in combined dataset (caches was allocated but never used/freed). * properly (but still ugly) fix stats counting * some "ugliness": use more const's and explicitly declare routines wich does not accept any arguments as such to be foo(void) (Marco D'Itri md at linux.it) * handle version.bind CH TXT (and version.server for that matter) * return REFUSED instead of FORMERR in case unrecognized query class (IN,ANY,etc) requested * defttl => def_ttl to be consistent with def_rr * set ttl for version.bind to be 0 * warn about truncating TXT records. Change interface of parse_a_txt() to include a line number * TXT RR is 255 bytes max, not 254 (generic dataset) * wildcards in dnset: *.example.com and example.com works as before, but .example.com is now interpreted as BOTH *.example.com and example.com. * clarifications in manpage: dataset vs zone * fix logging to various destinations (LOGTO_* constants, vdslog()) * allow to specify multiple listening addresses. * recognize -b host:port and [ip]:port syntax (only host previously) * -b option is now mandatory * do fork() early in the initialization process so that logging will have proper pid tag. This also slightly simplified init routine. * made array of listening sockets to be global * added ip4trie (experimental) dataset: set of IP4 CIDR ranges. * yet again, fix CIDR parsing (mask vs ~mask) * fix ip4trie common length calculation in case diff == 0 * fix dslog() to use vprintf, not printf -- Michael Tokarev Sun, 17 Aug 2003 21:48:54 +0400 rbldnsd (0.97b) unstable; urgency=low * fixed security bug in per-zone statistic counting. Thanks Marco D'Itri (md at linux.it) for pointing this out to me. -- Michael Tokarev Wed, 6 Aug 2003 03:20:36 +0400 rbldnsd (0.97a) unstable; urgency=medium * recognize and ignore "in" in `generic' dataset, as in: example.com 1D IN A 127.0.0.1 * do not truncate addresses in CIDR like 1.2.3.4/24 to 1.2.3.0/24 in ip4range() * fix a bug in ip4set_parse(): propagate error return properly -- Michael Tokarev Fri, 1 Aug 2003 13:43:28 +0400 rbldnsd (0.97) unstable; urgency=low * even better mempool align arith * remove some dnlen usage (only tiny slowdown of zone reloads) * use switch(firstchar) in ds_special() * return SOA first to ANY queries * Also return NS in AUTH in ANY queries inside base zone (cosmetic) * add SOA/NS even before all other recs when queried base DN * combined dataset optimization: try to reuse datasets from previous load instead of reallocating them on every reload to preserve statistics collected by subdatasets * in some rare cases, dnset missed one RR for a DN with multiple RRs * -DRECOGNIZE_IP4IN6 - treat reverse IPv6-mapped IPv4 queries as plain IPv4 qieries * Wirehub Dynablock => Easynet Dynablock * -DSTATS_LL to use long longs for statistic counters * per-basezone statistic counters * corrections of various spelling errors in manpage, great thanks to Bert Driehuis (driehuis{at}playbeing{dot}org) for this. -- Michael Tokarev Sun, 13 Jul 2003 03:19:56 +0400 rbldnsd (0.96) unstable; urgency=high * renamed almost all structures and variables, to be more accurate: struct dataset => struct dsdata (dsd) struct zonedataset => struct dataset (zds => ds) struct zonedatalist => struct dslist (zdl => dsl) struct zonefile => struct dsfile (zf => dsf) struct dataset_type => struct dstype (dst) dataset_types[] => ds_types[] zds_loading => ds_loading struct dnsqueryinfo => struct dnsqinfo (qi) connectzonedataset() => connectdataset() zds_special() => ds_special() and so on. * reworked NS and SOA internals, to use more natural data structures (no more ugly packing of several fields into one char[]) * always assume that SOA and NS refers to zone's base DN - less work for DN compression routines * precompute SOA and NS records (pack various fields, pre-compress domain names) for faster response time * return NS records if available in AUTHORITY section of positive replies * restore MX functionality that was broken for quite some time. Note that currently, MX records aren't compressed * do not lowercase domain names used in NS, SOA and MX records. Add dns_dnequ() routine, remove dns_dntol() call from parse_dn(). * pre-initialize zone caches when creating zone structure (before startup) - both to avoid memory fragmentation and to ensure all the required memory is allocated. * Only add NS records if reply contains some data * Add NS AUTH record for queries to base zone too (when not asked for NS) * simplify and clarify warning about too long NS or SOA RRs * remove zlog() * Moves: dslog() &Co from rbldnsd_zones.c to rbldnsd_utils.c dntoip4addr() from rbldnsd_utils.c to rbldnsd_packet.c ds_loading from rbldnsd_zones.c to rbldnsd.c * fixed alignment bug in mempool.c that caused allocation slip * simplify dssoa allocation -- Michael Tokarev Thu, 29 May 2003 12:46:37 +0400 rbldnsd (0.95) unstable; urgency=low * Portability: #include for sockaddr_in definition in rbldnsd_packet.c * Allocate dataset to be part of zonedataset -> remove dataset's allocfn * Added `combined' dummy dataset * Change loading routines: ds_loadfn become ds_startfn and ds_linefn, ds_linefn is now passed via zonedataset to readdslines. Preparations for combined dataset. * Implemented `combined' dataset, with many changes all other the places * Notes, TODO items updated * Fixed problem with DN compression introduced in recent changes. * cleanup of #include's * change names of zsoa, zns to reflect content more accurately * align argument for mp_alloc(), to align object(s) to sizeof(int). This allows to eliminate one mempool in combined dataset. * use varlen zns_ttldn[] * use one memory pool for all nested datasets in combined dataset * add additional argument to ds_resetfn_t - freeall boolean, to free all the memory instead of just zeroing it - needed for combined dataset, but not currently used anyway * in ip4set, remember size of previously allocated array to avoid unnecessary realloc()s * made ip4mask() to be an indexed array access to ip4addr_cidr_netmasks[] -- Michael Tokarev Tue, 27 May 2003 22:18:20 +0400 rbldnsd (0.94) unstable; urgency=low * implemented -d option (dump zone data in BIND format to stdout) * data loading warnings goes to stderr instead of stdout * Makefile portability tweaks for Solaris * recognize ';' as comment char in addition to '#'; also, officially recognize comments after an entry (IP address or domain name) in ip4set and dnset -- Michael Tokarev Mon, 26 May 2003 17:49:27 +0400 rbldnsd (0.93) unstable; urgency=low * reverse change made in 0.91: SOA TTL, when SOA is in AUTHORIRY section, should be set to MINTTL field of SOA itself. -- Michael Tokarev Sun, 18 May 2003 19:05:38 +0400 rbldnsd (0.92) unstable; urgency=medium * bugfix: fixed SOA screwup introduced in 0.91 -- Michael Tokarev Sat, 17 May 2003 05:24:39 +0400 rbldnsd (0.91) unstable; urgency=low * print version info at build time (Makefile) * made SIGNALLED_* names action-, not signal-dependant * allow compilation without IPv6 (-DNOIPv6) * free addrinfo structure for bind address (not a real leak: it used once) * rotate nameserver records (simple cyclic rotation) * do not reallocate memory for dataset headers on every reload * fixed glitch in dnset - min DN labels was always 0 (tiny speedup only) * fixed default A RR to be 127.0.0.2, not 2.0.0.127 * added a note about absolute vs relative domain names into manpage * allow to specify a unit (s, m, h, d, w) for all time values * changed default ttl to be 35m * use ISSPACE/SKIPSPACE everywhere * use PACK32/PACK16 everywhere * some more variants of parse_{time,uint32} * use parse_time() for -c option too * always use SOA's TTL when SOA is included in answer * added RPM .spec file -- Michael Tokarev Thu, 15 May 2003 02:35:35 +0400 rbldnsd (0.90) unstable; urgency=low * 0.90 final. Some little mods for IPv6. -- Michael Tokarev Sat, 10 May 2003 01:54:52 +0400 rbldnsd (0.89p4.ip6) unstable; urgency=low * IPv6 transport support. Default is to try any, specify -6 or -4 to select IPv6 or IPv4. * -a (ACL) and -L (log ACL) options are gone for now, because there is no IPv6 versions. -- Michael Tokarev Thu, 8 May 2003 21:24:02 +0400 rbldnsd (0.89p4) unstable; urgency=low * NXDOMAIN elimination is gone. Bind9 does that too, so I assume it is correct behaviour. Many code removed. * Another prerelease. -- Michael Tokarev Thu, 8 May 2003 21:24:02 +0400 rbldnsd (0.89p3) unstable; urgency=low * ip4vset and ip4set merged, ditto for dnvset and dnset. * $NS in datasets * $TTL in datasets generic records; now required for $SOA and $NS * $n substitutions * more details in NEWS -- Michael Tokarev Thu, 8 May 2003 16:59:33 +0400 rbldnsd (0.89p2) unstable; urgency=low * again, many changes. DN-based lists are now ok too from BIND point of view. Logging improvements. See NEWS file. -- Michael Tokarev Tue, 6 May 2003 03:33:37 +0400 rbldnsd (0.89p1) unstable; urgency=low * big number of changes, to match BIND's runtime behaviour. See NEWS for details -- Michael Tokarev Sun, 4 May 2003 18:27:42 +0400 rbldnsd (0.84p2) unstable; urgency=low * rewrote query parsing routine to be much more accurate (see version 0.83 change entry) and a bit faster. * cleanups, cleanups. * return definitive answer to AAAA, PTR and CNAME queries. A hack for now, seeking for a better way... -- Michael Tokarev Tue, 29 Apr 2003 04:29:02 +0400 rbldnsd (0.83) unstable; urgency=high * critical buffer overflow fix in dns query parsing code. Initial code (0.1 version) was right, but it was rewritten in 0.2 - the bug was here since 0.2!.. Ughhh!.. -- Michael Tokarev Sat, 19 Apr 2003 05:24:09 +0400 rbldnsd (0.83p1) unstable; urgency=low * -DNOSTDINT_H to use instead of for uint32_t. * changed slightly "zone loaded" message * added minimal access control (-a netlist option) * added query log filter (-L netlist option), and added logging of timestamps. * moved packet receiving/replying stuff into rbldnsd.c * one-by-off bug - retrying interrupted replies was wrong * use inline version of qsort in order to be able to use inline comparision routine. Speed up loading significantly. * allow to run as non-root; little cleanups * removed -DNOREMOVEDUPS, -DNOIP4RANGES, -DPRINT_TIMES => -DNOTIMES * added statistic counters (SIGUSR1/USR2/exit) - disable with -DNOSTATS * added meminfo logging via mallinfo (-m option) - disable with -DNOMEMINFO * fixed invocation w/o -b (bind address): rbldnsd wasn't work w/o -b at all -- Michael Tokarev Sat, 19 Apr 2003 02:25:18 +0400 rbldnsd (0.82) unstable; urgency=low * recognize another variation of IP address range, for easy use: 127.0.0.1-2 is now treated as 127.0.0.1-127.0.0.2 127.0-200 is now treated as 127.0.0.0-127.200.255.255 * rbldnsd w/o arguments will exit with 1 after printing usage info -h cause exit with 0 (I forgot to add exit() call after usage()) * example WirehubDynablock2rbldnsd.pl script * debianized, added startup script for Debian (rbldnsd is now a native Debian package) -- Michael Tokarev Sat, 5 Apr 2003 11:40:31 +0400 rbldnsd (0.81-0) unstable; urgency=low * Initial Debian Release. Previous CHANGES file is now in CHANGES-0.81 -- Michael Tokarev Sat, 5 Apr 2003 00:30:39 +0400 Local variables: mode: debian-changelog End: rbldnsd-0.997a/debian/copyright0000644000175000017500000000172311534653360014667 0ustar mjtmjtThis is rbldnsd, written and maintained by Michael Tokarev The original source and debian packages can always be found at: http://www.corpit.ru/mjt/rbldnsd.html Copyright (C) 2002 Michael Tokarev 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 with the Debian GNU/Linux distribution in file /usr/share/common-licenses/GPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA rbldnsd-0.997a/debian/rules0000775000175000017500000000215712120257742014014 0ustar mjtmjt#!/usr/bin/make -f # debian/rules for rbldnsd # GNU copyright 1997 to 1999 by Joey Hess. # GNU copyright 2003 by Michael Tokarev. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 DEFS = CFLAGS = -pipe -Wall -W -g ifeq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) CFLAGS += -O2 endif config: config.h Makefile config.h Makefile: configure Makefile.in dh_testdir CFLAGS="$(CFLAGS)" DEFS="$(DEFS)" ./configure --enable-zlib build: debian/build-stamp debian/build-stamp: config.h dh_testdir $(MAKE) touch $@ clean: dh_testdir rm -f debian/build-stamp [ ! -f Makefile ] || $(MAKE) distclean dh_clean install: debian/build-stamp dh_testdir dh_testroot dh_clean mkdir -p -m0755 debian/rbldnsd/usr/sbin cp rbldnsd debian/rbldnsd/usr/sbin dh_installman rbldnsd.8 dh_installinit dh_installdocs README.user NEWS TODO CHANGES-0.81 dh_installchangelogs dh_strip dh_compress dh_fixperms binary-indep: binary-arch: build install dh_testdir dh_testroot dh_installdeb dh_shlibdeps dh_gencontrol dh_md5sums dh_builddeb binary: binary-arch .PHONY: build clean binary-indep binary-arch binary install rbldnsd-0.997a/debian/control0000664000175000017500000000075112120257742014335 0ustar mjtmjtSource: rbldnsd Section: net Priority: optional Maintainer: Michael Tokarev Uploaders: Santiago Vila Build-Depends: debhelper (>> 4), zlib1g-dev Standards-Version: 3.7.2 Package: rbldnsd Architecture: any Depends: ${shlibs:Depends}, adduser Description: small nameserver daemon designed for DNSBLs Rbldnsd is a small authoritate-only DNS nameserver designed to serve DNS-based blocklists (DNSBLs). It may handle IP-based and name-based blocklists. rbldnsd-0.997a/debian/postinst0000664000175000017500000000262712120257742014544 0ustar mjtmjt#! /bin/sh # postinst script for rbldnsd # # see: dh_installdeb(1) set -e # summary of how this script can be called: # * `configure' # * `abort-upgrade' # * `abort-remove' `in-favour' # # * `abort-deconfigure' `in-favour' # `removing' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package # # quoting from the policy: # Any necessary prompting should almost always be confined to the # post-installation script, and should be protected with a conditional # so that unnecessary prompting doesn't happen if a package's # installation fails and the `postinst' is called with `abort-upgrade', # `abort-remove' or `abort-deconfigure'. case "$1" in configure) if ! getent passwd rbldns >/dev/null; then adduser --system --group --home /var/lib/rbldns --no-create-home rbldns mkdir -p -m0755 /var/lib/rbldns fi ;; abort-upgrade|abort-remove|abort-deconfigure) ;; *) echo "postinst called with unknown argument \`$1'" >&2 exit 1 ;; esac # dh_installdeb will replace this with shell code automatically # generated by other debhelper scripts. #DEBHELPER# rbldnsd-0.997a/debian/rbldnsd.default0000644000175000017500000000261611534654105015732 0ustar mjtmjt# /etc/default/rbldnsd # This file should set one variable, RBLDNSD, to be a multiline # list of all instances of rbldnsd to start. Every line in that # list consist of a key (basename for a pid file), and rbldnsd # command line, e.g.: # # RBLDNSD="dsbl -r/var/lib/rbldns/dsbl -b127.2 list.dsbl.org:ip4set:list" # # or, using multiple lines and line continuations: # # RBLDNSD="dsbl -r/var/lib/rbldns/dsbl -b 127.2 \ # list.dsbl.org:ip4set:list \ # multihop.dsbl.org:ip4set:multihop \ # unconfirmed.dsbl.org:ip4set:unconfirmed \ # # local -r/var/lib/rbldns/local -b 127.3 \ # dialups.bl.example.com:ip4set:dialups \ # spews.bl.example.com:ip4set:spews \ # inputs.bl.example.com:ip4set:inputs \ # bl.example.com:ip4set:dialups \ # bl.example.com:ip4set:spews \ # bl.example.com:ip4set:inputs \ # # " # # This is the recommended way to keep entries readable and # easily editable. # # the first word, key, will be used to form pid file name, like # /var/run/rbldnsd-dsbl.pid, /var/run/rbldnsd-local.pid etc. # So, all keys should be unique. This is done in order to support # several instances of rbldnsd, if that'll be required. In a # simple case, when only one instance is needed, key may be # specified as a single dash, -, and in this case pid file # will be /var/run/rbldnsd.pid : # # RBLDNSD="- -r/var/lib/rbldns -b127.2 \ # zone list...\ # " # # See rbldnsd(8) for descriptions of options. # rbldnsd-0.997a/debian/rbldnsd.init0000664000175000017500000000360212120257742015245 0ustar mjtmjt#! /bin/sh # rbldnsd startup script. # ### BEGIN INIT INFO # Provides: rbldnsd # Required-Start: $network $remote_fs $syslog # Required-Stop: $network $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 ### END INIT INFO # # chkconfig: 345 80 30 # description: rbldnsd is a DNS daemon for DNSBLs. Configure it in \ # /etc/default/rbldnsd or /etc/sysconfig/rbldnsd PATH=/sbin:/bin:/usr/sbin:/usr/bin NAME=rbldnsd DESC=$NAME DAEMON=/usr/sbin/$NAME test -f $DAEMON || exit 0 set -e RBLDNSD= if [ -f /etc/default/$NAME ] ; then . /etc/default/$NAME elif [ -f /etc/sysconfig/$NAME ]; then . /etc/sysconfig/$NAME else exit 0 fi test -n "$RBLDNSD" || exit 0 forall() { echo "$RBLDNSD" | while read name args; do case "$name" in ""|\#*) continue;; -) name=$NAME; pidfile=/var/run/$name.pid;; *) pidfile=/var/run/rbldnsd-$name.pid;; esac pid= if [ -f $pidfile ]; then read p < $pidfile if [ -n "$p" -a -f /proc/$p/cmdline ]; then case "`cat /proc/$p/cmdline 2>/dev/null`" in *$NAME*) pid=$p;; esac fi fi $1 done } report() { echo "$1 $DESC: $name" } runit() { $DAEMON -p $pidfile $args } start() { if [ ! "$pid" ]; then report Starting runit fi } stop() { if [ "$pid" ]; then report Stopping kill $pid rm -f $pidfile fi } restart() { if [ "$pid" ]; then report Restarting kill $pid sleep 1 runit else start fi } reload() { if [ "$pid" ]; then report Reloading kill -HUP $pid fi } case "$1" in start|restart) forall $1 if [ -d /var/lock/subsys ] ; then touch /var/lock/subsys/$NAME; fi ;; stop) forall $1 rm -f /var/lock/subsys/$NAME ;; reload|force-reload) forall reload ;; *) N=/etc/init.d/$NAME echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2 exit 1 ;; esac exit 0