/* A filter that checks for DNS hostnames that are simply
 * constructed from IP addresses. These are typically dialup,
 * DSL, or cable-modem users. Legitimate email from these sites
 * would normally be forwarded thru their ISP's SMTP server;
 * the only people sending directly tend to be spammers.
 *
 * Only works with IPv4 addresses for now; there aren't enough
 * IPv6 networks in service to draw any conclusions as yet.
 *
 * Copyright 2003-2005, Howard Chu  --  hyc@highlandsun.com
 *
 * See http://www.highlandsun.com/hyc/badDNS.html for details.
 *
 * This code is released under the terms of the GNU GPL
 * Version 2 http://www.gnu.org/copyleft/gpl.html
 */

#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sysexits.h>
#include <regex.h>

#include <libmilter/mfapi.h>
#include <netinet/in.h>

typedef struct my_regexp {
	struct my_regexp *next;
	regex_t patt;
	int	ret;
} my_regexp;

static my_regexp *my_rx;

static sfsistat
mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
{
	int i, len;
	char addr[4][4];
	char haddr[4][3];
	unsigned char *ptr;
	my_regexp *rx;
	sfsistat ret = SMFIS_CONTINUE;

	/* If it's not IPv4, we don't do anything with it */
	if (hostaddr->sa_family != AF_INET)
		goto done;

	/* lowercase for hex comparisons */
	for (ptr=(unsigned char *)hostname; *ptr; ptr++)
		if (isupper(*ptr)) *ptr = tolower(*ptr);
	len = ptr - (unsigned char *)hostname;

	/* look for any regexp matches */
	for (rx = my_rx; rx; rx=rx->next)
	{
		if (!regexec( &rx->patt, hostname, 0, NULL, 0) )
		{
			ret = rx->ret;
			goto done;
		}
	}

	/* If the client has no DNS name, reject it.
	 * A regexp can accept it if needed.
	 */
	if (hostname[0] == '[' && hostname[len-1] == ']')
	{
		ret = SMFIS_TEMPFAIL;
		goto done;
	}

	/* convert the binary address to text decimal and hex */
	for (i=0, ptr = (char *)&((struct sockaddr_in *)hostaddr)->sin_addr; i<4; i++)
	{
		sprintf(addr[i], "%d", *ptr);
		sprintf(haddr[i], "%x", *ptr);
		ptr++;
	}

	/* look for the decimal address */
	for (i=0, len=0; i<4; i++)
	{
		ptr = strstr(hostname, addr[i]);
		if (!ptr) continue;
		len++;
		memset(ptr, ' ', strlen(addr[i]));
	}

	/* if we got more than 1 match, kill it */
	if (len > 1)
	{
		ret = SMFIS_TEMPFAIL;
		goto done;
	}

	/* check for hex address */
	for (i=0;i<4; i++)
       	{
		ptr = strstr(hostname, haddr[i]);
		if (!ptr) continue;
		len++;
		memset(ptr, ' ', strlen(haddr[i]));
	}
	if (len > 1) ret = SMFIS_TEMPFAIL;

done:
	smfi_setpriv(ctx, (void *)ret);
	return SMFIS_CONTINUE;
}

static sfsistat
mlfi_rcpt(SMFICTX *ctx, char **argv)
{
	sfsistat ret = (sfsistat)smfi_getpriv( ctx );
	if (ret == SMFIS_TEMPFAIL)
	{
		smfi_setreply(ctx, "450", "4.7.0", "Bad address");
		sleep(9);
	}
	return ret;
}

static sfsistat
mlfi_close(SMFICTX *ctx)
{
	smfi_setpriv(ctx, NULL);
	return SMFIS_CONTINUE;
}

struct smfiDesc smfilter =
{
	"badDNS",	/* filter name */
	SMFI_VERSION,	/* version code -- do not change */
	SMFIF_NONE,	/* flags */
	mlfi_connect,	/* connection info filter */
	NULL,		/* SMTP HELO command filter */
	NULL,		/* envelope sender filter */
	mlfi_rcpt,	/* envelope recipient filter */
	NULL,		/* header filter */
	NULL,		/* end of header */
	NULL,		/* body block filter */
	NULL,		/* end of message */
	NULL,		/* message aborted */
	mlfi_close	/* connection cleanup */
};


int
main(argc, argv)
	int argc;
	char *argv[];
{
	int c;
	const char *args = "p:";
	char buf[1024], *ptr;
	FILE *f;
	my_regexp *reg, dum;

	/* Process command line options */
	while ((c = getopt(argc, argv, args)) != -1)
	{
		switch (c)
		{
		  case 'p':
			if (optarg == NULL || *optarg == '\0')
			{
				(void) fprintf(stderr, "Illegal conn: %s\n",
					       optarg);
				exit(EX_USAGE);
			}
			(void) smfi_setconn(optarg);
			break;

		}
	}
	f = fopen("/etc/mail/regexp", "r");
	if (f)
	{
		while ( fgets(buf, sizeof(buf), f) )
		{
			/* ignore comments */
			if (buf[0] == '#') continue;
			ptr = strchr(buf, '\n');
			if (ptr) *ptr = '\0';
			ptr = strchr(buf, ' ');
			if (!ptr) ptr = strchr(buf, '\t');
			if (!ptr)
			{
				fprintf(stderr, "Line %s missing OK/REJECT token\n", buf);
				exit(EX_USAGE);
			}
			*ptr++ = '\0';
			while( isspace(*ptr) ) ptr++;
			if (!strcasecmp(ptr, "OK")) dum.ret = SMFIS_CONTINUE;
			else if (!strcasecmp(ptr, "REJECT")) dum.ret = SMFIS_REJECT;
			else
			{
				fprintf(stderr, "Line %s missing OK/REJECT token\n", buf);
				exit(EX_USAGE);
			}

			c = regcomp( &dum.patt, buf, REG_EXTENDED|REG_ICASE|REG_NOSUB );
			if ( c )
			{
				fprintf(stderr, "Bad pattern %s(%d)\n", buf, c);
				exit(EX_USAGE);
			}
			reg = malloc(sizeof(dum));
			memcpy( &reg->patt, &dum.patt, sizeof(dum.patt) );
			reg->ret = dum.ret;
			reg->next = my_rx;
			my_rx = reg;
		}
		fclose(f);
	}
	if (smfi_register(smfilter) == MI_FAILURE)
	{
		fprintf(stderr, "smfi_register failed\n");
		exit(EX_UNAVAILABLE);
	}
	return smfi_main();
}
