RebaseDlg: Correctly remember commits for rewriting on Squash after (Edit|Squash...
[TortoiseGit.git] / src / Utils / HwSMTP.cpp
blob9ec2555df1c56b51b6f050ee245e608465750401
1 // HwSMTP.cpp: implementation of the CHwSMTP class.
2 //
3 // Schannel/SSPI implementation based on http://www.coastrd.com/c-schannel-smtp
4 //
5 //////////////////////////////////////////////////////////////////////
7 #include "stdafx.h"
8 #include "afxstr.h"
9 #include "HwSMTP.h"
10 #include "Windns.h"
11 #include <Afxmt.h>
12 #include "FormatMessageWrapper.h"
13 #include <atlenc.h>
14 #include "AppUtils.h"
15 #include "PathUtils.h"
16 #include "StringUtils.h"
18 #define IO_BUFFER_SIZE 0x10000
20 #pragma comment(lib, "Dnsapi.lib")
21 #pragma comment(lib, "Rpcrt4.lib")
22 #pragma comment(lib, "Secur32.lib")
24 #ifndef GET_SAFE_STRING
25 #define GET_SAFE_STRING(str) ((str) ? (str) : L"")
26 #endif
27 #define HANDLE_IS_VALID(h) (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)
43 return L"";
45 UDATE ud;
46 if (S_OK != VarUdateFromDate(DateTime.m_dt, 0, &ud))
47 return L"";
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);
55 CString strDate;
56 strDate.Format(L"%s, %d %s %d %02d:%02d:%02d %c%04d", weeks[ud.st.wDayOfWeek],
57 ud.st.wDay, month[ud.st.wMonth - 1], ud.st.wYear, ud.st.wHour,
58 ud.st.wMinute, ud.st.wSecond,
59 stTimeZone.Bias > 0 ? L'-' : L'+',
60 abs(stTimeZone.Bias * 10 / 6));
61 return strDate;
64 static CString GetGUID()
66 CString sGuid;
67 GUID guid;
68 if (CoCreateGuid(&guid) == S_OK)
70 RPC_WSTR guidStr;
71 if (UuidToString(&guid, &guidStr) == RPC_S_OK)
73 sGuid = reinterpret_cast<LPWSTR>(guidStr);
74 RpcStringFree(&guidStr);
77 return sGuid;
80 CHwSMTP::CHwSMTP()
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.";
86 AfxSocketInit();
89 CHwSMTP::~CHwSMTP()
93 CString CHwSMTP::GetServerAddress(const CString& in)
95 CString email;
96 CStringUtils::ParseEmailAddress(in, email);
98 int start = email.Find(L'@');
99 return email.Mid(start + 1);
102 BOOL CHwSMTP::SendSpeedEmail
104 LPCWSTR lpszAddrFrom,
105 LPCWSTR lpszAddrTo,
106 LPCWSTR lpszSubject,
107 LPCWSTR lpszBody,
108 CStringArray *pStrAryAttach,
109 LPCWSTR pStrAryCC,
110 LPCWSTR pSend
113 BOOL ret=true;
114 CString To;
115 To += GET_SAFE_STRING(lpszAddrTo);
116 To += L';';
117 To += GET_SAFE_STRING(pStrAryCC);
119 std::map<CString,std::vector<CString>> Address;
121 int start = 0;
122 while( start >= 0 )
124 CString one = To.Tokenize(L";", start).Trim();
125 if(one.IsEmpty())
126 continue;
128 CString addr = GetServerAddress(one);
129 if(addr.IsEmpty())
130 continue;
132 Address[addr].push_back(one);
135 for (const auto& maildomain : Address)
137 PDNS_RECORD pDnsRecord;
138 DNS_STATUS status =
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.
143 nullptr
145 if (status)
147 m_csLastError.Format(L"DNS query failed %d", status);
148 ret = false;
149 continue;
151 SCOPE_EXIT { DnsRecordListFree(pDnsRecord, DnsFreeRecordList); };
153 CString to;
154 std::for_each(maildomain.second.cbegin(), maildomain.second.cend(), [&to](auto recipient) {
155 to += recipient;
156 to += L';';
158 if(to.IsEmpty())
159 continue;
161 PDNS_RECORD pNext = pDnsRecord;
162 while(pNext)
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))
168 break;
169 pNext=pNext->pNext;
171 if (!pNext)
172 ret = false;
175 return ret;
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;
183 TimeStamp tsExpiry;
184 SECURITY_STATUS scRet;
185 BOOL fDoRead;
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);
192 if (!IoBuffer)
194 // printf("**** Out of memory (1)\n");
195 return SEC_E_INTERNAL_ERROR;
197 cbIoBuffer = 0;
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.
207 if (fDoRead)
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;
214 break;
216 else if (cbData == 0)
218 // printf("**** Server unexpectedly disconnected\n");
219 scRet = SEC_E_INTERNAL_ERROR;
220 break;
222 // printf("%d bytes of handshake data received\n", cbData);
223 cbIoBuffer += cbData;
225 else
226 fDoRead = TRUE;
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
247 // garbage later.
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 );
311 else
313 pExtraData->pvBuffer = nullptr;
314 pExtraData->cbBuffer = 0;
315 pExtraData->BufferType = SECBUFFER_EMPTY;
317 break; // Bail out to quit
320 // Check for fatal error.
321 if (FAILED(scRet))
323 // printf("**** Error 0x%x returned by InitializeSecurityContext (2)\n", scRet);
324 break;
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);
341 // Go around again.
342 fDoRead = FALSE;
343 scRet = SEC_I_CONTINUE_NEEDED;
344 continue;
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;
353 else
354 cbIoBuffer = 0;
357 // Delete the security context in the case of a fatal error.
358 if (FAILED(scRet))
359 g_pSSPI->DeleteSecurityContext(phContext);
361 return scRet;
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;
369 TimeStamp tsExpiry;
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);
389 return 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)
414 TimeStamp tsExpiry;
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;
427 if (aiKeyExch)
428 rgbSupportedAlgs[cSupportedAlgs++] = aiKeyExch;
430 if (cSupportedAlgs)
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)
458 return Status;
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];
468 DWORD cbMessage;
469 PBYTE pbMessage;
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.
496 if (FAILED(scRet))
498 // printf("**** Error 0x%x returned by EncryptMessage\n", scRet);
499 return 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)
509 PBYTE pbMessage;
510 DWORD dwType, dwSSPIFlags, dwSSPIOutFlags, cbMessage, cbData, Status;
511 SecBufferDesc OutBuffer;
512 SecBuffer OutBuffers[1];
513 TimeStamp tsExpiry;
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);
526 if (FAILED(Status))
528 // printf("**** Error 0x%x returned by ApplyControlToken\n", Status);
529 goto cleanup;
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);
545 if (FAILED(Status))
547 // printf("**** Error 0x%x returned by InitializeSecurityContext\n", Status);
548 goto cleanup;
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();
561 goto cleanup;
563 // printf("Sending Close Notify\n");
564 // printf("%d bytes of handshake data sent\n", cbData);
565 g_pSSPI->FreeContextBuffer(pbMessage); // Free output buffer.
568 cleanup:
569 g_pSSPI->DeleteSecurityContext(phContext); // Free the security context.
570 Socket->Close();
572 return Status;
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;
592 PBYTE buff;
594 // Read data from server until done.
595 cbIoBuffer = 0;
596 scRet = 0;
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;
606 break;
608 else if (cbData == 0) // Server disconnected.
610 if (cbIoBuffer)
612 // printf("**** Server unexpectedly disconnected\n");
613 scRet = SEC_E_INTERNAL_ERROR;
614 return scRet;
616 else
617 break; // All Done
619 else // success
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)
643 return scRet;
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.
657 if (pDataBuffer)
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
665 buff[length] = 0;
666 break;
671 // Move any "extra" data to the input buffer.
672 if (pExtraBuffer)
674 MoveMemory(pbIoBuffer, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer);
675 cbIoBuffer = pExtraBuffer->cbBuffer;
677 else
678 cbIoBuffer = 0;
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)
686 return scRet;
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
696 return SEC_E_OK;
699 BOOL CHwSMTP::SendEmail (
700 LPCWSTR lpszSmtpSrvHost,
701 CCredentials* credentials,
702 BOOL bMustAuth,
703 LPCWSTR lpszAddrFrom,
704 LPCWSTR lpszAddrTo,
705 LPCWSTR lpszSubject,
706 LPCWSTR lpszBody,
707 CStringArray* pStrAryAttach/*=nullptr*/,
708 LPCWSTR pStrAryCC/*=nullptr*/,
709 UINT nSmtpSrvPort,/*=25*/
710 LPCWSTR pSender,
711 LPCWSTR pToList,
712 DWORD secLevel
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!";
723 return FALSE;
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!";
730 return FALSE;
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;
743 if (
744 m_csAddrFrom.GetLength() <= 0 || m_csAddrTo.GetLength() <= 0
747 m_csLastError = L"Parameter Error!";
748 return FALSE;
751 if ( pStrAryAttach )
753 m_StrAryAttach.Append ( *pStrAryAttach );
755 if (m_StrAryAttach.IsEmpty())
756 m_csMIMEContentType = L"text/plain\r\nContent-Transfer-Encoding: 8bit";
758 // ´´½¨Socket
759 m_SendSock.Close();
760 if ( !m_SendSock.Create () )
762 //int nResult = GetLastError();
763 m_csLastError = L"Create socket failed!";
764 return FALSE;
767 switch (secLevel)
769 case 1:
770 m_iSecurityLevel = want_tls;
771 break;
772 case 2:
773 m_iSecurityLevel = ssl;
774 break;
775 default:
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));
782 return FALSE;
785 if (m_iSecurityLevel <= want_tls)
787 if (!GetResponse("220"))
788 return FALSE;
789 m_bConnected = TRUE;
790 Send(L"STARTTLS\r\n");
791 if (GetResponse("220"))
792 m_iSecurityLevel = tls_established;
793 else if (m_iSecurityLevel == want_tls)
794 return FALSE;
797 BOOL ret = FALSE;
799 SecBuffer ExtraData;
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));
819 return FALSE;
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));
827 return FALSE;
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));
833 if (Status)
835 m_csLastError = static_cast<LPCWSTR>(CFormatMessageWrapper(Status));
836 goto cleanup;
839 git_cert_x509 cert;
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.";
847 goto cleanup;
850 CertFreeCertificateContext(pRemoteCertContext);
852 Status = g_pSSPI->QueryContextAttributes(hContext, SECPKG_ATTR_STREAM_SIZES, &Sizes);
853 if (Status)
855 m_csLastError = static_cast<LPCWSTR>(CFormatMessageWrapper(Status));
856 goto cleanup;
859 // Create a buffer.
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";
865 goto cleanup;
867 SecureZeroMemory(pbIoBuffer, cbIoBufferLength);
870 if (m_iSecurityLevel <= ssl)
872 if (!GetResponse("220"))
873 goto cleanup;
874 m_bConnected = TRUE;
877 ret = SendEmail();
879 cleanup:
880 if (m_iSecurityLevel >= ssl)
882 if (hContext && hCreds)
883 DisconnectFromServer(&m_SendSock, hCreds, hContext);
884 if (pbIoBuffer)
886 SecureZeroMemory(pbIoBuffer, cbIoBufferLength);
887 LocalFree(pbIoBuffer);
888 pbIoBuffer = nullptr;
889 cbIoBufferLength = 0;
891 if (hContext)
893 g_pSSPI->DeleteSecurityContext(hContext);
894 hContext = nullptr;
896 if (hCreds)
898 g_pSSPI->FreeCredentialsHandle(hCreds);
899 hCreds = nullptr;
901 g_pSSPI = nullptr;
903 else
904 m_SendSock.Close();
906 return ret;
909 BOOL CHwSMTP::GetResponse(LPCSTR lpszVerifyCode)
911 if (!lpszVerifyCode || strlen(lpszVerifyCode) < 1)
912 return FALSE;
914 SECURITY_STATUS scRet = SEC_E_OK;
916 char szRecvBuf[1024] = {0};
917 int nRet = 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);
926 else
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";
932 return FALSE;
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)));
938 return FALSE;
941 return TRUE;
943 BOOL CHwSMTP::SendBuffer(const char* buff, int size)
945 if(size<0)
946 size = static_cast<int>(strlen(buff));
947 if ( !m_bConnected )
949 m_csLastError = L"Didn't connect";
950 return FALSE;
953 if (m_iSecurityLevel >= ssl)
955 int sent = 0;
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)
963 return FALSE;
964 sent += toSend;
967 else if (m_SendSock.Send ( buff, size ) != size)
969 m_csLastError = L"Socket send data failed";
970 return FALSE;
973 return TRUE;
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()
989 CStringA hostname;
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";
996 CStringA str;
997 str.Format("HELO %s\r\n", static_cast<LPCSTR>(hostname));
998 if (!Send(str))
999 return FALSE;
1000 if (!GetResponse("250"))
1001 return FALSE;
1003 if ( m_bMustAuth && !auth() )
1004 return FALSE;
1006 if ( !SendHead() )
1007 return FALSE;
1009 if (!SendSubject(CUnicodeUtils::GetUnicode(hostname)))
1010 return FALSE;
1012 if ( !SendBody() )
1013 return FALSE;
1015 if ( !SendAttach() )
1017 return FALSE;
1020 if (!Send(".\r\n"))
1021 return FALSE;
1022 if (!GetResponse("250"))
1023 return FALSE;
1025 if ( HANDLE_IS_VALID(m_SendSock.m_hSocket) )
1026 Send("QUIT\r\n");
1027 m_bConnected = FALSE;
1029 return TRUE;
1032 static CStringA EncodeBase64(const char* source, int len, bool noWrap)
1034 int neededLength = Base64EncodeGetRequiredLength(len);
1035 CStringA output;
1036 if (Base64Encode(reinterpret_cast<const BYTE*>(source), len, CStrBufA(output, neededLength), &neededLength, noWrap ? ATL_BASE64_FLAG_NOCRLF : 0))
1037 output.Truncate(neededLength);
1038 return output;
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))
1050 return text;
1052 return L"=?UTF-8?B?" + CUnicodeUtils::GetUnicode(EncodeBase64(text)) + L"?=";
1055 BOOL CHwSMTP::auth()
1057 if (!Send("auth login\r\n"))
1058 return FALSE;
1059 if (!GetResponse("334"))
1060 return FALSE;
1062 if (!Send(EncodeBase64(m_credentials->m_username) + "\r\n"))
1063 return FALSE;
1065 if (!GetResponse("334"))
1067 m_csLastError = L"Authentication UserName failed";
1068 return FALSE;
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);
1077 buf[ret] = '\0';
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);
1089 if (!successful)
1090 return FALSE;
1092 if (!Send("\r\n"))
1093 return FALSE;
1095 if (!GetResponse("235"))
1097 m_csLastError = L"Authentication Password failed";
1098 return FALSE;
1101 return TRUE;
1104 BOOL CHwSMTP::SendHead()
1106 CString str;
1107 CString addr;
1108 CStringUtils::ParseEmailAddress(m_csAddrFrom, addr);
1110 str.Format(L"MAIL From: <%s>\r\n", static_cast<LPCWSTR>(addr));
1111 if (!Send(str))
1112 return FALSE;
1114 if (!GetResponse("250"))
1115 return FALSE;
1117 int start=0;
1118 while(start>=0)
1120 CString one = m_csAddrTo.Tokenize(L";", start).Trim();
1121 if(one.IsEmpty())
1122 continue;
1124 CStringUtils::ParseEmailAddress(one, addr);
1126 str.Format(L"RCPT TO: <%s>\r\n", static_cast<LPCWSTR>(addr));
1127 if (!Send(str))
1128 return FALSE;
1129 if (!GetResponse("250"))
1130 return FALSE;
1133 if (!Send("DATA\r\n"))
1134 return FALSE;
1135 if (!GetResponse("354"))
1136 return FALSE;
1138 return TRUE;
1141 BOOL CHwSMTP::SendSubject(const CString &hostname)
1143 CString csSubject;
1144 csSubject += L"Date: ";
1145 COleDateTime tNow = COleDateTime::GetCurrentTime();
1146 if ( tNow > 1 )
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";
1170 return FALSE;
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()
1180 CString csBody;
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");
1192 csBody += m_csBody;
1193 csBody += L"\r\n";
1195 return Send(csBody);
1198 BOOL CHwSMTP::SendAttach()
1200 if (m_StrAryAttach.IsEmpty())
1201 return TRUE;
1203 int nCountAttach = static_cast<int>(m_StrAryAttach.GetSize());
1204 for (int i = 0; i < nCountAttach; ++i)
1206 if ( !SendOnAttach ( m_StrAryAttach.GetAt(i) ) )
1207 return FALSE;
1210 Send(L"--" + m_csPartBoundary + L"--\r\n");
1212 return TRUE;
1215 BOOL CHwSMTP::SendOnAttach(LPCWSTR lpszFileName)
1217 ASSERT ( lpszFileName );
1218 CString csAttach;
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);
1232 return FALSE;
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))
1240 return FALSE;
1242 if (!SendBuffer(filedata))
1243 return FALSE;
1245 catch (CFileException *e)
1247 e->GetErrorMessage(CStrBuf(m_csLastError, 1024), 1024);
1248 e->Delete();
1249 return FALSE;
1252 return TRUE;
1255 CString CHwSMTP::GetLastErrorText()
1257 return m_csLastError;