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

extern int logLevel;
extern int mcDebug;
extern struct mcCallbackPit *mcPitStart;

/* mutex signalling for nested calling */

#define CB_LOCKED	0	/* lock set */
#define CB_LOCKERR	1	/* lock set error */
#define CB_ALRLOCKED	2	/* lock setting skipped, set already */
#define CB_UNLOCKED	3	/* unlocked */
#define CB_UNLOCKERR	4	/* unlocking error */
#define CB_ALRUNLOCKED	5	/* unlock skipped, not locked */

int
initEmptyPit () {
    /* inits pit cache, 0 - success, 1 - fail */
    int memSize;
    char tmpLog[MAXLOGLINE];
    
    logNow("entered initEmptyPit().", 3, 0);
    memSize = sizeof(struct mcCallbackPit);
    snprintf(tmpLog, MAXLOGLINE, "Trying to get %d bytes for the new pit cache element.", memSize);
    logNow(&tmpLog[0], 3, 0);
    mcPitStart = malloc(memSize);
    if (mcPitStart == NULL) {
        logNow("Cannot allocate memory.", 0, 0);
	return(1);
    } else {
	mcPitStart -> nextNode = NULL;
	mcPitStart -> prevNode = NULL;
	bzero(mcPitStart -> relayPresIP, sizeof(mcPitStart -> relayPresIP));
	return(0);
    }
};

struct mcCallbackPit *
purgePitElem (struct mcCallbackPit *nodeToPurge, int *endMarker, struct mlfiPriv *privData) {
    /* deletes an element from pit, 0 - success, 1 - fail */
    struct mcCallbackPit *resNode;
    char tmpLog[MAXLOGLINE];

    logNow("entered purgePitElem().", 3, privData);
    snprintf(tmpLog, MAXLOGLINE, "Next node address: %p, previous node address: %p.", nodeToPurge -> nextNode, nodeToPurge -> prevNode);
    logNow(&tmpLog[0], 3, privData);
    if (nodeToPurge -> nextNode == NULL && nodeToPurge -> prevNode == NULL) {
	/* we got only one element */
	/* clearing it out and douing nothing */
	snprintf(tmpLog, MAXLOGLINE, "Got only one element in pit cache, resetting it instead of relinking.");
	logNow(&tmpLog[0], 3, privData);
	mcPitStart -> nextNode = NULL;
	mcPitStart -> prevNode = NULL;
	mcPitStart -> relayPresIP[0] = '\0';
	logNow("Leaving purgePitElem().", 3, privData);
	*endMarker = 1;
	return(mcPitStart);
    } else {
	if (nodeToPurge -> nextNode != NULL && nodeToPurge -> prevNode != NULL) {
	    /* we got a standard element */
	    logNow("Got standard pit cache element, relinking hooks.", 3, privData);
	    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 */
		logNow("Seems like we are at the beginning, relinking hooks.", 3, privData);
		resNode = nodeToPurge -> nextNode;
		snprintf(tmpLog, MAXLOGLINE, "I will return the node with address %p.", resNode);
		logNow(&tmpLog[0], 3, privData);
		snprintf(tmpLog, MAXLOGLINE, "Setting prevNode address of the node with address %p to NULL.", nodeToPurge -> nextNode);
		logNow(&tmpLog[0], 3, privData);
		nodeToPurge -> nextNode -> prevNode = NULL;
		/* now we need to relink mcPitStart */
		snprintf(tmpLog, MAXLOGLINE, "Relinking mcPitStart to address %p.", resNode);
		logNow(&tmpLog[0], 3, privData);
		mcPitStart = resNode;
	    } else {
		/*
		 either we're at the end, or we completely fucked up 
		 with the logic, the code, and entire MOTHERFUCKERING 
		 LIFE
		*/
		logNow("Seems like we are at the end of pit cache, relinking hooks.", 3, privData);
		resNode = nodeToPurge -> prevNode;
		nodeToPurge -> prevNode -> nextNode = NULL;
		*endMarker = 1;
	    }
        }
	/* new we're freeing pit cache entry */
	/* the most THERMONUCLEAR PART HERE */
	logNow("Trying to free() purged pit cache entry.", 3, privData);
	free(nodeToPurge);
	logNow("Leaving purgePitElem().", 3, privData);
	return(resNode);
    }
};


void
unPit (char *relayPresIP, struct mlfiPriv *privData) {
    int i = 0;
    int dels = 0;
    struct mcCallbackPit *pitMark;
    int thisIsTheEnd = 0;
    char tmpLog[MAXLOGLINE];

    logNow("entered unPit().", 3, privData);
    logNow("Displaying contents of the pit cache:", 3, privData);
    pitMark = mcPitStart;
    while (pitMark != NULL && thisIsTheEnd == 0) {
	snprintf(tmpLog, MAXLOGLINE, "%s.", pitMark -> relayPresIP);
	logNow(&tmpLog[0], 3, privData);
	if (strncmp(pitMark -> relayPresIP, relayPresIP, strlen(relayPresIP)) == 0) {
	    snprintf(tmpLog, MAXLOGLINE, "Callback for %s has ended, trying to clean it out.", relayPresIP);
	    logNow(&tmpLog[0], 3, privData);
	    /* this will increment the mark on success */
	    snprintf(tmpLog, MAXLOGLINE, "Calling purgePitElem() with an address %p as an argument.", pitMark );
	    logNow(&tmpLog[0], 3, privData);
	    snprintf(tmpLog, MAXLOGLINE, "Previous node of the argument is at %p, next node - at %p.", pitMark -> prevNode, pitMark -> nextNode);
	    logNow(&tmpLog[0], 3, privData);
	    dels++;
	    pitMark = purgePitElem(pitMark, &thisIsTheEnd, privData);
	} else {
	    snprintf(tmpLog, MAXLOGLINE, "The entry %s is obviously not %s.", pitMark -> relayPresIP, relayPresIP);
	    logNow(&tmpLog[0], 3, privData);
	    pitMark = pitMark -> nextNode;
	}
	i++;
    };
    snprintf(tmpLog, MAXLOGLINE, "Pit: %d entries, %d deleted.", i, dels);
    logNow(&tmpLog[0], 3, privData);
}

void
getPit (struct mlfiPriv *privData) {
    int i = 0;
    struct mcCallbackPit *pitMark;
    char tmpLog[MAXLOGLINE];

    logNow("entered getPit().", 3, privData);
    logNow("Displaying contents of the pit cache:", 3, privData);
    pitMark = mcPitStart;
    while (pitMark != NULL) {
	snprintf(tmpLog, MAXLOGLINE, "%s.", pitMark -> relayPresIP);
	logNow(&tmpLog[0], 3, privData);
	pitMark = pitMark -> nextNode;
	i++;
    };
    snprintf(tmpLog, MAXLOGLINE, "Pit: %d entries.", i);
    logNow(&tmpLog[0], 3, privData);
}

int
pitIsEmpty (struct mlfiPriv *privData) {
    /* returns number of elements */
    struct mcCallbackPit *pitMark;
    int i;
    char tmpLog[MAXLOGLINE];
    
    logNow("entered pitIsEmpty().", 3, privData);
    if (mcPitStart -> nextNode == NULL && strlen(mcPitStart -> relayPresIP) == 0) {
	logNow("Pit is empty.", 3, privData);
	return(0);
    } else {
	pitMark = mcPitStart;
	for (i = 0; pitMark != NULL; pitMark = pitMark -> nextNode) {
	    i++;
	};
    }
    snprintf(tmpLog, MAXLOGLINE, "Pit is %d elements long.", i);
    logNow(&tmpLog[0], 3, privData);
    return(i);
};

int
tryPitLock (struct mlfiPriv *privData) {
    char tmpLog[MAXLOGLINE];
    int mtxErr;

    if (privData -> pitLockOwner == 0) {
	snprintf(tmpLog, MAXLOGLINE, "Trying to lock the pit cache.");
	logNow(&tmpLog[0], 3, privData);
	mtxErr = pthread_mutex_lock(mcPitBusy);
        if (mtxErr == 0) {
	    privData -> pitLockOwner = 1;
    	    logNow("Lock aquired.", 3, privData);
	    return(CB_LOCKED);
	} else {
	    snprintf(tmpLog, MAXLOGLINE, "Lock on the pit cannot be aquired, pthread_mutex_lock() signalled %d.", mtxErr);
	    logNow(&tmpLog[0], 0, privData);
	    return(CB_LOCKERR);
	}
    } else {
	logNow("Skipping pit cache locking, this thread already holds a lock on it.", 3, privData);
	return(CB_ALRLOCKED);
    }
};

int
tryPitUnlock (struct mlfiPriv *privData) {
    char tmpLog[MAXLOGLINE];
    int mtxErr;
    
    if (privData -> pitLockOwner == 1) {
	snprintf(tmpLog, MAXLOGLINE, "Trying to unlock the pit cache.");
	logNow(&tmpLog[0], 3, privData);
	mtxErr = pthread_mutex_unlock(mcPitBusy);
	if (mtxErr == 0) {
	    privData -> pitLockOwner = 0;
	    logNow("Pit cache unlocked.", 3, privData);
	    return(CB_UNLOCKED);
	} else {
    	    snprintf(tmpLog, MAXLOGLINE, "Pit cache cannot be unlocked, pthread_mutex_unlock() signalled %d.", mtxErr);
	    logNow(&tmpLog[0], 0, privData);
	    return(CB_UNLOCKERR);
        }
    } else {
	logNow("Skipping pit cache unlocking, this thread doesn't hold a lock on it.", 3, privData);
	return(CB_ALRUNLOCKED);
    }
};

struct mcCallbackPit *
seekPit (struct mlfiPriv *privData, char *relayPresIP) {
/* returns address of the found element or null */
    struct mcCallbackPit *pitMark;
    int matchFound = 0;
    int unlockNeeded, unlockResult;
    
    char tmpLog[MAXLOGLINE];
    
    logNow("entered seekPit().", 3, privData);
    snprintf(tmpLog, MAXLOGLINE, "Got my arguments - relayPresIP %s.", relayPresIP);
    logNow(&tmpLog[0], 3, privData);
    
    unlockNeeded = tryPitLock(privData);
    snprintf(tmpLog, MAXLOGLINE, "tryPitLock() returned %d.", unlockNeeded);
    logNow(&tmpLog[0], 3, privData);
    
    pitMark = mcPitStart;
    while (pitMark != NULL && matchFound == 0) {
	snprintf(tmpLog, MAXLOGLINE, "Comparing %s against %s.", relayPresIP, pitMark -> relayPresIP);
	logNow(&tmpLog[0], 3, privData);
	if (strncmp(pitMark -> relayPresIP, relayPresIP, strlen(pitMark -> relayPresIP)) == 0) {
	    snprintf(tmpLog, MAXLOGLINE, "Found a match between supplied IP %s and pit cache entry %s.", relayPresIP, pitMark -> relayPresIP);
	    logNow(&tmpLog[0], 3, privData);
	    snprintf(tmpLog, MAXLOGLINE, "Pit cache hit detected.");
	    logNow(&tmpLog[0], 2, privData);
	    matchFound = 1;
	} else {
	    snprintf(tmpLog, MAXLOGLINE, "Supplied IP %s and pit cache entry %s don't match, skipping to next entry.", relayPresIP, pitMark -> relayPresIP);
	    logNow(&tmpLog[0], 3, privData);
	    pitMark = pitMark -> nextNode;
	}
    };
    
    if (unlockNeeded == CB_LOCKED) {
        logNow("Trying to clear the lock in seekPit(), looks like we need it.", 3, privData);
	unlockResult = tryPitUnlock(privData);
    } else {
	if (unlockNeeded == CB_LOCKERR) {
    	    logNow("Skipping lock cleansing in seekPit(), due to previous lock set error.", 3, privData);
	} else {
	    logNow("Skipping lock cleansing in seekPit(), we're in the nested lock call.", 3, privData);
    	    logNow(&tmpLog[0], 3, privData);
	}
    };
    
    if (matchFound == 1) {
	return(pitMark);
    } else {
	return(NULL);
    }
};

int
extendPit (struct mlfiPriv *privData, char *relayPresIP) {
/* adds element to pit cache, 0 - success, 1 - fail */

    struct mcCallbackPit *newPitEntry;
    int memSize;
    char tmpLog[MAXLOGLINE];
    
    void
    initPitElement () {
    	logNow("entered initPitElement.", 3, privData);
	snprintf(tmpLog, MAXLOGLINE, "Pushing %s to newPitEntry->relayPresIP.", relayPresIP);
    	logNow(&tmpLog[0], 3, privData);
	strncpy(&newPitEntry -> relayPresIP[0], relayPresIP, strlen(relayPresIP));
	/* while on debug - print out the pit cache */
	if (logLevel > 2) {
	    getPit(privData);
        }
    }
    
    logNow("entered extendPit().", 3, privData);
    snprintf(tmpLog, MAXLOGLINE, "got my privData %p.", privData);
    logNow(&tmpLog[0], 3, privData);
    /* since we are in threaded environment, we need once more, with busyflag set, check for a dupe */
    if (pitIsEmpty(privData) > 0 && seekPit(privData, relayPresIP) != NULL) {
	snprintf(tmpLog, MAXLOGLINE, "Supplied IP %s already exists in cache, skipping addition.", relayPresIP);
	logNow(&tmpLog[0], 2, privData);
	return(1);
    }
    snprintf(tmpLog, MAXLOGLINE, "After cache recheck the supplied IP %s wasn't found in pit cache, proceeding addition.", relayPresIP);
    logNow(&tmpLog[0], 2, privData);
    /* first we will check - maby we're just with empty cache */
    if (pitIsEmpty(privData) > 0) {
	/* we got non-empty pit cache */
    	logNow("Got non-empty pit cache, trying to add new first element.", 3, privData);
	memSize = sizeof(struct mcCallbackPit);
	snprintf(tmpLog, MAXLOGLINE, "Trying to get %d bytes for the new pit cache element.", memSize);
    	logNow(&tmpLog[0], 3, privData);
	newPitEntry = malloc(memSize);
	if (newPitEntry == NULL) {
    	    logNow("Cannot allocate memory.", 0, privData);
	    return(1);
	} else {
	    /* linking hooks of the new node */
	    mcPitStart -> prevNode = newPitEntry;
	    newPitEntry -> nextNode = mcPitStart;
	    newPitEntry -> prevNode = NULL;
	    bzero(newPitEntry -> relayPresIP, sizeof(newPitEntry -> relayPresIP));
	    /* changing the pointer to the beginning of the cache */
	    mcPitStart = newPitEntry;
    	    return(0);
	}
    } else {
	/* yeah, we got empty cache */
	/* just need to init it's only element */
    	logNow("Got empty pit cache, trying to initialize first element.", 3, privData);
	newPitEntry = mcPitStart;
	initPitElement();
        return(0);
    }
};
