/*
** Copyright 1998 - 2005 Double Precision, Inc.
** See COPYING for distribution information.
*/

#if	HAVE_CONFIG_H
#include	"config.h"
#endif

#include	"imaptoken.h"
#include	"imapwrite.h"
#include	<stdio.h>
#include	<ctype.h>
#include	<stdlib.h>
#include	<string.h>
#include	<sys/types.h>
#include	<sys/time.h>
#include	"numlib/numlib.h"
#if	HAVE_UNISTD_H
#include	<unistd.h>
#endif

#ifndef	BUFSIZ
#define	BUFSIZ	8192
#endif


static struct imaptoken curtoken;
static char readbuf[BUFSIZ];

char *imap_readptr=0;
size_t imap_readptrleft=0;
time_t start_time;

static time_t readtimeout;

extern FILE *debugfile;

extern unsigned long header_count, body_count;

unsigned long bytes_received_count = 0; /* counter for received bytes */
unsigned long bytes_sent_count; /* counter for sent bytes (imapwrite.c) */

extern void bye();

void bye_msg(const char *type)
{
	const char *a=getenv("AUTHENTICATED");
	char buf[NUMBUFSIZE];
	const char *tls=getenv("IMAP_TLS");

	libmail_str_time_t(time(NULL)-start_time, buf);

	if (tls && atoi(tls))
		tls=", starttls=1";
	else
		tls="";

	if (a && *a)
		fprintf(stderr, "%s, user=%s, "
			"ip=[%s], headers=%lu, body=%lu, rcvd=%lu, sent=%lu, time=%s%s\n",
			type,
			a, getenv("TCPREMOTEIP"), header_count, body_count, bytes_received_count, bytes_sent_count,
			buf, tls);
	else
		fprintf(stderr, "DEBUG: Disconnected, ip=[%s], time=%s%s\n",
			getenv("TCPREMOTEIP"),
			buf, tls);
}

void disconnected()
{
	bye_msg("INFO: DISCONNECTED");
	bye();
}

static void disconnected_timeout(void)
{
	writes("* BYE Disconnected for inactivity.\r\n");
	writeflush();
	bye_msg("INFO: TIMEOUT");
	bye();
}

int doidle(time_t idletimeout, int extraFd)
{
fd_set fds;
struct timeval tv;
time_t t;

       time(&t);
       if (t >= readtimeout)   disconnected_timeout();
       if (imap_readptrleft > 0) return 1;

       FD_ZERO(&fds);
       FD_SET(0, &fds);

       if (extraFd > 0)
       {
	       FD_SET(extraFd, &fds);
       }
       else
       {
	       extraFd=0;
       }

       tv.tv_sec=idletimeout;
       tv.tv_usec=0;

       select(extraFd + 1, &fds, 0, 0, &tv);
       return (FD_ISSET(0, &fds));
}

size_t doread(char *buf, size_t bufsiz)
{
fd_set	fds;
struct	timeval	tv;
time_t	t;
int n = 0;

	time(&t);
	if (t >= readtimeout)	disconnected_timeout();

	FD_ZERO(&fds);
	FD_SET(0, &fds);
	tv.tv_sec=readtimeout - t;
	tv.tv_usec=0;

	if (select(1, &fds, 0, 0, &tv) <= 0)
	{
		disconnected_timeout();
		return (0);
	}
	if (!FD_ISSET(0, &fds) || (n=read(0, buf, bufsiz)) <= 0)
	{
		if ( n > 0 )
			bytes_received_count += n; /* count received bytes */
		disconnected();
		return (0);
	}
	if ( n > 0 )
		bytes_received_count += n; /* count received bytes */
	return (n);
}

void readfill()
{
	imap_readptrleft=doread(readbuf, sizeof(readbuf));
	imap_readptr=readbuf;
}

#define	UNREAD(c) (*--imap_readptr=(c), ++imap_readptrleft)

void unread(int c)
{
	UNREAD(c);
}

void read_eol()
{
int	c;

	while ( (c=READ()) != '\n')
		;
	curtoken.tokentype=IT_EOL;
}

void read_timeout(time_t t)
{
time_t	tt;

	time(&tt);
	readtimeout=tt+t;
}

static void alloc_tokenbuf(unsigned l)
{
	if (l >= curtoken.tokenbuf_size)
	{
	char	*p=curtoken.tokenbuf ? realloc(curtoken.tokenbuf, l + 256):
			malloc(l + 256);

		if (!p)
			write_error_exit("malloc");

		curtoken.tokenbuf_size = l+256;
		curtoken.tokenbuf=p;
	}
}

static char LPAREN_CHAR='(';
static char RPAREN_CHAR=')';
static char LBRACKET_CHAR='[';
static char RBRACKET_CHAR=']';

void ignorepunct()
{
	LPAREN_CHAR=RPAREN_CHAR=LBRACKET_CHAR=RBRACKET_CHAR='\n';
}

#if SMAP

void smap_readline(char *buffer, size_t bufsize)
{
	int c;

	while ((c=READ()) != '\n')
	{
		if (bufsize > 1)
		{
			*buffer++ = c;
			--bufsize;
		}
	}
	*buffer=0;
}

#endif

static struct imaptoken *do_readtoken(int touc)
{
int	c=0;
unsigned l;

#define	appendch(c)	alloc_tokenbuf(l+1); curtoken.tokenbuf[l++]=(c);

	if (curtoken.tokentype == IT_ERROR)	return (&curtoken);

	do
	{
		c=READ();
	} while (c == '\r' || c == ' ' || c == '\t');

	if (c == '\n')
	{
		UNREAD(c);
		curtoken.tokentype=IT_EOL;
		return (&curtoken);
	}
	c=(unsigned char)c;
	if (c == LPAREN_CHAR)
	{
		curtoken.tokentype=IT_LPAREN;
		return (&curtoken);
	}

	if (c == RPAREN_CHAR)
	{
		curtoken.tokentype=IT_RPAREN;
		return (&curtoken);
	}

	if (c == LBRACKET_CHAR)
	{
		curtoken.tokentype=IT_LBRACKET;
		return (&curtoken);
	}

	if (c == RBRACKET_CHAR)
	{
		curtoken.tokentype=IT_RBRACKET;
		return (&curtoken);
	}

	if (c == '"')
	{
		l=0;
		while ((c=READ()) != '"')
		{
			if (c == '\\')
				c=READ();
			if (c == '\r' || c == '\n')
			{
				UNREAD(c);
				curtoken.tokentype=IT_ERROR;
				return (&curtoken);
			}
			if (l < 8192)
			{
				appendch(c);
			}
		}
		appendch(0);
		curtoken.tokentype=IT_QUOTED_STRING;
		return (&curtoken);
	}

	if (c == '{')
	{
		curtoken.tokennum=0;
		while ((c=READ()) != '}')
		{
			if (!isdigit((int)(unsigned char)c))
			{
				UNREAD(c);
				curtoken.tokentype=IT_ERROR;
				return (&curtoken);
			}
			curtoken.tokennum = curtoken.tokennum*10 + (c-'0');
		}
		c=READ();
		if (c == '\r')
		{
			c=READ();
		}
		if (c != '\n')
		{
			curtoken.tokentype=IT_ERROR;
			return (&curtoken);
		}
		curtoken.tokentype=IT_LITERAL_STRING_START;
		return (&curtoken);
	}

	l=0;
	if (c == '\\')
	{
		appendch(c);	/* Message flag */
		c=READ();
	}
	else if (isdigit(c))
	{
		curtoken.tokentype=IT_NUMBER;
		curtoken.tokennum=0;
		do
		{
			appendch(c);
			curtoken.tokennum = curtoken.tokennum*10 +
				(c-'0');
			c=READ();
		} while (isdigit( (int)(unsigned char)c));

		/* Could be stuff like mime.spec, so continue reading. */
	}

	while (c != '\r' && c != '\n'
		&& !isspace((int)(unsigned char)c)
		&& c != '\\' && c != '"' && c != LPAREN_CHAR && c != RPAREN_CHAR
		&& c != '{' && c != '}' && c != LBRACKET_CHAR && c != RBRACKET_CHAR)
	{
		curtoken.tokentype=IT_ATOM;
		if (l < IT_MAX_ATOM_SIZE)
		{
			if (touc)
				c=toupper(c);
			appendch(c);
		}
		else
		{
			write_error_exit("max atom size too small");  
		}
		c=READ();
	}
	if (l == 0)
	{
		curtoken.tokentype=IT_ERROR;
		return (&curtoken);
	}
	appendch(0);
	UNREAD(c);

	if (strcmp(curtoken.tokenbuf, "NIL") == 0)
		curtoken.tokentype=IT_NIL;
	return (&curtoken);
}

static struct imaptoken *readtoken(int touc)
{
struct imaptoken *tok=do_readtoken(touc);

	if (tok->tokentype == IT_LITERAL_STRING_START)
	{
	unsigned long nbytes=curtoken.tokennum;

		if (nbytes > 8192)
		{
			writes("* NO [ALERT] IMAP command too long.\r\n");
			tok->tokentype=IT_ERROR;
		}
		else
		{
		unsigned long i;

			writes("+ OK\r\n");
			writeflush();
			alloc_tokenbuf(nbytes+1);
			for (i=0; i<nbytes; i++)
				tok->tokenbuf[i]= READ();
			tok->tokenbuf[i]=0;
			tok->tokentype=IT_QUOTED_STRING;
		}
	}

	if (debugfile)
	{
	char	*p=0;

		fprintf(debugfile, "READ: ");
		switch (tok->tokentype) {
		case IT_ATOM:
			p=curtoken.tokenbuf; fprintf(debugfile, "ATOM"); break;
		case IT_NUMBER:
			p=curtoken.tokenbuf; fprintf(debugfile, "NUMBER"); break;
		case IT_QUOTED_STRING:
			p=curtoken.tokenbuf; fprintf(debugfile, "QUOTED_STRING"); break;
		case IT_LPAREN:
			fprintf(debugfile, "LPAREN"); break;
		case IT_RPAREN:
			fprintf(debugfile, "RPAREN"); break;
		case IT_NIL:
			fprintf(debugfile, "NIL"); break;
		case IT_ERROR:
			fprintf(debugfile, "ERROR"); break;
		case IT_EOL:
			fprintf(debugfile, "EOL"); break;
		case IT_LBRACKET:
			fprintf(debugfile, "LBRACKET"); break;
		case IT_RBRACKET:
			fprintf(debugfile, "RBRACKET"); break;
		}

		if (p)
			fprintf(debugfile, ": %s", p);
		fprintf(debugfile, "\n");
		fflush(debugfile);
	}
	return (tok);
}

struct imaptoken *nexttoken(void)
{
	return (readtoken(1));
}

struct imaptoken *nexttoken_nouc(void)
{
	return (readtoken(0));
}

/* RFC 2060 sucks */

struct imaptoken *nexttoken_okbracket(void)
{
	struct imaptoken *t;

	LBRACKET_CHAR=RBRACKET_CHAR='\n';

	t=nexttoken();

	LBRACKET_CHAR='[';
	RBRACKET_CHAR=']';
	return (t);
}

struct imaptoken *nexttoken_nouc_okbracket(void)
{
	struct imaptoken *t;

	LBRACKET_CHAR=RBRACKET_CHAR='\n';

	t=nexttoken_nouc();

	LBRACKET_CHAR='[';
	RBRACKET_CHAR=']';
	return (t);
}

struct imaptoken *currenttoken(void)
{
	return (&curtoken);
}

struct imaptoken *nexttoken_noparseliteral(void)
{
	return (do_readtoken(0));
}

/* Read an IMAP literal string (or a portion of) */

void read_string(char **ptr, unsigned long *left, unsigned long cnt)
{
	if (imap_readptrleft == 0)
	{
		/* Keep reading until we fill the buffer or until we've
		** read the entire string.
		*/

		read_timeout(SOCKET_TIMEOUT);
		imap_readptr=readbuf;
		while (imap_readptrleft < sizeof(readbuf) && imap_readptrleft < cnt)
			imap_readptrleft += doread(readbuf+imap_readptrleft,
						sizeof(readbuf)-imap_readptrleft);
	}

	if (cnt < imap_readptrleft)	/* Can satisfy fully from buffer */
	{
		*ptr=imap_readptr;
		*left=cnt;
		imap_readptr += cnt;
		imap_readptrleft -= cnt;
		return;
	}

	*ptr=imap_readptr;
	*left=imap_readptrleft;
	imap_readptrleft=0;
	return;
}

char *my_strdup(const char *s)
{
char	*q=strdup(s);

	if (!q)	write_error_exit("malloc");
	return (q);
}

int ismsgset(struct imaptoken *tok)
	 /* See if this token is a syntactically valid message set */
{

	if (tok->tokentype == IT_NUMBER)	return (1);
	if (tok->tokentype != IT_ATOM)		return (0);

	return ismsgset_str(tok->tokenbuf);
}

int ismsgset_str(const char *p)
{
	while (isdigit((int)(unsigned char)*p) || *p == '*')
	{
		if (*p == '0')	return (0);
		if (*p == '*')
			++p;
		else
			do
			{
				++p;
			} while (isdigit((int)(unsigned char)*p));

		if (*p == ':')
		{
			++p;
			if (!isdigit((int)(unsigned char)*p) &&
				*p != '*')
				return (0);
			if (*p == '0')	return (0);
			if (*p == '*')
				++p;
			else
				do
				{
					++p;
				} while (isdigit((int)(unsigned char)*p));
		}
		if (*p != ',')	break;
		++p;
	}
	if (*p)	return (0);
	return (1);
}
		
