/*
This file is a modify part of public message:

To: sslug-cprog@sslug.dk 
Subject: Re: [CPROG] DNS query p. MX record. 
From: "Erwin S. Andreasen" <erw@dde.dk.sslug.dk> 
Date: Mon, 25 Oct 1999 11:51:52 +0200 (CEST) 
Delivered-To: mailing list sslug-cprog@sslug.dk 
In-Reply-To: <381364DB.48919E5@risoe.dk> 
Mailing-List: contact sslug-cprog-help@sslug.dk; run by ezmlm 

http://www.sslug.dk/emailarkiv/cprog/1999_10/msg00056.html
*/


#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <ctype.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <arpa/inet.h>

#include "mxget.h"
#include "utils.h"
#include "spf1.h"

struct rrecord {
    int16_t r_type;
    int16_t r_class;
    u_int32_t r_ttl;
    int16_t r_length;
};

int check_a(char *hostname,char *client_address) {
struct hostent *hh;
int i;

hh=gethostbyname(hostname);
if(!hh) {
    if(h_errno==TRY_AGAIN || h_errno==NO_RECOVERY)
	return (-1); //dns error
    }
else {
for(i=0;i<256;i++) {
    if(hh->h_addr_list[i]) {
	char odp[256];
	
	snprintf(odp,sizeof(odp),"%d.%d.%d.%d",(unsigned char)hh->h_addr_list[i][0],
	    (unsigned char)hh->h_addr_list[i][1],
	    (unsigned char)hh->h_addr_list[i][2],
	    (unsigned char)hh->h_addr_list[i][3]);
	if (strncmp(odp,client_address,strlen(odp))==0) {
	    return 0;
	    }
	}
    else {
	i=256;
	}
    }
    }
return 1;
}

/* get_spf1     */
/* -2 txt but no spf1 */
/* -1 dns error */
/* 0 ok */
/* 1 multiple */

int get_spf1(char *domain,char *answer, int sanswer) {
    static char out[1000];
    unsigned char query[1000];
    HEADER *h;
    unsigned char *p;
    int qcount, acount;
    char buf[1000];
    struct rrecord *r;
    char result[1024];
    int spf1count=0;
    int kr;

    /* ask libc to do the query:
     * look up 'domain'
     * class C_IN (internet)
     * type  T_TXT
     * record results in query, max len 1000
     */
     
    
    kr=res_search(domain, C_IN, T_TXT, query, 1000);
    if(kr<0)
	return (-1); //dns error
//	return 1;

    h = (HEADER *) query;

    /* skip over queries in packet */
    p = query + sizeof(HEADER);
    qcount = ntohs(h->qdcount);	/* # of queries present */
    acount = ntohs(h->ancount);	/* # of answers present */

    /* get past queries */
    while (qcount--) {
	p += dn_expand(query, query + 1000, p, buf, 1000);
	p += 4;			/* qclass, qtype */
    }

    /* now we are in the answers section. Just get the best exchanger,
     * we don't want to spend too much time messing around
     */
    out[0] = 0;
    while (acount--) {
	/* name that this answer refers to */
	p += dn_expand(query, query + 1000, p, buf, 1000);

	r = (struct rrecord *) p;
	if (htons(r->r_type) == T_TXT) {		/* TXT record */
	    unsigned char *p1 = p + 11;
	    snprintf(result,sizeof(result),"%.*s",ntohs(r->r_length)-1,p1);

	    if(strncmp(result,"v=spf1 ",7)==0) {
		spf1count++;
		}
	    }
	p = p + 10 + ntohs(r->r_length);	/* skip RR header and data */
    }

if(spf1count==0) {
    return -2; //txt found, no spf1
    }
if(spf1count==1) {
    snprintf(answer,sanswer,"%s",result);
    return 0;
    }
return 1; // multiple spf1 records
}


/* parse_spf1 */
/* return -1 - dns error */
/* return  0 - pass     */
/* return  1 - false    */
/* return  2 - unknown */

int parse_spf1(char *r,char *client_ip,char *domain, int level) {
char *p,*k;
char w[1024];
char prefix[]="+";
int debug=0;

if(level==10)return 2; // depth limit

snprintf(w,sizeof(w),"%s ",r);
if(debug)printf("spf=`%s` client_ip=`%s` domain=`%s`\n",w,client_ip,domain);
p=w;
while (index(p,' ')) {
    k=(char *)index(p,' ');
    k++[0]=0;
if(debug)printf("> %s\n",p);

// modifiers
    if(index(p,'=')) {
	if (strncmp(p,"redirect=",9)==0) {
	    char dmn[1024];
	    char re[1024];
	    int k,w;

	    snprintf(dmn,sizeof(dmn),"%s",p+9);
	
	    if(strlen(dmn)==0)
		return 2;

	    k=get_spf1(dmn,re,sizeof(re));
	    if(k==0) {
		w=parse_spf1(re+7,client_ip,dmn,level+1);
		return w;
		}
			// -1 (450) dns error
			// 2 multiple or not found "unknown"
	    return k; 	// 1 false
	    }
	else
	if (strncmp(p,"exp=",4)==0) {
    
	    }
	}

// declaration
// prefix
    if (p[0]=='+' || p[0]=='-' || p[0]=='?') {
	prefix[0]=p[0];	
	p++;
	}

// mechanism
    if (strcmp(p,"all")==0) {
	if(prefix[0]=='+') {
	    if(debug)printf(">> match +all\n");
	    return 0;
	    }
	else
	if(prefix[0]=='-') {
	    if(debug)printf(">> match -all\n");
	    return 1;
	    }
	else {
	    if(debug)printf(">> match ?all\n");
	    return 2;
	    }
	}
    else
    if (strncmp(p,"include:",8)==0) {
	char dmn[1024];
	char re[1024];
	int k,w;

	snprintf(dmn,sizeof(dmn),"%s",p+8);
	
	if(strlen(dmn)==0)
	    return 2;

	k=get_spf1(dmn,re,sizeof(re));
	if(k==0) {
	    w=parse_spf1(re+7,client_ip,dmn,level+1);
	    if(w==-1) return -1;
	    if(w==2) return 2;
	    if(w==0) {
		if(prefix[0]=='+') return 0;
		else if(prefix[0]=='-') return 1;
		else return 2;
		}
	    }
	//if(k==-1) return -1; //(450) dns error
	if(k==2) return 2; //multiple or not found "unknown"
	}
    else
    if (strcmp(p,"a")==0 || strncmp(p,"a:",2)==0 ||
	    strncmp(p,"a/",2)==0) {
	char dmn[1024];
	char net[4]="32";
	int mr;
	char *n;
    
	if (strncmp(p,"a/",2)==0) {
	    snprintf(dmn,sizeof(dmn),"%s",domain);
	    snprintf(net,sizeof(net),"%s",p+2);
	    }
	else {
	    snprintf(dmn,sizeof(dmn),"%s",(index(p,':'))?p+2:domain);
	    n=(char *)index(dmn,'/');
	    if(n) {
		n++[0]=0;
		snprintf(net,sizeof(net),"%s",n);
		}
	    }
	
	if(debug)printf(">> Test a: %s %s /%s\n",dmn,client_ip,net);
	mr=check_a(dmn,client_ip);
	
	if(mr==0) {
	    if(debug)printf(">> match %sa %s %s\n",prefix,dmn,client_ip);
	    if(prefix[0]=='+')return 0; //pass
	    else if(prefix[0]=='-')return 1; //false
	    else return 2;
	    }
	if(mr==-1) return -1; //dns error
	}
    else
    if (strcmp(p,"mx")==0 || strncmp(p,"mx:",3)==0 ||
		strncmp(p,"mx/",3)==0) {
	char dmn[1024];
	char net[4]="32";
	int mr;
	char *n;

	if (strncmp(p,"mx/",3)==0) {
	    snprintf(dmn,sizeof(dmn),"%s",domain);
	    snprintf(net,sizeof(net),"%s",p+3);
	    }
	else {
	    snprintf(dmn,sizeof(dmn),"%s",(index(p,':'))?p+3:domain);
	    n=(char *)index(dmn,'/');
	    if(n) {
		n++[0]=0;
		snprintf(net,sizeof(net),"%s",n);
		}
	    }
	if(debug)printf(">> Test mx: %s %s\n",dmn,client_ip);
	mr=find_mailhost(dmn, client_ip, net);
	
	if(mr==0) {
	    if(debug)printf(">> match %smx %s %s\n",prefix,dmn,client_ip);
	    if(prefix[0]=='+')return 0; //pass
	    else if(prefix[0]=='-')return 1; //false
	    else return 2;
	    }
	if(mr==-1) return -1; //dns error
	}
    else
    if (strncmp(p,"ptr",3)==0) {
    
	}
    else
    if (strncmp(p,"ip4:",4)==0) {
	char dmn[1024];
	char net[4]="32";
	char *n;
	int mr;

	snprintf(dmn,sizeof(dmn),"%s",(index(p,':'))?p+4:domain);
	n=(char *)index(dmn,'/');
	if(n) {
	    n++[0]=0;
	    snprintf(net,sizeof(net),"%s",n);
	    }
	if(debug)printf(">> Test ip4: %s %s\n",dmn,client_ip);
	mr=check_ip4(dmn,client_ip,net);
	
	if(mr==0) {
	    if(debug)printf(">> match %sip4 %s %s\n",prefix,dmn,client_ip);
	    if(prefix[0]=='+')return 0; //pass
	    else if(prefix[0]=='-')return 1; //false
	    else return 2;
	    }
	if(mr==2) return 2; //unknown
	}
    else
    if (strncmp(p,"exists:",7)==0) {
    
	}
    
    p=k;
    }

return 0;
}

/* find txt spf1 domain */
/* return -1 temp dns error (450) */
/* return 0 OK */
/* return 1 false */
/* return 2 unknown */

int check_spf1(char *domain, char *client_ip) {
char r[1024];
int k,w;

k=get_spf1(domain,r,sizeof(r));
if(k==0) {
    w=parse_spf1(r+7,client_ip,domain,1);
    return w;
    }
if(k==-1) return -1; //(450) dns error
return 2; // unknown
}

/*
int main() { //spf
char r[1024];
char domain[]="comm.pl";
char client_ip[]="213.25.233.144";
int k,w;

printf("result: %d\n\n",parse_spf1("a include:pobox.com include:comm.pl -all","213.25.233.0","comm.pl",1));
//printf("result: %d\n\n",parse_spf1("a +ip4:213.25.0/24 ip4:213.25.0.0/16 -all","213.25.233.144","onet.pl",1));
//printf("result: %d\n\n",parse_spf1("a -ip4:a.b.c.d -ip4:192.168.0.2/24 -all","213.25.233.144","onet.pl",1));

k=get_spf1(domain,r,sizeof(r));
if(k==0) {
    w=parse_spf1(r+7,client_ip,domain,1);
    return w;
    }
if(k==-1) return -1; //(450) dns error
return 2; // unknown
}
*/
