/* 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 gracefulDNS;

int
dealWithMXes (char *emailaddress, char *hostPart, struct mlfiPriv *privData) {

/*
    Return Codes:
    0 - all of the MXes accepted emailAddress.
    1 - some of the MXes accepted emailAddress.
    2 - no MX for emailAddress found.
    3 - some software/memory error happened.
    4 - all MXes reject
    5 - some of the MXes tempfails
*/

    char emailBuf[MAXEMAIL];
    int i, dup, msgcount, returnCode;
    
    ns_msg handle;  /* handle for response packet */
    int rrnum;  /* resource record number */
    ns_rr rr;   /* expanded resource record */
    ns_sect section;
    
    #define MAX_MX 16
    
    /* MX array */
    char *mxList[MAX_MX];
    /* MX array length */
    int  mxNum = 0;
    
    union {
        HEADER hdr;              /* defined in resolv.h */
        u_char buf[NS_PACKETSZ]; /* defined in arpa/nameser.h */
    } response;                  /* response buffers */
    
    int responseLen;             /* buffer length */
    
    struct addrinfo hints, *res, *res0;
    int resError;
    int bufLen;
    
    char *relayVect;
    char *relayVectBeginsAt;
    char afterDog[MAXEMAIL];
    char *afterDogPointer;
    char tmpLog[MAXLOGLINE];
	
    int relayRes2DogPart, domLevel;
    
    bzero(emailBuf, sizeof(emailBuf));
    
    logNow("Entered dealWithMXes().", 3, privData);
    
    snprintf(tmpLog, MAXLOGLINE, "Got an e-mail, %s.", emailaddress);
    logNow(&tmpLog[0], 3, privData);
    snprintf(tmpLog, MAXLOGLINE, "Got a host: '%s', %d symbols.", hostPart, strlen(hostPart));
    logNow(&tmpLog[0], 3, privData);
    
    if ((responseLen = res_query(hostPart, ns_c_in, ns_t_mx, (u_char *) &response, sizeof(response))) < 0) {
	snprintf(tmpLog, MAXLOGLINE, "Error while resolving %s, got \"%s\".", hostPart, hstrerror(h_errno));
	logNow(&tmpLog[0], 0, privData);
	if (h_errno == 4) {
	    /* no MX found */
	    privData->MXesNum = 0;
	    privData->MXesThatAccept = 0;
	    privData->MXesThatReject = 0;
	    privData->MXesThatTempfail = 0;
	    privData->MXesOutOfOrder = 0;
	    snprintf(tmpLog, MAXLOGLINE, "No MXes for the domain, %s.", hostPart);
    	    logNow(&tmpLog[0], 0, privData);
	    return(2);
	}
    } else {
	logNow("Successfully queried DNS.", 3, privData);
	snprintf(tmpLog, MAXLOGLINE, "Response length is %d, or %d bytes.", responseLen, sizeof(response));
	logNow(&tmpLog[0], 3, privData);
	snprintf(tmpLog, MAXLOGLINE, "Size of response.buf was %d.", sizeof(response.buf));
	logNow(&tmpLog[0], 3, privData);
	
	if (ns_initparse(response.buf, responseLen, &handle) < 0) {
	    snprintf(tmpLog, MAXLOGLINE, "Error parsing response: %s.", strerror(errno));
	    logNow(&tmpLog[0], 0, privData);
	    return(3);
	} else {	
	    section = ns_s_an;
	    msgcount = ns_msg_count(handle, section);
	    snprintf(tmpLog, MAXLOGLINE, "Got %d records in answer section.", msgcount);
	    logNow(&tmpLog[0], 3, privData);
	    for(rrnum = 0; rrnum < msgcount; rrnum++) {
		/*
	        * Expand the resource record number rrnum into rr.
		*/
		snprintf(tmpLog, MAXLOGLINE, "Parsing record no %d.", rrnum);
		logNow(&tmpLog[0], 3, privData);
		
		if (ns_parserr(&handle, section, rrnum, &rr) < 0) {
		    snprintf(tmpLog, MAXLOGLINE, "ns_parserr: %s.", strerror(errno));
		    logNow(&tmpLog[0], 0, privData);
		    return(3);
		} else {
		    /*
		    * If the record type is MX, save the name of the
		    * name server.
		    */
		    if (ns_rr_type(rr) == ns_t_mx) {
			logNow("We got an MX record.", 3, privData);
			
			mxList[mxNum] = (char *) malloc(MAXDNAME);
			if(mxList[mxNum] == NULL){
			    logNow("Cannot allocate memory.", 0, privData);
			    return(3);
			}
			/* Expand the MX name */
			if (ns_name_uncompress(
			    ns_msg_base(handle),/* Start of the packet   */
			    ns_msg_end(handle), /* End of the packet     */
		    	    ns_rr_rdata(rr) + 2,    /* Position in the packet*/
			    mxList[mxNum],     /* Result                */
			    MAXDNAME)           /* Size of nsList buffer */
			    	    < 0) {    /* Negative: error       */
			    logNow("ns_name_uncompress failed.", 0, privData);
			    return(3);
			}
			/*
		        * Check the name we've just unpacked and add it to
		        * the list of MXes if it is not a duplicate.
		        * If it is a duplicate, just ignore it.
		        */
			for(i = 0, dup = 0; (i < mxNum) && !dup; i++) {
			    snprintf(tmpLog, MAXLOGLINE, "Comparing %s against %s.", mxList[mxNum], mxList[i]);
			    logNow(&tmpLog[0], 3, privData);
			    dup = !strncasecmp(mxList[i], mxList[mxNum], strlen(mxList[i]));
			}
			if(dup) {
			    logNow("We got a duplicate.", 3, privData);
			    if (mxList[mxNum] != NULL) {
				free(mxList[mxNum]);
			    }
			} else {
			    mxNum++;
			}
		    } else {
			snprintf(tmpLog, MAXLOGLINE, "We got a RR of type %d, skipping in wait for %d.", ns_rr_type(rr), ns_t_mx);
			logNow(&tmpLog[0], 3, privData);
		    }
		}
	    }
	    snprintf(tmpLog, MAXLOGLINE, "Got %d MXes:", mxNum);
	    logNow(&tmpLog[0], 2, privData);
	    for (i = 0; i < mxNum; i++) {
		snprintf(tmpLog, MAXLOGLINE, "%d: %s", i, mxList[i]);
		logNow(&tmpLog[0], 2, privData);
	    }
	}
    }
    /* here we already got the list of MXes */
    logNow("Commencing the mxlist parsing.", 2, privData);
    
    /* determining if relay is one on the MXes */
    for(i = 0; i < mxNum && privData->skipDirect == 0; i++) {
	snprintf(tmpLog, MAXLOGLINE, "Checking MX %d of %d, %s against the relay IP, %s.", i + 1, mxNum, mxList[i], privData->relayPresIP);
	logNow(&tmpLog[0], 2, privData);
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	resError = getaddrinfo(mxList[i], NULL, &hints, &res0);
	if (resError) {
	    snprintf(tmpLog, MAXLOGLINE, "Cannot resolve host %s: %s", mxList[i], gai_strerror(resError));
	    logNow(&tmpLog[0], 2, privData);
	} else {
	    for (res = res0; res; res = res->ai_next) {
		if (((struct sockaddr_in *)res->ai_addr)->sin_addr.s_addr == privData->relayIP) {
		    snprintf(tmpLog, MAXLOGLINE, "Setting skipDirect flag for relay %s - it's IP is identical to MX IP %s", privData->relayPresIP, mxList[i]);
		    logNow(&tmpLog[0], 2, privData);
		    privData->skipDirect = 1;
		}
	    }
	    freeaddrinfo(res0);
	}
    }
    
    if (privData->skipDirect == 0 && gracefulDNS == 1) {
	/* determinig if the relay IP resolves to the name containing full hostpart */
	relayRes2DogPart = 0 ;
	domLevel = 1;
	/* pushing email and relay*/
	bufLen = strlen(privData->resolvedRelayIP) + 1;
	relayVect = malloc(bufLen);
	bzero(relayVect, bufLen);
	strncpy(relayVect, privData->resolvedRelayIP, bufLen);	
	bzero(afterDog, sizeof(afterDog));
	strncpy(afterDog, emailaddress, sizeof(afterDog));
	if (relayVect == NULL) {
	    logNow("Cannot allocate memory.", 0, privData);
	} else {
	    relayVectBeginsAt = relayVect;
	    afterDogPointer = &afterDog[0];
	    strsep(&afterDogPointer, "@");
	    snprintf(tmpLog, MAXLOGLINE, "afterDogPointer is %s.", afterDogPointer);
	    logNow(&tmpLog[0], 3, privData);
	    while (relayVect != NULL && relayRes2DogPart == 0) {
		snprintf(tmpLog, MAXLOGLINE, "relayVect is %s.", relayVect);
		logNow(&tmpLog[0], 3, privData);
    		if (relayVect != NULL && strcmp(relayVect, afterDogPointer) == 0) {
		    snprintf(tmpLog, MAXLOGLINE, "%s is equal to %s.", relayVect, afterDogPointer);
		    logNow(&tmpLog[0], 3, privData);
        	    relayRes2DogPart = 1;
    		}
		strsep(&relayVect, ".");
	    }
	    if (relayRes2DogPart == 1) {
		/* finding the level of the domain */
		for (i = 0; i < strlen(afterDogPointer); i++) {
    		    if (afterDogPointer[i] == '.') {
    			domLevel++;
    	    	    }
		}
		snprintf(tmpLog, MAXLOGLINE, "Domain level of e-mail hostpart %s is %d.", afterDogPointer, domLevel);
		logNow(&tmpLog[0], 3, privData);
		if (domLevel >= 2) {
		    snprintf(tmpLog, MAXLOGLINE, "Setting graceful DNS bit, [%s] resolves to %s, which contains hostpart %s.", privData->relayPresIP, privData->resolvedRelayIP, afterDogPointer);
		    logNow(&tmpLog[0], 3, privData);
		    privData->gracefulDNS = 1;
		}
	    }
	    if (relayVectBeginsAt != NULL) {
		logNow("Trying to free relayVectBeginsAt in dealWithMXes().", 3, privData);
		free(relayVectBeginsAt);
	    }
	}
    } else {
	if (privData->skipDirect == 1) {
	    logNow("Skipped graceful DNS check, skipDirect flag already set.", 3, privData);
	}
    }
    
    privData->MXesNum = mxNum;
    privData->MXesThatAccept = 0;
    privData->MXesThatReject = 0;
    privData->MXesThatTempfail = 0;
    privData->MXesOutOfOrder = 0;
    
    for(i = 0; i < mxNum; i++) {
	snprintf(tmpLog, MAXLOGLINE, "Parsing relay %d of %d, %s.", i + 1, mxNum, mxList[i]);
	logNow(&tmpLog[0], 2, privData);
	returnCode = checkServerResponse(emailaddress, mxList[i], NULL, 0, privData);
	snprintf(tmpLog, MAXLOGLINE, "checkServerResponse() returned %d.", returnCode);
	logNow(&tmpLog[0], 3, privData);
	switch (returnCode) {
	    case 0:
		/* 0 - email accepped */
		logNow("Got one of the accepting MXes.", 2, privData);
		privData->MXesThatAccept++;
		break;
	    case 1:
		/* 1 - email not accepted */
		logNow("Got one of the rejecting MXes.", 2, privData);
		privData->MXesThatReject++;
		break;
	    case 2:
		/* 2 - email tempfailed */
		logNow("Got one of the tempfailing MXes.", 2, privData);
		privData->MXesThatTempfail++;
		break;
	    case 3:
		/* 3 - socket error */
		logNow("Got a socket error.", 2, privData);
		privData->MXesOutOfOrder++;
		break;
	    case 4:
		/* 4 - DNS error */
		logNow("Got a DNS error.", 2, privData);
		privData->MXesOutOfOrder++;
		break;
	    case 5:
		/* 5 - unknown answer code */
		logNow("Got an unknown SMTP answer code.", 0, privData);
		privData->MXesOutOfOrder++;
		break;
	    case 6:
	    default:
		/* 6 - memory error */
		logNow("Got a memory error.", 0, privData);
		privData->MXesOutOfOrder++;
		break;
	}
    }
    /* freeing mxList */
    logNow("Trying to free mxList in dealWithMXes().", 3, privData);
    for (i = 0; i < mxNum; i++) {
	if (mxList[i] != NULL) {
	    snprintf(tmpLog, MAXLOGLINE, "freeing MX %d in dealWithMXes().", i);
	    logNow(&tmpLog[0], 3, privData);
	    free(mxList[i]);
	}
    }
    
    if (privData->MXesThatAccept == mxNum) {
	return(0);
    } else {
	if (privData->MXesThatAccept > 0){
	    return(1);
	} else {
	    if (privData->MXesThatTempfail > 0) {
		return(5);
	    } else {
		if (privData->MXesOutOfOrder > 0) {
		    return(3);
		} else {
		    return(4);
		}
	    }
	}
    }
}
