Drop unused charset
[TortoiseGit.git] / src / Utils / HwSMTP.cpp
blob46f97a75717e1a2178bd0a644a6e5489b8e48668
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 "SpeedPostEmail.h"
11 #include "Windns.h"
12 #include <Afxmt.h>
13 #include "FormatMessageWrapper.h"
14 #include <atlenc.h>
15 #include "AppUtils.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()
37 CString sGuid;
38 GUID guid;
39 if (CoCreateGuid(&guid) == S_OK)
41 RPC_WSTR guidStr;
42 if (UuidToString(&guid, &guidStr) == RPC_S_OK)
44 sGuid = (LPTSTR)guidStr;
45 RpcStringFree(&guidStr);
48 return sGuid;
51 CHwSMTP::CHwSMTP () :
52 m_bConnected ( FALSE ),
53 m_nSmtpSrvPort ( 25 ),
54 m_bMustAuth ( TRUE )
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.";
60 hContext = nullptr;
61 hCreds = nullptr;
62 pbIoBuffer = nullptr;
63 cbIoBufferLength = 0;
65 m_iSecurityLevel = none;
67 SecureZeroMemory(&Sizes, sizeof(SecPkgContext_StreamSizes));
69 AfxSocketInit();
72 CHwSMTP::~CHwSMTP()
76 CString CHwSMTP::GetServerAddress(const CString& in)
78 CString email;
79 CStringUtils::ParseEmailAddress(in, email);
81 int start = email.Find(L'@');
82 return email.Mid(start + 1);
85 BOOL CHwSMTP::SendSpeedEmail
87 LPCTSTR lpszAddrFrom,
88 LPCTSTR lpszAddrTo,
89 LPCTSTR lpszSubject,
90 LPCTSTR lpszBody,
91 CStringArray *pStrAryAttach,
92 LPCTSTR pStrAryCC,
93 LPCTSTR pSend
96 BOOL ret=true;
97 CString To;
98 To += GET_SAFE_STRING(lpszAddrTo);
99 To += L';';
100 To += GET_SAFE_STRING(pStrAryCC);
102 std::map<CString,std::vector<CString>> Address;
104 int start = 0;
105 while( start >= 0 )
107 CString one = To.Tokenize(L";", start).Trim();
108 if(one.IsEmpty())
109 continue;
111 CString addr = GetServerAddress(one);
112 if(addr.IsEmpty())
113 continue;
115 Address[addr].push_back(one);
118 for (const auto& maildomain : Address)
120 PDNS_RECORD pDnsRecord;
121 DNS_STATUS status =
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.
126 nullptr
128 if (status)
130 m_csLastError.Format(L"DNS query failed %d", status);
131 ret = false;
132 continue;
134 SCOPE_EXIT { DnsRecordListFree(pDnsRecord, DnsFreeRecordList); };
136 CString to;
137 std::for_each(maildomain.second.cbegin(), maildomain.second.cend(), [&to](auto recipient) {
138 to += recipient;
139 to += L';';
141 if(to.IsEmpty())
142 continue;
144 PDNS_RECORD pNext = pDnsRecord;
145 while(pNext)
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))
151 break;
152 pNext=pNext->pNext;
154 if (!pNext)
155 ret = false;
158 return ret;
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;
166 TimeStamp tsExpiry;
167 SECURITY_STATUS scRet;
168 BOOL fDoRead;
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);
175 if (!IoBuffer)
177 // printf("**** Out of memory (1)\n");
178 return SEC_E_INTERNAL_ERROR;
180 cbIoBuffer = 0;
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.
190 if (fDoRead)
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;
197 break;
199 else if (cbData == 0)
201 // printf("**** Server unexpectedly disconnected\n");
202 scRet = SEC_E_INTERNAL_ERROR;
203 break;
205 // printf("%d bytes of handshake data received\n", cbData);
206 cbIoBuffer += cbData;
208 else
209 fDoRead = TRUE;
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
230 // garbage later.
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 );
294 else
296 pExtraData->pvBuffer = nullptr;
297 pExtraData->cbBuffer = 0;
298 pExtraData->BufferType = SECBUFFER_EMPTY;
300 break; // Bail out to quit
303 // Check for fatal error.
304 if (FAILED(scRet))
306 // printf("**** Error 0x%x returned by InitializeSecurityContext (2)\n", scRet);
307 break;
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);
324 // Go around again.
325 fDoRead = FALSE;
326 scRet = SEC_I_CONTINUE_NEEDED;
327 continue;
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;
336 else
337 cbIoBuffer = 0;
340 // Delete the security context in the case of a fatal error.
341 if (FAILED(scRet))
342 g_pSSPI->DeleteSecurityContext(phContext);
344 return scRet;
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;
352 TimeStamp tsExpiry;
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);
372 return 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)
397 TimeStamp tsExpiry;
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;
410 if (aiKeyExch)
411 rgbSupportedAlgs[cSupportedAlgs++] = aiKeyExch;
413 if (cSupportedAlgs)
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)
441 return Status;
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];
451 DWORD cbMessage;
452 PBYTE pbMessage;
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.
479 if (FAILED(scRet))
481 // printf("**** Error 0x%x returned by EncryptMessage\n", scRet);
482 return 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)
492 PBYTE pbMessage;
493 DWORD dwType, dwSSPIFlags, dwSSPIOutFlags, cbMessage, cbData, Status;
494 SecBufferDesc OutBuffer;
495 SecBuffer OutBuffers[1];
496 TimeStamp tsExpiry;
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);
509 if (FAILED(Status))
511 // printf("**** Error 0x%x returned by ApplyControlToken\n", Status);
512 goto cleanup;
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);
528 if (FAILED(Status))
530 // printf("**** Error 0x%x returned by InitializeSecurityContext\n", Status);
531 goto cleanup;
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();
544 goto cleanup;
546 // printf("Sending Close Notify\n");
547 // printf("%d bytes of handshake data sent\n", cbData);
548 g_pSSPI->FreeContextBuffer(pbMessage); // Free output buffer.
551 cleanup:
552 g_pSSPI->DeleteSecurityContext(phContext); // Free the security context.
553 Socket->Close();
555 return Status;
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;
575 PBYTE buff;
577 // Read data from server until done.
578 cbIoBuffer = 0;
579 scRet = 0;
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;
589 break;
591 else if (cbData == 0) // Server disconnected.
593 if (cbIoBuffer)
595 // printf("**** Server unexpectedly disconnected\n");
596 scRet = SEC_E_INTERNAL_ERROR;
597 return scRet;
599 else
600 break; // All Done
602 else // success
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)
626 return scRet;
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.
640 if (pDataBuffer)
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
648 buff[length] = 0;
649 break;
654 // Move any "extra" data to the input buffer.
655 if (pExtraBuffer)
657 MoveMemory(pbIoBuffer, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer);
658 cbIoBuffer = pExtraBuffer->cbBuffer;
660 else
661 cbIoBuffer = 0;
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)
669 return scRet;
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
679 return SEC_E_OK;
682 BOOL CHwSMTP::SendEmail (
683 LPCTSTR lpszSmtpSrvHost,
684 LPCTSTR lpszUserName,
685 LPCTSTR lpszPasswd,
686 BOOL bMustAuth,
687 LPCTSTR lpszAddrFrom,
688 LPCTSTR lpszAddrTo,
689 LPCTSTR lpszSubject,
690 LPCTSTR lpszBody,
691 CStringArray* pStrAryAttach/*=nullptr*/,
692 LPCTSTR pStrAryCC/*=nullptr*/,
693 UINT nSmtpSrvPort,/*=25*/
694 LPCTSTR pSender,
695 LPCTSTR pToList,
696 DWORD secLevel
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!";
707 return FALSE;
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!";
715 return FALSE;
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;
730 if (
731 m_csAddrFrom.GetLength() <= 0 || m_csAddrTo.GetLength() <= 0
734 m_csLastError = L"Parameter Error!";
735 return FALSE;
738 if ( pStrAryAttach )
740 m_StrAryAttach.Append ( *pStrAryAttach );
742 if ( m_StrAryAttach.GetSize() < 1 )
743 m_csMIMEContentType.Format(L"text/plain");
745 // ´´½¨Socket
746 m_SendSock.Close();
747 if ( !m_SendSock.Create () )
749 //int nResult = GetLastError();
750 m_csLastError = L"Create socket failed!";
751 return FALSE;
754 switch (secLevel)
756 case 1:
757 m_iSecurityLevel = want_tls;
758 break;
759 case 2:
760 m_iSecurityLevel = ssl;
761 break;
762 default:
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);
769 return FALSE;
772 if (m_iSecurityLevel == want_tls) {
773 if (!GetResponse("220"))
774 return FALSE;
775 m_bConnected = TRUE;
776 Send(L"STARTTLS\r\n");
777 if (!GetResponse("220"))
778 return FALSE;
779 m_iSecurityLevel = tls_established;
782 BOOL ret = FALSE;
784 SecBuffer ExtraData;
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);
804 return FALSE;
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);
812 return FALSE;
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);
818 if (Status)
820 m_csLastError = CFormatMessageWrapper(Status);
821 goto cleanup;
824 git_cert_x509 cert;
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.";
832 goto cleanup;
835 CertFreeCertificateContext(pRemoteCertContext);
837 Status = g_pSSPI->QueryContextAttributes(hContext, SECPKG_ATTR_STREAM_SIZES, &Sizes);
838 if (Status)
840 m_csLastError = CFormatMessageWrapper(Status);
841 goto cleanup;
844 // Create a buffer.
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";
850 goto cleanup;
852 SecureZeroMemory(pbIoBuffer, cbIoBufferLength);
855 if (m_iSecurityLevel <= ssl)
857 if (!GetResponse("220"))
858 goto cleanup;
859 m_bConnected = TRUE;
862 ret = SendEmail();
864 cleanup:
865 if (m_iSecurityLevel >= ssl)
867 if (hContext && hCreds)
868 DisconnectFromServer(&m_SendSock, hCreds, hContext);
869 if (pbIoBuffer)
871 LocalFree(pbIoBuffer);
872 pbIoBuffer = nullptr;
873 cbIoBufferLength = 0;
875 if (hContext)
877 g_pSSPI->DeleteSecurityContext(hContext);
878 hContext = nullptr;
880 if (hCreds)
882 g_pSSPI->FreeCredentialsHandle(hCreds);
883 hCreds = nullptr;
885 g_pSSPI = nullptr;
887 else
888 m_SendSock.Close();
890 return ret;
893 BOOL CHwSMTP::GetResponse(LPCSTR lpszVerifyCode)
895 if (!lpszVerifyCode || strlen(lpszVerifyCode) < 1)
896 return FALSE;
898 SECURITY_STATUS scRet = SEC_E_OK;
900 char szRecvBuf[1024] = {0};
901 int nRet = 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);
910 else
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";
916 return FALSE;
918 memcpy ( szStatusCode, szRecvBuf, 3 );
919 if (strcmp(szStatusCode, lpszVerifyCode) != 0)
921 m_csLastError.Format(L"Received invalid response: %s", (LPCTSTR)CUnicodeUtils::GetUnicode(szRecvBuf));
922 return FALSE;
925 return TRUE;
927 BOOL CHwSMTP::SendBuffer(const char* buff, int size)
929 if(size<0)
930 size=(int)strlen(buff);
931 if ( !m_bConnected )
933 m_csLastError = L"Didn't connect";
934 return FALSE;
937 if (m_iSecurityLevel >= ssl)
939 int sent = 0;
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)
947 return FALSE;
948 sent += toSend;
951 else if (m_SendSock.Send ( buff, size ) != size)
953 m_csLastError = L"Socket send data failed";
954 return FALSE;
957 return TRUE;
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()
973 CStringA hostname;
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";
980 CStringA str;
981 str.Format("HELO %s\r\n", (LPCSTR)hostname);
982 if (!Send(str))
983 return FALSE;
984 if (!GetResponse("250"))
985 return FALSE;
987 if ( m_bMustAuth && !auth() )
988 return FALSE;
990 if ( !SendHead() )
991 return FALSE;
993 if (!SendSubject(CUnicodeUtils::GetUnicode(hostname)))
994 return FALSE;
996 if ( !SendBody() )
997 return FALSE;
999 if ( !SendAttach() )
1001 return FALSE;
1004 if (!Send(".\r\n"))
1005 return FALSE;
1006 if (!GetResponse("250"))
1007 return FALSE;
1009 if ( HANDLE_IS_VALID(m_SendSock.m_hSocket) )
1010 Send("QUIT\r\n");
1011 m_bConnected = FALSE;
1013 return TRUE;
1016 static CStringA EncodeBase64(const char* source, int len)
1018 int neededLength = Base64EncodeGetRequiredLength(len);
1019 CStringA output;
1020 if (Base64Encode((BYTE*)source, len, CStrBufA(output, neededLength), &neededLength, ATL_BASE64_FLAG_NOCRLF))
1021 output.Truncate(neededLength);
1022 return output;
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))
1034 return text;
1036 return L"=?UTF-8?B?" + CUnicodeUtils::GetUnicode(EncodeBase64(text)) + L"?=";
1039 BOOL CHwSMTP::auth()
1041 if (!Send("auth login\r\n"))
1042 return FALSE;
1043 if (!GetResponse("334"))
1044 return FALSE;
1046 if (!Send(EncodeBase64(m_csUserName) + "\r\n"))
1047 return FALSE;
1049 if (!GetResponse("334"))
1051 m_csLastError = L"Authentication UserName failed";
1052 return FALSE;
1055 if (!Send(EncodeBase64(m_csPasswd) + "\r\n"))
1056 return FALSE;
1058 if (!GetResponse("235"))
1060 m_csLastError = L"Authentication Password failed";
1061 return FALSE;
1064 return TRUE;
1067 BOOL CHwSMTP::SendHead()
1069 CString str;
1070 CString addr;
1071 CStringUtils::ParseEmailAddress(m_csAddrFrom, addr);
1073 str.Format(L"MAIL From: <%s>\r\n", (LPCTSTR)addr);
1074 if (!Send(str))
1075 return FALSE;
1077 if (!GetResponse("250"))
1078 return FALSE;
1080 int start=0;
1081 while(start>=0)
1083 CString one = m_csAddrTo.Tokenize(L";", start).Trim();
1084 if(one.IsEmpty())
1085 continue;
1087 CStringUtils::ParseEmailAddress(one, addr);
1089 str.Format(L"RCPT TO: <%s>\r\n", (LPCTSTR)addr);
1090 if (!Send(str))
1091 return FALSE;
1092 if (!GetResponse("250"))
1093 return FALSE;
1096 if (!Send("DATA\r\n"))
1097 return FALSE;
1098 if (!GetResponse("354"))
1099 return FALSE;
1101 return TRUE;
1104 BOOL CHwSMTP::SendSubject(const CString &hostname)
1106 CString csSubject;
1107 csSubject += L"Date: ";
1108 COleDateTime tNow = COleDateTime::GetCurrentTime();
1109 if ( tNow > 1 )
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";
1135 return FALSE;
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()
1145 CString csBody;
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");
1157 csBody += m_csBody;
1158 csBody += L"\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) ) )
1171 return FALSE;
1174 Send(L"--" + m_csPartBoundary + L"--\r\n");
1176 return TRUE;
1179 BOOL CHwSMTP::SendOnAttach(LPCTSTR lpszFileName)
1181 ASSERT ( lpszFileName );
1182 CString csAttach;
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));
1195 return FALSE;
1197 auto pBuf = std::make_unique<char[]>(dwFileSize + 1);
1198 if (!pBuf)
1199 ::AfxThrowMemoryException();
1201 if (!Send(csAttach))
1202 return FALSE;
1204 CFile file;
1205 CStringA filedata;
1208 if ( !file.Open ( lpszFileName, CFile::modeRead ) )
1210 m_csLastError.Format(L"Open file [%s] failed", lpszFileName);
1211 return FALSE;
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)
1219 e->Delete();
1220 m_csLastError.Format(L"Read file [%s] failed", lpszFileName);
1221 return FALSE;
1224 if (!SendBuffer(filedata))
1225 return FALSE;
1227 return TRUE;
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 )
1240 return L"";
1242 UDATE ud;
1243 if (S_OK != VarUdateFromDate(DateTime.m_dt, 0, &ud))
1245 return L"";
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);
1254 CString strDate;
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)
1262 return strDate;
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) );
1273 BOOL bRet = FALSE;
1276 if ( CFile::GetStatus(lpFileName,fileStatus) )
1278 bRet = TRUE;
1281 CATCH (CFileException, e)
1283 ASSERT ( FALSE );
1284 bRet = FALSE;
1286 CATCH_ALL(e)
1288 ASSERT ( FALSE );
1289 bRet = FALSE;
1291 END_CATCH_ALL;
1293 if ( pFileStatus )
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*/ )
1309 CString csRes;
1310 if ( 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"");
1318 else
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"");
1328 return csRes;