#include <afxsock.h>
#include "MailMessage.h"
#define SMTP_PORT 25 // Standard port for SMTP servers
#define RESPONSE_BUFFER_SIZE 1024
class CSMTP
{
public:
CSMTP( LPCTSTR szSMTPServerName,UINT nPort = SMTP_PORT );
virtual ~CSMTP();
void SetServerProperties( LPCTSTR szSMTPServerName,UINT nPort = SMTP_PORT );
CString GetLastError();
UINT GetPort();
BOOL Disconnect();
BOOL Connect();
virtual BOOL FormatMailMessage( CMailMessage* msg );
BOOL SendMessage( CMailMessage* msg );
CString GetServerHostName();
private:
BOOL get_response( UINT response_expected );
CString cook_body( CMailMessage* msg );
CString m_sError;
BOOL m_bConnected;
UINT m_nPort;
CString m_sSMTPServerHostName;
CSocket m_wsSMTPServer;
protected:
virtual BOOL transmit_message( CMailMessage* msg );
//
// Helper Code
//
struct response_code
{
UINT nResponse; // Response we're looking for
TCHAR* sMessage; // Error message if we don't get it
};
enum eResponse
{
GENERIC_SUCCESS = 0,
CONNECT_SUCCESS,
DATA_SUCCESS,
QUIT_SUCCESS,
// Include any others here
LAST_RESPONSE // Do not add entries past this one
};
TCHAR *response_buf;
static response_code response_table[];
};
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
// Static member initializers
//
// Note,the order of the entries is important.
// They must be synchronized with eResponse entries,
CSMTP::response_code CSMTP::response_table[] =
{
// GENERIC_SUCCESS
{ 250,_T( "SMTP server error" ) },
// CONNECT_SUCCESS
{ 220,_T( "SMTP server not available" ) },
// DATA_SUCCESS
{ 354,_T( "SMTP server not ready for data" ) },
// QUIT_SUCCESS
{ 221,_T( "SMTP server didn't terminate session" ) }
};
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CSMTP::CSMTP( LPCTSTR szSMTPServerName,UINT nPort )
{
ASSERT( szSMTPServerName != NULL );
AfxSocketInit();
m_sSMTPServerHostName = szSMTPServerName;
m_nPort = nPort;
m_bConnected = FALSE;
m_sError = _T( "OK" );
response_buf = NULL;
}
CSMTP::~CSMTP()
{
Disconnect();
}
CString CSMTP::GetServerHostName()
{
return m_sSMTPServerHostName;
}
BOOL CSMTP::Connect()
{
CString sHello;
TCHAR local_host[ 80 ]; // Warning,arbitrary size
if( m_bConnected )
return TRUE;

try
{
// This will be deleted in Disconnect();
response_buf = new TCHAR[ RESPONSE_BUFFER_SIZE ];
// I can't count on all class users' applications
// to have exception-throwing operator-new implementations,
// so I'll soul-kiss the ones that don't.
if( response_buf == NULL )
{
m_sError = _T( "Not enough memory" );
return FALSE;
}
}
catch( CException *e )
{
response_buf = NULL;
m_sError = _T( "Not enough memory" );
delete e;
return FALSE;
}
if( !m_wsSMTPServer.Create() )
{
m_sError = _T( "Unable to create the socket." );
delete response_buf;
response_buf = NULL;
return FALSE;
}
if( !m_wsSMTPServer.Connect( GetServerHostName(),GetPort() ) )
{
m_sError = _T( "Unable to connect to server" );
m_wsSMTPServer.Close();
delete response_buf;
response_buf = NULL;
return FALSE;
}
if( !get_response( CONNECT_SUCCESS ) )
{
m_sError = _T( "Server didn't respond." );
m_wsSMTPServer.Close();
delete response_buf;
response_buf = NULL;
return FALSE;
}
gethostname( local_host,80 );
sHello.Format( _T( "HELO %s\r\n" ),local_host );
m_wsSMTPServer.Send( (LPCTSTR)sHello,sHello.GetLength() );
if( !get_response( GENERIC_SUCCESS ) )
{
m_wsSMTPServer.Close();
delete response_buf;
response_buf = NULL;
return FALSE;
}
m_bConnected = TRUE;
return TRUE;
}
BOOL CSMTP::Disconnect()
{
BOOL ret;
if( !m_bConnected )
return TRUE;
// Disconnect gracefully from the server and close the socket
CString sQuit = _T( "QUIT\r\n" );
m_wsSMTPServer.Send( (LPCTSTR)sQuit,sQuit.GetLength() );
// No need to check return value here.
// If it fails,the message is available with GetLastError
ret = get_response( QUIT_SUCCESS );
m_wsSMTPServer.Close();
if( response_buf != NULL )
{
delete[] response_buf;
response_buf = NULL;
}
m_bConnected = FALSE;
return ret;
}
UINT CSMTP::GetPort()
{
return m_nPort;
}
CString CSMTP::GetLastError()
{
return m_sError;
}
BOOL CSMTP::SendMessage(CMailMessage * msg)
{
ASSERT( msg != NULL );
if( !m_bConnected )
{
m_sError = _T( "Must be connected" );
return FALSE;
}
if( FormatMailMessage( msg ) == FALSE )
{
return FALSE;
}
if( transmit_message( msg ) == FALSE )
{
return FALSE;
}
return TRUE;
}
BOOL CSMTP::FormatMailMessage( CMailMessage* msg )
{
ASSERT( msg != NULL );
if( msg->GetNumRecipients() == 0 )
{
m_sError = _T( "No Recipients" );
return FALSE;
}
msg->FormatMessage();
return TRUE;
}
void CSMTP::SetServerProperties( LPCTSTR szSMTPServerName,UINT nPort)
{
ASSERT( szSMTPServerName != NULL );
// Needs to be safe in non-debug too
if( szSMTPServerName == NULL )
return;
m_sSMTPServerHostName = szSMTPServerName;
m_nPort = nPort;
}
CString CSMTP::cook_body(CMailMessage * msg)
{
ASSERT( msg != NULL );
CString sTemp;
CString sCooked = _T( "" );
LPTSTR szBad = _T( "\r\n.\r\n" );
LPTSTR szGood = _T( "\r\n..\r\n" );
int nPos;
int nStart = 0;
int nBadLength = strlen( szBad );
sTemp = msg->m_sBody;
if( sTemp.Left( 3 ) == _T( ".\r\n" ) )
sTemp = _T( "." ) + sTemp;
//
// This is a little inefficient because it beings a search
// at the beginning of the string each time,This was
// the only thing I could think of that handled ALL variations.
// In particular,the sequence "\r\n.\r\n.\r\n" is troublesome,
// (Even CStringEx's FindReplace wouldn't handle that situation
// with the global flag set.)
//
while( (nPos = sTemp.Find( szBad )) > -1 )
{
sCooked = sTemp.Mid( nStart,nPos );
sCooked += szGood;
sTemp = sCooked + sTemp.Right( sTemp.GetLength() - (nPos + nBadLength) );
}
return sTemp;
}
BOOL CSMTP::transmit_message(CMailMessage * msg)
{
CString sFrom;
CString sTo;
CString sTemp;
CString sEmail;
ASSERT( msg != NULL );
if( !m_bConnected )
{
m_sError = _T( "Must be connected" );
return FALSE;
}
// Send the MAIL command
//
sFrom.Format( _T( "MAIL From,<%s>\r\n" ),(LPCTSTR)msg->m_sFrom );
m_wsSMTPServer.Send( (LPCTSTR)sFrom,sFrom.GetLength() );
if( !get_response( GENERIC_SUCCESS ) )
return FALSE;

// Send RCPT commands (one for each recipient)
//
for( int i = 0; i < msg->GetNumRecipients(); i++ )
{
msg->GetRecipient( sEmail,sTemp,i );
sTo.Format( _T( "RCPT TO,<%s>\r\n" ),(LPCTSTR)sEmail );
m_wsSMTPServer.Send( (LPCTSTR)sTo,sTo.GetLength() );
get_response( GENERIC_SUCCESS );
}
// Send the DATA command
sTemp = _T( "DATA\r\n" );
m_wsSMTPServer.Send( (LPCTSTR)sTemp,sTemp.GetLength() );
if( !get_response( DATA_SUCCESS ) )
{
return FALSE;
}
// Send the header
//
m_wsSMTPServer.Send( (LPCTSTR)msg->m_sHeader,msg->m_sHeader.GetLength() );
// Send the body
//
sTemp = cook_body( msg );
m_wsSMTPServer.Send( (LPCTSTR)sTemp,sTemp.GetLength() );
// Signal end of data
//
sTemp = _T( "\r\n.\r\n" );
m_wsSMTPServer.Send( (LPCTSTR)sTemp,sTemp.GetLength() );
if( !get_response( GENERIC_SUCCESS ) )
{
return FALSE;
}
return TRUE;
}
BOOL CSMTP::get_response( UINT response_expected )
{
ASSERT( response_expected >= GENERIC_SUCCESS );
ASSERT( response_expected < LAST_RESPONSE );
CString sResponse;
UINT response;
response_code* pResp; // Shorthand
if( m_wsSMTPServer.Receive( response_buf,RESPONSE_BUFFER_SIZE ) == SOCKET_ERROR )
{
m_sError = _T( "Socket Error" );
return FALSE;
}
sResponse = response_buf;
sscanf( (LPCTSTR)sResponse.Left( 3 ),_T( "%d" ),&response );
pResp = &response_table[ response_expected ];
if( response != pResp->nResponse )
{
m_sError.Format( _T( "%d:%s" ),response,(LPCTSTR)pResp->sMessage );
return FALSE;
}
return TRUE;
}
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
#include <afxtempl.h>
// CMailMessage
// Formats a message compliant with RFC 822.
//
class CMailMessage
{
public:
CMailMessage();
virtual ~CMailMessage();
void FormatMessage();
int GetNumRecipients();
BOOL GetRecipient( CString& sEmailAddress,CString& sFriendlyName,int nIndex = 0 );
BOOL AddRecipient( LPCTSTR szEmailAddress,LPCTSTR szFriendlyName = "" );
BOOL AddMultipleRecipients( LPCTSTR szRecipients = NULL );
UINT GetCharsPerLine();
void SetCharsPerLine( UINT nCharsPerLine );
CString m_sFrom;
CString m_sSubject;
CString m_sEnvelope;
CString m_sMailerName;
CString m_sHeader;
CTime m_tDateTime;
CString m_sBody;
private:
UINT m_nCharsPerLine;
class CRecipient
{
public:
CString m_sEmailAddress;
CString m_sFriendlyName;
};
CArray <CRecipient,CRecipient&> m_Recipients;
protected:
// When overriding prepare_header(),call base class
// version first,then add specialized
// add_header_line calls.
// This ensures that the base class has a chance to
// create the header lines it needs.
virtual void prepare_header();
virtual void prepare_body();
virtual void end_header();
virtual void start_header();
// This rarely needs overwriting,but is virtual just in case.
// Do not include the trailing CR/LF in parameter.
virtual void add_header_line( LPCTSTR szHeaderLine );
};
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CMailMessage::CMailMessage()
{
m_sMailerName = _T( "WC Mail" );
SetCharsPerLine( 76 );
}
CMailMessage::~CMailMessage()
{
}
BOOL CMailMessage::AddRecipient( LPCTSTR szEmailAddress,LPCTSTR szFriendlyName)
{
ASSERT( szEmailAddress != NULL );
ASSERT( szFriendlyName != NULL );
CRecipient to;
to.m_sEmailAddress = szEmailAddress;
to.m_sFriendlyName = szFriendlyName;
m_Recipients.Add( to );
return TRUE;
}
// sEmailAddress and sFriendlyName are OUTPUT parameters.
// If the function fails,it will return FALSE,and the OUTPUT
// parameters will not be touched.
BOOL CMailMessage::GetRecipient(CString & sEmailAddress,CString & sFriendlyName,int nIndex)
{
CRecipient to;
if( nIndex < 0 || nIndex > m_Recipients.GetUpperBound() )
return FALSE;
to = m_Recipients[ nIndex ];
sEmailAddress = to.m_sEmailAddress;
sFriendlyName = to.m_sFriendlyName;
return TRUE;
}
int CMailMessage::GetNumRecipients()
{
return m_Recipients.GetSize();
}
BOOL CMailMessage::AddMultipleRecipients(LPCTSTR szRecipients )
{
TCHAR* buf;
UINT pos;
UINT start;
CString sTemp;
CString sEmail;
CString sFriendly;
UINT length;
int nMark;
int nMark2;
ASSERT( szRecipients != NULL );

// Add Recipients
//
length = strlen( szRecipients );
buf = new TCHAR[ length + 1 ]; // Allocate a work area (don't touch parameter itself)
strcpy( buf,szRecipients );
for( pos = 0,start = 0; pos <= length; pos++ )
{
if( buf[ pos ] == ';' ||
buf[ pos ] == 0 )
{
// First,pick apart the sub-strings (separated by ';')
// Store it in sTemp.
//
buf[ pos ] = 0; // Redundant when at the end of string,but who cares.
sTemp = &buf[ start ];
// Now divide the substring into friendly names and e-mail addresses.
//
nMark = sTemp.Find( '<' );
if( nMark >= 0 )
{
sFriendly = sTemp.Left( nMark );
nMark2 = sTemp.Find( '>' );
if( nMark2 < nMark )
{
delete[] buf;
return FALSE;
}
// End of mark at closing bracket or end of string
nMark2 > -1? nMark2 = nMark2,nMark2 = sTemp.GetLength() - 1;
sEmail = sTemp.Mid( nMark + 1,nMark2 - (nMark + 1) );
}
else
{
sEmail = sTemp;
sFriendly = _T( "" );
}
AddRecipient( sEmail,sFriendly );
start = pos + 1;
}
}
delete[] buf;
return TRUE;
}
void CMailMessage::FormatMessage()
{
start_header();
prepare_header();
end_header();
prepare_body();
}
void CMailMessage::SetCharsPerLine(UINT nCharsPerLine)
{
m_nCharsPerLine = nCharsPerLine;
}
UINT CMailMessage::GetCharsPerLine()
{
return m_nCharsPerLine;
}
// Create header as per RFC 822
//
void CMailMessage::prepare_header()
{
CString sTemp;
sTemp = _T( "" );
// From:
sTemp = _T( "From," ) + m_sFrom;
add_header_line( (LPCTSTR)sTemp );
// To:
sTemp = _T( "To," );
CString sEmail = _T( "" );
CString sFriendly = _T( "" );
for( int i = 0; i < GetNumRecipients(); i++ )
{
GetRecipient( sEmail,sFriendly,i );
sTemp += ( i > 0? _T( "," ),_T( "" ) );
sTemp += sFriendly;
sTemp += _T( "<" );
sTemp += sEmail;
sTemp += _T( ">" );
}
add_header_line( (LPCTSTR)sTemp );
// Date:
m_tDateTime = m_tDateTime.GetCurrentTime();
// Format,Mon,01 Jun 98 01:10:30 GMT
sTemp = _T( "Date," );
sTemp += m_tDateTime.Format( "%a,%d %b %y %H:%M:%S %Z" );
add_header_line( (LPCTSTR)sTemp );
// Subject:
sTemp = _T( "Subject," ) + m_sSubject;
add_header_line( (LPCTSTR)sTemp );
// X-Mailer
sTemp = _T( "X-Mailer," ) + m_sMailerName;
add_header_line( (LPCTSTR)sTemp );
}
void CMailMessage::prepare_body()
{
// Append a CR/LF to body if necessary.
if( m_sBody.Right( 2 ) != _T( "\r\n" ) )
m_sBody += _T( "\r\n" );
}
void CMailMessage::start_header()
{
m_sHeader = _T( "" );
}
void CMailMessage::end_header()
{
m_sHeader += _T( "\r\n" );
}
void CMailMessage::add_header_line(LPCTSTR szHeaderLine)
{
CString sTemp;
sTemp.Format( _T( "%s\r\n" ),szHeaderLine );
m_sHeader += sTemp;
}