/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_SOCKSTAT #include #include #include #endif #ifdef HAVE_FIREDNS #include #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 # 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"); } #define INVALID_OCTET(x) x < 0 || x > 255 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; }; 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; struct config_entry *blacklistbl; struct config_entry *whitelistbl; struct config_entry *blacklist; struct config_entry *whitelist; }; static struct config conf = { 0, 0, 1, 0, LOG_DAEMON, 0, 0, 0, NULL, NULL, NULL, NULL }; 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); typedef struct facility { char *string; int num; } facility; static struct facility facenum[] = { {"auth", LOG_AUTH}, {"authpriv", LOG_AUTHPRIV}, {"cron", LOG_CRON}, {"daemon", LOG_DAEMON}, {"kern", LOG_KERN}, {"lpr", LOG_LPR}, {"mail", LOG_MAIL}, {"news", LOG_NEWS}, {"syslog", LOG_SYSLOG}, {"user", LOG_USER}, {"uucp", LOG_UUCP}, {"local0", LOG_LOCAL0}, {"local1", LOG_LOCAL1}, {"local2", LOG_LOCAL2}, {"local3", LOG_LOCAL3}, {"local4", LOG_LOCAL4}, {"local5", LOG_LOCAL5}, {"local6", LOG_LOCAL6}, {"local7", LOG_LOCAL7}, NULL }; /* * SYNOPSIS: * void daemonize(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 0) { cache_hash = packet_cache_hash(ip) % packet_cache_len; } if (cache_hash>0 && cache_hashcurrtime) { 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, conf.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, conf.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, conf.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, conf.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; unsigned 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) { config_t *config = NULL; config_setting_t *config_setting = NULL; struct ce *config_entry = NULL; int result = 0; int i = 0; const char *facstr = NULL; config_init(config); result = config_read_file(config, "test.config"); if (result == CONFIG_FALSE) { if (config_error_type(config) == CONFIG_ERR_PARSE) { fprintf (stderr, "Error parsing config file %s, line %d: %s\n", config_error_file(config), config_error_line(config), config_error_text(config)); } if (config_error_type(config) == CONFIG_ERR_FILE_IO) { fprintf (stderr, "Error reading config file: %s\n", config_error_text(config)); } exit(EXIT_FAILURE); } /* there are default, so I'm not checking return values. If it fails, * then we'll just stay with the default, whatever that might be. */ config_lookup_bool(config, "options.fallthroughaccept", &conf.default_accept); config_lookup_bool(config, "options.allownonport25", &conf.allow_non25); config_lookup_bool(config, "options.dryrun", &conf.dryrun); config_lookup_bool(config, "options.allownonsyn", &conf.allow_nonsyn); config_lookup_bool(config, "options.quiet", &conf.quiet); #ifdef USE_CACHE config_lookup_int(config, "cache.ttl", &packet_cache_ttl); if (packet_cache_ttl < 0) { packet_cache_ttl = USE_CACHE_DEF_TTL; fprintf(stderr, "config cache TTL negative - using default"); } config_lookup_int(config, "cache.len", &packet_cache_len); if (packet_cache_len < 0) { packet_cache_len = USE_CACHE_DEF_LEN; fprintf(stderr, "config size TTL negative - using default"); } #endif config_lookup_string(config, "log.facility", &facstr); i = 0; while (&facenum[i] != NULL) { if (strcasecmp(facenum[i].string, facstr) == 0) { conf.log_facility = facenum[i].num; break; } else { i++; } } config_lookup_int(config, "config.queueno", &conf.queueno); if (conf.queueno < 0) { conf.queueno = 1; fprintf(stderr, "queueno negative - using default"); } config_setting = config_lookup(config, "blacklistbl"); parse_config_bl_list(config_setting, 1); config_setting = config_lookup(config, "whitelistbl"); parse_config_bl_list(config_setting, 2); config_setting = config_lookup(config, "blacklist"); parse_config_bl_list(config_setting, 3); config_setting = config_lookup(config, "whitelist"); parse_config_bl_list(config_setting, 4); } parse_config_bl_list(config_setting_t *c, int type) { struct config_entry *ce, *tmp; int i = 0, len = 0; char *setting; #ifdef HAVE_FIREDNS size_t blacklistlen = 0; #endif len = config_setting_length(c); while (i++ < len) { setting = config_setting_get_string_elem(c, i); ce = malloc(sizeof(struct config_entry)); if (ce == NULL) { /* shouldn't happen... */ fprintf(stderr, "Failed to allocate memory for ce struct\n"); exit(EXIT_FAILURE); } ce->string = (char *)strdup(setting); ce->next = NULL; #ifdef HAVE_FIREDNS blacklistlen = strlen(ce->string); if (ce->string[blacklistlen - 1] == '.') { ce->string[blacklistlen - 1] = '\0'; } #endif switch (type) { case 1: if (conf.blacklistbl == NULL) { conf.blacklistbl = ce; continue; } else { tmp = conf.blacklistbl; } break; case 2: if (conf.whitelistbl == NULL) { conf.whitelistbl = ce; continue; } else { tmp = conf.whitelistbl; } case 3: if (parse_cidr(ce) == -1) { fprintf(stderr, "Error parsing CIDR in %s, ignoring\n", ce->string); free(ce->string); free(ce); continue; } if (conf.blacklist == NULL) { conf.blacklist = ce; continue; } else { tmp = conf.blacklist; } break; case 4: if (parse_cidr(ce) == -1) { fprintf(stderr, "Error parsing CIDR in %s, ignoring\n", ce->string); free(ce->string); free(ce); continue; } if (conf.whitelist == NULL) { conf.whitelist = ce; continue; } else { tmp = conf.whitelist; } break; } while (tmp->next != NULL) { tmp = tmp->next; } tmp->next = ce; } } /* * 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; } /* * 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. int i = 0; char *counter, *c1, *numptr; 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'; i = atoi(number); switch(sep) { case 0: numptr = &ce->ip.b1; break; case 1: numptr = &ce->ip.b2; break; case 2: numptr = &ce->ip.b3; break; case 3: numptr = &ce->ip.b4; break; default: /* shouldn't happen. FIXME: add error */ ; } ce->ip.b1 = i; if (INVALID_OCTET(*numptr)) { return -1; } 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