/*
** Copyright 1998 - 2022 S. Varshavchik.
** See COPYING for distribution information.
**
*/

#if	HAVE_CONFIG_H
#include	"config.h"
#endif
#include	<sys/types.h>
#if HAVE_DIRENT_H
#include <dirent.h>
#define NAMLEN(dirent) strlen((dirent)->d_name)
#else
#define dirent direct
#define NAMLEN(dirent) (dirent)->d_namlen
#if HAVE_SYS_NDIR_H
#include <sys/ndir.h>
#endif
#if HAVE_SYS_DIR_H
#include <sys/dir.h>
#endif
#if HAVE_NDIR_H
#include <ndir.h>
#endif
#endif
#include	<sys/types.h>
#if	HAVE_SYS_STAT_H
#include	<sys/stat.h>
#endif
#include	<time.h>
#if HAVE_SYS_TIME_H
#include	<sys/time.h>
#endif
#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<ctype.h>
#if	HAVE_UNISTD_H
#include	<unistd.h>
#endif
#include	<signal.h>
#include	<errno.h>
#include	"numlib/numlib.h"
#include	"maildir/config.h"
#include	"maildir/maildirmisc.h"
#include	"maildir/maildirquota.h"
#include	"maildir/maildirgetquota.h"
#include	"maildir/maildircreate.h"
#include	"maildir/loginexec.h"
#include	"rfc2045/rfc2045.h"
#include	"courierauth.h"

#include	<vector>
#include	<algorithm>
#include	<fstream>

#define POP3DLIST "courierpop3dsizelist"
#define LISTVERSION 3

#ifndef RUNTIME_START
#define RUNTIME_START time(NULL)
#endif

#ifndef RUNTIME_CUR
#define RUNTIME_CUR time(NULL)
#endif

extern void pop3dcapa();
extern void pop3dlang(const char *);

static void acctout(const char *disc);
void rfc2045_error(const char *p)
{
	if (write(2, p, strlen(p)) < 0)
		_exit(1);
	_exit(0);
}

static const char *authaddr, *remoteip, *remoteport;

struct msglist {
	std::string filename;
	bool isdeleted=false;
	bool isnew=false;
	bool isutf8=false;
	off_t size=0;

	struct {
		unsigned long uidv;
		unsigned long n;
	} uid;

	bool operator<(const msglist &) const;

	void operator<<(const msglist &saved)
	{
		size=saved.size;
		uid=saved.uid;
		isutf8=saved.isutf8;
	}
} ;

static std::vector<msglist> msglist_l;

static struct stat enomem_stat;
static int enomem_1msg;
int utf8_enabled;
int savesizes=0;

/*
** When a disk error occurs while saving an updated courierpop3dsizelist
** file, proceed to recover as follows:
**
** If there's at least one existing message that's found in the old
** courierpop3dsizelist, then ignore all new messages that were not found
** in the old courierpop3dsizelist, and a new uid/uidv was assigned to them.
** Therefore, the client doesn't see any new messages.  Hopefully, after
** at least one existing message gets deleted, there'll be room to create
** a new courierpop3dsizelist file next time, and record new messages.
**
** If none of the messages in the maildir could be found in the
** courierpop3dsizelist, take the first message in the maildir only, and
** use a UIDL that's derived from the message's dev_t/ino_t.  The client
** will see that message only.  After deleting it, hopefully a new
** courierpop3dsizelist file could be written out next time.
*/


static unsigned long top_count=0;
static unsigned long retr_count=0;

static unsigned long bytes_sent_count=0;
static unsigned long bytes_received_count=0;

static unsigned long uidv=0;

static time_t start_time;

/*
** The RFC is pretty strict in stating that octet size must count the CR
** in the CRLF endofline.
*/

static void calcsize(msglist &m)
{
	std::ifstream f{m.filename};

	m.size=0;
	m.isutf8=false;

	if (!f)
		return;

	auto sbuf=f.rdbuf();
	std::istreambuf_iterator<char> b{sbuf}, e;

	rfc2045::entity::line_iter<false>::iter parser{b, e};

	rfc2045::entity message;

	message.parse(parser);

	m.size=message.nlines + message.endbody;

	if (m.size > 0 && sbuf->pubseekoff(-1, std::ios::end) > 0 &&
	    sbuf->sgetc() != '\n')
		m.size+=2; /* We'll add an extra CRLF ourselves */

	m.isutf8=false;
	if (message.all_errors() & RFC2045_ERR8BITHEADER)
		m.isutf8=true;
}

static FILE *
openpop3dlist()
{
	int tries;
	FILE *fp;

	tries = 0;
	do {
		fp = fopen(POP3DLIST, "r");
		if (fp != NULL)
			return (fp);
		if (errno != ESTALE) {
			if (errno != ENOENT)
				perror("failed to open " POP3DLIST " file");
			return (NULL);
		}
		++tries;
	} while (tries < 3); /* somewhat arbitrary */
	fprintf(stderr, "failed to open pop3dlist file after retries\n");
	return NULL;
}

/*
** Read courierpop3dsizelist
*/

static std::vector<msglist> readpop3dlist(unsigned long *uid)
{
	std::vector<msglist> list;

	char linebuf[2048];

	FILE *fp=openpop3dlist();

	int vernum=0;
	unsigned long size;

	uidv=RUNTIME_CUR;

	if (fp == NULL ||
	    fgets(linebuf, sizeof(linebuf)-1, fp) == NULL ||
	    linebuf[0] != '/' || sscanf(linebuf+1, "%d %lu %lu", &vernum,
					uid, &uidv)
	    < 2 || (vernum != LISTVERSION &&
		    vernum != (LISTVERSION-1)))
	{
		if (fp)
			fclose(fp);
		fp=NULL;
	}
	else
	{
		if (vernum != LISTVERSION)
			savesizes=1;
	}

	if (fp)
	{
		char *p, *q;

		size_t n=0;
		int ch;

		while ((ch=getc(fp)) != EOF)
		{
			if (ch != '\n')
			{
				if (n < sizeof(linebuf)-20)
					linebuf[n++]=ch;
				continue;
			}
			linebuf[n]=0;
			n=0;

			if ((p=strrchr(linebuf, ' ')) == NULL)
				continue;
			*p=0;
			if ((q=strrchr(linebuf, ' ')) == NULL)
				continue;
			*p=' ';
			p=q;
			*p++=0;

			if (linebuf[0] == 0)
				continue;

			list.emplace_back();
			auto m=--list.end();

			m->filename=linebuf;

			// Converting from LISTVERSION 2, assuming no UTF-8.
			// We have extra room at the end.
			strcat(p, ":0");

			int isutf8;
			if (sscanf(p, "%lu %lu:%lu:%d", &size,
				   &m->uid.n, &m->uid.uidv,
				   &isutf8) == 4)
			{
				m->size=size;
				m->isutf8=isutf8 != 0;
			}
			else
			{
				list.pop_back();
			}
		}
		fclose(fp);
	}

	std::sort(list.begin(), list.end());

	return list;
}

static int savepop3dlist(const std::vector<msglist> &list,
			 unsigned long uid)
{
#ifdef SAVEHOOK
	SAVEHOOK();
#endif
	maildir::tmpcreate_info createInfo;

	createInfo.uniq="pop3";
	createInfo.doordie=true;

	auto fp=createInfo.fp();

	if (!fp)
		return -1;

	fprintf(fp, "/%d %lu %lu\n", LISTVERSION, uid, uidv);

	for (const auto &m:list)
	{
		const char *p=m.filename.c_str();
		const char *q;

		if ((q=strrchr(p, '/')) != NULL)
			p=q+1;

		fprintf(fp, "%s %lu %lu:%lu:%d\n", p, (unsigned long)m.size,
			m.uid.n, m.uid.uidv, (int)m.isutf8);
	}

	if (fflush(fp) || ferror(fp))
	{
		fclose(fp);
		unlink(createInfo.tmpname.c_str());
		return -1;
	}

	if (fclose(fp) ||
	    rename(createInfo.tmpname.c_str(), POP3DLIST) < 0)
	{
		unlink(createInfo.tmpname.c_str());
		return -1;
	}

	return 0;
}

/* Scan cur, and pick up all messages contained therein */

static int scancur()
{
	DIR	*dirp;
	struct	dirent *de;

	if ((dirp=opendir("cur")) == 0)
	{
		perror("scancur opendir(\"cur\")");
		return 1;
	}

	while ((de=readdir(dirp)) != 0)
	{
		if ( de->d_name[0] == '.' )	continue;

		msglist_l.emplace_back();
		auto m=--msglist_l.end();

		m->filename.reserve(strlen(de->d_name)+5);

		m->filename="cur/";
		m->filename+=de->d_name;
	}
	closedir(dirp);
	return 0;
}

/*
** When sorting messages, sort on the arrival date - the first part of the
** name of the file in the maildir is the timestamp.
*/

bool msglist::operator<(const msglist &b) const
{
	const char *aname=filename.c_str();
	const char *bname=b.filename.c_str();
	const char *ap=strrchr(aname, '/');
	const char *bp=strrchr(bname, '/');
	long	na, nb;

	if (ap)
		++ap;
	else
		ap=aname;

	if (bp)
		++bp;
	else
		bp=bname;

	na=atol(ap);
	nb=atol(bp);

	if (na < nb)	return true;
	if (na > nb)	return false;

	while (*ap || *bp)
	{
		if (*ap == MDIRSEP[0] && *bp == MDIRSEP[0])
			break;

		if (*ap < *bp)
			return true;
		if (*ap > *bp)
			return false;
		++ap;
		++bp;
	}

	return false;
}

static void sortmsgs()
{
	unsigned long nextuid;

	if (msglist_l.empty())	return;

	std::sort(msglist_l.begin(), msglist_l.end());

	nextuid=1;

	auto prev_list=readpop3dlist(&nextuid);

	auto b=prev_list.begin(), e=prev_list.end();

	for (size_t i=0; i<msglist_l.size(); i++)
	{
		while (b != e && *b < msglist_l[i])
		{
			++b;
			savesizes=1;
		}

		if (b != e && !(msglist_l[i] < *b))
		{
			msglist_l[i] << *b;
			++b;
		}
		else
		{
			msglist_l[i].uid.n=nextuid++;
			msglist_l[i].uid.uidv=uidv;
			msglist_l[i].isnew=true;

			calcsize(msglist_l[i]);
			savesizes=1;
		}
	}

	if (b != e)
	{
		savesizes=1;
	}

	prev_list.clear();

	if (savesizes && savepop3dlist(msglist_l, nextuid) < 0)
	{
		fprintf(stderr, "ERR: Error while saving courierpop3dsizelist"
			", user=%s\n", authaddr);

		auto p=std::remove_if(
			msglist_l.begin(), msglist_l.end(),
			[&]
			(msglist &m)
			{
				return m.isnew;
			});

		if (!msglist_l.empty() && p == msglist_l.begin() &&
		    stat(msglist_l[0].filename.c_str(), &enomem_stat) == 0)
		{
			enomem_1msg=1;
			++p;
		}
		msglist_l.erase(p, msglist_l.end());
	}
}

static void mkupper(char *p)
{
	while (*p)
	{
		*p=toupper(*p);
		p++;
	}
}

static void cleanup()
{
	const char *cp=getenv("POP3_LOG_DELETIONS");
	int log_deletions= cp && *cp != '0';

	int64_t deleted_bytes=0;
	int64_t deleted_messages=0;

	for (auto &m:msglist_l)
		if (m.isdeleted)
		{
			unsigned long un=0;

			const char *filename=m.filename.c_str();

			if (maildirquota_countfile(filename))
			{
				if (maildir_parsequota(filename, &un))
				{
					struct stat stat_buf;

					if (stat(filename, &stat_buf) == 0)
						un=stat_buf.st_size;
				}
			}

			if (log_deletions)
				fprintf(stderr, "INFO: DELETED, user=%s, ip=[%s], port=[%s], filename=%s\n",
					getenv("AUTHENTICATED"),
					remoteip,
					remoteport,
					m.filename.c_str());

			if (unlink(m.filename.c_str()))
				un=0;

			if (un)
			{
				deleted_bytes -= un;
				deleted_messages -= 1;
			}
		}

	if (deleted_messages < 0)
		maildir_quota_deleted(".", deleted_bytes, deleted_messages);

	return;
}

#define printed(c) do {int cnt=(c); if (cnt > 0)			\
					   bytes_sent_count += cnt;	\
	} while(0)

#define printchar(ch) do { putchar((ch)); printed(1); } while(0);

/* POP3 STAT */

static void do_stat()
{
	off_t	n=0;
	unsigned j;
	char	buf[NUMBUFSIZE];

	j=0;
	for (auto &m:msglist_l)
	{
		if (m.isdeleted)	continue;
		n += m.size;
		++j;
	}

	printed(printf("+OK %u %s\r\n", j, libmail_str_off_t(n, buf)));
	fflush(stdout);
}

static unsigned getmsgnum(const char *p)
{
	unsigned i;

	if (!p || (i=atoi(p)) > msglist_l.size() || i == 0 ||
		msglist_l[i-1].isdeleted)
	{
		printed(printf("-ERR Invalid message number.\r\n"));
		fflush(stdout);
		return (0);
	}
	return (i);
}

/* POP3 LIST */

static void do_list(const char *msgnum)
{
	unsigned i;
	char	buf[NUMBUFSIZE];

	if (msgnum)
	{
		if ((i=getmsgnum(msgnum)) != 0)
		{
			printed(printf("+OK %u %s\r\n", i,
				       libmail_str_off_t(msglist_l[i-1].size,
							 buf)));
			fflush(stdout);
		}
		return;
	}

	printed(printf("+OK POP3 clients that break here, they violate STD53.\r\n"));
	i=0;

	for (auto &m:msglist_l)
	{
		if (m.isdeleted)	continue;
		printed(printf("%u %s\r\n", ++i,
			       libmail_str_off_t(m.size, buf)));
	}
	printed(printf(".\r\n"));
	fflush(stdout);
}

/* RETR and TOP POP3 commands */

#define MIMEWRAPTXT							\
	"From: Mail Delivery Subsystem <postmaster>\n"			\
	"Subject: Cannot display Unicode content\n"			\
	"Mime-Version: 1.0\n"						\
	"Content-Type: multipart/mixed; boundary=\"%s\"\n"		\
	"\n\n\n--%s\n"							\
	"Content-Type: text/plain\n\n"					\
	"This E-mail message was determined to be Unicode-formatted\n"	\
	"but your E-mail reader does not support Unicode E-mail.\n\n"	\
	"Please use an E-mail reader that supports POP3 with UTF-8\n"	\
	"(see https://datatracker.ietf.org/doc/html/rfc6856).\n\n"		\
	"This can also happen when the sender's E-mail program does not\n" \
	"correctly format the sent message.\n\n"			\
	"The original message is included as a separate attachment\n"	\
	"so that it can be downloaded manually.\n\n"			\
	"--%s\n"							\
	"Content-Type: %s\n\n"

struct retr_source {
	char *wrapheader;
	FILE *f;
	char *wrapfooter;
};

static int get_retr_source(struct retr_source *s)
{
	int c=(unsigned char)*s->wrapheader;

	if (c)
	{
		++s->wrapheader;
		return c;
	}

	if (s->f)
	{
		int c=getc(s->f);

		if (c >= 0)
			return c;
		s->f=0;
	}

	if (*s->wrapfooter)
	{
		int c=(unsigned char)*s->wrapfooter;

		++s->wrapfooter;
		return c;
	}
	return -1;
}

static void do_retr(msglist &m, unsigned *lptr)
{
	FILE	*f;
	int	c, lastc='\n';
	int	inheader=1;
	char	buf[NUMBUFSIZE];
	unsigned long *cntr;
	char boundary[256];
	char wrapheader[sizeof(MIMEWRAPTXT)+1024];
	char wrapfooter[512];
	const char *mime_message_type=getenv("MIME_UNICODE_MESSAGE_TYPE");
	struct retr_source rs;

	wrapheader[0]=0;
	wrapfooter[0]=0;

	if (!mime_message_type)
		mime_message_type="message/global";

	if (m.isutf8 && !utf8_enabled)
	{
		sprintf(boundary, "=_%d-%d", (int)getpid(), (int)RUNTIME_CUR);

		sprintf(wrapheader, MIMEWRAPTXT, boundary, boundary, boundary,
			mime_message_type);
		sprintf(wrapfooter, "\n--%s--\n", boundary);
	}

	f=fopen(m.filename.c_str(), "r");

	if (!f)
	{
		printed(printf("-ERR Can't open the message file - it's gone!\r\n"));
		fflush(stdout);
		return;
	}
	printed(printf( (lptr ? "+OK headers follow.\r\n":"+OK %s octets follow.\r\n"),
			libmail_str_off_t(m.size +
					  strlen(wrapheader) +
					  strlen(wrapfooter),
					  buf)));

	cntr= &retr_count;
	if (lptr)
		cntr= &top_count;

	rs.wrapheader=wrapheader;
	rs.f=f;
	rs.wrapfooter=wrapfooter;

	for (lastc=0; (c=get_retr_source(&rs)) >= 0; lastc=c)
	{
		if (lastc == '\n')
		{
			if (lptr)
			{
				if (inheader)
				{
					if (c == '\n')	inheader=0;
				}
				else if ( (*lptr)-- == 0)	break;
			}

			if (c == '.')
				printchar('.');
		}
		if (c == '\n')	printchar('\r');
		printchar(c);
		++*cntr;
	}
	if (ferror(f)) {
		/* Oops! All we can do is drop the connection */
		fprintf(stderr, "ERR: I/O error while reading message file %s: %s\n",
			m.filename.c_str(), strerror(errno));
		acctout("INFO: I/O error disconnect");
		exit(1);
	}
	if (lastc != '\n')	printed(printf("\r\n"));
	printed(printf(".\r\n"));
	fflush(stdout);
	fclose(f);
	if (lptr)	return;

	const char *q;
	if ((q=strchr(m.filename.c_str(), MDIRSEP[0])) != 0 &&
		(q[1] != '2' || q[2] != ',' || strchr(q, 'S') != 0))
		return;

	std::string p;

	p.reserve(m.filename.size()+5);

	p=m.filename;

	if (p.find(MDIRSEP[0]) == p.npos)
		p += MDIRSEP "2,";

	p += "S";

	if (lptr	/* Don't mark as seen for TOP */
	    || rename(m.filename.c_str(), p.c_str()))
	{
		return;
	}
	m.filename=p;
}

/*
** The UIDL of the message is really just its filename, up to the first MDIRSEP character
*/

static void print_uidl(msglist &m)
{
	const char *p;

	if (enomem_1msg)
		/* Error recovery - out of disk space, see comments
		** at the beginning of this file.
		*/
	{
		char dev_buf[NUMBUFSIZE];
		char ino_buf[NUMBUFSIZE];
		char mtime_buf[NUMBUFSIZE];

		printed(printf("ENOMEM-%s-%s-%s\r\n",
			       libmail_strh_time_t(enomem_stat.st_mtime, mtime_buf),
			       libmail_strh_dev_t(enomem_stat.st_dev, dev_buf),
			       libmail_strh_ino_t(enomem_stat.st_ino, ino_buf))
			);
		return;
	}

	if (m.uid.n != 0)
	{
		/* VERSION 1 and VERSION 2 UIDL */

		printed(printf((m.uid.uidv ?
				"UID%lu-%lu\r\n":"UID%lu\r\n"),
			       m.uid.n, m.uid.uidv));
		return;
	}

	/* VERSION 0 UIDL */

	p=strchr(m.filename.c_str(), '/')+1;

	while (*p && *p != MDIRSEP[0])
	{
		if (*p < 0x21 || *p > 0x7E || *p == '\'' || *p == '"' ||
			*p == '+')
			printed(printf("+%02X", (int)(unsigned char)*p));
		else
			printchar(*p);
		++p;
	}
	printed(printf("\r\n"));
}

static void do_uidl(const char *msgnum)
{
	unsigned i;

	if (msgnum)
	{
		if ((i=getmsgnum(msgnum)) != 0)
		{
			printed(printf("+OK %u ", i));
			print_uidl(msglist_l[i-1]);
			fflush(stdout);
		}
		return;
	}
	printed(printf("+OK\r\n"));

	i=0;
	for (auto &m:msglist_l)
	{
		if (m.isdeleted)	continue;
		printed(printf("%u ", i+1));
		print_uidl(m);
		++i;
	}
	printed(printf(".\r\n"));
	fflush(stdout);
}

static void acctout(const char *disc)
{
	static const char msg2[]=", user=";
	static const char msg3[]=", ip=[";
	static const char msgport[]="], port=[";
	static const char msg4[]="], top=";
	static const char msg5[]=", retr=";
	static const char msg6[]=", time=";
	static const char msg7[]=", stls=1";
	static const char msgAR[]=", rcvd=";
	static const char msgAS[]=", sent=";

	char num1[NUMBUFSIZE];
	char num2[NUMBUFSIZE];
	char num3[NUMBUFSIZE];
	char numAR[NUMBUFSIZE];
	char numAS[NUMBUFSIZE];

	const char *q;

	libmail_str_size_t(top_count, num1);
	libmail_str_size_t(retr_count, num2);
	libmail_str_time_t(RUNTIME_CUR-start_time, num3);
	libmail_str_size_t(bytes_received_count, numAR);
	libmail_str_size_t(bytes_sent_count, numAS);

	std::string p;

	p.reserve(
		strlen(authaddr)+strlen(remoteip)+strlen(remoteport)+
		strlen(disc)+
		strlen(num1)+strlen(num2)+strlen(num3)+
		strlen(numAR)+strlen(numAS)+200);	/* Should be enough */

	p="disc";
	p += msg2;
	p += authaddr;
	p += msg3;
	p += remoteip;
	p += msgport;
	p += remoteport;
	p += msg4;
	p += num1;
	p += msg5;
	p += num2;
	p += msgAR;
	p += numAR;
	p += msgAS;
	p += numAS;
	p += msg6;
	p += num3;

	if ((q=getenv("POP3_TLS")) && atoi(q))
		p += msg7;

	p += "\n";
	if (write(2, p.c_str(), p.size()) < 0)
		; /* make gcc shut up */
}

static void bye(int signum)
{
	acctout("INFO: TIMEOUT");
	exit(0);
}

static void loop()
{
char	buf[BUFSIZ];
char	*p;
int	c;

	signal(SIGALRM, bye);
	while (alarm(300), fgets(buf, sizeof(buf), stdin))
	{
		bytes_received_count += strlen(buf);

		alarm(0);
		if ((p=strchr(buf, '\n')) != 0)
			*p=0;
		else while ((c=getc(stdin)) >= 0 && c != '\n')
			;
		p=strtok(buf, " \t\r");
		if (!p)
		{
			buf[0]=0;
			p=buf;
		}

		mkupper(p);
		if (strcmp(p, "QUIT") == 0)
		{
			printed(printf("+OK Bye-bye.\r\n"));
			fflush(stdout);
			cleanup();
			acctout("INFO: LOGOUT");
			return;
		}

		if (strcmp(p, "STAT") == 0)
		{
			do_stat();
			continue;
		}

		if (strcmp(p, "LIST") == 0)
		{
			do_list(strtok(NULL, " \t\r"));
			continue;
		}

		if (strcmp(p, "RETR") == 0)
		{
		unsigned	i;

			if ((i=getmsgnum(strtok(NULL, " \t\r"))) == 0)
				continue;

			do_retr(msglist_l[i-1], 0);
			continue;
		}

		if (strcmp(p, "CAPA") == 0)
		{
			pop3dcapa();
			continue;
		}

		if (strcmp(p, "LANG") == 0)
		{
			pop3dlang(strtok(NULL, "\r\n"));
			continue;
		}

		if (strcmp(p, "DELE") == 0)
		{
		unsigned	i;

			if ((i=getmsgnum(strtok(NULL, " \t\r"))) == 0)
				continue;

			msglist_l[i-1].isdeleted=true;
			printed(printf("+OK Deleted.\r\n"));
			fflush(stdout);
			continue;
		}

		if (strcmp(p, "NOOP") == 0)
		{
			printed(printf("+OK Yup.\r\n"));
			fflush(stdout);
			continue;
		}

		if (strcmp(p, "RSET") == 0)
		{

			for (auto &m:msglist_l)
				m.isdeleted=false;
			printed(printf("+OK Resurrected.\r\n"));
			fflush(stdout);
			continue;
		}

		if (strcmp(p, "TOP") == 0)
		{
		unsigned	i, j;
		const	char *q;

			if ((i=getmsgnum(strtok(NULL, " \t\r"))) == 0)
				continue;

			q=strtok(NULL, " \t\r");

			if (!q)	goto error;

			j=atoi(q);
			do_retr(msglist_l[i-1], &j);
			continue;
		}

		if (strcmp(p, "UIDL") == 0)
		{
			do_uidl(strtok(NULL, " \t\r"));
			continue;
		}

		if (strcmp(p, "UTF8") == 0)
		{
			/* XXX workaround for MS Outlook */
			utf8_enabled=1;
			printed(printf("+OK UTF8 enabled\r\n"));
			fflush(stdout);
			continue;
		}

error:
		printed(printf("-ERR Invalid command.\r\n"));
		fflush(stdout);
	}
	acctout("INFO: DISCONNECTED");
}

/* Like every good Maildir reader, we purge the tmp subdirectory */

static void purgetmp()
{
	DIR	*p=opendir("tmp");
	time_t	t;
	struct	dirent *de;
	struct	stat	stat_buf;

	if (!p)	return;
	time (&t);
	t -= 48L * 60L * 60L;

	while ((de=readdir(p)) != 0)
	{
		if (de->d_name[0] == '.')	continue;

		std::string n;

		n.reserve(strlen(de->d_name)+5);

		n="tmp/";
		n += de->d_name;

		if (stat(n.c_str(), &stat_buf) == 0 && stat_buf.st_mtime < t)
			unlink(n.c_str());
	}
	closedir(p);
}


#include	<unistd.h>

int main(int argc, char **argv)
{
	const char	*p;

#ifdef HAVE_SETVBUF_IOLBF
	setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
#endif
	start_time=RUNTIME_START;

	if ((authaddr=getenv("AUTHADDR")) == NULL ||
	    *authaddr == 0)
	{
		authaddr=getenv("AUTHENTICATED");
		if (authaddr == NULL || *authaddr == 0)
			authaddr="nobody";
	}

	utf8_enabled=1; /* Until proven otherwise */

	{
		const char *utf8=getenv("UTF8");

		if (utf8)
			utf8_enabled=atoi(utf8);
	}
	if ((remoteip=getenv("TCPREMOTEIP")) == NULL)
		remoteip="127.0.0.1";

	if ((remoteport=getenv("TCPREMOTEPORT")) == NULL)
		remoteport="0";

	{
	struct	stat	buf;

		if ( stat(".", &buf) < 0 || buf.st_mode & S_ISVTX)
		{
			fprintf(stderr, "INFO: LOCKED, user=%s, ip=[%s], port=[%s]\n",
							authaddr, remoteip, remoteport);
			printed(printf("-ERR Your account is temporarily unavailable (+t bit set on home directory).\r\n"));
			exit(0);
		}
	}

	if (argc > 1)
		p=argv[1];
	else
		p=getenv("MAILDIR");

	if (!p)
		p="./Maildir";

	if (chdir(p))
	{
		fprintf(stderr, "chdir %s: %s\n", p, strerror(errno));
		printed(printf("-ERR chdir %s failed\n", p));
		fflush(stdout);
		exit(1);
	}

	maildir_loginexec();

	if (auth_getoptionenvint("disablepop3"))
	{
		printed(printf("-ERR POP3 access disabled for this account.\r\n"));
		fflush(stdout);
		exit(1);
	}

	if (    auth_getoptionenvint("disableinsecurepop3")
	    && ((p=getenv("POP3_TLS")) == NULL || !atoi(p)))
	{
		printed(printf("-ERR POP3 access disabled via insecure connection.\r\n"));
		fflush(stdout);
		exit(1);
	}

	fprintf(stderr, "INFO: LOGIN, user=%s, ip=[%s], port=[%s]%s\n",
			authaddr,
					remoteip,
					remoteport,
					(p=getenv("POP3_TLS")) != 0 && atoi(p) ? ", stls=1" : "");
	fflush(stderr);

	purgetmp();
	maildir_getnew(".", INBOX, NULL, NULL);
	if (scancur())
	{
		printed(printf("-ERR Maildir invalid (no 'cur' directory)\r\n"));
		return (0);
	}
	sortmsgs();
	printed(printf("+OK logged in.\r\n"));
	fflush(stdout);
	loop();
	return (0);
}
