/* Plugin of Miranda IM for communicating with users of the AIM protocol. Copyright (c) 2008-2012 Boris Krasnovskiy Copyright (C) 2005-2006 Aaron Myles Landwehr 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, see . */ #include "stdafx.h" #pragma pack(push, 1) // oscar file transfer 2 class- See On_Sending_Files_via_OSCAR.pdf struct oft2 { char protocol_version[4];//4 unsigned short length;//6 unsigned short type;//8 unsigned char icbm_cookie[8];//16 unsigned short encryption;//18 unsigned short compression;//20 unsigned short total_files;//22 unsigned short num_files_left;//24 unsigned short total_parts;//26 unsigned short parts_left;//28 unsigned long total_size;//32 unsigned long size;//36 unsigned long mod_time;//40 unsigned long checksum;//44 unsigned long recv_RFchecksum;//48 unsigned long RFsize;//52 unsigned long creation_time;//56 unsigned long RFchecksum;//60 unsigned long recv_bytes;//64 unsigned long recv_checksum;//68 unsigned char idstring[32];//100 unsigned char flags;//101 unsigned char list_name_offset;//102 unsigned char list_size_offset;//103 unsigned char dummy[69];//172 unsigned char mac_info[16];//188 unsigned short encoding;//190 unsigned short sub_encoding;//192 unsigned char filename[64];//256 }; #pragma pack(pop) bool send_init_oft2(file_transfer *ft, char* file) { aimString astr(file); unsigned short len = max(0x100, 0xc0 + astr.getTermSize()); oft2 *oft = (oft2*)alloca(len); memset(oft, 0, len); memcpy(oft->protocol_version, "OFT2", 4); oft->length = _htons(len); oft->type = 0x0101; oft->total_files = _htons(ft->pfts.totalFiles); oft->num_files_left = _htons(ft->pfts.totalFiles - ft->pfts.currentFileNumber); oft->total_parts = _htons(1); oft->parts_left = _htons(1); oft->total_size = _htonl(ft->pfts.totalBytes); oft->size = _htonl(ft->pfts.currentFileSize); oft->mod_time = _htonl(ft->pfts.currentFileTime); oft->checksum = _htonl(aim_oft_checksum_file(ft->pfts.tszCurrentFile)); oft->recv_RFchecksum = 0x0000FFFF; oft->RFchecksum = 0x0000FFFF; oft->recv_checksum = 0x0000FFFF; memcpy(oft->idstring, "Cool FileXfer", 13); oft->flags = 0x20; oft->list_name_offset = 0x1c; oft->list_size_offset = 0x11; oft->encoding = _htons(astr.isUnicode() ? 2 : 0); memcpy(oft->filename, astr.getBuf(), astr.getTermSize()); if (!ft->requester || ft->pfts.currentFileNumber) memcpy(oft->icbm_cookie, ft->icbm_cookie, 8); return Netlib_Send(ft->hConn, (char*)oft, len, 0) > 0; } void CAimProto::report_file_error(wchar_t *fname) { wchar_t errmsg[512]; wchar_t* error = mir_a2u(_strerror(nullptr)); mir_snwprintf(errmsg, TranslateT("Failed to open file: %s : %s"), fname, error); mir_free(error); ShowPopup((char*)errmsg, ERROR_POPUP | TCHAR_POPUP); } bool setup_next_file_send(file_transfer *ft) { wchar_t *file; struct _stati64 statbuf; for (;;) { file = ft->pfts.ptszFiles[ft->cf]; if (file == nullptr) return false; if (_wstat64(file, &statbuf) == 0 && (statbuf.st_mode & _S_IFDIR) == 0) break; ++ft->cf; } ft->pfts.tszCurrentFile = file; ft->pfts.currentFileSize = statbuf.st_size; ft->pfts.currentFileTime = statbuf.st_mtime; ft->pfts.currentFileProgress = 0; char* fnamea; T2Utf fname(file); if (ft->pfts.totalFiles > 1 && ft->file[0]) { size_t dlen = mir_strlen(ft->file); if (strncmp(fname, ft->file, dlen) == 0 && fname.get()[dlen] == '\\') { fnamea = &fname.get()[dlen + 1]; for (char *p = fnamea; *p; ++p) if (*p == '\\') *p = 1; } else fnamea = get_fname(fname); } else fnamea = get_fname(fname); send_init_oft2(ft, fnamea); return true; } int CAimProto::sending_file(file_transfer *ft, HANDLE hServerPacketRecver, NETLIBPACKETRECVER &packetRecv) { debugLogA("P2P: Entered file sending thread."); bool failed = true; bool failed_conn = false; if (!setup_next_file_send(ft)) return 2; debugLogA("Sent file information to buddy."); //start listen for packets stuff for (;;) { int recvResult = packetRecv.bytesAvailable - packetRecv.bytesUsed; if (recvResult <= 0) recvResult = Netlib_GetMorePackets(hServerPacketRecver, &packetRecv); if (recvResult == 0) { debugLogA("P2P: File transfer connection Error: 0"); break; } if (recvResult == SOCKET_ERROR) { failed_conn = true; debugLogA("P2P: File transfer connection Error: -1"); break; } if (recvResult > 0) { if (recvResult < 0x100) continue; oft2* recv_ft = (oft2*)&packetRecv.buffer[packetRecv.bytesUsed]; unsigned short pkt_len = _htons(recv_ft->length); if (recvResult < pkt_len) continue; packetRecv.bytesUsed += pkt_len; unsigned short type = _htons(recv_ft->type); if (type == 0x0202 || type == 0x0207) { debugLogA("P2P: Buddy Accepts our file transfer."); int fid = _wopen(ft->pfts.tszCurrentFile, _O_RDONLY | _O_BINARY, _S_IREAD); if (fid < 0) { report_file_error(ft->pfts.tszCurrentFile); return 2; } if (ft->pfts.currentFileProgress) _lseeki64(fid, ft->pfts.currentFileProgress, SEEK_SET); NETLIBSELECT tSelect = { 0 }; tSelect.hReadConns[0] = ft->hConn; ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->pfts); clock_t lNotify = clock(); for (;;) { char buffer[4096]; int bytes = _read(fid, buffer, sizeof(buffer)); if (bytes <= 0) break; if (Netlib_Send(ft->hConn, buffer, bytes, MSG_NODUMP) <= 0) break; ft->pfts.currentFileProgress += bytes; ft->pfts.totalProgress += bytes; if (clock() >= lNotify) { ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->pfts); if (Netlib_Select(&tSelect)) break; lNotify = clock() + 500; } } ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->pfts); debugLogA("P2P: Finished sending file bytes."); _close(fid); } else if (type == 0x0204) { // Handle file skip case if (ft->pfts.currentFileProgress == 0) { ft->pfts.totalProgress += ft->pfts.currentFileSize; } debugLogA("P2P: Buddy says they got the file successfully"); if ((ft->pfts.currentFileNumber + 1) < ft->pfts.totalFiles) { ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0); ++ft->pfts.currentFileNumber; ++ft->cf; if (!setup_next_file_send(ft)) { report_file_error(ft->pfts.tszCurrentFile); return 2; } } else { failed = _htonl(recv_ft->recv_bytes) != ft->pfts.currentFileSize; break; } } else if (type == 0x0205) { recv_ft = (oft2*)packetRecv.buffer; recv_ft->type = _htons(0x0106); ft->pfts.currentFileProgress = _htonl(recv_ft->recv_bytes); if (aim_oft_checksum_file(ft->pfts.tszCurrentFile, ft->pfts.currentFileProgress) != _htonl(recv_ft->recv_checksum)) { ft->pfts.currentFileProgress = 0; recv_ft->recv_bytes = 0; } debugLogA("P2P: Buddy wants us to start sending at a specified file point. (%I64u)", ft->pfts.currentFileProgress); if (Netlib_Send(ft->hConn, (char*)recv_ft, _htons(recv_ft->length), 0) <= 0) break; ft->pfts.totalProgress += ft->pfts.currentFileProgress; ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->pfts); } } } ft->success = !failed; return failed ? (failed_conn ? 1 : 2) : 0; } int CAimProto::receiving_file(file_transfer *ft, HANDLE hServerPacketRecver, NETLIBPACKETRECVER &packetRecv) { debugLogA("P2P: Entered file receiving thread."); bool failed = true; bool failed_conn = false; bool accepted_file = false; int fid = -1; oft2 *oft = nullptr; ft->pfts.tszWorkingDir = mir_utf8decodeW(ft->file); //start listen for packets stuff for (;;) { int recvResult = packetRecv.bytesAvailable - packetRecv.bytesUsed; if (recvResult <= 0) recvResult = Netlib_GetMorePackets(hServerPacketRecver, &packetRecv); if (recvResult == 0) { debugLogA("P2P: File transfer connection Error: 0"); break; } if (recvResult == SOCKET_ERROR) { failed_conn = true; debugLogA("P2P: File transfer connection Error: -1"); break; } if (recvResult > 0) { if (!accepted_file) { if (recvResult < 0x100) continue; oft2* recv_ft = (oft2*)&packetRecv.buffer[packetRecv.bytesUsed]; unsigned short pkt_len = _htons(recv_ft->length); if (recvResult < pkt_len) continue; packetRecv.bytesUsed += pkt_len; unsigned short type = _htons(recv_ft->type); if (type == 0x0101) { debugLogA("P2P: Buddy Ready to begin transfer."); oft = (oft2*)mir_realloc(oft, pkt_len); memcpy(oft, recv_ft, pkt_len); memcpy(oft->icbm_cookie, ft->icbm_cookie, 8); int buflen = pkt_len - 0x100 + 64; char *buf = (char*)mir_calloc(buflen + 2); unsigned short enc; ft->pfts.currentFileSize = _htonl(recv_ft->size); ft->pfts.totalBytes = _htonl(recv_ft->total_size); ft->pfts.currentFileTime = _htonl(recv_ft->mod_time); memcpy(buf, recv_ft->filename, buflen); enc = _htons(recv_ft->encoding); wchar_t *name; if (enc == 2) { wchar_t* wbuf = (wchar_t*)buf; wcs_htons(wbuf); for (wchar_t *p = wbuf; *p; ++p) { if (*p == 1) *p = '\\'; } name = mir_wstrdup(wbuf); } else { for (char *p = buf; *p; ++p) { if (*p == 1) *p = '\\'; } name = mir_a2u(buf); } mir_free(buf); wchar_t fname[256]; mir_snwprintf(fname, L"%s%s", ft->pfts.tszWorkingDir, name); mir_free(name); mir_free(ft->pfts.tszCurrentFile); ft->pfts.tszCurrentFile = mir_wstrdup(fname); ResetEvent(ft->hResumeEvent); if (ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FILERESUME, ft, (LPARAM)&ft->pfts)) WaitForSingleObject(ft->hResumeEvent, INFINITE); if (ft->pfts.tszCurrentFile) { wchar_t* dir = get_dir(ft->pfts.tszCurrentFile); CreateDirectoryTreeW(dir); mir_free(dir); oft->type = _htons(ft->pfts.currentFileProgress ? 0x0205 : 0x0202); const int flag = ft->pfts.currentFileProgress ? 0 : _O_TRUNC; fid = _wopen(ft->pfts.tszCurrentFile, _O_CREAT | _O_WRONLY | _O_BINARY | flag, _S_IREAD | _S_IWRITE); if (fid < 0) { report_file_error(fname); break; } accepted_file = ft->pfts.currentFileProgress == 0; if (ft->pfts.currentFileProgress) { bool the_same; oft->recv_bytes = _htonl(ft->pfts.currentFileProgress); oft->recv_checksum = _htonl(aim_oft_checksum_file(ft->pfts.tszCurrentFile)); the_same = oft->size == oft->recv_bytes && oft->checksum == oft->recv_checksum; if (the_same) { ft->pfts.totalProgress += ft->pfts.currentFileProgress; oft->type = _htons(0x0204); _close(fid); ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0); ++ft->pfts.currentFileNumber; ft->pfts.currentFileProgress = 0; } } } else { oft->type = _htons(0x0204); ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0); ++ft->pfts.currentFileNumber; ft->pfts.currentFileProgress = 0; } if (Netlib_Send(ft->hConn, (char*)oft, pkt_len, 0) == SOCKET_ERROR) break; if (ft->pfts.currentFileNumber >= ft->pfts.totalFiles && _htons(oft->type) == 0x0204) { failed = false; break; } } else if (type == 0x0106) { oft = (oft2*)mir_realloc(oft, pkt_len); memcpy(oft, recv_ft, pkt_len); ft->pfts.currentFileProgress = _htonl(oft->recv_bytes); ft->pfts.totalProgress += ft->pfts.currentFileProgress; _lseeki64(fid, ft->pfts.currentFileProgress, SEEK_SET); accepted_file = true; oft->type = _htons(0x0207); if (Netlib_Send(ft->hConn, (char*)oft, pkt_len, 0) == SOCKET_ERROR) break; } else break; } else { packetRecv.bytesUsed = packetRecv.bytesAvailable; _write(fid, packetRecv.buffer, packetRecv.bytesAvailable); ft->pfts.currentFileProgress += packetRecv.bytesAvailable; ft->pfts.totalProgress += packetRecv.bytesAvailable; ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->pfts); if (ft->pfts.currentFileSize == ft->pfts.currentFileProgress) { oft->type = _htons(0x0204); oft->recv_bytes = _htonl(ft->pfts.currentFileProgress); oft->recv_checksum = _htonl(aim_oft_checksum_file(ft->pfts.tszCurrentFile)); debugLogA("P2P: We got the file successfully"); Netlib_Send(ft->hConn, (char*)oft, _htons(oft->length), 0); if (_htons(oft->num_files_left) == 1) { failed = false; break; } else { accepted_file = false; _close(fid); ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0); ++ft->pfts.currentFileNumber; ft->pfts.currentFileProgress = 0; } } } } } if (accepted_file) _close(fid); mir_free(oft); ft->success = !failed; return failed ? (failed_conn ? 1 : 2) : 0; } void CAimProto::shutdown_file_transfers(void) { for (int i = 0; i < m_ft_list.getCount(); ++i) { file_transfer& ft = m_ft_list[i]; if (ft.hConn) Netlib_Shutdown(ft.hConn); } } ft_list_type::ft_list_type() : OBJLIST (10) {}; file_transfer* ft_list_type::find_by_cookie(char* cookie, MCONTACT hContact) { for (int i = 0; i < getCount(); ++i) { file_transfer *ft = items[i]; if (ft->hContact == hContact && memcmp(ft->icbm_cookie, cookie, 8) == 0) return ft; } return nullptr; } file_transfer* ft_list_type::find_by_port(unsigned short port) { for (int i = getCount(); i--; ) { file_transfer *ft = items[i]; if (ft->requester && ft->local_port == port) return ft; } return nullptr; } bool ft_list_type::find_by_ft(file_transfer *ft) { for (int i = 0; i < getCount(); ++i) if (items[i] == ft) return true; return false; } void ft_list_type::remove_by_ft(file_transfer *ft) { for (int i = 0; i < getCount(); ++i) { if (items[i] == ft) { remove(i); break; } } } file_transfer::file_transfer(MCONTACT hCont, char* nick, char* cookie) { memset(this, 0, sizeof(*this)); pfts.cbSize = sizeof(pfts); pfts.flags = PFTS_UNICODE; pfts.hContact = hCont; hContact = hCont; sn = mir_strdup(nick); if (cookie) memcpy(icbm_cookie, cookie, 8); else Utils_GetRandom(icbm_cookie, 8); hResumeEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); } file_transfer::~file_transfer() { stop_listen(); mir_free(file); mir_free(message); mir_free(sn); mir_free(pfts.tszWorkingDir); if (!sending) mir_free(pfts.tszCurrentFile); if (success && pfts.ptszFiles) { for (int i = 0; pfts.ptszFiles[i]; i++) mir_free(pfts.ptszFiles[i]); mir_free(pfts.ptszFiles); } CloseHandle(hResumeEvent); } void file_transfer::listen(CAimProto* ppro) { if (hDirectBoundPort) return; NETLIBBIND nlb = {}; nlb.pfnNewConnectionV2 = aim_direct_connection_initiated; nlb.pExtra = ppro; hDirectBoundPort = Netlib_BindPort(ppro->m_hNetlibPeer, &nlb); local_port = hDirectBoundPort ? nlb.wPort : 0; } void file_transfer::stop_listen(void) { if (hDirectBoundPort) { Netlib_CloseHandle(hDirectBoundPort); hDirectBoundPort = nullptr; local_port = 0; } }