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"
17 #define IO_BUFFER_SIZE 0x10000
19 #pragma comment(lib, "Secur32.lib")
21 DWORD dwProtocol
= SP_PROT_TLS1
; // SP_PROT_TLS1; // SP_PROT_PCT1; SP_PROT_SSL2; SP_PROT_SSL3; 0=default
22 ALG_ID aiKeyExch
= 0; // = default; CALG_DH_EPHEM; CALG_RSA_KEYX;
24 SCHANNEL_CRED SchannelCred
;
25 PSecurityFunctionTable g_pSSPI
;
27 //////////////////////////////////////////////////////////////////////
28 // Construction/Destruction
29 //////////////////////////////////////////////////////////////////////
32 m_bConnected ( FALSE
),
33 m_nSmtpSrvPort ( 25 ),
36 m_csPartBoundary
= _T( "WC_MAIL_PaRt_BoUnDaRy_05151998" );
37 m_csMIMEContentType
.Format(_T("multipart/mixed; boundary=%s"), (LPCTSTR
)m_csPartBoundary
);
38 m_csNoMIMEText
= _T( "This is a multi-part message in MIME format." );
39 //m_csCharSet = _T("\r\n\tcharset=\"iso-8859-1\"\r\n");
46 m_iSecurityLevel
= none
;
48 SecureZeroMemory(&Sizes
, sizeof(SecPkgContext_StreamSizes
));
57 void CHwSMTP::GetNameAddress(CString
&in
, CString
&name
,CString
&address
)
60 start
=in
.Find(_T('<'));
63 if(start
>=0 && end
>=0)
66 address
=in
.Mid(start
+1,end
-start
-1);
72 CString
CHwSMTP::GetServerAddress(CString
&email
)
77 start
= email
.Find(_T("<"));
78 end
= email
.Find(_T(">"));
80 if(start
>=0 && end
>=0)
82 str
=email
.Mid(start
+1,end
-start
-1);
89 start
= str
.Find(_T('@'));
90 return str
.Mid(start
+1);
94 BOOL
CHwSMTP::SendSpeedEmail
101 CStringArray
*pStrAryAttach
,
109 To
+= GET_SAFE_STRING(lpszAddrTo
);
111 To
+= GET_SAFE_STRING(pStrAryCC
);
113 std::map
<CString
,std::vector
<CString
>> Address
;
118 CString one
= To
.Tokenize(_T(";"),start
);
124 addr
= GetServerAddress(one
);
128 Address
[addr
].push_back(one
);
132 std::map
<CString
,std::vector
<CString
>>::iterator itr1
= Address
.begin();
133 for( ; itr1
!= Address
.end(); ++itr1
)
135 PDNS_RECORD pDnsRecord
;
139 DnsQuery(itr1
->first
,
140 DNS_TYPE_MX
,DNS_QUERY_STANDARD
,
141 NULL
, //Contains DNS server IP address.
142 &pDnsRecord
, //Resource record that contains the response.
147 m_csLastError
.Format(_T("DNS query failed %d"), status
);
154 for (size_t i
= 0; i
< itr1
->second
.size(); ++i
)
165 if(pNext
->wType
== DNS_TYPE_MX
)
166 if(SendEmail(pNext
->Data
.MX
.pNameExchange
,NULL
,NULL
,false,
167 lpszAddrFrom
,to
,lpszSubject
,lpszBody
,lpszCharSet
,pStrAryAttach
,pStrAryCC
,
168 25,pSend
,lpszAddrTo
))
176 DnsRecordListFree(pDnsRecord
,DnsFreeRecordList
);
182 static SECURITY_STATUS
ClientHandshakeLoop(CSocket
* Socket
, PCredHandle phCreds
, CtxtHandle
* phContext
, BOOL fDoInitialRead
, SecBuffer
* pExtraData
)
184 SecBufferDesc OutBuffer
, InBuffer
;
185 SecBuffer InBuffers
[2], OutBuffers
[1];
186 DWORD dwSSPIFlags
, dwSSPIOutFlags
, cbData
, cbIoBuffer
;
188 SECURITY_STATUS scRet
;
191 dwSSPIFlags
= ISC_REQ_SEQUENCE_DETECT
| ISC_REQ_REPLAY_DETECT
| ISC_REQ_CONFIDENTIALITY
|
192 ISC_RET_EXTENDED_ERROR
| ISC_REQ_ALLOCATE_MEMORY
| ISC_REQ_STREAM
;
194 // Allocate data buffer.
195 auto IoBuffer
= std::make_unique
<UCHAR
[]>(IO_BUFFER_SIZE
);
198 // printf("**** Out of memory (1)\n");
199 return SEC_E_INTERNAL_ERROR
;
202 fDoRead
= fDoInitialRead
;
204 // Loop until the handshake is finished or an error occurs.
205 scRet
= SEC_I_CONTINUE_NEEDED
;
207 while (scRet
== SEC_I_CONTINUE_NEEDED
|| scRet
== SEC_E_INCOMPLETE_MESSAGE
|| scRet
== SEC_I_INCOMPLETE_CREDENTIALS
)
209 if (0 == cbIoBuffer
|| scRet
== SEC_E_INCOMPLETE_MESSAGE
) // Read data from server.
213 cbData
= Socket
->Receive(IoBuffer
.get() + cbIoBuffer
, IO_BUFFER_SIZE
- cbIoBuffer
, 0);
214 if (cbData
== SOCKET_ERROR
)
216 // printf("**** Error %d reading data from server\n", WSAGetLastError());
217 scRet
= SEC_E_INTERNAL_ERROR
;
220 else if (cbData
== 0)
222 // printf("**** Server unexpectedly disconnected\n");
223 scRet
= SEC_E_INTERNAL_ERROR
;
226 // printf("%d bytes of handshake data received\n", cbData);
227 cbIoBuffer
+= cbData
;
233 // Set up the input buffers. Buffer 0 is used to pass in data
234 // received from the server. Schannel will consume some or all
235 // of this. Leftover data (if any) will be placed in buffer 1 and
236 // given a buffer type of SECBUFFER_EXTRA.
237 InBuffers
[0].pvBuffer
= IoBuffer
.get();
238 InBuffers
[0].cbBuffer
= cbIoBuffer
;
239 InBuffers
[0].BufferType
= SECBUFFER_TOKEN
;
241 InBuffers
[1].pvBuffer
= nullptr;
242 InBuffers
[1].cbBuffer
= 0;
243 InBuffers
[1].BufferType
= SECBUFFER_EMPTY
;
245 InBuffer
.cBuffers
= 2;
246 InBuffer
.pBuffers
= InBuffers
;
247 InBuffer
.ulVersion
= SECBUFFER_VERSION
;
249 // Set up the output buffers. These are initialized to NULL
250 // so as to make it less likely we'll attempt to free random
252 OutBuffers
[0].pvBuffer
= nullptr;
253 OutBuffers
[0].BufferType
= SECBUFFER_TOKEN
;
254 OutBuffers
[0].cbBuffer
= 0;
256 OutBuffer
.cBuffers
= 1;
257 OutBuffer
.pBuffers
= OutBuffers
;
258 OutBuffer
.ulVersion
= SECBUFFER_VERSION
;
260 // Call InitializeSecurityContext.
261 scRet
= g_pSSPI
->InitializeSecurityContext(phCreds
, phContext
, nullptr, dwSSPIFlags
, 0, SECURITY_NATIVE_DREP
, &InBuffer
, 0, nullptr, &OutBuffer
, &dwSSPIOutFlags
, &tsExpiry
);
263 // If InitializeSecurityContext was successful (or if the error was
264 // one of the special extended ones), send the contends of the output
265 // buffer to the server.
266 if (scRet
== SEC_E_OK
|| scRet
== SEC_I_CONTINUE_NEEDED
|| FAILED(scRet
) && (dwSSPIOutFlags
& ISC_RET_EXTENDED_ERROR
))
268 if (OutBuffers
[0].cbBuffer
!= 0 && OutBuffers
[0].pvBuffer
!= nullptr)
270 cbData
= Socket
->Send(OutBuffers
[0].pvBuffer
, OutBuffers
[0].cbBuffer
, 0 );
271 if(cbData
== SOCKET_ERROR
|| cbData
== 0)
273 // printf( "**** Error %d sending data to server (2)\n", WSAGetLastError() );
274 g_pSSPI
->FreeContextBuffer(OutBuffers
[0].pvBuffer
);
275 g_pSSPI
->DeleteSecurityContext(phContext
);
276 return SEC_E_INTERNAL_ERROR
;
278 // printf("%d bytes of handshake data sent\n", cbData);
280 // Free output buffer.
281 g_pSSPI
->FreeContextBuffer(OutBuffers
[0].pvBuffer
);
282 OutBuffers
[0].pvBuffer
= nullptr;
286 // If InitializeSecurityContext returned SEC_E_INCOMPLETE_MESSAGE,
287 // then we need to read more data from the server and try again.
288 if (scRet
== SEC_E_INCOMPLETE_MESSAGE
) continue;
290 // If InitializeSecurityContext returned SEC_E_OK, then the
291 // handshake completed successfully.
292 if (scRet
== SEC_E_OK
)
294 // If the "extra" buffer contains data, this is encrypted application
295 // protocol layer stuff. It needs to be saved. The application layer
296 // will later decrypt it with DecryptMessage.
297 // printf("Handshake was successful\n");
299 if (InBuffers
[1].BufferType
== SECBUFFER_EXTRA
)
301 pExtraData
->pvBuffer
= LocalAlloc( LMEM_FIXED
, InBuffers
[1].cbBuffer
);
302 if (pExtraData
->pvBuffer
== nullptr)
304 // printf("**** Out of memory (2)\n");
305 return SEC_E_INTERNAL_ERROR
;
308 MoveMemory(pExtraData
->pvBuffer
, IoBuffer
.get() + (cbIoBuffer
- InBuffers
[1].cbBuffer
), InBuffers
[1].cbBuffer
);
310 pExtraData
->cbBuffer
= InBuffers
[1].cbBuffer
;
311 pExtraData
->BufferType
= SECBUFFER_TOKEN
;
313 // printf( "%d bytes of app data was bundled with handshake data\n", pExtraData->cbBuffer );
317 pExtraData
->pvBuffer
= nullptr;
318 pExtraData
->cbBuffer
= 0;
319 pExtraData
->BufferType
= SECBUFFER_EMPTY
;
321 break; // Bail out to quit
324 // Check for fatal error.
327 // printf("**** Error 0x%x returned by InitializeSecurityContext (2)\n", scRet);
331 // If InitializeSecurityContext returned SEC_I_INCOMPLETE_CREDENTIALS,
332 // then the server just requested client authentication.
333 if (scRet
== SEC_I_INCOMPLETE_CREDENTIALS
)
335 // Busted. The server has requested client authentication and
336 // the credential we supplied didn't contain a client certificate.
337 // This function will read the list of trusted certificate
338 // authorities ("issuers") that was received from the server
339 // and attempt to find a suitable client certificate that
340 // was issued by one of these. If this function is successful,
341 // then we will connect using the new certificate. Otherwise,
342 // we will attempt to connect anonymously (using our current credentials).
343 //GetNewClientCredentials(phCreds, phContext);
347 scRet
= SEC_I_CONTINUE_NEEDED
;
351 // Copy any leftover data from the "extra" buffer, and go around again.
352 if ( InBuffers
[1].BufferType
== SECBUFFER_EXTRA
)
354 MoveMemory(IoBuffer
.get(), IoBuffer
.get() + (cbIoBuffer
- InBuffers
[1].cbBuffer
), InBuffers
[1].cbBuffer
);
355 cbIoBuffer
= InBuffers
[1].cbBuffer
;
361 // Delete the security context in the case of a fatal error.
363 g_pSSPI
->DeleteSecurityContext(phContext
);
368 static SECURITY_STATUS
PerformClientHandshake( CSocket
* Socket
, PCredHandle phCreds
, LPTSTR pszServerName
, CtxtHandle
* phContext
, SecBuffer
* pExtraData
)
370 SecBufferDesc OutBuffer
;
371 SecBuffer OutBuffers
[1];
372 DWORD dwSSPIFlags
, dwSSPIOutFlags
, cbData
;
374 SECURITY_STATUS scRet
;
376 dwSSPIFlags
= ISC_REQ_SEQUENCE_DETECT
| ISC_REQ_REPLAY_DETECT
| ISC_REQ_CONFIDENTIALITY
|
377 ISC_RET_EXTENDED_ERROR
| ISC_REQ_ALLOCATE_MEMORY
| ISC_REQ_STREAM
;
379 // Initiate a ClientHello message and generate a token.
380 OutBuffers
[0].pvBuffer
= nullptr;
381 OutBuffers
[0].BufferType
= SECBUFFER_TOKEN
;
382 OutBuffers
[0].cbBuffer
= 0;
384 OutBuffer
.cBuffers
= 1;
385 OutBuffer
.pBuffers
= OutBuffers
;
386 OutBuffer
.ulVersion
= SECBUFFER_VERSION
;
388 scRet
= g_pSSPI
->InitializeSecurityContext(phCreds
, nullptr, pszServerName
, dwSSPIFlags
, 0, SECURITY_NATIVE_DREP
, nullptr, 0, phContext
, &OutBuffer
, &dwSSPIOutFlags
, &tsExpiry
);
390 if (scRet
!= SEC_I_CONTINUE_NEEDED
)
392 // printf("**** Error %d returned by InitializeSecurityContext (1)\n", scRet);
396 // Send response to server if there is one.
397 if (OutBuffers
[0].cbBuffer
!= 0 && OutBuffers
[0].pvBuffer
!= nullptr)
399 cbData
= Socket
->Send(OutBuffers
[0].pvBuffer
, OutBuffers
[0].cbBuffer
, 0);
400 if (cbData
== SOCKET_ERROR
|| cbData
== 0)
402 // printf("**** Error %d sending data to server (1)\n", WSAGetLastError());
403 g_pSSPI
->FreeContextBuffer(OutBuffers
[0].pvBuffer
);
404 g_pSSPI
->DeleteSecurityContext(phContext
);
405 return SEC_E_INTERNAL_ERROR
;
407 // printf("%d bytes of handshake data sent\n", cbData);
409 g_pSSPI
->FreeContextBuffer(OutBuffers
[0].pvBuffer
); // Free output buffer.
410 OutBuffers
[0].pvBuffer
= nullptr;
413 return ClientHandshakeLoop(Socket
, phCreds
, phContext
, TRUE
, pExtraData
);
416 static SECURITY_STATUS
CreateCredentials(PCredHandle phCreds
)
419 SECURITY_STATUS Status
;
420 DWORD cSupportedAlgs
= 0;
421 ALG_ID rgbSupportedAlgs
[16];
423 // Build Schannel credential structure. Currently, this sample only
424 // specifies the protocol to be used (and optionally the certificate,
425 // of course). Real applications may wish to specify other parameters as well.
426 SecureZeroMemory(&SchannelCred
, sizeof(SchannelCred
));
428 SchannelCred
.dwVersion
= SCHANNEL_CRED_VERSION
;
429 SchannelCred
.grbitEnabledProtocols
= dwProtocol
;
432 rgbSupportedAlgs
[cSupportedAlgs
++] = aiKeyExch
;
436 SchannelCred
.cSupportedAlgs
= cSupportedAlgs
;
437 SchannelCred
.palgSupportedAlgs
= rgbSupportedAlgs
;
440 SchannelCred
.dwFlags
|= SCH_CRED_NO_DEFAULT_CREDS
;
442 // The SCH_CRED_MANUAL_CRED_VALIDATION flag is specified because
443 // this sample verifies the server certificate manually.
444 // Applications that expect to run on WinNT, Win9x, or WinME
445 // should specify this flag and also manually verify the server
446 // certificate. Applications running on newer versions of Windows can
447 // leave off this flag, in which case the InitializeSecurityContext
448 // function will validate the server certificate automatically.
449 SchannelCred
.dwFlags
|= SCH_CRED_MANUAL_CRED_VALIDATION
;
451 // Create an SSPI credential.
452 Status
= g_pSSPI
->AcquireCredentialsHandle(nullptr, // Name of principal
453 UNISP_NAME
, // Name of package
454 SECPKG_CRED_OUTBOUND
, // Flags indicating use
455 nullptr, // Pointer to logon ID
456 &SchannelCred
, // Package specific data
457 nullptr, // Pointer to GetKey() func
458 nullptr, // Value to pass to GetKey()
459 phCreds
, // (out) Cred Handle
460 &tsExpiry
); // (out) Lifetime (optional)
465 static DWORD
EncryptSend(CSocket
* Socket
, CtxtHandle
* phContext
, PBYTE pbIoBuffer
, SecPkgContext_StreamSizes Sizes
)
466 // http://msdn.microsoft.com/en-us/library/aa375378(VS.85).aspx
467 // The encrypted message is encrypted in place, overwriting the original contents of its buffer.
469 SECURITY_STATUS scRet
;
470 SecBufferDesc Message
;
471 SecBuffer Buffers
[4];
475 pbMessage
= pbIoBuffer
+ Sizes
.cbHeader
; // Offset by "header size"
476 cbMessage
= (DWORD
)strlen((char *)pbMessage
);
478 // Encrypt the HTTP request.
479 Buffers
[0].pvBuffer
= pbIoBuffer
; // Pointer to buffer 1
480 Buffers
[0].cbBuffer
= Sizes
.cbHeader
; // length of header
481 Buffers
[0].BufferType
= SECBUFFER_STREAM_HEADER
; // Type of the buffer
483 Buffers
[1].pvBuffer
= pbMessage
; // Pointer to buffer 2
484 Buffers
[1].cbBuffer
= cbMessage
; // length of the message
485 Buffers
[1].BufferType
= SECBUFFER_DATA
; // Type of the buffer
487 Buffers
[2].pvBuffer
= pbMessage
+ cbMessage
; // Pointer to buffer 3
488 Buffers
[2].cbBuffer
= Sizes
.cbTrailer
; // length of the trailor
489 Buffers
[2].BufferType
= SECBUFFER_STREAM_TRAILER
; // Type of the buffer
491 Buffers
[3].pvBuffer
= SECBUFFER_EMPTY
; // Pointer to buffer 4
492 Buffers
[3].cbBuffer
= SECBUFFER_EMPTY
; // length of buffer 4
493 Buffers
[3].BufferType
= SECBUFFER_EMPTY
; // Type of the buffer 4
495 Message
.ulVersion
= SECBUFFER_VERSION
; // Version number
496 Message
.cBuffers
= 4; // Number of buffers - must contain four SecBuffer structures.
497 Message
.pBuffers
= Buffers
; // Pointer to array of buffers
499 scRet
= g_pSSPI
->EncryptMessage(phContext
, 0, &Message
, 0); // must contain four SecBuffer structures.
502 // printf("**** Error 0x%x returned by EncryptMessage\n", scRet);
507 // Send the encrypted data to the server.
508 return Socket
->Send(pbIoBuffer
, Buffers
[0].cbBuffer
+ Buffers
[1].cbBuffer
+ Buffers
[2].cbBuffer
, 0);
511 static LONG
DisconnectFromServer(CSocket
* Socket
, PCredHandle phCreds
, CtxtHandle
* phContext
)
514 DWORD dwType
, dwSSPIFlags
, dwSSPIOutFlags
, cbMessage
, cbData
, Status
;
515 SecBufferDesc OutBuffer
;
516 SecBuffer OutBuffers
[1];
519 dwType
= SCHANNEL_SHUTDOWN
; // Notify schannel that we are about to close the connection.
521 OutBuffers
[0].pvBuffer
= &dwType
;
522 OutBuffers
[0].BufferType
= SECBUFFER_TOKEN
;
523 OutBuffers
[0].cbBuffer
= sizeof(dwType
);
525 OutBuffer
.cBuffers
= 1;
526 OutBuffer
.pBuffers
= OutBuffers
;
527 OutBuffer
.ulVersion
= SECBUFFER_VERSION
;
529 Status
= g_pSSPI
->ApplyControlToken(phContext
, &OutBuffer
);
532 // printf("**** Error 0x%x returned by ApplyControlToken\n", Status);
536 // Build an SSL close notify message.
537 dwSSPIFlags
= ISC_REQ_SEQUENCE_DETECT
| ISC_REQ_REPLAY_DETECT
| ISC_REQ_CONFIDENTIALITY
| ISC_RET_EXTENDED_ERROR
| ISC_REQ_ALLOCATE_MEMORY
| ISC_REQ_STREAM
;
539 OutBuffers
[0].pvBuffer
= nullptr;
540 OutBuffers
[0].BufferType
= SECBUFFER_TOKEN
;
541 OutBuffers
[0].cbBuffer
= 0;
543 OutBuffer
.cBuffers
= 1;
544 OutBuffer
.pBuffers
= OutBuffers
;
545 OutBuffer
.ulVersion
= SECBUFFER_VERSION
;
547 Status
= g_pSSPI
->InitializeSecurityContext(phCreds
, phContext
, nullptr, dwSSPIFlags
, 0, SECURITY_NATIVE_DREP
, nullptr, 0, phContext
, &OutBuffer
, &dwSSPIOutFlags
, &tsExpiry
);
551 // printf("**** Error 0x%x returned by InitializeSecurityContext\n", Status);
555 pbMessage
= (PBYTE
)OutBuffers
[0].pvBuffer
;
556 cbMessage
= OutBuffers
[0].cbBuffer
;
558 // Send the close notify message to the server.
559 if (pbMessage
!= nullptr && cbMessage
!= 0)
561 cbData
= Socket
->Send(pbMessage
, cbMessage
, 0);
562 if (cbData
== SOCKET_ERROR
|| cbData
== 0)
564 Status
= WSAGetLastError();
567 // printf("Sending Close Notify\n");
568 // printf("%d bytes of handshake data sent\n", cbData);
569 g_pSSPI
->FreeContextBuffer(pbMessage
); // Free output buffer.
573 g_pSSPI
->DeleteSecurityContext(phContext
); // Free the security context.
579 static SECURITY_STATUS
ReadDecrypt(CSocket
* Socket
, PCredHandle phCreds
, CtxtHandle
* phContext
, PBYTE pbIoBuffer
, DWORD cbIoBufferLength
)
581 // calls recv() - blocking socket read
582 // http://msdn.microsoft.com/en-us/library/ms740121(VS.85).aspx
584 // The encrypted message is decrypted in place, overwriting the original contents of its buffer.
585 // http://msdn.microsoft.com/en-us/library/aa375211(VS.85).aspx
588 SecBuffer ExtraBuffer
;
589 SecBuffer
* pDataBuffer
, * pExtraBuffer
;
591 SECURITY_STATUS scRet
;
592 SecBufferDesc Message
;
593 SecBuffer Buffers
[4];
595 DWORD cbIoBuffer
, cbData
, length
;
598 // Read data from server until done.
601 while (TRUE
) // Read some data.
603 if (cbIoBuffer
== 0 || scRet
== SEC_E_INCOMPLETE_MESSAGE
) // get the data
605 cbData
= Socket
->Receive(pbIoBuffer
+ cbIoBuffer
, cbIoBufferLength
- cbIoBuffer
, 0);
606 if (cbData
== SOCKET_ERROR
)
608 // printf("**** Error %d reading data from server\n", WSAGetLastError());
609 scRet
= SEC_E_INTERNAL_ERROR
;
612 else if (cbData
== 0) // Server disconnected.
616 // printf("**** Server unexpectedly disconnected\n");
617 scRet
= SEC_E_INTERNAL_ERROR
;
625 // printf("%d bytes of (encrypted) application data received\n", cbData);
626 cbIoBuffer
+= cbData
;
630 // Decrypt the received data.
631 Buffers
[0].pvBuffer
= pbIoBuffer
;
632 Buffers
[0].cbBuffer
= cbIoBuffer
;
633 Buffers
[0].BufferType
= SECBUFFER_DATA
; // Initial Type of the buffer 1
634 Buffers
[1].BufferType
= SECBUFFER_EMPTY
; // Initial Type of the buffer 2
635 Buffers
[2].BufferType
= SECBUFFER_EMPTY
; // Initial Type of the buffer 3
636 Buffers
[3].BufferType
= SECBUFFER_EMPTY
; // Initial Type of the buffer 4
638 Message
.ulVersion
= SECBUFFER_VERSION
; // Version number
639 Message
.cBuffers
= 4; // Number of buffers - must contain four SecBuffer structures.
640 Message
.pBuffers
= Buffers
; // Pointer to array of buffers
642 scRet
= g_pSSPI
->DecryptMessage(phContext
, &Message
, 0, nullptr);
643 if (scRet
== SEC_I_CONTEXT_EXPIRED
)
644 break; // Server signalled end of session
645 // if (scRet == SEC_E_INCOMPLETE_MESSAGE - Input buffer has partial encrypted record, read more
646 if (scRet
!= SEC_E_OK
&& scRet
!= SEC_I_RENEGOTIATE
&& scRet
!= SEC_I_CONTEXT_EXPIRED
)
649 // Locate data and (optional) extra buffers.
650 pDataBuffer
= nullptr;
651 pExtraBuffer
= nullptr;
652 for (int i
= 1; i
< 4; ++i
)
654 if (pDataBuffer
== nullptr && Buffers
[i
].BufferType
== SECBUFFER_DATA
)
655 pDataBuffer
= &Buffers
[i
];
656 if (pExtraBuffer
== nullptr && Buffers
[i
].BufferType
== SECBUFFER_EXTRA
)
657 pExtraBuffer
= &Buffers
[i
];
660 // Display the decrypted data.
663 length
= pDataBuffer
->cbBuffer
;
664 if (length
) // check if last two chars are CR LF
666 buff
= (PBYTE
)pDataBuffer
->pvBuffer
;
667 if (buff
[length
-2] == 13 && buff
[length
-1] == 10) // Found CRLF
675 // Move any "extra" data to the input buffer.
678 MoveMemory(pbIoBuffer
, pExtraBuffer
->pvBuffer
, pExtraBuffer
->cbBuffer
);
679 cbIoBuffer
= pExtraBuffer
->cbBuffer
;
684 // The server wants to perform another handshake sequence.
685 if (scRet
== SEC_I_RENEGOTIATE
)
687 // printf("Server requested renegotiate!\n");
688 scRet
= ClientHandshakeLoop( Socket
, phCreds
, phContext
, FALSE
, &ExtraBuffer
);
689 if (scRet
!= SEC_E_OK
)
692 if (ExtraBuffer
.pvBuffer
) // Move any "extra" data to the input buffer.
694 MoveMemory(pbIoBuffer
, ExtraBuffer
.pvBuffer
, ExtraBuffer
.cbBuffer
);
695 cbIoBuffer
= ExtraBuffer
.cbBuffer
;
698 } // Loop till CRLF is found at the end of the data
703 BOOL
CHwSMTP::SendEmail (
704 LPCTSTR lpszSmtpSrvHost
,
705 LPCTSTR lpszUserName
,
708 LPCTSTR lpszAddrFrom
,
712 LPCTSTR lpszCharSet
, // ×Ö·û¼¯ÀàÐÍ£¬ÀýÈ磺·±ÌåÖÐÎÄÕâÀïÓ¦ÊäÈë"big5"£¬¼òÌåÖÐÎÄʱÊäÈë"gb2312"
713 CStringArray
*pStrAryAttach
/*=NULL*/,
714 LPCTSTR pStrAryCC
/*=NULL*/,
715 UINT nSmtpSrvPort
,/*=25*/
721 m_StrAryAttach
.RemoveAll();
723 m_StrCC
+= GET_SAFE_STRING(pStrAryCC
);
725 m_csSmtpSrvHost
= GET_SAFE_STRING ( lpszSmtpSrvHost
);
726 if ( m_csSmtpSrvHost
.GetLength() <= 0 )
728 m_csLastError
.Format ( _T("Parameter Error!") );
731 m_csUserName
= GET_SAFE_STRING ( lpszUserName
);
732 m_csPasswd
= GET_SAFE_STRING ( lpszPasswd
);
733 m_bMustAuth
= bMustAuth
;
734 if ( m_bMustAuth
&& m_csUserName
.GetLength() <= 0 )
736 m_csLastError
.Format ( _T("Parameter Error!") );
740 m_csAddrFrom
= GET_SAFE_STRING ( lpszAddrFrom
);
741 m_csAddrTo
= GET_SAFE_STRING ( lpszAddrTo
);
742 // m_csFromName = GET_SAFE_STRING ( lpszFromName );
743 // m_csReceiverName = GET_SAFE_STRING ( lpszReceiverName );
744 m_csSubject
= GET_SAFE_STRING ( lpszSubject
);
745 m_csBody
= GET_SAFE_STRING ( lpszBody
);
747 this->m_csSender
= GET_SAFE_STRING(pSender
);
748 this->m_csToList
= GET_SAFE_STRING(pToList
);
750 m_nSmtpSrvPort
= nSmtpSrvPort
;
752 if ( lpszCharSet
&& lstrlen(lpszCharSet
) > 0 )
753 m_csCharSet
.Format ( _T("\r\n\tcharset=\"%s\"\r\n"), lpszCharSet
);
756 m_csAddrFrom
.GetLength() <= 0 || m_csAddrTo
.GetLength() <= 0
759 m_csLastError
.Format ( _T("Parameter Error!") );
765 m_StrAryAttach
.Append ( *pStrAryAttach
);
767 if ( m_StrAryAttach
.GetSize() < 1 )
768 m_csMIMEContentType
.Format(_T("text/plain; %s"), (LPCTSTR
)m_csCharSet
);
772 if ( !m_SendSock
.Create () )
774 //int nResult = GetLastError();
775 m_csLastError
.Format ( _T("Create socket failed!") );
782 m_iSecurityLevel
= want_tls
;
785 m_iSecurityLevel
= ssl
;
788 m_iSecurityLevel
= none
;
792 if ( !m_SendSock
.Connect ( m_csSmtpSrvHost
, m_nSmtpSrvPort
) )
794 m_csLastError
.Format(_T("Connect to [%s] failed"), (LPCTSTR
)m_csSmtpSrvHost
);
798 if (m_iSecurityLevel
== want_tls
) {
799 if (!GetResponse("220"))
803 if (!GetResponse("220"))
805 m_iSecurityLevel
= tls_established
;
811 SECURITY_STATUS Status
;
813 CtxtHandle contextStruct
;
814 CredHandle credentialsStruct
;
816 if (m_iSecurityLevel
>= ssl
)
818 g_pSSPI
= InitSecurityInterface();
820 contextStruct
.dwLower
= 0;
821 contextStruct
.dwUpper
= 0;
823 hCreds
= &credentialsStruct
;
824 credentialsStruct
.dwLower
= 0;
825 credentialsStruct
.dwUpper
= 0;
826 Status
= CreateCredentials(hCreds
);
827 if (Status
!= SEC_E_OK
)
829 m_csLastError
= CFormatMessageWrapper(Status
);
833 hContext
= &contextStruct
;
834 Status
= PerformClientHandshake(&m_SendSock
, hCreds
, m_csSmtpSrvHost
.GetBuffer(), hContext
, &ExtraData
);
835 if (Status
!= SEC_E_OK
)
837 m_csLastError
= CFormatMessageWrapper(Status
);
841 PCCERT_CONTEXT pRemoteCertContext
= nullptr;
842 // Authenticate server's credentials. Get server's certificate.
843 Status
= g_pSSPI
->QueryContextAttributes(hContext
, SECPKG_ATTR_REMOTE_CERT_CONTEXT
, (PVOID
)&pRemoteCertContext
);
846 m_csLastError
= CFormatMessageWrapper(Status
);
851 cert
.parent
.cert_type
= GIT_CERT_X509
;
852 cert
.data
= pRemoteCertContext
->pbCertEncoded
;
853 cert
.len
= pRemoteCertContext
->cbCertEncoded
;
854 if (CAppUtils::Git2CertificateCheck((git_cert
*)&cert
, 0, CUnicodeUtils::GetUTF8(m_csSmtpSrvHost
), nullptr))
856 CertFreeCertificateContext(pRemoteCertContext
);
857 m_csLastError
= _T("Invalid certificate.");
861 CertFreeCertificateContext(pRemoteCertContext
);
863 Status
= g_pSSPI
->QueryContextAttributes(hContext
, SECPKG_ATTR_STREAM_SIZES
, &Sizes
);
866 m_csLastError
= CFormatMessageWrapper(Status
);
871 cbIoBufferLength
= Sizes
.cbHeader
+ Sizes
.cbMaximumMessage
+ Sizes
.cbTrailer
;
872 pbIoBuffer
= (PBYTE
)LocalAlloc(LMEM_FIXED
, cbIoBufferLength
);
873 SecureZeroMemory(pbIoBuffer
, cbIoBufferLength
);
874 if (pbIoBuffer
== nullptr)
876 m_csLastError
= _T("Could not allocate memory");
881 if (m_iSecurityLevel
<= ssl
)
883 if (!GetResponse("220"))
891 if (m_iSecurityLevel
>= ssl
)
893 if (hContext
&& hCreds
)
894 DisconnectFromServer(&m_SendSock
, hCreds
, hContext
);
897 LocalFree(pbIoBuffer
);
898 pbIoBuffer
= nullptr;
899 cbIoBufferLength
= 0;
903 g_pSSPI
->DeleteSecurityContext(hContext
);
908 g_pSSPI
->FreeCredentialsHandle(hCreds
);
919 BOOL
CHwSMTP::GetResponse(LPCSTR lpszVerifyCode
)
921 if (!lpszVerifyCode
|| strlen(lpszVerifyCode
) < 1)
924 SECURITY_STATUS scRet
= SEC_E_OK
;
926 char szRecvBuf
[1024] = {0};
928 char szStatusCode
[4] = {0};
930 if (m_iSecurityLevel
>= ssl
)
932 scRet
= ReadDecrypt(&m_SendSock
, hCreds
, hContext
, pbIoBuffer
, cbIoBufferLength
);
933 SecureZeroMemory(szRecvBuf
, 1024);
934 memcpy(szRecvBuf
, pbIoBuffer
+Sizes
.cbHeader
, 1024);
937 nRet
= m_SendSock
.Receive(szRecvBuf
, sizeof(szRecvBuf
));
938 //TRACE(_T("Received : %s\r\n"), szRecvBuf);
939 if (nRet
== 0 && m_iSecurityLevel
== none
|| m_iSecurityLevel
>= ssl
&& scRet
!= SEC_E_OK
)
941 m_csLastError
.Format ( _T("Receive TCP data failed") );
944 memcpy ( szStatusCode
, szRecvBuf
, 3 );
945 if (strcmp(szStatusCode
, lpszVerifyCode
) != 0)
947 m_csLastError
.Format(_T("Received invalid response: %s"), (LPCTSTR
)CUnicodeUtils::GetUnicode(szRecvBuf
));
953 BOOL
CHwSMTP::SendBuffer(const char* buff
, int size
)
956 size
=(int)strlen(buff
);
959 m_csLastError
.Format ( _T("Didn't connect") );
963 if (m_iSecurityLevel
>= ssl
)
966 while (size
- sent
> 0)
968 int toSend
= min(size
- sent
, (int)Sizes
.cbMaximumMessage
);
969 SecureZeroMemory(pbIoBuffer
+ Sizes
.cbHeader
, Sizes
.cbMaximumMessage
);
970 memcpy(pbIoBuffer
+ Sizes
.cbHeader
, buff
+ sent
, toSend
);
971 DWORD cbData
= EncryptSend(&m_SendSock
, hContext
, pbIoBuffer
, Sizes
);
972 if (cbData
== SOCKET_ERROR
|| cbData
== 0)
977 else if (m_SendSock
.Send ( buff
, size
) != size
)
979 m_csLastError
.Format ( _T("Socket send data failed") );
985 // ÀûÓÃsocket·¢ËÍÊý¾Ý£¬Êý¾Ý³¤¶È²»Äܳ¬¹ý10M
986 BOOL
CHwSMTP::Send(const CString
&str
)
988 return Send(CUnicodeUtils::GetUTF8(str
));
991 BOOL
CHwSMTP::Send(const CStringA
&str
)
993 //TRACE(_T("Send: %s\r\n"), (LPCTSTR)CUnicodeUtils::GetUnicode(str));
994 return SendBuffer(str
, str
.GetLength());
997 BOOL
CHwSMTP::SendEmail()
1000 gethostname(CStrBufA(hostname
, 64), 64);
1002 // make sure helo hostname can be interpreted as a FQDN
1003 if (hostname
.Find(".") == -1)
1004 hostname
+= ".local";
1008 str
.Format("HELO %s\r\n", (LPCSTR
)hostname
);
1011 if (!GetResponse("250"))
1014 if ( m_bMustAuth
&& !auth() )
1024 if (!SendSubject(CUnicodeUtils::GetUnicode(hostname
)))
1034 if ( !SendAttach() )
1041 if (!GetResponse("250"))
1045 if ( HANDLE_IS_VALID(m_SendSock
.m_hSocket
) )
1047 m_bConnected
= FALSE
;
1052 static CStringA
EncodeBase64(const char* source
, int len
)
1054 int neededLength
= Base64EncodeGetRequiredLength(len
);
1056 Base64Encode((BYTE
*)source
, len
, CStrBufA(output
, neededLength
), &neededLength
);
1060 static CStringA
EncodeBase64(const CString
& source
)
1062 CStringA buf
= CUnicodeUtils::GetUTF8(source
);
1063 return EncodeBase64(buf
, buf
.GetLength());
1066 BOOL
CHwSMTP::auth()
1068 if (!Send("auth login\r\n"))
1070 if (!GetResponse("334"))
1073 if (!Send(EncodeBase64(m_csUserName
)))
1076 if (!GetResponse("334"))
1078 m_csLastError
.Format ( _T("Authentication UserName failed") );
1082 if (!Send(EncodeBase64(m_csPasswd
)))
1085 if (!GetResponse("235"))
1087 m_csLastError
.Format ( _T("Authentication Password failed") );
1094 BOOL
CHwSMTP::SendHead()
1098 GetNameAddress(m_csAddrFrom
,name
,addr
);
1100 str
.Format(_T("MAIL From: <%s>\r\n"), (LPCTSTR
)addr
);
1104 if (!GetResponse("250"))
1110 CString one
=m_csAddrTo
.Tokenize(_T(";"),start
);
1116 GetNameAddress(one
,name
,addr
);
1118 str
.Format(_T("RCPT TO: <%s>\r\n"), (LPCTSTR
)addr
);
1121 if (!GetResponse("250"))
1125 if (!Send("DATA\r\n"))
1127 if (!GetResponse("354"))
1133 BOOL
CHwSMTP::SendSubject(const CString
&hostname
)
1136 csSubject
+= _T("Date: ");
1137 COleDateTime tNow
= COleDateTime::GetCurrentTime();
1140 csSubject
+= FormatDateTime (tNow
, _T("%a, %d %b %y %H:%M:%S %Z"));
1142 csSubject
+= _T("\r\n");
1143 csSubject
.AppendFormat(_T("From: %s\r\n"), (LPCTSTR
)m_csAddrFrom
);
1145 if (!m_StrCC
.IsEmpty())
1146 csSubject
.AppendFormat(_T("CC: %s\r\n"), (LPCTSTR
)m_StrCC
);
1148 if(m_csSender
.IsEmpty())
1149 m_csSender
= this->m_csAddrFrom
;
1151 csSubject
.AppendFormat(_T("Sender: %s\r\n"), (LPCTSTR
)m_csSender
);
1153 if(this->m_csToList
.IsEmpty())
1154 m_csToList
= m_csReceiverName
;
1156 csSubject
.AppendFormat(_T("To: %s\r\n"), (LPCTSTR
)m_csToList
);
1158 csSubject
.AppendFormat(_T("Subject: %s\r\n"), (LPCTSTR
)m_csSubject
);
1162 HRESULT hr
= CoCreateGuid(&guid
);
1166 if (UuidToString(&guid
, &guidStr
) == RPC_S_OK
)
1168 m_ListID
= (LPTSTR
)guidStr
;
1169 RpcStringFree(&guidStr
);
1172 if (m_ListID
.IsEmpty())
1174 m_csLastError
= _T("Could not generate Message-ID");
1177 csSubject
.AppendFormat(_T("Message-ID: <%s@%s>\r\n"), (LPCTSTR
)m_ListID
, (LPCTSTR
)hostname
);
1178 csSubject
.AppendFormat(_T("X-Mailer: TortoiseGit\r\nMIME-Version: 1.0\r\nContent-Type: %s\r\n\r\n"), (LPCTSTR
)m_csMIMEContentType
);
1180 return Send(csSubject
);
1183 BOOL
CHwSMTP::SendBody()
1185 CString csBody
, csTemp
;
1187 if ( m_StrAryAttach
.GetSize() > 0 )
1189 csBody
.AppendFormat(_T("%s\r\n\r\n"), (LPCTSTR
)m_csNoMIMEText
);
1190 csBody
.AppendFormat(_T("--%s\r\n"), (LPCTSTR
)m_csPartBoundary
);
1191 csBody
.AppendFormat(_T("Content-Type: text/plain\r\n%sContent-Transfer-Encoding: UTF-8\r\n\r\n"), m_csCharSet
);
1194 //csTemp.Format ( _T("%s\r\n"), m_csBody );
1196 csBody
+= _T("\r\n");
1198 return Send(csBody
);
1201 BOOL
CHwSMTP::SendAttach()
1203 int nCountAttach
= (int)m_StrAryAttach
.GetSize();
1204 if ( nCountAttach
< 1 ) return TRUE
;
1206 for ( int i
=0; i
<nCountAttach
; i
++ )
1208 if ( !SendOnAttach ( m_StrAryAttach
.GetAt(i
) ) )
1212 Send(L
"--" + m_csPartBoundary
+ L
"--\r\n");
1217 BOOL
CHwSMTP::SendOnAttach(LPCTSTR lpszFileName
)
1219 ASSERT ( lpszFileName
);
1220 CString csAttach
, csTemp
;
1222 csTemp
= lpszFileName
;
1223 CString csShortFileName
= csTemp
.GetBuffer(0) + csTemp
.ReverseFind ( '\\' );
1224 csShortFileName
.TrimLeft ( _T("\\") );
1226 csAttach
.AppendFormat(_T("--%s\r\n"), (LPCTSTR
)m_csPartBoundary
);
1227 csAttach
.AppendFormat(_T("Content-Type: application/octet-stream; file=%s\r\n"), (LPCTSTR
)csShortFileName
);
1228 csAttach
.AppendFormat(_T("Content-Transfer-Encoding: base64\r\n"));
1229 csAttach
.AppendFormat(_T("Content-Disposition: attachment; filename=%s\r\n\r\n"), (LPCTSTR
)csShortFileName
);
1231 DWORD dwFileSize
= hwGetFileAttr(lpszFileName
);
1232 if ( dwFileSize
> 5*1024*1024 )
1234 m_csLastError
.Format ( _T("File [%s] too big. File size is : %s"), lpszFileName
, FormatBytes(dwFileSize
) );
1237 auto pBuf
= std::make_unique
<char[]>(dwFileSize
+ 1);
1239 ::AfxThrowMemoryException();
1241 if (!Send(csAttach
))
1248 if ( !file
.Open ( lpszFileName
, CFile::modeRead
) )
1250 m_csLastError
.Format ( _T("Open file [%s] failed"), lpszFileName
);
1253 UINT nFileLen
= file
.Read(pBuf
.get(), dwFileSize
);
1254 filedata
= EncodeBase64(pBuf
.get(), nFileLen
);
1255 filedata
+= _T("\r\n\r\n");
1257 catch (CFileException
*e
)
1260 m_csLastError
.Format ( _T("Read file [%s] failed"), lpszFileName
);
1264 if (!SendBuffer(filedata
))
1270 CString
CHwSMTP::GetLastErrorText()
1272 return m_csLastError
;
1276 CString
FormatDateTime (COleDateTime
&DateTime
, LPCTSTR
/*pFormat*/)
1278 // If null, return empty string
1279 if ( DateTime
.GetStatus() == COleDateTime::null
|| DateTime
.GetStatus() == COleDateTime::invalid
)
1283 if (S_OK
!= VarUdateFromDate(DateTime
.m_dt
, 0, &ud
))
1288 TCHAR
*weeks
[]={_T("Sun"),_T("Mon"),_T("Tue"),_T("Wen"),_T("Thu"),_T("Fri"),_T("Sat")};
1289 TCHAR
*month
[]={_T("Jan"),_T("Feb"),_T("Mar"),_T("Apr"),
1290 _T("May"),_T("Jun"),_T("Jul"),_T("Aug"),
1291 _T("Sep"),_T("Oct"),_T("Nov"),_T("Dec")};
1293 TIME_ZONE_INFORMATION stTimeZone
;
1294 GetTimeZoneInformation(&stTimeZone
);
1297 strDate
.Format(_T("%s, %d %s %d %02d:%02d:%02d %c%04d")
1298 ,weeks
[ud
.st
.wDayOfWeek
],
1299 ud
.st
.wDay
,month
[ud
.st
.wMonth
-1],ud
.st
.wYear
,ud
.st
.wHour
,
1300 ud
.st
.wMinute
,ud
.st
.wSecond
,
1301 stTimeZone
.Bias
>0?_T('-'):_T('+'),
1302 abs(stTimeZone
.Bias
*10/6)
1307 int hwGetFileAttr ( LPCTSTR lpFileName
, OUT CFileStatus
*pFileStatus
/*=NULL*/ )
1309 if ( !lpFileName
|| lstrlen(lpFileName
) < 1 ) return -1;
1311 CFileStatus fileStatus
;
1312 fileStatus
.m_attribute
= 0;
1313 fileStatus
.m_size
= 0;
1314 memset ( fileStatus
.m_szFullName
, 0, sizeof(fileStatus
.m_szFullName
) );
1318 if ( CFile::GetStatus(lpFileName
,fileStatus
) )
1323 CATCH (CFileException
, e
)
1337 pFileStatus
->m_ctime
= fileStatus
.m_ctime
;
1338 pFileStatus
->m_mtime
= fileStatus
.m_mtime
;
1339 pFileStatus
->m_atime
= fileStatus
.m_atime
;
1340 pFileStatus
->m_size
= fileStatus
.m_size
;
1341 pFileStatus
->m_attribute
= fileStatus
.m_attribute
;
1342 lstrcpy ( pFileStatus
->m_szFullName
, fileStatus
.m_szFullName
);
1346 return (int)fileStatus
.m_size
;
1349 CString
FormatBytes ( double fBytesNum
, BOOL bShowUnit
/*=TRUE*/, int nFlag
/*=0*/ )
1354 if ( fBytesNum
>= 1024.0 && fBytesNum
< 1024.0*1024.0 )
1355 csRes
.Format ( _T("%.2f%s"), fBytesNum
/ 1024.0, bShowUnit
?_T(" K"):_T("") );
1356 else if ( fBytesNum
>= 1024.0*1024.0 && fBytesNum
< 1024.0*1024.0*1024.0 )
1357 csRes
.Format ( _T("%.2f%s"), fBytesNum
/ (1024.0*1024.0), bShowUnit
?_T(" M"):_T("") );
1358 else if ( fBytesNum
>= 1024.0*1024.0*1024.0 )
1359 csRes
.Format ( _T("%.2f%s"), fBytesNum
/ (1024.0*1024.0*1024.0), bShowUnit
?_T(" G"):_T("") );
1361 csRes
.Format ( _T("%.2f%s"), fBytesNum
, bShowUnit
?_T(" B"):_T("") );
1363 else if ( nFlag
== 1 )
1365 csRes
.Format ( _T("%.2f%s"), fBytesNum
/ 1024.0, bShowUnit
?_T(" K"):_T("") );
1367 else if ( nFlag
== 2 )
1369 csRes
.Format ( _T("%.2f%s"), fBytesNum
/ (1024.0*1024.0), bShowUnit
?_T(" M"):_T("") );
1371 else if ( nFlag
== 3 )
1373 csRes
.Format ( _T("%.2f%s"), fBytesNum
/ (1024.0*1024.0*1024.0), bShowUnit
?_T(" G"):_T("") );