/* packet-icq.c * Routines for ICQ packet disassembly * * $Id: packet-icq.c,v 1.48 2002/08/28 21:00:17 jmayer Exp $ * * Ethereal - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* * This file: by Kojak * * Decoding code ripped, reference to the original author at the * appropriate place with the code itself. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_STDDEF_H #include #endif #include #include #include #include #include #include #include #include #include static int proto_icq = -1; static int hf_icq_uin = -1; static int hf_icq_client_cmd = -1; static int hf_icq_server_cmd = -1; static int hf_icq_sessionid = -1; static int hf_icq_checkcode = -1; static int hf_icq_decode = -1; static int hf_icq_type = -1; static gint ett_icq = -1; static gint ett_icq_header = -1; static gint ett_icq_decode = -1; static gint ett_icq_body = -1; static gint ett_icq_body_parts = -1; #define UDP_PORT_ICQ 4000 enum { ICQ5_client, ICQ5_server}; static void dissect_icqv5(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree); static void dissect_icqv5Server(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree, int pktsize); /* Offsets of fields in the ICQ headers */ /* Can be 0x0002 or 0x0005 */ #define ICQ_VERSION 0x00 /* Is either one (server) or four (client) bytes long */ /* Client header offsets */ #define ICQ5_UNKNOWN 0x02 #define ICQ5_CL_UIN 0x06 #define ICQ5_CL_SESSIONID 0x0a #define ICQ5_CL_CMD 0x0e #define ICQ5_CL_SEQNUM1 0x10 #define ICQ5_CL_SEQNUM2 0x12 #define ICQ5_CL_CHECKCODE 0x14 #define ICQ5_CL_PARAM 0x18 #define ICQ5_CL_HDRSIZE 0x18 /* Server header offsets */ #define ICQ5_SRV_SESSIONID 0x03 #define ICQ5_SRV_CMD 0x07 #define ICQ5_SRV_SEQNUM1 0x09 #define ICQ5_SRV_SEQNUM2 0x0b #define ICQ5_SRV_UIN 0x0d #define ICQ5_SRV_CHECKCODE 0x11 #define ICQ5_SRV_PARAM 0x15 #define ICQ5_SRV_HDRSIZE 0x15 #define SRV_ACK 0x000a #define SRV_SILENT_TOO_LONG 0x001e #define SRV_GO_AWAY 0x0028 #define SRV_NEW_UIN 0x0046 /* LOGIN_REPLY is very scary. It has a lot of fields that are undocumented * Only the IP field makes sense */ #define SRV_LOGIN_REPLY 0x005a #define SRV_LOGIN_REPLY_IP 0x000c #define SRV_BAD_PASS 0x0064 #define SRV_USER_ONLINE 0x006e #define SRV_USER_ONL_UIN 0x0000 #define SRV_USER_ONL_IP 0x0004 #define SRV_USER_ONL_PORT 0x0008 #define SRV_USER_ONL_REALIP 0x000c #define SRV_USER_ONL_X1 0x0010 #define SRV_USER_ONL_STATUS 0x0013 #define SRV_USER_ONL_X2 0x0015 #define SRV_USER_OFFLINE 0x0078 #define SRV_USER_OFFLINE_UIN 0x0000 #define SRV_MULTI 0x0212 #define SRV_MULTI_NUM 0x0000 #define SRV_META_USER 0x03de #define SRV_META_USER_SUBCMD 0x0000 #define SRV_META_USER_RESULT 0x0002 #define SRV_META_USER_DATA 0x0003 #define SRV_UPDATE_SUCCESS 0x01e0 #define SRV_UPDATE_FAIL 0x01ea /* * ICQv5 SRV_META_USER subcommands */ #define META_EX_USER_FOUND 0x0190 #define META_USER_FOUND 0x019a #define META_ABOUT 0x00e6 #define META_USER_INFO 0x00c8 #define SRV_RECV_MESSAGE 0x00dc #define SRV_RECV_MSG_UIN 0x0000 #define SRV_RECV_MSG_YEAR 0x0004 #define SRV_RECV_MSG_MONTH 0x0006 #define SRV_RECV_MSG_DAY 0x0007 #define SRV_RECV_MSG_HOUR 0x0008 #define SRV_RECV_MSG_MINUTE 0x0009 #define SRV_RECV_MSG_MSG_TYPE 0x000a #define SRV_RAND_USER 0x024e #define SRV_RAND_USER_UIN 0x0000 #define SRV_RAND_USER_IP 0x0004 #define SRV_RAND_USER_PORT 0x0008 #define SRV_RAND_USER_REAL_IP 0x000c #define SRV_RAND_USER_CLASS 0x0010 #define SRV_RAND_USER_X1 0x0011 #define SRV_RAND_USER_STATUS 0x0015 #define SRV_RAND_USER_TCP_VER 0x0019 /* This message has the same structure as cmd_send_msg */ #define SRV_SYS_DELIVERED_MESS 0x0104 static const value_string serverMetaSubCmdCode[] = { { META_USER_FOUND, "META_USER_FOUND" }, { META_EX_USER_FOUND, "META_EX_USER_FOUND" }, { META_ABOUT, "META_ABOUT" }, { META_USER_INFO, "META_USER_INFO" }, { 0, NULL } }; static const value_string serverCmdCode[] = { { SRV_ACK, "SRV_ACK" }, { SRV_SILENT_TOO_LONG, "SRV_SILENT_TOO_LONG" }, { SRV_GO_AWAY, "SRV_GO_AWAY" }, { SRV_NEW_UIN, "SRV_NEW_UIN" }, { SRV_LOGIN_REPLY, "SRV_LOGIN_REPLY" }, { SRV_BAD_PASS, "SRV_BAD_PASS" }, { SRV_USER_ONLINE, "SRV_USER_ONLINE" }, { SRV_USER_OFFLINE, "SRV_USER_OFFLINE" }, { 130, "SRV_QUERY" }, { 140, "SRV_USER_FOUND" }, { 160, "SRV_END_OF_SEARCH" }, { 180, "SRV_NEW_USER" }, { 200, "SRV_UPDATE_EXT" }, { SRV_RECV_MESSAGE, "SRV_RECV_MESSAGE" }, { 230, "SRV_END_OFFLINE_MESSAGES" }, { 240, "SRV_NOT_CONNECTED" }, { 250, "SRV_TRY_AGAIN" }, { SRV_SYS_DELIVERED_MESS, "SRV_SYS_DELIVERED_MESS" }, { 280, "SRV_INFO_REPLY" }, { 290, "SRV_EXT_INFO_REPLY" }, { 420, "SRV_STATUS_UPDATE" }, { 450, "SRV_SYSTEM_MESSAGE" }, { SRV_UPDATE_SUCCESS, "SRV_UPDATE_SUCCESS" }, { SRV_UPDATE_FAIL, "SRV_UPDATE_FAIL" }, { 500, "SRV_AUTH_UPDATE" }, { SRV_MULTI, "SRV_MULTI_PACKET" }, { 540, "SRV_END_CONTACTLIST_STATUS" }, { SRV_RAND_USER, "SRV_RAND_USER" }, { SRV_META_USER, "SRV_META_USER" }, { 0, NULL } }; #define MSG_TEXT 0x0001 #define MSG_URL 0x0004 #define MSG_AUTH_REQ 0x0006 #define MSG_AUTH 0x0008 #define MSG_USER_ADDED 0x000c #define MSG_EMAIL 0x000e #define MSG_CONTACTS 0x0013 #define STATUS_ONLINE 0x00000000 #define STATUS_AWAY 0x00000001 #define STATUS_DND 0x00000013 #define STATUS_INVISIBLE 0x00000100 #define STATUS_OCCUPIED 0x00000010 #define STATUS_NA 0x00000004 #define STATUS_CHAT 0x00000020 /* Offsets for all packets measured from the start of the payload; i.e. * with the ICQ header removed */ #define CMD_ACK 0x000a #define CMD_ACK_RANDOM 0x0000 #define CMD_SEND_MSG 0x010E #define CMD_SEND_MSG_RECV_UIN 0x0000 #define CMD_SEND_MSG_MSG_TYPE 0x0004 #define CMD_SEND_MSG_MSG_LEN 0x0006 #define CMD_SEND_MSG_MSG_TEXT 0x0008 /* The rest of the packet should be a null-term string */ #define CMD_LOGIN 0x03E8 #define CMD_LOGIN_TIME 0x0000 #define CMD_LOGIN_PORT 0x0004 #define CMD_LOGIN_PASSLEN 0x0008 #define CMD_LOGIN_PASSWD 0x000A /* The password is variable length; so when we've decoded the passwd, * the structure starts counting at 0 again. */ #define CMD_LOGIN_IP 0x0004 #define CMD_LOGIN_STATUS 0x0009 #define CMD_CONTACT_LIST 0x0406 #define CMD_CONTACT_LIST_NUM 0x0000 #define CMD_USER_META 0x064a #define CMD_REG_NEW_USER 0x03fc #define CMD_ACK_MESSAGES 0x0442 #define CMD_ACK_MESSAGES_RANDOM 0x0000 #define CMD_KEEP_ALIVE 0x042e #define CMD_KEEP_ALIVE_RANDOM 0x0000 #define CMD_SEND_TEXT_CODE 0x0438 #define CMD_SEND_TEXT_CODE_LEN 0x0000 #define CMD_SEND_TEXT_CODE_TEXT 0x0002 #define CMD_MSG_TO_NEW_USER 0x0456 #define CMD_QUERY_SERVERS 0x04ba #define CMD_QUERY_ADDONS 0x04c4 #define CMD_STATUS_CHANGE 0x04d8 #define CMD_STATUS_CHANGE_STATUS 0x0000 #define CMD_ADD_TO_LIST 0x053c #define CMD_ADD_TO_LIST_UIN 0x0000 #define CMD_RAND_SEARCH 0x056e #define CMD_RAND_SEARCH_GROUP 0x0000 #define CMD_META_USER 0x064a static const value_string msgTypeCode[] = { { MSG_TEXT, "MSG_TEXT" }, { MSG_URL, "MSG_URL" }, { MSG_AUTH_REQ, "MSG_AUTH_REQ" }, { MSG_AUTH, "MSG_AUTH" }, { MSG_USER_ADDED, "MSG_USER_ADDED" }, { MSG_EMAIL, "MSG_EMAIL" }, { MSG_CONTACTS, "MSG_CONTACTS" }, { 0, NULL } }; static const value_string statusCode[] = { { STATUS_ONLINE, "ONLINE" }, { STATUS_AWAY, "AWAY" }, { STATUS_DND, "DND" }, { STATUS_INVISIBLE, "INVISIBLE" }, { STATUS_OCCUPIED, "OCCUPIED" }, { STATUS_NA, "NA" }, { STATUS_CHAT, "Free for Chat" }, { 0, NULL } }; static const value_string clientCmdCode[] = { { CMD_ACK, "CMD_ACK" }, { CMD_SEND_MSG, "CMD_SEND_MESSAGE" }, { CMD_LOGIN, "CMD_LOGIN" }, { CMD_REG_NEW_USER, "CMD_REG_NEW_USER" }, { 1030, "CMD_CONTACT_LIST" }, { 1050, "CMD_SEARCH_UIN" }, { 1060, "CMD_SEARCH_USER" }, { 1070, "CMD_KEEP_ALIVE" }, { CMD_SEND_TEXT_CODE, "CMD_SEND_TEXT_CODE" }, { CMD_ACK_MESSAGES, "CMD_ACK_MESSAGES" }, { 1100, "CMD_LOGIN_1" }, { CMD_MSG_TO_NEW_USER, "CMD_MSG_TO_NEW_USER" }, { 1120, "CMD_INFO_REQ" }, { 1130, "CMD_EXT_INFO_REQ" }, { 1180, "CMD_CHANGE_PW" }, { 1190, "CMD_NEW_USER_INFO" }, { 1200, "CMD_UPDATE_EXT_INFO" }, { CMD_QUERY_SERVERS, "CMD_QUERY_SERVERS" }, { CMD_QUERY_ADDONS, "CMD_QUERY_ADDONS" }, { CMD_STATUS_CHANGE, "CMD_STATUS_CHANGE" }, { 1260, "CMD_NEW_USER_1" }, { 1290, "CMD_UPDATE_INFO" }, { 1300, "CMD_AUTH_UPDATE" }, { 1310, "CMD_KEEP_ALIVE2" }, { 1320, "CMD_LOGIN_2" }, { CMD_ADD_TO_LIST, "CMD_ADD_TO_LIST" }, { 1380, "CMD_RAND_SET" }, { CMD_RAND_SEARCH, "CMD_RAND_SEARCH" }, { CMD_META_USER, "CMD_META_USER" }, { 1700, "CMD_INVIS_LIST" }, { 1710, "CMD_VIS_LIST" }, { 1720, "CMD_UPDATE_LIST" }, { 0, NULL } }; /* * All ICQv5 decryption code thanx to Sebastien Dault (daus01@gel.usherb.ca) */ static const guchar table_v5 [] = { 0x59, 0x60, 0x37, 0x6B, 0x65, 0x62, 0x46, 0x48, 0x53, 0x61, 0x4C, 0x59, 0x60, 0x57, 0x5B, 0x3D, 0x5E, 0x34, 0x6D, 0x36, 0x50, 0x3F, 0x6F, 0x67, 0x53, 0x61, 0x4C, 0x59, 0x40, 0x47, 0x63, 0x39, 0x50, 0x5F, 0x5F, 0x3F, 0x6F, 0x47, 0x43, 0x69, 0x48, 0x33, 0x31, 0x64, 0x35, 0x5A, 0x4A, 0x42, 0x56, 0x40, 0x67, 0x53, 0x41, 0x07, 0x6C, 0x49, 0x58, 0x3B, 0x4D, 0x46, 0x68, 0x43, 0x69, 0x48, 0x33, 0x31, 0x44, 0x65, 0x62, 0x46, 0x48, 0x53, 0x41, 0x07, 0x6C, 0x69, 0x48, 0x33, 0x51, 0x54, 0x5D, 0x4E, 0x6C, 0x49, 0x38, 0x4B, 0x55, 0x4A, 0x62, 0x46, 0x48, 0x33, 0x51, 0x34, 0x6D, 0x36, 0x50, 0x5F, 0x5F, 0x5F, 0x3F, 0x6F, 0x47, 0x63, 0x59, 0x40, 0x67, 0x33, 0x31, 0x64, 0x35, 0x5A, 0x6A, 0x52, 0x6E, 0x3C, 0x51, 0x34, 0x6D, 0x36, 0x50, 0x5F, 0x5F, 0x3F, 0x4F, 0x37, 0x4B, 0x35, 0x5A, 0x4A, 0x62, 0x66, 0x58, 0x3B, 0x4D, 0x66, 0x58, 0x5B, 0x5D, 0x4E, 0x6C, 0x49, 0x58, 0x3B, 0x4D, 0x66, 0x58, 0x3B, 0x4D, 0x46, 0x48, 0x53, 0x61, 0x4C, 0x59, 0x40, 0x67, 0x33, 0x31, 0x64, 0x55, 0x6A, 0x32, 0x3E, 0x44, 0x45, 0x52, 0x6E, 0x3C, 0x31, 0x64, 0x55, 0x6A, 0x52, 0x4E, 0x6C, 0x69, 0x48, 0x53, 0x61, 0x4C, 0x39, 0x30, 0x6F, 0x47, 0x63, 0x59, 0x60, 0x57, 0x5B, 0x3D, 0x3E, 0x64, 0x35, 0x3A, 0x3A, 0x5A, 0x6A, 0x52, 0x4E, 0x6C, 0x69, 0x48, 0x53, 0x61, 0x6C, 0x49, 0x58, 0x3B, 0x4D, 0x46, 0x68, 0x63, 0x39, 0x50, 0x5F, 0x5F, 0x3F, 0x6F, 0x67, 0x53, 0x41, 0x25, 0x41, 0x3C, 0x51, 0x54, 0x3D, 0x5E, 0x54, 0x5D, 0x4E, 0x4C, 0x39, 0x50, 0x5F, 0x5F, 0x5F, 0x3F, 0x6F, 0x47, 0x43, 0x69, 0x48, 0x33, 0x51, 0x54, 0x5D, 0x6E, 0x3C, 0x31, 0x64, 0x35, 0x5A, 0x00, 0x00 }; static char* findMsgType(int num) { return val_to_str(num, msgTypeCode, "Unknown"); } static char* findSubCmd(int num) { return val_to_str(num, serverMetaSubCmdCode, "Unknown (0x%04x)"); } static char* findClientCmd(int num) { return val_to_str(num, clientCmdCode, "Unknown (%u)"); } static char* findServerCmd(int num) { return val_to_str(num, serverCmdCode, "Unknown (%u)"); } static char* findStatus(int num) { return val_to_str(num, statusCode, "Unknown (0x%08x)"); } static guint32 get_v5key(tvbuff_t *tvb, int len) { guint32 a1, a2, a3, a4, a5; guint32 code, check, key; code = tvb_get_letohl(tvb, ICQ5_CL_CHECKCODE); a1 = code & 0x0001f000; a2 = code & 0x07c007c0; a3 = code & 0x003e0001; a4 = code & 0xf8000000; a5 = code & 0x0000083e; a1 = a1 >> 0x0c; a2 = a2 >> 0x01; a3 = a3 << 0x0a; a4 = a4 >> 0x10; a5 = a5 << 0x0f; check = a5 + a1 + a2 + a3 + a4; key = len * 0x68656C6C; key += check; return key; } static void decrypt_v5(guchar *bfr, guint32 size,guint32 key) { guint32 i; guint32 k; for (i=ICQ5_CL_SESSIONID; i < size; i+=4 ) { k = key+table_v5[i&0xff]; if ( i != 0x16 ) { bfr[i] ^= (guchar)(k & 0xff); bfr[i+1] ^= (guchar)((k & 0xff00)>>8); } if ( i != 0x12 ) { bfr[i+2] ^= (guchar)((k & 0xff0000)>>16); bfr[i+3] ^= (guchar)((k & 0xff000000)>>24); } } } static void dissect_icqv4(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { proto_tree *icq_tree = NULL; proto_item *ti = NULL; /* Not really implemented yet */ if (check_col(pinfo->cinfo, COL_PROTOCOL)) { col_set_str(pinfo->cinfo, COL_PROTOCOL, "ICQv4 (UDP)"); } if (check_col(pinfo->cinfo, COL_INFO)) { col_set_str(pinfo->cinfo, COL_INFO, "ICQ Version 4 protocol"); } if (tree) { ti = proto_tree_add_protocol_format(tree, proto_icq, tvb, 0, -1, "ICQv4"); icq_tree = proto_item_add_subtree(ti, ett_icq); proto_tree_add_text(icq_tree, tvb, ICQ_VERSION, 2, "Version: %u", tvb_get_letohs(tvb, ICQ_VERSION)); } } static void dissect_icqv3(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { proto_tree *icq_tree = NULL; proto_item *ti = NULL; /* Not really implemented yet */ if (check_col(pinfo->cinfo, COL_PROTOCOL)) { col_set_str(pinfo->cinfo, COL_PROTOCOL, "ICQv3 (UDP)"); } if (check_col(pinfo->cinfo, COL_INFO)) { col_set_str(pinfo->cinfo, COL_INFO, "ICQ Version 3 protocol"); } if (tree) { ti = proto_tree_add_protocol_format(tree, proto_icq, tvb, 0, -1, "ICQv3"); icq_tree = proto_item_add_subtree(ti, ett_icq); proto_tree_add_text(icq_tree, tvb, ICQ_VERSION, 2, "Version: %u", tvb_get_letohs(tvb, ICQ_VERSION)); } } static void dissect_icqv2(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { proto_tree *icq_tree = NULL; proto_item *ti = NULL; /* Not really implemented yet */ if (check_col(pinfo->cinfo, COL_PROTOCOL)) { col_set_str(pinfo->cinfo, COL_PROTOCOL, "ICQv2 (UDP)"); } if (check_col(pinfo->cinfo, COL_INFO)) { col_set_str(pinfo->cinfo, COL_INFO, "ICQ Version 2 protocol"); } if (tree) { ti = proto_tree_add_protocol_format(tree, proto_icq, tvb, 0, -1, "ICQv2"); icq_tree = proto_item_add_subtree(ti, ett_icq); proto_tree_add_text(icq_tree, tvb, ICQ_VERSION, 2, "Version: %u", tvb_get_letohs(tvb, ICQ_VERSION)); } } /* * The packet has, at offset "offset" a (len, string) pair. * Display the length and string in the tree. * * If anything is wrong, return -1, since -1 is not a valid string * length. Else, return the number of chars processed. */ static guint16 proto_add_icq_attr(proto_tree* tree, /* The tree to add to */ tvbuff_t *tvb, /* Tvbuff with packet */ const int offset, /* Offset from the start of packet of field */ char* descr) /* The description to use in the tree */ { guint16 len; len = tvb_get_letohs(tvb, offset); if (len > tvb_reported_length_remaining(tvb, offset)) return -1; /* length goes past end of packet */ proto_tree_add_text(tree, tvb, offset, sizeof(guint16) + len, "%s[%u]: %.*s", descr, len, len, tvb_get_ptr(tvb, offset + sizeof(guint16), len)); return len + sizeof(guint16); } static void icqv5_decode_msgType(proto_tree* tree, tvbuff_t *tvb, int offset, int size) { proto_item* ti = NULL; proto_tree* subtree = NULL; int left = size; guint16 msgType; gint sep_offset; int sz; /* Size of the current element */ unsigned int n; static char* url_field_descr[] = { "Description", "URL", }; #define N_URL_FIELDS (sizeof url_field_descr / sizeof url_field_descr[0]) static char* email_field_descr[] = { "Nickname", "First name", "Last name", "Email address", "Unknown", "Text" }; #define N_EMAIL_FIELDS (sizeof email_field_descr / sizeof email_field_descr[0]) static char* auth_req_field_descr[] = { "Nickname", "First name", "Last name", "Email address", "Unknown", "Reason" }; #define N_AUTH_REQ_FIELDS (sizeof auth_req_field_descr / sizeof auth_req_field_descr[0]) static char* user_added_field_descr[] = { "Nickname", "First name", "Last name", "Email address", }; #define N_USER_ADDED_FIELDS (sizeof user_added_field_descr / sizeof user_added_field_descr[0]) msgType = tvb_get_letohs(tvb, offset); ti = proto_tree_add_text(tree, tvb, offset, size, "Message: Type = %u (%s)", msgType, findMsgType(msgType)); /* Create a new subtree */ subtree = proto_item_add_subtree(ti, ett_icq_body_parts); proto_tree_add_text(subtree, tvb, offset, 2, "Type: %u (%s)", msgType, findMsgType(msgType)); offset += 2; left -= 2; if (msgType != MSG_AUTH) { /* * XXX - does a MSG_AUTH message really have 3 bytes of information * rather than a length field? */ proto_tree_add_text(subtree, tvb, offset, 2, "Length: %u", tvb_get_letohs(tvb, offset)); offset += 2; left -= 2; } switch(msgType) { case 0xffff: /* Field unknown */ break; default: fprintf(stderr, "Unknown msgType: %u (%04x)\n", msgType, msgType); break; case MSG_TEXT: proto_tree_add_text(subtree, tvb, offset, left, "Msg: %.*s", left-1, tvb_get_ptr(tvb, offset, left)); break; case MSG_URL: for (n = 0; n < N_URL_FIELDS; n++) { if (n != N_URL_FIELDS - 1) { sep_offset = tvb_find_guint8(tvb, offset, left, 0xfe); sz = sep_offset - offset + 1; } else sz = left; if (sz != 0) { proto_tree_add_text(subtree, tvb, offset, sz, "%s: %.*s", url_field_descr[n], sz - 1, tvb_get_ptr(tvb, offset, sz)); } else { proto_tree_add_text(subtree, tvb, offset, 0, "%s: %s", url_field_descr[n], "(empty)"); } offset += sz; left -= sz; } break; case MSG_EMAIL: for (n = 0; n < N_EMAIL_FIELDS; n++) { if (n != N_EMAIL_FIELDS - 1) { sep_offset = tvb_find_guint8(tvb, offset, left, 0xfe); sz = sep_offset - offset + 1; } else sz = left; if (sz != 0) { proto_tree_add_text(subtree, tvb, offset, sz, "%s: %.*s", email_field_descr[n], sz - 1, tvb_get_ptr(tvb, offset, sz)); } else { proto_tree_add_text(subtree, tvb, offset, 0, "%s: %s", email_field_descr[n], "(empty)"); } offset += sz; left -= sz; } break; case MSG_AUTH: { /* Three bytes, first is a char signifying success */ unsigned char auth_suc; auth_suc = tvb_get_guint8(tvb, offset); proto_tree_add_text(subtree, tvb, offset, 1, "Authorization: (%u) %s",auth_suc, (auth_suc==0)?"Denied":"Allowed"); offset++; proto_tree_add_text(subtree, tvb, offset, sizeof(guint16), "x1: 0x%04x", tvb_get_letohs(tvb, offset)); break; } case MSG_AUTH_REQ: for (n = 0; n < N_AUTH_REQ_FIELDS; n++) { if (n != N_AUTH_REQ_FIELDS - 1) { sep_offset = tvb_find_guint8(tvb, offset, left, 0xfe); sz = sep_offset - offset + 1; } else sz = left; if (sz != 0) { proto_tree_add_text(subtree, tvb, offset, sz, "%s: %.*s", auth_req_field_descr[n], sz - 1, tvb_get_ptr(tvb, offset, sz)); } else { proto_tree_add_text(subtree, tvb, offset, 0, "%s: %s", auth_req_field_descr[n], "(empty)"); } offset += sz; left -= sz; } break; case MSG_USER_ADDED: for (n = 0; n < N_USER_ADDED_FIELDS; n++) { if (n != N_USER_ADDED_FIELDS - 1) { sep_offset = tvb_find_guint8(tvb, offset, left, 0xfe); sz = sep_offset - offset + 1; } else sz = left; if (sz != 0) { proto_tree_add_text(subtree, tvb, offset, sz, "%s: %.*s", user_added_field_descr[n], sz - 1, tvb_get_ptr(tvb, offset, sz)); } else { proto_tree_add_text(subtree, tvb, offset, 0, "%s: %s", user_added_field_descr[n], "(empty)"); } offset += sz; left -= sz; } break; case MSG_CONTACTS: { gint sep_offset_prev; int sz = 0; /* Size of the current element */ int n = 0; /* The nth element */ gboolean last = FALSE; while (!last) { sep_offset = tvb_find_guint8(tvb, offset, left, 0xfe); if (sep_offset != -1) sz = sep_offset - offset + 1; else { sz = left; last = TRUE; } if (n == 0) { /* The first element is the number of Nick/UIN pairs follow */ proto_tree_add_text(subtree, tvb, offset, sz, "Number of pairs: %.*s", sz - 1, tvb_get_ptr(tvb, offset, sz)); n++; } else if (!last) { int svsz = sz; left -= sz; sep_offset_prev = sep_offset; sep_offset = tvb_find_guint8(tvb, sep_offset_prev, left, 0xfe); if (sep_offset != -1) sz = sep_offset - offset + 1; else { sz = left; last = TRUE; } proto_tree_add_text(subtree, tvb, offset, sz + svsz, "%.*s: %.*s", svsz - 1, tvb_get_ptr(tvb, offset, svsz), sz - 1, tvb_get_ptr(tvb, sep_offset_prev + 1, sz)); n += 2; } left -= (sz+1); offset = sep_offset + 1; } break; } } } /********************************* * * Client commands * *********************************/ static void icqv5_cmd_ack(proto_tree* tree,/* Tree to put the data in */ tvbuff_t *tvb, /* Tvbuff with decrypted packet data */ int offset) /* Offset from the start of the packet to the content */ { proto_tree* subtree; proto_item* ti; if (tree){ ti = proto_tree_add_text(tree, tvb, offset, 4, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); proto_tree_add_text(subtree, tvb, offset + CMD_ACK_RANDOM, 4, "Random: 0x%08x", tvb_get_letohl(tvb, offset + CMD_ACK_RANDOM)); } } static void icqv5_cmd_rand_search(proto_tree* tree, /* Tree to put the data in */ tvbuff_t *tvb, /* Decrypted packet content */ int offset, /* Offset from the start of the packet to the content */ int size) /* Number of chars left to do */ { guint16 group; proto_tree* subtree; proto_item* ti; static const char* groups[] = { "Name", "General", "Romance", "Games", "Students", "20 Something", "30 Something", "40 Something", "50 or worse", "Man want women", "Women want men" }; if (tree){ if (size < 4) { ti = proto_tree_add_text(tree, tvb, offset, size, "Body (%d bytes, should be 4)", size); return; } ti = proto_tree_add_text(tree, tvb, offset, 4, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); group = tvb_get_letohs(tvb, offset + CMD_RAND_SEARCH_GROUP); if (group>0 && (group<=sizeof(groups)/sizeof(const char*))) proto_tree_add_text(subtree, tvb, offset + CMD_RAND_SEARCH_GROUP, 4, "Group: (%u) %s", group, groups[group-1]); else proto_tree_add_text(subtree, tvb, offset + CMD_RAND_SEARCH_GROUP, 4, "Group: (%u)", group); } } static void icqv5_cmd_ack_messages(proto_tree* tree, /* Tree to put the data in */ tvbuff_t *tvb, /* Decrypted packet content */ int offset) /* Offset from the start of the packet to the content */ { proto_tree* subtree; proto_item* ti; if (tree){ ti = proto_tree_add_text(tree, tvb, offset, 4, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); proto_tree_add_text(subtree, tvb, offset + CMD_ACK_MESSAGES_RANDOM, 4, "Random: 0x%08x", tvb_get_letohl(tvb, offset + CMD_ACK_MESSAGES_RANDOM)); } } static void icqv5_cmd_keep_alive(proto_tree* tree, /* Tree to put the data in */ tvbuff_t *tvb, /* Decrypted packet content */ int offset) /* Offset from the start of the packet to the content */ { guint32 random; proto_tree* subtree; proto_item* ti; if (tree){ ti = proto_tree_add_text(tree, tvb, offset, 4, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); random = tvb_get_letohl(tvb, offset + CMD_KEEP_ALIVE_RANDOM); proto_tree_add_text(subtree, tvb, offset + CMD_KEEP_ALIVE_RANDOM, 4, "Random: 0x%08x", random); } } static void icqv5_cmd_send_text_code(proto_tree* tree, /* Tree to put the data in */ tvbuff_t *tvb, /* Decrypted packet content */ int offset, /* Offset from the start of the packet to the content */ int size) /* Number of chars left to do */ { proto_tree* subtree = NULL; proto_item* ti = NULL; guint16 len; guint16 x1; if (tree){ ti = proto_tree_add_text(tree, tvb, offset, size, "Body"); } len = tvb_get_letohs(tvb, offset+CMD_SEND_TEXT_CODE_LEN); if (tree){ subtree = proto_item_add_subtree(ti, ett_icq_body); proto_tree_add_text(subtree, tvb, offset + CMD_SEND_TEXT_CODE_LEN, 2, "Length: %d", len); } if (len>0) { if (tree){ proto_tree_add_text(subtree, tvb, offset + CMD_SEND_TEXT_CODE_TEXT, len, "Text: %.*s", len, tvb_get_ptr(tvb, offset + CMD_SEND_TEXT_CODE_TEXT, len)); } } x1 = tvb_get_letohs(tvb, offset + CMD_SEND_TEXT_CODE_TEXT + len); if (tree){ proto_tree_add_text(subtree, tvb, offset + CMD_SEND_TEXT_CODE_TEXT + len, 2, "X1: 0x%04x", x1); } } static void icqv5_cmd_add_to_list(proto_tree* tree, /* Tree to put the data in */ tvbuff_t *tvb, /* Decrypted packet content */ int offset) /* Offset from the start of the packet to the content */ { guint32 uin; proto_tree* subtree; proto_item* ti; if (tree){ ti = proto_tree_add_text(tree, tvb, offset, 4, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); uin = tvb_get_letohl(tvb, offset + CMD_ADD_TO_LIST); proto_tree_add_text(subtree, tvb, offset + CMD_ADD_TO_LIST_UIN, 4, "UIN: %u", uin); } } static void icqv5_cmd_status_change(proto_tree* tree, /* Tree to put the data in */ tvbuff_t *tvb, /* Decrypted packet content */ int offset) /* Offset from the start of the packet to the content */ { guint32 status; proto_tree* subtree; proto_item* ti; if (tree){ ti = proto_tree_add_text(tree, tvb, offset, 4, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); status = tvb_get_letohl(tvb, offset + CMD_STATUS_CHANGE_STATUS); proto_tree_add_text(subtree, tvb, offset + CMD_STATUS_CHANGE_STATUS, 4, "Status: %s", findStatus(status)); } } static void icqv5_cmd_send_msg(proto_tree* tree, tvbuff_t *tvb, int offset, int size) { proto_tree* subtree; proto_item* ti; int left = size; /* left chars to do */ if (tree) { if (size < 4) { ti = proto_tree_add_text(tree, tvb, offset, size, "Body (%d bytes, should be >= 4)", size); return; } ti = proto_tree_add_text(tree, tvb, offset, size, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); proto_tree_add_text(subtree, tvb, offset + CMD_SEND_MSG_RECV_UIN, 4, "Receiver UIN: %u", tvb_get_letohl(tvb, offset + CMD_SEND_MSG_RECV_UIN)); left -= 4; icqv5_decode_msgType(subtree, tvb, offset + CMD_SEND_MSG_MSG_TYPE, left); } } static void icqv5_cmd_login(proto_tree* tree, tvbuff_t *tvb, int offset, int size) { proto_item* ti; proto_tree* subtree; time_t theTime; char *aTime; guint32 port; guint32 passwdLen; const guchar *ipAddrp; guint32 status; if (tree) { ti = proto_tree_add_text(tree, tvb, offset, size, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); theTime = tvb_get_letohl(tvb, offset + CMD_LOGIN_TIME); aTime = ctime(&theTime); aTime[strlen(aTime)-1] = '\0'; proto_tree_add_text(subtree, tvb, offset + CMD_LOGIN_TIME, 4, "Time: %ld = %s", (long)theTime, aTime); port = tvb_get_letohl(tvb, offset + CMD_LOGIN_PORT); proto_tree_add_text(subtree, tvb, offset + CMD_LOGIN_PORT, 4, "Port: %u", port); passwdLen = tvb_get_letohs(tvb, offset + CMD_LOGIN_PASSLEN); proto_tree_add_text(subtree, tvb, offset + CMD_LOGIN_PASSLEN, 2 + passwdLen, "Passwd: %.*s", (int)passwdLen, tvb_get_ptr(tvb, offset + CMD_LOGIN_PASSWD, passwdLen)); ipAddrp = tvb_get_ptr(tvb, offset + CMD_LOGIN_PASSWD + passwdLen + CMD_LOGIN_IP, 4); proto_tree_add_text(subtree, tvb, offset + CMD_LOGIN_PASSWD + passwdLen + CMD_LOGIN_IP, 4, "IP: %s", ip_to_str(ipAddrp)); status = tvb_get_letohs(tvb, offset + CMD_LOGIN_PASSWD + passwdLen + CMD_LOGIN_STATUS); proto_tree_add_text(subtree, tvb, offset + CMD_LOGIN_PASSWD + passwdLen + CMD_LOGIN_STATUS, 4, "Status: %s", findStatus(status)); } } static void icqv5_cmd_contact_list(proto_tree* tree, tvbuff_t *tvb, int offset, int size) { proto_tree* subtree; proto_item* ti; unsigned char num; int i; guint32 uin; if (tree) { ti = proto_tree_add_text(tree, tvb, offset, size, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); num = tvb_get_guint8(tvb, offset + CMD_CONTACT_LIST_NUM); proto_tree_add_text(subtree, tvb, offset + CMD_CONTACT_LIST, 1, "Number of uins: %u", num); /* * A sequence of num times UIN follows */ offset += (CMD_CONTACT_LIST_NUM + 1); for (i = 0; i < num; i++) { uin = tvb_get_letohl(tvb, offset); proto_tree_add_text(subtree, tvb, offset, 4, "UIN[%d]: %u", i ,uin); offset += 4; } } } static void icqv5_cmd_no_params(proto_tree* tree, /* Tree to put the data in */ tvbuff_t *tvb, /* Decrypted packet content */ int offset) /* Offset from the start of the packet to the content */ { proto_tree* subtree; proto_item* ti; if (tree){ ti = proto_tree_add_text(tree, tvb, offset, 0, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); proto_tree_add_text(subtree, tvb, offset, 0, "No parameters"); } } /********************** * * Server commands * ********************** */ static void icqv5_srv_no_params(proto_tree* tree, /* Tree to put the data in */ tvbuff_t *tvb, /* Packet content */ int offset) /* Offset from the start of the packet to the content */ { proto_tree* subtree; proto_item* ti; if (tree){ ti = proto_tree_add_text(tree, tvb, offset, 0, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); proto_tree_add_text(subtree, tvb, offset, 0, "No Parameters"); } } static void icqv5_srv_login_reply(proto_tree* tree,/* Tree to put the data in */ tvbuff_t *tvb, /* Tvbuff with packet */ int offset, /* Offset from the start of the packet to the content */ int size) /* Number of chars left to do */ { proto_tree* subtree; proto_item* ti; const guchar *ipAddrp; if (tree) { if (size < SRV_LOGIN_REPLY_IP + 8) { ti = proto_tree_add_text(tree, tvb, offset, size, "Body (%d bytes, should be %d)", size, SRV_LOGIN_REPLY_IP + 8); return; } ti = proto_tree_add_text(tree, tvb, offset, SRV_LOGIN_REPLY_IP + 8, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); ipAddrp = tvb_get_ptr(tvb, offset + SRV_LOGIN_REPLY_IP, 4); proto_tree_add_text(subtree, tvb, offset + SRV_LOGIN_REPLY_IP, 4, "IP: %s", ip_to_str(ipAddrp)); } } static void icqv5_srv_user_online(proto_tree* tree,/* Tree to put the data in */ tvbuff_t *tvb, /* Tvbuff with packet */ int offset, /* Offset from the start of the packet to the content */ int size) /* Number of chars left to do */ { proto_tree* subtree; proto_item* ti; const guchar *ipAddrp; const guchar *realipAddrp; guint32 status; if (tree) { if (size < SRV_LOGIN_REPLY_IP + 8) { ti = proto_tree_add_text(tree, tvb, offset, size, "Body (%d bytes, should be %d)", size, SRV_LOGIN_REPLY_IP + 8); return; } ti = proto_tree_add_text(tree, tvb, offset, SRV_LOGIN_REPLY_IP + 8, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); proto_tree_add_text(subtree, tvb, offset + SRV_USER_ONL_UIN, 4, "UIN: %u", tvb_get_letohl(tvb, offset + SRV_USER_ONL_UIN)); ipAddrp = tvb_get_ptr(tvb, offset + SRV_USER_ONL_IP, 4); proto_tree_add_text(subtree, tvb, offset + SRV_USER_ONL_IP, 4, "IP: %s", ip_to_str(ipAddrp)); proto_tree_add_text(subtree, tvb, offset + SRV_USER_ONL_PORT, 4, "Port: %u", tvb_get_letohl(tvb, offset + SRV_USER_ONL_PORT)); realipAddrp = tvb_get_ptr(tvb, offset + SRV_USER_ONL_REALIP, 4); proto_tree_add_text(subtree, tvb, offset + SRV_USER_ONL_REALIP, 4, "RealIP: %s", ip_to_str(realipAddrp)); status = tvb_get_letohs(tvb, offset + SRV_USER_ONL_STATUS); proto_tree_add_text(subtree, tvb, offset + SRV_USER_ONL_STATUS, 2, "Status: %s", findStatus(status)); /* * Kojak: Hypothesis is that this field might be an encoding for the * version used by the UIN that changed. To test this, I included * this line to the code. */ proto_tree_add_text(subtree, tvb, offset + SRV_USER_ONL_X2, 4, "Version: %08x", tvb_get_letohl(tvb, offset + SRV_USER_ONL_X2)); } } static void icqv5_srv_user_offline(proto_tree* tree,/* Tree to put the data in */ tvbuff_t *tvb, /* Tvbuff with packet */ int offset, /* Offset from the start of the packet to the content */ int size) /* Number of chars left to do */ { proto_tree* subtree; proto_item* ti; if (tree) { if (size < SRV_USER_OFFLINE_UIN + 4) { ti = proto_tree_add_text(tree, tvb, offset, size, "Body (%d bytes, should be %d)", size, SRV_USER_OFFLINE_UIN + 4); return; } ti = proto_tree_add_text(tree, tvb, offset, SRV_USER_OFFLINE_UIN + 4, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); proto_tree_add_text(subtree, tvb, offset + SRV_USER_OFFLINE_UIN, 4, "UIN: %u", tvb_get_letohl(tvb, offset + SRV_USER_OFFLINE)); } } static void icqv5_srv_multi(proto_tree* tree, /* Tree to put the data in */ tvbuff_t *tvb, /* Packet content */ int offset, /* Offset from the start of the packet to the content */ int size, /* Number of chars left to do */ packet_info* pinfo) { proto_tree* subtree; proto_item* ti; guint8 num; guint16 pktSz; int i; if (tree) { ti = proto_tree_add_text(tree, tvb, offset, size, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); num = tvb_get_guint8(tvb, offset + SRV_MULTI_NUM); proto_tree_add_text(subtree, tvb, offset + SRV_MULTI_NUM, 1, "Number of pkts: %u", num); /* * A sequence of num times ( pktsize, packetData) follows */ offset += (SRV_MULTI_NUM + 1); for (i = 0; i < num; i++) { pktSz = tvb_get_letohs(tvb, offset); offset += 2; dissect_icqv5Server(tvb, offset, pinfo, subtree, pktSz); offset += pktSz; } } } static void icqv5_srv_meta_user(proto_tree* tree, /* Tree to put the data in */ tvbuff_t *tvb, /* Tvbuff with packet */ int offset, /* Offset from the start of the packet to the content */ int size) /* Number of chars left to do */ { #if 0 proto_tree* subtree = NULL; #endif proto_tree* sstree = NULL; proto_item* ti = NULL; int left = size; guint16 subcmd; unsigned char result; if (tree) { #if 0 ti = proto_tree_add_text(tree, tvb, offset, size, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); subcmd = tvb_get_letohs(tvb, offset + SRV_META_USER_SUBCMD); ti = proto_tree_add_text(subtree, tvb, offset + SRV_META_USER_SUBCMD, 2, "%s", findSubCmd(subcmd)); result = tvb_get_guint8(tvb, offset + SRV_META_USER_RESULT); proto_tree_add_text(subtree, tvb, offset + SRV_META_USER_RESULT, 1, "%s", (result==0x0a)?"Success":"Failure"); sstree = proto_item_add_subtree(ti, ett_icq_body_parts); #else subcmd = tvb_get_letohs(tvb, offset + SRV_META_USER_SUBCMD); ti = proto_tree_add_text(tree, tvb, offset + SRV_META_USER_SUBCMD, 2, "%s", findSubCmd(subcmd)); sstree = proto_item_add_subtree(ti, ett_icq_body_parts); result = tvb_get_guint8(tvb, offset + SRV_META_USER_RESULT); proto_tree_add_text(sstree, tvb, offset + SRV_META_USER_RESULT, 1, "%s", (result==0x0a)?"Success":"Failure"); #endif /* Skip the META_USER header */ left -= 3; offset += 3; switch(subcmd) { case META_EX_USER_FOUND: { /* This is almost the same as META_USER_FOUND, * however, there's an extra length field */ guint16 pktLen; /* Read the length field */ pktLen = tvb_get_letohs(tvb, offset); proto_tree_add_text(sstree, tvb, offset, sizeof(guint16), "Length: %u", pktLen); offset += sizeof(guint16); left -= sizeof(guint16); } case META_USER_FOUND: { /* The goto mentioned in this block should be local to this * block if C'd allow it. * * They are used to "implement" a poorman's exception handling */ int len = 0; char *descr[] = { "Nick", "First name", "Last name", "Email", NULL}; char** d = descr; unsigned char auth; /* * Read UIN */ proto_tree_add_text(sstree, tvb, offset, sizeof(guint32), "UIN: %u", tvb_get_letohl(tvb, offset)); offset+=sizeof(guint32);left-=sizeof(guint32); for ( ; *d!=NULL; d++) { len = proto_add_icq_attr(sstree, tvb, offset, *d); if (len == -1) return; offset += len; left -= len; } /* Get the authorize setting */ auth = tvb_get_guint8(tvb, offset); proto_tree_add_text(sstree, tvb, offset, 1, "authorization: %s", (auth==0x01)?"Neccessary":"Who needs it"); offset++; left--; /* Get x2 */ proto_tree_add_text(sstree, tvb, offset, sizeof(guint16), "x2: 0x%04x", tvb_get_letohs(tvb, offset)); offset+=sizeof(guint16);left-=sizeof(guint16); /* Get x3 */ proto_tree_add_text(sstree, tvb, offset, sizeof(guint32), "x3: 0x%08x", tvb_get_letohl(tvb, offset)); offset+=sizeof(guint32);left-=sizeof(guint32); break; } case META_ABOUT: { int len; /* Get the about information */ len = tvb_get_letohs(tvb, offset); offset+=sizeof(guint16);left-=sizeof(guint16); proto_tree_add_text(sstree, tvb, offset - sizeof(guint16), sizeof(guint16)+len, "About(%d): %.*s", len, len, tvb_get_ptr(tvb, offset, len)); offset+=len;left-=len; break; } case META_USER_INFO: { /* The goto mentioned in this block should be local to this * block if C'd allow it. * * They are used to "implement" a poorman's exception handling */ static const char* descr[] = { "Nick", "First name", "Last name", "Primary email", "Secundary email", "Old email", "City", "State", "Phone", "Fax", "Street", "Cellphone", "Zip", NULL}; const char** d = descr; guint16 country; guint8 user_timezone; guint8 auth; int len = 0; #if 0 /* Get the uin */ uin = tvb_get_letohl(tvb, offset); proto_tree_add_text(sstree, tvb, offset, sizeof(guint32), "UIN: %u", uin); offset+=sizeof(guint32);left-=sizeof(guint32); #endif /* * Get every field from the description */ while ((*d)!=NULL) { len = tvb_get_letohs(tvb, offset); offset+=sizeof(guint16);left-=sizeof(guint16); if (len>0) { proto_tree_add_text(sstree, tvb, offset - sizeof(guint16), sizeof(guint16)+len, "%s(%d): %.*s",*d, len, len - 1, tvb_get_ptr(tvb, offset, len - 1)); offset+=len;left-=len; } d++; } /* Get country code */ country = tvb_get_letohs(tvb, offset); proto_tree_add_text(sstree, tvb, offset, sizeof(guint16), "Countrycode: %u", country); offset+=sizeof(guint16); left-=sizeof(guint16); /* Get the timezone setting */ user_timezone = tvb_get_guint8(tvb, offset); proto_tree_add_text(sstree, tvb, offset, sizeof(unsigned char), "Timezone: %u", user_timezone); offset++; left--; /* Get the authorize setting */ auth = tvb_get_guint8(tvb, offset); proto_tree_add_text(sstree, tvb, offset, sizeof(unsigned char), "Authorization: (%u) %s", auth, (auth==0)?"No":"Yes"); offset++; left--; /* Get the webaware setting */ auth = tvb_get_guint8(tvb, offset); proto_tree_add_text(sstree, tvb, offset, sizeof(unsigned char), "Webaware: (%u) %s", auth, (auth==0)?"No":"Yes"); offset++; left--; /* Get the authorize setting */ auth = tvb_get_guint8(tvb, offset); proto_tree_add_text(sstree, tvb, offset, sizeof(unsigned char), "HideIP: (%u) %s", auth, (auth==0)?"No":"Yes"); offset++; left--; break; } default: /* This information is already printed in the tree */ fprintf(stderr, "Meta subcmd: %04x\n", subcmd); break; } } } static void icqv5_srv_recv_message(proto_tree* tree, /* Tree to put the data in */ tvbuff_t* tvb, /* Packet content */ int offset, /* Offset from the start of the packet to the content */ int size) /* Number of chars left to do */ { proto_tree* subtree = NULL; proto_item* ti = NULL; int left = size; guint16 year; guint8 month; guint8 day; guint8 hour; guint8 minute; if (tree) { ti = proto_tree_add_text(tree, tvb, offset, 4, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); proto_tree_add_item(subtree, hf_icq_uin, tvb, offset + SRV_RECV_MSG_UIN, sizeof(guint32), TRUE); year = tvb_get_letohs(tvb, offset + SRV_RECV_MSG_YEAR); month = tvb_get_guint8(tvb, offset + SRV_RECV_MSG_MONTH); day = tvb_get_guint8(tvb, offset + SRV_RECV_MSG_DAY); hour = tvb_get_guint8(tvb, offset + SRV_RECV_MSG_HOUR); minute = tvb_get_guint8(tvb, offset + SRV_RECV_MSG_MINUTE); proto_tree_add_text(subtree, tvb, offset + SRV_RECV_MSG_YEAR, sizeof(guint16) + 4*sizeof(unsigned char), "Time: %u-%u-%u %02u:%02u", day, month, year, hour, minute); icqv5_decode_msgType(subtree, tvb, offset + SRV_RECV_MSG_MSG_TYPE, left); } } static void icqv5_srv_rand_user(proto_tree* tree, /* Tree to put the data in */ tvbuff_t *tvb, /* Tvbuff with packet */ int offset) /* Offset from the start of the packet to the content */ { proto_tree* subtree = NULL; proto_item* ti = NULL; guint32 uin; const unsigned char* IP = NULL; guint32 port; const unsigned char* realIP = NULL; guint8 commClass; guint32 status; guint16 tcpVer; if (tree) { ti = proto_tree_add_text(tree, tvb, offset, SRV_RAND_USER_TCP_VER + 2, "Body"); subtree = proto_item_add_subtree(ti, ett_icq_body); /* guint32 UIN */ uin = tvb_get_letohl(tvb, offset + SRV_RAND_USER_UIN); proto_tree_add_text(subtree, tvb, offset + SRV_RAND_USER_UIN, sizeof(guint32), "UIN: %u", uin); /* guint32 IP */ IP = tvb_get_ptr(tvb, offset + SRV_RAND_USER_IP, 4); proto_tree_add_text(subtree, tvb, offset + SRV_RAND_USER_IP, sizeof(guint32), "IP: %s", ip_to_str(IP)); /* guint16 portNum */ /* XXX - 16 bits, or 32 bits? */ port = tvb_get_letohs(tvb, offset + SRV_RAND_USER_PORT); proto_tree_add_text(subtree, tvb, offset + SRV_RAND_USER_UIN, sizeof(guint32), "Port: %u", port); /* guint32 realIP */ realIP = tvb_get_ptr(tvb, offset + SRV_RAND_USER_REAL_IP, 4); proto_tree_add_text(subtree, tvb, offset + SRV_RAND_USER_REAL_IP, sizeof(guint32), "RealIP: %s", ip_to_str(realIP)); /* guint8 Communication Class */ commClass = tvb_get_guint8(tvb, offset + SRV_RAND_USER_CLASS); proto_tree_add_text(subtree, tvb, offset + SRV_RAND_USER_CLASS, sizeof(guint8), "Class: %s", (commClass!=4)?"User to User":"Through Server"); /* guint32 status */ /* XXX - 16 bits, or 32 bits? */ status = tvb_get_letohs(tvb, offset + SRV_RAND_USER_STATUS); proto_tree_add_text(subtree, tvb, offset + SRV_RAND_USER_STATUS, sizeof(guint32), "Status: %s", findStatus(status)); /* guint16 tcpVersion */ tcpVer = tvb_get_letohs(tvb, offset + SRV_RAND_USER_TCP_VER); proto_tree_add_text(subtree, tvb, offset + SRV_RAND_USER_TCP_VER, sizeof(guint16), "TCPVersion: %u", tcpVer); } } /* * Dissect all the v5 client traffic. This is encrypted, so be careful. */ static void dissect_icqv5Client(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { proto_tree *icq_tree = NULL; proto_tree *icq_header_tree = NULL; proto_item *ti = NULL; int pktsize; /* The actual size of the ICQ content */ int capturedsize; /* The captured size of the ICQ content */ guint32 rounded_size; guint32 key; guint16 cmd; guint8 *decr_pd; /* Decrypted content */ tvbuff_t *decr_tvb; pktsize = tvb_reported_length(tvb); capturedsize = tvb_length(tvb); /* Get the encryption key */ key = get_v5key(tvb, pktsize); /* * Make a copy of the packet data, and decrypt it. * The decryption processes 4 bytes at a time, and starts at * an offset of ICQ5_CL_SESSIONID (which isn't a multiple of 4), * so we make sure that there are * * (ICQ5_CL_SESSIONID + a multiple of 4) * * bytes in the buffer. */ rounded_size = ((((capturedsize - ICQ5_CL_SESSIONID) + 3)/4)*4) + ICQ5_CL_SESSIONID; decr_pd = g_malloc(rounded_size); tvb_memcpy(tvb, decr_pd, 0, capturedsize); decrypt_v5(decr_pd, rounded_size, key); /* Allocate a new tvbuff, referring to the decrypted data. */ decr_tvb = tvb_new_real_data(decr_pd, capturedsize, pktsize); /* Arrange that the allocated packet data copy be freed when the tvbuff is freed. */ tvb_set_free_cb(decr_tvb, g_free); /* Add the tvbuff to the list of tvbuffs to which the tvbuff we were handed refers, so it'll get cleaned up when that tvbuff is cleaned up. */ tvb_set_child_real_data_tvbuff(tvb, decr_tvb); /* Add the decrypted data to the data source list. */ add_new_data_source(pinfo, decr_tvb, "Decrypted"); cmd = tvb_get_letohs(decr_tvb, ICQ5_CL_CMD); if (check_col(pinfo->cinfo, COL_INFO)) col_add_fstr(pinfo->cinfo, COL_INFO, "ICQv5 %s", findClientCmd(cmd)); if (tree) { ti = proto_tree_add_protocol_format(tree, proto_icq, tvb, 0, -1, "ICQv5 %s (len %u)", findClientCmd(cmd), pktsize); icq_tree = proto_item_add_subtree(ti, ett_icq); ti = proto_tree_add_uint_format(icq_tree, hf_icq_type, tvb, 0, ICQ5_CL_HDRSIZE, ICQ5_client, "Header"); icq_header_tree = proto_item_add_subtree(ti, ett_icq_header); proto_tree_add_text(icq_header_tree, tvb, ICQ_VERSION, 2, "Version: %u", tvb_get_letohs(tvb, ICQ_VERSION)); proto_tree_add_item(icq_header_tree, hf_icq_uin, tvb, ICQ5_CL_UIN, 4, TRUE); proto_tree_add_item(icq_header_tree, hf_icq_sessionid, decr_tvb, ICQ5_CL_SESSIONID, 4, TRUE); proto_tree_add_uint_format(icq_header_tree, hf_icq_client_cmd, decr_tvb, ICQ5_CL_CMD, 2, cmd, "Command: %s (%u)", val_to_str(cmd, clientCmdCode, "Unknown"), cmd); proto_tree_add_text(icq_header_tree, decr_tvb, ICQ5_CL_SEQNUM1, 2, "Seq Number 1: 0x%04x", tvb_get_letohs(decr_tvb, ICQ5_CL_SEQNUM1)); proto_tree_add_text(icq_header_tree, decr_tvb, ICQ5_CL_SEQNUM2, 2, "Seq Number 2: 0x%04x", tvb_get_letohs(decr_tvb, ICQ5_CL_SEQNUM2)); proto_tree_add_uint_format(icq_header_tree, hf_icq_checkcode, tvb, ICQ5_CL_CHECKCODE, 4, key, "Key: 0x%08x", key); switch(cmd) { case CMD_ACK: icqv5_cmd_ack(icq_tree, decr_tvb, ICQ5_CL_HDRSIZE); break; case CMD_SEND_MSG: case CMD_MSG_TO_NEW_USER: icqv5_cmd_send_msg(icq_tree, decr_tvb, ICQ5_CL_HDRSIZE, pktsize - ICQ5_CL_HDRSIZE); break; case CMD_RAND_SEARCH: icqv5_cmd_rand_search(icq_tree, decr_tvb, ICQ5_CL_HDRSIZE, pktsize - ICQ5_CL_HDRSIZE); break; case CMD_LOGIN: icqv5_cmd_login(icq_tree, decr_tvb, ICQ5_CL_HDRSIZE, pktsize - ICQ5_CL_HDRSIZE); break; case CMD_SEND_TEXT_CODE: icqv5_cmd_send_text_code(icq_tree, decr_tvb, ICQ5_CL_HDRSIZE, pktsize - ICQ5_CL_HDRSIZE); break; case CMD_STATUS_CHANGE: icqv5_cmd_status_change(icq_tree, decr_tvb, ICQ5_CL_HDRSIZE); break; case CMD_ACK_MESSAGES: icqv5_cmd_ack_messages(icq_tree, decr_tvb, ICQ5_CL_HDRSIZE); break; case CMD_KEEP_ALIVE: icqv5_cmd_keep_alive(icq_tree, decr_tvb, ICQ5_CL_HDRSIZE); break; case CMD_ADD_TO_LIST: icqv5_cmd_add_to_list(icq_tree, decr_tvb, ICQ5_CL_HDRSIZE); break; case CMD_CONTACT_LIST: icqv5_cmd_contact_list(icq_tree, decr_tvb, ICQ5_CL_HDRSIZE, pktsize - ICQ5_CL_HDRSIZE); break; case CMD_META_USER: case CMD_REG_NEW_USER: case CMD_QUERY_SERVERS: case CMD_QUERY_ADDONS: icqv5_cmd_no_params(icq_tree, decr_tvb, ICQ5_CL_HDRSIZE); break; default: proto_tree_add_text(icq_tree, decr_tvb, ICQ5_CL_HDRSIZE, pktsize - ICQ5_CL_HDRSIZE, "Body"); fprintf(stderr,"Missing: %s\n", findClientCmd(cmd)); break; } } } static void dissect_icqv5Server(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree, int pktsize) { /* Server traffic is easy, not encrypted */ proto_tree *icq_tree = NULL; proto_tree *icq_header_tree = NULL; proto_item *ti = NULL; int changeCol = (pktsize==-1); guint16 cmd; cmd = tvb_get_letohs(tvb, offset + ICQ5_SRV_CMD); if (changeCol && check_col(pinfo->cinfo, COL_INFO)) col_add_fstr(pinfo->cinfo, COL_INFO, "ICQv5 %s", findServerCmd(cmd)); if (pktsize == -1) pktsize = tvb_reported_length(tvb); if (tree) { ti = proto_tree_add_protocol_format(tree, proto_icq, tvb, offset, pktsize, "ICQv5 %s (len %u)", findServerCmd(cmd), pktsize); icq_tree = proto_item_add_subtree(ti, ett_icq); ti = proto_tree_add_uint_format(icq_tree, hf_icq_type, tvb, offset, ICQ5_SRV_HDRSIZE, ICQ5_server, "Header"); icq_header_tree = proto_item_add_subtree(ti, ett_icq_header); proto_tree_add_text(icq_header_tree, tvb, offset + ICQ_VERSION, 2, "Version: %u", tvb_get_letohs(tvb, ICQ_VERSION)); proto_tree_add_item(icq_header_tree, hf_icq_sessionid, tvb, offset + ICQ5_SRV_SESSIONID, 4, TRUE); proto_tree_add_uint_format(icq_header_tree, hf_icq_server_cmd, tvb, offset + ICQ5_SRV_CMD, 2, cmd, "Command: %s (%u)", val_to_str(cmd, serverCmdCode, "Unknown"), cmd); proto_tree_add_text(icq_header_tree, tvb, offset + ICQ5_SRV_SEQNUM1, 2, "Seq Number 1: 0x%04x", tvb_get_letohs(tvb, offset + ICQ5_SRV_SEQNUM1)); proto_tree_add_text(icq_header_tree, tvb, offset + ICQ5_SRV_SEQNUM2, 2, "Seq Number 2: 0x%04x", tvb_get_letohs(tvb, offset + ICQ5_SRV_SEQNUM2)); proto_tree_add_item(icq_header_tree, hf_icq_uin, tvb, offset + ICQ5_SRV_UIN, 4, TRUE); proto_tree_add_item(icq_header_tree, hf_icq_checkcode, tvb, offset + ICQ5_SRV_CHECKCODE, 4, TRUE); switch (cmd) { case SRV_RAND_USER: icqv5_srv_rand_user(icq_tree, tvb, offset + ICQ5_SRV_HDRSIZE); break; case SRV_SYS_DELIVERED_MESS: /* The message structures are all the same. Why not run * the same routine? */ icqv5_cmd_send_msg(icq_tree, tvb, offset + ICQ5_SRV_HDRSIZE, pktsize - ICQ5_SRV_HDRSIZE); break; case SRV_USER_ONLINE: icqv5_srv_user_online(icq_tree, tvb, offset + ICQ5_SRV_HDRSIZE, pktsize - ICQ5_SRV_HDRSIZE); break; case SRV_USER_OFFLINE: icqv5_srv_user_offline(icq_tree, tvb, offset + ICQ5_SRV_HDRSIZE, pktsize - ICQ5_SRV_HDRSIZE); break; case SRV_LOGIN_REPLY: icqv5_srv_login_reply(icq_tree, tvb, offset + ICQ5_SRV_HDRSIZE, pktsize - ICQ5_SRV_HDRSIZE); break; case SRV_META_USER: icqv5_srv_meta_user(icq_tree, tvb, offset + ICQ5_SRV_HDRSIZE, pktsize - ICQ5_SRV_HDRSIZE); break; case SRV_RECV_MESSAGE: icqv5_srv_recv_message(icq_tree, tvb, offset + ICQ5_SRV_HDRSIZE, pktsize - ICQ5_SRV_HDRSIZE); break; case SRV_MULTI: icqv5_srv_multi(icq_tree, tvb, offset + ICQ5_SRV_HDRSIZE, pktsize - ICQ5_SRV_HDRSIZE, pinfo); break; case SRV_ACK: case SRV_SILENT_TOO_LONG: case SRV_GO_AWAY: case SRV_NEW_UIN: case SRV_BAD_PASS: case SRV_UPDATE_SUCCESS: icqv5_srv_no_params(icq_tree, tvb, offset + ICQ5_SRV_HDRSIZE); break; default: proto_tree_add_text(icq_tree, tvb, offset + ICQ5_SRV_HDRSIZE, pktsize - ICQ5_SRV_HDRSIZE, "Body"); fprintf(stderr,"Missing: %s\n", findServerCmd(cmd)); break; } } } static void dissect_icqv5(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { guint32 unknown; if (check_col(pinfo->cinfo, COL_PROTOCOL)) col_set_str(pinfo->cinfo, COL_PROTOCOL, "ICQv5 (UDP)"); if (check_col(pinfo->cinfo, COL_INFO)) col_set_str(pinfo->cinfo, COL_INFO, "ICQv5 packet"); unknown = tvb_get_letohl(tvb, ICQ5_UNKNOWN); if (unknown == 0x0L) { dissect_icqv5Client(tvb, pinfo, tree); } else { dissect_icqv5Server(tvb, 0, pinfo, tree, -1); } } static void dissect_icq(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { int version; if (check_col(pinfo->cinfo, COL_PROTOCOL)) { col_set_str(pinfo->cinfo, COL_PROTOCOL, "ICQ"); } if (check_col(pinfo->cinfo, COL_INFO)) { col_clear(pinfo->cinfo, COL_INFO); } version = tvb_get_letohs(tvb, ICQ_VERSION); switch (version) { case 0x0005: dissect_icqv5(tvb, pinfo, tree); break; case 0x0004: dissect_icqv4(tvb, pinfo, tree); break; case 0x0003: dissect_icqv3(tvb, pinfo, tree); break; case 0x0002: dissect_icqv2(tvb, pinfo, tree); break; default: fprintf(stderr, "ICQ: Unknown version (%d)\n", version); break; } } /* registration with the filtering engine */ void proto_register_icq(void) { static hf_register_info hf[] = { { &hf_icq_type, {"Type", "icq.type", FT_UINT16, BASE_DEC, NULL, 0x0, "", HFILL }}, { &hf_icq_uin, {"UIN", "icq.uin", FT_UINT32, BASE_DEC, NULL, 0x0, "", HFILL }}, { &hf_icq_sessionid, {"Session ID", "icq.sessionid", FT_UINT32, BASE_HEX, NULL, 0x0, "", HFILL }}, { &hf_icq_client_cmd, {"Client command", "icq.client_cmd", FT_UINT16, BASE_HEX, VALS(clientCmdCode), 0x0, "", HFILL }}, { &hf_icq_server_cmd, {"Server command", "icq.server_cmd", FT_UINT16, BASE_DEC, VALS(serverCmdCode), 0x0, "", HFILL }}, { &hf_icq_checkcode, {"Checkcode", "icq.checkcode", FT_UINT32, BASE_HEX, NULL, 0x0, "", HFILL }}, { &hf_icq_decode, {"Decode", "icq.decode", FT_STRING, BASE_NONE, NULL, 0x0, "", HFILL }} }; static gint *ett[] = { &ett_icq, &ett_icq_header, &ett_icq_decode, &ett_icq_body, &ett_icq_body_parts, }; proto_icq = proto_register_protocol("ICQ Protocol", "ICQ", "icq"); proto_register_field_array(proto_icq, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); } void proto_reg_handoff_icq(void) { dissector_handle_t icq_handle; icq_handle = create_dissector_handle(dissect_icq, proto_icq); dissector_add("udp.port", UDP_PORT_ICQ, icq_handle); }