Fix warning
[TortoiseGit.git] / src / Utils / HwSMTP.cpp
blobac0680d393249e3d48d48ae34bb6a600fb8fc8b7
1 // HwSMTP.cpp: implementation of the CHwSMTP class.
2 //
3 // Schannel/SSPI implementation based on http://www.coastrd.com/c-schannel-smtp
4 //
5 //////////////////////////////////////////////////////////////////////
7 #include "stdafx.h"
8 #include "afxstr.h"
9 #include "HwSMTP.h"
10 #include "Windns.h"
11 #include <Afxmt.h>
12 #include "FormatMessageWrapper.h"
13 #include <atlenc.h>
14 #include "AppUtils.h"
15 #include "PathUtils.h"
16 #include "StringUtils.h"
18 #define IO_BUFFER_SIZE 0x10000
20 #pragma comment(lib, "Dnsapi.lib")
21 #pragma comment(lib, "Rpcrt4.lib")
22 #pragma comment(lib, "Secur32.lib")
24 #ifndef GET_SAFE_STRING
25 #define GET_SAFE_STRING(str) ((str) ? (str) : L"")
26 #endif
27 #define HANDLE_IS_VALID(h) ((HANDLE)(h) != NULL && (HANDLE)(h) != INVALID_HANDLE_VALUE)
29 DWORD dwProtocol = SP_PROT_TLS1; // SP_PROT_TLS1; // SP_PROT_PCT1; SP_PROT_SSL2; SP_PROT_SSL3; 0=default
30 ALG_ID aiKeyExch = 0; // = default; CALG_DH_EPHEM; CALG_RSA_KEYX;
32 SCHANNEL_CRED SchannelCred;
33 PSecurityFunctionTable g_pSSPI;
35 //////////////////////////////////////////////////////////////////////
36 // Construction/Destruction
37 //////////////////////////////////////////////////////////////////////
39 static CString FormatDateTime(COleDateTime& DateTime)
41 // If null, return empty string
42 if (DateTime.GetStatus() == COleDateTime::null || DateTime.GetStatus() == COleDateTime::invalid)
43 return L"";
45 UDATE ud;
46 if (S_OK != VarUdateFromDate(DateTime.m_dt, 0, &ud))
47 return L"";
49 static TCHAR* weeks[] = { L"Sun", L"Mon", L"Tue", L"Wen", L"Thu", L"Fri", L"Sat" };
50 static TCHAR* month[] = { L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" };
52 TIME_ZONE_INFORMATION stTimeZone;
53 GetTimeZoneInformation(&stTimeZone);
55 CString strDate;
56 strDate.Format(L"%s, %d %s %d %02d:%02d:%02d %c%04d", weeks[ud.st.wDayOfWeek],
57 ud.st.wDay, month[ud.st.wMonth - 1], ud.st.wYear, ud.st.wHour,
58 ud.st.wMinute, ud.st.wSecond,
59 stTimeZone.Bias > 0 ? L'-' : L'+',
60 abs(stTimeZone.Bias * 10 / 6));
61 return strDate;
64 static int GetFileSize(LPCTSTR lpFileName)
66 if (!lpFileName || lstrlen(lpFileName) < 1)
67 return -1;
69 CFileStatus fileStatus = { 0 };
70 memset(fileStatus.m_szFullName, 0, sizeof(fileStatus.m_szFullName));
71 try
73 CFile::GetStatus(lpFileName, fileStatus);
75 catch (CException&)
77 ASSERT(FALSE);
80 return (int)fileStatus.m_size;
83 static CString FormatBytes(double fBytesNum, BOOL bShowUnit = TRUE , int nFlag = 0)
85 CString csRes;
86 if (nFlag == 0)
88 if (fBytesNum >= 1024.0 && fBytesNum < 1024.0 * 1024.0)
89 csRes.Format(L"%.2f%s", fBytesNum / 1024.0, bShowUnit ? L" K" : L"");
90 else if (fBytesNum >= 1024.0 * 1024.0 && fBytesNum < 1024.0 * 1024.0 * 1024.0)
91 csRes.Format(L"%.2f%s", fBytesNum / (1024.0 * 1024.0), bShowUnit ? L" M" : L"");
92 else if (fBytesNum >= 1024.0 * 1024.0 * 1024.0)
93 csRes.Format(L"%.2f%s", fBytesNum / (1024.0 * 1024.0 * 1024.0), bShowUnit ? L" G" : L"");
94 else
95 csRes.Format(L"%.2f%s", fBytesNum, bShowUnit ? L" B" : L"");
97 else if (nFlag == 1)
98 csRes.Format(L"%.2f%s", fBytesNum / 1024.0, bShowUnit ? L" K" : L"");
99 else if (nFlag == 2)
100 csRes.Format(L"%.2f%s", fBytesNum / (1024.0 * 1024.0), bShowUnit ? L" M" : L"");
101 else if (nFlag == 3)
102 csRes.Format(L"%.2f%s", fBytesNum / (1024.0 * 1024.0 * 1024.0), bShowUnit ? L" G" : L"");
104 return csRes;
107 static CString GetGUID()
109 CString sGuid;
110 GUID guid;
111 if (CoCreateGuid(&guid) == S_OK)
113 RPC_WSTR guidStr;
114 if (UuidToString(&guid, &guidStr) == RPC_S_OK)
116 sGuid = (LPTSTR)guidStr;
117 RpcStringFree(&guidStr);
120 return sGuid;
123 CHwSMTP::CHwSMTP () :
124 m_bConnected ( FALSE ),
125 m_nSmtpSrvPort ( 25 ),
126 m_bMustAuth ( TRUE )
128 m_csPartBoundary = L"NextPart_" + GetGUID();
129 m_csMIMEContentType.Format(L"multipart/mixed; boundary=%s", (LPCTSTR)m_csPartBoundary);
130 m_csNoMIMEText = L"This is a multi-part message in MIME format.";
132 hContext = nullptr;
133 hCreds = nullptr;
134 pbIoBuffer = nullptr;
135 cbIoBufferLength = 0;
137 m_iSecurityLevel = none;
139 SecureZeroMemory(&Sizes, sizeof(SecPkgContext_StreamSizes));
141 AfxSocketInit();
144 CHwSMTP::~CHwSMTP()
148 CString CHwSMTP::GetServerAddress(const CString& in)
150 CString email;
151 CStringUtils::ParseEmailAddress(in, email);
153 int start = email.Find(L'@');
154 return email.Mid(start + 1);
157 BOOL CHwSMTP::SendSpeedEmail
159 LPCTSTR lpszAddrFrom,
160 LPCTSTR lpszAddrTo,
161 LPCTSTR lpszSubject,
162 LPCTSTR lpszBody,
163 CStringArray *pStrAryAttach,
164 LPCTSTR pStrAryCC,
165 LPCTSTR pSend
168 BOOL ret=true;
169 CString To;
170 To += GET_SAFE_STRING(lpszAddrTo);
171 To += L';';
172 To += GET_SAFE_STRING(pStrAryCC);
174 std::map<CString,std::vector<CString>> Address;
176 int start = 0;
177 while( start >= 0 )
179 CString one = To.Tokenize(L";", start).Trim();
180 if(one.IsEmpty())
181 continue;
183 CString addr = GetServerAddress(one);
184 if(addr.IsEmpty())
185 continue;
187 Address[addr].push_back(one);
190 for (const auto& maildomain : Address)
192 PDNS_RECORD pDnsRecord;
193 DNS_STATUS status =
194 DnsQuery(maildomain.first,
195 DNS_TYPE_MX,DNS_QUERY_STANDARD,
196 nullptr, //Contains DNS server IP address.
197 &pDnsRecord, //Resource record that contains the response.
198 nullptr
200 if (status)
202 m_csLastError.Format(L"DNS query failed %d", status);
203 ret = false;
204 continue;
206 SCOPE_EXIT { DnsRecordListFree(pDnsRecord, DnsFreeRecordList); };
208 CString to;
209 std::for_each(maildomain.second.cbegin(), maildomain.second.cend(), [&to](auto recipient) {
210 to += recipient;
211 to += L';';
213 if(to.IsEmpty())
214 continue;
216 PDNS_RECORD pNext = pDnsRecord;
217 while(pNext)
219 if(pNext->wType == DNS_TYPE_MX)
220 if (SendEmail(pNext->Data.MX.pNameExchange, nullptr, nullptr, false,
221 lpszAddrFrom, to, lpszSubject, lpszBody, pStrAryAttach, pStrAryCC,
222 25,pSend,lpszAddrTo))
223 break;
224 pNext=pNext->pNext;
226 if (!pNext)
227 ret = false;
230 return ret;
233 static SECURITY_STATUS ClientHandshakeLoop(CSocket * Socket, PCredHandle phCreds, CtxtHandle * phContext, BOOL fDoInitialRead, SecBuffer * pExtraData)
235 SecBufferDesc OutBuffer, InBuffer;
236 SecBuffer InBuffers[2], OutBuffers[1];
237 DWORD dwSSPIFlags, dwSSPIOutFlags, cbData, cbIoBuffer;
238 TimeStamp tsExpiry;
239 SECURITY_STATUS scRet;
240 BOOL fDoRead;
242 dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY |
243 ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM;
245 // Allocate data buffer.
246 auto IoBuffer = std::make_unique<UCHAR[]>(IO_BUFFER_SIZE);
247 if (!IoBuffer)
249 // printf("**** Out of memory (1)\n");
250 return SEC_E_INTERNAL_ERROR;
252 cbIoBuffer = 0;
253 fDoRead = fDoInitialRead;
255 // Loop until the handshake is finished or an error occurs.
256 scRet = SEC_I_CONTINUE_NEEDED;
258 while (scRet == SEC_I_CONTINUE_NEEDED || scRet == SEC_E_INCOMPLETE_MESSAGE || scRet == SEC_I_INCOMPLETE_CREDENTIALS)
260 if (0 == cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE) // Read data from server.
262 if (fDoRead)
264 cbData = Socket->Receive(IoBuffer.get() + cbIoBuffer, IO_BUFFER_SIZE - cbIoBuffer, 0);
265 if (cbData == SOCKET_ERROR)
267 // printf("**** Error %d reading data from server\n", WSAGetLastError());
268 scRet = SEC_E_INTERNAL_ERROR;
269 break;
271 else if (cbData == 0)
273 // printf("**** Server unexpectedly disconnected\n");
274 scRet = SEC_E_INTERNAL_ERROR;
275 break;
277 // printf("%d bytes of handshake data received\n", cbData);
278 cbIoBuffer += cbData;
280 else
281 fDoRead = TRUE;
284 // Set up the input buffers. Buffer 0 is used to pass in data
285 // received from the server. Schannel will consume some or all
286 // of this. Leftover data (if any) will be placed in buffer 1 and
287 // given a buffer type of SECBUFFER_EXTRA.
288 InBuffers[0].pvBuffer = IoBuffer.get();
289 InBuffers[0].cbBuffer = cbIoBuffer;
290 InBuffers[0].BufferType = SECBUFFER_TOKEN;
292 InBuffers[1].pvBuffer = nullptr;
293 InBuffers[1].cbBuffer = 0;
294 InBuffers[1].BufferType = SECBUFFER_EMPTY;
296 InBuffer.cBuffers = 2;
297 InBuffer.pBuffers = InBuffers;
298 InBuffer.ulVersion = SECBUFFER_VERSION;
300 // Set up the output buffers. These are initialized to nullptr
301 // so as to make it less likely we'll attempt to free random
302 // garbage later.
303 OutBuffers[0].pvBuffer = nullptr;
304 OutBuffers[0].BufferType= SECBUFFER_TOKEN;
305 OutBuffers[0].cbBuffer = 0;
307 OutBuffer.cBuffers = 1;
308 OutBuffer.pBuffers = OutBuffers;
309 OutBuffer.ulVersion = SECBUFFER_VERSION;
311 // Call InitializeSecurityContext.
312 scRet = g_pSSPI->InitializeSecurityContext(phCreds, phContext, nullptr, dwSSPIFlags, 0, SECURITY_NATIVE_DREP, &InBuffer, 0, nullptr, &OutBuffer, &dwSSPIOutFlags, &tsExpiry);
314 // If InitializeSecurityContext was successful (or if the error was
315 // one of the special extended ones), send the contends of the output
316 // buffer to the server.
317 if (scRet == SEC_E_OK || scRet == SEC_I_CONTINUE_NEEDED || FAILED(scRet) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR))
319 if (OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != nullptr)
321 cbData = Socket->Send(OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0 );
322 if(cbData == SOCKET_ERROR || cbData == 0)
324 // printf( "**** Error %d sending data to server (2)\n", WSAGetLastError() );
325 g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer);
326 g_pSSPI->DeleteSecurityContext(phContext);
327 return SEC_E_INTERNAL_ERROR;
329 // printf("%d bytes of handshake data sent\n", cbData);
331 // Free output buffer.
332 g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer);
333 OutBuffers[0].pvBuffer = nullptr;
337 // If InitializeSecurityContext returned SEC_E_INCOMPLETE_MESSAGE,
338 // then we need to read more data from the server and try again.
339 if (scRet == SEC_E_INCOMPLETE_MESSAGE) continue;
341 // If InitializeSecurityContext returned SEC_E_OK, then the
342 // handshake completed successfully.
343 if (scRet == SEC_E_OK)
345 // If the "extra" buffer contains data, this is encrypted application
346 // protocol layer stuff. It needs to be saved. The application layer
347 // will later decrypt it with DecryptMessage.
348 // printf("Handshake was successful\n");
350 if (InBuffers[1].BufferType == SECBUFFER_EXTRA)
352 pExtraData->pvBuffer = LocalAlloc( LMEM_FIXED, InBuffers[1].cbBuffer );
353 if (pExtraData->pvBuffer == nullptr)
355 // printf("**** Out of memory (2)\n");
356 return SEC_E_INTERNAL_ERROR;
359 MoveMemory(pExtraData->pvBuffer, IoBuffer.get() + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer);
361 pExtraData->cbBuffer = InBuffers[1].cbBuffer;
362 pExtraData->BufferType = SECBUFFER_TOKEN;
364 // printf( "%d bytes of app data was bundled with handshake data\n", pExtraData->cbBuffer );
366 else
368 pExtraData->pvBuffer = nullptr;
369 pExtraData->cbBuffer = 0;
370 pExtraData->BufferType = SECBUFFER_EMPTY;
372 break; // Bail out to quit
375 // Check for fatal error.
376 if (FAILED(scRet))
378 // printf("**** Error 0x%x returned by InitializeSecurityContext (2)\n", scRet);
379 break;
382 // If InitializeSecurityContext returned SEC_I_INCOMPLETE_CREDENTIALS,
383 // then the server just requested client authentication.
384 if (scRet == SEC_I_INCOMPLETE_CREDENTIALS)
386 // Busted. The server has requested client authentication and
387 // the credential we supplied didn't contain a client certificate.
388 // This function will read the list of trusted certificate
389 // authorities ("issuers") that was received from the server
390 // and attempt to find a suitable client certificate that
391 // was issued by one of these. If this function is successful,
392 // then we will connect using the new certificate. Otherwise,
393 // we will attempt to connect anonymously (using our current credentials).
394 //GetNewClientCredentials(phCreds, phContext);
396 // Go around again.
397 fDoRead = FALSE;
398 scRet = SEC_I_CONTINUE_NEEDED;
399 continue;
402 // Copy any leftover data from the "extra" buffer, and go around again.
403 if ( InBuffers[1].BufferType == SECBUFFER_EXTRA )
405 MoveMemory(IoBuffer.get(), IoBuffer.get() + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer);
406 cbIoBuffer = InBuffers[1].cbBuffer;
408 else
409 cbIoBuffer = 0;
412 // Delete the security context in the case of a fatal error.
413 if (FAILED(scRet))
414 g_pSSPI->DeleteSecurityContext(phContext);
416 return scRet;
419 static SECURITY_STATUS PerformClientHandshake( CSocket * Socket, PCredHandle phCreds, LPTSTR pszServerName, CtxtHandle * phContext, SecBuffer * pExtraData)
421 SecBufferDesc OutBuffer;
422 SecBuffer OutBuffers[1];
423 DWORD dwSSPIFlags, dwSSPIOutFlags, cbData;
424 TimeStamp tsExpiry;
425 SECURITY_STATUS scRet;
427 dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY |
428 ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM;
430 // Initiate a ClientHello message and generate a token.
431 OutBuffers[0].pvBuffer = nullptr;
432 OutBuffers[0].BufferType = SECBUFFER_TOKEN;
433 OutBuffers[0].cbBuffer = 0;
435 OutBuffer.cBuffers = 1;
436 OutBuffer.pBuffers = OutBuffers;
437 OutBuffer.ulVersion = SECBUFFER_VERSION;
439 scRet = g_pSSPI->InitializeSecurityContext(phCreds, nullptr, pszServerName, dwSSPIFlags, 0, SECURITY_NATIVE_DREP, nullptr, 0, phContext, &OutBuffer, &dwSSPIOutFlags, &tsExpiry);
441 if (scRet != SEC_I_CONTINUE_NEEDED)
443 // printf("**** Error %d returned by InitializeSecurityContext (1)\n", scRet);
444 return scRet;
447 // Send response to server if there is one.
448 if (OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != nullptr)
450 cbData = Socket->Send(OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0);
451 if (cbData == SOCKET_ERROR || cbData == 0)
453 // printf("**** Error %d sending data to server (1)\n", WSAGetLastError());
454 g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer);
455 g_pSSPI->DeleteSecurityContext(phContext);
456 return SEC_E_INTERNAL_ERROR;
458 // printf("%d bytes of handshake data sent\n", cbData);
460 g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); // Free output buffer.
461 OutBuffers[0].pvBuffer = nullptr;
464 return ClientHandshakeLoop(Socket, phCreds, phContext, TRUE, pExtraData);
467 static SECURITY_STATUS CreateCredentials(PCredHandle phCreds)
469 TimeStamp tsExpiry;
470 SECURITY_STATUS Status;
471 DWORD cSupportedAlgs = 0;
472 ALG_ID rgbSupportedAlgs[16];
474 // Build Schannel credential structure. Currently, this sample only
475 // specifies the protocol to be used (and optionally the certificate,
476 // of course). Real applications may wish to specify other parameters as well.
477 SecureZeroMemory(&SchannelCred, sizeof(SchannelCred));
479 SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;
480 SchannelCred.grbitEnabledProtocols = dwProtocol;
482 if (aiKeyExch)
483 rgbSupportedAlgs[cSupportedAlgs++] = aiKeyExch;
485 if (cSupportedAlgs)
487 SchannelCred.cSupportedAlgs = cSupportedAlgs;
488 SchannelCred.palgSupportedAlgs = rgbSupportedAlgs;
491 SchannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS;
493 // The SCH_CRED_MANUAL_CRED_VALIDATION flag is specified because
494 // this sample verifies the server certificate manually.
495 // Applications that expect to run on WinNT, Win9x, or WinME
496 // should specify this flag and also manually verify the server
497 // certificate. Applications running on newer versions of Windows can
498 // leave off this flag, in which case the InitializeSecurityContext
499 // function will validate the server certificate automatically.
500 SchannelCred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION;
502 // Create an SSPI credential.
503 Status = g_pSSPI->AcquireCredentialsHandle(nullptr, // Name of principal
504 UNISP_NAME, // Name of package
505 SECPKG_CRED_OUTBOUND, // Flags indicating use
506 nullptr, // Pointer to logon ID
507 &SchannelCred, // Package specific data
508 nullptr, // Pointer to GetKey() func
509 nullptr, // Value to pass to GetKey()
510 phCreds, // (out) Cred Handle
511 &tsExpiry ); // (out) Lifetime (optional)
513 return Status;
516 static DWORD EncryptSend(CSocket * Socket, CtxtHandle * phContext, PBYTE pbIoBuffer, SecPkgContext_StreamSizes Sizes)
517 // http://msdn.microsoft.com/en-us/library/aa375378(VS.85).aspx
518 // The encrypted message is encrypted in place, overwriting the original contents of its buffer.
520 SECURITY_STATUS scRet;
521 SecBufferDesc Message;
522 SecBuffer Buffers[4];
523 DWORD cbMessage;
524 PBYTE pbMessage;
526 pbMessage = pbIoBuffer + Sizes.cbHeader; // Offset by "header size"
527 cbMessage = (DWORD)strlen((char *)pbMessage);
529 // Encrypt the HTTP request.
530 Buffers[0].pvBuffer = pbIoBuffer; // Pointer to buffer 1
531 Buffers[0].cbBuffer = Sizes.cbHeader; // length of header
532 Buffers[0].BufferType = SECBUFFER_STREAM_HEADER; // Type of the buffer
534 Buffers[1].pvBuffer = pbMessage; // Pointer to buffer 2
535 Buffers[1].cbBuffer = cbMessage; // length of the message
536 Buffers[1].BufferType = SECBUFFER_DATA; // Type of the buffer
538 Buffers[2].pvBuffer = pbMessage + cbMessage; // Pointer to buffer 3
539 Buffers[2].cbBuffer = Sizes.cbTrailer; // length of the trailor
540 Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; // Type of the buffer
542 Buffers[3].pvBuffer = SECBUFFER_EMPTY; // Pointer to buffer 4
543 Buffers[3].cbBuffer = SECBUFFER_EMPTY; // length of buffer 4
544 Buffers[3].BufferType = SECBUFFER_EMPTY; // Type of the buffer 4
546 Message.ulVersion = SECBUFFER_VERSION; // Version number
547 Message.cBuffers = 4; // Number of buffers - must contain four SecBuffer structures.
548 Message.pBuffers = Buffers; // Pointer to array of buffers
550 scRet = g_pSSPI->EncryptMessage(phContext, 0, &Message, 0); // must contain four SecBuffer structures.
551 if (FAILED(scRet))
553 // printf("**** Error 0x%x returned by EncryptMessage\n", scRet);
554 return scRet;
558 // Send the encrypted data to the server.
559 return Socket->Send(pbIoBuffer, Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer, 0);
562 static LONG DisconnectFromServer(CSocket * Socket, PCredHandle phCreds, CtxtHandle * phContext)
564 PBYTE pbMessage;
565 DWORD dwType, dwSSPIFlags, dwSSPIOutFlags, cbMessage, cbData, Status;
566 SecBufferDesc OutBuffer;
567 SecBuffer OutBuffers[1];
568 TimeStamp tsExpiry;
570 dwType = SCHANNEL_SHUTDOWN; // Notify schannel that we are about to close the connection.
572 OutBuffers[0].pvBuffer = &dwType;
573 OutBuffers[0].BufferType = SECBUFFER_TOKEN;
574 OutBuffers[0].cbBuffer = sizeof(dwType);
576 OutBuffer.cBuffers = 1;
577 OutBuffer.pBuffers = OutBuffers;
578 OutBuffer.ulVersion = SECBUFFER_VERSION;
580 Status = g_pSSPI->ApplyControlToken(phContext, &OutBuffer);
581 if (FAILED(Status))
583 // printf("**** Error 0x%x returned by ApplyControlToken\n", Status);
584 goto cleanup;
587 // Build an SSL close notify message.
588 dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM;
590 OutBuffers[0].pvBuffer = nullptr;
591 OutBuffers[0].BufferType = SECBUFFER_TOKEN;
592 OutBuffers[0].cbBuffer = 0;
594 OutBuffer.cBuffers = 1;
595 OutBuffer.pBuffers = OutBuffers;
596 OutBuffer.ulVersion = SECBUFFER_VERSION;
598 Status = g_pSSPI->InitializeSecurityContext(phCreds, phContext, nullptr, dwSSPIFlags, 0, SECURITY_NATIVE_DREP, nullptr, 0, phContext, &OutBuffer, &dwSSPIOutFlags, &tsExpiry);
600 if (FAILED(Status))
602 // printf("**** Error 0x%x returned by InitializeSecurityContext\n", Status);
603 goto cleanup;
606 pbMessage = (PBYTE)OutBuffers[0].pvBuffer;
607 cbMessage = OutBuffers[0].cbBuffer;
609 // Send the close notify message to the server.
610 if (pbMessage != nullptr && cbMessage != 0)
612 cbData = Socket->Send(pbMessage, cbMessage, 0);
613 if (cbData == SOCKET_ERROR || cbData == 0)
615 Status = WSAGetLastError();
616 goto cleanup;
618 // printf("Sending Close Notify\n");
619 // printf("%d bytes of handshake data sent\n", cbData);
620 g_pSSPI->FreeContextBuffer(pbMessage); // Free output buffer.
623 cleanup:
624 g_pSSPI->DeleteSecurityContext(phContext); // Free the security context.
625 Socket->Close();
627 return Status;
630 static SECURITY_STATUS ReadDecrypt(CSocket * Socket, PCredHandle phCreds, CtxtHandle * phContext, PBYTE pbIoBuffer, DWORD cbIoBufferLength)
632 // calls recv() - blocking socket read
633 // http://msdn.microsoft.com/en-us/library/ms740121(VS.85).aspx
635 // The encrypted message is decrypted in place, overwriting the original contents of its buffer.
636 // http://msdn.microsoft.com/en-us/library/aa375211(VS.85).aspx
639 SecBuffer ExtraBuffer;
640 SecBuffer * pDataBuffer, * pExtraBuffer;
642 SECURITY_STATUS scRet;
643 SecBufferDesc Message;
644 SecBuffer Buffers[4];
646 DWORD cbIoBuffer, cbData, length;
647 PBYTE buff;
649 // Read data from server until done.
650 cbIoBuffer = 0;
651 scRet = 0;
652 while (TRUE) // Read some data.
654 if (cbIoBuffer == 0 || scRet == SEC_E_INCOMPLETE_MESSAGE) // get the data
656 cbData = Socket->Receive(pbIoBuffer + cbIoBuffer, cbIoBufferLength - cbIoBuffer, 0);
657 if (cbData == SOCKET_ERROR)
659 // printf("**** Error %d reading data from server\n", WSAGetLastError());
660 scRet = SEC_E_INTERNAL_ERROR;
661 break;
663 else if (cbData == 0) // Server disconnected.
665 if (cbIoBuffer)
667 // printf("**** Server unexpectedly disconnected\n");
668 scRet = SEC_E_INTERNAL_ERROR;
669 return scRet;
671 else
672 break; // All Done
674 else // success
676 // printf("%d bytes of (encrypted) application data received\n", cbData);
677 cbIoBuffer += cbData;
681 // Decrypt the received data.
682 Buffers[0].pvBuffer = pbIoBuffer;
683 Buffers[0].cbBuffer = cbIoBuffer;
684 Buffers[0].BufferType = SECBUFFER_DATA; // Initial Type of the buffer 1
685 Buffers[1].BufferType = SECBUFFER_EMPTY; // Initial Type of the buffer 2
686 Buffers[2].BufferType = SECBUFFER_EMPTY; // Initial Type of the buffer 3
687 Buffers[3].BufferType = SECBUFFER_EMPTY; // Initial Type of the buffer 4
689 Message.ulVersion = SECBUFFER_VERSION; // Version number
690 Message.cBuffers = 4; // Number of buffers - must contain four SecBuffer structures.
691 Message.pBuffers = Buffers; // Pointer to array of buffers
693 scRet = g_pSSPI->DecryptMessage(phContext, &Message, 0, nullptr);
694 if (scRet == SEC_I_CONTEXT_EXPIRED)
695 break; // Server signalled end of session
696 // if (scRet == SEC_E_INCOMPLETE_MESSAGE - Input buffer has partial encrypted record, read more
697 if (scRet != SEC_E_OK && scRet != SEC_I_RENEGOTIATE && scRet != SEC_I_CONTEXT_EXPIRED)
698 return scRet;
700 // Locate data and (optional) extra buffers.
701 pDataBuffer = nullptr;
702 pExtraBuffer = nullptr;
703 for (int i = 1; i < 4; ++i)
705 if (pDataBuffer == nullptr && Buffers[i].BufferType == SECBUFFER_DATA)
706 pDataBuffer = &Buffers[i];
707 if (pExtraBuffer == nullptr && Buffers[i].BufferType == SECBUFFER_EXTRA)
708 pExtraBuffer = &Buffers[i];
711 // Display the decrypted data.
712 if (pDataBuffer)
714 length = pDataBuffer->cbBuffer;
715 if (length) // check if last two chars are CR LF
717 buff = (PBYTE)pDataBuffer->pvBuffer;
718 if (buff[length-2] == 13 && buff[length-1] == 10) // Found CRLF
720 buff[length] = 0;
721 break;
726 // Move any "extra" data to the input buffer.
727 if (pExtraBuffer)
729 MoveMemory(pbIoBuffer, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer);
730 cbIoBuffer = pExtraBuffer->cbBuffer;
732 else
733 cbIoBuffer = 0;
735 // The server wants to perform another handshake sequence.
736 if (scRet == SEC_I_RENEGOTIATE)
738 // printf("Server requested renegotiate!\n");
739 scRet = ClientHandshakeLoop( Socket, phCreds, phContext, FALSE, &ExtraBuffer);
740 if (scRet != SEC_E_OK)
741 return scRet;
743 if (ExtraBuffer.pvBuffer) // Move any "extra" data to the input buffer.
745 MoveMemory(pbIoBuffer, ExtraBuffer.pvBuffer, ExtraBuffer.cbBuffer);
746 cbIoBuffer = ExtraBuffer.cbBuffer;
749 } // Loop till CRLF is found at the end of the data
751 return SEC_E_OK;
754 BOOL CHwSMTP::SendEmail (
755 LPCTSTR lpszSmtpSrvHost,
756 LPCTSTR lpszUserName,
757 LPCTSTR lpszPasswd,
758 BOOL bMustAuth,
759 LPCTSTR lpszAddrFrom,
760 LPCTSTR lpszAddrTo,
761 LPCTSTR lpszSubject,
762 LPCTSTR lpszBody,
763 CStringArray* pStrAryAttach/*=nullptr*/,
764 LPCTSTR pStrAryCC/*=nullptr*/,
765 UINT nSmtpSrvPort,/*=25*/
766 LPCTSTR pSender,
767 LPCTSTR pToList,
768 DWORD secLevel
771 m_StrAryAttach.RemoveAll();
773 m_StrCC = GET_SAFE_STRING(pStrAryCC);
775 m_csSmtpSrvHost = GET_SAFE_STRING ( lpszSmtpSrvHost );
776 if ( m_csSmtpSrvHost.GetLength() <= 0 )
778 m_csLastError = L"Parameter Error!";
779 return FALSE;
781 m_csUserName = GET_SAFE_STRING ( lpszUserName );
782 m_csPasswd = GET_SAFE_STRING ( lpszPasswd );
783 m_bMustAuth = bMustAuth;
784 if ( m_bMustAuth && m_csUserName.GetLength() <= 0 )
786 m_csLastError = L"Parameter Error!";
787 return FALSE;
790 m_csAddrFrom = GET_SAFE_STRING ( lpszAddrFrom );
791 m_csAddrTo = GET_SAFE_STRING ( lpszAddrTo );
792 m_csSubject = GET_SAFE_STRING ( lpszSubject );
793 m_csBody = GET_SAFE_STRING ( lpszBody );
795 this->m_csSender = GET_SAFE_STRING(pSender);
796 this->m_csToList = GET_SAFE_STRING(pToList);
798 m_nSmtpSrvPort = nSmtpSrvPort;
800 if (
801 m_csAddrFrom.GetLength() <= 0 || m_csAddrTo.GetLength() <= 0
804 m_csLastError = L"Parameter Error!";
805 return FALSE;
808 if ( pStrAryAttach )
810 m_StrAryAttach.Append ( *pStrAryAttach );
812 if ( m_StrAryAttach.GetSize() < 1 )
813 m_csMIMEContentType.Format(L"text/plain");
815 // ´´½¨Socket
816 m_SendSock.Close();
817 if ( !m_SendSock.Create () )
819 //int nResult = GetLastError();
820 m_csLastError = L"Create socket failed!";
821 return FALSE;
824 switch (secLevel)
826 case 1:
827 m_iSecurityLevel = want_tls;
828 break;
829 case 2:
830 m_iSecurityLevel = ssl;
831 break;
832 default:
833 m_iSecurityLevel = none;
836 if ( !m_SendSock.Connect ( m_csSmtpSrvHost, m_nSmtpSrvPort ) )
838 m_csLastError.Format(L"Connect to [%s] failed", (LPCTSTR)m_csSmtpSrvHost);
839 return FALSE;
842 if (m_iSecurityLevel == want_tls) {
843 if (!GetResponse("220"))
844 return FALSE;
845 m_bConnected = TRUE;
846 Send(L"STARTTLS\r\n");
847 if (!GetResponse("220"))
848 return FALSE;
849 m_iSecurityLevel = tls_established;
852 BOOL ret = FALSE;
854 SecBuffer ExtraData;
855 SECURITY_STATUS Status;
857 CtxtHandle contextStruct;
858 CredHandle credentialsStruct;
860 if (m_iSecurityLevel >= ssl)
862 g_pSSPI = InitSecurityInterface();
864 contextStruct.dwLower = 0;
865 contextStruct.dwUpper = 0;
867 hCreds = &credentialsStruct;
868 credentialsStruct.dwLower = 0;
869 credentialsStruct.dwUpper = 0;
870 Status = CreateCredentials(hCreds);
871 if (Status != SEC_E_OK)
873 m_csLastError = CFormatMessageWrapper(Status);
874 return FALSE;
877 hContext = &contextStruct;
878 Status = PerformClientHandshake(&m_SendSock, hCreds, m_csSmtpSrvHost.GetBuffer(), hContext, &ExtraData);
879 if (Status != SEC_E_OK)
881 m_csLastError = CFormatMessageWrapper(Status);
882 return FALSE;
885 PCCERT_CONTEXT pRemoteCertContext = nullptr;
886 // Authenticate server's credentials. Get server's certificate.
887 Status = g_pSSPI->QueryContextAttributes(hContext, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pRemoteCertContext);
888 if (Status)
890 m_csLastError = CFormatMessageWrapper(Status);
891 goto cleanup;
894 git_cert_x509 cert;
895 cert.parent.cert_type = GIT_CERT_X509;
896 cert.data = pRemoteCertContext->pbCertEncoded;
897 cert.len = pRemoteCertContext->cbCertEncoded;
898 if (CAppUtils::Git2CertificateCheck((git_cert*)&cert, 0, CUnicodeUtils::GetUTF8(m_csSmtpSrvHost), nullptr))
900 CertFreeCertificateContext(pRemoteCertContext);
901 m_csLastError = L"Invalid certificate.";
902 goto cleanup;
905 CertFreeCertificateContext(pRemoteCertContext);
907 Status = g_pSSPI->QueryContextAttributes(hContext, SECPKG_ATTR_STREAM_SIZES, &Sizes);
908 if (Status)
910 m_csLastError = CFormatMessageWrapper(Status);
911 goto cleanup;
914 // Create a buffer.
915 cbIoBufferLength = Sizes.cbHeader + Sizes.cbMaximumMessage + Sizes.cbTrailer;
916 pbIoBuffer = (PBYTE)LocalAlloc(LMEM_FIXED, cbIoBufferLength);
917 if (pbIoBuffer == nullptr)
919 m_csLastError = L"Could not allocate memory";
920 goto cleanup;
922 SecureZeroMemory(pbIoBuffer, cbIoBufferLength);
925 if (m_iSecurityLevel <= ssl)
927 if (!GetResponse("220"))
928 goto cleanup;
929 m_bConnected = TRUE;
932 ret = SendEmail();
934 cleanup:
935 if (m_iSecurityLevel >= ssl)
937 if (hContext && hCreds)
938 DisconnectFromServer(&m_SendSock, hCreds, hContext);
939 if (pbIoBuffer)
941 LocalFree(pbIoBuffer);
942 pbIoBuffer = nullptr;
943 cbIoBufferLength = 0;
945 if (hContext)
947 g_pSSPI->DeleteSecurityContext(hContext);
948 hContext = nullptr;
950 if (hCreds)
952 g_pSSPI->FreeCredentialsHandle(hCreds);
953 hCreds = nullptr;
955 g_pSSPI = nullptr;
957 else
958 m_SendSock.Close();
960 return ret;
963 BOOL CHwSMTP::GetResponse(LPCSTR lpszVerifyCode)
965 if (!lpszVerifyCode || strlen(lpszVerifyCode) < 1)
966 return FALSE;
968 SECURITY_STATUS scRet = SEC_E_OK;
970 char szRecvBuf[1024] = {0};
971 int nRet = 0;
972 char szStatusCode[4] = {0};
974 if (m_iSecurityLevel >= ssl)
976 scRet = ReadDecrypt(&m_SendSock, hCreds, hContext, pbIoBuffer, cbIoBufferLength);
977 SecureZeroMemory(szRecvBuf, 1024);
978 memcpy(szRecvBuf, pbIoBuffer+Sizes.cbHeader, 1024);
980 else
981 nRet = m_SendSock.Receive(szRecvBuf, sizeof(szRecvBuf));
982 //TRACE(L"Received : %s\r\n", szRecvBuf);
983 if (nRet == 0 && m_iSecurityLevel == none || m_iSecurityLevel >= ssl && scRet != SEC_E_OK)
985 m_csLastError = L"Receive TCP data failed";
986 return FALSE;
988 memcpy ( szStatusCode, szRecvBuf, 3 );
989 if (strcmp(szStatusCode, lpszVerifyCode) != 0)
991 m_csLastError.Format(L"Received invalid response: %s", (LPCTSTR)CUnicodeUtils::GetUnicode(szRecvBuf));
992 return FALSE;
995 return TRUE;
997 BOOL CHwSMTP::SendBuffer(const char* buff, int size)
999 if(size<0)
1000 size=(int)strlen(buff);
1001 if ( !m_bConnected )
1003 m_csLastError = L"Didn't connect";
1004 return FALSE;
1007 if (m_iSecurityLevel >= ssl)
1009 int sent = 0;
1010 while (size - sent > 0)
1012 int toSend = min(size - sent, (int)Sizes.cbMaximumMessage);
1013 SecureZeroMemory(pbIoBuffer + Sizes.cbHeader, Sizes.cbMaximumMessage);
1014 memcpy(pbIoBuffer + Sizes.cbHeader, buff + sent, toSend);
1015 DWORD cbData = EncryptSend(&m_SendSock, hContext, pbIoBuffer, Sizes);
1016 if (cbData == SOCKET_ERROR || cbData == 0)
1017 return FALSE;
1018 sent += toSend;
1021 else if (m_SendSock.Send ( buff, size ) != size)
1023 m_csLastError = L"Socket send data failed";
1024 return FALSE;
1027 return TRUE;
1030 BOOL CHwSMTP::Send(const CString &str )
1032 return Send(CUnicodeUtils::GetUTF8(str));
1035 BOOL CHwSMTP::Send(const CStringA &str)
1037 //TRACE(L"Send: %s\r\n", (LPCTSTR)CUnicodeUtils::GetUnicode(str));
1038 return SendBuffer(str, str.GetLength());
1041 BOOL CHwSMTP::SendEmail()
1043 CStringA hostname;
1044 gethostname(CStrBufA(hostname, 64), 64);
1046 // make sure helo hostname can be interpreted as a FQDN
1047 if (hostname.Find(".") == -1)
1048 hostname += ".local";
1050 CStringA str;
1051 str.Format("HELO %s\r\n", (LPCSTR)hostname);
1052 if (!Send(str))
1053 return FALSE;
1054 if (!GetResponse("250"))
1055 return FALSE;
1057 if ( m_bMustAuth && !auth() )
1058 return FALSE;
1060 if ( !SendHead() )
1061 return FALSE;
1063 if (!SendSubject(CUnicodeUtils::GetUnicode(hostname)))
1064 return FALSE;
1066 if ( !SendBody() )
1067 return FALSE;
1069 if ( !SendAttach() )
1071 return FALSE;
1074 if (!Send(".\r\n"))
1075 return FALSE;
1076 if (!GetResponse("250"))
1077 return FALSE;
1079 if ( HANDLE_IS_VALID(m_SendSock.m_hSocket) )
1080 Send("QUIT\r\n");
1081 m_bConnected = FALSE;
1083 return TRUE;
1086 static CStringA EncodeBase64(const char* source, int len)
1088 int neededLength = Base64EncodeGetRequiredLength(len);
1089 CStringA output;
1090 if (Base64Encode((BYTE*)source, len, CStrBufA(output, neededLength), &neededLength, ATL_BASE64_FLAG_NOCRLF))
1091 output.Truncate(neededLength);
1092 return output;
1095 static CStringA EncodeBase64(const CString& source)
1097 CStringA buf = CUnicodeUtils::GetUTF8(source);
1098 return EncodeBase64(buf, buf.GetLength());
1101 CString CHwSMTP::GetEncodedHeader(const CString& text)
1103 if (CStringUtils::IsPlainReadableASCII(text))
1104 return text;
1106 return L"=?UTF-8?B?" + CUnicodeUtils::GetUnicode(EncodeBase64(text)) + L"?=";
1109 BOOL CHwSMTP::auth()
1111 if (!Send("auth login\r\n"))
1112 return FALSE;
1113 if (!GetResponse("334"))
1114 return FALSE;
1116 if (!Send(EncodeBase64(m_csUserName) + "\r\n"))
1117 return FALSE;
1119 if (!GetResponse("334"))
1121 m_csLastError = L"Authentication UserName failed";
1122 return FALSE;
1125 if (!Send(EncodeBase64(m_csPasswd) + "\r\n"))
1126 return FALSE;
1128 if (!GetResponse("235"))
1130 m_csLastError = L"Authentication Password failed";
1131 return FALSE;
1134 return TRUE;
1137 BOOL CHwSMTP::SendHead()
1139 CString str;
1140 CString addr;
1141 CStringUtils::ParseEmailAddress(m_csAddrFrom, addr);
1143 str.Format(L"MAIL From: <%s>\r\n", (LPCTSTR)addr);
1144 if (!Send(str))
1145 return FALSE;
1147 if (!GetResponse("250"))
1148 return FALSE;
1150 int start=0;
1151 while(start>=0)
1153 CString one = m_csAddrTo.Tokenize(L";", start).Trim();
1154 if(one.IsEmpty())
1155 continue;
1157 CStringUtils::ParseEmailAddress(one, addr);
1159 str.Format(L"RCPT TO: <%s>\r\n", (LPCTSTR)addr);
1160 if (!Send(str))
1161 return FALSE;
1162 if (!GetResponse("250"))
1163 return FALSE;
1166 if (!Send("DATA\r\n"))
1167 return FALSE;
1168 if (!GetResponse("354"))
1169 return FALSE;
1171 return TRUE;
1174 BOOL CHwSMTP::SendSubject(const CString &hostname)
1176 CString csSubject;
1177 csSubject += L"Date: ";
1178 COleDateTime tNow = COleDateTime::GetCurrentTime();
1179 if ( tNow > 1 )
1180 csSubject += FormatDateTime(tNow);
1181 csSubject += L"\r\n";
1182 csSubject.AppendFormat(L"From: %s\r\n", (LPCTSTR)m_csAddrFrom);
1184 if (!m_StrCC.IsEmpty())
1185 csSubject.AppendFormat(L"CC: %s\r\n", (LPCTSTR)m_StrCC);
1187 if(m_csSender.IsEmpty())
1188 m_csSender = this->m_csAddrFrom;
1190 csSubject.AppendFormat(L"Sender: %s\r\n", (LPCTSTR)m_csSender);
1192 if(this->m_csToList.IsEmpty())
1193 m_csToList = m_csReceiverName;
1195 csSubject.AppendFormat(L"To: %s\r\n", (LPCTSTR)m_csToList);
1197 csSubject.AppendFormat(L"Subject: %s\r\n", (LPCTSTR)GetEncodedHeader(m_csSubject));
1199 CString m_ListID(GetGUID());
1200 if (m_ListID.IsEmpty())
1202 m_csLastError = L"Could not generate Message-ID";
1203 return FALSE;
1205 csSubject.AppendFormat(L"Message-ID: <%s@%s>\r\n", (LPCTSTR)m_ListID, (LPCTSTR)hostname);
1206 csSubject.AppendFormat(L"X-Mailer: TortoiseGit\r\nMIME-Version: 1.0\r\nContent-Type: %s\r\n\r\n", (LPCTSTR)m_csMIMEContentType);
1208 return Send(csSubject);
1211 BOOL CHwSMTP::SendBody()
1213 CString csBody;
1215 if ( m_StrAryAttach.GetSize() > 0 )
1217 csBody.AppendFormat(L"%s\r\n\r\n", (LPCTSTR)m_csNoMIMEText);
1218 csBody.AppendFormat(L"--%s\r\n", (LPCTSTR)m_csPartBoundary);
1219 csBody.AppendFormat(L"Content-Type: text/plain\r\nContent-Transfer-Encoding: UTF-8\r\n\r\n");
1222 m_csBody.Replace(L"\n.\n", L"\n..\n");
1223 m_csBody.Replace(L"\n.\r\n", L"\n..\r\n");
1225 csBody += m_csBody;
1226 csBody += L"\r\n";
1228 return Send(csBody);
1231 BOOL CHwSMTP::SendAttach()
1233 int nCountAttach = (int)m_StrAryAttach.GetSize();
1234 if ( nCountAttach < 1 ) return TRUE;
1236 for ( int i=0; i<nCountAttach; i++ )
1238 if ( !SendOnAttach ( m_StrAryAttach.GetAt(i) ) )
1239 return FALSE;
1242 Send(L"--" + m_csPartBoundary + L"--\r\n");
1244 return TRUE;
1247 BOOL CHwSMTP::SendOnAttach(LPCTSTR lpszFileName)
1249 ASSERT ( lpszFileName );
1250 CString csAttach;
1251 CString csShortFileName = CPathUtils::GetFileNameFromPath(lpszFileName);
1253 csAttach.AppendFormat(L"--%s\r\n", (LPCTSTR)m_csPartBoundary);
1254 csAttach.AppendFormat(L"--%s\r\n", (LPCTSTR)m_csPartBoundary);
1255 csAttach.AppendFormat(L"Content-Type: application/octet-stream; file=%s\r\n", (LPCTSTR)csShortFileName);
1256 csAttach.AppendFormat(L"Content-Transfer-Encoding: base64\r\n");
1257 csAttach.AppendFormat(L"Content-Disposition: attachment; filename=%s\r\n\r\n", (LPCTSTR)csShortFileName);
1259 auto dwFileSize = GetFileSize(lpszFileName);
1260 if ( dwFileSize > 5*1024*1024 )
1262 m_csLastError.Format(L"File [%s] too big. File size is : %s", lpszFileName, (LPCTSTR)FormatBytes(dwFileSize));
1263 return FALSE;
1265 auto pBuf = std::make_unique<char[]>(dwFileSize + 1);
1266 if (!pBuf)
1267 ::AfxThrowMemoryException();
1269 if (!Send(csAttach))
1270 return FALSE;
1272 CFile file;
1273 CStringA filedata;
1276 if ( !file.Open ( lpszFileName, CFile::modeRead ) )
1278 m_csLastError.Format(L"Open file [%s] failed", lpszFileName);
1279 return FALSE;
1281 UINT nFileLen = file.Read(pBuf.get(), dwFileSize);
1282 filedata = EncodeBase64(pBuf.get(), nFileLen);
1283 filedata += L"\r\n\r\n";
1285 catch (CFileException *e)
1287 e->Delete();
1288 m_csLastError.Format(L"Read file [%s] failed", lpszFileName);
1289 return FALSE;
1292 if (!SendBuffer(filedata))
1293 return FALSE;
1295 return TRUE;
1298 CString CHwSMTP::GetLastErrorText()
1300 return m_csLastError;