mxallowd-1.9/0000755000014500017510000000000011731623140012475 5ustar michaelstaffmxallowd-1.9/src/0000755000014500017510000000000011731622777013303 5ustar michaelstaffmxallowd-1.9/src/nfq.c0000644000014500017510000001012511731622777014232 0ustar michaelstaff#include #include #include #include #include #include #include #include #include "mxallowd.h" #include "log.h" #include "whitelist.h" #define NFQ_PACKET_BUFFER_SIZE 4096 /* * Helper function to decide what to do with packets sent by iptables * */ static int handlePacket(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfdata, void *packet_buffer) { (void)nfmsg; (void)packet_buffer; struct nfqnl_msg_packet_hdr *header; char *payload = NULL; int payload_length; if (!(header = nfq_get_msg_packet_hdr(nfdata))) /* Skip the packet if the header could not be obtained */ return -1; if ((payload_length = nfq_get_payload(nfdata, (char**)&payload)) == -1) /* Skip the packet if it has no payload */ return -1; /* OK, we'll shortly need to decide, so let's clean up the whitelists here */ cleanup_whitelist(); char dest_address[INET6_ADDRSTRLEN+1], source_address[INET6_ADDRSTRLEN+1]; memset(dest_address, '\0', INET6_ADDRSTRLEN+1); memset(source_address, '\0', INET6_ADDRSTRLEN+1); struct iphdr* ip_header = (struct iphdr*)payload; if (ip_header->version == IPVERSION) { if (inet_ntop(AF_INET, &(ip_header->daddr), dest_address, sizeof(dest_address)) == NULL || inet_ntop(AF_INET, &(ip_header->saddr), source_address, sizeof(source_address)) == NULL) return -1; } else { #ifdef IPV6 struct ip6_hdr *ip6_header = (struct ip6_hdr*)payload; if (inet_ntop(AF_INET6, &(ip6_header->ip6_dst), dest_address, sizeof(dest_address)) == NULL || inet_ntop(AF_INET6, &(ip6_header->ip6_src), source_address, sizeof(source_address)) == NULL) /* Address could not be read, packet has to be malformed */ return -1; #endif } /* Let's see if the packet was sent to MX1 */ if (is_included(fake_mailservers, dest_address)) { /* This packet was sent to MX1, whitelist the sender for MX2 */ slog("Successful connection from %s to a fake mailserver, adding to whitelist\n", source_address); add_to_whitelist(source_address, NULL, false, 0, NULL); } else if (is_included(real_mailservers, dest_address)) { /* This packet was sent to MX2, let's see what to do */ if (!is_whitelisted(source_address, true)) { /* The sender is not whitelisted, so we drop the packet */ slog("Dropping connection attempt from %s to a real mailserver\n", source_address); blocked_attempts++; return nfq_set_verdict(qh, ntohl(header->packet_id), NF_DROP, 0, NULL); } slog("Successful connection from %s to a real mailserver\n", source_address); } return nfq_set_verdict(qh, ntohl(header->packet_id), NF_ACCEPT, 0, NULL); } void nfq_init() { /* Create a libnetfilterqueue-handle */ struct nfq_handle *nfq = nfq_open(); if (nfq == NULL) diem("Error creating an nfq-handle"); /* Unbind to clean up previous instances */ #ifdef BROKEN_UNBIND (void)nfq_unbind_pf(nfq, AF_INET); #else if (nfq_unbind_pf(nfq, AF_INET) != 0) diem("Error unbinding AF_INET"); #endif /* Bind to IPv4 */ if (nfq_bind_pf(nfq, AF_INET) != 0) diem("Error binding to AF_INET"); #ifdef IPV6 /* Unbind to clean up previous instances */ #ifdef BROKEN_UNBIND (void)nfq_unbind_pf(nfq, AF_INET6); #else if (nfq_unbind_pf(nfq, AF_INET6) != 0) diem("Error unbinding AF_INET6"); #endif /* Bind to IPv6 */ if (nfq_bind_pf(nfq, AF_INET6) != 0) diem("Error binding to AF_INET6"); #endif /* Create queue */ struct nfq_q_handle *qh; if (!(qh = nfq_create_queue(nfq, queue_num, &handlePacket, NULL))) dief("Error creating queue %d\n", queue_num); /* We need a copy of the packet */ if (nfq_set_mode(qh, NFQNL_COPY_PACKET, NFQ_PACKET_BUFFER_SIZE) == -1) diem("Cannot set mode to NFQNL_COPY_PACKET"); char *packet_buffer = malloc(sizeof(unsigned char) * (NFQ_PACKET_BUFFER_SIZE + 1)); if (packet_buffer == NULL) diep("malloc()"); int fd = nfq_fd(nfq), rv; /* Read a packet in blocking mode */ while ((rv = recv(fd, packet_buffer, NFQ_PACKET_BUFFER_SIZE, 0)) >= 0) nfq_handle_packet(nfq, packet_buffer, rv); nfq_destroy_queue(qh); nfq_close(nfq); free(packet_buffer); } mxallowd-1.9/src/log.c0000644000014500017510000000334711731622777014237 0ustar michaelstaff/* * mxallowd * (c) 2007-2009 Michael Stapelberg * * See mxallowd.c for description, website and license information * */ #include #include #include #include #include #include #include #include #include "mxallowd.h" void slog(char *format, ...) { va_list ap; if (quiet) return; va_start(ap, format); if (to_stdout) { /* When not logging to syslog, we generate a timestamp */ char timebuf[23]; time_t t = time(NULL); struct tm *tmp = localtime(&t); strftime(timebuf, sizeof(timebuf), "%d.%m.%Y %H:%M:%S - ", tmp); printf("%s", timebuf); vprintf(format, ap); } else vsyslog(LOG_CONS | LOG_PID | LOG_DAEMON, format, ap); va_end(ap); } /* * Replaces perror() by logging to syslog if enabled * */ void slogerror(char *message) { if (to_stdout) fprintf(stderr, "%s: %s\n", message, strerror(errno)); else syslog(LOG_CONS | LOG_PID | LOG_DAEMON | LOG_PERROR, "%s: %s\n", message, strerror(errno)); } /* * Print the given message to stdout/syslog and exit(1) * */ void diem(char *message) { if (to_stdout) fprintf(stderr, "%s\n", message); else syslog(LOG_CONS | LOG_PID | LOG_DAEMON | LOG_PERROR, "%s\n", message); exit(1); } /* * Print the given message and errstr(errno) (like perror) to stdout/syslog and exit(1) * */ void diep(char *message) { slogerror(message); exit(1); } /* * Print the given message string (like printf) to stdout/syslog and exit(1) * */ void dief(char *format, ...) { va_list ap; va_start(ap, format); if (to_stdout) { vfprintf(stderr, format, ap); } else vsyslog(LOG_CONS | LOG_PID | LOG_DAEMON, format, ap); va_end(ap); exit(1); } mxallowd-1.9/src/whitelist.c0000644000014500017510000000770511731622777015474 0ustar michaelstaff/* * mxallowd * (c) 2007-2009 Michael Stapelberg * * See mxallowd.c for description, website and license information * */ #include #include #include #include #include #ifdef PF #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif #include "mxallowd.h" #include "log.h" #define _WHITELIST_INTERNAL #include "whitelist.h" #undef _WHITELIST_INTERNAL bool is_whitelisted(char *ip_address, bool useIt) { if (root == NULL) return false; struct whitelist_entry *cur = root; do { if (cur->ip_address != NULL && strcmp(cur->ip_address, ip_address) == 0) { if (useIt) cur->used = true; return true; } } while ((cur = cur->next) != NULL); return false; } /* * Doesn't free() old entries but invalidates them, they'll get reused * * */ void cleanup_whitelist() { if (root != NULL) { time_t current_time = time(NULL); struct whitelist_entry *cur = root; do { if (cur->ip_address != NULL && (current_time - cur->added) > allow_time) { /* If the entry is too old, invalidate it by freeing ip_address */ slog("Cleaning %s (RDNS: %s) from whitelist (timeout)\n", cur->ip_address, (cur->rdns != NULL ? cur->rdns : "unresolvable")); if (!cur->is_child) { if (cur->used) successful_connects++; else direct_to_fake++; } free(cur->ip_address); cur->ip_address = NULL; if (cur->rdns != NULL) { free(cur->rdns); cur->rdns = NULL; } } } while ((cur = cur->next) != NULL); } } /* * Adds the given ip address (with rdns if not NULL) to whitelist * is_child = flag whether the entry has to be counted as one mailserver * or whether it's just another IP of this mailserver * */ void add_to_whitelist(char *ip_address, char *rdns, bool is_child, int af, const void *source_addr) { if (is_whitelisted(ip_address, false)) return; struct whitelist_entry *new_entry = malloc(sizeof(struct whitelist_entry)); new_entry->next = NULL; new_entry->ip_address = strdup(ip_address); new_entry->rdns = (rdns != NULL ? strdup(rdns) : NULL); new_entry->rdns_tried = (rdns != NULL); new_entry->added = time(NULL); new_entry->is_child = is_child; new_entry->used = false; if (root == NULL) root = new_entry; else { struct whitelist_entry *cur = root; while (cur->next != NULL && cur->ip_address != NULL) cur = cur->next; if (cur->ip_address == NULL) { /* This is a cleaned up entry, overwrite values */ cur->ip_address = new_entry->ip_address; cur->rdns = new_entry->rdns; cur->rdns_tried = new_entry->rdns_tried; cur->added = new_entry->added; cur->is_child = is_child; cur->used = false; free(new_entry); } else cur->next = new_entry; } #ifdef PF struct pfioc_table table; struct pfr_addr addr; memset(&table, '\0', sizeof(struct pfioc_table)); memset(&addr, '\0', sizeof(struct pfr_addr)); table.pfrio_buffer = &addr; table.pfrio_esize = sizeof(struct pfr_addr); table.pfrio_size = 1; strcpy(table.pfrio_table.pfrt_name, "mx-white"); addr.pfra_af = af; addr.pfra_net = (af == AF_INET ? 32 : 128); if (af == AF_INET) memcpy(&(addr.pfra_ip4addr), ((struct sockaddr_in*)source_addr)->sin_addr, sizeof(struct in_addr)); else memcpy(&addr.pfra_ip6addr, ((struct sockaddr_in6*)source_addr)->sin6_addr, sizeof(struct in6_addr)); if (ioctl(pffd, DIOCRADDADDRS, &table) == -1) slogerror("Couldn't ioctl() on /dev/pf\n"); if (table.pfrio_nadd != 1) slog("Adding IP to table failed, it probably is already whitelisted\n"); #endif if (rdns_whitelist) { /* Inform the resolver-thread of the new entry to resolve */ pthread_mutex_lock(&resolv_thread_mutex); pthread_cond_broadcast(&resolv_new_cond); pthread_mutex_unlock(&resolv_thread_mutex); } } mxallowd-1.9/src/pf.c0000644000014500017510000000754311731622777014065 0ustar michaelstaff/* * All functions for BSD's pf are here. * * This file is based largely on OpenBSD 4.1's spamlogd.c. * * Copyright (c) 2008-2009 Michael Stapelberg * Copyright (c) 2006 Henning Brauer * Copyright (c) 2006 Berk D. Demir. * Copyright (c) 2004-2007 Bob Beck. * Copyright (c) 2001 Theo de Raadt. * Copyright (c) 2001 Can Erkin Acar. * All rights reserved * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mxallowd.h" #include "log.h" #include "whitelist.h" pcap_t *hpcap = NULL; char *pflogif = "pflog0"; char *pcap_filter = "port 25"; char errbuf[PCAP_ERRBUF_SIZE]; #define PCAPSNAP 512 #define PCAPTIMO 500 /* ms */ #define PCAPOPTZ 1 /* optimize filter */ #define MIN_PFLOG_HDRLEN 45 static void handlePacket(u_char *user, const struct pcap_pkthdr *h, const u_char *sp) { sa_family_t af; u_int8_t hdrlen; u_int32_t caplen = h->caplen; const struct ip *ip = NULL; const struct ip6_hdr *ip6 = NULL; const struct pfloghdr *hdr; cleanup_whitelist(); char dest_address[INET6_ADDRSTRLEN+1], source_address[INET6_ADDRSTRLEN+1]; memset(dest_address, '\0', INET6_ADDRSTRLEN+1); memset(source_address, '\0', INET6_ADDRSTRLEN+1); /* Sanity checks for the packet */ hdr = (const struct pfloghdr *)sp; if (hdr->length < MIN_PFLOG_HDRLEN) { slog("invalid pflog header length (%u/%u). " "packet dropped.\n", hdr->length, MIN_PFLOG_HDRLEN); return; } hdrlen = BPF_WORDALIGN(hdr->length); if (caplen < hdrlen) { slog("pflog header larger than caplen (%u/%u). " "packet dropped.\n", hdrlen, caplen); return; } af = hdr->af; const void *srcptr, *dstptr; if (af == AF_INET) { ip = (const struct ip *)(sp + hdrlen); srcptr = &(ip->ip_src); dstptr = &(ip->ip_dst); } else if (af == AF_INET6) { ip6 = (const struct ip6_hdr *)(sp + hdrlen); srcptr = &(ip6->ip6_src); dstptr = &(ip6->ip6_dst); } else return; inet_ntop(af, srcptr, source_address, sizeof(source_address)); inet_ntop(af, dstptr, dest_address, sizeof(dest_address)); /* Let's see if the packet was sent to MX1 */ if (is_included(fake_mailservers, dest_address)) { /* This packet was sent to MX1, whitelist the sender for MX2 */ slog("Successful connection from %s to a fake mailserver, adding to whitelist\n", source_address); add_to_whitelist(source_address, NULL, false, af, srcptr); } else if (is_included(real_mailservers, dest_address)) { /* This packet was sent to MX2, let's see what to do */ if (hdr->action == PF_PASS) slog("Successful connection from %s to a real mailserver\n", source_address); else { /* The sender is not whitelisted, so the packet was dropped */ slog("Dropping connection attempt from %s to a real mailserver\n", source_address); blocked_attempts++; } } } void pcap_init() { pcap_handler ph = handlePacket; struct bpf_program bpfp; if ((hpcap = pcap_open_live(pflogif, PCAPSNAP, 1, PCAPTIMO, errbuf)) == NULL) dief("Failed to initialize: %s\n", errbuf); if (pcap_datalink(hpcap) != DLT_PFLOG) { pcap_close(hpcap); dief("Invalid datalink type\n"); } if (pcap_compile(hpcap, &bpfp, pcap_filter, PCAPOPTZ, 0) == -1 || pcap_setfilter(hpcap, &bpfp) == -1) dief("%s\n", pcap_geterr(hpcap)); pcap_freecode(&bpfp); #ifdef BIOCLOCK if (ioctl(pcap_fileno(hpcap), BIOCLOCK) < 0) dief("BIOCLOCK: %s\n", strerror(errno)); #endif pcap_loop(hpcap, -1, ph, NULL); } mxallowd-1.9/src/mxallowd.c0000644000014500017510000002100711731622777015276 0ustar michaelstaff/* * mxallowd * (c) 2007-2009 Michael Stapelberg * http://michael.stapelberg.de/mxallowd * * * mxallowd is a daemon which uses libnetfilter_queue and iptables * to allow (or deny) connections to a mailserver (or similar * application) if the remote host hasn't connected to a * fake daemon before. * * This is an improved version of the so-called nolisting * (see http://www.nolisting.org/). The assumption is that spammers * are not using RFC 2821-compatible SMTP-clients and are * sending fire-and-forget spam (directly to the first or second * MX-entry without retrying on error). This direct access is * blocked with mxallowd, you'll only get a connection if you * retry. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * Or, point your browser to http://www.gnu.org/copyleft/gpl.html * * Works owned by the mxallowd project are granted a special exemption * to clause 2(b) so that the BSD-specific parts (src/pf.c, that is) * can be licensed using their original BSD license. * */ #include #include #include #include #include #include #include #include #include #include #include #include #define _MXALLOWD_INTERNAL #include "mxallowd.h" #undef _MXALLOWD_INTERNAL #include "log.h" #include "config.h" #include "whitelist.h" #include "resolve.h" #ifdef PF #include "pf.h" #else #include "nfq.h" #endif #define MXALLOWD_VERSION "1.9" /* * Adds a mailserver to the list of fake or real mailservers (configuration) * */ void add_mailserver(struct mailserver_list **root, char *mailserver_ip) { struct mailserver_list *new = malloc(sizeof(struct mailserver_list)); if (new == NULL) diep("malloc()"); new->ip_address = strdup(mailserver_ip); new->next = NULL; if (*root == NULL) *root = new; else { struct mailserver_list *cur = *root; while (cur->next != NULL) cur = cur->next; cur->next = new; } } /* * Checks whether a mailserver is included in the list of fake or real mailservers * */ bool is_included(struct mailserver_list *root, char *ip_address) { struct mailserver_list *cur = root; do { if (strcmp(cur->ip_address, ip_address) == 0) return true; } while ((cur = cur->next) != NULL); return false; } /* * Handle SIGHUP (reload configuration) * */ void handle_sighup(/*@unused@*/int sig) { slog("Reloading configuration (upon SIGHUP)\n"); (void)read_configuration(configfile); } /* * Handle SIGUSR1 (print out some statistics) * */ void handle_sigusr1(/*@unused@*/int sig) { cleanup_whitelist(); slog("Statistics: %lu blocked connection attempts, %lu successful connections, %lu direct-to-fake-mx\n", blocked_attempts, successful_connects, direct_to_fake); } void print_help(char *executable) { printf("\n\ mxallowd %s (c) 2007-2008 Michael Stapelberg\n\ (IPv6 %s in this build)\n\ \n\ Syntax: %s [-F] [-d] [-c configfile] [-t 3600] [-s] " "[-q]" "\n\t -f 192.168.1.254 -r 192.168.1.2 -n 23\n\ \n\ -c\t--config\n\ \tSpecify the path to a configuration file (default\n\ \t/etc/mxallowd.conf)\n\n\ -f\t--fake-mailserver\n\ \tSpecify which IP-address of the fake mailserver\n\ \t(connecting will whitelist you for the real server)\n\n\ -F\t--foreground\n\ \tDo not fork into background, stay on console\n\n\ -r\t--real-mailserver\n\ \tSpecify which IP-address the real mailserver has\n\n\ -t\t--whitelist-time\n\ \tSpecify the amount of time (in seconds) until an\n\ \tIP-address will be removed from the whitelist\n\n\ -d\t--no-rdns-whitelist\n\ \tDisable whitelisting all IP-addresses that have\n\ \tthe same RDNS as the connecting one (for google\n\ \tmail it is necessary to enable this!)\n\n\ -s\t--stdout\n\ \tLog to stdout, not to syslog\n\n\ -q\t--quiet\n\ \tDon't log anything but errors\n\n" #ifdef NFQUEUE "-n\t--queue-num\n\ \tThe queue number which will be used (--queue-num in NFQUEUE target)\n\n" #else "-p\t--pflog-interface\n\ \tSpecify the pflog-interface which you configured in pf.\n\n" "-l\t--pcap-filter\n\ \tSpecify the filter for pcap. (default is \"port 25\")\n\n" #endif "-h\t--help\n\ \tDisplay this help\n\ \n", MXALLOWD_VERSION, #ifdef IPV6 "supported", #else "not supported", #endif executable); } int main(int argc, char **argv) { /* Parse command line options */ int o, option_index = 0; static struct option long_options[] = { {"fake-mailserver", required_argument, 0, 'f'}, {"foreground", required_argument, 0, 'F'}, {"real-mailserver", required_argument, 0, 'r'}, {"whitelist-time", required_argument, 0, 't'}, {"no-rdns-whitelist", no_argument, 0, 'd'}, {"stdout", no_argument, 0, 's'}, {"help", no_argument, 0, 'h'}, {"config", required_argument, 0, 'c'}, #ifdef NFQUEUE {"queue-num", required_argument, 0, 'n'}, #else {"pflog-interface", required_argument, 0, 'p'}, {"pcap-filter", required_argument, 0, 'l'}, #endif {"quiet", no_argument, 0, 'q'}, {0, 0, 0, 0} }; #ifdef NFQUEUE char *options_string = "f:r:t:c:n:shdFq"; #else char *options_string = "f:r:t:c:p:l:shdFq"; #endif /* First loop is for looking if -c exists and reading the configuration */ while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { if ((char)o == 'c') { configfile = optarg; break; } else continue; } /* If the parameter was not given, try to read the default config */ if (!read_configuration(configfile) && argc == 1) { fprintf(stderr, "Error: Missing arguments and no configuration file could be read\n"); print_help(argv[0]); return 1; } /* Restart parsing */ #ifdef __GLIBC__ optind = 0; #else optind = 1; #endif #ifdef HAVE_OPTRESET /* For BSD */ optreset = 1; #endif while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { switch ((char)o) { case 'f': add_mailserver(&fake_mailservers, optarg); break; case 'F': stay_in_foreground = true; break; case 'r': add_mailserver(&real_mailservers, optarg); break; case 't': allow_time = atoi(optarg); break; case 's': to_stdout = true; break; case 'd': rdns_whitelist = false; break; case 'h': print_help(argv[0]); return 0; case 'q': quiet = true; break; case 'n': queue_num = atoi(optarg); break; #ifdef PF case 'p': pflogif = strdup(optarg); break; case 'l': pcap_filter = strdup(optarg); break; #endif } } if (fake_mailservers == NULL || real_mailservers == NULL) { fprintf(stderr, "Error: fake and real mailserver have to be specified\n"); print_help(argv[0]); return 1; } #ifdef NFQUEUE if (queue_num == -1) { fprintf(stderr, "Error: queue-num has to be specified\n"); print_help(argv[0]); return 1; } #endif if (!stay_in_foreground) { slog("Daemonizing...\n"); if (daemon(0, 0) < 0) slog("Cannot daemonize\n"); int fd; if ((fd = open("/var/run/mxallowd.pid", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) fprintf(stderr, "Error: Could not create /var/run/mxallowd.pid\n"); else { char buffer[256]; snprintf(buffer, 255, "%d", getpid()); write(fd, buffer, strlen(buffer)); close(fd); } } /* Setup signal handlers */ if (signal(SIGHUP, handle_sighup) == SIG_ERR) diep("signal(SIGHUP)"); if (signal(SIGUSR1, handle_sigusr1) == SIG_ERR) diep("signal(SIGUSR1)"); if (!quiet) { slog("mxallowd %s starting...\n", MXALLOWD_VERSION); struct mailserver_list *cur = fake_mailservers; do { slog("Fake Mailserver: %s\n", cur->ip_address); } while ((cur = cur->next) != NULL); cur = real_mailservers; do { slog("Real Mailserver: %s\n", cur->ip_address); } while ((cur = cur->next) != NULL); } /* Create resolver thread if necessary */ pthread_t resolv_thread; if (rdns_whitelist && pthread_create(&resolv_thread, NULL, resolve_thread, NULL) != 0) diep("Cannot create thread"); #ifdef PF if ((pffd = open("/dev/pf", O_RDWR)) < 0) diep("Couldn't open /dev/pf\n"); pcap_init(); #else nfq_init(); #endif return 0; } mxallowd-1.9/src/config.c0000644000014500017510000000717211731622777014723 0ustar michaelstaff/* * mxallowd * (c) 2007-2009 Michael Stapelberg * * See mxallowd.c for description, website and license information * */ #include #include #include #include #include "mxallowd.h" #include "log.h" #ifdef PF #include "pf.h" #endif #define INT_LENGTH 10 /* * Checks if the given string can be converted to an integer * */ bool isInteger(char *str) { /* * If the string has no content, it can't be an integer. * If it is too long to handle for our int, better return * false also... (7 bits because one is used for sign) * */ if (strlen(str) == 0 || strlen(str) > INT_LENGTH) return false; char *c; for (c = str; *c != 0; c++) if (*c < '0' || *c > '9') return false; return true; } /* * Loads configuration from configfile * */ void getNextConfigEntry(FILE *config, char **destName, char **destValue, char *wholeBuffer, int wholeBufferSize) { if (fgets(wholeBuffer, wholeBufferSize, config) == wholeBuffer) { char *c = wholeBuffer; /* Skip whitespaces in the beginning */ while ((*c == ' ' || *c == '\t') && *c != 0) c++; *destName = c; while (*c != 0 && *c != '\r' && *c != '\n' && *c != ' ' && *c != '\t') c++; /* Terminate string as soon as whitespaces begin or it's terminated anyway */ *c = 0; c++; /* Same for the value: strip whitespaces */ while ((*c == ' ' || *c == '\t') && *c != 0) c++; *destValue = c; while (*c != 0 && *c != '\r' && *c != '\n' && *c != ' ' && *c != '\t') c++; *c = 0; } } /* * Checks if the given value is not null and an integer and logs to syslog/stdout * if not * */ bool checkConfigValue(char *value, char *name) { if (value == NULL) slog("Option \"%s\" requires argument\n", name); else if (isInteger(value)) return true; else slog("Argument for \"%s\" is not an integer (%s)\n", value, name); return false; } /* * Reads the configuration from the given file * */ bool read_configuration(char *configfile) { FILE *config = fopen(configfile, "r"); if (config == NULL) { slogerror("Could not open configfile"); return false; } char *destName = NULL, *destValue = NULL, wholeBuffer[1024]; while (!feof(config)) { getNextConfigEntry(config, &destName, &destValue, wholeBuffer, 1024); /* No more entries? We're done! */ if (destName == NULL) break; /* Skip comments and empty lines */ if (destName[0] == '#' || strlen(destName) < 3) continue; if (strcasecmp(destName, "fake-mailserver") == 0) { if (destValue != NULL) add_mailserver(&fake_mailservers, destValue); else slog("Error: Option \"fake-mailserver\" requires an argument\n"); } else if (strcasecmp(destName, "real-mailserver") == 0) { if (destValue != NULL) add_mailserver(&real_mailservers, destValue); else slog("Error: Option \"real-mailserver\" requires an argument\n"); } else if (strcasecmp(destName, "whitelist-time") == 0) { if (checkConfigValue(destValue, destName)) allow_time = atoi(destValue); #ifdef NFQUEUE } else if (strcasecmp(destName, "queue-num") == 0) { if (checkConfigValue(destValue, destName)) queue_num = atoi(destValue); #else } else if (strcasecmp(destName, "pflog-interface") == 0) { pflogif = strdup(destValue); } else if (strcasecmp(destName, "pcap-filter") == 0) { pcap_filter = strdup(destValue); #endif } else if (strcasecmp(destName, "no-rdns-whitelist") == 0) { rdns_whitelist = false; } else if (strcasecmp(destName, "stdout") == 0) { to_stdout = true; } else if (strcasecmp(destName, "quiet") == 0) { quiet = true; } else slog("Unknown configfile option: %s\n", destName); destName = destValue = NULL; } fclose(config); return true; } mxallowd-1.9/src/resolve.c0000644000014500017510000000610611731622777015131 0ustar michaelstaff/* * mxallowd * (c) 2007-2009 Michael Stapelberg * * See mxallowd.c for description, website and license information * */ #include #include #include #include #include #include #include #include "mxallowd.h" #include "whitelist.h" #include "log.h" /* * Resolves the given IP-address using gethostbyaddr() and returns the hostname * or NULL if the IP-address could not be resolved * */ char *rdns_for_ip(char *ip_address) { struct in_addr address4; struct hostent *lookup = NULL; /* Try resolving the address using IPv4, if that fails, it's probably IPv6 */ if (inet_pton(AF_INET, ip_address, &address4) > 0) lookup = gethostbyaddr((const char *)&address4, sizeof(struct in_addr), AF_INET); #ifdef IPV6 else { struct in6_addr address6; (void)inet_pton(AF_INET6, ip_address, &address6); lookup = gethostbyaddr((const char *)&address6, sizeof(struct in6_addr), AF_INET6); } #endif if (lookup == NULL) { if (h_errno != HOST_NOT_FOUND && h_errno != NO_ADDRESS && h_errno != NO_DATA) { switch (h_errno) { case NO_RECOVERY: fprintf(stderr, "Non-recoverable error during gethostbyaddr()\n"); break; case TRY_AGAIN: fprintf(stderr, "Temporary error resolving using gethostbyaddr()\n"); break; default: fprintf(stderr, "Unknown error (code %d) during gethostbyaddr()\n", h_errno); break; } } return NULL; } return lookup->h_name; } /* * This thread does all the resolving in order not to block the main thread which should * respond as fast as possible to netfilter_queue-requests. * */ void *resolve_thread(void *data) { struct whitelist_entry *cur; char *rdns; char address[INET6_ADDRSTRLEN+1]; while (1) { pthread_mutex_lock(&resolv_thread_mutex); pthread_cond_wait(&resolv_new_cond, &resolv_thread_mutex); pthread_mutex_unlock(&resolv_thread_mutex); cur = root; do { if (cur->ip_address == NULL || cur->rdns_tried) continue; rdns = rdns_for_ip(cur->ip_address); slog("Resolved %s to %s\n", cur->ip_address, rdns); cur->rdns_tried = true; if (rdns == NULL) continue; rdns = strdup(rdns); cur->rdns = rdns; if (!rdns_whitelist) continue; /* Resolve the hostname to get further IP addresses */ struct addrinfo hints, *res, *res0; int error; memset(&hints, 0, sizeof(hints)); #ifndef IPV6 hints.ai_family = AF_INET; #endif hints.ai_socktype = SOCK_STREAM; if ((error = getaddrinfo(rdns, "smtp", &hints, &res0)) != 0) { fprintf(stderr, "Could not resolve \"%s\":smtp: %s\n", rdns, gai_strerror(error)); continue; } for (res = res0; res; res = res->ai_next) { (void)memset(address, '\0', INET6_ADDRSTRLEN+1); getnameinfo(res->ai_addr, res->ai_addrlen, address, sizeof(address), NULL, 0, NI_NUMERICHOST); if (strcmp(address, cur->ip_address) != 0) add_to_whitelist(address, rdns, true, res->ai_family, res->ai_addr); } /* Avoid unequal cache times for the new entries and the original one */ cur->added = time(NULL); } while ((cur = cur->next) != NULL); } } mxallowd-1.9/README0000644000014500017510000000246411731622777013402 0ustar michaelstaff mxallowd (c) 2007-2008 Michael Stapelberg http://michael.stapelberg.de/mxallowd mxallowd is a daemon which uses libnetfilter_queue to allow (or deny) connections to a mailserver (or similar application) if the remote host hasn't connected to a fake daemon before. This is an improved version of the so-called nolisting (see http://www.nolisting.org/). The assumption is that spammers are not using RFC 2821-compatible SMTP-clients and are sending fire-and-forget spam (directly to the first or second MX-entry without retrying on error). This direct access is blocked with mxallowd, you'll only get a connection if you retry. 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; in version 2 of the License. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Or, point your browser to http://www.gnu.org/copyleft/gpl.html mxallowd-1.9/configure0000755000014500017510000000740711731622777014433 0ustar michaelstaff#!/bin/sh # Configuration script for mxallowd # (c) 2007-2008 Michael Stapelberg _call="$*" INCLUDE_PATHS="/usr/include /usr/local/include" LIBRARY_PATHS="/usr/lib /usr/local/lib" # Temporary variable which will be set after need_file() succeeds DIRNAME="" show_help() { cat << EOF Usage: $0 [OPTIONS] Options: --enable-debug Enable compiling debug-symbols [disable debug] --dont-optimize Disable optimization (-O2) [optimize] --broken-unbind Enable when using Kernel >=2.6.23 [disabled] --disable-ipv6 Don't compile IPv6-support [enable] --prefix=[dir] Install to dir/{sbin,share} [/usr/local] --sysconfdir=[dir] Install configfiles to dir/ [PREFIX/etc] --mandir=[dir] Install manpages to dir/man1 [PREFIX/share/man] EOF exit 0 } # Checks if the file is in one of the given paths and sets its path in $DIRNAME need_file() { for i in $1; do [ -f "${i}/${2}" ] && { DIRNAME=$(dirname "${i}/${2}"); return 0; } done return 1 } # Defaults debug="no" optimize="yes" broken_unbind="no" disable_ipv6="no" prefix="/usr/local" sysconfdir="${prefix}/etc" mandir="${prefix}/share/man" queuedef="NFQUEUE" lib="-lnetfilter_queue" # Parse all options for ac_option do case "$ac_option" in --help|-help|-h) show_help;; --enable-debug) debug="yes";; --dont-optimize) optimize="no";; --broken-unbind) broken_unbind="yes";; --disable-ipv6) disable_ipv6="yes";; --prefix=*) prefix=$(echo $ac_option | cut -d '=' -f 2) sysconfdir="${prefix}/etc" mandir="${prefix}/share/man";; --sysconfdir=*) sysconfdir=$(echo $ac_option | cut -d '=' -f 2);; --mandir=*) mandir=$(echo $ac_option | cut -d '=' -f 2);; *) echo "Unknown option: $ac_option" exit 1;; esac done INSTALL=$(which install) || { echo "ERROR: \"install\" not found!" exit 1 } case "$(uname)" in *BSD) echo "Configuring on *BSD, assuming pf is available..." LIBPATH="" queuedef="PF" lib="-lpcap" ;; Linux) echo "Searching libnetfilter_queue.h..." need_file "${INCLUDE_PATHS}" "libnetfilter_queue.h" || need_file "${INCLUDE_PATHS}" "libnetfilter_queue/libnetfilter_queue.h" if [ $? -eq 1 ]; then echo -e "\nWARNING: libnetfilter_queue.h was not found. Please install package libnetfilter-queue-dev or similar (depends on your distribution)." fi echo "LIBPATH=\"-I${DIRNAME}\"" > Makefile need_file "${LIBRARY_PATHS}" "libnetfilter_queue.a" if [ "$?" -eq 1 ]; then echo "libnetfilter_queue was not found. Please install package libnetfilter-queue1 or similar (depends on your distribution)." exit 1 fi ;; *) echo "uname says this is neither BSD nor Linux." echo "mxallowd wasn't tested on your system and most likely it will not work." echo "If you feel like porting mxallowd, feel free to edit configure." esac if [ "$debug" = "yes" -a "$optimize" = "yes" ]; then echo "WARNING: You cannot use debugging AND optimization, turning off optimization..." optimize="no" fi echo "Enable debugging symbols... $debug" echo "Optimization (-O2 -s)... $optimize" echo "Broken unbind... $broken_unbind" echo "Disable IPv6... $disable_ipv6" if [ "$broken_unbind" = "yes" ]; then unb="-DBROKEN_UNBIND" else unb="" fi echo > Makefile if [ "$debug" = "yes" ]; then echo "CC_FLAGS=-g $unb" >> Makefile else if [ "$optimize" = "yes" ]; then echo "CC_FLAGS=-O2 -s $unb" >> Makefile else echo "CC_FLAGS=$unb" >> Makefile fi fi if [ "${disable_ipv6}" = "no" ]; then echo "V6DEF=-DIPV6" >> Makefile else echo "V6DEF=" >> Makefile fi echo "LIBS=${lib} -lpthread" >> Makefile echo "PREFIX=${prefix}" >> Makefile echo "SYSCONFDIR=${sysconfdir}" >> Makefile echo "MANDIR=${mandir}" >> Makefile if [ "${queuedef}" = "PF" ]; then echo "FILTERSRC=src/pf.o" >> Makefile else echo "FILTERSRC=src/nfq.o" >> Makefile fi echo "QUEUEDEF=-D${queuedef} CC=${CC:=cc} INSTALL=${INSTALL}" >> Makefile cat Makefile.in >> Makefile mxallowd-1.9/scheme1.png0000644000014500017510000003277711731622777014567 0ustar michaelstaffPNG  IHDRf*}iCCPICC Profilexڭy8_I컐,c'f 3fc %JDP(K"T!RJ43Tw{yu9s?rs9s=Gr8`fssa^ DP l__sS`bp_u(xJ+qѠ!d&oT:!L 8byL?|QLмp0s $D@q#~?7@&Jl>d?Zi?2E+G&`1U>;lL;"HMuC05`||f`(b0֯I_0Xmο L KMj8x"##(2z zE$'kΓEBXU+ڿ5en\xΏVde92'yUTՅ5\4k͡׍sWAS3eU}DC[1~ZLGKk[I8O;̇?//)ІP,r8fN*$QhwQџbV)VKT;$qTqGN'矾ua4sY7WfgU^ʮi}pˏk~sMXDT 2r|]M|HՇ[5ڵ\o]q# QrY_<0y(HUԓO;<ZקPWvKCg^; i8Z>v~<{kSS3O>xuIs,/Ys/_W0+ߞ~]-qqzc gʿ+ f ˇ#2;q)!{Qf^tƇ%{29l/*`Ȳ؇m]פee"6*ѪNթm372P6t5ƧT֙?xm`;Of-;/8wpu9JvstW]뛺_?= 4p?."7e2q9u +>&!НJB79z23)g#RSJ5eeJe\vɱ5hxQ>Ũ@PH5%7J\}lbj-j'j@YϨ;x]S M{ZZZ;>LzxwmqOvF?#w<[ѻ޷^6:6oPhPa7]_Zܹ8t2rW+~\-A_3YZȿӇekD  GA!_,嬭l+r 6Za&QE۵q;vb%<$oH}%('' / ($̣V:\AfV8mo]]^= 44D&&3fh;KV+T5`{avGQV g^VkIw-1 ^v^ g/Y5 @ v^BԈ)aդ^2E 3z[pA8xC5A%I5BF!H/8ُ(9;jCѝ ie,,GX RYٔ؝8A`3->$w>^I[|z|@0UHChR8kɖ"7DńfoݮCw\4`,Hju;"/$_`0xTIFOʢjZENQ::u{KԷ51504>jek&n5gX|zc|_Mm]}Ì.%_}|~} DBk&Mc) (bX ͇mgR:۩OHLKWtjʹw` Hjn1WICe^7Mo)Ո+uߤ:~Lovwuv Z2>=rL]{YǐOKe!.}?;G{gi"(+ e1dC"GU * u5Aѵu&3"*ŚFsqlalW, $H*d-EZ_!,6ö%lNY mI+@2w˩ɇ+((x֨Ԩт&jOݭO1H6}s6v kNXgan(Ofۯ'/ F0 Ni$2H8J6A38E=Ko=(}d(3gϖ%|z){><+ߋn^3,W*z|ܢ澪FmgH\ EMa-=4>jKhpxL+y[{(/_ M;k{`|lbۤلʏM.\_Z~6[[~%ן+ߌYZCǣ5w?3Y/m46Lj:oN٨oj,BR$O!S#<"<@QbeDPLTE33ff333333̙f̙ff̙ff333fffff33ffff̙̙3̙f3333f33f3333f3̙33ff33̙f3f3f3f3ff3f33fff3ff3f̙333fffffffff3f3ffff33̙fff3f3f3f3f3f33fff3f33ff3f̙f33f333 pHYs   IDATx{Ӹ 5NI4]RZZ.Pa.a. 3}{Wdi&v,{Ƒd[9#)8@J"MT54YLjo)9jA-ԂZP jA-ԂZP jA-ԂZP jA-ԂZP jA-ԂZP jA-ԂZP jA-ԂZP jA-ԂZP jA-ԂZP jA-ԂZP jA-ԂZP jA-Bj~]*j+k2P j-vMS[jZPk=rZS jA=ԂZV@-Z!ړC`cS{vv66Fوձ7I?k"#PmHõJ={cRK ~ 3 97Ņ+d'ڳMV]oiwrUY奛)zʍ:կ&f%]7x5m=ZTœ4=Eb3)z q9Q+;W'8XԞj> 3r7UtkڦG%>儒8Ud-_+9JE_VRy58g]Z\ {ۓf5 ?P2~|EmͤV0iW7^eǚ!I]䀶tlȹ0Z)~jS*7; ~ 9\'㙾-9'-?~D2jWlvSՃځ/XnֈpqMP͟U*$EZ1W!\OX@@ |UQy[cW$=rJO+eW-!IELT*[HIoYb+? k:*sjۚV[=Őa"K*W_W* ~jpOkϱ"=aRϷ_~%{fTTD[K%B-ǖH%B=sw)ëEuM<F7j7v`h-&kk.B P4ž޶7a6u 1\{Z~MjY!c+ ]bIfvP+_o8&l;Jz.ƚ@A{-<1 N*5Sˑ[ jumWWS aJjoK-;\cԪ5j vG>)3 f]l'9H9lP[g"e;HU˩'an>ڛ:j%rg̅@Rӵ$'jV)(Z1+3@檖ME:`mM&~-6IE@vPǦVNmtYn2Z&+f=@v=I[{PSn"_>4+@Dl؝9$V{-|#W(b'N튜I,[ĢùD'gnV;88%Xrp uZpHuheZbҗmꫲ+O{6%ȸpmcM37f2|El$q%9bXaf%"F#8\)D/7ʓGW% G7n.;БƑuY1{'Z,Z~-ԂZP jA-Ԃr:] ԂZ6@A-ԂZP jA-ԂZP jA-ԂZP jA-ԂZP jA-ԂZP jA-ԂZP jA-ԂZP jA-ԂZPh"R @ @ @ @ @ @ xiHӁomveELP0$ \cߥ! b[˞d \eA`@ܭ`[[A.J8v*.KZix̲XR6ă$kԴ ط`-K%& ڗ6g ZCp,K"`i!RI=8V^%u9jW4p樔ڒrûdJiv`)g- N{RJ8ZQ- D~Lּ42W|}èM~۟XfEȕp ~ 8L'/R .c݃f!T#%ŋ>=4BMMio|? ?uu!.: '`7#~!7c:2Besy )T󘲰dsb#5ަM G;h[?G6@?o{!3wiS%-tIQrX %͏>?\zݢar[>:U'iOukgX;Q!voX&)?!z`{ն\3E[F(~kl9CAmzbON;?gL!܌ ڮbc6g͌'=pت3WC[פ6ON>GQ]p,n_11#h#kcS۞C-A8@f_lj8(B⿪IGyu܃xgMGAf}$m@Zl ?@ @ @ @ @ @ @ @ @ @ lnAΤ.6=[K95;{kv=rok=Z5{{^={X"jsٔ}/~jl=f6["[SZZYZZZP jA-ԖZo$ڣS&F{sgQڊ%-rԎNkyVMX0W#-%eVKZ{4-YOy3>)k9Zִ&c;WleNmJKf`Vk?ToU;_MZ4}XQėّGaycN{%Piu*V_-Ej5M[kf=f,(~e/3jCqfD@X*;alEL53jmllϝO-m=+ m{xD{&iPG|:WҪB=gO9no}PeˋR{>#~[9CaVR/VWVsL%UgF s ;.{λ2*}OSeG=$:f -2Hl9 mZ٘-qqWgY<3̄kVheok}sqGpʓU쩵Dq={->0\<۱SMzȫgZy&YQkfc{~Oz='>6;j塵A'G~5UU&p$)Vh6sT܁/|dיK#+gv4kKT#rgdVZHgxrw+%cPΗB5UY$}i6nd׹y!#=leFmUv'mfj&=71=,>0^tMu;nVzvVŔO|o Z؞͈wPfkʔβ73%j=cF,B-L̨B=g]־K_ҷJ8}a:ߛy /1܇)Qkf6܁G%>qa9h&3j9oVEc;k-ll)hy#60~(fdk9UqxBLŦCBfϽPى{BP;Qlbm bXCڥQԱo&E߳Y0Fgu3X=˰#jEB (scI'h5+Omk!Q<Jm(RBdxڤ90frJO|]LqSӧVm^`Gg@ Fғyrd'Ua+6jiwx2i{9숾M8xTK-f݁sejj^K4;ZϏp(ZxU*ijֆ\P'.K^'*L?0N{yHDV$dϏ\č Ǚ͍CQOjU\Hc&A#x2T37gEuzrI6/~|fX4%xVo$# Ӌ>78rB=SB ӧZAWHlet<43jfSDmHN+O|j6ޕ?Sz0h46F٠KKR+jGo*ڣى;h$³,~ڧ٫zZԖR޼Z½Ms/[[&~<7amm4h +zin[ZԦ:jbV-Z@ @ @ @ @ @ @ @ @ @ A,{ielRFA7u|ӊ$68v ~&|K`v8[R[&}{pH/ek$rNmЩE>`=0N~yEQ=`u GH |nivk6-h2 ,ߚ'.6o/664yK)y5%lp7Xp:qeqrㆸ¼joHskvv%{]6Mic]ݜ%MdїN4u".RL9M~I45D~#DvϨNn️sw1}xtmw!LG.1*Dc+7\[~#ބvbC~K!lOmQE󻳚Z~L+u{ q C-9/6ޫlGң'uh%mEc781jw湻Dǰy}% I x!s+{7|HOfZڤ%Ow~-MӫrS _9ڛ 3Kj^q~7h9Kc`E*U:gP[_՞n1jIٗM%3urC^W^٭$>{g+b@ |{}l5ޑVќgJm܉tKԏ>֣ Rr*5f~}vîW\>b&ԨqQxݢ ]09[=ח̸ZA)}QKsj7%yy&9./ɳcffHSf8+pZ]IQSiK ݲfvmR}! qM֬}nצu+h8JƘc)J۷o~rYԾ73X=^Fm~5ZXCv_澠JjP>/'q.ЭsN=SW[nŕc-ȶvO fP{i>sl݈ffQNOK,"͵;qr?y ,|ܟh:9SJCݪ+pR4tH1^ēL}5K-]UsFtw{46Obܩc3N[oU\#K'}68'NHN itz#c!jCב4#>+?p^*VfZ󯇛!oמ#g*vK>n&, xW~^C@,:yONH(p R>ZrbuXL^n EnD找m8܌d.KM~"U #/CוW[7N\ hW*J he7TsIID`fZfB RzYSڀX"k )ލQZ=&˫D/7%lr~ފT74/r_FetL\ylaF5*U.4TqKŠ!7IDAT|iT͎htg֠v/ảzNc;Cu>YLw65Ib 1;=f [WٮM1ZԒ XݺJ'mtw v;߻9}>ws0gDB'B܊h^xj̋Lrjgqm0f6&jw@%6j+Wdw[Vk~݇t ._+-uklsL-IEwՖ(}QuEwbҤyѭ!jHE-͚Z^=Aj!Ɩ|AR~NP0\q6rS~7"huƏobn_ ?TAok;q^/2{ ӴO[WCb[w>~>1[dJ0[2+uv wBPjB=LWgՏoQK`zw -_Ț{:'w}[6VGUܻÔ5v=tk ?j7Pݒ#< A{ yj]ʠ-J-۱vGhJyN0Cx\d!|CWOx@ 6HҤEouTrcZ9Nm>ջV$tAk~Gm(rMn8!#.T4*jd<ښL6R_ persist real_mailserver="192.168.1.4" fake_mailserver="192.168.1.3" real_mailserver6="2001:dead:beef::1" fake_mailserver6="2001:dead:beef::2" pass in quick log on fxp0 proto tcp from \ to $real_mailserver port smtp pass in quick log on fxp0 inet6 proto tcp from \ to $real_mailserver6 port smtp block in log on fxp0 proto tcp \ to { $fake_mailserver $real_mailserver } port smtp block in log on fxp0 inet6 proto tcp \ to { $fake_mailserver6 $real_mailserver6 } port smtp \end{lstlisting} The important things are that the table \prog{mx-white} exist and that the pass- and the block-rules specify the log modifier. If you use another pflog-interface, you can tell mxallowd this via parameter. \section{Help, I cannot send any mails anymore!} That's right -- if you use the same mailserver to send mails, your mailclient usually tries only one connection (it's broken in this respect). I'd suggest you to use SMTPS (SSL) because its port (465) usually isn't filtered by \prog{mxallowd}. You could also configure your mailserver to listen on a completely different port which you use exclusively (spammer don't do portscans if they don't even use standard conforming mailers...). If you got a static IP address you can also whitelist it:\np \begin{lstlisting} iptables -I INPUT 1 -p tcp --dport 25 --s 192.168.2.3 -j ACCEPT \end{lstlisting} \end{document} mxallowd-1.9/mxallowd.conf0000644000014500017510000000014211731622777015207 0ustar michaelstaff# Example configuration file queue-num 23 fake-mailserver 192.168.1.1 real-mailserver 192.168.1.2 mxallowd-1.9/mxallowd.10000644000014500017510000001271511731622777014433 0ustar michaelstaff.de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .TH mxallowd 1 "MARCH 2012" Linux "User Manuals" .SH NAME mxallowd \- dynamically whitelist your Mail eXchanger .SH SYNOPSIS .B mxallowd .RB [\|\-d\|] .RB [\|\-c .IR configfile \|] .RB [\|\-t .IR whitelist-time \|] .RB [\|\-p .IR pflog-interface \|] .RB [\|\-l .IR pcap-filter \|] .RB [\|\-F\|] .RB [\|\-s\|] .RB [\|\-q\|] .RB [\|\-p\|] .RB \|\-f .IR fake-mailserver \| .RB \|\-r .IR real-mailserver \| .RB \|\-n .IR queue-num \| .SH DESCRIPTION .B mxallowd is a daemon which uses libnetfilter_queue (on Linux) or pf and pflog (on BSD) to allow (or deny) connections to a mailserver (or similar application) if the remote host hasn't connected to a fake daemon before. This is an improved version of the so-called nolisting (see http://www.nolisting.org/). The assumption is that spammers are not using RFC 2821-compatible SMTP-clients and are sending fire-and-forget spam (directly to the first or second MX-entry without retrying on error). This direct access is blocked with mxallowd, you'll only get a connection if you retry. NOTE: It is highly recommended to install nscd (nameserver caching daemon) or a similar software in order to speed-up DNS lookups. Since version 1.3, DNS lookups are done in a thread (so they don't block the main process), however, on very-high-traffic-sites, mxallowd may show significantly better overall performance in combination with nscd. .SH OPTIONS .TP .B \-b, \-\-no\-rdns\-whitelist Disable whitelisting all IP-addresses that have the same RDNS as the connecting one (necessary for google mail) .TP .B \-c, \-\-config Specifies an alternative configuration file (instead of /etc/mxallowd.conf) .TP .B \-t, \-\-whitelist\-time Specify the amount of time (in seconds) until an IP-address will be removed from the whitelist .TP .B \-s, \-\-stdout Log to stdout, not to syslog .TP .B \-q, \-\-quiet Don't log anything but errors. .TP .B \-f, \-\-fake\-mailserver Specify which IP-address the fake mailserver has (connecting to it will whitelist you for the real mailserver) .TP .B \-r, \-\-real\-mailserver Specify which IP-address the real mailserver has .TP .B \-F, \-\-foreground Do not fork into background, stay on console .TP .B \-n, \-\-queue\-num (only available when compiled for netfilter_queue) Specify the queue number which will be used for the netfilter_queue-link. This has to be the same which is specified in the iptables-rule and it has to be specified, there is no default. .TP .B \-p, \-\-pflog\-interface (only available when compiled for pf) Specify the pflog(4) interface which you configured in pf(4). The default is pflog0. Also see the pcap-filter-option if you use an interface which does not only get smtp-traffic. .TP .B \-l, \-\-pcap\-filter (only available when compiled for pf) Specify the filter for pcap. The default is "port 25". See tcpdump(8) for more information on the filters. .SH FILES .TP .B /etc/mxallowd.conf System-wide configuration file. Use the long options without the beginning two dashes. For example: .Vb 6 \& \& stdout \& fake-mailserver 192.168.1.3 \& fake-mailserver 192.168.1.4 \& real-mailserver 192.168.1.5 \& queue-num 23 .Ve .SH EXAMPLES FOR NETFILTER The machine has two IP-addresses. The mailserver only listens on 192.168.1.4, the nameserver returns the mx-records mx1.domain.com (192.168.1.3) with priority 5 and mx2.domain.com (192.168.1.4) with priority 10. .PP .Vb 3 \&# modprobe nfnetlink_queue \&# iptables \-A INPUT \-p tcp \-\-dport 25 \-m state \-\-state NEW \-j NFQUEUE \-\-queue-num 23 \&# mxallowd \-s \-F \-f 192.168.1.3 \-r 192.168.1.4 \-n 23 .Ve Then open a separate terminal and connect via telnet on your real mailserver. You'll see the connection attempt being dropped. Now connect to the fake mailserver and watch mxallowd's output. Afterwards, connect to the real mailserver to verify your mailserver is still working. .SH EXAMPLES FOR PF The machine has two IP-addresses. The mailserver only listens on 192.168.1.4, the nameserver returns the mx-records mx1.domain.com (192.168.1.3) with priority 5 and mx2.domain.com (192.168.1.4) with priority 10. Create a pf.conf like this: .PP .Vb 11 \& table persist \& \& real_mailserver="192.168.1.4" \& fake_mailserver="192.168.1.3" \& \& real_mailserver6="2001:dead:beef::1" \& fake_mailserver6="2001:dead:beef::2" \& \& pass in quick log on fxp0 proto tcp from to $real_mailserver port smtp \& pass in quick log on fxp0 inet6 proto tcp from to $real_mailserver6 port smtp \& block in log on fxp0 proto tcp to { $fake_mailserver $real_mailserver } port smtp \& block in log on fxp0 inet6 proto tcp to { $fake_mailserver6 $real_mailserver6 } port smtp .Ve Afterwards, load it and start mxallowd using the following commands: .PP .Vb 2 \&# pfctl \-f /etc/pf.conf \&# mxallowd \-s \-F \-f 192.168.1.3 \-r 192.168.1.4 .Ve Then open a separate terminal and connect via telnet on your real mailserver. You'll see the connection attempt being dropped. Now connect to the fake mailserver and watch mxallowd's output. Afterwards, connect to the real mailserver to verify your mailserver is still working. The ruleset for pf is actually longer because pf does more than netfilter on linux -- netfilter passes the packets and lets mxallowd decide whether to drop/accept whilst pf blocks/passes before even "passing" to mxallowd. .SH SEE ALSO .BR iptables (8), .BR pf (4), .BR pflog (4), .BR tcpdump (8) .SH AUTHOR Michael Stapelberg mxallowd-1.9/mxallowd.de.tex0000644000014500017510000001450311731622777015457 0ustar michaelstaff % % mxallowd 1.9 Manual % (c) 2007-2009 Michael Stapelberg % % See http://michael.stapelberg.de/mxallowd % \documentclass[12pt, a4paper]{scrartcl} \usepackage{ngerman} \usepackage[latin1]{inputenc} \usepackage{graphicx} \usepackage{fancyhdr} \usepackage{url} \usepackage{listings} \usepackage{palatino,avant} \usepackage[pdftex,bookmarks=true,bookmarksnumbered=true,bookmarksopen=true,colorlinks=true,filecolor=black, linkcolor=red,urlcolor=blue,plainpages=false,pdfpagelabels,citecolor=black, pdftitle={mxallowd-Dokumentation},pdfauthor={Michael Stapelberg}]{hyperref} % New paragraph \newcommand{\np}{\bigskip\noindent} % Program name \newcommand{\prog}[1]{\textit{#1}} % Command name \newcommand{\cmd}[1]{\textit{#1}} % Left and right angle brackets \newcommand{\la}{\textless} \newcommand{\ra}{\textgreater} % Figure names \renewcommand{\figurename}{Abb.} % No indention at new paragraphs \setlength{\parindent}{0pt} % listing-options \lstset{frame=trbl,basicstyle=\ttfamily\small,captionpos=b} % No tt-style \urlstyle{rm} \begin{document} \title{mxallowd-Dokumentation} \author{Michael Stapelberg} \date{Version 1.7, Dokumentation \today} \maketitle \tableofcontents \clearpage \section{Einleitung} \prog{mxallowd} ist ein Daemon fr Linux/Netfilter (via libnetfilter\_queue) oder BSD/pf (via pflog), der eine Verfeinerung der nolisting-Methode darstellt. Hierbei werden fr eine Domain zwei MX-Eintrge vom Nameserver ausgeliefert, wobei auf der IP-Adresse des ersten MX-Eintrags kein Mailserver luft. Einige Spammer versuchen nun, nur auf den ersten Mailserver Spam auszuliefern und werden damit keinen Erfolg haben. Auf der IP-Adresse des zweiten MX-Eintrags luft dann ein richtiger Mailserver, der die E-Mails entgegennimmt. Echte Mailserver probieren -- im Gegensatz zu Spammern -- alle MX-Eintrge in der angegeben Reihenfolge (geordnet nach Prioritt) durch, bis sie die Mail zustellen knnen. Somit kommen echte Mails an und Spam bleibt drauen.\np Das Problem beim nolisting ist nun, dass einige Spammer (vermutlich aufgrunddessen) direkt den zweiten MX-Eintrag benutzen ("`direct-to-second-mx"'). Hier kommt nun \prog{mxallowd} ins Spiel: Auf den zweiten Mailserver darf man sich nicht verbinden (das Paket wird einfach via netfilter/iptables verworfen), auer, wenn man es zuvor beim ersten Mailserver probiert hat.\np Dieses Problem htte man prinzipiell auch nur via iptables mit dem Modul \prog{ipt\_recent} lsen knnen, wenn es nicht ein kleines Problem dabei gbe: Einige Anbieter (wie zum Beispiel Google Mail) verwenden zwar den gleichen DNS-Namen, aber unterschiedliche IP-Adressen im selben Zustellzyklus. Das heit, dass \prog{ipt\_recent}, welches ausschlielich auf IP-Adress-Basis arbeitet, E-Mails von Google nicht durchlsst. \prog{mxallowd} fgt daher alle IP-Adressen des DNS-Eintrags in die Whitelist ein (auer, wenn man die Option $--$no-rdns-whitelist angibt).\np \subsection{Lizenz} \prog{mxallowd} ist freie Open-Source-Software unter der GPLv2\footnote{\url{http://www.gnu.org/licenses/old-licenses/gpl-2.0.html}}. \subsection{Entwicklung} Den momentanen Stand der Entwicklung kann man auf \url{https://code.stapelberg.de/mxallowd/} begutachten.\np ber Patches, Feature-Requests oder Bugreports bin ich natrlich erfreut, meine E-Mail-Adresse und meinen GPG-Key findest du auf \url{http://michael.stapelberg.de/Kontakt}. \clearpage \subsection{Funktionsweise} Auf folgender Grafik ist abgebildet, wie ein Verbindungsaufbau ablaufen kann. Dabei ist mit "`Spammer"' ein nicht-RFC-konformer Mailsender gemeint und mit "`Mailserver"' ein RFC-konformer. Das SMTP-RFC schreibt vor, dass ein Mailsender alle MX-Eintrge in der angegebenen Prioritt durchprobieren muss, um die Mail zu versenden.\np Der Spammer verbindet sich in dieser Grafik entweder direkt mit dem ersten (falschen) Mailserver oder direkt mit dem zweiten, whrend der Mailserver beide nacheinander durchprobiert. \begin{figure}[h!] \centering \includegraphics[width=0.75\textwidth]{scheme1.png} \caption{Funktionsweise} \end{figure} \clearpage \section{Installation unter Linux} Damit neue Verbindungen an \prog{mxallowd} geleitet werden, muss man folgende iptables-Regel hinzufgen: \begin{lstlisting} iptables -A INPUT -p tcp --dport 25 -m state --state NEW \ -j NFQUEUE --queue-num 23 \end{lstlisting} Falls das Einfgen dieser Regel nicht klappt, muss zuvor das Queue-Modul geladen werden: \begin{lstlisting} modprobe nfnetlink_queue \end{lstlisting} Die Regel kann man selbstverstndlich anpassen, sodass zum Beispiel nur an bestimmte IP-Adressen gerichtete Verbindungen gefiltert werden, oder dass Verbindungen von bestimmten IP-Adressen von vorneherein akzeptiert werden (-j ACCEPT am Ende).\np Seit Version 1.5 funktioniert das auch mit ip6tables und IPv6-Verbindungen. \section{Installation unter BSD} Eine \prog{/etc/pf.conf} knnte so aussehen: \begin{lstlisting} table persist real_mailserver="192.168.1.4" fake_mailserver="192.168.1.3" real_mailserver6="2001:dead:beef::1" fake_mailserver6="2001:dead:beef::2" pass in quick log on fxp0 proto tcp from \ to $real_mailserver port smtp pass in quick log on fxp0 inet6 proto tcp from \ to $real_mailserver6 port smtp block in log on fxp0 proto tcp \ to { $fake_mailserver $real_mailserver } port smtp block in log on fxp0 inet6 proto tcp \ to { $fake_mailserver6 $real_mailserver6 } port smtp \end{lstlisting} Wichtig dabei ist, dass die Table \prog{mx-white} existiert und dass sowohl die pass- als auch die block-Regeln loggen. Wenn man ein anderes pflog-interface verwendet, kann man mxallowd das via Parameter mitteilen. \section{Hilfe, ich kann keine Mails mehr versenden!} Das stimmt -- wenn du den selben Mailserver auch verwendest, um Mails zu versenden, probiert dein Mailclient in der Regel nur eine Verbindung. Ich wrde raten, die Mails ber SMTPS (SSL) zu versenden, denn dieser Port (465) wird nicht von \prog{mxallowd} gefiltert. Ansonsten kannst du deinen Mailserver auch zustzlich auf einem anderen Port laufen lassen, den nur zu benutzt (Spammer treiben nicht den Aufwand, einen Portscan durchzufhren, wenn sie nicht mal standardkonforme Mailer verwenden...). Falls du eine fixe IP-Adresse hast, kannst du diese natrlich auch via iptables whitelisten:\np \begin{lstlisting} iptables -I INPUT 1 -p tcp --dport 25 --s 192.168.2.3 -j ACCEPT \end{lstlisting} \end{document} mxallowd-1.9/Makefile.in0000644000014500017510000000245211731622777014564 0ustar michaelstaffCC_STDFLAGS=-Wall -D_REENTRANT -DSYSCONFDIR="\"${SYSCONFDIR}\"" mxallowd: src/log.o src/config.o src/resolve.o src/whitelist.o src/mxallowd.o ${FILTERSRC} Makefile ${CC} ${CC_STDFLAGS} ${QUEUEDEF} ${V6DEF} ${CC_FLAGS} ${LIBPATH} -o mxallowd src/*.o ${LIBS} src/%.o: src/%.c include/*.h Makefile ${CC} ${CC_STDFLAGS} ${QUEUEDEF} ${V6DEF} ${CC_FLAGS} ${LIBPATH} -Iinclude -c -o src/$$(basename $< .c).o $< doc: mxallowd.de.tex mxallowd.en.tex scheme1.png pdflatex mxallowd.de pdflatex mxallowd.de pdflatex mxallowd.en pdflatex mxallowd.en all: mxallowd install: mxallowd $(INSTALL) -m 755 -d $(DESTDIR)/${PREFIX}/sbin/ $(INSTALL) -m 755 -d $(DESTDIR)/${PREFIX}/share/man/man1 $(INSTALL) -m 755 -d $(DESTDIR)/${SYSCONFDIR} $(INSTALL) -m 755 mxallowd $(DESTDIR)/${PREFIX}/sbin/mxallowd $(INSTALL) -m 644 mxallowd.1 $(DESTDIR)/${MANDIR}/man1/mxallowd.1 $(INSTALL) -m 644 mxallowd.conf $(DESTDIR)/${SYSCONFDIR}/mxallowd.conf uninstall: rm $(DESTDIR)/${PREFIX}/sbin/mxallowd rm $(DESTDIR)/${PREFIX}/share/man/man1/mxallowd.1 clean: rm -f mxallowd src/*.o mxallowd.{de,en}.toc mxallowd.{de,en}.out mxallowd.{de,en}.pdf mxallowd.{de,en}.log mxallowd.{de,en}.aux distclean: clean rm Makefile lint: splint +unixlib +skipsysheaders -sysdirs /usr/include -hints -showfunc -showcolumn -Iinclude src/mxallowd.c mxallowd-1.9/include/0000755000014500017510000000000011731622777014137 5ustar michaelstaffmxallowd-1.9/include/pf.h0000644000014500017510000000014411731622777014714 0ustar michaelstaff#ifndef _PF_H #define _PF_H extern char *pflogif; extern char *pcap_filter; int pcap_init(); #endif mxallowd-1.9/include/resolve.h0000644000014500017510000000042711731622777015772 0ustar michaelstaff/* * mxallowd * (c) 2007-2008 Michael Stapelberg * * See mxallowd.c for description, website and license information * */ #ifndef _RESOLVE_INCLUDED #define _RESOLVE_INCLUDED char *rdns_for_ip(char*); struct hostent *ip_for_dns(char*); void *resolve_thread(void*); #endif mxallowd-1.9/include/whitelist.h0000644000014500017510000000206411731622777016326 0ustar michaelstaff/* * mxallowd * (c) 2007-2008 Michael Stapelberg * * See mxallowd.c for description, website and license information * */ #include #ifndef _WHITELIST_INCLUDED #define _WHITELIST_INCLUDED struct whitelist_entry { char *ip_address; char *rdns; /* * is_child will be set to true if this entry is one of the IP-addresses obtained * by looking up the RDNS * */ bool is_child; /* * used is the indicator for statistics. It will be set to true if this entry was * used for allowing the connection to a mailserver * */ bool used; /* * rdns_tried is set to 1 as soon as the resolver-thread tried to resolve the rdns. * This is needed because rdns can stay NULL if there is no hostname or if the * resolving fails * */ bool rdns_tried; time_t added; struct whitelist_entry *next; }; bool is_whitelisted(char*, bool); void cleanup_whitelist(); void add_to_whitelist(char*, char*, bool, int, const void *); #ifdef _WHITELIST_INTERNAL struct whitelist_entry *root = NULL; #else extern struct whitelist_entry *root; #endif #endif mxallowd-1.9/include/config.h0000644000014500017510000000052311731622777015555 0ustar michaelstaff/* * mxallowd * (c) 2007-2008 Michael Stapelberg * * See mxallowd.c for description, website and license information * */ #ifndef _CONFIG_INCLUDED #define _CONFIG_INCLUDED bool isInteger(char*); void getNextConfigEntry(FILE*, char**, char**, char*, int); bool checkConfigValue(char*, char*); bool read_configuration(char*); #endif mxallowd-1.9/include/nfq.h0000644000014500017510000000006611731622777015076 0ustar michaelstaff#ifndef _NFQ_H #define _NFQ_H void nfq_init(); #endif mxallowd-1.9/include/mxallowd.h0000644000014500017510000000243311731622777016141 0ustar michaelstaff#include #include #ifndef _MXALLOWD_INCLUDED #define _MXALLOWD_INCLUDED struct mailserver_list { char *ip_address; /*@null@*/ struct mailserver_list *next; }; #ifdef _MXALLOWD_INTERNAL int allow_time = 3600, queue_num = -1; #ifdef PF /* file descriptor for /dev/pf */ int pffd; #endif bool to_stdout = false, rdns_whitelist = true, quiet = false, stay_in_foreground = false; struct mailserver_list *fake_mailservers, *real_mailservers; unsigned long blocked_attempts = 0, successful_connects = 0, direct_to_fake = 0; char *configfile = SYSCONFDIR "/mxallowd.conf"; pthread_mutex_t resolv_thread_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t resolv_new_cond = PTHREAD_COND_INITIALIZER; #else #ifdef PF extern int pffd; #endif extern int allow_time; extern int queue_num; extern bool to_stdout, quiet, rdns_whitelist; extern struct mailserver_list *fake_mailservers, *real_mailservers; extern unsigned long blocked_attempts, successful_connects, direct_to_fake; extern pthread_mutex_t resolv_thread_mutex; extern pthread_cond_t resolv_new_cond; #endif void add_mailserver(struct mailserver_list**, char*); bool is_included(struct mailserver_list*, char*); void handle_sighup(int); void handle_sigusr1(int); void print_help(char*); #endif mxallowd-1.9/include/log.h0000644000014500017510000000052311731622777015071 0ustar michaelstaff/* * mxallowd * (c) 2007-2008 Michael Stapelberg * * See mxallowd.c for description, website and license information * */ #ifndef _LOG_INCLUDED #define _LOG_INCLUDED void slog(char*, ...); void slogerror(char*); void diem(char *message); void /*@noreturn@*/ diep(char *message); void /*@noreturn@*/ dief(char *format, ...); #endif