1 /*
2     qqnotifysocket.cpp - Notify Socket for the QQ Protocol
3     forked from msnnotifysocket.cpp
4 
5     Copyright (c) 2006      by Hui Jin <blueangel.jin@gmail.com>
6     Copyright (c) 2002      by Duncan Mac-Vicar Prett <duncan@kde.org>
7     Copyright (c) 2002-2003 by Martijn Klingens       <klingens@kde.org>
8     Copyright (c) 2002-2005 by Olivier Goffart        <ogoffart at kde.org>
9     Copyright (c) 2005      by Michaël Larouche       <larouche@kde.org>
10     Copyright (c) 2005      by Gregg Edghill          <gregg.edghill@gmail.com>
11 
12     Kopete    (c) 2002-2005 by the Kopete developers  <kopete-devel@kde.org>
13 
14     Portions taken from
15     KMerlin   (c) 2001      by Olaf Lueg              <olueg@olsd.de>
16 
17     *************************************************************************
18     *                                                                       *
19     * This program is free software; you can redistribute it and/or modify  *
20     * it under the terms of the GNU General Public License as published by  *
21     * the Free Software Foundation; either version 2 of the License, or     *
22     * (at your option) any later version.                                   *
23     *                                                                       *
24     *************************************************************************
25 */
26 
27 #include "qqnotifysocket.h"
28 
29 #include <kdebug.h>
30 #include <QHostAddress>
31 
32 #include "kopetestatusmessage.h"
33 #include "libeva.h"
34 
35 #include "qqaccount.h"
36 
QQNotifySocket(QQAccount * account,const QString & password)37 QQNotifySocket::QQNotifySocket( QQAccount *account, const QString &password )
38 : QQSocket( account )
39 {
40 	m_account = account;
41 	m_newstatus = Kopete::OnlineStatus::Offline;
42 	Eva::ByteArray pwd( password.toAscii().data(), password.size() );
43 	m_passwordKey = Eva::Packet::QQHash(pwd);
44 	pwd.release(); // the data is handled in QT
45 	m_loginMode = Eva::NormalLogin;
46 
47 	// FIXME: more error-checking.
48 	m_qqId = account->accountId().toInt();
49 
50 	m_heartbeat = new QTimer(this);
51 	QObject::connect( m_heartbeat, SIGNAL(timeout()), SLOT(heartbeat()) );
52 }
53 
~QQNotifySocket()54 QQNotifySocket::~QQNotifySocket()
55 {
56 	kDebug(14140) ;
57 	if( m_heartbeat->isActive() )
58 		m_heartbeat->stop();
59 
60 	delete m_heartbeat;
61 }
62 
doneConnect()63 void QQNotifySocket::doneConnect()
64 {
65 	// setup the status first
66 	QQSocket::doneConnect();
67 
68 	kDebug( 14140 ) << "Negotiating server protocol version";
69 	if( m_token.size() )
70 		sendPacket( Eva::login( m_qqId, m_id++, m_passwordKey, m_token, m_loginMode ) );
71 	else
72 		sendPacket( Eva::loginToken(m_qqId, m_id++) );
73 }
74 
disconnect()75 void QQNotifySocket::disconnect()
76 {
77 	kDebug(14140) << "online status =" <<
78 		onlineStatus() << endl;
79 	// FIXME: double check the logic, please.
80 	if(	m_disconnectReason==Kopete::Account::Unknown )
81 		m_disconnectReason=Kopete::Account::Manual;
82 	// sendGoodbye, shall we setup the status as well ?
83 	if( onlineStatus() == Connected )
84 		sendGoodbye();
85 
86 	// the socket is not connected yet, so I should force the signals
87 	if ( onlineStatus() == Disconnected || onlineStatus() == Connecting )
88 		emit socketClosed();
89 	else
90 		QQSocket::disconnect();
91 }
92 
handleError(uint code,uint id)93 void QQNotifySocket::handleError( uint code, uint id )
94 {
95 	kDebug(14140) ;
96 
97 	// TODO: Add support for all of these!
98 	switch( code )
99 	{
100 	default:
101 		QQSocket::handleError( code, id );
102 		break;
103 	}
104 }
105 
106 // Core functions
handleIncomingPacket(const QByteArray & rawData)107 void QQNotifySocket::handleIncomingPacket( const QByteArray& rawData )
108 {
109 	kDebug( 14140 ) << rawData;
110 	Eva::Packet packet( rawData.data(), rawData.size() );
111 	Eva::ByteArray text;
112 
113 	Eva::ByteArray initKey((char*) Eva::Packet::getInitKey(), 16 );
114 	initKey.release();
115 
116 	kDebug( 14140 ) << "command = " << packet.command();
117 	switch( packet.command() )
118 	{
119 		case Eva::Command::RequestLoginToken :
120 			text = Eva::Packet::loginToken( packet.body() );
121 			break;
122 
123 		case Eva::Command::Login :
124 			text = Eva::Packet::decrypt( packet.body(), m_passwordKey );
125 			if( text.size() == 0 )
126 				text = Eva::Packet::decrypt( packet.body(), initKey );
127 			break;
128 
129 		default:
130 			text = Eva::Packet::decrypt( packet.body(), m_sessionKey );
131 			if ( text.size() == 0 )
132 				text = Eva::Packet::decrypt( packet.body(), m_passwordKey );
133 	}
134 
135 	kDebug( 14140 ) << "text = " << QByteArray( text.c_str(), text.size() );
136 
137 	switch( packet.command() )
138 	{
139 		// FIXME: use table-driven pattern ?
140 		case Eva::Command::Logout :
141 		case Eva::Command::Heartbeat:
142 			break;
143 		case Eva::Command::UpdateInfo :
144 		case Eva::Command::Search :
145 		case Eva::Command::UserInfo :
146 		{
147 			// map std::map to QMap
148 			std::map<const char*, std::string, Eva::ltstr> dict = Eva::Packet::contactDetail(text);
149 			QMap<const char*, QByteArray> qmap;
150 
151 			QString id = QString( dict["qqId"].c_str() );
152 			std::map<const char*, std::string, Eva::ltstr>::const_iterator it = dict.begin();
153 
154 			for( ; it != dict.end(); it++ )
155 				qmap.insert( (*it).first, QByteArray((*it).second.c_str() ) );
156 
157 			emit contactDetailReceived(id, qmap);
158 		}
159 
160 			break;
161 		case Eva::Command::AddBuddy:
162 		case Eva::Command::RemoveBuddy:
163 		case Eva::Command::AuthInvite :
164 			break;
165 		case Eva::Command::ChangeStatus :
166 			if( Eva::Packet::replyCode(text) == Eva::ChangeStatusOK )
167 			{
168 				kDebug( 14140 ) << "ChangeStatus ok";
169 				emit statusChanged( m_newstatus );
170 			}
171 			else // TODO: Debug me.
172 				disconnect();
173 			break;
174 
175 		case Eva::Command::AckSysMsg :
176 		case Eva::Command::SendMsg :
177 			break;
178 		case Eva::Command::ReceiveMsg :
179 		{
180 			Eva::MessageEnvelop envelop(text);
181 			kDebug(14140) << "Received message from " << envelop.sender << " to " << envelop.receiver << " type=" << envelop.type;
182 			kDebug(14140) << "seq = " << envelop.sequence << " from " << envelop.ip << ":" << envelop.port;
183 
184 			sendPacket( Eva::messageReply(m_qqId, packet.sequence(), m_sessionKey, Eva::Packet::replyKey(text) ));
185 			Eva::ByteArray body( text.data() + sizeof(envelop), text.size() - sizeof(envelop) );
186 			body.release();
187 
188 			// TODO: check whether this is a duplicated message
189 			switch( envelop.type )
190 			{
191 				case 0x0010:
192 					kDebug(14140) << "command 0x0010: " << QByteArray( body.c_str(), body.size() );
193 					break;
194 				case Eva::RcvFromBuddy:
195 				{
196 					Eva::MessageHeader mh(body);
197 					kDebug(14140) << "message header:";
198 					kDebug(14140) << "ver:" << mh.version << " sender:" << mh.sender
199 						<< " receiver:" << mh.receiver
200 						<< " type:" << mh.type << " seq:" << mh.sequence
201 						<< " timestamp:" << mh.timestamp << " avatar:" << mh.avatar
202 						<< endl;
203 
204 					if( mh.receiver != m_qqId )
205 					{
206 						kDebug(14140) << "receive other(" << mh.receiver <<")'s message";
207 						break;
208 					}
209 
210 					// FIXME: replace the magic number!
211 					// FIXME: the code stinks!
212 					Eva::uchar* p = body.data()+36;
213 					bool hasFontStyle = p[3] != 0;
214 					Eva::uchar replyType = p[8];
215 
216 					// clear compiler warnings
217 					Q_UNUSED(hasFontStyle);
218 					Q_UNUSED(replyType);
219 
220 					Eva::ByteArray msg(body.size());
221 					p += 9;
222 
223 					while( *p )
224 						msg += *p++;
225 					msg += char(0x0);
226 
227 					kDebug(14140) << "message received: " << msg.data();
228 					// FIXME: use a function to generate guid!
229 					emit messageReceived(mh, msg);
230 
231 					break;
232 				}
233 				default:
234 					break;
235 			}
236 			break;
237 		}
238 
239 		case Eva::Command::RemoveMe :
240 			break;
241 
242 		case Eva::Command::RequestKey :
243 		{
244 			char type = text.data()[0];
245 			char reply = text.data()[1];
246 
247 			if( reply == Eva::RequestKeyOK )
248 			{
249 				// NOTE: the type of the key supports TransferKey only.
250 				if( type == Eva::TransferKey )
251 				{
252 					m_transferKey = Eva::Packet::transferKey( text );
253 					m_transferToken = Eva::Packet::transferToken( text );
254 					kDebug( 14140 ) << "transferKey =" << QByteArray( m_transferKey.c_str(), m_transferKey.size());
255 					kDebug( 14140 ) << "transferToken =" << QByteArray( m_transferToken.c_str(), m_transferToken.size());
256 
257 				}
258 			}
259 			break;
260 		}
261 
262 		case Eva::Command::GetCell :
263 			break;
264 
265 		case Eva::Command::Login :
266 			switch( Eva::Packet::replyCode(text)  )
267 			{
268 				case Eva::LoginOK:
269 					kDebug( 14140 ) << "Bingo! QQ:#" << m_qqId << " logged in!";
270 					// show off some meta data :
271 					m_sessionKey = Eva::Packet::sessionKey(text);
272 					kDebug( 14140 ) << "sessionKey = " <<
273 						QByteArray( m_sessionKey.c_str(), m_sessionKey.size() ) << endl;
274 
275 					kDebug( 14140 )  << "remote IP: " << QHostAddress( Eva::Packet::remoteIP(text) ).toString();
276 					kDebug( 14140 )  << "remote port: " << Eva::Packet::remotePort(text);
277 					kDebug( 14140 )  << "local IP: " << QHostAddress( Eva::Packet::localIP(text) ).toString();
278 					kDebug( 14140 )  << "local port: " << Eva::Packet::localPort(text);
279 					kDebug( 14140 )  << "login time: " << Eva::Packet::loginTime(text);
280 					kDebug( 14140 )  << "last login from: " << QHostAddress( Eva::Packet::lastLoginFrom(text) ).toString();
281 					kDebug( 14140 )  << "last login time: " << Eva::Packet::lastLoginTime(text);
282 
283 					// start the heartbeat
284 					if( !m_heartbeat->isActive() )
285 					{
286 						m_heartbeat->setSingleShot(false);
287 						m_heartbeat->start(60000);
288 					}
289 
290 					// FIXME: refactor me!
291 					emit newContactList();
292 					// FIXME: We might login in as invisible as well.
293 					m_newstatus = Kopete::OnlineStatus::Online;
294 					sendPacket( Eva::statusUpdate( m_qqId, m_id++, m_sessionKey, Eva::Online) );
295 					sendPacket( Eva::transferKey( m_qqId, m_id++, m_sessionKey) );
296 
297 					// get the meta data for myself
298 					contactDetail(m_qqId);
299 
300 					// fetch the online contacts
301 					sendListOnlineContacts();
302 
303 					break;
304 
305 				case Eva::LoginRedirect :
306 					kDebug( 14140 ) << "Redirect to "
307 						<< QHostAddress(Eva::Packet::redirectedIP(text)).toString()
308 						<< " : " << Eva::Packet::redirectedPort(text) << endl;
309 					disconnect();
310 					connect( QHostAddress( Eva::Packet::redirectedIP(text) ).toString(), Eva::Packet::redirectedPort(text) );
311 					break;
312 
313 				case Eva::LoginWrongPassword :
314 					kDebug( 14140 )  << "password is wrong. ";
315 					break;
316 
317 				case Eva::LoginMiscError :
318 					kDebug( 14140 )  << "unknown error. ";
319 					break;
320 
321 				default:
322 					kDebug( 14140 ) << "Bad, we are not supposed to be here !";
323 					break;
324 			}
325 
326 			break;
327 
328 		case Eva::Command::AllContacts:
329 			/*
330 			{
331 				len = 2;
332 				while( len < text.size() )
333 					emit contactList( Eva::contactInfo( text.data(), len ) );
334 				short pos = ntohs( Eva::type_cast<short> (text.data()) );
335 
336 				if( pos != Eva::ContactListEnd )
337 					sendPacket( Eva::allContacts( m_qqId, m_id++, m_sessionKey, pos ) );
338 			}
339 			*/
340 			break;
341 		case Eva::Command::ContactsOnline :
342 
343 			break;
344 		case Eva::Command::GetCell2 :
345 		case Eva::Command::SIP :
346 		case Eva::Command::Test :
347 			break;
348 		case Eva::Command::GroupNames :
349 			groupNames( text );
350 			break;
351 
352 		case Eva::Command::UploadGroups :
353 		case Eva::Command::Memo :
354 			break;
355 		case Eva::Command::DownloadGroups :
356 			groupInfos( text );
357 			break;
358 
359 		case Eva::Command::GetLevel :
360 			break;
361 
362 		case Eva::Command::RequestLoginToken :
363 			m_token = text;
364 			kDebug( 14140 ) << "command = " << packet.command() << ": token = " <<
365 				QByteArray ( m_token.c_str(), m_token.size() ) << endl;
366 			sendPacket( Eva::login( m_qqId, m_id++, m_passwordKey, m_token, m_loginMode ) );
367 			break;
368 
369 		case Eva::Command::ExtraInfo :
370 		case Eva::Command::Signature :
371 		case Eva::Command::ReceiveSysMsg :
372 			break;
373 		case Eva::Command::ContactStausChanged :
374 		{
375 			kDebug( 14140 ) << "contact status signal";
376 			Eva::ContactStatus cs(text.data());
377 			kDebug( 14140 ) << "contact status detail:";
378 			kDebug( 14140 ) << "id = " << cs.qqId << " status = " << cs.status;
379 			emit contactStatusChanged( cs );
380 			break;
381 		}
382 		default:
383 			break;
384 
385 	}
386 }
387 
contactDetail(Eva::uint qqId)388 void QQNotifySocket::contactDetail(Eva::uint qqId)
389 {
390 	sendPacket( Eva::contactDetail( m_qqId, m_id++, m_sessionKey, qqId) );
391 }
392 
sendTextMessage(const uint toId,const QByteArray & message)393 void QQNotifySocket::sendTextMessage( const uint toId, const QByteArray& message )
394 {
395 	// Translate the message to Eva::ByteArray
396 	// TODO: color and font
397 	kDebug( 14140 ) << "Send the message: " << message << " from " << m_qqId << " to " << toId;
398 	// attach the ByteArray to QString:
399 	// FIXME: Add an adapter to ByteArray
400 	Eva::ByteArray text( (char*)message.data(), message.size() );
401 	text.release();
402 
403 	Eva::ByteArray packet = Eva::textMessage(m_qqId, m_id++, m_sessionKey, toId, m_transferKey, text );
404 	QQSocket::sendPacket( QByteArray( packet.c_str(), packet.size()) );
405 }
406 
heartbeat()407 void QQNotifySocket::heartbeat()
408 {
409 	sendPacket( Eva::heartbeat( m_qqId, m_id++, m_sessionKey ));
410 }
411 
sendListOnlineContacts(uint pos)412 void QQNotifySocket::sendListOnlineContacts(uint pos)
413 {
414 	sendPacket( Eva::onlineContacts( m_qqId, m_id++, m_sessionKey, pos) );
415 }
416 
groupNames(const Eva::ByteArray & text)417 void QQNotifySocket::groupNames( const Eva::ByteArray& text )
418 {
419 	QStringList ql;
420 	std::list< std::string > l = Eva::Packet::groupNames( text );
421 	for( std::list<std::string>::const_iterator it = l.begin(); it != l.end(); it++ )
422 		ql.append( QString( (*it).c_str() ) );
423 
424 	kDebug(14140) ;
425 	emit groupNames( ql );
426 }
427 
groupInfos(const Eva::ByteArray & text)428 void QQNotifySocket::groupInfos( const Eva::ByteArray& text )
429 {
430 	kDebug(14140) ;
431 	std::list< Eva::GroupInfo > gis = Eva::Packet::groupInfos( text );
432 	// TODO: send it one by one.
433 	for( std::list< Eva::GroupInfo >::const_iterator it = gis.begin();
434 		it != gis.end(); it++ )
435 	{
436 		kDebug(14140) << "buddy: qqId = " << (*it).qqId << " type = " << (*it).type
437 			<< " groupId = " << (*it).groupId << endl;
438 			emit contactInGroup( (*it).qqId, (*it).type, (*it).groupId );
439 	}
440 
441 	int next = Eva::Packet::nextGroupId( text );
442 	if( next )
443 		sendDownloadGroups( next );
444 }
445 
doGetContactStatuses(const Eva::ByteArray & text)446 void QQNotifySocket::doGetContactStatuses( const Eva::ByteArray& text )
447 {
448 	kDebug(14140) ;
449 	Eva::uchar pos = Eva::ContactListBegin;
450 	std::list< Eva::ContactStatus > css = Eva::Packet::onlineContacts( text, pos );
451 	for( std::list< Eva::ContactStatus >::const_iterator it = css.begin();
452 		it != css.end(); it++ )
453 	{
454 		kDebug(14140) << "buddy: qqId = " << (*it).qqId << " status = " << (*it).status;
455 		emit contactStatusChanged(*it);
456 	}
457 
458 	if( pos != 0xff )
459 		sendListOnlineContacts(pos);
460 }
461 
462