Mercurial > dnsbl
diff src/dnsbl.cpp @ 0:96a9758165cd original
Initial revision
author | carl |
---|---|
date | Tue, 20 Apr 2004 20:02:29 -0700 |
parents | |
children | bd0b1a153f67 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dnsbl.cpp Tue Apr 20 20:02:29 2004 -0700 @@ -0,0 +1,823 @@ +/* + +Copyright (c) 2004 Carl Byington - 510 Software Group, released under +the GPL version 2 or any later version at your choice available at +http://www.fsf.org/licenses/gpl.txt + +Based on a sample milter Copyright (c) 2000-2003 Sendmail, Inc. and its +suppliers. Inspired by the DCC by Rhyolite Software + +-p port The port through which the MTA will connect to this milter. +-t sec The timeout value. + +TODO: + +1) Add "include-dcc NAME fn" to read a dcc whiteclnt file looking +for many substitute mail-host domain, and add the equivalent "env_from +domain black" into the NAME mapping. That allows clients to use just the +DCC for white/blacklisting, but the backup mx machines can use dnsbl +and get the same effect. + +*/ + + +// from sendmail sample +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +// needed for socket io +#include <sys/ioctl.h> +#include <net/if.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <netdb.h> +#include <sys/socket.h> + +// needed for thread +#include <pthread.h> + +// needed for std c++ collections +#include <set> +#include <map> +#include <list> + +// for the dns resolver +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <resolv.h> + +// misc stuff needed here +#include <ctype.h> +#include <fstream> +#include <syslog.h> + + +using namespace std; + + +extern "C" { + #include "libmilter/mfapi.h" + sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr); + sfsistat mlfi_envfrom(SMFICTX *ctx, char **argv); + sfsistat mlfi_envrcpt(SMFICTX *ctx, char **argv); + sfsistat mlfi_eom_or_abort(SMFICTX *ctx); + sfsistat mlfi_close(SMFICTX *ctx); +} + +#ifndef bool +# define bool int +# define TRUE 1 +# define FALSE 0 +#endif /* ! bool */ + +struct ltstr { + bool operator()(char* s1, char* s2) const { + return strcmp(s1, s2) < 0; + } +}; + +struct DNSBL { + char *suffix; // blacklist suffix like blackholes.five-ten-sg.com + char *message; // error message with one or two %s operators for the ip address replacement + DNSBL(char *s, char *m); +}; +DNSBL::DNSBL(char *s, char *m) { + suffix = s; + message = m; +} + +typedef DNSBL * DNSBLP; +typedef list<DNSBLP> DNSBLL; +typedef DNSBLL * DNSBLLP; +typedef map<char *, char *, ltstr> string_map; +typedef map<char *, string_map *, ltstr> from_map; +typedef map<char *, DNSBLP, ltstr> dnsblp_map; +typedef map<char *, DNSBLLP, ltstr> dnsbllp_map; +typedef set<char *, ltstr> string_set; +typedef list<char *> string_list; + +struct CONFIG { + // the only mutable stuff once it has been loaded from the config file + int reference_count; // protected by the global config_mutex + // all the rest is constant after loading from the config file + time_t load_time; + string_list config_files; + dnsblp_map dnsbls; + dnsbllp_map dnsblls; + from_map env_from; + string_map env_to_dnsbll; // map recipient to a named dnsbll + string_map env_to_chkfrom; // map recipient to a named from map + CONFIG(); + ~CONFIG(); +}; +CONFIG::CONFIG() { + reference_count = 0; + load_time = 0; +} +CONFIG::~CONFIG() { + for (dnsblp_map::iterator i=dnsbls.begin(); i!=dnsbls.end(); i++) { + DNSBLP d = (*i).second; + delete d; + } + for (dnsbllp_map::iterator i=dnsblls.begin(); i!=dnsblls.end(); i++) { + DNSBLLP d = (*i).second; + delete d; + } + for (from_map::iterator i=env_from.begin(); i!=env_from.end(); i++) { + string_map *d = (*i).second; + delete d; + } +} + +static string_set all_strings; // owns all the strings, only modified by the config loader thread +static CONFIG * config = NULL; // protected by the config_mutex + +static pthread_mutex_t config_mutex; +static pthread_mutex_t syslog_mutex; +static pthread_mutex_t resolve_mutex; + + + +//////////////////////////////////////////////// +// predefined names +// +#define DEFAULT "default" +#define WHITE "white" +#define BLACK "black" + + +//////////////////////////////////////////////// +// mail filter private data, held for us by sendmail +// +enum status {oksofar, // not rejected yet + white, // whitelisted by envelope from + black, // blacklisted by envelope from or to + reject}; // rejected by a dns list +struct mlfiPriv +{ + CONFIG *pc; // global context with our maps + int ip; // ip4 address of the smtp client + char *mailaddr; // envelope from value + bool authenticated; // client authenticated? if so, suppress all dnsbl checks + map<DNSBLP, status> checked; // status from those lists + mlfiPriv(); + ~mlfiPriv(); +}; +mlfiPriv::mlfiPriv() { + pthread_mutex_lock(&config_mutex); + pc = config; + pc->reference_count++; + pthread_mutex_unlock(&config_mutex); + ip = 0; + mailaddr = NULL; +} +mlfiPriv::~mlfiPriv() { + pthread_mutex_lock(&config_mutex); + pc->reference_count--; + pthread_mutex_unlock(&config_mutex); + if (mailaddr) free(mailaddr); +} + +#define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx)) + + +//////////////////////////////////////////////// +// syslog a message +// +static void my_syslog(char *text); +static void my_syslog(char *text) { + pthread_mutex_lock(&syslog_mutex); + openlog("dnsbl", LOG_PID, LOG_MAIL); + syslog(LOG_NOTICE, "%s", text); + closelog(); + pthread_mutex_unlock(&syslog_mutex); +} + + +//////////////////////////////////////////////// +// register a global string +// +static char* register_string(char *name); +static char* register_string(char *name) { + string_set::iterator i = all_strings.find(name); + if (i != all_strings.end()) return *i; + char *x = strdup(name); + all_strings.insert(x); + return x; +} + + +static char* next_token(char *delim); +static char* next_token(char *delim) { + char *name = strtok(NULL, delim); + if (!name) return name; + return register_string(name); +} + + +//////////////////////////////////////////////// +// lookup an email address in the env_from or env_to maps +// +static char* lookup1(char *email, string_map map); +static char* lookup1(char *email, string_map map) { + string_map::iterator i = map.find(email); + if (i != map.end()) return (*i).second; + char *x = strchr(email, '@'); + if (!x) return DEFAULT; + x++; + i = map.find(x); + if (i != map.end()) return (*i).second; + return DEFAULT; +} + + +//////////////////////////////////////////////// +// lookup an email address in the env_from or env_to maps +// this email address is passed in from sendmail, and will +// always be enclosed in <>. It may have mixed case, just +// as the mail client sent it. +// +static char* lookup(char* email, string_map map); +static char* lookup(char* email, string_map map) { + int n = strlen(email)-2; + if (n < 1) return DEFAULT; // malformed + char *key = strdup(email+1); + key[n] = '\0'; + for (int i=0; i<n; i++) key[i] = tolower(key[i]); + char *rc = lookup1(key, map); + free(key); + return rc; +} + + +//////////////////////////////////////////////// +// find the dnsbl with a specific name +// +static DNSBLP find_dnsbl(CONFIG &dc, char *name); +static DNSBLP find_dnsbl(CONFIG &dc, char *name) { + dnsblp_map::iterator i = dc.dnsbls.find(name); + if (i == dc.dnsbls.end()) return NULL; + return (*i).second; +} + + +//////////////////////////////////////////////// +// find the dnsbll with a specific name +// +static DNSBLLP find_dnsbll(CONFIG &dc, char *name); +static DNSBLLP find_dnsbll(CONFIG &dc, char *name) { + dnsbllp_map::iterator i = dc.dnsblls.find(name); + if (i == dc.dnsblls.end()) return NULL; + return (*i).second; +} + + +//////////////////////////////////////////////// +// find the envfrom map with a specific name +// +static string_map* find_from_map(CONFIG &dc, char *name); +static string_map* find_from_map(CONFIG &dc, char *name) { + from_map::iterator i = dc.env_from.find(name); + if (i == dc.env_from.end()) return NULL; + return (*i).second; +} + + +static string_map& really_find_from_map(CONFIG &dc, char *name); +static string_map& really_find_from_map(CONFIG &dc, char *name) { + string_map *sm = find_from_map(dc, name); + if (!sm) { + sm = new string_map; + dc.env_from[name] = sm; + } + return *sm; +} + + +//////////////////////////////////////////////// +// check a single dnsbl - we don't try very hard, just +// using the default resolver retry settings. If we cannot +// get an answer, we just accept the mail. The caller +// must ensure thread safety. +// +static status check_single(int ip, DNSBL &bl); +static status check_single(int ip, DNSBL &bl) { + // make a dns question + const u_char *src = (const u_char *)&ip; + if (src[0] == 127) return oksofar; // don't do dns lookups on localhost + char question[NS_MAXDNAME]; + snprintf(question, sizeof(question), "%u.%u.%u.%u.%s.", src[3], src[2], src[1], src[0], bl.suffix); + // ask the question + u_char answer[NS_PACKETSZ]; + int length = res_search(question, ns_c_in, ns_t_a, answer, sizeof(answer)); + if (length < 0) return oksofar; // error in getting answer + // parse the answer + ns_msg handle; + ns_rr rr; + if (ns_initparse(answer, length, &handle) != 0) return oksofar; + int rrnum = 0; + while (ns_parserr(&handle, ns_s_an, rrnum++, &rr) == 0) { + if (ns_rr_type(rr) == ns_t_a) { + // we see an A record, implies blacklisted ip address + return reject; + } + } + return oksofar; +} + + +//////////////////////////////////////////////// +// check the dnsbls specified for this recipient +// +static status check_dnsbl(mlfiPriv &priv, DNSBLLP dnsbllp, DNSBLP &rejectlist); +static status check_dnsbl(mlfiPriv &priv, DNSBLLP dnsbllp, DNSBLP &rejectlist) { + if (priv.authenticated) return oksofar; + if (!dnsbllp) return oksofar; + DNSBLL &dnsbll = *dnsbllp; + for (DNSBLL::iterator i=dnsbll.begin(); i!=dnsbll.end(); i++) { + DNSBLP dp = *i; // non null by construction + status st; + map<DNSBLP, status>::iterator f = priv.checked.find(dp); + if (f == priv.checked.end()) { + // have not checked this list yet + pthread_mutex_lock(&resolve_mutex); + st = check_single(priv.ip, *dp); + pthread_mutex_unlock(&resolve_mutex); + rejectlist = dp; + priv.checked[dp] = st; + } + else { + st = (*f).second; + rejectlist = (*f).first; + } + if (st == reject) return st; + } + return oksofar; +} + + +//////////////////////////////////////////////// +// start of sendmail milter interfaces +// +sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr) +{ + // allocate some private memory + mlfiPriv *priv = new mlfiPriv; + if (hostaddr->sa_family == AF_INET) { + priv->ip = ((struct sockaddr_in *)hostaddr)->sin_addr.s_addr; + } + + // save the private data + smfi_setpriv(ctx, (void*)priv); + + // continue processing + return SMFIS_CONTINUE; +} + +sfsistat mlfi_envfrom(SMFICTX *ctx, char **from) +{ + mlfiPriv &priv = *MLFIPRIV; + priv.mailaddr = strdup(from[0]); + priv.authenticated = (smfi_getsymval(ctx, "{auth_authen}") != NULL); + return SMFIS_CONTINUE; +} + +sfsistat mlfi_envrcpt(SMFICTX *ctx, char **rcpt) +{ + DNSBLP rejectlist = NULL; // list that caused the reject + status st = oksofar; + mlfiPriv &priv = *MLFIPRIV; + CONFIG &dc = *priv.pc; + char *rcptaddr = rcpt[0]; + char *dnsname = lookup(rcptaddr, dc.env_to_dnsbll); + char *fromname = lookup(rcptaddr, dc.env_to_chkfrom); + if ((strcmp(dnsname, BLACK) == 0) || + (strcmp(fromname, BLACK) == 0)) { + st = black; // two options to blacklist this recipient + } + else if (strcmp(fromname, WHITE) == 0) { + st = white; + } + else { + // check an env_from map + string_map *sm = find_from_map(dc, fromname); + if (sm != NULL) { + fromname = lookup(priv.mailaddr, *sm); // returns default if name not in map + if (strcmp(fromname, BLACK) == 0) { + st = black; // blacklist this envelope from value + } + if (strcmp(fromname, WHITE) == 0) { + st = white; // blacklist this envelope from value + } + } + } + if ((st == oksofar) && (strcmp(dnsname, WHITE) != 0)) { + // check dns lists + st = check_dnsbl(priv, find_dnsbll(dc, dnsname), rejectlist); + } + + if (st == reject) { + // reject the recipient based on some dnsbl + char adr[sizeof "255.255.255.255"]; + adr[0] = '\0'; + const char *rc = inet_ntop(AF_INET, (const u_char *)&priv.ip, adr, sizeof(adr)); + char buf[2000]; + snprintf(buf, sizeof(buf), rejectlist->message, adr, adr); + smfi_setreply(ctx, "550", "5.7.1", buf); + return SMFIS_REJECT; + } + else if (st == black) { + // reject the recipient based on blacklisting either from or to + smfi_setreply(ctx, "550", "5.7.1", "no such user"); + return SMFIS_REJECT; + } + else { + // accept the recipient + return SMFIS_CONTINUE; + } +} + +sfsistat mlfi_eom_or_abort(SMFICTX *ctx) +{ + mlfiPriv &priv = *MLFIPRIV; + if (priv.mailaddr) { + free(priv.mailaddr); + priv.mailaddr = NULL; + } + return SMFIS_CONTINUE; +} + +sfsistat mlfi_close(SMFICTX *ctx) +{ + mlfiPriv *priv = MLFIPRIV; + if (!priv) return SMFIS_CONTINUE; + delete priv; + smfi_setpriv(ctx, NULL); + return SMFIS_CONTINUE; +} + +struct smfiDesc smfilter = +{ + "DNSBL", // filter name + SMFI_VERSION, // version code -- do not change + SMFIF_DELRCPT, // flags + mlfi_connect, // connection info filter + NULL, // SMTP HELO command filter + mlfi_envfrom, // envelope sender filter + mlfi_envrcpt, // envelope recipient filter + NULL, // header filter + NULL, // end of header + NULL, // body block filter + mlfi_eom_or_abort, // end of message + mlfi_eom_or_abort, // message aborted + mlfi_close, // connection cleanup +}; + + +static void dumpit(char *name, string_map map); +static void dumpit(char *name, string_map map) { + fprintf(stderr, "\n"); + for (string_map::iterator i=map.begin(); i!=map.end(); i++) { + fprintf(stderr, "%s %s->%s\n", name, (*i).first, (*i).second); + } +} + + +static void dumpit(from_map map); +static void dumpit(from_map map) { + fprintf(stderr, "\n"); + for (from_map::iterator i=map.begin(); i!=map.end(); i++) { + fprintf(stderr, "envfrom map %s\n", (*i).first); + string_map *sm = (*i).second; + dumpit("envelope from", *sm); + } +} + + +static void dumpit(); +static void dumpit() { + CONFIG &dc = *config; + fprintf(stderr, "dnsbls\n"); + for (dnsblp_map::iterator i=dc.dnsbls.begin(); i!=dc.dnsbls.end(); i++) { + fprintf(stderr, "%s %s %s\n", (*i).first, (*i).second->suffix, (*i).second->message); + } + fprintf(stderr, "dnsbl_lists\n"); + for (dnsbllp_map::iterator i=dc.dnsblls.begin(); i!=dc.dnsblls.end(); i++) { + char *name = (*i).first; + DNSBLL &dl = *((*i).second); + fprintf(stderr, "%s", name); + for (DNSBLL::iterator j=dl.begin(); j!=dl.end(); j++) { + DNSBL &d = **j; + fprintf(stderr, " %s", d.suffix); + } + fprintf(stderr, "\n"); + } +} + + +//////////////////////////////////////////////// +// load a single config file +// +static void load_conf(CONFIG &dc, char *fn); +static void load_conf(CONFIG &dc, char *fn) { + dc.config_files.push_back(fn); + map<char*, int, ltstr> commands; + enum {dummy, dnsbl, dnsbll, envfrom, envto, include}; + commands["dnsbl" ] = dnsbl; + commands["dnsbl_list"] = dnsbll; + commands["env_from" ] = envfrom; + commands["env_to" ] = envto; + commands["include" ] = include; + const int LINE_SIZE = 2000; + ifstream is(fn); + if (is.fail()) return; + char line[LINE_SIZE]; + char orig[LINE_SIZE]; + char *delim = " \t"; + int curline = 0; + while (!is.eof()) { + is.getline(line, LINE_SIZE); + snprintf(orig, sizeof(orig), "%s", line); + curline++; + int n = strlen(line); + for (int i=0; i<n; i++) line[i] = tolower(line[i]); + char *cmd = strtok(line, delim); + if (cmd && (cmd[0] != '#') && (cmd[0] != '\0')) { + // have a decent command + bool processed = false; + switch (commands[cmd]) { + case dnsbl: { + // have a new dnsbl to use + char *name = next_token(delim); + if (!name) break; // no name name + if (find_dnsbl(dc, name)) break; // duplicate entry + char *suff = strtok(NULL, delim); + if (!suff) break; // no dns suffic + char *msg = suff + strlen(suff); + if ((msg - line) >= strlen(orig)) break; // line ended with the dns suffix + msg = strchr(msg+1, '\''); + if (!msg) break; // no reply message template + msg++; // move over the leading ' + if ((msg - line) >= strlen(orig)) break; // line ended with the leading quote + char *last = strchr(msg, '\''); + if (!last) break; // no trailing quote + *last = '\0'; // make it a null terminator + dc.dnsbls[name] = new DNSBL(register_string(suff), register_string(msg)); + processed = true; + } break; + + case dnsbll: { + // define a new combination of dnsbls + char *name = next_token(delim); + if (!name) break; + if (find_dnsbll(dc, name)) break; // duplicate entry + char *list = next_token(delim); + if (!list || (*list == '\0') || (*list == '#')) break; + DNSBLLP d = new DNSBLL; + DNSBLP p = find_dnsbl(dc, list); + if (p) d->push_back(p); + while (true) { + list = next_token(delim); + if (!list || (*list == '\0') || (*list == '#')) break; + DNSBLP p = find_dnsbl(dc, list); + if (p) d->push_back(p); + } + dc.dnsblls[name] = d; + processed = true; + } break; + + case envfrom: { + // add an entry into the named string_map + char *name = next_token(delim); + if (!name) break; + char *from = next_token(delim); + if (!from) break; + char *list = next_token(delim); + if (!list) break; + if ((strcmp(list, WHITE) == 0) || + (strcmp(list, BLACK) == 0)) { + string_map &fm = really_find_from_map(dc, name); + fm[from] = list; + processed = true; + } + else { + // list may be the name of a previously defined from_map + string_map *m = find_from_map(dc, list); + if (m && (strcmp(list,name) != 0)) { + string_map &pm = *m; + string_map &fm = really_find_from_map(dc, name); + fm.insert(pm.begin(), pm.end()); + processed = true; + } + } + } break; + + case envto: { + // define the dnsbl_list and env_from maps to use for this recipient + char *to = next_token(delim); + if (!to) break; + char *list = next_token(delim); + if (!list) break; + char *from = next_token(delim); + if (!from) break; + dc.env_to_dnsbll[to] = list; + dc.env_to_chkfrom[to] = from; + processed = true; + } break; + + case include: { + char *fn = next_token(delim); + if (fn) { + bool ok = true; + for (string_list::iterator i=dc.config_files.begin(); i!=dc.config_files.end(); i++) { + char *f = *i; + if (strcmp(f, fn) == 0) { + my_syslog("recursive include file detected"); + ok = false; + break; + } + } + if (ok) { + load_conf(dc, fn); + processed = true; + } + } + } break; + + default: { + } break; + } + if (!processed) { + pthread_mutex_lock(&syslog_mutex); + openlog("dnsbl", LOG_PID, LOG_MAIL); + syslog(LOG_ERR, "ignoring file %s line %d : %s\n", fn, curline, orig); + closelog(); + pthread_mutex_unlock(&syslog_mutex); + } + } + } + is.close(); +} + + +//////////////////////////////////////////////// +// reload the config +// +static CONFIG* new_conf(); +static CONFIG* new_conf() { + my_syslog("loading new configuration"); + CONFIG *newc = new CONFIG; + load_conf(*newc, "dnsbl.conf"); + newc->load_time = time(NULL); + return newc; +} + + +//////////////////////////////////////////////// +// thread to watch the old config files for changes +// and reload when needed. we also cleanup old +// configs whose reference count has gone to zero. +// +static void* config_loader(void *arg); +static void* config_loader(void *arg) { + typedef set<CONFIG *> configp_set; + configp_set old_configs; + while (true) { + sleep(180); // look for modifications every 3 minutes + CONFIG &dc = *config; + time_t then = dc.load_time; + struct stat st; + bool reload = false; + for (string_list::iterator i=dc.config_files.begin(); i!=dc.config_files.end(); i++) { + char *fn = *i; + if (stat(fn, &st)) reload = true; // file disappeared + else if (st.st_mtime > then) reload = true; // file modified + if (reload) break; + } + if (reload) { + CONFIG *newc = new_conf(); + // replace the global config pointer + pthread_mutex_lock(&config_mutex); + CONFIG *old = config; + config = newc; + pthread_mutex_unlock(&config_mutex); + // dumpit(env_from); + // dumpit("envelope to dnsbl", env_to_dnsbl); + // dumpit("envelope to check from", env_to_chkfrom); + // dumpit(); + if (old) old_configs.insert(old); + } + // now look for old configs with zero ref counts + for (configp_set::iterator i=old_configs.begin(); i!=old_configs.end(); ) { + CONFIG *old = *i; + if (!old->reference_count) { + delete old; // destructor does all the work + old_configs.erase(i++); + } + else i++; + } + } +} + + +static void usage(char *prog); +static void usage(char *prog) +{ + fprintf(stderr, "Usage: %s -p socket-addr [-t timeout]\n", prog); + fprintf(stderr, "where socket-addr is for the connection to sendmail and should be one of\n"); + fprintf(stderr, " inet:port@local-ip-address\n"); + fprintf(stderr, " local:local-domain-socket-file-name\n"); +} + + +int main(int argc, char**argv) +{ + bool setconn = FALSE; + int c; + const char *args = "p:s:h"; + extern char *optarg; + + // Process command line options + while ((c = getopt(argc, argv, args)) != -1) { + switch (c) { + case 'p': + if (optarg == NULL || *optarg == '\0') { + fprintf(stderr, "Illegal conn: %s\n", optarg); + exit(EX_USAGE); + } + if (smfi_setconn(optarg) == MI_FAILURE) { + fprintf(stderr, "smfi_setconn failed\n"); + exit(EX_SOFTWARE); + } + + if (strncasecmp(optarg, "unix:", 5) == 0) unlink(optarg + 5); + else if (strncasecmp(optarg, "local:", 6) == 0) unlink(optarg + 6); + setconn = TRUE; + break; + + case 't': + if (optarg == NULL || *optarg == '\0') { + fprintf(stderr, "Illegal timeout: %s\n", optarg); + exit(EX_USAGE); + } + if (smfi_settimeout(atoi(optarg)) == MI_FAILURE) { + fprintf(stderr, "smfi_settimeout failed\n"); + exit(EX_SOFTWARE); + } + break; + + case 'h': + default: + usage(argv[0]); + exit(EX_USAGE); + } + } + if (!setconn) { + fprintf(stderr, "%s: Missing required -p argument\n", argv[0]); + usage(argv[0]); + exit(EX_USAGE); + } + if (smfi_register(smfilter) == MI_FAILURE) { + fprintf(stderr, "smfi_register failed\n"); + exit(EX_UNAVAILABLE); + } + + // switch to background mode + if (daemon(1,0) < 0) { + fprintf(stderr, "daemon() call failed\n"); + exit(EX_UNAVAILABLE); + } + + // initialize the thread sync objects + pthread_mutex_init(&config_mutex, 0); + pthread_mutex_init(&syslog_mutex, 0); + pthread_mutex_init(&resolve_mutex, 0); + + // load the initial config + config = new_conf(); + + // only create threads after the fork() in daemon + pthread_t tid; + if (pthread_create(&tid, 0, config_loader, 0)) + my_syslog("failed to create config loader thread"); + if (pthread_detach(tid)) + my_syslog("failed to detach config loader thread"); + + // write the pid + const char *pidpath = "/var/run/dnsbl.pid"; + unlink(pidpath); + FILE *f = fopen(pidpath, "w"); + if (f) { + fprintf(f, "-%d\n", (u_int)getpgrp()); + fclose(f); + } + + int rc = smfi_main(); +}