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