#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <sys/types.h>
#include <regex.h>

#include "imapfilter.h"
#include "session.h"
#include "buffer.h"
#include "regexp.h"


extern options opts;

buffer ibuf;			/* Input buffer. */
enum {				/* Server data responses to be parsed;
				 * regular expressions index. */
	DATA_RESPONSE_TAGGED,
	DATA_RESPONSE_CAPABILITY,
	DATA_RESPONSE_AUTHENTICATE,
	DATA_RESPONSE_NAMESPACE,
	DATA_RESPONSE_STATUS,
	DATA_RESPONSE_STATUS_MESSAGES,
	DATA_RESPONSE_STATUS_RECENT,
	DATA_RESPONSE_STATUS_UNSEEN,
	DATA_RESPONSE_EXAMINE_EXISTS,
	DATA_RESPONSE_EXAMINE_RECENT,
	DATA_RESPONSE_LIST,
	DATA_RESPONSE_SEARCH,
	DATA_RESPONSE_FETCH,
	DATA_RESPONSE_FETCH_FLAGS,
	DATA_RESPONSE_FETCH_DATE,
	DATA_RESPONSE_FETCH_SIZE,
	DATA_RESPONSE_FETCH_BODY,
};
regexp responses[] = {		/* Server data responses to be parsed;
				 * regular expressions patterns. */
	{ "([[:xdigit:]]{4,4}) (OK|NO|BAD) [[:print:]]*\r\n", NULL, 0, NULL },
	{ "\\* CAPABILITY ([[:print:]]*)\r\n", NULL, 0, NULL },
	{ "\\+ ([[:graph:]]*)\r\n", NULL, 0, NULL },
	{ "\\* NAMESPACE (NIL|\\(\\(\"([[:graph:]]*)\" \"([[:print:]])\"\\)"
	  "[[:print:]]*\\)) (NIL|\\([[:print:]]*\\)) (NIL|\\([[:print:]]*\\))"
	  "\r\n", NULL, 0, NULL },
	{ "\\* STATUS [[:print:]]* \\(([[:alnum:] ]*)\\)\r\n", NULL, 0, NULL },
	{ "MESSAGES ([[:digit:]]+)", NULL, 0, NULL },
	{ "RECENT ([[:digit:]]+)", NULL, 0, NULL },
	{ "UNSEEN ([[:digit:]]+)", NULL, 0, NULL },
	{ "\\* ([[:digit:]]+) EXISTS\r\n", NULL, 0, NULL },
	{ "\\* ([[:digit:]]+) RECENT\r\n", NULL, 0, NULL },
	{ "\\* (LIST|LSUB) \\(([[:print:]]*)\\) (\"[[:print:]]\"|NIL) "
	  "(\"([[:print:]]+)\"|([[:print:]]+)|\\{([[:digit:]]+)\\}\r\n"
	  "([[:print:]]*))\r\n", NULL, 0, NULL },
	{ "\\* SEARCH ?([[:digit:] ]*)\r\n", NULL, 0, NULL },
	{ "\\* [[:digit:]]+ FETCH \\(([[:print:]]*)\\)\r\n", NULL, 0, NULL },
	{ "FLAGS \\(([[:print:]]*)\\)", NULL, 0, NULL },
	{ "INTERNALDATE \"([[:print:]]*)\"", NULL, 0, NULL },
	{ "RFC822.SIZE ([[:digit:]]+)", NULL, 0, NULL },
	{ "\\* [[:digit:]]+ FETCH \\([[:print:]]*BODY\\[[[:print:]]*\\] "
	  "(\\{([[:digit:]]+)\\}\r\n|\"([[:print:]]*)\")", NULL, 0, NULL },
	{ NULL, NULL, 0, NULL }
};


int receive_response(session *ssn, char *buf);

int check_tag(char *buf, session *ssn, int tag);
int check_bye(char *buf);
int check_continuation(char *buf);
int check_trycreate(char *buf);


/*
 * Read data the server sent.
 */
int
receive_response(session *ssn, char *buf)
{
	ssize_t n;

	if ((n = socket_read(ssn, buf, INPUT_BUF)) == -1)
		return -1;


	if (opts.debug > 0) {
		int i;
		
		debug("getting response (%d):\n\n", ssn->socket);

		for (i = 0; i < n; i++)
			debugc(buf[i]);

		debug("\n");
	}

	return n;
}


/*
 * Search for tagged response in the data that the server sent.
 */
int
check_tag(char *buf, session *ssn, int tag)
{
	int r;
	char t[4 + 1];
	regexp *re;

	r = STATUS_RESPONSE_NONE;

	snprintf(t, sizeof(t), "%04X", tag);

	re = &responses[DATA_RESPONSE_TAGGED];

	if (!regexec(re->preg, buf, re->nmatch, re->pmatch, 0)) {
		if (!strncasecmp(buf + re->pmatch[1].rm_so, t,
		    strlen(t))) {
			if (!strncasecmp(buf + re->pmatch[2].rm_so,
			    "OK", strlen("OK")))
				r = STATUS_RESPONSE_OK;
			else if (!strncasecmp(buf + re->pmatch[2].rm_so,
			    "NO", strlen("NO")))
				r = STATUS_RESPONSE_NO;
			else if (!strncasecmp(buf + re->pmatch[2].rm_so,
			    "BAD", strlen("BAD")))
				r = STATUS_RESPONSE_BAD;
		}
	}

	if (r != STATUS_RESPONSE_NONE)
		verbose("S (%d): %s", ssn->socket, buf + re->pmatch[0].rm_so);

	return r;
}


/*
 * Check if server sent a BYE response (connection is closed immediately).
 */
int
check_bye(char *buf)
{

	if (xstrcasestr(buf, "* BYE") &&
	    !xstrcasestr(buf, " LOGOUT "))
		return 1;
	else
		return 0;
}


/*
 * Check if the server sent a continuation request.
 */
int
check_continuation(char *buf)
{

	if (buf[0] == '+' && buf[1] == ' ')
		return 1;
	else
		return 0;
}


/*
 * Check if the server sent a TRYCREATE response.
 */
int
check_trycreate(char *buf)
{

	if (xstrcasestr(buf, "[TRYCREATE]"))
		return 1;
	else
		return 0;
}


/*
 * Get server data and make sure there is a tagged response inside them.
 */
int
response_generic(session *ssn, int tag)
{
	int r;
	ssize_t n;

	if (tag == -1)
		return -1;

	buffer_reset(&ibuf);

	do {
		buffer_check(&ibuf, ibuf.len + INPUT_BUF);
		if ((n = receive_response(ssn, ibuf.data + ibuf.len)) == -1)
			return -1;
		ibuf.len += n;

		if (check_bye(ibuf.data))
			return -1;
		if (check_continuation(ibuf.data))
			return STATUS_RESPONSE_CONTINUE;
	} while ((r = check_tag(ibuf.data, ssn, tag)) == STATUS_RESPONSE_NONE);

	if (r == STATUS_RESPONSE_NO &&
	    (check_trycreate(ibuf.data) || get_option_boolean("create")))
		return STATUS_RESPONSE_TRYCREATE;

	return r;
}


/*
 * Process the greeting that server sends during connection.
 */
int
response_greeting(session *ssn)
{

	buffer_reset(&ibuf);

	if (receive_response(ssn, ibuf.data) == -1)
		return -1;

	if (check_bye(ibuf.data))
		return -1;

	verbose("S (%d): %s", ssn->socket, ibuf.data);

	if (xstrcasestr(ibuf.data, "* PREAUTH"))
		return STATUS_RESPONSE_PREAUTH;

	return STATUS_RESPONSE_NONE;
}


/*
 * Process the data that server sent due to IMAP CAPABILITY client request.
 */
int
response_capability(session *ssn, int tag)
{
	int r;
	char *s;
	regexp *re;

	if ((r = response_generic(ssn, tag)) == -1)
		return -1;

	ssn->protocol = PROTOCOL_NONE;

	re = &responses[DATA_RESPONSE_CAPABILITY];

	if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) {
		s = xstrndup(ibuf.data + re->pmatch[1].rm_so,
		    re->pmatch[1].rm_eo - re->pmatch[1].rm_so);

		if (xstrcasestr(s, "IMAP4rev1"))
			ssn->protocol = PROTOCOL_IMAP4REV1;
		else if (xstrcasestr(s, "IMAP4"))
			ssn->protocol = PROTOCOL_IMAP4;
		else {
			error("server supports neither the IMAP4rev1 nor the "
			    "IMAP4 protocol\n");
			return -1;
		}

		ssn->capabilities = CAPABILITY_NONE;

		if (xstrcasestr(s, "NAMESPACE"))
			ssn->capabilities |= CAPABILITY_NAMESPACE;
#ifndef NO_CRAMMD5
		if (xstrcasestr(s, "AUTH=CRAM-MD5"))
			ssn->capabilities |= CAPABILITY_CRAMMD5;
#endif
#ifndef NO_SSLTLS
		if (xstrcasestr(s, "STARTTLS"))
			ssn->capabilities |= CAPABILITY_STARTTLS;
#endif
		if (xstrcasestr(s, "CHILDREN"))
			ssn->capabilities |= CAPABILITY_CHILDREN;

		xfree(s);
	}

	return r;
}


#ifndef NO_CRAMMD5
/*
 * Process the data that server sent due to IMAP AUTHENTICATE client request.
 */
int
response_authenticate(session *ssn, int tag, unsigned char **cont)
{
	int r;
	regexp *re;

	re = &responses[DATA_RESPONSE_AUTHENTICATE];

	if ((r = response_generic(ssn, tag)) == STATUS_RESPONSE_CONTINUE &&
	    !regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0))
		*cont = (unsigned char *)xstrndup(ibuf.data + re->pmatch[1].rm_so,
		    re->pmatch[1].rm_eo - re->pmatch[1].rm_so);

	return r;
}
#endif


/*
 * Process the data that server sent due to IMAP NAMESPACE client request.
 */
int
response_namespace(session *ssn, int tag)
{
	int r, n;
	regexp *re;

	if ((r = response_generic(ssn, tag)) == -1)
		return -1;

	ssn->ns.prefix = NULL;
	ssn->ns.delim = '\0';

	re = &responses[DATA_RESPONSE_NAMESPACE];

	if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) {
		n = re->pmatch[2].rm_eo - re->pmatch[2].rm_so;
		if (n > 0)
			ssn->ns.prefix = xstrndup(ibuf.data +
			    re->pmatch[2].rm_so, n);
		ssn->ns.delim = *(ibuf.data + re->pmatch[3].rm_so);
	}
	debug("namespace (%d): '%s' '%c'\n", ssn->socket,
	    (ssn->ns.prefix ? ssn->ns.prefix : ""), ssn->ns.delim);

	return r;
}


/*
 * Process the data that server sent due to IMAP STATUS client request.
 */
int
response_status(session *ssn, int tag, unsigned int *exist,
    unsigned int *recent, unsigned int *unseen)
{
	int r;
	char *s;
	regexp *re;

	if ((r = response_generic(ssn, tag)) == -1)
		return -1;

	re = &responses[DATA_RESPONSE_STATUS];

	if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) {
		s = xstrndup(ibuf.data + re->pmatch[1].rm_so,
		    re->pmatch[1].rm_eo - re->pmatch[1].rm_so);

		re = &responses[DATA_RESPONSE_STATUS_MESSAGES];
		if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
			*exist = strtol(s + re->pmatch[1].rm_so, NULL, 10);

		re = &responses[DATA_RESPONSE_STATUS_RECENT];
		if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
			*recent = strtol(s + re->pmatch[1].rm_so, NULL, 10);

		re = &responses[DATA_RESPONSE_STATUS_UNSEEN];
		if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
			*unseen = strtol(s + re->pmatch[1].rm_so, NULL, 10);

		xfree(s);
	}

	return r;
}


/*
 * Process the data that server sent due to IMAP EXAMINE client request.
 */
int
response_examine(session *ssn, int tag, unsigned int *exist,
    unsigned int *recent)
{
	int r;
	regexp *re;

	if ((r = response_generic(ssn, tag)) == -1)
		return -1;

	re = &responses[DATA_RESPONSE_EXAMINE_EXISTS];
	if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0))
		*exist = strtol(ibuf.data + re->pmatch[1].rm_so, NULL, 10);

	re = &responses[DATA_RESPONSE_EXAMINE_RECENT];
	if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0))
		*recent = strtol(ibuf.data + re->pmatch[1].rm_so, NULL, 10);

	return r;
}


/*
 * Process the data that server sent due to IMAP SELECT client request.
 */
int
response_select(session *ssn, int tag)
{
	int r;

	if ((r = response_generic(ssn, tag)) == -1)
		return -1;

	if (xstrcasestr(ibuf.data, "[READ-ONLY]"))
		return STATUS_RESPONSE_READONLY;

	return r;
}


/*
 * Process the data that server sent due to IMAP LIST or IMAP LSUB client
 * request.
 */
int
response_list(session *ssn, int tag, char **mboxs, char **folders)
{
	int r, n;
	char *b, *a, *s, *m, *f;
	const char *v;
	regexp *re;

	if ((r = response_generic(ssn, tag)) == -1)
		return -1;

	m = *mboxs = (char *)xmalloc((ibuf.len + 1) * sizeof(char));
	f = *folders = (char *)xmalloc((ibuf.len + 1) * sizeof(char));
	*m = *f = '\0';

	re = &responses[DATA_RESPONSE_LIST];

	b = ibuf.data;
	while (!regexec(re->preg, b, re->nmatch, re->pmatch, 0)) {
		a = xstrndup(b + re->pmatch[2].rm_so,
		    re->pmatch[2].rm_eo - re->pmatch[2].rm_so);

		if (re->pmatch[5].rm_so != -1 && re->pmatch[5].rm_so != -1)
			s = xstrndup(b + re->pmatch[5].rm_so,
			    re->pmatch[5].rm_eo - re->pmatch[5].rm_so);
		else if (re->pmatch[6].rm_so != -1 &&
		    re->pmatch[6].rm_so != -1)
			s = xstrndup(b + re->pmatch[6].rm_so,
			    re->pmatch[6].rm_eo - re->pmatch[6].rm_so);
		else
			s = xstrndup(b + re->pmatch[8].rm_so, strtoul(b +
			    re->pmatch[7].rm_so, NULL, 10));

		v = reverse_namespace(s, ssn->ns.prefix, ssn->ns.delim);
		n = strlen(v);

		if (!xstrcasestr(a, "\\NoSelect")) {
			xstrncpy(m, v, ibuf.len - (m - *mboxs));
			m += n;
			xstrncpy(m, "\n", ibuf.len - (m - *mboxs));
			m += strlen("\n");
		}

		if (!xstrcasestr(a, "\\NoInferiors") &&
		    (!(ssn->capabilities & CAPABILITY_CHILDREN) ||
		    ((ssn->capabilities & CAPABILITY_CHILDREN) &&
		    (xstrcasestr(a, "\\HasChildren")) &&
		    !xstrcasestr(a, "\\HasNoChildren")))) {
			xstrncpy(f, v, ibuf.len - (f - *folders));
			f += n;
			xstrncpy(f, "\n", ibuf.len - (f - *folders));
			f += strlen("\n");
		}

		b += re->pmatch[0].rm_eo;

		xfree(a);
		xfree(s);
	}

	return r;
}


/*
 * Process the data that server sent due to IMAP SEARCH client request.
 */
int
response_search(session *ssn, int tag, char **mesgs)
{
	int r;
	unsigned int min;
	regexp *re;
	char *b, *m;

	if ((r = response_generic(ssn, tag)) == -1)
		return -1;

	re = &responses[DATA_RESPONSE_SEARCH];

	b = ibuf.data;
	m = NULL;
	while (!regexec(re->preg, b, re->nmatch, re->pmatch, 0)) {
		if (!*mesgs) {
			m = *mesgs = (char *)xmalloc((ibuf.len + 1) *
			    sizeof(char));
			*m = '\0';
		}

		min = (unsigned int)(re->pmatch[1].rm_eo - re->pmatch[1].rm_so) < ibuf.len ?
		    (unsigned int)(re->pmatch[1].rm_eo - re->pmatch[1].rm_so) :
		    ibuf.len;

		xstrncpy(m, b + re->pmatch[1].rm_so, min);
		m += min;
		xstrncpy(m++, " ", ibuf.len - min);
		m += strlen(" ");

		b += re->pmatch[0].rm_eo;
	}

	return r;
}


/*
 * Process the data that server sent due to IMAP FETCH FAST client request.
 */
int
response_fetchfast(session *ssn, int tag, char **flags, char **date,
    char **size)
{
	int r;
	char *s;
	regexp *re;

	if ((r = response_generic(ssn, tag)) == -1)
		return -1;

	re = &responses[DATA_RESPONSE_FETCH];
	if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) { 
		s = xstrndup(ibuf.data + re->pmatch[1].rm_so,
		    re->pmatch[1].rm_eo - re->pmatch[1].rm_so);

		re = &responses[DATA_RESPONSE_FETCH_FLAGS];
		if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
			*flags = xstrndup(s + re->pmatch[1].rm_so,
			    re->pmatch[1].rm_eo - re->pmatch[1].rm_so);

		re = &responses[DATA_RESPONSE_FETCH_DATE];
		if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
			*date = xstrndup(s + re->pmatch[1].rm_so,
			    re->pmatch[1].rm_eo - re->pmatch[1].rm_so);

		re = &responses[DATA_RESPONSE_FETCH_SIZE];
		if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
			*size = xstrndup(s + re->pmatch[1].rm_so,
			    re->pmatch[1].rm_eo - re->pmatch[1].rm_so);

		xfree(s);
	}

	return r;
}


/*
 * Process the data that server sent due to IMAP FETCH FLAGS client request.
 */
int
response_fetchflags(session *ssn, int tag, char **flags)
{
	int r;
	char *s;
	regexp *re;

	if ((r = response_generic(ssn, tag)) == -1)
		return -1;

	re = &responses[DATA_RESPONSE_FETCH];
	if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) { 
		s = xstrndup(ibuf.data + re->pmatch[1].rm_so,
		    re->pmatch[1].rm_eo - re->pmatch[1].rm_so);

		re = &responses[DATA_RESPONSE_FETCH_FLAGS];
		if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
			*flags = xstrndup(s + re->pmatch[1].rm_so,
			    re->pmatch[1].rm_eo - re->pmatch[1].rm_so);

		xfree(s);
	}

	return r;
}


/*
 * Process the data that server sent due to IMAP FETCH INTERNALDATE client
 * request.
 */
int
response_fetchdate(session *ssn, int tag, char **date)
{
	int r;
	char *s;
	regexp *re;

	if ((r = response_generic(ssn, tag)) == -1)
		return -1;

	re = &responses[DATA_RESPONSE_FETCH];
	if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) { 
		s = xstrndup(ibuf.data + re->pmatch[1].rm_so,
		    re->pmatch[1].rm_eo - re->pmatch[1].rm_so);

		re = &responses[DATA_RESPONSE_FETCH_DATE];
		if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
			*date = xstrndup(s + re->pmatch[1].rm_so,
			    re->pmatch[1].rm_eo - re->pmatch[1].rm_so);

		xfree(s);
	}

	return r;
}


/*
 * Process the data that server sent due to IMAP FETCH RFC822.SIZE client
 * request.
 */
int
response_fetchsize(session *ssn, int tag, char **size)
{
	int r;
	char *s;
	regexp *re;

	if ((r = response_generic(ssn, tag)) == -1)
		return -1;

	re = &responses[DATA_RESPONSE_FETCH];
	if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) { 
		s = xstrndup(ibuf.data + re->pmatch[1].rm_so,
		    re->pmatch[1].rm_eo - re->pmatch[1].rm_so);

		re = &responses[DATA_RESPONSE_FETCH_SIZE];
		if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
			*size = xstrndup(s + re->pmatch[1].rm_so,
			    re->pmatch[1].rm_eo - re->pmatch[1].rm_so);

		xfree(s);
	}

	return r;
}


/*
 * Process the data that server sent due to IMAP FETCH BODY[] client request,
 * ie. FETCH BODY[HEADER], FETCH BODY[TEXT], FETCH BODY[HEADER.FIELDS ()].
 */
int
response_fetchbody(session *ssn, int tag, char **body, size_t *len)
{
	int r, match;
	unsigned int offset;
	ssize_t n;
	regexp *re;

	if (tag == -1)
		return -1;

	buffer_reset(&ibuf);

	match = -1;
	offset = 0;
	
	re = &responses[DATA_RESPONSE_FETCH_BODY];

	do {
		buffer_check(&ibuf, ibuf.len + INPUT_BUF);
		if ((n = receive_response(ssn, ibuf.data + ibuf.len)) == -1)
			return -1;
		ibuf.len += n;

		if (ibuf.len >= offset)
			if (check_bye(ibuf.data + offset))
				return -1;

		if (match != 0) {
			match = regexec(re->preg, ibuf.data, re->nmatch,
			    re->pmatch, 0);
			
			if (match == 0 && re->pmatch[2].rm_so != -1 &&
			    re->pmatch[2].rm_eo != -1) {
				*len = strtoul(ibuf.data + re->pmatch[2].rm_so,
				    NULL, 10);
				offset = re->pmatch[0].rm_eo + *len;
			}
		}
	} while (ibuf.len < offset || (r = check_tag(ibuf.data + offset, ssn,
	    tag)) == STATUS_RESPONSE_NONE);

	if (match == 0) {
		if (re->pmatch[2].rm_so != -1 &&
		    re->pmatch[2].rm_eo != -1) {
			*body = ibuf.data + re->pmatch[0].rm_eo;
		} else {
			*body = ibuf.data + re->pmatch[3].rm_so;
			*len = re->pmatch[3].rm_eo - re->pmatch[3].rm_so;
		}
	}

	return r;
}


syntax highlighted by Code2HTML, v. 0.9.1