paulo@0: /* paulo@0: * $Id: query_reply.c,v 1.3 2004/03/24 06:35:10 hipnod Exp $ paulo@0: * paulo@0: * Copyright (C) 2001-2003 giFT project (gift.sourceforge.net) paulo@0: * paulo@0: * This program is free software; you can redistribute it and/or modify it paulo@0: * under the terms of the GNU General Public License as published by the paulo@0: * Free Software Foundation; either version 2, or (at your option) any paulo@0: * later version. paulo@0: * paulo@0: * This program is distributed in the hope that it will be useful, but paulo@0: * WITHOUT ANY WARRANTY; without even the implied warranty of paulo@0: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU paulo@0: * General Public License for more details. paulo@0: */ paulo@0: paulo@0: #include "gt_gnutella.h" paulo@0: #include "message/msg_handler.h" paulo@0: paulo@0: #include "sha1.h" paulo@0: #include "xml.h" paulo@0: paulo@0: #include "gt_search.h" paulo@0: paulo@0: #include "gt_urn.h" paulo@0: #include "gt_ban.h" paulo@0: paulo@0: #include "gt_share_file.h" paulo@0: paulo@0: #include paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: /* paulo@0: * Whether to attach the number of hops each result travelled as a metadata paulo@0: * field "Hops". Interesting to see but probably not too useful for the paulo@0: * average user, and so disabled by default. paulo@0: */ paulo@0: #define HOPS_AS_META gt_config_get_int("search/hops_as_meta=0") paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: extern void gt_parse_extended_data (char *ext_block, gt_urn_t **r_urn, paulo@0: Dataset **r_meta); /* query.c */ paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: static void add_meta (ds_data_t *key, ds_data_t *value, FileShare *file) paulo@0: { paulo@0: char *keystr = key->data; paulo@0: char *valuestr = value->data; paulo@0: paulo@0: share_set_meta (file, keystr, valuestr); paulo@0: } paulo@0: paulo@0: /* parse XML data right before the client guid */ paulo@0: static void parse_xml_block (GtPacket *packet, size_t xml_bin_len, paulo@0: Share **results, size_t hits) paulo@0: { paulo@0: int old_offset; paulo@0: char *xml; paulo@0: uint8_t savechr; paulo@0: paulo@0: /* if the length is one there is only the null terminator */ paulo@0: if (xml_bin_len < 1) paulo@0: return; paulo@0: paulo@0: /* paulo@0: * Look for the XML before the client guid. Subtract the length of the paulo@0: * client guid and the size of the XML. paulo@0: */ paulo@0: old_offset = gt_packet_seek (packet, packet->len - 16 - xml_bin_len); paulo@0: paulo@0: /* hopefully, if xml_bin_len is bogus this will fail */ paulo@0: if (old_offset < 0) paulo@0: return; paulo@0: paulo@0: xml = gt_packet_get_ustr (packet, xml_bin_len); paulo@0: paulo@0: if (!xml) paulo@0: return; paulo@0: paulo@0: /* paulo@0: * Null terminate the packet even if the remote end didn't. We know this paulo@0: * is safe because the client GUID resides after the XML, and is 16 bytes paulo@0: * long. paulo@0: * paulo@0: * Note that the null char should be at xml[xml_bin_len - 1], so we end up paulo@0: * actually null-terminating twice if the XML is already terminated. paulo@0: * gt_xml_parse_indexed must take this into account (and it does by paulo@0: * largely ignoring xml_bin_len when the XML is plaintext). paulo@0: */ paulo@0: savechr = xml[xml_bin_len]; paulo@0: xml[xml_bin_len] = 0; paulo@0: paulo@0: if (XML_DEBUG) paulo@0: GT->dbg (GT, "xmldata=%s", xml); paulo@0: paulo@0: /* paulo@0: * The XML before the client guid that we are parsing has a special paulo@0: * property "index" indicating which share the XML property corresponds paulo@0: * to, and so needs to be handled differently from XML appearing in paulo@0: * each individual hit. paulo@0: */ paulo@0: gt_xml_parse_indexed (xml, xml_bin_len, results, hits); paulo@0: paulo@0: /* put the saved character back */ paulo@0: xml[xml_bin_len] = savechr; paulo@0: } paulo@0: paulo@0: /* paulo@0: * Attach the travelled hops as a metadata field. paulo@0: */ paulo@0: static void attach_hops (Share *share, int hops) paulo@0: { paulo@0: char buf[12]; paulo@0: paulo@0: if (!HOPS_AS_META) paulo@0: return; paulo@0: paulo@0: snprintf (buf, sizeof (buf) - 1, "%u", hops); paulo@0: share_set_meta (share, "Hops", buf); paulo@0: } paulo@0: paulo@0: void gt_query_hits_parse (GtPacket *packet, GtSearch *search, paulo@0: TCPC *c, gt_guid_t *client_guid) paulo@0: { paulo@0: uint8_t count; paulo@0: in_port_t port; paulo@0: in_addr_t host; paulo@0: uint32_t speed; paulo@0: Share *results[255]; paulo@0: uint16_t xml_len = 0; paulo@0: int i, availability = 1; paulo@0: BOOL firewalled = FALSE; paulo@0: int total; paulo@0: paulo@0: count = gt_packet_get_uint8 (packet); paulo@0: port = gt_packet_get_port (packet); paulo@0: host = gt_packet_get_ip (packet); paulo@0: speed = gt_packet_get_uint32 (packet); paulo@0: paulo@0: /* check if this host is banned */ paulo@0: if (gt_ban_ipv4_is_banned (host)) paulo@0: { paulo@0: GT->dbg (GT, "discarding search results from %s [address banned]", paulo@0: net_ip_str (host)); paulo@0: return; paulo@0: } paulo@0: paulo@0: for (i = 0; i < count; i++) paulo@0: { paulo@0: uint32_t index; paulo@0: uint32_t size; paulo@0: char *fname, *data; paulo@0: gt_urn_t *urn = NULL; paulo@0: Dataset *meta = NULL; paulo@0: Share *file; paulo@0: paulo@0: index = gt_packet_get_uint32 (packet); paulo@0: size = gt_packet_get_uint32 (packet); paulo@0: fname = gt_packet_get_str (packet); paulo@0: data = gt_packet_get_str (packet); paulo@0: paulo@0: /* If there was an error parsing the packet (not enough results), paulo@0: * stop parsing */ paulo@0: if (gt_packet_error (packet)) paulo@0: break; paulo@0: paulo@0: if (!fname || string_isempty (fname)) paulo@0: { paulo@0: results[i] = NULL; paulo@0: continue; paulo@0: } paulo@0: paulo@0: gt_parse_extended_data (data, &urn, &meta); paulo@0: paulo@0: /* paulo@0: * WARNING: calling gt_urn_data here assumes sha1. paulo@0: * paulo@0: * TODO: this is a potential bug if gt_share_new() makes assumptions paulo@0: * about the hash data's size and gt_urn_t changes to be multiple paulo@0: * sizes later. paulo@0: */ paulo@0: if (!(file = gt_share_new (fname, index, size, gt_urn_data (urn)))) paulo@0: { paulo@0: GIFT_ERROR (("error making fileshare, why?")); paulo@0: paulo@0: dataset_clear (meta); paulo@0: free (urn); paulo@0: paulo@0: /* make sure we find out about it if we're missing results ;) */ paulo@0: assert (0); paulo@0: paulo@0: results[i] = NULL; paulo@0: continue; paulo@0: } paulo@0: paulo@0: /* HACK: set the mimetype from the file extension */ paulo@0: share_set_mime (file, mime_type (fname)); paulo@0: paulo@0: dataset_foreach (meta, DS_FOREACH(add_meta), file); paulo@0: paulo@0: /* Attach the hops the search travelled as a metadata field */ paulo@0: attach_hops (file, gt_packet_hops (packet)); paulo@0: paulo@0: dataset_clear (meta); paulo@0: free (urn); paulo@0: paulo@0: results[i] = file; paulo@0: } paulo@0: paulo@0: total = i; paulo@0: paulo@0: /* look for the query hit descriptor */ paulo@0: if (!gt_packet_error (packet) && paulo@0: packet->len - packet->offset >= 16 + 7 /* min qhd len */) paulo@0: { paulo@0: unsigned char *vendor; paulo@0: uint8_t eqhd_len; paulo@0: uint8_t eqhd[2]; paulo@0: paulo@0: vendor = gt_packet_get_ustr (packet, 4); paulo@0: eqhd_len = gt_packet_get_uint8 (packet); paulo@0: eqhd[0] = gt_packet_get_uint8 (packet); paulo@0: eqhd[1] = gt_packet_get_uint8 (packet); paulo@0: paulo@0: /* set availability to 0 or 1 depending on the busy flag */ paulo@0: availability = ((eqhd[0] & EQHD1_HAS_BUSY) && paulo@0: !(eqhd[1] & EQHD2_BUSY_FLAG)) ? 1 : 0; paulo@0: paulo@0: /* set firewalled status based on the PUSH flag */ paulo@0: firewalled = BOOL_EXPR ((eqhd[0] & EQHD1_PUSH_FLAG) && paulo@0: (eqhd[1] & EQHD2_HAS_PUSH)); paulo@0: paulo@0: /* paulo@0: * Check for an XML metadata block, that is usually present paulo@0: * when the size of the "public area" is 4 paulo@0: */ paulo@0: if (eqhd_len >= 4) paulo@0: xml_len = gt_packet_get_uint16 (packet); paulo@0: paulo@0: if (xml_len > 0) paulo@0: { paulo@0: if (XML_DEBUG) paulo@0: { paulo@0: char str[5] = { 0 }; paulo@0: paulo@0: if (vendor) paulo@0: memcpy (str, vendor, 4); paulo@0: paulo@0: GT->dbg (GT, "(%s) xml_len=%d", str, xml_len); paulo@0: } paulo@0: paulo@0: parse_xml_block (packet, xml_len, results, total); paulo@0: } paulo@0: paulo@0: #if 0 paulo@0: if (MSG_DEBUG) paulo@0: { paulo@0: GT->DBGFN (GT, "vendor = %s, qhd_len = %u, qhd_0 = %x, qhd_1 = %x," paulo@0: " availability = %i, firewalled = %i", paulo@0: make_str (vendor, 4), qhd_len, qhd[0], qhd[1], paulo@0: availability, firewalled); paulo@0: } paulo@0: #endif paulo@0: } paulo@0: paulo@0: /* send the results to the interface protocol */ paulo@0: for (i = 0; i < total; i++) paulo@0: { paulo@0: if (results[i]) paulo@0: { paulo@0: gt_search_reply (search, c, host, port, client_guid, availability, paulo@0: firewalled, results[i]); paulo@0: paulo@0: gt_share_unref (results[i]); paulo@0: } paulo@0: } paulo@0: } paulo@0: paulo@0: /* should split this up into two routines */ paulo@0: GT_MSG_HANDLER(gt_msg_query_reply) paulo@0: { paulo@0: GtSearch *search; paulo@0: int save_offset; paulo@0: gt_guid_t *client_guid; paulo@0: paulo@0: /* Each client has a unique identifier at the end of the paulo@0: * packet. Grab that first. */ paulo@0: if (packet->len < 16) paulo@0: { paulo@0: if (MSG_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "illegal query response packet, < 16 bytes"); paulo@0: paulo@0: return; paulo@0: } paulo@0: paulo@0: /* hack the offset in the packet */ paulo@0: save_offset = packet->offset; paulo@0: packet->offset = packet->len - 16; paulo@0: paulo@0: client_guid = gt_packet_get_ustr (packet, 16); paulo@0: paulo@0: /* put the offset back */ paulo@0: packet->offset = save_offset; paulo@0: paulo@0: if (!(search = gt_search_find (gt_packet_guid (packet))) paulo@0: /*&& query_cache_lookup (packet->guid)*/) paulo@0: { paulo@0: /* TODO: support forwarding of query responses by paulo@0: * looking up their destinations in the guid cache */ paulo@0: paulo@0: /*gt_route_forward_packet (packet, c);*/ paulo@0: paulo@0: /* add the client GUID to the push cache: in case of a paulo@0: * push request we know where to send it */ paulo@0: /*push_cache_add (client_guid, c);*/ paulo@0: paulo@0: return; paulo@0: } paulo@0: paulo@0: gt_query_hits_parse (packet, search, c, client_guid); paulo@0: }