1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468 |
- //////////////////////////////////////////////////////////////////////
- /*
- Smtp.cpp: implementation of the CSmtp and CSmtpMessage classes
- Written by Robert Simpson ([email protected])
- 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 ([email protected])");
- 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: "[email protected]"
- // <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;
- }
|