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

#define MAXSPF 65535
#define ELAMESPF -1
#define ENOSPF -2
#define ESPFERR -3

extern int logLevel;

int
detectLameSPF (char *hostPart, int minLen, struct mlfiPriv *privData) {

    int msgcount;
    
    ns_msg handle;  /* handle for response packet */
    int rrnum;  /* resource record number */
    ns_rr rr;   /* expanded resource record */
    ns_sect section;
    
    union {
        HEADER hdr;              /* defined in resolv.h */
        u_char buf[NS_PACKETSZ]; /* defined in arpa/nameser.h */
    } response;                  /* response buffers */
    
    int responseLen;             /* buffer length */
        
    int retVal = 0;
    
    char *SPFString;
    char *SPFtoken;
    char *SPFNetToken;
    
    int convRes;
    char tmpLog[MAXLOGLINE];
    
    logNow("Entered detectLameSPF().", 3, privData);
    
    if ((responseLen = res_query(hostPart, ns_c_in, ns_t_txt, (u_char *) &response, sizeof(response))) < 0) {
	if (h_errno == 4) {
	    /* no TXT record found */
	    snprintf(tmpLog, MAXLOGLINE, "No TXT record found for %s.", hostPart);
	    logNow(&tmpLog[0], 2, privData);
	    
	    retVal = ENOSPF;
	}
    } else {
	snprintf(tmpLog, MAXLOGLINE, "TXT record found for %s.", hostPart);
	logNow(&tmpLog[0], 3, privData);
	if (ns_initparse(response.buf, responseLen, &handle) < 0) {
	    retVal = ESPFERR;
	} else {	
	    section = ns_s_an;
	    msgcount = ns_msg_count(handle, section);
	    snprintf(tmpLog, MAXLOGLINE, "Total messages number is %d.", msgcount);
	    logNow(&tmpLog[0], 3, privData);
	    for(rrnum = 0; rrnum < msgcount; rrnum++) {
		snprintf(tmpLog, MAXLOGLINE, "Parsing message no. %d.", rrnum);
		logNow(&tmpLog[0], 3, privData);
		/*
	        * Expand the resource record number rrnum into rr.
		*/
		
		if (ns_parserr(&handle, section, rrnum, &rr) < 0) {
		    snprintf(tmpLog, MAXLOGLINE, "DNS reply parse error.");
		    logNow(&tmpLog[0], 0, privData);
		    retVal = ESPFERR;
		} else {
		    /*
		    * If the record type is TXT, save it.
		    */
		    if (ns_rr_type(rr) == ns_t_txt) {
			SPFString = malloc(MAXSPF);
			bzero(SPFString, MAXSPF);
			if (SPFString == NULL) {
			    snprintf(tmpLog, MAXLOGLINE, "Cannot allocate memory.");
			    logNow(&tmpLog[0], 0, privData);
			    retVal = ESPFERR;
			} else {
			    /* length is one byte */
			    strncpy(SPFString, (char *)ns_rr_rdata(rr) + 1, rr.rdlength - 1);
			    snprintf(tmpLog, MAXLOGLINE, "SPF data is: %s.", SPFString);
			    logNow(&tmpLog[0], 3, privData);
			    /* now parsing SPF result, searching for invalid masklengths */
			    while (SPFString != NULL) {
				SPFtoken = strsep(&SPFString, " ");
				snprintf(tmpLog, MAXLOGLINE, "SPF token is %s.", SPFtoken);
				logNow(&tmpLog[0], 3, privData);
				snprintf(tmpLog, MAXLOGLINE, "Remaining string is %s.", SPFString);
				logNow(&tmpLog[0], 3, privData);
				
				/* ip4:0.0.0.0/0
				* - tokens shorter than this string aren't net/masks
				* so we can skip 'em */
				if (strlen(SPFtoken) >= 13 ) {
				    if (SPFtoken[0] == 'i' && SPFtoken[1] == 'p' && SPFtoken[2] == '4' && SPFtoken[3] == ':') {
					snprintf(tmpLog, MAXLOGLINE, "%s is an IP token.", SPFtoken);
					logNow(&tmpLog[0], 3, privData);
					/* now we find possible masks */
					/* there can be only one slash in a valid ip4: token */
					SPFNetToken = strsep(&SPFtoken, "/");
					snprintf(tmpLog, MAXLOGLINE, "Network part is %s.", SPFtoken);
					logNow(&tmpLog[0], 3, privData);
					/* network part is the rest */
					if (SPFtoken != NULL) {
					    convRes = strtol(SPFtoken, NULL, 10);
					    if (convRes > 0) {
						if (convRes < minLen) {
						    snprintf(tmpLog, MAXLOGLINE, "Mask %s of network %s of SPF record for host %s is way shorter than permitted %d, skipping further SPF checking.", SPFtoken, SPFNetToken, hostPart, minLen);
						    logNow(&tmpLog[0], 0, privData);
						    retVal = ELAMESPF;
						}
					    } else {
					        snprintf(tmpLog, MAXLOGLINE, "No conversion of %s to long can be made.", SPFtoken);
						logNow(&tmpLog[0], 2, privData);
					    }
					}
				    } else {
					snprintf(tmpLog, MAXLOGLINE, "%s is not an IP token.", SPFtoken);
					logNow(&tmpLog[0], 3, privData);
				    }
				} else {
				    /* skipping it */
				    snprintf(tmpLog, MAXLOGLINE, "%s is too short for an IP token.", SPFtoken);
				    logNow(&tmpLog[0], 3, privData);
				}
			    }
			    snprintf(tmpLog, MAXLOGLINE, "Freeing SPFString.");
			    logNow(&tmpLog[0], 3, privData);
			    free(SPFString);
			}
		    } else {
			/* not the type we searched for */
		    }
		}
	    }
	}
    }
    return(retVal);
}
