1
// HwSMTP.cpp: implementation of the CHwSMTP class.
3 // Schannel/SSPI implementation based on http://www.coastrd.com/c-schannel-smtp
5 //////////////////////////////////////////////////////////////////////
12 #include "FormatMessageWrapper.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"")
27 #define HANDLE_IS_VALID(h) (reinterpret_cast<HANDLE>(h) != NULL && reinterpret_cast<HANDLE>(h) != INVALID_HANDLE_VALUE)
29 DWORD dwProtocol
= SP_PROT_TLS1
| SP_PROT_TLS1_1
| SP_PROT_TLS1_2
| SP_PROT_TLS1_3
; // 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
)
46 if (S_OK
!= VarUdateFromDate(DateTime
.m_dt
, 0, &ud
))
49 static const wchar_t* weeks
[] = { L
"Sun", L
"Mon", L
"Tue", L
"Wen", L
"Thu", L
"Fri", L
"Sat" };
50 static const wchar_t* 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
);
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));
64 static CString
GetGUID()
68 if (CoCreateGuid(&guid
) == S_OK
)
71 if (UuidToString(&guid
, &guidStr
) == RPC_S_OK
)
73 sGuid
= reinterpret_cast<LPWSTR
>(guidStr
);
74 RpcStringFree(&guidStr
);
82 m_csPartBoundary
= L
"NextPart_" + GetGUID();
83 m_csMIMEContentType
.Format(L
"multipart/mixed; boundary=%s", static_cast<LPCWSTR
>(m_csPartBoundary
));
84 m_csNoMIMEText
= L
"This is a multi-part message in MIME format.";
93 CString
CHwSMTP::GetServerAddress(const CString
& in
)
96 CStringUtils::ParseEmailAddress(in
, email
);
98 int start
= email
.Find(L
'@');
99 return email
.Mid(start
+ 1);
102 BOOL
CHwSMTP::SendSpeedEmail
104 LPCWSTR lpszAddrFrom
,
108 CStringArray
*pStrAryAttach
,
115 To
+= GET_SAFE_STRING(lpszAddrTo
);
117 To
+= GET_SAFE_STRING(pStrAryCC
);
119 std::map
<CString
,std::vector
<CString
>> Address
;
124 CString one
= To
.Tokenize(L
";", start
).Trim();
128 CString addr
= GetServerAddress(one
);
132 Address
[addr
].push_back(one
);
135 for (const auto& maildomain
: Address
)
137 PDNS_RECORD pDnsRecord
;
139 DnsQuery(maildomain
.first
,
140 DNS_TYPE_MX
,DNS_QUERY_STANDARD
,
141 nullptr, //Contains DNS server IP address.
142 &pDnsRecord
, //Resource record that contains the response.
147 m_csLastError
.Format(L
"DNS query failed %d", status
);
151 SCOPE_EXIT
{ DnsRecordListFree(pDnsRecord
, DnsFreeRecordList
); };
154 std::for_each(maildomain
.second
.cbegin(), maildomain
.second
.cend(), [&to
](auto recipient
) {
161 PDNS_RECORD pNext
= pDnsRecord
;
164 if(pNext
->wType
== DNS_TYPE_MX
)
165 if (SendEmail(pNext
->Data
.MX
.pNameExchange
, nullptr, false,
166 lpszAddrFrom
, to
, lpszSubject
, lpszBody
, pStrAryAttach
, pStrAryCC
,
167 25,pSend
,lpszAddrTo
))
178 static SECURITY_STATUS
ClientHandshakeLoop(CSocket
* Socket
, PCredHandle phCreds
, CtxtHandle
* phContext
, BOOL fDoInitialRead
, SecBuffer
* pExtraData
)
180 SecBufferDesc OutBuffer
, InBuffer
;
181 SecBuffer InBuffers
[2], OutBuffers
[1];
182 DWORD dwSSPIFlags
, dwSSPIOutFlags
, cbData
, cbIoBuffer
;
184 SECURITY_STATUS scRet
;
187 dwSSPIFlags
= ISC_REQ_SEQUENCE_DETECT
| ISC_REQ_REPLAY_DETECT
| ISC_REQ_CONFIDENTIALITY
|
188 ISC_RET_EXTENDED_ERROR
| ISC_REQ_ALLOCATE_MEMORY
| ISC_REQ_STREAM
;
190 // Allocate data buffer.
191 auto IoBuffer
= std::make_unique
<UCHAR
[]>(IO_BUFFER_SIZE
);
194 // printf("**** Out of memory (1)\n");
195 return SEC_E_INTERNAL_ERROR
;
198 fDoRead
= fDoInitialRead
;
200 // Loop until the handshake is finished or an error occurs.
201 scRet
= SEC_I_CONTINUE_NEEDED
;
203 while (scRet
== SEC_I_CONTINUE_NEEDED
|| scRet
== SEC_E_INCOMPLETE_MESSAGE
|| scRet
== SEC_I_INCOMPLETE_CREDENTIALS
)
205 if (0 == cbIoBuffer
|| scRet
== SEC_E_INCOMPLETE_MESSAGE
) // Read data from server.
209 cbData
= Socket
->Receive(IoBuffer
.get() + cbIoBuffer
, IO_BUFFER_SIZE
- cbIoBuffer
, 0);
210 if (cbData
== SOCKET_ERROR
)
212 // printf("**** Error %d reading data from server\n", WSAGetLastError());
213 scRet
= SEC_E_INTERNAL_ERROR
;
216 else if (cbData
== 0)
218 // printf("**** Server unexpectedly disconnected\n");
219 scRet
= SEC_E_INTERNAL_ERROR
;
222 // printf("%d bytes of handshake data received\n", cbData);
223 cbIoBuffer
+= cbData
;
229 // Set up the input buffers. Buffer 0 is used to pass in data
230 // received from the server. Schannel will consume some or all
231 // of this. Leftover data (if any) will be placed in buffer 1 and
232 // given a buffer type of SECBUFFER_EXTRA.
233 InBuffers
[0].pvBuffer
= IoBuffer
.get();
234 InBuffers
[0].cbBuffer
= cbIoBuffer
;
235 InBuffers
[0].BufferType
= SECBUFFER_TOKEN
;
237 InBuffers
[1].pvBuffer
= nullptr;
238 InBuffers
[1].cbBuffer
= 0;
239 InBuffers
[1].BufferType
= SECBUFFER_EMPTY
;
241 InBuffer
.cBuffers
= 2;
242 InBuffer
.pBuffers
= InBuffers
;
243 InBuffer
.ulVersion
= SECBUFFER_VERSION
;
245 // Set up the output buffers. These are initialized to nullptr
246 // so as to make it less likely we'll attempt to free random
248 OutBuffers
[0].pvBuffer
= nullptr;
249 OutBuffers
[0].BufferType
= SECBUFFER_TOKEN
;
250 OutBuffers
[0].cbBuffer
= 0;
252 OutBuffer
.cBuffers
= 1;
253 OutBuffer
.pBuffers
= OutBuffers
;
254 OutBuffer
.ulVersion
= SECBUFFER_VERSION
;
256 // Call InitializeSecurityContext.
257 scRet
= g_pSSPI
->InitializeSecurityContext(phCreds
, phContext
, nullptr, dwSSPIFlags
, 0, SECURITY_NATIVE_DREP
, &InBuffer
, 0, nullptr, &OutBuffer
, &dwSSPIOutFlags
, &tsExpiry
);
259 // If InitializeSecurityContext was successful (or if the error was
260 // one of the special extended ones), send the contends of the output
261 // buffer to the server.
262 if (scRet
== SEC_E_OK
|| scRet
== SEC_I_CONTINUE_NEEDED
|| FAILED(scRet
) && (dwSSPIOutFlags
& ISC_RET_EXTENDED_ERROR
))
264 if (OutBuffers
[0].cbBuffer
!= 0 && OutBuffers
[0].pvBuffer
!= nullptr)
266 cbData
= Socket
->Send(OutBuffers
[0].pvBuffer
, OutBuffers
[0].cbBuffer
, 0 );
267 if(cbData
== SOCKET_ERROR
|| cbData
== 0)
269 // printf( "**** Error %d sending data to server (2)\n", WSAGetLastError() );
270 g_pSSPI
->FreeContextBuffer(OutBuffers
[0].pvBuffer
);
271 g_pSSPI
->DeleteSecurityContext(phContext
);
272 return SEC_E_INTERNAL_ERROR
;
274 // printf("%d bytes of handshake data sent\n", cbData);
276 // Free output buffer.
277 g_pSSPI
->FreeContextBuffer(OutBuffers
[0].pvBuffer
);
278 OutBuffers
[0].pvBuffer
= nullptr;
282 // If InitializeSecurityContext returned SEC_E_INCOMPLETE_MESSAGE,
283 // then we need to read more data from the server and try again.
284 if (scRet
== SEC_E_INCOMPLETE_MESSAGE
) continue;
286 // If InitializeSecurityContext returned SEC_E_OK, then the
287 // handshake completed successfully.
288 if (scRet
== SEC_E_OK
)
290 // If the "extra" buffer contains data, this is encrypted application
291 // protocol layer stuff. It needs to be saved. The application layer
292 // will later decrypt it with DecryptMessage.
293 // printf("Handshake was successful\n");
295 if (InBuffers
[1].BufferType
== SECBUFFER_EXTRA
)
297 pExtraData
->pvBuffer
= LocalAlloc( LMEM_FIXED
, InBuffers
[1].cbBuffer
);
298 if (pExtraData
->pvBuffer
== nullptr)
300 // printf("**** Out of memory (2)\n");
301 return SEC_E_INTERNAL_ERROR
;
304 MoveMemory(pExtraData
->pvBuffer
, IoBuffer
.get() + (cbIoBuffer
- InBuffers
[1].cbBuffer
), InBuffers
[1].cbBuffer
);
306 pExtraData
->cbBuffer
= InBuffers
[1].cbBuffer
;
307 pExtraData
->BufferType
= SECBUFFER_TOKEN
;
309 // printf( "%d bytes of app data was bundled with handshake data\n", pExtraData->cbBuffer );
313 pExtraData
->pvBuffer
= nullptr;
314 pExtraData
->cbBuffer
= 0;
315 pExtraData
->BufferType
= SECBUFFER_EMPTY
;
317 break; // Bail out to quit
320 // Check for fatal error.
323 // printf("**** Error 0x%x returned by InitializeSecurityContext (2)\n", scRet);
327 // If InitializeSecurityContext returned SEC_I_INCOMPLETE_CREDENTIALS,
328 // then the server just requested client authentication.
329 if (scRet
== SEC_I_INCOMPLETE_CREDENTIALS
)
331 // Busted. The server has requested client authentication and
332 // the credential we supplied didn't contain a client certificate.
333 // This function will read the list of trusted certificate
334 // authorities ("issuers") that was received from the server
335 // and attempt to find a suitable client certificate that
336 // was issued by one of these. If this function is successful,
337 // then we will connect using the new certificate. Otherwise,
338 // we will attempt to connect anonymously (using our current credentials).
339 //GetNewClientCredentials(phCreds, phContext);
343 scRet
= SEC_I_CONTINUE_NEEDED
;
347 // Copy any leftover data from the "extra" buffer, and go around again.
348 if ( InBuffers
[1].BufferType
== SECBUFFER_EXTRA
)
350 MoveMemory(IoBuffer
.get(), IoBuffer
.get() + (cbIoBuffer
- InBuffers
[1].cbBuffer
), InBuffers
[1].cbBuffer
);
351 cbIoBuffer
= InBuffers
[1].cbBuffer
;
357 // Delete the security context in the case of a fatal error.
359 g_pSSPI
->DeleteSecurityContext(phContext
);
364 static SECURITY_STATUS
PerformClientHandshake( CSocket
* Socket
, PCredHandle phCreds
, LPWSTR pszServerName
, CtxtHandle
* phContext
, SecBuffer
* pExtraData
)
366 SecBufferDesc OutBuffer
;
367 SecBuffer OutBuffers
[1];
368 DWORD dwSSPIFlags
, dwSSPIOutFlags
, cbData
;
370 SECURITY_STATUS scRet
;
372 dwSSPIFlags
= ISC_REQ_SEQUENCE_DETECT
| ISC_REQ_REPLAY_DETECT
| ISC_REQ_CONFIDENTIALITY
|
373 ISC_RET_EXTENDED_ERROR
| ISC_REQ_ALLOCATE_MEMORY
| ISC_REQ_STREAM
;
375 // Initiate a ClientHello message and generate a token.
376 OutBuffers
[0].pvBuffer
= nullptr;
377 OutBuffers
[0].BufferType
= SECBUFFER_TOKEN
;
378 OutBuffers
[0].cbBuffer
= 0;
380 OutBuffer
.cBuffers
= 1;
381 OutBuffer
.pBuffers
= OutBuffers
;
382 OutBuffer
.ulVersion
= SECBUFFER_VERSION
;
384 scRet
= g_pSSPI
->InitializeSecurityContext(phCreds
, nullptr, pszServerName
, dwSSPIFlags
, 0, SECURITY_NATIVE_DREP
, nullptr, 0, phContext
, &OutBuffer
, &dwSSPIOutFlags
, &tsExpiry
);
386 if (scRet
!= SEC_I_CONTINUE_NEEDED
)
388 // printf("**** Error %d returned by InitializeSecurityContext (1)\n", scRet);
392 // Send response to server if there is one.
393 if (OutBuffers
[0].cbBuffer
!= 0 && OutBuffers
[0].pvBuffer
!= nullptr)
395 cbData
= Socket
->Send(OutBuffers
[0].pvBuffer
, OutBuffers
[0].cbBuffer
, 0);
396 if (cbData
== SOCKET_ERROR
|| cbData
== 0)
398 // printf("**** Error %d sending data to server (1)\n", WSAGetLastError());
399 g_pSSPI
->FreeContextBuffer(OutBuffers
[0].pvBuffer
);
400 g_pSSPI
->DeleteSecurityContext(phContext
);
401 return SEC_E_INTERNAL_ERROR
;
403 // printf("%d bytes of handshake data sent\n", cbData);
405 g_pSSPI
->FreeContextBuffer(OutBuffers
[0].pvBuffer
); // Free output buffer.
406 OutBuffers
[0].pvBuffer
= nullptr;
409 return ClientHandshakeLoop(Socket
, phCreds
, phContext
, TRUE
, pExtraData
);
412 static SECURITY_STATUS
CreateCredentials(PCredHandle phCreds
)
415 SECURITY_STATUS Status
;
416 DWORD cSupportedAlgs
= 0;
417 ALG_ID rgbSupportedAlgs
[16];
419 // Build Schannel credential structure. Currently, this sample only
420 // specifies the protocol to be used (and optionally the certificate,
421 // of course). Real applications may wish to specify other parameters as well.
422 SecureZeroMemory(&SchannelCred
, sizeof(SchannelCred
));
424 SchannelCred
.dwVersion
= SCHANNEL_CRED_VERSION
;
425 SchannelCred
.grbitEnabledProtocols
= dwProtocol
;
428 rgbSupportedAlgs
[cSupportedAlgs
++] = aiKeyExch
;
432 SchannelCred
.cSupportedAlgs
= cSupportedAlgs
;
433 SchannelCred
.palgSupportedAlgs
= rgbSupportedAlgs
;
436 SchannelCred
.dwFlags
|= SCH_CRED_NO_DEFAULT_CREDS
;
438 // The SCH_CRED_MANUAL_CRED_VALIDATION flag is specified because
439 // this sample verifies the server certificate manually.
440 // Applications that expect to run on WinNT, Win9x, or WinME
441 // should specify this flag and also manually verify the server
442 // certificate. Applications running on newer versions of Windows can
443 // leave off this flag, in which case the InitializeSecurityContext
444 // function will validate the server certificate automatically.
445 SchannelCred
.dwFlags
|= SCH_CRED_MANUAL_CRED_VALIDATION
;
447 // Create an SSPI credential.
448 Status
= g_pSSPI
->AcquireCredentialsHandle(nullptr, // Name of principal
449 const_cast<SEC_WCHAR
*>(UNISP_NAME
), // Name of package
450 SECPKG_CRED_OUTBOUND
, // Flags indicating use
451 nullptr, // Pointer to logon ID
452 &SchannelCred
, // Package specific data
453 nullptr, // Pointer to GetKey() func
454 nullptr, // Value to pass to GetKey()
455 phCreds
, // (out) Cred Handle
456 &tsExpiry
); // (out) Lifetime (optional)
461 static DWORD
EncryptSend(CSocket
* Socket
, CtxtHandle
* phContext
, PBYTE pbIoBuffer
, SecPkgContext_StreamSizes Sizes
)
462 // http://msdn.microsoft.com/en-us/library/aa375378(VS.85).aspx
463 // The encrypted message is encrypted in place, overwriting the original contents of its buffer.
465 SECURITY_STATUS scRet
;
466 SecBufferDesc Message
;
467 SecBuffer Buffers
[4];
471 pbMessage
= pbIoBuffer
+ Sizes
.cbHeader
; // Offset by "header size"
472 cbMessage
= static_cast<DWORD
>(strlen(reinterpret_cast<char*>(pbMessage
)));
474 // Encrypt the HTTP request.
475 Buffers
[0].pvBuffer
= pbIoBuffer
; // Pointer to buffer 1
476 Buffers
[0].cbBuffer
= Sizes
.cbHeader
; // length of header
477 Buffers
[0].BufferType
= SECBUFFER_STREAM_HEADER
; // Type of the buffer
479 Buffers
[1].pvBuffer
= pbMessage
; // Pointer to buffer 2
480 Buffers
[1].cbBuffer
= cbMessage
; // length of the message
481 Buffers
[1].BufferType
= SECBUFFER_DATA
; // Type of the buffer
483 Buffers
[2].pvBuffer
= pbMessage
+ cbMessage
; // Pointer to buffer 3
484 Buffers
[2].cbBuffer
= Sizes
.cbTrailer
; // length of the trailor
485 Buffers
[2].BufferType
= SECBUFFER_STREAM_TRAILER
; // Type of the buffer
487 Buffers
[3].pvBuffer
= SECBUFFER_EMPTY
; // Pointer to buffer 4
488 Buffers
[3].cbBuffer
= SECBUFFER_EMPTY
; // length of buffer 4
489 Buffers
[3].BufferType
= SECBUFFER_EMPTY
; // Type of the buffer 4
491 Message
.ulVersion
= SECBUFFER_VERSION
; // Version number
492 Message
.cBuffers
= 4; // Number of buffers - must contain four SecBuffer structures.
493 Message
.pBuffers
= Buffers
; // Pointer to array of buffers
495 scRet
= g_pSSPI
->EncryptMessage(phContext
, 0, &Message
, 0); // must contain four SecBuffer structures.
498 // printf("**** Error 0x%x returned by EncryptMessage\n", scRet);
503 // Send the encrypted data to the server.
504 return Socket
->Send(pbIoBuffer
, Buffers
[0].cbBuffer
+ Buffers
[1].cbBuffer
+ Buffers
[2].cbBuffer
, 0);
507 static LONG
DisconnectFromServer(CSocket
* Socket
, PCredHandle phCreds
, CtxtHandle
* phContext
)
510 DWORD dwType
, dwSSPIFlags
, dwSSPIOutFlags
, cbMessage
, cbData
, Status
;
511 SecBufferDesc OutBuffer
;
512 SecBuffer OutBuffers
[1];
515 dwType
= SCHANNEL_SHUTDOWN
; // Notify schannel that we are about to close the connection.
517 OutBuffers
[0].pvBuffer
= &dwType
;
518 OutBuffers
[0].BufferType
= SECBUFFER_TOKEN
;
519 OutBuffers
[0].cbBuffer
= sizeof(dwType
);
521 OutBuffer
.cBuffers
= 1;
522 OutBuffer
.pBuffers
= OutBuffers
;
523 OutBuffer
.ulVersion
= SECBUFFER_VERSION
;
525 Status
= g_pSSPI
->ApplyControlToken(phContext
, &OutBuffer
);
528 // printf("**** Error 0x%x returned by ApplyControlToken\n", Status);
532 // Build an SSL close notify message.
533 dwSSPIFlags
= ISC_REQ_SEQUENCE_DETECT
| ISC_REQ_REPLAY_DETECT
| ISC_REQ_CONFIDENTIALITY
| ISC_RET_EXTENDED_ERROR
| ISC_REQ_ALLOCATE_MEMORY
| ISC_REQ_STREAM
;
535 OutBuffers
[0].pvBuffer
= nullptr;
536 OutBuffers
[0].BufferType
= SECBUFFER_TOKEN
;
537 OutBuffers
[0].cbBuffer
= 0;
539 OutBuffer
.cBuffers
= 1;
540 OutBuffer
.pBuffers
= OutBuffers
;
541 OutBuffer
.ulVersion
= SECBUFFER_VERSION
;
543 Status
= g_pSSPI
->InitializeSecurityContext(phCreds
, phContext
, nullptr, dwSSPIFlags
, 0, SECURITY_NATIVE_DREP
, nullptr, 0, phContext
, &OutBuffer
, &dwSSPIOutFlags
, &tsExpiry
);
547 // printf("**** Error 0x%x returned by InitializeSecurityContext\n", Status);
551 pbMessage
= static_cast<PBYTE
>(OutBuffers
[0].pvBuffer
);
552 cbMessage
= OutBuffers
[0].cbBuffer
;
554 // Send the close notify message to the server.
555 if (pbMessage
!= nullptr && cbMessage
!= 0)
557 cbData
= Socket
->Send(pbMessage
, cbMessage
, 0);
558 if (cbData
== SOCKET_ERROR
|| cbData
== 0)
560 Status
= WSAGetLastError();
563 // printf("Sending Close Notify\n");
564 // printf("%d bytes of handshake data sent\n", cbData);
565 g_pSSPI
->FreeContextBuffer(pbMessage
); // Free output buffer.
569 g_pSSPI
->DeleteSecurityContext(phContext
); // Free the security context.
575 static SECURITY_STATUS
ReadDecrypt(CSocket
* Socket
, PCredHandle phCreds
, CtxtHandle
* phContext
, PBYTE pbIoBuffer
, DWORD cbIoBufferLength
)
577 // calls recv() - blocking socket read
578 // http://msdn.microsoft.com/en-us/library/ms740121(VS.85).aspx
580 // The encrypted message is decrypted in place, overwriting the original contents of its buffer.
581 // http://msdn.microsoft.com/en-us/library/aa375211(VS.85).aspx
584 SecBuffer ExtraBuffer
;
585 SecBuffer
* pDataBuffer
, * pExtraBuffer
;
587 SECURITY_STATUS scRet
;
588 SecBufferDesc Message
;
589 SecBuffer Buffers
[4];
591 DWORD cbIoBuffer
, cbData
, length
;
594 // Read data from server until done.
597 while (TRUE
) // Read some data.
599 if (cbIoBuffer
== 0 || scRet
== SEC_E_INCOMPLETE_MESSAGE
) // get the data
601 cbData
= Socket
->Receive(pbIoBuffer
+ cbIoBuffer
, cbIoBufferLength
- cbIoBuffer
, 0);
602 if (cbData
== SOCKET_ERROR
)
604 // printf("**** Error %d reading data from server\n", WSAGetLastError());
605 scRet
= SEC_E_INTERNAL_ERROR
;
608 else if (cbData
== 0) // Server disconnected.
612 // printf("**** Server unexpectedly disconnected\n");
613 scRet
= SEC_E_INTERNAL_ERROR
;
621 // printf("%d bytes of (encrypted) application data received\n", cbData);
622 cbIoBuffer
+= cbData
;
626 // Decrypt the received data.
627 Buffers
[0].pvBuffer
= pbIoBuffer
;
628 Buffers
[0].cbBuffer
= cbIoBuffer
;
629 Buffers
[0].BufferType
= SECBUFFER_DATA
; // Initial Type of the buffer 1
630 Buffers
[1].BufferType
= SECBUFFER_EMPTY
; // Initial Type of the buffer 2
631 Buffers
[2].BufferType
= SECBUFFER_EMPTY
; // Initial Type of the buffer 3
632 Buffers
[3].BufferType
= SECBUFFER_EMPTY
; // Initial Type of the buffer 4
634 Message
.ulVersion
= SECBUFFER_VERSION
; // Version number
635 Message
.cBuffers
= 4; // Number of buffers - must contain four SecBuffer structures.
636 Message
.pBuffers
= Buffers
; // Pointer to array of buffers
638 scRet
= g_pSSPI
->DecryptMessage(phContext
, &Message
, 0, nullptr);
639 if (scRet
== SEC_I_CONTEXT_EXPIRED
)
640 break; // Server signalled end of session
641 // if (scRet == SEC_E_INCOMPLETE_MESSAGE - Input buffer has partial encrypted record, read more
642 if (scRet
!= SEC_E_OK
&& scRet
!= SEC_I_RENEGOTIATE
&& scRet
!= SEC_I_CONTEXT_EXPIRED
)
645 // Locate data and (optional) extra buffers.
646 pDataBuffer
= nullptr;
647 pExtraBuffer
= nullptr;
648 for (int i
= 1; i
< 4; ++i
)
650 if (pDataBuffer
== nullptr && Buffers
[i
].BufferType
== SECBUFFER_DATA
)
651 pDataBuffer
= &Buffers
[i
];
652 if (pExtraBuffer
== nullptr && Buffers
[i
].BufferType
== SECBUFFER_EXTRA
)
653 pExtraBuffer
= &Buffers
[i
];
656 // Display the decrypted data.
659 length
= pDataBuffer
->cbBuffer
;
660 if (length
) // check if last two chars are CR LF
662 buff
= static_cast<PBYTE
>(pDataBuffer
->pvBuffer
);
663 if (buff
[length
-2] == 13 && buff
[length
-1] == 10) // Found CRLF
671 // Move any "extra" data to the input buffer.
674 MoveMemory(pbIoBuffer
, pExtraBuffer
->pvBuffer
, pExtraBuffer
->cbBuffer
);
675 cbIoBuffer
= pExtraBuffer
->cbBuffer
;
680 // The server wants to perform another handshake sequence.
681 if (scRet
== SEC_I_RENEGOTIATE
)
683 // printf("Server requested renegotiate!\n");
684 scRet
= ClientHandshakeLoop( Socket
, phCreds
, phContext
, FALSE
, &ExtraBuffer
);
685 if (scRet
!= SEC_E_OK
)
688 if (ExtraBuffer
.pvBuffer
) // Move any "extra" data to the input buffer.
690 MoveMemory(pbIoBuffer
, ExtraBuffer
.pvBuffer
, ExtraBuffer
.cbBuffer
);
691 cbIoBuffer
= ExtraBuffer
.cbBuffer
;
694 } // Loop till CRLF is found at the end of the data
699 BOOL
CHwSMTP::SendEmail (
700 LPCWSTR lpszSmtpSrvHost
,
701 CCredentials
* credentials
,
703 LPCWSTR lpszAddrFrom
,
707 CStringArray
* pStrAryAttach
/*=nullptr*/,
708 LPCWSTR pStrAryCC
/*=nullptr*/,
709 UINT nSmtpSrvPort
,/*=25*/
715 m_StrAryAttach
.RemoveAll();
717 m_StrCC
= GET_SAFE_STRING(pStrAryCC
);
719 m_csSmtpSrvHost
= GET_SAFE_STRING ( lpszSmtpSrvHost
);
720 if ( m_csSmtpSrvHost
.GetLength() <= 0 )
722 m_csLastError
= L
"Parameter Error!";
725 m_credentials
= credentials
;
726 m_bMustAuth
= bMustAuth
;
727 if (m_bMustAuth
&& (!m_credentials
|| m_credentials
->m_username
.IsEmpty()))
729 m_csLastError
= L
"Parameter Error!";
733 m_csAddrFrom
= GET_SAFE_STRING ( lpszAddrFrom
);
734 m_csAddrTo
= GET_SAFE_STRING ( lpszAddrTo
);
735 m_csSubject
= GET_SAFE_STRING ( lpszSubject
);
736 m_csBody
= GET_SAFE_STRING ( lpszBody
);
738 this->m_csSender
= GET_SAFE_STRING(pSender
);
739 this->m_csToList
= GET_SAFE_STRING(pToList
);
741 m_nSmtpSrvPort
= nSmtpSrvPort
;
744 m_csAddrFrom
.GetLength() <= 0 || m_csAddrTo
.GetLength() <= 0
747 m_csLastError
= L
"Parameter Error!";
753 m_StrAryAttach
.Append ( *pStrAryAttach
);
755 if (m_StrAryAttach
.IsEmpty())
756 m_csMIMEContentType
= L
"text/plain\r\nContent-Transfer-Encoding: 8bit";
760 if ( !m_SendSock
.Create () )
762 //int nResult = GetLastError();
763 m_csLastError
= L
"Create socket failed!";
770 m_iSecurityLevel
= want_tls
;
773 m_iSecurityLevel
= ssl
;
776 m_iSecurityLevel
= none
;
779 if ( !m_SendSock
.Connect ( m_csSmtpSrvHost
, m_nSmtpSrvPort
) )
781 m_csLastError
.Format(L
"Connect to [%s] failed", static_cast<LPCWSTR
>(m_csSmtpSrvHost
));
785 if (m_iSecurityLevel
<= want_tls
)
787 if (!GetResponse("220"))
790 Send(L
"STARTTLS\r\n");
791 if (GetResponse("220"))
792 m_iSecurityLevel
= tls_established
;
793 else if (m_iSecurityLevel
== want_tls
)
800 SECURITY_STATUS Status
;
802 CtxtHandle contextStruct
;
803 CredHandle credentialsStruct
;
805 if (m_iSecurityLevel
>= ssl
)
807 g_pSSPI
= InitSecurityInterface();
809 contextStruct
.dwLower
= 0;
810 contextStruct
.dwUpper
= 0;
812 hCreds
= &credentialsStruct
;
813 credentialsStruct
.dwLower
= 0;
814 credentialsStruct
.dwUpper
= 0;
815 Status
= CreateCredentials(hCreds
);
816 if (Status
!= SEC_E_OK
)
818 m_csLastError
= static_cast<LPCWSTR
>(CFormatMessageWrapper(Status
));
822 hContext
= &contextStruct
;
823 Status
= PerformClientHandshake(&m_SendSock
, hCreds
, m_csSmtpSrvHost
.GetBuffer(), hContext
, &ExtraData
);
824 if (Status
!= SEC_E_OK
)
826 m_csLastError
= static_cast<LPCWSTR
>(CFormatMessageWrapper(Status
));
830 PCCERT_CONTEXT pRemoteCertContext
= nullptr;
831 // Authenticate server's credentials. Get server's certificate.
832 Status
= g_pSSPI
->QueryContextAttributes(hContext
, SECPKG_ATTR_REMOTE_CERT_CONTEXT
, static_cast<PVOID
>(&pRemoteCertContext
));
835 m_csLastError
= static_cast<LPCWSTR
>(CFormatMessageWrapper(Status
));
840 cert
.parent
.cert_type
= GIT_CERT_X509
;
841 cert
.data
= pRemoteCertContext
->pbCertEncoded
;
842 cert
.len
= pRemoteCertContext
->cbCertEncoded
;
843 if (CAppUtils::Git2CertificateCheck(reinterpret_cast<git_cert
*>(&cert
), 0, CUnicodeUtils::GetUTF8(m_csSmtpSrvHost
), nullptr))
845 CertFreeCertificateContext(pRemoteCertContext
);
846 m_csLastError
= L
"Invalid certificate.";
850 CertFreeCertificateContext(pRemoteCertContext
);
852 Status
= g_pSSPI
->QueryContextAttributes(hContext
, SECPKG_ATTR_STREAM_SIZES
, &Sizes
);
855 m_csLastError
= static_cast<LPCWSTR
>(CFormatMessageWrapper(Status
));
860 cbIoBufferLength
= Sizes
.cbHeader
+ Sizes
.cbMaximumMessage
+ Sizes
.cbTrailer
;
861 pbIoBuffer
= static_cast<PBYTE
>(LocalAlloc(LMEM_FIXED
, cbIoBufferLength
));
862 if (pbIoBuffer
== nullptr)
864 m_csLastError
= L
"Could not allocate memory";
867 SecureZeroMemory(pbIoBuffer
, cbIoBufferLength
);
870 if (m_iSecurityLevel
<= ssl
)
872 if (!GetResponse("220"))
880 if (m_iSecurityLevel
>= ssl
)
882 if (hContext
&& hCreds
)
883 DisconnectFromServer(&m_SendSock
, hCreds
, hContext
);
886 SecureZeroMemory(pbIoBuffer
, cbIoBufferLength
);
887 LocalFree(pbIoBuffer
);
888 pbIoBuffer
= nullptr;
889 cbIoBufferLength
= 0;
893 g_pSSPI
->DeleteSecurityContext(hContext
);
898 g_pSSPI
->FreeCredentialsHandle(hCreds
);
909 BOOL
CHwSMTP::GetResponse(LPCSTR lpszVerifyCode
)
911 if (!lpszVerifyCode
|| strlen(lpszVerifyCode
) < 1)
914 SECURITY_STATUS scRet
= SEC_E_OK
;
916 char szRecvBuf
[1024] = {0};
918 char szStatusCode
[4] = {0};
920 if (m_iSecurityLevel
>= ssl
)
922 scRet
= ReadDecrypt(&m_SendSock
, hCreds
, hContext
, pbIoBuffer
, cbIoBufferLength
);
923 SecureZeroMemory(szRecvBuf
, 1024);
924 memcpy(szRecvBuf
, pbIoBuffer
+Sizes
.cbHeader
, 1024);
927 nRet
= m_SendSock
.Receive(szRecvBuf
, sizeof(szRecvBuf
));
928 //TRACE(L"Received : %s\r\n", szRecvBuf);
929 if (nRet
== 0 && m_iSecurityLevel
== none
|| m_iSecurityLevel
>= ssl
&& scRet
!= SEC_E_OK
)
931 m_csLastError
= L
"Receive TCP data failed";
934 memcpy ( szStatusCode
, szRecvBuf
, 3 );
935 if (strcmp(szStatusCode
, lpszVerifyCode
) != 0)
937 m_csLastError
.Format(L
"Received invalid response: %s", static_cast<LPCWSTR
>(CUnicodeUtils::GetUnicode(szRecvBuf
)));
943 BOOL
CHwSMTP::SendBuffer(const char* buff
, int size
)
946 size
= static_cast<int>(strlen(buff
));
949 m_csLastError
= L
"Didn't connect";
953 if (m_iSecurityLevel
>= ssl
)
956 while (size
- sent
> 0)
958 int toSend
= min(size
- sent
, static_cast<int>(Sizes
.cbMaximumMessage
));
959 SecureZeroMemory(pbIoBuffer
+ Sizes
.cbHeader
, Sizes
.cbMaximumMessage
);
960 memcpy(pbIoBuffer
+ Sizes
.cbHeader
, buff
+ sent
, toSend
);
961 DWORD cbData
= EncryptSend(&m_SendSock
, hContext
, pbIoBuffer
, Sizes
);
962 if (cbData
== SOCKET_ERROR
|| cbData
== 0)
967 else if (m_SendSock
.Send ( buff
, size
) != size
)
969 m_csLastError
= L
"Socket send data failed";
976 BOOL
CHwSMTP::Send(const CString
&str
)
978 return Send(CUnicodeUtils::GetUTF8(str
));
981 BOOL
CHwSMTP::Send(const CStringA
&str
)
983 //TRACE(L"Send: %s\r\n", static_cast<LPCWSTR>(CUnicodeUtils::GetUnicode(str)));
984 return SendBuffer(str
, str
.GetLength());
987 BOOL
CHwSMTP::SendEmail()
990 gethostname(CStrBufA(hostname
, 64), 64);
992 // make sure helo hostname can be interpreted as a FQDN
993 if (hostname
.Find(".") == -1)
994 hostname
+= ".local";
997 str
.Format("HELO %s\r\n", static_cast<LPCSTR
>(hostname
));
1000 if (!GetResponse("250"))
1003 if ( m_bMustAuth
&& !auth() )
1009 if (!SendSubject(CUnicodeUtils::GetUnicode(hostname
)))
1015 if ( !SendAttach() )
1022 if (!GetResponse("250"))
1025 if ( HANDLE_IS_VALID(m_SendSock
.m_hSocket
) )
1027 m_bConnected
= FALSE
;
1032 static CStringA
EncodeBase64(const char* source
, int len
, bool noWrap
)
1034 int neededLength
= Base64EncodeGetRequiredLength(len
);
1036 if (Base64Encode(reinterpret_cast<const BYTE
*>(source
), len
, CStrBufA(output
, neededLength
), &neededLength
, noWrap
? ATL_BASE64_FLAG_NOCRLF
: 0))
1037 output
.Truncate(neededLength
);
1041 static CStringA
EncodeBase64(const CString
& source
)
1043 CStringA buf
= CUnicodeUtils::GetUTF8(source
);
1044 return EncodeBase64(buf
, buf
.GetLength(), true);
1047 CString
CHwSMTP::GetEncodedHeader(const CString
& text
)
1049 if (CStringUtils::IsPlainReadableASCII(text
))
1052 return L
"=?UTF-8?B?" + CUnicodeUtils::GetUnicode(EncodeBase64(text
)) + L
"?=";
1055 BOOL
CHwSMTP::auth()
1057 if (!Send("auth login\r\n"))
1059 if (!GetResponse("334"))
1062 if (!Send(EncodeBase64(m_credentials
->m_username
) + "\r\n"))
1065 if (!GetResponse("334"))
1067 m_csLastError
= L
"Authentication UserName failed";
1071 if (m_credentials
->m_password
[0] != L
'\0')
1073 auto len
= static_cast<int>(_tcslen(m_credentials
->m_password
));
1074 auto size
= WideCharToMultiByte(CP_UTF8
, 0, m_credentials
->m_password
, len
, nullptr, 0, nullptr, nullptr);
1075 auto buf
= std::make_unique
<char[]>(size
+ 1);
1076 auto ret
= WideCharToMultiByte(CP_UTF8
, 0, m_credentials
->m_password
, len
, buf
.get(), size
, nullptr, nullptr);
1079 int neededLength
= Base64EncodeGetRequiredLength(len
);
1080 auto bufBase64
= std::make_unique
<char[]>(neededLength
+ 1);
1081 bufBase64
[0] = '\0';
1082 if (Base64Encode(reinterpret_cast<const BYTE
*>(buf
.get()), ret
, bufBase64
.get(), &neededLength
, ATL_BASE64_FLAG_NOCRLF
))
1083 bufBase64
[neededLength
] = '\0';
1085 auto successful
= SendBuffer(bufBase64
.get(), neededLength
);
1087 SecureZeroMemory(bufBase64
.get(), neededLength
+ 1);
1088 SecureZeroMemory(buf
.get(), size
+ 1);
1095 if (!GetResponse("235"))
1097 m_csLastError
= L
"Authentication Password failed";
1104 BOOL
CHwSMTP::SendHead()
1108 CStringUtils::ParseEmailAddress(m_csAddrFrom
, addr
);
1110 str
.Format(L
"MAIL From: <%s>\r\n", static_cast<LPCWSTR
>(addr
));
1114 if (!GetResponse("250"))
1120 CString one
= m_csAddrTo
.Tokenize(L
";", start
).Trim();
1124 CStringUtils::ParseEmailAddress(one
, addr
);
1126 str
.Format(L
"RCPT TO: <%s>\r\n", static_cast<LPCWSTR
>(addr
));
1129 if (!GetResponse("250"))
1133 if (!Send("DATA\r\n"))
1135 if (!GetResponse("354"))
1141 BOOL
CHwSMTP::SendSubject(const CString
&hostname
)
1144 csSubject
+= L
"Date: ";
1145 COleDateTime tNow
= COleDateTime::GetCurrentTime();
1147 csSubject
+= FormatDateTime(tNow
);
1148 csSubject
+= L
"\r\n";
1149 csSubject
.AppendFormat(L
"From: %s\r\n", static_cast<LPCWSTR
>(m_csAddrFrom
));
1151 if (!m_StrCC
.IsEmpty())
1152 csSubject
.AppendFormat(L
"CC: %s\r\n", static_cast<LPCWSTR
>(m_StrCC
));
1154 if(m_csSender
.IsEmpty())
1155 m_csSender
= this->m_csAddrFrom
;
1157 csSubject
.AppendFormat(L
"Sender: %s\r\n", static_cast<LPCWSTR
>(m_csSender
));
1159 if(this->m_csToList
.IsEmpty())
1160 m_csToList
= m_csReceiverName
;
1162 csSubject
.AppendFormat(L
"To: %s\r\n", static_cast<LPCWSTR
>(m_csToList
));
1164 csSubject
.AppendFormat(L
"Subject: %s\r\n", static_cast<LPCWSTR
>(GetEncodedHeader(m_csSubject
)));
1166 CString
m_ListID(GetGUID());
1167 if (m_ListID
.IsEmpty())
1169 m_csLastError
= L
"Could not generate Message-ID";
1172 csSubject
.AppendFormat(L
"Message-ID: <%s@%s>\r\n", static_cast<LPCWSTR
>(m_ListID
), static_cast<LPCWSTR
>(hostname
));
1173 csSubject
.AppendFormat(L
"X-Mailer: TortoiseGit\r\nMIME-Version: 1.0\r\nContent-Type: %s\r\n\r\n", static_cast<LPCWSTR
>(m_csMIMEContentType
));
1175 return Send(csSubject
);
1178 BOOL
CHwSMTP::SendBody()
1182 if (!m_StrAryAttach
.IsEmpty())
1184 csBody
.AppendFormat(L
"%s\r\n\r\n", static_cast<LPCWSTR
>(m_csNoMIMEText
));
1185 csBody
.AppendFormat(L
"--%s\r\n", static_cast<LPCWSTR
>(m_csPartBoundary
));
1186 csBody
+= L
"Content-Type: text/plain\r\nContent-Transfer-Encoding: 8bit\r\n\r\n";
1189 m_csBody
= CStringUtils::EnsureCRLF(m_csBody
);
1190 m_csBody
.Replace(L
"\n.\r\n", L
"\n..\r\n");
1195 return Send(csBody
);
1198 BOOL
CHwSMTP::SendAttach()
1200 if (m_StrAryAttach
.IsEmpty())
1203 int nCountAttach
= static_cast<int>(m_StrAryAttach
.GetSize());
1204 for (int i
= 0; i
< nCountAttach
; ++i
)
1206 if ( !SendOnAttach ( m_StrAryAttach
.GetAt(i
) ) )
1210 Send(L
"--" + m_csPartBoundary
+ L
"--\r\n");
1215 BOOL
CHwSMTP::SendOnAttach(LPCWSTR lpszFileName
)
1217 ASSERT ( lpszFileName
);
1219 CString csShortFileName
= GetEncodedHeader(CPathUtils::GetFileNameFromPath(lpszFileName
));
1221 csAttach
.AppendFormat(L
"--%s\r\n", static_cast<LPCWSTR
>(m_csPartBoundary
));
1222 csAttach
.AppendFormat(L
"Content-Type: application/octet-stream; file=\"%s\"\r\n", static_cast<LPCWSTR
>(csShortFileName
));
1223 csAttach
.AppendFormat(L
"Content-Transfer-Encoding: base64\r\n");
1224 csAttach
.AppendFormat(L
"Content-Disposition: attachment; filename=\"%s\"\r\n\r\n", static_cast<LPCWSTR
>(csShortFileName
));
1228 CFile
file(lpszFileName
, CFile::modeRead
| CFile::typeBinary
| CFile::shareDenyWrite
);
1229 if (file
.GetLength() > 15 * 1024 * 1024)
1231 m_csLastError
.Format(L
"File [%s] too big (limit is 15 MiB).", lpszFileName
);
1234 auto pBuf
= std::make_unique
<char[]>(static_cast<UINT
>(file
.GetLength()) + 1);
1235 UINT nFileLen
= file
.Read(pBuf
.get(), static_cast<UINT
>(file
.GetLength()));
1236 CStringA filedata
= EncodeBase64(pBuf
.get(), nFileLen
, false);
1237 filedata
+= L
"\r\n\r\n";
1239 if (!Send(csAttach
))
1242 if (!SendBuffer(filedata
))
1245 catch (CFileException
*e
)
1247 e
->GetErrorMessage(CStrBuf(m_csLastError
, 1024), 1024);
1255 CString
CHwSMTP::GetLastErrorText()
1257 return m_csLastError
;