#include <stdlib.h>
#ifdef WIN32
#	include <io.h>
#endif
#include "sms_smtp.h"
#include "smslib.h"
#include "lprintf.h"
#include "thread.h"
#include "lib.h"
#include "tcplib.h"
#include "mime.h"
#include "debug.h"

extern int dmsg(const char *, ...);
extern int imsg(const char *, ...);
extern int emsg(const char *, ...);

#ifdef WIN32
#define vsnprintf _vsnprintf
#endif

#ifndef iseol
#	define iseol(x)	(x == '\0' || x == '\r' || x == '\n')
#endif

struct sms_smtp {
	char host[BFSZ],*sendhost,*greeting,*workpath,*todom,*fromdom;
	Mutex *filelock;
	int sendport;
	Chan *sock;
	FILE *flog;
	Sms *sms;	
};

enum { MODE_HELO, MODE_FROM, MODE_TO, MODE_DATA, MODE_MESSAGE_BODY, MODE_NEXT_MSG };
enum { STOP_NOT, STOP_ON_FROM, STOP_ON_TO };

int process_message(SmsSmtp *smtp, SmtpMessage *msg, int stop_on);
char *valid_subject(char *message, char *subject, int bfsz);
char *qp_encode(char *bf, int bfsz);
int qp_decode(char *bf, int bfsz);
/*int code_page_convert(char *charset, char *string, int bfsz);*/

int smtp_log(SmsSmtp *smtp, int n, char *format, ...) {
	char tmp[BFSZ];
	char bf[BFSZ];	
	va_list args;
	struct tm tm;
	time_t t;
	int l;

	if (!smtp || !smtp->flog) return 0;

	va_start(args,format);
	vsnprintf(bf,BFSZ-1,format,args);
	bf[BFSZ-1] = '\0';
	va_end(args);

	time(&t);
	l = fprintf(smtp->flog,"%.15s [%03d] %s\n",safe_asctime(safe_localtime(&t,&tm),tmp)+4,n,bf);
	fflush(smtp->flog);
	return l;
}

#define trimmed(d,s) ncpy(bf,s,BFSZ-1); d = strdup(strtrm(bf));

/* should NOT return NULL as struct is needed in order to find out what errcode means */
SmsSmtp *sms_smtp_init(Sms *sms, SmtpInit *init, int *errcode) {
	SmsSmtp *smtp = calloc(1,sizeof(SmsSmtp));
	char bf[BFSZ];
		
	if (init->listenport != 0) {
		dmsg("SMTP listen on %d",init->listenport);
		if (!tcp_listen(&smtp->sock,init->listenport,NULL,FALSE)) {
			if (errcode) *errcode = SMS_SMTP_LISTEN;
			tcp_close_nice(smtp->sock);
			return smtp;
		}
	}
	tcp_hostname(smtp->host);
	dmsg("Host name (%s)",smtp->host);

	trimmed(smtp->sendhost,init->sendhost);	
	smtp->sendport = init->sendport;
	dmsg("SMTP Send to %s:%d",init->sendhost,init->sendport);

	if (init->greeting) {
		smtp->greeting = strdup(init->greeting);
		dmsg("SMTP Greeting (%s)",init->greeting);
	}

	if (init->todom) {
		trimmed(smtp->todom,init->todom);
		dmsg("SMTP ToDomain (%s)",init->todom);
	}

	if (init->fromdom) {
		trimmed(smtp->fromdom,init->fromdom);
		dmsg("SMTP FromDomain (%s)",init->fromdom);
	}

	smtp->filelock = th_mutex_new("SMTP_FILE_LOCK");

	trimmed(smtp->workpath,init->workpath);
	dmsg("SMTP Workpath (%s)",init->workpath);
	if (access(smtp->workpath,00) != 0) {
		lib_create_dir(smtp->workpath,0700);
		lib_mkdir(smtp->workpath,0700);
	}

	if (init->smtplog) {
		smtp->flog = fopen(init->smtplog,"a");
		if (!smtp->flog) emsg("Failed to open (%s), %s",init->smtplog,strerror(errno));
	}
	smtp->sms = sms;
	return smtp;
}
void sms_smtp_free(SmsSmtp **smtp) {
	if (smtp && *smtp) {
		th_mutex_free((*smtp)->filelock);
		if ((*smtp)->sock) {
			tcp_close_nice((*smtp)->sock);
			tcp_free(&(*smtp)->sock);
		}
		myfree(*smtp);
	}
}
SmtpMessage *sms_smtp_wait(SmsSmtp *smtp, long timeout_msec, int *errcode) {
	if (smtp->sock && tcp_waitfor(smtp->sock,timeout_msec)) {
		SmtpMessage *msg;

		msg = calloc(1,sizeof(SmtpMessage));
		if (tcp_accept(smtp->sock,&msg->sock,"sms") && tcp_printf(msg->sock,"220 %s\r\n",smtp->greeting)) {
			char fromip[BFSZ];
			int port;

			tcp_getip(msg->sock,fromip);
			tcp_getport(msg->sock,&port);
			smtp_log(smtp,msg->sock->sk,"Smtp connection from %s:%d",fromip,port);
			smtp_log(smtp,msg->sock->sk,"SMTP->[220 %s]",smtp->greeting);

			tcp_set_read_timeout(msg->sock,10000);
			return msg;
		}
		tcp_close_nice(msg->sock);
		tcp_free(&msg->sock);
		myfree(msg);
	}
	return NULL;
}
int extract_line(char **start, char *bf, int bfsz) {
	char *ptr;
	int i;

	if (!start || !*start) return FALSE;
	if (*start == '\0') return FALSE;
	ptr = *start;

	for(i = 0; i < bfsz && *ptr && !iseol(*ptr); i++) {
		bf[i] = *ptr++;
	}
	bf[i] = '\0';
	
	if (*ptr == '\r' && *(ptr+1) == '\n') ptr++;
	*start = (*ptr)?ptr+1:NULL;

	return TRUE;
}
int sms_smtp_send(SmsSmtp *smtp, MessageExtra *e, char *full_from, char *full_to, char *message) {
	char subject[BFSZ];
	char from[BFSZ];
	char to[BFSZ];
	char bf[BFSZ];
	char *ptr;
	int retval = SMS_NO_ERROR;
	Chan *sock = NULL;	
	Pair *h;

	tcp_new(&sock);
	if (!tcp_open(sock,smtp->sendhost,smtp->sendport,NULL)) {
		smtp_log(smtp,0,"Smtp connect to %s:%d FAILED",smtp->sendhost,smtp->sendport);
		tcp_free(&sock);
		return SMS_SMTP_OPEN;
	}
	smtp_log(smtp,sock->sk,"Smtp connected to %s:%d",smtp->sendhost,smtp->sendport);
	
	/* RMH: removed in favour of less hack-like method...

	// find subject if specified, ignore other email headers,
	// move message body start point if Subject found
	subject[0] = '\0';
	for(ptr = message; TRUE; ) {
		if (!extract_line(&ptr,bf,BFSZ)) break;
		if (!strchr(bf,':')) break;
		if (strncmpnc(bf,"Subject: ",9) == 0) {
			ncpy(subject,&bf[9],BFSZ-1);			
			qp_header_encode("Subject",&bf[9],subject,BFSZ);
		}
	}
	if (subject[0] != '\0') message = ptr;
	else {
		lprintf(bf,BFSZ,"%.20s",message);
		qp_header_encode("Subject",bf,subject,BFSZ);
	}
	*/	

	if (!tcp_readline(sock,bf,BFSZ)) goto read_error;
	smtp_log(smtp,sock->sk,"SMTP<-[%s]",bf);
	if (bf[0] != '2') goto protocol_error;
	
	if (!tcp_printf(sock,"HELO %s\r\n",smtp->host)) goto write_error;
	smtp_log(smtp,sock->sk,"SMTP->[HELO %s]",smtp->host);
	if (!tcp_readline(sock,bf,BFSZ)) goto read_error;
	smtp_log(smtp,sock->sk,"SMTP<-[%s]",bf);
	if (bf[0] != '2') goto protocol_error;

	if (smtp->fromdom && !strchr(full_from,'@')) {
		lprintf(from,BFSZ,"%s@%s",full_from,smtp->fromdom);
		full_from = from;
	}
	else rfc_raw_email(full_from,from,BFSZ);

	if (!tcp_printf(sock,"MAIL FROM:<%s>\r\n",from)) goto write_error;
	smtp_log(smtp,sock->sk,"SMTP->[MAIL FROM:<%s>]",from);
	if (!tcp_readline(sock,bf,BFSZ)) goto read_error;
	smtp_log(smtp,sock->sk,"SMTP<-[%s]",bf);
	if (bf[0] != '2') goto protocol_error;
		
	if (smtp->todom && !strchr(full_to,'@')) {
		lprintf(to,BFSZ,"%s@%s",full_to,smtp->todom);
		full_to = to;
	}
	else rfc_raw_email(full_to,to,BFSZ);

	if (!tcp_printf(sock,"RCPT TO:<%s>\r\n",to)) goto write_error;
	smtp_log(smtp,sock->sk,"SMTP->[RCPT TO:<%s>]",to);
	if (!tcp_readline(sock,bf,BFSZ)) goto read_error;
	smtp_log(smtp,sock->sk,"SMTP<-[%s]",bf);
	if (bf[0] != '2') goto protocol_error;
	
	if (!tcp_printf(sock,"DATA\r\n")) goto write_error;
	smtp_log(smtp,sock->sk,"SMTP->[DATA]");
	if (!tcp_readline(sock,bf,BFSZ)) goto read_error;
	smtp_log(smtp,sock->sk,"SMTP<-[%s]",bf);
	if (bf[0] != '3') goto protocol_error;

	//Subject
	if (e && pair_find(e->headers,"Subject")[0]) qp_header_encode("Subject",pair_find(e->headers,"Subject"),subject,BFSZ);
	else {
		lprintf(bf,BFSZ,"%.20s",message);
		qp_header_encode("Subject",bf,subject,BFSZ);
	}
	if (!tcp_printf(sock,"Subject: %s\r\n",subject)) goto write_error;
	smtp_log(smtp,sock->sk,"SMTP->[Subject: %s]",subject);

	//Date
	if (!tcp_printf(sock,"Date: %s\r\n",lib_mail_time(bf,time(NULL)))) goto write_error;
	smtp_log(smtp,sock->sk,"SMTP->[Date: %s]",bf);

	//From
	if (!tcp_printf(sock,"From: %s\r\n",full_from)) goto write_error;
	smtp_log(smtp,sock->sk,"SMTP->[From: %s]",full_from);
	
	//To
	if (!tcp_printf(sock,"To: %s\r\n",full_to)) goto write_error;
	smtp_log(smtp,sock->sk,"SMTP->[To: %s]",full_to);

	//Content-type
	if (!tcp_printf(sock,"Content-type: text/plain;\r\n\tcharset=\"us-ascii\"\r\n")) goto write_error;
	smtp_log(smtp,sock->sk,"SMTP->[Content-type: text/plain; charset=\"us-ascii\"]");

	//Content-Transfer-Encoding
	if (!tcp_printf(sock,"Content-Transfer-Encoding: quoted-printable\r\n")) goto write_error;
	smtp_log(smtp,sock->sk,"SMTP->[Content-Transfer-Encoding: quoted-printable]");

	if (e) {
		for(h = e->headers; h; h = h->next) {
			if (strcmpnc(h->var,"subject")==0) continue;
			if (strcmpnc(h->var,"date")==0) continue;
			if (strcmpnc(h->var,"from")==0) continue;
			if (strcmpnc(h->var,"to")==0) continue;
			if (strcmpnc(h->var,"content-type")==0) continue;
			if (strcmpnc(h->var,"content-transfer-encoding")==0) continue;

			if (!tcp_printf(sock,"%s: %s\r\n",h->var,h->val)) goto write_error;
			smtp_log(smtp,sock->sk,"SMTP->[%s: %s]",h->var,h->val);
		}
	}

	//END OF HEADERS
	if (!tcp_printf(sock,"\r\n")) goto write_error;
	smtp_log(smtp,sock->sk,"SMTP->[]");

	for(ptr = message; TRUE; ) {
		if (!extract_line(&ptr,bf,BFSZ)) break;
		if (bf[0] == '.' && bf[1] == '\0') {
			bf[1] = '.'; bf[2] = '\0';
		}
		if (!tcp_printf(sock,"%s\r\n",qp_encode(bf,BFSZ))) goto write_error;
		smtp_log(smtp,sock->sk,"SMTP->[%s]",bf);
	}

	if (!tcp_printf(sock,".\r\n")) goto write_error;
	smtp_log(smtp,sock->sk,"SMTP->[.]");

	if (!tcp_readline(sock,bf,BFSZ)) goto read_error;
	smtp_log(smtp,sock->sk,"SMTP<-[%s]",bf);
	if (bf[0] != '2') goto protocol_error;
done:
	if (!tcp_printf(sock,"QUIT\r\n")) goto write_error;
	smtp_log(smtp,sock->sk,"SMTP->[QUIT]");
	if (!tcp_readline(sock,bf,BFSZ)) goto read_error;
	smtp_log(smtp,sock->sk,"SMTP<-[%s]",bf);
	tcp_close_nice(sock);
	tcp_free(&sock);
	return retval;
read_error:
	retval = SMS_SMTP_READ;
	goto done;
write_error:
	retval = SMS_SMTP_WRITE;
	goto done;
protocol_error:
	retval = SMS_SMTP_PROTOCOL;
	goto done;
}
int sms_smtp_recv(SmsSmtp *smtp, SmtpMessage *msg) {
	if (!msg->sock) return SMS_SMTP_CLOSED;
	return process_message(smtp,msg,STOP_ON_FROM);
}
int sms_smtp_accept(SmsSmtp *smtp, SmtpMessage *msg, int code, char *reason) {
	char *dflt_message;

	if (!msg->sock) return SMS_SMTP_CLOSED;
	if (msg->upto == MODE_FROM) {
		dflt_message = "Command MAIL OK";
		msg->upto = MODE_TO;
	}
	else if (msg->upto == MODE_TO) {
		dflt_message = "Command RCPT OK";
		//msg->upto = MODE_DATA;
	}
	else if (msg->upto == MODE_MESSAGE_BODY) {
		dflt_message = "Command DATA Processed mail data OK";
		msg->upto = MODE_NEXT_MSG;
	}
	else dflt_message = "Accepted";
	
	if (!tcp_printf(msg->sock,"%d %s\r\n",code,(reason!=NULL)?reason:dflt_message)) return SMS_SMTP_WRITE;
	smtp_log(smtp,msg->sock->sk,"SMTP->[%d %s]",code,(reason!=NULL)?reason:dflt_message);
	msg->needreply = FALSE;
	
	return process_message(smtp,msg,STOP_NOT);
}
int sms_smtp_reject(SmsSmtp *smtp, SmtpMessage *msg, int code, char *reason) {
	char *dflt_message;

	if (!msg->sock) return SMS_SMTP_CLOSED;
	if (msg->upto == MODE_FROM) dflt_message = "Command MAIL User NOT OK";
	else if (msg->upto == MODE_TO) dflt_message = "Command RCPT User NOT OK";
	else if (msg->upto == MODE_MESSAGE_BODY) dflt_message = "Command DATA NOT OK";
	else dflt_message = "Denied";
	if (!tcp_printf(msg->sock,"%d %s\r\n",code,(reason!=NULL)?reason:dflt_message)) return SMS_SMTP_WRITE;
	smtp_log(smtp,msg->sock->sk,"SMTP->[%d %s]",code,(reason!=NULL)?reason:dflt_message);
	msg->needreply = FALSE;
	if (msg->upto != MODE_TO) {
		msg->upto = MODE_NEXT_MSG;
		msg->message[0] = '\0';
		word_free(&msg->to);
		myfree(msg->from);	
	}
	return SMS_NO_ERROR;
}
char *sms_smtp_error(SmsSmtp *smtp, int errcode, char *bf, int bfsz) {
	char tmp[BFSZ];

	if (errcode == SMS_SMTP_OPEN) lprintf(bf,bfsz-1,"Failed to connect to the sending SMTP server");
	else if (errcode == SMS_SMTP_LISTEN) lprintf(bf,bfsz-1,"Error while listening for connections (%s)",tcp_error(smtp->sock,tmp));
	else if (errcode == SMS_SMTP_READ) ncpy(bf,"Error reading from the socket",bfsz-1);
	else if (errcode == SMS_SMTP_WRITE) ncpy(bf,"Error writing to the socket",bfsz-1);
	else if (errcode == SMS_SMTP_CLOSED) ncpy(bf,"Socket was closed",bfsz-1);
	else if (errcode == SMS_SMTP_PROTOCOL) ncpy(bf,"Un-expected reply from SMTP server",bfsz-1);
	else ncpy(bf,"Unknown Error Code",bfsz-1);
	return bf;
}
char *sms_smtp_fromip(SmsSmtp *smtp, SmtpMessage *msg) {
	if (!msg->ip) {
		char tmp[BFSZ];

		if (!tcp_getip(msg->sock,tmp)) return NULL;
		msg->ip = strdup(tmp);
	}
	return msg->ip;
}
char *sms_smtp_from(SmsSmtp *smtp, SmtpMessage *msg) {
	if (!msg->from) {
		if (!msg->sock) return NULL;
		if (process_message(smtp,msg,STOP_ON_FROM) != SMS_NO_ERROR)
			return NULL;
	}
	return msg->from;
}
void sms_smtp_remove_to(SmsSmtp *smtp, SmtpMessage *msg) {
	Wordlist *tmp = msg->to;
	msg->to = msg->to->next;
	free(tmp->word);
	free(tmp);
}
char *sms_smtp_to(SmsSmtp *smtp, SmtpMessage *msg) {
	Wordlist *tmp = msg->to;
	if (process_message(smtp,msg,STOP_ON_TO) != SMS_NO_ERROR)
		return NULL;
	if (tmp == msg->to) return NULL;
	return msg->to->word;
}
char *sms_smtp_body(SmsSmtp *smtp, SmtpMessage *msg) {
	if (!msg->gotheaders) {
		if (!msg->sock) return NULL;
		if (process_message(smtp,msg,STOP_NOT) != SMS_NO_ERROR)
			return NULL;
	}
	return msg->message;
}
void close_unique(SmtpMessage *msg) {
	if (msg->f) {
		fclose(msg->f);
		msg->f = NULL;
	}
	remove(msg->file);
	free(msg->file);
	msg->file = NULL;
}
void sms_smtp_msg_passoff(SmsSmtp *smtp, SmtpMessage *msg) {
	SmtpMessage *next = calloc(1,sizeof(SmtpMessage));

	imsg("Passing SMTP channel off to new thread...");
	next->name = strdup(msg->name);
	next->ip = strdup(msg->ip);
	next->upto = MODE_FROM;
	next->sock = msg->sock;

	sms_wait_incoming(smtp->sms,next);

	msg->sock = NULL;
}

void sms_smtp_msgfree(SmsSmtp *smtp, SmtpMessage **msg) {
	if (smtp && msg && *msg) {
		if ((*msg)->sock) {
			tcp_close_nice((*msg)->sock);
			tcp_free(&(*msg)->sock);
			(*msg)->sock = NULL;
		}
		close_unique(*msg);
		myfree((*msg)->from);
		myfree((*msg)->to);
		myfree((*msg)->ip);
		myfree(*msg);
	}
}

MessageExtra *sms_smtp_msgextra(SmtpMessage *from) {
	MessageExtra *e = calloc(1,sizeof(MessageExtra));
	if (from) pair_copy(&e->headers,from->headers);
	return e;
}
void sms_smtp_msgextra_free(MessageExtra **e) {
	if (!e) return ;
	pair_free(&(*e)->headers);
	free(*e); *e = NULL;
}

int open_unique(SmsSmtp *smtp, SmtpMessage *msg) {
	char file[BFSZ];
	int i;

	for(i = 0; i < 1000; i++) {
		lprintf(file,BFSZ,"%s/%05d%05d.msg",smtp->workpath,rand(),rand());
		th_mutex_get_or_die(smtp->filelock);
		if (access(file,00) != 0) {			
			msg->f = fopen(file,"w+");
			if (msg->f) {
				msg->file = strdup(file);
				th_mutex_release(smtp->filelock);
				break;
			}
			emsg("Failed to open (%s), %s",file,strerror(errno));
		}
		th_mutex_release(smtp->filelock);
	}
	dmsg("open_unique tries=(%d) file=(%s)",i,(msg->file)?msg->file:"");
	return !(msg->f == NULL);
}
int process_message_mime(SmsSmtp *smtp, SmtpMessage *msg) {
	char tmp[BFSZ],*last,*p,*n,*s;
	char raw[BFSZ],bin[BFSZ];	
	int r,len,usepart,bestpart,donepart;
	Mime *m;

	imsg("process_message_mime\n");

	if (fseek(msg->f,0,SEEK_SET) != 0)
		return FALSE;
	
	donepart = bestpart = FALSE;
	usepart = TRUE;
	mime_new(&m);
	last = NULL;

	for (r=TRUE;r;) {
		r = mime_line(m,msg->f,raw,bin,&len,BFSZ-1);
		switch (r) {
		case MIME_END:
			break;
		case MIME_HEADER:
			msg->gotheaders = TRUE;
			if (isspace(raw[0]) && last) {
				for(p = raw; isspace(*p); p++);
				lprintf(tmp,BFSZ,"%s%s",pair_find(msg->headers,last),mystrtok(p,"\r\n",&n));
				pair_add(&msg->headers,last,tmp);
				break;
			}
			p = mystrtok(raw,": ",&n);
			if (!p) break;
			s = mystrtok(NULL,"\r\n",&n);
			if (!s) break;
			for(; isspace(*s); s++);
			if (!s) break;
			pair_add(&msg->headers,p,s);
			free(last); last = strdup(p);			
			break;
		case MIME_CONTENT:
			usepart = FALSE;			
			if (strstr(raw,"text/")) {
				if (bestpart) break;
				if (strstr(raw,"text/plain"))
					bestpart = TRUE;				
				if (donepart) *msg->message = 0;
				donepart = usepart = TRUE;
			}
			break;
		case MIME_HEADER_END:
			break;
		case MIME_BODY:
			if (!usepart) break;
			ncat(msg->message,bin,BFSZ-1);
			break;
		case MIME_BOUNDARY:
			usepart = FALSE;
			break;
		}
	}

	free(last);

	imsg("Length of message to send %d %s\n",strlen(msg->message),msg->message);
	return TRUE;
}
enum { NOT_MULTI, IS_MULTI, NEXT_MULTI, THIS_MULTI, END_MULTI };
int process_message(SmsSmtp *smtp, SmtpMessage *msg, int stop_on) {
	char bf[BFSZ],tmp[BFSZ],*p,*boundary,*last_header;
	int retval = SMS_NO_ERROR,i,ismulti,qp;
	SmtpMessage *next = NULL;

	if (!msg->sock) return SMS_SMTP_CLOSED;

	if (msg->needreply) {
		if (msg->upto == MODE_FROM) {
			if (!tcp_printf(msg->sock,"250 Command MAIL OK\r\n")) goto write_error;
			smtp_log(smtp,msg->sock->sk,"SMTP->[250 Command MAIL OK]");
			msg->needreply = FALSE;
			msg->upto = MODE_TO;
		}
		else if (msg->upto == MODE_TO) {
			if (!tcp_printf(msg->sock,"250 Command RCPT User found OK\r\n")) goto write_error;
			smtp_log(smtp,msg->sock->sk,"SMTP->[250 Command RCPT User found OK]");
			msg->needreply = FALSE;
			//msg->upto = MODE_DATA;
		}
		else if (msg->upto == MODE_DATA) {
			if (!tcp_printf(msg->sock,"354 Command DATA Start mail input; end with <CRLF>.<CRLF>\r\n")) goto write_error;
			smtp_log(smtp,msg->sock->sk,"SMTP->[354 Command DATA Start mail input; end with <CRLF>.<CRLF>]");
			msg->upto = MODE_MESSAGE_BODY;
			msg->needreply = FALSE;
			last_header = boundary = NULL;			
			ismulti = NOT_MULTI;
			qp = 0;
		}
		else if (msg->upto == MODE_MESSAGE_BODY) {
			if (!tcp_printf(msg->sock,"250 Command DATA Processed mail data OK\r\n")) goto write_error;
			smtp_log(smtp,msg->sock->sk,"SMTP->[250 Command DATA Processed mail data OK]");
			msg->needreply = FALSE;
			msg->upto = MODE_NEXT_MSG;
		}
	}
	
	while(TRUE) {
		if (!tcp_readline(msg->sock,bf,BFSZ)) goto read_error;

		/* Strip \r\n */
		for(i = 0; bf[i]; i++);
		for(i--; i != -1 && iseol(bf[i]); i--) bf[i] = '\0';
		smtp_log(smtp,msg->sock->sk,"SMTP<-[%s]",bf);

		if (msg->upto != MODE_MESSAGE_BODY) {
			if (strcmpnc(bf,"QUIT") == 0) {
				if (!tcp_printf(msg->sock,"221 Command QUIT, disconnecting\r\n")) goto write_error;
				smtp_log(smtp,msg->sock->sk,"SMTP->[221 Command QUIT, disconnecting]");
				break;
			}
		}

		if (msg->upto == MODE_HELO) {
			if (strncmpnc(bf,"HELO",4) != 0) {
				if (!tcp_printf(msg->sock,"500 HELO expected\r\n")) goto write_error;
				smtp_log(smtp,msg->sock->sk,"SMTP->[500 HELO expected]");
				continue;
			}

			/* Get helo 'name' */
			for(p = &bf[4]; isspace(*p); p++);
			for(i = 0; i < BFSZ && *p; i++) tmp[i] = *p++;
			for(tmp[i--] = '\0'; i > -1 && isspace(tmp[i]); i--) tmp[i] = '\0';
			msg->name = strdup(tmp);

			if (!tcp_printf(msg->sock,"250 %s. Hello %s(%s)\r\n",smtp->host,msg->name,msg->ip)) goto write_error;
			smtp_log(smtp,msg->sock->sk,"SMTP->[250 %s. Hello %s(%s)]",smtp->host,msg->name,msg->ip);
			msg->upto = MODE_FROM;
			continue;
		}

		if (msg->upto == MODE_MESSAGE_BODY) {
			if (bf[0] == '.' && !bf[1]) {
				// end of message body
				/*
				if (qp) qp_decode(msg->message,BFSZ);
				if (boundary) myfree(boundary);
				msg->gotheaders = TRUE;
				msg->needreply = TRUE;
				*/
				process_message_mime(smtp,msg);
				return retval;
			}
			if (bf[0] == '.' && bf[1] == '.') fprintf(msg->f,"%s\n",&bf[1]);
			else fprintf(msg->f,"%s\n",bf);
			continue;
			/*
			if (!msg->gotheaders) {
				// read headers 
				if (bf[0] == '\0') {
					// no more headers
					if (last_header) myfree(last_header);
					msg->gotheaders = TRUE;					
				}
				else {
					// split header 'name: value'
					if (isspace(bf[0])) p = &bf[0];
					else {
						p = strchr(bf,':');
						if (!p) continue;
					}
					for(*p++ = '\0'; *p && *p == ' ' || *p == '\t'; p++);
					if (bf[0] == '\0') {
						// line started with a space/tab 
						if (ismulti == IS_MULTI) {
							p = strstr(p,"boundary=\"");
							if (p) {
								p += 10;
								boundary = strdup(p);
								p = strrchr(boundary,'\"');
								if (p) *p = '\0';
							}
						}
						if (last_header) {
							lprintf(tmp,BFSZ,"%s%s",pair_find(msg->headers,last_header),p);
							pair_add(&msg->headers,last_header,tmp);
						}
						continue;
					}
					else if (strcmpnc(bf,"Content-Transfer-Encoding")==0) {
						// check for quoted-printable encoding 
						if (strcmpnc(p,"quoted-printable")==0)
							qp = TRUE;
					}
					else if (strcmpnc(bf,"Content-Type")==0) {
						// check content type 
						if (ismulti == NOT_MULTI && strstrnc(p,"multipart")) ismulti = IS_MULTI;		
						if (ismulti == NEXT_MULTI && strcmpnc(p,"text/plain;")==0) ismulti = THIS_MULTI;
					}
					// add header 
					pair_add(&msg->headers,bf,p);
					free(last_header);
					last_header = strdup(bf);
				}
				continue;
			}			
			if ((ismulti == IS_MULTI || ismulti == NEXT_MULTI || ismulti == THIS_MULTI) && boundary) {
				// look for boundary
				if (bf[0] == '-' && bf[1] == '-' && strncmp(&bf[2],boundary,strlen(boundary)) == 0) {
					if (ismulti == THIS_MULTI) {
						// not looking at parts anymore, we have read the text part already
						ismulti = END_MULTI;
					}
					else {
						// into next multi-part part
						msg->gotheaders = FALSE;
						ismulti = NEXT_MULTI;
					}
					continue;
				}				
			}
			if (ismulti == NOT_MULTI || ismulti == THIS_MULTI) {
				//if (qp) qp_decode(bf,BFSZ);

				//if (qp) {
				//	qp = strlen(bf) + 1;
				//	for(p = strchr(bf,'='); p; p = strchr(p,'=')) {
				//		if (*(p+1) == '\r' || *(p+1) == '\n') {
							// soft line break
				//			qp--;
				//			memmove(p,p+1,qp-(p-bf));
				//			continue;
				//		}
						// encoded character
				//		*p = char2byte(p+1);
				//		qp -= 2;
				//		memmove(p+1,p+3,qp-(p-bf)+1);
				//		p++;
				//	}
				//}
				// append to message
				ncat(msg->message,bf,BFSZ-1);
				ncat(msg->message,"\n",BFSZ-1);
			}
			continue;
			*/
		}

		if (strcmpnc(bf,"RSET") == 0) {
			if (!tcp_printf(msg->sock,"250 Command RSET OK\r\n")) goto write_error;
			smtp_log(smtp,msg->sock->sk,"SMTP->[250 Command RSET OK]");

			/* RSET means we are getting more than 1 message on this connection.
			   Pass channel off to new msg struct and pass to callback in new thread
			*/
			sms_smtp_msg_passoff(smtp,msg);		
			break;
		}

		if (msg->upto == MODE_FROM || msg->upto == MODE_NEXT_MSG) {			
			if (strncmpnc(bf,"MAIL FROM:",10) == 0) {
				if (msg->upto == MODE_NEXT_MSG) {				
					sms_smtp_msg_passoff(smtp,msg);
					break;
				}

				for(p = &bf[10]; isspace(*p); p++);
				for(i = 0; i < BFSZ && *p; i++) tmp[i] = *p++;
				for(tmp[i--] = '\0'; i > -1 && isspace(tmp[i]); i--) tmp[i] = '\0';
				
				/* Remove last > if present */
				p = strrchr(tmp,'>');
				if (p) *p = '\0';

				if (tmp[0] == '<') msg->from = strdup(&tmp[1]);
				else msg->from = strdup(tmp);

				if (stop_on == STOP_ON_FROM) {
					msg->needreply = TRUE;
					return retval;
				}

				if (!tcp_printf(msg->sock,"250 Command MAIL OK\r\n")) goto write_error;
				smtp_log(smtp,msg->sock->sk,"SMTP->[250 Command MAIL OK]");
				msg->upto = MODE_TO;
				continue;
			}
		}
		else if (msg->upto == MODE_TO /*|| msg->upto == MODE_DATA*/) {
			if (strncmpnc(bf,"RCPT TO:",8) == 0) {
				for(p = &bf[8]; isspace(*p); p++);
				for(i = 0; i < BFSZ && *p; i++) tmp[i] = *p++;
				for(tmp[i--] = '\0'; isspace(tmp[i]); i--) tmp[i] = '\0';
				
				/* Remove last > if present */
				p = strrchr(tmp,'>');
				if (p) *p = '\0';

				if (tmp[0] == '<') word_add(&msg->to,&tmp[1]);
				else word_add(&msg->to,tmp);

				if (stop_on == STOP_ON_TO) {
					msg->needreply = TRUE;
					return retval;
				}

				if (!tcp_printf(msg->sock,"250 Command RCPT User found OK\r\n")) goto write_error;
				smtp_log(smtp,msg->sock->sk,"SMTP->[250 Command RCPT User found OK]");

				continue;
			}
			if (strcmpnc(bf,"DATA") == 0) {
				if (!open_unique(smtp,msg)) {
					if (!tcp_printf(msg->sock,"452 Failed to create temp file.\r\n")) goto write_error;
					smtp_log(smtp,msg->sock->sk,"SMTP->[452 Failed to create temp file in (%s), error (%s)]",smtp->workpath,strerror(errno));
					goto file_error;
				}

				/* added so we can have multiple rcpt to's */
				if (stop_on == STOP_ON_TO) {
					msg->upto = MODE_DATA;
					msg->needreply = TRUE;
					return retval;
				}
				
				if (!tcp_printf(msg->sock,"354 Command DATA Start mail input; end with <CRLF>.<CRLF>\r\n")) goto write_error;
				smtp_log(smtp,msg->sock->sk,"SMTP->[354 Command DATA Start mail input; end with <CRLF>.<CRLF>]");
				msg->upto = MODE_MESSAGE_BODY;
				/*
				last_header = boundary = NULL;
				ismulti = NOT_MULTI;
				qp = 0;
				*/
				continue;
			}
		}
		for(p = bf; *p && !isspace(*p); p++);
		*p = '\0';
		if (!tcp_printf(msg->sock,"500 Unknown command (%s)\r\n",bf)) goto write_error;
		smtp_log(smtp,msg->sock->sk,"SMTP->[500 Unknown command (%s)]",bf);
	}
done:
	if (msg->sock) {
		tcp_close_nice(msg->sock);
		tcp_free(&msg->sock);
		msg->sock = NULL;
	}
	return retval;
read_error:
	if (msg->upto == MODE_NEXT_MSG) goto done;
	retval = SMS_SMTP_READ;
	goto done;
write_error:
	retval = SMS_SMTP_WRITE;
	goto done;	
file_error:
	retval = SMS_SMTP_FILE;
	goto done;
}
char *valid_subject(char *message, char *subject, int bfsz) {
	char *p;
	int i;

	for(p = message, i = 0; !iseol(*p) && i < bfsz-1; i++) subject[i] = *p++;
	subject[i] = '\0';
	return subject;
}
/*
int mime_encode_quoted(char *in, int inlen, char *out, int outlen)
{

	char *start=out;
	int i;
	for (i=0; i<inlen; i++) {
		if (isprint(in[i]) && in[i] != '=') {
			*out++ = in[i];
		} else {
			*out++ = '=';
			sprintf(out,"%02x",(unsigned int) in[i]);
			out+=2;
		}
		if (out-start>(outlen-10)) break;
	}
	start[out-start] = 0;
	return out-start;
}
*/
char *qp_encode(char *bf, int bfsz) {
	unsigned char c;
	char *p;
	int l,u;

	bfsz--;
	l = strlen(bf);
	for(u = 0, p = bf; *p && u < bfsz; p++, l--, u++) {
		if (isprint(*p) && *p != '=') continue;
		c = *p;
		
		if (u+3 > bfsz) break;		

		memmove(p+3,p+1,l);		

		*p++ = '=';
		*p++ = "0123456789ABCDEF"[c/16];
		*p = "0123456789ABCDEF"[c%16];

		u += 2;
	}
	*p = '\0';
	return bf;
}
int qp_decode(char *bf, int bfsz) {
	char *p,*s;
	int len;

	len = strlen(bf) + 1;
	for(p = strchr(bf,'='); p; p = strchr(p,'=')) {
		if (*(p+1) == '\r' || *(p+1) == '\n') {
			// soft line break
			for(s = p+1, len--; iseol(*s); s++, len--);
			memmove(p,s,len-(s-bf)+1);
			continue;
		}
		// encoded character
		*p = char2byte(p+1);
		len -= 2;
		memmove(p+1,p+3,len-(p-bf)+1);
		p++;
	}
	return len;
}
int qp_header_encode(char *header, char *text, char *bf, int bfsz) {
	int i,l,r = FALSE;
	char *p = text;	

	if (!p) return 0;
	if (bfsz <= 18) return 0;
	bfsz--;

	ncpy(bf,"=?iso-8859-1?q?",bfsz-1);

	for(i = 15; *p && i < bfsz; p++) {
		if (isprint(*p) && *p != '=' && *p != '?' && *p != '_' && *p != ' ' 
		&& *p != ')' && *p != '(') {
			bf[i++] = *p;
			continue;
		}
		if (*p == ' ') {
			bf[i++] = '_';
			continue;
		}
		r = TRUE;
		if (i+3 > bfsz) break;
		bf[i++] = '=';
		bf[i++] = "0123456789ABCDEF"[(unsigned char)*p/16];
		bf[i++] = "0123456789ABCDEF"[(unsigned char)*p%16];
	}
	bf[i] = '\0';

	if (!r) {
		memmove(bf,&bf[15],i-15+1);
		for(p = bf; *p; p++) if (*p == '_') *p = ' ';
		return i-15;
	}

	//=?iso-8859-1?q?...?=

	l = strlen(header) + 2 + 17;
	for(r = 15; r < i; r++, l++) {
		if (l < 70) continue;
		for(; TRUE; r++, l++) {
			if (bf[r-1] == '=' || bf[r-2] == '=') continue;
			if (i+22 > bfsz) {
				bf[r] = '\0';
				i = r;
				break;
			}
			memmove(&bf[r+20],&bf[r],i-r+1);
			memcpy(&bf[r],"?=\r\n\t=?iso-8859-1?q?",20);
			i += 20;
			r += 20;
			l = 18;
			break;
		}
	}

	ncat(bf,"?=",bfsz-1);
	i += 2;

	return i;
}
int qp_header_decode(char *text, char *bf, int bfsz) {
	char *tmp,*p,*pp[100];
	int np,i;


	dmsg("qp_header_decode(%s)",text);

	bf[0] = '\0';

	if (strncmp(text,"=?",2)!=0) {
		ncpy(bf,text,bfsz-1);
		i = strlen(bf);
		dmsg("qp_header_decode done %d (%s)",i,bf);
		return i;
	}

	//=?iso-8859-1?q?...?=

	tmp = strdup(text);
	np = token_split(tmp,pp,"?",100);
	if (np >= 5) {
		for(i = 3; i < np; i += 4) {
			ncat(bf,pp[i],bfsz-1);
		}
		if (pp[2][0] == 'q' || pp[2][0] == 'Q') {
			for(p = bf; *p; p++) if (*p == '_') *p = ' ';
			//i = qp_decode(bf,bfsz);
			i = mime_decode_quoted(bf,tmp,BFSZ-1);
			ncpy(bf,tmp,bfsz-1);			
			free(tmp);
			dmsg("qp_header_decode done %d (%s)",i,bf);
			return i;
		}
		if (pp[2][0] == 'b' || pp[2][0] == 'B') {
			i = mime_decode_base64(bf,tmp,BFSZ-1);
			memcpy(bf,tmp,i);
			bf[i] = 0;
			free(tmp);
			dmsg("qp_header_decode done %d (%s)",i,bf);
			return i;
		}
	}
	free(tmp);
	dmsg("qp_header_decode done -1 ()");
	return -1;
}

/*
UINT find_codepage(char *charset);

int code_page_convert(char *charset, char *string, int bfsz) {
	WCHAR *tmp;
	UINT cp;
	int l,w;

	if (!string || !charset) return FALSE;

	cp = find_codepage(charset);
	if (cp == LOCALE_USER_DEFAULT) return TRUE;
	if (cp == 0) return FALSE;	

	l = strlen(string);
	tmp = calloc(l+1,sizeof(WCHAR));
	if (MultiByteToWideChar(cp,MB_PRECOMPOSED,string,l,tmp,l) == 0) {
		myfree(tmp);
		return FALSE;
	}
	if ((w = WideCharToMultiByte(LOCALE_USER_DEFAULT,0,tmp,l,string,bfsz,NULL,NULL)) == 0) {
		char bf[BFSZ];

		emsg("%s",windows_errmsg(bf,BFSZ));
		myfree(tmp);
		return FALSE;
	}
	string[w] = '\0';
	myfree(tmp);
	return TRUE;
}

struct charset_codepage {
	UINT codepage;
	char *charset;
};

//  Code Page			Charset				Description
struct charset_codepage conv_page[] = {
	{1252				,"iso-8859-1"},		//Western European Alphabets
	{708				,"ASMO-708"},		//Arabic ASMO-708
	{720				,"DOS-720"},		//Arabic Alphabet (DOS)
	{28596				,"iso-8859-6"},		//Arabic Alphabet (ISO)
	{1256				,"windows-1256"},	//Arabic Alphabet (Windows)
	{28594				,"iso-8859-4"},		//Baltic Alphabet (ISO)
	{1257				,"windows-1257"},	//Baltic Alphabet (Windows)
	{852				,"ibm852"},			//Central European (DOS)
	{28592				,"iso-8859-2"},		//Central European Alphabet (ISO)
	{1250				,"windows-1250"},	//Central European Alphabet (Windows)
	{936				,"gb2312"},			//Chinese Simplified (GB2312)
	{52936				,"hz-gb-2312"},		//Chinese Simplified (HZ)
	{950				,"big5"},			//Chinese Traditional
	{866				,"cp866"},			//Cyrillic Alphabet (DOS)
	{28595				,"iso-8859-5"},		//Cyrillic Alphabet (ISO)
	{20866				,"koi8-r"},			//Cyrillic Alphabet (KOI8-R)
	{1251				,"windows-1251"},	//Cyrillic Alphabet (Windows)
	{28597				,"iso-8859-7"},		//Greek Alphabet (ISO)
	{1253				,"iso-8859-7"},		//Greek Alphabet (Windows)
	{862				,"DOS-862"},		//Hebrew Alphabet (DOS)
	{28598				,"iso-8859-8"},		//Hebrew Alphabet (ISO)
	{1255				,"windows-1255"},	//Hebrew Alphabet (Windows)
	{50220				,"iso-2022-jp"},	//Japanese (JIS)
	{50221				,"csISO2022JP"},	//Japanese (JIS - allow 1-byte Kana)
	{50222				,"iso-2022-jp"},	//Japanese (JIS - allow 1-byte Kana)
	{51932				,"euc-jp"},			//Japanese (EUC)
	{932				,"shift_jis"},		//Japanese (Shift-JIS)
	{949				,"euc-kr"},			//Korean
	{50225				,"iso-2022-kr"},	//Korean (ISO)
	{28593				,"iso-8859-3"},		//Latin 3 Alphabet (ISO)
	{874				,"windows-874"},	//Thai (Windows)
	{1254				,"iso-8859-9"},		//Turkish Alphabet (Windows)
	{21866				,"koi8-ru"},		//Ukrainian Alphabet (KOI8-RU)
	{1200				,"unicode"},		//Unicode (BMP of ISO 10646)
	{65000				,"utf-7"},			//Universal Alphabet (UTF-7)
	{65001				,"utf-8"},			//Universal Alphabet (UTF-8)
	{1258				,"windows-1258"},	//Vietnamese Alphabet (Windows)
	{0,NULL}
};

UINT find_codepage(char *charset) {
	int i;

	for(i = 0; conv_page[i].charset; i++) { 
		if (strcmpnc(conv_page[i].charset,charset)!=0) continue;
		return conv_page[i].codepage;
	}
	return 0;
}
*/