| 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 stringvoid 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 charvoid 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 streamvoid 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 responseint 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 transferBOOL CSmtp::SmtpProgress(LPSTR /*pszBuffer*/, DWORD /*dwBytesSent*/, DWORD /*dwBytesTotal*/){  return TRUE; // Continue sending the data}// Raises a SmtpError() conditionint 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 withint 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 functionint 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;}
 |