/*
 *	Milter client-side API, a library to talk to Milter filters
 *
 *	(C) Copyright 2003 ADVA Research Center
 *	(C) Copyright 2003 ArkanoiD <ark@eltex.net>
 *	Based on code by Alexander Kosheverov
 */

#include "ci_mfapi.h"
#include "ci_mfdef.h"
#include "ci_milter.h"
#include "ci_milter_sys.h"
#include <time.h>
#include <strings.h>
#include <libgen.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>
#include <inttypes.h>
#include "milter_log.h"

extern int proxy_timeout;

#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#endif

#define MILTER_CHECK_DONE_MSG() \
	if (*state == SMFIR_DISCARD || \
	    *state == SMFIR_ACCEPT  || \
	    *state == SMFIR_TEMPFAIL)  \
	{ \
		/* Abort the filters to let them know we are done with msg */ \
		milter_abort_filter(m); \
	}

#define MILTER_CHECK_STATE(action) 					 \
	if ((m->mf_state == SMFS_DONE) || (m->mf_state == SMFS_ERROR) || \
 	    (m->mf_state == SMFS_CLOSED))				 \
		action;

#define EOM_TIMEOUT 600

extern	void* xmalloc(size_t request);
extern	char* xstrdup(const char*);
extern 	int soread(int,char*,int);
extern 	int sowrite(int,char*,int);
extern 	int conn_server(char*,int,int,char*);

static time_t
now(void)
{
        auto time_t t;
        (void) time(&t);
        return t;
}


/*
 * 	Low-level packet operation functions
 */

/*
 *	milter_formdata() -- compose milter data packet for milter_write()
 *
 *	parameters:	packet --   pointer to pointer to packet. packet
 *				    allocation is created dynamically, thus 
 *                  		    first portion of data is to be placed
 *				    setting this *(char **) to NULL
 *
 *			pktsize --  pointer to current packet size (it
 *				    changes authomagically as well
 *
 *			data --	    data to be appended to packet
 *
 *			datasize -- size of the above
 *
 *	returns:	nothing, but memory allocation failure terminates
 *			the program.	
 */			  

static 	void	
milter_formdata(char** packet,unsigned int* pktsize,const char *data,size_t datasize)
{
	if ((*pktsize+datasize) >= (MILTER_CHUNK_SIZE-1)) {
		FATAL("packet size too big");
	}
	if (!(*packet = realloc(*packet,*pktsize+datasize+1))) {
		FATAL("realloc() failed");
	}
	memcpy(*packet+*pktsize,data,datasize);
	*pktsize += datasize;
	return;
}

/*
 *	milter_formstring() -- append a string to packet
 *
 *	parameters:	packet and pktsize -- identical to milter_formdata
 *			string --	      string to be appended
 */

static	void	
milter_formstring(char** packet,unsigned int* pktsize,const char* string)
{
	milter_formdata(packet,pktsize,string,strlen(string)+1);
	return;
}

/*
 *  	milter_write() -- write a milter command packet to network
 *
 *	parameters: 	m -- milter to write (state should permit).
 *			cmd -- command to send.
 *			buf -- data packet (composed by milter_form*)
 *			len -- packet size.
 *
 *	returns: 	nothing, milter state changed to SMFS_ERROR
 *			if error condition occurs.
 */

static	void
milter_write(struct milter* m,char cmd,char* buf,ssize_t len)
{
	ssize_t sl;
	uint32_t nl;
	char data[MILTER_LEN_BYTES + 1];

	if (len < 0 || len > MILTER_CHUNK_SIZE) {
		m->mf_state = SMFS_ERROR; 
		return;
	}

 	MILTER_CHECK_STATE(return);

	nl = htonl((len + 1));	/* add 1 for the cmd char */
	(void) memcpy(data, (char *) &nl, sizeof(uint32_t));
	data[sizeof(uint32_t)] = cmd;
	sl = sizeof(uint32_t) + 1;

	if(sowrite(m->mf_sock, (void *) data, sl) != sl) {
		ERROR("sowrite failed");
		m->mf_state = SMFS_ERROR; 
		return;
	}

	if (len <= 0 || buf == NULL)
		return;

	if(sowrite(m->mf_sock, (void *) buf, len) != len){
#ifdef DEBUG
		ERROR("sowrite failed");
#endif
		m->mf_state = SMFS_ERROR; 
		return;
	}
	return;
}

/*
 *	milter_sysread() -- read fixed size data packet from network
 *
 *	parameters:	fd -- file descriptor to read
 *			buf -- data buffer
 *			buf_sz -- data buffer size
 *
 *	returns:	MI_FAILURE on error, number of bytes read otherwise
 */

static int
milter_sysread(int fd,char* buf,int buf_sz)
{
	ssize_t cur_len, path_len;

	if(buf_sz == 0)
		return MI_SUCCESS;
	path_len = 0;
	cur_len  = 0;
	while (1) {
		if((cur_len = soread(fd, buf + path_len, buf_sz - path_len)) <= 0) {
#ifdef DEBUG
			ERROR("soread failed");
#endif
			return MI_FAILURE;
		}
		path_len += cur_len;
		if(path_len < buf_sz)
			continue;
		break;
	}
	return path_len;

}

/*
 *	milter_read() -- read milter response packet from network
 *
 *	parameters:	m -- milter to read (should be in readable state)
 *			*cmd -- pointer to response command read
 *			*rlen -- pointer to returned packet size
 *
 *	returns:	dynamically allocated buffer with packet read
 */

static	char *
milter_read(struct milter* m, char* cmd, ssize_t* rlen)
{
	ssize_t expln;
	ssize_t chk;
	uint32_t i;
	char *buf;
	char data[MILTER_LEN_BYTES + 1];

	bzero(data, sizeof data);

	*rlen = 0;
	*cmd = '\0';

 	MILTER_CHECK_STATE(return NULL);

	if ((expln = milter_sysread(m->mf_sock, data, sizeof data)) == MI_FAILURE) {
		m->mf_state = SMFS_ERROR; 
		return NULL;
	}
	if(expln != MILTER_LEN_BYTES + 1) {
#ifdef DEBUG
		syslog(LOG_ERR, "libci_milter: milter_read: truncated packet header, %d bytes",expln);
#endif
		m->mf_state = SMFS_ERROR; 
		return NULL;
	}

	*cmd = data[MILTER_LEN_BYTES];
	data[MILTER_LEN_BYTES] = '\0';
	(void) memcpy(&i, data, MILTER_LEN_BYTES);
	expln = ntohl(i) - 1;

	if (expln == 0) 
		return NULL;

	if(expln > MILTER_CHUNK_SIZE) {
		syslog(LOG_ERR,"milter transaction failed expln=%d cmd=%.255s", (int) expln, cmd);
		m->mf_state = SMFS_ERROR; 
		return NULL;
	}

	buf = (char *) xmalloc(expln);

	if ((chk = milter_sysread(m->mf_sock, buf, expln)) == MI_FAILURE) {
		free(buf); 
		buf = NULL;
		m->mf_state = SMFS_ERROR; 
		return NULL;
	}
	if (chk != expln) {
		ERROR("truncated packet on milter protocol");
		free(buf);
		buf = NULL;
		m->mf_state = SMFS_ERROR; 
		return NULL;
	}
	*rlen = expln;
	return buf;
}

/*
 *  milter_open() -- connect to remote milter filter
 *
 *	parameters: 	m -- milter to connect to, SMFS_READY.
 *
 *	returns: 	connected socket if sucessful, 
 *			m state changes to SMFS_OPEN.  
 *			MI_FAILURE on error, state changes to SMFS_ERROR.
 */

static int
milter_open(struct milter* m)
{
	int socketfd = -1;
	struct sockaddr *filter_addr = NULL;
	int		filter_addrlen = 0;
	char	*p, *p1;

	m->mf_sock = MI_FAILURE;

	p = xstrdup(m->mf_conn);

	if ((p1 = index(p, ':'))) {
		*(p1++) = '\0';
		if (strcasecmp(p, "unix") == 0 || strcasecmp(p, "local") == 0) {
			filter_addr = (struct sockaddr *) xmalloc(sizeof(struct sockaddr_un));
			filter_addrlen = sizeof (struct sockaddr_un);
			bzero(filter_addr,filter_addrlen);
                        filter_addr->sa_family = AF_UNIX;
			if (strlen(p1) >= sizeof (((struct sockaddr_un*)
						   0)->sun_path)) {
				ERROR("unix socket path too long");
				free(filter_addr);
				filter_addr = NULL;
				free(p);
				m->mf_state = SMFS_ERROR; 
				return MI_FAILURE;
			}
			strncpy(((struct sockaddr_un*)filter_addr)->sun_path,p1, 
				sizeof (((struct sockaddr_un*)0)->sun_path));
#ifdef DEBUG
			syslog(LOG_INFO, "DEBUG: libci_milter: milter_open: socket is local:%s",
				((struct sockaddr_un*)filter_addr)->sun_path);
#endif 
		} else if (strcasecmp(p, "inet") == 0) {
			char	*host;

			if (!(host = index(p1,'@'))) {
				ERROR("syntax error in inet socket description");
				free(p);
				m->mf_state = SMFS_ERROR; 
				return MI_FAILURE;
			}
			*(host++) = '\0';

			if ((m->mf_sock =conn_server(host,atoi(p1),0,NULL))<0) {
				syslog(LOG_ERR, "milter_open: failed to connect socket: %m");
				filter_addr = NULL;
				m->mf_state = SMFS_ERROR; 
				return MI_FAILURE;
			}
			m->mf_state = SMFS_OPEN;
			return(m->mf_sock);
		} else {
			ERROR("unknown protocol");
			filter_addr = NULL;
			free(p);
			m->mf_state = SMFS_ERROR; 
			return MI_FAILURE;
		}
	}

	free(p);

	socketfd = socket(filter_addr->sa_family, SOCK_STREAM, 0);
	if(socketfd < 0) {
		ERROR("failed to create socket");
		free(filter_addr);
		filter_addr = NULL;
		m->mf_state = SMFS_ERROR; 
		return MI_FAILURE;
	}
	if(connect(socketfd, filter_addr, filter_addrlen)) {
		syslog(LOG_ERR, "milter_open: failed to connect socket: %m");
		free(filter_addr);
		filter_addr = NULL;
		m->mf_state = SMFS_ERROR; 
		return MI_FAILURE;
	}
	m->mf_state = SMFS_OPEN;
	m->mf_sock = socketfd;
#ifdef DEBUG
	syslog(LOG_INFO, "DEBUG: libci_milter: milter_open: connected");
#endif
	free(filter_addr);
	return(m->mf_sock);
}


/*
 *  	milter_new() -- allocate new milter. 
 *
 *	parameters: 	line -- filter name (client-defined).
 *
 *	returns: 	pointer to milter structure in SMFS_READY state
 */

static struct milter*
milter_new(char* line)
{
	struct milter *bm;
	bm = (struct milter *) xmalloc(sizeof *bm);

	bzero(bm, sizeof *bm);
	if(line)
		bm->mf_name = xstrdup(line);
	else
		bm->mf_name = xstrdup("milter");

	bm->mf_state = SMFS_READY;
	bm->mf_sock = -1;
	bm->mf_conn =  NULL;
	bm->mf_timeout = (time_t) EOM_TIMEOUT;
	return bm;
}

/*
 *  	milter_negotiate() -- get version and flags from filter
 *
 *	parameters: 	m -- milter filter structure (SMFS_OPEN state).
 *
 *	returns: 	0 on success (MI_SUCCESS), -1 otherwise (MI_FAILURE)
 */

static	int
milter_negotiate(struct milter* m)
{
	uint32_t fvers;
	uint32_t fflags;
	uint32_t pflags;
	char *response = NULL;
	char data[MILTER_OPTLEN];
	char m_rcmd;
	ssize_t rlen;

	bzero(data, sizeof data);

	if (m->mf_sock < 0 || m->mf_state != SMFS_OPEN) {
		return MI_FAILURE;
	}
	fvers = htonl(SMFI_VERSION);
	fflags = htonl(SMFI_CURR_ACTS);
	pflags = htonl(SMFI_CURR_PROT);

	(void) memcpy(data, (char *) &fvers, MILTER_LEN_BYTES);
	(void) memcpy(data + MILTER_LEN_BYTES,
			(char *) &fflags, MILTER_LEN_BYTES);
	(void) memcpy(data + (MILTER_LEN_BYTES * 2),
			(char *) &pflags, MILTER_LEN_BYTES);
	(void) milter_write(m, SMFIC_OPTNEG, data, sizeof data);

	if (m->mf_state == SMFS_ERROR)
		return MI_FAILURE;
	response = milter_read(m, &m_rcmd, &rlen);

	if (m->mf_state == SMFS_ERROR || m_rcmd != SMFIC_OPTNEG) {
		if(m->mf_sock >= 0) {
			syslog(LOG_ERR, "libci_milter: milter_negotiate: (%.128s): to error state", m->mf_name);
			close(m->mf_sock);
			m->mf_sock = -1;

		}
		m->mf_state = SMFS_ERROR; 
		return MI_FAILURE;
	}
	if (response == NULL || rlen < MILTER_LEN_BYTES)
		if(m->mf_sock >= 0) {
			ERROR("bad response for negotiate packet");
			close(m->mf_sock);
			m->mf_sock = -1;
			m->mf_state = SMFS_ERROR; 
			return MI_FAILURE;
		}

	(void) memcpy((char *) &fvers, response, MILTER_LEN_BYTES);


	if (rlen != MILTER_OPTLEN) {
		ERROR("bad response for negotiate packet");
		close(m->mf_sock);
		m->mf_sock = -1;
		m->mf_state = SMFS_ERROR; 
		return MI_FAILURE;
	}
	(void) memcpy((char *) &fflags, response + MILTER_LEN_BYTES,
		      MILTER_LEN_BYTES);
	(void) memcpy((char *) &pflags, response + (MILTER_LEN_BYTES * 2),
		      MILTER_LEN_BYTES);
	free(response);
	response = NULL;
	m->mf_fvers = ntohl(fvers);
	m->mf_fflags = ntohl(fflags);
	m->mf_pflags = ntohl(pflags);

	/* check for version compatibility */
	if (m->mf_fvers == 1 ||
	    m->mf_fvers > SMFI_VERSION) {
		syslog(LOG_ERR, "libci_milter: milter_negotiate: incompatible protocol version %lx",m->mf_fvers);
		close(m->mf_sock);
		m->mf_sock = -1;
		m->mf_state = SMFS_ERROR; 
		return MI_FAILURE;
	}
	/* check for filter feature mismatch */
	if ((m->mf_fflags & SMFI_CURR_ACTS) != m->mf_fflags) {
		ERROR("feature mismatch");
		close(m->mf_sock);
		m->mf_sock = -1;
		m->mf_state = SMFS_ERROR; 
		return MI_FAILURE;
	}
#ifdef	DEBUG
	syslog(LOG_NOTICE,"libci_milter: milter_negotiate: negotiated");
#endif
	return MI_SUCCESS;
}

/*
 *  	milter_send_command() -- send a command and parse the response for 
 *				 a filter
 *
 *	parameters: 		m -- current milter filter
 *				command -- command to send.
 *				data -- data packet (see above).
 *				sz -- packet size.
 *				state -- return state word.
 *
 *	returns: 		response string (may be NULL)
 */

static void
milter_send_command (struct milter* m,char command,void* data,ssize_t sz,char* state)
{
	char m_rcmd;
	ssize_t rlen;
	unsigned long skipflag;
	char *response;

	MILTER_CHECK_STATE(return);

	/* find skip flag and default failure */
	switch (command)
	{
	  case SMFIC_CONNECT:
		skipflag = SMFIP_NOCONNECT;
		break;

	  case SMFIC_HELO:
		skipflag = SMFIP_NOHELO;
		break;

	  case SMFIC_MAIL:
		skipflag = SMFIP_NOMAIL;
		break;

	  case SMFIC_RCPT:
		skipflag = SMFIP_NORCPT;
		break;

	  case SMFIC_HEADER:
		skipflag = SMFIP_NOHDRS;
		break;

	  case SMFIC_BODY:
		skipflag = SMFIP_NOBODY;
		break;

	  case SMFIC_EOH:
		skipflag = SMFIP_NOEOH;
		break;

	  case SMFIC_BODYEOB:
	  case SMFIC_OPTNEG:
	  case SMFIC_MACRO:
	  case SMFIC_ABORT:
	  case SMFIC_QUIT:
		/* NOTE: not handled by milter_send_command() */
		/* FALLTHROUGH */

	  default:
		skipflag = 0;
		break;
	}

	/* check if filter wants this command */
	if (skipflag != 0 && (skipflag & m->mf_pflags))
		return;
	/* send the command to the filter */
	(void) milter_write(m, command, data, sz);

	if (m->mf_state == SMFS_ERROR) 
		return;

	/* get the response from the filter */
	response = milter_read(m, &m_rcmd, &rlen);

	if (m->mf_state == SMFS_ERROR)
		return;

	switch (m_rcmd)
	{
	  case SMFIR_REPLYCODE:
	  case SMFIR_REJECT:
	  case SMFIR_DISCARD:
	  case SMFIR_QUARANTINE:
	  case SMFIR_TEMPFAIL:
		*state = m_rcmd;
		break;

	  case SMFIR_ACCEPT:
		/* this filter is done with message/connection 		  */
		/* we do not have CLOSABLE state because we deal with one */
		/* message per session					  */
	
		m->mf_state = SMFS_DONE;
		break;

	  case SMFIR_CONTINUE:
		/* if MAIL command is ok, filter is in message state */
		if (command == SMFIC_MAIL)
			m->mf_state = SMFS_INMSG;
		break;

	  default:
		/* Invalid response to command */
		syslog(LOG_ERR, "libci_milter: milter_send_command: returned bogus response (%d)", m_rcmd);
		m->mf_state = SMFS_ERROR;	
		break;
	}

	free(response); 
	return;
}

/*
 *  	milter_abort_filter() -- abort SMFS_INMSG state to SMFS_DONE
 *			         (we don't need this filter anymore to
 *				 handle this message)
 *
 *	parameters:	m -- milter in SMFS_INMSG state
 *
 *	returns:	none. m changes state to SMFS_DONE on success
 *			(or SMFS_ERROR on error)
 */

static	void
milter_abort_filter (struct milter* m)
{
#ifdef	DEBUG
	syslog(LOG_INFO,"libci_milter: milter_abort_filter: abort filter %s",
			  m->mf_name);
#endif

	if (m->mf_sock < 0 ||
	    m->mf_state != SMFS_INMSG) {
		return;
	}

	(void) milter_write(m, SMFIC_ABORT, (char *) NULL, 0);
	if (m->mf_state != SMFS_ERROR)
		m->mf_state = SMFS_DONE;
}


/*
 *	milter_init() -- inititalize, open and negotiate milter
 *
 *	parameters: 	 filter -- connection string
 *
 *	returns:   	 milter negotiated and in SMFS_OPEN state or NULL on
 *			 failure. 
 */


struct milter*
milter_init(char *filter)
{
	struct	milter*	m;

	m = milter_new(NULL);
	m -> mf_conn = xstrdup(filter);

	if (milter_open(m) == MI_FAILURE)
		return(NULL);
	if (m->mf_sock < 0) {
		FATAL("internal error");
	}
	if (milter_negotiate(m) == MI_FAILURE) 
		return(NULL);
	return(m);
}

/*
 *	milter_dohelo() -- pass connect info and HELO command to milter
 *
 *	parameters:	m -- milter to write (should be in SMFS_OPEN state)
 *			e -- current envelope
 *			state -- result code from milter
 *			rladdr -- peer fqdn to pass to filter
 *			riaddr -- peer ip in dotted notation to pass to filter
 *			appname -- application name
 *
 *	returns:	MI_SUCCESS or MI_FAILURE
 *			milter and envelope states are changed according
 *			to situation.
 */

int
milter_dohelo(struct milter* m,ENVELOPE* e,char* state, 
	      char* rladdr,char* riaddr,char* appname)
{
	char ourhost[MAXHOSTNAMELEN];
	char remoteid[512];
	char **packet;
	unsigned int  pktlen = 0; 
	uint16_t port;
	char x;


	if ((SMFIP_NOCONNECT & m->mf_pflags) || (m->mf_state == SMFS_DONE) || 
	    (e->e_state &  (MS_RJ | MS_QU)))
		return(MI_SUCCESS);

	MILTER_CHECK_STATE(return MI_FAILURE);

#ifdef	DEBUG
	syslog(LOG_NOTICE,"DEBUG: milter_envelope.c: milter_dohelo(): start");
#endif

/* 	
 * 	Create and send CONNECT macro	
 */
	packet = xmalloc(sizeof(char*));
	*packet = NULL;

	milter_formstring(packet,&pktlen,"Cj");
	gethostname(ourhost,MAXHOSTNAMELEN);
	milter_formstring(packet,&pktlen,ourhost);

	milter_formstring(packet,&pktlen,"_");
	snprintf(remoteid,sizeof(remoteid)-1,"%.128s [%.64s]",rladdr,riaddr);
	milter_formstring(packet,&pktlen,remoteid);

	milter_formstring(packet,&pktlen,"{daemon_name}");
	milter_formstring(packet,&pktlen,appname);

	milter_formstring(packet,&pktlen,"{if_name}");
	milter_formstring(packet,&pktlen,ourhost);

	milter_write(m,SMFIC_MACRO,*packet,pktlen);

	if (m->mf_state == SMFS_ERROR) {
		e->e_state |= MS_ER;
		e->e_state |= ER_NE;
		return(MI_FAILURE);
	}

/*
 * 	Create and send CONNECT command
 */

	free(*packet);
	*packet = NULL;
	pktlen = 0;

	milter_formstring(packet,&pktlen,rladdr);
	x = SMFIA_UNKNOWN; 
	milter_formdata(packet,&pktlen,&x,1);
	port = 0;
	milter_formdata(packet,&pktlen,(char*) &port,sizeof(uint16_t));
	milter_send_command(m,SMFIC_CONNECT,*packet,pktlen,state); 
/*
 * 	HELO macros and command may go here
 */
	if (m->mf_state == SMFS_ERROR) {
		e->e_state |= MS_ER;
		e->e_state |= ER_NE;
		return(MI_FAILURE);
	}
        switch (*state) {
                case SMFIR_CONTINUE:
                case SMFIR_ACCEPT:
                        return(MI_SUCCESS);
                case SMFIR_REJECT:
                case SMFIR_QUARANTINE:
                case SMFIR_REPLYCODE:
                case SMFIR_DISCARD:
                        e->e_state|=(*state==SMFIR_QUARANTINE)?MS_QU:MS_RJ;
                        return(MI_SUCCESS);
                case SMFIR_TEMPFAIL:
			return(MI_FAILURE);
                default: 
                        syslog(LOG_ERR,"libci_milter: milter_dohelo: remote requested operation out of state or unsupported here, SMFIC_CONNECT:%c",*state);
                        e->e_state |= (MS_ER|ER_PO);
			return(MI_FAILURE);
        }
}

/*
 *	milter_dofrom() -- pass FROM command to milter
 *
 *	parameters:	m -- milter to write (should be in SMFS_OPEN state)
 *			e -- current envelope with from address
 *			state -- result code from milter
 *			riaddr -- peer ip in dotted notation to pass to filter
 *
 *	returns:	MI_SUCCESS or MI_FAILURE
 *			milter and envelope states are changed according
 *			to situation.
 */

int
milter_dofrom(struct milter* m,ENVELOPE* e,char* state,char* rladdr)
{
	char 		**packet;
	unsigned int  	pktlen = 0;
	char		*queueid; 

	if ((SMFIP_NOMAIL & m->mf_pflags) || (m->mf_state == SMFS_DONE) ||
	    (e->e_state &  (MS_RJ | MS_QU)))
		return(MI_SUCCESS);

	MILTER_CHECK_STATE(return MI_FAILURE);
/* 	
 * 	Create and send MAIL macro	
 */

#ifdef	DEBUG
	syslog(LOG_NOTICE,"DEBUG: milter_envelope.c: milter_dofrom(): start");
#endif
	packet = xmalloc(sizeof(char*));
	if (!(queueid = basename(e->e_bfn))) {
		e->e_state |= MS_ER;
		e->e_state |= ER_ME;
		return(MI_FAILURE);
	}
	*packet = NULL;

	milter_formstring(packet,&pktlen,"Mi");
	milter_formstring(packet,&pktlen,queueid);
	milter_formstring(packet,&pktlen,"{mail_mailer}");
	milter_formstring(packet,&pktlen,"local");
	milter_formstring(packet,&pktlen,"{mail_addr}");
	milter_formstring(packet,&pktlen,e->e_envfrom);
	milter_formstring(packet,&pktlen,"{mail_host}");
	milter_formstring(packet,&pktlen,rladdr);
	milter_write(m,SMFIC_MACRO,*packet,pktlen);
	if (m->mf_state == SMFS_ERROR) {
		e->e_state |= MS_ER;
		e->e_state |= ER_NE;
		return(MI_FAILURE);
	}
/*
 * 	Create and send MAIL command
 */
	free(*packet);
	*packet = NULL;
	pktlen = 0;

	milter_formstring(packet,&pktlen,e->e_envfrom);
	milter_send_command(m,SMFIC_MAIL,*packet,pktlen,state); 

	MILTER_CHECK_DONE_MSG();
        switch (*state) {
                case SMFIR_REJECT:
                case SMFIR_QUARANTINE:
                case SMFIR_REPLYCODE:
                case SMFIR_DISCARD:
                        e->e_state|=(*state==SMFIR_QUARANTINE)?MS_QU:MS_RJ;
                case SMFIR_CONTINUE:
                case SMFIR_ACCEPT:
                        return(MI_SUCCESS);
                case SMFIR_TEMPFAIL:
                        return(MI_FAILURE);
                default: 
                        syslog(LOG_ERR,"libci_milter: milter_dofrom: remote requested operation out of state or unsupported here, SMFIC_MAIL:%c",*state);
                        e->e_state |= (MS_ER|ER_PO);
                        return(MI_FAILURE);
        }
}

/*
 *	milter_dorcpt() -- pass RCPT commands to milter
 *
 *	parameters:	m -- milter to write (should be in SMFS_OPEN state)
 *			e -- current envelope with receipient list
 *			state -- result code from milter
 *			riaddr -- peer ip in dotted notation to pass to filter
 *
 *	returns:	MI_SUCCESS or MI_FAILURE
 *			milter and envelope states are changed according
 *			to situation.
 *			receipient list is updated to exclude rejects.
 */

int
milter_dorcpt(struct milter* m,ENVELOPE*  e,char* state,char* rladdr)
{
	char 		**packet = NULL;
	unsigned int  	pktlen = 0; 
	RCPT		*r, *rp;

	if ((SMFIP_NORCPT & m->mf_pflags) || (m->mf_state == SMFS_DONE) ||
	    (e->e_state &  (MS_RJ | MS_QU)))
		return(MI_SUCCESS);

	MILTER_CHECK_STATE(return MI_FAILURE);

	rp = NULL;
	r = e->e_rcpts;

	while (r) {
/*	
 *		Create and send RCPT macro
 */
#ifdef	DEBUG
		syslog(LOG_NOTICE,"DEBUG: milter_envelope.c: milter_dorcpt(): rcpt %s",r->r_value);
#endif
		packet = xmalloc(sizeof(char*));
		*packet = NULL;
		milter_formstring(packet,&pktlen,"R{rcpt_mailer}");
		milter_formstring(packet,&pktlen,"local");
		milter_formstring(packet,&pktlen,"{rcpt_addr}");
		milter_formstring(packet,&pktlen,r->r_value);
		milter_formstring(packet,&pktlen,"{rcpt_host}");
		milter_formstring(packet,&pktlen,rladdr);
		milter_write(m,SMFIC_MACRO,*packet,pktlen);
		if (m->mf_state == SMFS_ERROR) {
			e->e_state |= MS_ER;
			e->e_state |= ER_NE;
			return(MI_FAILURE);
		}
/*
 * 		Create and send RCPT command
 */
		free(*packet);
		*packet = NULL;
		pktlen = 0;
	
		milter_formstring(packet,&pktlen,r->r_value);
		milter_send_command(m,SMFIC_RCPT,*packet,pktlen, state); 
		MILTER_CHECK_DONE_MSG();
		switch (*state) {
			case SMFIR_REJECT:
			case SMFIR_QUARANTINE:
				e->e_state|=(*state==SMFIR_REJECT)?MS_RJ:MS_QU;
			case SMFIR_DELRCPT:
			case SMFIR_REPLYCODE:
				if (rp) {
					rp->r_link = r->r_link;
					free(r->r_value);
					free(r);
					r = rp->r_link;
					e->e_state |= OK_HC;
				} else {
					e->e_rcpts = r->r_link;
					free(r->r_value);
					free(r);
					if (e->e_rcpts)
						r = e->e_rcpts->r_link;
					e->e_state |= OK_HC;
				}
				break;
			case SMFIR_CONTINUE:
				rp = r;
				r = r->r_link;
				break;
			case SMFIR_ACCEPT:
				free(*packet);
				return(MI_SUCCESS);
			default: 
				syslog(LOG_ERR,"libci_milter: milter_dorcpt: remote requested operation out of state or unsupported here, SMFIC_RCPT:%c",*state);
				e->e_state |= (MS_ER|ER_PO);
				return(MI_FAILURE);
		}
	}
	free(*packet);
	if (!(e->e_rcpts))
		e->e_state |= MS_RJ;
	return(MI_SUCCESS);
					
}


/*
 *	milter_headers() -- pass message headers to milter
 *
 *	parameters:	m -- milter to write (should be in SMFS_OPEN state)
 *			e -- current envelope with headers
 *			state -- result code from milter
 *
 *	returns:	MI_SUCCESS or MI_FAILURE
 *			milter and envelope states are changed according
 *			to situation.
 *			headers are updated un filter's behalf.
 */

static void
milter_headers (struct milter* m,ENVELOPE* e,char* state)
{
	HDR *h;
	h = e->e_headers;

	for (; h != NULL; h = h->h_link)
	{
		char *buf;
		ssize_t s;

		/* don't send over deleted headers */
		if (h->h_value == NULL)
		{
			continue;
		}

		s = strlen(h->h_field) + 1 + strlen(h->h_value) + 1;
		if (s < 0)
			continue;
		buf = (char *) xmalloc(s);
		(void) snprintf(buf, s, "%s%c%s",
			h->h_field, '\0', h->h_value);

		/* send it over */
		milter_send_command(m, SMFIC_HEADER, buf, s, state);

		free(buf); 
		buf = NULL;
		if (m->mf_state == SMFS_ERROR ||
		    m->mf_state == SMFS_DONE ||
		    *state != SMFIR_CONTINUE)
			break;
	}
}

/*
 *	milter_body() -- pass message body to milter
 *			    this command just passes body to filter,
 *			    replacement actions are taken elseplace
 *
 *	parameters:	m -- milter to write (should be in SMFS_OPEN state)
 *			e -- current envelope with e->bfd pointing to
 *			     message body start
 *			state -- result code from milter
 *
 *	returns:	MI_SUCCESS or MI_FAILURE
 *			milter and envelope states are changed according
 *			to situation.
 */

static void
milter_body (struct milter* m,ENVELOPE*  e,char* state)
{
	int len;
	char bufchar = '\0';
	char prevchar = '\0';
	char c;
	char *bp;
	char buf[MILTER_CHUNK_SIZE];

	bp = buf;
	if(*state != SMFIR_CONTINUE)
		*state = SMFIR_CONTINUE;

	if(lseek(e->e_bfd, 0, SEEK_SET) == -1) {
		e->e_state |= MS_ER;
		e->e_state |= ER_IO;
		ERROR("failed to seek file");
	}
	while ( (len = read(e->e_bfd, &c, 1)) != 0)
	{
		if(len == -1) {
			ERROR("failed to read data");
			return;
		}
		/*  Change LF to CRLF */
		if (c == '\n')
		{
			/* Not a CRLF already? */
			if (prevchar != '\r')
			{
				/* Room for CR now? */
				if (bp + 2 > &buf[sizeof buf])
				{
					/* No room, buffer LF */
					bufchar = c;
					/* and send CR now */
					c = '\r';
				}
				else
				{
					/* Room to do it now */
					*bp++ = '\r';
					prevchar = '\r';
				}
			}
		}
		*bp++ = (char) c;
		prevchar = c;
		if (bp >= &buf[sizeof buf])
		{
			/* send chunk */
			milter_send_command(m, SMFIC_BODY, buf,
					       bp - buf, state);
			bp = buf;
			if (bufchar != '\0')
			{
				*bp++ = bufchar;
				bufchar = '\0';
				prevchar = bufchar;
			}
		}
		if (m->mf_state == SMFS_ERROR ||
		    m->mf_state == SMFS_DONE ||
		    *state != SMFIR_CONTINUE)
			break;
	}


	/* check for read errors */
	/* ????????????????????????????????????????????????????? */
	/* ????????????????????????????????????????????????????? */
	/* ????????????????????????????????????????????????????? */

	/* send last body chunk */
	if (bp > buf &&
	    m->mf_state != SMFS_ERROR &&
	    m->mf_state != SMFS_DONE &&
	    *state == SMFIR_CONTINUE)
	{
		/* send chunk */
		milter_send_command(m, SMFIC_BODY, buf, bp - buf, state);
		bp = buf;
	}
}

/*
 *  	Actions requested by filter from xxfi_eom
 *	called by milter_data. argument is raw response received from milter
 */

/*
 *	milter_fixlinefeed() -- Supplementary function to fix line termination
 *      in add/change header functions; according to milter specification,
 *	filters do terminate lines in multiline headers with lf, but we deal with
 *	cr/lf termination at this point. Warning: the function returns pointer to
 *	a static buffer.
 *
 *	Parameters:
 *		rawheader -- raw header lines received from filter
 *
 *	Returns:
 *		pointer to a static buffer containing lines with proper termination
 */

static char*
milter_fixlinefeed (char* rawheader)
{
	static char fixedheader[MAX_LONGHEADER];
	char *p,*p1;

	p = rawheader;
	p1 = fixedheader;
	while (*p && (p1 - fixedheader < (int) sizeof(fixedheader) - 3)) {
		*p1 = *p++;
		if (*p1 == '\n') {
			*(p1++) = '\r';
			*(p1++) = '\n';
		} else
			p1++;
	}
	*p1 = '\0';
	return (fixedheader);
}

/*
 *  	milter_addheader() -- Add the supplied header to the message
 *
 *	Parameters:
 *		response -- encoded form of header/value.
 *		rlen -- length of response.
 *		e -- current envelope.
 *
 *	Returns:
 *		none
 */

static void
milter_addheader (char* response,ssize_t rlen,ENVELOPE* e)
{
	char *val;
	HDR *h = NULL;
	ssize_t rrlen;

	/* sanity checks */

	if (response == NULL)
	{
		e->e_state |= (MS_ER|ER_PO);
		ERROR("NULL response");
		return;
	}

	rrlen =  strlen(response);

	if ((rlen < 2) || ((size_t) rrlen + 1 >= (size_t) rlen))
	{
		e->e_state |= (MS_ER|ER_PO);
		ERROR("protocol error");
		return;
	}

	/* another sanity check - check if integral resonse size matches */

	if (rrlen + strlen(response + rrlen + 1) + 2 != (size_t) rlen)
	{
		e->e_state |= (MS_ER|ER_PO);
		ERROR("protocol error");
		return;
	}

	/* Find separating NUL and fix line termination */
	val = milter_fixlinefeed(response + rrlen + 1);


	if (*response == '\0')
	{
		ERROR("empty field name");
		return;
	}

	/* add to e_msgsize */
	e->e_msgsize += rrlen + 2 + strlen(val);

	syslog(LOG_INFO, "libci_milter: milter_addheader: Milter add: %.512s value with %.512s",
			  response, val);
	h = make_header(response, val);
	add_header(e, h);
	e->e_state |= OK_HC;
}

/*
 *  	milter_insheader() -- Insert the supplied header
 *
 *	Parameters:
 *		response -- encoded form of header/value.
 *		rlen -- length of response.
 *		e -- current envelope.
 *
 *	Returns:
 *		none
 *
 *  	Notes:
 *  		Unlike milter_addheader(), this does not attempt to determine
 *  		if the header already exists in the envelope, even a
 *  		deleted version.  It just blindly inserts.
 */

static void
milter_insheader(char* response,ssize_t rlen,ENVELOPE* e)
{
	int  idx, i;
	char *field;
	char *val;
	HDR  *h,*newh,*last = NULL;
	ssize_t rrlen;

	/* sanity checks */

	if (response == NULL)
	{
		e->e_state |= (MS_ER|ER_PO);
		ERROR("NULL response");
		return;
	}

	rrlen =  strlen(response);

	if ((rlen < 2) || ((size_t) rrlen + 1 >= (size_t) rlen))
	{
		e->e_state |= (MS_ER|ER_PO);
		ERROR("protocol error");
		return;
	}

	/* decode */

	(void) memcpy((char *) &i, response, MILTER_LEN_BYTES);
	idx = ntohl(i);

	field = response + MILTER_LEN_BYTES;
	val = milter_fixlinefeed(field + strlen(field) + 1);

	/* another sanity check */

	if (MILTER_LEN_BYTES + strlen(field) + 1 +
	    strlen(field + strlen(field) + 1) + 1 != (size_t) rlen)
	{
		e->e_state |= (MS_ER|ER_PO);
		ERROR("protocol error");
		return;
	}

	if (*field == '\0')
	{
		ERROR("empty field name");
		return;
	}

	/* add to e_msgsize */

	e->e_msgsize += rrlen + 2 + strlen(val);

	syslog(LOG_INFO, "libci_milter: milter_insheader: Milter add: %.512s value with %.512s",
			  response, val);

	newh = make_header(response, val);

	for (h = e->e_headers; h != NULL && idx > 0 ; h = h->h_link, idx--)
		last = h;
	
	if ((!h) || (!e->e_headers)) {
		add_header(e,newh);
		e->e_state |= OK_HC;
		return;
	}

	newh->h_link = last->h_link;
	last->h_link = newh;
}

/*
 *  	milter_changeheader() -- Change the supplied header in the message
 *
 *	Parameters:
 *		response -- encoded form of header/index/value.
 *		rlen -- length of response.
 *		e -- current envelope.
 *
 *	Returns:
 *		none
 */

static void
milter_changeheader(char* response,ssize_t rlen,ENVELOPE* e)
{
	int i, idx;
	char *field, *val;
	HDR *h;


	/* sanity checks */
	if (response == NULL)
	{
		e->e_state |= (MS_ER|ER_PO);
		ERROR("NULL response");
		return;
	}

	if (rlen < 2 || strlen(response) + 1 >= (size_t) rlen)
	{
		e->e_state |= (MS_ER|ER_PO);
		ERROR("protocol error");
		return;
	}

	/* Find separating NUL */
	(void) memcpy((char *) &i, response, MILTER_LEN_BYTES);
	idx = ntohl(i);
	field = response + MILTER_LEN_BYTES;
	val = milter_fixlinefeed(field + strlen(field) + 1);

	/* another sanity check */
	if (MILTER_LEN_BYTES + strlen(field) + 1 +
	    strlen(field + strlen(field) + 1) + 1 != (size_t) rlen)
	{
		e->e_state |= (MS_ER|ER_PO);
		ERROR("protocol error");
		return;
	}

	if (*field == '\0')
	{
		ERROR("empty field name");
		return;
	}

	for (h = e->e_headers; h != NULL; h = h->h_link)
	{
		if (strncasecmp(h->h_field, field, MIN(MAX_HEADER_LEN,
						 MAX(strlen(h->h_field), strlen(field)))) == 0)
		{
			if (--idx <= 0)
				break;
		}
	}

	if (h == NULL)
	{
		if (*val == '\0')
		{
#ifndef SILENCIO
			syslog(LOG_INFO, "libci_milter: milter_changeheader: delete (noop) %.255s:", field);
#endif
		}
		else
		{
			/* treat modify value with no existing header as add */
#ifndef SILENCIO
			syslog(LOG_INFO, "libci_milter: milter_changeheader: add: %.255s:%.255s", field,val);
#endif
			h = make_header(field, val);
			add_header(e, h);
		}
		return;
	}

	if (*val == '\0')
	{
#ifndef SILENCIO
		syslog(LOG_INFO, "libci_milter: milter_changeheader: delete %.64s: %.255s\n",
			   field,
			   h->h_value == NULL ? "<NULL>" : h->h_value);
#endif
	}
	else
	{
#ifndef SILENCIO
		syslog(LOG_INFO, "libci_milter: milter_changeheader: change %.64s: from %.64s to %.64s\n",
			   field,
			   h->h_value == NULL ? "<NULL>" : h->h_value,
			   val);
#endif
	}

	if (h->h_value)
		free(h->h_value);

	h->h_value = NULL;

	if (*val)
	{
		h->h_value = xstrdup(val);
		e->e_msgsize += strlen(h->h_value);
	}
	e->e_state |= OK_HC;
}

/*
 *  	milter_addrcpt() -- Add the supplied recipient to the message
 *
 *	Parameters:
 *		response -- encoded form of recipient address.
 *		rlen -- length of response.
 *		e -- current envelope.
 *
 *	Returns:
 *		none
 */

static void
milter_addrcpt (char* response,ssize_t rlen,ENVELOPE* e)
{
	RCPT *r;

	/* sanity checks */
	if (response == NULL)
	{
		e->e_state |= (MS_ER|ER_PO);
		ERROR("NULL response");
		return;
	}

	if (*response == '\0' ||
	    strlen(response) + 1 != (size_t) rlen)
	{
		e->e_state |= (MS_ER|ER_PO);
		syslog(LOG_ERR,"libci_milter: milter_addrcpt: protocol error (total len %d != rlen %d)\n",
			   (int) strlen(response), (int) (rlen - 1));
		return;
	}

#ifndef SILENCIO
	syslog(LOG_INFO, "libci_milter: milter_addrcpt: add: %.255s", response);
#endif

	for (r = e->e_rcpts; r != NULL; r = r->r_link)
	{
		if(strncasecmp(r->r_value, response, MIN(MAX_HEADER_LEN,
						 MAX(strlen(r->r_value), strlen(response)))) == 0) {
			syslog(LOG_INFO, "libci_milter: milter_addrcpt: RCPT: %.255s already exist, skipping", response);
			return;
		}
	}

	r->r_link = (RCPT *) xmalloc (sizeof (RCPT));
	r=r->r_link;
	r->r_link = NULL;
	r->r_value = xstrdup(response);
	e->e_state |= OK_HC;
	return;
}


/*
 *  	milter_delrcpt() -- delete the supplied recipient from the message
 *
 *	Parameters:
 *		response -- encoded form of recipient address.
 *		rlen -- length of response.
 *		e -- current envelope.
 *
 *	Returns:
 *		none
 */

static void
milter_delrcpt (char* response,ssize_t rlen,ENVELOPE* e)
{
	RCPT	*r, *r_prev;

	/* sanity checks */
	if (response == NULL)
	{
		e->e_state |= (MS_ER|ER_PO);
		ERROR("NULL response");
		return;
	}

	if (*response == '\0' ||
	    strlen(response) + 1 != (size_t) rlen)
	{
		e->e_state |= (MS_ER|ER_PO);
		ERROR("protocol error");
		return;
	}
	syslog(LOG_INFO,"libci_milter: milter_delrcpt: %.255s\n", response);
	r_prev = e->e_rcpts;
	for (r = e->e_rcpts; r != NULL; r = r->r_link)
	{
	 	if(strncasecmp(r->r_value, response, MIN(MAX_HEADER_LEN,
						 MAX(strlen(r->r_value), strlen(response)))) == 0) {
			if(r_prev == NULL) {
				e->e_state |= (MS_ER|ER_ME);
				ERROR("internal error");
				return;
			}

			if(r->r_link != NULL)
				r_prev->r_link = r->r_link;
			else
				r_prev->r_link = NULL;

			if(r->r_value != NULL) {
				free(r->r_value);
				r->r_value = NULL;
			}
			if(r != NULL) {
				free(r);
				r = NULL;
			}

#ifndef SILENCIO
			syslog(LOG_INFO, "libci_milter: milter_delrcpt: RCPT: %.255s deleted", response);
#endif
			e->e_state |= OK_HC;
			return;
		}
		r_prev = r;
	}
	e->e_state |= (MS_ER|ER_ME);
	syslog(LOG_ERR, "libci_milter: milter_delrcpt: RCPT: failed to delete recepient %.255s ", response);
	return;
}


/*
 *  	milter_replbody() -- replace the current data file with new body
 *			   the function takes data chunk and appends it
 *			   to new body (or creates new body when called
 *			   the first time)
 *
 *	Parameters:
 *		response -- encoded form of new body.
 *		rlen -- length of response.
 *		newfilter -- if first time called by a new filter
 *		e -- current envelope.
 *
 *	Returns:
 *		MI_SUCCESS or MI_FAILURE
 */

static int
milter_replbody (char* response,ssize_t rlen,bool newfilter,ENVELOPE* e)
{
	static char prevchar;
	static int len_chk;
	int i;
	char buf[MAX_HEADER_LEN];
	bzero(buf, sizeof(buf));

	/* If a new filter, reset previous character and truncate data file */
	if (newfilter)
	{
		if(close(e->e_bfd) == -1) {
			e->e_state |= MS_ER;
			e->e_state |= ER_IO;
			syslog(LOG_ERR, "libci_milter: milter_replbody: failed to close file %.255s fd %d %m", e->e_bfn, e->e_bfd);
			return MI_FAILURE;
		}
		e->e_bfd = -1;
		if((e->e_bfd = open(e->e_bfn, O_RDWR)) == -1) {
			e->e_state |= MS_ER;
			e->e_state |= ER_IO;
			syslog(LOG_ERR, "libci_milter: milter_replbody: failed to open file %.255s with O_RDWR option(s) %m", e->e_bfn);
			return MI_FAILURE;
		}
		if(lseek(e->e_bfd, 0, SEEK_SET) == -1) {
			e->e_state |= MS_ER;
			e->e_state |= ER_IO;
			syslog(LOG_ERR, "libci_milter: milter_replbody: failed to seek file %m");
			return MI_FAILURE;
		}
		e->e_state |= OK_BC;
		/* Reset static variables*/
		prevchar = '\000';
		len_chk  = 0;

	}

	if (response == NULL)
	{
		/* Flush the buffered '\r' */
		if (prevchar == '\r')
		{
			if(write(e->e_bfd, "\r\n", 2) != 2) {
				e->e_state |= MS_ER;
				e->e_state |= ER_IO;
				syslog(LOG_ERR, "libci_milter: milter_replbody: failed to write to the file %.255s %m", e->e_bfn);
				return MI_FAILURE;
			}
			e->e_msgsize+=2;
		}
		return MI_SUCCESS;
	}

	for(i = 0; i < rlen; i++, len_chk++) {
		int blksize;

		/* as said in rfc, here MUST be crlf so let's put it anyway */
		if((len_chk >= MAX_HEADER_LEN - 1)
		    && ((i + 1) < rlen)
		    && (response[i] != 0x0d)
		    && (response[i + 1] != 0x0a	)) {

			if(write(e->e_bfd, "\r\n", 2) != 2) {
				e->e_state |= MS_ER;
				e->e_state |= ER_IO;
				syslog(LOG_ERR, "libci_milter: milter_replbody: failed to write to the file %.255s %m", e->e_bfn);
				return MI_FAILURE;
			}
			e->e_msgsize+= 2;
			prevchar = (char)0;
			len_chk  = 1;
		}


		/* Buffered char from last chunk */
		if (i == 0 && prevchar == 0x0d)
		{
			prevchar = (char) 0;
			len_chk  = 1;
			if(write(e->e_bfd, "\r\n", 2) != 2) {
				e->e_state |= MS_ER;
				e->e_state |= ER_IO;
				syslog(LOG_ERR, "libci_milter: milter_replbody: failed to write to the file %.255s %m", e->e_bfn);
				return MI_FAILURE;
			}
			e->e_msgsize+=2;

			if (response[i] == 0x0a) {
				len_chk = 0;
				continue;
			}
		}

		for (blksize = 0; (blksize + i) < (rlen - 1) ; blksize++) {
			if ((response[i+blksize] == 0x0d) || (response[i+blksize] == 0x0a))
				break;
			if (len_chk + blksize >= MAX_HEADER_LEN -2)
				break;
		}
		if (blksize) {
			if(write(e->e_bfd, &response[i], blksize) != blksize) {
				e->e_state |= MS_ER;
				e->e_state |= ER_IO;
				syslog(LOG_ERR, "libci_milter: milter_replbody: failed to write to the file %.255s %m", e->e_bfn);
				return MI_FAILURE;
			}
			i += blksize - 1;
			len_chk += blksize - 1;
			e->e_msgsize += blksize;
			continue;
		}

		if (response[i] == 0x0d)
		{
			prevchar = 0x0d;
			if ((i + 1) >= rlen)
				continue;
			else {
				if (response[i + 1] != 0x0a) {
					prevchar = (char)0;
					len_chk  = 0;
					if(write(e->e_bfd, "\r\n", 2) != 2) {
						e->e_state |= MS_ER;
						e->e_state |= ER_IO;
						syslog(LOG_ERR, "libci_milter: milter_replbody: failed to write to the file %.255s %m", e->e_bfn);
						return MI_FAILURE;
					}
					e->e_msgsize+=2;
					continue;
				}
			}
		}

		if(response[i] == 0x0a) {
			if(prevchar != 0x0d){
				if(write(e->e_bfd, "\r\n", 2) != 2) {
					e->e_state |= MS_ER;
					e->e_state |= ER_IO;
					syslog(LOG_ERR, "libci_milter: milter_replbody: failed to write to the file %.255s %m", e->e_bfn);
					return MI_FAILURE;
				}
				prevchar = (char)0;
				len_chk  = 0;
				e->e_msgsize+=2;
				continue;
			}
			prevchar = (char)0;
			len_chk  = 0;
		}

		if(write(e->e_bfd, &response[i], 1) != 1) {
			e->e_state |= MS_ER;
			e->e_state |= ER_IO;
			syslog(LOG_ERR, "libci_milter: milter_replbody: failed to write to the file %.255s %m", e->e_bfn);
			return MI_FAILURE;
		}

		e->e_msgsize++;
	}
	return MI_SUCCESS;
}

/*
 *  	milter_quit_filter() -- close down a single filter
 *
 *	Parameters:
 *		m -- milter structure of filter to close down.
 *		e -- current envelope.
 *
 *	Returns:
 *		none
 */

int
milter_quit_filter (struct milter* m)
{

	/* Never replace error state */
	if (m->mf_state == SMFS_ERROR)
		return(MI_FAILURE);

	if (m->mf_sock < 0 ||
	    m->mf_state == SMFS_CLOSED ||
	    m->mf_state == SMFS_READY)
	{
		m->mf_sock = -1;
		m->mf_state = SMFS_CLOSED;
		return(MI_SUCCESS);
	}

	(void) milter_write(m, SMFIC_QUIT, (char *) NULL, 0);
	if (m->mf_sock >= 0)
	{
		(void) close(m->mf_sock);
		m->mf_sock = -1;
	}
	if (m->mf_state == SMFS_ERROR)
		return(MI_FAILURE);

	m->mf_state = SMFS_CLOSED;
	return(MI_SUCCESS);
}

/*
 *  	milter_data() -- send message headers/body and gather final results
 *
 *	Parameters:
 *		e -- current envelope.
 *		state -- return state from response.
 *
 *	Returns:
 *		response string (may be NULL)
 *
 *	Side effects:
 *		- Uses milter_replbody to perform actual body change
 *		- Can call the various milter action routines to
 *		  modify the envelope or message.
 */

# define MILTER_CHECK_RESULTS() \
	if (*state == SMFIR_ACCEPT || \
	    m->mf_state == SMFS_DONE || \
	    m->mf_state == SMFS_ERROR) \
	{ \
		if (m->mf_state != SMFS_ERROR) \
			m->mf_state = SMFS_DONE; \
		return MI_SUCCESS;	\
	} \
	if (*state != SMFIR_CONTINUE) \
	{ \
		m->mf_state = SMFS_DONE; \
		goto finishup; \
	}

int
milter_data (struct milter* m,ENVELOPE* e,char* state)
{
	bool replbody = false;		/* milter_replbody() called? */
	bool replfailed = false;	/* milter_replbody() failed? */
	bool newfilter;			/* reset on each new filter */
	char m_rcmd; 
	char *response = NULL;

	time_t eomsent;
	ssize_t rlen;
	*state = SMFIR_CONTINUE;


	if ((m->mf_state == SMFS_DONE) || (e->e_state &  (MS_RJ | MS_QU)))
		return(MI_SUCCESS);

	if (*state != SMFIR_CONTINUE && *state != SMFIR_ACCEPT) {
		/*
		**  A previous filter has dealt with the message,
		**  safe to stop processing the filters.
		*/
		ERROR("bad state");
		e->e_state |= (MS_ER|ER_ME);
		return(MI_FAILURE);
	}

		/* Now reset state for later evaluation */
	*state = SMFIR_CONTINUE;
	newfilter = true;

	/* previous problem? */
	if (m->mf_state == SMFS_ERROR)
	{
		return(MI_FAILURE);
	}

	/* sanity checks */
	if (m->mf_sock < 0 ||
	    (m->mf_state != SMFS_OPEN && m->mf_state != SMFS_INMSG))
		goto finishup;

	m->mf_state = SMFS_INMSG;

	/* check if filter wants the headers */
	if (!(SMFIP_NOHDRS & m->mf_pflags)) {
		milter_headers(m, e, state);
		MILTER_CHECK_RESULTS();
	}

	/* check if filter wants EOH */
	if (!(SMFIP_NOEOH & m->mf_pflags)) {
	/* send it over */
		milter_send_command(m, SMFIC_EOH, NULL, 0, state);
		MILTER_CHECK_RESULTS();
	}

	/* check if filter wants the body */
	if (!(SMFIP_NOBODY & m->mf_pflags) && e->e_bfd != -1) {
		milter_body(m, e, state);
		MILTER_CHECK_RESULTS();
	}

	/* send the final body chunk */
	milter_write(m, SMFIC_BODYEOB, NULL, 0);
	/* Get time EOM sent for timeout */
	eomsent = now();

	/* deal with the possibility of multiple responses */
	while (*state == SMFIR_CONTINUE) {
	/* Check total timeout from EOM to final ACK/NAK */
		if (m->mf_timeout > 0 &&
		    now() - eomsent >= m->mf_timeout) {
				ERROR("EOM ACK/NAK timeout");
			*state = SMFIR_TEMPFAIL;
			m->mf_state = SMFS_ERROR;
			e->e_state |= MS_ER;
			e->e_state |= ER_NE;
			break;
		}
		response = milter_read(m, &m_rcmd, &rlen);
		if (m->mf_state == SMFS_ERROR) {
			e->e_state |= (MS_ER|ER_NE);
			break;
		}

		switch (m_rcmd) {
		  case SMFIR_REPLYCODE:
			syslog(LOG_INFO,"libci_milter: milter_data: milter=%.128s, reject=%.128s",  m->mf_name, response);
			*state = m_rcmd;
			m->mf_state = SMFS_DONE;
			e->e_state |= MS_RJ;
			break;

		  case SMFIR_REJECT: /* log msg at end of function */
		  case SMFIR_DISCARD:
			*state = m_rcmd;
			m->mf_state = SMFS_DONE;
			e->e_state |= MS_RJ;
			break;

		  case SMFIR_TEMPFAIL:
			*state = m_rcmd;
			m->mf_state = SMFS_ERROR;
			e->e_state |= MS_ER;
			break;

		  case SMFIR_QUARANTINE:
			*state = m_rcmd;
			m->mf_state = SMFS_DONE;
			e->e_state |= MS_QU;
			break;

		  case SMFIR_CONTINUE:
		  case SMFIR_ACCEPT:
			/* this filter is done with message */
			if (replfailed)
				*state = SMFIR_TEMPFAIL;
			else
				*state = SMFIR_ACCEPT;
			m->mf_state = SMFS_DONE;
			break;

		  case SMFIR_PROGRESS:
			eomsent = now();
			break;

		  case SMFIR_ADDHEADER:
			if (!(SMFIF_ADDHDRS & m->mf_fflags)) {
#ifndef PARANOID
				syslog(LOG_WARNING,
					"libci_milter: milter_data: lied about adding headers, honoring request anyway");
#else
				syslog(LOG_ERR,
					"libci_milter: milter_data: lied about adding headers, aborting");
				*state = SMFIR_TEMPFAIL;
				m->mf_state = SMFS_ERROR;
				e->e_state |= MS_ER;
				e->e_state |= ER_PO;
				break;
#endif
			}
			(void) milter_addheader(response, rlen, e);
			break;

		  case SMFIR_INSHEADER:
			if (!(SMFIF_ADDHDRS & m->mf_fflags)) {
#ifndef PARANOID
				syslog(LOG_WARNING,
					"libci_milter: milter_data: lied about adding headers, honoring request anyway");
#else
				syslog(LOG_ERR,
					"libci_milter: milter_data: lied about adding headers, aborting");
				*state = SMFIR_TEMPFAIL;
				m->mf_state = SMFS_ERROR;
				e->e_state |= MS_ER;
				e->e_state |= ER_PO;
				break;
#endif
			}
			(void) milter_insheader(response, rlen, e);
			break;

		  case SMFIR_CHGHEADER:
			if (!(SMFIF_CHGHDRS & m->mf_fflags)) {
#ifndef PARANOID
				syslog(LOG_WARNING,
					"libci_milter: milter_data: lied about changing headers, honoring request anyway");
#else
				syslog(LOG_ERR,
					"libci_milter: milter_data: lied about changing headers, aborting");
				*state = SMFIR_TEMPFAIL;
				m->mf_state = SMFS_ERROR;
				e->e_state |= MS_ER;
				e->e_state |= ER_PO;
				break;
#endif
			}
			(void)milter_changeheader(response, rlen, e);
			break;

		  case SMFIR_ADDRCPT:
			if (!(SMFIF_ADDRCPT & m->mf_fflags)) {
#ifndef PARANOID
				syslog(LOG_WARNING,
					"libci_milter: milter_data: lied about adding recipients, honoring request anyway");
#else
				syslog(LOG_ERR,
					"libci_milter: milter_data: lied about adding receipients, aborting");
				*state = SMFIR_TEMPFAIL;
				m->mf_state = SMFS_ERROR;
				e->e_state |= MS_ER;
				e->e_state |= ER_PO;
				break;
#endif
			}
			(void)milter_addrcpt(response, rlen, e);
			break;

		  case SMFIR_DELRCPT:
			if (!(SMFIF_DELRCPT & m->mf_fflags)) {
#ifndef PARANOID
				syslog(LOG_WARNING,
					"libci_milter: milter_data: lied about removing recipients, honoring request anyway");
#else
				syslog(LOG_ERR,
					"libci_milter: milter_data: lied about removing receipients, aborting");
				*state = SMFIR_TEMPFAIL;
				m->mf_state = SMFS_ERROR;
				e->e_state |= MS_ER;
				e->e_state |= ER_PO;
				break;
#endif
			}
			milter_delrcpt(response, rlen, e);
			break;

		  case SMFIR_REPLBODY:
			if (!(SMFIF_MODBODY & m->mf_fflags)) {
				syslog(LOG_ERR,
					"libci_milter: milter_data: lied about replacing body, rejecting request and tempfailing message");
				replfailed = true;
				break;
			}

			/* already failed in attempt */
			if (replfailed)
				break;

			if (milter_replbody(response, rlen, newfilter, e) < 0)
				replfailed = true;
			newfilter = false;
			replbody = true;
			break;

		  default:
			/* Invalid response to command */
			syslog(LOG_ERR,
				"libci_milter: milter_data: returned bogus response %c", m_rcmd);
			e->e_state |= (MS_ER|ER_PO);
			break;
		}
		if (m_rcmd != SMFIR_REPLYCODE && response != NULL) {
			free(response); 
			response = NULL;
		}
		if (m->mf_state == SMFS_ERROR)
			break;
	}

	if (replbody && !replfailed) {
		/* flush possible buffered character */
		milter_replbody(NULL, 0, !replbody, e);
		replbody = false;
	}

finishup:
	/* leave things in the expected state if we touched it */
	if (replfailed)
	{
		if (*state == SMFIR_CONTINUE ||
		    *state == SMFIR_ACCEPT)
		{
			*state = SMFIR_TEMPFAIL;
			if(response) {
				free(response);
				response = NULL;
			}
		}

		e->e_state |= MS_ER;
		return(MI_FAILURE);
	}
	MILTER_CHECK_DONE_MSG();
        if ((*state == SMFIR_TEMPFAIL) || (e->e_state & MS_ER)) {
		e->e_state |= MS_ER;
		syslog(LOG_INFO, "libci_milter: milter_data: error");
		return(MI_FAILURE);
	}
	if ((*state == SMFIR_REJECT) || (e->e_state & MS_RJ)) { 
		e->e_state |= MS_RJ;
		syslog(LOG_INFO, "libci_milter: milter_data: reject");
	}
	if ((*state == SMFIR_QUARANTINE) || (e->e_state & MS_QU)) { 
		e->e_state |= MS_QU;
		syslog(LOG_INFO, "libci_milter: milter_data: quarantine");
	}
	return(MI_SUCCESS);
}
