1 /*
2 * This file is part of Licq, an instant messaging client for UNIX.
3 * Copyright (C) 2011-2012 Licq developers <licq-dev@googlegroups.com>
4 *
5 * Licq is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * Licq is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with Licq; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 /*
21 * These functions attempt to identify the user client based on published
22 * capabilities and signatures
23 *
24 * Most of this is taken from Miranda IcqOscar protocol 0.9.47
25 */
26
27 #include "icq.h"
28
29 #include <cstring>
30 #include <sstream>
31
32 #include "defines.h"
33
34 using std::string;
35 using std::stringstream;
36 using namespace LicqIcq;
37
findCapability(const char * caps,int capSize,const uint8_t * needle,int needleSize=CAP_LENGTH)38 const char* findCapability(const char* caps, int capSize,
39 const uint8_t* needle, int needleSize = CAP_LENGTH)
40 {
41 while (capSize >= CAP_LENGTH)
42 {
43 if (memcmp(caps, needle, needleSize) == 0)
44 return caps;
45 caps += CAP_LENGTH;
46 capSize -= CAP_LENGTH;
47 }
48 return NULL;
49 }
50
findCapability(const char * caps,int capSize,const char * needle,int needleSize=CAP_LENGTH)51 const char* findCapability(const char* caps, int capSize,
52 const char* needle, int needleSize = CAP_LENGTH)
53 {
54 return findCapability(
55 caps, capSize, reinterpret_cast<const uint8_t*>(needle), needleSize);
56 }
57
appendVersion(stringstream & buf,int min,const char * ver)58 static void appendVersion(stringstream& buf, int min, const char* ver)
59 {
60 buf << (int)ver[0];
61 if (ver[1] != 0 || min >= 2)
62 buf << '.' << (int)ver[1];
63 if (ver[2] != 0 || min >= 3)
64 buf << '.' << (int)ver[2];
65 if (ver[3] != 0 || min >= 4)
66 buf << '.' << (int)ver[3];
67 }
68
mirandaMod(stringstream & buf,const char * cap,bool unicode,const char * name)69 static string mirandaMod(stringstream& buf, const char* cap, bool unicode, const char* name)
70 {
71 buf << "Miranda IM ";
72 if (cap[4] != 0 || cap[5] != 0 || cap[6] != 0 || cap[7] != 0)
73 appendVersion(buf, 3, cap+4);
74 if (unicode)
75 buf << " Unicode";
76 buf << " (" << name << " v";
77 appendVersion(buf, 3, cap+8);
78 buf << ')';
79
80 if (memcmp(cap+12, "\x5A\xFE\xC0\xDE", 4) == 0)
81 buf << " + SecureIM";
82
83 return buf.str();
84 }
85
detectFromCapSign(const char * caps,int capSize,unsigned ts1,unsigned ts2,unsigned ts3)86 static string detectFromCapSign(const char* caps, int capSize, unsigned ts1, unsigned ts2, unsigned ts3)
87 {
88 const char* cap = caps;
89 int capsLeft = capSize / CAP_LENGTH;
90
91 stringstream buf;
92
93 while (capsLeft > 0)
94 {
95 if (memcmp(cap, ICQ_CAPABILITY_LICQxVER, 12) == 0)
96 {
97 buf << "Licq " << (int)cap[12] << '.' << (int)cap[13] << '.' << (int)cap[14];
98 if (cap[15] == 1)
99 buf << "/SSL";
100 return buf.str();
101 }
102 else if (memcmp(cap, "MirandaM", 8) == 0)
103 {
104 buf << "Miranda IM ";
105 if (cap[8] != 0 || cap[9] != 0 || cap[10] != 0 || cap[11] != 0)
106 appendVersion(buf, 3, cap+8);
107 if (ts1 == 0x7FFFFFFF)
108 buf << " Unicode";
109 buf << " (ICQ v";
110 appendVersion(buf, 3, cap+12);
111 buf << ')';
112
113 if (findCapability(caps, capSize, "icqj", 4))
114 {
115 buf << " (s7 & sss)";
116 if (findCapability(caps, capSize, "icqj Secure IM", 14))
117 buf << " + SecureIM";
118 }
119 else if ((ts1 & 0x7FFFFFFF) == 0x7FFFFFFF)
120 {
121 if (findCapability(caps, capSize, "MirandaMobile\0\0\0"))
122 buf << " (Mobile)";
123 if (ts3 == 0x5AFEC0DE)
124 buf << " + SecureIM";
125 }
126 return buf.str();
127 }
128 else if (memcmp(cap, "MirandaA", 8) == 0)
129 {
130 buf << "Miranda IM ";
131 if (cap[8] != 0 || cap[9] != 0 || cap[10] != 0 || cap[11] != 0)
132 appendVersion(buf, 3, cap+8);
133 buf << " (AimOscar v";
134 appendVersion(buf, 3, cap+12);
135 buf << ')';
136 return buf.str();
137 }
138 else if (memcmp(cap, "icqj", 4) == 0)
139 return mirandaMod(buf, cap, ts3==0x80000000, "ICQ S7 & SSS");
140
141 else if (memcmp(cap, "sinj", 4) == 0)
142 return mirandaMod(buf, cap, ts3==0x80000000, "ICQ S!N");
143
144 else if (memcmp(cap, "icqp", 4) == 0)
145 return mirandaMod(buf, cap, ts3==0x80000000, "ICQ Plus");
146
147 else if (memcmp(cap, "J2ME m@agent", 12) == 0)
148 {
149 buf << "Mail.ru Agent (Java) v";
150 appendVersion(buf, 2, cap+13);
151 return buf.str();
152 }
153 else if (memcmp(cap, "\x97\xB1\x27\x51\x24\x3C\x43\x34\xAD\x22\xD6\xAB\xF7\x3F\x14\x09", 16) == 0 ||
154 memcmp(cap, "\xF2\xE7\xC7\xF4\xFE\xAD\x4D\xFB\xB2\x35\x36\x79\x8B\xDF\0\0", 16) == 0)
155 {
156 if (findCapability(caps, capSize, ICQ_CAPABILITY_FILE))
157 return "Trillian Astra";
158 else if (findCapability(caps, capSize, ICQ_CAPABILITY_RTFxMSGS))
159 return "Trillian v3";
160 else if (findCapability(caps, capSize, "\x01\x38\xCA\x7B\x76\x9A\x49\x15\x88\xF2\x13\xFC\x00\x97\x9E\xA8"))
161 return "Trillian Astra";
162 else
163 return "Trillian";
164 }
165 else if (memcmp(cap, "\x97\xB1\x27\x51\x24\x3C\x43\x34\xAD\x22\xD6\xAB\xF7\x3F\x14", 15) == 0 &&
166 (unsigned char)cap[15] != 0x92 && ((unsigned char)cap[15] >= 0x20 || cap[15] == 0))
167 {
168 if ((cap[15] & 0xC0) == 0 || cap[15] == 0x40)
169 return "Kopete";
170
171 buf << "SIM " << ((((unsigned)cap[15])>>6)-1) << '.' << (cap[15]&0x1F);
172 return buf.str();
173 }
174 else if (memcmp(cap, "SIM client ", 12) == 0)
175 {
176 buf << "SIM " << (int)cap[12] << '.' << (int)cap[13] << '.' << (int)cap[14] << '.' << (cap[15]&0x0F);
177 if (cap[15] & 0x80)
178 buf << " (Win32)";
179 else if (cap[15] & 0x40)
180 buf << " (MacOS X)";
181 return buf.str();
182 }
183 else if (memcmp(cap, "Kopete ICQ ", 12) == 0)
184 {
185 buf << "Kopete " << (int)cap[12] << '.' << (int)cap[13] << '.' << (cap[14]*100 + cap[15]);
186 return buf.str();
187 }
188 else if (memcmp(cap, "climm\xA9 R.K. ", 12) == 0)
189 {
190 buf << "climm " << (cap[12]&0x7F) << '.' << (int)cap[13] << '.' << (int)cap[14] << '.' << (int)cap[15];
191 if (cap[12] & 0x80)
192 buf << " alpha";
193 if (ts3 == 0x02000020)
194 buf << " (Win32)";
195 else if (ts3 == 0x03000800)
196 buf << " (MacOS X)";
197 return buf.str();
198 }
199 else if (memcmp(cap, "mICQ \xA9 R.K. ", 12) == 0)
200 {
201 buf << "mICQ " << (cap[12]&0x7F) << '.' << (int)cap[13] << '.' << (int)cap[14] << '.' << (int)cap[15];
202 if (cap[12] & 0x80)
203 buf << " alpha";
204 return buf.str();
205 }
206 else if (memcmp(cap, "\x74\xED\xC3\x36\x44\xDF\x48\x5B\x8B\x1C\x67\x1A\x1F\x86\x09\x9F", 16) == 0)
207 return "IM2";
208
209 else if (memcmp(cap, "&RQinside", 9) == 0 ||
210 memcmp(cap, "R&Qinside", 9) == 0)
211 {
212 buf << cap[0] << cap[1] << cap[2] << ' ' << (int)cap[12] << '.' << (int)cap[11] << '.' << (int)cap[10] << '.' << (int)cap[9];
213 return buf.str();
214 }
215 else if (memcmp(cap, "IMadering Client", 16) == 0)
216 return "IMadering";
217
218 else if (memcmp(cap, "V?\xC8\x09\x0BoAQIP !", 16) == 0)
219 return "QIP PDA (Windows)";
220
221 else if (memcmp(cap, "\x51\xAD\xD1\x90\x72\x04\x47\x3D\xA1\xA1\x49\xF4\xA3\x97\xA4\x1F", 16) == 0)
222 return "QIP PDA (Symbian)";
223
224 else if (memcmp(cap, "\x60\xDE\x5C\x8A\xDF\x8C\x4E\x1D\xA4\xC8\xBC\x3B\xD9\x79\x4D\xD8", 16) == 0)
225 return "QIP Mobile (IPhone)";
226
227 else if (memcmp(cap, "\xB0\x82\x62\xF6\x7F\x7C\x45\x61\xAD\xC1\x1C\x6D\x75\x70\x5E\xC5", 16) == 0)
228 return "QIP Mobile (Java)";
229
230 else if (memcmp(cap, "\x7C\x73\x75\x02\xC3\xBE\x4F\x3E\xA6\x9F\x01\x53\x13\x43\x1E\x1A", 16) == 0)
231 {
232 buf << "QIP Infium";
233 if (ts1 != 0)
234 buf << " (" << ts1 << ')';
235 if (ts2 == 11)
236 buf << " Beta";
237 return buf.str();
238 }
239 else if (memcmp(cap, "\x7A\x7B\x7C\x7D\x7E\x7F\x0A\x03\x0B\x04\x01\x53", 12) == 0)
240 {
241 buf << "QIP 2010";
242 if (ts1 != 0)
243 buf << " (" << ts1 << ')';
244 return buf.str();
245 }
246 else if (memcmp(cap, "\x7F\x7F\x7C\x7D\x7E\x7F\x0A\x03\x0B\x04\x01\x53", 12) == 0)
247 {
248 buf << "QIP 2012";
249 if (ts1 != 0)
250 buf << " (" << ts1 << ')';
251 return buf.str();
252 }
253 else if (memcmp(cap, "V?\xC8\x09\x0BoAQIP 2005a", 14) == 0)
254 {
255 buf << "QIP ";
256 if (ts3 == 15)
257 buf << "2005";
258 else
259 buf << cap[11] << cap[12] << cap[13] << cap[14] << cap[15];
260 if (ts1 != 0 && ts2 == 14)
261 buf << ' ' << (int)(ts1>>24) << (int)((ts1>>16)&0xFF) << (int)((ts1>>8)&0xFF) << (int)(ts1&0xFF);
262 return buf.str();
263 }
264 else if (memcmp(cap, "mChat icq ", 10) == 0)
265 return "mChat " + string(cap+10, 6);
266
267 else if (memcmp(cap, "Jimm ", 5) == 0)
268 return "Jimm " + string(cap+5, 11);
269
270 else if (memcmp(cap, "CORE Pager", 10) == 0)
271 {
272 buf << "CORE Pager";
273 if (ts2 == 0x0FFFF0011 && ts3 == 0x1100FFFF && (ts1 & 0xFF000000) != 0)
274 {
275 buf << ' ' << (ts1>>24) << '.' << ((ts1>>16)&0xFF);
276 if ((ts1 & 0xFF) == 0x0B)
277 buf << " Beta";
278 }
279 return buf.str();
280 }
281 else if (memcmp(cap, "D[i]Chat ", 9) == 0)
282 return "D[i]Chat " + string(cap+9, 7);
283
284 else if (memcmp(cap, "\xDD\x16\xF2\x02\x84\xE6\x11\xD4\x90\xDB\x00\x10\x4B\x9B\x4B\x7D", 16) == 0)
285 return "ICQ for Mac";
286
287 else if (memcmp(cap, "\xA7\xE4\x0A\x96\xB3\xA0\x47\x9A\xB8\x45\xC9\xE4\x67\xC5\x6B\x1F", 16) == 0)
288 return "uIM";
289
290 else if (memcmp(cap, "\x44\xE5\xBF\xCE\xB0\x96\xE5\x47\xBD\x65\xEF\xD6\xA3\x7E\x36\x02", 16) == 0)
291 return "Anastasia";
292
293 else if (memcmp(cap, "JICQ\0\0\0\0\0\0\0\0", 12) == 0)
294 {
295 buf << "JICQ ";
296 appendVersion(buf, 2, cap+12);
297 return buf.str();
298 }
299 else if (memcmp(cap, "MIP ", 4) == 0)
300 {
301 if (memcmp(cap+4, "Client v", 8) == 0)
302 {
303 if ((unsigned char)cap[12] < 0x30)
304 {
305 buf << "MIP ";
306 appendVersion(buf, 2, cap+12);
307 return buf.str();
308 }
309 else
310 return "MIP " + string(cap+11, 5);
311 }
312 else
313 {
314 return "MIP " + string(cap+4, 12);
315 }
316 }
317 else if (memcmp(cap, "VmICQ ", 6) == 0)
318 return "VmICQ " + string(cap+6, 10);
319
320 else if (memcmp(cap, "Smaper ", 7) == 0)
321 return "SmapeR " + string(cap+7, 9);
322
323 else if (memcmp(cap, "Yapp", 4) == 0)
324 return "Yapp! v" + string(cap+8, 5);
325
326 else if (memcmp(cap, "digsby", 6) == 0 ||
327 memcmp(cap, "\x09\x46\x01\x05\x4c\x7f\x11\xd1\x82\x22\x44\x45\x45\x53\x54\x00", 16) == 0)
328 return "Digsby";
329
330 else if (memcmp(cap, "japp\xA9 by Sergo\0\0", 16) == 0)
331 return "japp";
332
333 else if (memcmp(cap, "PIGEON!", 7) == 0)
334 return "PIGEON";
335
336 else if (memcmp(cap, "qutim", 5) == 0)
337 {
338 buf << "qutIM ";
339 if (cap[6] == '.')
340 buf << cap[5] << '.' << cap[7];
341 else
342 {
343 buf << (int)cap[6] << '.' << (int)cap[7] << '.' << (int)cap[8] << '.' << ((cap[9]<<8) | cap[10]);
344 switch (cap[5])
345 {
346 case 'l': buf << " (Linux)"; break;
347 case 'w': buf << " (Win32)"; break;
348 case 'm': buf << " (MacOS X)"; break;
349 }
350 }
351 return buf.str();
352 }
353 else if (memcmp(cap, "bayanICQ", 8) == 0)
354 return "bayanICQ " + string(cap+8, 5);
355
356 else if (memcmp(cap, "JIT ", 4) == 0)
357 return "Jabber ICQ Transport";
358
359 else if (memcmp(cap, "IcqKid2", 7) == 0)
360 {
361 buf << "IcqKid2 v";
362 appendVersion(buf, 2, cap+7);
363 return buf.str();
364 }
365 else if (memcmp(cap, "WebIcqPro ", 10) == 0)
366 return "WebIcqPro";
367
368 else if (memcmp(cap, "\x09\x19\x19\x82\xDE\xAD\xBE\xEF\xCA\xFE\x44\x45\x53\x54\x00\x00", 16) == 0)
369 return "Citron IM";
370
371 else if (memcmp(cap, "\xFF\xFF\xFF\xFFnaim", 8) == 0)
372 return "naim";
373
374 else if (memcmp(cap, "NCICQ", 5) == 0)
375 return "NCICQ " + string(cap+6, 10);
376
377 else if (memcmp(cap, "LocID ", 6) == 0)
378 return "LocID";
379
380 --capsLeft;
381 cap += CAP_LENGTH;
382 }
383
384 return string();
385 }
386
detectFromTimestamps(int tcpVersion,unsigned ts1,unsigned ts2,unsigned ts3,time_t onlineSince,int webPort,int capSize)387 static string detectFromTimestamps(int tcpVersion, unsigned ts1, unsigned ts2,
388 unsigned ts3, time_t onlineSince, int webPort, int capSize)
389 {
390 stringstream buf;
391
392 if ((ts1 & 0xFF7F0000) == 0x7D000000)
393 {
394 int v = ts1 & 0xFFFF;
395 buf << "Licq v" << (v/1000) << '.' << ((v/10)%100) << '.' << (v%10);
396 if (ts1 & 0x00800000)
397 buf << "/SSL";
398 return buf.str();
399 }
400 else if (ts1 == 0xFFFFFFFF && ts2 == 0xFFFFFFFF)
401 return "Gaim";
402
403 else if (ts1 == 0xFFFFFFFF && ts2 == 0 && tcpVersion == 7)
404 return "WebICQ";
405
406 else if (ts1 == 0xFFFFFFFF && ts2 == 0 && ts3 == 0x3B7248ED)
407 return "Spam Bot";
408
409 else if ((ts1 & 0x7FFFFFFF) == 0x7FFFFFFF && ts2 != 0)
410 {
411 buf << "Miranda IM ";
412 if (ts1 & 0x80000000)
413 buf << "Unicode ";
414 if (ts2 == 1)
415 buf << "0.1.2 alpha";
416 else
417 buf << (ts2>>24) << '.' << ((ts2>>16)&0xFF) << '.' << ((ts2>>8)&0xFF) << '.' << (ts2&0xFF);
418 return buf.str();
419 }
420 else if (ts1 == 0xFFFFFF8F)
421 return"StrICQ";
422
423 else if (ts1 == 0xFFFFFF42)
424 return "mICQ";
425
426 else if (ts1 == 0xFFFFFFBE)
427 {
428 buf << "Alicq " << (ts2>>24) << '.' << ((ts2>>16)&0xFF) << '.' << ((ts2>>8)&0xFF);
429 return buf.str();
430 }
431 else if (ts1 == 0xFFFFFF7F)
432 return "&RQ";
433
434 else if (ts1 == 0xFFFFFFAB)
435 return "YSM";
436
437 else if (ts1 == 0x04031980)
438 return "vICQ";
439
440 else if (ts1 == 0x3B75AC09)
441 return "Trillian";
442
443 else if (ts1 == 0x3BA8DBAF && tcpVersion == 2)
444 return "stICQ";
445
446 else if (ts1 == 0xFFFFFFFE && ts3 == 0xFFFFFFFE)
447 return "Jimm";
448
449 else if (ts1 == 0x3FF19BEB && ts3 == 0x3FF19BEB)
450 return "IM2";
451
452 else if (ts1 == 0xDDDDEEFF && ts2 == 0 && ts3 == 0)
453 return "SmartICQ";
454
455 else if ((ts1 & 0xFFFFFFF0) == 0x494D2B00 && ts2 == 0 && ts3 == 0)
456 {
457 switch (ts1 & 0xFF)
458 {
459 case 3: return "IM+ (SmartPhone, Pocket PC)";
460 case 5: return "IM+ (Win32)";
461 default: return "IM+";
462 }
463 }
464 else if (ts1 == 0x3B4C4C0C && ts2 == 0 && ts3 == 0x3B7248ed)
465 return "KXicq2";
466
467 else if (ts1 == 0xFFFFF666 && ts3 == 0)
468 {
469 buf << "R&Q " << ts2;
470 return buf.str();
471 }
472 else if (ts1 == 0x66666666 && ts3 == 0x66666666)
473 {
474 buf << "D[i]Chat v.";
475 if (ts2 == 0x10000)
476 buf << "0.1a";
477 else
478 {
479 buf << ((ts2>>8)&0xF) << '.' << ((ts2>>4)&0xF);
480 switch (ts2 & 0x0F)
481 {
482 case 1: buf << " alpha"; break;
483 case 2: buf << " beta"; break;
484 case 3: buf << " final"; break;
485 }
486 }
487 return buf.str();
488 }
489 else if (ts1 == 0xFFFF8615 && ts3 == 0xFFFF8615)
490 return "NCICQ";
491
492 else if (ts1 == 0x48151623 && ts3 == 0x48151623)
493 {
494 buf << "LocID v" << ((ts2>>8)&0xF) << '.' << ((ts2>>4)&0xF) << (ts2&0xF) << " p" << ((ts2>>16)&0xFF);
495 return buf.str();
496 }
497 else if (ts1 == ts2 && ts2 == ts3 && tcpVersion == 8
498 && static_cast<time_t>(ts1) < onlineSince + 3600
499 && static_cast<time_t>(ts1) > onlineSince - 3600)
500 return "Spam Bot";
501
502 else if (ts1 == 0 && ts2 == 0 && ts3 == 0 && tcpVersion == 0 && capSize == 0 && webPort == 0x75BB)
503 return "Spam Bot";
504
505 else if (ts1 == 0x44F523B0 && ts2 == 0x44F523A6 && ts3 == 0x44F523A6 && tcpVersion == 8)
506 return "Virus";
507
508 return string();
509 }
510
detectFromCaps(const char * caps,int capSize,int userClass,int tcpVersion,unsigned ts1,unsigned ts2,unsigned ts3,int webPort)511 static string detectFromCaps(const char* caps, int capSize, int userClass,
512 int tcpVersion, unsigned ts1, unsigned ts2, unsigned ts3, int webPort)
513 {
514 // Check which capablities we've got
515 bool hasRtf = false;
516 bool hasUtf8 = false;
517 bool hasRelay = false;
518 bool hasXtraz = false;
519 bool hasTyping = false;
520 bool hasFile = false;
521 bool hasChat = false;
522 bool hasContacts = false;
523 bool hasTzers = false;
524 bool hasIcqDirect = false;
525 bool hasAimIcon = false;
526 bool hasAimDirect = false;
527 bool hasAimFileShare = false;
528 bool hasIcqDevils = false;
529 bool hasAimSmartCaps = false;
530 bool hasAimLiveVideo = false;
531 bool hasAimLiveAudio = false;
532 bool hasStatusTextAware = false;
533 bool hasComm20012 = false;
534 bool hasRambler = false;
535 bool hasAbv = false;
536 bool hasNetvigator = false;
537 bool hasIMSecKey = false;
538 bool hasFakeHtml = false;
539 bool hasXtrazVideo = false;
540 bool is2001 = false;
541 bool is2002 = false;
542 bool isIcqLite = false;
543
544 const char* cap = caps;
545 int capsLeft = capSize / CAP_LENGTH;
546 while (capsLeft > 0)
547 {
548 if (memcmp(cap+4, "\x4C\x7F\x11\xD1\x82\x22\x44\x45\x53\x54\x00\x00", 12) == 0)
549 {
550 if (cap[0] == 0x09 && cap[1] == 0x46)
551 {
552 if (cap[2] == 0x13)
553 {
554 switch (cap[3])
555 {
556 case 0x43: hasFile = true; break;
557 case 0x44: hasIcqDirect = true; break;
558 case 0x45: hasAimDirect = true; break;
559 case 0x46: hasAimIcon = true; break;
560 case 0x48: hasAimFileShare = true; break;
561 case 0x49: hasRelay = true; break;
562 case 0x4B: hasContacts = true; break;
563 case 0x4C: hasIcqDevils = true; break;
564 case 0x4E: hasUtf8 = true; break;
565 }
566 }
567 else if (cap[2] == 0x01)
568 {
569 switch ((unsigned)cap[3])
570 {
571 case 0xFF: hasAimSmartCaps = true; break;
572 case 0x01: hasAimLiveVideo = true; break;
573 case 0x04: hasAimLiveAudio = true; break;
574 case 0x0A: hasStatusTextAware = true; break;
575 }
576 }
577 }
578 else if (memcmp(cap, "\xA0\xE9\x3F\x37", 4) == 0)
579 hasComm20012 = true;
580 else if (memcmp(cap, "\x10\xCF\x40\xD1", 4) == 0)
581 is2002 = true;
582 }
583 else if (memcmp(cap, ICQ_CAPABILITY_RTFxMSGS, CAP_LENGTH) == 0)
584 hasRtf = true;
585 else if (memcmp(cap, ICQ_CAPABILITY_XTRAZ, CAP_LENGTH) == 0)
586 hasXtraz = true;
587 else if (memcmp(cap, ICQ_CAPABILITY_TYPING, CAP_LENGTH) == 0)
588 hasTyping = true;
589 else if (memcmp(cap, "\xB2\xEC\x8F\x16\x7C\x6F\x45\x1B\xBD\x79\xDC\x58\x49\x78\x88\xB9", CAP_LENGTH) == 0)
590 hasTzers = true;
591 else if (memcmp(cap, "\x2E\x7A\x64\x75\xFA\xDF\x4D\xC8\x88\x6F\xEA\x35\x95\xFD\xB6\xDF", CAP_LENGTH) == 0)
592 is2001 = true;
593 else if (memcmp(cap, "\xC8\x95\x3A\x9F\x21\xF1\x4F\xAA\xB0\xB2\x6D\xE6\x63\xAB\xF5\xB7", CAP_LENGTH) == 0)
594 isIcqLite = true;
595 else if (memcmp(cap, "\x7E\x11\xB7\x78\xA3\x53\x49\x26\xA8\x02\x44\x73\x52\x08\xC4\x2A", CAP_LENGTH) == 0)
596 hasRambler = true;
597 else if (memcmp(cap, "\x00\xE7\xE0\xDF\xA9\xD0\x4F\xE1\x91\x62\xC8\x90\x9A\x13\x2A\x1B", CAP_LENGTH) == 0)
598 hasAbv = true;
599 else if (memcmp(cap, "\x4C\x6B\x90\xA3\x3D\x2D\x48\x0E\x89\xD6\x2E\x4B\x2C\x10\xD9\x9F", CAP_LENGTH) == 0)
600 hasNetvigator = true;
601 else if (memcmp(cap, "\x01\x01\x01\x01\x01\x01", 6) == 0 || memcmp(cap, "\x02\x02\x02\x02\x02\x02", 6) == 0)
602 hasIMSecKey = true;
603 else if (memcmp(cap, "\x01\x38\xCA\x7B\x76\x9A\x49\x15\x88\xF2\x13\xFC\x00\x97\x9E\xA8", CAP_LENGTH) == 0)
604 hasFakeHtml = true;
605 else if (memcmp(cap, "\x17\x8C\x2D\x9B\xDA\xA5\x45\xBB\x8D\xDB\xF3\xBD\xBD\x53\xA1\x0A", CAP_LENGTH) == 0)
606 hasXtrazVideo = true;
607 else if (memcmp(cap, "\x74\x8F\x24\x20\x62\x87\x11\xD1\x82\x22\x44\x45\x53\x54\x00\x00", CAP_LENGTH) == 0)
608 hasChat = true;
609
610 cap += CAP_LENGTH;
611 --capsLeft;
612 }
613
614 if (ts1 == 0x3AA773EE && ts2 == 0x3AA66380)
615 {
616 // libicq2000, try to determine which client is behind it
617 if (hasRtf)
618 // centericq added rtf capability to libicq2000
619 return "Centericq";
620 else if (hasUtf8)
621 // IcyJuice added unicode capability to libicq2000
622 return "libicq2000 (Unicode)";
623 else
624 // others - like jabber transport uses unmodified library, thus cannot be detected
625 return "libicq2000";
626 }
627
628 // AIM clients
629 if ((userClass & 0x0040) == 0)
630 {
631 if (hasAimIcon && hasChat && hasUtf8 && hasTyping && capSize == 0x40)
632 return "Meebo";
633
634 else if (hasRelay && hasUtf8 && hasTyping && hasXtraz && hasChat && hasAimIcon && hasAimDirect && hasFakeHtml && capSize == 0x90)
635 return "libpurple";
636
637 else if (hasRelay && hasUtf8 && hasTyping && hasXtraz && hasChat & hasAimIcon && hasFakeHtml && capSize == 0x70)
638 return "Meebo";
639
640 else
641 return "AIM";
642 }
643
644 if (tcpVersion == 8 && hasXtraz && hasIMSecKey)
645 tcpVersion = 9;
646
647 switch (tcpVersion)
648 {
649 case 8:
650 if (hasComm20012 || hasRelay)
651 {
652 if (is2001)
653 {
654 if (ts1 == 0 && ts2 == 0 && ts3 == 0)
655 {
656 if (hasRtf)
657 return "TICQClient"; // possibly also older GnomeICU
658 else
659 return "ICQ for Pocket PC";
660 }
661 else
662 return "ICQ 2001";
663 }
664 else if (is2002)
665 return "ICQ 2002";
666
667 else if (hasRelay && hasUtf8 && hasRtf)
668 {
669 if (ts1 == 0 && ts2 == 0 && ts3 == 0)
670 {
671 if (webPort == 0)
672 return "GnomeICU 0.99.5+";
673 else
674 return "IC@";
675 }
676 else
677 return "ICQ 2002/2003a";
678 }
679 else if (hasRelay && hasUtf8 && hasTyping && hasXtraz && hasChat && hasAimIcon && hasFakeHtml)
680 {
681 if (hasAimDirect)
682 return "libpurple";
683 else
684 return "Meebo";
685 }
686 else if (hasRelay && hasUtf8 && hasTyping && ts1 == 0 && ts2 == 0 && ts3 == 0)
687 return "PreludeICQ";
688 }
689 else if (hasUtf8 && hasTyping && hasAimIcon && hasAimDirect)
690 return "imo.im";
691
692 break;
693
694 case 9:
695 if (hasXtraz)
696 {
697 if (hasFile)
698 {
699 string base;
700 if (hasTzers)
701 {
702 if (isIcqLite && hasStatusTextAware && hasAimLiveVideo && hasAimLiveAudio)
703 base = "ICQ 7";
704
705 else if (hasFakeHtml)
706 {
707 if (hasAimLiveVideo && hasAimLiveAudio)
708 base = "ICQ 6";
709
710 else if (hasRtf && hasContacts && hasIcqDevils)
711 base = "Qnext v4";
712 }
713 else
714 base = "icq5.1";
715 }
716 else
717 {
718 base = "icq5";
719 }
720
721 if (hasRambler)
722 return base + " (Rambler)";
723 else if (hasAbv)
724 return base + " (Abv)";
725 else if (hasNetvigator)
726 return base + " (Netvigator)";
727 else
728 return base;
729 }
730 else if (hasIcqDirect)
731 {
732 if (hasRtf)
733 return "Qnext";
734 else if (hasTyping && hasTzers && hasFakeHtml)
735 {
736 if (hasRelay && hasUtf8 && hasAimLiveAudio)
737 return "Mail.ru Agent (PC)";
738 else
739 return "Fring";
740 }
741 else
742 return "pyICQ";
743 }
744 else
745 return "ICQ Lite v4";
746 }
747 else if (isIcqLite)
748 return "ICQ Lite";
749
750 else if (!hasIcqDirect)
751 {
752 if (hasFakeHtml && hasChat && hasAimSmartCaps)
753 return "Trillian Astra";
754 else
755 return "pyICQ";
756 }
757 break;
758
759 case 7:
760 if (hasRtf)
761 return "GnomeICU";
762 else if (hasRelay)
763 {
764 if (ts1 == 0 && ts2 == 0 && ts3 == 0)
765 return "&RQ";
766 else
767 return "ICQ 2000";
768 }
769 else if (hasUtf8)
770 {
771 if (hasTyping)
772 return "Icq2Go! (Java)";
773 else if (userClass & 0x0080)
774 return "Pocket Web 1&1";
775 else
776 return "Icq2Go!";
777 }
778 break;
779
780 case 10:
781 if (!hasRtf && !hasUtf8)
782 return "Qnext";
783 else if (hasRtf && hasUtf8 && ts1 == 0 && ts2 == 0 && ts3 == 0)
784 return "NanoICQ";
785
786 break;
787
788 case 11:
789 if (hasXtraz && hasRelay && hasTyping && hasUtf8 && hasIcqDevils)
790 return "Mail.ru Agent (Symbian)";
791
792 break;
793
794 case 0:
795 if (ts1 == 0 && ts2 == 0 && ts3 == 0 && webPort == 0)
796 {
797 if (hasTyping && is2001 &&is2002 && hasComm20012)
798 return "Spam Bot";
799
800 else if (hasAimIcon && hasAimDirect && hasFile && hasUtf8)
801 {
802 if (hasRelay)
803 return "Adium X";
804 else if (hasTyping)
805 return "libpurple";
806 else
807 return "libgaim";
808 }
809 else if (hasAimIcon && hasAimDirect && hasChat && hasFile && capSize == 0x40)
810 return "libgaim";
811
812 else if (hasFile && hasChat && capSize == 0x20)
813 return "Easy Message";
814
815 else if (hasUtf8 && hasTyping && hasAimIcon && hasChat && capSize == 0x40)
816 return "Meebo";
817
818 else if (hasUtf8 && hasAimIcon && capSize == 0x20)
819 return "PyICQ-t Jabber Transport";
820
821 else if (hasUtf8 && hasXtraz && hasAimIcon && hasXtrazVideo)
822 return "PyICQ-t Jabber Transport";
823
824 else if (hasUtf8 && hasRelay && hasIcqDirect && hasTyping && capSize == 0x40)
825 return "Agile Messenger"; // Smartphone 2002
826
827 else if (hasUtf8 && hasRelay && hasIcqDirect && hasFile && hasAimFileShare)
828 return "Slick";
829
830 else if (hasUtf8 && hasRelay && hasFile && hasContacts && hasAimFileShare && hasAimIcon)
831 return "Digsby";
832
833 else if (hasUtf8 && hasRelay && hasContacts && hasAimIcon && hasFakeHtml)
834 return "mundu IM";
835
836 else if (hasUtf8 && hasFile && hasChat)
837 return "eBuddy";
838
839 else if (hasContacts && hasFile && hasAimIcon && hasAimDirect && hasChat)
840 return "IloveIM";
841 }
842 break;
843 }
844
845 return string();
846 }
847
detectGeneric(int tcpVersion)848 static string detectGeneric(int tcpVersion)
849 {
850 switch (tcpVersion)
851 {
852 case 6: return "ICQ99";
853 case 7: return "ICQ 2000/ICQ2Go";
854 case 8: return "ICQ 2001-2003a";
855 case 9: return "ICQ Lite";
856 case 10: return "ICQ 2003b";
857 }
858 return string();
859 }
860
detectExtras(string & client,const char * caps,int capSize)861 static void detectExtras(string& client, const char* caps, int capSize)
862 {
863 if (client.substr(0, 10) == "Miranda IM")
864 {
865 const char* m = findCapability(caps, capSize, "MIM/", 4);
866 if (m != NULL)
867 client += " [" + string(m+4, 12) + "]";
868 }
869
870 if (findCapability(caps, capSize, "SIMPSIMPSIMPSIMP"))
871 client += " + SimpLite";
872 else if (findCapability(caps, capSize, "SIMP_PROSIMP_PRO"))
873 client += " + SimpPro";
874 else if (findCapability(caps, capSize, "IMsecureCphr\0\0\x06\x01") ||
875 findCapability(caps, capSize, "\x01\x01\x01\x01\x01\x01", 6) || findCapability(caps, capSize, "\x02\x02\x02\x02\x02\x02", 6))
876 client += " + IMsecure";
877 }
878
detectUserClient(const char * caps,int capSize,int userClass,int tcpVersion,unsigned ts1,unsigned ts2,unsigned ts3,time_t onlineSince,int webPort)879 string IcqProtocol::detectUserClient(const char* caps, int capSize, int userClass,
880 int tcpVersion, unsigned ts1, unsigned ts2, unsigned ts3, time_t onlineSince, int webPort)
881 {
882 string client;
883
884 if (capSize > 0)
885 {
886 client = detectFromCapSign(caps, capSize, ts1, ts2, ts3);
887
888 // Make sure we didin't get any null characters by mistake
889 string::size_type pos = client.find('\x0');
890 if (pos != string::npos)
891 client.erase(pos);
892 }
893
894 if (client.empty())
895 client = detectFromTimestamps(tcpVersion, ts1, ts2, ts3, onlineSince, webPort, capSize);
896
897 if (client.empty())
898 client = detectFromCaps(caps, capSize, userClass, tcpVersion, ts1, ts2, ts3, webPort);
899
900 if (client.empty())
901 client = detectGeneric(tcpVersion);
902
903 if (!client.empty())
904 detectExtras(client, caps, capSize);
905
906 return client;
907 }
908