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