/*
 * 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 <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <arpa/inet.h>
#include <getopt.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#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;
}
