// This is a part of the Active Template Library.
// Copyright (C) Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Active Template Library Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Active Template Library product.

#ifndef __ATLSMTPCONNECTION_H__
#define __ATLSMTPCONNECTION_H__

#pragma once

#ifndef _ATL_NO_DEFAULT_LIBS
#pragma comment(lib, "ws2_32.lib")
#endif  // !_ATL_NO_DEFAULT_LIBS

#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <atlstr.h>
#include <atlcoll.h>
#include <atlfile.h>
#include <atlmime.h>
#include <atlspriv.h>
#include <atlsmtputil.h>
#include <atlsocket.h>

// SMTP Return Codes
#define ATLSMTP_MAIL_SUCCESS      250
#define ATLSMTP_RCPT_SUCCESS      250
#define ATLSMTP_RCPT_NOT_LOCAL    251
#define ATLSMTP_DATA_INTERMEDIATE 354

#define ATLSMTP_CONN_SUCC "220"
#define ATLSMTP_HELO_SUCC "250"
#define ATLSMTP_MAIL_SUCC "250"
#define ATLSMTP_RCPT_SUCC "250"
#define ATLSMTP_RCPT_NLOC "251"
#define ATLSMTP_DATA_INTM "354"
#define ATLSMTP_DATA_SUCC "250"
#define ATLSMTP_RSET_SUCC "250"

// SMTP flags
#define ATLSMTP_DUMP_SENDER 1
#define ATLSMTP_DUMP_RECIPS 2
#define ATLSMTP_FOR_SEND    4


struct CSMTPWSAStartup
{
private:
	bool m_bInit;

public:
	CSMTPWSAStartup() throw()
		:m_bInit(false)
	{
		Init();
	}

	~CSMTPWSAStartup() throw()
	{
		Uninit();
	}

	bool Init() throw()
	{
		if (m_bInit)
			return true;

		WSADATA wsadata;
		if (WSAStartup(ATLSMTP_WSA_VERSION, &wsadata))
			return false;
		m_bInit = true;
		ATLASSERT(wsadata.wHighVersion >= 2);
		return true;
	}

	bool Uninit() throw()
	{
		if (m_bInit)
			if (WSACleanup())
				return false;
		m_bInit = false;
		return true;
	}
};

__declspec(selectany) CSMTPWSAStartup _g_smtp_init;

#pragma pack(push,_ATL_PACKING)
namespace ATL {

class CSMTPConnection
{
protected:

	// the socket
	SOCKET m_hSocket;

	// the OVERLAPPED struct
	OVERLAPPED m_Overlapped;

public:

	CSMTPConnection() throw()
		:m_hSocket(INVALID_SOCKET)
	{
		// initialize the OVERLAPPED struct
		memset(&m_Overlapped, 0, sizeof(OVERLAPPED));
	}

	~CSMTPConnection() throw()
	{
		Disconnect();
	}

	// Attempt to connect to the socket
	// lpszHostName - the host name to connect to
	BOOL Connect(LPCTSTR lpszHostName, DWORD dwTimeout = 10000) throw()
	{
		ATLASSERT(lpszHostName != NULL);

		// If we're already connected
		if (Connected())
		{
			return FALSE;
		}

		if (!_g_smtp_init.Init())
		{
			return FALSE;
		}

		CSocketAddr address;
		if (address.FindAddr(lpszHostName, IPPORT_SMTP, 0, PF_UNSPEC, SOCK_STREAM, 0))
		{
			return FALSE;
		}

		ADDRINFOT *pAI;
		
		BOOL bRet = FALSE;
		int nIndex = 0;
		while ((pAI = address.GetAddrInfo(nIndex++)) != NULL)
		{
			// create the socket
			m_hSocket = WSASocket(pAI->ai_family, pAI->ai_socktype, pAI->ai_protocol, NULL, 0, WSA_FLAG_OVERLAPPED);

			if (m_hSocket == INVALID_SOCKET)
			{
				return FALSE;
			}

			bRet = FALSE;
			WSAEVENT hEventConnect = WSACreateEvent();
			if (hEventConnect != NULL)
			{
				if (SOCKET_ERROR != WSAEventSelect(m_hSocket, hEventConnect, FD_CONNECT))
				{
					if (WSAConnect(m_hSocket, pAI->ai_addr, (int)pAI->ai_addrlen, 
							NULL, NULL, NULL, NULL))
					{
						if (WSAGetLastError() == WSAEWOULDBLOCK)
						{
							DWORD dwWait = WaitForSingleObject((HANDLE) hEventConnect, dwTimeout);
							if (dwWait == WAIT_OBJECT_0)
							{
								// make sure there were no connection errors.
								WSANETWORKEVENTS wse;
								ZeroMemory(&wse, sizeof(wse));
								WSAEnumNetworkEvents(m_hSocket, NULL, &wse);
								if (wse.iErrorCode[FD_CONNECT_BIT]==0)
								{
									bRet = TRUE;
								}
							}
						}
					}
				}

				// we're done with the event
				WSACloseEvent(hEventConnect);
			}
			if (bRet)
			{
				break;
			}
			
			shutdown(m_hSocket, SD_BOTH);
			closesocket(m_hSocket);
			m_hSocket = INVALID_SOCKET;
		}
		

		// Create an event for asynchronous I/O
		if (bRet)
		{
			ATLASSUME(m_Overlapped.hEvent == NULL);
			m_Overlapped.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
			if (m_Overlapped.hEvent == NULL)
			{
				bRet = FALSE;
			}
		}

		char szBuf[ATLSMTP_MAX_LINE_LENGTH+1];
		int nBufLen = ATLSMTP_MAX_LINE_LENGTH;
		if (bRet)
		{
			// See if the connect returns success
			bRet = AtlSmtpReadData((HANDLE)m_hSocket, szBuf, &nBufLen, &m_Overlapped);
			if (bRet)
			{
				if (strncmp(szBuf, ATLSMTP_CONN_SUCC, ATLSMTP_RETCODE_LEN))
				{
					bRet = FALSE;
				}
			}
		}

		char szLocalHost[ATLSMTP_MAX_SERVER_NAME_LENGTH+1];

		// gethostname should return 0 on success
		if (bRet && gethostname(szLocalHost, ATLSMTP_MAX_SERVER_NAME_LENGTH))
		{
			bRet = FALSE;
		}

		// Send HELO command and get reply
		if (bRet)
		{
			nBufLen = sprintf_s(szBuf, ATLSMTP_MAX_LINE_LENGTH+1, "HELO %s\r\n", szLocalHost);
			if (nBufLen > 0)
			{
				bRet = AtlSmtpSendAndCheck((HANDLE)m_hSocket, szBuf, nBufLen, szBuf, &nBufLen, 
										ATLSMTP_MAX_LINE_LENGTH, ATLSMTP_HELO_SUCC, &m_Overlapped);
			}
			else
			{
				bRet = FALSE;
			}
		}

		if (!bRet)
		{
			if (m_Overlapped.hEvent != NULL)
				CloseHandle(m_Overlapped.hEvent);
			shutdown(m_hSocket, SD_BOTH);
			closesocket(m_hSocket);
			m_hSocket = INVALID_SOCKET;
		}
		
		return bRet;
	}

	// Disconnect the socket
	inline BOOL Disconnect() throw()
	{
		if (!Connected())
		{
			return FALSE;
		}

		// shutdown should return 0 on success
		if (shutdown(m_hSocket, SD_BOTH))
		{
			return FALSE;
		}

		// closesocket should return 0 on success
		if (closesocket(m_hSocket))
		{
			return FALSE;
		}

		// close the handle to the overlapped event
		CloseHandle(m_Overlapped.hEvent);
		m_hSocket = INVALID_SOCKET;
		memset((void*)&m_Overlapped, 0, sizeof(OVERLAPPED));
		return TRUE;
	}

	// Are we connected?
	inline BOOL Connected() throw()
	{
		return (m_hSocket != INVALID_SOCKET ? TRUE : FALSE);
	}

	// Send a message from a file
	// lpszFileName - the file containing the message
	// lpszRecipients - the recipients to send to (optional - if not specified, the recipients specified
	//		in the file will be used
	// lpszSender - the sender (optional - if not specified, the recipients specified in the file
	//		will be used
	BOOL SendMessage(LPCTSTR lpszFileName, LPCTSTR lpszRecipients = NULL, LPCTSTR lpszSender = NULL) throw()
	{
		if (!Connected())
		{
			return FALSE;
		}

		//Try to open the file
		CAtlFile readFile;
		if (FAILED(readFile.Create(lpszFileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL)))
		{
			return FALSE;
		}

		char szBuf[ATLSMTP_MAX_LINE_LENGTH+1];
		int nBufLen = ATLSMTP_MAX_LINE_LENGTH;
		BOOL bDumpedSender = FALSE;

		//If the caller specifies the sender, rather than having an existing one in the file...
		if (lpszSender)
		{
			nBufLen = sprintf_s(szBuf, ATLSMTP_MAX_LINE_LENGTH+1, 
				"MAIL FROM:<%s>\r\n", (LPCSTR) CT2CA(lpszSender));
			if ((nBufLen < 0) || 
				(!AtlSmtpSendAndCheck((HANDLE)m_hSocket, szBuf, nBufLen, szBuf, &nBufLen, ATLSMTP_MAX_LINE_LENGTH, 
					ATLSMTP_MAIL_SUCC, &m_Overlapped)))
			{
				return FALSE;
			}
			bDumpedSender = TRUE;
		}
		nBufLen = ATLSMTP_MAX_LINE_LENGTH;

#ifdef ATLSMTP_DOUBLE_BUFFERED
		char buffer1[ATLSMTP_READBUFFER_SIZE];
		char buffer2[ATLSMTP_READBUFFER_SIZE];
		char* currBuffer = buffer1;
		char* prevBuffer = NULL;

		int nCurrBuffer = 0;
		DWORD dwPrevLength = 0;
		DWORD dwWritten = 0;
#else
		char bakBuffer[ATLSMTP_READBUFFER_SIZE];
		char* currBuffer = bakBuffer;

#endif // ATLSMTP_DOUBLE_BUFFERED
		DWORD dwRead = 0;
		DWORD dwBytesInBuffer = 0;
		DWORD dwBufPos = 0;

		//first handle the MAIL FROM and RCPT TO commands
		BOOL bDumpedRecipients = FALSE;
		BOOL bRet = TRUE;
		while (bRet)
		{
			int nRetCode = 0;

			//if we have dumped the sender, and we have extra recipients to send,
			//and we haven't alredy done so, do it
			if (lpszRecipients && !bDumpedRecipients && bDumpedSender)
			{
				bRet = DumpRecipients((HANDLE)m_hSocket, CT2A(lpszRecipients), &m_Overlapped, ATLSMTP_FOR_SEND);
			}

			if (bRet)
			{
				dwRead = 0;
				BOOL bFullLine = FALSE;
				bRet = ReadLine(readFile, currBuffer, szBuf, &dwBytesInBuffer, &dwBufPos,
					ATLSMTP_READBUFFER_SIZE, ATLSMTP_MAX_LINE_LENGTH, &dwRead, &bFullLine);
				if (dwRead == 0 || bFullLine == FALSE)
					bRet = FALSE;
			}

			if (bRet)
			{
				bRet = AtlSmtpSendAndWait((HANDLE)m_hSocket, szBuf, (int)(dwRead), &m_Overlapped);
			}

			if (bRet)
			{
				nBufLen = ATLSMTP_MAX_LINE_LENGTH;
				bRet = AtlSmtpReadData((HANDLE)m_hSocket, szBuf, &nBufLen, &m_Overlapped);
			}

			if (bRet)
			{	
				nRetCode = atoi(szBuf);
				//if the command is equal to ATLSMTP_MAIL_SUCC (or RCPT_SUCC: they are equivalent)
				if (nRetCode == ATLSMTP_MAIL_SUCCESS || nRetCode == ATLSMTP_RCPT_NOT_LOCAL || nRetCode == ATLSMTP_RCPT_SUCCESS)
				{
					bDumpedSender = TRUE;
					continue;
				}

				//If the command is equal to the data intermediate success code,
				//break out of the loop
				if (nRetCode == ATLSMTP_DATA_INTERMEDIATE)
					break;
			}

			//otherwise, we got an error code
			CancelMessage();
			return FALSE;
		}

		dwRead = dwBytesInBuffer;
		currBuffer+= dwBufPos;
		DWORD dwErr = 0;
		do
		{
			dwErr = 0;

			//Try to send the data
#ifdef ATLSMTP_DOUBLE_BUFFERED
			if (!AtlSmtpSendOverlapped((HANDLE)m_hSocket, currBuffer, dwRead, prevBuffer, dwPrevLength, &m_Overlapped))
			{
				bRet = FALSE;
				break;
			}
#else
			if (!AtlSmtpSendAndWait((HANDLE)m_hSocket, currBuffer, dwRead, &m_Overlapped))
			{
				bRet = FALSE;
				break;
			}
#endif // ATLSMTP_DOUBLE_BUFFERED

			//swap the current and previous buffer
#ifdef ATLSMTP_DOUBLE_BUFFERED
			prevBuffer = currBuffer;
			currBuffer = (nCurrBuffer == 0 ? buffer2 : buffer1);
			nCurrBuffer = (nCurrBuffer == 0 ? 1 : 0);
			dwPrevLength = dwBytesInBuffer;
#else
			currBuffer = bakBuffer;
#endif // ATLSMTP_DOUBLE_BUFFERED

			if (FAILED(readFile.Read(currBuffer, ATLSMTP_READBUFFER_SIZE, dwRead)))
			{
				bRet = FALSE;
				break;
			}
		} while (dwRead != 0);

		//ensure that the last of the data is sent
#ifdef ATLSMTP_DOUBLE_BUFFERED
		if (!GetOverlappedResult((HANDLE)m_hSocket, &m_Overlapped, &dwWritten, TRUE))
		{
			if ((dwErr = GetLastError()) != ERROR_IO_PENDING && dwErr != ERROR_IO_INCOMPLETE)
				bRet = FALSE;
			else if (dwWritten < dwPrevLength)
				bRet = AtlSmtpSendAndWait((HANDLE)m_hSocket, prevBuffer+dwWritten, dwPrevLength-dwWritten, &m_Overlapped);
		}
#endif // ATLSMTP_DOUBLE_BUFFERED


		if (bRet)
		{
			// End the message with a CRLF.CRLF
			nBufLen = sprintf_s(szBuf, _countof(szBuf), "\r\n.\r\n");
			if (!AtlSmtpSendAndCheck((HANDLE)m_hSocket, szBuf, nBufLen, 
				szBuf, &nBufLen, ATLSMTP_MAX_LINE_LENGTH, ATLSMTP_DATA_SUCC, &m_Overlapped))
			{
				bRet = FALSE;
			}
		}

		return bRet;
	}

	// Send the message
	// msg - the CMimeMessage to send
	// lpszSender - the sender 
	inline BOOL SendMessage(CMimeMessage& msg, LPCTSTR lpszRecipients = NULL, LPCTSTR lpszSender = NULL) throw()
	{
		if (!Connected())
		{
			return FALSE;
		}

		char szBuf[ATLSMTP_MAX_LINE_LENGTH+1];

		//Send MAIL FROM command and get reply
		int nBufLen = sprintf_s(szBuf, ATLSMTP_MAX_LINE_LENGTH+1, "MAIL FROM:<%s>\r\n", 
			(lpszSender ? (LPCSTR) CT2CA(lpszSender) : msg.GetSender()));
		if ((nBufLen < 0) ||
			(!AtlSmtpSendAndCheck((HANDLE)m_hSocket, szBuf, nBufLen, 
				szBuf, &nBufLen, ATLSMTP_MAX_LINE_LENGTH, ATLSMTP_MAIL_SUCC, &m_Overlapped)))
		{
			return FALSE;
		}

		BOOL bRet = TRUE;
		if (!lpszRecipients)
		{
			LPSTR lpszRecipientsA = NULL;
			DWORD dwLen = msg.GetRequiredRecipientsStringLength();
			lpszRecipientsA = static_cast<LPSTR>(calloc(sizeof(char),dwLen));
			if (!lpszRecipientsA || msg.GetRecipientsString(lpszRecipientsA, &dwLen) == FALSE)
			{
				bRet = FALSE;
			}
			if (bRet)
				bRet = DumpRecipients((HANDLE)m_hSocket, lpszRecipientsA, &m_Overlapped, ATLSMTP_FOR_SEND);
			free(lpszRecipientsA);
		}
		else
		{
			bRet = DumpRecipients((HANDLE)m_hSocket, CT2CA(lpszRecipients), 
						&m_Overlapped, ATLSMTP_FOR_SEND);
		}

		//Begin the data output
		if (bRet)
		{
			nBufLen = sprintf_s(szBuf, _countof(szBuf), "DATA\r\n");
			bRet = AtlSmtpSendAndCheck((HANDLE)m_hSocket, szBuf, nBufLen, 
						szBuf, &nBufLen, ATLSMTP_MAX_LINE_LENGTH, ATLSMTP_DATA_INTM, &m_Overlapped);
		}

		if (!bRet)
			CancelMessage();

		//Attempt to write the data to the socket
		if (bRet)
		{
			bRet = msg.WriteData((HANDLE)m_hSocket, &m_Overlapped, NULL, ATLSMTP_FORMAT_SMTP);
		}

		if (bRet)
		{
			//End the message with a <CRLF>.<CRLF>
			nBufLen = sprintf_s(szBuf, _countof(szBuf), "\r\n.\r\n");
			if (!AtlSmtpSendAndCheck((HANDLE)m_hSocket, szBuf, nBufLen, 
					szBuf, &nBufLen, ATLSMTP_MAX_LINE_LENGTH, ATLSMTP_DATA_SUCC, &m_Overlapped))
			{
				return FALSE;
			}
		}

		return bRet;
	}

	// Send a chunk of raw data
	inline BOOL SendRaw(LPCTSTR lpszRawData, DWORD dwLen, LPCTSTR lpszRecipients, LPCTSTR lpszSender) throw()
	{
		ATLASSERT(lpszRawData != NULL);
		ATLASSERT(lpszRecipients != NULL);
		ATLASSERT(lpszSender != NULL);

		if (!Connected())
			return FALSE;

		char szBuf[ATLSMTP_MAX_LINE_LENGTH+1];

		//Send MAIL FROM command and get reply
		int nBufLen = sprintf_s(szBuf, ATLSMTP_MAX_LINE_LENGTH+1, 
			"MAIL FROM:<%s>\r\n", (LPCSTR) CT2CA(lpszSender));
		if ((nBufLen < 0) || 
			(!AtlSmtpSendAndCheck((HANDLE)m_hSocket, szBuf, nBufLen, 
				szBuf, &nBufLen, ATLSMTP_MAX_LINE_LENGTH, ATLSMTP_MAIL_SUCC, &m_Overlapped)))
		{
			return FALSE;
		}

		BOOL bRet = DumpRecipients((HANDLE)m_hSocket, CT2CA(lpszRecipients),
						&m_Overlapped, ATLSMTP_FOR_SEND);

		// Begin the data output
		if (bRet)
		{
			nBufLen = sprintf_s(szBuf, _countof(szBuf), "DATA\r\n");
			bRet = AtlSmtpSendAndCheck((HANDLE)m_hSocket, szBuf, nBufLen,
						szBuf, &nBufLen, ATLSMTP_MAX_LINE_LENGTH, ATLSMTP_DATA_INTM, &m_Overlapped);
		}

		if (!bRet)
			CancelMessage();

		if (bRet)
		{
			bRet = AtlSmtpSendAndWait((HANDLE)m_hSocket, (LPSTR)(lpszRawData), dwLen, &m_Overlapped);
		}

		if (bRet)
		{
			//End the message with a <CRLF>.<CRLF>
			nBufLen = sprintf_s(szBuf, _countof(szBuf), "\r\n.\r\n");
			if (!AtlSmtpSendAndCheck((HANDLE)m_hSocket, szBuf, nBufLen, 
					szBuf, &nBufLen, ATLSMTP_MAX_LINE_LENGTH, ATLSMTP_DATA_SUCC, &m_Overlapped))
			{
				return FALSE;
			}
		}

		return bRet;
	}

	inline BOOL SendSimple(LPCTSTR lpszRecipients, LPCTSTR lpszSender, LPCTSTR lpszSubject, LPCTSTR lpszBody, int nTextLen = -1) throw()
	{
		CMimeMessage msg;
		BOOL bRet = msg.SetSubject(lpszSubject);
		if (bRet)
			bRet = msg.AddText(lpszBody, nTextLen);

		CFixedStringT<CString, MAX_PATH> strRecip;
		LPCTSTR szTmp = lpszRecipients;
		LPCTSTR szTmp2 = lpszRecipients;
		while (*szTmp && bRet)
		{
			if (AtlSmtpIsRecipientDelimiter((char) *szTmp2))
			{
				_ATLTRY
				{
					strRecip.SetString(szTmp, (int)(szTmp2-szTmp));
					bRet = msg.AddRecipient((LPCTSTR) strRecip);

					if (*szTmp2)
					{
						while (*szTmp2 && AtlSmtpIsRecipientDelimiter((char) *szTmp2))
						{
							szTmp2++;
						}
					}
					szTmp = szTmp2;
				}
				_ATLCATCHALL()
				{
					bRet = FALSE;
				}
			}
			else
			{
				szTmp2++;
			}
		}

		if (bRet)
			bRet = SendMessage(msg, lpszRecipients, lpszSender);

		return bRet;
	}

	// Save a MIME message to a file
	// lpszFileName - the file name
	// lpszRecipients - the recipients string (optional)
	// lpszSender - the sender (optional)
	// dwFlags - the flags (optional)
	inline BOOL WriteToFile(LPCTSTR lpszFileName, CMimeMessage& msg, LPCTSTR lpszRecipients = NULL, 
		LPCTSTR lpszSender = NULL, DWORD dwFlags = 0) throw()
	{
		//Try to create/open the file
		HANDLE hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL,
			CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);

		if (hFile == INVALID_HANDLE_VALUE)
		{
			return FALSE;
		}

		// Use CHandle to close the file handle
		// (CAtlFile does not allow for overlapped I/O)
		CHandle hdlFile(hFile);

		//Create and initialize the OVERLAPPED struct
		OVERLAPPED writeOverlapped;
		memset((void*)&writeOverlapped, 0, sizeof(OVERLAPPED));
		writeOverlapped.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
		if (writeOverlapped.hEvent == NULL)
		{
			return FALSE;
		}

		// Use CHandle to close the event handle
		CHandle hdlEvent(writeOverlapped.hEvent);

		char szBuf[ATLSMTP_MAX_LINE_LENGTH+1];
		BOOL bRet = TRUE;

		int nBufLen = 0;

		//if writing to file for purposes of sending, write out the
		//commands as well
		if (lpszSender || (dwFlags & ATLSMTP_DUMP_SENDER))
		{
			nBufLen = sprintf_s(szBuf, ATLSMTP_MAX_LINE_LENGTH+1, "MAIL FROM:<%s>\r\n", 
				(lpszSender ? (LPCSTR) CT2CA(lpszSender) : msg.GetSender()));
			if (nBufLen > 0)
			{
				bRet = AtlSmtpSendAndWait(hFile, szBuf, nBufLen, &writeOverlapped);
			}
			else
			{
				bRet = FALSE;
			}
		}

		if (bRet && (lpszRecipients || (dwFlags & ATLSMTP_DUMP_RECIPS)))
		{
			if (!lpszRecipients)
			{
				LPSTR lpszRecipientsA = NULL;
				DWORD dwLen = msg.GetRequiredRecipientsStringLength();
				lpszRecipientsA = static_cast<LPSTR>(calloc(sizeof(char),dwLen));
				if (!lpszRecipientsA || msg.GetRecipientsString(lpszRecipientsA, &dwLen) == FALSE)
				{
					bRet = FALSE;
				}
				if (bRet)
					bRet = DumpRecipients(hFile, lpszRecipientsA, &writeOverlapped);
				free(lpszRecipientsA);
			}
			else
			{
				bRet = DumpRecipients(hFile, CT2CA(lpszRecipients), &writeOverlapped);
			}
		}

		if (bRet)
		{
			nBufLen = sprintf_s(szBuf, _countof(szBuf), "DATA\r\n");
			bRet = AtlSmtpSendAndWait(hFile, szBuf, nBufLen, &writeOverlapped);
		}

		if (bRet)
		{
			bRet = msg.WriteData(hFile, &writeOverlapped, NULL, ATLSMTP_FORMAT_SMTP);
		}

		return bRet;
	}

protected:

	// disallow copy construction
	CSMTPConnection(const CSMTPConnection&) throw()
	{
		ATLASSERT(FALSE);
	}

	// disallow assignment
	const CSMTPConnection& operator=(const CSMTPConnection&) throw()
	{
		ATLASSERT(FALSE);
		return *this;
	}

	// Tell the server we are aborting the message
	inline BOOL CancelMessage() throw()
	{
		char szBuf[ATLSMTP_MAX_LINE_LENGTH+1];
		int nBufLen  = 0;
		nBufLen = sprintf_s(szBuf, _countof(szBuf), "RSET\r\n");
		if (!AtlSmtpSendAndCheck((HANDLE)m_hSocket, szBuf, nBufLen, szBuf, &nBufLen, ATLSMTP_MAX_LINE_LENGTH, 
			ATLSMTP_RSET_SUCC, &m_Overlapped))
		{
			Disconnect();
			return FALSE;
		}
		return TRUE;
	}

	// Dump the recipients to hFile
	// lpszRecipients - the recipients string
	// pOverlapped - the OVERALAPPED struct
	// dwFlags - the flags
	inline BOOL DumpRecipients(HANDLE hFile, LPCSTR lpszRecipients, LPOVERLAPPED pOverlapped, DWORD dwFlags = 0)
	{
		ATLENSURE(lpszRecipients != NULL);
		ATLASSERT(pOverlapped != NULL);

		char  rcptBuf[ATLSMTP_MAX_LINE_LENGTH-12+1];
		char  szBuf[ATLSMTP_MAX_LINE_LENGTH+1];
		LPSTR tmpBuf = rcptBuf;
		char ch;
		BOOL bRet = TRUE;
		int nMaxLength = ATLSMTP_MAX_LINE_LENGTH;
		int nRetCode = 0;
		size_t nCnt = 0;
		do
		{
			ch = *lpszRecipients;
			if (ch)
				lpszRecipients++;
			if (AtlSmtpIsRecipientDelimiter(ch))
			{
				*tmpBuf = 0;
				int nBufLen = sprintf_s(szBuf, ATLSMTP_MAX_LINE_LENGTH, 
					"RCPT TO:<%s>\r\n", rcptBuf);
				if (nBufLen > 0)
				{
					bRet = AtlSmtpSendAndWait(hFile, szBuf, nBufLen, pOverlapped);
				}
				else
				{
					bRet = FALSE;
				}

				if (bRet && (dwFlags & ATLSMTP_FOR_SEND))
				{
					bRet = AtlSmtpReadData(hFile, szBuf, &nMaxLength, pOverlapped);
					nRetCode = atoi(szBuf);
					if (!bRet || (nRetCode != ATLSMTP_RCPT_SUCCESS && nRetCode != ATLSMTP_RCPT_NOT_LOCAL))
					{
						bRet = FALSE;
						break;
					}
				}
				tmpBuf = rcptBuf;
				nCnt = 0;
				nMaxLength = ATLSMTP_MAX_LINE_LENGTH;
				while (isspace(static_cast<unsigned char>(*lpszRecipients)))
					lpszRecipients++;
				continue;
			}

			if (nCnt >= sizeof(rcptBuf)-1)
			{
				// recipient string too long
				bRet = FALSE;
				break;
			}

			*tmpBuf++ = ch;
			nCnt++;
		} while (ch != '\0');

		return bRet;
	}

	// Implementation - used from ReadLine
	// fills pBuf with up to dwMaxLen bytes
	BOOL FillBuffer(__in HANDLE hFile, __out_ecount_part(dwMaxLen, *pdwLen) LPSTR pBuf, __in DWORD dwMaxLen, __out LPDWORD pdwLen) throw()
	{
		ATLASSERT(hFile != INVALID_HANDLE_VALUE);
		ATLASSERT(pdwLen != NULL);

		DWORD dwRead = 0;
		DWORD dwTotalRead = 0;
		int nRet = 0;

		do 
		{
			nRet = ReadFile(hFile, pBuf, dwMaxLen-dwTotalRead, &dwRead, NULL);
			if (!nRet && GetLastError() != ERROR_HANDLE_EOF)
			{
				return FALSE;
			}

			if (dwRead == 0)
				break;

			dwTotalRead+= dwRead;
		} while (dwTotalRead < dwMaxLen);

		*pdwLen = dwTotalRead;

		return TRUE;
	}

	// Implementation
	// Read a line (terminated by LF) from hFile
	BOOL ReadLine(__in HANDLE hFile, __out_ecount_part_z(dwMaxSrcLen, *pdwSrcLen) LPSTR pSrc, __out_ecount_part_z(dwMaxDestLen, *pdwRead) LPSTR pDest, __inout LPDWORD pdwSrcLen, __inout LPDWORD pdwBufPos, __in DWORD dwMaxSrcLen, 
			__in DWORD dwMaxDestLen, __out_opt LPDWORD pdwRead=NULL, __out_opt LPBOOL pbFullLine=NULL) 
	{
		ATLENSURE(hFile != INVALID_HANDLE_VALUE);
		ATLENSURE(pSrc != NULL);
		ATLENSURE(pDest != NULL);
		ATLENSURE(pdwSrcLen != NULL);
		ATLENSURE(pdwBufPos != NULL);

		BOOL bRet = TRUE;
		DWORD dwLen = 0;
		DWORD dwBufPos = 0;
		DWORD dwSrcLen = *pdwSrcLen;
		LPSTR pSrcCurrent = pSrc + *pdwBufPos;

		while (bRet && dwLen < dwMaxDestLen)
		{
			if (dwSrcLen == 0)
			{
				if (!FillBuffer(hFile, pSrc, dwMaxSrcLen, pdwSrcLen) || *pdwSrcLen == 0)
					break;

				dwBufPos = 0;
				*pdwBufPos = 0;
				dwSrcLen = *pdwSrcLen;
				pSrcCurrent = pSrc;
			}

			--dwSrcLen;
			*pDest = *pSrcCurrent++;
			dwLen++;
			dwBufPos++;
			if (*pDest == '\n')
			{
				break;
			}
			pDest++;
		}

		*pdwSrcLen = dwSrcLen;

		if (pbFullLine)
		{
			if (*pDest != '\n')
				*pbFullLine = FALSE;
			else
				*pbFullLine = TRUE;
		}

		if (pdwRead)
			*pdwRead = dwLen;

		*pdwBufPos += dwBufPos;

		return bRet;
	}

}; // class CSMTPConnection

} // namespace ATL
#pragma pack(pop)

#endif // __ATLSMTPCONNECTION_H__
