/**************
 * Copyright (c) mjd@digitaleveryware.com 2003 
 * GPL Licensed see http://www.gnu.org/licenses/gpl.html
 * Version 0.04: I don't even have it in cvs yet 
 * ChangeHistory:
 * Version 0.05: support for IPv6 using PostgreSQL <gurubert@gurubert.de>
 * Version 0.04: support for qmail
 * Version 0.03: added whitelisting by incoming domain address
 * Version 0.02: added relay_ip matching by /24 subnet
 *
 *************/

#include "local_scan.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libpq-fe.h"

/************************/
#define DEFAULT_DBHOST NULL
#define DEFAULT_DBUSER NULL
#define DEFAULT_DBPASS NULL
#define DEFAULT_DB     "relaydelay"
#define DEFAULT_BLOCK_EXPIRE  45   /* minutes until email is accepted */
#define DEFAULT_RECORD_EXPIRE 500  /* minutes until record expires */
#define DEFAULT_RECORD_EXPIRE_GOOD  72 /* days until record expires after accepting email */
#define DEFAULT_LOCAL_SCAN_DEBUG   0 /* set to 1 to enable debugging */

/*************************/

#define SQLCMDSIZE 1024

char *dbhost, *dbuser, *dbpass, *db;
int block_expire, record_expire, record_expire_good, local_scan_debug;

PGresult *pg_query_wrapper(PGconn *pgconn, char *sqltext, char *debugmsg) 
{
	PGresult *result;

	result = PQexec(pgconn, sqltext);
	if(local_scan_debug == 1)
		fprintf(stderr,
		        "local_scan: %s SQL: |%s| result status: '%s'\n",
		        debugmsg,
		        sqltext,
		        PQresStatus(PQresultStatus(result)));
	return result;
}

int convert_true_false(char *input) {
	if (!strcmp(input, "t"))
		return TRUE;
	return FALSE;
}

/* returns TRUE FALSE if record exists in database */
int checkWhiteListIP(PGconn *pgconn, int *action) {
	char sql[SQLCMDSIZE], sid[32];
	int white, black, exists = FALSE;
	PGresult *result;
	int row;
	
  /* check for whitelisted sender ip address */
	sprintf(sql,
	        "SELECT id, block_expires > NOW(), block_expires < NOW() FROM relaytofrom WHERE record_expires > NOW() AND mail_from IS NULL AND rcpt_to IS NULL AND relay_ip >>= '%s' ORDER BY masklen(relay_ip)",
	       sender_host_address);
	
	result = pg_query_wrapper(pgconn,sql,"IP");
	if(PQresultStatus(result) == PGRES_TUPLES_OK) {
		for (row = 0; row < PQntuples(result); row++) {
			exists = TRUE;
			strncpy(sid, PQgetvalue(result, row, 0), sizeof(sid));
			black = convert_true_false(PQgetvalue(result, row, 1));
			white = convert_true_false(PQgetvalue(result, row, 2));
		}
	}
	PQclear(result); // free memory used by result
	if(exists && white) {
		fprintf(stderr,
		        "local_scan: %s -> xxx (%s) Whitelist IP Accept id = %s\n",
		        sender_address,
		        sender_host_address,
		        sid);
		*action = LOCAL_SCAN_ACCEPT;
	}
	else
		if (exists && black) {
			fprintf(stderr,
			        "local_scan: %s -> xxx (%s) Blacklist IP Permanent Reject id = %s\n",
			        sender_address,
			        sender_host_address,
			        sid);
			*action = LOCAL_SCAN_REJECT;
		}
	return exists;
}

/* returns TRUE FALSE if record exists in database */
int checkWhiteListDomain(PGconn *pgconn, int *action, int i) {
	PGresult *result; 
	int row;
	
	char sql[SQLCMDSIZE], sid[32], *indomain;
	int white, black, exists = FALSE;
	
	indomain = strrchr(recipients_list[i].address,'@');
	if(indomain != NULL) {
		indomain++; /* skip the '@' char */
		
    /* check for whitelisted receiver domain address */
		sprintf(sql,
		        "SELECT id, block_expires > NOW(), block_expires < NOW() FROM relaytofrom WHERE record_expires > NOW() AND mail_from IS NULL AND relay_ip IS NULL AND rcpt_to = '%s'",
		        indomain);
		result = pg_query_wrapper(pgconn,sql,"domain");
		if(PQresultStatus(result) == PGRES_TUPLES_OK) {
			for (row = 0; row < PQntuples(result); row++) {
				exists = TRUE;
				strncpy(sid, PQgetvalue(result, row, 0), sizeof(sid));
				black = convert_true_false(PQgetvalue(result, row, 1));
				white = convert_true_false(PQgetvalue(result, row, 2));
			}
		}
		PQclear(result);
		if (exists && white) {
			fprintf(stderr,
			        "local_scan: %s -> %s (%s) Whitelist Domain Accept id = %s\n",
			        sender_address,
			        recipients_list[i].address,
			        sender_host_address,
			        sid);
			*action = LOCAL_SCAN_ACCEPT;
		}
		else if (exists && black) {
			fprintf(stderr,
			        "local_scan: %s -> xxx (%s) Blacklist Domain Permanent Reject id = %s\n",
			        sender_address,
			        sender_host_address,
			        sid);
			*action = LOCAL_SCAN_REJECT;
		}
	}
	if (!exists) {
    /* check for whitelisted receiver address */
		sprintf(sql,
		        "SELECT id, block_expires > NOW(), block_expires < NOW() FROM relaytofrom WHERE record_expires > NOW() AND mail_from IS NULL AND relay_ip IS NULL AND rcpt_to = '%s'",
		        recipients_list[i].address);
		result = pg_query_wrapper(pgconn,sql,"address");
		if(PQresultStatus(result) == PGRES_TUPLES_OK) {
			for (row = 0; row < PQntuples(result); row++) {
				exists = TRUE;
				strncpy(sid, PQgetvalue(result, row, 0), sizeof(sid));
				black = convert_true_false(PQgetvalue(result, row, 1));
				white = convert_true_false(PQgetvalue(result, row, 2));
			}
		}
		PQclear(result);
		if (exists && white) {
			fprintf(stderr,
			        "local_scan: %s -> %s (%s) Whitelist Domain Accept id = %s\n",
			        sender_address,
			        recipients_list[i].address,
			        sender_host_address,
			        sid);
			*action = LOCAL_SCAN_ACCEPT;
		}
		else if (exists && black) {
			fprintf(stderr,
			        "local_scan: %s -> xxx (%s) Blacklist Domain Permanent Reject id = %s\n",
			        sender_address,
			        sender_host_address,
			        sid);
			*action = LOCAL_SCAN_REJECT;
		}
	}
	return exists;
}

void buildLookupSql(char *sql, int recipIndex) 
{
	if (! getenv("GREYLIST_ONLY_SUBNETS")) {
    /* this first sprintf does an exact match on ipaddr */
		sprintf(sql,
		        "SELECT id, NOW() > block_expires FROM relaytofrom WHERE record_expires > NOW() AND mail_from = '%s' AND rcpt_to = '%s' AND relay_ip = '%s' order by block_expires desc",
		        sender_address,
		        recipients_list[recipIndex].address,
		        sender_host_address);
	} else {
    /* this second version matches anything in the same /24 (IPv4) or /64 (IPv6) subnet */
		sprintf(sql,
		        "SELECT id, NOW() > block_expires FROM relaytofrom WHERE record_expires > NOW() AND mail_from = '%s' AND rcpt_to = '%s' AND family(relay_ip) = family('%s') and set_masklen(relay_ip, case family('%s') when 6 then 64 else 24 end) >>= '%s'",
		        sender_address,
		        recipients_list[recipIndex].address,
		        sender_host_address,
		        sender_host_address,
		        sender_host_address
		       );
	}
}

int checkGreylist( PGconn *pgconn,  int *action, int i) 
{
	char sql[SQLCMDSIZE], sid[32];
	int notexpired, exists = FALSE;
	PGresult *result;
	int row;
	
 // lookup to see if record exists
	buildLookupSql(sql, i);
	result = pg_query_wrapper(pgconn,sql,"greylist");
	if(PQresultStatus(result) == PGRES_TUPLES_OK) {
		for (row = 0; row < PQntuples(result); row++) {
			exists = TRUE;
			strncpy(sid, PQgetvalue(result, row, 0), sizeof(sid));
			notexpired = convert_true_false(PQgetvalue(result, row, 1));
		}
		PQclear(result);
   // if it exists and is not record expired and the block_expired passes
		if (exists && notexpired) {
     // update expire time to 36 days, and pass count
			sprintf(sql,
			        "update relaytofrom set record_expires = NOW() + '%d DAYS', passed_count = passed_count + 1, last_update = now() where id ='%s'",
			        record_expire_good,
			        sid);
			result = pg_query_wrapper(pgconn, sql, "exists accept");
			fprintf(stderr,
			        "local_scan: %s -> %s (%s) Exists Accept id = %s expire = %d\n",
			        sender_address,
			        recipients_list[i].address,
			        sender_host_address,
			        sid,
			        notexpired);
			*action = LOCAL_SCAN_ACCEPT;
		} else if ( exists && ! notexpired) {
     // update fail count
			sprintf(sql,
			        "update relaytofrom set blocked_count = blocked_count + 1, last_update = now() where id = %s",
			        sid);
			result = pg_query_wrapper(pgconn, sql, "exists block");
			fprintf(stderr,
			        "local_scan: %s -> %s (%s) Exists Block id = %s expire = %d\n",
			        sender_address,
			        recipients_list[i].address,
			        sender_host_address,
			        sid,
			        notexpired);
			*action = LOCAL_SCAN_TEMPREJECT;
		} else /* doesn't exist */ {
     // it doesn't exist make and entry
			sprintf(sql,
			        "insert into relaytofrom (relay_ip, mail_from, rcpt_to, block_expires, record_expires, blocked_count, origin_type) values ('%s','%s','%s',NOW() + '%d MINUTES',NOW() + '%d MINUTES',1,'A')",
			        sender_host_address,
			        sender_address,
			        recipients_list[i].address,
			        block_expire,
			        record_expire);
			result = pg_query_wrapper(pgconn,sql,"doesn't exist block");
			fprintf(stderr,
			        "local_scan: %s -> %s (%s) Doesn't Exist Block\n",
			        sender_address,
			        recipients_list[i].address,
			        sender_host_address);
			*action = LOCAL_SCAN_TEMPREJECT;
		}
	}
	PQclear(result);
}

void get_config(void) {

	char *t;

	dbhost = getenv("DBHOST");
	if (!dbhost) {
		dbhost = DEFAULT_DBHOST;
	}
	dbuser = getenv("DBUSER");
	if (!dbuser) {
		dbuser = DEFAULT_DBUSER;
	}
	dbpass = getenv("DBPASS");
	if (!dbpass) {
		dbpass = DEFAULT_DBPASS;
	}
	db = getenv("DB");
	if (!db) {
		db = DEFAULT_DB;
	}
	t = getenv("BLOCK_EXPIRE");
	if (t) {
		block_expire = atoi(t);
	} else {
		block_expire = DEFAULT_BLOCK_EXPIRE;
	}
	t = getenv("RECORD_EXPIRE");
	if (t) {
		record_expire = atoi(t);
	} else {
		record_expire = DEFAULT_RECORD_EXPIRE;
	}
	t = getenv("RECORD_EXPIRE_GOOD");
	if (t) {
		record_expire_good = atoi(t);
	} else {
		record_expire_good = DEFAULT_RECORD_EXPIRE_GOOD;
	}
	t = getenv("LOCAL_SCAN_DEBUG");
	if (t) {
		local_scan_debug = atoi(t);
	} else {
		local_scan_debug = DEFAULT_LOCAL_SCAN_DEBUG;
	}
}

int local_scan(int fd, uschar **return_text)
{
	PGconn *pgconn = NULL;
	int i, ret = LOCAL_SCAN_ACCEPT;
	
	get_config();
	
	if(local_scan_debug == 1) fprintf(stderr,"local_scan: protocol = %s %s  \n",received_protocol,sender_address);
	
	pgconn = PQsetdbLogin(dbhost, NULL, NULL, NULL, db, dbuser, dbpass);
	if (PQstatus(pgconn)==CONNECTION_OK) {
		if ( !checkWhiteListIP( pgconn, &ret )) {      /* check for whitelisted sender ip address */
			for(i= 0; i <  recipients_count; i++) {
				if(strlen(sender_host_address) + strlen(sender_address) +
				   strlen(recipients_list[i].address) < SQLCMDSIZE - 200) { /* check to avoid buffer overflows */
					   if(!checkWhiteListDomain( pgconn, &ret, i))
						   checkGreylist(pgconn, &ret,i);
				   }
			}
		}
	} else {
		fprintf(stderr, "local_scan: unable to connect to postgresql database %s@%s\n", db, dbhost);
	}
	if(pgconn) PQfinish(pgconn);
	if(local_scan_debug == 1) fprintf(stderr, "local_scan: --------\n");
	return ret;
}

/* End of local_scan.c */

