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