/*
 *	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 <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <ctype.h>
#include <fcntl.h>
#include <strings.h>
#include "milter_log.h"
#include "base64.h"

extern void* xmalloc(size_t);
extern char* xstrdup(const char*);
extern struct milter* milter_init(char*);
extern int milter_quit_filter(struct milter*);
extern time_t sotimeout(int);
extern void addlist(char*,char***);
extern void freelist(char***);

static void
del_hdrs (ENVELOPE* env)
{
	HDR *h, *p;
	int i = 0;

	h = env->e_headers;

	while(h != NULL && i < 100)
	{
		p = h;
		free(p->h_value);
		p->h_value = NULL;
		free(p->h_field);
		p->h_field = NULL;
		h = h->h_link;
		free(p);
		p = NULL;
		++i;
	}
}

static char*
get_hdrcrlf_line (int fd)
{
	int len = 0;
	char ch;
	char buf[MAX_HEADER_LEN]; /* Max header leght 998 + 2 ("\r\n") */
	int ret;
	bzero(buf, sizeof (buf));

	while ((ret = (read(fd, &ch, 1))) > 0)
	{
		buf[len] = ch;
		if(ch == LF)
		{
			/* Is it CRLF? */
			if (len > 1 && buf[len - 1] == CR)
				break;
			buf[len++] = CR;
			buf[len] = LF;
			break;
		}
		if (len <= MAX_HEADER_LEN - 2)
			len++;
	}
	if (ret == -1) {
		ERROR("failed to read from file");
		return NULL;
	}
	if (ret == 0) /* EOF */
		return NULL;
	buf[++len] = '\0';
	return xstrdup(buf);
}

HDR *
milter_read_header (int fd)
{
	HDR  *h = NULL;
	int len = 0;
	int totl_len = 0;
	register char *p;
	char *p_str;
	char *buf;
	char wsp;

	if ((p_str = get_hdrcrlf_line(fd)) == NULL)
	{
		ERROR("read_header: failed");
		return NULL;
	}
	while (isspace(*p_str) && ((*p_str != CR) && (*p_str != LF)));
	p = strchr(p_str, ':');
	len = strlen(p_str);
	if (p == NULL)
		return NULL;
	if ((strlen(p) - 2) <= 0)
	{
#ifdef	DEBUG
		syslog(LOG_WARNING, "libci_milter: read_header: warning: no value");
#endif
		return NULL;
	}
	*p++ = '\0';
	h = (HDR *)xmalloc(sizeof(HDR));

	h->h_field = xstrdup(p_str);

	totl_len = strlen(p);

	/* Check for WSP (RFC2822 2.2.3) */
	buf = xstrdup(p);
	(void) free(p_str);
	p_str = NULL;
	p = NULL;

	while(read(fd, &wsp, 1) > 0) {
		if (wsp == 0x20 || wsp == '\t') {
			if(lseek(fd, -1 , SEEK_CUR) == -1) {
				ERROR("failed to seek file");
				return NULL;
			}
			if ((p_str = get_hdrcrlf_line(fd)) == NULL) {
				ERROR("read long value failed");
				return NULL;
			}
			len = strlen(p_str);
			if(len + totl_len + 1< MAX_LONGHEADER) {
				buf = realloc(buf, len + totl_len + 1);
				(void) memmove(buf + totl_len, p_str, len);
				totl_len += len;
			} else {
				/* This may mess truncated headers, but those
				   are already broken, so who cares?        */

				syslog(LOG_ERR,"securityalert: header field exceeds builtin limit, %u", MAX_LONGHEADER);
				continue;
			}
			(void) free(p_str);
			p_str = NULL;
		} else {
			/* Returning descriptor to old place */
			if(lseek(fd, -1 , SEEK_CUR) == -1) {
				ERROR("failed to seek file");
				return NULL;
			}
			break;

		}
	}
	buf[totl_len] = '\0';
	p = buf;
	while (isspace(*p))
		p++;
	if ((len = strlen(p)) > 2) {
		h->h_value = (char *)xmalloc(len);
		memmove(h->h_value, p, len - 1);
		h->h_value[len-2] = '\0';
	} else {
		h->h_value = xstrdup(" ");
	}
	free(buf);
	buf = NULL;
	free(p_str);
	p_str = NULL;
	return h;
}

void
add_header (ENVELOPE* e,HDR* h)
{
	HDR  *c_h = e->e_headers;

	if(c_h == NULL)/* list is empty? */
	{
		e->e_headers = h;
		h->h_link = NULL;
	} else {
        	while(c_h->h_link != NULL)
			c_h = c_h->h_link;
		c_h->h_link = h;
		h->h_link = NULL;
	}
	e->e_msgsize += strlen(h->h_field) + strlen(h->h_value);
}

HDR *
make_header (const char* field,const char* value)
{
	HDR* h;

	h = (HDR *)xmalloc(sizeof(HDR));
	h->h_field = xstrdup(field);
	h->h_value = xstrdup(value);
	return h;
}


ENVELOPE*
milter_envelope_new()
{
	ENVELOPE *env;

	env = (ENVELOPE *) xmalloc(sizeof (ENVELOPE));
	env->e_envfrom = NULL;
	env->e_rcpts = NULL;
	env->e_headers = NULL;
	env->e_bfn = NULL;
	env->e_state =(int) 0;
	env->e_bfd = (int) -1;
	env->e_msgsize = (ssize_t)0;
	return(env);
}

void
free_envelope (ENVELOPE* env)
{
	if(env == NULL)
		return;
	(void)del_hdrs(env);
	if(env->e_bfn != NULL) {
		unlink(env->e_bfn);
		(void)free(env->e_bfn);
		env->e_bfn = NULL;
	}
	(void)free(env);
	env = NULL;
	return;

}

void
free_milter (struct milter* m)
{
	if(m == NULL)
		return;
	free(m->mf_name);
	m->mf_name = NULL;
	m->mf_fvers	=0;
	m->mf_fflags	=0;
	m->mf_pflags	=0;
	free(m->mf_conn);
	m->mf_conn = NULL;
	close(m->mf_sock);
	m->mf_sock	= -1;
	m->mf_state	='\0';
	m->mf_timeout	=0;
	free(m);
	m = NULL;
	return;
}

int
milter_rebuild_msg (ENVELOPE* e,char* fn)
{
	char buf[MILTER_CHUNK_SIZE];
	RCPT *r;
	HDR  *h;
	int fd;
	ssize_t len;

	bzero(buf, sizeof(buf));

#ifdef DEBUG
	snprintf(buf, strlen(fn) + 5, "%.255s", fn);
#else
	snprintf(buf, strlen(fn) + 1, "%.255s", fn);
#endif

	if((fd = open(buf, O_RDWR|O_TRUNC|O_CREAT)) == -1) {
			ERROR("failed to reopen message file");
			e->e_state |= MS_ER;
			e->e_state |= ER_IO;
			return MI_FAILURE;
	}
	(void) bzero(buf, sizeof(buf));
	r = e->e_rcpts;

	if (r == NULL)
		return MI_FAILURE;

        for(h = e->e_headers; h != NULL; h = h->h_link) {
		if (!h->h_value) 
			continue;
		if ((write(fd,h->h_field,strlen(h->h_field)) 
			!= (int) strlen(h->h_field)) ||
		     (write(fd,": ",2) != 2)   ||
		     (write(fd,h->h_value,strlen(h->h_value))
			!= (int) strlen(h->h_value)) ||
		     (write(fd,"\r\n",2) != 2)  ) {

			e->e_state |= MS_ER;
			e->e_state |= ER_IO;
			return MI_FAILURE;
		}
		(void) bzero (buf, sizeof(buf));
        }

	if(write(fd, "\r\n", 2)!=2) {
			e->e_state |= MS_ER;
			e->e_state |= ER_IO;
			return MI_FAILURE;
	}

	(void) bzero(buf, sizeof(buf));

	if(lseek(e->e_bfd, 0, SEEK_SET) == -1) {
		e->e_state |= MS_ER;
		e->e_state |= ER_IO;
		ERROR("failed to seek message body");
		return MI_FAILURE;
	}

	while ((len = read(e->e_bfd, buf, sizeof(buf))) != 0 ) {
		if (len == -1) {
			e->e_state |= MS_ER;
			e->e_state |= ER_IO;
			ERROR("failed to read message body");
			return MI_FAILURE;
		}

		if(write(fd, buf, len)!=len) {
			e->e_state |= MS_ER;
			e->e_state |= ER_IO;
			ERROR("failed to write message file");
			return MI_FAILURE;
		}
		(void)bzero(buf, sizeof(buf));
	}

	/* Are where CR and LF at the end? */
	if(lseek(e->e_bfd, -2, SEEK_CUR) == -1) {
/*
 *		Skip if body is empty
 */
		if (errno == EINVAL)
			goto finish;

		e->e_state |= MS_ER;
		e->e_state |= ER_IO;
		ERROR("libci_milter: rebuild_msg: failed to seek file");
		return MI_FAILURE;
	}

	if((len = read(e->e_bfd, buf, sizeof(buf))) != 2) {
		e->e_state |= MS_ER;
		e->e_state |= ER_IO;
		ERROR("failed to read message body");
		return MI_FAILURE;
	} else {
		if(buf[0] != 0x0d && buf[1] != 0x0a)
		    if(write(fd, "\r\n", 2)!=2) {
			e->e_state |= MS_ER;
			e->e_state |= ER_IO;
			ERROR("failed to write message file");
			return MI_FAILURE;
		}
	}
finish:
	close(fd);
	fd = -1;
	close(e->e_bfd);
	e->e_bfd = -1;
	return MI_SUCCESS;
}

int
milter_inspect_mail(char *appname, char *rladdr, char *riaddr,
		    char *mconn, char *f_name, char *from, char ***to)
{
	struct milter *m;
	ENVELOPE *env = NULL;
	char *state = NULL;
	int old_to;
	int fd,bfd;
	char **rcp;
	int estate;
	RCPT *r = NULL;
	HDR *h;
	char bodyfname[] = "/tmp/tempbodyXXXXXXXXXXX";
	ssize_t len;
	char buf[MAX_HEADER_LEN];

	old_to = sotimeout(300);

	state = (char *)xmalloc(sizeof *state);
	*state = SMFIR_CONTINUE;

	if (!(m = milter_init(mconn))) {
		(void) sotimeout (old_to);
		return(MS_ER|ER_NE); 
	}

	env = milter_envelope_new();

	if ((fd = open(f_name, O_RDWR)) == -1) {
		ERROR("cannot open quarantine file");
		free_envelope(env);
		free_milter(m);
		(void) sotimeout (old_to);
		return (MS_ER|ER_IO);
	}
	env->e_envfrom = (char*) xstrdup(from);


	for (rcp = *to; *rcp; rcp++) {
		if(!env->e_rcpts) {
			env->e_rcpts = (RCPT *) xmalloc (sizeof (RCPT));
			r = env->e_rcpts;
		} else {
			if (r) {
				r->r_link =  (RCPT *) xmalloc (sizeof (RCPT));
				r = r->r_link;
			} else {
				FATAL("internal error - envelope not empty");
			}
		}
		r->r_link = NULL;
		r->r_value = (char*) xstrdup(*rcp);
	}

	while ((h = milter_read_header(fd)) != NULL) {
		(void)add_header(env, h);
	}

	/* Descriptor fd now must be pointed to begining of the body */

/*
 *	XXXX
 */
	if ((bfd = mkstemp(bodyfname)) == -1) {
		ERROR("cannot create temporary file");
		free_envelope(env);
		free_milter(m);
		close(fd);
		(void) sotimeout (old_to);
		return (MS_ER|ER_IO);
	}

	while ((len = read(fd, buf, sizeof(buf))) != 0 ) {
		if ((len == -1) || (write(bfd, buf, len) != len)) {
			ERROR("input/output error");
			free_envelope(env);
			free_milter(m);
			(void) sotimeout (old_to);
			return (MS_ER|ER_IO);
		}
		bzero(buf, sizeof(buf));
	}

	close(fd);

	if(lseek(bfd, 0, SEEK_SET) == -1) {
		ERROR("failed to seek temporary file");
		free_envelope(env);
		free_milter(m);
		(void) sotimeout (old_to);
		return (MS_ER|ER_IO);
	}

	env->e_bfd = bfd;
        env->e_bfn = xstrdup(bodyfname);

	if ((milter_dohelo(m,env,state,
			   rladdr,riaddr,appname) == MI_FAILURE) ||
	    (milter_dofrom(m,env,state,rladdr)	    == MI_FAILURE) || 
	    (milter_dorcpt(m,env,state,rladdr)	    == MI_FAILURE) ||
	    (milter_data(m,env,state) 			    == MI_FAILURE) ||
	    (milter_quit_filter(m)		    	    == MI_FAILURE)  ) {
		estate = env->e_state;
		free_envelope(env);
		free_milter(m);
		(void) sotimeout (old_to);
		return (MS_ER|estate);
	}
	if(milter_rebuild_msg (env, f_name) == MI_FAILURE) {
		ERROR("failed to build a message");
	}

	freelist(to);
	for (r = env->e_rcpts; r != NULL; r = r->r_link) 
		addlist(r->r_value,to);

	estate = env->e_state;
	free_envelope(env);
	free_milter(m);
	(void) sotimeout (old_to);
	return (estate);

}

int
milter_inspect_binary(char *appname, char *rladdr, char *riaddr,
	    	char *mconn, char *f_name, char *from, char ***to, 
		char *f_name_orig, char *ctype)
{
	struct milter *m;
	ENVELOPE *env = NULL;
	char *state = NULL;
	int old_to;
	int fd,bfd;
	char **rcp;
	int estate;
	RCPT *r = NULL;
	char bodyfname[] = "/tmp/tempbodyXXXXXXXXXXX";
	ssize_t len;
	char buf[MAX_HEADER_LEN];

	old_to = sotimeout(300);

	state = (char *)xmalloc(sizeof *state);
	*state = SMFIR_CONTINUE;

	if (!(m = milter_init(mconn))) {
		(void) sotimeout (old_to);
		return(MS_ER|ER_NE); 
	}

	env = milter_envelope_new();

	if ((fd = open(f_name, O_RDWR)) == -1) {
		ERROR("cannot open quarantine file");
		free_envelope(env);
		free_milter(m);
		(void) sotimeout (old_to);
		return (MS_ER|ER_IO);
	}
	env->e_envfrom = (char*) xstrdup(from);


	for (rcp = *to; *rcp; rcp++) {
		if(!env->e_rcpts) {
			env->e_rcpts = (RCPT *) xmalloc (sizeof (RCPT));
			r = env->e_rcpts;
		} else {
			if (r) {
				r->r_link =  (RCPT *) xmalloc (sizeof (RCPT));
				r = r->r_link;
			} else {
				FATAL("internal error - envelope not empty");
			}
		}
		r->r_link = NULL;
		r->r_value = (char*) xstrdup(*rcp);
	}
	if (ctype)
		(void)add_header(env, make_header("Content-Type",ctype));
	else
		(void)add_header(env, make_header("Content-Type","application/binary"));

	(void)add_header(env, make_header("Content-transfer-encoding","base64"));
	if (f_name_orig) 
		snprintf(buf,MAX_HEADER_LEN,"attachment; filename=\"%.128s\"", f_name_orig);
	else
		snprintf(buf,MAX_HEADER_LEN,"attachment");
	(void)add_header(env, make_header("Content-Disposition",buf));
	(void)add_header(env, make_header("Mime-Version","1.0"));
	
	if ((bfd = mkstemp(bodyfname)) == -1) {
		ERROR("cannot create temporary file");
		free_envelope(env);
		free_milter(m);
		close(fd);
		(void) sotimeout (old_to);
		return (MS_ER|ER_IO);
	}

	while ((len = read(fd, buf, 60)) != 0 ) {
		int enclen = 0;
		char *encbuf;
		if ((len == -1) || !(enclen = base64_encode(buf, len, &encbuf)) 
		     || (write(bfd, encbuf, enclen) != enclen) 
		     || (write(bfd, "\r\n", 2) != 2)) {
			syslog(LOG_ERR,"abort at len=%d enclen=%d", (int) len, enclen);
			ERROR("input/output error");
			free_envelope(env);
			free_milter(m);
			free(encbuf);
			(void) sotimeout (old_to);
			return (MS_ER|ER_IO);
		}
		free(encbuf);
		bzero(buf, sizeof(buf));
	}
	close(fd);

	if(lseek(bfd, 0, SEEK_SET) == -1) {
		ERROR("failed to seek temporary file");
		free_envelope(env);
		free_milter(m);
		(void) sotimeout (old_to);
		return (MS_ER|ER_IO);
	}

	env->e_bfd = bfd;
        env->e_bfn = xstrdup(bodyfname);

	if ((milter_dohelo(m,env,state,
			   rladdr,riaddr,appname) == MI_FAILURE) ||
	    (milter_dofrom(m,env,state,rladdr)	    == MI_FAILURE) || 
	    (milter_dorcpt(m,env,state,rladdr)	    == MI_FAILURE) ||
	    (milter_data(m,env,state) 			    == MI_FAILURE) ||
	    (milter_quit_filter(m)		    	    == MI_FAILURE)  ) {
		estate = env->e_state;
		free_envelope(env);
		free_milter(m);
		(void) sotimeout (old_to);
		return (MS_ER|estate);
	}
	freelist(to);
	for (r = env->e_rcpts; r != NULL; r = r->r_link) 
		addlist(r->r_value,to);

	estate = env->e_state;
	free_envelope(env);
	free_milter(m);
	(void) sotimeout (old_to);
	return (estate);
}
