/*
**    spasm - The spasm anti-spam milter
**    Copyright (C) 2002 Sean Hanson <aradia@theasylum.org>
**
**    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 <ctype.h>
#include <db2/db.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pwd.h>
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sysexits.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>

#include "libmilter/mfapi.h"
#include "spasm.h"


/* Private data structure */
struct mlfiPriv {
    char *hostname;
    char *hostdomain;
    unsigned long int ipaddress;
    char *helohost;
    char *helodomain;
    char *mailfrom;
    char *mailfromdomain;
    char *rcptfile;
    char *ipstring;
    char *envrcpt;
    char *username;
    char *domain;
    int unknownusers;
    unsigned long int rejected;
};

#define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx))

/* Various random thingies */
#define DOMAIN_NAME_REGEX "([^.]*[.][^.]*)[.]*$"
static regex_t re_sld;
static char *progname;

/* Zombie prevention */
void sigCHLD() {
    int status;
    syslog(LOG_INFO, "(sigCHLD) Caught SIGCHLD");
    while(wait3(&status, WNOHANG, (struct rusage *) 0) >= 0);
	/* Sit around and twiddle our thumbs */
}

/* SIGINT, SIGTERM */
void cleanup() {
    regfree(&re_sld);
    syslog(LOG_INFO, "(cleanup) Shutting down");
    closelog();
    exit(0);    
}

/* Try to catch SEGVs */
void sigSEGV() {
    syslog(LOG_ERR, "(sigSEGV) Caught SIGSEGV");
    closelog();
    regfree(&re_sld);
    exit(0);
}

/* lowercase() - set a string to lowercase letters for case-insensitive matching */
void lowercase(char *buf) {
    size_t i, n;

    n = strlen(buf);
    for(i=0;i<n;i++) if(isupper(buf[i])) buf[i] = tolower(buf[i]);
}

/* hash_lookup() - lookup a db2 hash entry, return the value */
char* hash_lookup(char *dbfile, char *key) {
    DB *db;
    DB_ENV dbenv;
    DB_INFO dbinfo;
    DBT dbk, dbv;
    int rv;
    char *rp = NULL;

    if(key == NULL || *key == '\0') return 0;

    memset(&dbenv, '\0', sizeof(DB_ENV));
    memset(&dbinfo, '\0', sizeof(DB_INFO));
    memset(&dbk, '\0', sizeof(DBT));
    memset(&dbv, '\0', sizeof(DBT));

    dbinfo.db_pagesize = 512;
    rv = db_open(dbfile, DB_HASH, DB_RDONLY, 0, &dbenv, &dbinfo, &db);
    if(rv == 0) {
        dbk.data = key;
        dbk.size = strlen(key);
	if(DEBUGGING) syslog(LOG_DEBUG, "Searching for %s in %s", key, dbfile);
        rv = db->get(db, NULL, &dbk, &dbv, 0);
        if(rv || (dbv.data == NULL)) rp = NULL;
	else {
	    if((rp = calloc(dbv.size + 1, 1)) != NULL)
	        strncpy(rp, (char *)dbv.data, dbv.size);
	}
	if(DEBUGGING) syslog(LOG_DEBUG, "Returned %s", rp);
        db->close(db, 0);
    }
    return rp;
}

/* add_to_blacklist() - add an entry to a blacklist textfile */
void add_to_blacklist(char *blfile, char *key) {
    DB *db;
    DB_ENV dbenv;
    DB_INFO dbinfo;
    DBT dbk, dbv;
    int dbfd;
    struct flock lockinfo;

    if(chdir(BLACKLIST_DIR) != 0) { 
        syslog(LOG_ERR, "(add_to_blacklist) Error changing directory: %m");
        return;
    }

    memset(&dbenv, '\0', sizeof(DB_ENV));
    memset(&dbinfo, '\0', sizeof(DB_INFO));
    memset(&dbk, '\0', sizeof(DBT));
    memset(&dbv, '\0', sizeof(DBT));
    memset(&lockinfo, '\0', sizeof(lockinfo));

    lockinfo.l_type = F_WRLCK;
    dbinfo.db_pagesize = 512;

    if(db_open(blfile, DB_HASH, DB_CREATE, 0644, &dbenv, &dbinfo, &db) != 0) {
	syslog(LOG_ERR, "(add_to_blacklist) Error opening database: %m");
	return;
    }
    if(db->fd(db, &dbfd) != 0) {
	syslog(LOG_ERR, "(add_to_blacklist) Error reading file descriptor for database: %m\n");
	db->close(db, 0);
	return;
    }
    if(fcntl(dbfd, F_SETLKW, &lockinfo) == -1) {
	syslog(LOG_ERR, "(add_to_blacklist) Error locking database: %m");
	db->close(db, 0);
	return;
    }
    dbk.data = key;
    dbk.size = strlen(key);
    dbv.data = "1";
    dbv.size = strlen(dbv.data);

    syslog(LOG_INFO, "(add_to_blacklist) Adding %s to %s", key, blfile);
    db->put(db, NULL, &dbk, &dbv, 0);
    db->sync(db, 0);
    db->close(db, 0);

    return;
}

/* log_rejection() - Log rejected spam in per-user logfiles */
void log_rejection(char *rcptfile, char *rcpt, char *mailfrom, char *hostname, char *ipstring, char *helo, char *reason) {
    int fd;
    time_t timep;
    char times[35];
    char *buf;
    struct flock lockinfo;
    size_t strsize;

    lowercase(rcptfile);
    lockinfo.l_type = F_WRLCK;
    lockinfo.l_start = 0;
    lockinfo.l_whence = SEEK_SET;
    lockinfo.l_len = 0;

    if(rcptfile == NULL) {
	syslog(LOG_ERR, "(log_rejection) Error: priv->rcptfile is NULL");
	return;
    }

    if(chdir(LOG_DIRECTORY) != 0) {
	syslog(LOG_ERR, "(log_rejection) Error changing directory: %m");
	return;
    }
    fd = open(rcptfile, O_WRONLY|O_CREAT|O_APPEND, 0644);
    if(fd < 0) {
	syslog(LOG_ERR, "(log_rejection) Error opening file %s: %m", rcptfile);
	return;
    }
    if(fcntl(fd, F_SETLKW, &lockinfo) == -1) {
	syslog(LOG_ERR, "(log_rejection) Error locking file %s: %m", rcptfile);
	close(fd);
	return;
    }
    timep = time(NULL);
    memset(times, '\0', sizeof(times));
    sprintf(times, "%s", (char *)ctime((time_t*)&timep));
    /* Get rid of ctime()'s newline */
    times[strlen(times)-1] = '\0';
    strsize = strlen(rcpt) + strlen(mailfrom) + strlen(hostname) + strlen(ipstring) + strlen(reason) + strlen(times) + 10;
    if(helo != NULL) strsize += strlen(helo);
    else strsize += 6;
    if((buf = calloc(strsize, 1)) != NULL) {
	sprintf(buf, "%s:%s:%s:%s:%s:%s:%s\n", times, rcpt, mailfrom, hostname, ipstring, helo, reason);
	write(fd, buf, strlen(buf));
	free(buf);
    }
    close(fd);
    return;
}

/* userbl_lookup() - Lookup per-user blacklist options */
unsigned long int userbl_lookup(char *username, char *domain) {
    unsigned long int userbl;
    char *dbfile;
    char *key;
    char *value;

    if(DEBUGGING) syslog(LOG_DEBUG, "(userbl_lookup) Starting");
    if((dbfile = calloc(strlen(SPASM_DIR) + strlen(USERBL_DB) + 2, 1)) == NULL) {
	syslog(LOG_ERR, "(userbl_lookup) Error allocating memory for dbfile: %m");
	return 0;
    }
    if((key = calloc(strlen(username) + strlen(domain) + 2, 1)) == NULL) {
	syslog(LOG_ERR, "(userbl_lookup) Error allocating memory for key: %m");
	free(dbfile);
	return 0;
    }
    sprintf(dbfile, "%s/%s", SPASM_DIR, USERBL_DB);
    sprintf(key, "%s@%s", username, domain);
    /* First, lookup exact match */
    if((value = hash_lookup(dbfile, key)) == NULL) {
	/* If not found, lookup default domain options */
	if((value = hash_lookup(dbfile, domain)) == NULL) {
	    /* If not found, return 0 */
	    userbl = 0;
	} else {
	    userbl = strtoul(value, NULL, 10);
	}
    } else {
	userbl = strtoul(value, NULL, 10);
    }

    free(key);
    free(dbfile);
    syslog(LOG_DEBUG, "(userbl_lookup) Finished");
    return userbl;
}

/* userwl_lookup() - Lookup a whitelist entry */
int userwl_lookup(char *username, char *domain, char *mailfrom, char *hostname, char *ipstring) {
    char *userdbfile;
    char *domaindbfile;
    char *ipa;
    char *buf;
    int rv = 0;

    if(DEBUGGING) syslog(LOG_DEBUG, "(userwl_lookup) Starting");
    if((userdbfile = calloc(strlen(WHITELIST_DIR) + strlen(username) + strlen(domain) + 4, 1)) == NULL) {
	syslog(LOG_ERR, "(userwl_lookup) Error allocating memory for userdbfile: %m");
	return 0;
    }
    if((domaindbfile = calloc(strlen(WHITELIST_DIR) + strlen(domain) + 3, 1)) == NULL) {
	syslog(LOG_ERR, "(userwl_lookup) Error allocating memory for domaindbfile: %m");
	free(userdbfile);
	return 0;
    }
    ipa = strdup(ipstring);
    if(ipa == NULL) {
	syslog(LOG_ERR, "(userwl_lookup) Error allocating memory for ipa: %m");
	free(userdbfile);
	free(domaindbfile);
	return 0;
    }
    sprintf(userdbfile, "%s/%s@%s", WHITELIST_DIR, username, domain);
    sprintf(domaindbfile, "%s/%s", WHITELIST_DIR, domain);

    /* Lookup mailfrom in user's whilelist */
    if(hash_lookup(userdbfile, mailfrom) != NULL) rv = 1;
    /* Lookup mailfrom in domain's whitelist */
    else if(hash_lookup(domaindbfile, mailfrom) != NULL) rv = 1;
    /* Lookup hostname in user's whitelist */
    else if(hash_lookup(userdbfile, hostname) != NULL) rv = 1;
    /* Lookup hostname in domain's whitelist */
    else if(hash_lookup(domaindbfile, hostname) != NULL) rv = 1;
    /* Lookup ip address in user's whitelist */
    else if(hash_lookup(userdbfile, ipstring) != NULL) rv = 1;
    /* Lookup ip address in domain's whitelist */
    else if(hash_lookup(domaindbfile, ipstring) != NULL) rv = 1;
    else {
	/* Check all subdomains in hostname */
	buf = hostname;
        while((buf = strchr(buf, '.')) && rv == 0) {
	    buf++;
	    if(hash_lookup(userdbfile, buf) != NULL) rv = 1;
	    else if(hash_lookup(domaindbfile, buf) != NULL) rv = 1;
	}
	/* Check all pseudo-classful network blocks in ip address */
	while((buf = strrchr(ipa, '.')) && rv == 0) {
	    *buf = '\0';
	    if(hash_lookup(userdbfile, ipa) != NULL) rv = 1;
	    else if(hash_lookup(domaindbfile, ipa) != NULL) rv = 1;
	}
    }
    free(userdbfile);
    free(domaindbfile);
    free(ipa);
    if(DEBUGGING) syslog(LOG_DEBUG, "(userwl_lookup) Finished");
    return rv;
}

/* user_lookup() - verifies a user for unknownusers count */
int user_lookup(char *username, char *domain) {

    /* Rather use a regex? Put it here! */
	/* 
	 * Example:
	 * if(regex("POSIX_Extended_RE") == 1) return 1;
	 *
	 * Or, if you want to reject a matched regex:
	 * if(regex("POSIX_Extended_RE") == 1) return 0;
	 */

    /* Lookup username in passwd and aliases files */
    if(strcmp(domain, DEFAULT_DOMAIN) == 0) {
	/* passwd file check */
	if(getpwnam(username) != NULL) return 1;
	/* alias file check */
	{
	    FILE *fp;
	    char buf[256];

	    fp = fopen("/etc/mail/aliases", "r");
	    while(fgets(buf, sizeof(buf), fp) != NULL) {
	        if(strncasecmp(buf, username, strlen(username)) == 0) {
		    fclose(fp);
		    return 1;
	        }
	    }
	    fclose(fp);
	}
	return 0;
    }

    /* Check for virtual email addresses */


    return 1;
}

/* dnsbl_lookup() - Lookup a hostname, return 1 if found, 0 if !found */
int dnsbl_lookup(char *hostname) {
    struct hostent hostbuf, *hp;
    size_t hstbuflen;
    char *tmphstbuf;
    int res;
    int herr;

    if(DEBUGGING) syslog(LOG_DEBUG, "(dnsbl_lookup) Starting");
    if(hostname == NULL) return 0;
    hstbuflen = 1024;
    tmphstbuf = (char *)malloc(hstbuflen);
    while((res = gethostbyname_r(hostname, &hostbuf, tmphstbuf, hstbuflen, &hp, &herr)) == ERANGE)
    {
        hstbuflen *= 2;
        tmphstbuf = (char *)realloc(tmphstbuf, hstbuflen);
    }
    free(tmphstbuf);
    if(DEBUGGING) syslog(LOG_DEBUG, "(dnsbl_lookup) Finished");
    if(res || hp == NULL) {
        if(hp != NULL) free(hp);
        return 0;
    }
    return 1;
}

/* search_regex() - Perform an individual regex */
int search_regex(char *pattern, char *key) {
    regmatch_t pmatch;
    regex_t re;

    if(pattern == NULL || key == NULL) return 0;
    if(regcomp(&re, pattern, REG_EXTENDED) != 0) {
	syslog(LOG_ERR, "(search_regex) Error compiling regex \"%s\": %m", pattern);
	return 0;
    }
    if(regexec(&re, key, 1, &pmatch, 0) == 0) {
	regfree(&re);
	return 1;
    }
    regfree(&re);
    return 0;
}

/* search_regex_spamtrap() = Run each entry in spamtrap database against arguments */
int search_regex_spamtrap(char *username, char *email) {
    char *buf;
    DB *db;
    DB_ENV dbenv;
    DB_INFO dbinfo;
    DBT dbk, dbv;
    DBC *dbcp;

    if(username == NULL || email == NULL) return 0;

    memset(&dbenv, '\0', sizeof(DB_ENV));
    memset(&dbinfo, '\0', sizeof(DB_INFO));
    memset(&dbk, '\0', sizeof(DBT));
    memset(&dbv, '\0', sizeof(DBT));

    if(chdir(BLACKLIST_DIR) != 0) {
	syslog(LOG_ERR, "(search_regex_spamtrap) Error changing directory: %m");
	return 0;
    }

    if(db_open(SPAMTRAP_DB, DB_HASH, DB_RDONLY, 0, &dbenv, &dbinfo, &db) != 0) {
	return 0;
    }
    if(db->cursor(db, NULL, &dbcp) != 0) {
	db->close(db, 0);
	return 0;
    }
    while(dbcp->c_get(dbcp, &dbk, &dbv, DB_NEXT) == 0) {
	if((buf = calloc(dbk.size+1,1)) == NULL) {
	    syslog(LOG_ERR, "(search_regex_spamtrap) Error allocating memory for key: %m");
	    dbcp->c_close(dbcp);
	    db->close(db,0);
	    return 0;
	} else {
	    strncpy(buf, dbk.data, dbk.size);
	    if(search_regex(buf, username) == 1 || search_regex(buf, email) == 1) {
		dbcp->c_close(dbcp);
		db->close(db, 0);
		free(buf);
		return 1;
	    }
	    free(buf);
	}
    }
    dbcp->c_close(dbcp);
    db->close(db, 0);
    return 0;
}

/* search_regex_bl() - Run each entry in regex database against argument */
int search_regex_bl(char *key) {
    FILE *fp;
    char data[256];
    char *buf;

    if(key == NULL) return 0;
    if(chdir(BLACKLIST_DIR) != 0) {
	syslog(LOG_ERR, "(search_regex_bl) Error changing directory: %m");
	return 0;
    }

    fp = fopen(RE_ENVFROM_BLACKLIST, "r");
    if(fp == NULL) {
	syslog(LOG_ERR, "(search_regex_bl) Error opening re_bl file %s: %m", RE_ENVFROM_BLACKLIST);
	return 0;
    }

    while(fgets(data, sizeof(data), stdin) != NULL) {
	if(strncmp(data, "#", 1) == 0) continue;
	if((buf = strchr(data, '\n')) != NULL) *buf = '\0';
	if(search_regex(data, key) == 1) {
	    fclose(fp);
	    return 1;
	}
    }

    fclose(fp);
    return 0;
}

/* filter_local() - Lookup entries in local db2 hash */
int filter_local(char *mailfrom, char *mailfromdomain, char *hostname, char *ipstring) {
    char *buf;
    char *ipa;
    char *dbfile;
    int rv = 0;

    if(DEBUGGING) syslog(LOG_DEBUG, "(filter_local) Starting");
    if((dbfile = calloc(strlen(BLACKLIST_DIR) + strlen(LOCALFILTERS_DB) + 2, 1)) == NULL) {
	syslog(LOG_ERR, "(filter_local) Error allocating memory for dbfile: %m");
	return -1;
    }
    if((ipa = strdup(ipstring)) == NULL) {
	syslog(LOG_ERR, "(filter_local) Error allocating memory for ipa: %m");
	free(dbfile);
	return -1;
    }
    sprintf(dbfile, "%s/%s", BLACKLIST_DIR, LOCALFILTERS_DB);

    /* Lookup mailfrom address */
    if(hash_lookup(dbfile, mailfrom) != NULL) rv = 1;

    /* Lookup mailfrom address's domain */
    else if(hash_lookup(dbfile, mailfromdomain)) rv = 1;

    /* Lookup server hostname */
    else if(hash_lookup(dbfile, hostname) != NULL) rv = 1;

    /* Lookup server ip address */
    else if(hash_lookup(dbfile, ipstring) != NULL) rv = 1;
    
    /* Lookup all subdomains of server hostname */
    if(hostname != NULL) {
        buf = hostname;
        while((buf = strchr(buf, '.')) && rv != 1) {
	    buf++;
	    if(hash_lookup(dbfile, buf) != NULL) rv = 1;
        }
    }

    /* Lookup pseudo-classful network blocks in ip address */
    while((buf = strrchr(ipa, '.')) && rv != 1) {
	*buf = '\0';
	if(hash_lookup(dbfile, ipa) != NULL) rv = 1;
    }

    free(dbfile);
    free(ipa);
    if(DEBUGGING) syslog(LOG_DEBUG, "(filter_local) Finished");
    return rv;
}

/* filter_rDNS() - Determine if ip address was resolved */
int filter_rDNS(char *hostname) {
    if(hostname == NULL) return 0;
    if(strncmp(hostname, "[", 1) == 0) {
	return 1;
    }
    return 0;
}

/* filter_HELO() - Reject if no HELO/EHLO given, or argument != resolved hostname */
int filter_HELO(char *helohost, char *hostname) {
    if(hostname == NULL) return 0;
    if(helohost == NULL) return 1;
    if(strcmp(helohost, hostname) != 0) return 1;
    return 0;
}

/* filter_MAIL_HOST() - Determine if domain of mailfrom == domain of hostname */
int filter_MAIL_HOST(char *mailfromdomain, char *hostdomain) {
    if(mailfromdomain == NULL || hostdomain == NULL) return 0;
    if(strcmp(mailfromdomain, hostdomain) == 0) return 0;
    else return 1;
}

sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr) {
    struct mlfiPriv *priv;
    unsigned char *ipa;
    regmatch_t pmatch;

    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_connect) Starting");
    ipa = (unsigned char *) &((struct sockaddr_in *) hostaddr)->sin_addr.s_addr;
    if((priv = calloc(1, sizeof(*priv))) == NULL) {
	syslog(LOG_ERR, "(mlfi_connect) Error allocating memory for priv: %m");
	return SMFIS_CONTINUE;
    }
    priv->hostname = strdup(hostname);
    if(priv->hostname == NULL) {
	syslog(LOG_ERR, "(mlfi_connect) Error allocating memory for priv->hostname: %m");
	free(priv);
	return SMFIS_CONTINUE;
    }
    lowercase(priv->hostname);
    priv->ipaddress = ((struct sockaddr_in *) hostaddr)->sin_addr.s_addr;
    if((priv->ipstring = calloc(16, 1)) == NULL) {
	syslog(LOG_ERR, "(mlfi_connect) Error allocating memory for priv->ipstring: %m");
	free(priv->hostname);
	free(priv);
	return SMFIS_CONTINUE;
    }
    sprintf(priv->ipstring, "%d.%d.%d.%d", ipa[0], ipa[1], ipa[2], ipa[3]);
    if(regexec(&re_sld, priv->hostname, 1, &pmatch, 0) == 0) {
	if((priv->hostdomain = calloc(pmatch.rm_eo - pmatch.rm_so + 1, 1)) == NULL) {
	    syslog(LOG_ERR, "(mlfi_connect) Error allocating memory for priv->hostdomain: %m");
	} else {
	    strncpy(priv->hostdomain, (priv->hostname)+pmatch.rm_so, pmatch.rm_eo-pmatch.rm_so);
	}
    } else {
	priv->hostdomain = NULL;
    }

    /*
    priv->helohost = NULL;
    priv->helodomain = NULL;
    priv->mailfrom = NULL;
    priv->mailfromdomain = NULL;
    priv->unknownusers = 0;
    priv->rcptfile = NULL;
    priv->envrcpt = NULL;
    priv->username = NULL;
    priv->domain = NULL;
    priv->rejected = 0;
    */

    smfi_setpriv(ctx, priv);
    syslog(LOG_INFO, "(mlfi_connect) Connection from %s (%s)", priv->ipstring, priv->hostname);
    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_connect) Finished");
    
    return SMFIS_CONTINUE;
}

sfsistat mlfi_helo(SMFICTX *ctx, char *helohost) {
    regmatch_t pmatch;

    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_helo) Starting");
    if(MLFIPRIV == NULL) return SMFIS_CONTINUE;
    if(helohost == NULL) {
	syslog(LOG_INFO, "(mlfi_helo) No HELO issued");
	return SMFIS_CONTINUE;
    }
    MLFIPRIV->helohost = strdup(helohost);
    if(MLFIPRIV->helohost == NULL) {
	syslog(LOG_WARNING, "(mlfi_helo) Error allocating memory for priv->helohost: %m");
	return SMFIS_CONTINUE;
    }
    lowercase(MLFIPRIV->helohost);
    if(regexec(&re_sld, MLFIPRIV->helohost, 1, &pmatch, 0) == 0) {
        if((MLFIPRIV->helodomain = calloc(pmatch.rm_eo - pmatch.rm_so + 1, 1)) == NULL) {
            syslog(LOG_ERR, "(mlfi_helo) Error allocating memory for priv->helodomain: %m");
        } else {
            strncpy(MLFIPRIV->helodomain, (MLFIPRIV->helohost)+pmatch.rm_so, pmatch.rm_eo-pmatch.rm_so);
        }
    }

    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_helo) Finished");
    return SMFIS_CONTINUE;
}

sfsistat mlfi_envfrom(SMFICTX *ctx, char **argv) {
    char *chr;
    regmatch_t pmatch;

    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envfrom) Starting");
    if(MLFIPRIV == NULL) return SMFIS_CONTINUE;

    if(**argv == '<') MLFIPRIV->mailfrom = strdup((*argv)+1);
    else MLFIPRIV->mailfrom = strdup(*argv);
    if(MLFIPRIV->mailfrom == NULL) {
	syslog(LOG_WARNING, "(mlfi_envfrom) Error allocating memory for priv->mailfrom");
	return SMFIS_CONTINUE;
    }
    chr = strchr(MLFIPRIV->mailfrom, '>');
    if(chr != NULL) *chr = '\0';
    lowercase(MLFIPRIV->mailfrom);

    chr = strchr(MLFIPRIV->mailfrom, '@');
    if(chr != NULL) {
	chr++;
        if(regexec(&re_sld, chr, 1, &pmatch, 0) == 0) {
            if((MLFIPRIV->mailfromdomain = calloc(pmatch.rm_eo - pmatch.rm_so + 1, 1)) == NULL) {
                syslog(LOG_ERR, "(mlfi_envfrom) Error allocating memory for priv->mailfromdomain: %m");
            } else {
                strncpy(MLFIPRIV->mailfromdomain, chr+pmatch.rm_so, pmatch.rm_eo-pmatch.rm_so);
            }
        }
    }

    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envfrom) Finished");
    return SMFIS_CONTINUE;
}

sfsistat mlfi_envrcpt(SMFICTX *ctx, char **argv) {
    unsigned long int userbl = 0;
    unsigned char *ipa;
    char *username;
    char *domain;
    char *chr;
    char *mbuf;
    char buf[256];
    int tagonly = 0;
    regmatch_t pmatch;

    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) Starting");
    if(MLFIPRIV == NULL) return SMFIS_CONTINUE;

    if(MLFIPRIV->unknownusers > MAX_UNKNOWN_USERS) {
	if(DEBUGGING) syslog(LOG_INFO, "(mlfi_envrcpt) Adding %s to MAX_UNKNOWN_USERS_BL", MLFIPRIV->ipstring);
	add_to_blacklist(MAX_UNKNOWN_USERS_BL, MLFIPRIV->ipstring);
    }

    MLFIPRIV->envrcpt = strdup(*argv);
    if(MLFIPRIV->envrcpt == NULL) {
	syslog(LOG_WARNING, "(mlfi_envrcpt) Error allocating memory for priv->envrcpt");
    }

    if(**argv == '<') username = strdup((*argv)+1);
    else username = strdup(*argv);
    if(username == NULL) {
	syslog(LOG_ERR, "(mlfi_envrcpt) Error allocating memory for username");
	return SMFIS_CONTINUE;
    }
    chr = strchr(username, '>');
    if(chr != NULL) *chr = '\0';

    domain = strchr(username, '@');
    if(domain != NULL) {
	*domain = '\0';
	domain++;
        if(regexec(&re_sld, domain, 1, &pmatch, 0) == 0) {
            if((MLFIPRIV->domain = calloc(pmatch.rm_eo - pmatch.rm_so + 1, 1)) == NULL) {
                syslog(LOG_ERR, "(mlfi_envrcpt) Error allocating memory for priv->domain: %m");
	        free(username);
	        return SMFIS_CONTINUE;
            } else {
                strncpy(MLFIPRIV->domain, domain+pmatch.rm_so, pmatch.rm_eo-pmatch.rm_so);
            }
        }
    }

    MLFIPRIV->username = strdup(username);
    if(MLFIPRIV->username == NULL) {
	syslog(LOG_ERR, "(mlfi_envrcpt) Error allocating memory for priv->username: %m");
	free(username);
	return SMFIS_CONTINUE;
    }
    free(username);

    if(user_lookup(MLFIPRIV->username, MLFIPRIV->domain) == 0) {
	if(DEBUGGING) syslog(LOG_WARNING, "Unknown user from %s (%s - %s)", MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring);
	MLFIPRIV->unknownusers++;
	smfi_setreply(ctx, "550", "5.1.1", "User unknown");
	return SMFIS_REJECT;
    }

    if((MLFIPRIV->rcptfile = calloc(strlen(MLFIPRIV->username) + strlen(MLFIPRIV->domain) + 2, 1)) == NULL) {
	syslog(LOG_WARNING, "(mlfi_envrcpt) Error allocating memory for priv->rcptfile: %m");
	return SMFIS_CONTINUE;
    }
    sprintf(MLFIPRIV->rcptfile, "%s@%s", MLFIPRIV->username, MLFIPRIV->domain);

#ifdef SPAMTRAP_DB
    if((mbuf = calloc(strlen(MLFIPRIV->username) + strlen(MLFIPRIV->domain) + 2, 1)) == NULL) {
	syslog(LOG_ERR, "(mlfi_envrcpt) Error allocating memory for mbuf: %m");
    } else {
	sprintf(mbuf, "%s@%s", MLFIPRIV->username, MLFIPRIV->domain);
        if(hash_lookup(SPAMTRAP_DB, MLFIPRIV->username) || hash_lookup(SPAMTRAP_DB, mbuf)) {
	    free(mbuf);
	    log_rejection(SPAMTRAP_LOG, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SPAMTRAP");
	    add_to_blacklist(SPAMTRAP_BL, MLFIPRIV->ipstring);
	    smfi_setreply(ctx, "550", "5.7.1", "Rejected");
	    return SMFIS_REJECT;
        }
	/* Run regexes based on spamtrap database entries */
	if(search_regex_spamtrap(MLFIPRIV->username, mbuf)) {
	    free(mbuf);
	    log_rejection(SPAMTRAP_LOG, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SPAMTRAP");
	    add_to_blacklist(SPAMTRAP_BL, MLFIPRIV->ipstring);
	    smfi_setreply(ctx, "550", "5.7.1", "Rejected");
	    return SMFIS_REJECT;
	}
	free(mbuf);
    }
#endif

    userbl = userbl_lookup(MLFIPRIV->username, MLFIPRIV->domain);

    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) Using %lu for %s@%s", userbl, MLFIPRIV->username, MLFIPRIV->domain);
    if(userbl == 0) {
	return SMFIS_CONTINUE;
    }

    if(userwl_lookup(MLFIPRIV->username, MLFIPRIV->domain, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring))
	return SMFIS_CONTINUE;

    ipa = (unsigned char *) &(MLFIPRIV->ipaddress);

    /* Reserved System Enhancements */
    if(userbl & SF_TAG_ONLY) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_TAG_ONLY");
	tagonly = 1;
    }

    if(userbl & SF_WHITELIST_ONLY) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_WHITELIST_ONLY");
	log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_WHITELIST_ONLY");
	if(tagonly == 0) {
	    smfi_setreply(ctx, "550", "5.7.1", "Rejected by spasm");
	    return SMFIS_REJECT;
	}
    }

    if(userbl & SF_SPAMTRAP) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_SPAMTRAP");
	if(hash_lookup(SPAMTRAP_BL, MLFIPRIV->ipstring) != NULL) {
	    log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_SPAMTRAP");
	    if(tagonly) MLFIPRIV->rejected |= SF_SPAMTRAP;
	    else {
		smfi_setreply(ctx, "550", "5.7.1", "Rejected by spasm");
		return SMFIS_REJECT;
	    }
	}
    }

    if(userbl & SF_MAX_UNKNOWN_USERS) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_MAX_UNKNOWN_USERS");
	if(hash_lookup(MAX_UNKNOWN_USERS_BL, MLFIPRIV->ipstring) != NULL) {
	    log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_MAX_UNKNOWN_USERS");
	    if(tagonly) MLFIPRIV->rejected |= SF_MAX_UNKNOWN_USERS;
	    else {
		smfi_setreply(ctx, "550", "5.7.1", "Rejected by spasm");
		return SMFIS_REJECT;
	    }
	}
    }

    /* User-definable filters */
    if(userbl & SF_LOCALFILTER) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_LOCALFILTER"); 
	if(filter_local(MLFIPRIV->mailfrom, MLFIPRIV->mailfromdomain, MLFIPRIV->hostname, MLFIPRIV->ipstring) || search_regex_bl(MLFIPRIV->mailfrom)) {
	    log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_LOCALFILTER");
	    if(tagonly) MLFIPRIV->rejected |= SF_LOCALFILTER;
	    else {
	        smfi_setreply(ctx, "550", "5.7.1", "Rejected by local filters");
	        return SMFIS_REJECT;
	    }
	}
    }

    if(userbl & SF_OSIRUSOFT) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_OSIRUSOFT");
	memset(buf, '\0', sizeof(buf));
	sprintf(buf, "%d.%d.%d.%d.relays.osirusoft.com", ipa[3], ipa[2], ipa[1], ipa[0]);
	if(dnsbl_lookup(buf)) {
	    log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_OSIRUSOFT");
	    if(tagonly) MLFIPRIV->rejected |= SF_OSIRUSOFT;
	    else {
	        smfi_setreply(ctx, "550", "5.7.1", "Rejected by Osirusoft: see http://relays.osirusoft.com");
	        return SMFIS_REJECT;
	    }
	}
    }

    if(userbl & SF_POSTMASTERRFC) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_POSTMASTERRFC");
	memset(buf, '\0', sizeof(buf));
	sprintf(buf, "%s.postmaster.rfc-ignorant.org", MLFIPRIV->hostname);
	if(dnsbl_lookup(buf)) {
	    log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_POSTMASTERRFC");
	    if(tagonly) MLFIPRIV->rejected |= SF_POSTMASTERRFC;
	    else {
	        smfi_setreply(ctx, "550", "5.7.1", "Rejected by postmaster.rfc-ignorant.org: see http://www.rfc-ignorant.org");
	        return SMFIS_REJECT;
	    }
	}
    }

    if(userbl & SF_DSNRFC) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_DSNRFC");
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%s.dsn.rfc-ignorant.org", MLFIPRIV->hostname);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_DSNRFC");
	    if(tagonly) MLFIPRIV->rejected |= SF_DSNRFC;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by dsn.rfc-ignorant.org: see http://www.rfc-ignorant.org");
                return SMFIS_REJECT;
	    }
        }   
    }       

    if(userbl & SF_ABUSERFC) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_ABUSERFC");
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%s.abuse.rfc-ignorant.org", MLFIPRIV->hostname);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_ABUSERFC");
	    if(tagonly) MLFIPRIV->rejected |= SF_ABUSERFC;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by abuse.rfc-ignorant.org: see http://www.rfc-ignorant.org");
                return SMFIS_REJECT;
	    }
        }   
    }       

    if(userbl & SF_WHOISRFC) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_WHOISRFC");
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%s.whois.rfc-ignorant.org", MLFIPRIV->hostname);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_WHOISRFC");
	    if(tagonly) MLFIPRIV->rejected |= SF_WHOISRFC;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by whois.rfc-ignorant.org: see http://www.rfc-ignorant.org");
                return SMFIS_REJECT;
	    }
        }   
    }       

    if(userbl & SF_IPWHOISRFC) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_IPWHOISRFC");
	memset(buf, '\0', sizeof(buf));
	sprintf(buf, "%d.%d.%d.%d.ipwhois.rfc-ignorant.org", ipa[3], ipa[2], ipa[1], ipa[0]);
	if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_IPWHOISRFC");
	    if(tagonly) MLFIPRIV->rejected |= SF_IPWHOISRFC;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by ipwhois.rfc-ignorant.org: see http://www.rfc-ignorant.org");
                return SMFIS_REJECT;
	    }
        }
    }


   if(userbl & SF_ORDB) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_ORDB");
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%d.%d.%d.%d.relays.ordb.org", ipa[3], ipa[2], ipa[1], ipa[0]);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_ORDB");
	    if(tagonly) MLFIPRIV->rejected |= SF_ORDB;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by ORDB: see http://www.ordb.org");
                return SMFIS_REJECT;
	    }
        }
    }

    if(userbl & SF_RDNS) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_RDNS");
	if(filter_rDNS(MLFIPRIV->hostname)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_RDNS");
	    if(tagonly) MLFIPRIV->rejected |= SF_RDNS;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected: No Reverse DNS");
                return SMFIS_REJECT;
	    }
	}
    }

    if(userbl & SF_HELO) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_HELO");	    
        if(filter_HELO(MLFIPRIV->helohost, MLFIPRIV->hostname)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_HELO");
	    if(tagonly) MLFIPRIV->rejected |= SF_HELO;
	    else {
                memset(buf, '\0', sizeof(buf));
                sprintf(buf, "Rejected: You are not %s", MLFIPRIV->helohost);
                smfi_setreply(ctx, "550", "5.7.1", buf);
                return SMFIS_REJECT;
	    }
        }
    }

    if(userbl & SF_XBL) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_XBL");
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%d.%d.%d.%d.xbl.selwerd.cx", ipa[3], ipa[2], ipa[1], ipa[0]);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_XBL");
	    if(tagonly) MLFIPRIV->rejected |= SF_XBL;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by xbl.selwerd.cx: Don't bother");
                return SMFIS_REJECT;
	    }
        }
    }       

    if(userbl & SF_SPAMCOP) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_SPAMCOP");
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%d.%d.%d.%d.bl.spamcop.net", ipa[3], ipa[2], ipa[1], ipa[0]);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_SPAMCOP");
	    if(tagonly) MLFIPRIV->rejected |= SF_SPAMCOP;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by SpamCop: see http://spamcop.net/bl.shtml");
                return SMFIS_REJECT;
	    }
        }
    }       

    if(userbl & SF_MAIL_HOST) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_MAIL_HOST");
        if(filter_MAIL_HOST(MLFIPRIV->mailfromdomain, MLFIPRIV->hostdomain)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_MAIL_HOST");
	    if(tagonly) MLFIPRIV->rejected |= SF_MAIL_HOST;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected: Sender's domain does not match server's domain");
                return SMFIS_REJECT;
	    }
        }
    }

    if(userbl & SF_KOREABLACKHOLES) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) SF_KOREABLACKHOLES");
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%d.%d.%d.%d.korea.blackholes.us", ipa[3], ipa[2], ipa[1], ipa[0]);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_KOREABLACKHOLES");
	    if(tagonly) MLFIPRIV->rejected |= SF_KOREABLACKHOLES;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by korea.blackholes.us");
                return SMFIS_REJECT;
	    }
        }
    }

    if(userbl & SF_CHINABLACKHOLES) {
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%d.%d.%d.%d.china.blackholes.us", ipa[3], ipa[2], ipa[1], ipa[0]);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_CHINABLACKHOLES");
	    if(tagonly) MLFIPRIV->rejected |= SF_CHINABLACKHOLES;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by china.blackholes.us");
                return SMFIS_REJECT;
	    }
        }
    }

    if(userbl & SF_BRAZILBLACKHOLES) {
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%d.%d.%d.%d.brazil.blackholes.us", ipa[3], ipa[2], ipa[1], ipa[0]);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_BRAZILBLACKHOLES");
	    if(tagonly) MLFIPRIV->rejected |= SF_BRAZILBLACKHOLES;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by brazil.blackholes.us");
                return SMFIS_REJECT;
	    }
        }
    }

    if(userbl & SF_JAPANBLACKHOLES) {
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%d.%d.%d.%d.japan.blackholes.us", ipa[3], ipa[2], ipa[1], ipa[0]);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_JAPANBLACKHOLES");
	    if(tagonly) MLFIPRIV->rejected |= SF_JAPANBLACKHOLES;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by japan.blackholes.us");
                return SMFIS_REJECT;
	    }
        }
    }
    if(userbl & SF_TAIWANBLACKHOLES) {
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%d.%d.%d.%d.taiwan.blackholes.us", ipa[3], ipa[2], ipa[1], ipa[0]);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_TAIWANBLACKHOLES");
	    if(tagonly) MLFIPRIV->rejected |= SF_TAIWANBLACKHOLES;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by taiwan.blackholes.us");
                return SMFIS_REJECT;
	    }
        }
    }
    if(userbl & SF_VERIOBLACKHOLES) {
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%d.%d.%d.%d.verio.blackholes.us", ipa[3], ipa[2], ipa[1], ipa[0]);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_VERIOBLACKHOLES");
	    if(tagonly) MLFIPRIV->rejected |= SF_VERIOBLACKHOLES;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by verio.blackholes.us");
                return SMFIS_REJECT;
	    }
        }
    }
    if(userbl & SF_VALUEWEBBLACKHOLES) {
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%d.%d.%d.%d.valueweb.blackholes.us", ipa[3], ipa[2], ipa[1], ipa[0]);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_VALUEWEBBLACKHOLES");
	    if(tagonly) MLFIPRIV->rejected |= SF_VALUEWEBBLACKHOLES;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by valueweb.blackholes.us");
                return SMFIS_REJECT;
	    }
        }
    }
    if(userbl & SF_RACKSPACEBLACKHOLES) {
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%d.%d.%d.%d.rackspace.blackholes.us", ipa[3], ipa[2], ipa[1], ipa[0]);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_RACKSPACEBLACKHOLES");
	    if(tagonly) MLFIPRIV->rejected |= SF_RACKSPACEBLACKHOLES;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by rackspace.blackholes.us");
                return SMFIS_REJECT;
	    }
        }
    }
    if(userbl & SF_INFLOWBLACKHOLES) {
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%d.%d.%d.%d.inflow.blackholes.us", ipa[3], ipa[2], ipa[1], ipa[0]);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_INFLOWBLACKHOLES");
	    if(tagonly) MLFIPRIV->rejected |= SF_INFLOWBLACKHOLES;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by inflow.blackholes.us");
                return SMFIS_REJECT;
	    }
        }
    }
    if(userbl & SF_BROADWINGBLACKHOLES) {
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%d.%d.%d.%d.broadwing.blackholes.us", ipa[3], ipa[2], ipa[1], ipa[0]);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_BROADWINGBLACKHOLES");
	    if(tagonly) MLFIPRIV->rejected |= SF_BROADWINGBLACKHOLES;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by broadwing.blackholes.us");
                return SMFIS_REJECT;
	    }
        }
    }
    if(userbl & SF_XOBLACKHOLES) {
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%d.%d.%d.%d.xo.blackholes.us", ipa[3], ipa[2], ipa[1], ipa[0]);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_XOBLACKHOLES");
	    if(tagonly) MLFIPRIV->rejected |= SF_XOBLACKHOLES;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by xo.blackholes.us");
                return SMFIS_REJECT;
	    }
        }
    }
    if(userbl & SF_ELIBLACKHOLES) {
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%d.%d.%d.%d.eli.blackholes.us", ipa[3], ipa[2], ipa[1], ipa[0]);
        if(dnsbl_lookup(buf)) {
            log_rejection(MLFIPRIV->rcptfile, *argv, MLFIPRIV->mailfrom, MLFIPRIV->hostname, MLFIPRIV->ipstring, MLFIPRIV->helohost, "SF_ELIBLACKHOLES");
	    if(tagonly) MLFIPRIV->rejected |= SF_ELIBLACKHOLES;
	    else {
                smfi_setreply(ctx, "550", "5.7.1", "Rejected by eli.blackholes.us");
                return SMFIS_REJECT;
	    }
        }
    }

    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_envrcpt) Finished");
    return SMFIS_CONTINUE;
}

sfsistat mlfi_header(SMFICTX *ctx, char *headerf, char *headerv) {
    /* Header processing goes here */
    return SMFIS_CONTINUE;
}

sfsistat mlfi_eoh(SMFICTX *ctx) {
    /* Processing at end of headers goes here */
    return SMFIS_CONTINUE;
}

sfsistat mlfi_body(SMFICTX *ctx, u_char *bodyp, size_t bodylen) {
    /* Body processing goes here */
    return SMFIS_CONTINUE;
}

sfsistat mlfi_eom(SMFICTX *ctx) {
    struct mlfiPriv *priv = MLFIPRIV;
    size_t i;

    /* End of body/End of message processing goes here */

    /* If rejected, add the X-spasm header */
    if(priv->rejected) {
	for(i=0;filters[i].num != 0;i++) {
	    if(priv->rejected & filters[i].num) smfi_addheader(ctx, "X-spasm", filters[i].name);
	}
    }

    /* End of message - Clear message-related vars, keep private data
       and connection-related vars (hostname, ipaddress, ipstring) */
    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_eom) Starting");
    if(priv != NULL) {
	if(priv->helohost != NULL) { free(priv->helohost); priv->helohost = NULL; }
	if(priv->helodomain != NULL) { free(priv->helodomain); priv->helodomain = NULL; }
	if(priv->mailfrom != NULL) { free(priv->mailfrom); priv->mailfrom = NULL; }
	if(priv->mailfromdomain != NULL) { free(priv->mailfromdomain); priv->mailfromdomain = NULL; }
	if(priv->rcptfile != NULL) { free(priv->rcptfile); priv->rcptfile = NULL; }
	if(priv->envrcpt != NULL) { free(priv->envrcpt); priv->envrcpt = NULL; }
	if(priv->username != NULL) { free(priv->username); priv->username = NULL; }
	if(priv->domain != NULL) { free(priv->domain); priv->domain = NULL; }
	priv->rejected = 0;
    }
    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_eom) Finished");
    return SMFIS_CONTINUE;
}

sfsistat mlfi_abort(SMFICTX *ctx) {
    /* Message Aborted - Do nothing, as mlfi_close will be called on connection termination */
    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_abort) Starting");
    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_abort) Finished");
    return SMFIS_CONTINUE;
}

sfsistat mlfi_close(SMFICTX *ctx) {
    struct mlfiPriv *priv = MLFIPRIV;

    /* Connection Closed - Free all allocated memory, set private data to NULL */
    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_close) Starting");
    if(priv != NULL) {
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_close) Freeing hostname");
        if(priv->hostname != NULL) free(priv->hostname);
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_close) Freeing hostdomain");
	if(priv->hostdomain != NULL) free(priv->hostdomain);
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_close) Freeing helohost");
        if(priv->helohost != NULL) free(priv->helohost);
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_close) Freeing helodomain");
	if(priv->helodomain != NULL) free(priv->helodomain);
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_close) Freeing mailfrom");
        if(priv->mailfrom != NULL) free(priv->mailfrom);
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_close) Freeing mailfromdomain");
	if(priv->mailfromdomain != NULL) free(priv->mailfromdomain);
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_close) Freeing ipstring");
	if(priv->ipstring != NULL) free(priv->ipstring);
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_close) Freeing rcptfile");
        if(priv->rcptfile != NULL) free(priv->rcptfile);
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_close) Freeing envrcpt");
        if(priv->envrcpt != NULL) free(priv->envrcpt);
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_close) Freeing username");
        if(priv->username != NULL) free(priv->username);
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_close) Freeing domain");
	if(priv->domain != NULL) free(priv->domain);
	if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_close) Freeing priv");
        free(priv);
    }
    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_close) Setting private data to NULL");
    smfi_setpriv(ctx, NULL);
    if(DEBUGGING) syslog(LOG_DEBUG, "(mlfi_close) Finished");
    return SMFIS_CONTINUE;
}

struct smfiDesc smfilter =
{
    "spasm",
    SMFI_VERSION,
    SMFIF_ADDHDRS,
    mlfi_connect,
    mlfi_helo,
    mlfi_envfrom,
    mlfi_envrcpt,
    mlfi_header,
    mlfi_eoh,
    mlfi_body,
    mlfi_eom,
    mlfi_abort,
    mlfi_close
};

void die(char *msg, int rc) {
    char errstring[FILENAME_MAX + 128];

    snprintf(errstring, sizeof(errstring), "%s: %s", progname, msg);
    perror(errstring);
    exit(rc);
}

void usage() {
    fprintf(stderr, "usage: %s -p <connection>\n", progname);
    exit(EX_USAGE);
}

int main(int argc, char **argv) {
    int c, rc;
    const char *args = "p:";
    struct passwd *pw;
    char msg[128];

    progname = argv[0];

    /* Drop privs immediately */
    if((pw = getpwnam(SPASM_USER)) == NULL) {
	snprintf(msg, sizeof(msg), "error reading passwd entry for \"%s\"", SPASM_USER);
	die(msg, 1);
    }
    if((setgid(pw->pw_gid) != 0) || (setuid(pw->pw_uid) != 0)) {
	die("error dropping privileges", 1);
    }

    /* Did they give a "-p <connection>" argument? */
    if(argc != 3) usage();

    /* Get arguments, setup milter connection */
    opterr = 0;
    while((c = getopt(argc, argv, args)) != EOF) {
	switch(c) {
	    case 'p':
		if(*optarg == '\0') {
		    fprintf(stderr, "%s: Illegal conn arg: \"%s\"\n", progname, optarg);
		    exit(EX_USAGE);
		}
		if(smfi_setconn(optarg) == MI_FAILURE) {
		    fprintf(stderr, "%s: mlfi_setconn() failed\n", progname);
		    exit(EX_UNAVAILABLE);
		}
		break;
	    default:
		usage();
	}
    }

    /* Register the milter */
    if(smfi_register(smfilter) == MI_FAILURE) {
	fprintf(stderr, "%s: smfi_register() failed\n", progname);
	exit(EX_UNAVAILABLE);
    }

    /* Compile our regex to return (SLD.TLD) of a FQDN */
    if((rc = regcomp(&re_sld, DOMAIN_NAME_REGEX, REG_EXTENDED)) != 0) {
	fprintf(stderr, "%s: error compiling regex \"%s\" (errcode: %d)\n",
		progname, DOMAIN_NAME_REGEX, rc);
	exit(EX_UNAVAILABLE);
    }

    /* Set Signal Handlers */
    signal(SIGCHLD, sigCHLD);
    signal(SIGINT, cleanup);
    signal(SIGTERM, cleanup);
    signal(SIGSEGV, sigSEGV);

    /* Fork daemon. Looping code adapted from vile's npopen.c */
    {
        int tries = 0, pid, nap = 1;

        while ((pid = fork()) < 0) {
            if (++tries > 5) {
                die("daemon fork() failed", EX_UNAVAILABLE);
            }
            sleep(nap);
            nap <<= 1;
        }
	/* We have forkage! */
        if (pid != 0)
	    /* parental unit returns */
            return (0);
        else {
            /* child goes out to play -- let the spam rejection begin! */
            openlog("spasm", LOG_PID, LOGFACILITY);
            syslog(LOG_INFO, "(main) Starting");
            return (smfi_main());
        }
    }
}
