/*
	autorespond for qmail

	Copyright 1998 Eric Huss
	E-mail: e-huss@netmeridian.com

	Usage:

			autorespond time num message dir

		time - time in seconds to consider consecutive
		num - maximum number of messages to respond to within time seconds
		message - the message file
		dir - directory to store list of e-mail addresses
*/

/*
Exit codes:
0 - OK
99 - OK...stop processing lines in .qmail
100 - hard error
111 - soft error
*/

/*
SENDER - envelope sender
NEWSENDER - forwarding evelope sender
RECIPIENT - envelope "to"
USER - the user
HOME - your home directory
HOST - host of recipient address
LOCAL - local of recipient address
EXT - .qmail extension...etc.
DTLINE - Delivered-To
RPLINE - Return-Path
*/

/*Change this value here to the location of your qmail*/
#define QMAIL_LOCATION "/var/qmail"

/*MySQL database specific defines*/
#define DB_USER		"autoresponder"
#define DB_PASSWORD	""
#define DB_DB		"autoresponder"

#include <time.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/file.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "mysql.h"

void * safe_malloc(size_t size)
{
void * ptr;

	ptr = malloc(size);
	if(ptr==NULL) {
		/*exit...no memory*/
		_exit(111);
	}

	return ptr;
}


void * safe_realloc(void * ptr, size_t size)
{
void * p;

	p = realloc(ptr,size);
	if(p==NULL) {
		/*exit...no memory*/
		_exit(111);
	}

	return p;
}

char * read_file(char * filename)
{
FILE * f;
unsigned long size;
char * buffer;

	if(!filename) {
		/*failed*/
		return NULL;
	}

	f = fopen(filename,"rb");
	if(f==NULL) {
		/*failed*/
		return NULL;
	}

	/*seek to end*/
	if(fseek(f,0,SEEK_END)==-1) {
		/*failed*/
		fclose(f);
		return NULL;
	}

	/*this may not be portable (although it almost always is)*/
	size = ftell(f);

	/*return to the beginning*/
	if(fseek(f,0,SEEK_SET)==-1) {
		fclose(f);
		return NULL;
	}

	buffer = (char *) safe_malloc(size+100);

	if(fread(buffer,1,size,f)!=size) {
		fclose(f);
		return NULL;
	}
	buffer[size]=0;					/*for safety*/
	fclose(f);
	return buffer;
}


/*see header file for more info*/

static char *binqqargs[2] = { "bin/qmail-queue", 0 };
static char *montab[12] = {
"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
};

/* A wrapper for qmail-queue
   borrowed from djb*/
int send_message(char * msg, char * from, char ** recipients, int num_recipients)
{
/*	...Adds Date:
	...Adds Message-Id:*/
int r;
int wstat;
int i;
struct tm * dt;
unsigned long msgwhen;
FILE * fdm;
FILE * fde;
pid_t pid;
int pim[2];				/*message pipe*/
int pie[2];				/*envelope pipe*/

	/*open a pipe to qmail-queue*/
	if(pipe(pim)==-1 || pipe(pie)==-1) {
		return -1;
	}
	pid = vfork();
	if(pid == -1) {
		/*failure*/
		return -1;
	}
	if(pid == 0) {
		/*I am the child*/
		close(pim[1]);
		close(pie[1]);
		/*switch the pipes to fd 0 and 1*/
		/*pim[0] goes to 0 (stdin)...the message*/
		if(fcntl(pim[0],F_GETFL,0) == -1) {
/*			fprintf(stderr,"Failure getting status flags.\n");*/
			_exit(120);
		}
		close(0);
		if(fcntl(pim[0],F_DUPFD,0)==-1) {
/*			fprintf(stderr,"Failure duplicating file descriptor.\n");*/
			_exit(120);
		}
		close(pim[0]);
		/*pie[0] goes to 1 (stdout)*/
		if(fcntl(pie[0],F_GETFL,0) == -1) {
/*			fprintf(stderr,"Failure getting status flags.\n");*/
			_exit(120);
		}
		close(1);
		if(fcntl(pie[0],F_DUPFD,1)==-1) {
/*			fprintf(stderr,"Failure duplicating file descriptor.\n");*/
			_exit(120);
		}
		close(pie[0]);
		if(chdir(QMAIL_LOCATION) == -1) {
			_exit(120);
		}
		execv(*binqqargs,binqqargs);
		_exit(120);
	}

	/*I am the parent*/
	fdm = fdopen(pim[1],"wb");					/*updating*/
	fde = fdopen(pie[1],"wb");
	if(fdm==NULL || fde==NULL) {
		return -1;
	}
	close(pim[0]);
	close(pie[0]);

	/*prepare to add date and message-id*/
	msgwhen = time(NULL);
	dt = gmtime((long *)&msgwhen);
	/*start outputting to qmail-queue
	  date is in 822 format*/
	fprintf(fdm,"Date: %u %s %u %02u:%02u:%02u -0000\n"
				"Message-ID: <%lu.%lu.blah>\n"
				"%s\n"
				,dt->tm_mday,montab[dt->tm_mon],dt->tm_year+1900,dt->tm_hour,dt->tm_min,dt->tm_sec
				,msgwhen,getpid()
				,msg);
	fclose(fdm);


	/*send the envelopes*/

	fprintf(fde,"F%s",from);
	fwrite("",1,1,fde);					/*write a null char*/
	for(i=0;i<num_recipients;i++) {
		fprintf(fde,"T%s",recipients[i]);
		fwrite("",1,1,fde);					/*write a null char*/
	}
	fwrite("",1,1,fde);					/*write a null char*/
	fclose(fde);

	/*wait for qmail-queue to close*/
	do {
		r = wait(&wstat);
	} while ((r != pid) && ((r != -1) || (errno == EINTR)));
	if(r != pid) {
		/*failed while waiting for qmail-queue*/
		return -1;
	}
	if(wstat & 127) {
		/*failed while waiting for qmail-queue*/
		return -1;
	}
	/*the exit code*/

	if((wstat >> 8)!=0) {
		/*non-zero exit status
		  failed while waiting for qmail-queue*/
		return -1;
	}
	return 0;
}


char * query_encode(char * str)
{
char * new_str;
int i;
int j;

	new_str = (char *) safe_malloc(strlen(str)*2+1);
	for(i=0,j=0;str[i];i++,j++) {
		if(	str[i]=='\'' ||
			str[i]=='\"' ||
			str[i]=='\\' ) {
			new_str[j] = '\\';
			j++;
		}
		new_str[j] = str[i];
	}

	new_str[j] = 0;

	return new_str;
}

int main(int argc, char ** argv)
{
char * sender;

char * message;
unsigned int time_message;
unsigned int timer;
unsigned int num;
char * message_filename;
char * dir;

char * msg;
char * incoming_message;
char * ptr;
unsigned int k;
unsigned int incoming_message_index;
unsigned int i;
unsigned int size;
char * my_delivered_to;

MYSQL db_handle;
MYSQL_RES * result;
MYSQL_ROW row;
char * query;
char * encoded_sender;

	setvbuf(stderr, NULL, _IONBF, 0);

	if(argc!=4) {
		fprintf(stderr, "AUTORESPOND: Invalid arguments.\n");
		_exit(111);
	}

	time_message = strtoul(argv[1],NULL,10);
	timer = time(NULL);
	num = strtoul(argv[2],NULL,10);
	message_filename = argv[3];
	dir = argv[4];

	/*prepare the "delivered-to" string*/
	my_delivered_to = "Delivered-To: Autoresponder\n";

	/*load the incoming message into a variable*/
	i = 0;
	incoming_message = (char *) safe_malloc(10005);
	size = 10000;
	while(1) {
		/*read in a bunch*/
		k = read(0,incoming_message+i,10000);
		if(k==-1) {
			/*failed*/
			fprintf(stderr,"Failure while reading standard input.\n");
			_exit(111);						/*soft error*/
		} else if(k==0) {
			/*done reading*/
			break;
		} else {
			i+=k;
		}
		if( size-i < 10000 ) {
			/*need to allocate more space*/
			incoming_message = (char *) safe_realloc(incoming_message,size+10005);
			size+=10000;
		}
	}
	incoming_message[i]=0;

	message = read_file(message_filename);
	if(message==NULL) {
		fprintf(stderr, "AUTORESPOND: Failed to open message file.\n");
		_exit(111);
	}

	/*don't autorespond in certain situations*/
	sender = getenv("SENDER");
	if(sender==NULL)
		sender = "";

	/*don't autorespond to a mailer-daemon*/
	if( sender[0]==0 || 
		strncasecmp(sender,"mailer-daemon",13)==0 ||
		strchr(sender,'@')==NULL ||
		strcmp(sender,"#@[]")==0 ) {
		/*exit with success but request to stop parsing .qmail file*/
		fprintf(stderr,"AUTORESPOND:  Stopping on mail from [%s].\n",sender);
		_exit(99);
	}

	/*don't autorespond to myself*/
	incoming_message_index = 0;
	while(1) {
		/*break once the headers are done*/
		if( incoming_message[incoming_message_index]=='\n' || 
			incoming_message[incoming_message_index]==0)
			break;
		/*check if this is not a continuing header*/
		if( incoming_message[incoming_message_index]!=' ' && 
			incoming_message[incoming_message_index]!='\t') {
			if(strncasecmp(incoming_message+incoming_message_index,"mailing-list:",13)==0) {
				/*don't support a message with a mailing-list header*/
				fprintf(stderr,"AUTORESPOND: I can't handle a message with a Mailing-List header.\n");
				_exit(100);			/*hard error*/
			}
			if(strncasecmp(incoming_message+incoming_message_index,my_delivered_to
									,strlen(my_delivered_to))==0) {
				/*got one of my own messages...*/
				fprintf(stderr,"AUTORESPOND: This message is looping...it has my Delivered-To header.\n");
				_exit(100);
			}
			/*check for precedence header*/
			if(strncasecmp(incoming_message+incoming_message_index,"precedence:",11)==0) {
				incoming_message_index += 11;
				/*could go too far...use temp*/
				i = 0;
				while(	incoming_message[incoming_message_index+i]==' ' ||
						incoming_message[incoming_message_index+i]=='\t' ||
						incoming_message[incoming_message_index+i]==10 ||
						incoming_message[incoming_message_index+i]==13) {
					i++;
				}
				if( strncasecmp(incoming_message+incoming_message_index+i,"junk",4)==0 ||
					strncasecmp(incoming_message+incoming_message_index+i,"bulk",4)==0 ||
					strncasecmp(incoming_message+incoming_message_index+i,"list",4)==0) {
					fprintf(stderr,"AUTORESPOND: Junk mail received.\n");
					_exit(100);
				}
			} /*end if precedence*/
		} /*end if not continuation*/
		/*get the end of the line*/
		ptr = strchr(incoming_message+incoming_message_index,'\n');
		if(ptr==NULL)
			ptr = strchr(incoming_message+incoming_message_index,0);		/*assume this works*/
		k = ptr-(incoming_message+incoming_message_index);
		incoming_message_index += k;			/*newline will be skipped with ++ at end*/
		if(incoming_message[incoming_message_index]==0)
			break;
		incoming_message_index++;
	} /*end while 1*/

	/*check the logs*/
	query = (char *) safe_malloc(1000+strlen(sender));
	if(!(mysql_connect(&db_handle,NULL,DB_USER,DB_PASSWORD))) {
		fprintf(stderr,"AUTORESPOND: Failed to connect to database.\n");
		_exit(111);
	}
	if(mysql_select_db(&db_handle,DB_DB)) {
		fprintf(stderr,"AUTORESPOND: Failed to switch database.\n");
		_exit(111);
	}
	sprintf(query,"DELETE FROM autoresponder WHERE time_sent<%u",timer-time_message);
	if(mysql_query(&db_handle,query)) {
		fprintf(stderr,"AUTORESPOND: Failed to remove entries.\n");
		_exit(111);
	}

	/*add entry*/
	encoded_sender = query_encode(sender);
	sprintf(query,"INSERT INTO autoresponder (e_mail,time_sent)"
				" VALUES ('%s',%u)",encoded_sender,timer);
	if(mysql_query(&db_handle,query)) {
		fprintf(stderr,"AUTORESPOND: Failed to add entry.\n");
		_exit(111);
	}

	/*check if there are too many responses in the logs*/
	sprintf(query,"SELECT COUNT(*) FROM autoresponder"
				" WHERE e_mail='%s'",encoded_sender);
	if(mysql_query(&db_handle,query)) {
		fprintf(stderr,"AUTORESPOND: Failed to add entry.\n");
		_exit(111);
	}
	if(!(result = mysql_store_result(&db_handle))) {
		fprintf(stderr,"AUTORESPOND: Failed to store result.\n");
		_exit(111);
	}
	if((row = mysql_fetch_row(result))) {
		if(row[0] && strtoul(row[0],NULL,10)>num) {
			fprintf(stderr,"AUTORESPOND: too many received from [%s]\n",sender);
			_exit(99);
		}
	}
	mysql_free_result(result);
	mysql_close(&db_handle);

	msg = (char *) safe_malloc(strlen(message) + strlen(my_delivered_to) +
								strlen(sender) + strlen(incoming_message) +
								1000);
	sprintf(msg,"%s"
				 "To: %s\n"
				 "%s\n"
				 "\n"
				 "%s\n"
				 ,my_delivered_to
				 ,sender
				 ,message
				 ,incoming_message);

	/*send the autoresponse...ignore errors?*/
	send_message(msg,"",&sender,1);

	_exit(0);
	return 0;					/*compiler warning squelch*/
}
