Fixed issue #2785: Rebase/Cherry-pick with conflict in renamed file fails
[TortoiseGit.git] / src / Utils / HwSMTP.cpp
bloba05746653d75711d05701a2acdceab86ec892e0a
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, "Secur32.lib")
23 DWORD dwProtocol = SP_PROT_TLS1; // SP_PROT_TLS1; // SP_PROT_PCT1; SP_PROT_SSL2; SP_PROT_SSL3; 0=default
24 ALG_ID aiKeyExch = 0; // = default; CALG_DH_EPHEM; CALG_RSA_KEYX;
26 SCHANNEL_CRED SchannelCred;
27 PSecurityFunctionTable g_pSSPI;
29 //////////////////////////////////////////////////////////////////////
30 // Construction/Destruction
31 //////////////////////////////////////////////////////////////////////
33 static CString GetGUID()
35 CString sGuid;
36 GUID guid;
37 if (CoCreateGuid(&guid) == S_OK)
39 RPC_WSTR guidStr;
40 if (UuidToString(&guid, &guidStr) == RPC_S_OK)
42 sGuid = (LPTSTR)guidStr;
43 RpcStringFree(&guidStr);
46 return sGuid;
49 CHwSMTP::CHwSMTP () :
50 m_bConnected ( FALSE ),
51 m_nSmtpSrvPort ( 25 ),
52 m_bMustAuth ( TRUE )
54 m_csPartBoundary = L"NextPart_" + GetGUID();
55 m_csMIMEContentType.Format(_T("multipart/mixed; boundary=%s"), (LPCTSTR)m_csPartBoundary);
56 m_csNoMIMEText = _T( "This is a multi-part message in MIME format." );
57 //m_csCharSet = _T("\r\n\tcharset=\"iso-8859-1\"\r\n");
59 hContext = nullptr;
60 hCreds = nullptr;
61 pbIoBuffer = nullptr;
62 cbIoBufferLength = 0;
64 m_iSecurityLevel = none;
66 SecureZeroMemory(&Sizes, sizeof(SecPkgContext_StreamSizes));
68 AfxSocketInit();
71 CHwSMTP::~CHwSMTP()
75 CString CHwSMTP::GetServerAddress(const CString& in)
77 CString email;
78 CStringUtils::ParseEmailAddress(in, email);
80 int start = email.Find(L'@');
81 return email.Mid(start + 1);
84 BOOL CHwSMTP::SendSpeedEmail
86 LPCTSTR lpszAddrFrom,
87 LPCTSTR lpszAddrTo,
88 LPCTSTR lpszSubject,
89 LPCTSTR lpszBody,
90 LPCTSTR lpszCharSet,
91 CStringArray *pStrAryAttach,
92 LPCTSTR pStrAryCC,
93 LPCTSTR pSend
96 BOOL ret=true;
97 CString To;
98 To += GET_SAFE_STRING(lpszAddrTo);
99 To += _T(";");
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(_T(";"),start);
108 one=one.Trim();
109 if(one.IsEmpty())
110 continue;
112 CString addr;
113 addr = GetServerAddress(one);
114 if(addr.IsEmpty())
115 continue;
117 Address[addr].push_back(one);
121 std::map<CString,std::vector<CString>>::iterator itr1 = Address.begin();
122 for( ; itr1 != Address.end(); ++itr1 )
124 PDNS_RECORD pDnsRecord;
125 PDNS_RECORD pNext;
127 DNS_STATUS status =
128 DnsQuery(itr1->first ,
129 DNS_TYPE_MX,DNS_QUERY_STANDARD,
130 nullptr, //Contains DNS server IP address.
131 &pDnsRecord, //Resource record that contains the response.
132 nullptr
134 if (status)
136 m_csLastError.Format(_T("DNS query failed %d"), status);
137 ret = false;
138 continue;
141 CString to;
142 to.Empty();
143 for (size_t i = 0; i < itr1->second.size(); ++i)
145 to+=itr1->second[i];
146 to+=_T(";");
148 if(to.IsEmpty())
149 continue;
151 pNext=pDnsRecord;
152 while(pNext)
154 if(pNext->wType == DNS_TYPE_MX)
155 if (SendEmail(pNext->Data.MX.pNameExchange, nullptr, nullptr, false,
156 lpszAddrFrom,to,lpszSubject,lpszBody,lpszCharSet,pStrAryAttach,pStrAryCC,
157 25,pSend,lpszAddrTo))
158 break;
159 pNext=pNext->pNext;
161 if (!pNext)
162 ret = false;
164 if (pDnsRecord)
165 DnsRecordListFree(pDnsRecord,DnsFreeRecordList);
168 return ret;
171 static SECURITY_STATUS ClientHandshakeLoop(CSocket * Socket, PCredHandle phCreds, CtxtHandle * phContext, BOOL fDoInitialRead, SecBuffer * pExtraData)
173 SecBufferDesc OutBuffer, InBuffer;
174 SecBuffer InBuffers[2], OutBuffers[1];
175 DWORD dwSSPIFlags, dwSSPIOutFlags, cbData, cbIoBuffer;
176 TimeStamp tsExpiry;
177 SECURITY_STATUS scRet;
178 BOOL fDoRead;
180 dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY |
181 ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM;
183 // Allocate data buffer.
184 auto IoBuffer = std::make_unique<UCHAR[]>(IO_BUFFER_SIZE);
185 if (!IoBuffer)
187 // printf("**** Out of memory (1)\n");
188 return SEC_E_INTERNAL_ERROR;
190 cbIoBuffer = 0;
191 fDoRead = fDoInitialRead;
193 // Loop until the handshake is finished or an error occurs.
194 scRet = SEC_I_CONTINUE_NEEDED;
196 while (scRet == SEC_I_CONTINUE_NEEDED || scRet == SEC_E_INCOMPLETE_MESSAGE || scRet == SEC_I_INCOMPLETE_CREDENTIALS)
198 if (0 == cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE) // Read data from server.
200 if (fDoRead)
202 cbData = Socket->Receive(IoBuffer.get() + cbIoBuffer, IO_BUFFER_SIZE - cbIoBuffer, 0);
203 if (cbData == SOCKET_ERROR)
205 // printf("**** Error %d reading data from server\n", WSAGetLastError());
206 scRet = SEC_E_INTERNAL_ERROR;
207 break;
209 else if (cbData == 0)
211 // printf("**** Server unexpectedly disconnected\n");
212 scRet = SEC_E_INTERNAL_ERROR;
213 break;
215 // printf("%d bytes of handshake data received\n", cbData);
216 cbIoBuffer += cbData;
218 else
219 fDoRead = TRUE;
222 // Set up the input buffers. Buffer 0 is used to pass in data
223 // received from the server. Schannel will consume some or all
224 // of this. Leftover data (if any) will be placed in buffer 1 and
225 // given a buffer type of SECBUFFER_EXTRA.
226 InBuffers[0].pvBuffer = IoBuffer.get();
227 InBuffers[0].cbBuffer = cbIoBuffer;
228 InBuffers[0].BufferType = SECBUFFER_TOKEN;
230 InBuffers[1].pvBuffer = nullptr;
231 InBuffers[1].cbBuffer = 0;
232 InBuffers[1].BufferType = SECBUFFER_EMPTY;
234 InBuffer.cBuffers = 2;
235 InBuffer.pBuffers = InBuffers;
236 InBuffer.ulVersion = SECBUFFER_VERSION;
238 // Set up the output buffers. These are initialized to nullptr
239 // so as to make it less likely we'll attempt to free random
240 // garbage later.
241 OutBuffers[0].pvBuffer = nullptr;
242 OutBuffers[0].BufferType= SECBUFFER_TOKEN;
243 OutBuffers[0].cbBuffer = 0;
245 OutBuffer.cBuffers = 1;
246 OutBuffer.pBuffers = OutBuffers;
247 OutBuffer.ulVersion = SECBUFFER_VERSION;
249 // Call InitializeSecurityContext.
250 scRet = g_pSSPI->InitializeSecurityContext(phCreds, phContext, nullptr, dwSSPIFlags, 0, SECURITY_NATIVE_DREP, &InBuffer, 0, nullptr, &OutBuffer, &dwSSPIOutFlags, &tsExpiry);
252 // If InitializeSecurityContext was successful (or if the error was
253 // one of the special extended ones), send the contends of the output
254 // buffer to the server.
255 if (scRet == SEC_E_OK || scRet == SEC_I_CONTINUE_NEEDED || FAILED(scRet) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR))
257 if (OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != nullptr)
259 cbData = Socket->Send(OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0 );
260 if(cbData == SOCKET_ERROR || cbData == 0)
262 // printf( "**** Error %d sending data to server (2)\n", WSAGetLastError() );
263 g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer);
264 g_pSSPI->DeleteSecurityContext(phContext);
265 return SEC_E_INTERNAL_ERROR;
267 // printf("%d bytes of handshake data sent\n", cbData);
269 // Free output buffer.
270 g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer);
271 OutBuffers[0].pvBuffer = nullptr;
275 // If InitializeSecurityContext returned SEC_E_INCOMPLETE_MESSAGE,
276 // then we need to read more data from the server and try again.
277 if (scRet == SEC_E_INCOMPLETE_MESSAGE) continue;
279 // If InitializeSecurityContext returned SEC_E_OK, then the
280 // handshake completed successfully.
281 if (scRet == SEC_E_OK)
283 // If the "extra" buffer contains data, this is encrypted application
284 // protocol layer stuff. It needs to be saved. The application layer
285 // will later decrypt it with DecryptMessage.
286 // printf("Handshake was successful\n");
288 if (InBuffers[1].BufferType == SECBUFFER_EXTRA)
290 pExtraData->pvBuffer = LocalAlloc( LMEM_FIXED, InBuffers[1].cbBuffer );
291 if (pExtraData->pvBuffer == nullptr)
293 // printf("**** Out of memory (2)\n");
294 return SEC_E_INTERNAL_ERROR;
297 MoveMemory(pExtraData->pvBuffer, IoBuffer.get() + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer);
299 pExtraData->cbBuffer = InBuffers[1].cbBuffer;
300 pExtraData->BufferType = SECBUFFER_TOKEN;
302 // printf( "%d bytes of app data was bundled with handshake data\n", pExtraData->cbBuffer );
304 else
306 pExtraData->pvBuffer = nullptr;
307 pExtraData->cbBuffer = 0;
308 pExtraData->BufferType = SECBUFFER_EMPTY;
310 break; // Bail out to quit
313 // Check for fatal error.
314 if (FAILED(scRet))
316 // printf("**** Error 0x%x returned by InitializeSecurityContext (2)\n", scRet);
317 break;
320 // If InitializeSecurityContext returned SEC_I_INCOMPLETE_CREDENTIALS,
321 // then the server just requested client authentication.
322 if (scRet == SEC_I_INCOMPLETE_CREDENTIALS)
324 // Busted. The server has requested client authentication and
325 // the credential we supplied didn't contain a client certificate.
326 // This function will read the list of trusted certificate
327 // authorities ("issuers") that was received from the server
328 // and attempt to find a suitable client certificate that
329 // was issued by one of these. If this function is successful,
330 // then we will connect using the new certificate. Otherwise,
331 // we will attempt to connect anonymously (using our current credentials).
332 //GetNewClientCredentials(phCreds, phContext);
334 // Go around again.
335 fDoRead = FALSE;
336 scRet = SEC_I_CONTINUE_NEEDED;
337 continue;
340 // Copy any leftover data from the "extra" buffer, and go around again.
341 if ( InBuffers[1].BufferType == SECBUFFER_EXTRA )
343 MoveMemory(IoBuffer.get(), IoBuffer.get() + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer);
344 cbIoBuffer = InBuffers[1].cbBuffer;
346 else
347 cbIoBuffer = 0;
350 // Delete the security context in the case of a fatal error.
351 if (FAILED(scRet))
352 g_pSSPI->DeleteSecurityContext(phContext);
354 return scRet;
357 static SECURITY_STATUS PerformClientHandshake( CSocket * Socket, PCredHandle phCreds, LPTSTR pszServerName, CtxtHandle * phContext, SecBuffer * pExtraData)
359 SecBufferDesc OutBuffer;
360 SecBuffer OutBuffers[1];
361 DWORD dwSSPIFlags, dwSSPIOutFlags, cbData;
362 TimeStamp tsExpiry;
363 SECURITY_STATUS scRet;
365 dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY |
366 ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM;
368 // Initiate a ClientHello message and generate a token.
369 OutBuffers[0].pvBuffer = nullptr;
370 OutBuffers[0].BufferType = SECBUFFER_TOKEN;
371 OutBuffers[0].cbBuffer = 0;
373 OutBuffer.cBuffers = 1;
374 OutBuffer.pBuffers = OutBuffers;
375 OutBuffer.ulVersion = SECBUFFER_VERSION;
377 scRet = g_pSSPI->InitializeSecurityContext(phCreds, nullptr, pszServerName, dwSSPIFlags, 0, SECURITY_NATIVE_DREP, nullptr, 0, phContext, &OutBuffer, &dwSSPIOutFlags, &tsExpiry);
379 if (scRet != SEC_I_CONTINUE_NEEDED)
381 // printf("**** Error %d returned by InitializeSecurityContext (1)\n", scRet);
382 return scRet;
385 // Send response to server if there is one.
386 if (OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != nullptr)
388 cbData = Socket->Send(OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0);
389 if (cbData == SOCKET_ERROR || cbData == 0)
391 // printf("**** Error %d sending data to server (1)\n", WSAGetLastError());
392 g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer);
393 g_pSSPI->DeleteSecurityContext(phContext);
394 return SEC_E_INTERNAL_ERROR;
396 // printf("%d bytes of handshake data sent\n", cbData);
398 g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); // Free output buffer.
399 OutBuffers[0].pvBuffer = nullptr;
402 return ClientHandshakeLoop(Socket, phCreds, phContext, TRUE, pExtraData);
405 static SECURITY_STATUS CreateCredentials(PCredHandle phCreds)
407 TimeStamp tsExpiry;
408 SECURITY_STATUS Status;
409 DWORD cSupportedAlgs = 0;
410 ALG_ID rgbSupportedAlgs[16];
412 // Build Schannel credential structure. Currently, this sample only
413 // specifies the protocol to be used (and optionally the certificate,
414 // of course). Real applications may wish to specify other parameters as well.
415 SecureZeroMemory(&SchannelCred, sizeof(SchannelCred));
417 SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;
418 SchannelCred.grbitEnabledProtocols = dwProtocol;
420 if (aiKeyExch)
421 rgbSupportedAlgs[cSupportedAlgs++] = aiKeyExch;
423 if (cSupportedAlgs)
425 SchannelCred.cSupportedAlgs = cSupportedAlgs;
426 SchannelCred.palgSupportedAlgs = rgbSupportedAlgs;
429 SchannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS;
431 // The SCH_CRED_MANUAL_CRED_VALIDATION flag is specified because
432 // this sample verifies the server certificate manually.
433 // Applications that expect to run on WinNT, Win9x, or WinME
434 // should specify this flag and also manually verify the server
435 // certificate. Applications running on newer versions of Windows can
436 // leave off this flag, in which case the InitializeSecurityContext
437 // function will validate the server certificate automatically.
438 SchannelCred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION;
440 // Create an SSPI credential.
441 Status = g_pSSPI->AcquireCredentialsHandle(nullptr, // Name of principal
442 UNISP_NAME, // Name of package
443 SECPKG_CRED_OUTBOUND, // Flags indicating use
444 nullptr, // Pointer to logon ID
445 &SchannelCred, // Package specific data
446 nullptr, // Pointer to GetKey() func
447 nullptr, // Value to pass to GetKey()
448 phCreds, // (out) Cred Handle
449 &tsExpiry ); // (out) Lifetime (optional)
451 return Status;
454 static DWORD EncryptSend(CSocket * Socket, CtxtHandle * phContext, PBYTE pbIoBuffer, SecPkgContext_StreamSizes Sizes)
455 // http://msdn.microsoft.com/en-us/library/aa375378(VS.85).aspx
456 // The encrypted message is encrypted in place, overwriting the original contents of its buffer.
458 SECURITY_STATUS scRet;
459 SecBufferDesc Message;
460 SecBuffer Buffers[4];
461 DWORD cbMessage;
462 PBYTE pbMessage;
464 pbMessage = pbIoBuffer + Sizes.cbHeader; // Offset by "header size"
465 cbMessage = (DWORD)strlen((char *)pbMessage);
467 // Encrypt the HTTP request.
468 Buffers[0].pvBuffer = pbIoBuffer; // Pointer to buffer 1
469 Buffers[0].cbBuffer = Sizes.cbHeader; // length of header
470 Buffers[0].BufferType = SECBUFFER_STREAM_HEADER; // Type of the buffer
472 Buffers[1].pvBuffer = pbMessage; // Pointer to buffer 2
473 Buffers[1].cbBuffer = cbMessage; // length of the message
474 Buffers[1].BufferType = SECBUFFER_DATA; // Type of the buffer
476 Buffers[2].pvBuffer = pbMessage + cbMessage; // Pointer to buffer 3
477 Buffers[2].cbBuffer = Sizes.cbTrailer; // length of the trailor
478 Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; // Type of the buffer
480 Buffers[3].pvBuffer = SECBUFFER_EMPTY; // Pointer to buffer 4
481 Buffers[3].cbBuffer = SECBUFFER_EMPTY; // length of buffer 4
482 Buffers[3].BufferType = SECBUFFER_EMPTY; // Type of the buffer 4
484 Message.ulVersion = SECBUFFER_VERSION; // Version number
485 Message.cBuffers = 4; // Number of buffers - must contain four SecBuffer structures.
486 Message.pBuffers = Buffers; // Pointer to array of buffers
488 scRet = g_pSSPI->EncryptMessage(phContext, 0, &Message, 0); // must contain four SecBuffer structures.
489 if (FAILED(scRet))
491 // printf("**** Error 0x%x returned by EncryptMessage\n", scRet);
492 return scRet;
496 // Send the encrypted data to the server.
497 return Socket->Send(pbIoBuffer, Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer, 0);
500 static LONG DisconnectFromServer(CSocket * Socket, PCredHandle phCreds, CtxtHandle * phContext)
502 PBYTE pbMessage;
503 DWORD dwType, dwSSPIFlags, dwSSPIOutFlags, cbMessage, cbData, Status;
504 SecBufferDesc OutBuffer;
505 SecBuffer OutBuffers[1];
506 TimeStamp tsExpiry;
508 dwType = SCHANNEL_SHUTDOWN; // Notify schannel that we are about to close the connection.
510 OutBuffers[0].pvBuffer = &dwType;
511 OutBuffers[0].BufferType = SECBUFFER_TOKEN;
512 OutBuffers[0].cbBuffer = sizeof(dwType);
514 OutBuffer.cBuffers = 1;
515 OutBuffer.pBuffers = OutBuffers;
516 OutBuffer.ulVersion = SECBUFFER_VERSION;
518 Status = g_pSSPI->ApplyControlToken(phContext, &OutBuffer);
519 if (FAILED(Status))
521 // printf("**** Error 0x%x returned by ApplyControlToken\n", Status);
522 goto cleanup;
525 // Build an SSL close notify message.
526 dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM;
528 OutBuffers[0].pvBuffer = nullptr;
529 OutBuffers[0].BufferType = SECBUFFER_TOKEN;
530 OutBuffers[0].cbBuffer = 0;
532 OutBuffer.cBuffers = 1;
533 OutBuffer.pBuffers = OutBuffers;
534 OutBuffer.ulVersion = SECBUFFER_VERSION;
536 Status = g_pSSPI->InitializeSecurityContext(phCreds, phContext, nullptr, dwSSPIFlags, 0, SECURITY_NATIVE_DREP, nullptr, 0, phContext, &OutBuffer, &dwSSPIOutFlags, &tsExpiry);
538 if (FAILED(Status))
540 // printf("**** Error 0x%x returned by InitializeSecurityContext\n", Status);
541 goto cleanup;
544 pbMessage = (PBYTE)OutBuffers[0].pvBuffer;
545 cbMessage = OutBuffers[0].cbBuffer;
547 // Send the close notify message to the server.
548 if (pbMessage != nullptr && cbMessage != 0)
550 cbData = Socket->Send(pbMessage, cbMessage, 0);
551 if (cbData == SOCKET_ERROR || cbData == 0)
553 Status = WSAGetLastError();
554 goto cleanup;
556 // printf("Sending Close Notify\n");
557 // printf("%d bytes of handshake data sent\n", cbData);
558 g_pSSPI->FreeContextBuffer(pbMessage); // Free output buffer.
561 cleanup:
562 g_pSSPI->DeleteSecurityContext(phContext); // Free the security context.
563 Socket->Close();
565 return Status;
568 static SECURITY_STATUS ReadDecrypt(CSocket * Socket, PCredHandle phCreds, CtxtHandle * phContext, PBYTE pbIoBuffer, DWORD cbIoBufferLength)
570 // calls recv() - blocking socket read
571 // http://msdn.microsoft.com/en-us/library/ms740121(VS.85).aspx
573 // The encrypted message is decrypted in place, overwriting the original contents of its buffer.
574 // http://msdn.microsoft.com/en-us/library/aa375211(VS.85).aspx
577 SecBuffer ExtraBuffer;
578 SecBuffer * pDataBuffer, * pExtraBuffer;
580 SECURITY_STATUS scRet;
581 SecBufferDesc Message;
582 SecBuffer Buffers[4];
584 DWORD cbIoBuffer, cbData, length;
585 PBYTE buff;
587 // Read data from server until done.
588 cbIoBuffer = 0;
589 scRet = 0;
590 while (TRUE) // Read some data.
592 if (cbIoBuffer == 0 || scRet == SEC_E_INCOMPLETE_MESSAGE) // get the data
594 cbData = Socket->Receive(pbIoBuffer + cbIoBuffer, cbIoBufferLength - cbIoBuffer, 0);
595 if (cbData == SOCKET_ERROR)
597 // printf("**** Error %d reading data from server\n", WSAGetLastError());
598 scRet = SEC_E_INTERNAL_ERROR;
599 break;
601 else if (cbData == 0) // Server disconnected.
603 if (cbIoBuffer)
605 // printf("**** Server unexpectedly disconnected\n");
606 scRet = SEC_E_INTERNAL_ERROR;
607 return scRet;
609 else
610 break; // All Done
612 else // success
614 // printf("%d bytes of (encrypted) application data received\n", cbData);
615 cbIoBuffer += cbData;
619 // Decrypt the received data.
620 Buffers[0].pvBuffer = pbIoBuffer;
621 Buffers[0].cbBuffer = cbIoBuffer;
622 Buffers[0].BufferType = SECBUFFER_DATA; // Initial Type of the buffer 1
623 Buffers[1].BufferType = SECBUFFER_EMPTY; // Initial Type of the buffer 2
624 Buffers[2].BufferType = SECBUFFER_EMPTY; // Initial Type of the buffer 3
625 Buffers[3].BufferType = SECBUFFER_EMPTY; // Initial Type of the buffer 4
627 Message.ulVersion = SECBUFFER_VERSION; // Version number
628 Message.cBuffers = 4; // Number of buffers - must contain four SecBuffer structures.
629 Message.pBuffers = Buffers; // Pointer to array of buffers
631 scRet = g_pSSPI->DecryptMessage(phContext, &Message, 0, nullptr);
632 if (scRet == SEC_I_CONTEXT_EXPIRED)
633 break; // Server signalled end of session
634 // if (scRet == SEC_E_INCOMPLETE_MESSAGE - Input buffer has partial encrypted record, read more
635 if (scRet != SEC_E_OK && scRet != SEC_I_RENEGOTIATE && scRet != SEC_I_CONTEXT_EXPIRED)
636 return scRet;
638 // Locate data and (optional) extra buffers.
639 pDataBuffer = nullptr;
640 pExtraBuffer = nullptr;
641 for (int i = 1; i < 4; ++i)
643 if (pDataBuffer == nullptr && Buffers[i].BufferType == SECBUFFER_DATA)
644 pDataBuffer = &Buffers[i];
645 if (pExtraBuffer == nullptr && Buffers[i].BufferType == SECBUFFER_EXTRA)
646 pExtraBuffer = &Buffers[i];
649 // Display the decrypted data.
650 if (pDataBuffer)
652 length = pDataBuffer->cbBuffer;
653 if (length) // check if last two chars are CR LF
655 buff = (PBYTE)pDataBuffer->pvBuffer;
656 if (buff[length-2] == 13 && buff[length-1] == 10) // Found CRLF
658 buff[length] = 0;
659 break;
664 // Move any "extra" data to the input buffer.
665 if (pExtraBuffer)
667 MoveMemory(pbIoBuffer, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer);
668 cbIoBuffer = pExtraBuffer->cbBuffer;
670 else
671 cbIoBuffer = 0;
673 // The server wants to perform another handshake sequence.
674 if (scRet == SEC_I_RENEGOTIATE)
676 // printf("Server requested renegotiate!\n");
677 scRet = ClientHandshakeLoop( Socket, phCreds, phContext, FALSE, &ExtraBuffer);
678 if (scRet != SEC_E_OK)
679 return scRet;
681 if (ExtraBuffer.pvBuffer) // Move any "extra" data to the input buffer.
683 MoveMemory(pbIoBuffer, ExtraBuffer.pvBuffer, ExtraBuffer.cbBuffer);
684 cbIoBuffer = ExtraBuffer.cbBuffer;
687 } // Loop till CRLF is found at the end of the data
689 return SEC_E_OK;
692 BOOL CHwSMTP::SendEmail (
693 LPCTSTR lpszSmtpSrvHost,
694 LPCTSTR lpszUserName,
695 LPCTSTR lpszPasswd,
696 BOOL bMustAuth,
697 LPCTSTR lpszAddrFrom,
698 LPCTSTR lpszAddrTo,
699 LPCTSTR lpszSubject,
700 LPCTSTR lpszBody,
701 LPCTSTR lpszCharSet, // ×Ö·û¼¯ÀàÐÍ£¬ÀýÈ磺·±ÌåÖÐÎÄÕâÀïÓ¦ÊäÈë"big5"£¬¼òÌåÖÐÎÄʱÊäÈë"gb2312"
702 CStringArray* pStrAryAttach/*=nullptr*/,
703 LPCTSTR pStrAryCC/*=nullptr*/,
704 UINT nSmtpSrvPort,/*=25*/
705 LPCTSTR pSender,
706 LPCTSTR pToList,
707 DWORD secLevel
710 m_StrAryAttach.RemoveAll();
712 m_StrCC += GET_SAFE_STRING(pStrAryCC);
714 m_csSmtpSrvHost = GET_SAFE_STRING ( lpszSmtpSrvHost );
715 if ( m_csSmtpSrvHost.GetLength() <= 0 )
717 m_csLastError.Format ( _T("Parameter Error!") );
718 return FALSE;
720 m_csUserName = GET_SAFE_STRING ( lpszUserName );
721 m_csPasswd = GET_SAFE_STRING ( lpszPasswd );
722 m_bMustAuth = bMustAuth;
723 if ( m_bMustAuth && m_csUserName.GetLength() <= 0 )
725 m_csLastError.Format ( _T("Parameter Error!") );
726 return FALSE;
729 m_csAddrFrom = GET_SAFE_STRING ( lpszAddrFrom );
730 m_csAddrTo = GET_SAFE_STRING ( lpszAddrTo );
731 // m_csFromName = GET_SAFE_STRING ( lpszFromName );
732 // m_csReceiverName = GET_SAFE_STRING ( lpszReceiverName );
733 m_csSubject = GET_SAFE_STRING ( lpszSubject );
734 m_csBody = GET_SAFE_STRING ( lpszBody );
736 this->m_csSender = GET_SAFE_STRING(pSender);
737 this->m_csToList = GET_SAFE_STRING(pToList);
739 m_nSmtpSrvPort = nSmtpSrvPort;
741 if ( lpszCharSet && lstrlen(lpszCharSet) > 0 )
742 m_csCharSet.Format ( _T("\r\n\tcharset=\"%s\"\r\n"), lpszCharSet );
744 if (
745 m_csAddrFrom.GetLength() <= 0 || m_csAddrTo.GetLength() <= 0
748 m_csLastError.Format ( _T("Parameter Error!") );
749 return FALSE;
752 if ( pStrAryAttach )
754 m_StrAryAttach.Append ( *pStrAryAttach );
756 if ( m_StrAryAttach.GetSize() < 1 )
757 m_csMIMEContentType.Format(_T("text/plain; %s"), (LPCTSTR)m_csCharSet);
759 // ´´½¨Socket
760 m_SendSock.Close();
761 if ( !m_SendSock.Create () )
763 //int nResult = GetLastError();
764 m_csLastError.Format ( _T("Create socket failed!") );
765 return FALSE;
768 switch (secLevel)
770 case 1:
771 m_iSecurityLevel = want_tls;
772 break;
773 case 2:
774 m_iSecurityLevel = ssl;
775 break;
776 default:
777 m_iSecurityLevel = none;
780 if ( !m_SendSock.Connect ( m_csSmtpSrvHost, m_nSmtpSrvPort ) )
782 m_csLastError.Format(_T("Connect to [%s] failed"), (LPCTSTR)m_csSmtpSrvHost);
783 return FALSE;
786 if (m_iSecurityLevel == want_tls) {
787 if (!GetResponse("220"))
788 return FALSE;
789 m_bConnected = TRUE;
790 Send(L"STARTTLS\n");
791 if (!GetResponse("220"))
792 return FALSE;
793 m_iSecurityLevel = tls_established;
796 BOOL ret = FALSE;
798 SecBuffer ExtraData;
799 SECURITY_STATUS Status;
801 CtxtHandle contextStruct;
802 CredHandle credentialsStruct;
804 if (m_iSecurityLevel >= ssl)
806 g_pSSPI = InitSecurityInterface();
808 contextStruct.dwLower = 0;
809 contextStruct.dwUpper = 0;
811 hCreds = &credentialsStruct;
812 credentialsStruct.dwLower = 0;
813 credentialsStruct.dwUpper = 0;
814 Status = CreateCredentials(hCreds);
815 if (Status != SEC_E_OK)
817 m_csLastError = CFormatMessageWrapper(Status);
818 return FALSE;
821 hContext = &contextStruct;
822 Status = PerformClientHandshake(&m_SendSock, hCreds, m_csSmtpSrvHost.GetBuffer(), hContext, &ExtraData);
823 if (Status != SEC_E_OK)
825 m_csLastError = CFormatMessageWrapper(Status);
826 return FALSE;
829 PCCERT_CONTEXT pRemoteCertContext = nullptr;
830 // Authenticate server's credentials. Get server's certificate.
831 Status = g_pSSPI->QueryContextAttributes(hContext, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pRemoteCertContext);
832 if (Status)
834 m_csLastError = CFormatMessageWrapper(Status);
835 goto cleanup;
838 git_cert_x509 cert;
839 cert.parent.cert_type = GIT_CERT_X509;
840 cert.data = pRemoteCertContext->pbCertEncoded;
841 cert.len = pRemoteCertContext->cbCertEncoded;
842 if (CAppUtils::Git2CertificateCheck((git_cert*)&cert, 0, CUnicodeUtils::GetUTF8(m_csSmtpSrvHost), nullptr))
844 CertFreeCertificateContext(pRemoteCertContext);
845 m_csLastError = _T("Invalid certificate.");
846 goto cleanup;
849 CertFreeCertificateContext(pRemoteCertContext);
851 Status = g_pSSPI->QueryContextAttributes(hContext, SECPKG_ATTR_STREAM_SIZES, &Sizes);
852 if (Status)
854 m_csLastError = CFormatMessageWrapper(Status);
855 goto cleanup;
858 // Create a buffer.
859 cbIoBufferLength = Sizes.cbHeader + Sizes.cbMaximumMessage + Sizes.cbTrailer;
860 pbIoBuffer = (PBYTE)LocalAlloc(LMEM_FIXED, cbIoBufferLength);
861 SecureZeroMemory(pbIoBuffer, cbIoBufferLength);
862 if (pbIoBuffer == nullptr)
864 m_csLastError = _T("Could not allocate memory");
865 goto cleanup;
869 if (m_iSecurityLevel <= ssl)
871 if (!GetResponse("220"))
872 goto cleanup;
873 m_bConnected = TRUE;
876 ret = SendEmail();
878 cleanup:
879 if (m_iSecurityLevel >= ssl)
881 if (hContext && hCreds)
882 DisconnectFromServer(&m_SendSock, hCreds, hContext);
883 if (pbIoBuffer)
885 LocalFree(pbIoBuffer);
886 pbIoBuffer = nullptr;
887 cbIoBufferLength = 0;
889 if (hContext)
891 g_pSSPI->DeleteSecurityContext(hContext);
892 hContext = nullptr;
894 if (hCreds)
896 g_pSSPI->FreeCredentialsHandle(hCreds);
897 hCreds = nullptr;
899 g_pSSPI = nullptr;
901 else
902 m_SendSock.Close();
904 return ret;
907 BOOL CHwSMTP::GetResponse(LPCSTR lpszVerifyCode)
909 if (!lpszVerifyCode || strlen(lpszVerifyCode) < 1)
910 return FALSE;
912 SECURITY_STATUS scRet = SEC_E_OK;
914 char szRecvBuf[1024] = {0};
915 int nRet = 0;
916 char szStatusCode[4] = {0};
918 if (m_iSecurityLevel >= ssl)
920 scRet = ReadDecrypt(&m_SendSock, hCreds, hContext, pbIoBuffer, cbIoBufferLength);
921 SecureZeroMemory(szRecvBuf, 1024);
922 memcpy(szRecvBuf, pbIoBuffer+Sizes.cbHeader, 1024);
924 else
925 nRet = m_SendSock.Receive(szRecvBuf, sizeof(szRecvBuf));
926 //TRACE(_T("Received : %s\r\n"), szRecvBuf);
927 if (nRet == 0 && m_iSecurityLevel == none || m_iSecurityLevel >= ssl && scRet != SEC_E_OK)
929 m_csLastError.Format ( _T("Receive TCP data failed") );
930 return FALSE;
932 memcpy ( szStatusCode, szRecvBuf, 3 );
933 if (strcmp(szStatusCode, lpszVerifyCode) != 0)
935 m_csLastError.Format(_T("Received invalid response: %s"), (LPCTSTR)CUnicodeUtils::GetUnicode(szRecvBuf));
936 return FALSE;
939 return TRUE;
941 BOOL CHwSMTP::SendBuffer(const char* buff, int size)
943 if(size<0)
944 size=(int)strlen(buff);
945 if ( !m_bConnected )
947 m_csLastError.Format ( _T("Didn't connect") );
948 return FALSE;
951 if (m_iSecurityLevel >= ssl)
953 int sent = 0;
954 while (size - sent > 0)
956 int toSend = min(size - sent, (int)Sizes.cbMaximumMessage);
957 SecureZeroMemory(pbIoBuffer + Sizes.cbHeader, Sizes.cbMaximumMessage);
958 memcpy(pbIoBuffer + Sizes.cbHeader, buff + sent, toSend);
959 DWORD cbData = EncryptSend(&m_SendSock, hContext, pbIoBuffer, Sizes);
960 if (cbData == SOCKET_ERROR || cbData == 0)
961 return FALSE;
962 sent += toSend;
965 else if (m_SendSock.Send ( buff, size ) != size)
967 m_csLastError.Format ( _T("Socket send data failed") );
968 return FALSE;
971 return TRUE;
974 BOOL CHwSMTP::Send(const CString &str )
976 return Send(CUnicodeUtils::GetUTF8(str));
979 BOOL CHwSMTP::Send(const CStringA &str)
981 //TRACE(_T("Send: %s\r\n"), (LPCTSTR)CUnicodeUtils::GetUnicode(str));
982 return SendBuffer(str, str.GetLength());
985 BOOL CHwSMTP::SendEmail()
987 CStringA hostname;
988 gethostname(CStrBufA(hostname, 64), 64);
990 // make sure helo hostname can be interpreted as a FQDN
991 if (hostname.Find(".") == -1)
992 hostname += ".local";
994 CStringA str;
995 str.Format("HELO %s\r\n", (LPCSTR)hostname);
996 if (!Send(str))
997 return FALSE;
998 if (!GetResponse("250"))
999 return FALSE;
1001 if ( m_bMustAuth && !auth() )
1002 return FALSE;
1004 if ( !SendHead() )
1005 return FALSE;
1007 if (!SendSubject(CUnicodeUtils::GetUnicode(hostname)))
1008 return FALSE;
1010 if ( !SendBody() )
1011 return FALSE;
1013 if ( !SendAttach() )
1015 return FALSE;
1018 if (!Send(".\r\n"))
1019 return FALSE;
1020 if (!GetResponse("250"))
1021 return FALSE;
1023 if ( HANDLE_IS_VALID(m_SendSock.m_hSocket) )
1024 Send("QUIT\r\n");
1025 m_bConnected = FALSE;
1027 return TRUE;
1030 static CStringA EncodeBase64(const char* source, int len)
1032 int neededLength = Base64EncodeGetRequiredLength(len);
1033 CStringA output;
1034 if (Base64Encode((BYTE*)source, len, CStrBufA(output, neededLength), &neededLength, ATL_BASE64_FLAG_NOCRLF))
1035 output.Truncate(neededLength);
1036 return output;
1039 static CStringA EncodeBase64(const CString& source)
1041 CStringA buf = CUnicodeUtils::GetUTF8(source);
1042 return EncodeBase64(buf, buf.GetLength());
1045 CString CHwSMTP::GetEncodedHeader(const CString& text)
1047 if (CStringUtils::IsPlainReadableASCII(text))
1048 return text;
1050 return L"=?UTF-8?B?" + CUnicodeUtils::GetUnicode(EncodeBase64(text)) + L"?=";
1053 BOOL CHwSMTP::auth()
1055 if (!Send("auth login\r\n"))
1056 return FALSE;
1057 if (!GetResponse("334"))
1058 return FALSE;
1060 if (!Send(EncodeBase64(m_csUserName) + "\r\n"))
1061 return FALSE;
1063 if (!GetResponse("334"))
1065 m_csLastError.Format ( _T("Authentication UserName failed") );
1066 return FALSE;
1069 if (!Send(EncodeBase64(m_csPasswd) + "\r\n"))
1070 return FALSE;
1072 if (!GetResponse("235"))
1074 m_csLastError.Format ( _T("Authentication Password failed") );
1075 return FALSE;
1078 return TRUE;
1081 BOOL CHwSMTP::SendHead()
1083 CString str;
1084 CString addr;
1085 CStringUtils::ParseEmailAddress(m_csAddrFrom, addr);
1087 str.Format(_T("MAIL From: <%s>\r\n"), (LPCTSTR)addr);
1088 if (!Send(str))
1089 return FALSE;
1091 if (!GetResponse("250"))
1092 return FALSE;
1094 int start=0;
1095 while(start>=0)
1097 CString one=m_csAddrTo.Tokenize(_T(";"),start);
1098 one=one.Trim();
1099 if(one.IsEmpty())
1100 continue;
1102 CStringUtils::ParseEmailAddress(one, addr);
1104 str.Format(_T("RCPT TO: <%s>\r\n"), (LPCTSTR)addr);
1105 if (!Send(str))
1106 return FALSE;
1107 if (!GetResponse("250"))
1108 return FALSE;
1111 if (!Send("DATA\r\n"))
1112 return FALSE;
1113 if (!GetResponse("354"))
1114 return FALSE;
1116 return TRUE;
1119 BOOL CHwSMTP::SendSubject(const CString &hostname)
1121 CString csSubject;
1122 csSubject += _T("Date: ");
1123 COleDateTime tNow = COleDateTime::GetCurrentTime();
1124 if ( tNow > 1 )
1126 csSubject += FormatDateTime (tNow, _T("%a, %d %b %y %H:%M:%S %Z"));
1128 csSubject += _T("\r\n");
1129 csSubject.AppendFormat(_T("From: %s\r\n"), (LPCTSTR)m_csAddrFrom);
1131 if (!m_StrCC.IsEmpty())
1132 csSubject.AppendFormat(_T("CC: %s\r\n"), (LPCTSTR)m_StrCC);
1134 if(m_csSender.IsEmpty())
1135 m_csSender = this->m_csAddrFrom;
1137 csSubject.AppendFormat(_T("Sender: %s\r\n"), (LPCTSTR)m_csSender);
1139 if(this->m_csToList.IsEmpty())
1140 m_csToList = m_csReceiverName;
1142 csSubject.AppendFormat(_T("To: %s\r\n"), (LPCTSTR)m_csToList);
1144 csSubject.AppendFormat(_T("Subject: %s\r\n"), (LPCTSTR)GetEncodedHeader(m_csSubject));
1146 CString m_ListID(GetGUID());
1147 if (m_ListID.IsEmpty())
1149 m_csLastError = _T("Could not generate Message-ID");
1150 return FALSE;
1152 csSubject.AppendFormat(_T("Message-ID: <%s@%s>\r\n"), (LPCTSTR)m_ListID, (LPCTSTR)hostname);
1153 csSubject.AppendFormat(_T("X-Mailer: TortoiseGit\r\nMIME-Version: 1.0\r\nContent-Type: %s\r\n\r\n"), (LPCTSTR)m_csMIMEContentType);
1155 return Send(csSubject);
1158 BOOL CHwSMTP::SendBody()
1160 CString csBody, csTemp;
1162 if ( m_StrAryAttach.GetSize() > 0 )
1164 csBody.AppendFormat(_T("%s\r\n\r\n"), (LPCTSTR)m_csNoMIMEText);
1165 csBody.AppendFormat(_T("--%s\r\n"), (LPCTSTR)m_csPartBoundary);
1166 csBody.AppendFormat(_T("Content-Type: text/plain\r\n%sContent-Transfer-Encoding: UTF-8\r\n\r\n"), m_csCharSet);
1169 m_csBody.Replace(L"\n.\n", L"\n..\n");
1170 m_csBody.Replace(L"\n.\r\n", L"\n..\r\n");
1172 //csTemp.Format ( _T("%s\r\n"), m_csBody );
1173 csBody += m_csBody;
1174 csBody += _T("\r\n");
1176 return Send(csBody);
1179 BOOL CHwSMTP::SendAttach()
1181 int nCountAttach = (int)m_StrAryAttach.GetSize();
1182 if ( nCountAttach < 1 ) return TRUE;
1184 for ( int i=0; i<nCountAttach; i++ )
1186 if ( !SendOnAttach ( m_StrAryAttach.GetAt(i) ) )
1187 return FALSE;
1190 Send(L"--" + m_csPartBoundary + L"--\r\n");
1192 return TRUE;
1195 BOOL CHwSMTP::SendOnAttach(LPCTSTR lpszFileName)
1197 ASSERT ( lpszFileName );
1198 CString csAttach;
1199 CString csShortFileName = CPathUtils::GetFileNameFromPath(lpszFileName);
1201 csAttach.AppendFormat(_T("--%s\r\n"), (LPCTSTR)m_csPartBoundary);
1202 csAttach.AppendFormat(_T("Content-Type: application/octet-stream; file=%s\r\n"), (LPCTSTR)csShortFileName);
1203 csAttach.AppendFormat(_T("Content-Transfer-Encoding: base64\r\n"));
1204 csAttach.AppendFormat(_T("Content-Disposition: attachment; filename=%s\r\n\r\n"), (LPCTSTR)csShortFileName);
1206 DWORD dwFileSize = hwGetFileAttr(lpszFileName);
1207 if ( dwFileSize > 5*1024*1024 )
1209 m_csLastError.Format ( _T("File [%s] too big. File size is : %s"), lpszFileName, FormatBytes(dwFileSize) );
1210 return FALSE;
1212 auto pBuf = std::make_unique<char[]>(dwFileSize + 1);
1213 if (!pBuf)
1214 ::AfxThrowMemoryException();
1216 if (!Send(csAttach))
1217 return FALSE;
1219 CFile file;
1220 CStringA filedata;
1223 if ( !file.Open ( lpszFileName, CFile::modeRead ) )
1225 m_csLastError.Format ( _T("Open file [%s] failed"), lpszFileName );
1226 return FALSE;
1228 UINT nFileLen = file.Read(pBuf.get(), dwFileSize);
1229 filedata = EncodeBase64(pBuf.get(), nFileLen);
1230 filedata += _T("\r\n\r\n");
1232 catch (CFileException *e)
1234 e->Delete();
1235 m_csLastError.Format ( _T("Read file [%s] failed"), lpszFileName );
1236 return FALSE;
1239 if (!SendBuffer(filedata))
1240 return FALSE;
1242 return TRUE;
1245 CString CHwSMTP::GetLastErrorText()
1247 return m_csLastError;
1251 CString FormatDateTime (COleDateTime &DateTime, LPCTSTR /*pFormat*/)
1253 // If null, return empty string
1254 if ( DateTime.GetStatus() == COleDateTime::null || DateTime.GetStatus() == COleDateTime::invalid )
1255 return _T("");
1257 UDATE ud;
1258 if (S_OK != VarUdateFromDate(DateTime.m_dt, 0, &ud))
1260 return _T("");
1263 TCHAR *weeks[]={_T("Sun"),_T("Mon"),_T("Tue"),_T("Wen"),_T("Thu"),_T("Fri"),_T("Sat")};
1264 TCHAR *month[]={_T("Jan"),_T("Feb"),_T("Mar"),_T("Apr"),
1265 _T("May"),_T("Jun"),_T("Jul"),_T("Aug"),
1266 _T("Sep"),_T("Oct"),_T("Nov"),_T("Dec")};
1268 TIME_ZONE_INFORMATION stTimeZone;
1269 GetTimeZoneInformation(&stTimeZone);
1271 CString strDate;
1272 strDate.Format(_T("%s, %d %s %d %02d:%02d:%02d %c%04d")
1273 ,weeks[ud.st.wDayOfWeek],
1274 ud.st.wDay,month[ud.st.wMonth-1],ud.st.wYear,ud.st.wHour,
1275 ud.st.wMinute,ud.st.wSecond,
1276 stTimeZone.Bias>0?_T('-'):_T('+'),
1277 abs(stTimeZone.Bias*10/6)
1279 return strDate;
1282 int hwGetFileAttr(LPCTSTR lpFileName, OUT CFileStatus* pFileStatus/*=nullptr*/)
1284 if ( !lpFileName || lstrlen(lpFileName) < 1 ) return -1;
1286 CFileStatus fileStatus;
1287 fileStatus.m_attribute = 0;
1288 fileStatus.m_size = 0;
1289 memset ( fileStatus.m_szFullName, 0, sizeof(fileStatus.m_szFullName) );
1290 BOOL bRet = FALSE;
1293 if ( CFile::GetStatus(lpFileName,fileStatus) )
1295 bRet = TRUE;
1298 CATCH (CFileException, e)
1300 ASSERT ( FALSE );
1301 bRet = FALSE;
1303 CATCH_ALL(e)
1305 ASSERT ( FALSE );
1306 bRet = FALSE;
1308 END_CATCH_ALL;
1310 if ( pFileStatus )
1312 pFileStatus->m_ctime = fileStatus.m_ctime;
1313 pFileStatus->m_mtime = fileStatus.m_mtime;
1314 pFileStatus->m_atime = fileStatus.m_atime;
1315 pFileStatus->m_size = fileStatus.m_size;
1316 pFileStatus->m_attribute = fileStatus.m_attribute;
1317 lstrcpy ( pFileStatus->m_szFullName, fileStatus.m_szFullName );
1321 return (int)fileStatus.m_size;
1324 CString FormatBytes ( double fBytesNum, BOOL bShowUnit/*=TRUE*/, int nFlag/*=0*/ )
1326 CString csRes;
1327 if ( nFlag == 0 )
1329 if ( fBytesNum >= 1024.0 && fBytesNum < 1024.0*1024.0 )
1330 csRes.Format ( _T("%.2f%s"), fBytesNum / 1024.0, bShowUnit?_T(" K"):_T("") );
1331 else if ( fBytesNum >= 1024.0*1024.0 && fBytesNum < 1024.0*1024.0*1024.0 )
1332 csRes.Format ( _T("%.2f%s"), fBytesNum / (1024.0*1024.0), bShowUnit?_T(" M"):_T("") );
1333 else if ( fBytesNum >= 1024.0*1024.0*1024.0 )
1334 csRes.Format ( _T("%.2f%s"), fBytesNum / (1024.0*1024.0*1024.0), bShowUnit?_T(" G"):_T("") );
1335 else
1336 csRes.Format ( _T("%.2f%s"), fBytesNum, bShowUnit?_T(" B"):_T("") );
1338 else if ( nFlag == 1 )
1339 csRes.Format ( _T("%.2f%s"), fBytesNum / 1024.0, bShowUnit?_T(" K"):_T("") );
1340 else if ( nFlag == 2 )
1341 csRes.Format ( _T("%.2f%s"), fBytesNum / (1024.0*1024.0), bShowUnit?_T(" M"):_T("") );
1342 else if ( nFlag == 3 )
1343 csRes.Format ( _T("%.2f%s"), fBytesNum / (1024.0*1024.0*1024.0), bShowUnit?_T(" G"):_T("") );
1345 return csRes;