/* packet-dcc.c * Routines for Distributed Checksum Clearinghouse packet dissection * DCC Home: http://www.rhyolite.com/anti-spam/dcc/ * * Copyright 1999, Nathan Neulinger * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * Copied from packet-tftp.c * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include #include #include "packet-dcc.h" void proto_register_dcc(void); void proto_reg_handoff_dcc(void); static int proto_dcc = -1; static int hf_dcc_len = -1; static int hf_dcc_pkt_vers = -1; static int hf_dcc_op = -1; static int hf_dcc_clientid = -1; static int hf_dcc_opnums_host = -1; static int hf_dcc_opnums_pid = -1; static int hf_dcc_opnums_report = -1; static int hf_dcc_opnums_retrans = -1; static int hf_dcc_signature = -1; static int hf_dcc_max_pkt_vers = -1; static int hf_dcc_qdelay_ms = -1; static int hf_dcc_brand = -1; static int hf_dcc_ck_type = -1; static int hf_dcc_ck_len = -1; static int hf_dcc_ck_sum = -1; static int hf_dcc_date = -1; static int hf_dcc_target = -1; static int hf_dcc_response_text = -1; static int hf_dcc_adminop = -1; static int hf_dcc_adminval = -1; static int hf_dcc_floodop = -1; static int hf_dcc_trace = -1; static int hf_dcc_trace_admin = -1; static int hf_dcc_trace_anon = -1; static int hf_dcc_trace_client = -1; static int hf_dcc_trace_rlim = -1; static int hf_dcc_trace_query = -1; static int hf_dcc_trace_ridc = -1; static int hf_dcc_trace_flood = -1; static int hf_dcc_addr = -1; static int hf_dcc_id = -1; static int hf_dcc_last_used = -1; static int hf_dcc_requests = -1; static int hf_dcc_pad = -1; static int hf_dcc_unused = -1; static gint ett_dcc = -1; static gint ett_dcc_opnums = -1; static gint ett_dcc_op = -1; static gint ett_dcc_ck = -1; static gint ett_dcc_trace = -1; static expert_field ei_dcc_len = EI_INIT; /* Utility macros */ #define D_SIGNATURE() \ proto_tree_add_item(dcc_optree, hf_dcc_signature, tvb, \ offset, (int)sizeof(DCC_SIGNATURE), ENC_NA); \ offset += (int)sizeof(DCC_SIGNATURE); #define D_LABEL(hf_label, len, encoding) \ proto_tree_add_item(dcc_optree, hf_label, tvb, offset, len, encoding); \ offset += len; #define D_TEXT(hf_label, endpad) { \ int next_offset,left; \ while (tvb_offset_exists(tvb, offset+endpad)) { \ left = tvb_reported_length_remaining(tvb,offset) - endpad; \ tvb_find_line_end(tvb, offset, left, &next_offset, \ FALSE); \ proto_tree_add_item(dcc_optree, hf_label, tvb, offset, \ next_offset - offset, ENC_ASCII|ENC_NA); \ offset = next_offset; \ } \ } #define D_TARGET() \ proto_tree_add_item(dcc_tree, hf_dcc_target, tvb, \ offset, (int)sizeof(DCC_TGTS), ENC_BIG_ENDIAN); \ offset += (int)sizeof(DCC_TGTS); #define D_DATE() { \ nstime_t ts; \ ts.nsecs = 0; \ ts.secs = tvb_get_ntohl(tvb,offset); \ proto_tree_add_time(dcc_optree, hf_dcc_date, tvb, offset, 4, &ts); \ offset += 4; \ } #define D_CHECKSUM() { \ proto_tree *cktree; \ cktree = proto_tree_add_subtree_format(dcc_optree, tvb, offset, (int)sizeof(DCC_CK), \ ett_dcc_ck, NULL, "Checksum - %s", val_to_str(tvb_get_guint8(tvb,offset), \ dcc_cktype_vals, \ "Unknown Type: %u")); \ proto_tree_add_item(cktree, hf_dcc_ck_type, tvb, offset, 1, ENC_BIG_ENDIAN); \ offset += 1; \ proto_tree_add_item(cktree, hf_dcc_ck_len, tvb, offset, 1, ENC_BIG_ENDIAN); \ offset += 1; \ proto_tree_add_item(cktree, hf_dcc_ck_sum, tvb, offset, \ (int)sizeof(DCC_SUM), ENC_NA); \ offset += (int)sizeof(DCC_SUM); \ } /* Lookup string tables */ static const value_string dcc_op_vals[] = { {DCC_OP_INVALID, "Invalid Op"}, {DCC_OP_NOP, "No-Op"}, {DCC_OP_REPORT, "Report and Query"}, {DCC_OP_QUERY, "Query"}, {DCC_OP_QUERY_RESP, "Server Response"}, {DCC_OP_ADMN, "Admin"}, {DCC_OP_OK, "Ok"}, {DCC_OP_ERROR, "Server Failing"}, {DCC_OP_DELETE, "Delete Checksum(s)"}, {0, NULL} }; static const value_string dcc_cktype_vals[] = { {DCC_CK_INVALID, "Invalid/Deleted from DB when seen"}, {DCC_CK_IP, "MD5 of binary source IPv6 address"}, {DCC_CK_ENV_FROM, "MD5 of envelope Mail From value"}, {DCC_CK_FROM, "MD5 of header From: line"}, {DCC_CK_SUB, "MD5 of substitute header line"}, {DCC_CK_MESSAGE_ID, "MD5 of header Message-ID: line"}, {DCC_CK_RECEIVED, "MD5 of last header Received: line"}, {DCC_CK_BODY, "MD5 of body"}, {DCC_CK_FUZ1, "MD5 of filtered body - FUZ1"}, {DCC_CK_FUZ2, "MD5 of filtered body - FUZ2"}, {DCC_CK_FUZ3, "MD5 of filtered body - FUZ3"}, {DCC_CK_FUZ4, "MD5 of filtered body - FUZ4"}, {DCC_CK_SRVR_ID, "hostname for server-ID check "}, {DCC_CK_ENV_TO, "MD5 of envelope Rcpt To value"}, {0, NULL}, }; static const value_string dcc_adminop_vals[] = { {DCC_AOP_OK, "Never sent"}, {DCC_AOP_STOP, "Stop Gracefully"}, {DCC_AOP_NEW_IDS, "Load keys and client IDs"}, {DCC_AOP_FLOD, "Flood control"}, {DCC_AOP_DB_UNLOCK, "Start Switch to new database"}, {DCC_AOP_DB_NEW, "Finish Switch to new database"}, {DCC_AOP_STATS, "Return counters"}, {DCC_AOP_STATS_CLEAR, "Return and zero counters"}, {DCC_AOP_TRACE_ON, "Enable tracing"}, {DCC_AOP_TRACE_OFF, "Disable tracing"}, {DCC_AOP_CUR_CLIENTS, "List clients"}, {0, NULL}, }; static const value_string dcc_target_vals[] = { {DCC_TGTS_TOO_MANY, "Targets (>= 16777200)"}, {DCC_TGTS_OK, "Certified not spam"}, {DCC_TGTS_OK2, "Half certified not spam"}, {DCC_TGTS_DEL, "Deleted checksum"}, {DCC_TGTS_INVALID, "Invalid"}, {0, NULL}, }; static const value_string dcc_floodop_vals[] = { {DCC_AOP_FLOD_CHECK, "Check"}, {DCC_AOP_FLOD_SHUTDOWN, "Shutdown"}, {DCC_AOP_FLOD_HALT, "Halt"}, {DCC_AOP_FLOD_RESUME, "Resume"}, {DCC_AOP_FLOD_REWIND, "Rewind"}, {DCC_AOP_FLOD_LIST, "List"}, {DCC_AOP_FLOD_STATS, "Stats"}, {DCC_AOP_FLOD_STATS_CLEAR, "Clear Stats"}, {0,NULL}, }; static gboolean dissect_dcc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { proto_tree *dcc_tree, *dcc_optree, *dcc_opnumtree, *ti; proto_tree *dcc_tracetree; proto_item *len_item; int offset = 0; int client_is_le = 0; int op = 0; int i, is_response; if (pinfo->srcport != DCC_PORT && pinfo->destport != DCC_PORT) { /* Not the right port - not a DCC packet. */ return FALSE; } /* get at least a full packet structure */ if ( tvb_reported_length(tvb) < sizeof(DCC_HDR) ) { /* Doesn't have enough bytes to contain packet header. */ return FALSE; } col_set_str(pinfo->cinfo, COL_PROTOCOL, "DCC"); offset = 0; is_response = pinfo->srcport == DCC_PORT; col_add_fstr(pinfo->cinfo, COL_INFO, "%s: %s", is_response ? "Response" : "Request", val_to_str(tvb_get_guint8(tvb, offset+3), dcc_op_vals, "Unknown Op: %u")); ti = proto_tree_add_item(tree, proto_dcc, tvb, offset, -1, ENC_NA); dcc_tree = proto_item_add_subtree(ti, ett_dcc); len_item = proto_tree_add_item(dcc_tree, hf_dcc_len, tvb, offset, 2, ENC_BIG_ENDIAN); if ( tvb_reported_length(tvb) < tvb_get_ntohs(tvb, offset)) { /* Doesn't have number of bytes that header claims. */ expert_add_info(pinfo, len_item, &ei_dcc_len); } offset += 2; if (tree) { proto_tree_add_item(dcc_tree, hf_dcc_pkt_vers, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; op = tvb_get_guint8(tvb, offset); proto_tree_add_item(dcc_tree, hf_dcc_op, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; proto_tree_add_item(dcc_tree, hf_dcc_clientid, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; dcc_opnumtree = proto_tree_add_subtree(dcc_tree, tvb, offset, -1, ett_dcc_opnums, NULL, "Operation Numbers (Opaque to Server)"); /* Note - these are indeterminate - they are sortof considered opaque to the client */ /* Make some attempt to figure out if this data is little endian, not guaranteed to be correct if connection went through a firewall or similar. */ /* Very hokey check - if all three of pid/report/retrans look like little-endian numbers, host is probably little endian. Probably innacurate on super-heavily-used DCC clients though. This should be good enough for now. */ client_is_le = ( (tvb_get_guint8(tvb, offset+4) | tvb_get_guint8(tvb, offset+5)) && (tvb_get_guint8(tvb, offset+8) | tvb_get_guint8(tvb, offset+9)) && (tvb_get_guint8(tvb, offset+12) | tvb_get_guint8(tvb, offset+13)) ); proto_tree_add_item(dcc_opnumtree, hf_dcc_opnums_host, tvb, offset, 4, client_is_le); offset += 4; proto_tree_add_item(dcc_opnumtree, hf_dcc_opnums_pid, tvb, offset, 4, client_is_le); offset += 4; proto_tree_add_item(dcc_opnumtree, hf_dcc_opnums_report, tvb, offset, 4, client_is_le); offset += 4; proto_tree_add_item(dcc_opnumtree, hf_dcc_opnums_retrans, tvb, offset, 4, client_is_le); offset += 4; dcc_optree = proto_tree_add_subtree_format(dcc_tree, tvb, offset, -1, ett_dcc_op, NULL, "Operation: %s", val_to_str(op, dcc_op_vals, "Unknown Op: %u")); switch(op) { case DCC_OP_NOP: D_SIGNATURE(); break; case DCC_OP_REPORT: D_TARGET(); for (i=0; i<=DCC_QUERY_MAX && tvb_bytes_exist(tvb, offset+(int)sizeof(DCC_SIGNATURE),1); i++) { D_CHECKSUM(); } D_SIGNATURE(); break; case DCC_OP_QUERY_RESP: for (i=0; i<=DCC_QUERY_MAX && tvb_bytes_exist(tvb, offset+(int)sizeof(DCC_SIGNATURE),1); i++) { D_TARGET(); } D_SIGNATURE(); break; case DCC_OP_ADMN: if ( is_response ) { int left_local = tvb_reported_length_remaining(tvb, offset) - (int)sizeof(DCC_SIGNATURE); if ( left_local == sizeof(DCC_ADMN_RESP_CLIENTS) ) { D_LABEL(hf_dcc_addr, 16, ENC_NA); D_LABEL(hf_dcc_id, (int)sizeof(DCC_CLNT_ID), ENC_BIG_ENDIAN); D_LABEL(hf_dcc_last_used, 4, ENC_BIG_ENDIAN); D_LABEL(hf_dcc_requests, 4, ENC_BIG_ENDIAN); } else { D_TEXT(hf_dcc_response_text, (int)sizeof(DCC_SIGNATURE)); } D_SIGNATURE(); } else { int aop; D_DATE(); aop = tvb_get_guint8(tvb, offset+4); proto_tree_add_item(dcc_optree, hf_dcc_adminop, tvb, offset+4, 1, ENC_BIG_ENDIAN); col_append_fstr(pinfo->cinfo, COL_INFO, ", %s", val_to_str(tvb_get_guint8(tvb,offset+4), dcc_adminop_vals, "Unknown (%u)")); if (aop == DCC_AOP_TRACE_ON || aop == DCC_AOP_TRACE_OFF ) { ti = proto_tree_add_item(dcc_optree, hf_dcc_trace, tvb, offset, 4, ENC_BIG_ENDIAN); dcc_tracetree = proto_item_add_subtree(ti, ett_dcc_trace); proto_tree_add_item(dcc_tracetree, hf_dcc_trace_admin, tvb, offset, 4, ENC_BIG_ENDIAN); proto_tree_add_item(dcc_tracetree, hf_dcc_trace_anon, tvb, offset, 4, ENC_BIG_ENDIAN); proto_tree_add_item(dcc_tracetree, hf_dcc_trace_client, tvb, offset, 4, ENC_BIG_ENDIAN); proto_tree_add_item(dcc_tracetree, hf_dcc_trace_rlim, tvb, offset, 4, ENC_BIG_ENDIAN); proto_tree_add_item(dcc_tracetree, hf_dcc_trace_query, tvb, offset, 4, ENC_BIG_ENDIAN); proto_tree_add_item(dcc_tracetree, hf_dcc_trace_ridc, tvb, offset, 4, ENC_BIG_ENDIAN); proto_tree_add_item(dcc_tracetree, hf_dcc_trace_flood, tvb, offset, 4, ENC_BIG_ENDIAN); } else if ( aop == DCC_AOP_FLOD ) { proto_tree_add_item(dcc_optree, hf_dcc_floodop, tvb, offset, 4, ENC_BIG_ENDIAN); col_append_fstr(pinfo->cinfo, COL_INFO, ", %s", val_to_str(tvb_get_ntohl(tvb,offset), dcc_floodop_vals, "Unknown (%u)")); } else { proto_tree_add_item(dcc_optree, hf_dcc_adminval, tvb, offset, 4, ENC_BIG_ENDIAN); } offset += 4; offset += 1; /* admin op we did in reverse order */ D_LABEL(hf_dcc_pad, 3, ENC_NA); D_SIGNATURE(); } break; case DCC_OP_OK: proto_tree_add_item(dcc_optree, hf_dcc_max_pkt_vers, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; D_LABEL(hf_dcc_unused, 1, ENC_NA); proto_tree_add_item(dcc_optree, hf_dcc_qdelay_ms, tvb, offset, 2, ENC_BIG_ENDIAN); offset += 2; proto_tree_add_item(dcc_optree, hf_dcc_brand, tvb, offset, (int)sizeof(DCC_BRAND), ENC_ASCII|ENC_NA); offset += (int)sizeof(DCC_BRAND); D_SIGNATURE(); break; default: /* do nothing */ break; } } return TRUE; } void proto_register_dcc(void) { static hf_register_info hf[] = { { &hf_dcc_len, { "Packet Length", "dcc.len", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }}, { &hf_dcc_pkt_vers, { "Packet Version", "dcc.pkt_vers", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }}, { &hf_dcc_op, { "Operation Type", "dcc.op", FT_UINT8, BASE_DEC, VALS(dcc_op_vals), 0, NULL, HFILL }}, { &hf_dcc_clientid, { "Client ID", "dcc.clientid", FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }}, { &hf_dcc_opnums_host, { "Host", "dcc.opnums.host", FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }}, { &hf_dcc_opnums_pid, { "Process ID", "dcc.opnums.pid", FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }}, { &hf_dcc_opnums_report, { "Report", "dcc.opnums.report", FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }}, { &hf_dcc_opnums_retrans, { "Retransmission", "dcc.opnums.retrans", FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }}, { &hf_dcc_signature, { "Signature", "dcc.signature", FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_dcc_max_pkt_vers, { "Maximum Packet Version", "dcc.max_pkt_vers", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }}, { &hf_dcc_qdelay_ms, { "Client Delay", "dcc.qdelay_ms", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }}, { &hf_dcc_brand, { "Server Brand", "dcc.brand", FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_dcc_ck_type, { "Type", "dcc.checksum.type", FT_UINT8, BASE_DEC, VALS(dcc_cktype_vals), 0, "Checksum Type", HFILL }}, { &hf_dcc_ck_len, { "Length", "dcc.checksum.length", FT_UINT8, BASE_DEC, NULL, 0, "Checksum Length", HFILL }}, { &hf_dcc_ck_sum, { "Sum", "dcc.checksum.sum", FT_BYTES, BASE_NONE, NULL, 0, "Checksum", HFILL }}, { &hf_dcc_target, { "Target", "dcc.target", FT_UINT32, BASE_HEX, VALS(dcc_target_vals), 0, NULL, HFILL }}, { &hf_dcc_response_text, { "Response Text", "dcc.response_text", FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_dcc_date, { "Date", "dcc.date", FT_ABSOLUTE_TIME, ABSOLUTE_TIME_LOCAL, NULL, 0, NULL, HFILL }}, { &hf_dcc_adminop, { "Admin Op", "dcc.adminop", FT_UINT8, BASE_DEC, VALS(dcc_adminop_vals), 0, NULL, HFILL }}, { &hf_dcc_adminval, { "Admin Value", "dcc.adminval", FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }}, { &hf_dcc_trace, { "Trace Bits", "dcc.trace", FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL }}, { &hf_dcc_trace_admin, { "Admin Requests", "dcc.trace.admin", FT_BOOLEAN, 32, NULL, 0x00000001, NULL, HFILL }}, { &hf_dcc_trace_anon, { "Anonymous Requests", "dcc.trace.anon", FT_BOOLEAN, 32, NULL, 0x00000002, NULL, HFILL }}, { &hf_dcc_trace_client, { "Authenticated Client Requests", "dcc.trace.client", FT_BOOLEAN, 32, NULL, 0x00000004, NULL, HFILL }}, { &hf_dcc_trace_rlim, { "Rate-Limited Requests", "dcc.trace.rlim", FT_BOOLEAN, 32, NULL, 0x00000008, NULL, HFILL }}, { &hf_dcc_trace_query, { "Queries and Reports", "dcc.trace.query", FT_BOOLEAN, 32, NULL, 0x00000010, NULL, HFILL }}, { &hf_dcc_trace_ridc, { "RID Cache Messages", "dcc.trace.ridc", FT_BOOLEAN, 32, NULL, 0x00000020, NULL, HFILL }}, { &hf_dcc_trace_flood, { "Input/Output Flooding", "dcc.trace.flood", FT_BOOLEAN, 32, NULL, 0x00000040, NULL, HFILL }}, { &hf_dcc_floodop, { "Flood Control Operation", "dcc.floodop", FT_UINT32, BASE_DEC, VALS(dcc_floodop_vals), 0, NULL, HFILL }}, { &hf_dcc_id, { "Id", "dcc.id", FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }}, { &hf_dcc_last_used, { "Last Used", "dcc.last_used", FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }}, { &hf_dcc_requests, { "Requests", "dcc.requests", FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }}, { &hf_dcc_addr, { "Addr", "dcc.addr", FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_dcc_pad, { "Pad", "dcc.pad", FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_dcc_unused, { "Unused", "dcc.unused", FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }}, }; static gint *ett[] = { &ett_dcc, &ett_dcc_op, &ett_dcc_ck, &ett_dcc_opnums, &ett_dcc_trace, }; static ei_register_info ei[] = { { &ei_dcc_len, { "dcc.len.short", PI_MALFORMED, PI_ERROR, "Error - packet is shorter than header claims!", EXPFILL }}, }; expert_module_t* expert_dcc; proto_dcc = proto_register_protocol("Distributed Checksum Clearinghouse protocol", "DCC", "dcc"); proto_register_field_array(proto_dcc, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); expert_dcc = expert_register_protocol(proto_dcc); expert_register_field_array(expert_dcc, ei, array_length(ei)); } void proto_reg_handoff_dcc(void) { heur_dissector_add("udp", dissect_dcc, "Distributed Checksum Clearinghouse over UDP", "dcc_udp", proto_dcc, HEURISTIC_ENABLE); } /* * Editor modelines - http://www.wireshark.org/tools/modelines.html * * Local variables: * c-basic-offset: 8 * tab-width: 8 * indent-tabs-mode: t * End: * * vi: set shiftwidth=8 tabstop=8 noexpandtab: * :indentSize=8:tabSize=8:noTabs=false: */