/*
*
*              Reverse MX Filter for Postfix 2
*                    reject_bad_rmx
*           (c) 2004 Elita rozanski@sergiusz.com
*
*/

/*
MXfilter 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 2, or (at your option) any later
version.
*/

#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <resolv.h>
#include <arpa/nameser.h>
#include <stdio.h>
#include <netdb.h>
#include <syslog.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "utils.h"
#include "mxget.h"
#include "fecyk.h"
#include "acl.h"
#include "spfmacro.h"
#include "spf1.h"

char matchs[7][11]={"default","spf1","dmp","user.relay","relay","a","mx"};

int test_spf=1;
int test_dmp=0;
int test_a=0;
int test_mx=0;
int default_result=2; //unknown
int dunno_on_error=0;
int dunno_on_fail=0;

int load_config (char *plik) {
    FILE *kk;
    char linia[1000],*k,*sp;
    
    kk=fopen(plik,"r");
    if(!kk)return 0;
    while(fgets(linia,sizeof(linia)-1,kk)) {
	k=(char *)index(linia,10);
	if(k)k[0]=0;
	k=(char *)index(linia,13);
	if(k)k[0]=0;
    
	if(strlen(linia)>0) {
	    if(linia[0]!='#') {
		k=(char *)index(linia,9);
		sp=(char *)index(linia,32);
		if(k && sp)
		    if(sp<k)k=sp;
		if(!k && sp)k=sp;
		if(k) {
		    k++[0]=0;
		    if(strcmp(linia,"dunno_on_error")==0) {
			dunno_on_error=atoi(k);
			}
		    else
		    if(strcmp(linia,"dunno_on_fail")==0) {
			dunno_on_fail=atoi(k);
			}
		    else
		    if(strcmp(linia,"test_spf")==0) {
			test_spf=atoi(k);
			}
		    else
		    if(strcmp(linia,"test_dmp")==0) {
			test_dmp=atoi(k);
			}
		    else
		    if(strcmp(linia,"test_a")==0) {
			test_a=atoi(k);
			}
		    else
		    if(strcmp(linia,"test_mx")==0) {
			test_mx=atoi(k);
			}
		    else
		    if(strcmp(linia,"default_result")==0) {
			default_result=atoi(k);
			}
		    }
		}
	    }
	}
    fclose(kk);
    return 0;
    }

/*
-1 dns error
0  pass
1  fail
2  unknown
3  softfail
*/

int check_mx(char *sender,char *client_address,int tolerance, int *match, int tmode, struct tspfm *spfm, char *spf_exp_res, int spf_sizeof_exp_res) {
struct hostent *hh;
int i,f;
char *domain;
char user[1024];
char fquery[1024];

if (!index(sender,'@')) return 2; //invalid email address
domain=index(sender,'@')+1;
if (strlen(domain)<3) return 2; //domain too short

if (domain[0]=='[' && domain[strlen(domain)-1]==']') return 2; //ip in domain (todo: helo)

snprintf(user,sizeof(user),"%s",sender);
index(user,'@')[0]=0;

if (strlen(user)==0)return 2; //username too short

*match=0;

/* spf1 */
if(test_spf) {
    if(tmode)printf("Test spf1:\n");
    *match=1;
    f=check_spf1(domain, client_address,tmode, spfm, spf_exp_res, spf_sizeof_exp_res);

    if(!tmode) {
	if(f==-1) return -1; //dns error
	if(f==0) return 0; //ok dunno
	if(f==1) return 1; //fail
    //if(f==2) return 2; //unknown (continue)
	if(f==3) return 3; //softfail
	};
    };
    
/* simple fecyk query (ehlo ignored)*/
if(test_dmp) {
    if(tmode)printf("Test dmp:\n");
    *match=2;
    snprintf(fquery,sizeof(fquery),"_smtp-client.%s",domain);
    f=find_dmp(fquery);

    if(!tmode)
	if(f==-1) return (-1); //dns error
    if(f==0) { //domain has dmp records
	char rev_ca[100];
    
	if(tmode)printf("[+] %s\n",domain);

	rev_addr(client_address,rev_ca,sizeof(rev_ca));
	snprintf(fquery,sizeof(fquery),"%s.in-addr._smtp-client.%s",rev_ca,domain);
	f=find_dmp(fquery);
	if(!tmode) {
	    if(f==1) return 0; // pass
	    if(f==2) return 2; // fail
	    if(f==(-1)) return (-1); //dns error 450
	    }
	};
    // if(i<>0) continue
    };
//else try RMXF other tests

/* a */
if(test_a) {
    if(tmode)printf("Test a:\n");
    *match=5;
    hh=gethostbyname(domain);
    if(!hh) {
	if(h_errno==TRY_AGAIN || h_errno==NO_RECOVERY)
	    if(!tmode)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],
		    (tolerance==1)?0:(unsigned char)hh->h_addr_list[i][3]);
		if(!tmode) {
		    if (strncmp(odp,client_address,strlen(odp)-tolerance)==0) {
			return 0;
			}
		//    if (strncmp(odp,"127.0.0",7)==0) {
		//	return 2; //admin reject
		//	}
		    }
		else {
		    if (strncmp(odp,"127.0.0",7)!=0)
			printf("[+] %s/%s\n",odp,(tolerance)?"24":"32");
		    else
			printf("[-] 0/0\n");
		    }
		}
	    else {
		i=256;
		}
	    }
	}
    }
    
/* mx */
if(test_mx) {
    if(tmode)printf("Test mx:\n");
    *match=6;
    i=find_mailhost(domain,client_address,(tolerance)?"24":"32", tmode,"+");
    if(i==0)return 0; //pass
    // if(i==1) //fail
    if(i==-1) return -1; //dns error
    // if(i==-2) //not found
    }

*match=0;
return default_result;
}

int main (int argc, char *argv[]) {

int log=1;
int tmode=0;
int prep=1;

char buf[1001];
char *k;

char sender[257];
char client_address[257];
char recipient[257];
char helo_name[257];
char client_name[257];
char queue_id[30];

int match=0;

struct tspfm spfm;

load_config ("/etc/postfix/mxfilter.cf");

memset(&sender,0,sizeof(sender));
memset(&client_address,0,sizeof(client_address));
memset(&recipient,0,sizeof(recipient));
memset(&client_name,0,sizeof(client_name));
memset(&helo_name,0,sizeof(helo_name));
memset(&queue_id,0,sizeof(queue_id));

if(argc==2) {
    char spf_reason[1024]="";

    tmode=1;
    snprintf(sender,sizeof(sender),"%s",argv[1]);
    snprintf(client_address,sizeof(client_address),"127.0.0.1");
    
    k=(char *)index(sender,'@');
    if(k) {
	k[0]=0;
	snprintf(spfm.l,sizeof(spfm.l),"%s",sender);
	snprintf(spfm.o,sizeof(spfm.o),"%s",k+1);
	snprintf(spfm.d,sizeof(spfm.d),"%s",k+1);
	k[0]='@';
	snprintf(spfm.s,sizeof(spfm.s),"%s",sender);
	}
    else {
	snprintf(spfm.l,sizeof(spfm.l),"postmaster");
	snprintf(spfm.o,sizeof(spfm.o),"%s",sender);
	snprintf(spfm.d,sizeof(spfm.d),"%s",sender);
	snprintf(spfm.s,sizeof(spfm.s),"%s",sender);    
	}
    snprintf(spfm.i,sizeof(spfm.i),"127.0.0.1");
    snprintf(spfm.t,sizeof(spfm.t),"%d",(int)time(NULL));
    snprintf(spfm.p,sizeof(spfm.p),"localhost");
    snprintf(spfm.h,sizeof(spfm.h),"localhost");
    
    printf("RMXF test: %s\n",sender);
    check_mx(sender,client_address,1,&match,1,&spfm,spf_reason,sizeof(spf_reason));
    printf("[spf] exp=%s\n",spf_reason);
    return 0;
    }

// int i=0;


/* input
sender=ktos@comm.pl
client_address=213.25.233.144
recipient=rozanski@sergiusz.com
*/

while((fgets(buf,1000,stdin))) {
    k=(char *)index(buf,10);
    if(k)k[0]=0;
    k=(char *)index(buf,13);
    if(k)k[0]=0;

//    printf("%d %s\n",i++,buf);

    if(strncmp(buf,"sender=",7)==0) {
	snprintf(sender,sizeof(sender),"%s",index(buf,'=')+1);
	}
    if(strncmp(buf,"queue_id=",9)==0) {
	snprintf(queue_id,sizeof(queue_id),"%s",index(buf,'=')+1);
	}
    if(strncmp(buf,"client_address=",15)==0) {
	snprintf(client_address,sizeof(client_address),"%s",index(buf,'=')+1);
	}
    if(strncmp(buf,"recipient=",10)==0) {
	snprintf(recipient,sizeof(recipient),"%s",index(buf,'=')+1);
	}
    if(strncmp(buf,"helo_name=",10)==0) {
	snprintf(helo_name,sizeof(helo_name),"%s",index(buf,'=')+1);
	}
    if(strncmp(buf,"client_name=",12)==0) {
	snprintf(client_name,sizeof(client_name),"%s",index(buf,'=')+1);
	}
    if(strlen(buf)==0) {
	int result=0;
	char exitcode[30]="";
	char reason[1024]="";
	char spf_reason[1024]="";
	int acl;
	int s_ok=0;
	int use_helo=0;	

	snprintf(spfm.i,sizeof(spfm.i),"%s",client_address);
	snprintf(spfm.t,sizeof(spfm.t),"%d",(int)time(NULL));
	snprintf(spfm.p,sizeof(spfm.p),"%s",client_name);
	snprintf(spfm.h,sizeof(spfm.h),"%s",helo_name);

	k=(char *)index(sender,'@');
	if(k) {
	    k[0]=0;
	    if(strlen(sender)==0)
		snprintf(spfm.l,sizeof(spfm.l),"postmaster");
	    else
		snprintf(spfm.l,sizeof(spfm.l),"%s",sender);

	    if(strlen(k+1)>0) {
		snprintf(spfm.o,sizeof(spfm.o),"%s",k+1);
		snprintf(spfm.d,sizeof(spfm.d),"%s",k+1);
		k[0]='@';
		snprintf(spfm.s,sizeof(spfm.s),"%s",sender);
		s_ok=1;
		}
	    else {
		if(strlen(helo_name)>0) {
		    s_ok=1;
		    use_helo=1;
		    // envelope sender has no domain use the helo
		    snprintf(sender,sizeof(sender),"%s@%s",spfm.l,helo_name);

		    snprintf(spfm.o,sizeof(spfm.o),"%s",helo_name);
		    snprintf(spfm.d,sizeof(spfm.d),"%s",helo_name);
		    snprintf(spfm.s,sizeof(spfm.s),"%s",sender);
		    }
		}
	    }
	else {
	    if(strlen(sender)==0)
		snprintf(spfm.l,sizeof(spfm.l),"postmaster");
	    else
		snprintf(spfm.l,sizeof(spfm.l),"%s",sender);

	    if(strlen(helo_name)>0) {
		s_ok=1;
		use_helo=1;
		// envelope sender has no domain use the helo
		snprintf(sender,sizeof(sender),"%s@%s",spfm.l,helo_name);

		snprintf(spfm.o,sizeof(spfm.o),"%s",helo_name);
		snprintf(spfm.d,sizeof(spfm.d),"%s",helo_name);
		snprintf(spfm.s,sizeof(spfm.s),"%s",sender);
		}
	    }

	if(strlen(queue_id)>0)
	    prep=0;

	printf("action=");
	
	if(s_ok==0) {
	    if(log) {
		openlog("postfix/mxfilter", LOG_PID, LOG_MAIL);
		syslog(0,"[%s] %s %s %s\n",client_address,sender,recipient,"ERROR: bad sender");
		closelog();
		}
	    if(prep)
		printf("prepend Received-SPF: unknown (empty sender)\n");
	    printf("dunno\n\n"); //not @ in sender?
	    return 0;
	    }

	acl=mxfilter_acl(recipient,exitcode,sizeof(exitcode),reason,sizeof(reason));
	if(acl<0) { //acl file error
	    if(log) {
		openlog("postfix/mxfilter", LOG_PID, LOG_MAIL);
		syslog(0,"[%s] %s %s %s\n",client_address,sender,recipient,"ERROR: access list");
		closelog();
		}
	    printf("450 Email system error: mxfilter.acl problem. http://rmxf.comm.pl/\n\n");
	    return 0;
	    }
	if(acl==0) {
	    if(log) {
		openlog("postfix/mxfilter", LOG_PID, LOG_MAIL);
		syslog(0,"[%s] %s %s %s\n",client_address,sender,recipient,"ACL: skip");
		closelog();
		}
	    if(prep)
		printf("prepend Received-SPF: unknown (test skip)\n");
	    printf("dunno\n\n");
	    return 0;
	    }
	result=check_mx(sender,client_address,1,&match,0, &spfm, spf_reason, sizeof(spf_reason));

	if(result == -1) { // dns error
	    if(dunno_on_error) {
		if(prep)
		    printf("prepend Received-SPF: error\n"); //error
		printf("dunno\n\n");
		}
	    else
		printf("450 DNS error, try again later.\n\n");
	    }
	else
	if(result==0) { //pass
	    if(prep)
		printf("prepend Received-SPF: pass (test: %s)\n",matchs[match]); //ok
	    printf("dunno\n\n");
	    }
	else
	if(result==1) { // fail
	    if(dunno_on_fail) {
		if(prep)
		    printf("prepend Received-SPF: fail\n");
		printf("dunno\n\n");
		}
	    else {
		char lres[1024];
		if(strlen(spf_reason)>0 && match==1) {
		    snprintf(lres,sizeof(lres),"%s",spf_reason);
		    }
		else {
		    snprintf(lres,sizeof(lres),"Undelivered, check spf records. http://spf.pobox.com/");
		    }
		printf("550 %s\n\n",lres);
		}
	    }
	else
	if(result==2) { // unknown
	    char lres[1024];
	    if(strcmp(exitcode,"HMARK")==0) {
		if(prep)
		    printf("prepend Received-SPF: none\n");
		printf("dunno\n\n");
		}
	    else {
		if(strlen(spf_reason)>0 && match==1) {
		    snprintf(lres,sizeof(lres),"%s",spf_reason);
		    }
		else {
		    snprintf(lres,sizeof(lres),"Setup spf records. http://spf.pobox.com/");
		    }
		printf("%s %s\n\n",
		    (strlen(exitcode)==0)?"550":exitcode,
		    (strlen(reason)==0)?lres:reason);
		}
	    }
	else
	//if(result==3) // softfail
	    {
	    if(prep)
		printf("prepend Received-SPF: softfail\n");
	    printf("dunno\n\n");
	    }

	if(log) {
	    openlog("postfix/mxfilter", LOG_PID, LOG_MAIL);
	    syslog(0,"[%s] from:%s%s to:%s rmx:%s->%s acl:%s\n",
		client_address,
		sender,
		(use_helo)?"/helo":"",
		recipient,
		(match==6 && result!=0)?"false":matchs[match],
		(result==0)?"PASS":(result==1)?"FALSE":(result==2)?"UNKNOWN":(result==3)?"SOFTFAIL":"DNSERROR",
		exitcode);
	    closelog();
	    }

	return 0;
	}
    }

// strange, postfix false?
if(log) {
    openlog("postfix/mxfilter", 0, LOG_MAIL);
    syslog(0,"%s %s %s %s\n",client_address,sender,recipient,"ERROR: stdin empty");
    closelog();
    }
printf("action=dunno\n\n");
return 0;
}
