/* milter-callback - a sendmail milter to perform variuos sender verifications
 Copyright (C) 2008  Eugene M. Zheganin, milter-callback@norma.perm.ru

 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 3 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, see <http://www.gnu.org/licenses/>.
*/

#ifndef bool
# define bool   int
# define TRUE   1
# define FALSE  0
#endif /* ! bool */

#define NS_TYPE_ELT                     0x40 /* EDNS0 extended label type */
#define DNS_LABELTYPE_BITSTRING         0x41
#define CB_UNKNOWN			-1
#define CB_PASS				0
#define CB_REJECT			1
#define CB_TEMPFAIL			2
#define CB_SKIP				3
#define CB_GRACEFUL			4
#define MAXEMAIL			324
#define MAXIDENT			274
#define MAXLOGLINE			1023
#define MAXHOSTNAME			256
#define MAXPATH				2048

#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <arpa/inet.h>
#include <resolv.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sysexits.h>
#include <unistd.h>
#include <syslog.h>
#include <stdarg.h>
#include <libmilter/mfapi.h>
#include <pthread.h>
#include <time.h>
#include <signal.h>

struct mlfiPriv {
    char *mcfi_connectfrom;
    char *mcfi_helofrom;
    int	skipDirect;
    uint32_t relayIP;
    char relayPresIP[INET_ADDRSTRLEN];
    char envFrom[MAXEMAIL];
    int MXesThatReject;
    int MXesThatAccept;
    int MXesOutOfOrder;
    int MXesThatTempfail;
    int MXesNum;
    int reverseRes;
    int fakeRes;
    int whitelisted;
    int gracefulDNS;
    int killMeOnData;
    /* flag indicating that IP of the relay of this context is in the pit */
    int prisoner;
    int cached;
    /* flag indicating this thread is cache lock owner */
    int cacheLockOwner;
    /* flag indicating this thread is pit lock owner */
    int pitLockOwner;
    char resolvedRelayIP[MAXHOSTNAME];
#ifdef WITH_PGSQL
    int SQLlogged;
#endif
#ifdef WITH_SPF
    int SPFResult;
#endif
};

#ifdef WITH_PGSQL
#include "libpq-fe.h"
#endif

#include <dealwithmxes.h>
#include <processmxpart.h>
#include <processdirectpart.h>
#include <makedecision.h>
#include <commonprocs.h>
#include <pushhistory.h>
#include <cache.h>
#include <cpit.h>
#include <pgupdate.h>
#include <sigusr1.h>

#ifdef WITH_SPF
#include <spf2/spf.h>
SPF_server_t *SPFServer;
int minSPFLen = 16;
int SPFPoolLen = 5;
struct SPFHeap {
    SPF_server_t **SPFServerArr;
    pthread_mutex_t **SPFMutexArr;
};
struct SPFHeap SPFPool;
#include <mcspf.h>
#include <detectlamespf.h>
#endif


#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif

char *mcVersion = "1.5.13";
char *mcName = "milter-callback";
char *mcCR = "(c) eugene zheganin, 2008-2010";

int mcDebug = 0;

uint32_t whiteNetMask;
struct netList {
    struct in_addr *whiteNet;
    struct in_addr *whiteMask;
    int netCount;
};
struct netList whiteNets;
/* this is the main cache */
struct mcCache {
    char relayPresIP[INET_ADDRSTRLEN];
    char *envFrom;
    time_t expiresAt;
    struct mcCache *nextNode;
    struct mcCache *prevNode;
    int action;
    int MXesThatReject;
    int MXesThatAccept;
    int MXesOutOfOrder;
    int MXesThatTempfail;
    int MXesNum;
    int reverseRes;
    int fakeRes;
    int whitelisted;
    int gracefulDNS;
    char resolvedRelayIP[MAXHOSTNAME];
#ifdef WITH_SPF
    SPF_result_t SPFResult;
#endif
};
struct mcCache mcCache;
struct mcCache *mcCacheStart;
pthread_mutex_t *mcCacheBusy;
time_t cacheTTL = 21600;
int cacheLen = 500;

/* cache to uphold the list of nodes which callback is on for */
struct mcCallbackPit {
    char relayPresIP[INET_ADDRSTRLEN];
    struct mcCallbackPit *nextNode;
    struct mcCallbackPit *prevNode;
};
struct mcCallbackPit mcPit;
struct mcPit *mcPitStart;
pthread_mutex_t *mcPitBusy;

char callbackMode[256];
char fakeEmail[MAXEMAIL];
char milterEmail[MAXEMAIL] = "milter-callback@norma.perm.ru";
char socketPath[2048];
char milterUser[256];
#ifdef WITH_PGSQL
char milterDBHost[256];
char milterDBName[256];
char milterDBUser[256];
char milterDBPass[256];
char myHostName[MAXHOSTNAME];
int hostNameVal;
char action[32];
char *pgConnInfo;
int connPoolLen = 5;
struct connList {
    PGconn **pgConnArr;
    PGresult **pgResArr;
    pthread_mutex_t **pgMutexArr;
};
struct connList connPool;
#endif
char replyURL[1024];
int logLevel = 0;
int gracefulDNS = 0;

/* Error variables */
extern int h_errno;  /* for resolver errors */
extern int errno;    /* general system errors */

sfsistat
mcfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr) {
    struct mlfiPriv *priv;
    char ident[MAXIDENT];
    char presIP[INET_ADDRSTRLEN];
    char tmpLog[MAXLOGLINE];
    
    logNow("Entered mcfi_connect().", 3, 0);
    
    /* allocate some private memory */
    priv = malloc(sizeof(struct mlfiPriv));
    if (priv == NULL) {
        /* can't accept this message right now */
	logNow("Cannot allocate memory.", 0, 0);
	return SMFIS_TEMPFAIL;
    }
    memset(priv, '\0', sizeof(struct mlfiPriv));
    /* setting the skipDirect flag to off */
    priv -> skipDirect = 0;
    /* resetting relayIP */
    priv -> relayIP = 0;
    /* save the private data */
    /* dropping MXes flags */
    priv -> MXesNum = -1;
    priv -> MXesThatAccept = -1;
    priv -> MXesThatReject = -1;
    priv -> MXesOutOfOrder = -1;
    priv -> MXesThatTempfail = -1;
    priv -> reverseRes = CB_UNKNOWN;
    priv -> fakeRes = CB_UNKNOWN;
    priv -> whitelisted = 0;
    priv -> gracefulDNS = 0;
    priv -> killMeOnData = 0;
    priv -> killMeOnData = 0;
    priv -> cached = 0;
    priv -> cacheLockOwner = 0;
    priv -> pitLockOwner = 0;
    priv -> prisoner = 0;
    strncpy(priv->resolvedRelayIP, hostname, MAXHOSTNAME);
#ifdef WITH_PGSQL
    /* connection was never open */
    priv -> SQLlogged = 0;
#endif
#ifdef WITH_SPF
    /* result was never set */
    priv -> SPFResult = -1;
#endif
    smfi_setpriv(ctx, priv);
    
    logNow("Getting ident from socket address.", 3, priv);
    if (inet_ntop(AF_INET, &((struct sockaddr_in *)hostaddr)->sin_addr, &presIP[0], sizeof(presIP)) == NULL) {
        logNow("Cannot parse IP address of connecting host from socket address.", 0, priv);
        return SMFIS_TEMPFAIL;
    }
    snprintf(tmpLog, MAXLOGLINE, "Connect from host %s [%s].", hostname, presIP);
    logNow(&tmpLog[0], 3, priv);
    priv->relayIP = ((struct sockaddr_in *)hostaddr)->sin_addr.s_addr;
    strncpy(priv->relayPresIP, presIP, INET_ADDRSTRLEN);
    snprintf(ident, sizeof(ident), "%s [%s]", hostname, presIP);
    
    if ((priv->mcfi_connectfrom = strdup(ident)) == NULL) {
	logNow("Cannot process ident, memory failure.", 0, priv);
	return SMFIS_TEMPFAIL;
    }
    /* continue processing */
    return SMFIS_CONTINUE;
}

sfsistat
mcfi_helo(SMFICTX *ctx, char *helohost) {
    size_t len;
    char *tls;
    char *buf;
    struct mlfiPriv *priv; 
    char tmpLog[MAXLOGLINE];
    
    priv = ((struct mlfiPriv *) smfi_getpriv(ctx));
    
    logNow("Entered mcfi_helo().", 3, priv);
    
    tls = smfi_getsymval(ctx, "{tls_version}");
    if (tls == NULL) {
	tls = "No TLS";
    };
    if (helohost == NULL) {
	helohost = "???";
    }
    len = strlen(tls) + strlen(helohost) + 3;
    if ((buf = (char*) malloc(len)) == NULL) {
	logNow("Cannot allocate memory.", 0, priv);
	return SMFIS_TEMPFAIL;
    }
    snprintf(buf, len, "%s, %s", helohost, tls);
    if (priv->mcfi_helofrom != NULL) {
	free(priv->mcfi_helofrom);
    }
    priv->mcfi_helofrom = buf;
    if (priv->mcfi_helofrom == NULL) {
	snprintf(tmpLog, MAXLOGLINE, "Received null HELO from host %s.", priv->relayPresIP);
	logNow(&tmpLog[0], 0, priv);
    } else {
	snprintf(tmpLog, MAXLOGLINE, "Received HELO from host %s, %s.", priv->relayPresIP, priv->mcfi_helofrom);
	logNow(&tmpLog[0], 3, priv);
    }
    /* continue processing */
    return SMFIS_CONTINUE;
}

sfsistat
mcfi_envfrom(SMFICTX *ctx, char **argv) {
    struct mlfiPriv *priv;
    
    char *mailHost;
    char *formattedEmail;
    char *emailDelim = "@";
    char emailBuf[MAXEMAIL];
    char *emailBP;
    char tmpLog[MAXLOGLINE];
    int decisionCode;
    
    int hostIsInWhitelist;
    struct mcCache *cachedEntry;
    
    priv = ((struct mlfiPriv *) smfi_getpriv(ctx));    
    
    logNow("Entered mcfi_envfrom().", 3, priv);
    
    /* getting the whiltelist seek result, the function will also set the WL flag */
    /* this is needed here and not below to pass not-so-valid messages from whitelisted peers */
    hostIsInWhitelist = hostIsWhitelisted(priv);
    
    snprintf(tmpLog, MAXLOGLINE, "Got formatted sender address, %s.", argv[0]);
    logNow(&tmpLog[0], 2, priv);
    
    if (strcmp(argv[0], "") == 0 && !(hostIsInWhitelist == 0 && priv->whitelisted == 1)) {
	snprintf(tmpLog, MAXLOGLINE, "Rejecting message, invalid envelope-from address received, \"%s\", RFC 821 violation.", argv[0]);
	logNow(&tmpLog[0], 2, priv);
	mc_setreply(ctx, "550", &tmpLog[0]);
	return SMFIS_REJECT;
    }
    
    if (strcmp(argv[0], "<>") == 0 && !(hostIsInWhitelist == 0 && priv->whitelisted == 1)) {
	/* setting a flag to kill this message on data stage, and passing it - this how I see a null-envfrom callback mode */
	snprintf(tmpLog, MAXLOGLINE, "Null-envfrom received from relay %s, passing message, will refuse it on DATA stage.", priv->relayPresIP);
	logNow(&tmpLog[0], 0, priv);
	priv -> killMeOnData = 1;
	return SMFIS_CONTINUE;
    }
    
    formattedEmail = strdup(argv[0]);
    if (formattedEmail == NULL) {
	logNow("Cannot allocate memory.", 0, priv);
	return SMFIS_TEMPFAIL;
    }
    
    /* stripping brackets */
    if (formattedEmail[0] == '<') {
	formattedEmail++;
    }
    if (formattedEmail[strlen(formattedEmail) - 1] == '>') {
	formattedEmail[strlen(formattedEmail) - 1] = '\0';
    }
    
    /* Getting host part of the address in case ${mail_host} not available */
    bzero(emailBuf, sizeof(emailBuf));

    strncpy(emailBuf, formattedEmail, sizeof(emailBuf));
    emailBP = &emailBuf[0];
    strsep(&emailBP, emailDelim);

    mailHost = emailBP;
    
    if ((formattedEmail == NULL || strlen(formattedEmail) < 3 || mailHost == NULL || strlen(mailHost) == 0) && !(hostIsInWhitelist == 0 && priv->whitelisted == 1)) {
	snprintf(tmpLog, MAXLOGLINE, "Rejecting message, invalid from address received, \"%s\".", argv[0]);
	logNow(&tmpLog[0], 2, priv);
	return SMFIS_REJECT;
    }
    
    /* pushing envfrom to mlfiPriv, to use lately in header flag setting */
    strncpy(priv -> envFrom, formattedEmail, MAXEMAIL);

    snprintf(tmpLog, MAXLOGLINE, "Got sender address, %s.", formattedEmail);
    logNow(&tmpLog[0], 2, priv);
    snprintf(tmpLog, MAXLOGLINE, "Got host part, %s.", mailHost);
    logNow(&tmpLog[0], 2, priv);
    snprintf(tmpLog, MAXLOGLINE, "Got sender host, %s.", priv->mcfi_connectfrom);
    logNow(&tmpLog[0], 2, priv);
    
    /* Non-structural check for existance of the host in whitelisted networks */
    /* this check is here and not above to get the sender parameters such as env-from address */
    /* we don't cache whitelisted entries, 'cause it's absolutely useless */
    if (hostIsInWhitelist == 0 && priv->whitelisted == 1) {
#ifdef WITH_PGSQL
	strncpy(action, "passed", sizeof(action));
	pushHistory(priv, &action[0], formattedEmail);
#endif
	return SMFIS_CONTINUE;
    } else {
	/* same thing as with empty envelope-from if we find that connected host is in pit - means it's probably running milter-callback */
	if (pitIsEmpty(priv) > 0 && seekPit(priv, priv -> relayPresIP) != NULL) {
	    snprintf(tmpLog, MAXLOGLINE, "Relay IP %s is found in pit, it probably running milter-callback itself, passing the message for now, but will drop it on DATA stage.", priv->relayPresIP);
	    logNow(&tmpLog[0], 0, priv);
	    priv -> prisoner = 1;
	    return SMFIS_CONTINUE;
        }
    
	/* return reject if some critical error happened */
	if (hostIsWhitelisted(priv) == 1) {
	    return SMFIS_REJECT;
	}
	/* do nothing if host is not whitelisted */
    }
    /* end of it */
    
    /* now checking the cache */
    snprintf(tmpLog, MAXLOGLINE, "Calling seekCache() with arguments %s/%s.", priv -> relayPresIP, formattedEmail);
    logNow(&tmpLog[0], 3, priv);
    cachedEntry = seekCache(priv, formattedEmail);
    if (cachedEntry == NULL) {
	/* no match */
	snprintf(tmpLog, MAXLOGLINE, "The pair %s/%s not found in cache.", priv -> relayPresIP, formattedEmail);
	logNow(&tmpLog[0], 2, priv);
    } else {
	/* found the match */
	snprintf(tmpLog, MAXLOGLINE, "The pair %s/%s is found in cache.", priv -> relayPresIP, formattedEmail);
	logNow(&tmpLog[0], 2, priv);	
	switch (cachedEntry -> action) {
            case CB_PASS:
		/* copy attributes */
		snprintf(tmpLog, MAXLOGLINE, "Copying attributes of the pair %s/%s from cache.", priv -> relayPresIP, formattedEmail);
		logNow(&tmpLog[0], 2, priv);
		copyStuff(priv, cachedEntry);
		snprintf(tmpLog, MAXLOGLINE, "Passing the pair %s/%s, cache action is pass.", priv -> relayPresIP, formattedEmail);
		logNow(&tmpLog[0], 2, priv);
#ifdef WITH_PGSQL
		strncpy(action, "passed", sizeof(action));
		pushHistory(priv, &action[0], formattedEmail);
#endif
		return SMFIS_CONTINUE;
		break;
	    case CB_REJECT:
		snprintf(tmpLog, MAXLOGLINE, "Rejecting the pair %s/%s, cache action is reject.", priv -> relayPresIP, formattedEmail);
		logNow(&tmpLog[0], 2, priv);
#ifdef WITH_PGSQL
		strncpy(action, "dropped", sizeof(action));
		pushHistory(priv, &action[0], formattedEmail);
#endif
		return SMFIS_REJECT;
		break;
	    case CB_TEMPFAIL:
		snprintf(tmpLog, MAXLOGLINE, "Tempfailing the pair %s/%s, cache action is tempfail.", priv -> relayPresIP, formattedEmail);
		logNow(&tmpLog[0], 2, priv);	
#ifdef WITH_PGSQL
		strncpy(action, "tempfailed", sizeof(action));
		pushHistory(priv, &action[0], formattedEmail);
#endif
		return SMFIS_TEMPFAIL;
		break;
	    default:
		/* something is wrong */
		snprintf(tmpLog, MAXLOGLINE, "Found unknown cache action %d, aborting.", cachedEntry -> action);
		logNow(&tmpLog[0], 0, priv);	
		exit(EXIT_FAILURE);
		break;
	}
	
    }
#ifdef WITH_SPF
    /* SPF pre-check */                                                                                                 
    /* Now our goal is to block mail passing from relays with bogus SPF like                                            
    * "v=spf1 a mx ip4:55.11.65.20/2 ip4:90.2.123.112/2 ip4:176.33.87.19/2 ip4:212.63.89.33/2 -all"                       
    * - we have to do full cecking or we can reject such mail immidiately
    */
    
    /* minSPFLen = 0 disables the checks */
    if (minSPFLen == 0 || detectLameSPF(mailHost, minSPFLen, priv) != -1) {
	snprintf(tmpLog, MAXLOGLINE, "Calling goWithSPF() with %s as second argument.", formattedEmail);
	logNow(&tmpLog[0], 3, priv);
	priv -> SPFResult = goWithSPF (priv, formattedEmail);
	snprintf(tmpLog, MAXLOGLINE, "Got SPFResult, %d.", priv -> SPFResult);
	logNow(&tmpLog[0], 3, priv);
	/* now we immidiately drop SPF_RESULT_FAIL and pass SPF_RESULT_PASS */
    
	if (priv -> SPFResult == SPF_RESULT_PASS) {
	    snprintf(tmpLog, MAXLOGLINE, "Passing the mail from %s, relay [%s], because I got positive SPF result.", formattedEmail, priv -> relayPresIP);
	    logNow(&tmpLog[0], 0, priv);
#ifdef WITH_PGSQL
	    strncpy(action, "passed", sizeof(action));
	    pushHistory(priv, &action[0], formattedEmail);
#endif
	    return SMFIS_CONTINUE;
	}
    
	if (priv -> SPFResult == SPF_RESULT_FAIL) {
	    snprintf(tmpLog, MAXLOGLINE, "Dropping the mail from %s, relay [%s], because I got negative SPF result.", formattedEmail, priv -> relayPresIP);
	    logNow(&tmpLog[0], 0, priv);
#ifdef WITH_PGSQL
	    strncpy(action, "dropped", sizeof(action));
	    pushHistory(priv, &action[0], formattedEmail);
#endif
	    return SMFIS_REJECT;
	}    
#endif
    }
    
    logNow("Going into makeDecision.", 3, priv);
    decisionCode = makeDecision(priv, formattedEmail, mailHost, ctx);
    snprintf(tmpLog, MAXLOGLINE, "Got decisionCode %d.", decisionCode);
    logNow(&tmpLog[0], 3, priv);
    return(decisionCode);
}

sfsistat
mcfi_eom(SMFICTX *ctx) {
    char host[512];
    char hbuf[2048];
    int haStatus;
    char *headFlagName = "X-Callback";
    char *headDBGFlagName = "X-Callback-Status";
    char *headCacheFlagName = "X-Callback-Cached";
    char *headEnvFromName = "X-Callback-Envelope-From";
    char tmpLog[MAXLOGLINE];
#ifdef WITH_SPF
    char *headSPFFlagName = "X-Callback-SPF";
#endif
    
    struct mlfiPriv *priv;
    priv = ((struct mlfiPriv *) smfi_getpriv(ctx));
    
    logNow("Entered mcfi_eom().", 3, priv);
    

    /* add a header to the message announcing our presence */
    if (gethostname(host, sizeof(host)) < 0) {
        snprintf(host, sizeof(host), "localhost");
    }
    bzero(hbuf, sizeof(hbuf));
    snprintf(hbuf, sizeof(hbuf), "Sender verified by milter-callback %s at %s.", mcVersion, host);
    haStatus = smfi_addheader(ctx, headFlagName, hbuf);
    if (haStatus != MI_SUCCESS) {
        /* failed; we have to wait until later */
        snprintf(tmpLog, MAXLOGLINE, "Couldn't add header: %s: %s, got %d from libmilter.", headFlagName, hbuf, haStatus);
	logNow(&tmpLog[0], 0, priv);
        return SMFIS_TEMPFAIL;
    } else {
	snprintf(tmpLog, MAXLOGLINE, "Successfully added header, %s: %s", headFlagName, hbuf);
	logNow(&tmpLog[0], 2, priv);
    }
    if (priv -> whitelisted == 1) {
	snprintf(hbuf, sizeof(hbuf), "relay [%s] found in white list.", priv->relayPresIP);
    } else {
#ifdef WITH_SPF
	if (priv -> SPFResult == SPF_RESULT_PASS) {
	    snprintf(hbuf, sizeof(hbuf), "mail passed due to positive SPF result.");
	} else {
#endif
	    if (priv->skipDirect == 0 && priv->skipDirect == 0) {
		snprintf(hbuf, sizeof(hbuf), "accepting MXes: [%d/%d], rejecting MXes: [%d/%d], tempfailing MXes [%d/%d], MXes out of order [%d/%d], reverse/fake relaying done.",
	    		priv->MXesThatAccept, priv->MXesNum, priv->MXesThatReject, priv->MXesNum, priv->MXesThatTempfail, priv->MXesNum, priv->MXesOutOfOrder, priv->MXesNum);
	    } else {
		if (priv->skipDirect == 1) {
		    snprintf(hbuf, sizeof(hbuf), "accepting MXes: [%d/%d], rejecting MXes: [%d/%d], tempfailing MXes [%d/%d], MXes out of order [%d/%d], reverse/fake relaying skipped.",
	    		    priv->MXesThatAccept, priv->MXesNum, priv->MXesThatReject, priv->MXesNum, priv->MXesThatTempfail, priv->MXesNum, priv->MXesOutOfOrder, priv->MXesNum);
		} else {
		    snprintf(hbuf, sizeof(hbuf), "accepting MXes: [%d/%d], rejecting MXes: [%d/%d], tempfailing MXes [%d/%d], MXes out of order [%d/%d], graceful DNS used.",
	    	    	    priv->MXesThatAccept, priv->MXesNum, priv->MXesThatReject, priv->MXesNum, priv->MXesThatTempfail, priv->MXesNum, priv->MXesOutOfOrder, priv->MXesNum);
		}	
	    }
#ifdef WITH_SPF
	}
#endif
    }
    haStatus = smfi_addheader(ctx, headDBGFlagName, hbuf);
    if (haStatus != MI_SUCCESS) {
        /* failed; we have to wait until later */
        snprintf(tmpLog, MAXLOGLINE, "Couldn't add header: %s: %s, got %d from libmilter.", headDBGFlagName, hbuf, haStatus);
	logNow(&tmpLog[0], 0, priv);
        return SMFIS_TEMPFAIL;
    } else {
	snprintf(tmpLog, MAXLOGLINE, "Successfully added header, %s: %s", headDBGFlagName, hbuf);
	logNow(&tmpLog[0], 2, priv);
    }
    if (priv->cached == 1) {
	snprintf(hbuf, sizeof(hbuf), "YES");
	haStatus = smfi_addheader(ctx, headCacheFlagName, hbuf);
	if (haStatus != MI_SUCCESS) {
    	    /* failed; we have to wait until later */
    	    snprintf(&tmpLog[0], MAXLOGLINE, "Couldn't add header: %s: %s, got %d from libmilter.", headCacheFlagName, hbuf, haStatus);
	    logNow(tmpLog, 0, priv);
    	    return SMFIS_TEMPFAIL;
	} else {
	    snprintf(tmpLog, MAXLOGLINE, "Successfully added header, %s: %s", headCacheFlagName, hbuf);
	    logNow(&tmpLog[0], 2, priv);
	}
    };
#ifdef WITH_SPF
    if (priv->SPFResult == SPF_RESULT_PASS) {
	snprintf(hbuf, sizeof(hbuf), "YES");
	haStatus = smfi_addheader(ctx, headSPFFlagName, hbuf);
	if (haStatus != MI_SUCCESS) {
    	    /* failed; we have to wait until later */
    	    snprintf(tmpLog, MAXLOGLINE, "Couldn't add header: %s: %s, got %d from libmilter.", headSPFFlagName, hbuf, haStatus);
	    logNow(&tmpLog[0], 0, priv);
    	    return SMFIS_TEMPFAIL;
	} else {
	    snprintf(tmpLog, MAXLOGLINE, "Successfully added header, %s: %s", headSPFFlagName, hbuf);
	    logNow(&tmpLog[0], 2, priv);
	}
    };
#endif
    /* adding envelope-from flag */
    snprintf(hbuf, sizeof(hbuf), priv -> envFrom);
    haStatus = smfi_addheader(ctx, headEnvFromName, hbuf);
    if (haStatus != MI_SUCCESS) {
    	/* failed; we have to wait until later */
	snprintf(tmpLog, MAXLOGLINE, "Couldn't add header: %s: %s, got %d from libmilter.", headEnvFromName, hbuf, haStatus);
        logNow(&tmpLog[0], 0, priv);
        return SMFIS_TEMPFAIL;
    } else {
        snprintf(tmpLog, MAXLOGLINE, "Successfully added header, %s: %s", headEnvFromName, hbuf);
        logNow(&tmpLog[0], 2, priv);
    }
    
    /* continue processing */
    return SMFIS_CONTINUE;
}

sfsistat
mcfi_close(SMFICTX *ctx) {
    struct mlfiPriv *priv;
    
    priv = ((struct mlfiPriv *) smfi_getpriv(ctx));
    
    logNow("Entered mcfi_close().", 3, priv);
    
    if (priv != NULL) {
	logNow("Trying to release private context pointer.", 3, priv);
	if (priv->mcfi_connectfrom != NULL) {
	    free(priv->mcfi_connectfrom);
	}
	if (priv->mcfi_helofrom != NULL) {
	    free(priv->mcfi_helofrom);
	}
	free(priv);
    }
    smfi_setpriv(ctx, NULL);
    return SMFIS_CONTINUE;
}

sfsistat
mcfi_negotiate(SMFICTX *ctx, unsigned long f0, unsigned long f1, unsigned long f2, unsigned long f3, unsigned long *pf0, unsigned long *pf1, unsigned long *pf2, unsigned long *pf3) {
    return SMFIS_ALL_OPTS;
}

sfsistat
mcfi_data(SMFICTX *ctx) {
    struct mlfiPriv *priv;
    char tmpLog[MAXLOGLINE];
    
    priv = ((struct mlfiPriv *) smfi_getpriv(ctx));
    
    if (priv->killMeOnData == 1) {
	/* rejecting a message with null-envfrom, if this is a callback - then it's already done */
	/* MAILER-DAEMON notifications will be added later, at this point they are sacrificed in the name of the Holy Mail */
	snprintf(tmpLog, MAXLOGLINE, "An attempt to pass a mail with null-envelope-from from a relay [%s] is made, dropping it.", priv->relayPresIP);
	logNow(&tmpLog[0], 0, priv);
	mc_setreply(ctx, "550", &tmpLog[0]);
	return SMFIS_REJECT;
    } else {
	if (priv->killMeOnData == 1) {
	    /* tempfailing message, this is an incoming connect from a host that */
	    /* is currently callbacked, probably it is running some callback */
	    /* procedure on us, may be even using milter-callback */
	    snprintf(tmpLog, MAXLOGLINE, "An attempt to pass a mail with null-envelope-from from a relay [%s] is made, dropping it.", priv->relayPresIP);
	    logNow(&tmpLog[0], 0, priv);
	    mc_setreply(ctx, "550", &tmpLog[0]);
	    return SMFIS_TEMPFAIL;
	} else {
	    return SMFIS_CONTINUE;
	}
    }
}	

struct smfiDesc smfilter =
{
    "Milter-Callback",	/* filter name */
    SMFI_VERSION,	/* version code -- do not change */
    SMFIF_ADDHDRS,	/* flags */
    mcfi_connect,	/* connection info filter */
    mcfi_helo,		/* SMTP HELO command filter */
    mcfi_envfrom,	/* envelope sender filter */
    NULL,
    NULL,
    NULL,
    NULL,
    mcfi_eom,		/* end of message */
    NULL,		/* message aborted */
    mcfi_close,		/* connection closing */
    NULL,
    mcfi_data,
    mcfi_negotiate	/* Once, at the start of each SMTP connection */
};

static void
usage(char *prog) {
    fprintf(stderr, "Usage: %s [-s socket-address] [-t timeout] [-m milter-email-address] [-u milter-user] [-f fake-email-address] [-c callback-mode] [-l loglevel] [-D] \n", prog);
}

int
main(int argc, char **argv) {

    bool setconn = FALSE;
    int c;
    char *cfToken;
    const char *args = "s:t:f:u:m:c:h:l:vDi:";
    extern char *optarg;
    const char *scPrefix = "unix:";
    FILE *configFile;
    char configFilePath[MAXPATH];
    char pidFilePath[MAXPATH];
    const char *configOptDelimiters = " ,";
    const char *cidrDelimiters = "/";
    
    char *whiteNetworks;
    char whitePresNetworks[4096];
    char *whiteNetwork;
    char *cidrMask;
    struct in_addr *bufNet, *bufMask, *newWhiteNet, *newWhiteMask;
    
    char fmSocketPath[2048];
    struct stat *socketFile;
    int genErr;
    
    char *configBuffer;
    char *configDelimiter = "=";
    
    char *debugBuf;
    
    pid_t   pid, sid;
    
    struct mlfiPriv *privEmu;
    struct in_addr ipEmu;
    int minit;
    char tmpLog[MAXLOGLINE];
#ifdef WITH_PGSQL
    int i, wantedInfoSize;
#endif
    
    void
    createPid(int pid) {
	FILE *pidFile;
	
	pidFile = fopen(pidFilePath, "w");
	if (pidFile == NULL) {
	    snprintf(tmpLog, MAXLOGLINE, "Cannot create pidfile at path %s, %s.", pidFilePath, strerror(errno));
	    logNow(&tmpLog[0], 3, 0);
	    exit(EX_SOFTWARE);
	} else {
	    snprintf(tmpLog, MAXLOGLINE, "Writing the pid %d to pidfile at %s.", pid, pidFilePath);
	    logNow(&tmpLog[0], 3, 0);
	    fprintf(pidFile, "%d", pid);
	    fclose(pidFile);
	}
    }
    
    /* init some paths */
    bzero(configFilePath, sizeof(configFilePath));
    bzero(pidFilePath, sizeof(pidFilePath));
    strncpy(configFilePath, "/usr/local/etc/mail/milter-callback.conf", sizeof(configFilePath));
    strncpy(pidFilePath, "/var/run/milter-callback.pid", sizeof(pidFilePath));
    
    /* initing counter to accept multiple lines */
    whiteNets.netCount = 0;
    
    /* initing replyURL string */
    bzero(replyURL, sizeof(replyURL));
    
    /* Process the config file */
    configFile = fopen(configFilePath, "r");
    if (configFile == NULL) {
	fprintf(stderr, "Cannot open config file at path %s, %s.\n", configFilePath, strerror(errno));
	exit(EX_SOFTWARE);
    } else {
	snprintf(tmpLog, MAXLOGLINE, "%s %s started.", mcName, mcVersion);
	logNow(&tmpLog[0], 0, 0);
    }
    
    int
    showWhiteList (){
	int i;
	char net[INET_ADDRSTRLEN], mask[INET_ADDRSTRLEN];
    
	snprintf(tmpLog, MAXLOGLINE, "Got those networks in white list:");
	logNow(&tmpLog[0], 3, 0);
	for (i = 0; i < whiteNets.netCount; i++) {
	    snprintf(tmpLog, MAXLOGLINE, "Hex: net 0x%x mask 0x%x, network byteorder.",  whiteNets.whiteNet[i].s_addr, whiteNets.whiteMask[i].s_addr);
	    logNow(&tmpLog[0], 3, 0);
	    if (inet_ntop(AF_INET, &whiteNets.whiteNet[i], net, INET_ADDRSTRLEN) == NULL) {
		fprintf(stderr, "Cannot convert %x to presentation format, aborting.\n", whiteNets.whiteNet[i].s_addr);
		exit(1);
	    }
	    if (inet_ntop(AF_INET, &whiteNets.whiteMask[i], mask, INET_ADDRSTRLEN) == NULL){
	        fprintf(stderr, "Cannot convert %x to presentation format, aborting.\n", whiteNets.whiteMask[i].s_addr);
		exit(1);
	    }
	    snprintf(tmpLog, MAXLOGLINE, "Presentation: net %s mask %s.", net, mask);
	    logNow(&tmpLog[0], 3, 0);
	}
	return(0);
    }
    
    char *
    trimSpaces (char *cfToken) {
	while (cfToken[strlen(cfToken) - 1] == ' ' || cfToken[strlen(cfToken) - 1] == '\t') {
	    cfToken[strlen(cfToken) - 1] = '\0';
	}
	while (cfToken[0] == ' ' || cfToken[0] == '\t') {
	    cfToken++;
	}
	return(cfToken);
    }
        
    if (configFile != NULL) {
	configBuffer = malloc(2048);
	if (configBuffer == NULL) {
	    fprintf(stderr, "Cannot allocate memory, aborting.\n");
	    exit(1);
	}
	while (!feof(configFile)) {
	    debugBuf = fgets(configBuffer, 1024, configFile);
	    if (debugBuf != NULL) {
		if (configBuffer[strlen(configBuffer) - 1] == '\n'){
			configBuffer[strlen(configBuffer) - 1] = '\0';
		}
		snprintf(tmpLog, MAXLOGLINE, "Current line: \"%s\".", configBuffer);
		logNow(&tmpLog[0], 3, 0);
		if (configBuffer[0] != '\0' && configBuffer[0] != '\n' && configBuffer[0] != '#') {
		    cfToken = strsep(&configBuffer, configDelimiter);
		    snprintf(tmpLog, MAXLOGLINE, "cfToken is \"%s\".", cfToken);
		    logNow(&tmpLog[0], 3, 0);
		    snprintf(tmpLog, MAXLOGLINE, "Rest of the line is \"%s\".", configBuffer);
		    logNow(&tmpLog[0], 3, 0);
		    cfToken = trimSpaces(cfToken);
		    configBuffer = trimSpaces(configBuffer);
		    snprintf(tmpLog, MAXLOGLINE, "After trimming the line is \"%s\".", configBuffer);
		    logNow(&tmpLog[0], 3, 0);
    		    if (strcmp(cfToken, "socket path") == 0){
			strncpy(socketPath, configBuffer, sizeof(socketPath));
		    }
		    if (strcmp(cfToken, "pidfile path") == 0){
			strncpy(pidFilePath, configBuffer, sizeof(pidFilePath));
		    }
		    if (strcmp(cfToken, "loglevel") == 0){
			logLevel = atol(configBuffer);
		    }
		    if (strcmp(cfToken, "verification mode") == 0){
			strncpy(callbackMode, configBuffer, sizeof(callbackMode));
		    }
		    if (strcmp(cfToken, "graceful dns") == 0){
			if (strcmp(configBuffer, "yes") == 0) {
			    gracefulDNS = 1;
			}
		    }
		    if (strcmp(cfToken, "milter user") == 0){
			strncpy(milterUser, configBuffer, sizeof(milterUser));
		    }
		    if (strcmp(cfToken, "fake e-mail") == 0){
			if (configBuffer[0] == '<' || configBuffer[strlen(configBuffer) - 1] == '>') {
			    fprintf(stderr, "Wrong symbols in e-mail address, aborting.\n");
			    exit(1);
			}
			strncpy(fakeEmail, configBuffer, sizeof(fakeEmail));
		    }
		    if (strcmp(cfToken, "milter e-mail") == 0){
			if (configBuffer[0] == '<' || configBuffer[strlen(configBuffer) - 1] == '>') {
			    fprintf(stderr, "Wrong symbols in e-mail address, aborting.\n");
			    exit(1);
			}
		        strncpy(milterEmail, configBuffer, sizeof(milterEmail));
		    }
		    if (strcmp(cfToken, "reply url") == 0){
		        strncpy(replyURL, configBuffer, sizeof(replyURL));
		    }
		    if (strcmp(cfToken, "cache ttl") == 0){
			cacheTTL = atol(configBuffer);
		    }
		    if (strcmp(cfToken, "cache length") == 0){
			cacheLen = atol(configBuffer);
		    }
#ifdef WITH_SPF
		    /* SPF-related parameters */
		    if (strcmp(cfToken, "minimum spf length") == 0){
		        minSPFLen = atol(configBuffer);
			if (connPoolLen < 1) {
			    fprintf(stderr, "Wrong SPF length parameter, aborting.\n");
			    exit(1);
			};
		    }
		    if (strcmp(cfToken, "spf pool length") == 0){
			SPFPoolLen = atol(configBuffer);
		    }
#endif
#ifdef WITH_PGSQL
		    /* database parameters part */
		    if (strcmp(cfToken, "dbhostname") == 0){
		        strncpy(milterDBHost, configBuffer, sizeof(milterDBHost));
		    }
		    if (strcmp(cfToken, "dbname") == 0){
		        strncpy(milterDBName, configBuffer, sizeof(milterDBName));
		    }
		    if (strcmp(cfToken, "dbuser") == 0){
		        strncpy(milterDBUser, configBuffer, sizeof(milterDBUser));
		    }
		    if (strcmp(cfToken, "dbpassword") == 0){
		        strncpy(milterDBPass, configBuffer, sizeof(milterDBPass));
		    }
		    if (strcmp(cfToken, "connections") == 0){
		        connPoolLen = atol(configBuffer);
			if (connPoolLen < 1) {
			    fprintf(stderr, "Wrong connection parameter, aborting.\n");
			    exit(1);
			} else {
			    if (connPoolLen == 0) {
				connPoolLen = 5;
			    }
			}
		    }
		    /* database parameters part end */
#endif		    
		    if (strcmp(cfToken, "whitelisted networks") == 0){
			whiteNetworks = malloc(strlen(configBuffer) + 1);
			if (whiteNetworks == NULL) {
			    fprintf(stderr, "Memory allocation failed.\n");
			    exit(1);
			}
			bzero(whiteNetworks, strlen(configBuffer) + 1);
			/* we just reserved enough memory, so it's not a mistake */
			strncpy(whiteNetworks, configBuffer, strlen(configBuffer));
			strncpy(whitePresNetworks, whiteNetworks, sizeof(whitePresNetworks));
			while (whiteNetworks != NULL) {
			    snprintf(tmpLog, MAXLOGLINE, "Working with line %s.", whiteNetworks);
			    logNow(&tmpLog[0], 3, 0);
			    whiteNetwork = strsep(&whiteNetworks, configOptDelimiters);
			    snprintf(tmpLog, MAXLOGLINE, "After shifting the rest of the config line is  %s.", whiteNetworks);
			    logNow(&tmpLog[0], 3, 0);
			    if (strlen(whiteNetwork) > 0) {
				snprintf(tmpLog, MAXLOGLINE, "Got white network %s.", whiteNetwork);
				logNow(&tmpLog[0], 3, 0);
				/* striping cidr mask */
				cidrMask = strdup(whiteNetwork);
				whiteNetwork = strsep(&whiteNetwork, cidrDelimiters);
				strsep(&cidrMask, cidrDelimiters);
				snprintf(tmpLog, MAXLOGLINE, "Got white network mask /%s.", cidrMask);
				logNow(&tmpLog[0], 3, 0);
				/* getting new cidr mask to proper format */
				if (cidrMask == NULL) {
				    whiteNetMask = 32;
				} else {
				    whiteNetMask = strtol(cidrMask, NULL, 0);
				}
				if (whiteNetMask == 0) {
				    fprintf(stderr, "Cannot convert %s to netmask.\n", cidrMask);
				    exit(1);
				}
				if (whiteNetMask < 0 || whiteNetMask > 32) {
				    fprintf(stderr, "%d isnt't a valid netmask.\n", whiteNetMask);
				    exit(1);
				}
				whiteNetMask = 0xffffffff << (32 - whiteNetMask);
				whiteNetMask = htonl(whiteNetMask);
				snprintf(tmpLog, MAXLOGLINE, "After conversion of mask /%s to network format got 0x%x in host order, 0x%x natively.", cidrMask, ntohl(whiteNetMask), whiteNetMask);
				logNow(&tmpLog[0], 3, 0);
				newWhiteNet = malloc(sizeof(struct in_addr));
				if (newWhiteNet == NULL) {
				    fprintf(stderr, "Cannot allocate memory, aborting.\n");
				    exit(1);
				}
				newWhiteMask = malloc(sizeof(struct in_addr));
				if (newWhiteMask == NULL) {
				    fprintf(stderr, "Cannot allocate memory, aborting.\n");
				    exit(1);
				}
				newWhiteMask->s_addr = whiteNetMask;
				if (inet_pton(AF_INET, whiteNetwork, newWhiteNet) == 1) {
				    snprintf(tmpLog, MAXLOGLINE, "After conversion of net %s to network format got 0x%x in host order, 0x%x natively.", 
					    whiteNetwork, ntohl(newWhiteNet->s_addr), newWhiteNet->s_addr);
				    logNow(&tmpLog[0], 3, 0);
				    if (whiteNets.netCount == 0) {
					/* kinda we got first time here */
					snprintf(tmpLog, MAXLOGLINE, "Initializing network whitelist.");
					logNow(&tmpLog[0], 3, 0);
					snprintf(tmpLog, MAXLOGLINE, "Pushing network %x to new position %d.", newWhiteNet->s_addr, whiteNets.netCount);
					logNow(&tmpLog[0], 3, 0);
					snprintf(tmpLog, MAXLOGLINE, "Pushing mask %x to new position %d.", newWhiteMask->s_addr, whiteNets.netCount);
					logNow(&tmpLog[0], 3, 0);
					whiteNets.whiteNet = newWhiteNet;
					whiteNets.whiteMask = newWhiteMask;
				    } else {
					/* kinda we need to extend our array */
					snprintf(tmpLog, MAXLOGLINE, "Pushing network %x to new position %d.", newWhiteNet->s_addr, whiteNets.netCount + 1);
					logNow(&tmpLog[0], 3, 0);
					bufNet = realloc(whiteNets.whiteNet, (whiteNets.netCount + 1) * sizeof(struct in_addr));
					if (bufNet == NULL) {
					    fprintf(stderr, "Cannot allocate memory, aborting.\n");
					    exit(1);
					} else {
					    whiteNets.whiteNet = bufNet;
					    /* copying contents of one pointer to another */
					    whiteNets.whiteNet[whiteNets.netCount] = *newWhiteNet;
					}
					snprintf(tmpLog, MAXLOGLINE, "Pushing mask %x to new position %d.", newWhiteMask->s_addr, whiteNets.netCount + 1);;
					logNow(&tmpLog[0], 3, 0);
					bufMask = realloc(whiteNets.whiteMask, (whiteNets.netCount + 1) * sizeof(struct in_addr));
					if (bufMask == NULL) {
					    fprintf(stderr, "Cannot allocate memory, aborting.");
					    exit(1);
					} else {
					    whiteNets.whiteMask = bufMask;
					    /* copying contents of one pointer to another */
					    whiteNets.whiteMask[whiteNets.netCount] = *newWhiteMask;
					}
				    }
				    if (whiteNetworks != NULL) {
					whiteNets.netCount++;
				    }
				} else {
				    printf("Cannot convert %s to network format, aborting.\n", whiteNetwork);
				    exit(1);
				}
			    }
			}
			snprintf(tmpLog, MAXLOGLINE, "Got %d networks in whitelist.", whiteNets.netCount++);
			logNow(&tmpLog[0], 3, 0);
			showWhiteList();
		    }
		}
	    }
	}
	logNow("After config file processing I got following attributes:", 3, 0);
	snprintf(tmpLog, MAXLOGLINE, "Socket path, %s.", socketPath);
	logNow(&tmpLog[0], 3, 0);
	snprintf(tmpLog, MAXLOGLINE, "Pid file path, %s.", pidFilePath);
	logNow(&tmpLog[0], 3, 0);
	snprintf(tmpLog, MAXLOGLINE, "Milter mode, %s.", callbackMode);
	logNow(&tmpLog[0], 3, 0);
	snprintf(tmpLog, MAXLOGLINE, "Milter user, %s.", milterUser);
	logNow(&tmpLog[0], 3, 0);
	snprintf(tmpLog, MAXLOGLINE, "Fake e-mail, %s.", fakeEmail);
	logNow(&tmpLog[0], 3, 0);
	snprintf(tmpLog, MAXLOGLINE, "Milter e-mail, %s.", milterEmail);
	logNow(&tmpLog[0], 3, 0);
	snprintf(tmpLog, MAXLOGLINE, "Whitelisted networks, %s.", whitePresNetworks);
	logNow(&tmpLog[0], 3, 0);
	snprintf(tmpLog, MAXLOGLINE, "Log level, %d.", logLevel);
	logNow(&tmpLog[0], 3, 0);
	snprintf(tmpLog, MAXLOGLINE, "Graceful DNS, %d.", gracefulDNS);
	logNow(&tmpLog[0], 3, 0);
	snprintf(tmpLog, MAXLOGLINE, "Cache TTL, %d.", cacheTTL);
	logNow(&tmpLog[0], 3, 0);
#ifdef WITH_SPF
	snprintf(tmpLog, MAXLOGLINE, "Minimum SPF netmask length TTL, %d.", minSPFLen);
	logNow(&tmpLog[0], 3, 0);
	snprintf(tmpLog, MAXLOGLINE, "SPF pool length , %d.", SPFPoolLen);
	logNow(&tmpLog[0], 3, 0);
#endif
#ifdef WITH_PGSQL
	snprintf(tmpLog, MAXLOGLINE, "Database host, %s.", milterDBHost);
	logNow(&tmpLog[0], 3, 0);
	snprintf(tmpLog, MAXLOGLINE, "Database name, %s.", milterDBName);
	logNow(&tmpLog[0], 3, 0);
	snprintf(tmpLog, MAXLOGLINE, "Database user, %s.", milterDBUser);
	logNow(&tmpLog[0], 3, 0);
	snprintf(tmpLog, MAXLOGLINE, "Database password, %s.", milterDBPass);
	logNow(&tmpLog[0], 3, 0);
	snprintf(tmpLog, MAXLOGLINE, "Database connections, %d.", connPoolLen);
	logNow(&tmpLog[0], 3, 0);
#endif
	/* releasing white networks string, not needed anymore */
	if (whiteNetworks != NULL) {
    	    free(whiteNetworks);
	}
	fclose(configFile);
    }  else {
	fprintf(stderr, "Cannot open config file %s, aborting.\n", configFilePath);
    }
    
    /* Process command line options */
    while ((c = getopt(argc, argv, args)) != -1) {
        switch (c) {
            case 's':
		if (optarg != NULL && *optarg != '\0') {
		    strncpy(socketPath, optarg, sizeof(socketPath));
                }
		break;
	    case 'm':
		if (optarg != NULL && *optarg != '\0') {
		    strncpy(milterEmail, optarg, sizeof(milterEmail));
                }
		break;
	    case 'u':
		if (optarg != NULL && *optarg != '\0') {
		    strncpy(milterUser, optarg, sizeof(milterUser));
                }
		break;
	    case 'f':
		if (optarg != NULL && *optarg != '\0') {
		    strncpy(fakeEmail, optarg, sizeof(fakeEmail));
                }
		break;
	    case 'v':
		fprintf(stdout, "%s %s %s.\n", mcName, mcVersion, mcCR);
		exit(EXIT_SUCCESS);
		break;
	    case 'c':
		if (optarg != NULL && *optarg != '\0' && (strncmp(optarg, "rfc", 3) || strncmp(optarg, "direct", 6) || strncmp(optarg, "both", 4))) {
		    strncpy(callbackMode, optarg, sizeof(callbackMode));
                }
		break;
	    case 'l':
		if (optarg != NULL && *optarg != '\0') {
		    logLevel = atol(optarg);
                }
		break;
	    case 'D':
		mcDebug = 1;
		break;
            case 't':
                if (optarg == NULL || *optarg == '\0') {
                    fprintf(stderr, "Illegal timeout: %s\n", optarg);
		    exit(EX_USAGE);
		}
		if (smfi_settimeout(atoi(optarg)) == MI_FAILURE) {
		    (void) fprintf(stderr, "smfi_settimeout failed\n");
		    exit(EX_SOFTWARE);
		}
		break;
	    case 'i':
		/* address testing mode */
		privEmu = malloc(sizeof(struct mlfiPriv));
		if (privEmu == NULL) {
		    fprintf(stderr, "Cannot allocate memory, aborting.\n");
		    exit(EX_SOFTWARE);
		} else {
		    privEmu->whitelisted = 0;
		    mcDebug = 1;
		    if (optarg == NULL || *optarg == '\0') {
			exit(EX_USAGE);
		    } else {
			if (inet_pton(AF_INET, optarg, &ipEmu) != 1) {
			    fprintf(stderr, "Cannot convert %s to network format, invalid IP.\n", optarg);
			} else {
			    /* all was good, need some inits */
			    privEmu->relayIP = ipEmu.s_addr;
			    strncpy(privEmu->relayPresIP, optarg, INET_ADDRSTRLEN);
			    if (hostIsWhitelisted(privEmu) == 1) {
				fprintf(stderr, "Testing error.\n");
			    }
			}
		    } 
		    exit(EXIT_SUCCESS);
		    if (privEmu != NULL) {
			free(privEmu);
		    }
		}
		break;
	    case 'h':
	    default:
		usage(argv[0]);
		exit(EX_USAGE);
	}
    }
    snprintf(tmpLog, MAXLOGLINE, "After comandline swithes processing I got following attributes:");
    logNow(&tmpLog[0], 3, 0);
    snprintf(tmpLog, MAXLOGLINE, "Socket path, %s.", socketPath);
    logNow(&tmpLog[0], 3, 0);
    snprintf(tmpLog, MAXLOGLINE, "Pid file path, %s.", pidFilePath);
    logNow(&tmpLog[0], 3, 0);
    snprintf(tmpLog, MAXLOGLINE, "Milter mode, %s.", callbackMode);
    logNow(&tmpLog[0], 3, 0);
    snprintf(tmpLog, MAXLOGLINE, "Milter user, %s.", milterUser);
    logNow(&tmpLog[0], 3, 0);
    snprintf(tmpLog, MAXLOGLINE, "Fake e-mail, %s.", fakeEmail);
    logNow(&tmpLog[0], 3, 0);
    snprintf(tmpLog, MAXLOGLINE, "Milter e-mail, %s.", milterEmail);
    logNow(&tmpLog[0], 3, 0);
    snprintf(tmpLog, MAXLOGLINE, "Log level, %d.", logLevel);
    logNow(&tmpLog[0], 3, 0);
    snprintf(tmpLog, MAXLOGLINE, "Graceful DNS, %d.", gracefulDNS);
    logNow(&tmpLog[0], 3, 0);

    bzero(fmSocketPath, sizeof(fmSocketPath));
    strncat(fmSocketPath, scPrefix, strlen(scPrefix));
    strncat(fmSocketPath, socketPath, strlen(socketPath));
    
    /* clearing dead socket, if exists*/
    socketFile = malloc(sizeof(struct stat));
    genErr = lstat(socketPath, socketFile);
    if (genErr != 0) {
	/* some error happened */
	if (errno != ENOENT) {
	    /* some other error than just a nonexistent file happened */
	    fprintf(stderr, "Cannot query the socket at path %s, aborting.\n", socketPath);
	    exit(EX_SOFTWARE);
	} else {
	    /* just a nonexistent file */
	    snprintf(tmpLog, MAXLOGLINE, "File at path %s doesn't seem to exist.", socketPath);
	    logNow(&tmpLog[0], 3, 0);
	}
    } else {
	/* file exists */
	if (S_ISSOCK(socketFile->st_mode) == 0) {
	    /* file exists but it isn't a socket */
	    fprintf(stderr, "File exist at path %s, but it's not a socket, refusing to delete, aborting.\n", socketPath);
	} else {
	    /* file exists and it's a socket */
	    snprintf(tmpLog, MAXLOGLINE, "Flushing dead(is he really ?) socket at path %s.", socketPath);
	    logNow(&tmpLog[0], 3, 0);
	    genErr = unlink(socketPath);
	    if (genErr != 0) {
		fprintf(stderr, "Cannot delete socket at path  %s, aborting.\n", socketPath);
		exit(EX_SOFTWARE);
	    }
	}
    }
    
    if (smfi_setconn(fmSocketPath) == MI_FAILURE) {
        fprintf(stderr, "smfi_setconn failed.\n");
	exit(EX_SOFTWARE);
    } else {
	setconn = TRUE;
    }
    if (!setconn) {
	fprintf(stderr, "Cannot live with socket you specified. Think twice about it.\n");
	exit(EX_USAGE);
    }
    if (smfi_register(smfilter) == MI_FAILURE) {
	fprintf(stderr, "smfi_register failed.\n");
	exit(EX_UNAVAILABLE);
    }
    /* now we can daemonize */
    /* free - at last ! */
    if (mcDebug == 0) {
	logNow("Attempting to go to the land of the daemons...", 3, 0);
    
	pid = fork();
    
	if (pid < 0) {
	    fprintf(stderr, "Cannot fork, aborting.\n");
	    exit(EXIT_FAILURE);
	} else if (pid > 0) {
	    /* creating a pidfile */
	    createPid(pid);
    	    exit(EXIT_SUCCESS);
	}

	umask(0);
     
	sid = setsid();
	if (sid < 0) {
	    logNow("Detaching failed, aborting.", 0, 0);
	    exit(EXIT_FAILURE);
	}
    
	if ((chdir("/")) < 0) {
	    logNow("Chrooting failed, aborting.", 0, 0);
	    exit(EXIT_FAILURE);
	}
    } else {
	/* creating a pidfile */
	createPid(getpid());
	logNow("Running in debug mode, not daemonizing.", 3, 0);
    }
#ifdef WITH_PGSQL
    /* determine the name of this host */
    bzero(&myHostName[0], sizeof(myHostName));
    hostNameVal = gethostname(&myHostName[0], sizeof(myHostName));
    if (hostNameVal != 0) {
        logNow("Cannot determine hostname, aborting.", 0, 0);
        exit(EXIT_FAILURE);
    }
	
    /* now we need to allocate memory for connection pool */
    /* connections */
    connPool.pgConnArr = malloc(sizeof(PGconn *) * connPoolLen);
    if (connPool.pgConnArr == NULL) {
        logNow("Cannot allocate memory for connections pool, aborting.\n", 0, 0);
        exit(EXIT_FAILURE);
    }
    /* results */
    connPool.pgResArr = malloc(sizeof(PGresult *) * connPoolLen);
    if (connPool.pgResArr == NULL) {
        logNow("Cannot allocate memory for results pool, aborting.\n", 0, 0);
        exit(EXIT_FAILURE);
    }
    /* mutexes */
    connPool.pgMutexArr = malloc(sizeof(pthread_mutex_t *) * connPoolLen);
    if (connPool.pgMutexArr == NULL) {
        logNow("Cannot allocate memory for mutex pool, aborting.\n", 0, 0);
        exit(EXIT_FAILURE);
    }
    /* initializing connection info */
    wantedInfoSize = snprintf(pgConnInfo, 0, "host = %s dbname = %s user = %s password = %s", &milterDBHost[0], &milterDBName[0], &milterDBUser[0], &milterDBPass[0]);
    wantedInfoSize++;
    pgConnInfo = malloc(wantedInfoSize);
    if (pgConnInfo == NULL) {
    	logNow("Cannot allocate memory for connection, aborting.", 0, 0);
    	exit(EXIT_FAILURE);
    }
    snprintf(pgConnInfo, wantedInfoSize, "host = %s dbname = %s user = %s password = %s", &milterDBHost[0], &milterDBName[0], &milterDBUser[0], &milterDBPass[0]);	
    /* initializing connection pool */
    for (i = 0; i < connPoolLen; i++) {
        logNow("Trying to make a connection to database.", 0, 0);
        /* allocating memory */
        connPool.pgMutexArr[i] = malloc(sizeof(pthread_mutex_t));

	if (connPool.pgMutexArr[i] == 0) {
	    snprintf(tmpLog, MAXLOGLINE, "Cannot alocate memory for mutex pool element %d, aborting.", i);
	    logNow(&tmpLog[0], 0, 0);
    	    exit(EXIT_FAILURE);
	}

	connPool.pgConnArr[i] = PQconnectdb(pgConnInfo);
	/* verifying that we connected successfully */
	if (PQstatus(connPool.pgConnArr[i]) != CONNECTION_OK) {
	    snprintf(tmpLog, MAXLOGLINE, "Connection %d failed, aborting.", i);
    	    logNow(&tmpLog[0], 0, 0);
    	    exit(EXIT_FAILURE);
	} else {
	    snprintf(tmpLog, MAXLOGLINE, "Connection %d successfully established.", i);
	    logNow(&tmpLog[0], 0, 0);
	}
	/* initializing mutex */
	minit = pthread_mutex_init(connPool.pgMutexArr[i], NULL);
	if (minit != 0) {
    	    snprintf(tmpLog, MAXLOGLINE, "Mutex %d initialization failed, aborting.", i);
	    logNow(&tmpLog[0], 0, 0);
	    exit(EXIT_FAILURE);
	} else {
	    snprintf(tmpLog, MAXLOGLINE, "Mutex %d successfully initializated.", i);
	    logNow(&tmpLog[0], 0, 0);
	}
    }
    /* going with the needed updates */
    getUpdates();
#endif
    /* initializing empty cache */
    logNow("Trying to initialize empty cache.", 3, 0);
    initEmptyCache();
    /* initlializing cache mutex */
    mcCacheBusy = malloc(sizeof(pthread_mutex_t));
    if (mcCacheBusy == NULL) {
	logNow("Cannot allocate mamory.", 0, 0);
	exit(EXIT_FAILURE);
    }
    logNow("Trying to initialize cache mutex.", 3, 0);
    minit = pthread_mutex_init(mcCacheBusy, NULL);
    if (minit != 0) {
        logNow("Cache mutex initialization failed, aborting.", 0, 0);
	    exit(EXIT_FAILURE);
    } else {
	logNow("Cache mutex successfully initializated.", 0, 0);
    }
    
    /* same things with mcPit */
    logNow("Trying to initialize empty pit.", 3, 0);
    initEmptyPit();
    mcPitBusy = malloc(sizeof(pthread_mutex_t));
    if (mcPitBusy == NULL) {
	logNow("Cannot allocate mamory.", 0, 0);
	exit(EXIT_FAILURE);
    }
    logNow("Trying to initialize pit mutex.", 3, 0);
    minit = pthread_mutex_init(mcPitBusy, NULL);
    if (minit != 0) {
        logNow("Pit mutex initialization failed, aborting.", 0, 0);
	    exit(EXIT_FAILURE);
    } else {
	logNow("Pit mutex successfully initializated.", 0, 0);
    }
#ifdef WITH_SPF                                                                                                                    
    /* now we need to allocate memory for SPF server pool */
    /* servers */
    SPFPool.SPFServerArr = malloc(sizeof(SPF_server_t *) * SPFPoolLen);
    if (SPFPool.SPFServerArr == NULL) {
        logNow("Cannot allocate memory for connections pool, aborting.\n", 0, 0);
        exit(EXIT_FAILURE);
    }
    /* mutexes */
    SPFPool.SPFMutexArr = malloc(sizeof(pthread_mutex_t *) * SPFPoolLen);
    if (SPFPool.SPFMutexArr == NULL) {
        logNow("Cannot allocate memory for SPF mutex pool, aborting.\n", 0, 0);
        exit(EXIT_FAILURE);
    }
    /* initializing SPF server pool */
    for (i = 0; i < SPFPoolLen; i++) {
        logNow("Trying to create an SPF server.", 0, 0);
        /* allocating memory */
        SPFPool.SPFMutexArr[i] = malloc(sizeof(pthread_mutex_t));
	if (SPFPool.SPFMutexArr[i] == 0) {
	    snprintf(tmpLog, MAXLOGLINE, "Cannot alocate memory for SPF mutex pool element %d, aborting.", i);
	    logNow(&tmpLog[0], 0, 0);
    	    exit(EXIT_FAILURE);
	}
	SPFPool.SPFServerArr[i] = SPF_server_new(SPF_DNS_CACHE, 0);
	/* verifying that we connected successfully */
	if (SPFPool.SPFServerArr[i] == NULL) {
	    snprintf(tmpLog, MAXLOGLINE, "Creation of the SPF server %d failed, aborting.", i);
    	    logNow(&tmpLog[0], 0, 0);
    	    exit(EXIT_FAILURE);
	} else {
	    snprintf(tmpLog, MAXLOGLINE, "SPF server %d successfully created.", i);
	    logNow(&tmpLog[0], 0, 0);
	}
	/* initializing mutex */
	minit = pthread_mutex_init(SPFPool.SPFMutexArr[i], NULL);
	if (minit != 0) {
    	    snprintf(tmpLog, MAXLOGLINE, "SPF Mutex %d initialization failed, aborting.", i);
	    logNow(&tmpLog[0], 0, 0);
	    exit(EXIT_FAILURE);
	} else {
	    snprintf(tmpLog, MAXLOGLINE, "SPF Mutex %d successfully initializated.", i);
	    logNow(&tmpLog[0], 0, 0);
	}
    }
#endif
    /* handling signals */
    (void) signal(SIGUSR1, getCaches);
    
    return smfi_main();
}
/* eof */
