1
// HwSMTP.cpp: implementation of the CHwSMTP class.
3 // Schannel/SSPI implementation based on http://www.coastrd.com/c-schannel-smtp
5 //////////////////////////////////////////////////////////////////////
10 #include "SpeedPostEmail.h"
13 #include "FormatMessageWrapper.h"
16 #include "PathUtils.h"
17 #include "StringUtils.h"
19 #define IO_BUFFER_SIZE 0x10000
21 #pragma comment(lib, "Dnsapi.lib")
22 #pragma comment(lib, "Rpcrt4.lib")
23 #pragma comment(lib, "Secur32.lib")
25 DWORD dwProtocol
= SP_PROT_TLS1
; // SP_PROT_TLS1; // SP_PROT_PCT1; SP_PROT_SSL2; SP_PROT_SSL3; 0=default
26 ALG_ID aiKeyExch
= 0; // = default; CALG_DH_EPHEM; CALG_RSA_KEYX;
28 SCHANNEL_CRED SchannelCred
;
29 PSecurityFunctionTable g_pSSPI
;
31 //////////////////////////////////////////////////////////////////////
32 // Construction/Destruction
33 //////////////////////////////////////////////////////////////////////
35 static CString
GetGUID()
39 if (CoCreateGuid(&guid
) == S_OK
)
42 if (UuidToString(&guid
, &guidStr
) == RPC_S_OK
)
44 sGuid
= (LPTSTR
)guidStr
;
45 RpcStringFree(&guidStr
);
52 m_bConnected ( FALSE
),
53 m_nSmtpSrvPort ( 25 ),
56 m_csPartBoundary
= L
"NextPart_" + GetGUID();
57 m_csMIMEContentType
.Format(L
"multipart/mixed; boundary=%s", (LPCTSTR
)m_csPartBoundary
);
58 m_csNoMIMEText
= L
"This is a multi-part message in MIME format.";
65 m_iSecurityLevel
= none
;
67 SecureZeroMemory(&Sizes
, sizeof(SecPkgContext_StreamSizes
));
76 CString
CHwSMTP::GetServerAddress(const CString
& in
)
79 CStringUtils::ParseEmailAddress(in
, email
);
81 int start
= email
.Find(L
'@');
82 return email
.Mid(start
+ 1);
85 BOOL
CHwSMTP::SendSpeedEmail
91 CStringArray
*pStrAryAttach
,
98 To
+= GET_SAFE_STRING(lpszAddrTo
);
100 To
+= GET_SAFE_STRING(pStrAryCC
);
102 std::map
<CString
,std::vector
<CString
>> Address
;
107 CString one
= To
.Tokenize(L
";", start
).Trim();
111 CString addr
= GetServerAddress(one
);
115 Address
[addr
].push_back(one
);
118 for (const auto& maildomain
: Address
)
120 PDNS_RECORD pDnsRecord
;
122 DnsQuery(maildomain
.first
,
123 DNS_TYPE_MX
,DNS_QUERY_STANDARD
,
124 nullptr, //Contains DNS server IP address.
125 &pDnsRecord
, //Resource record that contains the response.
130 m_csLastError
.Format(L
"DNS query failed %d", status
);
134 SCOPE_EXIT
{ DnsRecordListFree(pDnsRecord
, DnsFreeRecordList
); };
137 std::for_each(maildomain
.second
.cbegin(), maildomain
.second
.cend(), [&to
](auto recipient
) {
144 PDNS_RECORD pNext
= pDnsRecord
;
147 if(pNext
->wType
== DNS_TYPE_MX
)
148 if (SendEmail(pNext
->Data
.MX
.pNameExchange
, nullptr, nullptr, false,
149 lpszAddrFrom
, to
, lpszSubject
, lpszBody
, pStrAryAttach
, pStrAryCC
,
150 25,pSend
,lpszAddrTo
))
161 static SECURITY_STATUS
ClientHandshakeLoop(CSocket
* Socket
, PCredHandle phCreds
, CtxtHandle
* phContext
, BOOL fDoInitialRead
, SecBuffer
* pExtraData
)
163 SecBufferDesc OutBuffer
, InBuffer
;
164 SecBuffer InBuffers
[2], OutBuffers
[1];
165 DWORD dwSSPIFlags
, dwSSPIOutFlags
, cbData
, cbIoBuffer
;
167 SECURITY_STATUS scRet
;
170 dwSSPIFlags
= ISC_REQ_SEQUENCE_DETECT
| ISC_REQ_REPLAY_DETECT
| ISC_REQ_CONFIDENTIALITY
|
171 ISC_RET_EXTENDED_ERROR
| ISC_REQ_ALLOCATE_MEMORY
| ISC_REQ_STREAM
;
173 // Allocate data buffer.
174 auto IoBuffer
= std::make_unique
<UCHAR
[]>(IO_BUFFER_SIZE
);
177 // printf("**** Out of memory (1)\n");
178 return SEC_E_INTERNAL_ERROR
;
181 fDoRead
= fDoInitialRead
;
183 // Loop until the handshake is finished or an error occurs.
184 scRet
= SEC_I_CONTINUE_NEEDED
;
186 while (scRet
== SEC_I_CONTINUE_NEEDED
|| scRet
== SEC_E_INCOMPLETE_MESSAGE
|| scRet
== SEC_I_INCOMPLETE_CREDENTIALS
)
188 if (0 == cbIoBuffer
|| scRet
== SEC_E_INCOMPLETE_MESSAGE
) // Read data from server.
192 cbData
= Socket
->Receive(IoBuffer
.get() + cbIoBuffer
, IO_BUFFER_SIZE
- cbIoBuffer
, 0);
193 if (cbData
== SOCKET_ERROR
)
195 // printf("**** Error %d reading data from server\n", WSAGetLastError());
196 scRet
= SEC_E_INTERNAL_ERROR
;
199 else if (cbData
== 0)
201 // printf("**** Server unexpectedly disconnected\n");
202 scRet
= SEC_E_INTERNAL_ERROR
;
205 // printf("%d bytes of handshake data received\n", cbData);
206 cbIoBuffer
+= cbData
;
212 // Set up the input buffers. Buffer 0 is used to pass in data
213 // received from the server. Schannel will consume some or all
214 // of this. Leftover data (if any) will be placed in buffer 1 and
215 // given a buffer type of SECBUFFER_EXTRA.
216 InBuffers
[0].pvBuffer
= IoBuffer
.get();
217 InBuffers
[0].cbBuffer
= cbIoBuffer
;
218 InBuffers
[0].BufferType
= SECBUFFER_TOKEN
;
220 InBuffers
[1].pvBuffer
= nullptr;
221 InBuffers
[1].cbBuffer
= 0;
222 InBuffers
[1].BufferType
= SECBUFFER_EMPTY
;
224 InBuffer
.cBuffers
= 2;
225 InBuffer
.pBuffers
= InBuffers
;
226 InBuffer
.ulVersion
= SECBUFFER_VERSION
;
228 // Set up the output buffers. These are initialized to nullptr
229 // so as to make it less likely we'll attempt to free random
231 OutBuffers
[0].pvBuffer
= nullptr;
232 OutBuffers
[0].BufferType
= SECBUFFER_TOKEN
;
233 OutBuffers
[0].cbBuffer
= 0;
235 OutBuffer
.cBuffers
= 1;
236 OutBuffer
.pBuffers
= OutBuffers
;
237 OutBuffer
.ulVersion
= SECBUFFER_VERSION
;
239 // Call InitializeSecurityContext.
240 scRet
= g_pSSPI
->InitializeSecurityContext(phCreds
, phContext
, nullptr, dwSSPIFlags
, 0, SECURITY_NATIVE_DREP
, &InBuffer
, 0, nullptr, &OutBuffer
, &dwSSPIOutFlags
, &tsExpiry
);
242 // If InitializeSecurityContext was successful (or if the error was
243 // one of the special extended ones), send the contends of the output
244 // buffer to the server.
245 if (scRet
== SEC_E_OK
|| scRet
== SEC_I_CONTINUE_NEEDED
|| FAILED(scRet
) && (dwSSPIOutFlags
& ISC_RET_EXTENDED_ERROR
))
247 if (OutBuffers
[0].cbBuffer
!= 0 && OutBuffers
[0].pvBuffer
!= nullptr)
249 cbData
= Socket
->Send(OutBuffers
[0].pvBuffer
, OutBuffers
[0].cbBuffer
, 0 );
250 if(cbData
== SOCKET_ERROR
|| cbData
== 0)
252 // printf( "**** Error %d sending data to server (2)\n", WSAGetLastError() );
253 g_pSSPI
->FreeContextBuffer(OutBuffers
[0].pvBuffer
);
254 g_pSSPI
->DeleteSecurityContext(phContext
);
255 return SEC_E_INTERNAL_ERROR
;
257 // printf("%d bytes of handshake data sent\n", cbData);
259 // Free output buffer.
260 g_pSSPI
->FreeContextBuffer(OutBuffers
[0].pvBuffer
);
261 OutBuffers
[0].pvBuffer
= nullptr;
265 // If InitializeSecurityContext returned SEC_E_INCOMPLETE_MESSAGE,
266 // then we need to read more data from the server and try again.
267 if (scRet
== SEC_E_INCOMPLETE_MESSAGE
) continue;
269 // If InitializeSecurityContext returned SEC_E_OK, then the
270 // handshake completed successfully.
271 if (scRet
== SEC_E_OK
)
273 // If the "extra" buffer contains data, this is encrypted application
274 // protocol layer stuff. It needs to be saved. The application layer
275 // will later decrypt it with DecryptMessage.
276 // printf("Handshake was successful\n");
278 if (InBuffers
[1].BufferType
== SECBUFFER_EXTRA
)
280 pExtraData
->pvBuffer
= LocalAlloc( LMEM_FIXED
, InBuffers
[1].cbBuffer
);
281 if (pExtraData
->pvBuffer
== nullptr)
283 // printf("**** Out of memory (2)\n");
284 return SEC_E_INTERNAL_ERROR
;
287 MoveMemory(pExtraData
->pvBuffer
, IoBuffer
.get() + (cbIoBuffer
- InBuffers
[1].cbBuffer
), InBuffers
[1].cbBuffer
);
289 pExtraData
->cbBuffer
= InBuffers
[1].cbBuffer
;
290 pExtraData
->BufferType
= SECBUFFER_TOKEN
;
292 // printf( "%d bytes of app data was bundled with handshake data\n", pExtraData->cbBuffer );
296 pExtraData
->pvBuffer
= nullptr;
297 pExtraData
->cbBuffer
= 0;
298 pExtraData
->BufferType
= SECBUFFER_EMPTY
;
300 break; // Bail out to quit
303 // Check for fatal error.
306 // printf("**** Error 0x%x returned by InitializeSecurityContext (2)\n", scRet);
310 // If InitializeSecurityContext returned SEC_I_INCOMPLETE_CREDENTIALS,
311 // then the server just requested client authentication.
312 if (scRet
== SEC_I_INCOMPLETE_CREDENTIALS
)
314 // Busted. The server has requested client authentication and
315 // the credential we supplied didn't contain a client certificate.
316 // This function will read the list of trusted certificate
317 // authorities ("issuers") that was received from the server
318 // and attempt to find a suitable client certificate that
319 // was issued by one of these. If this function is successful,
320 // then we will connect using the new certificate. Otherwise,
321 // we will attempt to connect anonymously (using our current credentials).
322 //GetNewClientCredentials(phCreds, phContext);
326 scRet
= SEC_I_CONTINUE_NEEDED
;
330 // Copy any leftover data from the "extra" buffer, and go around again.
331 if ( InBuffers
[1].BufferType
== SECBUFFER_EXTRA
)
333 MoveMemory(IoBuffer
.get(), IoBuffer
.get() + (cbIoBuffer
- InBuffers
[1].cbBuffer
), InBuffers
[1].cbBuffer
);
334 cbIoBuffer
= InBuffers
[1].cbBuffer
;
340 // Delete the security context in the case of a fatal error.
342 g_pSSPI
->DeleteSecurityContext(phContext
);
347 static SECURITY_STATUS
PerformClientHandshake( CSocket
* Socket
, PCredHandle phCreds
, LPTSTR pszServerName
, CtxtHandle
* phContext
, SecBuffer
* pExtraData
)
349 SecBufferDesc OutBuffer
;
350 SecBuffer OutBuffers
[1];
351 DWORD dwSSPIFlags
, dwSSPIOutFlags
, cbData
;
353 SECURITY_STATUS scRet
;
355 dwSSPIFlags
= ISC_REQ_SEQUENCE_DETECT
| ISC_REQ_REPLAY_DETECT
| ISC_REQ_CONFIDENTIALITY
|
356 ISC_RET_EXTENDED_ERROR
| ISC_REQ_ALLOCATE_MEMORY
| ISC_REQ_STREAM
;
358 // Initiate a ClientHello message and generate a token.
359 OutBuffers
[0].pvBuffer
= nullptr;
360 OutBuffers
[0].BufferType
= SECBUFFER_TOKEN
;
361 OutBuffers
[0].cbBuffer
= 0;
363 OutBuffer
.cBuffers
= 1;
364 OutBuffer
.pBuffers
= OutBuffers
;
365 OutBuffer
.ulVersion
= SECBUFFER_VERSION
;
367 scRet
= g_pSSPI
->InitializeSecurityContext(phCreds
, nullptr, pszServerName
, dwSSPIFlags
, 0, SECURITY_NATIVE_DREP
, nullptr, 0, phContext
, &OutBuffer
, &dwSSPIOutFlags
, &tsExpiry
);
369 if (scRet
!= SEC_I_CONTINUE_NEEDED
)
371 // printf("**** Error %d returned by InitializeSecurityContext (1)\n", scRet);
375 // Send response to server if there is one.
376 if (OutBuffers
[0].cbBuffer
!= 0 && OutBuffers
[0].pvBuffer
!= nullptr)
378 cbData
= Socket
->Send(OutBuffers
[0].pvBuffer
, OutBuffers
[0].cbBuffer
, 0);
379 if (cbData
== SOCKET_ERROR
|| cbData
== 0)
381 // printf("**** Error %d sending data to server (1)\n", WSAGetLastError());
382 g_pSSPI
->FreeContextBuffer(OutBuffers
[0].pvBuffer
);
383 g_pSSPI
->DeleteSecurityContext(phContext
);
384 return SEC_E_INTERNAL_ERROR
;
386 // printf("%d bytes of handshake data sent\n", cbData);
388 g_pSSPI
->FreeContextBuffer(OutBuffers
[0].pvBuffer
); // Free output buffer.
389 OutBuffers
[0].pvBuffer
= nullptr;
392 return ClientHandshakeLoop(Socket
, phCreds
, phContext
, TRUE
, pExtraData
);
395 static SECURITY_STATUS
CreateCredentials(PCredHandle phCreds
)
398 SECURITY_STATUS Status
;
399 DWORD cSupportedAlgs
= 0;
400 ALG_ID rgbSupportedAlgs
[16];
402 // Build Schannel credential structure. Currently, this sample only
403 // specifies the protocol to be used (and optionally the certificate,
404 // of course). Real applications may wish to specify other parameters as well.
405 SecureZeroMemory(&SchannelCred
, sizeof(SchannelCred
));
407 SchannelCred
.dwVersion
= SCHANNEL_CRED_VERSION
;
408 SchannelCred
.grbitEnabledProtocols
= dwProtocol
;
411 rgbSupportedAlgs
[cSupportedAlgs
++] = aiKeyExch
;
415 SchannelCred
.cSupportedAlgs
= cSupportedAlgs
;
416 SchannelCred
.palgSupportedAlgs
= rgbSupportedAlgs
;
419 SchannelCred
.dwFlags
|= SCH_CRED_NO_DEFAULT_CREDS
;
421 // The SCH_CRED_MANUAL_CRED_VALIDATION flag is specified because
422 // this sample verifies the server certificate manually.
423 // Applications that expect to run on WinNT, Win9x, or WinME
424 // should specify this flag and also manually verify the server
425 // certificate. Applications running on newer versions of Windows can
426 // leave off this flag, in which case the InitializeSecurityContext
427 // function will validate the server certificate automatically.
428 SchannelCred
.dwFlags
|= SCH_CRED_MANUAL_CRED_VALIDATION
;
430 // Create an SSPI credential.
431 Status
= g_pSSPI
->AcquireCredentialsHandle(nullptr, // Name of principal
432 UNISP_NAME
, // Name of package
433 SECPKG_CRED_OUTBOUND
, // Flags indicating use
434 nullptr, // Pointer to logon ID
435 &SchannelCred
, // Package specific data
436 nullptr, // Pointer to GetKey() func
437 nullptr, // Value to pass to GetKey()
438 phCreds
, // (out) Cred Handle
439 &tsExpiry
); // (out) Lifetime (optional)
444 static DWORD
EncryptSend(CSocket
* Socket
, CtxtHandle
* phContext
, PBYTE pbIoBuffer
, SecPkgContext_StreamSizes Sizes
)
445 // http://msdn.microsoft.com/en-us/library/aa375378(VS.85).aspx
446 // The encrypted message is encrypted in place, overwriting the original contents of its buffer.
448 SECURITY_STATUS scRet
;
449 SecBufferDesc Message
;
450 SecBuffer Buffers
[4];
454 pbMessage
= pbIoBuffer
+ Sizes
.cbHeader
; // Offset by "header size"
455 cbMessage
= (DWORD
)strlen((char *)pbMessage
);
457 // Encrypt the HTTP request.
458 Buffers
[0].pvBuffer
= pbIoBuffer
; // Pointer to buffer 1
459 Buffers
[0].cbBuffer
= Sizes
.cbHeader
; // length of header
460 Buffers
[0].BufferType
= SECBUFFER_STREAM_HEADER
; // Type of the buffer
462 Buffers
[1].pvBuffer
= pbMessage
; // Pointer to buffer 2
463 Buffers
[1].cbBuffer
= cbMessage
; // length of the message
464 Buffers
[1].BufferType
= SECBUFFER_DATA
; // Type of the buffer
466 Buffers
[2].pvBuffer
= pbMessage
+ cbMessage
; // Pointer to buffer 3
467 Buffers
[2].cbBuffer
= Sizes
.cbTrailer
; // length of the trailor
468 Buffers
[2].BufferType
= SECBUFFER_STREAM_TRAILER
; // Type of the buffer
470 Buffers
[3].pvBuffer
= SECBUFFER_EMPTY
; // Pointer to buffer 4
471 Buffers
[3].cbBuffer
= SECBUFFER_EMPTY
; // length of buffer 4
472 Buffers
[3].BufferType
= SECBUFFER_EMPTY
; // Type of the buffer 4
474 Message
.ulVersion
= SECBUFFER_VERSION
; // Version number
475 Message
.cBuffers
= 4; // Number of buffers - must contain four SecBuffer structures.
476 Message
.pBuffers
= Buffers
; // Pointer to array of buffers
478 scRet
= g_pSSPI
->EncryptMessage(phContext
, 0, &Message
, 0); // must contain four SecBuffer structures.
481 // printf("**** Error 0x%x returned by EncryptMessage\n", scRet);
486 // Send the encrypted data to the server.
487 return Socket
->Send(pbIoBuffer
, Buffers
[0].cbBuffer
+ Buffers
[1].cbBuffer
+ Buffers
[2].cbBuffer
, 0);
490 static LONG
DisconnectFromServer(CSocket
* Socket
, PCredHandle phCreds
, CtxtHandle
* phContext
)
493 DWORD dwType
, dwSSPIFlags
, dwSSPIOutFlags
, cbMessage
, cbData
, Status
;
494 SecBufferDesc OutBuffer
;
495 SecBuffer OutBuffers
[1];
498 dwType
= SCHANNEL_SHUTDOWN
; // Notify schannel that we are about to close the connection.
500 OutBuffers
[0].pvBuffer
= &dwType
;
501 OutBuffers
[0].BufferType
= SECBUFFER_TOKEN
;
502 OutBuffers
[0].cbBuffer
= sizeof(dwType
);
504 OutBuffer
.cBuffers
= 1;
505 OutBuffer
.pBuffers
= OutBuffers
;
506 OutBuffer
.ulVersion
= SECBUFFER_VERSION
;
508 Status
= g_pSSPI
->ApplyControlToken(phContext
, &OutBuffer
);
511 // printf("**** Error 0x%x returned by ApplyControlToken\n", Status);
515 // Build an SSL close notify message.
516 dwSSPIFlags
= ISC_REQ_SEQUENCE_DETECT
| ISC_REQ_REPLAY_DETECT
| ISC_REQ_CONFIDENTIALITY
| ISC_RET_EXTENDED_ERROR
| ISC_REQ_ALLOCATE_MEMORY
| ISC_REQ_STREAM
;
518 OutBuffers
[0].pvBuffer
= nullptr;
519 OutBuffers
[0].BufferType
= SECBUFFER_TOKEN
;
520 OutBuffers
[0].cbBuffer
= 0;
522 OutBuffer
.cBuffers
= 1;
523 OutBuffer
.pBuffers
= OutBuffers
;
524 OutBuffer
.ulVersion
= SECBUFFER_VERSION
;
526 Status
= g_pSSPI
->InitializeSecurityContext(phCreds
, phContext
, nullptr, dwSSPIFlags
, 0, SECURITY_NATIVE_DREP
, nullptr, 0, phContext
, &OutBuffer
, &dwSSPIOutFlags
, &tsExpiry
);
530 // printf("**** Error 0x%x returned by InitializeSecurityContext\n", Status);
534 pbMessage
= (PBYTE
)OutBuffers
[0].pvBuffer
;
535 cbMessage
= OutBuffers
[0].cbBuffer
;
537 // Send the close notify message to the server.
538 if (pbMessage
!= nullptr && cbMessage
!= 0)
540 cbData
= Socket
->Send(pbMessage
, cbMessage
, 0);
541 if (cbData
== SOCKET_ERROR
|| cbData
== 0)
543 Status
= WSAGetLastError();
546 // printf("Sending Close Notify\n");
547 // printf("%d bytes of handshake data sent\n", cbData);
548 g_pSSPI
->FreeContextBuffer(pbMessage
); // Free output buffer.
552 g_pSSPI
->DeleteSecurityContext(phContext
); // Free the security context.
558 static SECURITY_STATUS
ReadDecrypt(CSocket
* Socket
, PCredHandle phCreds
, CtxtHandle
* phContext
, PBYTE pbIoBuffer
, DWORD cbIoBufferLength
)
560 // calls recv() - blocking socket read
561 // http://msdn.microsoft.com/en-us/library/ms740121(VS.85).aspx
563 // The encrypted message is decrypted in place, overwriting the original contents of its buffer.
564 // http://msdn.microsoft.com/en-us/library/aa375211(VS.85).aspx
567 SecBuffer ExtraBuffer
;
568 SecBuffer
* pDataBuffer
, * pExtraBuffer
;
570 SECURITY_STATUS scRet
;
571 SecBufferDesc Message
;
572 SecBuffer Buffers
[4];
574 DWORD cbIoBuffer
, cbData
, length
;
577 // Read data from server until done.
580 while (TRUE
) // Read some data.
582 if (cbIoBuffer
== 0 || scRet
== SEC_E_INCOMPLETE_MESSAGE
) // get the data
584 cbData
= Socket
->Receive(pbIoBuffer
+ cbIoBuffer
, cbIoBufferLength
- cbIoBuffer
, 0);
585 if (cbData
== SOCKET_ERROR
)
587 // printf("**** Error %d reading data from server\n", WSAGetLastError());
588 scRet
= SEC_E_INTERNAL_ERROR
;
591 else if (cbData
== 0) // Server disconnected.
595 // printf("**** Server unexpectedly disconnected\n");
596 scRet
= SEC_E_INTERNAL_ERROR
;
604 // printf("%d bytes of (encrypted) application data received\n", cbData);
605 cbIoBuffer
+= cbData
;
609 // Decrypt the received data.
610 Buffers
[0].pvBuffer
= pbIoBuffer
;
611 Buffers
[0].cbBuffer
= cbIoBuffer
;
612 Buffers
[0].BufferType
= SECBUFFER_DATA
; // Initial Type of the buffer 1
613 Buffers
[1].BufferType
= SECBUFFER_EMPTY
; // Initial Type of the buffer 2
614 Buffers
[2].BufferType
= SECBUFFER_EMPTY
; // Initial Type of the buffer 3
615 Buffers
[3].BufferType
= SECBUFFER_EMPTY
; // Initial Type of the buffer 4
617 Message
.ulVersion
= SECBUFFER_VERSION
; // Version number
618 Message
.cBuffers
= 4; // Number of buffers - must contain four SecBuffer structures.
619 Message
.pBuffers
= Buffers
; // Pointer to array of buffers
621 scRet
= g_pSSPI
->DecryptMessage(phContext
, &Message
, 0, nullptr);
622 if (scRet
== SEC_I_CONTEXT_EXPIRED
)
623 break; // Server signalled end of session
624 // if (scRet == SEC_E_INCOMPLETE_MESSAGE - Input buffer has partial encrypted record, read more
625 if (scRet
!= SEC_E_OK
&& scRet
!= SEC_I_RENEGOTIATE
&& scRet
!= SEC_I_CONTEXT_EXPIRED
)
628 // Locate data and (optional) extra buffers.
629 pDataBuffer
= nullptr;
630 pExtraBuffer
= nullptr;
631 for (int i
= 1; i
< 4; ++i
)
633 if (pDataBuffer
== nullptr && Buffers
[i
].BufferType
== SECBUFFER_DATA
)
634 pDataBuffer
= &Buffers
[i
];
635 if (pExtraBuffer
== nullptr && Buffers
[i
].BufferType
== SECBUFFER_EXTRA
)
636 pExtraBuffer
= &Buffers
[i
];
639 // Display the decrypted data.
642 length
= pDataBuffer
->cbBuffer
;
643 if (length
) // check if last two chars are CR LF
645 buff
= (PBYTE
)pDataBuffer
->pvBuffer
;
646 if (buff
[length
-2] == 13 && buff
[length
-1] == 10) // Found CRLF
654 // Move any "extra" data to the input buffer.
657 MoveMemory(pbIoBuffer
, pExtraBuffer
->pvBuffer
, pExtraBuffer
->cbBuffer
);
658 cbIoBuffer
= pExtraBuffer
->cbBuffer
;
663 // The server wants to perform another handshake sequence.
664 if (scRet
== SEC_I_RENEGOTIATE
)
666 // printf("Server requested renegotiate!\n");
667 scRet
= ClientHandshakeLoop( Socket
, phCreds
, phContext
, FALSE
, &ExtraBuffer
);
668 if (scRet
!= SEC_E_OK
)
671 if (ExtraBuffer
.pvBuffer
) // Move any "extra" data to the input buffer.
673 MoveMemory(pbIoBuffer
, ExtraBuffer
.pvBuffer
, ExtraBuffer
.cbBuffer
);
674 cbIoBuffer
= ExtraBuffer
.cbBuffer
;
677 } // Loop till CRLF is found at the end of the data
682 BOOL
CHwSMTP::SendEmail (
683 LPCTSTR lpszSmtpSrvHost
,
684 LPCTSTR lpszUserName
,
687 LPCTSTR lpszAddrFrom
,
691 CStringArray
* pStrAryAttach
/*=nullptr*/,
692 LPCTSTR pStrAryCC
/*=nullptr*/,
693 UINT nSmtpSrvPort
,/*=25*/
699 m_StrAryAttach
.RemoveAll();
701 m_StrCC
+= GET_SAFE_STRING(pStrAryCC
);
703 m_csSmtpSrvHost
= GET_SAFE_STRING ( lpszSmtpSrvHost
);
704 if ( m_csSmtpSrvHost
.GetLength() <= 0 )
706 m_csLastError
= L
"Parameter Error!";
709 m_csUserName
= GET_SAFE_STRING ( lpszUserName
);
710 m_csPasswd
= GET_SAFE_STRING ( lpszPasswd
);
711 m_bMustAuth
= bMustAuth
;
712 if ( m_bMustAuth
&& m_csUserName
.GetLength() <= 0 )
714 m_csLastError
= L
"Parameter Error!";
718 m_csAddrFrom
= GET_SAFE_STRING ( lpszAddrFrom
);
719 m_csAddrTo
= GET_SAFE_STRING ( lpszAddrTo
);
720 // m_csFromName = GET_SAFE_STRING ( lpszFromName );
721 // m_csReceiverName = GET_SAFE_STRING ( lpszReceiverName );
722 m_csSubject
= GET_SAFE_STRING ( lpszSubject
);
723 m_csBody
= GET_SAFE_STRING ( lpszBody
);
725 this->m_csSender
= GET_SAFE_STRING(pSender
);
726 this->m_csToList
= GET_SAFE_STRING(pToList
);
728 m_nSmtpSrvPort
= nSmtpSrvPort
;
731 m_csAddrFrom
.GetLength() <= 0 || m_csAddrTo
.GetLength() <= 0
734 m_csLastError
= L
"Parameter Error!";
740 m_StrAryAttach
.Append ( *pStrAryAttach
);
742 if ( m_StrAryAttach
.GetSize() < 1 )
743 m_csMIMEContentType
.Format(L
"text/plain");
747 if ( !m_SendSock
.Create () )
749 //int nResult = GetLastError();
750 m_csLastError
= L
"Create socket failed!";
757 m_iSecurityLevel
= want_tls
;
760 m_iSecurityLevel
= ssl
;
763 m_iSecurityLevel
= none
;
766 if ( !m_SendSock
.Connect ( m_csSmtpSrvHost
, m_nSmtpSrvPort
) )
768 m_csLastError
.Format(L
"Connect to [%s] failed", (LPCTSTR
)m_csSmtpSrvHost
);
772 if (m_iSecurityLevel
== want_tls
) {
773 if (!GetResponse("220"))
776 Send(L
"STARTTLS\r\n");
777 if (!GetResponse("220"))
779 m_iSecurityLevel
= tls_established
;
785 SECURITY_STATUS Status
;
787 CtxtHandle contextStruct
;
788 CredHandle credentialsStruct
;
790 if (m_iSecurityLevel
>= ssl
)
792 g_pSSPI
= InitSecurityInterface();
794 contextStruct
.dwLower
= 0;
795 contextStruct
.dwUpper
= 0;
797 hCreds
= &credentialsStruct
;
798 credentialsStruct
.dwLower
= 0;
799 credentialsStruct
.dwUpper
= 0;
800 Status
= CreateCredentials(hCreds
);
801 if (Status
!= SEC_E_OK
)
803 m_csLastError
= CFormatMessageWrapper(Status
);
807 hContext
= &contextStruct
;
808 Status
= PerformClientHandshake(&m_SendSock
, hCreds
, m_csSmtpSrvHost
.GetBuffer(), hContext
, &ExtraData
);
809 if (Status
!= SEC_E_OK
)
811 m_csLastError
= CFormatMessageWrapper(Status
);
815 PCCERT_CONTEXT pRemoteCertContext
= nullptr;
816 // Authenticate server's credentials. Get server's certificate.
817 Status
= g_pSSPI
->QueryContextAttributes(hContext
, SECPKG_ATTR_REMOTE_CERT_CONTEXT
, (PVOID
)&pRemoteCertContext
);
820 m_csLastError
= CFormatMessageWrapper(Status
);
825 cert
.parent
.cert_type
= GIT_CERT_X509
;
826 cert
.data
= pRemoteCertContext
->pbCertEncoded
;
827 cert
.len
= pRemoteCertContext
->cbCertEncoded
;
828 if (CAppUtils::Git2CertificateCheck((git_cert
*)&cert
, 0, CUnicodeUtils::GetUTF8(m_csSmtpSrvHost
), nullptr))
830 CertFreeCertificateContext(pRemoteCertContext
);
831 m_csLastError
= L
"Invalid certificate.";
835 CertFreeCertificateContext(pRemoteCertContext
);
837 Status
= g_pSSPI
->QueryContextAttributes(hContext
, SECPKG_ATTR_STREAM_SIZES
, &Sizes
);
840 m_csLastError
= CFormatMessageWrapper(Status
);
845 cbIoBufferLength
= Sizes
.cbHeader
+ Sizes
.cbMaximumMessage
+ Sizes
.cbTrailer
;
846 pbIoBuffer
= (PBYTE
)LocalAlloc(LMEM_FIXED
, cbIoBufferLength
);
847 if (pbIoBuffer
== nullptr)
849 m_csLastError
= L
"Could not allocate memory";
852 SecureZeroMemory(pbIoBuffer
, cbIoBufferLength
);
855 if (m_iSecurityLevel
<= ssl
)
857 if (!GetResponse("220"))
865 if (m_iSecurityLevel
>= ssl
)
867 if (hContext
&& hCreds
)
868 DisconnectFromServer(&m_SendSock
, hCreds
, hContext
);
871 LocalFree(pbIoBuffer
);
872 pbIoBuffer
= nullptr;
873 cbIoBufferLength
= 0;
877 g_pSSPI
->DeleteSecurityContext(hContext
);
882 g_pSSPI
->FreeCredentialsHandle(hCreds
);
893 BOOL
CHwSMTP::GetResponse(LPCSTR lpszVerifyCode
)
895 if (!lpszVerifyCode
|| strlen(lpszVerifyCode
) < 1)
898 SECURITY_STATUS scRet
= SEC_E_OK
;
900 char szRecvBuf
[1024] = {0};
902 char szStatusCode
[4] = {0};
904 if (m_iSecurityLevel
>= ssl
)
906 scRet
= ReadDecrypt(&m_SendSock
, hCreds
, hContext
, pbIoBuffer
, cbIoBufferLength
);
907 SecureZeroMemory(szRecvBuf
, 1024);
908 memcpy(szRecvBuf
, pbIoBuffer
+Sizes
.cbHeader
, 1024);
911 nRet
= m_SendSock
.Receive(szRecvBuf
, sizeof(szRecvBuf
));
912 //TRACE(L"Received : %s\r\n", szRecvBuf);
913 if (nRet
== 0 && m_iSecurityLevel
== none
|| m_iSecurityLevel
>= ssl
&& scRet
!= SEC_E_OK
)
915 m_csLastError
= L
"Receive TCP data failed";
918 memcpy ( szStatusCode
, szRecvBuf
, 3 );
919 if (strcmp(szStatusCode
, lpszVerifyCode
) != 0)
921 m_csLastError
.Format(L
"Received invalid response: %s", (LPCTSTR
)CUnicodeUtils::GetUnicode(szRecvBuf
));
927 BOOL
CHwSMTP::SendBuffer(const char* buff
, int size
)
930 size
=(int)strlen(buff
);
933 m_csLastError
= L
"Didn't connect";
937 if (m_iSecurityLevel
>= ssl
)
940 while (size
- sent
> 0)
942 int toSend
= min(size
- sent
, (int)Sizes
.cbMaximumMessage
);
943 SecureZeroMemory(pbIoBuffer
+ Sizes
.cbHeader
, Sizes
.cbMaximumMessage
);
944 memcpy(pbIoBuffer
+ Sizes
.cbHeader
, buff
+ sent
, toSend
);
945 DWORD cbData
= EncryptSend(&m_SendSock
, hContext
, pbIoBuffer
, Sizes
);
946 if (cbData
== SOCKET_ERROR
|| cbData
== 0)
951 else if (m_SendSock
.Send ( buff
, size
) != size
)
953 m_csLastError
= L
"Socket send data failed";
960 BOOL
CHwSMTP::Send(const CString
&str
)
962 return Send(CUnicodeUtils::GetUTF8(str
));
965 BOOL
CHwSMTP::Send(const CStringA
&str
)
967 //TRACE(L"Send: %s\r\n", (LPCTSTR)CUnicodeUtils::GetUnicode(str));
968 return SendBuffer(str
, str
.GetLength());
971 BOOL
CHwSMTP::SendEmail()
974 gethostname(CStrBufA(hostname
, 64), 64);
976 // make sure helo hostname can be interpreted as a FQDN
977 if (hostname
.Find(".") == -1)
978 hostname
+= ".local";
981 str
.Format("HELO %s\r\n", (LPCSTR
)hostname
);
984 if (!GetResponse("250"))
987 if ( m_bMustAuth
&& !auth() )
993 if (!SendSubject(CUnicodeUtils::GetUnicode(hostname
)))
1006 if (!GetResponse("250"))
1009 if ( HANDLE_IS_VALID(m_SendSock
.m_hSocket
) )
1011 m_bConnected
= FALSE
;
1016 static CStringA
EncodeBase64(const char* source
, int len
)
1018 int neededLength
= Base64EncodeGetRequiredLength(len
);
1020 if (Base64Encode((BYTE
*)source
, len
, CStrBufA(output
, neededLength
), &neededLength
, ATL_BASE64_FLAG_NOCRLF
))
1021 output
.Truncate(neededLength
);
1025 static CStringA
EncodeBase64(const CString
& source
)
1027 CStringA buf
= CUnicodeUtils::GetUTF8(source
);
1028 return EncodeBase64(buf
, buf
.GetLength());
1031 CString
CHwSMTP::GetEncodedHeader(const CString
& text
)
1033 if (CStringUtils::IsPlainReadableASCII(text
))
1036 return L
"=?UTF-8?B?" + CUnicodeUtils::GetUnicode(EncodeBase64(text
)) + L
"?=";
1039 BOOL
CHwSMTP::auth()
1041 if (!Send("auth login\r\n"))
1043 if (!GetResponse("334"))
1046 if (!Send(EncodeBase64(m_csUserName
) + "\r\n"))
1049 if (!GetResponse("334"))
1051 m_csLastError
= L
"Authentication UserName failed";
1055 if (!Send(EncodeBase64(m_csPasswd
) + "\r\n"))
1058 if (!GetResponse("235"))
1060 m_csLastError
= L
"Authentication Password failed";
1067 BOOL
CHwSMTP::SendHead()
1071 CStringUtils::ParseEmailAddress(m_csAddrFrom
, addr
);
1073 str
.Format(L
"MAIL From: <%s>\r\n", (LPCTSTR
)addr
);
1077 if (!GetResponse("250"))
1083 CString one
= m_csAddrTo
.Tokenize(L
";", start
).Trim();
1087 CStringUtils::ParseEmailAddress(one
, addr
);
1089 str
.Format(L
"RCPT TO: <%s>\r\n", (LPCTSTR
)addr
);
1092 if (!GetResponse("250"))
1096 if (!Send("DATA\r\n"))
1098 if (!GetResponse("354"))
1104 BOOL
CHwSMTP::SendSubject(const CString
&hostname
)
1107 csSubject
+= L
"Date: ";
1108 COleDateTime tNow
= COleDateTime::GetCurrentTime();
1111 csSubject
+= FormatDateTime(tNow
, L
"%a, %d %b %y %H:%M:%S %Z");
1113 csSubject
+= L
"\r\n";
1114 csSubject
.AppendFormat(L
"From: %s\r\n", (LPCTSTR
)m_csAddrFrom
);
1116 if (!m_StrCC
.IsEmpty())
1117 csSubject
.AppendFormat(L
"CC: %s\r\n", (LPCTSTR
)m_StrCC
);
1119 if(m_csSender
.IsEmpty())
1120 m_csSender
= this->m_csAddrFrom
;
1122 csSubject
.AppendFormat(L
"Sender: %s\r\n", (LPCTSTR
)m_csSender
);
1124 if(this->m_csToList
.IsEmpty())
1125 m_csToList
= m_csReceiverName
;
1127 csSubject
.AppendFormat(L
"To: %s\r\n", (LPCTSTR
)m_csToList
);
1129 csSubject
.AppendFormat(L
"Subject: %s\r\n", (LPCTSTR
)GetEncodedHeader(m_csSubject
));
1131 CString
m_ListID(GetGUID());
1132 if (m_ListID
.IsEmpty())
1134 m_csLastError
= L
"Could not generate Message-ID";
1137 csSubject
.AppendFormat(L
"Message-ID: <%s@%s>\r\n", (LPCTSTR
)m_ListID
, (LPCTSTR
)hostname
);
1138 csSubject
.AppendFormat(L
"X-Mailer: TortoiseGit\r\nMIME-Version: 1.0\r\nContent-Type: %s\r\n\r\n", (LPCTSTR
)m_csMIMEContentType
);
1140 return Send(csSubject
);
1143 BOOL
CHwSMTP::SendBody()
1147 if ( m_StrAryAttach
.GetSize() > 0 )
1149 csBody
.AppendFormat(L
"%s\r\n\r\n", (LPCTSTR
)m_csNoMIMEText
);
1150 csBody
.AppendFormat(L
"--%s\r\n", (LPCTSTR
)m_csPartBoundary
);
1151 csBody
.AppendFormat(L
"Content-Type: text/plain\r\nContent-Transfer-Encoding: UTF-8\r\n\r\n");
1154 m_csBody
.Replace(L
"\n.\n", L
"\n..\n");
1155 m_csBody
.Replace(L
"\n.\r\n", L
"\n..\r\n");
1160 return Send(csBody
);
1163 BOOL
CHwSMTP::SendAttach()
1165 int nCountAttach
= (int)m_StrAryAttach
.GetSize();
1166 if ( nCountAttach
< 1 ) return TRUE
;
1168 for ( int i
=0; i
<nCountAttach
; i
++ )
1170 if ( !SendOnAttach ( m_StrAryAttach
.GetAt(i
) ) )
1174 Send(L
"--" + m_csPartBoundary
+ L
"--\r\n");
1179 BOOL
CHwSMTP::SendOnAttach(LPCTSTR lpszFileName
)
1181 ASSERT ( lpszFileName
);
1183 CString csShortFileName
= CPathUtils::GetFileNameFromPath(lpszFileName
);
1185 csAttach
.AppendFormat(L
"--%s\r\n", (LPCTSTR
)m_csPartBoundary
);
1186 csAttach
.AppendFormat(L
"--%s\r\n", (LPCTSTR
)m_csPartBoundary
);
1187 csAttach
.AppendFormat(L
"Content-Type: application/octet-stream; file=%s\r\n", (LPCTSTR
)csShortFileName
);
1188 csAttach
.AppendFormat(L
"Content-Transfer-Encoding: base64\r\n");
1189 csAttach
.AppendFormat(L
"Content-Disposition: attachment; filename=%s\r\n\r\n", (LPCTSTR
)csShortFileName
);
1191 DWORD dwFileSize
= hwGetFileAttr(lpszFileName
);
1192 if ( dwFileSize
> 5*1024*1024 )
1194 m_csLastError
.Format(L
"File [%s] too big. File size is : %s", lpszFileName
, (LPCTSTR
)FormatBytes(dwFileSize
));
1197 auto pBuf
= std::make_unique
<char[]>(dwFileSize
+ 1);
1199 ::AfxThrowMemoryException();
1201 if (!Send(csAttach
))
1208 if ( !file
.Open ( lpszFileName
, CFile::modeRead
) )
1210 m_csLastError
.Format(L
"Open file [%s] failed", lpszFileName
);
1213 UINT nFileLen
= file
.Read(pBuf
.get(), dwFileSize
);
1214 filedata
= EncodeBase64(pBuf
.get(), nFileLen
);
1215 filedata
+= L
"\r\n\r\n";
1217 catch (CFileException
*e
)
1220 m_csLastError
.Format(L
"Read file [%s] failed", lpszFileName
);
1224 if (!SendBuffer(filedata
))
1230 CString
CHwSMTP::GetLastErrorText()
1232 return m_csLastError
;
1236 CString
FormatDateTime (COleDateTime
&DateTime
, LPCTSTR
/*pFormat*/)
1238 // If null, return empty string
1239 if ( DateTime
.GetStatus() == COleDateTime::null
|| DateTime
.GetStatus() == COleDateTime::invalid
)
1243 if (S_OK
!= VarUdateFromDate(DateTime
.m_dt
, 0, &ud
))
1248 static TCHAR
* weeks
[] = { L
"Sun", L
"Mon", L
"Tue", L
"Wen", L
"Thu", L
"Fri", L
"Sat" };
1249 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" };
1251 TIME_ZONE_INFORMATION stTimeZone
;
1252 GetTimeZoneInformation(&stTimeZone
);
1255 strDate
.Format(L
"%s, %d %s %d %02d:%02d:%02d %c%04d"
1256 ,weeks
[ud
.st
.wDayOfWeek
],
1257 ud
.st
.wDay
,month
[ud
.st
.wMonth
-1],ud
.st
.wYear
,ud
.st
.wHour
,
1258 ud
.st
.wMinute
,ud
.st
.wSecond
,
1259 stTimeZone
.Bias
> 0 ? L
'-' : L
'+',
1260 abs(stTimeZone
.Bias
*10/6)
1265 int hwGetFileAttr(LPCTSTR lpFileName
, OUT CFileStatus
* pFileStatus
/*=nullptr*/)
1267 if ( !lpFileName
|| lstrlen(lpFileName
) < 1 ) return -1;
1269 CFileStatus fileStatus
;
1270 fileStatus
.m_attribute
= 0;
1271 fileStatus
.m_size
= 0;
1272 memset ( fileStatus
.m_szFullName
, 0, sizeof(fileStatus
.m_szFullName
) );
1276 if ( CFile::GetStatus(lpFileName
,fileStatus
) )
1281 CATCH (CFileException
, e
)
1295 pFileStatus
->m_ctime
= fileStatus
.m_ctime
;
1296 pFileStatus
->m_mtime
= fileStatus
.m_mtime
;
1297 pFileStatus
->m_atime
= fileStatus
.m_atime
;
1298 pFileStatus
->m_size
= fileStatus
.m_size
;
1299 pFileStatus
->m_attribute
= fileStatus
.m_attribute
;
1300 lstrcpy ( pFileStatus
->m_szFullName
, fileStatus
.m_szFullName
);
1304 return (int)fileStatus
.m_size
;
1307 CString
FormatBytes ( double fBytesNum
, BOOL bShowUnit
/*=TRUE*/, int nFlag
/*=0*/ )
1312 if ( fBytesNum
>= 1024.0 && fBytesNum
< 1024.0*1024.0 )
1313 csRes
.Format(L
"%.2f%s", fBytesNum
/ 1024.0, bShowUnit
? L
" K" : L
"");
1314 else if ( fBytesNum
>= 1024.0*1024.0 && fBytesNum
< 1024.0*1024.0*1024.0 )
1315 csRes
.Format(L
"%.2f%s", fBytesNum
/ (1024.0 * 1024.0), bShowUnit
? L
" M" : L
"");
1316 else if ( fBytesNum
>= 1024.0*1024.0*1024.0 )
1317 csRes
.Format(L
"%.2f%s", fBytesNum
/ (1024.0 * 1024.0 * 1024.0), bShowUnit
?L
" G":L
"");
1319 csRes
.Format(L
"%.2f%s", fBytesNum
, bShowUnit
? L
" B" : L
"");
1321 else if ( nFlag
== 1 )
1322 csRes
.Format(L
"%.2f%s", fBytesNum
/ 1024.0, bShowUnit
? L
" K" : L
"");
1323 else if ( nFlag
== 2 )
1324 csRes
.Format(L
"%.2f%s", fBytesNum
/ (1024.0 * 1024.0), bShowUnit
? L
" M" : L
"");
1325 else if ( nFlag
== 3 )
1326 csRes
.Format(L
"%.2f%s", fBytesNum
/ (1024.0 * 1024.0 * 1024.0), bShowUnit
? L
" G" : L
"");