/*
 * Copyright (c) 1998-2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 */

/*
 * pop3-gw.c, a Post Office Protocol version 3 proxy designed to work
 *                with TIS fwtk
 *
 * (C) Copyright 1998-2004 by ArkanoiD <ark@eltex.ru>
 *
 */

#include <stdio.h>
#include <stdlib.h>  
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include <signal.h>
#include <unistd.h> 
#include <sys/types.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#ifdef	OpenBSD
#include <ssl/md5.h> 
#else
#include <openssl/md5.h> 
#endif
#include <assert.h> 
#include <net/if.h>
#include "firewall.h"
#include "firewall2.h"
#include "fwfunc.h"
#include "auth.h"
#include "newio.h"
#include "ci_milter.h"

static  char*   moduleId ATTR_UNUSED = "$Id: pop3-gw.c,v 1.29 2007/09/22 14:35:25 arkenoi Exp $";

#define MLINE	0400		/* hardcoded	   */

#define	QTEMPLATE	"/tmp/pop3-gw.XXXXXXXXXX"

#define TIMESTAMPLEN 64
#include "transaction.h"


#define	BUFSIZE	4096		/* should not be less than any definitions
				   below				    */	
#define MAXCMD	256		/* Maximum length of pop3 command with args */
#define MAXRESP	256		/* Longest single-line responce		    */
#define MAXLINE 998		/* Longest message line, rfc2822	    */

static	int	fallback = 0;
static	int	transparent = 0;
static	int	apop_only = 0;
static	int	extendperm = 0;

static	Cfg	*confp;
static	char	**validests = (char **)0;
static	char	**validusers = (char **)0;
static  char	separator[2] = "@";
static	int	user_limit = AUTH_USIZ;
static	int	pass_limit = AUTH_PWSIZ;
static	int	cdscp = 0;
static	int	sdscp = 0;

static	char	username[AUTH_USIZ+1];

#if (MAXCMD >= MAX_STR) || (MAXRESP >= MAX_STR) || (MAXLINE >= MAX_STR)
#error MAX_STR is too small!
#endif

static fwparm	options[] = {	
	{	FWPARM_BOOL,	"-apop-only",	(char*)	&apop_only	},
	{	FWPARM_BOOL,	"-fallback",	(char*)	&fallback	},
 	{	FWPARM_BOOL,	"-transparent",	(char*)	&transparent	},
	{	FWPARM_BOOL,	"-extnd",	(char*)	&extendperm	},
	{	FWPARM_CHAR,	"-separator",		separator	},
	{	FWPARM_LIST,	"-dest",	(char*)	&validests	},
	{	FWPARM_LIST,	"-user",	(char*) &validusers	},
	{	FWPARM_STRING,	"-plug-to",	proxy_stats.dst		},
	{	FWPARM_PORT,	"-port",	(char*)&proxy_stats.port},
	{	FWPARM_STRING,	"-authuser",	proxy_stats.authuser	},
	{	FWPARM_OPLOG,	"-log",	(char*) &transaction_descr	},
	{	FWPARM_OPDENY,	"-deny",(char*) &transaction_descr	},
	{       FWPARM_DSCP,    "-client-dscp", (char*) &cdscp		},
	{       FWPARM_DSCP,    "-server-dscp", (char*) &sdscp		},
	{	0, 		0,		0 			} };

extern	int auth_perm(Cfg*,char*,char*,char*,char**); 
static  int get_response(int,int);
static  int get_multiline(int);

extern int milter_inspect_mail(char*,char*,char*,char*,char*,char*,char***);

static int 
trim_eol(s)
char	*s;
{
	if (!s || strcmp(s+strlen(s)-2,"\r\n"))
		return(1);
	else
		s[strlen(s)-2] = '\0';
	return(0);
}

static int
read_server(server_fd,buffer,maxsize)
int	server_fd;
char*	buffer;
int	maxsize;
{
	int	x;

	memset(buffer,0,maxsize);
	if (((x = (sogets(server_fd, buffer, maxsize-1))) <= 0) ||
	    trim_eol(buffer)) {
		if (((x = strlen(buffer))== (maxsize-1))) {
			syslog(LLEV,"securityalert: server line too long, truncated");
			return(x);
		}
		close(server_fd);
		syslog(LLEV,"error reading server data");
		sosay(0,"-ERR error reading server data");
		proxy_exit();
	} else
		return(x-=2);
	return(-1); 
}

static void
puts_fd(fd,line)
int	fd;
char	*line;
{
	int	x;
	x = strlen(line);
	if (sosayn(fd,line,x)) {
		close(fd);
		if (fd) {
			syslog(LLEV,"error writing server data, %s", strerror(errno));
			sosay(0,"-ERR error writing server data");
		} else
			syslog(LLEV,"error writing client data, %s", strerror(errno));
		proxy_exit();
	}
	if (fd)
		proxy_stats.outbytes += x+2;
	else
		proxy_stats.inbytes += x+2;
}

static void
puts_qf(fd,line)
int	fd;
char	*line;
{
	int	x;
	x = strlen(line);
	if ((write(fd,line,x) != x) || (write(fd,"\r\n",2) != 2)) {
		close(fd);
		syslog(LLEV,"fwtksyserr: error writing quarantine file, %s", strerror(errno));
		proxy_exit();
	}
}

static void
reject_qf(char* qf)
{
	int	tfd;

	if (unlink(qf)) {
		syslog(LLEV,"fwtksyserr: error unlinking quarantine file, %s", strerror(errno));
		proxy_exit();
	}
	if ((tfd = open(qf,S_IRUSR|S_IWUSR,O_CREAT|O_TRUNC|O_WRONLY)) == -1) {
		syslog(LLEV,"fwtksyserr: cannot create temporary file");
		proxy_exit();
	}
        puts_qf(tfd, "Return-Path: <quarantine@localhost>");
	puts_qf(tfd, "From: \"Firewall content inspection service\" <quarantine@localhost>");
	puts_qf(tfd, "Subject: Message was rejected by content inspection service");
	puts_qf(tfd, "X-Reject: YES");
	puts_qf(tfd, "Lines: 1\r\n");
	puts_qf(tfd, "");
	puts_qf(tfd, "The message does not meet filtering criteria and cannot be displayed");
	close(tfd);
}

/*
 *	Parse rfc2822 address simple way and get address itself.
 *	NOTE: this function works for reference information only and
 *	may provide incaccurate data, keeps inline comments, ingores
 *	quoting etc etc. It is not important for pop3 gateway but
 *	use of this function should be restricted for such purpose only.
 *	That's why we are static.
 */

static char*
parse_address(address)
char*	address;
{
	char		*p,*p1;
	static char	mybuf[MAXLINE];
	
	memset(mybuf,0,sizeof(mybuf));

	if (!address || !(*address))
		return(NULL);

	strncpy(mybuf,address,sizeof(mybuf)-1);
	if (!(p=index(mybuf,'<'))) 
		return(mybuf);
	if (!(*(++p)) || !(p1=rindex(p,'>')))
		return(NULL);
	*(p1) = '\0';
	return(p);
}

/*
 *	Transaction state functions 
 */


/*
 *	Handle a pop3 command that has no arguments
 */

int
do_noargs(server_fd,command,flags,argc,argv)
int	server_fd;
char*	command;
int	flags;
int	argc;
char	**argv;
{
	if (argc != 1) {
		syslog(LLEV,"%.100s command syntax error %d",command,argc);
		puts_fd(0,"-ERR syntax error");
		return(0);
	} else {
		puts_fd(server_fd,argv[0]);
		get_response(server_fd,flags);
	}
	return(0);
}

/*
 *	Handle pop3 commands that take single message number as argument
 */

int
do_message(server_fd,command,flags,argc,argv)
int	server_fd;
char*	command;
int	flags;
int	argc;
char	**argv;
{
	char			buf[1024];
	unsigned	int	msgnum;

	memset(buf,0,sizeof(buf));

	if (argc != 2) {
		syslog(LLEV,"%.100s command syntax error",command);
		puts_fd(0,"-ERR syntax error");
		return(0);
	} else {
		if (!(msgnum = atoi(argv[1]))) {
			puts_fd(0,"-ERR message number invalid");
			return(0);
		}
		snprintf(buf,1023,"%.4s %u",argv[0],msgnum);
		puts_fd(server_fd,buf);
		get_response(server_fd,flags);
	}
	return(0);
}

/*
 *	Handle generic pop3 command - no advanced parsing here (wrong?)
 */

static int
do_generic(server_fd,command,flags,argc,argv)
int	server_fd;
char*	command;
int	flags;
int	argc;
char	**argv;
{
	puts_fd(server_fd,command);
	get_response(server_fd,flags);
	return(0);
}


/*
 *	Handle RETR and TOP commands (special case)
 */

int
do_retr_top(server_fd,command,flags,argc,argv)
int	server_fd;
char*	command;
int	flags;
int	argc;
char	**argv;
{
	char			buf[1024];
	char			okmesg[1024];
	unsigned	int	msgnum;
	unsigned	int	numlines;
	unsigned	int	savedcount;
	char*			from;
	Cfg*			cf;
	int			tfd;
	char*			qf;
	char**			rcpt;
	FILE			*tf;

	memset(buf,0,sizeof(buf));
	memset(okmesg,0,sizeof(okmesg));
	numlines = 99999999;

	if (argc < 2) {
		syslog(LLEV,"%.100s command syntax error",command);
		puts_fd(0,"-ERR syntax error");
		return(0);
	}

	if (!(msgnum = atoi(argv[1]))) {
		puts_fd(0,"-ERR message number invalid");
		return(0);
	}

	if (!(strcasecmp(argv[0],"RETR"))) {
		if (argc != 2) {
			syslog(LLEV,"%.100s command syntax error",command);
			puts_fd(0,"-ERR syntax error");
			return(0);
		}
		snprintf(buf,1023,"RETR %u",msgnum);
	} else if (!(strcasecmp(argv[0],"TOP"))) {
		if (argc != 3) {
			syslog(LLEV,"%.100s command syntax error",command);
			puts_fd(0,"-ERR syntax error");
			return(0);
		}
		numlines = atoi(argv[2]);
		snprintf(buf,1023,"TOP %u %u",msgnum,numlines);
	} else {
		syslog(LLEV,"fwtksyserr: internal logic error, cannot handle %.128s",command);
		proxy_exit();
	}

	if (!numlines)
		return(do_generic(server_fd,command,flags,argc,argv));

	puts_fd(server_fd,buf);
	read_server(server_fd,buf,MAXRESP);
	if (strncmp(buf,"+OK",3)) {
		puts_fd(0,buf);
		return(0);
	}

	qf = xstrdup(QTEMPLATE);
	if ((tfd = mkstemp(qf)) == -1) {
		syslog(LLEV,"fwtksyserr: cannot create temporary file");
		proxy_exit();
	}
	savedcount = proxy_stats.inbytes;
	from = NULL;
/* 
 *	XXX the way we get from address is ugly and definitely needs fixing
 */

/*
 *	header processing starts here
 */

	while(1) {
		read_server(server_fd,buf,MAXLINE);
		puts_qf(tfd,buf);
		if (!from) {
			if (!strncmp(buf,"Return-Path: ",13)) {
				if (!(from = parse_address(buf+13)))
					syslog(LLEV,"DEBUG: malformed Return-Path");
			} else if (!strncmp(buf,"From: ",6)) {
				if (!(from = parse_address(buf+6)))
					syslog(LLEV,"DEBUG: malformed From");
			} 
		}
		if (!strcmp(buf,".")) 
			goto qfdone;
/*
 *		End of header - stop
 */
		if (!strcmp(buf,"")) 
			break;
	}
/*
 *	Now we continue with message body
 */
	while(1) {
		read_server(server_fd,buf,MAXLINE); 
		if (!strcmp(buf,"."))
			break;
		puts_qf(tfd,buf);
	}
qfdone:
	close(tfd);
	if (!from)
		from = "unknown";
	snprintf(buf,sizeof(buf)-1,"%.40s@%.80s",username,proxy_stats.dst);
	rcpt = xmalloc(sizeof(char*)*2);
	rcpt[0] = xstrdup(buf);
	rcpt[1] = NULL;
	cf=cfg_get("milter", confp);
	while (cf != (Cfg *)0) {
		int mfstate;
		if((cf->argc < 1) || (cf->argv[0][0] == '-')) {
			syslog(LLEV,"fwtkcfgerr: missing parameter, line %d",cf->ln);
		}
		if (((mfstate = milter_inspect_mail("pop3-gw",
						    proxy_stats.rladdr,
						    proxy_stats.riaddr,
						    cf->argv[0],
						    qf,from,&rcpt)) & MS_ER)) {
			syslog(LLEV,"fwtksyserr: milter %s failed",cf->argv[0]);
			puts_fd(0,"-ERR milter failed");
		 	proxy_exit();	
		}
		if (mfstate & MS_RJ) {
			char *reject_policy;
			/*
		 	 *	no policy or "replace" - replace message with special one
			 *	no special action taken - the mail cleint will decide
		 	 */
			if (!(reject_policy = proxy_conf_string(proxy_confp,
							       "milter-reject-policy")) ||
				!(strcmp(reject_policy,"replace"))) {
				reject_qf(qf);
				goto resp;
			}
			/*
			 *	"delete" policy - delete message and send -ERR to client
			 */	
			if (!(strcmp(reject_policy,"delete"))) {
				snprintf(buf,1023,"DELE %u",msgnum);
				puts_fd(server_fd,buf);
				read_server(server_fd,buf,MAXRESP);
				if (strncmp(buf,"+OK",3)) {
					puts_fd(0,"-ERR message rejected by content filter; "
						"further errors occured while deleting message");
					return(0);
				}
			}
			/*
			 *	"keep" policy - send -ERR to client but do not delete
			 */
			puts_fd(0,"-ERR message rejected by content filter");
			return(0);
		}
		cf=cfg_get("milter",(Cfg *)0);
	}
resp:
	puts_fd(0,"+OK");
	if (!(tf = fopen(qf,"r"))) {
		syslog(LLEV,"fwtksyserr: cannot open quarantine file");
		proxy_exit();
	}
	memset(buf,0,sizeof(buf));
	while(fgets(buf,sizeof(buf),tf)) {
		if (trim_eol(buf))
			syslog(LLEV,"warning: unterminated line");
		puts_fd(0,buf);
		memset(buf,0,sizeof(buf));
	}
	puts_fd(0,".");
	fclose(tf);
	unlink(qf);  
	free(rcpt[0]);
	free(rcpt);
	free(qf);

	syslog(LLEV,"mail from=<%.128s> to=<%.40s@%.80s> bytes=%u",
		     from,username,proxy_stats.dst,
		     proxy_stats.inbytes-savedcount);
	return(0);
}

/*
 *	Commands that are multiline if no arg is given and provide single
 *	responce if message number is ised as argument (LIST and UIDL)
 */

int
do_list(server_fd,command,flags,argc,argv)
int     server_fd;
char*   command;
int     flags;
int     argc;
char    **argv;
{
	switch (argc) {
	case 1:
		return(do_noargs(server_fd,command,MLINE,argc,argv));
	case 2:
		return(do_message(server_fd,command,0,argc,argv));
	default:
		syslog(LLEV,"%.100s command syntax error",command);
		puts_fd(0,"-ERR syntax error");
		return(0);
	}
}

/*
 *	Handle restricted and unimplemented commands
 */

int
do_restrict(server_fd,command,flags,argc,argv)
int	server_fd;
char*	command;
int	flags;
int	argc;
char	**argv;
{
	puts_fd(0,"-ERR not implemented");
	return(0);
}

/*
 *	Handle QUIT command and termination state
 */

int
do_quit(server_fd,command,flags,argc,argv)
int	server_fd;
char*	command;
int	flags;
int	argc;
char	**argv;
{
	puts_fd(server_fd,"QUIT");
	get_response(server_fd,0);
	proxy_exit();
	return(0);
}

static int
get_multiline(int server_fd)
{
	char		buf[1024];

	while(1) {
		read_server(server_fd,buf,MAXLINE); 
		puts_fd(0,buf);
		if (!strcmp(buf,"."))
			return(0);
	}
}

static int
get_response(int server_fd,int flags)
{
	char		buf[BUFSIZE];

	read_server(server_fd,buf,MAXRESP);
	puts_fd(0,buf);

	if (!strncmp(buf,"+OK",3) && (flags & MLINE)) 
		return(get_multiline(server_fd));
	return(0);
}

static int
parse_user_cmd(buffer,user)
char	*buffer;
char	*user;
{
	char	*p;

	if ((p = strtok(buffer+5,separator)) != NULL)
		strlcpy(user,p,AUTH_USIZ);
	if ((p = strtok(NULL,":")) != NULL)
		strlcpy(proxy_stats.dst,p,MAX_HOSTNAME);
	if ((p = strtok(NULL,"\r\n")) != NULL)
		proxy_stats.port = atoi(p);
	if (!proxy_stats.port)
		proxy_stats.port = str_to_port("pop3");
	if ((strlen(user)==0) || (strlen(proxy_stats.dst)==0) || 
	    (strlen(user) > user_limit))
		return(1);
	return(0);
}

static int
parse_apop_cmd(buffer,user,digest)
char	*buffer;
char	*user;
char	*digest;
{
	char	*p;
	if ((p = strtok(buffer+5," ")) != NULL)
		strlcpy(user,p,AUTH_USIZ);
	if ((p = strtok(NULL,"\r\n")) != NULL)
		strncpy(digest,p,32);
	return ((strlen(user)==0) || 
		(strlen(user) > user_limit) || (strlen(digest)!=32));
}

static int
parse_server_greeting(buffer,timestamp)
char	*buffer;
char	*timestamp;
{
	char	*p;

	if (strncasecmp(buffer,"+OK",3)) 
		return(1);
	
	strlcpy(timestamp,"none",TIMESTAMPLEN);

	if ((p = strchr(buffer,'<'))) {
		if(!strchr(p,'>')) {
			syslog(LLEV,"warning: server provided no timestamp and probably does not support APOP");
		} else {
			strlcpy(timestamp,p,TIMESTAMPLEN);
			syslog(LLEV,"received timestamp from server: %.64s",timestamp);
		}
	} else
		syslog(LLEV,"warning: server provided no timestamp and probably does not support APOP");
	return(0);

}

static int
do_server_auth_raw(server_fd,user,digest)
int	server_fd;
char	*user;
char	*digest;
{
	char buf[BUFSIZE];

	snprintf(buf,BUFSIZE,"APOP %s %s",user,digest);
	puts_fd(server_fd,buf);
	if(read_server(server_fd,buf,MAXRESP) <= 0) 
		return(1);
	if (strncasecmp(buf,"+OK",3)) 
		return(1);

	puts_fd(0,"+OK authenticated to server via APOP");
	return(0);
}

static void
tohex(hex,raw,len)
char	*hex; 
char	*raw;
int	len;
{
	char *hdig="0123456789abcdef";

	while(len > 0){
		len--;
		*hex++ = hdig[((*raw)>>4)&0xf];
		*hex++ = hdig[(*raw++)&0xf];
	}
	*hex++ = '\0';
	return;
}

static int
do_server_auth(server_fd,user,password,timestamp)
int	server_fd;
char	*user;
char	*password;
char	*timestamp;
{
	MD5_CTX	ctx;
	unsigned char digest_raw[17];
	unsigned char digest_hex[35];
	char buf[BUFSIZE];

	MD5_Init(&ctx);
	MD5_Update(&ctx,(unsigned char*) timestamp,strlen(timestamp));
	MD5_Update(&ctx,(unsigned char*) password,strlen(password));
	MD5_Final(digest_raw,&ctx);
	tohex(digest_hex,digest_raw,16);
	snprintf(buf,BUFSIZE,"APOP %s %s",user,digest_hex);
	puts_fd(server_fd,buf);
	if(read_server(server_fd,buf, MAXRESP) <= 0) 
		return(1);
	if (strncasecmp(buf,"+OK",3)) 
		return(1);

	puts_fd(0,"+OK authenticated to server via APOP");
	return(0);

}

static int
do_server_pauth(server_fd,user,password)
int	server_fd;
char	*user;
char	*password;
{
	char buf[MAXRESP];

	snprintf(buf,MAXRESP,"USER %.128s",user);
	puts_fd(server_fd,buf);
	read_server(server_fd,buf,sizeof(buf));
	if (strncasecmp(buf,"+OK",3)) 
		return(1);
	snprintf(buf,MAXRESP,"PASS %.128s",password);
	puts_fd(server_fd,buf);
	read_server(server_fd,buf,sizeof(buf));
	if (strncasecmp(buf,"+OK",3)) 
		return(1);

	puts_fd(0,"+OK authenticated to server via USER/PASS");
	return(0);
	
}

int
main(int argc, char *argv[])
{
	char			buffer[MAX_STR];
	char			pass[AUTH_PWSIZ];
	char			timestamp[TIMESTAMPLEN];
	char			digest_hex[35];
	int			server_fd = -1;
	int			stage_complete;
 	char			our_host[MAXHOSTNAMELEN];
	char			tokbuf[MAX_STR];
	char			*tokav[MAX_ARG];
	int			tokac;


	proxy_stats.port = 110;

	proxy_init(argc,argv);
	proxy_chroot_setugid();
	if (cdscp)
		 proxy_set_dscp(0,cdscp);

	if ((confp=proxy_conf_hosts(proxy_confp,proxy_stats.rladdr,						       proxy_stats.riaddr))) 
		proxy_parse_options(confp,options);
	else {
		sosay(0, "-ERR access denied");
		proxy_exit();
	}

	user_limit = proxy_conf_int(proxy_confp,"user-limit",1,sizeof(username)-1,user_limit);
	pass_limit = proxy_conf_int(proxy_confp,"pass-limit",1,sizeof(pass)-1,pass_limit);

	sotimeout(proxy_timeout);
	memset(buffer, 0, sizeof(buffer));
	memset(username, 0, sizeof(username));
	memset(pass, 0, sizeof(pass));

	if (!(*proxy_stats.dst) && !transparent) {
		gethostname(our_host,MAXHOSTNAMELEN);
		snprintf(timestamp,TIMESTAMPLEN,"<%d.%u@%.64s>", getpid(), (u_int) proxy_stats.start_time, our_host);
	} else {
		if (transparent) {
/*
 *	We do not check if port number is permitted because it comes
 *	from redirection rule we defined manually anyways.
 */
			proxy_get_transparent_dst(proxy_stats.dst,&proxy_stats.port);

			if (proxy_check_dest(validests,extendperm)) {
				sosay(0,"-ERR POP3 gateway denied destination access");
				proxy_exit();
			}
		} 

		proxy_update_operation("CONNECTING");

		if((server_fd = conn_server(proxy_stats.dst,proxy_stats.port,0,(char *)0)) < 0) {
			syslog(LLEV,"cannot connect to server %s/%d: %s", proxy_stats.dst,proxy_stats.port, strerror(errno));
			sosay(0,"-ERR POP3 gateway destination connect failed");
			proxy_exit();
		}
		read_server(server_fd,buffer,MAXRESP);
		if (parse_server_greeting(buffer,timestamp) != 0) {
			close(server_fd);
			syslog(LLEV,"server greeting message is invalid");
			sosay(0,"-ERR server greetimg message is invalid");
			proxy_exit();
		} 
	}

	snprintf(buffer,sizeof(buffer),"+OK POP3 gateway ready %.128s",timestamp);
	sosay(0,buffer);

	stage_complete = 0;
	proxy_update_operation("AUTHENTICATION");
	while(!stage_complete) {
		if((sogets(0, buffer, 512) <= 0) || trim_eol(buffer))
			proxy_exit();
		if (!strncasecmp(buffer,"USER ",5)) {
			if (apop_only) {
				sosay(0,"-ERR insecure protocol is not permitted");
				goto skip_user;
			}
			if (!(*proxy_stats.dst)) {
				if (parse_user_cmd(buffer,username) != 0) {
					sosay(0,"-ERR incorrect syntax, use user@host[:port] to specify destination"); 
				} else {
					if (searchlist(username,validusers)) {
						sosay(0,"-ERR access denied");	
                                        	syslog(LLEV,"deny host=%.512s/%.20s access for user %s",proxy_stats.rladdr,proxy_stats.riaddr,username);  
						proxy_exit();
					}
					if (proxy_check_dest(validests,extendperm)) {
						sosay(0,"-ERR destination access denied");
						proxy_exit();
					}
	
					proxy_update_operation("CONNECTING");
					if((server_fd = conn_server(proxy_stats.dst,proxy_stats.port,0,(char *)0)) < 0) {
						syslog(LLEV,"cannot connect to server %s/%d: %s", proxy_stats.dst,proxy_stats.port, strerror(errno));
						sosay(0,"-ERR cannot connect to server");
						proxy_exit();
					}
					proxy_update_operation("AUTHENTICATION");
					read_server(server_fd,buffer,MAXRESP);
					if (parse_server_greeting(buffer,timestamp) != 0) {
						close(server_fd);
						syslog(LLEV,"server greeting message is invalid");
						sosay(0,"-ERR server greetimg message is invalid");
						proxy_exit();
					} 
					if (!strcmp(timestamp,"none") && !fallback) {
						close(server_fd);
						syslog(LLEV,"fallback to USER/PASS authentication is not permitted");
						sosay(0,"-ERR server does not support APOP");
					} else 
						stage_complete = 1;
				}  
			} else {
				if ((buffer+5) && *(char*)(buffer+5) && 
				    (strlen(buffer+5) <= user_limit)) {
					strncpy(username,buffer+5,sizeof(username));
					if (searchlist(username,validusers)) {
						sosay(0,"-ERR access denied");	
                                        	syslog(LLEV,"deny host=%.512s/%.20s access for user %s",proxy_stats.rladdr,proxy_stats.riaddr,username);  
						proxy_exit();
					}
					stage_complete = 1;
				} else {
					sosay(0,"-ERR syntax error");
				}
			}

skip_user:		;

		} else if (!strncasecmp(buffer,"APOP ",5)) { 
			if (!strcmp(timestamp,"none") || !*proxy_stats.dst) {
				sosay(0,"-ERR APOP is unsupported on client side");	
			} else {
				if (parse_apop_cmd(buffer,username,digest_hex)) {
					sosay(0,"-ERR syntax error");
				} else {
					if (searchlist(username,validusers)) {
						sosay(0,"-ERR access denied");	
                                        	syslog(LLEV,"deny host=%.512s/%.20s access for user %s",proxy_stats.rladdr,proxy_stats.riaddr,username);  
						proxy_exit();
					}
					if (do_server_auth_raw(server_fd,username,digest_hex)) {
						sosay(0,"-ERR APOP authentication failed");
						proxy_exit();
					} else goto transaction_state;
				}
			}
						

		} else if (!strncasecmp(buffer,"QUIT",4)) { 
			sosay(0,"+OK proxy session terminated");	
			proxy_exit();
		} else if (!strncasecmp(buffer,"CAPA",4) || 
          		   !strncasecmp(buffer,"AUTH",4)) {
			sosay(0,"-ERR not implemented");
		} else {
			sosay(0,"-ERR invalid command");
			syslog(LLEV,"unknown authorization state command %.100s",buffer);
		}
	}

	sosay(0,"+OK we are connected to server");

	stage_complete = 0;

	while (!stage_complete) {
		if((sogets(0, buffer, 512) <= 0) || trim_eol(buffer)) {
			syslog(LLEV,"error reading client data");
			proxy_exit();
		} else if (!strncasecmp(buffer,"QUIT",4)) { 
			sosay(0,"+OK proxy session terminated");	
			proxy_exit();
		} else if (!strncasecmp(buffer,"PASS ",5)) {
			strncpy(pass,buffer+5,sizeof(pass));
			if (strlen(pass) <= pass_limit)
				stage_complete = 1;
			else {
				sosay(0,"-ERR password too long");
			}
		} else {
			sosay(0,"-ERR PASS command required to complete AUTHORIZATION state");
		} 
	}

	if ( !strcmp(timestamp,"none") ||
	      (do_server_auth(server_fd,username,pass,timestamp) != 0)) {
/*
 *		APOP authentication failed or not available
 */
		if (!fallback) {
			close(server_fd);
			syslog(LLEV,"user failed to authenticate to server");
			sosay(0,"-ERR server authentication failed");
			proxy_exit();
		} else if (strcmp(timestamp,"none")) {
/*
 *		APOP authentication failed so we have to reconnect to server
 */
			close(server_fd);
			reinit_io();
			syslog(LLEV,"warning: APOP authentication failed, reverting to insecure mode"); 
			proxy_update_operation("CONNECTING");
			if((server_fd = conn_server(proxy_stats.dst,proxy_stats.port,0,(char *)0)) < 0) {
				syslog(LLEV,"cannot connect to server %s/%d: %s", proxy_stats.dst,proxy_stats.port, strerror(errno));
				sosay(0,"-ERR cannot connect to server");
				proxy_exit();
			}
			read_server(server_fd,buffer,MAXRESP);
			if (strncasecmp(buffer,"+OK",3)) {
				close(server_fd);
				syslog(LLEV,"server greeting message is invalid");
				sosay(0,"-ERR server greetimg message is invalid");
				proxy_exit();
			}
		}
/*
 *		Now trying USER/PASS
 */
	        if (do_server_pauth(server_fd,username,pass) != 0) {
			close(server_fd);
			syslog(LLEV,"user failed to authenticate to server");
			sosay(0,"-ERR server authentication failed");
			proxy_exit();
		}
	}
	if (sdscp)
		proxy_set_dscp(server_fd,sdscp);

/*
 *	Proceed to TRANSACTION state
 */
transaction_state:

	while(1) {
		int				msg_int;
		const	struct	hash_entry	*he;
		int				(*handler)(int,char*,int,int,char**);

		proxy_update_operation("IDLE");
		if((sogets(0, buffer, 512) <= 0) || trim_eol(buffer)) {
			syslog(LLEV,"error reading client data");
			proxy_exit();
		}
		tokac = enargv(buffer,tokav,56,tokbuf,sizeof(tokbuf));
		if(!tokav[0])
			continue;
		if (!(he = find_he2(&transaction_descr,tokav[0],strlen(tokav[0])))) {
			syslog(LLEV,"unknown pop3 command %.100s",buffer);
			sosay(0,"-ERR command unknown or out of state");
			continue;
		} else {
			proxy_update_operation(buffer);
			if (he->flags & OP_LOG)
				syslog(LLEV,"%.512s/%.20s: %.512s",proxy_stats.rladdr,proxy_stats.riaddr,buffer);
			if (he->flags & OP_DENY) {
				syslog(LLEV,"deny host=%.512s/%.20s operation %.100s",proxy_stats.rladdr,proxy_stats.riaddr,buffer);
				sosay(0,"-ERR operation denied");
				continue;
			}
			if(extendperm && (he->flags & OP_XTND)) {
				msg_int = auth_perm(proxy_confp,proxy_stats.authuser,"pop3-gw",proxy_stats.riaddr,tokav);
				if(msg_int == 1 || msg_int == -1) {
					syslog(LLEV,"deny host=%.512s/%.20s operation %.100s - extended permissions",proxy_stats.rladdr,proxy_stats.riaddr,buffer);
					sosay(0,"-ERR operation denied");
					continue;
				}
			}
			handler = he->handler;
			assert (TRANSACTION_HANDLER (handler));
			handler(server_fd,buffer,he->flags,tokac,tokav);
			continue;
		}
	}
/*
 *	Unreached
 */					
	proxy_exit();
}


