pax_global_header00006660000000000000000000000064147600104350014512gustar00rootroot0000000000000052 comment=bd889b4ad16e9d2224b7cbb99dad9b88a28ace62 blockdomains-0.1/000077500000000000000000000000001476001043500140175ustar00rootroot00000000000000blockdomains-0.1/.gitweb000066400000000000000000000001331476001043500152760ustar00rootroot00000000000000description = Firewall agent blocking selected HTTP and SSL connections category = goodies blockdomains-0.1/Makefile000066400000000000000000000031731476001043500154630ustar00rootroot00000000000000# Distribution Makefile for blockdomains # Generated files SBINFILES = blockdomains BINFILES = blockdomainsctl MANFILES = blockdomains.5 blockdomains.8 blockdomainsctl.8 CFGDIRS = blocked/ acl/ default: $(SBINFILES) $(MANFILES) .PHONY: install clean # Building targets $(MANFILES): %: doc/%.adoc asciidoctor -bmanpage -o $@ $< README.html: README.adoc asciidoctor -bhtml $< blockdomains: CPPFLAGS = $(shell dpkg-buildflags --get CPPFLAGS) blockdomains: CFLAGS = $(shell dpkg-buildflags --get CFLAGS) blockdomains: LDFLAGS = $(shell dpkg-buildflags --get LDFLAGS) blockdomains: CFLAGS += -Wall blockdomains: src/blockdomains.c src/cache.c src/database.c $(LINK.c) -o $@ $^ -lnetfilter_queue # Installation SBINDIR = $(DESTDIR)/usr/sbin BINDIR = $(DESTDIR)/usr/bin CFGTOP = $(DESTDIR)/etc/blockdomains MAN5DIR = $(DESTDIR)/usr/share/man/man5 MAN8DIR = $(DESTDIR)/usr/share/man/man8 SYSVINIT = $(DESTDIR)/etc/init.d/blockdomains SYSTEMDINIT = $(DESTDIR)/usr/lib/systemd/system/blockdomains.service install: $(addprefix $(SBINDIR)/,$(SBINFILES)) install: $(addprefix $(BINDIR)/,$(BINFILES)) install: $(addprefix $(MAN5DIR)/,$(filter %.5,$(MANFILES))) install: $(addprefix $(MAN8DIR)/,$(filter %.8,$(MANFILES))) install: $(addprefix $(CFGTOP)/,$(CFGDIRS)) install: $(SYSVINIT) $(SYSTEMDINIT) $(SYSVINIT): init/blockdomains install -D $< $@ $(SYSTEMDINIT): init/blockdomains.service install -m 644 -D $< $@ $(CFGTOP)/%/: mkdir -p $@ $(SBINDIR)/%: % install -D $< $@ $(BINDIR)/%: % install -D $< $@ $(MAN5DIR)/%: % install -m 644 -D $< $@ $(MAN8DIR)/%: % install -m 644 -D $< $@ # Cleaning up clean: rm -f $(SBINFILES) $(MANFILES) blockdomains-0.1/README.adoc000066400000000000000000000055421476001043500156120ustar00rootroot00000000000000= Block list based domain name filtering The `blockdomains` utility is a block list based network traffic filter for `iptables` via `libnetfilter-queue`. It applies to HTTP and SSL traffic for recognizing and dropping packets that are directed to listed domain names. == Dendencies Operationally `blockdomains` depends on the `libnetfilter-queue-dev` and `iptables` packages, and for building, you'll also need a C build environment including `make`. == Build and Install === Custom build and install The `Makefile` offers an `install` target that makes use of a `DESTDIR` variable to nominate a root directory for installation. The prerequies for this are a gcc build environment, and that the `libnetfilter_queue` library is duly installed. .Illustration of custom build and install ---- $ mkdir here $ make install DESTDIR=here ---- NOTE: The control and init scripts assume it installed at system root. They need to be edited for other installation choices. === Build debian package This project has a `debian` branch that is set up for building a debian package in a debian build environment. The prerequesites are obtained by installing the debian package `git-buildpackage`. .Illustration of debian package build ---- $ git checkout debian $ gbp buildpackage --git-export-dir=build ---- When the illustration build succeeds, it results in a `.deb` file in the newly created `build` directory, and there are also some other useful files == Setup and Confguration The blockdomains utility uses block list files that nominate the domains to block. Control scripting supports the notion of a configuration root directory `/etc/blockdomains/` with two sub direcotries `acl/` and `blocked/`. The idea is that `acl/` contains all the block list files and that `blocked/` is set up with links into `acl/` files for the __enabled__ block list files. Thus, you enable a block list `acl/foo.acl` by setting a link ---- /etc/blockdomains/blocked -> ../acl/foo.acl ---- == Running The `blockdomains` utility has control scripting to either operate it manually via the `blockdomainsctl` script, or as a system service via the `init/blockdomains` service control script. == Technical Detail The filtering uses the given lists of domain names for rejecting packets. It inspects all packets so as to recognize HTTP message headers and SSL certificate requests and then pick out the targeted domain name. If that name is listed or a sub domain of a listed domain, then the packet is rejected. Doing so "disturbs" the communication with the domain to effectively block it. The filtering has decision cache so that subsequent decisions for the same target can be made quickly. NOTE: It is technically possible but highly unlikely to run into 'false positives' in the filtering. The result would then be an unexpected interruption and break of a TCP communication. == AUTHOR Ralph Ronnquist blockdomains-0.1/blockdomainsctl000077500000000000000000000006461476001043500171230ustar00rootroot00000000000000#!/bin/sh # # Control script for manual use of blockdomains. do_start() { iptables -I OUTPUT -p tcp -j NFQUEUE --queue-num 99 blockdomains /etc/blockdomains/blocked/*.acl & } do_stop() { iptables -D OUTPUT -p tcp -j NFQUEUE --queue-num 99 pkill blockdomains } case "$1" in start) do_start ;; reload) do_stop ; do_start ;; stop) do_stop ;; *) echo "Use start, stop or reload" >&2 ;; esac blockdomains-0.1/doc/000077500000000000000000000000001476001043500145645ustar00rootroot00000000000000blockdomains-0.1/doc/blockdomains.5.adoc000066400000000000000000000021141476001043500202220ustar00rootroot00000000000000= blockdomains(5) == NAME blockdomains - block list file format == SYNOPSIS /etc/blockdomains/acl/blocklist.acl ln -s ../acl/blocklist.acl /etc/blockdomains/blocked/ == DESCRIPTION **blockdomains** uses one or more block list files which contain declaratios of the domains to block, one domain per line that starts with any number of whitespace characters followed by a period (".") before the domain to block. The blocking applies to the domain and all its sub domains. ==== Anything not starting with a period (".") is a comment and leading whitespace is ignored. Block list domains start with optional whitespace and a period, followed by the domain name to block, optionally followed by a whitespace and a comment. Like the following: ==== .Example of block list ---- .bad.domain.com -- domain name up to whitespace is blocked Blank lines are fine too; they treated as comments. The block list domains don't need to be column aligned. So, here is another: .another.domain.to.block End of block list example. ---- == SEE ALSO blockdomains(8) == AUTHOR Ralph Ronnquist blockdomains-0.1/doc/blockdomains.8.adoc000066400000000000000000000007161476001043500202330ustar00rootroot00000000000000= blockdomains(8) == NAME blockdomains - Firewall agent blocking selected HTTP and HTTPS connections == SYNOPSIS blockdomains __blocklistfile__+ == DESCRIPTION The **blockdomains** utility is a block list based network traffic filter for iptables via libnetfilter-queue. It applies to HTTP and SSL traffic for recognizing and dropping packets that are directed to listed domain names. == SEE ALSO blockdomains(5) == AUTHOR Ralph Ronnquist blockdomains-0.1/doc/blockdomainsctl.8.adoc000066400000000000000000000004521476001043500207330ustar00rootroot00000000000000= blockdomainsctl(8) == NAME blockdomainsctl - utility for manual start/reload/stop of blockdomains == SYNOPSIS blockdomainsctl __action__ == DESCRIPTION blockdomainsctl is a utility for manual operation of blockdomains. == SEE ALSO blockdomains(8) == AUTHOR Ralph Ronnquist blockdomains-0.1/doc/ssl.txt000066400000000000000000000042431476001043500161310ustar00rootroot00000000000000const unsigned char good_data_2[] = { // TLS record 0x16, // [0] Content Type: Handshake 0x03, 0x01, // [1,2] Version: TLS 1.0 0x00, 0x6c, // [3,4] Length (use for bounds checking) // Handshake 0x01, // [5] Handshake Type: Client Hello 0x00, 0x00, 0x68, // [6,7,8] Length (use for bounds checking) 0x03, 0x03, // [9,10] Version: TLS 1.2 // [11,,42] Random (32 bytes fixed length) 0xb6, 0xb2, 0x6a, 0xfb, 0x55, 0x5e, 0x03, 0xd5, 0x65, 0xa3, 0x6a, 0xf0, 0x5e, 0xa5, 0x43, 0x02, 0x93, 0xb9, 0x59, 0xa7, 0x54, 0xc3, 0xdd, 0x78, 0x57, 0x58, 0x34, 0xc5, 0x82, 0xfd, 0x53, 0xd1, 0x00, // [43] Session ID Length (skip past this much) 0x00, 0x04, // [44,45] Cipher Suites Length (skip past this much) 0x00, 0x01, // NULL-MD5 0x00, 0xff, // RENEGOTIATION INFO SCSV 0x01, // Compression Methods Length (skip past this much) 0x00, // NULL 0x00, 0x3b, // Extensions Length (use for bounds checking) // Extension 0x00, 0x00, // Extension Type: Server Name (check extension type) 0x00, 0x0e, // Length (use for bounds checking) 0x00, 0x0c, // Server Name Indication Length 0x00, // Server Name Type: host_name (check server name type) 0x00, 0x09, // Length (length of your data) // "localhost" (data your after) 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, // Extension 0x00, 0x0d, // Extension Type: Signature Algorithms (check extension type) 0x00, 0x20, // Length (skip past since this is the wrong extension) // Data 0x00, 0x1e, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05, 0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03, // Extension 0x00, 0x0f, // Extension Type: Heart Beat (check extension type) 0x00, 0x01, // Length (skip past since this is the wrong extension) 0x01 // Mode: Peer allows to send requests }; blockdomains-0.1/init/000077500000000000000000000000001476001043500147625ustar00rootroot00000000000000blockdomains-0.1/init/blockdomains000077500000000000000000000013531476001043500173570ustar00rootroot00000000000000#!/lib/init/init-d-script ### BEGIN INIT INFO # Provides: blockdomains # Required-Start: mountkernfs $local_fs # X-Start-Before: $network # Required-Stop: mountkernfs $local_fs # Default-Start: S # Default-Stop: 0 6 # Short-Description: Block selected HTTP and HTTPS connections # Description: Firewall agent that blocks outbound connections # for selected domains ### END INIT INFO DAEMON=/usr/sbin/blockdomains DAEMON_ARGS="$(ls /etc/blockdomains/blocked/*.acl 2>/dev/null)" START_ARGS="-b -O /var/log/blockdomains.log" PIDFILE=none do_start_prepare() { iptables -I OUTPUT -p tcp -j NFQUEUE --queue-num 99 } do_stop_prepare() { iptables -D OUTPUT -p tcp -j NFQUEUE --queue-num 99 } blockdomains-0.1/init/blockdomains.service000066400000000000000000000003671476001043500210170ustar00rootroot00000000000000[Unit] Description=Firewall agent that blocks outbound selected domains connections Documentation=man:blockdomains(8) [Service] ExecStart=/usr/bin/blockdomainsctl start ExecStop=/usr/bin/blockdomainsctl stop [Install] WantedBy=multi-user.target blockdomains-0.1/src/000077500000000000000000000000001476001043500146065ustar00rootroot00000000000000blockdomains-0.1/src/blockdomains.c000066400000000000000000000216331476001043500174240ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include /* for NF_ACCEPT */ #include /** * This file implements the overall program logic by setting up a * netfilter queue callback for analysing packets and make an ACCEPT * or REJECT verdict for them. */ // API for caching of verdicts unsigned int lookup_cache(unsigned char *domain); void add_cache(unsigned char *domain,unsigned int ix); int hash_code(unsigned char *domain); // API for BAD domains database unsigned int check_domain(unsigned char *domain); void load_domains(char *file); void start_domain_database_loading(void); void end_domain_database_loading(void); /** * Return packet id, or 0 on error. */ static u_int32_t get_packet_id(struct nfq_data *tb) { struct nfqnl_msg_packet_hdr *ph = nfq_get_msg_packet_hdr( tb ); return ( ph )? ntohl( ph->packet_id ) : 0; } /** * Packet header if ipv4 packet. */ struct ipv4_pkt { struct ip first; // .ip_dst[4 bytes] struct tcphdr second; }; /** * Packet header if ipv6 packet. */ struct ipv6_pkt { struct ip6_hdr first; // .ip6_dst[16 bytes] struct tcphdr second; }; /** * Convenience type for network packets of undetermined family. */ struct packet { union { struct ipv4_pkt pkt4; struct ipv6_pkt pkt6; } p; }; /** * Locates the header of a packet byte blob. */ static struct packet *get_headerP(unsigned char *data) { return (struct packet *) data; } static const char *tell_ip(struct packet *ip) { static char THEIP[200]; switch ( ip->p.pkt4.first.ip_v ) { case 4: return inet_ntop( AF_INET, &ip->p.pkt4.first.ip_dst, THEIP, 200 ); case 6: return inet_ntop( AF_INET6, &ip->p.pkt6.first.ip6_dst, THEIP, 200 ); } snprintf( THEIP, 200, "%d ???", ip->p.pkt4.first.ip_v ); return THEIP; } /** * Review payload packet payload */ static void view_payload(unsigned char *data,int length) { struct packet *header = get_headerP( data ); u_int16_t port = 0; u_int8_t syn = 0; unsigned char *body = data ;//+ sizeof( struct packet ); switch ( header->p.pkt4.first.ip_v ) { case 4: port = ntohs( ((struct ipv4_pkt *) data )->second.th_dport ); syn = sizeof( struct ipv4_pkt ); break; case 6: port = ntohs( ((struct ipv6_pkt *) data )->second.th_dport ); syn = sizeof( struct ipv6_pkt ); break; } #define END 400 unsigned char * end = body + ( ( length > END )? END : length ); fprintf( stderr, "%s %d %d %d ", tell_ip( header ), syn, port, length ); while ( body < end ) { unsigned char c = *body++; if ( c < ' ' || c >= 127 || 1 ) { fprintf( stderr, "%02x ", c ); } else { fprintf( stderr, "%c", c ); } } fprintf( stderr, "\n" ); } ////////////////// static unsigned char buffer[1000]; /** * SSL traffic includes a data packet with a clear text host name. * This is knwon as the SNI extension. */ static unsigned char *ssl_host(unsigned char *data,int length) { // Check that it's a "Client Hello" message unsigned char *p; switch ( ((struct packet *) data)->p.pkt4.first.ip_v ) { case 4: p = data + sizeof( struct ipv4_pkt ) + 12; //?? break; case 6: p = data + sizeof( struct ipv6_pkt ) + 0; //?? break; default: return 0; } if ( p[0] != 0x16 || p[1] != 0x03 || p[5] != 0x01 || p[6] != 0x00 ) { return 0; } fprintf( stderr, "Client Hello\n" ); // Note minor version p[2] is not checked // record_length = 256 * p[3] + p[4] // handshake_message_length = 256 * p[7] + p[8] if ( p[9] != 0x03 || p[10] != 0x03 ) { // TLS 1.2 (?ralph?) return 0; } fprintf( stderr, "TLS 1.2\n" ); unsigned int i = 46 + ( 256 * p[44] ) + p[45]; i += p[i] + 1; unsigned int extensions_length = ( 256 * p[i] ) + p[i+1]; i += 2; int k = 0; fprintf( stderr, "TLS 1.2 %d %d\n", i, extensions_length ); while ( k < extensions_length ) { unsigned int type = ( 256 * p[i+k] ) + p[i+k+1]; k += 2; unsigned int length = ( 256 * p[i+k] ) + p[i+k+1]; k += 2; fprintf( stderr, "Extension %d %d\n", k-4, type ); if ( type == 0 ) { // Server Name if ( p[i+k+2] ) { break; // Name badness } unsigned int name_length = ( 256 * p[i+k+3] ) + p[i+k+4]; unsigned char *path = &p[i+k+5]; memcpy( buffer, path, name_length ); buffer[ name_length ] = '\0'; return buffer; } k += length; } // This point is only reached on "missing or bad SNI". view_payload( data, length ); return 0; } /** * HTTP traffic includes a data packet with the host name as a * "Host:" attribute. */ static unsigned char *http_host(unsigned char *data,int length) { unsigned char *body = data + sizeof( struct packet ); switch ( ((struct packet *) data)->p.pkt4.first.ip_v ) { case 4: body = data + sizeof( struct ipv4_pkt ); break; case 6: body = data + sizeof( struct ipv6_pkt ); break; default: return 0; } if ( ( strncmp( (char*) body, "GET ", 4 ) != 0 ) && ( strncmp( (char*) body, "POST ", 5 ) != 0 ) ) { return 0; } unsigned char *end = data + length - 6; int check = 0; for ( ; body < end; body++ ) { if ( check ) { if ( strncmp( (char*) body, "Host:", 5 ) == 0 ) { body += 5; for( ; body < end; body++ ) if ( *body != ' ' ) break; unsigned char *start = body; int n = 0; for( ; body < end; n++, body++ ) if ( *body <= ' ' ) break; if ( n < 5 ) { return 0; } memcpy( buffer, start, n ); buffer[ n ] = '\0'; return buffer; } if ( strncmp( (char*) body, "\r\n", 2 ) == 0 ) { return 0; } for( ; body < end; body++ ) if ( *body == '\n' ) break; if ( body >= end ) { return 0; } } check = ( *body == '\n' ); } return 0; } /** * Callback function to handle a packet. */ static int cb( struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *code ) { u_int32_t id = get_packet_id( nfa ); unsigned char *data; int length = nfq_get_payload( nfa, &data); int verdict = NF_ACCEPT; struct packet *header = get_headerP( data ); if ( length >= 100 ) { unsigned char *host = http_host( data, length ); if ( host == 0 ) { host = ssl_host( data, length ); } if ( host ) { int i = lookup_cache( host ); if ( i < 0 ) { unsigned int ix = check_domain( host ); add_cache( host, ix ); if ( ix > 0 ) { // Notify "new" domain blocking fprintf( stderr, "%d: block %s at %s by %d\n", hash_code( host ), host, tell_ip( header ), ix ); verdict = NF_DROP; } } else if ( i > 0 ) { verdict = NF_DROP; } } } return nfq_set_verdict(qh, id, verdict, 0, NULL); } /** * Program main function. Load block lists, register netfilter * calllback and go for it. */ int main(int argc, char **argv) { // Load the database start_domain_database_loading(); int n = 1; for ( ; n < argc; n++ ) { fprintf( stderr, "blockdomains loads block list %s\n", argv[ n ] ); load_domains( argv[ n ] ); } end_domain_database_loading(); struct nfq_handle *h; struct nfq_q_handle *qh; //struct nfnl_handle *nh; int fd; int rv; char buf[4096] __attribute__ ((aligned)); fprintf( stderr, "blockdomains opens library handle\n"); h = nfq_open(); if ( !h ) { fprintf(stderr, "blockdomains error during nfq_open()\n"); exit(1); } fprintf( stderr, "blockdomains unbinds any existing nf_queue handler\n" ); if ( nfq_unbind_pf(h, AF_INET) < 0 ) { fprintf(stderr, "error during nfq_unbind_pf()\n"); exit(1); } fprintf( stderr, "blockdomains binds as nf_queue handler\n" ); if ( nfq_bind_pf(h, AF_INET) < 0 ) { fprintf(stderr, "error during nfq_bind_pf()\n"); exit(1); } #define THEQUEUE 99 fprintf( stderr, "blockdomains registers to queue '%d'\n", THEQUEUE ); qh = nfq_create_queue( h, THEQUEUE, &cb, NULL ); if ( !qh ) { fprintf(stderr, "blockdomains error during nfq_create_queue()\n"); exit(1); } fprintf( stderr, "blockdomains setting copy_packet mode\n" ); if ( nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff ) < 0) { fprintf(stderr, "blockdomains can't set packet_copy mode\n"); exit(1); } fd = nfq_fd( h ); while ( ( rv = recv(fd, buf, sizeof(buf), 0) ) && rv >= 0 ) { //printf( "pkt received\n" ); nfq_handle_packet(h, buf, rv); } fprintf( stderr, "blockdomains unbinding from queue %d\n", THEQUEUE); nfq_destroy_queue(qh); #ifdef INSANE /* normally, applications SHOULD NOT issue this command, since it detaches other programs/sockets from AF_INET, too ! */ fprintf( stderr, "blockdomains unbinding from AF_INET\n"); nfq_unbind_pf(h, AF_INET); #endif fprintf( stderr, "blockdomains closing library handle\n"); nfq_close( h ); exit( 0 ); } blockdomains-0.1/src/cache.c000066400000000000000000000033351476001043500160210ustar00rootroot00000000000000#include #include #include /** * This file implements a "cache" of "known" domains together with * their verdicts. */ /** * This is the CacheEntry type of a malloc-ed cstring domain and a * verdict. */ typedef struct _CacheEntry { unsigned char *domain; unsigned int ix; } CacheEntry; /** * This is the cache root structure as apointer to the CacheEntry * array and its size. */ struct { CacheEntry *table; int size; } cache; /** * This computes a hash code for a domain which is its cache index. * This hashing simply adds the domain characters modulo cache table * size. */ int hash_code(unsigned char *domain) { int i = 0; for ( ; *domain; domain++ ) { i += *domain; } return i % cache.size; } /** * Look up a domain in the cache and return its cached verdict, or -1 * of not present. */ int lookup_cache(unsigned char *domain) { if ( cache.table ) { int i = hash_code( domain ); if ( cache.table[i].domain && strcmp( (char*) domain, (char*) cache.table[i].domain ) == 0 ) { return cache.table[i].ix; } } return -1; } /** * Add a domain and verdict into the cache. The domain hash code is * used as index into the table, to replace any prior CacheEntry * setting. The domain is a NUL terminated string that is copied into * the cache. */ void add_cache(unsigned char *domain,unsigned int ix) { if ( cache.table == 0 ) { cache.size = 1024; cache.table = (CacheEntry*) calloc( cache.size, sizeof( CacheEntry ) ); } int i = hash_code( domain ); if ( cache.table[i].domain ) { free( cache.table[i].domain ); } cache.table[i].domain = (unsigned char*) strdup( (char*) domain ); cache.table[i].ix = ix; } blockdomains-0.1/src/database.c000066400000000000000000000143671476001043500165310ustar00rootroot00000000000000#include #include #include #include #include #include #include /** * This file implements a "database" of "bad" domains, loaded from * ".acl" files of a fairly strict format; each domain to block is * written on a line starting with a period, immediately followed by * the domain to block, then an optional comment. * * The database is populated by using the call sequence: * 1. start_domain_database_loading(); * 2. load_domains( filename ); // repeated * N. end_domain_database_loading(); * * The final call triggers a reordering of domains so as to support * binary search in reverse text order, for matching domain suffixes. * See the function `tail_compare` for details. */ /** * This is the Entry type for the "database", which basically is an * array of these. The domain pointer will point at a domain name in * the loaded ".acl" file, and length is the domain name length. */ typedef struct _Entry { int length; unsigned char *domain; } Entry; /** * This is the domain name database root structure. It holds a pointer * to the array of Entry records, the fill of that array, and the * allocated size for that array (no lesser than the fill, of course). */ static struct { Entry *table; int fill; int size; } database = { 0, 0, 0 }; /** * This function compares strings backwars; the last k bytes of string * (a,na) versus string (b,nb). It also holds '.' as the least of * characters, so as to ensure that refined/extended domain names are * comparatively greater that their base domain names. */ static int tail_compare(unsigned char *a,unsigned char *b,int k) { while ( k-- > 0 ) { int c = *(--a) - *(--b); if ( c != 0) { if ( *a == '.' ) { return -1; } if ( *b == '.' ) { return 1; } return c; } } return 0; } /** * Extend the domain name table to allow additions. */ #define STARTSIZE 100000 static void grow() { if ( database.table ) { Entry *old = database.table; int s = database.size; database.size += 100000; database.table = (Entry*) calloc( database.size, sizeof( Entry ) ); memcpy( database.table, old, s * sizeof( Entry ) ); free( old ); } else { database.table = (Entry*) calloc( STARTSIZE, sizeof( Entry ) ); database.size = STARTSIZE; } } /** * Determine the index for given domain. This matches computes a tail * match between the given domain and the databse domains, returning * the index for the matching database entry, or (-index-1) to * indicate insertion point. In lookup mode, a database entry being a * tail domain part of the given domain is also considered a match. */ static int index_domain(unsigned char *domain,int n,int lookup) { int lo = 0; int hi = database.fill; while ( lo < hi ) { int m = ( lo + hi ) / 2; Entry *p = &database.table[ m ]; int k = p->length; if ( n < k ) { k = n; } int q = tail_compare( p->domain + p->length, domain + n, k ); #if 0 fprintf( stderr, "%s %d %d %d\n", domain, k, m, q ); #endif if ( q == 0 ) { if ( p->length < n ) { // table entry shorter => new entry after, or match on lookup if ( lookup && *(domain+n-k-1) == '.' ) { return m; } lo = m + 1; } else if ( p->length > n ) { // table entry longer => new entry before hi = m; } else { // equal return m; } } else if ( q < 0 ) { // new entry after lo = m + 1; } else { // new entry before hi = m; } } return -lo - 1; } /** * Determine the length of a "word" */ static int wordlen(unsigned char *p) { unsigned char *q = p; while ( *q > ' ' ) { q++; } return q - p; } /** * Add an Entry for a given domain string and length. */ static void fast_add_domain(unsigned char *domain,int length) { int fill = database.fill; if ( fill >= database.size ) { grow(); } database.table[ fill ].length = length; database.table[ fill ].domain = domain; database.fill++; } /** * Return a marker of the sort order of two given Entry records, with * ab by * (non-zero) positive integer. */ static int table_order(Entry *a,Entry *b) { int k = ( a->length < b->length )? a->length : b->length; int c = tail_compare( a->domain + a->length, b->domain + b->length, k ); if ( c != 0 ) { return c; } return a->length - b->length; } /** * External call to check whether a given domain is in the database, * returning 0 if not and non-zero if the domain is present. */ unsigned int check_domain(unsigned char *domain) { int i = index_domain( domain, wordlen( domain ), 1 ); return ( i < 0 )? 0 : ( i + 1 ); } /** * This function doesn't do anything at all but is present for the * sake of the ABI. */ void start_domain_database_loading(void) { } /** * This function sorts the database to support the lookup by binary * search. */ void end_domain_database_loading(void) { qsort( database.table, database.fill, sizeof( Entry ), (__compar_fn_t) table_order ); } /** * Load BAD domain names from file. The file is line based where data * lines consist of domain name starting with period and ending with * space or newline, and other lines ignored. */ void load_domains(char *file) { struct stat info; unsigned char *data; //fprintf( stderr, "state(\"%s\",&info)\n", file ); if ( stat( file, &info ) ) { perror( file ); exit( 1 ); } int n = info.st_size; data = (unsigned char *) malloc( n ); //fprintf( stderr, "open(\"%s\",)\n", file ); int fd = open( file, O_RDONLY ); if ( fd < 0 ) { perror( file ); exit( 1 ); } //fprintf( stderr, "Loading %s\n", file ); unsigned char *end = data; while ( n > 0 ) { int k = read( fd, end, n ); if ( k == 0 ) { fprintf( stderr, "Premature EOF for %s\n", file ); exit( 1 ); } end += k; n -= k; } //fprintf( stderr, "processing %s %p %p\n", file, data, end ); unsigned char *p = data; while( p < end ) { // Consume a line if ( *p <= ' ' ) { p++; continue; } if ( *p == '.' ) { unsigned char *domain = ++p; while ( p < end && *p > ' ' ) { p++; } fast_add_domain( domain, p - domain ); } while ( p < end && *p != '\n' ) { p++; } p++; } close( fd ); }