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

/*
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 <netinet/in.h>
#include <resolv.h>
#include <sys/socket.h>
#include <arpa/nameser.h>
#include <stdio.h>
#include <netdb.h>
#include <syslog.h>
#include <ctype.h>
#include <string.h>

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

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

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

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

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

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

/* spf1 */
*match=1;
f=check_spf1(domain, client_address);
if(f==0) return 0; //ok dunno
if(f==1) return 1; //fail
//if(f==-1) return -1; //dns error
//if(f==2) return ??; //unknown

/* simple fecyk query (ehlo ignored)*/
// _smtp-client.domain.tld first
*match=2;
snprintf(fquery,sizeof(fquery),"_smtp-client.%s",domain);
f=find_dmp(fquery);

if(f==0) { //domain has dmp records
    char rev_ca[100];

// rev_client_address.in-addr._smtp-client.domain.tld
    
    rev_addr(client_address,rev_ca,sizeof(rev_ca));
//    printf("fecyk %s, %s",client_address,rev_ca);
    snprintf(fquery,sizeof(fquery),"%s.in-addr._smtp-client.%s",rev_ca,domain);
    f=find_dmp(fquery);
    if(f==1) {
	return 0; //ok dunno
	}
    if(f==2 || f==(-2) || f==0) {
	return 2; //adm reject or too many answers or empty (acl action (550))
	}
    if(f==(-1)) {
	return (-1); //dns error 450
	}
    };
//else try RMXF other tests

/* a 'user.relay' */
*match=3;
snprintf(relay,sizeof(relay),"%s.relay.%s",user,domain);
hh=gethostbyname(relay);
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],
	    (tolerance==1)?0:(unsigned char)hh->h_addr_list[i][3]);
	if (strncmp(odp,client_address,strlen(odp)-tolerance)==0) {
	    return 0; //ok
	    }
	if (strncmp(odp,"127.0.0",7)==0) {
	    return 2; //admin reject
	    }
	}
    else {
	i=256;
	}
    }
    }

/* a 'relay' */
*match=4;
snprintf(relay,sizeof(relay),"relay.%s",domain);
hh=gethostbyname(relay);
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],
	    (tolerance==1)?0:(unsigned char)hh->h_addr_list[i][3]);
	if (strncmp(odp,client_address,strlen(odp)-tolerance)==0) {
	    return 0;
	    }
	if (strncmp(odp,"127.0.0",7)==0) {
	    return 2; //admin reject
	    }
	}
    else {
	i=256;
	}
    }
    }

/* a */
*match=5;
hh=gethostbyname(domain);
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],
	    (tolerance==1)?0:(unsigned char)hh->h_addr_list[i][3]);
	if (strncmp(odp,client_address,strlen(odp)-tolerance)==0) {
	    return 0;
	    }
	if (strncmp(odp,"127.0.0",7)==0) {
	    return 2; //admin reject
	    }
	}
    else {
	i=256;
	}
    }
    }

/* mx */
*match=6;
i=find_mailhost(domain,client_address,(tolerance)?"24":"32");

return i;
}

int main (void) {
#ifdef TESTER_ONLY
char tester[]="rozanski@sergiusz.com";
#endif
int log=1;

char buf[1001];
char *k;

char sender[257];
char client_address[257];
char recipient[257];

int match=0;

// int i=0;

memset(&sender,0,sizeof(sender));
memset(&client_address,0,sizeof(client_address));
memset(&recipient,0,sizeof(recipient));

/* 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,"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(strlen(buf)==0) {
	int result=0;
	char exitcode[30]="";
	char reason[1024]="";
#ifndef TESTER_ONLY
	int acl;
#endif
	
	k=(char *)index(sender,'@');
	if(!k) {
	    if(log) {
		openlog("postfix/mxfilter", 0, LOG_MAIL);
		syslog(0,"%s %s %s %s\n",client_address,sender,recipient,"ERROR: bad sender");
		closelog();
		}
	    printf("action=550 Sender address rejected\n\n"); //not @ in sender?, bug in postfix?
	    return 0;
	    }

#ifdef TESTER_ONLY
	if(strcmp(recipient,tester)==0) {
	    result=check_mx(sender,client_address,1,&match);
	    }
#else
	acl=mxfilter_acl(recipient,exitcode,sizeof(exitcode),reason,sizeof(reason));
	if(acl<0) { //acl file error
	    if(log) {
		openlog("postfix/mxfilter", 0, LOG_MAIL);
		syslog(0,"%s %s %s %s\n",client_address,sender,recipient,"ERROR: access list");
		closelog();
		}
	    printf("action=450 Email system error: mxfilter.acl problem. http://rmxf.comm.pl/\n\n");
	    return 0;
	    }
	if(acl)
	    result=check_mx(sender,client_address,1,&match);
#endif

	if(result==0) {
	    printf("action=prepend X-mxfilter: pass %s\ndunno\n\n",matchs[match]); //ok
	    }
	if(result==1) {
	    char lres[1024];
	    if(strcmp(exitcode,"HMARK")==0) {
		printf("action=prepend X-mxfilter: rmx-false\ndunno\n\n");
		}
	    else {
		snprintf(lres,sizeof(lres),"Host %s not MX or A for domain %s. Check dns or use true relay host. http://rmxf.comm.pl/",
	    	    client_address,
	    	    index(sender,'@')+1);
		printf("action=%s %s\n\n",
		    (strlen(exitcode)==0)?"550":exitcode,
		    (strlen(reason)==0)?lres:reason);
		}
	    }
	if(result==2) {
	    printf("action=550 Address %s rejected by domain administrator. http://rmxf.comm.pl/\n\n",
	        sender);
	    }
	if(result==(-1)) {
	    printf("action=450 DNS Error try again later. http://rmxf.comm.pl/\n\n");
	    }

	if(log) {
	    openlog("postfix/mxfilter", 0, LOG_MAIL);
	    syslog(0,"%s %s %s %s\n",
		client_address,
		sender,
		recipient,
		(result==0)?"OK":(result==1)?"REJECT":(result==2)?"ADMREJECT":"DNSERROR");
	    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;
}
