1469 lines
43 KiB
C++
1469 lines
43 KiB
C++
|
//////////////////////////////////////////////////////////////////////
|
||
|
/*
|
||
|
Smtp.cpp: implementation of the CSmtp and CSmtpMessage classes
|
||
|
|
||
|
Written by Robert Simpson (robert@blackcastlesoft.com)
|
||
|
Created 11/1/2000
|
||
|
Version 1.7 -- Last Modified 06/18/2001
|
||
|
|
||
|
1.7 - Modified the code that gets the GMT offset and the code that
|
||
|
parses the date/time as per Noa Karsten's suggestions on
|
||
|
codeguru.
|
||
|
- Added an FD_ZERO(&set) to the last part of SendCmd(), since
|
||
|
I use the set twice and only zero it out once. Submitted by
|
||
|
Marc Allen.
|
||
|
- Removed the requirement that a message have a body and/or an
|
||
|
attachment. This allows for sending messages with only a
|
||
|
subject line. Submitted by Marc Allen.
|
||
|
1.6 - Apparently older versions of the STL do not have the clear()
|
||
|
method for basic_string's. I modified the code to use
|
||
|
erase() instead.
|
||
|
- Added #include <atlbase.h> to the smtp.h file, which will
|
||
|
allow any app to use these classes without problems.
|
||
|
1.5 - Guess I should have checked EncodeQuotedPrintable() as well,
|
||
|
since it did the same thing BreakMessage() did in adding an
|
||
|
extranneous CRLF to the end of any text it processed. Fixed.
|
||
|
1.4 - BreakMesage() added an extranneous CRLF to the end of any
|
||
|
text it processed, which is now fixed. Certainly not a big
|
||
|
deal, but it caused text attachments to not be 100% identical
|
||
|
to the original.
|
||
|
1.3 - Added a new class, CSmtpMimePart, to which the CSmtpAttachment
|
||
|
and CSmtpMessageBody classes inherit. This was done for
|
||
|
future expansion. CSmtpMimePart has a new ContentId string
|
||
|
value for optionally assigning a unique content ID value to
|
||
|
body parts and attachments. This was done to support the
|
||
|
multipart/related enhancement
|
||
|
- Support for multipart/related messages, which can be used
|
||
|
for sending html messages with embedded images.
|
||
|
- Modifed CSmtpMessage, adding a new MimeType member variable
|
||
|
so the user can specify a certain type of MIME format to use
|
||
|
when coding the message.
|
||
|
- Fixed a bug where multipart/alternative messages with multiple
|
||
|
message bodies were not properly processed when attachments
|
||
|
were also included in the message.
|
||
|
- Some small optimizations during the CSmtpMessage::Parse routine
|
||
|
|
||
|
1.2 - Vastly improved the time it takes to break a message,
|
||
|
which was dog slow with large attachments. My bad.
|
||
|
- Added another overridable, SmtpProgress() which is
|
||
|
called during the CSmtp::SendCmd() function when there
|
||
|
is a large quantity of data being sent over the wire.
|
||
|
Added CMD_BLOCK_SIZE to support the above new feature
|
||
|
- Added support for UNICODE
|
||
|
- Added the CSmtpAttachment class for better control and
|
||
|
expandability for attachments.
|
||
|
- Added alternative implementations for CSmtp::SendMessage
|
||
|
which make it easier to send simple messages via a single
|
||
|
function call.
|
||
|
- Added a constructor to CSmtpAddress for assigning default
|
||
|
values during initialization.
|
||
|
- Added a #pragma comment(lib,"wsock32.lib") to the smtp.h
|
||
|
file so existing projects don't have to have their linker
|
||
|
options modified.
|
||
|
|
||
|
1.1 - Rearranged the headers so they are written out as:
|
||
|
From,To,Subject,Date,MimeVersion,
|
||
|
followed by all remaining headers
|
||
|
- Modified the class to support multipart/alternative with
|
||
|
multiple message bodies.
|
||
|
|
||
|
Note that CSimpleMap does not sort the key values, and CSmtp
|
||
|
takes advantage of this by writing the headers out in the reverse
|
||
|
order of how they will be parsed before being sent to the SMTP
|
||
|
server. If you modify the code to use std::map or any other map
|
||
|
class, the headers may be alphabetized by key, which may cause
|
||
|
some mail clients to show the headers in the body of the message
|
||
|
or cause other undesirable results when viewing the message.
|
||
|
*/
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
#include "Smtp.h"
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
// Construction/Destruction for CSmtpMessageBody
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
CSmtpMessageBody::CSmtpMessageBody(LPCTSTR pszBody, LPCTSTR pszEncoding, LPCTSTR pszCharset, EncodingEnum encode)
|
||
|
{
|
||
|
|
||
|
// Set the default message encoding method
|
||
|
// To transfer html messages, make Encoding = _T("text/html")
|
||
|
if (pszEncoding) Encoding = pszEncoding;
|
||
|
if (pszCharset) Charset = pszCharset;
|
||
|
if (pszBody) Data = pszBody;
|
||
|
TransferEncoding = encode;
|
||
|
}
|
||
|
|
||
|
const CSmtpMessageBody& CSmtpMessageBody::operator=(LPCTSTR pszBody)
|
||
|
{
|
||
|
Data = pszBody;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
const CSmtpMessageBody& CSmtpMessageBody::operator=(const String& strBody)
|
||
|
{
|
||
|
Data = strBody;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
// Construction/Destruction for CSmtpAttachment
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
CSmtpAttachment::CSmtpAttachment(LPCTSTR pszFilename, LPCTSTR pszAltName, BOOL bIsInline, LPCTSTR pszEncoding, LPCTSTR pszCharset, EncodingEnum encode)
|
||
|
{
|
||
|
if (pszFilename) FileName = pszFilename;
|
||
|
if (pszAltName) AltName = pszAltName;
|
||
|
if (pszEncoding) Encoding = pszEncoding;
|
||
|
if (pszCharset) Charset = pszCharset;
|
||
|
TransferEncoding = encode;
|
||
|
Inline = bIsInline;
|
||
|
}
|
||
|
|
||
|
const CSmtpAttachment& CSmtpAttachment::operator=(LPCTSTR pszFilename)
|
||
|
{
|
||
|
FileName = pszFilename;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
const CSmtpAttachment& CSmtpAttachment::operator=(const String& strFilename)
|
||
|
{
|
||
|
FileName = strFilename;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
// Construction/Destruction for CSmtpAddress
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
CSmtpAddress::CSmtpAddress(LPCTSTR pszAddress, LPCTSTR pszName)
|
||
|
{
|
||
|
if (pszAddress) Address = pszAddress;
|
||
|
if (pszName) Name = pszName;
|
||
|
}
|
||
|
|
||
|
const CSmtpAddress& CSmtpAddress::operator=(LPCTSTR pszAddress)
|
||
|
{
|
||
|
Address = pszAddress;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
const CSmtpAddress& CSmtpAddress::operator=(const String& strAddress)
|
||
|
{
|
||
|
Address = strAddress;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
// Construction/Destruction for CSmtpMessage
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
CSmtpMessage::CSmtpMessage()
|
||
|
{
|
||
|
TIME_ZONE_INFORMATION tzi;
|
||
|
DWORD dwRet;
|
||
|
long Offset;
|
||
|
|
||
|
// Get local time and timezone offset
|
||
|
GetLocalTime(&Timestamp);
|
||
|
GMTOffset = 0;
|
||
|
dwRet = GetTimeZoneInformation(&tzi);
|
||
|
Offset = tzi.Bias;
|
||
|
if (dwRet == TIME_ZONE_ID_STANDARD) Offset += tzi.StandardBias;
|
||
|
if (dwRet == TIME_ZONE_ID_DAYLIGHT) Offset += tzi.DaylightBias;
|
||
|
GMTOffset = -((Offset / 60) * 100 + (Offset % 60));
|
||
|
|
||
|
MimeType = mimeGuess;
|
||
|
}
|
||
|
|
||
|
// Write all the headers to the e-mail message.
|
||
|
// This is done just before sending it, when we're sure the user wants it to go out.
|
||
|
void CSmtpMessage::CommitHeaders()
|
||
|
{
|
||
|
TCHAR szTime[64] = {0};
|
||
|
TCHAR szDate[64] = {0};
|
||
|
TCHAR szOut[1024] = {0};
|
||
|
String strHeader;
|
||
|
String strValue;
|
||
|
int n;
|
||
|
|
||
|
// Assign a few standard headers to the message
|
||
|
strHeader = _T("X-Priority");
|
||
|
strValue = _T("3 (Normal)");
|
||
|
// Only add the key if it doesn't exist already in the headers map
|
||
|
if (Headers.FindKey(strHeader) == -1) Headers.Add(strHeader,strValue);
|
||
|
|
||
|
strHeader = _T("X-MSMail-Priority");
|
||
|
strValue = _T("Normal");
|
||
|
if (Headers.FindKey(strHeader) == -1) Headers.Add(strHeader,strValue);
|
||
|
|
||
|
strHeader = _T("X-Mailer");
|
||
|
strValue = _T("ATL CSmtp Class Mailer by Robert Simpson (robert@blackcastlesoft.com)");
|
||
|
if (Headers.FindKey(strHeader) == -1) Headers.Add(strHeader,strValue);
|
||
|
|
||
|
strHeader = _T("Importance");
|
||
|
strValue = _T("Normal");
|
||
|
if (Headers.FindKey(strHeader) == -1) Headers.Add(strHeader,strValue);
|
||
|
|
||
|
// Get the time/date stamp and GMT offset for the Date header.
|
||
|
GetDateFormat(MAKELCID(LANG_ENGLISH, SORT_DEFAULT),0,&Timestamp,_T("ddd, d MMM yyyy"),szDate,64);
|
||
|
GetTimeFormat(MAKELCID(LANG_ENGLISH, SORT_DEFAULT),0,&Timestamp,_T("H:mm:ss"),szTime,64);
|
||
|
|
||
|
// Add the date/time stamp to the message headers
|
||
|
wsprintf(szOut,_T("%s %s %c%4.4d"),szDate,szTime,(GMTOffset>0)?'+':'-',GMTOffset);
|
||
|
|
||
|
strHeader = _T("Date");
|
||
|
strValue = szOut;
|
||
|
Headers.Remove(strHeader);
|
||
|
Headers.Add(strHeader,strValue);
|
||
|
|
||
|
// Write out the subject header
|
||
|
strHeader = _T("Subject");
|
||
|
strValue = Subject;
|
||
|
Headers.Remove(strHeader);
|
||
|
Headers.Add(strHeader,strValue);
|
||
|
|
||
|
// Write out the TO header
|
||
|
strValue.erase();
|
||
|
strHeader = _T("To");
|
||
|
if (Recipient.Name.length())
|
||
|
{
|
||
|
wsprintf(szOut,_T("\"%s\" "),Recipient.Name.c_str());
|
||
|
strValue += szOut;
|
||
|
}
|
||
|
if (Recipient.Address.length())
|
||
|
{
|
||
|
wsprintf(szOut,_T("<%s>"),Recipient.Address.c_str());
|
||
|
strValue += szOut;
|
||
|
}
|
||
|
// Write out all the CC'd names
|
||
|
for (n = 0;n < CC.GetSize();n++)
|
||
|
{
|
||
|
if (strValue.length()) strValue += _T(",\r\n\t");
|
||
|
if (CC[n].Name.length())
|
||
|
{
|
||
|
wsprintf(szOut,_T("\"%s\" "),CC[n].Name.c_str());
|
||
|
strValue += szOut;
|
||
|
}
|
||
|
wsprintf(szOut,_T("<%s>"),CC[n].Address.c_str());
|
||
|
strValue += szOut;
|
||
|
}
|
||
|
Headers.Remove(strHeader);
|
||
|
Headers.Add(strHeader,strValue);
|
||
|
|
||
|
// Write out the FROM header
|
||
|
strValue.erase();
|
||
|
strHeader = _T("From");
|
||
|
if (Sender.Name.length())
|
||
|
{
|
||
|
wsprintf(szOut,_T("\"%s\" "),Sender.Name.c_str());
|
||
|
strValue += szOut;
|
||
|
}
|
||
|
wsprintf(szOut,_T("<%s>"),Sender.Address.c_str());
|
||
|
strValue += szOut;
|
||
|
Headers.Remove(strHeader);
|
||
|
Headers.Add(strHeader,strValue);
|
||
|
}
|
||
|
|
||
|
// Parse a message into a single string
|
||
|
void CSmtpMessage::Parse(String& strDest)
|
||
|
{
|
||
|
String strHeader;
|
||
|
String strValue;
|
||
|
String strTemp;
|
||
|
String strBoundary;
|
||
|
String strInnerBoundary;
|
||
|
TCHAR szOut[1024];
|
||
|
int n;
|
||
|
|
||
|
strDest.erase();
|
||
|
// Get a count of the sections to see if this will be a multipart message
|
||
|
n = Message.GetSize();
|
||
|
n += Attachments.GetSize();
|
||
|
|
||
|
// Remove this header in case the message is being reused
|
||
|
strHeader = _T("Content-Type");
|
||
|
Headers.Remove(strHeader);
|
||
|
|
||
|
// If we have more than one section, then this is a multipart MIME message
|
||
|
if (n > 1)
|
||
|
{
|
||
|
wsprintf(szOut,_T("CSmtpMsgPart123X456_000_%8.8X"),GetTickCount());
|
||
|
strBoundary = szOut;
|
||
|
|
||
|
lstrcpy(szOut,_T("multipart/"));
|
||
|
|
||
|
if (MimeType == mimeGuess)
|
||
|
{
|
||
|
if (Attachments.GetSize() == 0) MimeType = mimeAlternative;
|
||
|
else MimeType = mimeMixed;
|
||
|
}
|
||
|
switch(MimeType)
|
||
|
{
|
||
|
case mimeAlternative:
|
||
|
lstrcat(szOut,_T("alternative"));
|
||
|
break;
|
||
|
case mimeMixed:
|
||
|
lstrcat(szOut,_T("mixed"));
|
||
|
break;
|
||
|
case mimeRelated:
|
||
|
lstrcat(szOut,_T("related"));
|
||
|
break;
|
||
|
}
|
||
|
lstrcat(szOut,_T(";\r\n\tboundary=\""));
|
||
|
lstrcat(szOut,strBoundary.c_str());
|
||
|
lstrcat(szOut,_T("\""));
|
||
|
|
||
|
strValue = szOut;
|
||
|
Headers.Add(strHeader,strValue);
|
||
|
}
|
||
|
|
||
|
strHeader = _T("MIME-Version");
|
||
|
strValue = MIME_VERSION;
|
||
|
Headers.Remove(strHeader);
|
||
|
Headers.Add(strHeader,strValue);
|
||
|
|
||
|
// Remove any message ID in the header and replace it with this message ID, if it exists
|
||
|
strHeader = _T("Message-ID");
|
||
|
Headers.Remove(strHeader);
|
||
|
if (MessageId.length())
|
||
|
{
|
||
|
wsprintf(szOut,_T("<%s>"),MessageId.c_str());
|
||
|
strValue = szOut;
|
||
|
Headers.Add(strHeader,strValue);
|
||
|
}
|
||
|
|
||
|
// Finalize the message headers
|
||
|
CommitHeaders();
|
||
|
|
||
|
// Write out all the message headers -- done backwards on purpose!
|
||
|
for (n = Headers.GetSize();n > 0;n--)
|
||
|
{
|
||
|
wsprintf(szOut,_T("%s: %s\r\n"),Headers.GetKeyAt(n-1).c_str(),Headers.GetValueAt(n-1).c_str());
|
||
|
strDest += szOut;
|
||
|
}
|
||
|
if (strBoundary.length())
|
||
|
{
|
||
|
wsprintf(szOut,_T("\r\n%s\r\n"),MULTIPART_MESSAGE);
|
||
|
strDest += szOut;
|
||
|
}
|
||
|
|
||
|
// If we have attachments and multiple message bodies, create a new multipart section
|
||
|
// This is done so we can display our multipart/alternative section separate from the
|
||
|
// main multipart/mixed environment, and support both attachments and alternative bodies.
|
||
|
if (Attachments.GetSize() && Message.GetSize() > 1 && strBoundary.length())
|
||
|
{
|
||
|
wsprintf(szOut,_T("CSmtpMsgPart123X456_001_%8.8X"),GetTickCount());
|
||
|
strInnerBoundary = szOut;
|
||
|
|
||
|
wsprintf(szOut,_T("\r\n--%s\r\nContent-Type: multipart/alternative;\r\n\tboundary=\"%s\"\r\n"),strBoundary.c_str(),strInnerBoundary.c_str());
|
||
|
strDest += szOut;
|
||
|
}
|
||
|
|
||
|
for (n = 0;n < Message.GetSize();n++)
|
||
|
{
|
||
|
// If we're multipart, then write the boundary line
|
||
|
if (strBoundary.length() || strInnerBoundary.length())
|
||
|
{
|
||
|
strDest += _T("\r\n--");
|
||
|
// If we have an inner boundary, write that one. Otherwise write the outer one
|
||
|
if (strInnerBoundary.length()) strDest += strInnerBoundary;
|
||
|
else strDest += strBoundary;
|
||
|
strDest += _T("\r\n");
|
||
|
}
|
||
|
strValue.erase();
|
||
|
strDest += _T("Content-Type: ");
|
||
|
strDest += Message[n].Encoding;
|
||
|
// Include the character set if the message is text
|
||
|
if (_tcsnicmp(Message[n].Encoding.c_str(),_T("text/"),5) == 0)
|
||
|
{
|
||
|
wsprintf(szOut,_T(";\r\n\tcharset=\"%s\""),Message[n].Charset.c_str());
|
||
|
strDest += szOut;
|
||
|
}
|
||
|
strDest += _T("\r\n");
|
||
|
|
||
|
// Encode the message
|
||
|
strValue = Message[n].Data;
|
||
|
EncodeMessage(Message[n].TransferEncoding,strValue,strTemp);
|
||
|
|
||
|
// Write out the encoding method used and the encoded message
|
||
|
strDest += _T("Content-Transfer-Encoding: ");
|
||
|
strDest += strTemp;
|
||
|
|
||
|
// If the message body part has a content ID, write it out
|
||
|
if (Message[n].ContentId.length())
|
||
|
{
|
||
|
wsprintf(szOut,_T("\r\nContent-ID: <%s>"),Message[n].ContentId.c_str());
|
||
|
strDest += szOut;
|
||
|
}
|
||
|
strDest += _T("\r\n\r\n");
|
||
|
strDest += strValue;
|
||
|
}
|
||
|
|
||
|
// If we have multiple message bodies, write out the trailing inner end sequence
|
||
|
if (strInnerBoundary.length())
|
||
|
{
|
||
|
wsprintf(szOut,_T("\r\n--%s--\r\n"),strInnerBoundary.c_str());
|
||
|
strDest += szOut;
|
||
|
}
|
||
|
|
||
|
// Process any attachments
|
||
|
for (n = 0;n < Attachments.GetSize();n++)
|
||
|
{
|
||
|
DWORD dwBytes = 0;
|
||
|
CRegKey cKey;
|
||
|
TCHAR szFilename[MAX_PATH] = {0};
|
||
|
|
||
|
// Get the filename of the attachment
|
||
|
strValue = Attachments[n].FileName;
|
||
|
|
||
|
// Open the file
|
||
|
lstrcpy(szFilename,strValue.c_str());
|
||
|
HANDLE hFile = CreateFile(szFilename,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
|
||
|
if (hFile != INVALID_HANDLE_VALUE)
|
||
|
{
|
||
|
// Get the size of the file, allocate the memory and read the contents.
|
||
|
DWORD dwSize = GetFileSize(hFile,NULL);
|
||
|
LPBYTE pData = (LPBYTE)malloc(dwSize + 1);
|
||
|
ZeroMemory(pData,dwSize+1);
|
||
|
|
||
|
if (ReadFile(hFile,pData,dwSize,&dwBytes,NULL))
|
||
|
{
|
||
|
// Write out our boundary marker
|
||
|
if (strBoundary.length())
|
||
|
{
|
||
|
wsprintf(szOut,_T("\r\n--%s\r\n"),strBoundary.c_str());
|
||
|
strDest += szOut;
|
||
|
}
|
||
|
|
||
|
// If no alternate name is supplied, strip the path to get the base filename
|
||
|
LPTSTR pszFile;
|
||
|
if (!Attachments[n].AltName.length())
|
||
|
{
|
||
|
// Strip the path from the filename
|
||
|
pszFile = _tcsrchr(szFilename,'\\');
|
||
|
if (!pszFile) pszFile = szFilename;
|
||
|
else pszFile ++;
|
||
|
}
|
||
|
else pszFile = (LPTSTR)Attachments[n].AltName.c_str();
|
||
|
|
||
|
// Set the content type for the attachment.
|
||
|
TCHAR szType[MAX_PATH] = {0};
|
||
|
lstrcpy(szType,_T("application/octet-stream"));
|
||
|
|
||
|
// Check the registry for a content type that overrides the above default
|
||
|
LPTSTR pszExt = _tcschr(pszFile,'.');
|
||
|
if (pszExt)
|
||
|
{
|
||
|
if (!cKey.Open(HKEY_CLASSES_ROOT,pszExt,KEY_READ))
|
||
|
{
|
||
|
DWORD dwSize = MAX_PATH;
|
||
|
cKey.QueryValue(_T("Content Type"), NULL, szType, &dwSize);
|
||
|
cKey.Close();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If the attachment has a specific encoding method, use it instead
|
||
|
if (Attachments[n].Encoding.length())
|
||
|
lstrcpy(szType,Attachments[n].Encoding.c_str());
|
||
|
|
||
|
// Write out the content type and attachment types to the message
|
||
|
wsprintf(szOut,_T("Content-Type: %s"),szType);
|
||
|
strDest += szOut;
|
||
|
// If the content type is text, write the charset
|
||
|
if (_tcsnicmp(szType,_T("text/"),5) == 0)
|
||
|
{
|
||
|
wsprintf(szOut,_T(";\r\n\tcharset=\"%s\""),Attachments[n].Charset.c_str());
|
||
|
strDest += szOut;
|
||
|
}
|
||
|
wsprintf(szOut,_T(";\r\n\tname=\"%s\"\r\n"),pszFile);
|
||
|
strDest += szOut;
|
||
|
|
||
|
// Encode the attachment
|
||
|
EncodeMessage(Attachments[n].TransferEncoding,strValue,strTemp,pData,dwSize);
|
||
|
|
||
|
// Write out the transfer encoding method
|
||
|
wsprintf(szOut,_T("Content-Transfer-Encoding: %s\r\n"),strTemp.c_str());
|
||
|
strDest += szOut;
|
||
|
|
||
|
// Write out the attachment's disposition
|
||
|
strDest += _T("Content-Disposition: ");
|
||
|
|
||
|
if (Attachments[n].Inline) strDest += _T("inline");
|
||
|
else strDest += _T("attachment");
|
||
|
|
||
|
strDest += _T(";\r\n\tfilename=\"");
|
||
|
strDest += pszFile;
|
||
|
|
||
|
// If the attachment has a content ID, write it out
|
||
|
if (Attachments[n].ContentId.length())
|
||
|
{
|
||
|
wsprintf(szOut,_T("\r\nContent-ID: <%s>"),Attachments[n].ContentId.c_str());
|
||
|
strDest += szOut;
|
||
|
}
|
||
|
strDest += _T("\r\n\r\n");
|
||
|
|
||
|
// Write out the encoded attachment
|
||
|
strDest += strValue;
|
||
|
strTemp.erase();
|
||
|
strValue.erase();
|
||
|
}
|
||
|
// Close the file and clear the temp buffer
|
||
|
CloseHandle(hFile);
|
||
|
free(pData);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we are multipart, write out the trailing end sequence
|
||
|
if (strBoundary.length())
|
||
|
{
|
||
|
wsprintf(szOut,_T("\r\n--%s--\r\n"),strBoundary.c_str());
|
||
|
strDest += szOut;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Parses text into quoted-printable lines.
|
||
|
// See RFC 1521 for full details on how this works.
|
||
|
void CSmtpMessage::EncodeQuotedPrintable(String& strDest, String& strSrc)
|
||
|
{
|
||
|
String strTemp;
|
||
|
String strTemp2;
|
||
|
LPTSTR pszTok1;
|
||
|
LPTSTR pszTok2;
|
||
|
TCHAR szSub[16];
|
||
|
TCHAR ch;
|
||
|
int n;
|
||
|
|
||
|
strDest.erase();
|
||
|
if (!strSrc.length()) return;
|
||
|
|
||
|
// Change = signs and non-printable characters to =XX
|
||
|
pszTok1 = (LPTSTR)strSrc.c_str();
|
||
|
pszTok2 = pszTok1;
|
||
|
do
|
||
|
{
|
||
|
if (*pszTok2 == '=' || *pszTok2 > 126 ||
|
||
|
(*pszTok2 < 32 && (*pszTok2 != '\r' && *pszTok2 != '\n' && *pszTok2 != '\t')))
|
||
|
{
|
||
|
ch = *pszTok2;
|
||
|
*pszTok2 = 0;
|
||
|
strTemp += pszTok1;
|
||
|
*pszTok2 = ch;
|
||
|
wsprintf(szSub,_T("=%2.2X"),(BYTE)*pszTok2);
|
||
|
strTemp += szSub;
|
||
|
pszTok1 = pszTok2 + 1;
|
||
|
}
|
||
|
pszTok2 ++;
|
||
|
} while (pszTok2 && *pszTok2);
|
||
|
|
||
|
// Append anything left after the search
|
||
|
if (_tcslen(pszTok1)) strTemp += pszTok1;
|
||
|
|
||
|
pszTok1 = (LPTSTR)strTemp.c_str();
|
||
|
while (pszTok1)
|
||
|
{
|
||
|
pszTok2 = _tcschr(pszTok1,'\r');
|
||
|
if (pszTok2) *pszTok2 = 0;
|
||
|
while (1)
|
||
|
{
|
||
|
if (_tcslen(pszTok1) > 76)
|
||
|
{
|
||
|
n = 75; // Breaking at the 75th character
|
||
|
if (pszTok1[n-1] == '=') n -= 1; // If the last character is an =, don't break the line there
|
||
|
else if (pszTok1[n-2] == '=') n -= 2; // If we're breaking in the middle of a = sequence, back up!
|
||
|
|
||
|
// Append the first section of the line to the total string
|
||
|
ch = pszTok1[n];
|
||
|
pszTok1[n] = 0;
|
||
|
strDest += pszTok1;
|
||
|
pszTok1[n] = ch;
|
||
|
strDest += _T("=\r\n");
|
||
|
pszTok1 += n;
|
||
|
}
|
||
|
else // Line is less than or equal to 76 characters
|
||
|
{
|
||
|
n = (int)_tcslen(pszTok1); // If we have some trailing data, process it.
|
||
|
if (n)
|
||
|
{
|
||
|
if (pszTok1[n-1] == ' ' || pszTok1[n-1] == '\t') // Last character is a space or tab
|
||
|
{
|
||
|
wsprintf(szSub,_T("=%2.2X"),(BYTE)pszTok1[n-1]);
|
||
|
// Replace the last character with an =XX sequence
|
||
|
pszTok1[n-1] = 0;
|
||
|
strTemp2 = pszTok1;
|
||
|
strTemp2 += szSub;
|
||
|
// Since the string may now be larger than 76 characters, we have to reprocess the line
|
||
|
pszTok1 = (LPTSTR)strTemp2.c_str();
|
||
|
}
|
||
|
else // Last character is not a space or tab
|
||
|
{
|
||
|
strDest += pszTok1;
|
||
|
if (pszTok2) strDest += _T("\r\n");
|
||
|
break; // Exit the loop which processes this line, and move to the next line
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (pszTok2) strDest += _T("\r\n");
|
||
|
break; // Move to the next line
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (pszTok2)
|
||
|
{
|
||
|
*pszTok2 = '\r';
|
||
|
pszTok2 ++;
|
||
|
if (*pszTok2 == '\n') pszTok2 ++;
|
||
|
}
|
||
|
pszTok1 = pszTok2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Breaks a message's lines into a maximum of 76 characters
|
||
|
// Does some semi-intelligent wordwrapping to ensure the text is broken properly.
|
||
|
// If a line contains no break characters, it is forcibly truncated at the 76th char
|
||
|
void CSmtpMessage::BreakMessage(String& strDest, String& strSrc, int nLength)
|
||
|
{
|
||
|
String strTemp = strSrc;
|
||
|
LPTSTR pszTok1;
|
||
|
LPTSTR pszTok2;
|
||
|
LPTSTR pszBreak;
|
||
|
LPTSTR pszBreaks = _T(" -;.,?!");
|
||
|
TCHAR ch;
|
||
|
int nLen;
|
||
|
|
||
|
strDest.erase();
|
||
|
if (!strSrc.length()) return;
|
||
|
|
||
|
nLen = (int)strTemp.length();
|
||
|
nLen += (nLen / 60) * 2;
|
||
|
|
||
|
strDest.reserve(nLen);
|
||
|
|
||
|
// Process each line one at a time
|
||
|
pszTok1 = (LPTSTR)strTemp.c_str();
|
||
|
while (pszTok1)
|
||
|
{
|
||
|
pszTok2 = _tcschr(pszTok1,'\r');
|
||
|
if (pszTok2) *pszTok2 = 0;
|
||
|
|
||
|
BOOL bNoBreaks = (!_tcspbrk(pszTok1,pszBreaks));
|
||
|
nLen = (int)_tcslen(pszTok1);
|
||
|
while (nLen > nLength)
|
||
|
{
|
||
|
// Start at the 76th character, and move backwards until we hit a break character
|
||
|
pszBreak = &pszTok1[nLength - 1];
|
||
|
|
||
|
// If there are no break characters in the string, skip the backward search for them!
|
||
|
if (!bNoBreaks)
|
||
|
{
|
||
|
while (!_tcschr(pszBreaks,*pszBreak) && pszBreak > pszTok1)
|
||
|
pszBreak--;
|
||
|
}
|
||
|
pszBreak ++;
|
||
|
ch = *pszBreak;
|
||
|
*pszBreak = 0;
|
||
|
strDest += pszTok1;
|
||
|
|
||
|
strDest += _T("\r\n");
|
||
|
*pszBreak = ch;
|
||
|
|
||
|
nLen -= (int)(pszBreak - pszTok1);
|
||
|
// Advance the search to the next segment of text after the break
|
||
|
pszTok1 = pszBreak;
|
||
|
}
|
||
|
strDest += pszTok1;
|
||
|
if (pszTok2)
|
||
|
{
|
||
|
strDest += _T("\r\n");
|
||
|
*pszTok2 = '\r';
|
||
|
pszTok2 ++;
|
||
|
if (*pszTok2 == '\n') pszTok2 ++;
|
||
|
}
|
||
|
pszTok1 = pszTok2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Makes the message into a 7bit stream
|
||
|
void CSmtpMessage::Make7Bit(String& strDest, String& strSrc)
|
||
|
{
|
||
|
LPTSTR pszTok;
|
||
|
|
||
|
strDest = strSrc;
|
||
|
|
||
|
pszTok = (LPTSTR)strDest.c_str();
|
||
|
do
|
||
|
{
|
||
|
// Replace any characters above 126 with a ? character
|
||
|
if (*pszTok > 126 || *pszTok < 0)
|
||
|
*pszTok = '?';
|
||
|
pszTok ++;
|
||
|
} while (pszTok && *pszTok);
|
||
|
}
|
||
|
|
||
|
// Encodes a message or binary stream into a properly-formatted message
|
||
|
// Takes care of breaking the message into 76-byte lines of text, encoding to
|
||
|
// Base64, quoted-printable and etc.
|
||
|
void CSmtpMessage::EncodeMessage(EncodingEnum code, String& strMsg, String& strMethod, LPBYTE pByte, DWORD dwSize)
|
||
|
{
|
||
|
String strTemp;
|
||
|
LPTSTR pszTok1;
|
||
|
LPTSTR pszTok2;
|
||
|
LPSTR pszBuffer = NULL;
|
||
|
DWORD dwStart = GetTickCount();
|
||
|
|
||
|
if (!pByte)
|
||
|
{
|
||
|
pszBuffer = (LPSTR)malloc(strMsg.length() + 1);
|
||
|
_T2A(pszBuffer,strMsg.c_str());
|
||
|
pByte = (LPBYTE)pszBuffer;
|
||
|
dwSize = (DWORD)strMsg.length();
|
||
|
}
|
||
|
|
||
|
// Guess the encoding scheme if we have to
|
||
|
if (code == encodeGuess) code = GuessEncoding(pByte, dwSize);
|
||
|
|
||
|
switch(code)
|
||
|
{
|
||
|
case encodeQuotedPrintable:
|
||
|
strMethod = _T("quoted-printable");
|
||
|
|
||
|
pszTok1 = (LPTSTR)malloc((dwSize+1) * sizeof(TCHAR));
|
||
|
_A2T(pszTok1,(LPSTR)pByte);
|
||
|
strMsg = pszTok1;
|
||
|
free(pszTok1);
|
||
|
|
||
|
EncodeQuotedPrintable(strTemp, strMsg);
|
||
|
break;
|
||
|
case encodeBase64:
|
||
|
strMethod = _T("base64");
|
||
|
{
|
||
|
CBase64 cvt;
|
||
|
cvt.Encode(pByte, dwSize);
|
||
|
LPSTR pszTemp = (LPSTR)cvt.EncodedMessage();
|
||
|
pszTok1 = (LPTSTR)malloc((lstrlenA(pszTemp)+1) * sizeof(TCHAR));
|
||
|
_A2T(pszTok1,pszTemp);
|
||
|
}
|
||
|
strMsg = pszTok1;
|
||
|
free(pszTok1);
|
||
|
|
||
|
BreakMessage(strTemp, strMsg);
|
||
|
break;
|
||
|
case encode7Bit:
|
||
|
strMethod = _T("7bit");
|
||
|
|
||
|
pszTok1 = (LPTSTR)malloc((dwSize+1) * sizeof(TCHAR));
|
||
|
_A2T(pszTok1,(LPSTR)pByte);
|
||
|
strMsg = pszTok1;
|
||
|
free(pszTok1);
|
||
|
|
||
|
Make7Bit(strTemp, strMsg);
|
||
|
strMsg = strTemp;
|
||
|
BreakMessage(strTemp, strMsg);
|
||
|
break;
|
||
|
case encode8Bit:
|
||
|
strMethod = _T("8bit");
|
||
|
|
||
|
pszTok1 = (LPTSTR)malloc((dwSize+1) * sizeof(TCHAR));
|
||
|
_A2T(pszTok1,(LPSTR)pByte);
|
||
|
strMsg = pszTok1;
|
||
|
free(pszTok1);
|
||
|
|
||
|
BreakMessage(strTemp, strMsg);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (pszBuffer) free(pszBuffer);
|
||
|
|
||
|
strMsg.erase();
|
||
|
|
||
|
// Parse the message text, replacing CRLF. sequences with CRLF.. sequences
|
||
|
pszTok1 = (LPTSTR)strTemp.c_str();
|
||
|
do
|
||
|
{
|
||
|
pszTok2 = _tcsstr(pszTok1,_T("\r\n."));
|
||
|
if (pszTok2)
|
||
|
{
|
||
|
*pszTok2 = 0;
|
||
|
strMsg += pszTok1;
|
||
|
*pszTok2 = '\r';
|
||
|
strMsg += _T("\r\n..");
|
||
|
pszTok1 = pszTok2 + 3;
|
||
|
}
|
||
|
} while (pszTok2);
|
||
|
strMsg += pszTok1;
|
||
|
|
||
|
TCHAR szOut[MAX_PATH] = {0};
|
||
|
wsprintf(szOut,_T("Encoding took %dms\n"),GetTickCount() - dwStart);
|
||
|
OutputDebugString(szOut);
|
||
|
}
|
||
|
|
||
|
// Makes a best-guess of the proper encoding to use for this stream of bytes
|
||
|
// It does this by counting the # of lines, the # of 8bit bytes and the number
|
||
|
// of 7bit bytes. It also records the line and the count of lines over
|
||
|
// 76 characters.
|
||
|
// If the stream is 90% or higher 7bit, it uses a text encoding method. If the stream
|
||
|
// is all at or under 76 characters, it uses 7bit or 8bit, depending on the content.
|
||
|
// If the lines are longer than 76 characters, use quoted printable.
|
||
|
// If the stream is under 90% 7bit characters, use base64 encoding.
|
||
|
EncodingEnum CSmtpMessage::GuessEncoding(LPBYTE pByte, DWORD dwLen)
|
||
|
{
|
||
|
int n7Bit = 0;
|
||
|
int n8Bit = 0;
|
||
|
int nLineStart = 0;
|
||
|
int nLinesOver76 = 0;
|
||
|
int nLines = 0;
|
||
|
DWORD n;
|
||
|
|
||
|
// Count the content type, byte by byte
|
||
|
for (n = 0;n < dwLen; n++)
|
||
|
{
|
||
|
if (pByte[n] > 126 || (pByte[n] < 32 && pByte[n] != '\t' && pByte[n] != '\r' && pByte[n] != '\n'))
|
||
|
n8Bit ++;
|
||
|
else n7Bit ++;
|
||
|
|
||
|
// New line? If so, record the line size
|
||
|
if (pByte[n] == '\r')
|
||
|
{
|
||
|
nLines ++;
|
||
|
nLineStart = (n - nLineStart) - 1;
|
||
|
if (nLineStart > 76) nLinesOver76 ++;
|
||
|
nLineStart = n + 1;
|
||
|
}
|
||
|
}
|
||
|
// Determine if it is mostly 7bit data
|
||
|
if ((n7Bit * 100) / dwLen > 89)
|
||
|
{
|
||
|
// At least 90% text, so use a text-base encoding scheme
|
||
|
if (!nLinesOver76)
|
||
|
{
|
||
|
if (!n8Bit) return encode7Bit;
|
||
|
else return encode8Bit;
|
||
|
}
|
||
|
else return encodeQuotedPrintable;
|
||
|
}
|
||
|
return encodeBase64;
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
// Construction/Destruction for CSmtp
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
CSmtp::CSmtp()
|
||
|
{
|
||
|
LPSERVENT pEnt;
|
||
|
|
||
|
m_bExtensions = TRUE; // Use ESMTP if possible
|
||
|
m_dwCmdTimeout = 30; // Default to 30 second timeout
|
||
|
m_hSocket = INVALID_SOCKET;
|
||
|
m_bConnected = m_bUsingExtensions = FALSE;
|
||
|
|
||
|
// Try and get the SMTP service entry by name
|
||
|
pEnt = getservbyname("SMTP","tcp");
|
||
|
if (pEnt) m_wSmtpPort = pEnt->s_port;
|
||
|
else m_wSmtpPort = htons(25);
|
||
|
|
||
|
}
|
||
|
|
||
|
CSmtp::~CSmtp()
|
||
|
{
|
||
|
// Make sure any open connections are shut down
|
||
|
Close();
|
||
|
}
|
||
|
|
||
|
// Connects to a SMTP server. Returns TRUE if successfully connected, or FALSE otherwise.
|
||
|
BOOL CSmtp::Connect(LPTSTR pszServer)
|
||
|
{
|
||
|
SOCKADDR_IN addr;
|
||
|
int nRet;
|
||
|
CHAR szHost[MAX_PATH] = {0};
|
||
|
|
||
|
_T2A(szHost,pszServer);
|
||
|
// Shut down any active connection
|
||
|
Close();
|
||
|
// test
|
||
|
WORD wVersionRequested;
|
||
|
WSADATA wsaData;
|
||
|
wVersionRequested = MAKEWORD( 2, 1 );
|
||
|
WSAStartup( wVersionRequested, &wsaData );
|
||
|
// end test
|
||
|
// Resolve the hostname
|
||
|
addr.sin_family = AF_INET;
|
||
|
addr.sin_port = m_wSmtpPort;
|
||
|
addr.sin_addr.s_addr = inet_addr(szHost);
|
||
|
|
||
|
if (addr.sin_addr.s_addr == INADDR_NONE)
|
||
|
{
|
||
|
|
||
|
LPHOSTENT pHost = gethostbyname(szHost);
|
||
|
if (!pHost)
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
addr.sin_addr.s_addr = *(LPDWORD)pHost->h_addr;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// Create a socket
|
||
|
m_hSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
|
||
|
if (m_hSocket == INVALID_SOCKET) return FALSE;
|
||
|
|
||
|
// Connect to the host
|
||
|
if (connect(m_hSocket,(LPSOCKADDR)&addr,sizeof(addr)) == SOCKET_ERROR)
|
||
|
{
|
||
|
Close();
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
// Get the initial response string
|
||
|
nRet = SendCmd(NULL);
|
||
|
if (nRet != 220)
|
||
|
{
|
||
|
RaiseError(nRet);
|
||
|
Close();
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
// Send a HELLO message to the SMTP server
|
||
|
if (SendHello())
|
||
|
{
|
||
|
Close();
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
// Closes any active SMTP sessions and shuts down the socket.
|
||
|
void CSmtp::Close()
|
||
|
{
|
||
|
if (m_hSocket != INVALID_SOCKET)
|
||
|
{
|
||
|
// If we're connected to a server, tell them we're quitting
|
||
|
if (m_bConnected) SendQuitCmd();
|
||
|
// Shutdown and close the socket
|
||
|
shutdown(m_hSocket,2);
|
||
|
closesocket(m_hSocket);
|
||
|
}
|
||
|
m_hSocket = INVALID_SOCKET;
|
||
|
}
|
||
|
|
||
|
// Send a command to the SMTP server and wait for a response
|
||
|
int CSmtp::SendCmd(LPTSTR pszCmd)
|
||
|
{
|
||
|
USES_CONVERSION;
|
||
|
FD_SET set;
|
||
|
TIMEVAL tv;
|
||
|
int nRet = 0;
|
||
|
DWORD dwTick;
|
||
|
CHAR szResult[CMD_RESPONSE_SIZE] = {0};
|
||
|
LPSTR pszPos;
|
||
|
LPSTR pszTok;
|
||
|
BOOL bReportProgress = FALSE;
|
||
|
LPSTR pszBuff;
|
||
|
|
||
|
ZeroMemory(szResult,CMD_RESPONSE_SIZE);
|
||
|
FD_ZERO(&set);
|
||
|
|
||
|
// If we have a command to send, then send it.
|
||
|
if (pszCmd)
|
||
|
{
|
||
|
pszBuff = (LPSTR)malloc(lstrlen(pszCmd)+1);
|
||
|
_T2A(pszBuff,pszCmd);
|
||
|
|
||
|
// Make sure the input buffer is clear before sending
|
||
|
nRet = 1;
|
||
|
while (nRet > 0)
|
||
|
{
|
||
|
FD_SET(m_hSocket,&set);
|
||
|
tv.tv_sec = 0;
|
||
|
tv.tv_usec = 0;
|
||
|
nRet = select(1,&set,NULL,NULL,&tv);
|
||
|
if (nRet == 1) nRet = recv(m_hSocket,szResult,CMD_RESPONSE_SIZE,0);
|
||
|
}
|
||
|
DWORD dwPosition = 0;
|
||
|
DWORD dwLen = lstrlen(pszCmd);
|
||
|
if (dwLen > CMD_BLOCK_SIZE) bReportProgress = TRUE;
|
||
|
|
||
|
while (dwLen != dwPosition)
|
||
|
{
|
||
|
DWORD dwMax = min(CMD_BLOCK_SIZE,dwLen - dwPosition);
|
||
|
nRet = send(m_hSocket,&pszBuff[dwPosition],dwMax,0);
|
||
|
if (nRet == SOCKET_ERROR)
|
||
|
{
|
||
|
free(pszBuff);
|
||
|
return nRet;
|
||
|
}
|
||
|
dwPosition += dwMax;
|
||
|
if (bReportProgress)
|
||
|
{
|
||
|
if (!SmtpProgress(pszBuff,dwPosition,dwLen))
|
||
|
{
|
||
|
free(pszBuff);
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Wait for the CMD to finish being sent
|
||
|
FD_ZERO(&set);
|
||
|
FD_SET(m_hSocket,&set);
|
||
|
nRet = select(1,NULL,&set,NULL,NULL);
|
||
|
free(pszBuff);
|
||
|
}
|
||
|
|
||
|
// Prepare to receive a response
|
||
|
ZeroMemory(szResult,CMD_RESPONSE_SIZE);
|
||
|
pszPos = szResult;
|
||
|
// Wait for the specified timeout for a full response string
|
||
|
dwTick = GetTickCount();
|
||
|
while (GetTickCount() - dwTick < (m_dwCmdTimeout * 1000))
|
||
|
{
|
||
|
FD_SET(m_hSocket,&set);
|
||
|
|
||
|
tv.tv_sec = m_dwCmdTimeout - ((GetTickCount() - dwTick) / 1000);
|
||
|
tv.tv_usec = 0;
|
||
|
|
||
|
// Check the socket for readability
|
||
|
nRet = select(1,&set,NULL,NULL,&tv);
|
||
|
if (nRet == SOCKET_ERROR) break;
|
||
|
|
||
|
// If the socket has data, read it.
|
||
|
if (nRet == 1)
|
||
|
{
|
||
|
nRet = recv(m_hSocket,pszPos,CMD_RESPONSE_SIZE - (int)(pszPos - szResult),0);
|
||
|
// Treats a graceful shutdown as an error
|
||
|
if (nRet == 0) nRet = SOCKET_ERROR;
|
||
|
if (nRet == SOCKET_ERROR) break;
|
||
|
|
||
|
// Add the data to the total response string & check for a LF
|
||
|
pszPos += nRet;
|
||
|
pszTok = strrchr(szResult,'\n');
|
||
|
if (pszTok)
|
||
|
{
|
||
|
// Truncate CRLF combination and exit our wait loop
|
||
|
pszTok --;
|
||
|
pszTok[0] = 0;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Assign the response string
|
||
|
m_strResult = A2CT(szResult);
|
||
|
|
||
|
// Evaluate the numeric response code
|
||
|
if (nRet && nRet != SOCKET_ERROR)
|
||
|
{
|
||
|
szResult[3] = 0;
|
||
|
nRet = atoi(szResult);
|
||
|
SmtpCommandResponse(pszCmd, nRet, (LPTSTR)m_strResult.c_str());
|
||
|
}
|
||
|
else nRet = -1;
|
||
|
|
||
|
return nRet;
|
||
|
}
|
||
|
|
||
|
// Placeholder function -- overridable
|
||
|
// This function is called when the SMTP server gives us an unexpected error
|
||
|
// The <nError> value is the SMTP server's numeric error response, and the <pszErr>
|
||
|
// is the descriptive error text
|
||
|
//
|
||
|
// <pszErr> may be NULL if the server failed to respond before the timeout!
|
||
|
// <nError> will be -1 if a catastrophic failure occurred.
|
||
|
//
|
||
|
// Return 0, or nError. The return value is currently ignored.
|
||
|
int CSmtp::SmtpError(int /*nError*/, LPTSTR pszErr)
|
||
|
{
|
||
|
#ifdef _DEBUG
|
||
|
if (pszErr)
|
||
|
{
|
||
|
OutputDebugString(_T("SmtpError: "));
|
||
|
OutputDebugString(pszErr);
|
||
|
OutputDebugString(_T("\n"));
|
||
|
}
|
||
|
#endif
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// Placeholder function -- overridable
|
||
|
// Currently the only warning condition that this class is designed for is
|
||
|
// an authentication failure. In that case, <nWarning> will be 535,
|
||
|
// which is the RFC error for authentication failure. If authentication
|
||
|
// fails, you can override this function to prompt the user for a new
|
||
|
// username and password. Change the <m_strUser> and <m_strPass> member
|
||
|
// variables and return TRUE to retry authentication.
|
||
|
//
|
||
|
// <pszWarning> may be NULL if the server did not respond in time!
|
||
|
//
|
||
|
// Return FALSE to abort authentication, or TRUE to retry.
|
||
|
int CSmtp::SmtpWarning(int /*nWarning*/, LPTSTR pszWarning)
|
||
|
{
|
||
|
#ifdef _DEBUG
|
||
|
if (pszWarning)
|
||
|
{
|
||
|
OutputDebugString(_T("SmtpWarning: "));
|
||
|
OutputDebugString(pszWarning);
|
||
|
OutputDebugString(_T("\n"));
|
||
|
}
|
||
|
#endif
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// Placeholder function -- overridable
|
||
|
// This is an informational callback only, and provides a means to inform
|
||
|
// the caller as the SMTP session progresses.
|
||
|
// ALWAYS check for NULL values on <pszCmd> and <pszResponse> before performing
|
||
|
// any actions!
|
||
|
// <nResponse> will be -1 if a catastrophic failure occurred, but that will
|
||
|
// be raised in the SmtpError() event later on during processing.
|
||
|
void CSmtp::SmtpCommandResponse(LPTSTR pszCmd, int /*nResponse*/, LPTSTR pszResponse)
|
||
|
{
|
||
|
#ifdef _DEBUG
|
||
|
if (pszCmd)
|
||
|
{
|
||
|
TCHAR szOut[MAX_PATH+1] = {0};
|
||
|
OutputDebugString(_T("SmtpCommand : "));
|
||
|
while (lstrlen(pszCmd) > MAX_PATH)
|
||
|
{
|
||
|
lstrcpyn(szOut,pszCmd,MAX_PATH+1);
|
||
|
OutputDebugString(szOut);
|
||
|
Sleep(100);
|
||
|
pszCmd += MAX_PATH;
|
||
|
}
|
||
|
OutputDebugString(pszCmd);
|
||
|
}
|
||
|
OutputDebugString(_T("SmtpResponse: "));
|
||
|
OutputDebugString(pszResponse);
|
||
|
OutputDebugString(_T("\n"));
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
// Placeholder function -- overridable
|
||
|
// This is a progress callback to indicate that data is being sent over the wire
|
||
|
// and that the operation may take some time.
|
||
|
// Return TRUE to continue sending, or FALSE to abort the transfer
|
||
|
BOOL CSmtp::SmtpProgress(LPSTR /*pszBuffer*/, DWORD /*dwBytesSent*/, DWORD /*dwBytesTotal*/)
|
||
|
{
|
||
|
return TRUE; // Continue sending the data
|
||
|
}
|
||
|
|
||
|
// Raises a SmtpError() condition
|
||
|
int CSmtp::RaiseError(int nError)
|
||
|
{
|
||
|
// If the error code is -1, something catastrophic happened
|
||
|
// so we're effectively not connected to any SMTP server.
|
||
|
if (nError == -1) m_bConnected = FALSE;
|
||
|
return SmtpError(nError, (LPTSTR)m_strResult.c_str());
|
||
|
}
|
||
|
|
||
|
// Warnings are recoverable errors that we may be able to continue working with
|
||
|
int CSmtp::RaiseWarning(int nWarning)
|
||
|
{
|
||
|
return SmtpWarning(nWarning, (LPTSTR)m_strResult.c_str());
|
||
|
}
|
||
|
|
||
|
// E-Mail's a message
|
||
|
// Returns 0 if successful, -1 if an internal error occurred, or a positive
|
||
|
// error value if the SMTP server gave an error or failure response.
|
||
|
int CSmtp::SendMessage(CSmtpMessage &msg)
|
||
|
{
|
||
|
int nRet;
|
||
|
int n;
|
||
|
// int nRecipients = 0;
|
||
|
int nRecipientCount = 0;
|
||
|
|
||
|
// Check if we have a sender
|
||
|
if (!msg.Sender.Address.length()) return -1;
|
||
|
|
||
|
// Check if we have recipients
|
||
|
if (!msg.Recipient.Address.length() && !msg.CC.GetSize()) return -1;
|
||
|
|
||
|
// Check if we have a message body or attachments
|
||
|
// *** Commented out to remove the requirement that a message have a body or attachments
|
||
|
// if (!msg.Message.GetSize() && !msg.Attachments.GetSize()) return -1;
|
||
|
|
||
|
// Send the sender's address
|
||
|
nRet = SendFrom((LPTSTR)msg.Sender.Address.c_str());
|
||
|
if (nRet) return nRet;
|
||
|
// If we have a recipient, send it
|
||
|
nRecipientCount = 0; // Count of recipients
|
||
|
if (msg.Recipient.Address.length())
|
||
|
{
|
||
|
nRet = SendTo((LPTSTR)msg.Recipient.Address.c_str());
|
||
|
if (!nRet) nRecipientCount ++;
|
||
|
}
|
||
|
|
||
|
// If we have any CC's, send those.
|
||
|
for (n = 0;n < msg.CC.GetSize();n++)
|
||
|
{
|
||
|
nRet = SendTo((LPTSTR)msg.CC[n].Address.c_str());
|
||
|
if (!nRet) nRecipientCount ++;
|
||
|
}
|
||
|
|
||
|
// If we have any bcc's, send those.
|
||
|
for (n = 0;n < msg.BCC.GetSize();n++)
|
||
|
{
|
||
|
nRet = SendTo((LPTSTR)msg.BCC[n].Address.c_str());
|
||
|
if (!nRet) nRecipientCount ++;
|
||
|
}
|
||
|
// If we failed on all recipients, we must abort.
|
||
|
if (!nRecipientCount)
|
||
|
RaiseError(nRet);
|
||
|
else
|
||
|
nRet = SendData(msg);
|
||
|
|
||
|
return nRet;
|
||
|
}
|
||
|
|
||
|
// Simplified way to send a message.
|
||
|
// <pvAttachments> can be either an LPTSTR containing NULL terminated strings, in which
|
||
|
// case <dwAttachmentCount> should be zero, or <pvAttachments> can be an LPTSTR *
|
||
|
// containing an array of LPTSTR's, in which case <dwAttachmentCount> should equal the
|
||
|
// number of strings in the array.
|
||
|
int CSmtp::SendMessage(CSmtpAddress &addrFrom, CSmtpAddress &addrTo, LPCTSTR pszSubject, LPTSTR pszMessage, LPVOID pvAttachments, DWORD dwAttachmentCount)
|
||
|
{
|
||
|
CSmtpMessage message;
|
||
|
CSmtpMessageBody body;
|
||
|
CSmtpAttachment attach;
|
||
|
|
||
|
body = pszMessage;
|
||
|
|
||
|
message.Sender = addrFrom;
|
||
|
message.Recipient = addrTo;
|
||
|
message.Message.Add(body);
|
||
|
message.Subject = pszSubject;
|
||
|
|
||
|
// If the attachment count is zero, but the pvAttachments variable is not NULL,
|
||
|
// assume that the ppvAttachments variable is a string value containing NULL terminated
|
||
|
// strings. A double NULL ends the list.
|
||
|
// Example: LPTSTR pszAttachments = "foo.exe\0bar.zip\0autoexec.bat\0\0";
|
||
|
if (!dwAttachmentCount && pvAttachments)
|
||
|
{
|
||
|
LPTSTR pszAttachments = (LPTSTR)pvAttachments;
|
||
|
while (lstrlen(pszAttachments))
|
||
|
{
|
||
|
attach.FileName = pszAttachments;
|
||
|
message.Attachments.Add(attach);
|
||
|
pszAttachments = &pszAttachments[lstrlen(pszAttachments)];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// dwAttachmentCount is not zero, so assume pvAttachments is an array of LPTSTR's
|
||
|
// Example: LPTSTR *ppszAttachments = {"foo.exe","bar.exe","autoexec.bat"};
|
||
|
if (pvAttachments && dwAttachmentCount)
|
||
|
{
|
||
|
LPTSTR *ppszAttachments = (LPTSTR *)pvAttachments;
|
||
|
while (dwAttachmentCount-- && ppszAttachments)
|
||
|
{
|
||
|
attach.FileName = ppszAttachments[dwAttachmentCount];
|
||
|
message.Attachments.Add(attach);
|
||
|
}
|
||
|
}
|
||
|
return SendMessage(message);
|
||
|
}
|
||
|
|
||
|
// Yet an even simpler method for sending a message
|
||
|
// <pszAddrFrom> and <pszAddrTo> should be e-mail addresses with no decorations
|
||
|
// Example: "foo@bar.com"
|
||
|
// <pvAttachments> and <dwAttachmentCount> are described above in the alternative
|
||
|
// version of this function
|
||
|
int CSmtp::SendMessage(LPTSTR pszAddrFrom, LPTSTR pszAddrTo, LPTSTR pszSubject, LPTSTR pszMessage, LPVOID pvAttachments, DWORD dwAttachmentCount)
|
||
|
{
|
||
|
CSmtpAddress addrFrom(pszAddrFrom);
|
||
|
CSmtpAddress addrTo(pszAddrTo);
|
||
|
|
||
|
return SendMessage(addrFrom,addrTo,pszSubject,pszMessage,pvAttachments,dwAttachmentCount);
|
||
|
}
|
||
|
|
||
|
// Tell the SMTP server we're quitting
|
||
|
// Returns 0 if successful, or a positive
|
||
|
// error value if the SMTP server gave an error or failure response.
|
||
|
int CSmtp::SendQuitCmd()
|
||
|
{
|
||
|
int nRet;
|
||
|
|
||
|
if (!m_bConnected) return 0;
|
||
|
|
||
|
nRet = SendCmd(_T("QUIT\r\n"));
|
||
|
if (nRet != 221) RaiseError(nRet);
|
||
|
|
||
|
m_bConnected = FALSE;
|
||
|
|
||
|
return (nRet == 221) ? 0:nRet;
|
||
|
}
|
||
|
|
||
|
// Initiate a conversation with the SMTP server
|
||
|
// Returns 0 if successful, or a positive
|
||
|
// error value if the SMTP server gave an error or failure response.
|
||
|
int CSmtp::SendHello()
|
||
|
{
|
||
|
int nRet = 0;
|
||
|
TCHAR szName[64] = {0};
|
||
|
TCHAR szMsg[MAX_PATH] = {0};
|
||
|
DWORD dwSize = 64;
|
||
|
|
||
|
GetComputerName(szName,&dwSize);
|
||
|
|
||
|
// First try a EHLO if we're using ESMTP
|
||
|
wsprintf(szMsg,_T("EHLO %s\r\n"),szName);
|
||
|
if (m_bExtensions) nRet = SendCmd(szMsg);
|
||
|
|
||
|
// If we got a 250 response, we're using ESMTP, otherwise revert to regular SMTP
|
||
|
if (nRet != 250)
|
||
|
{
|
||
|
m_bUsingExtensions = FALSE;
|
||
|
szMsg[0] = 'H';
|
||
|
szMsg[1] = 'E';
|
||
|
nRet = SendCmd(szMsg);
|
||
|
}
|
||
|
else m_bUsingExtensions = TRUE;
|
||
|
|
||
|
// Raise any unexpected responses
|
||
|
if (nRet != 250)
|
||
|
{
|
||
|
RaiseError(nRet);
|
||
|
return nRet;
|
||
|
}
|
||
|
|
||
|
// We're connected!
|
||
|
m_bConnected = TRUE;
|
||
|
|
||
|
// Send authentication if we have any.
|
||
|
// We don't fail just because authentication failed, however.
|
||
|
if (m_bUsingExtensions) SendAuthentication();
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// Requests authentication for the session if the server supports it,
|
||
|
// and attempts to submit the user's credentials.
|
||
|
// Returns 0 if successful, or a positive
|
||
|
// error value if the SMTP server gave an error or failure response.
|
||
|
int CSmtp::SendAuthentication()
|
||
|
{
|
||
|
USES_CONVERSION;
|
||
|
int nRet = 0;
|
||
|
CBase64 cvt;
|
||
|
LPCSTR pszTemp;
|
||
|
TCHAR szMsg[MAX_PATH] = {0};
|
||
|
CHAR szAuthType[MAX_PATH] = {0};
|
||
|
|
||
|
// This is an authentication loop, we can authenticate multiple times in case of failure.
|
||
|
while(1)
|
||
|
{
|
||
|
// If we don't have a username, skip authentication
|
||
|
if (!m_strUser.length()) return 0;
|
||
|
|
||
|
// Make the authentication request
|
||
|
nRet = SendCmd(_T("AUTH LOGIN\r\n"));
|
||
|
// If it was rejected, we have to abort.
|
||
|
if (nRet != 334)
|
||
|
{
|
||
|
RaiseWarning(nRet);
|
||
|
return nRet;
|
||
|
}
|
||
|
|
||
|
// Authentication has 2 stages for username and password.
|
||
|
// It is possible if the authentication fails here that we can
|
||
|
// resubmit proper credentials.
|
||
|
while (1)
|
||
|
{
|
||
|
// Decode the authentication string being requested
|
||
|
_T2A(szAuthType,&(m_strResult.c_str())[4]);
|
||
|
|
||
|
cvt.Decode(szAuthType);
|
||
|
pszTemp = cvt.DecodedMessage();
|
||
|
|
||
|
if (!lstrcmpiA(pszTemp,"Username:"))
|
||
|
cvt.Encode(T2CA(m_strUser.c_str()));
|
||
|
else if (!lstrcmpiA(pszTemp,"Password:"))
|
||
|
cvt.Encode(T2CA(m_strPass.c_str()));
|
||
|
else break;
|
||
|
|
||
|
wsprintf(szMsg,_T("%s\r\n"),A2CT(cvt.EncodedMessage()));
|
||
|
nRet = SendCmd(szMsg);
|
||
|
|
||
|
// If we got a failed authentication request, raise a warning.
|
||
|
// this gives the owner a chance to change the username and password.
|
||
|
if (nRet == 535)
|
||
|
{
|
||
|
// Return FALSE to fail, or TRUE to retry
|
||
|
nRet = RaiseWarning(nRet);
|
||
|
if (!nRet)
|
||
|
{
|
||
|
// Reset the error back to 535. It's now an error rather than a warning
|
||
|
nRet = 535;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
// Break on any response other than 334, which indicates a request for more information
|
||
|
if (nRet != 334) break;
|
||
|
}
|
||
|
// Break if we're not retrying a failed authentication
|
||
|
if (nRet != TRUE) break;
|
||
|
}
|
||
|
// Raise an error if we failed to authenticate
|
||
|
if (nRet != 235) RaiseError(nRet);
|
||
|
|
||
|
return (nRet == 235) ? 0:nRet;
|
||
|
}
|
||
|
|
||
|
// Send a MAIL FROM command to the server
|
||
|
// Returns 0 if successful, or a positive
|
||
|
// error value if the SMTP server gave an error or failure response.
|
||
|
int CSmtp::SendFrom(LPTSTR pszFrom)
|
||
|
{
|
||
|
int nRet = 0;
|
||
|
TCHAR szMsg[MAX_PATH] = {0};
|
||
|
|
||
|
wsprintf(szMsg,_T("MAIL FROM: <%s>\r\n"),pszFrom);
|
||
|
|
||
|
while (1)
|
||
|
{
|
||
|
nRet = SendCmd(szMsg);
|
||
|
// Send authentication if required, and retry the command
|
||
|
if (nRet == 530) nRet = SendAuthentication();
|
||
|
else break;
|
||
|
}
|
||
|
// Raise an error if we failed
|
||
|
if (nRet != 250) RaiseError(nRet);
|
||
|
return (nRet == 250) ? 0:nRet;
|
||
|
}
|
||
|
|
||
|
// Send a RCPT TO command to the server
|
||
|
// Returns 0 if successful, or a positive
|
||
|
// error value if the SMTP server gave an error or failure response.
|
||
|
int CSmtp::SendTo(LPTSTR pszTo)
|
||
|
{
|
||
|
int nRet;
|
||
|
TCHAR szMsg[MAX_PATH] = {0};
|
||
|
|
||
|
wsprintf(szMsg,_T("RCPT TO: <%s>\r\n"),pszTo);
|
||
|
nRet = SendCmd(szMsg);
|
||
|
if (nRet != 250 && nRet != 251) RaiseWarning(nRet);
|
||
|
return (nRet == 250 || nRet == 251) ? 0:nRet;
|
||
|
}
|
||
|
|
||
|
// Send the body of an e-mail message to the server
|
||
|
// Returns 0 if successful, or a positive
|
||
|
// error value if the SMTP server gave an error or failure response.
|
||
|
int CSmtp::SendData(CSmtpMessage &msg)
|
||
|
{
|
||
|
int nRet;
|
||
|
String strMsg;
|
||
|
|
||
|
// Send the DATA command. We need a 354 to proceed
|
||
|
nRet = SendCmd(_T("DATA\r\n"));
|
||
|
if (nRet != 354)
|
||
|
{
|
||
|
RaiseError(nRet);
|
||
|
return nRet;
|
||
|
}
|
||
|
|
||
|
// Parse the body of the email message
|
||
|
msg.Parse(strMsg);
|
||
|
strMsg += _T("\r\n.\r\n");
|
||
|
|
||
|
// Send the body and expect a 250 OK reply.
|
||
|
nRet = SendCmd((LPTSTR)strMsg.c_str());
|
||
|
if (nRet != 250) RaiseError(nRet);
|
||
|
|
||
|
return (nRet == 250) ? 0:nRet;
|
||
|
}
|