Smtp.cpp 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468
  1. //////////////////////////////////////////////////////////////////////
  2. /*
  3. Smtp.cpp: implementation of the CSmtp and CSmtpMessage classes
  4. Written by Robert Simpson ([email protected])
  5. Created 11/1/2000
  6. Version 1.7 -- Last Modified 06/18/2001
  7. 1.7 - Modified the code that gets the GMT offset and the code that
  8. parses the date/time as per Noa Karsten's suggestions on
  9. codeguru.
  10. - Added an FD_ZERO(&set) to the last part of SendCmd(), since
  11. I use the set twice and only zero it out once. Submitted by
  12. Marc Allen.
  13. - Removed the requirement that a message have a body and/or an
  14. attachment. This allows for sending messages with only a
  15. subject line. Submitted by Marc Allen.
  16. 1.6 - Apparently older versions of the STL do not have the clear()
  17. method for basic_string's. I modified the code to use
  18. erase() instead.
  19. - Added #include <atlbase.h> to the smtp.h file, which will
  20. allow any app to use these classes without problems.
  21. 1.5 - Guess I should have checked EncodeQuotedPrintable() as well,
  22. since it did the same thing BreakMessage() did in adding an
  23. extranneous CRLF to the end of any text it processed. Fixed.
  24. 1.4 - BreakMesage() added an extranneous CRLF to the end of any
  25. text it processed, which is now fixed. Certainly not a big
  26. deal, but it caused text attachments to not be 100% identical
  27. to the original.
  28. 1.3 - Added a new class, CSmtpMimePart, to which the CSmtpAttachment
  29. and CSmtpMessageBody classes inherit. This was done for
  30. future expansion. CSmtpMimePart has a new ContentId string
  31. value for optionally assigning a unique content ID value to
  32. body parts and attachments. This was done to support the
  33. multipart/related enhancement
  34. - Support for multipart/related messages, which can be used
  35. for sending html messages with embedded images.
  36. - Modifed CSmtpMessage, adding a new MimeType member variable
  37. so the user can specify a certain type of MIME format to use
  38. when coding the message.
  39. - Fixed a bug where multipart/alternative messages with multiple
  40. message bodies were not properly processed when attachments
  41. were also included in the message.
  42. - Some small optimizations during the CSmtpMessage::Parse routine
  43. 1.2 - Vastly improved the time it takes to break a message,
  44. which was dog slow with large attachments. My bad.
  45. - Added another overridable, SmtpProgress() which is
  46. called during the CSmtp::SendCmd() function when there
  47. is a large quantity of data being sent over the wire.
  48. Added CMD_BLOCK_SIZE to support the above new feature
  49. - Added support for UNICODE
  50. - Added the CSmtpAttachment class for better control and
  51. expandability for attachments.
  52. - Added alternative implementations for CSmtp::SendMessage
  53. which make it easier to send simple messages via a single
  54. function call.
  55. - Added a constructor to CSmtpAddress for assigning default
  56. values during initialization.
  57. - Added a #pragma comment(lib,"wsock32.lib") to the smtp.h
  58. file so existing projects don't have to have their linker
  59. options modified.
  60. 1.1 - Rearranged the headers so they are written out as:
  61. From,To,Subject,Date,MimeVersion,
  62. followed by all remaining headers
  63. - Modified the class to support multipart/alternative with
  64. multiple message bodies.
  65. Note that CSimpleMap does not sort the key values, and CSmtp
  66. takes advantage of this by writing the headers out in the reverse
  67. order of how they will be parsed before being sent to the SMTP
  68. server. If you modify the code to use std::map or any other map
  69. class, the headers may be alphabetized by key, which may cause
  70. some mail clients to show the headers in the body of the message
  71. or cause other undesirable results when viewing the message.
  72. */
  73. //////////////////////////////////////////////////////////////////////
  74. #include "Smtp.h"
  75. //////////////////////////////////////////////////////////////////////
  76. // Construction/Destruction for CSmtpMessageBody
  77. //////////////////////////////////////////////////////////////////////
  78. CSmtpMessageBody::CSmtpMessageBody(LPCTSTR pszBody, LPCTSTR pszEncoding, LPCTSTR pszCharset, EncodingEnum encode)
  79. {
  80. // Set the default message encoding method
  81. // To transfer html messages, make Encoding = _T("text/html")
  82. if (pszEncoding) Encoding = pszEncoding;
  83. if (pszCharset) Charset = pszCharset;
  84. if (pszBody) Data = pszBody;
  85. TransferEncoding = encode;
  86. }
  87. const CSmtpMessageBody& CSmtpMessageBody::operator=(LPCTSTR pszBody)
  88. {
  89. Data = pszBody;
  90. return *this;
  91. }
  92. const CSmtpMessageBody& CSmtpMessageBody::operator=(const String& strBody)
  93. {
  94. Data = strBody;
  95. return *this;
  96. }
  97. //////////////////////////////////////////////////////////////////////
  98. // Construction/Destruction for CSmtpAttachment
  99. //////////////////////////////////////////////////////////////////////
  100. CSmtpAttachment::CSmtpAttachment(LPCTSTR pszFilename, LPCTSTR pszAltName, BOOL bIsInline, LPCTSTR pszEncoding, LPCTSTR pszCharset, EncodingEnum encode)
  101. {
  102. if (pszFilename) FileName = pszFilename;
  103. if (pszAltName) AltName = pszAltName;
  104. if (pszEncoding) Encoding = pszEncoding;
  105. if (pszCharset) Charset = pszCharset;
  106. TransferEncoding = encode;
  107. Inline = bIsInline;
  108. }
  109. const CSmtpAttachment& CSmtpAttachment::operator=(LPCTSTR pszFilename)
  110. {
  111. FileName = pszFilename;
  112. return *this;
  113. }
  114. const CSmtpAttachment& CSmtpAttachment::operator=(const String& strFilename)
  115. {
  116. FileName = strFilename;
  117. return *this;
  118. }
  119. //////////////////////////////////////////////////////////////////////
  120. // Construction/Destruction for CSmtpAddress
  121. //////////////////////////////////////////////////////////////////////
  122. CSmtpAddress::CSmtpAddress(LPCTSTR pszAddress, LPCTSTR pszName)
  123. {
  124. if (pszAddress) Address = pszAddress;
  125. if (pszName) Name = pszName;
  126. }
  127. const CSmtpAddress& CSmtpAddress::operator=(LPCTSTR pszAddress)
  128. {
  129. Address = pszAddress;
  130. return *this;
  131. }
  132. const CSmtpAddress& CSmtpAddress::operator=(const String& strAddress)
  133. {
  134. Address = strAddress;
  135. return *this;
  136. }
  137. //////////////////////////////////////////////////////////////////////
  138. // Construction/Destruction for CSmtpMessage
  139. //////////////////////////////////////////////////////////////////////
  140. CSmtpMessage::CSmtpMessage()
  141. {
  142. TIME_ZONE_INFORMATION tzi;
  143. DWORD dwRet;
  144. long Offset;
  145. // Get local time and timezone offset
  146. GetLocalTime(&Timestamp);
  147. GMTOffset = 0;
  148. dwRet = GetTimeZoneInformation(&tzi);
  149. Offset = tzi.Bias;
  150. if (dwRet == TIME_ZONE_ID_STANDARD) Offset += tzi.StandardBias;
  151. if (dwRet == TIME_ZONE_ID_DAYLIGHT) Offset += tzi.DaylightBias;
  152. GMTOffset = -((Offset / 60) * 100 + (Offset % 60));
  153. MimeType = mimeGuess;
  154. }
  155. // Write all the headers to the e-mail message.
  156. // This is done just before sending it, when we're sure the user wants it to go out.
  157. void CSmtpMessage::CommitHeaders()
  158. {
  159. TCHAR szTime[64] = {0};
  160. TCHAR szDate[64] = {0};
  161. TCHAR szOut[1024] = {0};
  162. String strHeader;
  163. String strValue;
  164. int n;
  165. // Assign a few standard headers to the message
  166. strHeader = _T("X-Priority");
  167. strValue = _T("3 (Normal)");
  168. // Only add the key if it doesn't exist already in the headers map
  169. if (Headers.FindKey(strHeader) == -1) Headers.Add(strHeader,strValue);
  170. strHeader = _T("X-MSMail-Priority");
  171. strValue = _T("Normal");
  172. if (Headers.FindKey(strHeader) == -1) Headers.Add(strHeader,strValue);
  173. strHeader = _T("X-Mailer");
  174. strValue = _T("ATL CSmtp Class Mailer by Robert Simpson ([email protected])");
  175. if (Headers.FindKey(strHeader) == -1) Headers.Add(strHeader,strValue);
  176. strHeader = _T("Importance");
  177. strValue = _T("Normal");
  178. if (Headers.FindKey(strHeader) == -1) Headers.Add(strHeader,strValue);
  179. // Get the time/date stamp and GMT offset for the Date header.
  180. GetDateFormat(MAKELCID(LANG_ENGLISH, SORT_DEFAULT),0,&Timestamp,_T("ddd, d MMM yyyy"),szDate,64);
  181. GetTimeFormat(MAKELCID(LANG_ENGLISH, SORT_DEFAULT),0,&Timestamp,_T("H:mm:ss"),szTime,64);
  182. // Add the date/time stamp to the message headers
  183. wsprintf(szOut,_T("%s %s %c%4.4d"),szDate,szTime,(GMTOffset>0)?'+':'-',GMTOffset);
  184. strHeader = _T("Date");
  185. strValue = szOut;
  186. Headers.Remove(strHeader);
  187. Headers.Add(strHeader,strValue);
  188. // Write out the subject header
  189. strHeader = _T("Subject");
  190. strValue = Subject;
  191. Headers.Remove(strHeader);
  192. Headers.Add(strHeader,strValue);
  193. // Write out the TO header
  194. strValue.erase();
  195. strHeader = _T("To");
  196. if (Recipient.Name.length())
  197. {
  198. wsprintf(szOut,_T("\"%s\" "),Recipient.Name.c_str());
  199. strValue += szOut;
  200. }
  201. if (Recipient.Address.length())
  202. {
  203. wsprintf(szOut,_T("<%s>"),Recipient.Address.c_str());
  204. strValue += szOut;
  205. }
  206. // Write out all the CC'd names
  207. for (n = 0;n < CC.GetSize();n++)
  208. {
  209. if (strValue.length()) strValue += _T(",\r\n\t");
  210. if (CC[n].Name.length())
  211. {
  212. wsprintf(szOut,_T("\"%s\" "),CC[n].Name.c_str());
  213. strValue += szOut;
  214. }
  215. wsprintf(szOut,_T("<%s>"),CC[n].Address.c_str());
  216. strValue += szOut;
  217. }
  218. Headers.Remove(strHeader);
  219. Headers.Add(strHeader,strValue);
  220. // Write out the FROM header
  221. strValue.erase();
  222. strHeader = _T("From");
  223. if (Sender.Name.length())
  224. {
  225. wsprintf(szOut,_T("\"%s\" "),Sender.Name.c_str());
  226. strValue += szOut;
  227. }
  228. wsprintf(szOut,_T("<%s>"),Sender.Address.c_str());
  229. strValue += szOut;
  230. Headers.Remove(strHeader);
  231. Headers.Add(strHeader,strValue);
  232. }
  233. // Parse a message into a single string
  234. void CSmtpMessage::Parse(String& strDest)
  235. {
  236. String strHeader;
  237. String strValue;
  238. String strTemp;
  239. String strBoundary;
  240. String strInnerBoundary;
  241. TCHAR szOut[1024];
  242. int n;
  243. strDest.erase();
  244. // Get a count of the sections to see if this will be a multipart message
  245. n = Message.GetSize();
  246. n += Attachments.GetSize();
  247. // Remove this header in case the message is being reused
  248. strHeader = _T("Content-Type");
  249. Headers.Remove(strHeader);
  250. // If we have more than one section, then this is a multipart MIME message
  251. if (n > 1)
  252. {
  253. wsprintf(szOut,_T("CSmtpMsgPart123X456_000_%8.8X"),GetTickCount());
  254. strBoundary = szOut;
  255. lstrcpy(szOut,_T("multipart/"));
  256. if (MimeType == mimeGuess)
  257. {
  258. if (Attachments.GetSize() == 0) MimeType = mimeAlternative;
  259. else MimeType = mimeMixed;
  260. }
  261. switch(MimeType)
  262. {
  263. case mimeAlternative:
  264. lstrcat(szOut,_T("alternative"));
  265. break;
  266. case mimeMixed:
  267. lstrcat(szOut,_T("mixed"));
  268. break;
  269. case mimeRelated:
  270. lstrcat(szOut,_T("related"));
  271. break;
  272. }
  273. lstrcat(szOut,_T(";\r\n\tboundary=\""));
  274. lstrcat(szOut,strBoundary.c_str());
  275. lstrcat(szOut,_T("\""));
  276. strValue = szOut;
  277. Headers.Add(strHeader,strValue);
  278. }
  279. strHeader = _T("MIME-Version");
  280. strValue = MIME_VERSION;
  281. Headers.Remove(strHeader);
  282. Headers.Add(strHeader,strValue);
  283. // Remove any message ID in the header and replace it with this message ID, if it exists
  284. strHeader = _T("Message-ID");
  285. Headers.Remove(strHeader);
  286. if (MessageId.length())
  287. {
  288. wsprintf(szOut,_T("<%s>"),MessageId.c_str());
  289. strValue = szOut;
  290. Headers.Add(strHeader,strValue);
  291. }
  292. // Finalize the message headers
  293. CommitHeaders();
  294. // Write out all the message headers -- done backwards on purpose!
  295. for (n = Headers.GetSize();n > 0;n--)
  296. {
  297. wsprintf(szOut,_T("%s: %s\r\n"),Headers.GetKeyAt(n-1).c_str(),Headers.GetValueAt(n-1).c_str());
  298. strDest += szOut;
  299. }
  300. if (strBoundary.length())
  301. {
  302. wsprintf(szOut,_T("\r\n%s\r\n"),MULTIPART_MESSAGE);
  303. strDest += szOut;
  304. }
  305. // If we have attachments and multiple message bodies, create a new multipart section
  306. // This is done so we can display our multipart/alternative section separate from the
  307. // main multipart/mixed environment, and support both attachments and alternative bodies.
  308. if (Attachments.GetSize() && Message.GetSize() > 1 && strBoundary.length())
  309. {
  310. wsprintf(szOut,_T("CSmtpMsgPart123X456_001_%8.8X"),GetTickCount());
  311. strInnerBoundary = szOut;
  312. wsprintf(szOut,_T("\r\n--%s\r\nContent-Type: multipart/alternative;\r\n\tboundary=\"%s\"\r\n"),strBoundary.c_str(),strInnerBoundary.c_str());
  313. strDest += szOut;
  314. }
  315. for (n = 0;n < Message.GetSize();n++)
  316. {
  317. // If we're multipart, then write the boundary line
  318. if (strBoundary.length() || strInnerBoundary.length())
  319. {
  320. strDest += _T("\r\n--");
  321. // If we have an inner boundary, write that one. Otherwise write the outer one
  322. if (strInnerBoundary.length()) strDest += strInnerBoundary;
  323. else strDest += strBoundary;
  324. strDest += _T("\r\n");
  325. }
  326. strValue.erase();
  327. strDest += _T("Content-Type: ");
  328. strDest += Message[n].Encoding;
  329. // Include the character set if the message is text
  330. if (_tcsnicmp(Message[n].Encoding.c_str(),_T("text/"),5) == 0)
  331. {
  332. wsprintf(szOut,_T(";\r\n\tcharset=\"%s\""),Message[n].Charset.c_str());
  333. strDest += szOut;
  334. }
  335. strDest += _T("\r\n");
  336. // Encode the message
  337. strValue = Message[n].Data;
  338. EncodeMessage(Message[n].TransferEncoding,strValue,strTemp);
  339. // Write out the encoding method used and the encoded message
  340. strDest += _T("Content-Transfer-Encoding: ");
  341. strDest += strTemp;
  342. // If the message body part has a content ID, write it out
  343. if (Message[n].ContentId.length())
  344. {
  345. wsprintf(szOut,_T("\r\nContent-ID: <%s>"),Message[n].ContentId.c_str());
  346. strDest += szOut;
  347. }
  348. strDest += _T("\r\n\r\n");
  349. strDest += strValue;
  350. }
  351. // If we have multiple message bodies, write out the trailing inner end sequence
  352. if (strInnerBoundary.length())
  353. {
  354. wsprintf(szOut,_T("\r\n--%s--\r\n"),strInnerBoundary.c_str());
  355. strDest += szOut;
  356. }
  357. // Process any attachments
  358. for (n = 0;n < Attachments.GetSize();n++)
  359. {
  360. DWORD dwBytes = 0;
  361. CRegKey cKey;
  362. TCHAR szFilename[MAX_PATH] = {0};
  363. // Get the filename of the attachment
  364. strValue = Attachments[n].FileName;
  365. // Open the file
  366. lstrcpy(szFilename,strValue.c_str());
  367. HANDLE hFile = CreateFile(szFilename,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
  368. if (hFile != INVALID_HANDLE_VALUE)
  369. {
  370. // Get the size of the file, allocate the memory and read the contents.
  371. DWORD dwSize = GetFileSize(hFile,NULL);
  372. LPBYTE pData = (LPBYTE)malloc(dwSize + 1);
  373. ZeroMemory(pData,dwSize+1);
  374. if (ReadFile(hFile,pData,dwSize,&dwBytes,NULL))
  375. {
  376. // Write out our boundary marker
  377. if (strBoundary.length())
  378. {
  379. wsprintf(szOut,_T("\r\n--%s\r\n"),strBoundary.c_str());
  380. strDest += szOut;
  381. }
  382. // If no alternate name is supplied, strip the path to get the base filename
  383. LPTSTR pszFile;
  384. if (!Attachments[n].AltName.length())
  385. {
  386. // Strip the path from the filename
  387. pszFile = _tcsrchr(szFilename,'\\');
  388. if (!pszFile) pszFile = szFilename;
  389. else pszFile ++;
  390. }
  391. else pszFile = (LPTSTR)Attachments[n].AltName.c_str();
  392. // Set the content type for the attachment.
  393. TCHAR szType[MAX_PATH] = {0};
  394. lstrcpy(szType,_T("application/octet-stream"));
  395. // Check the registry for a content type that overrides the above default
  396. LPTSTR pszExt = _tcschr(pszFile,'.');
  397. if (pszExt)
  398. {
  399. if (!cKey.Open(HKEY_CLASSES_ROOT,pszExt,KEY_READ))
  400. {
  401. DWORD dwSize = MAX_PATH;
  402. cKey.QueryValue(_T("Content Type"), NULL, szType, &dwSize);
  403. cKey.Close();
  404. }
  405. }
  406. // If the attachment has a specific encoding method, use it instead
  407. if (Attachments[n].Encoding.length())
  408. lstrcpy(szType,Attachments[n].Encoding.c_str());
  409. // Write out the content type and attachment types to the message
  410. wsprintf(szOut,_T("Content-Type: %s"),szType);
  411. strDest += szOut;
  412. // If the content type is text, write the charset
  413. if (_tcsnicmp(szType,_T("text/"),5) == 0)
  414. {
  415. wsprintf(szOut,_T(";\r\n\tcharset=\"%s\""),Attachments[n].Charset.c_str());
  416. strDest += szOut;
  417. }
  418. wsprintf(szOut,_T(";\r\n\tname=\"%s\"\r\n"),pszFile);
  419. strDest += szOut;
  420. // Encode the attachment
  421. EncodeMessage(Attachments[n].TransferEncoding,strValue,strTemp,pData,dwSize);
  422. // Write out the transfer encoding method
  423. wsprintf(szOut,_T("Content-Transfer-Encoding: %s\r\n"),strTemp.c_str());
  424. strDest += szOut;
  425. // Write out the attachment's disposition
  426. strDest += _T("Content-Disposition: ");
  427. if (Attachments[n].Inline) strDest += _T("inline");
  428. else strDest += _T("attachment");
  429. strDest += _T(";\r\n\tfilename=\"");
  430. strDest += pszFile;
  431. // If the attachment has a content ID, write it out
  432. if (Attachments[n].ContentId.length())
  433. {
  434. wsprintf(szOut,_T("\r\nContent-ID: <%s>"),Attachments[n].ContentId.c_str());
  435. strDest += szOut;
  436. }
  437. strDest += _T("\r\n\r\n");
  438. // Write out the encoded attachment
  439. strDest += strValue;
  440. strTemp.erase();
  441. strValue.erase();
  442. }
  443. // Close the file and clear the temp buffer
  444. CloseHandle(hFile);
  445. free(pData);
  446. }
  447. }
  448. // If we are multipart, write out the trailing end sequence
  449. if (strBoundary.length())
  450. {
  451. wsprintf(szOut,_T("\r\n--%s--\r\n"),strBoundary.c_str());
  452. strDest += szOut;
  453. }
  454. }
  455. // Parses text into quoted-printable lines.
  456. // See RFC 1521 for full details on how this works.
  457. void CSmtpMessage::EncodeQuotedPrintable(String& strDest, String& strSrc)
  458. {
  459. String strTemp;
  460. String strTemp2;
  461. LPTSTR pszTok1;
  462. LPTSTR pszTok2;
  463. TCHAR szSub[16];
  464. TCHAR ch;
  465. int n;
  466. strDest.erase();
  467. if (!strSrc.length()) return;
  468. // Change = signs and non-printable characters to =XX
  469. pszTok1 = (LPTSTR)strSrc.c_str();
  470. pszTok2 = pszTok1;
  471. do
  472. {
  473. if (*pszTok2 == '=' || *pszTok2 > 126 ||
  474. (*pszTok2 < 32 && (*pszTok2 != '\r' && *pszTok2 != '\n' && *pszTok2 != '\t')))
  475. {
  476. ch = *pszTok2;
  477. *pszTok2 = 0;
  478. strTemp += pszTok1;
  479. *pszTok2 = ch;
  480. wsprintf(szSub,_T("=%2.2X"),(BYTE)*pszTok2);
  481. strTemp += szSub;
  482. pszTok1 = pszTok2 + 1;
  483. }
  484. pszTok2 ++;
  485. } while (pszTok2 && *pszTok2);
  486. // Append anything left after the search
  487. if (_tcslen(pszTok1)) strTemp += pszTok1;
  488. pszTok1 = (LPTSTR)strTemp.c_str();
  489. while (pszTok1)
  490. {
  491. pszTok2 = _tcschr(pszTok1,'\r');
  492. if (pszTok2) *pszTok2 = 0;
  493. while (1)
  494. {
  495. if (_tcslen(pszTok1) > 76)
  496. {
  497. n = 75; // Breaking at the 75th character
  498. if (pszTok1[n-1] == '=') n -= 1; // If the last character is an =, don't break the line there
  499. else if (pszTok1[n-2] == '=') n -= 2; // If we're breaking in the middle of a = sequence, back up!
  500. // Append the first section of the line to the total string
  501. ch = pszTok1[n];
  502. pszTok1[n] = 0;
  503. strDest += pszTok1;
  504. pszTok1[n] = ch;
  505. strDest += _T("=\r\n");
  506. pszTok1 += n;
  507. }
  508. else // Line is less than or equal to 76 characters
  509. {
  510. n = (int)_tcslen(pszTok1); // If we have some trailing data, process it.
  511. if (n)
  512. {
  513. if (pszTok1[n-1] == ' ' || pszTok1[n-1] == '\t') // Last character is a space or tab
  514. {
  515. wsprintf(szSub,_T("=%2.2X"),(BYTE)pszTok1[n-1]);
  516. // Replace the last character with an =XX sequence
  517. pszTok1[n-1] = 0;
  518. strTemp2 = pszTok1;
  519. strTemp2 += szSub;
  520. // Since the string may now be larger than 76 characters, we have to reprocess the line
  521. pszTok1 = (LPTSTR)strTemp2.c_str();
  522. }
  523. else // Last character is not a space or tab
  524. {
  525. strDest += pszTok1;
  526. if (pszTok2) strDest += _T("\r\n");
  527. break; // Exit the loop which processes this line, and move to the next line
  528. }
  529. }
  530. else
  531. {
  532. if (pszTok2) strDest += _T("\r\n");
  533. break; // Move to the next line
  534. }
  535. }
  536. }
  537. if (pszTok2)
  538. {
  539. *pszTok2 = '\r';
  540. pszTok2 ++;
  541. if (*pszTok2 == '\n') pszTok2 ++;
  542. }
  543. pszTok1 = pszTok2;
  544. }
  545. }
  546. // Breaks a message's lines into a maximum of 76 characters
  547. // Does some semi-intelligent wordwrapping to ensure the text is broken properly.
  548. // If a line contains no break characters, it is forcibly truncated at the 76th char
  549. void CSmtpMessage::BreakMessage(String& strDest, String& strSrc, int nLength)
  550. {
  551. String strTemp = strSrc;
  552. LPTSTR pszTok1;
  553. LPTSTR pszTok2;
  554. LPTSTR pszBreak;
  555. LPTSTR pszBreaks = _T(" -;.,?!");
  556. TCHAR ch;
  557. int nLen;
  558. strDest.erase();
  559. if (!strSrc.length()) return;
  560. nLen = (int)strTemp.length();
  561. nLen += (nLen / 60) * 2;
  562. strDest.reserve(nLen);
  563. // Process each line one at a time
  564. pszTok1 = (LPTSTR)strTemp.c_str();
  565. while (pszTok1)
  566. {
  567. pszTok2 = _tcschr(pszTok1,'\r');
  568. if (pszTok2) *pszTok2 = 0;
  569. BOOL bNoBreaks = (!_tcspbrk(pszTok1,pszBreaks));
  570. nLen = (int)_tcslen(pszTok1);
  571. while (nLen > nLength)
  572. {
  573. // Start at the 76th character, and move backwards until we hit a break character
  574. pszBreak = &pszTok1[nLength - 1];
  575. // If there are no break characters in the string, skip the backward search for them!
  576. if (!bNoBreaks)
  577. {
  578. while (!_tcschr(pszBreaks,*pszBreak) && pszBreak > pszTok1)
  579. pszBreak--;
  580. }
  581. pszBreak ++;
  582. ch = *pszBreak;
  583. *pszBreak = 0;
  584. strDest += pszTok1;
  585. strDest += _T("\r\n");
  586. *pszBreak = ch;
  587. nLen -= (int)(pszBreak - pszTok1);
  588. // Advance the search to the next segment of text after the break
  589. pszTok1 = pszBreak;
  590. }
  591. strDest += pszTok1;
  592. if (pszTok2)
  593. {
  594. strDest += _T("\r\n");
  595. *pszTok2 = '\r';
  596. pszTok2 ++;
  597. if (*pszTok2 == '\n') pszTok2 ++;
  598. }
  599. pszTok1 = pszTok2;
  600. }
  601. }
  602. // Makes the message into a 7bit stream
  603. void CSmtpMessage::Make7Bit(String& strDest, String& strSrc)
  604. {
  605. LPTSTR pszTok;
  606. strDest = strSrc;
  607. pszTok = (LPTSTR)strDest.c_str();
  608. do
  609. {
  610. // Replace any characters above 126 with a ? character
  611. if (*pszTok > 126 || *pszTok < 0)
  612. *pszTok = '?';
  613. pszTok ++;
  614. } while (pszTok && *pszTok);
  615. }
  616. // Encodes a message or binary stream into a properly-formatted message
  617. // Takes care of breaking the message into 76-byte lines of text, encoding to
  618. // Base64, quoted-printable and etc.
  619. void CSmtpMessage::EncodeMessage(EncodingEnum code, String& strMsg, String& strMethod, LPBYTE pByte, DWORD dwSize)
  620. {
  621. String strTemp;
  622. LPTSTR pszTok1;
  623. LPTSTR pszTok2;
  624. LPSTR pszBuffer = NULL;
  625. DWORD dwStart = GetTickCount();
  626. if (!pByte)
  627. {
  628. pszBuffer = (LPSTR)malloc(strMsg.length() + 1);
  629. _T2A(pszBuffer,strMsg.c_str());
  630. pByte = (LPBYTE)pszBuffer;
  631. dwSize = (DWORD)strMsg.length();
  632. }
  633. // Guess the encoding scheme if we have to
  634. if (code == encodeGuess) code = GuessEncoding(pByte, dwSize);
  635. switch(code)
  636. {
  637. case encodeQuotedPrintable:
  638. strMethod = _T("quoted-printable");
  639. pszTok1 = (LPTSTR)malloc((dwSize+1) * sizeof(TCHAR));
  640. _A2T(pszTok1,(LPSTR)pByte);
  641. strMsg = pszTok1;
  642. free(pszTok1);
  643. EncodeQuotedPrintable(strTemp, strMsg);
  644. break;
  645. case encodeBase64:
  646. strMethod = _T("base64");
  647. {
  648. CBase64 cvt;
  649. cvt.Encode(pByte, dwSize);
  650. LPSTR pszTemp = (LPSTR)cvt.EncodedMessage();
  651. pszTok1 = (LPTSTR)malloc((lstrlenA(pszTemp)+1) * sizeof(TCHAR));
  652. _A2T(pszTok1,pszTemp);
  653. }
  654. strMsg = pszTok1;
  655. free(pszTok1);
  656. BreakMessage(strTemp, strMsg);
  657. break;
  658. case encode7Bit:
  659. strMethod = _T("7bit");
  660. pszTok1 = (LPTSTR)malloc((dwSize+1) * sizeof(TCHAR));
  661. _A2T(pszTok1,(LPSTR)pByte);
  662. strMsg = pszTok1;
  663. free(pszTok1);
  664. Make7Bit(strTemp, strMsg);
  665. strMsg = strTemp;
  666. BreakMessage(strTemp, strMsg);
  667. break;
  668. case encode8Bit:
  669. strMethod = _T("8bit");
  670. pszTok1 = (LPTSTR)malloc((dwSize+1) * sizeof(TCHAR));
  671. _A2T(pszTok1,(LPSTR)pByte);
  672. strMsg = pszTok1;
  673. free(pszTok1);
  674. BreakMessage(strTemp, strMsg);
  675. break;
  676. }
  677. if (pszBuffer) free(pszBuffer);
  678. strMsg.erase();
  679. // Parse the message text, replacing CRLF. sequences with CRLF.. sequences
  680. pszTok1 = (LPTSTR)strTemp.c_str();
  681. do
  682. {
  683. pszTok2 = _tcsstr(pszTok1,_T("\r\n."));
  684. if (pszTok2)
  685. {
  686. *pszTok2 = 0;
  687. strMsg += pszTok1;
  688. *pszTok2 = '\r';
  689. strMsg += _T("\r\n..");
  690. pszTok1 = pszTok2 + 3;
  691. }
  692. } while (pszTok2);
  693. strMsg += pszTok1;
  694. TCHAR szOut[MAX_PATH] = {0};
  695. wsprintf(szOut,_T("Encoding took %dms\n"),GetTickCount() - dwStart);
  696. OutputDebugString(szOut);
  697. }
  698. // Makes a best-guess of the proper encoding to use for this stream of bytes
  699. // It does this by counting the # of lines, the # of 8bit bytes and the number
  700. // of 7bit bytes. It also records the line and the count of lines over
  701. // 76 characters.
  702. // If the stream is 90% or higher 7bit, it uses a text encoding method. If the stream
  703. // is all at or under 76 characters, it uses 7bit or 8bit, depending on the content.
  704. // If the lines are longer than 76 characters, use quoted printable.
  705. // If the stream is under 90% 7bit characters, use base64 encoding.
  706. EncodingEnum CSmtpMessage::GuessEncoding(LPBYTE pByte, DWORD dwLen)
  707. {
  708. int n7Bit = 0;
  709. int n8Bit = 0;
  710. int nLineStart = 0;
  711. int nLinesOver76 = 0;
  712. int nLines = 0;
  713. DWORD n;
  714. // Count the content type, byte by byte
  715. for (n = 0;n < dwLen; n++)
  716. {
  717. if (pByte[n] > 126 || (pByte[n] < 32 && pByte[n] != '\t' && pByte[n] != '\r' && pByte[n] != '\n'))
  718. n8Bit ++;
  719. else n7Bit ++;
  720. // New line? If so, record the line size
  721. if (pByte[n] == '\r')
  722. {
  723. nLines ++;
  724. nLineStart = (n - nLineStart) - 1;
  725. if (nLineStart > 76) nLinesOver76 ++;
  726. nLineStart = n + 1;
  727. }
  728. }
  729. // Determine if it is mostly 7bit data
  730. if ((n7Bit * 100) / dwLen > 89)
  731. {
  732. // At least 90% text, so use a text-base encoding scheme
  733. if (!nLinesOver76)
  734. {
  735. if (!n8Bit) return encode7Bit;
  736. else return encode8Bit;
  737. }
  738. else return encodeQuotedPrintable;
  739. }
  740. return encodeBase64;
  741. }
  742. //////////////////////////////////////////////////////////////////////
  743. // Construction/Destruction for CSmtp
  744. //////////////////////////////////////////////////////////////////////
  745. CSmtp::CSmtp()
  746. {
  747. LPSERVENT pEnt;
  748. m_bExtensions = TRUE; // Use ESMTP if possible
  749. m_dwCmdTimeout = 30; // Default to 30 second timeout
  750. m_hSocket = INVALID_SOCKET;
  751. m_bConnected = m_bUsingExtensions = FALSE;
  752. // Try and get the SMTP service entry by name
  753. pEnt = getservbyname("SMTP","tcp");
  754. if (pEnt) m_wSmtpPort = pEnt->s_port;
  755. else m_wSmtpPort = htons(25);
  756. }
  757. CSmtp::~CSmtp()
  758. {
  759. // Make sure any open connections are shut down
  760. Close();
  761. }
  762. // Connects to a SMTP server. Returns TRUE if successfully connected, or FALSE otherwise.
  763. BOOL CSmtp::Connect(LPTSTR pszServer)
  764. {
  765. SOCKADDR_IN addr;
  766. int nRet;
  767. CHAR szHost[MAX_PATH] = {0};
  768. _T2A(szHost,pszServer);
  769. // Shut down any active connection
  770. Close();
  771. // test
  772. WORD wVersionRequested;
  773. WSADATA wsaData;
  774. wVersionRequested = MAKEWORD( 2, 1 );
  775. WSAStartup( wVersionRequested, &wsaData );
  776. // end test
  777. // Resolve the hostname
  778. addr.sin_family = AF_INET;
  779. addr.sin_port = m_wSmtpPort;
  780. addr.sin_addr.s_addr = inet_addr(szHost);
  781. if (addr.sin_addr.s_addr == INADDR_NONE)
  782. {
  783. LPHOSTENT pHost = gethostbyname(szHost);
  784. if (!pHost)
  785. {
  786. return FALSE;
  787. }
  788. addr.sin_addr.s_addr = *(LPDWORD)pHost->h_addr;
  789. }
  790. // Create a socket
  791. m_hSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
  792. if (m_hSocket == INVALID_SOCKET) return FALSE;
  793. // Connect to the host
  794. if (connect(m_hSocket,(LPSOCKADDR)&addr,sizeof(addr)) == SOCKET_ERROR)
  795. {
  796. Close();
  797. return FALSE;
  798. }
  799. // Get the initial response string
  800. nRet = SendCmd(NULL);
  801. if (nRet != 220)
  802. {
  803. RaiseError(nRet);
  804. Close();
  805. return FALSE;
  806. }
  807. // Send a HELLO message to the SMTP server
  808. if (SendHello())
  809. {
  810. Close();
  811. return FALSE;
  812. }
  813. return TRUE;
  814. }
  815. // Closes any active SMTP sessions and shuts down the socket.
  816. void CSmtp::Close()
  817. {
  818. if (m_hSocket != INVALID_SOCKET)
  819. {
  820. // If we're connected to a server, tell them we're quitting
  821. if (m_bConnected) SendQuitCmd();
  822. // Shutdown and close the socket
  823. shutdown(m_hSocket,2);
  824. closesocket(m_hSocket);
  825. }
  826. m_hSocket = INVALID_SOCKET;
  827. }
  828. // Send a command to the SMTP server and wait for a response
  829. int CSmtp::SendCmd(LPTSTR pszCmd)
  830. {
  831. USES_CONVERSION;
  832. FD_SET set;
  833. TIMEVAL tv;
  834. int nRet = 0;
  835. DWORD dwTick;
  836. CHAR szResult[CMD_RESPONSE_SIZE] = {0};
  837. LPSTR pszPos;
  838. LPSTR pszTok;
  839. BOOL bReportProgress = FALSE;
  840. LPSTR pszBuff;
  841. ZeroMemory(szResult,CMD_RESPONSE_SIZE);
  842. FD_ZERO(&set);
  843. // If we have a command to send, then send it.
  844. if (pszCmd)
  845. {
  846. pszBuff = (LPSTR)malloc(lstrlen(pszCmd)+1);
  847. _T2A(pszBuff,pszCmd);
  848. // Make sure the input buffer is clear before sending
  849. nRet = 1;
  850. while (nRet > 0)
  851. {
  852. FD_SET(m_hSocket,&set);
  853. tv.tv_sec = 0;
  854. tv.tv_usec = 0;
  855. nRet = select(1,&set,NULL,NULL,&tv);
  856. if (nRet == 1) nRet = recv(m_hSocket,szResult,CMD_RESPONSE_SIZE,0);
  857. }
  858. DWORD dwPosition = 0;
  859. DWORD dwLen = lstrlen(pszCmd);
  860. if (dwLen > CMD_BLOCK_SIZE) bReportProgress = TRUE;
  861. while (dwLen != dwPosition)
  862. {
  863. DWORD dwMax = min(CMD_BLOCK_SIZE,dwLen - dwPosition);
  864. nRet = send(m_hSocket,&pszBuff[dwPosition],dwMax,0);
  865. if (nRet == SOCKET_ERROR)
  866. {
  867. free(pszBuff);
  868. return nRet;
  869. }
  870. dwPosition += dwMax;
  871. if (bReportProgress)
  872. {
  873. if (!SmtpProgress(pszBuff,dwPosition,dwLen))
  874. {
  875. free(pszBuff);
  876. return -1;
  877. }
  878. }
  879. }
  880. // Wait for the CMD to finish being sent
  881. FD_ZERO(&set);
  882. FD_SET(m_hSocket,&set);
  883. nRet = select(1,NULL,&set,NULL,NULL);
  884. free(pszBuff);
  885. }
  886. // Prepare to receive a response
  887. ZeroMemory(szResult,CMD_RESPONSE_SIZE);
  888. pszPos = szResult;
  889. // Wait for the specified timeout for a full response string
  890. dwTick = GetTickCount();
  891. while (GetTickCount() - dwTick < (m_dwCmdTimeout * 1000))
  892. {
  893. FD_SET(m_hSocket,&set);
  894. tv.tv_sec = m_dwCmdTimeout - ((GetTickCount() - dwTick) / 1000);
  895. tv.tv_usec = 0;
  896. // Check the socket for readability
  897. nRet = select(1,&set,NULL,NULL,&tv);
  898. if (nRet == SOCKET_ERROR) break;
  899. // If the socket has data, read it.
  900. if (nRet == 1)
  901. {
  902. nRet = recv(m_hSocket,pszPos,CMD_RESPONSE_SIZE - (int)(pszPos - szResult),0);
  903. // Treats a graceful shutdown as an error
  904. if (nRet == 0) nRet = SOCKET_ERROR;
  905. if (nRet == SOCKET_ERROR) break;
  906. // Add the data to the total response string & check for a LF
  907. pszPos += nRet;
  908. pszTok = strrchr(szResult,'\n');
  909. if (pszTok)
  910. {
  911. // Truncate CRLF combination and exit our wait loop
  912. pszTok --;
  913. pszTok[0] = 0;
  914. break;
  915. }
  916. }
  917. }
  918. // Assign the response string
  919. m_strResult = A2CT(szResult);
  920. // Evaluate the numeric response code
  921. if (nRet && nRet != SOCKET_ERROR)
  922. {
  923. szResult[3] = 0;
  924. nRet = atoi(szResult);
  925. SmtpCommandResponse(pszCmd, nRet, (LPTSTR)m_strResult.c_str());
  926. }
  927. else nRet = -1;
  928. return nRet;
  929. }
  930. // Placeholder function -- overridable
  931. // This function is called when the SMTP server gives us an unexpected error
  932. // The <nError> value is the SMTP server's numeric error response, and the <pszErr>
  933. // is the descriptive error text
  934. //
  935. // <pszErr> may be NULL if the server failed to respond before the timeout!
  936. // <nError> will be -1 if a catastrophic failure occurred.
  937. //
  938. // Return 0, or nError. The return value is currently ignored.
  939. int CSmtp::SmtpError(int /*nError*/, LPTSTR pszErr)
  940. {
  941. #ifdef _DEBUG
  942. if (pszErr)
  943. {
  944. OutputDebugString(_T("SmtpError: "));
  945. OutputDebugString(pszErr);
  946. OutputDebugString(_T("\n"));
  947. }
  948. #endif
  949. return 0;
  950. }
  951. // Placeholder function -- overridable
  952. // Currently the only warning condition that this class is designed for is
  953. // an authentication failure. In that case, <nWarning> will be 535,
  954. // which is the RFC error for authentication failure. If authentication
  955. // fails, you can override this function to prompt the user for a new
  956. // username and password. Change the <m_strUser> and <m_strPass> member
  957. // variables and return TRUE to retry authentication.
  958. //
  959. // <pszWarning> may be NULL if the server did not respond in time!
  960. //
  961. // Return FALSE to abort authentication, or TRUE to retry.
  962. int CSmtp::SmtpWarning(int /*nWarning*/, LPTSTR pszWarning)
  963. {
  964. #ifdef _DEBUG
  965. if (pszWarning)
  966. {
  967. OutputDebugString(_T("SmtpWarning: "));
  968. OutputDebugString(pszWarning);
  969. OutputDebugString(_T("\n"));
  970. }
  971. #endif
  972. return 0;
  973. }
  974. // Placeholder function -- overridable
  975. // This is an informational callback only, and provides a means to inform
  976. // the caller as the SMTP session progresses.
  977. // ALWAYS check for NULL values on <pszCmd> and <pszResponse> before performing
  978. // any actions!
  979. // <nResponse> will be -1 if a catastrophic failure occurred, but that will
  980. // be raised in the SmtpError() event later on during processing.
  981. void CSmtp::SmtpCommandResponse(LPTSTR pszCmd, int /*nResponse*/, LPTSTR pszResponse)
  982. {
  983. #ifdef _DEBUG
  984. if (pszCmd)
  985. {
  986. TCHAR szOut[MAX_PATH+1] = {0};
  987. OutputDebugString(_T("SmtpCommand : "));
  988. while (lstrlen(pszCmd) > MAX_PATH)
  989. {
  990. lstrcpyn(szOut,pszCmd,MAX_PATH+1);
  991. OutputDebugString(szOut);
  992. Sleep(100);
  993. pszCmd += MAX_PATH;
  994. }
  995. OutputDebugString(pszCmd);
  996. }
  997. OutputDebugString(_T("SmtpResponse: "));
  998. OutputDebugString(pszResponse);
  999. OutputDebugString(_T("\n"));
  1000. #endif
  1001. }
  1002. // Placeholder function -- overridable
  1003. // This is a progress callback to indicate that data is being sent over the wire
  1004. // and that the operation may take some time.
  1005. // Return TRUE to continue sending, or FALSE to abort the transfer
  1006. BOOL CSmtp::SmtpProgress(LPSTR /*pszBuffer*/, DWORD /*dwBytesSent*/, DWORD /*dwBytesTotal*/)
  1007. {
  1008. return TRUE; // Continue sending the data
  1009. }
  1010. // Raises a SmtpError() condition
  1011. int CSmtp::RaiseError(int nError)
  1012. {
  1013. // If the error code is -1, something catastrophic happened
  1014. // so we're effectively not connected to any SMTP server.
  1015. if (nError == -1) m_bConnected = FALSE;
  1016. return SmtpError(nError, (LPTSTR)m_strResult.c_str());
  1017. }
  1018. // Warnings are recoverable errors that we may be able to continue working with
  1019. int CSmtp::RaiseWarning(int nWarning)
  1020. {
  1021. return SmtpWarning(nWarning, (LPTSTR)m_strResult.c_str());
  1022. }
  1023. // E-Mail's a message
  1024. // Returns 0 if successful, -1 if an internal error occurred, or a positive
  1025. // error value if the SMTP server gave an error or failure response.
  1026. int CSmtp::SendMessage(CSmtpMessage &msg)
  1027. {
  1028. int nRet;
  1029. int n;
  1030. // int nRecipients = 0;
  1031. int nRecipientCount = 0;
  1032. // Check if we have a sender
  1033. if (!msg.Sender.Address.length()) return -1;
  1034. // Check if we have recipients
  1035. if (!msg.Recipient.Address.length() && !msg.CC.GetSize()) return -1;
  1036. // Check if we have a message body or attachments
  1037. // *** Commented out to remove the requirement that a message have a body or attachments
  1038. // if (!msg.Message.GetSize() && !msg.Attachments.GetSize()) return -1;
  1039. // Send the sender's address
  1040. nRet = SendFrom((LPTSTR)msg.Sender.Address.c_str());
  1041. if (nRet) return nRet;
  1042. // If we have a recipient, send it
  1043. nRecipientCount = 0; // Count of recipients
  1044. if (msg.Recipient.Address.length())
  1045. {
  1046. nRet = SendTo((LPTSTR)msg.Recipient.Address.c_str());
  1047. if (!nRet) nRecipientCount ++;
  1048. }
  1049. // If we have any CC's, send those.
  1050. for (n = 0;n < msg.CC.GetSize();n++)
  1051. {
  1052. nRet = SendTo((LPTSTR)msg.CC[n].Address.c_str());
  1053. if (!nRet) nRecipientCount ++;
  1054. }
  1055. // If we have any bcc's, send those.
  1056. for (n = 0;n < msg.BCC.GetSize();n++)
  1057. {
  1058. nRet = SendTo((LPTSTR)msg.BCC[n].Address.c_str());
  1059. if (!nRet) nRecipientCount ++;
  1060. }
  1061. // If we failed on all recipients, we must abort.
  1062. if (!nRecipientCount)
  1063. RaiseError(nRet);
  1064. else
  1065. nRet = SendData(msg);
  1066. return nRet;
  1067. }
  1068. // Simplified way to send a message.
  1069. // <pvAttachments> can be either an LPTSTR containing NULL terminated strings, in which
  1070. // case <dwAttachmentCount> should be zero, or <pvAttachments> can be an LPTSTR *
  1071. // containing an array of LPTSTR's, in which case <dwAttachmentCount> should equal the
  1072. // number of strings in the array.
  1073. int CSmtp::SendMessage(CSmtpAddress &addrFrom, CSmtpAddress &addrTo, LPCTSTR pszSubject, LPTSTR pszMessage, LPVOID pvAttachments, DWORD dwAttachmentCount)
  1074. {
  1075. CSmtpMessage message;
  1076. CSmtpMessageBody body;
  1077. CSmtpAttachment attach;
  1078. body = pszMessage;
  1079. message.Sender = addrFrom;
  1080. message.Recipient = addrTo;
  1081. message.Message.Add(body);
  1082. message.Subject = pszSubject;
  1083. // If the attachment count is zero, but the pvAttachments variable is not NULL,
  1084. // assume that the ppvAttachments variable is a string value containing NULL terminated
  1085. // strings. A double NULL ends the list.
  1086. // Example: LPTSTR pszAttachments = "foo.exe\0bar.zip\0autoexec.bat\0\0";
  1087. if (!dwAttachmentCount && pvAttachments)
  1088. {
  1089. LPTSTR pszAttachments = (LPTSTR)pvAttachments;
  1090. while (lstrlen(pszAttachments))
  1091. {
  1092. attach.FileName = pszAttachments;
  1093. message.Attachments.Add(attach);
  1094. pszAttachments = &pszAttachments[lstrlen(pszAttachments)];
  1095. }
  1096. }
  1097. // dwAttachmentCount is not zero, so assume pvAttachments is an array of LPTSTR's
  1098. // Example: LPTSTR *ppszAttachments = {"foo.exe","bar.exe","autoexec.bat"};
  1099. if (pvAttachments && dwAttachmentCount)
  1100. {
  1101. LPTSTR *ppszAttachments = (LPTSTR *)pvAttachments;
  1102. while (dwAttachmentCount-- && ppszAttachments)
  1103. {
  1104. attach.FileName = ppszAttachments[dwAttachmentCount];
  1105. message.Attachments.Add(attach);
  1106. }
  1107. }
  1108. return SendMessage(message);
  1109. }
  1110. // Yet an even simpler method for sending a message
  1111. // <pszAddrFrom> and <pszAddrTo> should be e-mail addresses with no decorations
  1112. // Example: "[email protected]"
  1113. // <pvAttachments> and <dwAttachmentCount> are described above in the alternative
  1114. // version of this function
  1115. int CSmtp::SendMessage(LPTSTR pszAddrFrom, LPTSTR pszAddrTo, LPTSTR pszSubject, LPTSTR pszMessage, LPVOID pvAttachments, DWORD dwAttachmentCount)
  1116. {
  1117. CSmtpAddress addrFrom(pszAddrFrom);
  1118. CSmtpAddress addrTo(pszAddrTo);
  1119. return SendMessage(addrFrom,addrTo,pszSubject,pszMessage,pvAttachments,dwAttachmentCount);
  1120. }
  1121. // Tell the SMTP server we're quitting
  1122. // Returns 0 if successful, or a positive
  1123. // error value if the SMTP server gave an error or failure response.
  1124. int CSmtp::SendQuitCmd()
  1125. {
  1126. int nRet;
  1127. if (!m_bConnected) return 0;
  1128. nRet = SendCmd(_T("QUIT\r\n"));
  1129. if (nRet != 221) RaiseError(nRet);
  1130. m_bConnected = FALSE;
  1131. return (nRet == 221) ? 0:nRet;
  1132. }
  1133. // Initiate a conversation with the SMTP server
  1134. // Returns 0 if successful, or a positive
  1135. // error value if the SMTP server gave an error or failure response.
  1136. int CSmtp::SendHello()
  1137. {
  1138. int nRet = 0;
  1139. TCHAR szName[64] = {0};
  1140. TCHAR szMsg[MAX_PATH] = {0};
  1141. DWORD dwSize = 64;
  1142. GetComputerName(szName,&dwSize);
  1143. // First try a EHLO if we're using ESMTP
  1144. wsprintf(szMsg,_T("EHLO %s\r\n"),szName);
  1145. if (m_bExtensions) nRet = SendCmd(szMsg);
  1146. // If we got a 250 response, we're using ESMTP, otherwise revert to regular SMTP
  1147. if (nRet != 250)
  1148. {
  1149. m_bUsingExtensions = FALSE;
  1150. szMsg[0] = 'H';
  1151. szMsg[1] = 'E';
  1152. nRet = SendCmd(szMsg);
  1153. }
  1154. else m_bUsingExtensions = TRUE;
  1155. // Raise any unexpected responses
  1156. if (nRet != 250)
  1157. {
  1158. RaiseError(nRet);
  1159. return nRet;
  1160. }
  1161. // We're connected!
  1162. m_bConnected = TRUE;
  1163. // Send authentication if we have any.
  1164. // We don't fail just because authentication failed, however.
  1165. if (m_bUsingExtensions) SendAuthentication();
  1166. return 0;
  1167. }
  1168. // Requests authentication for the session if the server supports it,
  1169. // and attempts to submit the user's credentials.
  1170. // Returns 0 if successful, or a positive
  1171. // error value if the SMTP server gave an error or failure response.
  1172. int CSmtp::SendAuthentication()
  1173. {
  1174. USES_CONVERSION;
  1175. int nRet = 0;
  1176. CBase64 cvt;
  1177. LPCSTR pszTemp;
  1178. TCHAR szMsg[MAX_PATH] = {0};
  1179. CHAR szAuthType[MAX_PATH] = {0};
  1180. // This is an authentication loop, we can authenticate multiple times in case of failure.
  1181. while(1)
  1182. {
  1183. // If we don't have a username, skip authentication
  1184. if (!m_strUser.length()) return 0;
  1185. // Make the authentication request
  1186. nRet = SendCmd(_T("AUTH LOGIN\r\n"));
  1187. // If it was rejected, we have to abort.
  1188. if (nRet != 334)
  1189. {
  1190. RaiseWarning(nRet);
  1191. return nRet;
  1192. }
  1193. // Authentication has 2 stages for username and password.
  1194. // It is possible if the authentication fails here that we can
  1195. // resubmit proper credentials.
  1196. while (1)
  1197. {
  1198. // Decode the authentication string being requested
  1199. _T2A(szAuthType,&(m_strResult.c_str())[4]);
  1200. cvt.Decode(szAuthType);
  1201. pszTemp = cvt.DecodedMessage();
  1202. if (!lstrcmpiA(pszTemp,"Username:"))
  1203. cvt.Encode(T2CA(m_strUser.c_str()));
  1204. else if (!lstrcmpiA(pszTemp,"Password:"))
  1205. cvt.Encode(T2CA(m_strPass.c_str()));
  1206. else break;
  1207. wsprintf(szMsg,_T("%s\r\n"),A2CT(cvt.EncodedMessage()));
  1208. nRet = SendCmd(szMsg);
  1209. // If we got a failed authentication request, raise a warning.
  1210. // this gives the owner a chance to change the username and password.
  1211. if (nRet == 535)
  1212. {
  1213. // Return FALSE to fail, or TRUE to retry
  1214. nRet = RaiseWarning(nRet);
  1215. if (!nRet)
  1216. {
  1217. // Reset the error back to 535. It's now an error rather than a warning
  1218. nRet = 535;
  1219. break;
  1220. }
  1221. }
  1222. // Break on any response other than 334, which indicates a request for more information
  1223. if (nRet != 334) break;
  1224. }
  1225. // Break if we're not retrying a failed authentication
  1226. if (nRet != TRUE) break;
  1227. }
  1228. // Raise an error if we failed to authenticate
  1229. if (nRet != 235) RaiseError(nRet);
  1230. return (nRet == 235) ? 0:nRet;
  1231. }
  1232. // Send a MAIL FROM command to the server
  1233. // Returns 0 if successful, or a positive
  1234. // error value if the SMTP server gave an error or failure response.
  1235. int CSmtp::SendFrom(LPTSTR pszFrom)
  1236. {
  1237. int nRet = 0;
  1238. TCHAR szMsg[MAX_PATH] = {0};
  1239. wsprintf(szMsg,_T("MAIL FROM: <%s>\r\n"),pszFrom);
  1240. while (1)
  1241. {
  1242. nRet = SendCmd(szMsg);
  1243. // Send authentication if required, and retry the command
  1244. if (nRet == 530) nRet = SendAuthentication();
  1245. else break;
  1246. }
  1247. // Raise an error if we failed
  1248. if (nRet != 250) RaiseError(nRet);
  1249. return (nRet == 250) ? 0:nRet;
  1250. }
  1251. // Send a RCPT TO command to the server
  1252. // Returns 0 if successful, or a positive
  1253. // error value if the SMTP server gave an error or failure response.
  1254. int CSmtp::SendTo(LPTSTR pszTo)
  1255. {
  1256. int nRet;
  1257. TCHAR szMsg[MAX_PATH] = {0};
  1258. wsprintf(szMsg,_T("RCPT TO: <%s>\r\n"),pszTo);
  1259. nRet = SendCmd(szMsg);
  1260. if (nRet != 250 && nRet != 251) RaiseWarning(nRet);
  1261. return (nRet == 250 || nRet == 251) ? 0:nRet;
  1262. }
  1263. // Send the body of an e-mail message to the server
  1264. // Returns 0 if successful, or a positive
  1265. // error value if the SMTP server gave an error or failure response.
  1266. int CSmtp::SendData(CSmtpMessage &msg)
  1267. {
  1268. int nRet;
  1269. String strMsg;
  1270. // Send the DATA command. We need a 354 to proceed
  1271. nRet = SendCmd(_T("DATA\r\n"));
  1272. if (nRet != 354)
  1273. {
  1274. RaiseError(nRet);
  1275. return nRet;
  1276. }
  1277. // Parse the body of the email message
  1278. msg.Parse(strMsg);
  1279. strMsg += _T("\r\n.\r\n");
  1280. // Send the body and expect a 250 OK reply.
  1281. nRet = SendCmd((LPTSTR)strMsg.c_str());
  1282. if (nRet != 250) RaiseError(nRet);
  1283. return (nRet == 250) ? 0:nRet;
  1284. }