具有发送、验证、BASE64编码功能,已经修改调试完成
想要具体说明请发信 aaachii@263.net
// CSmtp.cpp: implementation of the CSmtp class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "CSmtp.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
// CMailMessage
// Formats a message compliant with RFC 822.
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CMailMessage::CMailMessage()
{
m_sMailerName = IDS_APPNAME;
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;
}
//////////////////////////////////////////////////////////////////////
// CMIMEContentAgent
//////////////////////////////////////////////////////////////////////
CMIMEContentAgent::CMIMEContentAgent(int nMIMEType)
{ m_nMIMETypeIHandle = nMIMEType; }
CMIMEContentAgent::~CMIMEContentAgent()
{}
BOOL CMIMEContentAgent::QueryType(int nContentType)
{ return nContentType == m_nMIMETypeIHandle ? TRUE : FALSE; }
//////////////////////////////////////////////////////////////////////
// CMIMECode
//////////////////////////////////////////////////////////////////////
CMIMECode::CMIMECode()
{
}
CMIMECode::~CMIMECode()
{
}
//////////////////////////////////////////////////////////////////////
// CBase64
//////////////////////////////////////////////////////////////////////
// Static Member Initializers
//
// The 7-bit alphabet used to encode binary information
CString CBase64::m_sBase64Alphabet =
_T( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" );
int CBase64::m_nMask[] = { 0, 1, 3, 7, 15, 31, 63, 127, 255 };
CBase64::CBase64()
{
}
CBase64::~CBase64()
{
}
CString CBase64::Encode(LPCTSTR szEncoding, int nSize)
{
CString sOutput = _T( "" );
int nNumBits = 6;
UINT nDigit;
int lp = 0;
ASSERT( szEncoding != NULL );
if( szEncoding == NULL )
return sOutput;
m_szInput = szEncoding;
m_nInputSize = nSize;
m_nBitsRemaining = 0;
nDigit = read_bits( nNumBits, &nNumBits, lp );
while( nNumBits > 0 )
{
sOutput += m_sBase64Alphabet[ (int)nDigit ];
nDigit = read_bits( nNumBits, &nNumBits, lp );
}
// Pad with '=' as per RFC 1521
while( sOutput.GetLength() % 4 != 0 )
{
sOutput += '=';
}
return sOutput;
}
// The size of the output buffer must not be less than
// 3/4 the size of the input buffer. For simplicity,
// make them the same size.
int CBase64::Decode(LPCTSTR szDecoding, LPTSTR szOutput)
{
CString sInput;
int c, lp =0;
int nDigit;
int nDecode[ 256 ];
ASSERT( szDecoding != NULL );
ASSERT( szOutput != NULL );
if( szOutput == NULL )
return 0;
if( szDecoding == NULL )
return 0;
sInput = szDecoding;
if( sInput.GetLength() == 0 )
return 0;
// Build Decode Table
//
for( int i = 0; i < 256; i++ )
nDecode[i] = -2; // Illegal digit
for( i=0; i < 64; i++ )
{
nDecode[ m_sBase64Alphabet[ i ] ] = i;
nDecode[ m_sBase64Alphabet[ i ] | 0x80 ] = i; // Ignore 8th bit
nDecode[ '=' ] = -1;
nDecode[ '=' | 0x80 ] = -1; // Ignore MIME padding char
}
// Clear the output buffer
memset( szOutput, 0, sInput.GetLength() + 1 );
// Decode the Input
//
for( lp = 0, i = 0; lp < sInput.GetLength(); lp++ )
{
c = sInput[ lp ];
nDigit = nDecode[ c & 0x7F ];
if( nDigit < -1 )
{
return 0;
}
else if( nDigit >= 0 )
// i (index into output) is incremented by write_bits()
write_bits( nDigit & 0x3F, 6, szOutput, i );
}
return i;
}
UINT CBase64::read_bits(int nNumBits, int * pBitsRead, int& lp)
{
ULONG lScratch;
while( ( m_nBitsRemaining < nNumBits ) &&
( lp < m_nInputSize ) )
{
int c = m_szInput[ lp++ ];
m_lBitStorage <<= 8;
m_lBitStorage |= (c & 0xff);
m_nBitsRemaining += 8;
}
if( m_nBitsRemaining < nNumBits )
{
lScratch = m_lBitStorage << ( nNumBits - m_nBitsRemaining );
*pBitsRead = m_nBitsRemaining;
m_nBitsRemaining = 0;
}
else
{
lScratch = m_lBitStorage >> ( m_nBitsRemaining - nNumBits );
*pBitsRead = nNumBits;
m_nBitsRemaining -= nNumBits;
}
return (UINT)lScratch & m_nMask[nNumBits];
}
void CBase64::write_bits(UINT nBits,
int nNumBits,
LPTSTR szOutput,
int& i)
{
UINT nScratch;
m_lBitStorage = (m_lBitStorage << nNumBits) | nBits;
m_nBitsRemaining += nNumBits;
while( m_nBitsRemaining > 7 )
{
nScratch = m_lBitStorage >> (m_nBitsRemaining - 8);
szOutput[ i++ ] = nScratch & 0xFF;
m_nBitsRemaining -= 8;
}
}
//////////////////////////////////////////////////////////////////////
// CAppOctetStream
//////////////////////////////////////////////////////////////////////
// IMPORTANT: The number of bytes we read must be
// a multiple of 3 because CBase64's Encode()
// method will append padding characters ('=')
// to make the output's size a multiple of 4.
// (Base64 treats 3 8-bit bytes as 4 6-bit 'bytes').
// MIME decoders are free to treat '=' as a signal
// that there's no more data, so we don't want to pad
// until we're supposed to.
// When at the end of the file, the # of bytes read
// may not be a multiple of 3, but that's okay
// because we DO want the padding chars then.
#define BYTES_TO_READ 54 // This number guarantess output won't
// won't exceed line-length limit
CAppOctetStream::CAppOctetStream(int nContentType)
:CMIMEContentAgent(nContentType)
{
}
CAppOctetStream::~CAppOctetStream()
{
}
BOOL CAppOctetStream::AppendPart(LPCTSTR szContent,
LPCTSTR szParameters,
int nEncoding,
BOOL bPath,
CString & sDestination)
{
CStdioFile fAttachment;
ASSERT(szContent != NULL);
// This class handles only file attachments, so
// it ignores the bPath parameter.
if(szContent == NULL)
return FALSE;
if(!fAttachment.Open(szContent, (CFile::modeRead|CFile::shareDenyWrite|CFile::typeBinary)))
return FALSE;
sDestination += build_sub_header(szContent,
szParameters,
nEncoding,
TRUE);
attach_file(&fAttachment, CMIMEMessage::BASE64, sDestination );
fAttachment.Close();
return TRUE;
}
CString CAppOctetStream::build_sub_header(LPCTSTR szContent,
LPCTSTR szParameters,
int nEncoding,
BOOL bPath)
{
CString sSubHeader;
CString sTemp;
TCHAR szFName[ _MAX_FNAME ];
TCHAR szExt[ _MAX_EXT ];
_tsplitpath( szContent, NULL, NULL, szFName, szExt );
// This class ignores szParameters and nEncoding.
// It controls its own parameters and only handles
// Base64 encoding.
if( bPath )
sTemp.Format( "; file=%s%s", szFName, szExt );
else
sTemp = _T( "" );
sSubHeader.Format( _T( "Content-Type: %s%s\r\n" ),
(LPCTSTR)GetContentTypeString(),
(LPCTSTR)sTemp );
sSubHeader += _T( "Content-Transfer-Encoding: base64\r\n" );
sTemp.Format( _T( "Content-Disposition: attachment; filename=%s%s\r\n" ),
szFName, szExt );
sSubHeader += sTemp;
// Signal end of sub-header.
sSubHeader += _T( "\r\n" ); // Warning: numerous concatenations
// are inefficient.
return sSubHeader;
}
CString CAppOctetStream::GetContentTypeString()
{
CString s;
s = _T( "application/octet-stream" );
return s;
}
// Caller is responsible for opening and closing the file
void CAppOctetStream::attach_file(CStdioFile* pFileAtt,
int nEncoding,
CString & sDestination)
{
CMIMECode* pEncoder;
int nBytesRead;
TCHAR szBuffer[ BYTES_TO_READ + 1 ];
ASSERT( pFileAtt != NULL );
if( pFileAtt == NULL )
return;
switch( nEncoding )
{
// This class handles only Base64 encoding, but others
// may be added here.
default:
// Fall through to...
case CMIMEMessage::BASE64:
try
{
pEncoder = new CBase64;
}
catch( CMemoryException* e )
{
delete e;
return;
}
}
if( pEncoder == NULL ) // Old habits are hard to break
return;
do
{
try
{
nBytesRead = pFileAtt->Read( szBuffer, BYTES_TO_READ );
}
catch( CFileException* e )
{
delete e;
break;
}
szBuffer[ nBytesRead ] = 0; // Terminate the string
sDestination += pEncoder->Encode( szBuffer, nBytesRead );
sDestination += _T( "\r\n" );
} while( nBytesRead == BYTES_TO_READ );
sDestination += _T( "\r\n" );
delete pEncoder;
}
//////////////////////////////////////////////////////////////////////
// CTextPlain
//////////////////////////////////////////////////////////////////////
CTextPlain::CTextPlain( int nContentType, UINT nWrapPos )
:CMIMEContentAgent( nContentType )
{
m_nWrapPos = nWrapPos;
}
CTextPlain::~CTextPlain()
{
}
CString CTextPlain::GetContentTypeString()
{
CString s;
s = _T( "text/html" );
return s;
}
BOOL CTextPlain::AppendPart(LPCTSTR szContent,
LPCTSTR szParameters,
int nEncoding,
BOOL bPath,
CString & sDestination)
{
CString sSubHeader;
CString sWrapped;
sSubHeader = build_sub_header( szContent,
szParameters,
nEncoding,
bPath );
sWrapped = wrap_text( szContent );
sDestination += (sSubHeader + sWrapped);
return TRUE;
}
CString CTextPlain::build_sub_header(LPCTSTR szContent,
LPCTSTR szParameters,
int nEncoding,
BOOL bPath)
{
CString sSubHeader;
sSubHeader.Format( _T( "Content-Type: %s%s\r\n" ),
(LPCTSTR)GetContentTypeString(),
szParameters );
sSubHeader += _T( "Content-Transfer-Encoding: " );
switch( nEncoding )
{
// This class handles only 7bit encoding, but others
// may be added here.
default:
case CMIMEMessage::_7BIT:
sSubHeader += _T( "7Bit" );
}
sSubHeader += _T( "\r\n\r\n" );
return sSubHeader;
}
CString CTextPlain::wrap_text(LPCTSTR szText)
{
CString sTemp;
CString sLeft;
CString sRight;
int lp = 0;
UINT nCount = 0;
int nSpacePos = 0;
ASSERT( szText != NULL );
if( szText == NULL )
return sTemp;
sTemp = szText;
while( lp < sTemp.GetLength() )
{
if( sTemp[ lp ] == ' ' )
nSpacePos = lp;
// Reset counter on newline
if( sTemp.Mid( lp, 2 ) == _T( "\r\n" ) )
nCount = 0;
// Wrap text at last found space
if( nCount > m_nWrapPos )
{
sLeft = sTemp.Left( nSpacePos );
sRight = sTemp.Right( sTemp.GetLength() - nSpacePos );
sLeft.TrimRight();
sRight.TrimLeft();
sLeft += _T( "\r\n" );
sTemp = sLeft + sRight;
nCount = 0;
}
else
nCount++;
lp++;
}
return sTemp;
}
//////////////////////////////////////////////////////////////////////
// CMIMEMessage
//////////////////////////////////////////////////////////////////////
// Static Member Initializers
CMIMEMessage::CMIMETypeManager CMIMEMessage::m_MIMETypeManager;
CMIMEMessage::CMIMEMessage()
{
m_sMIMEContentType = _T( "multipart/mixed");
m_sPartBoundary = _T( "WC_MAIL_PaRt_BoUnDaRy_05151998" );
m_sNoMIMEText = _T( "This is a multi-part message in MIME format." );
// Register the MIME types handled by this class
//
CMIMEContentAgent* pType;
// These objects are deleted by CMIMTypeManager's destructor
pType = new CTextPlain( TEXT_PLAIN, GetCharsPerLine() );
register_mime_type( pType );
pType = new CAppOctetStream( APPLICATION_OCTETSTREAM );
register_mime_type( pType );
}
CMIMEMessage::~CMIMEMessage()
{
}
// This implementation adds the part to the part-list used
// to build the body.
BOOL CMIMEMessage::AddMIMEPart(LPCTSTR szContent,
int nContentType,
LPCTSTR szParameters,
int nEncoding,
BOOL bPath )
{
CMIMEPart part;
part.m_nContentType = nContentType;
part.m_sParameters = szParameters;
part.m_nEncoding = nEncoding;
part.m_bPath = bPath;
part.m_sContent = szContent;
part.m_sContent.TrimLeft();
part.m_sContent.TrimRight();
if( nContentType == TEXT_PLAIN )
m_MIMEPartList.AddHead( part );
else
m_MIMEPartList.AddTail( part );
return TRUE;
}
void CMIMEMessage::prepare_header()
{
CString sTemp;
// Let the base class add its headers
CMailMessage::prepare_header();
add_header_line( _T( "MIME-Version: 1.0" ) );
sTemp.Format( _T( "Content-Type: %s; boundary=%s" ),
(LPCTSTR)m_sMIMEContentType,
(LPCTSTR)m_sPartBoundary );
add_header_line( (LPCTSTR)sTemp );
}
void CMIMEMessage::prepare_body()
{
// Class user may have assigned body text directly.
// Convert it to just another MIME part to be processed.
// If this default Content-Type isn't good enough for the
// class user, he or she should have used AddMIMEPart() instead.
if( m_sBody != _T( "" ) )
AddMIMEPart( (LPCTSTR)m_sBody, TEXT_PLAIN, "", _7BIT, FALSE );
// Initialize the body (replace current contents).
m_sBody = m_sNoMIMEText;
m_sBody += _T( "\r\n\r\n" );
append_mime_parts();
insert_message_end( m_sBody );
// Let the base class take me to Funky Town
CMailMessage::prepare_body();
}
void CMIMEMessage::insert_boundary( CString& sText )
{
CString sTemp;
if( sText.Right( 2 ) != _T( "\r\n" ) )
sText += _T( "\r\n" );
sTemp.Format( _T( "--%s\r\n" ), (LPCTSTR)m_sPartBoundary );
sText += sTemp;
}
void CMIMEMessage::insert_message_end( CString& sText )
{
CString sTemp;
if( sText.Right( 2 ) != _T( "\r\n" ) )
sText += _T( "\r\n" );
sTemp.Format( _T( "--%s--\r\n" ), (LPCTSTR)m_sPartBoundary );
sText += sTemp;
}
void CMIMEMessage::register_mime_type(CMIMEContentAgent* pMIMEType)
{
ASSERT( pMIMEType != NULL );
if( pMIMEType == NULL )
return;
m_MIMETypeManager.RegisterMIMEType( pMIMEType );
}
void CMIMEMessage::append_mime_parts()
{
POSITION part_position;
CMIMEPart* pMIMEPart = NULL;
CMIMEContentAgent* pMIMEType = NULL;
part_position = m_MIMEPartList.GetHeadPosition();
// Get each part from the list, retrieve a handler for it,
// and let the handler do its thing.
while( part_position != NULL )
{
pMIMEPart = & m_MIMEPartList.GetNext( part_position );
pMIMEType = m_MIMETypeManager.GetHandler( pMIMEPart->m_nContentType );
if( pMIMEType != NULL )
{
insert_boundary( m_sBody );
pMIMEType->AppendPart( pMIMEPart->m_sContent,
pMIMEPart->m_sParameters,
pMIMEPart->m_nEncoding,
pMIMEPart->m_bPath,
m_sBody );
}
}
}
//////////////////////////////////////////////////////////////////////
// CMIMETypeManager Implementation
//////////////////////////////////////////////////////////////////////
CMIMEMessage::CMIMETypeManager::CMIMETypeManager()
{
}
CMIMEMessage::CMIMETypeManager::~CMIMETypeManager()
{
POSITION pos;
CMIMEContentAgent* p;
m_csAccess.Lock();
pos = m_MIMETypeList.GetHeadPosition();
while( pos != NULL )
{
p = m_MIMETypeList.GetNext( pos );
delete p;
}
}
void CMIMEMessage::CMIMETypeManager::RegisterMIMEType(CMIMEContentAgent *pMIMEType)
{
ASSERT( pMIMEType != NULL );
if( pMIMEType == NULL )
return;
m_csAccess.Lock();
m_MIMETypeList.AddTail( pMIMEType );
}
CMIMEContentAgent* CMIMEMessage::CMIMETypeManager::GetHandler(int nContentType)
{
POSITION pos;
CMIMEContentAgent* pType = NULL;
m_csAccess.Lock();
pos = m_MIMETypeList.GetHeadPosition();
while( pos != NULL )
{
pType = m_MIMETypeList.GetNext( pos );
if( pType->QueryType( nContentType ) == TRUE )
break;
}
return pType;
}
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
// 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")},
// AUTHQUE_SUCCESS
{334, _T("SMTP server authentication error")},
// AUTH_SUCCESS
{235, _T("Error username or password")},
// 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::Auth()
{
CString sAuth;
if(!m_bConnected) return FALSE;
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;
}
sAuth.Format(_T( "auth login\r\n" )); file://construct auth quest
m_wsSMTPServer.Send((LPCTSTR)sAuth, sAuth.GetLength());
if(!get_response(AUTHQUE_SUCCESS)) {
m_sError="SMTP server with no auth";
m_bAuthed=TRUE;
return TRUE;
}
sAuth.Empty();
sAuth.Format(_T( "%s\r\n" ), m_sSMTPUser); file://m_sSMTPUser is an string encoded with CBASE64
m_wsSMTPServer.Send((LPCTSTR)sAuth, sAuth.GetLength());
if(!get_response(AUTHQUE_SUCCESS)) {
m_sError="Unknown Error";
m_wsSMTPServer.Close();
delete response_buf;
response_buf = NULL;
return FALSE;
}
sAuth.Empty();
sAuth.Format(_T( "%s\r\n" ), m_sSMTPPass); file://m_sSMTPPass is an string encoded with CBASE64
m_wsSMTPServer.Send((LPCTSTR)sAuth, sAuth.GetLength());
if(!get_response(AUTH_SUCCESS)) {
m_wsSMTPServer.Close();
delete response_buf;
response_buf = NULL;
return FALSE;
}
m_bAuthed = 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(!m_bAuthed) {
m_sError = _T("Must be authed");
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;
}
if(!m_bAuthed) {
m_sError = _T("Must be authed");
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;
}
// CSmtp.h: interface for the CSmtp class.
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_CSMTP_H__A0D4A072_65DE_11D2_9816_9523BDBAF506__INCLUDED_)
#define AFX_CSMTP_H__A0D4A072_65DE_11D2_9816_9523BDBAF506__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
//////////////////////////////////////////////////////////////////////
// CMailMessage
// Formats a message compliant with RFC 822
#include <afxtempl.h>
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;
CString m_sUser;
CString m_sPass;
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);
};
// CMIMEContentAgent
// Abstract base class. Content agents support MIME
// content types on behalf of CMIMEMessage
//
class CMIMEContentAgent
{
public:
CMIMEContentAgent(int nMIMEType);
virtual ~CMIMEContentAgent();
BOOL QueryType(int nContentType);
virtual BOOL AppendPart(LPCTSTR szContent,
LPCTSTR szParameters,
int nEncoding,
BOOL bPath,
CString& sDestination) = 0;
virtual CString GetContentTypeString() = 0;
protected:
virtual CString build_sub_header(LPCTSTR szContent,
LPCTSTR szParameters,
int nEncoding,
BOOL bPath) = 0;
private:
int m_nMIMETypeIHandle;
};
//////////////////////////////////////////////////////////////////////
// CMIMECode
class CMIMECode
{
public:
CMIMECode();
virtual ~CMIMECode();
virtual int Decode( LPCTSTR szDecoding, LPTSTR szOutput ) = 0;
virtual CString Encode( LPCTSTR szEncoding, int nSize ) = 0;
};
//////////////////////////////////////////////////////////////////////
// CBase64
// An encoding agent that handles Base64
class CBase64 : public CMIMECode
{
public:
CBase64();
virtual ~CBase64();
// Override the base class mandatory functions
virtual int Decode( LPCTSTR szDecoding, LPTSTR szOutput );
virtual CString Encode( LPCTSTR szEncoding, int nSize );
protected:
void write_bits( UINT nBits, int nNumBts, LPTSTR szOutput, int& lp );
UINT read_bits( int nNumBits, int* pBitsRead, int& lp );
int m_nInputSize;
int m_nBitsRemaining;
ULONG m_lBitStorage;
LPCTSTR m_szInput;
static int m_nMask[];
static CString m_sBase64Alphabet;
private:
};
//////////////////////////////////////////////////////////////////////
// CAppOctetStream
class CAppOctetStream : public CMIMEContentAgent
{
public:
virtual CString GetContentTypeString();
CAppOctetStream( int nContentType );
virtual ~CAppOctetStream();
virtual BOOL AppendPart( LPCTSTR szContent,
LPCTSTR szParameters,
int nEncoding,
BOOL bPath,
CString& sDestination );
protected:
virtual void attach_file( CStdioFile* pFileAtt, int nEncoding, CString& sDestination );
virtual CString build_sub_header( LPCTSTR szContent,
LPCTSTR szParameters,
int nEncoding,
BOOL bPath );
};
// CTextPlain
// A MIME content agent that handles the "text/plain"
// content type
class CTextPlain : public CMIMEContentAgent
{
public:
CTextPlain(int nContentType, UINT nWrapPos = 72);
virtual ~CTextPlain();
virtual BOOL AppendPart(LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath, CString& sDestination);
virtual CString GetContentTypeString();
protected:
UINT m_nWrapPos;
CString wrap_text(LPCTSTR szText);
virtual CString build_sub_header(LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath);
};
#include <afxmt.h>
// CMIMEMessage
// Formats a message using the MIME specification.
class CMIMEMessage : public CMailMessage
{
public:
CMIMEMessage();
virtual ~CMIMEMessage();
// MIME Type Codes
enum eMIMETypeCode {
TEXT_PLAIN = 0,
APPLICATION_OCTETSTREAM,
NEXT_FREE_MIME_CODE
};
enum eMIMEEncodingCode {
_7BIT = 0,
_8BIT,
BINARY,
QUOTED_PRINTABLE,
BASE64,
NEXT_FREE_ENCODING_CODE
};
BOOL AddMIMEPart(LPCTSTR szContent,
int nContentType = APPLICATION_OCTETSTREAM,
LPCTSTR szParameters = _T(""),
int nEncoding = BASE64,
BOOL bPath = TRUE);
protected:
void insert_message_end(CString& sText);
void register_mime_type(CMIMEContentAgent* pMIMEType);
void insert_boundary(CString& sText);
virtual void append_mime_parts();
virtual void prepare_header();
virtual void prepare_body();
CString m_sNoMIMEText;
CString m_sPartBoundary;
CString m_sMIMEContentType;
private:
class CMIMEPart
{
public:
int m_nEncoding;
int m_nContentType;
CString m_sParameters;
BOOL m_bPath;
CString m_sContent;
};
CList <CMIMEPart, CMIMEPart&> m_MIMEPartList;
class CMIMETypeManager
{
public:
CMIMEContentAgent* GetHandler(int nContentType);
void RegisterMIMEType(CMIMEContentAgent* pMIMEType);
virtual ~CMIMETypeManager();
CMIMETypeManager();
private:
CCriticalSection m_csAccess;
CList <CMIMEContentAgent*, CMIMEContentAgent*> m_MIMETypeList;
};
static CMIMETypeManager m_MIMETypeManager;
};
//////////////////////////////////////////////////////////////////////
// CSmtp
// Main class for SMTP
#include <afxsock.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;
BOOL m_bAuthed;
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[];
};
#endif // !defined(AFX_CSMTP_H__A0D4A072_65DE_11D2_9816_9523BDBAF506__INCLUDED_)