/*
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;
}
}