1//
2// This file is part of the aMule Project.
3//
4// Copyright (c) 2004-2011 aMule Team ( admin@amule.org / http://www.amule.org )
5// Copyright (c) 2004-2011 Angel Vidal ( kry@amule.org )
6//
7// Any parts of this program derived from the xMule, lMule or eMule project,
8// or contributed by third-party developers are copyrighted by their
9// respective authors.
10//
11// This program is free software; you can redistribute it and/or modify
12// it under the terms of the GNU General Public License as published by
13// the Free Software Foundation; either version 2 of the License, or
14// (at your option) any later version.
15//
16// This program is distributed in the hope that it will be useful,
17// but WITHOUT ANY WARRANTY; without even the implied warranty of
18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19// GNU General Public License for more details.
20//
21// You should have received a copy of the GNU General Public License
22// along with this program; if not, write to the Free Software
23// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
24//
25
26#include "RemoteConnect.h"
27
28#include <common/MD5Sum.h>
29#include <common/Format.h>
30
31#include <wx/intl.h>
32
33using std::auto_ptr;
34
35DEFINE_LOCAL_EVENT_TYPE(wxEVT_EC_CONNECTION)
36
37CECLoginPacket::CECLoginPacket(const wxString& client, const wxString& version,
38							   bool canZLIB, bool canUTF8numbers, bool canNotify)
39:
40CECPacket(EC_OP_AUTH_REQ)
41{
42	AddTag(CECTag(EC_TAG_CLIENT_NAME, client));
43	AddTag(CECTag(EC_TAG_CLIENT_VERSION, version));
44	AddTag(CECTag(EC_TAG_PROTOCOL_VERSION, (uint64)EC_CURRENT_PROTOCOL_VERSION));
45
46	#ifdef EC_VERSION_ID
47	CMD4Hash versionhash;
48	wxCHECK2(versionhash.Decode(wxT(EC_VERSION_ID)), /* Do nothing. */);
49	AddTag(CECTag(EC_TAG_VERSION_ID, versionhash));
50	#endif
51
52	// Send capabilities:
53	// support ZLIB compression
54	if (canZLIB)		AddTag(CECEmptyTag(EC_TAG_CAN_ZLIB));
55	// support encoding of integers as UTF-8
56	if (canUTF8numbers) AddTag(CECEmptyTag(EC_TAG_CAN_UTF8_NUMBERS));
57	// client accepts push messages
58	if (canNotify)		AddTag(CECEmptyTag(EC_TAG_CAN_NOTIFY));
59}
60
61CECAuthPacket::CECAuthPacket(const wxString& pass)
62:
63CECPacket(EC_OP_AUTH_PASSWD)
64{
65	CMD4Hash passhash;
66	wxCHECK2(passhash.Decode(pass), /* Do nothing. */);
67	AddTag(CECTag(EC_TAG_PASSWD_HASH, passhash));
68}
69
70/*!
71 * Connection to remote core
72 *
73 */
74
75CRemoteConnect::CRemoteConnect(wxEvtHandler* evt_handler)
76:
77CECMuleSocket(evt_handler != 0),
78m_ec_state(EC_INIT),
79m_req_fifo(),
80// Give application some indication about how fast requests are served
81// When request fifo contain more that certain number of entries, it may
82// indicate that either core or network is slowing us down
83m_req_count(0),
84// This is not mean to be absolute limit, because we can't drop requests
85// out of calling context; it is just signal to application to slow down
86m_req_fifo_thr(20),
87m_notifier(evt_handler),
88m_canZLIB(false),
89m_canUTF8numbers(false),
90m_canNotify(false)
91{
92}
93
94void CRemoteConnect::SetCapabilities(bool canZLIB, bool canUTF8numbers, bool canNotify)
95{
96	m_canZLIB = canZLIB;
97	if (canZLIB) {
98		m_my_flags |= EC_FLAG_ZLIB;
99	}
100	m_canUTF8numbers = canUTF8numbers;
101	if (canUTF8numbers) {
102		m_my_flags |= EC_FLAG_UTF8_NUMBERS;
103	}
104	m_canNotify = canNotify;
105}
106
107bool CRemoteConnect::ConnectToCore(const wxString &host, int port,
108	const wxString &WXUNUSED(login), const wxString &pass,
109	const wxString& client, const wxString& version)
110{
111	m_connectionPassword = pass;
112
113	m_client = client;
114	m_version = version;
115
116	// don't even try to connect without a valid password
117	if (m_connectionPassword.IsEmpty() || m_connectionPassword == wxT("d41d8cd98f00b204e9800998ecf8427e")) {
118		m_server_reply = _("You must specify a non-empty password.");
119		return false;
120	} else {
121		CMD4Hash hash;
122		if (!hash.Decode(m_connectionPassword)) {
123			m_server_reply = _("Invalid password, not a MD5 hash!");
124			return false;
125		} else if (hash.IsEmpty()) {
126			m_server_reply = _("You must specify a non-empty password.");
127			return false;
128		}
129	}
130
131	wxIPV4address addr;
132
133	addr.Hostname(host);
134	addr.Service(port);
135
136	if (ConnectSocket(addr)) {
137		CECLoginPacket login_req(m_client, m_version, m_canZLIB, m_canUTF8numbers, m_canNotify);
138
139		std::auto_ptr<const CECPacket> getSalt(SendRecvPacket(&login_req));
140		m_ec_state = EC_REQ_SENT;
141
142		ProcessAuthPacket(getSalt.get());
143
144		CECAuthPacket passwdPacket(m_connectionPassword);
145
146		std::auto_ptr<const CECPacket> reply(SendRecvPacket(&passwdPacket));
147		m_ec_state = EC_PASSWD_SENT;
148
149		return ProcessAuthPacket(reply.get());
150	} else if (m_notifier) {
151		m_ec_state = EC_CONNECT_SENT;
152	} else {
153		return false;
154	}
155
156	return true;
157}
158
159bool CRemoteConnect::IsConnectedToLocalHost()
160{
161	wxIPV4address addr;
162	return GetPeer(addr) ? addr.IsLocalHost() : false;
163}
164
165void CRemoteConnect::WriteDoneAndQueueEmpty()
166{
167}
168
169void CRemoteConnect::OnConnect() {
170	if (m_notifier) {
171		wxASSERT(m_ec_state == EC_CONNECT_SENT);
172		CECLoginPacket login_req(m_client, m_version, m_canZLIB, m_canUTF8numbers, m_canNotify);
173		CECSocket::SendPacket(&login_req);
174
175		m_ec_state = EC_REQ_SENT;
176	} else {
177		// do nothing, calling code will take from here
178	}
179}
180
181void CRemoteConnect::OnLost() {
182	if (m_notifier) {
183		// Notify app of failure
184		wxECSocketEvent event(wxEVT_EC_CONNECTION,false,_("Connection failure"));
185		m_notifier->AddPendingEvent(event);
186	}
187}
188
189const CECPacket *CRemoteConnect::OnPacketReceived(const CECPacket *packet, uint32 trueSize)
190{
191	CECPacket *next_packet = 0;
192	m_req_count--;
193	packet->DebugPrint(true, trueSize);
194	switch(m_ec_state) {
195		case EC_REQ_SENT:
196			if (ProcessAuthPacket(packet)) {
197				CECAuthPacket passwdPacket(m_connectionPassword);
198				CECSocket::SendPacket(&passwdPacket);
199				m_ec_state = EC_PASSWD_SENT;
200			}
201			break;
202		case EC_PASSWD_SENT:
203			ProcessAuthPacket(packet);
204			break;
205		case EC_OK:
206			if ( !m_req_fifo.empty() ) {
207				CECPacketHandlerBase *handler = m_req_fifo.front();
208				m_req_fifo.pop_front();
209				if ( handler ) {
210					handler->HandlePacket(packet);
211				}
212			} else {
213				printf("EC error - packet received, but request fifo is empty\n");
214			}
215			break;
216		default:
217			break;
218	}
219
220	// no reply by default
221	return next_packet;
222}
223
224/*
225 * Our requests are served by core in FCFS order. And core always replies. So, even
226 * if we're not interested in reply, we preserve place in request fifo.
227 */
228void CRemoteConnect::SendRequest(CECPacketHandlerBase *handler, const CECPacket *request)
229{
230	m_req_count++;
231	m_req_fifo.push_back(handler);
232	CECSocket::SendPacket(request);
233}
234
235void CRemoteConnect::SendPacket(const CECPacket *request)
236{
237	SendRequest(0, request);
238}
239
240bool CRemoteConnect::ProcessAuthPacket(const CECPacket *reply) {
241	bool result = false;
242
243	if (!reply) {
244		m_server_reply = _("EC connection failed. Empty reply.");
245		CloseSocket();
246	} else {
247		if ((m_ec_state == EC_REQ_SENT) && (reply->GetOpCode() == EC_OP_AUTH_SALT)) {
248				const CECTag *passwordSalt = reply->GetTagByName(EC_TAG_PASSWD_SALT);
249				if ( NULL != passwordSalt) {
250					wxString saltHash = MD5Sum(CFormat(wxT("%lX")) % passwordSalt->GetInt()).GetHash();
251					m_connectionPassword = MD5Sum(m_connectionPassword.Lower() + saltHash).GetHash();
252					m_ec_state = EC_SALT_RECEIVED;
253					return true;
254				} else {
255					m_server_reply = _("External Connection: Bad reply, handshake failed. Connection closed.");
256					m_ec_state = EC_FAIL;
257					CloseSocket();
258				}
259		} else if ((m_ec_state == EC_PASSWD_SENT) && (reply->GetOpCode() == EC_OP_AUTH_OK)) {
260			m_ec_state = EC_OK;
261			result = true;
262			if (reply->GetTagByName(EC_TAG_SERVER_VERSION)) {
263				m_server_reply = _("Succeeded! Connection established to aMule ") +
264					reply->GetTagByName(EC_TAG_SERVER_VERSION)->GetStringData();
265			} else {
266				m_server_reply = _("Succeeded! Connection established.");
267			}
268		}else {
269			m_ec_state = EC_FAIL;
270			const CECTag *reason = reply->GetTagByName(EC_TAG_STRING);
271			if (reason != NULL) {
272				m_server_reply = wxString(_("External Connection: Access denied because: ")) +
273					wxGetTranslation(reason->GetStringData());
274			} else {
275				m_server_reply = _("External Connection: Handshake failed.");
276			}
277			CloseSocket();
278		}
279	}
280	if ( m_notifier ) {
281		wxECSocketEvent event(wxEVT_EC_CONNECTION, result, m_server_reply);
282		m_notifier->AddPendingEvent(event);
283	}
284	return result;
285}
286
287/******************** EC API ***********************/
288
289void CRemoteConnect::StartKad() {
290	CECPacket req(EC_OP_KAD_START);
291	SendPacket(&req);
292}
293
294void CRemoteConnect::StopKad() {
295	CECPacket req(EC_OP_KAD_STOP);
296	SendPacket(&req);
297}
298
299void CRemoteConnect::ConnectED2K(uint32 ip, uint16 port) {
300	CECPacket req(EC_OP_SERVER_CONNECT);
301	if (ip && port) {
302		req.AddTag(CECTag(EC_TAG_SERVER, EC_IPv4_t(ip, port)));
303	}
304	SendPacket(&req);
305}
306
307void CRemoteConnect::DisconnectED2K() {
308	CECPacket req(EC_OP_SERVER_DISCONNECT);
309	SendPacket(&req);
310}
311
312void CRemoteConnect::RemoveServer(uint32 ip, uint16 port) {
313	CECPacket req(EC_OP_SERVER_REMOVE);
314	if (ip && port) {
315		req.AddTag(CECTag(EC_TAG_SERVER, EC_IPv4_t(ip, port)));
316	}
317	SendPacket(&req);
318}
319// File_checked_for_headers
320