/* 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/>.
*/
	   
#include <milter-callback.h>

extern char *logString;
extern int logLevel;
extern int mcDebug;
extern struct mcCache *mcCacheStart;
extern time_t cacheTTL;

int
initEmptyCache () {
/* inits cache, 0 - success, 1 - fail */
    int memSize;
    
    snprintf(logString, MAXLOGLINE, "entered initEmptyCache().");
    logNow(logString, 3);
    memSize = sizeof(struct mcCache);
    snprintf(logString, MAXLOGLINE, "Trying to get %d bytes for the new cache element.", memSize);
    logNow(logString, 3);
    mcCacheStart = malloc(memSize);
    if (mcCacheStart == NULL) {
	snprintf(logString, MAXLOGLINE, "Cannot allocate memory.");
        logNow(logString, 0);
	return(1);
    } else {
	mcCacheStart -> nextNode = NULL;
	mcCacheStart -> prevNode = NULL;
	mcCacheStart -> expiresAt = 0;
	bzero(mcCacheStart -> relayPresIP, sizeof(mcCacheStart -> relayPresIP));
	mcCacheStart -> envFrom = NULL;
	mcCacheStart -> MXesThatReject = -1;
	mcCacheStart -> MXesThatAccept = -1;
	mcCacheStart -> MXesOutOfOrder = -1;
	mcCacheStart -> MXesThatTempfail = -1;
	mcCacheStart -> MXesNum = -1;
	mcCacheStart -> reverseRes = CB_UNKNOWN;
	mcCacheStart -> fakeRes = CB_UNKNOWN;
	mcCacheStart -> whitelisted = -1;
	mcCacheStart -> gracefulDNS = -1;
#ifdef WITH_SPF
	mcCacheStart -> SPFResult = -1;
#endif
	bzero(mcCacheStart -> resolvedRelayIP, sizeof(mcCacheStart -> resolvedRelayIP));
	return(0);
    }
};

struct mcCache *
purgeCacheElem (struct mcCache *nodeToPurge, int *endMarker) {
/* deletes an element from cache, 0 - success, 1 - fail */
    struct mcCache *resNode;

    snprintf(logString, MAXLOGLINE, "entered purgeCacheElem().");
    logNow(logString, 3);
    snprintf(logString, MAXLOGLINE, "Next node address: %p, previous node address: %p.", nodeToPurge -> nextNode, nodeToPurge -> prevNode);
    logNow(logString, 3);
    if (nodeToPurge -> nextNode == NULL && nodeToPurge -> prevNode == NULL) {
	/* we got only one element */
	/* clearing it out and douing nothing */
	snprintf(logString, MAXLOGLINE, "Got only one element in cache, resetting it instead of relinking.");
	logNow(logString, 3);
	mcCacheStart -> nextNode = NULL;
	mcCacheStart -> prevNode = NULL;
	mcCacheStart -> expiresAt = 0;
	mcCacheStart -> relayPresIP[0] = '\0';
	mcCacheStart -> envFrom = NULL;
	mcCacheStart -> MXesThatReject = -1;
	mcCacheStart -> MXesThatAccept = -1;
	mcCacheStart -> MXesOutOfOrder = -1;
	mcCacheStart -> MXesThatTempfail = -1;
	mcCacheStart -> MXesNum = -1;
	mcCacheStart -> reverseRes = CB_UNKNOWN;
	mcCacheStart -> fakeRes = CB_UNKNOWN;
	mcCacheStart -> whitelisted = -1;
	mcCacheStart -> gracefulDNS = -1;
	mcCacheStart -> resolvedRelayIP[0] = '\0';
	snprintf(logString, MAXLOGLINE, "Leaving purgeCacheElem().");
	logNow(logString, 3);
	*endMarker = 1;
	return(mcCacheStart);
    } else {
	if (nodeToPurge -> nextNode != NULL && nodeToPurge -> prevNode != NULL) {
	    /* we got a standard element */
	    snprintf(logString, MAXLOGLINE, "Got standard cache element, relinking hooks.");
	    logNow(logString, 3);
	    resNode = nodeToPurge -> nextNode;
	    nodeToPurge -> nextNode -> prevNode = nodeToPurge -> prevNode;
	    nodeToPurge -> prevNode -> nextNode = nodeToPurge -> nextNode;
	} else {
	    /* okay, we're at the beginning or in the end */
	    if (nodeToPurge -> nextNode != NULL) {
		/* seems more like beginning */
		snprintf(logString, MAXLOGLINE, "Seems like we are at the beginning, relinking hooks.");
		logNow(logString, 3);
		resNode = nodeToPurge -> nextNode;
		snprintf(logString, MAXLOGLINE, "I will return the node with address %p.", resNode);
		logNow(logString, 3);
		snprintf(logString, MAXLOGLINE, "Setting prevNode address of the node with address %p to NULL.", nodeToPurge -> nextNode);
		logNow(logString, 3);
		nodeToPurge -> nextNode -> prevNode = NULL;
		/* now we need to relink mcCacheStart */
		snprintf(logString, MAXLOGLINE, "Relinking mcCacheStart to address %p.", resNode);
		logNow(logString, 3);
		mcCacheStart = resNode;
	    } else {
		/*
		 either we're at the end, or we completely fucked up 
		 with the logic, the code, and entire MOTHERFUCKERING 
		 LIFE
		*/
		snprintf(logString, MAXLOGLINE, "Seems like we are at the end of cache, relinking hooks.");
		logNow(logString, 3);
		resNode = nodeToPurge -> prevNode;
		nodeToPurge -> prevNode -> nextNode = NULL;
		*endMarker = 1;
	    }
        }
	/* new we're freeing cache entry */
	/* the most THERMONUCLEAR PART HERE */
	snprintf(logString, MAXLOGLINE, "Trying to free() envFrom element of purged cache entry.");
	logNow(logString, 3);
	free(nodeToPurge -> envFrom);
	snprintf(logString, MAXLOGLINE, "Trying to free() purged cache entry.");
	logNow(logString, 3);
	free(nodeToPurge);
	snprintf(logString, MAXLOGLINE, "Leaving purgeCacheElem().");
	logNow(logString, 3);
	return(resNode);
    }
};


void
purgeCache () {
    int i = 0;
    int dels = 0;
    struct mcCache *cacheMark;
    int thisIsTheEnd = 0;

    snprintf(logString, MAXLOGLINE, "entered purgeCache().");
    logNow(logString, 3);
    snprintf(logString, MAXLOGLINE, "Displaying contents of the cache:");
    logNow(logString, 3);
    cacheMark = mcCacheStart;
    while (cacheMark != NULL && thisIsTheEnd == 0) {
	snprintf(logString, MAXLOGLINE, "%s(%s)/%s.", cacheMark -> relayPresIP, cacheMark -> resolvedRelayIP, cacheMark -> envFrom);
	logNow(logString, 3);
	if (cacheMark -> expiresAt > 0 && cacheMark -> expiresAt < time(NULL)) {
	    snprintf(logString, MAXLOGLINE, "This entry is expired %d seconds ago, trying to clean it out.", time(NULL) - cacheMark -> expiresAt);
	    logNow(logString, 3);
	    /* this will increment the mark on success */
	    snprintf(logString, MAXLOGLINE, "Calling purgeCacheElem() with an address %p as an argument.", cacheMark );
	    logNow(logString, 3);
	    snprintf(logString, MAXLOGLINE, "Previous node of the argument is at %p, next node - at %p.", cacheMark -> prevNode, cacheMark -> nextNode);
	    logNow(logString, 3);
	    dels++;
	    cacheMark = purgeCacheElem(cacheMark, &thisIsTheEnd);
	} else {
	    snprintf(logString, MAXLOGLINE, "This entry will expire in %d seconds, skipping it for now.", cacheMark -> expiresAt - time(NULL));
	    logNow(logString, 3);
	    cacheMark = cacheMark -> nextNode;
	}
	i++;
    };
    snprintf(logString, MAXLOGLINE, "Cache: %d entries, %d deleted.", i, dels);
    logNow(logString, 3);
}

void
getCache () {
    int i = 0;
    struct mcCache *cacheMark;

    snprintf(logString, MAXLOGLINE, "entered getCache().");
    logNow(logString, 3);
    snprintf(logString, MAXLOGLINE, "Displaying contents of the cache:");
    logNow(logString, 3);
    cacheMark = mcCacheStart;
    while (cacheMark != NULL) {
	snprintf(logString, MAXLOGLINE, "%s(%s)/%s.", cacheMark -> relayPresIP, cacheMark -> resolvedRelayIP, cacheMark -> envFrom);
	logNow(logString, 3);
	cacheMark = cacheMark -> nextNode;
	i++;
    };
    snprintf(logString, MAXLOGLINE, "Cache: %d entries.", i);
    logNow(logString, 3);
}

int
cacheIsEmpty () {
/* returns number of elements */
    struct mcCache *cacheMark;
    int i;
    
    snprintf(logString, MAXLOGLINE, "entered cacheIsEmpty().");
    logNow(logString, 3);
    cacheMark = mcCacheStart;
    for (i = 0; cacheMark != NULL; cacheMark = cacheMark -> nextNode) {
	i++;
    };
    return(i);
};

struct mcCache *
seekCache (char *relayPresIP, char *envFrom) {
/* returns address of the found element or null */
    struct mcCache *cacheMark;
    int matchFound = 0;
    
    snprintf(logString, MAXLOGLINE, "entered seekCache().");
    logNow(logString, 3);
    snprintf(logString, MAXLOGLINE, "Got my arguments - relayPresIP %s, envFrom %s.", relayPresIP, envFrom);
    logNow(logString, 3);
    cacheMark = mcCacheStart;
    while (cacheMark != NULL && matchFound == 0 && cacheMark -> expiresAt > 0) {
	snprintf(logString, MAXLOGLINE, "Comparing %s and %s against %s and %s.", cacheMark -> relayPresIP, cacheMark -> envFrom, relayPresIP, envFrom);
	logNow(logString, 3);
	if (strncmp(cacheMark -> relayPresIP, relayPresIP, strlen(cacheMark -> relayPresIP)) == 0 && strncmp(cacheMark -> envFrom, envFrom, strlen(cacheMark -> envFrom)) == 0) {
	    snprintf(logString, MAXLOGLINE, "Found a match between supplied pair %s/%s and cache entry %s/%s.", relayPresIP, envFrom, cacheMark -> relayPresIP, cacheMark -> envFrom);
	    logNow(logString, 3);
	    snprintf(logString, MAXLOGLINE, "Cache hit detected. Resetting this entry TTL.");
	    logNow(logString, 2);
	    cacheMark -> expiresAt = time(NULL) + cacheTTL;
	    matchFound = 1;
	} else {
	    snprintf(logString, MAXLOGLINE, "Supplied pair %s/%s and cache entry %s/%s don't match, skipping to next entry.", relayPresIP, envFrom, cacheMark -> relayPresIP, cacheMark -> envFrom);
	    logNow(logString, 3);
	    cacheMark = cacheMark -> nextNode;
	}
    };
    if (matchFound == 1) {
	return(cacheMark);
    } else {
	return(NULL);
    }
};

int
extendCache (char *envFrom, struct mlfiPriv *privData, int action) {
/* adds element to cache, 0 - success, 1 - fail */

    struct mcCache *newCacheEntry;
    int memSize;
    
    int
    initCacheElement () {
	snprintf(logString, MAXLOGLINE, "entered initCacheElement.");
    	logNow(logString, 3);
	newCacheEntry -> expiresAt = time(NULL) + cacheTTL;
    	newCacheEntry -> action = action;
	newCacheEntry -> MXesThatReject = privData -> MXesThatReject;
	newCacheEntry -> MXesThatAccept  = privData -> MXesThatAccept;
	newCacheEntry -> MXesOutOfOrder = privData -> MXesOutOfOrder;
	newCacheEntry -> MXesThatTempfail = privData -> MXesThatTempfail;
	newCacheEntry -> MXesNum = privData -> MXesNum;
	newCacheEntry -> reverseRes = privData -> reverseRes;
	newCacheEntry -> fakeRes = privData -> fakeRes;
	newCacheEntry -> whitelisted = privData -> whitelisted;
	newCacheEntry -> gracefulDNS = privData -> gracefulDNS;
#ifdef WITH_SPF
	newCacheEntry -> SPFResult = privData -> SPFResult;
#endif
	snprintf(logString, MAXLOGLINE, "Pushing %s to newCacheEntry->relayPresIP.", privData -> relayPresIP);
    	logNow(logString, 3);
	strncpy(&newCacheEntry -> relayPresIP[0], privData -> relayPresIP, strlen(privData -> relayPresIP));
	snprintf(logString, MAXLOGLINE, "Pushing %s to newCacheEntry->resolvedRelayIP.", privData -> resolvedRelayIP);
    	logNow(logString, 3);
	strncpy(&newCacheEntry -> resolvedRelayIP[0], privData -> resolvedRelayIP, strlen(privData -> resolvedRelayIP));	
	newCacheEntry -> envFrom = malloc(strlen(envFrom) + 1);
	if (newCacheEntry -> envFrom == NULL) {
	    snprintf(logString, MAXLOGLINE, "Cannot allocate memory.");
    	    logNow(logString, 0);
	    return(1);
	} else {
	    bzero(newCacheEntry -> envFrom, strlen(envFrom) + 1);
	    snprintf(logString, MAXLOGLINE, "And finally trying to push %s to newCacheEntry -> envFrom.", envFrom);
	    logNow(logString, 3);
	    strncpy(newCacheEntry -> envFrom,  envFrom, strlen(envFrom));
	    /* while on debug - print out the cache */
	    if (logLevel > 2) {
		getCache();
	    }
	    return(0);
	}
    }
    
    snprintf(logString, MAXLOGLINE, "entered extendCache().");
    logNow(logString, 3);
    snprintf(logString, MAXLOGLINE, "got my env-from %s, my privData %p and my action %d.", envFrom, privData, action);
    logNow(logString, 3);
    /* since we are in threaded environment, we need once more, with busyflag set, check for a dupe */
    if (seekCache(privData -> relayPresIP, envFrom) != NULL) {
	snprintf(logString, MAXLOGLINE, "Supplied pair %s/%s already exists in cache, skipping addition.", privData -> relayPresIP, envFrom);
	logNow(logString, 2);
	return(1);
    }
    snprintf(logString, MAXLOGLINE, "After cache recheck the Supplied pair %s/%s wasn't found in cache, proceeding addition.", privData -> relayPresIP, envFrom);
    logNow(logString, 2);
    /* first we will check - maby we're just with empty cache */
    if (mcCacheStart -> expiresAt > 0 || cacheIsEmpty() > 1) {
	/* we got non-empty cache with more than 2 elements */
	snprintf(logString, MAXLOGLINE, "Got non-empty cache, trying to add new first element.");
    	logNow(logString, 3);
	memSize = sizeof(struct mcCache);
	snprintf(logString, MAXLOGLINE, "Trying to get %d bytes for the new cache element.", memSize);
    	logNow(logString, 3);
	newCacheEntry = malloc(memSize);
	if (newCacheEntry == NULL) {
	    snprintf(logString, MAXLOGLINE, "Cannot allocate memory.");
    	    logNow(logString, 0);
	    return(1);
	} else {
	    /* linking hooks of the new node */
	    mcCacheStart -> prevNode = newCacheEntry;
	    newCacheEntry -> nextNode = mcCacheStart;
	    newCacheEntry -> prevNode = NULL;
	    bzero(newCacheEntry -> relayPresIP, sizeof(newCacheEntry -> relayPresIP));
	    bzero(newCacheEntry -> resolvedRelayIP, sizeof(newCacheEntry -> resolvedRelayIP));
	    /* changing the pointer to the beginning of the cache */
	    mcCacheStart = newCacheEntry;
	    if (initCacheElement() == 0) {
		return(0);
	    } else {
		return(1);
	    }
	}
    } else {
	/* yeah, we got empty cache */
	/* just need to init it's only element */
	snprintf(logString, MAXLOGLINE, "Got empty cache, trying to initialize first element.");
    	logNow(logString, 3);
	newCacheEntry = mcCacheStart;
	if (initCacheElement() == 0) {
	    return(0);
	} else {
	    return(1);
	}
    }
};

int
copyStuff (struct mlfiPriv *privData, struct mcCache *cacheEntry) {
    /* copies the attributes from the cache to the actual context-associated structure */
    snprintf(logString, MAXLOGLINE, "entered copyStuff().");
    logNow(logString, 3);
    privData -> MXesThatReject = cacheEntry -> MXesThatReject;
    privData -> MXesThatAccept = cacheEntry -> MXesThatAccept;
    privData -> MXesOutOfOrder = cacheEntry -> MXesOutOfOrder;
    privData -> MXesThatTempfail = cacheEntry -> MXesThatTempfail;
    privData -> MXesNum = cacheEntry -> MXesNum;
    privData -> reverseRes = cacheEntry -> reverseRes;
    privData -> fakeRes = cacheEntry -> fakeRes;
    privData -> whitelisted = cacheEntry -> whitelisted;
    privData -> gracefulDNS = cacheEntry -> gracefulDNS;
    privData -> cached = 1;
#ifdef WITH_SPF
    privData -> SPFResult = cacheEntry -> SPFResult;
#endif
    /* these are strings of fixed length */
    strncpy(privData -> resolvedRelayIP, cacheEntry -> resolvedRelayIP, strlen(cacheEntry -> resolvedRelayIP));
    return(0);
}
