/*
* Soft Fail, more info see README, COPYRIGHT and CHANGELOG files
*/
#include "envelope_vrf.h"
#include "softfail.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <mysql/mysql.h>
FILE *sflog = NULL;
/*
* Function to add timestamp to logs
*/
char * tlg()
{
static char buff[23];
time_t tm;
tm = time(NULL);
ctime_r(&tm,buff);
sprintf(buff,"%.24s",buff);
return(buff);
}
int sf_custom_sqlquery(MYSQL *mysql, char *query)
{
int res;
res = mysql_query(mysql,query);
if (sflog)
#ifdef DEBUGTLOG
fprintf(sflog,"\nSQL: ret=%d [ %s ]\n",res,query);
#endif
return res;
}
void MakeOrStatement(char *query,char *in_ipaddr)
{
int i;
char *p;
char ipaddr[256];
strncpy(ipaddr, in_ipaddr, sizeof(ipaddr));
for (i = 0; i < 4; i++) {
if (i)
strcat(query, " OR ");
strcat(query , "relay_ip = '" );
strcat(query ,ipaddr);
strcat(query , "'");
p = strrchr(ipaddr,'.'); // strip off the last octet
if(p)
*p = '\0';
}
}
/* returns TRUE FALSE if record exists in database */
int checkWhiteListIP( MYSQL *mysql, int *action)
{
MYSQL_RES *myres;
MYSQL_ROW myrow;
char sql[SQLCMDSIZE], sid[32];
int white, black,exists = FALSE;
/* check for whitelisted sender ip address */
sprintf(sql, "SELECT id, block_expires > NOW(), block_expires < NOW() FROM softfail WHERE record_expires > NOW() AND mail_from IS NULL AND rcpt_to IS NULL AND (");
MakeOrStatement(sql, mf_fqdnaddr);
strcat(sql, ") ORDER BY length(relay_ip)");
#ifdef DEBUGTLOG
fprintf(sflog,"\ncheck whitelist: %s\n",sql);
#endif
if(!sf_custom_sqlquery(mysql,sql)) {
myres = mysql_store_result(mysql);
while ((myrow = mysql_fetch_row(myres)) != NULL) {
exists = TRUE;
strncpy(sid,myrow[0],sizeof(sid));
black = atoi(myrow[1]);
white = atoi(myrow[2]);
}
mysql_free_result(myres); // free memory used by result
}
if (exists && white) {
fprintf(sflog,"%s qmail-sf: %s -> xxx (%s) Whitelist IP Accept (id = %s)\n", tlg(), mf_addr, mf_fqdnaddr,sid);
*action = ENVELOPE_ACCEPT_CODE;
}
else if (exists && black) {
fprintf(sflog,"%s qmail-sf: %s -> xxx (%s) Blacklist IP Permanent Reject (id = %s)\n", tlg(), mf_addr, mf_fqdnaddr,sid);
*action = ENVELOPE_REJECT_CODE;
}
return exists;
}
/* returns TRUE FALSE if record exists in database */
int checkWhiteListDomain( MYSQL *mysql, int *action, int i)
{
MYSQL_RES *myres;
MYSQL_ROW myrow;
char sql[SQLCMDSIZE], sid[32], *indomain;
int white, black,exists = FALSE;
indomain = strrchr(sf_rcpt_l[i].mail,'@');
if(indomain != NULL) {
indomain++; /* skip the '@' char */
/* check for whitelisted receiver domain address */
sprintf(sql, "SELECT id, block_expires > NOW(), block_expires < NOW() FROM softfail WHERE record_expires > NOW() AND mail_from IS NULL AND relay_ip IS NULL AND rcpt_to = '%s'",indomain);
if(!sf_custom_sqlquery(mysql,sql)) {
myres = mysql_store_result(mysql);
while ((myrow = mysql_fetch_row(myres)) != NULL) {
exists = TRUE;
strncpy(sid,myrow[0],sizeof(sid));
black = atoi(myrow[1]);
white = atoi(myrow[2]);
}
mysql_free_result(myres); // free memory used by result
}
/* check for whitelisted sender domain address - patrick*/
indomain = strrchr(mf_addr,'@');
if(indomain != NULL) {
indomain++; /* We don't wat @domain, only domain */
sprintf(sql, "SELECT id, block_expires > NOW(), block_expires < NOW() FROM softfail WHERE record_expires > NOW() AND mail_from = '%s' AND relay_ip IS NULL AND rcpt_to IS NULL",indomain);
if(!sf_custom_sqlquery(mysql,sql)) {
myres = mysql_store_result(mysql);
while ((myrow = mysql_fetch_row(myres)) != NULL) {
exists = TRUE;
strncpy(sid,myrow[0],sizeof(sid));
black = atoi(myrow[1]);
white = atoi(myrow[2]);
}
mysql_free_result(myres); // free memory used by result
}
}
/* end check for whitelisted sender domain address */
if (exists && white) {
fprintf(sflog,"%s qmail-sf: %s -> %s (%s) Whitelist Domain Accept (id = %s)\n",tlg(), mf_addr, sf_rcpt_l[i].mail, mf_fqdnaddr,sid);
*action = ENVELOPE_ACCEPT_CODE;
}
else if (exists && black) {
fprintf(sflog,"%s qmail-sf: %s -> xxx (%s) Blacklist Domain Permanent Reject (id = %s)\n",tlg(), mf_addr, mf_fqdnaddr,sid);
*action = ENVELOPE_REJECT_CODE;
}
}
return exists;
}
void buildLookupSql(char *sql, int recipIndex)
{
char hostip[32];
char *p;
int sfseentime;
if (!getenv("SFSEENTIME"))
sfseentime = RFCSEENTIME;
else
sfseentime = atoi(getenv("SFSEENTIME"));
if (FALSE) {
/* this first sprintf does an exact match on ipaddr */
sprintf(sql,"SELECT id, NOW() > block_expires, last_update > (NOW() - %d), earlyseen_cnt FROM softfail WHERE record_expires > NOW() AND mail_from = '%s' AND rcpt_to = '%s' AND relay_ip = '%s' order by block_expires desc",sfseentime,mf_addr,sf_rcpt_l[recipIndex].mail,mf_fqdnaddr);
}
else {
/* this second version matches anything in the same /24 subnet */
sprintf(sql,"SELECT id, NOW() > block_expires, last_update > (NOW() - %d), earlyseen_cnt FROM softfail WHERE record_expires > NOW() AND mail_from = '%s' AND rcpt_to = '%s' AND relay_ip like '",sfseentime,mf_addr,sf_rcpt_l[recipIndex].mail);
strcpy(hostip,mf_fqdnaddr);
/* now remove the last octet and add a '%' */
p = strrchr(hostip,'.'); // strip off the last octet
if(p)
*p = '\0';
strcat(sql,hostip);
strcat(sql, "%' order by block_expires desc");
}
}
int checkGreylist( MYSQL *mysql, int *action, int i)
{
MYSQL_RES *myres;
MYSQL_ROW myrow;
char sql[SQLCMDSIZE], sid[32];
int notexpired, exists = FALSE;
int seenbeforerfc = FALSE;
int earlyseen_cnt, sfmaxseentimes, sfgltime;
if (!getenv("SFMAXSEENTIMES"))
sfmaxseentimes = SEENCONSECMAXTIME;
else
sfmaxseentimes = atoi(getenv("SFMAXSEENTIMES"));
if (!getenv("SFGLTIME"))
sfgltime = UNBLOCK_AFTER_SEEN;
else
sfgltime = atoi(getenv("SFGLTIME"));
// lookup to see if record exists
buildLookupSql(sql,i);
if(!sf_custom_sqlquery(mysql,sql)) {
myres = mysql_store_result(mysql);
while ((myrow = mysql_fetch_row(myres)) != NULL) {
exists = TRUE;
strncpy(sid,myrow[0],sizeof(sid));
notexpired = atoi(myrow[1]);
seenbeforerfc = atoi(myrow[2]);
earlyseen_cnt = atoi(myrow[3]);
#ifdef DEBUGTLOG
fprintf(sflog,"\nid = %s expire = %d\n",sid,notexpired);
#endif
}
mysql_free_result(myres); // free memory used by result
// if it exists and is not record expired and the block_expired passes
if (exists && notexpired) {
// update expire time to EXPIRE_ACCEPTED days, and pass count
sprintf(sql,"update softfail set record_expires = NOW() + INTERVAL %d DAY, passed_count = passed_count + 1 where id ='%s'",EXPIRE_ACCEPTED,sid);
sf_custom_sqlquery(mysql, sql);
fprintf(sflog,"%s qmail-sf: %s -> %s (%s) Exists Accept (id = %s)\n",tlg(),mf_addr,sf_rcpt_l[i].mail, mf_fqdnaddr,sid);
*action = ENVELOPE_ACCEPT_CODE;
}
else if ( exists && ! notexpired) {
// update fail count, remember number of times we have seen
// a certain envelope before we expected - early seen
// or just update the number of fail count as usual
if (seenbeforerfc)
sprintf(sql,"update softfail set blocked_count = blocked_count + 1, earlyseen_cnt = earlyseen_cnt +1 where id = %s",sid);
else
sprintf(sql,"update softfail set blocked_count = blocked_count + 1 where id = %s",sid);
sf_custom_sqlquery(mysql, sql);
if (seenbeforerfc) {
if (earlyseen_cnt >= sfmaxseentimes)
{
fprintf(sflog,"%s qmail-sf: %s -> %s (%s) Exists Block (id = %s, RFC note: seen too early for (%d) times: Blacklisted)\n",tlg(),mf_addr,sf_rcpt_l[i].mail, mf_fqdnaddr,sid,earlyseen_cnt);
sprintf(sql,"update softfail set block_expires = '9999-12-31 23:59:59', record_expires = '9999-12-31 23:59:59', origin_type = 'AUTO', mail_from = NULL, rcpt_to = NULL where id = %s",sid); // blacklist query
//insert into relaytofrom values (0,"10.69.69.250",NULL,NULL,"9999-12-31 23:59:59","9999-12-31 23:59:59",0,0,0,"MANUAL",NOW(),NOW(),0);
sf_custom_sqlquery(mysql, sql);
}
else
fprintf(sflog,"%s qmail-sf: %s -> %s (%s) Exists Block (id = %s, RFC note: seen IP again (%d/%d) too early)\n",tlg(),mf_addr,sf_rcpt_l[i].mail, mf_fqdnaddr,sid,earlyseen_cnt,sfmaxseentimes);
}
else {
fprintf(sflog,"%s qmail-sf: %s -> %s (%s) Exists Block (id = %s)\n",tlg(),mf_addr,sf_rcpt_l[i].mail, mf_fqdnaddr,sid);
// sprintf(sql,"update softfail set earlyseen_cnt = earlyseen_cnt - 1 where id = %s",sid); // if we ever want to follow absolute number of times RFC was broken by this host, this would be the query
sprintf(sql,"update softfail set earlyseen_cnt = 0 where id = %s",sid); // but for now we want to follow the consecutive number of times RFC was broken by this host
sf_custom_sqlquery(mysql, sql);
}
*action = ENVELOPE_SOFTFAIL_CODE;
}
else /* doesn't exist */ {
// it doesn't exist make and entry
sprintf(sql,"insert into softfail values (0,'%s','%s','%s',NOW() + INTERVAL %d SECOND,NOW() + INTERVAL %d MINUTE,1,0,0,'AUTO',NOW(),NOW(),0)",mf_fqdnaddr,mf_addr,sf_rcpt_l[i].mail,sfgltime,EXPIRE_SEENMSG);
sf_custom_sqlquery(mysql,sql);
fprintf(sflog,"%s qmail-sf: %s -> %s (%s) Doesn't Exists Block (%ds)\n",tlg(),mf_addr,sf_rcpt_l[i].mail,mf_fqdnaddr,sfgltime);
*action = ENVELOPE_SOFTFAIL_CODE;
}
}
}
int envelope_vrf(int fd, uschar **retmsg)
{
MYSQL *mysql = NULL;
int i, ret = ENVELOPE_ACCEPT_CODE;
sflog = fopen(SFLOGFILE,"a");
#ifdef DEBUGTLOG
fprintf(sflog,"protocol = %s %s \n",proto_used,mf_addr);
#endif
fd = fd;
retmsg = retmsg;
mysql = mysql_init(NULL);
if (mysql && strcmp(proto_used,"local") ) {
if (mysql_real_connect(mysql,MYSQLSERVER,MYSQLUSER,MYSQLPASSWORD,MYSQLDB,0,NULL,0)) {
if ( !checkWhiteListIP( mysql, &ret )) { /* check for whitelisted sender ip address */
for(i= 0 ; i < rcpt_n; i++ ) {
if(strlen(mf_fqdnaddr) + strlen(mf_addr) +
strlen(sf_rcpt_l[i].mail) < SQLCMDSIZE - 200) { /* check to avoid buffer overflows */
if(!checkWhiteListDomain( mysql, &ret,i ))
checkGreylist(mysql, &ret,i);
}
}
}
}
}
if(mysql) mysql_close(mysql);
if(sflog) {
#ifdef DEBUGTLOG
fprintf(sflog, "--------\n");
#endif
fclose(sflog);
}
return ret;
}
syntax highlighted by Code2HTML, v. 0.9.1