/* 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>
#include <pushhistory.h>
#include <processmxpart.h>
#include <processdirectpart.h>
#include <cache.h>

extern char callbackMode[256];
extern char fakeEmail[MAXEMAIL];
extern char action[32];
extern int cacheLen;
extern pthread_mutex_t *mcCacheBusy;

int
cacheThis (struct mlfiPriv *privData, char *formattedEmail, int action) {
    /* 0 - success, 1 - failure */
    int actCacheLen;
    char tmpLog[MAXLOGLINE];
    int unlockNeeded, unlockResult;
    
    /* caching entry */
    logNow("entered cacheThis().", 3, privData);
    actCacheLen = cacheIsEmpty(privData);
    if (actCacheLen + 1 < cacheLen) {
        snprintf(tmpLog, MAXLOGLINE, "Trying to cache the pair %s/%s, cache length is %d.", privData -> relayPresIP, formattedEmail, actCacheLen);
        logNow(&tmpLog[0], 2, privData);
	/* flag mutex */
	unlockNeeded = tryCacheLock(privData);
	
	extendCache(formattedEmail, privData, action);
	purgeCache();
	
	/* unflag mutex */
	if (unlockNeeded == CB_LOCKED) {
	    logNow("Trying to clear the lock in cacheThis(), looks like we need it.", 3, privData);
	    unlockResult = tryCacheUnlock(privData);
	} else {
	    if (unlockNeeded == CB_LOCKERR) {
	        logNow("Skipping lock cleansing in cacheThis(), due to previous lock set error.", 3, privData);
	    } else {
	        logNow("Skipping lock cleansing in cacheThis(), we're in the nested lock call.", 3, privData);
                logNow(&tmpLog[0], 3, privData);
            }
        };
	
	return(0);
    } else {
        snprintf(tmpLog, MAXLOGLINE, "Cannot cache the pair %s/%s, cache is full.", privData -> relayPresIP, formattedEmail);
        logNow(&tmpLog[0], 2, privData);
        logNow("Just for you to know: cache is full.", 0, privData);
	return(1);
    }
}

int
makeDecision (struct mlfiPriv *privData, char *formattedEmail, char *mailHost, SMFICTX *ctx) {
    int mxPassKey, directPassKey, fakePassKey;
    /*
        0 - pass
        1 - reject
        2 - tempfail
    */
    char dTranscription[256];
    char fTranscription[256];
    char tmpLog[MAXLOGLINE];

    /* need to handle direct/rfc/both modes */
    if (strncmp(callbackMode, "rfc", 3) == 0) {
	snprintf(tmpLog, MAXLOGLINE, "Working in \"rfc\" mode.");
	logNow(&tmpLog[0], 2, privData);
	snprintf(tmpLog, MAXLOGLINE, "Calling processMXes with e-mail %s and hostpart %s.", formattedEmail, mailHost);
	logNow(&tmpLog[0], 2, privData);
	mxPassKey = processMXpart(formattedEmail, mailHost, privData);
	switch (mxPassKey) {
	    case CB_PASS:
		snprintf(tmpLog, MAXLOGLINE, "Passing e-mail from %s, [%d/%d] MXes are accepting.", formattedEmail, privData->MXesThatAccept, privData->MXesNum);
		logNow(&tmpLog[0], 2, privData);
#ifdef WITH_PGSQL
		strncpy(action, "passed", sizeof(action));
		pushHistory(privData, &action[0], formattedEmail);
#endif
		
		/* caching entry */
		cacheThis(privData, formattedEmail, CB_PASS);
		return SMFIS_CONTINUE;
		break;
	    case CB_REJECT:
		snprintf(tmpLog, MAXLOGLINE, "Dropping e-mail from %s and relay [%s], [%d/%d] of MXes for the domain %s rejected this e-mail in reverse direction.", formattedEmail, privData->relayPresIP, privData->MXesThatReject, privData->MXesNum, mailHost);
		logNow(&tmpLog[0], 2, privData);
		mc_setreply(ctx, "550", &tmpLog[0]);
#ifdef WITH_PGSQL
		strncpy(action, "dropped", sizeof(action));
		pushHistory(privData, &action[0], formattedEmail);
#endif
		/* caching entry */
		cacheThis(privData, formattedEmail, CB_REJECT);
		return SMFIS_REJECT;
		break;
	    case CB_TEMPFAIL:
		snprintf(tmpLog, MAXLOGLINE, "Tempfailing e-mail from %s and relay [%s], either MX relays for the domain %s are tempfailing, or some other error happened.", formattedEmail,privData->relayPresIP, mailHost);
		logNow(&tmpLog[0], 2, privData);
		mc_setreply(ctx, "451", &tmpLog[0]);
#ifdef WITH_PGSQL
		strncpy(action, "tempfailed", sizeof(action));
		pushHistory(privData, &action[0], formattedEmail);
#endif
		/* caching entry */
		cacheThis(privData, formattedEmail, CB_TEMPFAIL);
		return SMFIS_TEMPFAIL;
		break;
	}
    } else {
	if (strncmp(callbackMode, "direct", 6) == 0) {
	    logNow("Working in \"direct\" mode.", 2, privData);
	    /* checking original email */
	    snprintf(tmpLog, MAXLOGLINE, "Calling processDirectPart with e-mail %s and hostpart %x.", formattedEmail, privData->relayIP);
	    logNow(&tmpLog[0], 2, privData);
	    directPassKey = processDirectPart(formattedEmail, privData->relayIP, privData);
	    /* checking fake email */
	    snprintf(tmpLog, MAXLOGLINE, "Calling processDirectPart with fake e-mail %s and hostpart %x.", formattedEmail, privData->relayIP);
	    logNow(&tmpLog[0], 2, privData);
	    fakePassKey = processDirectPart(fakeEmail, privData->relayIP, privData);
	    if (fakePassKey == CB_PASS) {
		/* relay is accepting fake emails */
		privData -> fakeRes = CB_PASS;
		snprintf(tmpLog, MAXLOGLINE, "Dropping e-mail from %s, relay %s that the mail is coming from is accepting fake emails, probably openrelaying.", formattedEmail, privData->mcfi_connectfrom);
		logNow(&tmpLog[0], 0, privData);
		mc_setreply(ctx, "550", &tmpLog[0]);
#ifdef WITH_PGSQL
		strncpy(action, "dropped", sizeof(action));
		pushHistory(privData, &action[0], formattedEmail);
#endif
		/* caching entry */
		cacheThis(privData, formattedEmail, CB_REJECT);
	        return SMFIS_REJECT;
	    } else {
	        if (directPassKey == CB_REJECT) {
		    privData -> reverseRes = CB_REJECT;
		    snprintf(tmpLog, MAXLOGLINE, "Dropping e-mail from %s, relay %s that the mail is coming from isn't accepting email it's trying to pass.", formattedEmail, privData->mcfi_connectfrom);
		    logNow(&tmpLog[0], 0, privData);
		    mc_setreply(ctx, "550", &tmpLog[0]);
#ifdef WITH_PGSQL
		    strncpy(action, "dropped", sizeof(action));
		    pushHistory(privData, &action[0], formattedEmail);
#endif
		    /* caching entry */
		    cacheThis(privData, formattedEmail, CB_REJECT);
	    	    return SMFIS_REJECT;
	        } else {
		    if (directPassKey == CB_PASS) {
			privData -> reverseRes = CB_PASS;
			snprintf(tmpLog, MAXLOGLINE, "Passing e-mail from %s, relay %s isn't accepting fake email and is accepting original email.", formattedEmail, privData->mcfi_connectfrom);
			logNow(&tmpLog[0], 0, privData);
#ifdef WITH_PGSQL
			strncpy(action, "passed", sizeof(action));
			pushHistory(privData, &action[0], formattedEmail);
#endif
			/* caching entry */
			cacheThis(privData, formattedEmail, CB_PASS);
	    		return SMFIS_CONTINUE;
		    } else {
			snprintf(tmpLog, MAXLOGLINE, "Tempfailing email from %s, suspicious relay %s - it %s reversing e-mail.", 
			    formattedEmail, privData->mcfi_connectfrom, 
			    transcriptAnswerCode(directPassKey, dTranscription));
			    snprintf(tmpLog, MAXLOGLINE, "Tempfailing e-mail from %s: accepting MXes [%d/%d], rejecting MXes [%d/%d], tempfailing MXes [%d/%d], MXes out of order [%d/%d].",
				formattedEmail,
				privData->MXesThatAccept,
				privData->MXesNum,
				privData->MXesThatReject,
				privData->MXesNum,
				privData->MXesThatTempfail,
				privData->MXesNum,
				privData->MXesOutOfOrder,
				privData->MXesNum);
			logNow(&tmpLog[0], 0, privData);
			mc_setreply(ctx, "451", &tmpLog[0]);
#ifdef WITH_PGSQL
			strncpy(action, "tempfailed", sizeof(action));
			pushHistory(privData, &action[0], formattedEmail);
#endif
			/* caching entry */
			cacheThis(privData, formattedEmail, CB_TEMPFAIL);
                        return SMFIS_TEMPFAIL;
		    }
		}
	    }
	} else {
	    logNow("Working in \"both\" mode.", 2, privData);
	    /* then simply both */
	    snprintf(tmpLog, MAXLOGLINE, "Calling processMXes with e-mail %s and hostpart %s.", formattedEmail, mailHost);
	    logNow(&tmpLog[0], 2, privData);
	    mxPassKey = processMXpart(formattedEmail, mailHost, privData);
	    snprintf(tmpLog, MAXLOGLINE, "Calling processDirectPart with e-mail %s and hostpart %x.", formattedEmail, privData->relayIP);
	    logNow(&tmpLog[0], 2, privData);
	    directPassKey = processDirectPart(formattedEmail, privData->relayIP, privData);
	    /* checking fake email */
	    snprintf(tmpLog, MAXLOGLINE, "Calling processDirectPart with fake e-mail %s and hostpart %x.", fakeEmail, privData->relayIP);
	    logNow(&tmpLog[0], 2, privData);
	    fakePassKey = processDirectPart(fakeEmail, privData->relayIP, privData);
	    if (fakePassKey == CB_PASS) {
		/* relay is accepting fake emails */
		privData -> fakeRes = CB_PASS;
		snprintf(tmpLog, MAXLOGLINE, "Dropping e-mail from %s, sending relay %s that is sending the mail is accepting fake emails, probably openrelaying.", formattedEmail, privData->mcfi_connectfrom);
		logNow(&tmpLog[0], 0, privData);
		mc_setreply(ctx, "550", &tmpLog[0]);
#ifdef WITH_PGSQL
		strncpy(action, "dropped", sizeof(action));
		pushHistory(privData, &action[0], formattedEmail);
#endif
		/* caching entry */
		cacheThis(privData, formattedEmail, CB_REJECT);
	        return SMFIS_REJECT;
	    } else {
	        if (directPassKey == CB_REJECT) {
		    privData -> reverseRes = CB_REJECT;
		    snprintf(tmpLog, MAXLOGLINE, "Dropping e-mail from %s, relay %s that is sending the mail isn't accepting email it's trying to pass.", formattedEmail, privData->mcfi_connectfrom);
		    logNow(&tmpLog[0], 0, privData);
		    mc_setreply(ctx, "550", &tmpLog[0]);
#ifdef WITH_PGSQL
		    strncpy(action, "dropped", sizeof(action));
		    pushHistory(privData, &action[0], formattedEmail);
#endif
		    /* caching entry */
		    cacheThis(privData, formattedEmail, CB_REJECT);
	    	    return SMFIS_REJECT;
	        } else {
		    if (directPassKey == CB_PASS || directPassKey == CB_SKIP || directPassKey == CB_GRACEFUL) {
			privData -> reverseRes = CB_PASS;
			/* additionally need to check MX answercode before passing */
			if (mxPassKey == CB_PASS) {
			    switch (directPassKey) {
				case CB_SKIP:
				    snprintf(tmpLog, MAXLOGLINE, "Passing email from %s, relay %s is one of the MXes and [%d/%d] MXes are accepting original email existing on MX.", formattedEmail, mailHost, privData->MXesThatAccept, privData->MXesNum);
				    logNow(&tmpLog[0], 0, privData);
				    break;
				case CB_GRACEFUL:
				    snprintf(tmpLog, MAXLOGLINE, "Passing email from %s, graceful DNS in use and [%d/%d] MXes are accepting original email existing on MX.", formattedEmail, privData->MXesThatAccept, privData->MXesNum);
				    logNow(&tmpLog[0], 0, privData);
				    break;
				case CB_PASS:
				    snprintf(tmpLog, MAXLOGLINE, "Passing email from %s, relay MXes %s aren't accepting fake email and [%d/%d] MXes are accepting original email existing on MX.", formattedEmail, mailHost, privData->MXesThatAccept, privData->MXesNum);
				    logNow(&tmpLog[0], 0, privData);
				    break;
			    }
#ifdef WITH_PGSQL
			    strncpy(action, "passed", sizeof(action));
			    pushHistory(privData, &action[0], formattedEmail);
#endif
			    /* caching entry */
			    cacheThis(privData, formattedEmail, CB_PASS);
	    		    return SMFIS_CONTINUE;
			} else {
			    if (mxPassKey == CB_REJECT) {
				/* Original email doesn't exist on the target MX*/
				snprintf(tmpLog, MAXLOGLINE, "Dropping e-mail from %s and relay [%s], [%d/%d] of MXes for the domain %s rejected that mail.", formattedEmail, privData->relayPresIP, privData->MXesThatReject, privData->MXesNum, mailHost);
				logNow(&tmpLog[0], 0, privData);
				mc_setreply(ctx, "550", &tmpLog[0]);
#ifdef WITH_PGSQL
				strncpy(action, "dropped", sizeof(action));
				pushHistory(privData, &action[0], formattedEmail);
#endif
				/* caching entry */
				cacheThis(privData, formattedEmail, CB_REJECT);
	    			return SMFIS_REJECT;
			    } else {
				snprintf(tmpLog, MAXLOGLINE, "Tempfailing e-mail from %s, suspicious relay %s: when tested this mail on the MXes for domain %s got: accepting MXes [%d/%d], rejecting MXes [%d/%d], tempfailing MXes [%d/%d], MXes out of order [%d/%d]; reverse relaying %s, fake relaying %s.",
					formattedEmail, 
					privData->mcfi_connectfrom,
					mailHost,
			    		privData->MXesThatAccept,
					privData->MXesNum,
					privData->MXesThatReject,
					privData->MXesNum,
					privData->MXesThatTempfail,
					privData->MXesNum,
					privData->MXesOutOfOrder,
					privData->MXesNum,
					transcriptAnswerCode(directPassKey, dTranscription),
					transcriptAnswerCode(fakePassKey, fTranscription));
				logNow(&tmpLog[0], 0, privData);
				mc_setreply(ctx, "451", &tmpLog[0]);
#ifdef WITH_PGSQL
				strncpy(action, "tempfailed", sizeof(action));
				pushHistory(privData, &action[0], formattedEmail);
#endif
				/* caching entry */
				cacheThis(privData, formattedEmail, CB_TEMPFAIL);
                    		return SMFIS_TEMPFAIL;
			    }
			}
		    } else {
			snprintf(tmpLog, MAXLOGLINE, "Tempfailing e-mail from %s, suspicious relay %s: when tested this mail on the MXes for domain %s got: accepting MXes [%d/%d], rejecting MXes [%d/%d], tempfailing MXes [%d/%d], MXes out of order [%d/%d]; reverse relaying %s, fake relaying %s.",
				    formattedEmail,
				    privData->mcfi_connectfrom,
				    mailHost, 
				    privData->MXesThatAccept,
				    privData->MXesNum,
				    privData->MXesThatReject,
				    privData->MXesNum,
				    privData->MXesThatTempfail,
				    privData->MXesNum,
				    privData->MXesOutOfOrder,
				    privData->MXesNum,
				    transcriptAnswerCode(directPassKey, dTranscription),
				    transcriptAnswerCode(fakePassKey, fTranscription));
			logNow(&tmpLog[0], 0, privData);
			mc_setreply(ctx, "451", &tmpLog[0]);
#ifdef WITH_PGSQL
			strncpy(action, "tempfailed", sizeof(action));
			pushHistory(privData, &action[0], formattedEmail);
#endif
			/* caching entry */
			cacheThis(privData, formattedEmail, CB_TEMPFAIL);
                        return SMFIS_TEMPFAIL;
		    }
		}
	    }
	    snprintf(tmpLog, MAXLOGLINE, "Tempfailing e-mail from %s, unable to parse conditions from relay %s: accepting MXes [%d/%d], rejecting MXes [%d/%d], tempfailing MXes [%d/%d], MXes out of order [%d/%d]; reverse relaying %s, fake relaying %s.",
		    formattedEmail, privData->mcfi_connectfrom,
		    privData->MXesThatAccept,
		    privData->MXesNum,
		    privData->MXesThatReject,
		    privData->MXesNum,
		    privData->MXesThatTempfail,
		    privData->MXesNum,
		    privData->MXesOutOfOrder,
		    privData->MXesNum,
		    transcriptAnswerCode(directPassKey, dTranscription),
		    transcriptAnswerCode(fakePassKey, fTranscription));
	    logNow(&tmpLog[0], 0, privData);
	    mc_setreply(ctx, "451", &tmpLog[0]);
#ifdef WITH_PGSQL
	    strncpy(action, "tempfailed", sizeof(action));
	    pushHistory(privData, &action[0], formattedEmail);
#endif
	    /* caching entry */
	    cacheThis(privData, formattedEmail, CB_TEMPFAIL);
	    return SMFIS_TEMPFAIL;
	}
    }
    /* if we are here - then it's something wrong */
    snprintf(tmpLog, MAXLOGLINE, "Tempfailing email from %s, unable to parse conditions.", formattedEmail);
    logNow(&tmpLog[0], 0, privData);
    mc_setreply(ctx, "451", &tmpLog[0]);
    if (formattedEmail != NULL) {
	logNow("Trying to free formattedEmail in mcfi_envfrom().", 3, privData);
	free(formattedEmail);
    }
#ifdef WITH_PGSQL
    strncpy(action, "tempfailed", sizeof(action));
    pushHistory(privData, &action[0], formattedEmail);
#endif
    /* caching entry */
    cacheThis(privData, formattedEmail, CB_TEMPFAIL);
    return SMFIS_TEMPFAIL;
}
