/*
 * Copyright 2007-2015, Haiku, Inc. All rights reserved.
 * Copyright 2001-2002 Dr. Zoidberg Enterprises. All rights reserved.
 * Copyright 2011, Clemens Zeidler <haiku@clemens-zeidler.de>
 *
 * Distributed under the terms of the MIT License.
 */
 
 
//!	Implementation of the SMTP protocol
 
 
#include "SMTP.h"
 
#include <map>
 
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
 
#include <Alert.h>
#include <Catalog.h>
#include <DataIO.h>
#include <Entry.h>
#include <File.h>
#include <MenuField.h>
#include <Message.h>
#include <Path.h>
#include <Socket.h>
#include <SecureSocket.h>
#include <TextControl.h>
 
#include <crypt.h>
#include <mail_encoding.h>
#include <MailSettings.h>
#include <NodeMessage.h>
#include <ProtocolConfigView.h>
 
#include "md5.h"
 
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "smtp"
 
 
#define CRLF "\r\n"
#define SMTP_RESPONSE_SIZE 8192
 
//#define DEBUG
#ifdef DEBUG
#	define D(x) x
#	define bug printf
#else
#	define D(x) ;
#endif
 
 
// Authentication types recognized. Not all methods are implemented.
enum AuthType {
	LOGIN		= 1,
	PLAIN		= 1 << 2,
	CRAM_MD5	= 1 << 3,
	DIGEST_MD5	= 1 << 4
};
 
 
using namespace std;
 
/*
** Function: md5_hmac
** taken from the file rfc2104.txt
** written by Martin Schaaf <mascha@ma-scha.de>
*/
void
MD5Hmac(unsigned char *digest, const unsigned char* text, int text_len,
	const unsigned char* key, int key_len)
{
	MD5_CTX context;
	unsigned char k_ipad[64];
		// inner padding - key XORd with ipad
	unsigned char k_opad[64];
		// outer padding - key XORd with opad
	int i;
 
	/* start out by storing key in pads */
	memset(k_ipad, 0, sizeof k_ipad);
	memset(k_opad, 0, sizeof k_opad);
	if (key_len > 64) {
		/* if key is longer than 64 bytes reset it to key=MD5(key) */
		MD5_CTX tctx;
 
		MD5_Init(&tctx);
		MD5_Update(&tctx, (unsigned char*)key, key_len);
		MD5_Final(k_ipad, &tctx);
		MD5_Final(k_opad, &tctx);
	} else {
		memcpy(k_ipad, key, key_len);
		memcpy(k_opad, key, key_len);
	}
 
	/*
	 * the HMAC_MD5 transform looks like:
	 *
	 * MD5(K XOR opad, MD5(K XOR ipad, text))
	 *
	 * where K is an n byte key
	 * ipad is the byte 0x36 repeated 64 times
	 * opad is the byte 0x5c repeated 64 times
	 * and text is the data being protected
	 */
 
	/* XOR key with ipad and opad values */
	for (i = 0; i < 64; i++) {
		k_ipad[i] ^= 0x36;
		k_opad[i] ^= 0x5c;
	}
 
	/*
	 * perform inner MD5
	 */
	MD5_Init(&context);		      /* init context for 1st
					       * pass */
	MD5_Update(&context, k_ipad, 64);     /* start with inner pad */
	MD5_Update(&context, (unsigned char*)text, text_len); /* then text of datagram */
	MD5_Final(digest, &context);	      /* finish up 1st pass */
	/*
	 * perform outer MD5
	 */
	MD5_Init(&context);		      /* init context for 2nd
					       * pass */
	MD5_Update(&context, k_opad, 64);     /* start with outer pad */
	MD5_Update(&context, digest, 16);     /* then results of 1st
					       * hash */
	MD5_Final(digest, &context);	      /* finish up 2nd pass */
}
 
 
void
MD5HexHmac(char *hexdigest, const unsigned char* text, int text_len,
	const unsigned char* key, int key_len)
{
	unsigned char digest[16];
	int i;
	unsigned char c;
 
	MD5Hmac(digest, text, text_len, key, key_len);
  	for (i = 0;  i < 16;  i++) {
  		c = digest[i];
		*hexdigest++ = (c > 0x9F ? 'a'-10 : '0')+(c>>4);
		*hexdigest++ = ((c&0x0F) > 9 ? 'a'-10 : '0')+(c&0x0F);
  	}
  	*hexdigest = '\0';
}
 
 
/*
** Function: MD5Sum
** generates an MD5-sum from the given string
*/
void
MD5Sum (char* sum, unsigned char *text, int text_len) {
	MD5_CTX context;
	MD5_Init(&context);
	MD5_Update(&context, text, text_len);
	MD5_Final((unsigned char*)sum, &context);
	sum[16] = '\0';
}
 
/*
** Function: MD5Digest
** generates an MD5-digest from the given string
*/
void MD5Digest (char* hexdigest, unsigned char *text, int text_len) {
	int i;
	unsigned char digest[17];
	unsigned char c;
 
	MD5Sum((char*)digest, text, text_len);
 
  	for (i = 0;  i < 16;  i++) {
  		c = digest[i];
		*hexdigest++ = (c > 0x9F ? 'a'-10 : '0')+(c>>4);
		*hexdigest++ = ((c&0x0F) > 9 ? 'a'-10 : '0')+(c&0x0F);
  	}
  	*hexdigest = '\0';
}
 
/*
** Function: SplitChallengeIntoMap
** splits a challenge-string into the given map (see RFC-2831)
*/
// :
static bool
SplitChallengeIntoMap(BString str, map<BString,BString>& m)
{
	m.clear();
	const char* key;
	const char* val;
	char* s = (char*)str.String();
	while(*s != 0) {
		while(isspace(*s))
			s++;
		key = s;
		while(isalpha(*s))
			s++;
		if (*s != '=')
			return false;
		*s++ = '\0';
		while(isspace(*s))
			s++;
		if (*s=='"') {
			val = ++s;
			while(*s!='"') {
				if (*s == 0)
					return false;
				s++;
			}
			*s++ = '\0';
		} else {
			val = s;
			while(*s!=0 && *s!=',' && !isspace(*s))
				s++;
			if (*s != 0)
				*s++ = '\0';
		}
		m[key] = val;
		while(isspace(*s))
			s++;
		if (*s != ',')
			return false;
		s++;
	}
	return true;
}
 
 
// #pragma mark -
 
 
SMTPProtocol::SMTPProtocol(const BMailAccountSettings& settings)
	:
	BOutboundMailProtocol("SMTP", settings),
	fAuthType(0)
{
	fSettingsMessage = settings.OutboundSettings();
}
 
 
SMTPProtocol::~SMTPProtocol()
{
}
 
 
status_t
SMTPProtocol::Connect()
{
	BString errorMessage;
	int32 authMethod = fSettingsMessage.FindInt32("auth_method");
 
	status_t status = B_ERROR;
 
	if (authMethod == 2) {
		// POP3 authentication is handled here instead of SMTPProtocol::Login()
		// because some servers obviously don't like establishing the connection
		// to the SMTP server first...
		status_t status = _POP3Authentication();
		if (status < B_OK) {
			errorMessage << B_TRANSLATE("POP3 authentication failed. The "
				"server said:\n") << fLog;
			ShowError(errorMessage.String());
			return status;
		}
	}
 
	status = Open(fSettingsMessage.FindString("server"),
		fSettingsMessage.FindInt32("port"), authMethod == 1);
	if (status < B_OK) {
		errorMessage << B_TRANSLATE("Error while opening connection to %serv");
		errorMessage.ReplaceFirst("%serv",
			fSettingsMessage.FindString("server"));
 
		if (fSettingsMessage.FindInt32("port") > 0)
			errorMessage << ":" << fSettingsMessage.FindInt32("port");
 
		if (fLog.Length() > 0)
			errorMessage << B_TRANSLATE(". The server says:\n") << fLog;
		else {
			errorMessage << ". " << strerror(status);
		}
 
		ShowError(errorMessage.String());
 
		return status;
	}
 
	const char* password = get_passwd(&fSettingsMessage, "cpasswd");
	status = Login(fSettingsMessage.FindString("username"), password);
	delete[] password;
 
	if (status != B_OK) {
		errorMessage << B_TRANSLATE("Error while logging in to %serv")
			<< B_TRANSLATE(". The server said:\n") << fLog;
		errorMessage.ReplaceFirst("%serv",
			fSettingsMessage.FindString("server"));
 
		ShowError(errorMessage.String());
	}
	return B_OK;
}
 
 
void
SMTPProtocol::Disconnect()
{
	Close();
}
 
 
//! Process EMail to be sent
status_t
SMTPProtocol::HandleSendMessages(const BMessage& message, off_t totalBytes)
{
	type_code type;
	int32 count;
	status_t status = message.GetInfo("ref", &type, &count);
	if (status != B_OK)
		return status;
 
	// TODO: sort out already sent messages -- the request could
	// be issued while we're busy sending them already
 
	SetTotalItems(count);
	SetTotalItemsSize(totalBytes);
 
	status = Connect();
	if (status != B_OK)
		return status;
 
	entry_ref ref;
	for (int32 i = 0; message.FindRef("ref", i++, &ref) == B_OK;) {
		status = _SendMessage(ref);
		if (status != B_OK) {
			BString error;
			error << "An error occurred while sending the message "
				<< ref.name << " (" << strerror(status) << "):\n" << fLog;
			ShowError(error.String());
 
			ResetProgress();
			break;
		}
	}
 
	Disconnect();
	return B_ERROR;
}
 
 
//! Opens connection to server
status_t
SMTPProtocol::Open(const char *address, int port, bool esmtp)
{
	ReportProgress(0, 0, B_TRANSLATE("Connecting to server" B_UTF8_ELLIPSIS));
 
	use_ssl = (fSettingsMessage.FindInt32("flavor") == 1);
 
	if (port <= 0)
		port = use_ssl ? 465 : 25;
 
	BNetworkAddress addr(address);
	if (addr.InitCheck() != B_OK) {
		BString str;
		str.SetToFormat("Invalid network address for SMTP server: %s",
			strerror(addr.InitCheck()));
		ShowError(str.String());
		return addr.InitCheck();
	}
		
	if (addr.Port() == 0)
		addr.SetPort(port);
 
	if (use_ssl)
		fSocket = new(std::nothrow) BSecureSocket;
	else
		fSocket = new(std::nothrow) BSocket;
	
	if (!fSocket)
		return B_NO_MEMORY;
	
	if (fSocket->Connect(addr) != B_OK) {
		BString error;
		error << "Could not connect to SMTP server "
			<< fSettingsMessage.FindString("server");
		error << ":" << addr.Port();
		ShowError(error.String());
		delete fSocket;
		return B_ERROR;
	}
 
	BString line;
	ReceiveResponse(line);
 
	char localhost[255];
	gethostname(localhost, 255);
 
	if (localhost[0] == 0)
		strcpy(localhost, "namethisbebox");
 
	char *cmd = new char[::strlen(localhost)+8];
	if (!esmtp)
		::sprintf(cmd,"HELO %s" CRLF, localhost);
	else
		::sprintf(cmd,"EHLO %s" CRLF, localhost);
 
	if (SendCommand(cmd) != B_OK) {
		delete[] cmd;
		return B_ERROR;
	}
 
	delete[] cmd;
 
	// Check auth type
	if (esmtp) {
		const char *res = fLog.String();
		char *p;
		if ((p = ::strstr(res, "250-AUTH")) != NULL
				|| (p = ::strstr(res, "250 AUTH")) != NULL) {
			if(::strstr(p, "LOGIN"))
				fAuthType |= LOGIN;
			if(::strstr(p, "PLAIN"))
				fAuthType |= PLAIN;
			if(::strstr(p, "CRAM-MD5"))
				fAuthType |= CRAM_MD5;
			if(::strstr(p, "DIGEST-MD5")) {
				fAuthType |= DIGEST_MD5;
				fServerName = address;
			}
		}
	}
	return B_OK;
}
 
 
status_t
SMTPProtocol::_SendMessage(const entry_ref& ref)
{
	// open read write to be able to manipulate in MessageReadyToSend hook
	BFile file(&ref, B_READ_WRITE);
	status_t status = file.InitCheck();
	if (status != B_OK)
		return status;
 
	BMessage header;
	file >> header;
 
	const char *from = header.FindString("MAIL:from");
	const char *to = header.FindString("MAIL:recipients");
	if (to == NULL)
		to = header.FindString("MAIL:to");
 
	if (to == NULL || from == NULL) {
		fLog = "Invalid message headers";
		return B_ERROR;
	}
 
	NotifyMessageReadyToSend(ref, file);
	status = Send(to, from, &file);
	if (status != B_OK)
		return status;
	NotifyMessageSent(ref, file);
 
	off_t size = 0;
	file.GetSize(&size);
	ReportProgress(size, 1);
 
	return B_OK;
}
 
 
status_t
SMTPProtocol::_POP3Authentication()
{
	const entry_ref& entry = fAccountSettings.InboundAddOnRef();
	if (strcmp(entry.name, "POP3") != 0)
		return B_ERROR;
 
	status_t (*pop3_smtp_auth)(const BMailAccountSettings&);
 
	BPath path(&entry);
	image_id image = load_add_on(path.Path());
	if (image < 0)
		return B_ERROR;
	if (get_image_symbol(image, "pop3_smtp_auth",
			B_SYMBOL_TYPE_TEXT, (void **)&pop3_smtp_auth) != B_OK) {
		unload_add_on(image);
		image = -1;
		return B_ERROR;
	}
	status_t status = (*pop3_smtp_auth)(fAccountSettings);
	unload_add_on(image);
	return status;
}
 
 
status_t
SMTPProtocol::Login(const char *_login, const char *password)
{
	if (fAuthType == 0)
		return B_OK;
 
	const char *login = _login;
	char hex_digest[33];
	BString out;
 
	int32 loginlen = ::strlen(login);
	int32 passlen = ::strlen(password);
 
	if (fAuthType & DIGEST_MD5) {
		//******* DIGEST-MD5 Authentication ( tested. works fine [with Cyrus SASL] )
		// this implements only the subpart of DIGEST-MD5 which is
		// required for authentication to SMTP-servers. Integrity-
		// and confidentiality-protection are not implemented, as
		// they are provided by the use of OpenSSL.
		SendCommand("AUTH DIGEST-MD5" CRLF);
		const char *res = fLog.String();
 
		if (strncmp(res, "334", 3) != 0)
			return B_ERROR;
		int32 baselen = ::strlen(&res[4]);
		char *base = new char[baselen+1];
		baselen = ::decode_base64(base, &res[4], baselen);
		base[baselen] = '\0';
 
		D(bug("base: %s\n", base));
 
		map<BString,BString> challengeMap;
		SplitChallengeIntoMap(base, challengeMap);
 
		delete[] base;
 
		BString rawResponse = BString("username=") << '"' << login << '"';
		rawResponse << ",realm=" << '"' << challengeMap["realm"] << '"';
		rawResponse << ",nonce=" << '"' << challengeMap["nonce"] << '"';
		rawResponse << ",nc=00000001";
		char temp[33];
		for( int i=0; i<32; ++i)
			temp[i] = 1+(rand()%254);
		temp[32] = '\0';
		BString rawCnonce(temp);
		BString cnonce;
		char* cnoncePtr = cnonce.LockBuffer(rawCnonce.Length()*2);
		baselen = ::encode_base64(cnoncePtr, rawCnonce.String(), rawCnonce.Length(), true /* headerMode */);
		cnoncePtr[baselen] = '\0';
		cnonce.UnlockBuffer(baselen);
		rawResponse << ",cnonce=" << '"' << cnonce << '"';
		rawResponse << ",qop=auth";
		BString digestUriValue	= BString("smtp/") << fServerName;
		rawResponse << ",digest-uri=" << '"' << digestUriValue << '"';
		char sum[17], hex_digest2[33];
		BString a1,a2,kd;
		BString t1 = BString(login) << ":"
				<< challengeMap["realm"] << ":"
				<< password;
		MD5Sum(sum, (unsigned char*)t1.String(), t1.Length());
		a1 << sum << ":" << challengeMap["nonce"] << ":" << cnonce;
		MD5Digest(hex_digest, (unsigned char*)a1.String(), a1.Length());
		a2 << "AUTHENTICATE:" << digestUriValue;
		MD5Digest(hex_digest2, (unsigned char*)a2.String(), a2.Length());
		kd << hex_digest << ':' << challengeMap["nonce"]
		   << ":" << "00000001" << ':' << cnonce << ':' << "auth"
		   << ':' << hex_digest2;
		MD5Digest(hex_digest, (unsigned char*)kd.String(), kd.Length());
 
		rawResponse << ",response=" << hex_digest;
		BString postResponse;
		char *resp = postResponse.LockBuffer(rawResponse.Length() * 2 + 10);
		baselen = ::encode_base64(resp, rawResponse.String(), rawResponse.Length(), true /* headerMode */);
		resp[baselen] = 0;
		postResponse.UnlockBuffer();
		postResponse.Append(CRLF);
 
		SendCommand(postResponse.String());
 
		res = fLog.String();
		if (atol(res) >= 500)
			return B_ERROR;
		// actually, we are supposed to check the rspauth sent back
		// by the SMTP-server, but that isn't strictly required,
		// so we skip that for now.
		SendCommand(CRLF);	// finish off authentication
		res = fLog.String();
		if (atol(res) < 500)
			return B_OK;
	}
	if (fAuthType & CRAM_MD5) {
		//******* CRAM-MD5 Authentication ( tested. works fine [with Cyrus SASL] )
		SendCommand("AUTH CRAM-MD5" CRLF);
		const char *res = fLog.String();
 
		if (strncmp(res, "334", 3) != 0)
			return B_ERROR;
		int32 baselen = ::strlen(&res[4]);
		char *base = new char[baselen+1];
		baselen = ::decode_base64(base, &res[4], baselen);
		base[baselen] = '\0';
 
		D(bug("base: %s\n", base));
 
		::MD5HexHmac(hex_digest, (const unsigned char *)base, (int)baselen,
			(const unsigned char *)password, (int)passlen);
 
		D(bug("%s\n%s\n", base, hex_digest));
 
		delete[] base;
 
		BString preResponse, postResponse;
		preResponse = login;
		preResponse << " " << hex_digest << CRLF;
		char *resp = postResponse.LockBuffer(preResponse.Length() * 2 + 10);
		baselen = ::encode_base64(resp, preResponse.String(), preResponse.Length(), true /* headerMode */);
		resp[baselen] = 0;
		postResponse.UnlockBuffer();
		postResponse.Append(CRLF);
 
		SendCommand(postResponse.String());
 
		res = fLog.String();
		if (atol(res) < 500)
			return B_OK;
	}
	if (fAuthType & LOGIN) {
		//******* LOGIN Authentication ( tested. works fine)
		ssize_t encodedsize; // required by our base64 implementation
 
		SendCommand("AUTH LOGIN" CRLF);
		const char *res = fLog.String();
 
		if (strncmp(res, "334", 3) != 0)
			return B_ERROR;
 
		// Send login name as base64
		char *login64 = new char[loginlen*3 + 6];
		encodedsize = ::encode_base64(login64, (char *)login, loginlen, true /* headerMode */);
		login64[encodedsize] = 0;
		strcat (login64, CRLF);
		SendCommand(login64);
		delete[] login64;
 
		res = fLog.String();
		if (strncmp(res,"334",3) != 0)
			return B_ERROR;
 
		// Send password as base64
		login64 = new char[passlen*3 + 6];
		encodedsize = ::encode_base64(login64, (char *)password, passlen, true /* headerMode */);
		login64[encodedsize] = 0;
		strcat (login64, CRLF);
		SendCommand(login64);
		delete[] login64;
 
		res = fLog.String();
		if (atol(res) < 500)
			return B_OK;
	}
	if (fAuthType & PLAIN) {
		//******* PLAIN Authentication ( tested. works fine [with Cyrus SASL] )
		// format is:
		// 	authenticateID + \0 + username + \0 + password
		// 	(where authenticateID is always empty !?!)
		BString preResponse, postResponse;
		char *stringPntr;
		ssize_t encodedLength;
		stringPntr = preResponse.LockBuffer(loginlen + passlen + 3);
			// +3 to make room for the two \0-chars between the tokens and
			// the final delimiter added by sprintf().
		sprintf (stringPntr, "%c%s%c%s", 0, login, 0, password);
		preResponse.UnlockBuffer(loginlen + passlen + 2);
			// +2 in order to leave out the final delimiter (which is not part
			// of the string).
		stringPntr = postResponse.LockBuffer(preResponse.Length() * 3);
		encodedLength = ::encode_base64(stringPntr, preResponse.String(),
			preResponse.Length(), true /* headerMode */);
		stringPntr[encodedLength] = 0;
		postResponse.UnlockBuffer();
		postResponse.Prepend("AUTH PLAIN ");
		postResponse << CRLF;
 
		SendCommand(postResponse.String());
 
		const char *res = fLog.String();
		if (atol(res) < 500)
			return B_OK;
	}
	return B_ERROR;
}
 
 
void
SMTPProtocol::Close()
{
 
	BString cmd = "QUIT";
	cmd += CRLF;
 
	if (SendCommand(cmd.String()) != B_OK) {
		// Error
	}
 
	delete fSocket;
}
 
 
status_t
SMTPProtocol::Send(const char* to, const char* from, BPositionIO *message)
{
	BString cmd = from;
	cmd.Remove(0, cmd.FindFirst("\" <") + 2);
	cmd.Prepend("MAIL FROM: ");
	cmd += CRLF;
	if (SendCommand(cmd.String()) != B_OK)
		return B_ERROR;
 
	int32 len = strlen(to);
	BString addr("");
	for (int32 i = 0;i < len;i++) {
		char c = to[i];
		if (c != ',')
			addr += (char)c;
		if (c == ','||i == len-1) {
			if(addr.Length() == 0)
				continue;
			cmd = "RCPT TO: ";
			cmd << addr.String() << CRLF;
			if (SendCommand(cmd.String()) != B_OK)
				return B_ERROR;
 
			addr ="";
		}
	}
 
	cmd = "DATA";
	cmd += CRLF;
	if (SendCommand(cmd.String()) != B_OK)
		return B_ERROR;
 
	// Send the message data.  Convert lines starting with a period to start
	// with two periods and so on.  The actual sequence is CR LF Period.  The
	// SMTP server will remove the periods.  Of course, the POP server may then
	// add some of its own, but the POP client should take care of them.
 
	ssize_t amountRead;
	ssize_t amountToRead;
	ssize_t amountUnread;
	ssize_t bufferLen = 0;
	const int bufferMax = 2000;
	bool foundCRLFPeriod;
	int i;
	bool messageEndedWithCRLF = false;
 
	message->Seek(0, SEEK_END);
	amountUnread = message->Position();
	message->Seek(0, SEEK_SET);
	char *data = new char[bufferMax];
 
	while (true) {
		// Fill the buffer if it is getting low, but not every time, to avoid
		// small reads.
		if (bufferLen < bufferMax / 2) {
			amountToRead = bufferMax - bufferLen;
			if (amountToRead > amountUnread)
				amountToRead = amountUnread;
			if (amountToRead > 0) {
				amountRead = message->Read (data + bufferLen, amountToRead);
				if (amountRead <= 0 || amountRead > amountToRead)
					amountUnread = 0; // Just stop reading when an error happens.
				else {
					amountUnread -= amountRead;
					bufferLen += amountRead;
				}
			}
		}
 
		// Look for the next CRLFPeriod triple.
		foundCRLFPeriod = false;
		for (i = 0; i <= bufferLen - 3; i++) {
			if (data[i] == '\r' && data[i+1] == '\n' && data[i+2] == '.') {
				foundCRLFPeriod = true;
				// Send data up to the CRLF, and include the period too.
				if (fSocket->Write(data, i + 3) < 0) {
					amountUnread = 0; // Stop when an error happens.
					bufferLen = 0;
					break;
				}
				ReportProgress (i + 2 /* Don't include the double period here */,0);
				// Move the data over in the buffer, but leave the period there
				// so it gets sent a second time.
				memmove(data, data + (i + 2), bufferLen - (i + 2));
				bufferLen -= i + 2;
				break;
			}
		}
 
		if (!foundCRLFPeriod) {
			if (amountUnread <= 0) { // No more data, all we have is in the buffer.
				if (bufferLen > 0) {
					fSocket->Write(data, bufferLen);
					ReportProgress(bufferLen, 0);
					if (bufferLen >= 2)
						messageEndedWithCRLF = (data[bufferLen-2] == '\r' &&
							data[bufferLen-1] == '\n');
				}
				break; // Finished!
			}
 
			// Send most of the buffer, except a few characters to overlap with
			// the next read, in case the CRLFPeriod is split between reads.
			if (bufferLen > 3) {
				if (fSocket->Write(data, bufferLen - 3) < 0)
					break; // Stop when an error happens.
 
				ReportProgress(bufferLen - 3, 0);
				memmove (data, data + bufferLen - 3, 3);
				bufferLen = 3;
			}
		}
	}
	delete [] data;
 
	if (messageEndedWithCRLF)
		cmd = "." CRLF; // The standard says don't add extra CRLF.
	else
		cmd = CRLF "." CRLF;
 
	if (SendCommand(cmd.String()) != B_OK)
		return B_ERROR;
 
	return B_OK;
}
 
 
//! Receives response from server.
int32
SMTPProtocol::ReceiveResponse(BString &out)
{
	out = "";
	int32 len = 0,r;
	char buf[SMTP_RESPONSE_SIZE];
	bigtime_t timeout = 1000000*180; // timeout 180 secs
	bool gotCode = false;
	int32 errCode;
	BString searchStr = "";
 
	if (fSocket->WaitForReadable(timeout) == B_OK) {
		while (1) {
			r = fSocket->Read(buf, SMTP_RESPONSE_SIZE - 1);
			if (r <= 0)
				break;
 
			if (!gotCode) {
				if (buf[3] == ' ' || buf[3] == '-') {
					errCode = atol(buf);
					gotCode = true;
					searchStr << errCode << ' ';
				}
			}
 
			len += r;
			out.Append(buf, r);
 
			if (strstr(buf, CRLF) && (out.FindFirst(searchStr) != B_ERROR))
				break;
		}
	} else
		fLog = "SMTP socket timeout.";
 
	D(bug("S:%s\n", out.String()));
	return len;
}
 
 
// Sends SMTP command. Result kept in fLog
 
status_t
SMTPProtocol::SendCommand(const char *cmd)
{
	D(bug("C:%s\n", cmd));
 
	if (fSocket->Write(cmd, ::strlen(cmd)) < 0)
		return B_ERROR;
	fLog = "";
 
	// Receive
	while (1) {
		int32 len = ReceiveResponse(fLog);
 
		if (len <= 0) {
			D(bug("SMTP: len == %" B_PRId32 "\n", len));
			return B_ERROR;
		}
 
		if (fLog.Length() > 4 && (fLog[3] == ' ' || fLog[3] == '-'))
		{
			int32 num = atol(fLog.String());
			D(bug("ReplyNumber: %" B_PRId32 "\n", num));
			if (num >= 500)
				return B_ERROR;
 
			break;
		}
	}
 
	return B_OK;
}
 
 
// #pragma mark -
 
 
extern "C" BOutboundMailProtocol*
instantiate_outbound_protocol(const BMailAccountSettings& settings)
{
	return new SMTPProtocol(settings);
}

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fSocket, use_ssl, fStatus.