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

extern int logLevel;
extern int mcDebug;
extern int connPoolLen;
extern struct connList connPool;
extern char milterEmail[MAXEMAIL];
extern struct netList whiteNets;
extern char replyURL[1024];

void
logNow(char *stringToLog, int logWhenPrio, struct mlfiPriv *privData) {
    if (logWhenPrio <= logLevel && mcDebug == 0) {
	/* log only when appropriate logLevel is set */
	switch(logWhenPrio) {
	    case 0:
		syslog(LOG_ERR, "[%p]: %s", privData, stringToLog);
		break;
	    case 1:
		syslog(LOG_WARNING, "[%p]: %s", privData, stringToLog);
		break;
	    case 2:
		syslog(LOG_NOTICE, "[%p]: %s", privData, stringToLog);
		break;
	    case 3:
		syslog(LOG_INFO, "[%p]: %s", privData, stringToLog);
		break;
	}
    } else {
	if (mcDebug == 1) {
	    fprintf(stdout, "%s\n", stringToLog);
	}
    };
}

#ifdef WITH_PGSQL
int
getPgConn (){
    int res, i, rndConn;
    char tmpLog[MAXLOGLINE];
    
    for (i = 0; i < connPoolLen; i++) {
	snprintf(tmpLog, MAXLOGLINE, "Trying to aquire a lock on connection %d.", i);
	logNow(&tmpLog[0], 3, 0);
	res = pthread_mutex_trylock(connPool.pgMutexArr[i]);
	if (res == 0) {
	    snprintf(tmpLog, MAXLOGLINE, "Connection %d successfully aquired.", i);
	    logNow(&tmpLog[0], 3, 0);
	    return i;
	}
    }
    /* all connection busy */
    /* reporting that */
    snprintf(tmpLog, MAXLOGLINE, "All connections busy, consider increasing pool length from %d.", connPoolLen);
    logNow(&tmpLog[0], 0, 0);
    /* getting random connection and breaking until it's available */
    /* rndConn = random(connPoolLen - 1); */
    /* random seems to be a complete rocket science so let's just take last connection */
    rndConn = connPoolLen - 1;
    snprintf(tmpLog, MAXLOGLINE, "All connections busy, trying to aquire not so random connection %d.", rndConn);
    logNow(&tmpLog[0], 0, 0);
    res = pthread_mutex_lock(connPool.pgMutexArr[rndConn]);
    if (res == 0) {
	snprintf(tmpLog, MAXLOGLINE, "Random connection %d successfully aquired.", rndConn);
	logNow(&tmpLog[0], 0, 0);
	return rndConn;
    } else {
	snprintf(tmpLog, MAXLOGLINE, "Failed to aquire random connection %d, this is just wrong, aborting.", rndConn);
	logNow(&tmpLog[0], 0, 0);
	exit(EXIT_FAILURE);
    }
}

void
unlockPgConn(int pgConnId) {
    char tmpLog[MAXLOGLINE];
    
    snprintf(tmpLog, MAXLOGLINE, "Trying to unlock mutex on pool element %d.", pgConnId);
    logNow(&tmpLog[0], 3, 0);
    if (pthread_mutex_unlock(connPool.pgMutexArr[pgConnId]) == 0) {
	snprintf(tmpLog, MAXLOGLINE, "Successfully unlocked mutex on pool element %d.", pgConnId);
	logNow(&tmpLog[0], 3, 0);
    } else {
	snprintf(tmpLog, MAXLOGLINE, "Failure unlocking mutex on pool element %d, this is wrong, aborting.", pgConnId);
	logNow(&tmpLog[0], 0, 0);
    }
}
#endif

int
parseAddressString (char *addressString, struct in_addr *network, struct in_addr *mask) {
/* 0 - success
   1 - failure
*/
    char *cidrMask;
    const char *cidrDelimiters = "/";
    uint32_t netMask;
    char tmpLog[MAXLOGLINE];

    /* striping cidr mask */
    cidrMask = strdup(addressString);
    addressString = strsep(&addressString, cidrDelimiters);
    strsep(&cidrMask, cidrDelimiters);
    
    snprintf(tmpLog, MAXLOGLINE, "Got new network mask /%s.", cidrMask);
    logNow(&tmpLog[0], 3, 0);
    /* getting new cidr mask to proper format */
    if (cidrMask == NULL) {
        netMask = 32;
    } else {
        netMask = strtol(cidrMask, NULL, 0);
    }
    if (netMask == 0) {
	snprintf(tmpLog, MAXLOGLINE, "Cannot convert %s to netmask, aborting.", cidrMask);
	logNow(&tmpLog[0], 0, 0);
	return 1;
    }
    if (netMask < 0 || netMask > 32) {
	snprintf(tmpLog, MAXLOGLINE, "%d isnt't a valid netmask.", netMask);
	logNow(&tmpLog[0], 0, 0);
	return 1;
    }
    netMask = 0xffffffff << (32 - netMask);
    netMask = htonl(netMask);
    snprintf(tmpLog, MAXLOGLINE, "After conversion of mask /%s to network format got 0x%x in host order, 0x%x natively.", cidrMask, ntohl(netMask), netMask);
    logNow(&tmpLog[0], 3, 0);
    mask->s_addr = netMask;
    
    if (inet_pton(AF_INET, addressString, network) == 1) {
	snprintf(tmpLog, MAXLOGLINE, "After conversion of net %s to network format got 0x%x in host order, 0x%x natively.", 
	addressString, ntohl(network->s_addr), network->s_addr);
	logNow(&tmpLog[0], 3, 0);
	return 0;
    } else {
	snprintf(tmpLog, MAXLOGLINE, "Cannot convert %s to network format, aborting.", addressString);
	logNow(&tmpLog[0], 3, 0);
	return 1;
    }
}

int
hostIsWhitelisted(struct mlfiPriv *privData) {
/*
    0 - no error
    1 - some critical error happened, need to reject message
*/

    char presNet[INET_ADDRSTRLEN], presMask[INET_ADDRSTRLEN];
    int i = 0;
    int whiteListed = 0;
    char tmpLog[MAXLOGLINE];
#ifdef WITH_PGSQL
    char *whiteListEntry;
    int whiteListEntryLen, retVal, pgConnId;
    struct in_addr *netToTest, *maskToTest;
#endif

    snprintf(tmpLog, MAXLOGLINE, "Entered hostIsWhitelisted().");
    logNow(&tmpLog[0], 3, privData);
    bzero(presNet, sizeof(presNet));
    bzero(presMask, sizeof(presMask));
    while (i < whiteNets.netCount && whiteListed == 0) {
	if (inet_ntop(AF_INET, &whiteNets.whiteNet[i], presNet, INET_ADDRSTRLEN) == NULL) {
    	    snprintf(tmpLog, MAXLOGLINE, "Cannot convert %d to presentation format.", whiteNets.whiteNet[i].s_addr);
	    logNow(&tmpLog[0], 0, privData);
	    return 1;
	}
	if (inet_ntop(AF_INET, &whiteNets.whiteMask[i], presMask, INET_ADDRSTRLEN) == NULL) {
	    snprintf(tmpLog, MAXLOGLINE, "Cannot convert %d to presentation format.", whiteNets.whiteMask[i].s_addr);
	    logNow(&tmpLog[0], 0, privData);
	    return 1;
	}
	if ((privData->relayIP & whiteNets.whiteMask[i].s_addr) == whiteNets.whiteNet[i].s_addr) {
	    whiteListed = 1;
	    snprintf(tmpLog, MAXLOGLINE, "Found host %s in whitelisted network %s, mask %s, passing message", privData->relayPresIP, presNet, presMask);
	    logNow(&tmpLog[0], 0, privData);
	    privData->whitelisted = 1;
	    return 0;
	} else {
	    snprintf(tmpLog, MAXLOGLINE, "Host %s doesn't belong to network %s, mask %s.", privData->relayPresIP, presNet, presMask);
	    logNow(&tmpLog[0], 2, privData);
	}
	i++;
    }
#ifdef WITH_PGSQL
    pgConnId = getPgConn();
    connPool.pgResArr[pgConnId] = PQexec(connPool.pgConnArr[pgConnId], "select address from whitelist");
    if (PQresultStatus(connPool.pgResArr[pgConnId]) != PGRES_TUPLES_OK){
        snprintf(tmpLog, MAXLOGLINE, "SELECT command failed: %s", PQerrorMessage(connPool.pgConnArr[pgConnId]));
	logNow(&tmpLog[0], 0, privData);
	snprintf(tmpLog, MAXLOGLINE, "The result status is: %s",PQresStatus(PQresultStatus(connPool.pgResArr[pgConnId])));
	logNow(&tmpLog[0], 0, privData);
        PQclear(connPool.pgResArr[pgConnId]);
	return 1;
    }
    snprintf(tmpLog, MAXLOGLINE, "Successfully queried database, got %d rows.", PQntuples(connPool.pgResArr[pgConnId]));
    logNow(&tmpLog[0], 2, privData);
    
    /* here the main check starts */
    i = 0;
    while (i < PQntuples(connPool.pgResArr[pgConnId]) && privData->whitelisted == 0){
	whiteListEntryLen = strlen(PQgetvalue(connPool.pgResArr[pgConnId], i, 0));
	whiteListEntry = malloc(whiteListEntryLen + 1);
	if (whiteListEntry == NULL) {
	    logNow("Cannot allocate memory.", 0, privData);
	    PQclear(connPool.pgResArr[pgConnId]);
	    return 1;
	}
	bzero(whiteListEntry, whiteListEntryLen + 1);
	strncpy(whiteListEntry, PQgetvalue(connPool.pgResArr[pgConnId], i, 0), whiteListEntryLen);
    
	snprintf(tmpLog, MAXLOGLINE, "Got white network %s from database.", whiteListEntry);
	logNow(&tmpLog[0], 3, privData);
	
	netToTest = malloc(sizeof(struct in_addr));
	maskToTest = malloc(sizeof(struct in_addr));
	if (netToTest == NULL  || maskToTest == NULL) {
	    logNow("Cannot allocate memory.", 0, privData);
	    PQclear(connPool.pgResArr[pgConnId]);
	    return 1;
	}
	
	retVal = parseAddressString(whiteListEntry, netToTest, maskToTest);
	if (retVal == 0) {
	    /* the main check itself */
	    if ((privData->relayIP & maskToTest->s_addr) == netToTest->s_addr) {
		snprintf(tmpLog, MAXLOGLINE, "Found host %s in whitelisted network %s, passing message.", privData->relayPresIP, whiteListEntry);
		logNow(&tmpLog[0], 0, privData);
		privData->whitelisted = 1;		
	    } else {
		snprintf(tmpLog, MAXLOGLINE, "Host %s doesn't belong to network %s.", privData->relayPresIP, whiteListEntry);
		logNow(&tmpLog[0], 2, privData);
	    }
	} else {
	    logNow("parseAddressString returned error, aborting.", 0, privData);
	    PQclear(connPool.pgResArr[pgConnId]);
	    return 1;
	}
	
	logNow("Trying to free() netToTest in hostIsWhitelisted().", 3, privData);
	free(netToTest);
	logNow("Trying to free() maskToTest in hostIsWhitelisted().", 3, privData);
	free(maskToTest);
	i++;
    }
    /* flush out the rest */
    logNow("Trying to PQClear() pgRes in hostIsWhitelisted().", 3, privData);
    PQclear(connPool.pgResArr[pgConnId]);
    /* freeing lock */
    unlockPgConn(pgConnId);
#endif
    return 0;
}


int
checkServerResponse (char *emailAddress, char *serverName, struct in_addr *serverIP, int resolved, struct mlfiPriv *privData) {
/*
    return codes:
    0 - email accepped
    1 - email not accepted
    2 - email tempfailed
    3 - socket error
    4 - DNS error
    5 - unknown answer code
    6 - memory error
*/

    const int smtpPort = 25;
    char myHostname[MAXHOSTNAME];
    char *myHostnameP;
    int sockfd, socketResCode;
    struct sockaddr_in serverAddress;
    struct hostent *server;
    
    char *smtpCommand;
    char smtpAnswer[2048], smtpString[2048];
    char *smtpAnswerPointer;
    char *smtpAnswerCode;
    char serverPresIP[INET_ADDRSTRLEN];
    
    const char *eolDelimiter = "\r\n";
    
    int connectResCode, resCode;
    int need2FreeSN = 0;
    
    struct timeval sockTimeout;
    
    char tmpLog[MAXLOGLINE];
    
    int unlockNeeded, unlockResult;
    
    char
    *readSMTPCode (char *answerString) {
	
	char *smtpBuf;
	char *smtpAnswerCode;
	const char *smtpCodeDelimiter = " -";
	
	logNow("Entered readSMTPCode().", 3, privData);
	snprintf(tmpLog, MAXLOGLINE, "answerString is %s.", answerString);
	logNow(&tmpLog[0], 3, privData);
				
	smtpAnswerCode = malloc(strlen(answerString) + 1);
	if (smtpAnswerCode == NULL) {
	    logNow("Cannot allocate memory.", 0, privData);
	    return(NULL);
        }
	bzero(smtpAnswerCode, strlen(answerString) + 1);
        /* checking SMTP answer code */
	/* we just reserved enough memory, so "wrong buffer length" it's not a mistake */
        strncpy(smtpAnswerCode, answerString, strlen(answerString));
	smtpBuf = smtpAnswerCode;
        strsep(&smtpBuf, smtpCodeDelimiter);
        return smtpAnswerCode;
    }

    void
    closeSocket (int socketId, int exitNicely) {
	/* nice ppl say QUIT on exit */
	logNow("Entered closeSocket().", 3, privData);
	
	if (exitNicely == 1){
	    smtpCommand = "QUIT";
	
	    bzero(smtpString, sizeof(smtpString));
	    strcat(smtpString, smtpCommand);
	    snprintf(tmpLog, MAXLOGLINE, "Saying \"%s\" to SMTP server.", smtpString);
	    logNow(&tmpLog[0], 3, privData);
	    strcat(smtpString, eolDelimiter);
	
	    socketResCode = write(socketId, smtpString, strlen(smtpString));
	    if (socketResCode < 0){
		logNow("ERROR writing to socket.", 0, privData);
	    }
	    if (smtpAnswerCode != NULL) {
		logNow("Trying to free smtpAnswerCode in closeSocket().", 3, privData);
		free(smtpAnswerCode);
	    }
	}
	/* now closing the socket */
	close(socketId);
    }
    
    void
    doTheUnPitting () {
	/* removing relay IP from the pit */
	/* flag mutex */
	unlockNeeded = tryPitLock(privData);

	snprintf(tmpLog, MAXLOGLINE, "Getting IP %s off the pit.", serverPresIP);
	logNow(&tmpLog[0], 3, privData);
	unPit(serverPresIP, privData);

	/* unflag mutex */
	if (unlockNeeded == CB_LOCKED) {
    	    logNow("Trying to clear the pit lock in checkServerResponse(), looks like we need it.", 3, privData);
    	    unlockResult = tryPitUnlock(privData);
	} else {
    	    if (unlockNeeded == CB_LOCKERR) {
        	logNow("Skipping pit lock cleansing in checkServerResponse(), due to previous lock set error.", 3, privData);
    	    } else {
        	logNow("Skipping pit lock cleansing in checkServerResponse(), we're in the nested lock call.", 3, privData);
        	logNow(&tmpLog[0], 3, privData);
    	    };
	};
    };
    
    logNow("Entered checkServerResponse().", 3, privData);
    snprintf(tmpLog, MAXLOGLINE, "Got emailAddress, %s.", emailAddress);
    logNow(&tmpLog[0], 3, privData);
    
    strncpy(&myHostname[0], &milterEmail[0], MAXHOSTNAME);
    myHostnameP = &myHostname[0];
    strsep(&myHostnameP, "@");

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
	logNow("Error opening socket.", 0, privData);
        return(3);
    };
    
    /* setting the sending-receiving timeout to 30 */
    sockTimeout.tv_sec = 90;
    sockTimeout.tv_usec = 0;
    
    if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &sockTimeout, sizeof(struct timeval)) != 0){
	logNow("Failed to set socket sending timeout.", 0, privData);
    }
    
    if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &sockTimeout, sizeof(struct timeval)) != 0){
	logNow("Failed to set socket receiving timeout.", 0, privData);
    }
    
    if (resolved == 0) {
	snprintf(tmpLog, MAXLOGLINE, "Looking up %s.", serverName);
	logNow(&tmpLog[0], 3, privData);
	server = gethostbyname(serverName);
	/* TODO: error handling */
	/* this code need more reviewing, the main branch seems to be never triggering */
	if (server == NULL) {
    	    snprintf(tmpLog, MAXLOGLINE, "ERROR, no such host - %s.", serverName);
	    logNow(&tmpLog[0], 0, privData);
    	    return(4);
	} else {
	    snprintf(tmpLog, MAXLOGLINE, "Successfully resolved %s.", serverName);
	    logNow(&tmpLog[0], 3, privData);
	}
	bzero(&serverAddress, sizeof(serverAddress));
	bcopy(server->h_addr, &serverAddress.sin_addr.s_addr, server->h_length);
	bzero(serverPresIP, sizeof(serverPresIP));
        inet_ntop(AF_INET, server->h_addr, serverPresIP, INET_ADDRSTRLEN);
        snprintf(tmpLog, MAXLOGLINE, "Address of %s is %s.", serverName, serverPresIP);
        logNow(&tmpLog[0], 3, privData);
	/* TODO: network addresses cycling */
    } else {
	/* we already got resolved address in desired order */
	serverAddress.sin_addr.s_addr = serverIP->s_addr;
	server = gethostbyaddr(&serverIP->s_addr, sizeof(struct in_addr), AF_INET);
	if (server == NULL) {
	    serverName = malloc(INET_ADDRSTRLEN);
            if (serverName == NULL) {
                logNow("Cannot allocate memory.", 0, privData);
                return(6);
            } else {
		need2FreeSN = 1;
	    }
            inet_ntop(AF_INET, &serverAddress.sin_addr.s_addr, serverName, INET_ADDRSTRLEN);
	    snprintf(tmpLog, MAXLOGLINE, "Failed to resolve IP of %s.", serverName);
	    logNow(&tmpLog[0], 0, privData);
        } else {
            serverName = server->h_name;
        }
    }
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(smtpPort);
    snprintf(tmpLog, MAXLOGLINE, "Got server name, %s.", serverName);
    logNow(&tmpLog[0], 3, privData);
    inet_ntop(AF_INET, &serverAddress.sin_addr.s_addr, serverPresIP, sizeof(serverPresIP));
    snprintf(tmpLog, MAXLOGLINE, "Got server IP, %s.", serverPresIP);
    logNow(&tmpLog[0], 3, privData);
    snprintf(tmpLog, MAXLOGLINE, "Checking %s on server %s.", emailAddress, serverName);
    logNow(&tmpLog[0], 3, privData);
    
    /* adding entry to the pit */
    /* flag mutex */
    unlockNeeded = tryPitLock(privData);

    snprintf(tmpLog, MAXLOGLINE, "Putting IP %s in the pit.", serverPresIP);
    logNow(&tmpLog[0], 3, privData);
    extendPit(privData, serverPresIP);

    /* unflag mutex */
    if (unlockNeeded == CB_LOCKED) {
        logNow("Trying to clear the pit lock in checkServerResponse(), looks like we need it.", 3, privData);
        unlockResult = tryPitUnlock(privData);
    } else {
        if (unlockNeeded == CB_LOCKERR) {
            logNow("Skipping pit lock cleansing in checkServerResponse(), due to previous lock set error.", 3, privData);
        } else {
            logNow("Skipping pit lock cleansing in checkServerResponse(), we're in the nested lock call.", 3, privData);
            logNow(&tmpLog[0], 3, privData);
        };
    };
    
    snprintf(tmpLog, MAXLOGLINE, "Trying to connect to %x, port 25.", serverAddress.sin_addr.s_addr);
    logNow(&tmpLog[0], 3, privData);
    connectResCode = connect(sockfd, (const struct sockaddr *)&serverAddress, sizeof(serverAddress));
    if (connectResCode < 0) {
        snprintf(tmpLog, MAXLOGLINE, "Cannot connect to port %d on server %s, got \"%s\".", smtpPort, serverName, strerror(errno));
	logNow(&tmpLog[0], 0, privData);
	closeSocket(sockfd, 0);
	doTheUnPitting();
	return(3);
    } else {
	snprintf(tmpLog, MAXLOGLINE, "Successfully connected to port %d on server %s.", smtpPort, serverName);
	logNow(&tmpLog[0], 2, privData);
    }
    
    /* reading greeting */
    bzero(smtpAnswer, sizeof(smtpAnswer));
    socketResCode = read(sockfd,smtpAnswer,sizeof(smtpAnswer) - 1);
    if (socketResCode < 0) {
         snprintf(tmpLog, MAXLOGLINE, "ERROR reading from socket while in session with %s, got \"%s\".", serverName, strerror(errno));
	 logNow(&tmpLog[0], 0, privData);
	 closeSocket(sockfd, 0);
	 doTheUnPitting();
	 return(3);
    } else {
	snprintf(tmpLog, MAXLOGLINE, "Successfully read greeting on server %s, greeting is %s.", serverName, smtpAnswer);
	logNow(&tmpLog[0], 3, privData);
    }
    smtpAnswerPointer = &smtpAnswer[0];
    strsep(&smtpAnswerPointer, eolDelimiter);
    snprintf(tmpLog, MAXLOGLINE, "Calling readSMTPCode with string %s.", smtpAnswer);
    logNow(&tmpLog[0], 3, privData);
    smtpAnswerCode = readSMTPCode(&smtpAnswer[0]);
    snprintf(tmpLog, MAXLOGLINE, "The answer was: \"%s\".", smtpAnswer);
    logNow(&tmpLog[0], 3, privData);
    snprintf(tmpLog, MAXLOGLINE, "The SMTP answer code is: \"%s\".", smtpAnswerCode);
    logNow(&tmpLog[0], 2, privData);
    
    /* if the code isn't 220 - tempfail message */
    if (strcmp(smtpAnswerCode, "220") != 0) {
	snprintf(tmpLog, MAXLOGLINE, "Returning tempfailing relay, %s received in greeting.", smtpAnswerCode);
	logNow(&tmpLog[0], 2, privData);
	closeSocket(sockfd, 1);
	doTheUnPitting();
	return(2);
    }
    if (smtpAnswerCode != NULL) {
	logNow("Trying to free smtpAnswerCode in checkServerResponse().", 3, privData);
	free(smtpAnswerCode);
    }
    
    /* nice ppl say HELO */
    smtpCommand = "HELO ";
    bzero(smtpString, sizeof(smtpString));
    
    strcat(smtpString, smtpCommand);
    strcat(smtpString, myHostnameP);
    snprintf(tmpLog, MAXLOGLINE, "Saying \"%s\" to SMTP server.", smtpString);
    logNow(&tmpLog[0], 3, privData);
    strcat(smtpString, eolDelimiter);
    
    socketResCode = write(sockfd,smtpString,strlen(smtpString));    
    if (socketResCode < 0){
        snprintf(tmpLog, MAXLOGLINE, "ERROR writing to socket while in session with %s, got \"%s\".", serverName, strerror(errno));
	logNow(&tmpLog[0], 0, privData);
	closeSocket(sockfd, 0);
	doTheUnPitting();
	return(3);
    }
    
    /* reading answer */
    bzero(smtpAnswer,sizeof(smtpAnswer));
    socketResCode = read(sockfd,smtpAnswer,sizeof(smtpAnswer) - 1);
    if (socketResCode < 0) {
         snprintf(tmpLog, MAXLOGLINE, "ERROR reading from socket while in session with %s, got \"%s\".", serverName, strerror(errno));
	 logNow(&tmpLog[0], 0, privData);
	 closeSocket(sockfd, 0);
	 doTheUnPitting();
	 return(3);
    };
    smtpAnswerPointer = &smtpAnswer[0];
    strsep(&smtpAnswerPointer, eolDelimiter);
    smtpAnswerCode = readSMTPCode(&smtpAnswer[0]);
    snprintf(tmpLog, MAXLOGLINE, "The answer was: \"%s\".", smtpAnswer);
    logNow(&tmpLog[0], 3, privData);
    snprintf(tmpLog, MAXLOGLINE, "The SMTP answer code is: \"%s\".", smtpAnswerCode);
    logNow(&tmpLog[0], 2, privData);
    
    /* if the code isn't 250 - tempfail message */
    if (strcmp(smtpAnswerCode, "250") != 0) {
	snprintf(tmpLog, MAXLOGLINE, "Returning tempfailing relay, %s received after HELO.", smtpAnswerCode);
	logNow(&tmpLog[0], 2, privData);
	closeSocket(sockfd, 1);
	doTheUnPitting();
	return(2);
    }
    if (smtpAnswerCode != NULL) {
	logNow("Trying to free smtpAnswerCode in checkServerResponse().", 3, privData);
	free(smtpAnswerCode);
    }
    
    /* beginning main procedure */
    smtpCommand = "MAIL FROM: ";
    bzero(smtpString, sizeof(smtpString));
    strcat(smtpString, smtpCommand);
    strcat(smtpString, "<");
    strcat(smtpString, milterEmail);
    strcat(smtpString, ">");
    snprintf(tmpLog, MAXLOGLINE, "Saying \"%s\" to SMTP server.", smtpString);
    logNow(&tmpLog[0], 3, privData);
    strcat(smtpString, eolDelimiter);
    
    socketResCode = write(sockfd,smtpString,strlen(smtpString));    
    if (socketResCode < 0){
        snprintf(tmpLog, MAXLOGLINE, "ERROR writing to socket while in session with %s, got \"%s\".", serverName, strerror(errno));
	logNow(&tmpLog[0], 0, privData);
	closeSocket(sockfd, 0);
	doTheUnPitting();
	return(3);
    };
    
    /* reading answer */
    bzero(smtpAnswer, sizeof(smtpAnswer));
    socketResCode = read(sockfd,smtpAnswer,sizeof(smtpAnswer) - 1);
    if (socketResCode < 0) {
        snprintf(tmpLog, MAXLOGLINE, "ERROR reading from socket while in session with %s, got \"%s\".", serverName, strerror(errno));
	logNow(&tmpLog[0], 0, privData);
	closeSocket(sockfd, 0);
	doTheUnPitting();
	return(3);
    };
    smtpAnswerPointer = &smtpAnswer[0];
    strsep(&smtpAnswerPointer, eolDelimiter);
    smtpAnswerCode = readSMTPCode(&smtpAnswer[0]);
    snprintf(tmpLog, MAXLOGLINE, "The answer was: \"%s\".", smtpAnswer);
    logNow(&tmpLog[0], 3, privData);
    snprintf(tmpLog, MAXLOGLINE, "The SMTP answer code is: \"%s\".", smtpAnswerCode);
    logNow(&tmpLog[0], 2, privData);
    
    /* if the code isn't 250 - tempfail message */
    if (strcmp(smtpAnswerCode, "250") != 0) {
	snprintf(tmpLog, MAXLOGLINE, "Returning tempfailing relay, %s received after MAIL FROM.", smtpAnswerCode);
	logNow(&tmpLog[0], 2, privData);
	closeSocket(sockfd, 1);
	doTheUnPitting();
	return(2);
    }
    if (smtpAnswerCode != NULL) {
	logNow("Trying to free smtpAnswerCode in checkServerResponse().", 3, privData);
	free(smtpAnswerCode);
    }
    
    /* Telling the recipient address */
    smtpCommand = "RCPT TO: ";
    bzero(smtpString, sizeof(smtpString));
    strcat(smtpString, smtpCommand);
    strcat(smtpString, "<");
    strcat(smtpString, emailAddress);
    strcat(smtpString, ">");
    snprintf(tmpLog, MAXLOGLINE, "Saying \"%s\" to SMTP server.", smtpString);
    logNow(&tmpLog[0], 3, privData);
    strcat(smtpString, eolDelimiter);
    
    socketResCode = write(sockfd,smtpString,strlen(smtpString));    
    if (socketResCode < 0){
        snprintf(tmpLog, MAXLOGLINE, "ERROR writing to socket while in session with %s, got \"%s\".", serverName, strerror(errno));
	logNow(&tmpLog[0], 0, privData);
	closeSocket(sockfd, 0);
	doTheUnPitting();
	return(3);
    };
    
    /* reading answer */
    bzero(smtpAnswer,sizeof(smtpAnswer));
    socketResCode = read(sockfd,smtpAnswer,sizeof(smtpAnswer) - 1);
    if (socketResCode < 0) {
        snprintf(tmpLog, MAXLOGLINE, "ERROR reading from socket while in session with %s, got \"%s\".", serverName, strerror(errno));
	logNow(&tmpLog[0], 0, privData);
	closeSocket(sockfd, 0);
	doTheUnPitting();
	return(3);
    };
    smtpAnswerPointer = &smtpAnswer[0];
    strsep(&smtpAnswerPointer, eolDelimiter);
    smtpAnswerCode = readSMTPCode(&smtpAnswer[0]);
    snprintf(tmpLog, MAXLOGLINE, "The answer was: \"%s\".", smtpAnswer);
    logNow(&tmpLog[0], 3, privData);
    snprintf(tmpLog, MAXLOGLINE, "The SMTP answer code is: \"%s\".", smtpAnswerCode);
    logNow(&tmpLog[0], 2, privData);
    
    resCode = 0;
    resCode = strtol(smtpAnswerCode, NULL, 10);
    if (serverName != NULL && need2FreeSN == 1) {
	logNow("Trying to free serverName in checkServerResponse().", 3, privData);
	free(serverName);
    }
    
    /* closing socket */
    closeSocket(sockfd, 1);
    
    doTheUnPitting();
    
    if (resCode == 0) {
        snprintf(tmpLog, MAXLOGLINE, "Cannot parse SMTP answer code %s.", smtpAnswerCode);
        logNow(&tmpLog[0], 0, privData);
        return(5);
    } else {
        if (resCode >= 400 && resCode <= 499) {
    	    snprintf(tmpLog, MAXLOGLINE, "Returning tempfailing relay, received %d after RCPT TO.", resCode);
	    logNow(&tmpLog[0], 2, privData);
	    return(2);
	} else {
	    if (resCode >= 500 && resCode <= 599) {
	        snprintf(tmpLog, MAXLOGLINE, "Returning rejecting relay, received %d after RCPT TO.", resCode);
	        logNow(&tmpLog[0], 2, privData);
	        return(1);
	    } else {
	        if (resCode >= 250 && resCode <= 251) {
	    	    snprintf(tmpLog, MAXLOGLINE, "Returning passing relay, received %d after RCPT TO.", resCode);
	    	    logNow(&tmpLog[0], 2, privData);
	    	    return(0);
	        } else {
	    	    snprintf(tmpLog, MAXLOGLINE, "Returning tempfailing relay, received unknow code %d after RCPT TO.", resCode);
	    	    logNow(&tmpLog[0], 2, privData);
	    	    return(5);
		}
	    }
	}
    }
}

void
mc_setreply(SMFICTX *ctx, char *rCode, char *rText) {
    char *replyCode, *replyText;
    char tmpLog[MAXLOGLINE];
    
    replyCode = malloc(strlen(rCode) + 1);
    replyText = malloc(strlen(rText) + 1);
    if (replyCode == NULL || replyText == NULL) {
        logNow("Cannot allocate memory", 0, 0);
    } else {
        bzero(replyCode, strlen(rCode) + 1);
        bzero(replyText, strlen(rText) + 1);
        strcpy(replyCode, rCode);
        if (strlen(replyURL) > 0) {
            strcpy(replyText, replyURL);
        } else {
            strcpy(replyText, rText);
        }
        if (smfi_setreply(ctx, replyCode, NULL, replyText) != MI_SUCCESS) {
            snprintf(tmpLog, MAXLOGLINE, "Failed to set code %s: %s", replyCode, replyText);
            logNow(&tmpLog[0], 0, 0);
        } else {
            snprintf(tmpLog, MAXLOGLINE, "Successfully set code %s: %s", replyCode, replyText);
            logNow(&tmpLog[0], 2, 0);
        }
        if (replyCode != NULL) {
            logNow("Trying to free replyCode in mc_setreply().", 3, 0);
            free(replyCode);
        }
        if (replyText != NULL) {
            logNow("Trying to free replyText in mc_setreply().", 3, 0);
            free(replyText);
        }
    }
}

char
*transcriptAnswerCode(int answerCode, char *transcript) {
    switch(answerCode) {
        case CB_PASS:
            sprintf(transcript, "accepts");
            break;
        case CB_REJECT:
            sprintf(transcript, "rejects");
            break;
        case CB_TEMPFAIL:
            sprintf(transcript, "tempfails");
            break;
        case CB_SKIP:
            sprintf(transcript, "was skipped (sending relay is one of the MXes)");
            break;
        case CB_GRACEFUL:
            sprintf(transcript, "was skipped (sending relay IP reverse name contains e-mail hostpart)");
            break;
    }
    return transcript;
}
