/*---------------------------------------------------------------------------
 * SaberNet.net
 *---------------------------------------------------------------------------
 *
 *	@(#)nospam.c	1.27	05/28/98	
 * 
 *  Verifies the sender's addresses and mailrelay against a DB
 *  of known spammers. Requires ndbm(3).
 *
 *  There is no warranty or other guarantee of fitness of this software.
 *  It is provided solely "as is". The author(s) disclaim(s) all
 *  responsibility and liability with respect to this software's usage
 *  or its effect upon hardware or computer systems.
 *
 *  Author:  Jason R. Rhoads - 26 August 1997
 *
 *
 *  Revisions:
 *
 *  07-May-98  JRR  Added support for wildcards in relay-domain
 *  05-May-98  JRR  Added more comments/cleaned up code.
 *  28-Mar-98  JRR  Added inRelayDomain().  Allow empty sender from internal
 *                  hosts.
 *  05-Mar-98  JRR  Added check for empty sender.
 *  25-Mar-98  ALB  Made empty sender check optional (comply with RFC821)
 *  25-Feb-98  JRR  Added error message for mailFromSpammer()
 *  02-Dec-97  JRR  Added support for Paul Vixie's MAPS RBL
 *  10-Nov-97  JRR  Added support for multiple relay-domain lines.
 *  07-Nov-97  JRR  Fixed core dump when the relay-domain is not specified.
 *  05-Nov-97  JRR  Fixed domain choping for top level domains.
 *  02-Oct-97  JRR  Added DNS query to verify sender's email address.
 *  01-Oct-97  JRR  Added support for multiple relay-domain entries
 *                  (http://maps.vix.com/rbl/usage.html)
 *  18-Sep-97  JRR  Added relay blocker
 *
 *---------------------------------------------------------------------------*/
#include "firewall.h"
#include "fwfunc.h"
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#ifndef DB4
#include	<ndbm.h>
#else
#define         DB_DBM_HSEARCH  1
#include	<db.h>
#endif
#include <fcntl.h>
#include <syslog.h>
#include <ctype.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <netdb.h>
#include <string.h>

extern  size_t 	strlcat(char*,const char*,size_t);
extern  size_t 	strlcpy(char*,const char*,size_t);

/*--------------------------------[static data]-------------------------------*/
#define RBL_DOM ".rbl.maps.vix.com"


/*------------------------------[local functions]-----------------------------*/
static int option_selected(const char *pri, const char *sec, Cfg *configTable);
static char *strmcpy(const char* s);
static char* rblcpy(const char* addr);
static int cntchr(char *s, char c);
static char *chop_domain(char *s);
static int spam_db_key_exists(char *s, DBM *db);
static int relay_domain(char*,Cfg*);


/*--------------------------------[init_resolv]--------------------------------
 * Establishes a static TCP connection to a nameserver.
 *
 *  Paramaters:
 *		hostname		Current hostname
 *
 *	Note:
 *		Must be called before chroot(1M) 
 *----------------------------------------------------------------------------*/
void init_resolv(char *hostname)
{
	union
	{
		HEADER hdr;				/* defined in resolv.h */
		u_char	buf[PACKETSZ];	/* defined in arpa/nameserver.h */		
	} response;

	res_init();
	_res.options |=RES_USEVC|RES_STAYOPEN;
	res_query(hostname, C_IN, T_NS, (u_char *)&response, sizeof(response));
}

/*------------------------------[matches_spam_db]------------------------------
 * Checks the sender's IP and email addresses against the spam database
 *
 *	Paramaters:
 *		sender			Sender's email address from the rcpt to: command
 *      hostname    	Mail client's hostname
 *		addr			Mail client's IP address
 *		config     		Pointer to the configuration table
 *
 *	Returns:
 *		1				Match
 *      0				No match or spam-database not configured
 *
 *----------------------------------------------------------------------------*/
int matches_spam_db(char *sender, char *hostname, char *addr, Cfg *config)
{
	Cfg		*cf;
	DBM		*db;
	char	*senderCopy;
    char    *senderDomain;
	char	*dbPath = (char *)0;
	int		 returnValue = 0;

	/* Handle empty from: lines */ 
	if (option_selected("nospam", "RFC821", config) == 0)
    {
		if ( (strlen(sender) == 0) || (strcmp(sender, "<>") == 0)
	   	                           || (strcmp(sender, "<@>") == 0) )
		{
			printf("550 sender required\r\n");
			return(1);
		}
	}

	if ((cf = cfg_get("spam-database", config)) != (Cfg *)0)
	{
		if (cf->argc != 1)
		{
			syslog(LLEV, "spam-database must have one parameter, line %d", cf->ln);
			return(0);
		}
		dbPath = cf->argv[0];
	}
	
	/* Did we get a path? */
	if (dbPath == (char *)0)
	{
		return(0);			
	}	

	/* Malloc a copy of the sender */
	if ((senderCopy = strmcpy(sender)) == (char *)0)
	{
		syslog(LLEV, "fwtksyserr: out of memory: %s", strerror(errno));
		return(0);			
	}

	/* Open the database */
	if ((db = dbm_open(dbPath, O_RDONLY, 0440)) == (DBM *)0)
	{
		syslog(LLEV, "error opening spam-database: %s", dbPath);
		return(0);
	}

	senderDomain = chop_domain(senderCopy);

	if ( ((senderDomain != (char *)0) &&
		   (spam_db_key_exists(senderDomain, db))) ||
	     (spam_db_key_exists(senderCopy, db)) )      
	{
		printf("550 sender ignored: %s matches spam database entry\r\n",
		sender);
		returnValue = 1;
	}
	else if ( (spam_db_key_exists(chop_domain(hostname), db))  || 
   	     (spam_db_key_exists(hostname, db))          || 
         (spam_db_key_exists(addr, db))                 ) 
	{
		printf("550 sender ignored: %s/%s matches spam database entry\r\n",
			hostname, addr);
		returnValue = 1;
	}

	dbm_close(db);
	free(senderCopy);
	return (returnValue);
}

/*------------------------------[valid_recipient]------------------------------
 * External clients may only address mail to local destinations.  Local 
 * clients may send mail to both internal and external sites.
 *
 *	Paramaters:
 *		rcpt		Recipient's email address
 *		addr		Mail client's IP address
 *   	config		Pointer to the configuration table
 *
 *	Returns:
 *		1			Valid or relay-domain not configured
 *      0			Invalid 
 *----------------------------------------------------------------------------*/
int valid_recipient(char *rcpt, char *addr, Cfg *config)
{
	Cfg		*cf;
	char	*rcptCopy;
	char	*rcptDomain;
	int		status = 1;

	/* Check for relay-domain option */
	if ((cf = cfg_get("relay-domain", config)) == (Cfg *)0)
	{
		return(1);	
	}

	/* Check if client matches a relay-domain */
	if (relay_domain(addr, config) == 1)
	{
		return(1);
	}

	/* Malloc a copy of rcpt */
  	if ((rcptCopy = strmcpy(rcpt)) == (char *)0)
	{
		syslog(LLEV, "fwtksyserr: out of memory: %s", strerror(errno));
		return(1);			
	}

 	rcptDomain = chop_domain(rcptCopy);
 
	/* Check if the mail is destined for a relay-domain */
	if (relay_domain(rcptDomain, config) == 0)
	{
		printf("571 %s... we do not relay\r\n", rcpt);
		status = 0;
	}
	free(rcptCopy);

	return(status);
}

/*-------------------------------[valid_sender]-------------------------------
 * Verifies that the mail client has a valid NS record.
 *
 *	Paramaters:
 *		sender		Mail client's email address
 *		config		Pointer to the configuration table
 *
 *	Returns:
 *		0			No NS record found
 *		1			NS record found or ns-required not configured
 *----------------------------------------------------------------------------*/
int valid_sender(char *sender, Cfg *configTable)
{
    char    *sp;
    char    *senderCopy;
    int      returnValue = 1;
    union
    {
        HEADER hdr;             /* defined in resolv.h */
        u_char  buf[PACKETSZ];  /* defined in arpa/nameserver.h */
    } response;

	if ( (option_selected("nospam", "ns-required", configTable) == 0) ||
         (sender == (char *)0) )
	{
			return(1);
	}

    if ((senderCopy = strmcpy(sender)) == (char *)0)
    {
        syslog(LLEV, "fwtksyserr: out of memory: %s", strerror(errno));
        return(1);
    }

    sp = chop_domain(senderCopy);

    if (res_query(sp, C_IN, T_NS, (u_char *)&response, sizeof(response)) < 0)
    {
        if (h_errno == HOST_NOT_FOUND)
        {
            printf("550 sender ignored: %s does not have a name server record\r\n", sp);
            returnValue = 0;
        }
    }

    free(senderCopy);
    return(returnValue);
}


/*-----------------------------[matches_maps_rbl]-----------------------------
 * Checks the mail client's IP address agains Paul Vixie's MAPS RBL
 *
 *	Paramaters:
 *		addr		Mail client's IP address
 *		config		Pointer to the configuration table
 *
 *	Returns:
 *		0			No match or maps-rbl not configured
 *      1			Match
 *----------------------------------------------------------------------------*/
int matches_maps_rbl(char *addr, Cfg *configTable)
{
	char	*raddr;
	int		 returnValue = 0;

	if ( (option_selected("nospam", "maps-rbl", configTable) == 0) ||
         (addr == (char *)0) )
			return(0);

	if ((raddr = rblcpy(addr)) == (char *)0)
	{
		syslog(LLEV, "fwtksyserr: out of memory: %s", strerror(errno));
		return(0);			
	}

	if (gethostbyname(raddr) != NULL)
	{
		printf("550 sender ignored: %s was found in the MAPS RBL\r\n",
			addr);
		returnValue = 1;
	}

	free(raddr);
	return(returnValue);
}

/*-------------------------------[relay_domain]-------------------------------
 * Attempts to match an IP address with the relay_domain patterns
 *
 * 	Paramaters:
 *		addr		IP address to match
 *		config		Pointer to the configuration table
 *
 *	Returns:
 *		1			Match
 *		0			No match
 *----------------------------------------------------------------------------*/
static int relay_domain(char *addr, Cfg *config)
{
	char *local = NULL;
	int	  cnt;
	Cfg  *cf = cfg_get("relay-domain", config);

	for (; cf != (Cfg *)0; cf = cfg_get("relay-domain",(Cfg *)0))
	{
    	if (cf->argc > 0)
		{
			for (cnt = 0; cnt < cf->argc; cnt++)
			{
				local=cf->argv[cnt];	
				if (hostmatch(local, addr) == 1)
				{
					return(1);
				}
			}
		}
	}
	return(0);
}



/*------------------------------[option_selected]------------------------------
 * Checks for nospam options
 *
 *	Paramaters:
 *		section		Section to check in (nospam) 
 *		option		Option to look for
 *		config		Pointer to the configuration table 
 *
 *	Returns:
 *		0			Not selected
 *		1			Selected	
 *----------------------------------------------------------------------------*/
static int option_selected(const char *section, const char *option, Cfg *config)
{
	int	 cnt;
	Cfg *cf = cfg_get(section, config);

	for (; cf != (Cfg *)0; cf = cfg_get(section, (Cfg *)0))
	{
    	if (cf->argc > 0)
		{
			for (cnt = 0; cnt < cf->argc; cnt++)
			{
				if (strcasecmp(cf->argv[cnt], option) == 0)
					return(1);
			}
		}
	}

	return(0);
}

/*----------------------------------[strmcpy]----------------------------------
 * Malloc and copy a string
 *
 *	Paramaters:
 *		s			String to copy
 *
 *	Returns:
 *		char*		New string or NULL if malloc failed	
 *
 *  Note:
 *		User is responsible for cleanup.
 *----------------------------------------------------------------------------*/
static char *strmcpy(const char* s)
{
	int	 len = strlen(s);
	char *newString = malloc((len+1)*sizeof(char));
	int  i, j = 0;

	if (newString)
	{
		for (i=0; i < len; i++)
		{
			if (s[i] != '<' && s[i] != '>')
			{
				newString[j++] = tolower(s[i]);
			}
		}
		newString[j] = 0;
	}

	return(newString);
}

/*----------------------------------[rblcpy]----------------------------------
 * Creates a new string by reversing addr and appending the rbl domain 
 * 		Example: a.b.c.d => d.c.b.a.rbl.maps.vix.com
 *
 *	Paramater:
 *		addr		Mail client's IP address
 *
 *	Returns:
 *		char*		New string or NULL if malloc failed
 *
 *	Note:
 *		User is responsible for clean up.
 *----------------------------------------------------------------------------*/
static char* rblcpy(const char* addr)
{
	int  addrlen    = strlen(addr);
	int	 len        = addrlen + strlen(RBL_DOM);
	char *newString = malloc((len+1)*sizeof(char));
	int i;		
	int j = 0;		
	int k = addrlen;	

	if (newString)
	{
		for (i=addrlen - 1; i >= 0; i--)
		{
			if ((addr[i] == '.') || (i == 0))
			{
				int l = (i ? i + 1 : i);

				for (;l < k; l++) newString[j++] = addr[l];
				newString[j++] = '.';
				k = i;
			}	
		}
		newString[strlen(addr)] = 0;
		strlcat(newString, RBL_DOM, len);
	}

	return(newString);
}

/*----------------------------------[cntchr]----------------------------------
 * Returns the number of times c appears in s
 *----------------------------------------------------------------------------*/
static int cntchr(char *s, char c)
{
	char *sp = s;
	int   cnt = 0;

	if (sp == (char *)NULL) return(0);

	while (*sp != '\0') { if ( *sp == c) cnt ++; sp++; }

	return(cnt);
}

/*--------------------------------[chop_domain]--------------------------------
 * Chops US top level domains to x.x and others to x.x.x
 *
 *	Paramater:
 *		s			String to chop
 *
 *	Returns:
 *		s			Modified string or NULL on error
 *----------------------------------------------------------------------------*/
static char *chop_domain(char *s)
{
	int		dots;
	char	*rval = s;

    if (s == (char *)0) return(s);

	rval = strrchr(s, '@');
	if (rval == (char *)NULL) { rval = s; } else { rval++; }

	dots = cntchr(rval, '.');
	while ( (rval != (char *)NULL) && (dots-- > 2) )
	{
		if ((rval = strchr(rval, '.')) != (char *)NULL) rval++;
	}

	if ( (rval != (char *)NULL) && (dots) )
	{
		char *last = strrchr(s, '.');
		if (last != (char *)NULL)
		{
			last++;
			if ( (strcmp(last, "com")   == 0) ||
		     	 (strcmp(last, "edu")   == 0) ||
		     	 (strcmp(last, "net")   == 0) ||
		     	 (strcmp(last, "org")   == 0) ||
		     	 (strcmp(last, "gov")   == 0) ||
		     	 (strcmp(last, "mil")   == 0) ||
		     	 (strcmp(last, "firm")  == 0) ||
		     	 (strcmp(last, "store") == 0) ||
		     	 (strcmp(last, "web")   == 0) ||
		     	 (strcmp(last, "arts")  == 0) ||
		     	 (strcmp(last, "rec")   == 0) ||
		     	 (strcmp(last, "info")  == 0) ||
		     	 (strcmp(last, "nom")   == 0)   )
			{
				if ((rval = strchr(rval, '.')) != (char *)NULL) rval++;
			}
		}
	}

	if (rval != (char *)NULL) return(rval);

	return(s);
}

/*----------------------------[spam_db_key_exists]----------------------------
 * Check for key in the spam database
 *
 * 	Parmaters:
 *		s  			Key to check for
 *      db			Database handle
 *
 *	Returns:
 *		0			Key not found
 *		1			Key found
 *----------------------------------------------------------------------------*/
static int spam_db_key_exists(char *s, DBM *db)
{
	datum key, val;
	static	char* matchingKey;

	key.dptr  = s;
	key.dsize = strlen(s) + 1;
	val = dbm_fetch(db, key);

	matchingKey = val.dptr;

	return(val.dptr != NULL);
}


