/* Copyright 2004 Russell Miller 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 along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "packetbl.h" #include <stdlib.h> #include <stdio.h> #include <stdint.h> #include <string.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <resolv.h> #include <netdb.h> #include <ctype.h> #include <syslog.h> #include <sys/stat.h> #include <sys/types.h> #include <string.h> #include <unistd.h> #include <getopt.h> #include <time.h> #include <errno.h> #include <linux/netfilter.h> #include <dotconf.h> #include <libpool.h> #ifdef USE_SOCKSTAT #include <sys/socket.h> #include <sys/un.h> #include <pthread.h> #endif #ifdef HAVE_FIREDNS #include <firedns.h> #endif #ifndef BUFFERSIZE #define BUFFERSIZE 65536 #endif #ifdef USE_CACHE # ifndef USE_CACHE_DEF_LEN # define USE_CACHE_DEF_LEN 8192 # endif # ifndef USE_CACHE_DEF_TTL # define USE_CACHE_DEF_TTL 3600 # endif #endif # define TH_FIN 0x01 # define TH_SYN 0x02 # define TH_RST 0x04 # define TH_PUSH 0x08 # define TH_ACK 0x10 # define TH_URG 0x20 # include <libnetfilter_queue.h> # define SET_VERDICT nfq_set_verdict # define PBL_HANDLE nfq_q_handle # define PBL_SET_MODE nfq_set_mode # define PBL_COPY_PACKET NFQNL_COPY_PACKET # define PBL_ID_T u_int32_t # define PBL_ERRSTR "" #define DEBUG(x, y) if (conf.debug >= x) { printf(y "\n"); } struct packet_info { uint8_t b1; uint8_t b2; uint8_t b3; uint8_t b4; int s_port; int d_port; int flags; }; struct cidr { uint32_t ip; uint32_t network; uint32_t processed; /* network, but as a bitmask */ }; struct config_entry { char *string; struct config_entry *next; struct packet_info ip; struct cidr cidr; }; struct config_entry *blacklistbl = NULL; struct config_entry *whitelistbl = NULL; struct config_entry *blacklist = NULL; struct config_entry *whitelist = NULL; struct bl_context { int permissions; const char *current_end_token; pool_t *pool; }; enum permissions { O_ROOT = 1, O_HOSTSECTION = 2, O_LAST = 4 }; static DOTCONF_CB(host_section_open); static DOTCONF_CB(common_section_close); static DOTCONF_CB(common_option); static DOTCONF_CB(toggle_option); static DOTCONF_CB(facility_option); static const char *end_host = "</host>"; char msgbuf[BUFFERSIZE]; struct config { int allow_non25; int allow_nonsyn; int default_accept; int dryrun; int log_facility; int queueno; int quiet; int debug; }; static struct config conf = { 0, 0, 1, 0, LOG_DAEMON, 0 }; struct pbl_stat_info { uint32_t cacheaccept; uint32_t cachereject; uint32_t whitelistblhits; uint32_t blacklistblhits; uint32_t whitelisthits; uint32_t blacklisthits; uint32_t fallthroughhits; uint32_t totalpackets; }; static struct pbl_stat_info statistics = { 0, 0, 0, 0, 0, 0, 0 }; #ifdef USE_CACHE struct packet_cache_t { uint32_t ipaddr; time_t expires; int action; }; struct packet_cache_t *packet_cache = NULL; uint32_t packet_cache_len = USE_CACHE_DEF_LEN; uint16_t packet_cache_ttl = USE_CACHE_DEF_TTL; #endif struct config_entry *hostlistcache = NULL; int get_packet_info(char *payload, struct packet_info *ip); int check_packet_list(const struct packet_info *ip, struct config_entry *list); int check_packet_dnsbl(const struct packet_info *ip, struct config_entry *list); int parse_cidr(struct config_entry *ce); /* int validate_blacklist(char *); */ void parse_config(void); void parse_arguments(int argc, char **argv); void pbl_init_sockstat(void); static void get_ip_string(const struct packet_info *ip); static void pbl_set_verdict(struct PBL_HANDLE *h, PBL_ID_T id, unsigned int verdict); static int pbl_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data); static const configoption_t options[] = { {"<host>", ARG_NONE, host_section_open, NULL, O_ROOT}, {"</host>", ARG_NONE, common_section_close, NULL, O_ROOT}, {"blacklistbl", ARG_STR, common_option, NULL, O_HOSTSECTION}, {"whitelistbl", ARG_STR, common_option, NULL, O_HOSTSECTION}, {"whitelist", ARG_STR, common_option, NULL, O_HOSTSECTION}, {"blacklist", ARG_STR, common_option, NULL, O_HOSTSECTION}, {"fallthroughaccept", ARG_TOGGLE, toggle_option, NULL, O_ROOT}, {"allownonport25", ARG_TOGGLE, toggle_option, NULL, O_ROOT}, {"allownonsyn", ARG_TOGGLE, toggle_option, NULL, O_ROOT}, {"dryrun", ARG_TOGGLE, toggle_option, NULL, O_ROOT}, {"quiet", ARG_TOGGLE, toggle_option, NULL, O_ROOT}, #ifdef USE_CACHE {"cachettl", ARG_INT, toggle_option, NULL, O_ROOT}, {"cachesize", ARG_INT, toggle_option, NULL, O_ROOT}, #endif {"logfacility", ARG_STR, facility_option, NULL, O_ROOT}, #ifdef HAVE_NFQUEUE {"queueno", ARG_INT, common_option, NULL, O_ROOT}, #endif LAST_OPTION }; FUNC_ERRORHANDLER(error_handler) { fprintf(stderr, "[error] %s\n", msg); return 1; } /* * SYNOPSIS: * void daeomize(void); * * NOTES: * This function accomplishes everything needed to become a daemon. * Including closing standard in/out/err and forking. * It returns nothing, on failure the program must abort. * */ void daemonize(void) { pid_t pid; chdir("/"); close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); setsid(); pid = fork(); if (pid > 0) { exit(EXIT_SUCCESS); } if (pid < 0) { if (conf.debug == 0) { syslog(LOG_ERR, "Fork failed while daemonizing: %s", strerror(errno)); } else { fprintf(stderr, "Fork failed while daemonizing: %s", strerror(errno)); } exit(EXIT_FAILURE); } } #ifdef USE_CACHE /* * SYNOPSIS: * static uint32_t packet_cache_hash( * const struct packet_info ip * ); * * ARGUMENTS: * struct packet_info ip Structure containing information about the * IP address to create the hash. * * RETURN VALUE: * An integer representing the hash value is returned. This value *MAY BE* * greater than the size of the hash table, so it should be checked before * use. * * NOTES: * * CURRENT IMPLEMENTATION NOTES (do not rely on this for design): * Currently, only the IP portion of the structure is used for computing the * hash. * The current implementation will never return a value greater than 21675 * so having a hash table larger than that would be wasteful. * */ static uint32_t packet_cache_hash(const struct packet_info ip) { uint32_t hash = 0; hash = ip.b1 << 6; hash += ip.b2 << 4; hash += ip.b3 << 2; hash += ip.b4; return hash; } /* * SYNOPSIS: * void packet_cache_clear(void); * * ARGUMENTS: * (none) * * RETURN VALUE: * (none) * * NOTES: * This function must succeed even if "packet_cache" is NULL. * This function initializes the values inside the previously allocated * "packet_cache" array to safe values so that we may check entries * safely. * */ void packet_cache_clear(void) { uint32_t i; if (packet_cache==NULL) return; for (i=0; i<packet_cache_len; i++) { packet_cache[i].ipaddr = 0; packet_cache[i].action = NF_ACCEPT; packet_cache[i].expires = 0; } return; } #endif /* * SYNOPSIS: * static uint32_t packet_info_to_ip( * const struct packet_info ip * ); * * ARGUMENTS: * struct packet_info ip Structure containing IP fields to convert to * a 32bit unsigned integer. * * RETURN VALUE: * This function returns a 32bit unsigned integer that represents a * "one-to-one" mapping of IP octets and integer addresses, it will not * overlap, therefore. * * NOTES: * */ static uint32_t packet_info_to_ip(const struct packet_info ip) { return ((ip.b1 & 0xff) << 24) | ((ip.b2 & 0xff) << 16) | ((ip.b3 & 0xff) << 8) | (ip.b4 & 0xff); } /* * SYNOPSIS: * int packet_check_ip( * const struct packet_info ip * ); * * ARGUMENTS: * struct packet_info ip Structure containing information about * packet to check. * * RETURN VALUE: * "packet_check_ip" returns an action to supply to "pbl_set_verdict". * Currently, it will be one of NF_DROP or NF_ACCEPT but other values should * be accounted for. The supplied information is checked against the * configued DNS RBLs and Whitelists to determine the appropriate action. * * NOTES: * This function may return stale entries due to caching. * This function MUST continue to work if "packet_cache" is NULL. * */ int packet_check_ip(const struct packet_info ip) { int retval; #ifdef USE_CACHE uint32_t ipaddr_check; uint32_t cache_hash = 0; time_t currtime; char *actionstr; currtime = time(NULL); ipaddr_check = packet_info_to_ip(ip); if (packet_cache_len > 0) { cache_hash = packet_cache_hash(ip) % packet_cache_len; } if (cache_hash>0 && cache_hash<packet_cache_len && packet_cache != NULL) { if (packet_cache[cache_hash].ipaddr==ipaddr_check && packet_cache[cache_hash].expires>currtime) { get_ip_string(&ip); retval = packet_cache[cache_hash].action; switch (retval) { case NF_DROP: actionstr="reject"; statistics.cachereject++; break; case NF_ACCEPT: actionstr="accept"; statistics.cacheaccept++; break; default: actionstr="???"; break; } if (!conf.quiet) { if (conf.debug == 0) { syslog(LOG_INFO, "[Found in cache (%s)] [%s]", actionstr, msgbuf); } else { fprintf(stderr, "[Found in cache (%s)] [%s]", actionstr, msgbuf); } } return retval; } } #endif /* the get_ip_string is set AFTER the check_packet_* * calls because of the possibility they could screw with * msgbuf. They shouldn't, really, but better safe than * sorry, at least for now. */ if (check_packet_list(&ip, whitelist) == 1) { get_ip_string(&ip); if (!conf.quiet) { if (conf.debug == 0) { syslog(LOG_INFO, "[accept whitelist] [%s]", msgbuf); } else { fprintf(stderr, "[accept whitelist] [%s]", msgbuf); } } statistics.whitelisthits++; retval=NF_ACCEPT; } else if (check_packet_list(&ip, blacklist) == 1) { get_ip_string(&ip); if (!conf.quiet) { if (conf.debug == 0) { syslog(LOG_INFO, "[reject blacklist] [%s]", msgbuf); } else { fprintf(stderr, "[reject blacklist] [%s]", msgbuf); } } statistics.blacklisthits++; retval=NF_DROP; } else if (check_packet_dnsbl(&ip, whitelistbl) == 1) { get_ip_string(&ip); if (!conf.quiet) { if (conf.debug == 0) { syslog(LOG_INFO, "[accept dnsbl] [%s]", msgbuf); } else { fprintf(stderr, "[accept dnsbl] [%s]", msgbuf); } } statistics.whitelistblhits++; retval=NF_ACCEPT; } else if (check_packet_dnsbl(&ip, blacklistbl) == 1) { get_ip_string(&ip); if (!conf.quiet) { if (conf.debug == 0) { syslog(LOG_INFO, "[reject dnsbl] [%s]", msgbuf); } else { fprintf(stderr, "[reject dnsbl] [%s]", msgbuf); } } statistics.blacklistblhits++; retval=NF_DROP; } else { get_ip_string(&ip); if (conf.default_accept == 1) { if (!conf.quiet) { if (conf.debug == 0) { syslog(LOG_INFO, "[accept fallthrough] [%s]", msgbuf); } else { fprintf(stderr, "[accept fallthrough] [%s]", msgbuf); } } retval=NF_ACCEPT; } else { if (!conf.quiet) { if (conf.debug == 0) { syslog(LOG_INFO, "[reject fallthrough] [%s]", msgbuf); } else { fprintf(stderr, "[reject fallthrough] [%s]", msgbuf); } } retval=NF_DROP; } statistics.fallthroughhits++; } #ifdef USE_CACHE /* Put current action into the cache. */ if (packet_cache != NULL) { packet_cache[cache_hash].ipaddr = ipaddr_check; packet_cache[cache_hash].action = retval; packet_cache[cache_hash].expires = currtime + packet_cache_ttl; } #endif return retval; } static int pbl_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) { int ret; int id; struct nfqnl_msg_packet_hdr *ph; char *nfdata; struct packet_info ip; DEBUG(2, "Entering callback"); if (ph = nfq_get_msg_packet_hdr(nfa)) { id = ntohl(ph->packet_id); } ret = nfq_get_payload(nfa, &nfdata); /* what return codes here? */ ret = get_packet_info(nfdata, &ip); if (ret == -1) { pbl_set_verdict(qh, id, NF_ACCEPT); return; } ret = packet_check_ip(ip); if (conf.debug >= 2) { printf ("Got packet from %hhu.%hhu.%hhu.%hhu: %d\n", ip.b1, ip.b2, ip.b3, ip.b4, ret); } pbl_set_verdict(qh, id, ret); } /* * SYNOPSIS: * static void pbl_set_verdict( * const struct PBL_HANDLE *h, * ipq_id_t id, * unsigned int verdict * ); * * ARGUMENTS: * struct PBL_HANDLE *h IP Queue handle, must not be NULL * ipq_id_t id XXX: Id ??? * unsigned int verdict Verdict to assign this packet in the queue. * * RETURN VALUE: * (none) * * NOTES: * This function calls ipq_set_verdict() to the appropriate "verdict" * It must be able to handle the condition where "conf.dryrun" is set * causing all "verdict" values to be treated as NF_ACCEPT regardless * of their actual value. * */ static void pbl_set_verdict(struct PBL_HANDLE *h, PBL_ID_T id, unsigned int verdict) { if (conf.dryrun == 1) { SET_VERDICT(h, id, NF_ACCEPT, 0, NULL); } else { SET_VERDICT(h, id, verdict, 0, NULL); } } /* * SYNOPSIS: * int main( * int argc, * char **argv * ); * * ARGUMENTS: * int argc "Argument Count," number of valid elements * in the "argv" array too. * char **argv "Argument Vector," array of pointers to the * arguments passed to this process. * * RETURN VALUE: * This function should never return, since we are a daemon. The parent * process exits with success (EXIT_SUCCESS) from daemonize(); * * NOTES: * This is the function that should be called before any others, it does * many important initialization routines (reading configuration file, * setting up the IP Queue routines, system logging, etc) and provides * the main loop where packets are read and processed. * */ int main(int argc, char **argv) { struct PBL_HANDLE *handle; char buf[BUFFERSIZE]; struct nfq_handle *h; struct nfnl_handle *nh; int fd; struct packet_info ip; struct stat fbuf; int action; int rv; conf.debug = 0; if (stat("/proc/net/netfilter/nfnetlink_queue", &fbuf) == ENOENT) { fprintf(stderr, "Please make sure you have\ncompiled a kernel with the Netfilter QUEUE target built in, or loaded the appropriate module.\n"); exit(EXIT_FAILURE); } /* Parse our configuration data. */ parse_config(); /* We parse arguments after parsing the config file so we can override the config file. */ parse_arguments(argc, argv); if (conf.debug > 0) { fprintf(stderr, "Debug level %d\n", conf.debug); } if (conf.debug > 0) { fprintf(stderr, "Linking to queue %d\n", conf.queueno); } openlog("packetbl", LOG_PID, conf.log_facility); if (conf.debug == 0) { daemonize(); } #ifdef USE_SOCKSTAT pbl_init_sockstat(); #endif #ifdef USE_CACHE if (packet_cache_len > 0) { /* Allocate space for the packet cache if a positive number of elements is requested. */ packet_cache = malloc(sizeof(*packet_cache) * packet_cache_len); } else { packet_cache = NULL; } packet_cache_clear(); #endif DEBUG(2, "Creating nfq handle..."); if ((h = nfq_open()) == NULL) { syslog(LOG_ERR, "Couldn't create nfq handle: %s", strerror(errno)); DEBUG(1, "Couldn't create nfq handle"); exit(EXIT_FAILURE); } DEBUG(2, "unbinding nfq handle..."); if (nfq_unbind_pf(h, AF_INET) < 0) { syslog(LOG_ERR, "Couldn't unbind nf_queue handler for AF_INET"); DEBUG(1, "Couldn't unbind nf_queue handler for AF_INET"); exit(EXIT_FAILURE); } DEBUG(2, "binding nfq handle..."); if (nfq_bind_pf(h, AF_INET) < 0) { syslog(LOG_ERR, "Couldn't bind ns_queue handler for AF_INET"); DEBUG(1, "Couldn't bind ns_queue handler for AF_INET"); exit(EXIT_FAILURE); } DEBUG(2, "creating queue..."); if ((handle = nfq_create_queue(h, conf.queueno, &pbl_callback, NULL)) == NULL) { syslog(LOG_ERR, "nfq_create_queue failed"); DEBUG(1, "nfq_create_queue failed"); exit(EXIT_FAILURE); } if ((PBL_SET_MODE(handle, PBL_COPY_PACKET, BUFFERSIZE)) == -1) { syslog(LOG_ERR, "ipq_set_mode error: %s", PBL_ERRSTR); DEBUG(1, "ipq_set_mode error"); if (errno == 111) { syslog(LOG_ERR, "try loading the ip_queue module"); } exit(EXIT_FAILURE); } syslog(LOG_INFO, "packetbl started successfully"); DEBUG(1, "packetbl started successfully"); /* main packet processing loop. This loop should never terminate * unless a signal is received or some other unforeseen thing * happens. */ while (1) { nh = nfq_nfnlh(h); fd = nfnl_fd(nh); DEBUG(2, "Entering main loop."); DEBUG(2, "waiting for a packet..."); while ((rv = recv(fd, buf, sizeof(buf), 0)) > 0) { DEBUG(2, "Handling a packet"); nfq_handle_packet(h, buf, rv); } DEBUG(2, "Packet got."); statistics.totalpackets++; } } /* * SYNOPSIS: * int get_packet_info( * ipq_packet_msg_t *packet, * struct packet_info *ip * ); * * ARGUMENTS: * ipq_packet_msg_t *packet IP Queue supplied packet headers * struct packet_info *ip Structure to be filled in. * * RETURN VALUE: * 0 is returned on success, non-zero indicates that the packet could not * be properly processed (i.e., it's off the wrong protocol or version). * * NOTES: * This function fills in the previously allocated "ip" parameter with * data from the "packet" parameter. * */ int get_packet_info(char *payload, struct packet_info *ip) { int version; int ip_header_length, header_size;; if (ip == NULL || payload == NULL) { return -1; } ip->s_port = 0; ip->d_port = 0; /* Get IP Version Byte 1 of IP Header */ version = payload[0] & 0xF0; version >>= 4; /* Get IP Header length Byte 2 of IP Header * Header length is usually 20, or 5 32-bit words */ ip_header_length = payload[0] & 0x0F; header_size = ip_header_length * 4; /* We're not handling IPV6 packets yet. I'll probably rewrite * this whole damned thing in C++ first. */ if (version != 4) { return -1; } /* IP Address Bytes 13 - 16 of IP header */ ip->b1 = payload[12]; ip->b2 = payload[13]; ip->b3 = payload[14]; ip->b4 = payload[15]; /* Source Port Bytes 21 - 22 of IP Header * Bytes 1 - 2 of TCP Header */ ip->s_port = payload[header_size] * 256; ip->s_port += payload[header_size + 1]; /* Destination Port Bytes 23 - 24 of IP Header * Bytes 3 - 4 of TCP Header */ ip->d_port = payload[header_size + 2] * 256; ip->d_port += payload[header_size + 3]; /* TCP Flags Byte 14 of TCP header * Last six bits * We're only interested at present in the SYN Flag. * But there's no reason not to copy all of them, the operation * would take pretty much the same time anyway. */ ip->flags = payload[header_size + 13] & 0x3F; /* Returning -1, at present accepts the packet unconditionally. */ if (conf.allow_non25 == 0 && ip->d_port != 25) { return -1; } if ((conf.allow_nonsyn == 0) && ((ip->flags & TH_SYN) == 0)) { return -1; } /* Return success */ return 0; } /* * SYNOPSIS: * void parse_config(void); * * ARGUMENTS: * (none) * * RETURN VALUE: * (none) * * NOTES: * This function parses the configuration file and sets the appropriate * global variables. It may cause the program to abort with a failure * if the configuration is unreadable or unparsable. Due to this fact, * it should only be called during start-up and not from the main loop. * */ void parse_config(void) { configfile_t *configfile; struct bl_context context; context.pool = pool_new(NULL); configfile = dotconf_create(CONFIGFILE, options, (void *)&context, CASE_INSENSITIVE); if (!configfile) { fprintf(stderr, "Error opening config file\n"); exit(EXIT_FAILURE); } if (dotconf_command_loop(configfile) == 0) { fprintf(stderr, "Error reading configuration file\n"); exit(EXIT_FAILURE); } dotconf_cleanup(configfile); pool_free(context.pool); return; } /* * SYNOPSIS: * void parse_arguments( * int argc, * char **argv * ); * * ARGUMENTS: * int argc "Argument Count," number of valid elements * in the "argv" array too. * char **argv "Argument Vector," array of pointers to the * arguments to be considered for processing. * * RETURN VALUE: * (none) * * NOTES: * Use getopt() to parse passed short arguments. This should be done after * parsing the config file, because we might need to override some of its * settings. We cannot return sucess or failure, so upon failure we should * abort the program. * */ void parse_arguments(int argc, char **argv) { int ch; while ((ch = getopt(argc, argv, "qVd")) != -1) { switch (ch) { case 'q': conf.quiet = 1; break; case 'V': printf("PacketBL version %s\n", PACKAGE_VERSION); exit(EXIT_SUCCESS); break; case 'd': conf.debug++; break; case '?': case ':': default: exit(EXIT_FAILURE); break; } } return; } DOTCONF_CB(common_section_close) { struct bl_context *context = (struct bl_context *)ctx; return context->current_end_token; } DOTCONF_CB(toggle_option) { if (strcasecmp(cmd->name, "fallthroughaccept") == 0) { conf.default_accept = cmd->data.value; return NULL; } if (strcasecmp(cmd->name, "allownonport25") == 0) { conf.allow_non25 = cmd->data.value; return NULL; } if (strcasecmp(cmd->name, "dryrun") == 0) { conf.dryrun = cmd->data.value; return NULL; } if (strcasecmp(cmd->name, "allownonsyn") == 0) { conf.allow_nonsyn = cmd->data.value; return NULL; } if (strcasecmp(cmd->name, "quiet") == 0) { conf.quiet = cmd->data.value; return NULL; } #ifdef USE_CACHE if (strcasecmp(cmd->name, "cachettl") == 0) { if (cmd->data.value < 0) { fprintf(stderr, "Error parsing config: cachettl cannot be a negative value\n"); exit(EXIT_FAILURE); } packet_cache_ttl = cmd->data.value; return NULL; } if (strcasecmp(cmd->name, "cachesize") == 0) { if (cmd->data.value < 0) { fprintf(stderr, "Error parsing config: cachelen cannot be a negative value\n"); exit(EXIT_FAILURE); } packet_cache_len = cmd->data.value; return NULL; } #endif return NULL; } DOTCONF_CB(facility_option) { if (strcasecmp(cmd->data.str, "auth") == 0) { conf.log_facility = LOG_AUTH; } else if (strcasecmp(cmd->data.str, "authpriv") == 0) { conf.log_facility = LOG_AUTHPRIV; } else if (strcasecmp(cmd->data.str, "cron") == 0) { conf.log_facility = LOG_CRON; } else if (strcasecmp(cmd->data.str, "daemon") == 0) { conf.log_facility = LOG_DAEMON; } else if (strcasecmp(cmd->data.str, "kern") == 0) { conf.log_facility = LOG_KERN; } else if (strcasecmp(cmd->data.str, "lpr") == 0) { conf.log_facility = LOG_LPR; } else if (strcasecmp(cmd->data.str, "mail") == 0) { conf.log_facility = LOG_MAIL; } else if (strcasecmp(cmd->data.str, "news") == 0) { conf.log_facility = LOG_NEWS; } else if (strcasecmp(cmd->data.str, "syslog") == 0) { conf.log_facility = LOG_SYSLOG; } else if (strcasecmp(cmd->data.str, "user") == 0) { conf.log_facility = LOG_USER; } else if (strcasecmp(cmd->data.str, "uucp") == 0) { conf.log_facility = LOG_UUCP; } else if (strcasecmp(cmd->data.str, "local0") == 0) { conf.log_facility = LOG_LOCAL0; } else if (strcasecmp(cmd->data.str, "local1") == 0) { conf.log_facility = LOG_LOCAL1; } else if (strcasecmp(cmd->data.str, "local2") == 0) { conf.log_facility = LOG_LOCAL2; } else if (strcasecmp(cmd->data.str, "local3") == 0) { conf.log_facility = LOG_LOCAL3; } else if (strcasecmp(cmd->data.str, "local4") == 0) { conf.log_facility = LOG_LOCAL4; } else if (strcasecmp(cmd->data.str, "local5") == 0) { conf.log_facility = LOG_LOCAL5; } else if (strcasecmp(cmd->data.str, "local6") == 0) { conf.log_facility = LOG_LOCAL6; } else if (strcasecmp(cmd->data.str, "local7") == 0) { conf.log_facility = LOG_LOCAL7; } else { fprintf(stderr, "Log facility %s is invalid\n", cmd->data.str); exit(EXIT_FAILURE); } return NULL; } DOTCONF_CB(common_option) { struct config_entry *ce, *tmp=NULL; #ifdef HAVE_FIREDNS size_t blacklistlen = 0; #endif if (strcasecmp(cmd->name, "queueno") == 0) { conf.queueno = cmd->data.value; return NULL; } ce = malloc(sizeof(struct config_entry)); if (ce == NULL) { return NULL; } ce->string = (char *)strdup(cmd->data.str); ce->next = NULL; if (strcasecmp(cmd->name, "blacklistbl") == 0) { #ifdef HAVE_FIREDNS blacklistlen = strlen(ce->string); if (ce->string[blacklistlen-1] == '.') { ce->string[blacklistlen-1]='\0'; } #endif /* resolution check completely removed. Will put it back * during config file and architectural revamp. */ if (blacklistbl == NULL) { blacklistbl = ce; return NULL; } else { tmp = blacklistbl; } } if (strcasecmp(cmd->name, "whitelistbl") == 0) { #ifdef HAVE_FIREDNS blacklistlen = strlen(ce->string); if (ce->string[blacklistlen-1] == '.') { ce->string[blacklistlen-1]='\0'; } #endif /* resolution check completely removed. Will put it back * during config file and architectural revamp. */ if (whitelistbl == NULL) { whitelistbl = ce; return NULL; } else { tmp = whitelistbl; } } if (strcasecmp(cmd->name, "whitelist") == 0) { if (parse_cidr(ce) == -1) { fprintf(stderr, "Error parsing CIDR in %s, ignoring\n", ce->string); free(ce->string); free(ce); return NULL; } if (whitelist == NULL) { whitelist = ce; return NULL; } else { tmp = whitelist; } } if (strcasecmp(cmd->name, "blacklist") == 0) { if (parse_cidr(ce) == -1) { fprintf(stderr, "Error parsing CIDR in %s, ignoring\n", ce->string); free(ce->string); free(ce); return NULL; } if (blacklist == NULL) { blacklist = ce; return NULL; } else { tmp = blacklist; } } while (tmp->next != NULL) { tmp = tmp->next; } tmp->next = ce; return NULL; } DOTCONF_CB(host_section_open) { struct bl_context *context = (struct bl_context *)ctx; const char *old_end_token = context->current_end_token; int old_override = context->permissions; const char *err = NULL; context->permissions |= O_HOSTSECTION; context->current_end_token = end_host; while (!cmd->configfile->eof) { err = dotconf_command_loop_until_error(cmd->configfile); if (!err) { err = "</host> is missing"; break; } if (err == context->current_end_token) break; dotconf_warning(cmd->configfile, DCLOG_ERR, 0, err); } context->current_end_token = old_end_token; context->permissions = old_override; if (err != end_host) return err; return NULL; } /* * SYNOPSIS: * int parse_cidr( * struct config_entry *ce * ); * * ARGUMENTS: * struct config_entry *ce Structure to be filled in, ->string must * be supplied. * * RETURN VALUE: * On success 0 is returned, non-zero is returned on error. * * NOTES: * This routine is rather tortured, but it works and is believed * correct. Please don't mess with it without a good reason. * */ int parse_cidr(struct config_entry *ce) { int sep = 0; // which separator we're on. char *counter, *c1; char number[BUFFERSIZE]; if (ce == NULL) { return -1; } c1 = ce->string; // initialize state counter for (counter = ce->string; (counter - ce->string) < strlen(ce->string); counter++) { switch (*counter) { case '.': case '/': // separator strncpy(number, c1, (int)(counter - c1)); number[(int)(counter - c1)] = '\0'; switch(sep) { case 0: ce->ip.b1 = atoi(number); if (ce->ip.b1 < 0 || ce->ip.b1 > 255) { return -1; } break; case 1: ce->ip.b2 = atoi(number); if (ce->ip.b2 < 0 || ce->ip.b2 > 255) { return -1; } break; case 2: ce->ip.b3 = atoi(number); if (ce->ip.b3 < 0 || ce->ip.b3 > 255) { return -1; } break; case 3: ce->ip.b4 = atoi(number); if (ce->ip.b4 < 0 || ce->ip.b4 > 255) { return -1; } break; } sep++; c1 = counter + 1; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': continue; default: // this character doesn't belong here. return -1; break; } } strncpy (number, c1, (int)(counter - c1)); number[(int)(counter - c1)] = '\0'; ce->cidr.network = atoi(number); ce->cidr.processed = 0; ce->cidr.processed = 0xffffffff << (32 - ce->cidr.network); ce->cidr.ip = 0; ce->cidr.ip = ce->ip.b1 << 24; ce->cidr.ip |= ce->ip.b2 << 16; ce->cidr.ip |= ce->ip.b3 << 8; ce->cidr.ip |= ce->ip.b4; /* Mask out the bits that aren't in the network in cidr.ip. * We don't care about them and they'll just confuse the issue. */ ce->cidr.ip &= ce->cidr.processed; return 0; } /* * this routine isn't necessary right now. int validate_blacklist(char *str) { struct hostent *host; assert(str != NULL); host = gethostbyname(str); if (host == NULL && h_errno != NETDB_SUCCESS) { return -1; } return 0; } */ /* * SYNOPSIS: * int check_packet_dnsbl( * const struct packet_info * struct config_entry *list * ); * * ARGUMENTS: * struct packet_info *ip IP address data to check in DNS RBL. * struct config_entry *list Configured DNS RBL to check. * * RETURN VALUE: * 0 is returned if the "ip" cannot be found in the given "list". 1 is * returned on a successful match. * * NOTES: * "check_packet_dnsbl" searches the given list parameter (which is a list * of configured DNS RBLs in ->string) to determine if the data passed in * "ip" should be blocked. * This function must be able to cope with NULL "ip" and "list" paramters * without aborting. * */ int check_packet_dnsbl(const struct packet_info *ip, struct config_entry *list) { struct config_entry *wltmp = NULL; #ifndef HAVE_FIREDNS struct hostent *host; #else struct in_addr *host; #endif if (ip == NULL || list == NULL) { return 0; } wltmp = list; while (1) { char lookupbuf[BUFFERSIZE]; snprintf(lookupbuf, sizeof(lookupbuf), "%hhu.%hhu.%hhu.%hhu.%s", ip->b4, ip->b3, ip->b2, ip->b1, wltmp->string); #ifndef HAVE_FIREDNS host = gethostbyname(lookupbuf); #else host = firedns_resolveip4(lookupbuf); #endif if (host == NULL) { #ifndef HAVE_FIREDNS if (h_errno != HOST_NOT_FOUND) { syslog(LOG_ERR, "Error looking up host %s", lookupbuf ); } #else ; #endif } else { // found. return 1; } if (wltmp->next == NULL) { /* Termination case */ return 0; } wltmp = wltmp->next; } return 0; } /* * SYNOPSIS: * int check_packet_list( * const struct packet_info *ip * struct config_entry *list * ); * * ARGUMENTS: * struct packet_info *ip IP address data to check in supplied list. * struct config_entry *list List that contains data to check in against, * whitelist for example. * * RETURN VALUE: * 0 is returned if the "ip" cannot be found in the given "list". 1 is * returned on a successful match. * * NOTES: * "check_packet_list" searches the given list parameter (which is a list * CIDRs) to determine if the data passed in "ip" matches (whitelist, for * for example). * This function must be able to cope with NULL "ip" and "list" paramters * without aborting. * */ int check_packet_list(const struct packet_info *ip, struct config_entry *list) { struct config_entry *wltmp = NULL; unsigned int ip_proc; int rv; if (ip == NULL || list == NULL) { return 0; } ip_proc = ip->b1 << 24; ip_proc |= ip->b2 << 16; ip_proc |= ip->b3 << 8; ip_proc |= ip->b4; wltmp = list; while (1) { uint32_t p = 0; p = ip_proc; p &= wltmp->cidr.processed; if (p == wltmp->cidr.ip) { rv = snprintf(msgbuf, sizeof(msgbuf), "%hhu.%hhu.%hhu.%hhu %x/%d", ip->b1, ip->b2, ip->b3, ip->b4, wltmp->cidr.ip, wltmp->cidr.network); if (rv < 0) { syslog(LOG_ERR, "snprintf failed at line %d: %s", __LINE__, strerror(errno)); exit (1); } return 1; } if (wltmp->next == NULL) { break; } wltmp = wltmp->next; } return 0; } /* * SYNOPSIS: * static void get_ip_string( * const struct packet_info *ip * ); * * ARGUMENTS: * struct packet_info *ip Structure containing IP parts to construct * the ASCII representation from. * * RETURN VALUE: * (none) * * NOTES: * This function takes the data in the parameter "ip" and stores an ASCII * representation in the global variable "msgbuf." * It must be able to cope with "ip" being NULL. * */ static void get_ip_string(const struct packet_info *ip) { int rv; if (ip == NULL) { rv = sprintf(msgbuf, "-"); if (rv < 0) { syslog(LOG_ERR, "sprintf failed in line %d: %s", __LINE__, strerror(errno)); exit(1); } return; } rv = snprintf(msgbuf, sizeof(msgbuf), "%hhu.%hhu.%hhu.%hhu:%d.%d", ip->b1, ip->b2, ip->b3, ip->b4, ip->s_port,ip->d_port); if (rv < 0) { syslog(LOG_ERR, "snprintf failed in line %d: %s", __LINE__, strerror(errno)); exit(1); } return; } #ifdef USE_SOCKSTAT /* * SYNOPSIS: * void *pbl_sockstat_thread( * void *tdata * ); * * ARGUMENTS: * void *tdata Data to pass into the thread. This is unused * currently. * * RETURN VALUE: * This function always returns NULL. * * NOTES: */ void *pbl_sockstat_thread(void *tdata) { struct sockaddr_un sockinfo; FILE *sockfp = NULL; char buf[1024]={0}; time_t current_time; int master_sockfd, sockfd; int bindret, listenret, snprintfret; int sockinfolen; /* Delete any stray sockets left lying around. */ unlink(SOCKSTAT_PATH); /* Create a UNIX domain socket. */ master_sockfd = socket(PF_UNIX, SOCK_STREAM, 0); if (master_sockfd < 0) { syslog(LOG_ERR, "Error creating socket: %s", strerror(errno)); pthread_exit(NULL); } /* Bind our socket to the pathname. */ sockinfo.sun_family = AF_UNIX; strncpy(sockinfo.sun_path, SOCKSTAT_PATH, sizeof(sockinfo.sun_path)); bindret = bind(master_sockfd, (struct sockaddr *) &sockinfo, sizeof(sockinfo)); if (bindret < 0) { syslog(LOG_ERR, "Error binding to socket: %s", strerror(errno)); if (close(master_sockfd) < 0) { syslog(LOG_ERR, "%s:%d close() failed: %s", __FILE__,__LINE__, strerror(errno)); } pthread_exit(NULL); } /* Start listening for connections. */ listenret = listen(master_sockfd, 3); if (listenret < 0) { syslog(LOG_ERR, "Error listening on socket: %s", strerror(errno)); if (close(master_sockfd) < 0) { syslog(LOG_ERR, "%s:%d close() failed: %s", __FILE__,__LINE__, strerror(errno)); } if (unlink(SOCKSTAT_PATH) < 0) { syslog(LOG_ERR, "%s:%d removing socket failed: %s", __FILE__,__LINE__, strerror(errno)); } pthread_exit(NULL); } current_time = time(NULL); ctime_r(¤t_time, buf); while (1) { sockinfolen = sizeof(sockinfo); sockfd = accept(master_sockfd, (struct sockaddr *) &sockinfo, &sockinfolen); if (sockfd < 0) continue; sockfp = fdopen(sockfd, "w"); if (sockfp == NULL) { if (close(sockfd) < 0) { syslog(LOG_ERR, "%s:%d close() failed: %s", __FILE__,__LINE__, strerror(errno)); } continue; } fprintf(sockfp, "Running since: %s", buf); fprintf(sockfp, "Statistics:\n"); fprintf(sockfp, " Cache hits (accept): %d\n", statistics.cacheaccept); fprintf(sockfp, " Cache hits (reject): %d\n", statistics.cachereject); fprintf(sockfp, " DNS Whitelist hits: %d\n", statistics.whitelistblhits); fprintf(sockfp, " DNS Blacklist hits: %d\n", statistics.blacklistblhits); fprintf(sockfp, " Whitelist hits: %d\n", statistics.whitelisthits); fprintf(sockfp, " Blacklist hits: %d\n", statistics.blacklisthits); fprintf(sockfp, " Fall through hits: %d\n", statistics.fallthroughhits); fprintf(sockfp, " Total packets: %d\n", statistics.totalpackets); fclose(sockfp); } close(master_sockfd); if (close(master_sockfd) < 0) { syslog(LOG_ERR, "%s:%d close() failed: %s", __FILE__,__LINE__, strerror(errno)); } /* Cleanup sockets. */ if (unlink(SOCKSTAT_PATH) < 0) { syslog(LOG_ERR, "%s:%d removing socket failed: %s", __FILE__,__LINE__, strerror(errno)); } /* Terminate our thread without taking down the entire process. */ pthread_exit(NULL); /* This should never be reached. */ return(NULL); } /* * SYNOPSIS: * void pbl_init_sockstat(void); * * ARGUMENTS: * (none) * * RETURN VALUE: * (none) * * NOTES: */ void pbl_init_sockstat(void) { pthread_t pthread_data; int pthread_ret = 0; /* Create the thread to handle socket requests. */ pthread_ret = pthread_create( &pthread_data, NULL, pbl_sockstat_thread, NULL); if (pthread_ret < 0) { syslog(LOG_ERR, "pthread_create failed: %s", strerror(errno)); } return; } #endif