/*
 * This code implements basics of POP3 protocol
 *
 * (c) majvan 2002-2004
 */
/* This was made from the libspopc project
 * copyright c 2002 Benoit Rouits <brouits@free.fr>
 * released under the terms of GNU LGPL
 * (GNU Lesser General Public Licence).
 * libspopc offers simple API for a pop3 client (MTA).
 * See RFC 1725 for pop3 specifications.
 * more information on http://brouits.free.fr/libspopc/
 */
/*
 * This file is not original and is changed by majvan <om3tn@psg.sk>
 * for mail checker purpose. Please see original web page to
 * obtain the original. I rewrote it in C++, but good ideas were,
 * I think, unchanged.
 *
 * Note that this file was not designed to work under Unix. It's
 * needed to add Unix-specific features. I was interested only in
 * Windows for my project. majvan
 *
 */

#include "../../stdafx.h"

 //--------------------------------------------------------------------------------------------------

 //Connects to the server through the netlib
 //if not success, exception is throwed
 //returns welcome string returned by server
 //sets AckFlag
char *CPop3Client::Connect(const char *servername, const int port, BOOL UseSSL, BOOL NoTLS)
{
	if (Stopped) // check if we can work with this POP3 client session
		throw POP3Error = (uint32_t)EPOP3_STOPPED;

	delete NetClient;
	SSL = UseSSL;
	NetClient = new CNLClient;

	#ifdef DEBUG_DECODE
	DebugLog(DecodeFile, "Connect:servername: %s port:%d\n", servername, port);
	#endif
	POP3Error = EPOP3_CONNECT;
	NetClient->Connect(servername, port);
	POP3Error = 0;

	if (SSL) {
		try {
			NetClient->SSLify();
		}
		catch (...) {
			NetClient->Disconnect();
			return nullptr;
		}
	}

	char *temp = RecvRest(NetClient->Recv(), POP3_SEARCHACK);
	extern BOOL SSLLoaded;
	if (!NoTLS & !(SSL)) {
		if (NetClient->Stopped)			//check if we can work with this POP3 client session
			throw POP3Error = (uint32_t)EPOP3_STOPPED;
		NetClient->Send("STLS\r\n");
		free(temp);
		temp = RecvRest(NetClient->Recv(), POP3_SEARCHACK);
		if (AckFlag == POP3_FOK) { // Ok, we are going to tls
			try {
				NetClient->SSLify();
			}
			catch (...) {
				NetClient->Disconnect();
				return nullptr;
			}
		}
	}
	return temp;
}

//Receives data to the end of packet
// prev- previous data read (appends to this string next received data)
// mode- mode of packet. 
//       Packet can end with ack state (+OK or -ERR): set mode to POP3_SEARCHACK
//       If packet ends with '.' (end of string), set mode to POP3_SEARCHDOT
// size- received data are stored to memory, but if length of data is more than allocated memory, function allocates
//       new memory. New allocated memory has allocated size more bytes
//       This value can be selectable: if you think it is better to reallocate by 1kB size, select size to 1024,
//       default is 128. You do not need to use this parameter

char *CPop3Client::RecvRest(char *prev, int mode, int size)
{
	int SizeRead = 0;
	int SizeLeft = size - NetClient->Rcv;
	int RcvAll = NetClient->Rcv;
	char *LastString, *PrevString = prev;

	AckFlag = 0;

	while (((mode == POP3_SEARCHDOT) && !SearchFromEnd(PrevString + RcvAll - 1, RcvAll - 3, POP3_SEARCHDOT) && !SearchFromStart(PrevString, 2, POP3_SEARCHERR)) ||		//we are looking for dot or -err phrase
		((mode == POP3_SEARCHACK) && (!SearchFromStart(PrevString, RcvAll - 3, mode) || !((RcvAll > 3) && SearchFromEnd(PrevString + RcvAll - 1, 1, POP3_SEARCHNL)))))			//we are looking for +ok or -err phrase ended with newline
	{		//if not found
		if (NetClient->Stopped)			//check if we can work with this POP3 client session
		{
			if (PrevString != nullptr)
				free(PrevString);
			throw POP3Error = (uint32_t)EPOP3_STOPPED;
		}
		if (SizeLeft == 0)						//if block is full
		{
			SizeRead += size;
			SizeLeft = size;
			LastString = NetClient->Recv(nullptr, SizeLeft);
			PrevString = (char *)realloc(PrevString, sizeof(char) * (SizeRead + size));
			if (PrevString == nullptr)
				throw POP3Error = (uint32_t)EPOP3_RESTALLOC;
			memcpy(PrevString + SizeRead, LastString, size);
			free(LastString);
		}
		else
			NetClient->Recv(PrevString + RcvAll, SizeLeft);			//to Rcv stores received bytes
		SizeLeft = SizeLeft - NetClient->Rcv;
		RcvAll += NetClient->Rcv;
	}
	NetClient->Rcv = RcvAll;			//at the end, store the number of all bytes, no the number of last received bytes
	return PrevString;
}

// CPop3Client::SearchFromEnd
// returns 1 if substring DOTLINE or ENDLINE found from end in bs bytes
// if you need to add condition for mode, insert it into switch statement
BOOL CPop3Client::SearchFromEnd(char *end, int bs, int mode)
{
	while (bs >= 0) {
		switch (mode) {
		case POP3_SEARCHDOT:
			if (DOTLINE(end))
				return 1;
			break;

		case POP3_SEARCHNL:
			if (ENDLINE(end))
				return 1;
			break;
		}
		end--;
		bs--;
	}
	return 0;
}

//Finds for a occurence of some pattern in string
// returns 1 if substring OKLINE, ERRLINE or any of them found from start in bs bytes
//call only this function to retrieve ack status (+OK or -ERR), because it sets flag AckFlag
//if you need to add condition for mode, insert it into switch statement
BOOL CPop3Client::SearchFromStart(char *start, int bs, int mode)
{
	while (bs >= 0) {
		switch (mode) {
		case POP3_SEARCHOK:
			if (OKLINE(start)) {
				AckFlag = POP3_FOK;
				return 1;
			}
			break;
		case POP3_SEARCHERR:
			if (ERRLINE(start)) {
				AckFlag = POP3_FERR;
				return 1;
			}
			break;
		case POP3_SEARCHACK:
			if (ACKLINE(start)) {
				OKLINE(start) ? AckFlag = POP3_FOK : AckFlag = POP3_FERR;
				return 1;
			}
			break;
		}
		start++;
		bs--;
	}
	return 0;
}

//Performs "USER" pop query and returns server response
//sets AckFlag
char *CPop3Client::User(char *name)
{
	if (NetClient->Stopped) // check if we can work with this POP3 client session
		throw POP3Error = (uint32_t)EPOP3_STOPPED;

	char query[128];
	char *Result;

	mir_snprintf(query, "USER %s\r\n", name);
	NetClient->Send(query);
	Result = RecvRest(NetClient->Recv(), POP3_SEARCHACK);
	if (AckFlag == POP3_FERR)
		throw POP3Error = (uint32_t)EPOP3_BADUSER;
	POP3Error = 0;
	return Result;
}

//Performs "PASS" pop query and returns server response
//sets AckFlag
char *CPop3Client::Pass(char *pw)
{
	if (NetClient->Stopped)			//check if we can work with this POP3 client session
		throw POP3Error = (uint32_t)EPOP3_STOPPED;

	char query[128];
	mir_snprintf(query, "PASS %s\r\n", pw);
	NetClient->Send(query);

	char *Result = RecvRest(NetClient->Recv(), POP3_SEARCHACK);
	if (AckFlag == POP3_FERR)
		throw POP3Error = (uint32_t)EPOP3_BADPASS;
	return Result;
}

//Performs "APOP" pop query and returns server response
//sets AckFlag
char *CPop3Client::APOP(char *name, char *pw, char *timestamp)
{
	if (NetClient->Stopped) // check if we can work with this POP3 client session
		throw POP3Error = (uint32_t)EPOP3_STOPPED;

	char query[512];
	char *Result;
	unsigned char digest[16];

	if (timestamp == nullptr)
		throw POP3Error = (uint32_t)EPOP3_APOP;
	mir_md5_state_s ctx;
	mir_md5_init(&ctx);
	mir_md5_append(&ctx, (const unsigned char *)timestamp, (unsigned int)mir_strlen(timestamp));
	mir_md5_append(&ctx, (const unsigned char *)pw, (unsigned int)mir_strlen(pw));
	mir_md5_finish(&ctx, digest);

	char hexdigest[40];
	mir_snprintf(query, "APOP %s %s\r\n", name, bin2hex(digest, sizeof(digest), hexdigest));

	NetClient->Send(query);
	Result = RecvRest(NetClient->Recv(), POP3_SEARCHACK);
	if (AckFlag == POP3_FERR)
		throw POP3Error = (uint32_t)EPOP3_BADUSER;
	return Result;
}

//Performs "QUIT" pop query and returns server response
//sets AckFlag
char *CPop3Client::Quit()
{
	char query[] = "QUIT\r\n";

	NetClient->Send(query);
	return RecvRest(NetClient->Recv(), POP3_SEARCHACK);
}

//Performs "STAT" pop query and returns server response
//sets AckFlag
char *CPop3Client::Stat()
{
	if (NetClient->Stopped) //check if we can work with this POP3 client session
		throw POP3Error = (uint32_t)EPOP3_STOPPED;

	char query[] = "STAT\r\n";
	NetClient->Send(query);
	return RecvRest(NetClient->Recv(), POP3_SEARCHACK);
}

//Performs "LIST" pop query and returns server response
//sets AckFlag
char *CPop3Client::List()
{
	if (NetClient->Stopped) // check if we can work with this POP3 client session
		throw POP3Error = (uint32_t)EPOP3_STOPPED;

	char query[] = "LIST\r\n";

	NetClient->Send(query);
	return RecvRest(NetClient->Recv(), POP3_SEARCHDOT);
}

//Performs "TOP" pop query and returns server response
//sets AckFlag
char *CPop3Client::Top(int nr, int lines)
{
	if (NetClient->Stopped) // check if we can work with this POP3 client session
		throw POP3Error = (uint32_t)EPOP3_STOPPED;

	char query[128];

	mir_snprintf(query, "TOP %d %d\r\n", nr, lines);
	NetClient->Send(query);
	return RecvRest(NetClient->Recv(), POP3_SEARCHDOT);
}

//Performs "UIDL" pop query and returns server response
//sets AckFlag
char *CPop3Client::Uidl(int nr)
{
	if (NetClient->Stopped) // check if we can work with this POP3 client session
		throw POP3Error = (uint32_t)EPOP3_STOPPED;

	char query[128];
	if (nr) {
		mir_snprintf(query, "UIDL %d\r\n", nr);
		NetClient->Send(query);
		return RecvRest(NetClient->Recv(), POP3_SEARCHACK);
	}
	mir_snprintf(query, "UIDL\r\n");
	NetClient->Send(query);
	return RecvRest(NetClient->Recv(), POP3_SEARCHDOT);
}

//Performs "DELE" pop query and returns server response
//sets AckFlag
char *CPop3Client::Dele(int nr)
{
	if (NetClient->Stopped) // check if we can work with this POP3 client session
		throw POP3Error = (uint32_t)EPOP3_STOPPED;

	char query[128];

	mir_snprintf(query, "DELE %d\r\n", nr);
	NetClient->Send(query);
	return RecvRest(NetClient->Recv(), POP3_SEARCHACK);
}

//Performs "RETR" pop query and returns server response
//sets AckFlag
char *CPop3Client::Retr(int nr)
{
	if (NetClient->Stopped) // check if we can work with this POP3 client session
		throw POP3Error = (uint32_t)EPOP3_STOPPED;

	char query[128];
	mir_snprintf(query, "RETR %d\r\n", nr);
	NetClient->Send(query);

	RecvRest(NetClient->Recv(), POP3_SEARCHACK);
	return NetClient->Recv();
}