From cb4a46e7fbe62d788e66ed6121c717a2d22a4d7c Mon Sep 17 00:00:00 2001 From: watcherhd Date: Thu, 21 Apr 2011 14:14:52 +0000 Subject: svn.miranda.im is moving to a new home! git-svn-id: http://miranda-plugins.googlecode.com/svn/trunk@7 e753b5eb-9565-29b2-b5c5-2cc6f99dfbcb --- icqj_s7_sss_mod/oscar_filetransfer.c | 2519 ++++++++++++++++++++++++++++++++++ 1 file changed, 2519 insertions(+) create mode 100644 icqj_s7_sss_mod/oscar_filetransfer.c (limited to 'icqj_s7_sss_mod/oscar_filetransfer.c') diff --git a/icqj_s7_sss_mod/oscar_filetransfer.c b/icqj_s7_sss_mod/oscar_filetransfer.c new file mode 100644 index 0000000..e7849f9 --- /dev/null +++ b/icqj_s7_sss_mod/oscar_filetransfer.c @@ -0,0 +1,2519 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000,2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001,2002 Jon Keating, Richard Hughes +// Copyright © 2002,2003,2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004,2005,2006,2007 Joe Kucera +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// ----------------------------------------------------------------------------- +// +// File name : $URL: https://icqjplusmod.googlecode.com/svn/trunk/oscar_filetransfer.c $ +// Revision : $Revision: 50 $ +// Last change on : $Date: 2007-08-28 02:57:00 +0300 (Ð’Ñ‚, 28 авг 2007) $ +// Last change by : $Author: sss123next $ +// +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- + +#include "icqoscar.h" + + +typedef struct { + int type; + int incoming; + HANDLE hContact; + HANDLE hConnection; + DWORD dwRemoteIP; + oscar_filetransfer *ft; + oscar_listener *listener; +} oscarthreadstartinfo; + + +// small utility function +extern void NormalizeBackslash(char* path); + +static void oft_newConnectionReceived(HANDLE hNewConnection, DWORD dwRemoteIP, void *pExtra); + +static DWORD __stdcall oft_connectionThread(oscarthreadstartinfo *otsi); +static void sendOscarPacket(oscar_connection *oc, icq_packet *packet); +static int oft_handlePackets(oscar_connection *oc, unsigned char *buf, int len); +static int oft_handleFileData(oscar_connection *oc, unsigned char *buf, int len); +static int oft_handleProxyData(oscar_connection *oc, unsigned char *buf, int len); +static void handleOFT2FramePacket(oscar_connection *oc, WORD datatype, BYTE *pBuffer, WORD wLen); +static void sendOFT2FramePacket(oscar_connection *oc, WORD datatype); + +static void oft_sendFileData(oscar_connection *oc); +static void oft_sendPeerInit(oscar_connection *oc); + +static void proxy_sendInitTunnel(oscar_connection *oc); +static void proxy_sendJoinTunnel(oscar_connection *oc, WORD wPort); + + +static CRITICAL_SECTION oftMutex; +static int fileTransferCount = 0; +static basic_filetransfer** fileTransferList = NULL; + +static oscar_filetransfer* CreateOscarTransfer(); +static void ReleaseFileTransfer(void *ft); +static oscar_filetransfer* FindOscarTransfer(HANDLE hContact, DWORD dwID1, DWORD dwID2); + + +// +// Common functions +///////////////////////////// + +char* FindFilePathContainer(const char** files, int iFile, char* szContainer) +{ + int i; + const char* szThisFile = files[iFile]; + char* szFileName = ExtractFileName(szThisFile); + + szContainer[0] = '\0'; + + if (szThisFile != szFileName) + { // find an earlier subdirectory to be used as a container + for (i = iFile - 1; i >= 0; i--) + { + int len = strlennull(files[i]); + + if (!_strnicmp(files[i], szThisFile, len) && (szThisFile[len] == '\\' || szThisFile[len] == '/')) + { + char* pszLastBackslash; + + if (((pszLastBackslash = strrchr(files[i], '\\')) == NULL) && + ((pszLastBackslash = strrchr(files[i], '/')) == NULL)) + { + strcpy(szContainer, files[i]); + } + else + { + len = pszLastBackslash - files[i] + 1; + strncpy(szContainer, szThisFile + len, szFileName - szThisFile - len); + szContainer[szFileName - szThisFile - len] = '\0'; + } + } + } + } + return szFileName; +} + + +// +// Utility functions +///////////////////////////// + + +static oscar_filetransfer* CreateOscarTransfer() +{ + oscar_filetransfer* ft = (oscar_filetransfer*)SAFE_MALLOC(sizeof(oscar_filetransfer)); + + ft->ft_magic = FT_MAGIC_OSCAR; // Setup signature + // Init members + ft->fileId = -1; + + EnterCriticalSection(&oftMutex); + + fileTransferList = (basic_filetransfer**)SAFE_REALLOC(fileTransferList, sizeof(basic_filetransfer*)*(fileTransferCount + 1)); + fileTransferList[fileTransferCount++] = (basic_filetransfer*)ft; + +#ifdef _DEBUG + NetLog_Direct("OFT: FT struct 0x%x created", ft); +#endif + LeaveCriticalSection(&oftMutex); + + return ft; +} + + + +filetransfer *CreateIcqFileTransfer() +{ + filetransfer *ft = (filetransfer*)SAFE_MALLOC(sizeof(filetransfer)); + + ft->ft_magic = FT_MAGIC_ICQ; + + EnterCriticalSection(&oftMutex); + + fileTransferList = (basic_filetransfer**)SAFE_REALLOC(fileTransferList, sizeof(basic_filetransfer*)*(fileTransferCount + 1)); + fileTransferList[fileTransferCount++] = (basic_filetransfer*)ft; + +#ifdef _DEBUG + NetLog_Direct("FT struct 0x%x created", ft); +#endif + LeaveCriticalSection(&oftMutex); + + return ft; +} + + + +static int getFileTransferIndex(void *ft) +{ + int i; + + for (i = 0; i < fileTransferCount; i++) + { + if (fileTransferList[i] == ft) + return i; + } + return -1; +} + + + +static void ReleaseFileTransfer(void *ft) +{ + int i = getFileTransferIndex(ft); + + if (i != -1) + { + fileTransferCount--; + fileTransferList[i] = fileTransferList[fileTransferCount]; + fileTransferList = (basic_filetransfer**)SAFE_REALLOC(fileTransferList, sizeof(basic_filetransfer*)*fileTransferCount); + } +} + + + +int IsValidFileTransfer(void *ft) +{ + int res = 0; + + EnterCriticalSection(&oftMutex); + + if (getFileTransferIndex(ft) != -1) res = 1; + + LeaveCriticalSection(&oftMutex); + + return res; +} + + + +int IsValidOscarTransfer(void *ft) +{ + int res = 0; + + EnterCriticalSection(&oftMutex); + + if (getFileTransferIndex(ft) != -1 && ((basic_filetransfer*)ft)->ft_magic == FT_MAGIC_OSCAR) + res = 1; + + LeaveCriticalSection(&oftMutex); + + return res; +} + + + +static oscar_filetransfer* FindOscarTransfer(HANDLE hContact, DWORD dwID1, DWORD dwID2) +{ + int i; + + EnterCriticalSection(&oftMutex); + + for (i = 0; i < fileTransferCount; i++) + { + if (((basic_filetransfer*)fileTransferList[i])->ft_magic == FT_MAGIC_OSCAR) + { + oscar_filetransfer *oft = (oscar_filetransfer*)fileTransferList[i]; + + if (oft->hContact == hContact && oft->pMessage.dwMsgID1 == dwID1 && oft->pMessage.dwMsgID2 == dwID2) + { + LeaveCriticalSection(&oftMutex); + + return oft; + } + } + } + + LeaveCriticalSection(&oftMutex); + + return NULL; +} + + + +// Release file transfer structure +void SafeReleaseFileTransfer(void **ft) +{ + basic_filetransfer **bft = (basic_filetransfer**)ft; + + EnterCriticalSection(&oftMutex); + + // Check for filetransfer validity + if (getFileTransferIndex(*ft) == -1) + { + LeaveCriticalSection(&oftMutex); + return; + } + + if (*bft) + { + if ((*bft)->ft_magic == FT_MAGIC_ICQ) + { // release ICQ filetransfer structure and its contents + filetransfer *ift = (filetransfer*)(*bft); + + SAFE_FREE(&ift->szFilename); + SAFE_FREE(&ift->szDescription); + SAFE_FREE(&ift->szSavePath); + SAFE_FREE(&ift->szThisFile); + SAFE_FREE(&ift->szThisSubdir); + if (ift->files) + { + int i; + + for (i = 0; i < (int)ift->dwFileCount; i++) + SAFE_FREE(&ift->files[i]); + SAFE_FREE((char**)&ift->files); + } + // Invalidate transfer + ReleaseFileTransfer(ift); +#ifdef _DEBUG + NetLog_Direct("FT struct 0x%x released", ft); +#endif + // Release memory + SAFE_FREE(ft); + } + else if ((*bft)->ft_magic == FT_MAGIC_OSCAR) + { // release oscar filetransfer structure and its contents + oscar_filetransfer *oft = (oscar_filetransfer*)(*bft); + // If connected, close connection + if (oft->connection) + CloseOscarConnection(oft->connection); + // Release oscar listener + if (oft->listener) + ReleaseOscarListener((oscar_listener**)&oft->listener); + // Release cookie + if (oft->dwCookie) + FreeCookie(oft->dwCookie); + // Release all dynamic members + SAFE_FREE(&oft->rawFileName); + SAFE_FREE(&oft->szSavePath); + SAFE_FREE(&oft->szThisFile); + SAFE_FREE(&oft->szThisPath); + if (oft->files) + { + int i; + + for (i = 0; i < oft->wFilesCount; i++) + SAFE_FREE(&oft->files[i].szFile); + SAFE_FREE((void**)&oft->files); + } + if (oft->files_ansi) + { + int i; + + for (i = 0; i < oft->wFilesCount; i++) + SAFE_FREE(&oft->files_ansi[i]); + SAFE_FREE((void**)&oft->files_ansi); + } + if (oft->file_containers) + { + int i; + + for (i = 0; i < oft->containerCount; i++) + SAFE_FREE(&oft->file_containers[i]); + SAFE_FREE((void**)&oft->file_containers); + } + if (oft->fileId != -1) + { +#ifdef _DEBUG + NetLog_Direct("OFT: _close(%u)", oft->fileId); +#endif + _close(oft->fileId); + } + // Invalidate transfer + ReleaseFileTransfer(oft); +#ifdef _DEBUG + NetLog_Direct("OFT: FT struct 0x%x released", ft); +#endif + // Release memory + SAFE_FREE(ft); + } + } + LeaveCriticalSection(&oftMutex); +} + + + + +// Calculate oft checksum of buffer +// -------------------------------- +// Information was gathered from Gaim's sources, thanks +// +DWORD oft_calc_checksum(int offset, const BYTE *buffer, int len, DWORD dwChecksum) +{ + DWORD checksum; + int i; + + checksum = (dwChecksum >> 16) & 0xffff; + for (i = 0; i < len; i++) + { + WORD val = buffer[i]; + DWORD oldchecksum = checksum; + + if (((i + offset) & 1) == 0) + val = val << 8; + + if (checksum < val) + checksum -= val + 1; + else // simulate carry + checksum -= val; + } + checksum = ((checksum & 0x0000ffff) + (checksum >> 16)); + checksum = ((checksum & 0x0000ffff) + (checksum >> 16)); + return checksum << 16; +} + + + +DWORD oft_calc_file_checksum(int hFile, __int64 maxSize) +{ + BYTE buf[OFT_BUFFER_SIZE]; + int bytesRead; + __int64 offset = 0; + DWORD dwCheck = 0xFFFF0000; + + _lseek(hFile, 0, SEEK_SET); + bytesRead = _read(hFile, buf, sizeof(buf)); + if (bytesRead == -1) + return dwCheck; + + while(bytesRead) + { + dwCheck = oft_calc_checksum((int)offset, buf, bytesRead, dwCheck); + offset += bytesRead; + bytesRead = _read(hFile, buf, sizeof(buf)); + if (bytesRead + offset > maxSize) bytesRead = (int)(maxSize - offset); + } + _lseek(hFile, 0, SEEK_SET); // back to beginning + + return dwCheck; +} + + + +oscar_listener* CreateOscarListener(oscar_filetransfer *ft, NETLIBNEWCONNECTIONPROC_V2 handler) +{ + oscar_listener *listener = (oscar_listener*)SAFE_MALLOC(sizeof(oscar_listener)); + + listener->ft = ft; + if (listener->hBoundPort = NetLib_BindPort(handler, listener, &listener->wPort, NULL)) + return listener; // Success + + SAFE_FREE(&listener); + + return NULL; // Failure +} + + + +void ReleaseOscarListener(oscar_listener **pListener) +{ + oscar_listener *listener = *pListener; + + if (listener) + { // Close listening port + if (listener->hBoundPort) + NetLib_SafeCloseHandle(&listener->hBoundPort); + + NetLog_Direct("Oscar listener on port %d released.", listener->wPort); + } + SAFE_FREE(pListener); +} + + +// +// Miranda FT interface handlers & services +///////////////////////////// + +void InitOscarFileTransfer() +{ + InitializeCriticalSection(&oftMutex); +} + + + +void UninitOscarFileTransfer() +{ + DeleteCriticalSection(&oftMutex); +} + + + +void handleRecvServMsgOFT(unsigned char *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwID1, DWORD dwID2, WORD wCommand) +{ + HANDLE hContact = HContactFromUID(dwUin, szUID, NULL); + + if (wCommand == 0) + { // this is OFT request + oscar_tlv_chain* chain = readIntoTLVChain(&buf, wLen, 0); + + if (chain) + { + WORD wAckType = getWordFromChain(chain, 0x0A, 1); + + if (wAckType == 1) + { // This is first request in this OFT + oscar_filetransfer *ft = CreateOscarTransfer(); + char* pszFileName = NULL; + char* pszDescription = NULL; + WORD wFilenameLength; + + NetLog_Server("This is a file request"); + + // This TLV chain may contain the following TLVs: + // TLV(A): Acktype 0x0001 - file request / abort request + // 0x0002 - file ack + // TLV(F): Unknown + // TLV(E): Language ? + // TLV(2): Proxy IP + // TLV(16): Proxy IP Check + // TLV(3): External IP + // TLV(4): Internal IP + // TLV(5): Port + // TLV(17): Port Check + // TLV(10): Proxy Flag + // TLV(D): Charset of User Message + // TLV(C): User Message (ICQ_COOL_FT) + // TLV(2711): FT info + // TLV(2712): Charset of file name + + // init filetransfer structure + ft->pMessage.dwMsgID1 = dwID1; + ft->pMessage.dwMsgID2 = dwID2; + ft->bUseProxy = getTLV(chain, 0x10, 1) ? 1 : 0; + ft->dwProxyIP = getDWordFromChain(chain, 0x02, 1); + ft->dwRemoteInternalIP = getDWordFromChain(chain, 0x03, 1); + ft->dwRemoteExternalIP = getDWordFromChain(chain, 0x04, 1); + ft->wRemotePort = getWordFromChain(chain, 0x05, 1); + ft->wReqNum = wAckType; + + { // User Message + oscar_tlv* tlv = getTLV(chain, 0x0C, 1); + + if (tlv) + { // parse User Message + BYTE* tBuf = tlv->pData; + + pszDescription = (char*)_alloca(tlv->wLen + 2); + unpackString(&tBuf, pszDescription, tlv->wLen); + pszDescription[tlv->wLen] = '\0'; + pszDescription[tlv->wLen+1] = '\0'; + { // apply User Message encoding + oscar_tlv *charset = getTLV(chain, 0x0D, 1); + char *str = pszDescription; + char *bTag,*eTag; + + if (charset) + { // decode charset + char *szEnc = (char*)_alloca(charset->wLen + 1); + + strncpy(szEnc, charset->pData, charset->wLen); + szEnc[charset->wLen] = '\0'; + str = ApplyEncoding(pszDescription, szEnc); + } + else + str = null_strdup(str); + // eliminate HTML tags + pszDescription = EliminateHtml(str, strlennull(str)); + + bTag = strstr(pszDescription, ""); + if (bTag) + { // take special Description - ICQJ's extension + eTag = strstr(bTag, ""); + if (eTag) + { + *eTag = '\0'; + str = null_strdup(bTag + 6); + SAFE_FREE(&pszDescription); + pszDescription = str; + } + } + else + { + bTag = strstr(pszDescription, ""); + if (bTag) + { // take only - Description tag if present + eTag = strstr(bTag, ""); + if (eTag) + { + *eTag = '\0'; + str = null_strdup(bTag + 4); + SAFE_FREE(&pszDescription); + pszDescription = str; + } + } + } + } + } + if (!strlennull(pszDescription)) pszDescription = ICQTranslateUtf("No description given"); + } + { // parse File Transfer Info block + oscar_tlv* tlv = getTLV(chain, 0x2711, 1); + BYTE* tBuf = tlv->pData; + WORD tLen = tlv->wLen; + WORD wFlag; + + unpackWord(&tBuf, &wFlag); // FT flag + unpackWord(&tBuf, &ft->wFilesCount); + unpackDWord(&tBuf, (DWORD*)&ft->qwTotalSize); + tLen -= 8; + // Filename / Directory Name + wFilenameLength = tLen - 1; + pszFileName = (char*)_alloca(tLen); + unpackString(&tBuf, pszFileName, wFilenameLength); + pszFileName[wFilenameLength] = '\0'; + { // apply Filename / Directory Name encoding + oscar_tlv* charset = getTLV(chain, 0x2712, 1); + + if (charset) + { + char* szEnc = (char*)_alloca(charset->wLen + 1); + + strncpy(szEnc, charset->pData, charset->wLen); + szEnc[charset->wLen] = '\0'; + pszFileName = ApplyEncoding(pszFileName, szEnc); + } + else + pszFileName = ansi_to_utf8(pszFileName); + } + if (ft->wFilesCount == 1) + { // Filename - use for DB event (convert to Ansi - File DB events does not support Unicode) + char* szAnsi = (char*)_alloca(strlennull(pszFileName) + 2); + utf8_decode_static(pszFileName, szAnsi, strlennull(pszFileName) + 1); + SAFE_FREE(&pszFileName); + pszFileName = szAnsi; + } + else + { // Save Directory name for future use + ft->szThisPath = pszFileName; + // for multi-file transfer we do not display "folder" name, but create only a simple notice + pszFileName = (char*)_alloca(64); + + null_snprintf(pszFileName, 64, ICQTranslate("%d Files"), ft->wFilesCount); + } + } + { // Total Size TLV (ICQ 6 and AIM 6) + oscar_tlv *tlv = getTLV(chain, 0x2713, 1); + + if (tlv && tlv->wLen >= 8) + { + BYTE *tBuf = tlv->pData; + + unpackQWord(&tBuf, &ft->qwTotalSize); + } + } + { + CCSDATA ccs; + PROTORECVEVENT pre; + char* szBlob; + int bAdded; + HANDLE hContact = HContactFromUID(dwUin, szUID, &bAdded); + char* szAnsi; + + ft->hContact = hContact; + ft->szDescription = pszDescription; + ft->fileId = -1; + + szAnsi = (char*)_alloca(strlennull(pszDescription)+2); + utf8_decode_static(pszDescription, szAnsi, strlennull(pszDescription)+1); + + // Send chain event + szBlob = (char*)_alloca(sizeof(DWORD) + strlennull(pszFileName) + strlennull(szAnsi) + 2); + *(PDWORD)szBlob = (DWORD)ft; + strcpy(szBlob + sizeof(DWORD), pszFileName); + strcpy(szBlob + sizeof(DWORD) + strlennull(pszFileName) + 1, szAnsi); // DB event is ansi only! + ccs.szProtoService = PSR_FILE; + ccs.hContact = hContact; + ccs.wParam = 0; + ccs.lParam = (LPARAM)⪯ + pre.flags = 0; + pre.timestamp = time(NULL); + pre.szMessage = szBlob; + pre.lParam = 0; + + CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs); + } + } + else if (wAckType == 2) + { // First attempt failed, reverse requested + oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2); + + if (ft) + { + NetLog_Direct("OFT: Redirect received (%d)", wAckType); + + ft->wReqNum = wAckType; + + if (ft->sending) + { + ReleaseOscarListener((oscar_listener**)&ft->listener); + + ft->bUseProxy = getTLV(chain, 0x10, 1) ? 1 : 0; + ft->dwProxyIP = getDWordFromChain(chain, 0x02, 1); + ft->dwRemoteInternalIP = getDWordFromChain(chain, 0x03, 1); + ft->dwRemoteExternalIP = getDWordFromChain(chain, 0x04, 1); + ft->wRemotePort = getWordFromChain(chain, 0x05, 1); + + OpenOscarConnection(hContact, ft, ft->bUseProxy ? OCT_PROXY_RECV: OCT_REVERSE); + } + else + { // Just sanity + ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)ft, 0); + // Release transfer + SafeReleaseFileTransfer(&ft); + } + } + else + NetLog_Server("Error: Invalid request, no such transfer"); + } + else if (wAckType == 3) + { // Transfering thru proxy, join tunnel + oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2); + + if (ft) + { // release possible previous listener + NetLog_Direct("OFT: Redirect received (%d)", wAckType); + + ft->wReqNum = wAckType; + + ReleaseOscarListener((oscar_listener**)&ft->listener); + + ft->bUseProxy = getTLV(chain, 0x10, 1) ? 1 : 0; + ft->dwProxyIP = getDWordFromChain(chain, 0x02, 1); + ft->wRemotePort = getWordFromChain(chain, 0x05, 1); + + if (ft->bUseProxy && ft->dwProxyIP) + { // Init proxy connection + OpenOscarConnection(hContact, ft, OCT_PROXY_RECV); + } + else + { // try Stage 4 + OpenOscarConnection(hContact, ft, OCT_PROXY); + } + } + else + NetLog_Server("Error: Invalid request, no such transfer"); + } + else if (wAckType == 4) + { + oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2); + + if (ft) + { + NetLog_Direct("OFT: Redirect received (%d)", wAckType); + + ft->wReqNum = wAckType; + ft->bUseProxy = getTLV(chain, 0x10, 1) ? 1 : 0; + ft->dwProxyIP = getDWordFromChain(chain, 0x02, 1); + ft->wRemotePort = getWordFromChain(chain, 0x05, 1); + + if (ft->bUseProxy && ft->dwProxyIP) + { // Init proxy connection + OpenOscarConnection(hContact, ft, OCT_PROXY_RECV); + } + else + NetLog_Server("Error: Invalid request, IP missing."); + } + else + NetLog_Server("Error: Invalid request, no such transfer"); + } + else + NetLog_Server("Error: Uknown Stage %d request", wAckType); + + disposeChain(&chain); + } + else + NetLog_Server("Error: Missing TLV chain in OFT request"); + } + else if (wCommand == 1) + { // transfer cancelled/aborted + oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2); + + if (ft) + { + NetLog_Server("OFT: File transfer cancelled by %s", strUID(dwUin, szUID)); + + ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)ft, 0); + // Notify user, that the FT was cancelled // TODO: new ACKRESULT_? + icq_LogMessage(LOG_ERROR, "The file transfer was aborted by the other user."); + // Release transfer + SafeReleaseFileTransfer(&ft); + } + else + NetLog_Server("Error: Invalid request, no such transfer"); + } + else if (wCommand == 2) + { // transfer accepted - connection established + oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2); + + if (ft) + { + NetLog_Direct("OFT: Session established."); + // Init connection + if (ft->sending) + { + if (ft->connection && ((oscar_connection*)(ft->connection))->status == OCS_CONNECTED) + oft_sendPeerInit((oscar_connection*)ft->connection); + else + ft->initialized = 1; // accept was received + } + else + NetLog_Server("Warning: Received invalid rendezvous accept"); + } + else + NetLog_Server("Error: Invalid request, no such transfer"); + } + else + { + NetLog_Server("Error: Unknown wCommand=0x%x in OFT request", wCommand); + } +} + + + +void handleRecvServResponseOFT(unsigned char *buf, WORD wLen, DWORD dwUin, char *szUID, void* ft) +{ + WORD wDataLen; + + if (wLen < 2) return; + + unpackWord(&buf, &wDataLen); + + if (wDataLen == 2) + { + oscar_filetransfer *oft = (oscar_filetransfer*)ft; + WORD wStatus; + + unpackWord(&buf, &wStatus); + + switch (wStatus) + { + case 1: + { // FT denied (icq5) + NetLog_Server("OFT: File transfer denied by %s", strUID(dwUin, szUID)); + + ICQBroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_DENIED, (HANDLE)oft, 0); + // Release transfer + SafeReleaseFileTransfer(&oft); + } + break; + + case 4: // Proxy error + { + icq_LogMessage(LOG_ERROR, "The file transfer failed: Proxy error"); + + ICQBroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0); + // Release transfer + SafeReleaseFileTransfer(&oft); + } + break; + + case 5: // Invalid request + { + icq_LogMessage(LOG_ERROR, "The file transfer failed: Invalid request"); + + ICQBroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0); + // Release transfer + SafeReleaseFileTransfer(&oft); + } + break; + + case 6: // Proxy Failed (IP = 0) + { + icq_LogMessage(LOG_ERROR, "The file transfer failed: Proxy unavailable"); + + ICQBroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0); + // Release transfer + SafeReleaseFileTransfer(&oft); + } + break; + + default: + { + NetLog_Server("OFT: Uknown request response code 0x%x", wStatus); + + ICQBroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0); + // Release transfer + SafeReleaseFileTransfer(&oft); + } + } + } +} + + + +static char* oftGetFileContainer(oscar_filetransfer* oft, const char** files, int iFile) +{ + char szPath[MAX_PATH]; + char* szFileName = FindFilePathContainer(files, iFile, szPath); + char* szPathUtf = ansi_to_utf8(szPath); + int i; + + // try to find existing container + for (i = 0; i < oft->containerCount; i++) + if (!strcmp(szPathUtf, oft->file_containers[i])) + { + SAFE_FREE(&szPathUtf); + + return oft->file_containers[i]; + } + + // create new container + i = oft->containerCount++; + oft->file_containers = (char**)SAFE_REALLOC(oft->file_containers, (sizeof(char*) * oft->containerCount)); + oft->file_containers[i] = szPathUtf; + + return oft->file_containers[i]; +} + + + +int oftInitTransfer(HANDLE hContact, DWORD dwUin, char* szUid, char** files, char* pszDesc) +{ + oscar_filetransfer *ft; + int i, filesCount; + struct _stati64 statbuf; + + // Initialize filetransfer struct + NetLog_Server("Init file send"); + + ft = CreateOscarTransfer(); + ft->hContact = hContact; + ft->pMessage.bMessageType = MTYPE_FILEREQ; + InitMessageCookie(&ft->pMessage); + + for (filesCount = 0; files[filesCount]; filesCount++); + ft->files = (oft_file_record *)SAFE_MALLOC(sizeof(oft_file_record) * filesCount); + ft->files_ansi = (char **)SAFE_MALLOC(sizeof(char *) * filesCount); + ft->qwTotalSize = 0; + // Prepare files arrays + for (i = 0; i < filesCount; i++) + { + if (_stati64(files[i], &statbuf)) + NetLog_Server("IcqSendFile() was passed invalid filename \"%s\"", files[i]); + else + { + if (!(statbuf.st_mode&_S_IFDIR)) + { // take only files + ft->files[ft->wFilesCount].szFile = FileNameToUtf(files[i]); + ft->files[ft->wFilesCount].szContainer = oftGetFileContainer(ft, files, i); + ft->files_ansi[ft->wFilesCount] = null_strdup(files[i]); + + ft->wFilesCount++; + ft->qwTotalSize += statbuf.st_size; + } + } + } + if (!ft->wFilesCount) + { // found no valid files to send + icq_LogMessage(LOG_ERROR, LPGEN("Failed to Initialize File Transfer. No valid files were specified.")); + // Notify UI + ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)ft, 0); + // Release transfer + SafeReleaseFileTransfer(&ft); + + return 0; // Failure + } + if (ft->qwTotalSize >= 0x100000000 && ft->wFilesCount > 1) + { // file larger than 4GB can be send only as single + icq_LogMessage(LOG_ERROR, "The files are too big to be sent at once. Files bigger than 4GB can be sent only separately."); + // Notify UI + ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)ft, 0); + // Release transfer + SafeReleaseFileTransfer(&ft); + + return 0; // Failure + } + + NetLog_Server("OFT: Found %d files.", ft->wFilesCount); + + ft->szDescription = ansi_to_utf8(pszDesc); + ft->sending = 1; + ft->fileId = -1; + ft->iCurrentFile = 0; + ft->dwCookie = AllocateCookie(CKT_FILE, ICQ_MSG_SRV_SEND, hContact, ft); + + // Init oscar fields + { + ft->wEncrypt = 0; + ft->wCompress = 0; + ft->wPartsCount = 1; + ft->wPartsLeft = 1; + strcpy(ft->rawIDString, "Cool FileXfer"); + ft->bHeaderFlags = 0x20; + ft->bNameOff = 0x1C; + ft->bSizeOff = 0x11; + ft->dwRecvForkCheck = 0xFFFF0000; + ft->dwThisForkCheck = 0xFFFF0000; + ft->dwRecvFileCheck = 0xFFFF0000; + } + + // Send file transfer request + { + char* pszFiles; + + if (ft->wFilesCount == 1) + { // transfering single file, give filename + pszFiles = ExtractFileName(ft->files[0].szFile); + } + else + { // check if transfering one directory + char *szFirstDiv, *szFirstDir = ft->file_containers[0]; + int nFirstDirLen; + + // default is no root dir + pszFiles = ""; + + if ((szFirstDiv = strstr(szFirstDir, "\\")) || (szFirstDiv = strstr(szFirstDir, "/"))) + nFirstDirLen = szFirstDiv - szFirstDir; + else + nFirstDirLen = strlennull(szFirstDir); + + if (nFirstDirLen) + { // got root dir from first container, check if others are only sub-dirs + for (i = 0; i < ft->containerCount; i++) + { + if (strnicmp(ft->file_containers[i], szFirstDir, nFirstDirLen)) + { + szFirstDir = NULL; + break; + } + } + if (szFirstDir) + { // fine, we are sending only one directory + pszFiles = szFirstDir; + if (szFirstDiv) szFirstDiv[0] = '\0'; + nFirstDirLen++; // include backslash + // cut all files container by root dir - it is transferred as root separately + for (i = 0; i < ft->wFilesCount; i++) + ft->files[i].szContainer += nFirstDirLen; + } + } + } + + // Create listener + ft->listener = CreateOscarListener(ft, oft_newConnectionReceived); + + // Send packet + if (ft->listener) + { + oft_sendFileRequest(dwUin, szUid, ft, pszFiles, ICQGetContactSettingDword(NULL, "RealIP", 0)); + } + else + { // try stage 1 proxy + ft->szThisFile = null_strdup(pszFiles); + OpenOscarConnection(hContact, ft, OCT_PROXY_INIT); + } + } + + return (int)(HANDLE)ft; // Success +} + + + +DWORD oftFileAllow(HANDLE hContact, WPARAM wParam, LPARAM lParam) +{ + oscar_filetransfer* ft = (oscar_filetransfer*)wParam; + DWORD dwUin; + uid_str szUid; + + if (ICQGetContactSettingUID(hContact, &dwUin, &szUid)) + return 0; // Invalid contact + + ft->szSavePath = ansi_to_utf8((char *)lParam); + if (ft->szThisPath) + { // Append Directory name to the save path, when transfering a directory + ft->szSavePath = (char*)SAFE_REALLOC(ft->szSavePath, strlennull(ft->szSavePath) + strlennull(ft->szThisPath) + 4); + NormalizeBackslash(ft->szSavePath); + strcat(ft->szSavePath, ft->szThisPath); + NormalizeBackslash(ft->szSavePath); + } +#ifdef _DEBUG + NetLog_Direct("OFT: Request accepted, saving to '%s'.", ft->szSavePath); +#endif + + // Create cookie + ft->dwCookie = AllocateCookie(CKT_FILE, ICQ_MSG_SRV_SEND, hContact, ft); + + OpenOscarConnection(hContact, ft, ft->bUseProxy ? OCT_PROXY_RECV: OCT_NORMAL); + + return wParam; // Success +} + + + +DWORD oftFileDeny(HANDLE hContact, WPARAM wParam, LPARAM lParam) +{ + oscar_filetransfer* ft = (oscar_filetransfer*)wParam; + DWORD dwUin; + uid_str szUid; + + if (IsValidOscarTransfer(ft)) + { + if (ICQGetContactSettingUID(hContact, &dwUin, &szUid)) + return 1; // Invalid contact + +#ifdef _DEBUG + NetLog_Direct("OFT: Request denied."); +#endif + + oft_sendFileDeny(dwUin, szUid, ft); + + // Release structure + SafeReleaseFileTransfer(&ft); + + return 0; // Success + } + return 1; // Invalid transfer +} + + + +DWORD oftFileCancel(HANDLE hContact, WPARAM wParam, LPARAM lParam) +{ + oscar_filetransfer* ft = (oscar_filetransfer*)wParam; + DWORD dwUin; + uid_str szUid; + + if (IsValidOscarTransfer(ft)) + { + if (ft->hContact != hContact) + return 1; // Bad contact or hTransfer + + if (ICQGetContactSettingUID(hContact, &dwUin, &szUid)) + return 1; // Invalid contact + +#ifdef _DEBUG + NetLog_Direct("OFT: Transfer cancelled."); +#endif + + oft_sendFileCancel(dwUin, szUid, ft); + + ICQBroadcastAck(hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0); + + // Release structure + SafeReleaseFileTransfer(&ft); + + return 0; // Success + } + return 1; // Invalid transfer +} + + + +void oftFileResume(oscar_filetransfer *ft, int action, const char *szFilename) +{ + oscar_connection *oc; + int openFlags; + + if (ft->connection == NULL) + return; + + oc = (oscar_connection*)ft->connection; + +#ifdef _DEBUG + NetLog_Direct("OFT: Resume Transfer, Action: %d, FileName: '%s'", action, szFilename); +#endif + + switch (action) + { + case FILERESUME_RESUME: + openFlags = _O_BINARY | _O_RDWR; + break; + + case FILERESUME_OVERWRITE: + openFlags = _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY; + ft->qwFileBytesDone = 0; + break; + + case FILERESUME_SKIP: + openFlags = _O_BINARY | _O_WRONLY; + ft->qwFileBytesDone = ft->qwThisFileSize; + break; + + case FILERESUME_RENAME: + openFlags = _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY; + SAFE_FREE(&ft->szThisFile); + ft->szThisFile = ansi_to_utf8(szFilename); + ft->qwFileBytesDone = 0; + break; + + default: // workaround for bug in Miranda Core + if (ft->resumeAction == FILERESUME_RESUME) + openFlags = _O_BINARY | _O_RDWR; + else + { // default to overwrite + openFlags = _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY; + ft->qwFileBytesDone = 0; + } + } + ft->resumeAction = action; + + ft->fileId = OpenFileUtf(ft->szThisFile, openFlags, _S_IREAD | _S_IWRITE); +#ifdef _DEBUG + NetLog_Direct("OFT: OpenFileUtf(%s, %u) returned %u", ft->szThisFile, openFlags, ft->fileId); +#endif + if (ft->fileId == -1) + { +#ifdef _DEBUG + NetLog_Direct("OFT: errno=%d", errno); +#endif + icq_LogMessage(LOG_ERROR, "Your file receive has been aborted because Miranda could not open the destination file in order to write to it. You may be trying to save to a read-only folder."); + + ICQBroadcastAck(oc->ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc->ft, 0); + // Release transfer + SafeReleaseFileTransfer(&oc->ft); + return; + } + + if (action == FILERESUME_RESUME) + ft->qwFileBytesDone = _lseeki64(ft->fileId, 0, SEEK_END); + else + _lseeki64(ft->fileId, ft->qwFileBytesDone, SEEK_SET); + + ft->qwBytesDone += ft->qwFileBytesDone; + + if (action == FILERESUME_RESUME) + { // use smart-resume + oc->status = OCS_RESUME; + ft->dwRecvFileCheck = oft_calc_file_checksum(ft->fileId, ft->qwFileBytesDone); + _lseek(ft->fileId, 0, SEEK_END); + +#ifdef _DEBUG + NetLog_Direct("OFT: Starting Smart-Resume"); +#endif + + sendOFT2FramePacket(oc, OFT_TYPE_RESUMEREQUEST); + + return; + } + else if (action == FILERESUME_SKIP) + { // we are skiping the file, send "we are done" + oc->status = OCS_NEGOTIATION; + } + else + { // Send "we are ready" + oc->status = OCS_DATA; + + sendOFT2FramePacket(oc, OFT_TYPE_READY); + } + ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0); + + if (!ft->qwThisFileSize || action == FILERESUME_SKIP) + { // if the file is empty we will not receive any data + BYTE buf; + oft_handleFileData(oc, &buf, 0); + } +} + + + +static void oft_buildProtoFileTransferStatus(oscar_filetransfer* ft, PROTOFILETRANSFERSTATUS* pfts) +{ + ZeroMemory(pfts, sizeof(PROTOFILETRANSFERSTATUS)); + pfts->cbSize = sizeof(PROTOFILETRANSFERSTATUS); + pfts->hContact = ft->hContact; + pfts->sending = ft->sending; + if (ft->sending) + pfts->files = ft->files_ansi; + else + pfts->files = NULL; /* FIXME */ + pfts->totalFiles = ft->wFilesCount; + pfts->currentFileNumber = ft->iCurrentFile; + pfts->totalBytes = (DWORD)ft->qwTotalSize; // FIXME + pfts->totalProgress = (DWORD)ft->qwBytesDone; // FIXME +// utf8_decode(ft->szPath, &pfts->workingDir); // not used by the UI anyway + utf8_decode(ft->szThisFile, &pfts->currentFile); + pfts->currentFileSize = (DWORD)ft->qwThisFileSize; // FIXME + pfts->currentFileTime = ft->dwThisFileDate; + pfts->currentFileProgress = (DWORD)ft->qwFileBytesDone; // FIXME +} + + + +void CloseOscarConnection(oscar_connection *oc) +{ + EnterCriticalSection(&oftMutex); + + if (oc) + { + oc->type = OCT_CLOSING; + + if (oc->hConnection) + { // we need this for Netlib handle consistency + NetLib_CloseConnection(&oc->hConnection, FALSE); + } + } + LeaveCriticalSection(&oftMutex); +} + + + +void OpenOscarConnection(HANDLE hContact, oscar_filetransfer *ft, int type) +{ + oscarthreadstartinfo *otsi = (oscarthreadstartinfo*)SAFE_MALLOC(sizeof(oscarthreadstartinfo)); + + otsi->hContact = hContact; + otsi->type = type; + otsi->ft = ft; + + ICQCreateThread(oft_connectionThread, otsi); +} + + + +static int CreateOscarProxyConnection(oscar_connection *oc) +{ + NETLIBOPENCONNECTION nloc = {0}; + + // inform UI + ICQBroadcastAck(oc->ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTPROXY, oc->ft, 0); + + nloc.szHost = OSCAR_PROXY_HOST; + nloc.wPort = ICQGetContactSettingWord(NULL, "OscarPort", DEFAULT_SERVER_PORT); + if (nloc.wPort == 0) + nloc.wPort = RandRange(1024, 65535); + + oc->hConnection = NetLib_OpenConnection(ghServerNetlibUser, "Proxy ", &nloc); + if (!oc->hConnection) + { // proxy connection failed + return 0; + } + oc->type = OCT_PROXY; + oc->status = OCS_PROXY; + oc->ft->connection = oc; + // init proxy + proxy_sendInitTunnel(oc); + + return 1; // Success +} + + + +// This function is called from the Netlib when someone is connecting to our oscar_listener +static void oft_newConnectionReceived(HANDLE hNewConnection, DWORD dwRemoteIP, void *pExtra) +{ + oscarthreadstartinfo *otsi = (oscarthreadstartinfo*)SAFE_MALLOC(sizeof(oscarthreadstartinfo)); + oscar_listener* listener = (oscar_listener*)pExtra; + + otsi->type = listener->ft->sending ? OCT_NORMAL : OCT_REVERSE; + otsi->incoming = 1; + otsi->hConnection = hNewConnection; + otsi->dwRemoteIP = dwRemoteIP; + otsi->listener = listener; + + // Start a new thread for the incomming connection + ICQCreateThread(oft_connectionThread, otsi); +} + + + +static DWORD __stdcall oft_connectionThread(oscarthreadstartinfo *otsi) +{ + oscar_connection oc = {0}; + oscar_listener *source; + NETLIBPACKETRECVER packetRecv={0}; + HANDLE hPacketRecver; + + oc.hContact = otsi->hContact; + oc.hConnection = otsi->hConnection; + oc.type = otsi->type; + oc.incoming = otsi->incoming; + oc.ft = otsi->ft; + source = otsi->listener; + if (oc.incoming) + { + if (IsValidOscarTransfer(source->ft)) + { + oc.ft = source->ft; + oc.ft->dwRemoteExternalIP = otsi->dwRemoteIP; + oc.hContact = oc.ft->hContact; + oc.ft->connection = &oc; + oc.status = OCS_CONNECTED; + + ReleaseOscarListener((oscar_listener**)&oc.ft->listener); + } + else + { // FT is already over, kill listener + NetLog_Direct("Received unexpected connection, closing."); + + CloseOscarConnection(&oc); + ReleaseOscarListener(&source); + + SAFE_FREE(&otsi); + + return 0; + } + } + SAFE_FREE(&otsi); + + if (oc.hContact) + { // Load contact information + ICQGetContactSettingUID(oc.hContact, &oc.dwUin, &oc.szUid); + } + + // Load local IP information + oc.dwLocalExternalIP = ICQGetContactSettingDword(NULL, "IP", 0); + oc.dwLocalInternalIP = ICQGetContactSettingDword(NULL, "RealIP", 0); + + if (!oc.incoming) + { // create outgoing connection + if (oc.type == OCT_NORMAL || oc.type == OCT_REVERSE) + { // create outgoing connection to peer + NETLIBOPENCONNECTION nloc = {0}; + IN_ADDR addr = {0}, addr2 = {0}; + + if (oc.ft->dwRemoteExternalIP == oc.dwLocalExternalIP && oc.ft->dwRemoteInternalIP) + addr.S_un.S_addr = htonl(oc.ft->dwRemoteInternalIP); + else + { + addr.S_un.S_addr = htonl(oc.ft->dwRemoteExternalIP); + // for different internal, try it also (for LANs with multiple external IP, VPNs, etc.) + if (oc.ft->dwRemoteInternalIP != oc.ft->dwRemoteExternalIP) + addr2.S_un.S_addr = htonl(oc.ft->dwRemoteInternalIP); + } + + // Inform UI that we will attempt to connect + ICQBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTING, oc.ft, 0); + + if (!addr.S_un.S_addr && oc.type == OCT_NORMAL) + { // IP to connect to is empty, request reverse + oscar_listener* listener = CreateOscarListener(oc.ft, oft_newConnectionReceived); + + if (listener) + { // we got listening port, fine send request + oc.ft->listener = listener; + // notify UI + ICQBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_LISTENING, oc.ft, 0); + + oft_sendFileRedirect(oc.dwUin, oc.szUid, oc.ft, oc.dwLocalInternalIP, listener->wPort, FALSE); + + return 0; + } + if (!CreateOscarProxyConnection(&oc)) + { // normal connection failed, notify peer, wait for error or stage 3 proxy + oft_sendFileRedirect(oc.dwUin, oc.szUid, oc.ft, 0, 0, FALSE); + // stage 3 can follow + return 0; + } + } + else if (addr.S_un.S_addr && oc.ft->wRemotePort) + { + nloc.szHost = inet_ntoa(addr); + nloc.wPort = oc.ft->wRemotePort; + nloc.timeout = 8; // 8 secs to connect + oc.hConnection = NetLib_OpenConnection(ghDirectNetlibUser, oc.type==OCT_REVERSE?"Reverse ":NULL, &nloc); + if (!oc.hConnection && addr2.S_un.S_addr) + { // first address failed, try second one if available + nloc.szHost = inet_ntoa(addr2); + oc.hConnection = NetLib_OpenConnection(ghDirectNetlibUser, oc.type==OCT_REVERSE?"Reverse ":NULL, &nloc); + } + if (!oc.hConnection) + { + if (oc.type == OCT_NORMAL) + { // connection failed, try reverse + oscar_listener* listener = CreateOscarListener(oc.ft, oft_newConnectionReceived); + + if (listener) + { // we got listening port, fine send request + oc.ft->listener = listener; + // notify UI that we await connection + ICQBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_LISTENING, oc.ft, 0); + + oft_sendFileRedirect(oc.dwUin, oc.szUid, oc.ft, oc.dwLocalInternalIP, listener->wPort, FALSE); + + return 0; + } + } + if (!CreateOscarProxyConnection(&oc)) + { // proxy connection failed, notify peer, wait for error or stage 4 proxy + oft_sendFileRedirect(oc.dwUin, oc.szUid, oc.ft, 0, 0, FALSE); + // stage 3 or stage 4 can follow + return 0; + } + } + else + { + oc.status = OCS_CONNECTED; + // ack normal connection + oc.ft->connection = &oc; + // acknowledge OFT - connection is ready + oft_sendFileAccept(oc.dwUin, oc.szUid, oc.ft); + // signal UI + ICQBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTED, oc.ft, 0); + } + } + else + { // try proxy, stage 3 (sending) + if (!CreateOscarProxyConnection(&oc)) + { // proxy connection failed, notify peer, wait for error or stage 4 proxy + oft_sendFileRedirect(oc.dwUin, oc.szUid, oc.ft, 0, 0, FALSE); + // stage 4 can follow + return 0; + } + } + } + else if (oc.type == OCT_PROXY_RECV) + { // stage 2 & stage 4 + if (oc.ft->dwProxyIP && oc.ft->wRemotePort) + { // create proxy connection, join tunnel + NETLIBOPENCONNECTION nloc = {0}; + IN_ADDR addr = {0}; + + // inform UI that we will connect to file proxy + ICQBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTPROXY, oc.ft, 0); + + addr.S_un.S_addr = htonl(oc.ft->dwProxyIP); + nloc.szHost = inet_ntoa(addr); + nloc.wPort = ICQGetContactSettingWord(NULL, "OscarPort", DEFAULT_SERVER_PORT); + if (nloc.wPort == 0) + nloc.wPort = RandRange(1024, 65535); + oc.hConnection = NetLib_OpenConnection(ghServerNetlibUser, "Proxy ", &nloc); + if (!oc.hConnection) + { // proxy connection failed, we are out of possibilities + ICQBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0); + // notify the other side, that we failed + oft_sendFileResponse(oc.dwUin, oc.szUid, oc.ft, 0x04); + // Release structure + SafeReleaseFileTransfer(&oc.ft); + + return 0; + } + oc.status = OCS_PROXY; + oc.ft->connection = &oc; + // Join proxy tunnel + proxy_sendJoinTunnel(&oc, oc.ft->wRemotePort); + } + else // stage 2 failed (empty IP) + { // try stage 3, or send response error 0x06 + if (!CreateOscarProxyConnection(&oc)) + { + oft_sendFileResponse(oc.dwUin, oc.szUid, oc.ft, 0x06); + // notify UI + ICQBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0); + // Release structure + SafeReleaseFileTransfer(&oc.ft); + + return 0; + } + } + } + else if (oc.type == OCT_PROXY) + { // stage 4 + if (!CreateOscarProxyConnection(&oc)) + { // proxy connection failed, we are out of possibilities + ICQBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0); + // notify the other side, that we failed + oft_sendFileResponse(oc.dwUin, oc.szUid, oc.ft, 0x06); + // Release structure + SafeReleaseFileTransfer(&oc.ft); + + return 0; + } + } + else if (oc.type == OCT_PROXY_INIT) + { // stage 1 + if (!CreateOscarProxyConnection(&oc)) + { // We failed to init transfer, notify UI + icq_LogMessage(LOG_ERROR, "Failed to Initialize File Transfer. Unable to bind local port and File proxy unavailable."); + // Release transfer + SafeReleaseFileTransfer(&oc.ft); + + return 0; + } + else + oc.type = OCT_PROXY_INIT; + } + } + if (!oc.hConnection) + { // one more sanity check + NetLog_Direct("Error: No OFT connection."); + return 0; + } + if (oc.status != OCS_PROXY) + { // Connected, notify FT UI + ICQBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, oc.ft, 0); + + // send init OFT frame - just for different order of packets (just like Trillian) + if (oc.status == OCS_CONNECTED && oc.ft->sending && (oc.ft->initialized || oc.type == OCT_REVERSE)) + oft_sendPeerInit((oscar_connection*)oc.ft->connection); + } + hPacketRecver = (HANDLE)CallService(MS_NETLIB_CREATEPACKETRECVER, (WPARAM)oc.hConnection, 8192); + packetRecv.cbSize = sizeof(packetRecv); + + // Packet receiving loop + + while (oc.hConnection) + { + int recvResult; + + packetRecv.dwTimeout = oc.wantIdleTime ? 0 : 120000; + + recvResult = CallService(MS_NETLIB_GETMOREPACKETS, (WPARAM)hPacketRecver, (LPARAM)&packetRecv); + if (!recvResult) + { + NetLog_Direct("Clean closure of oscar socket (%p)", oc.hConnection); + break; + } + + if (recvResult == SOCKET_ERROR) + { + if (GetLastError() == ERROR_TIMEOUT) + { // TODO: this will not work on some systems + if (oc.wantIdleTime) + { // here we want to send file data packets + oft_sendFileData(&oc); + } + else if (oc.status != OCS_WAITING) + { + NetLog_Direct("Connection timeouted, closing."); + break; + } + } + else if (oc.type != OCT_CLOSING || GetLastError() != 87) + { // log only significant errors, not "connection killed by us" + NetLog_Direct("Abortive closure of oscar socket (%p) (%d)", oc.hConnection, GetLastError()); + break; + } + } + + if (oc.type == OCT_CLOSING) + packetRecv.bytesUsed = packetRecv.bytesAvailable; + else + packetRecv.bytesUsed = oft_handlePackets(&oc, packetRecv.buffer, packetRecv.bytesAvailable); + } + + // End of packet receiving loop + + NetLib_SafeCloseHandle(&hPacketRecver); + + CloseOscarConnection(&oc); + + // Clean up + EnterCriticalSection(&oftMutex); + if (getFileTransferIndex(oc.ft) != -1) + { + oc.ft->connection = NULL; // release link + } + LeaveCriticalSection(&oftMutex); + // Give server some time for abort/cancel to arrive + SleepEx(1000, TRUE); + // Error handling + if (IsValidOscarTransfer(oc.ft)) + { + if (oc.status == OCS_DATA) + { + ICQBroadcastAck(oc.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0); + + icq_LogMessage(LOG_ERROR, "Connection lost during file transfer."); + // Release structure + SafeReleaseFileTransfer(&oc.ft); + } + else if (oc.status == OCS_NEGOTIATION) + { + ICQBroadcastAck(oc.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0); + + icq_LogMessage(LOG_ERROR, "File transfer negotiation failed for unknown reason."); + // Release structure + SafeReleaseFileTransfer(&oc.ft); + } + } + + return 0; +} + + + +static void sendOscarPacket(oscar_connection *oc, icq_packet *packet) +{ + if (oc->hConnection) + { + int nResult; + + nResult = Netlib_Send(oc->hConnection, (const char*)packet->pData, packet->wLen, 0); + + if (nResult == SOCKET_ERROR) + { + NetLog_Direct("Oscar %p socket error: %d, closing", oc->hConnection, GetLastError()); + CloseOscarConnection(oc); + } + } + + SAFE_FREE(&packet->pData); +} + + + +static int oft_handlePackets(oscar_connection *oc, unsigned char *buf, int len) +{ + BYTE *pBuf; + DWORD dwHead; + WORD datalen; + WORD datatype; + int bytesUsed = 0; + + while (len > 0) + { + if (oc->status == OCS_DATA && !oc->ft->sending) + { + return oft_handleFileData(oc, buf, len); + } + else if (oc->status == OCS_PROXY) + { + return oft_handleProxyData(oc, buf, len); + } + if (len < 6) + break; + + pBuf = buf; + unpackDWord(&pBuf, &dwHead); + if (dwHead != 0x4F465432) + { // bad packet + NetLog_Direct("OFT: Received invalid packet (dwHead = 0x%x).", dwHead); + + CloseOscarConnection(oc); + break; + } + + unpackWord(&pBuf, &datalen); + + if (len < datalen) // wait for whole packet + break; + + unpackWord(&pBuf, &datatype); +#ifdef _DEBUG + NetLog_Direct("OFT2: Type %u, Length %u bytes", datatype, datalen); +#endif + handleOFT2FramePacket(oc, datatype, pBuf, (WORD)(datalen - 8)); + + /* Increase pointers so we can check for more data */ + buf += datalen; + len -= datalen; + bytesUsed += datalen; + } + + return bytesUsed; +} + + + +static int oft_handleProxyData(oscar_connection *oc, unsigned char *buf, int len) +{ + oscar_filetransfer *ft = oc->ft; + BYTE *pBuf; + WORD datalen; + WORD wCommand; + int bytesUsed = 0; + + + while (len > 2) + { + pBuf = buf; + + unpackWord(&pBuf, &datalen); + datalen += 2; + + if (len < datalen) + break; // packet is not complete + + if (datalen < 12) + { // malformed packet + CloseOscarConnection(oc); + break; + } + pBuf += 2; // packet version + unpackWord(&pBuf, &wCommand); + pBuf += 6; + // handle packet + switch (wCommand) + { + case 0x01: // Error + { + WORD wError; + char* szError; + + unpackWord(&pBuf, &wError); + switch(wError) + { + case 0x0D: + szError = "Bad request"; + break; + case 0x0E: + szError = "Malformed packet"; + break; + case 0x10: + szError = "Initial request timeout"; + break; + case 0x1A: + szError = "Accept period timeout"; + break; + case 0x1C: + szError = "Invalid data"; + break; + + default: + szError = "Unknown"; + } + // Notify peer + oft_sendFileResponse(oc->dwUin, oc->szUid, oc->ft, 0x06); + + NetLog_Server("Proxy Error: %s (0x%x)", szError, wError); + // Notify UI + ICQBroadcastAck(oc->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc->ft, 0); + // Release structure + SafeReleaseFileTransfer(&oc->ft); + } + break; + + case 0x03: // Tunnel created + { + WORD wCode; + DWORD dwIP; + + unpackWord(&pBuf, &wCode); + unpackDWord(&pBuf, &dwIP); + + if (oc->type == OCT_PROXY_INIT) + { // Proxy ready, send Stage 1 Request + ft->bUseProxy = 1; + ft->wRemotePort = wCode; + ft->dwProxyIP = dwIP; + oft_sendFileRequest(oc->dwUin, oc->szUid, ft, ft->szThisFile, 0); + SAFE_FREE(&ft->szThisFile); + // Notify UI + ICQBroadcastAck(oc->hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, oc->ft, 0); + } + else + { + NetLog_Server("Proxy Tunnel ready, notify peer."); + oft_sendFileRedirect(oc->dwUin, oc->szUid, ft, dwIP, wCode, TRUE); + } + } + break; + + case 0x05: // Connection ready + oc->status = OCS_CONNECTED; // connection ready to send packets + // Notify UI + ICQBroadcastAck(oc->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTED, oc->ft, 0); + // signal we are ready + if (oc->type == OCT_PROXY_RECV) + { + oft_sendFileAccept(oc->dwUin, oc->szUid, ft); + if (ft->sending) // accept processed (sending only) + ft->initialized = 1; + } + + NetLog_Server("Proxy Tunnel established"); + + if (ft->initialized && ft->sending) + oft_sendPeerInit((oscar_connection*)ft->connection); + break; + + default: + NetLog_Server("Unknown proxy command 0x%x", wCommand); + } + + buf += datalen; + len -= datalen; + bytesUsed += datalen; + } + + return bytesUsed; +} + + + +static int oft_handleFileData(oscar_connection *oc, unsigned char *buf, int len) +{ + oscar_filetransfer *ft = oc->ft; + DWORD dwLen = len; + int bytesUsed = 0; + + // do not accept more data than expected + if (ft->qwThisFileSize - ft->qwFileBytesDone < dwLen) + dwLen = (int)(ft->qwThisFileSize - ft->qwFileBytesDone); + + if (ft->fileId == -1) + { // something went terribly bad +#ifdef _DEBUG + NetLog_Direct("Error: handleFileData(%u bytes) without fileId!", len); +#endif + CloseOscarConnection(oc); + return 0; + } + _write(ft->fileId, buf, dwLen); + // update checksum + ft->dwRecvFileCheck = oft_calc_checksum((int)ft->qwFileBytesDone, buf, dwLen, ft->dwRecvFileCheck); + bytesUsed += dwLen; + ft->qwBytesDone += dwLen; + ft->qwFileBytesDone += dwLen; + + if (GetTickCount() > ft->dwLastNotify + 700 || ft->qwFileBytesDone == ft->qwThisFileSize) + { // notify FT UI of our progress, at most every 700ms - do not be faster than Miranda + PROTOFILETRANSFERSTATUS pfts; + + oft_buildProtoFileTransferStatus(ft, &pfts); + ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&pfts); + SAFE_FREE(&pfts.currentFile); + SAFE_FREE(&pfts.workingDir); + oc->ft->dwLastNotify = GetTickCount(); + } + if (ft->qwFileBytesDone == ft->qwThisFileSize) + { + /* EOF */ +#ifdef _DEBUG + NetLog_Direct("OFT: _close(%u)", ft->fileId); +#endif + _close(ft->fileId); + ft->fileId = -1; + + if (ft->resumeAction != FILERESUME_SKIP && ft->dwRecvFileCheck != ft->dwThisFileCheck) + { + NetLog_Direct("Error: File checksums does not match!"); + { // Notify UI + char *pszMsg = ICQTranslateUtf("The checksum of file \"%s\" does not match, the file is probably damaged."); + char szBuf[MAX_PATH]; + + null_snprintf(szBuf, MAX_PATH, pszMsg, ExtractFileName(ft->szThisFile)); + icq_LogMessage(LOG_ERROR, szBuf); + + SAFE_FREE(&pszMsg); + } + } // keep transfer going (icq6 ignores checksums completely) + else if (ft->resumeAction == FILERESUME_SKIP) + NetLog_Direct("OFT: File receive skipped."); + else + NetLog_Direct("OFT: File received successfully."); + + if ((DWORD)(ft->iCurrentFile + 1) == ft->wFilesCount) + { + ft->bHeaderFlags = 0x01; // the whole process is over + // ack received file + sendOFT2FramePacket(oc, OFT_TYPE_DONE); + oc->type = OCT_CLOSING; + NetLog_Direct("File Transfer completed successfully."); + ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, ft, 0); + // Release transfer + SafeReleaseFileTransfer(&ft); + } + else + { // ack received file + sendOFT2FramePacket(oc, OFT_TYPE_DONE); + oc->status = OCS_NEGOTIATION; + } + + } + return bytesUsed; +} + + + +static void handleOFT2FramePacket(oscar_connection *oc, WORD datatype, BYTE *pBuffer, WORD wLen) +{ + DWORD dwID1; + DWORD dwID2; + + if (wLen < 232) + { // allow shorter packets, but at least with filename + NetLog_Direct("Error: Malformed OFT2 Frame, ignoring."); + return; + } + + unpackLEDWord(&pBuffer, &dwID1); + wLen -= 4; + unpackLEDWord(&pBuffer, &dwID2); + wLen -= 4; + + if (datatype == OFT_TYPE_REQUEST && !oc->ft->initialized) + { // first request does not contain MsgIDs we need to send them in ready packet + dwID1 = oc->ft->pMessage.dwMsgID1; + dwID2 = oc->ft->pMessage.dwMsgID2; + } + + if (oc->ft->pMessage.dwMsgID1 != dwID1 || oc->ft->pMessage.dwMsgID2 != dwID2) + { // this is not the right packet - bad Message IDs + NetLog_Direct("Error: Invalid Packet Cookie, closing."); + CloseOscarConnection(oc); + + return; + } + + switch (datatype) + { + case OFT_TYPE_REQUEST: + { // Sender ready + oscar_filetransfer *ft = oc->ft; + char *szFullPath; + + if (ft->sending) + { // just sanity check - this is only for receiving client + NetLog_Direct("Error: Invalid Packet, closing."); + CloseOscarConnection(oc); + + return; + } + + // Read Frame data + if (!ft->initialized) + { + unpackWord(&pBuffer, &ft->wEncrypt); + unpackWord(&pBuffer, &ft->wCompress); + unpackWord(&pBuffer, &ft->wFilesCount); + } + else + pBuffer += 6; + unpackWord(&pBuffer, &ft->wFilesLeft); + ft->iCurrentFile = ft->wFilesCount - oc->ft->wFilesLeft; + if (!ft->initialized) + unpackWord(&pBuffer, &ft->wPartsCount); + else + pBuffer += 2; + unpackWord(&pBuffer, &ft->wPartsLeft); + if (!ft->initialized) + { // just check it + DWORD dwSize; + + unpackDWord(&pBuffer, &dwSize); + if (dwSize != (DWORD)ft->qwTotalSize) + { // the 32bits does not match, use them as full size + ft->qwTotalSize = dwSize; + + NetLog_Server("Warning: Invalid total size."); + } + } + else + pBuffer += 4; + { // this allows us to receive single >4GB file correctly + DWORD dwSize; + + unpackDWord(&pBuffer, &dwSize); + if (dwSize == (DWORD)ft->qwTotalSize && ft->wFilesCount == 1) + ft->qwThisFileSize = ft->qwTotalSize; + else + ft->qwThisFileSize = dwSize; + } + unpackDWord(&pBuffer, &ft->dwThisFileDate); + unpackDWord(&pBuffer, &ft->dwThisFileCheck); + unpackDWord(&pBuffer, &ft->dwRecvForkCheck); + unpackDWord(&pBuffer, &ft->dwThisForkSize); + unpackDWord(&pBuffer, &ft->dwThisFileCreation); + unpackDWord(&pBuffer, &ft->dwThisForkCheck); + pBuffer += 4; // File Bytes Done + unpackDWord(&pBuffer, &ft->dwRecvFileCheck); + if (!ft->initialized) + unpackString(&pBuffer, ft->rawIDString, 32); + else + pBuffer += 32; + unpackByte(&pBuffer, &ft->bHeaderFlags); + unpackByte(&pBuffer, &ft->bNameOff); + unpackByte(&pBuffer, &ft->bSizeOff); + if (!ft->initialized) + { + unpackString(&pBuffer, ft->rawDummy, 69); + unpackString(&pBuffer, ft->rawMacInfo, 16); + } + else + pBuffer += 85; + unpackWord(&pBuffer, &ft->wEncoding); + unpackWord(&pBuffer, &ft->wSubEncoding); + ft->cbRawFileName = wLen - 176; + SAFE_FREE(&ft->rawFileName); // release previous buffers + SAFE_FREE(&ft->szThisFile); + ft->rawFileName = (char*)SAFE_MALLOC(ft->cbRawFileName + 2); + unpackString(&pBuffer, ft->rawFileName, ft->cbRawFileName); + // Prepare file + if (ft->wEncoding == 2) + { // UCS-2 encoding + ft->szThisFile = ApplyEncoding(ft->rawFileName, "unicode-2-0"); + } + else + { + ft->szThisFile = ansi_to_utf8(ft->rawFileName); + } + + { // convert dir markings to normal backslashes + DWORD i; + + for (i = 0; i < strlennull(ft->szThisFile); i++) + { + if (ft->szThisFile[i] == 0x01) ft->szThisFile[i] = '\\'; + } + } + + ft->initialized = 1; // First Frame Processed + + NetLog_Direct("File '%s', %I64u Bytes", ft->szThisFile, ft->qwThisFileSize); + + { // Prepare Path Information + char* szFile = strrchr(ft->szThisFile, '\\'); + + SAFE_FREE(&ft->szThisPath); // release previous path + if (szFile) + { + ft->szThisPath = ft->szThisFile; + szFile[0] = '\0'; // split that strings + ft->szThisFile = null_strdup(szFile + 1); + // no cheating with paths + if (!IsValidRelativePath(ft->szThisPath)) + { + NetLog_Direct("Invalid path information"); + break; + } + } + else + ft->szThisPath = null_strdup(""); + } + + /* no cheating with paths */ + if (!IsValidRelativePath(ft->szThisFile)) + { + NetLog_Direct("Invalid path information"); + break; + } + szFullPath = (char*)SAFE_MALLOC(strlennull(ft->szSavePath)+strlennull(ft->szThisPath)+strlennull(ft->szThisFile)+3); + strcpy(szFullPath, ft->szSavePath); + NormalizeBackslash(szFullPath); + strcat(szFullPath, ft->szThisPath); + NormalizeBackslash(szFullPath); + // make sure the dest dir exists + if (MakeDirUtf(szFullPath)) + NetLog_Direct("Failed to create destination directory!"); + + strcat(szFullPath, ft->szThisFile); + // we joined the full path to dest file + SAFE_FREE(&ft->szThisFile); + ft->szThisFile = szFullPath; + + ft->qwFileBytesDone = 0; + + { + /* file resume */ + PROTOFILETRANSFERSTATUS pfts; + + oft_buildProtoFileTransferStatus(ft, &pfts); + if (ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FILERESUME, ft, (LPARAM)&pfts)) + { + oc->status = OCS_WAITING; + + SAFE_FREE(&pfts.currentFile); + SAFE_FREE(&pfts.workingDir); + break; /* UI supports resume: it will call PS_FILERESUME */ + } + SAFE_FREE(&pfts.currentFile); + SAFE_FREE(&pfts.workingDir); + + ft->fileId = OpenFileUtf(ft->szThisFile, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE); +#ifdef _DEBUG + NetLog_Direct("OFT: OpenFileUtf(%s, %u) returned %u", ft->szThisFile, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, ft->fileId); +#endif + if (ft->fileId == -1) + { +#ifdef _DEBUG + NetLog_Direct("OFT: errno=%d", errno); +#endif + icq_LogMessage(LOG_ERROR, "Your file receive has been aborted because Miranda could not open the destination file in order to write to it. You may be trying to save to a read-only folder."); + + ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0); + // Release transfer + SafeReleaseFileTransfer(&oc->ft); + return; + } + } + // Send "we are ready" + oc->status = OCS_DATA; + + sendOFT2FramePacket(oc, OFT_TYPE_READY); + ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0); + if (!ft->qwThisFileSize) + { // if the file is empty we will not receive any data + BYTE buf; + + oft_handleFileData(oc, &buf, 0); + } + return; + } + + case OFT_TYPE_READY: + case OFT_TYPE_RESUMEACK: + { // Receiver is ready + oscar_filetransfer *ft = oc->ft; + + oc->status = OCS_DATA; + oc->wantIdleTime = 1; + + NetLog_Direct("OFT: Receiver ready."); + } + break; + + case OFT_TYPE_RESUMEREQUEST: + { // Receiver wants to resume file transfer from point + oscar_filetransfer *ft = oc->ft; + DWORD dwResumeCheck, dwResumeOffset, dwFileCheck; + + if (!ft->sending) + { // just sanity check - this is only for sending client + NetLog_Direct("Error: Invalid Packet, closing."); + CloseOscarConnection(oc); + + return; + } + // Read Resume Frame data + pBuffer += 44; + unpackDWord(&pBuffer, &dwResumeOffset); + unpackDWord(&pBuffer, &dwResumeCheck); + + dwFileCheck = oft_calc_file_checksum(ft->fileId, dwResumeOffset); + if (dwFileCheck == dwResumeCheck && dwResumeOffset <= ft->qwThisFileSize) + { // resume seems ok + ft->qwFileBytesDone = dwResumeOffset; + ft->qwBytesDone += dwResumeOffset; + lseek(ft->fileId, dwResumeOffset, SEEK_SET); + + NetLog_Direct("OFT: Resume request, ready."); + } + else + NetLog_Direct("OFT: Resume request, restarting."); + + // Ready for resume + sendOFT2FramePacket(oc, OFT_TYPE_RESUMEREADY); + } + break; + + case OFT_TYPE_RESUMEREADY: + { // Process Smart-resume reply + oscar_filetransfer *ft = oc->ft; + DWORD dwResumeOffset, dwResumeCheck; + + if (ft->sending) + { // just sanity check - this is only for receiving client + NetLog_Direct("Error: Invalid Packet, closing."); + CloseOscarConnection(oc); + + return; + } + // Read Resume Reply data + pBuffer += 44; + unpackDWord(&pBuffer, &dwResumeOffset); + unpackDWord(&pBuffer, &dwResumeCheck); + + if (ft->qwFileBytesDone != dwResumeOffset) + { + ft->qwBytesDone -= (ft->qwFileBytesDone - dwResumeOffset); + ft->qwFileBytesDone = dwResumeOffset; + ft->dwRecvFileCheck = dwResumeCheck; + } + lseek(ft->fileId, dwResumeOffset, SEEK_SET); + + if (ft->qwThisFileSize != ft->qwFileBytesDone) + NetLog_Direct("OFT: Resuming from offset %u.", dwResumeOffset); + + // Prepare to receive data + oc->status = OCS_DATA; + + ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0); + + // Ready for receive + sendOFT2FramePacket(oc, OFT_TYPE_RESUMEACK); + + if (ft->qwThisFileSize == ft->qwFileBytesDone) + { // all data already processed + BYTE buf; + + oft_handleFileData(oc, &buf, 0); + } + } + break; + + case OFT_TYPE_DONE: + { // File done + oscar_filetransfer *ft = oc->ft; + + oc->status = OCS_NEGOTIATION; + oc->wantIdleTime = 0; + + NetLog_Direct("OFT: File sent successfully."); + +#ifdef _DEBUG + NetLog_Direct("OFT: _close(%u)", ft->fileId); +#endif + _close(ft->fileId); // FIXME: this needs fix for "skip file" feature + ft->fileId = -1; + ft->iCurrentFile++; + // continue with next file + oft_sendPeerInit(oc); + } + break; + + default: + NetLog_Direct("Error: Uknown OFT frame type 0x%x", datatype); + } +} + + +// +// Proxy packets +///////////////////////////// + +static void proxy_sendInitTunnel(oscar_connection *oc) +{ + icq_packet packet; + WORD wLen = 39 + getUINLen(dwLocalUIN); + + packet.wLen = wLen; + init_generic_packet(&packet, 2); + + packWord(&packet, wLen); + packWord(&packet, OSCAR_PROXY_VERSION); + packWord(&packet, 0x02); // wCommand + packDWord(&packet, 0); // Unknown + packWord(&packet, 0); // Flags? + packUIN(&packet, dwLocalUIN); + packLEDWord(&packet, oc->ft->pMessage.dwMsgID1); + packLEDWord(&packet, oc->ft->pMessage.dwMsgID2); + packDWord(&packet, 0x00010010); // TLV(1) + packGUID(&packet, MCAP_FILE_TRANSFER); + + sendOscarPacket(oc, &packet); +} + + + +static void proxy_sendJoinTunnel(oscar_connection *oc, WORD wPort) +{ + icq_packet packet; + WORD wLen = 41 + getUINLen(dwLocalUIN); + + packet.wLen = wLen; + init_generic_packet(&packet, 2); + + packWord(&packet, wLen); + packWord(&packet, OSCAR_PROXY_VERSION); + packWord(&packet, 0x04); // wCommand + packDWord(&packet, 0); // Unknown + packWord(&packet, 0); // Flags? + packUIN(&packet, dwLocalUIN); + packWord(&packet, wPort); + packLEDWord(&packet, oc->ft->pMessage.dwMsgID1); + packLEDWord(&packet, oc->ft->pMessage.dwMsgID2); + packDWord(&packet, 0x00010010); // TLV(1) + packGUID(&packet, MCAP_FILE_TRANSFER); + + sendOscarPacket(oc, &packet); +} + + +// +// Direct packets +///////////////////////////// + +static void oft_sendFileData(oscar_connection *oc) +{ + oscar_filetransfer *ft = oc->ft; + BYTE buf[OFT_BUFFER_SIZE]; + int bytesRead = 0; + icq_packet packet; + + if (ft->fileId == -1) + return; + bytesRead = _read(ft->fileId, buf, sizeof(buf)); + if (bytesRead == -1) + return; + + if (!bytesRead) + { // + oc->wantIdleTime = 0; + return; + } + + if ((DWORD)bytesRead > (ft->qwThisFileSize - ft->qwFileBytesDone)) + { // do not send more than expected, limit to known size + bytesRead = (DWORD)(ft->qwThisFileSize - ft->qwFileBytesDone); + oc->wantIdleTime = 0; + } + packet.wLen = bytesRead; + init_generic_packet(&packet, 0); + packBuffer(&packet, buf, (WORD)bytesRead); // we are sending raw data + sendOscarPacket(oc, &packet); + + ft->qwBytesDone += bytesRead; + ft->qwFileBytesDone += bytesRead; + + if (GetTickCount() > ft->dwLastNotify + 700 || oc->wantIdleTime == 0 || ft->qwFileBytesDone == ft->qwThisFileSize) + { // notify only once a while or after last data packet sent + PROTOFILETRANSFERSTATUS pfts; + + oft_buildProtoFileTransferStatus(ft, &pfts); + ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&pfts); + SAFE_FREE(&pfts.currentFile); + SAFE_FREE(&pfts.workingDir); + ft->dwLastNotify = GetTickCount(); + } +} + + + +static void oft_sendPeerInit(oscar_connection *oc) +{ + oscar_filetransfer *ft = oc->ft; + struct _stati64 statbuf; + char *pszThisFileName; + wchar_t *pwsThisFile; + + // prepare init frame + if (ft->iCurrentFile >= (int)ft->wFilesCount) + { // All files done, great! + ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, ft, 0); + // Release transfer + SafeReleaseFileTransfer(&oc->ft); + return; + } + + SAFE_FREE(&ft->szThisFile); + ft->szThisFile = null_strdup(ft->files[ft->iCurrentFile].szFile); + if (FileStatUtf(ft->szThisFile, &statbuf)) + { + icq_LogMessage(LOG_ERROR, "Your file transfer has been aborted because one of the files that you selected to send is no longer readable from the disk. You may have deleted or moved it."); + + ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0); + // Release transfer + SafeReleaseFileTransfer(&oc->ft); + return; + } + + { // create full relative filename + char* szThisContainer = ft->files[ft->iCurrentFile].szContainer; + + pszThisFileName = (char*)SAFE_MALLOC(strlennull(ft->szThisFile) + strlennull(szThisContainer) + 4); + strcpy(pszThisFileName, szThisContainer); + NormalizeBackslash(pszThisFileName); + strcat(pszThisFileName, ExtractFileName(ft->szThisFile)); + } + { // convert backslashes to dir markings + DWORD i; + + for (i = 0; i < strlennull(pszThisFileName); i++) + { + if (pszThisFileName[i] == '\\' || pszThisFileName[i] == '/') pszThisFileName[i] = 0x01; + } + } + + ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0); + + ft->fileId = OpenFileUtf(ft->szThisFile, _O_BINARY | _O_RDONLY, 0); +#ifdef _DEBUG + NetLog_Direct("OFT: OpenFileUtf(%s, %u) returned %u", ft->szThisFile, _O_BINARY | _O_RDONLY, ft->fileId); +#endif + if (ft->fileId == -1) + { +#ifdef _DEBUG + NetLog_Direct("OFT: errno=%d", errno); +#endif + SAFE_FREE(&pszThisFileName); + icq_LogMessage(LOG_ERROR, "Your file transfer has been aborted because one of the files that you selected to send is no longer readable from the disk. You may have deleted or moved it."); + // + ICQBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0); + // Release transfer + SafeReleaseFileTransfer(&oc->ft); + return; + } + + ft->qwThisFileSize = statbuf.st_size; + ft->dwThisFileDate = statbuf.st_mtime; + ft->dwThisFileCreation = statbuf.st_ctime; + ft->dwThisFileCheck = oft_calc_file_checksum(ft->fileId, ft->qwThisFileSize); + ft->qwFileBytesDone = 0; + ft->dwRecvFileCheck = 0xFFFF0000; + SAFE_FREE(&ft->rawFileName); + + if (IsUSASCII(pszThisFileName, strlennull(pszThisFileName))) + { + ft->wEncoding = 0; // ascii + ft->cbRawFileName = strlennull(pszThisFileName) + 1; + if (ft->cbRawFileName < 64) ft->cbRawFileName = 64; + ft->rawFileName = (char*)SAFE_MALLOC(ft->cbRawFileName); + strcpy(ft->rawFileName, pszThisFileName); + SAFE_FREE(&pszThisFileName); + } + else + { + ft->wEncoding = 2; // ucs-2 + pwsThisFile = make_unicode_string(pszThisFileName); + SAFE_FREE(&pszThisFileName); + ft->cbRawFileName = wcslen(pwsThisFile) * sizeof(wchar_t) + 2; + if (ft->cbRawFileName < 64) ft->cbRawFileName = 64; + ft->rawFileName = (char*)SAFE_MALLOC(ft->cbRawFileName); + // convert to LE ordered string + unpackWideString((char**)&pwsThisFile, (wchar_t*)ft->rawFileName, (WORD)(wcslen(pwsThisFile) * sizeof(wchar_t))); + SAFE_FREE(&pwsThisFile); + } + ft->wFilesLeft = (WORD)(ft->wFilesCount - ft->iCurrentFile); + + sendOFT2FramePacket(oc, OFT_TYPE_REQUEST); +} + + + +static void sendOFT2FramePacket(oscar_connection *oc, WORD datatype) +{ + oscar_filetransfer *ft = oc->ft; + icq_packet packet; + + packet.wLen = 192 + ft->cbRawFileName; + init_generic_packet(&packet, 0); + // Basic Oscar Frame + packDWord(&packet, 0x4F465432); // Magic + packWord(&packet, packet.wLen); + packWord(&packet, datatype); + // Cookie + packLEDWord(&packet, ft->pMessage.dwMsgID1); + packLEDWord(&packet, ft->pMessage.dwMsgID2); + packWord(&packet, ft->wEncrypt); + packWord(&packet, ft->wCompress); + packWord(&packet, ft->wFilesCount); + packWord(&packet, ft->wFilesLeft); + packWord(&packet, ft->wPartsCount); + packWord(&packet, ft->wPartsLeft); + packDWord(&packet, (DWORD)ft->qwTotalSize); + packDWord(&packet, (DWORD)ft->qwThisFileSize); + packDWord(&packet, ft->dwThisFileDate); + packDWord(&packet, ft->dwThisFileCheck); + packDWord(&packet, ft->dwRecvForkCheck); + packDWord(&packet, ft->dwThisForkSize); + packDWord(&packet, ft->dwThisFileCreation); + packDWord(&packet, ft->dwThisForkCheck); + packDWord(&packet, (DWORD)ft->qwFileBytesDone); + packDWord(&packet, ft->dwRecvFileCheck); + packBuffer(&packet, ft->rawIDString, 32); + packByte(&packet, ft->bHeaderFlags); + packByte(&packet, ft->bNameOff); + packByte(&packet, ft->bSizeOff); + packBuffer(&packet, ft->rawDummy, 69); + packBuffer(&packet, ft->rawMacInfo, 16); + packWord(&packet, ft->wEncoding); + packWord(&packet, ft->wSubEncoding); + packBuffer(&packet, ft->rawFileName, ft->cbRawFileName); + + sendOscarPacket(oc, &packet); +} -- cgit v1.2.3