From c9aec3d55e6ee372a73d697e7a21f556031ae978 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 27 Jan 2011 00:05:16 -0500 Subject: [PATCH] It's good to be back. Add jdns and make libtomahawk link to it, so that we can do proper SRV resolving. Also create thirdparty/ which portfwd and others should really be moved in to --- CMakeLists.txt | 1 + src/libtomahawk/CMakeLists.txt | 2 + thirdparty/CMakeLists.txt | 1 + thirdparty/jdns/CMakeLists.txt | 44 + thirdparty/jdns/README | 85 + thirdparty/jdns/TODO | 20 + thirdparty/jdns/jdns.c | 3437 ++++++++++++++++++++++++++++++++ thirdparty/jdns/jdns.h | 507 +++++ thirdparty/jdns/jdns.pri | 27 + thirdparty/jdns/jdns.pro | 9 + thirdparty/jdns/jdns_mdnsd.c | 1125 +++++++++++ thirdparty/jdns/jdns_mdnsd.h | 120 ++ thirdparty/jdns/jdns_p.h | 95 + thirdparty/jdns/jdns_packet.c | 1008 ++++++++++ thirdparty/jdns/jdns_packet.h | 116 ++ thirdparty/jdns/jdns_sys.c | 850 ++++++++ thirdparty/jdns/jdns_util.c | 1553 +++++++++++++++ thirdparty/jdns/main.cpp | 596 ++++++ thirdparty/jdns/qjdns.cpp | 1047 ++++++++++ thirdparty/jdns/qjdns.h | 158 ++ thirdparty/jdns/qjdns_sock.cpp | 184 ++ thirdparty/jdns/qjdns_sock.h | 33 + 22 files changed, 11018 insertions(+) create mode 100644 thirdparty/CMakeLists.txt create mode 100644 thirdparty/jdns/CMakeLists.txt create mode 100644 thirdparty/jdns/README create mode 100644 thirdparty/jdns/TODO create mode 100644 thirdparty/jdns/jdns.c create mode 100644 thirdparty/jdns/jdns.h create mode 100644 thirdparty/jdns/jdns.pri create mode 100644 thirdparty/jdns/jdns.pro create mode 100644 thirdparty/jdns/jdns_mdnsd.c create mode 100644 thirdparty/jdns/jdns_mdnsd.h create mode 100644 thirdparty/jdns/jdns_p.h create mode 100644 thirdparty/jdns/jdns_packet.c create mode 100644 thirdparty/jdns/jdns_packet.h create mode 100644 thirdparty/jdns/jdns_sys.c create mode 100644 thirdparty/jdns/jdns_util.c create mode 100644 thirdparty/jdns/main.cpp create mode 100644 thirdparty/jdns/qjdns.cpp create mode 100644 thirdparty/jdns/qjdns.h create mode 100644 thirdparty/jdns/qjdns_sock.cpp create mode 100644 thirdparty/jdns/qjdns_sock.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 73079fa95..2b3b92c94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ ELSE() ADD_SUBDIRECTORY( rtaudio ) ENDIF( UNIX AND NOT APPLE ) +ADD_SUBDIRECTORY( thirdparty ) ADD_SUBDIRECTORY( libportfwd ) ADD_SUBDIRECTORY( qxt ) ADD_SUBDIRECTORY( src/libtomahawk ) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 18eaf3332..b2328485a 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -236,6 +236,7 @@ SET( OS_SPECIFIC_LINK_LIBRARIES "dnsapi.dll" "dsound.dll" "winmm.dll" + "advapi32.dll" "${CMAKE_CURRENT_SOURCE_DIR}/../../rtaudio/librtaudio.dll" ) @@ -282,6 +283,7 @@ target_link_libraries( tomahawklib vorbisfile ogg FLAC++ + jdns ) install( TARGETS tomahawklib DESTINATION lib ) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt new file mode 100644 index 000000000..6fc67cd39 --- /dev/null +++ b/thirdparty/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory( jdns ) diff --git a/thirdparty/jdns/CMakeLists.txt b/thirdparty/jdns/CMakeLists.txt new file mode 100644 index 000000000..8111be46e --- /dev/null +++ b/thirdparty/jdns/CMakeLists.txt @@ -0,0 +1,44 @@ +PROJECT(jdns) + +CMAKE_MINIMUM_REQUIRED(VERSION 2.6 FATAL_ERROR) +SET(CMAKE_VERBOSE_MAKEFILE ON) + +SET( QT_USE_QTNETWORK TRUE ) +INCLUDE( ${QT_USE_FILE} ) + +if(WIN32) + SET(PLATFORM_SPECIFIC_LIBS "ws2_32.dll" "advapi32.dll" ) +endif(WIN32) + +set(JDNS_SOURCES + jdns_util.c + jdns_packet.c + jdns_mdnsd.c + jdns_sys.c + jdns.c + qjdns_sock.cpp + qjdns.cpp +) + +set(JDNS_HEADERS + qjdns.h +) + +include_directories( + ${QT_INCLUDE_DIR} + ${QT_INCLUDES} +) + +qt4_wrap_cpp( JDNS_MOC ${JDNS_HEADERS} ) + +if(WIN32) + ADD_LIBRARY(jdns SHARED ${JDNS_SOURCES} ${JDNS_MOC}) +else() + ADD_LIBRARY(jdns STATIC ${JDNS_SOURCES} ${JDNS_MOC}) +endif() + +target_link_libraries(jdns + ${QT_LIBRARIES} +) + +INSTALL(TARGETS jdns ARCHIVE DESTINATION lib) diff --git a/thirdparty/jdns/README b/thirdparty/jdns/README new file mode 100644 index 000000000..466a0eaac --- /dev/null +++ b/thirdparty/jdns/README @@ -0,0 +1,85 @@ +JDNS +---- +Date: October 1st, 2005 +Author: Justin Karneges + +JDNS is a simple DNS implementation that can perform normal DNS queries +of any record type (notably SRV), as well as Multicast DNS queries and +advertising. Multicast support is based on Jeremie Miller's "mdnsd" +implementation. + +For maximum flexibility, JDNS is written in C with no direct dependencies, +and is licensed under the MIT license. Your application must supply +functionality to JDNS, such as UDP sending/receiving, via callbacks. + +For Qt users there is a wrapper available called QJDns. jdns.pri can +be used to include everything into a qmake project. jdns.pro will build +the sample Qt-based commandline tool 'jdns'. + +Features: + - DNS client "stub" resolver + - Can fetch any record type, but provides handy decoding for many + known types: A, AAAA, SRV, MX, TXT, etc. + - Performs retries, caching/expiration, and CNAME following + - Algorithm logic adapted from Q3Dns + - Multicast queries + - Multicast advertising + +Why? + - Trolltech is phasing out the Qt DNS implementation, which in Qt 4 has + been relegated to the Qt3Support module. A replacement was desired. + + - While there are many DNS libraries available, at the time of this + writing it was (and still may be) hard to find one that satisfies + three essential conditions: cross-platform friendliness (and this + includes Windows 9x!), the ability to integrate into existing + eventloops, sensible licensing (ie, not GPL). + +How to use: + - Prepare callbacks and call jdns_session_new() + - Call jdns_init_unicast() or jdns_init_multicast(), depending on + if you want regular or multicast DNS. If you want both kinds, you + can always make two sessions. + - Make queries and have fun + - Call jdns_step() at the right times to advance JDNS processing + +What is left to you: + - The callback functions, obviously. + - Querying for several "qualified" names. Here is what Q3Dns does: + Query for name as provided + Query for name + '.domain' (for every domain the computer is in) + - Detecting for '.local' in a name to be queried, and using that + to decide whether to query via Multicast or normal DNS. + - Recognition of IP addresses. If you want an IP address to resolve + to itself, then do that yourself. Passing an IP address as a DNS + name to JDNS won't work (especially since it wouldn't make any + sense in some contexts, like SRV). + - Recognition of known hosts. If you want this, compare inputs against + jdns_system_dnsparams(). + - For zeroconf/Bonjour, keep in mind that JDNS only provides Multicast + DNS capability. DNS-SD and any higher layers would be your job. + +Using a custom DNS implementation has the drawback that it is difficult +to take advantage of platform-specific features (for example, an OS-wide +DNS cache or LDAP integration). + +An application strategy for normal DNS should probably be: + - If an A or AAAA record is desired, use a native lookup. + - Else, if the platform has advanced DNS features already (ie, + res_query), use those. + - Else, use JDNS. + +However, it may not be a bad idea at first to use JDNS for all occasions, +so that it can be debugged. + +For Multicast DNS, awareness of the platform is doubly important. There +should only be one Multicast DNS "Responder" per computer, and using JDNS +at the same time could result in a conflict. + +An application strategy for Multicast DNS should be: + - If the platform has a Multicast DNS daemon installed already, use + it somehow. + - Else, use JDNS. + +Have fun! + diff --git a/thirdparty/jdns/TODO b/thirdparty/jdns/TODO new file mode 100644 index 000000000..0032c1982 --- /dev/null +++ b/thirdparty/jdns/TODO @@ -0,0 +1,20 @@ +(nothing) + +but, this stuff couldn't hurt: + fields that need to be an explicit size should use int16_t, etc + support for other DNS record types (SOA, NSPTR) + detect CNAME loops, rather than looping max times in order to fail + don't follow CNAME for SRV (or so I'm told?) + if it is not possible to implement DNSSEC outside of jdns, then add the + minimal number of hooks to jdns so that it becomes possible + use hash tables to speed up the list lookups + unit tests + qjdns debug lines reworking: + init should emit debugLinesReady, and debug should be available + immediately after call (the emit will break SS/DS, put a note about + this in a comment about init()) + anywhere else, debugLines should conform to SS, but be emitted at the + proper time, not deferred. currently, the debug arrives out of + sequence with the other signals, resulting in strange output in the + commandline tool. + consideration for these changes in jdnsshared diff --git a/thirdparty/jdns/jdns.c b/thirdparty/jdns/jdns.c new file mode 100644 index 000000000..adb6407e1 --- /dev/null +++ b/thirdparty/jdns/jdns.c @@ -0,0 +1,3437 @@ +/* + * Copyright (C) 2005-2008 Justin Karneges + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "jdns_p.h" + +#include + +#include "jdns_packet.h" +#include "jdns_mdnsd.h" + +#define JDNS_UDP_UNI_OUT_MAX 512 +#define JDNS_UDP_UNI_IN_MAX 16384 +#define JDNS_UDP_MUL_OUT_MAX 9000 +#define JDNS_UDP_MUL_IN_MAX 16384 + +// cache no more than 7 days +#define JDNS_TTL_MAX (86400 * 7) +#define JDNS_CACHE_MAX 16384 +#define JDNS_CNAME_MAX 16 +#define JDNS_QUERY_MAX 4096 + +//---------------------------------------------------------------------------- +// util +//---------------------------------------------------------------------------- + +// declare this here, but implement it later after we define jdns_session_t +static void _debug_line(jdns_session_t *s, const char *format, ...); + +static unsigned char _hex_nibble(unsigned char c) +{ + if(c <= 9) + return '0' + c; + else if(c <= 15) + return 'a' + (c - 10); + else + return '?'; +} + +static void _hex_byte(unsigned char c, unsigned char *dest) +{ + dest[0] = _hex_nibble((unsigned char)(c >> 4)); + dest[1] = _hex_nibble((unsigned char)(c & 0x0f)); +} + +static jdns_string_t *_make_printable(const unsigned char *str, int size) +{ + unsigned char *buf; + int n, i; + jdns_string_t *out; + + if(size == 0) + { + out = jdns_string_new(); + jdns_string_set_cstr(out, ""); + return out; + } + + // make room for the largest possible result + buf = (unsigned char *)malloc(size * 4); + i = 0; + for(n = 0; n < size; ++n) + { + unsigned char c = str[n]; + if(c == '\\') + { + buf[i++] = '\\'; + buf[i++] = '\\'; + } + else if(c >= 0x20 && c < 0x7f) + { + buf[i++] = c; + } + else + { + buf[i++] = '\\'; + buf[i++] = 'x'; + _hex_byte(c, buf + i); + i += 2; + } + } + + out = jdns_string_new(); + jdns_string_set(out, buf, i); + free(buf); + return out; +} + +static jdns_string_t *_make_printable_str(const jdns_string_t *str) +{ + return _make_printable(str->data, str->size); +} + +static jdns_string_t *_make_printable_cstr(const char *str) +{ + return _make_printable((const unsigned char *)str, strlen(str)); +} + +static unsigned char *_fix_input(const unsigned char *in) +{ + unsigned char *out; + int len; + + // truncate + len = _ustrlen(in); + if(len > 254) + len = 254; + + // add a dot to the end if needed + if(in[len - 1] != '.' && len < 254) + { + out = (unsigned char *)malloc(len + 2); // a dot and a zero + memcpy(out, in, len); + out[len] = '.'; + out[len+1] = 0; + ++len; + } + else + { + out = (unsigned char *)malloc(len + 1); // a zero + memcpy(out, in, len); + out[len] = 0; + } + + return out; +} + +static const char *_qtype2str(int qtype) +{ + const char *str; + switch(qtype) + { + case JDNS_RTYPE_A: str = "A"; break; + case JDNS_RTYPE_AAAA: str = "AAAA"; break; + case JDNS_RTYPE_MX: str = "MX"; break; + case JDNS_RTYPE_SRV: str = "SRV"; break; + case JDNS_RTYPE_CNAME: str = "CNAME"; break; + case JDNS_RTYPE_PTR: str = "PTR"; break; + case JDNS_RTYPE_TXT: str = "TXT"; break; + case JDNS_RTYPE_HINFO: str = "HINFO"; break; + case JDNS_RTYPE_NS: str = "NS"; break; + case JDNS_RTYPE_ANY: str = "ANY"; break; + default: str = ""; break; + } + return str; +} + +static int _cmp_rdata(const jdns_rr_t *a, const jdns_rr_t *b) +{ + if(a->rdlength != b->rdlength) + return 0; + if(memcmp(a->rdata, b->rdata, a->rdlength) != 0) + return 0; + return 1; +} + +static int _cmp_rr(const jdns_rr_t *a, const jdns_rr_t *b) +{ + if(a->type != b->type) + return 0; + if(!jdns_domain_cmp(a->owner, b->owner)) + return 0; + switch(a->type) + { + case JDNS_RTYPE_A: + if(!jdns_address_cmp(a->data.address, b->data.address)) + return 0; + break; + case JDNS_RTYPE_AAAA: + if(!_cmp_rdata(a, b)) + return 0; + break; + case JDNS_RTYPE_MX: + // unsupported + return 0; + case JDNS_RTYPE_SRV: + if(a->data.server->port != b->data.server->port + || a->data.server->priority != b->data.server->priority + || a->data.server->weight != b->data.server->weight + || !jdns_domain_cmp(a->data.server->name, b->data.server->name) + ) + return 0; + break; + case JDNS_RTYPE_CNAME: + if(!jdns_domain_cmp(a->data.name, b->data.name)) + return 0; + break; + case JDNS_RTYPE_PTR: + if(!jdns_domain_cmp(a->data.name, b->data.name)) + return 0; + break; + case JDNS_RTYPE_TXT: + if(!_cmp_rdata(a, b)) + return 0; + break; + case JDNS_RTYPE_HINFO: + if(!_cmp_rdata(a, b)) + return 0; + break; + case JDNS_RTYPE_NS: + // unsupported + return 0; + default: + if(!_cmp_rdata(a, b)) + return 0; + break; + } + return 1; +} + +static jdns_response_t *_packet2response(const jdns_packet_t *packet, const unsigned char *qname, int qtype, int classmask) +{ + int n; + jdns_response_t *r; + + r = jdns_response_new(); + for(n = 0; n < packet->answerRecords->count; ++n) + { + jdns_packet_resource_t *res = (jdns_packet_resource_t *)packet->answerRecords->item[n]; + jdns_rr_t *rr; + int put_in_answer; + if((res->qclass & classmask) != 0x0001) + continue; + rr = jdns_rr_from_resource(res, packet); + if(!rr) + continue; + // if qname is set, restrict answers to those that match + // the question + put_in_answer = 1; + if(qname) + { + // name must match. type must either match or be CNAME, + // unless the query was for any type + if((qtype != JDNS_RTYPE_ANY && res->qtype != qtype && res->qtype != JDNS_RTYPE_CNAME) || !jdns_domain_cmp(res->qname->data, qname)) + { + // put unusable records in additional section + put_in_answer = 0; + } + } + if(put_in_answer) + jdns_response_append_answer(r, rr); + else + jdns_response_append_additional(r, rr); + jdns_rr_delete(rr); + } + for(n = 0; n < packet->authorityRecords->count; ++n) + { + jdns_packet_resource_t *res = (jdns_packet_resource_t *)packet->authorityRecords->item[n]; + jdns_rr_t *rr; + if((res->qclass & classmask) != 0x0001) + continue; + rr = jdns_rr_from_resource(res, packet); + if(!rr) + continue; + jdns_response_append_authority(r, rr); + jdns_rr_delete(rr); + } + for(n = 0; n < packet->additionalRecords->count; ++n) + { + jdns_packet_resource_t *res = (jdns_packet_resource_t *)packet->additionalRecords->item[n]; + jdns_rr_t *rr; + if((res->qclass & classmask) != 0x0001) + continue; + rr = jdns_rr_from_resource(res, packet); + if(!rr) + continue; + jdns_response_append_additional(r, rr); + jdns_rr_delete(rr); + } + return r; +} + +// size must be 1 to 16 +static void _print_hexdump_line(jdns_session_t *s, const unsigned char *buf, int size) +{ + char line[67]; // 3 * 16 + 2 + 16 + zero byte + int n; + + memset(line, ' ', 66); + line[66] = 0; + if(size > 16) + size = 16; + for(n = 0; n < size; ++n) + { + unsigned char c = buf[n]; + _hex_byte(c, ((unsigned char *)line) + n * 3); + line[n * 3 + 2] = ' '; + if(c >= 0x20 && c < 0x7f) + line[50 + n] = c; + else + line[50 + n] = '.'; + } + _debug_line(s, " %s", line); +} + +static void _print_hexdump(jdns_session_t *s, const unsigned char *buf, int size) +{ + int n; + int lines; + int at, len; + + lines = size / 16; + if(size % 16 != 0) + ++lines; + for(n = 0; n < lines; ++n) + { + at = n * 16; + if(at + 16 <= size) + len = 16; + else + len = size - at; + _print_hexdump_line(s, buf + at, len); + } +} + +static void _print_packet_resources(jdns_session_t *s, const jdns_list_t *reslist) +{ + int n; + for(n = 0; n < reslist->count; ++n) + { + jdns_packet_resource_t *r; + jdns_string_t *str; + r = (jdns_packet_resource_t *)reslist->item[n]; + str = _make_printable_str(r->qname); + _debug_line(s, " %04x/%04x [%s] ttl=%ld size=%d", r->qclass, r->qtype, str->data, r->ttl, r->rdlength); + jdns_string_delete(str); + } +} + +static void _print_packet(jdns_session_t *s, const jdns_packet_t *packet) +{ + int n; + _debug_line(s, "Packet:"); + _debug_line(s, " id: %d", packet->id); + _debug_line(s, " opts: qr:%d, opcode:%d, aa:%d, tc:%d, rd:%d, ra:%d, z:%d, rcode:%d", + packet->opts.qr, packet->opts.opcode, packet->opts.aa, packet->opts.tc, + packet->opts.rd, packet->opts.ra, packet->opts.z, packet->opts.rcode); + _debug_line(s, " qdcount=%d, ancount=%d, nscount=%d, arcount=%d", + packet->qdcount, packet->ancount, packet->nscount, packet->arcount); + if(packet->questions->count > 0) + { + _debug_line(s, " questions: (class/type name)"); + for(n = 0; n < packet->questions->count; ++n) + { + jdns_packet_question_t *q; + jdns_string_t *str; + q = (jdns_packet_question_t *)packet->questions->item[n]; + str = _make_printable_str(q->qname); + _debug_line(s, " %04x/%04x [%s]", q->qclass, q->qtype, str->data); + jdns_string_delete(str); + } + } + if(packet->answerRecords->count > 0) + { + _debug_line(s, " answerRecords: (class/type owner ttl size)"); + _print_packet_resources(s, packet->answerRecords); + } + if(packet->authorityRecords->count > 0) + { + _debug_line(s, " authorityRecords: (class/type owner ttl size)"); + _print_packet_resources(s, packet->authorityRecords); + } + if(packet->additionalRecords->count > 0) + { + _debug_line(s, " additionalRecords: (class/type owner ttl size)"); + _print_packet_resources(s, packet->additionalRecords); + } +} + +static void _print_rr(jdns_session_t *s, const jdns_rr_t *rr, const unsigned char *owner) +{ + int n; + jdns_string_t *ownerstr; + + ownerstr = jdns_string_new(); + + // not the expected owner? + if(!owner || !jdns_domain_cmp(owner, rr->owner)) + { + unsigned char *buf; + jdns_string_t *str = _make_printable_cstr((const char *)rr->owner); + buf = (unsigned char *)malloc(str->size + 3); // " [%s]" + buf[0] = ' '; + buf[1] = '['; + memcpy(buf + 2, str->data, str->size); + buf[str->size + 2] = ']'; + jdns_string_set(ownerstr, buf, str->size + 3); + jdns_string_delete(str); + free(buf); + } + else + jdns_string_set_cstr(ownerstr, ""); + + switch(rr->type) + { + case JDNS_RTYPE_A: + { + _debug_line(s, " A: [%s] (ttl=%d)%s", rr->data.address->c_str, rr->ttl, ownerstr->data); + break; + } + case JDNS_RTYPE_AAAA: + { + _debug_line(s, " AAAA: [%s] (ttl=%d)%s", rr->data.address->c_str, rr->ttl, ownerstr->data); + break; + } + case JDNS_RTYPE_MX: + { + jdns_string_t *str = _make_printable_cstr((const char *)rr->data.server->name); + _debug_line(s, " MX: [%s] priority=%d (ttl=%d)%s", str->data, rr->data.server->priority, rr->ttl, ownerstr->data); + jdns_string_delete(str); + break; + } + case JDNS_RTYPE_SRV: + { + jdns_string_t *str = _make_printable_cstr((const char *)rr->data.server->name); + _debug_line(s, " SRV: [%s] port=%d priority=%d weight=%d (ttl=%d)%s", str->data, rr->data.server->port, rr->data.server->priority, rr->data.server->weight, rr->ttl, ownerstr->data); + jdns_string_delete(str); + break; + } + case JDNS_RTYPE_CNAME: + { + jdns_string_t *str = _make_printable_cstr((const char *)rr->data.name); + _debug_line(s, " CNAME: [%s] (ttl=%d)%s", str->data, rr->ttl, ownerstr->data); + jdns_string_delete(str); + break; + } + case JDNS_RTYPE_PTR: + { + jdns_string_t *str = _make_printable_cstr((const char *)rr->data.name); + _debug_line(s, " PTR: [%s] (ttl=%d)%s", str->data, rr->ttl, ownerstr->data); + jdns_string_delete(str); + break; + } + case JDNS_RTYPE_TXT: + { + _debug_line(s, " TXT: count=%d (ttl=%d)%s", rr->data.texts->count, rr->ttl, ownerstr->data); + for(n = 0; n < rr->data.texts->count; ++n) + { + jdns_string_t *str, *pstr; + str = rr->data.texts->item[n]; + pstr = _make_printable_str(str); + _debug_line(s, " len=%d [%s]", str->size, pstr->data); + jdns_string_delete(pstr); + } + break; + } + case JDNS_RTYPE_HINFO: + { + jdns_string_t *cpu, *os; + cpu = _make_printable_str(rr->data.hinfo.cpu); + os = _make_printable_str(rr->data.hinfo.os); + _debug_line(s, " HINFO: [%s] [%s] (ttl=%d)%s", cpu->data, os->data, rr->ttl, ownerstr->data); + jdns_string_delete(cpu); + jdns_string_delete(os); + break; + } + case JDNS_RTYPE_NS: + { + jdns_string_t *str = _make_printable_cstr((const char *)rr->data.name); + _debug_line(s, " NS: [%s] (ttl=%d)%s", str->data, rr->ttl, ownerstr->data); + jdns_string_delete(str); + break; + } + default: + { + _debug_line(s, " Unknown (%d): %d bytes (ttl=%d)%s", rr->type, rr->rdlength, rr->ttl, ownerstr->data); + break; + } + } + jdns_string_delete(ownerstr); +} + +static void _print_records(jdns_session_t *s, const jdns_response_t *r, const unsigned char *owner) +{ + int n; + _debug_line(s, "Records:"); + _debug_line(s, " Answer Records: %d", r->answerCount); + for(n = 0; n < r->answerCount; ++n) + _print_rr(s, r->answerRecords[n], owner); + _debug_line(s, " Authority Records: %d", r->authorityCount); + for(n = 0; n < r->authorityCount; ++n) + _print_rr(s, r->authorityRecords[n], owner); + _debug_line(s, " Additional Records: %d", r->additionalCount); + for(n = 0; n < r->additionalCount; ++n) + _print_rr(s, r->additionalRecords[n], owner); +} + +static int _min(int a, int b) +{ + return (a < b) ? a : b; +} + +//---------------------------------------------------------------------------- +// jdns_event +//---------------------------------------------------------------------------- +jdns_event_t *jdns_event_new() +{ + jdns_event_t *e = alloc_type(jdns_event_t); + e->response = 0; + return e; +} + +void jdns_event_delete(jdns_event_t *e) +{ + if(!e) + return; + jdns_response_delete(e->response); + jdns_free(e); +} + +//---------------------------------------------------------------------------- +// jdns - internal types +//---------------------------------------------------------------------------- +typedef struct list_item +{ + void (*dtor)(void *); +} list_item_t; + +typedef struct list +{ + int count; + list_item_t **item; +} list_t; + +static list_t *list_new() +{ + list_t *l = alloc_type(list_t); + l->count = 0; + l->item = 0; + return l; +} + +static void list_delete(list_t *l) +{ + int n; + if(!l) + return; + for(n = 0; n < l->count; ++n) + l->item[n]->dtor(l->item[n]); + if(l->item) + free(l->item); + jdns_free(l); +} + +static void list_insert(list_t *l, void *item, int pos) +{ + list_item_t *i = (list_item_t *)item; + if(!l->item) + l->item = (list_item_t **)malloc(sizeof(list_item_t *)); + else + l->item = (list_item_t **)realloc(l->item, sizeof(list_item_t *) * (l->count + 1)); + if(pos != -1) + memmove(l->item + pos + 1, l->item + pos, (l->count - pos) * sizeof(list_item_t *)); + else + pos = l->count; + l->item[pos] = i; + ++l->count; +} + +static void list_remove(list_t *l, void *item) +{ + int n; + list_item_t *i = (list_item_t *)item; + int pos = -1; + for(n = 0; n < l->count; ++n) + { + if(l->item[n] == i) + { + pos = n; + break; + } + } + if(pos == -1) + return; + + i->dtor(i); + if(l->count > 1) + { + memmove(l->item + pos, l->item + pos + 1, (l->count - pos - 1) * sizeof(list_item_t *)); + --l->count; + } + else + { + free(l->item); + l->item = 0; + l->count = 0; + } +} + +typedef struct name_server +{ + void (*dtor)(struct name_server *); + int id; + jdns_address_t *address; + int port; +} name_server_t; + +static void name_server_delete(name_server_t *ns); + +static name_server_t *name_server_new() +{ + name_server_t *ns = alloc_type(name_server_t); + ns->dtor = name_server_delete; + ns->address = 0; + return ns; +} + +void name_server_delete(name_server_t *ns) +{ + if(!ns) + return; + jdns_address_delete(ns->address); + jdns_free(ns); +} + +int _intarray_indexOf(int *array, int count, int val) +{ + int n; + for(n = 0; n < count; ++n) + { + if(array[n] == val) + return n; + } + return -1; +} + +int _intarray_add(int **array, int *count, int val) +{ + int *p; + if(!*array) + p = (int *)malloc(sizeof(int)); + else + p = (int *)realloc(*array, sizeof(int) * (*count + 1)); + if(!p) + return 0; + *array = p; + (*array)[*count] = val; + ++(*count); + return 1; +} + +void _intarray_remove(int **array, int *count, int pos) +{ + int *p; + if(*count > 1) + { + memmove(*array + pos, *array + pos + 1, (*count - pos - 1) * sizeof(int)); + --(*count); + p = (int *)realloc(*array, sizeof(int) * (*count)); + if(p) + *array = p; + } + else + { + free(*array); + *array = 0; + *count = 0; + } +} + +typedef struct query +{ + void (*dtor)(struct query *); + + int id; + + // user request ids + int req_ids_count; + int *req_ids; + + // packet id + int dns_id; + + // what we are looking up + unsigned char *qname; + int qtype; + + // how many transmission attempts we have done. note this + // is not actually how many packets have been sent, since + // it is possible for the first transmission to send many + // at once. this variable lets us decide when to give up. + // (idea taken from qdns). + // set to -1 to deactivate (stop sending packets) + int step; + + // which nameservers we've tried (stored as a list of ids) + int servers_tried_count; + int *servers_tried; + + // which servers we shouldn't try again + int servers_failed_count; + int *servers_failed; + + // flag to indicate whether or not we've tried all available + // nameservers already. this means that all future + // transmissions are likely repeats, and should be slowed + // down. + int retrying; + + // flag to indicate if we've received nxdomain as an error so far + int nxdomain; + + // holds a timeout for the next step (time_start == -1 means no timer) + int time_start; + int time_next; + + // whether or not to look in the cache for this query + int trycache; + + // cname subquerying. only cname_parent or cname_child may be set, + // never both. + int cname_chain_count; + struct query *cname_parent; + struct query *cname_child; + + // accumulates known multicast records to prevent duplicates + jdns_response_t *mul_known; +} query_t; + +void query_delete(query_t *q); + +query_t *query_new() +{ + query_t *q = alloc_type(query_t); + q->dtor = query_delete; + q->req_ids_count = 0; + q->req_ids = 0; + q->qname = 0; + q->servers_tried_count = 0; + q->servers_tried = 0; + q->servers_failed_count = 0; + q->servers_failed = 0; + q->nxdomain = 0; + q->cname_chain_count = 0; + q->cname_parent = 0; + q->cname_child = 0; + q->mul_known = 0; + return q; +} + +void query_delete(query_t *q) +{ + if(!q) + return; + if(q->req_ids) + free(q->req_ids); + if(q->qname) + free(q->qname); + if(q->servers_tried) + free(q->servers_tried); + if(q->servers_failed) + free(q->servers_failed); + jdns_response_delete(q->mul_known); + jdns_free(q); +} + +int query_have_req_id(const query_t *q, int req_id) +{ + if(_intarray_indexOf(q->req_ids, q->req_ids_count, req_id) != -1) + return 1; + return 0; +} + +void query_add_req_id(query_t *q, int req_id) +{ + _intarray_add(&q->req_ids, &q->req_ids_count, req_id); +} + +void query_remove_req_id(query_t *q, int req_id) +{ + int pos; + + pos = _intarray_indexOf(q->req_ids, q->req_ids_count, req_id); + if(pos != -1) + _intarray_remove(&q->req_ids, &q->req_ids_count, pos); +} + +int query_server_tried(const query_t *q, int ns_id) +{ + if(_intarray_indexOf(q->servers_tried, q->servers_tried_count, ns_id) != -1) + return 1; + return 0; +} + +void query_add_server_tried(query_t *q, int ns_id) +{ + _intarray_add(&q->servers_tried, &q->servers_tried_count, ns_id); +} + +int query_server_failed(const query_t *q, int ns_id); + +void query_clear_servers_tried(query_t *q) +{ + int n; + + // all failed servers must continue to be considered tried servers, so + // only clear tried servers that haven't failed + for(n = 0; n < q->servers_tried_count; ++n) + { + if(!query_server_failed(q, q->servers_tried[n])) + { + _intarray_remove(&q->servers_tried, &q->servers_tried_count, n); + --n; // adjust position + } + } +} + +int query_server_failed(const query_t *q, int ns_id) +{ + if(_intarray_indexOf(q->servers_failed, q->servers_failed_count, ns_id) != -1) + return 1; + return 0; +} + +void query_add_server_failed(query_t *q, int ns_id) +{ + _intarray_add(&q->servers_failed, &q->servers_failed_count, ns_id); +} + +void query_name_server_gone(query_t *q, int ns_id) +{ + int pos; + + pos = _intarray_indexOf(q->servers_tried, q->servers_tried_count, ns_id); + if(pos != -1) + _intarray_remove(&q->servers_tried, &q->servers_tried_count, pos); + + pos = _intarray_indexOf(q->servers_failed, q->servers_failed_count, ns_id); + if(pos != -1) + _intarray_remove(&q->servers_failed, &q->servers_failed_count, pos); +} + +typedef struct datagram +{ + void (*dtor)(struct datagram *); + int handle; + jdns_address_t *dest_address; + int dest_port; + unsigned char *data; + int size; + + // query association + query_t *query; + int query_send_type; // 0 == normal, 1 == first step send-all + + // name server association + int ns_id; +} datagram_t; + +void datagram_delete(datagram_t *a); + +datagram_t *datagram_new() +{ + datagram_t *a = alloc_type(datagram_t); + a->dtor = datagram_delete; + a->dest_address = 0; + a->data = 0; + a->size = 0; + a->query = 0; + return a; +} + +void datagram_delete(datagram_t *a) +{ + if(!a) + return; + jdns_address_delete(a->dest_address); + if(a->data) + free(a->data); + jdns_free(a); +} + +typedef struct cache_item +{ + void (*dtor)(struct cache_item *); + unsigned char *qname; + int qtype; + int time_start; + int ttl; + jdns_rr_t *record; // if zero, nxdomain is assumed +} cache_item_t; + +void cache_item_delete(cache_item_t *e); + +cache_item_t *cache_item_new() +{ + cache_item_t *a = alloc_type(cache_item_t); + a->dtor = cache_item_delete; + a->qname = 0; + a->record = 0; + return a; +} + +void cache_item_delete(cache_item_t *a) +{ + if(!a) + return; + if(a->qname) + free(a->qname); + jdns_rr_delete(a->record); + jdns_free(a); +} + +typedef struct event +{ + void (*dtor)(struct event *); + jdns_event_t *event; +} event_t; + +void event_delete(event_t *e); + +event_t *event_new() +{ + event_t *e = alloc_type(event_t); + e->dtor = event_delete; + e->event = 0; + return e; +} + +void event_delete(event_t *e) +{ + if(!e) + return; + jdns_event_delete(e->event); + jdns_free(e); +} + +typedef struct published_item +{ + void (*dtor)(struct published_item *); + int id; + int mode; + unsigned char *qname; + int qtype; + mdnsdr rec; + jdns_rr_t *rr; +} published_item_t; + +void published_item_delete(published_item_t *a); + +published_item_t *published_item_new() +{ + published_item_t *a = alloc_type(published_item_t); + a->dtor = published_item_delete; + a->qname = 0; + a->rec = 0; + a->rr = 0; + return a; +} + +void published_item_delete(published_item_t *a) +{ + if(!a) + return; + if(a->qname) + free(a->qname); + jdns_rr_delete(a->rr); + jdns_free(a); +} + +//---------------------------------------------------------------------------- +// jdns +//---------------------------------------------------------------------------- +struct jdns_session +{ + jdns_callbacks_t cb; + int mode; + int shutdown; + int next_qid; + int next_req_id; + int last_time; + int next_timer; + int next_name_server_id; + int handle; + int handle_readable, handle_writable; + int port; + list_t *name_servers; + list_t *queries; + list_t *outgoing; + list_t *events; + list_t *cache; + + // for blocking req_ids from reuse until user explicitly releases + int do_hold_req_ids; + int held_req_ids_count; + int *held_req_ids; + + // mdns + mdnsd mdns; + list_t *published; + jdns_address_t *maddr; +}; + +jdns_session_t *jdns_session_new(jdns_callbacks_t *callbacks) +{ + jdns_session_t *s = alloc_type(jdns_session_t); + memcpy(&s->cb, callbacks, sizeof(jdns_callbacks_t)); + s->shutdown = 0; + s->next_qid = 0; + s->next_req_id = 1; + s->last_time = 0; + s->next_timer = 0; + s->next_name_server_id = 0; + s->handle = 0; + s->handle_readable = 0; + s->handle_writable = 1; + s->port = 0; + s->name_servers = list_new(); + s->queries = list_new(); + s->outgoing = list_new(); + s->events = list_new(); + s->cache = list_new(); + + s->do_hold_req_ids = 0; + s->held_req_ids_count = 0; + s->held_req_ids = 0; + + s->mdns = 0; + s->published = list_new(); + s->maddr = 0; + + return s; +} + +void jdns_session_delete(jdns_session_t *s) +{ + if(!s) + return; + if(s->handle) + s->cb.udp_unbind(s, s->cb.app, s->handle); + list_delete(s->name_servers); + list_delete(s->queries); + list_delete(s->outgoing); + list_delete(s->events); + list_delete(s->cache); + + if(s->held_req_ids) + free(s->held_req_ids); + + if(s->mdns) + mdnsd_free(s->mdns); + + list_delete(s->published); + jdns_address_delete(s->maddr); + + free(s); +} + +// declare some internal functions +static int _callback_time_now(mdnsd d, void *arg); +static int _callback_rand_int(mdnsd d, void *arg); + +static void _append_event(jdns_session_t *s, jdns_event_t *event); +static void _append_event_and_hold_id(jdns_session_t *s, jdns_event_t *event); +static void _remove_name_server_datagrams(jdns_session_t *s, int ns_id); +static void _remove_query_datagrams(jdns_session_t *s, const query_t *q); + +static int _unicast_query(jdns_session_t *s, const unsigned char *name, int qtype); +static void _unicast_cancel(jdns_session_t *s, query_t *q); +static int _multicast_query(jdns_session_t *s, const unsigned char *name, int qtype); +static void _multicast_cancel(jdns_session_t *s, int req_id); +static int _multicast_publish(jdns_session_t *s, int mode, const jdns_rr_t *rr); +static void _multicast_update_publish(jdns_session_t *s, int id, const jdns_rr_t *rr); +static void _multicast_cancel_publish(jdns_session_t *s, int id); +static void _multicast_flush(jdns_session_t *s); + +static int jdns_step_unicast(jdns_session_t *s, int now); +static int jdns_step_multicast(jdns_session_t *s, int now); + +static void _hold_req_id(jdns_session_t *s, int req_id) +{ + int pos; + + // make sure we don't hold an id twice + pos = _intarray_indexOf(s->held_req_ids, s->held_req_ids_count, req_id); + if(pos != -1) + return; + + _intarray_add(&s->held_req_ids, &s->held_req_ids_count, req_id); +} + +static void _unhold_req_id(jdns_session_t *s, int req_id) +{ + int pos; + + pos = _intarray_indexOf(s->held_req_ids, s->held_req_ids_count, req_id); + if(pos != -1) + _intarray_remove(&s->held_req_ids, &s->held_req_ids_count, pos); +} + +static void _set_hold_ids_enabled(jdns_session_t *s, int enabled) +{ + if(enabled && !s->do_hold_req_ids) + { + s->do_hold_req_ids = 1; + } + else if(!enabled && s->do_hold_req_ids) + { + s->do_hold_req_ids = 0; + + if(s->held_req_ids) + free(s->held_req_ids); + s->held_req_ids = 0; + s->held_req_ids_count = 0; + } +} + +static int _int_wrap(int *src, int start) +{ + int x; + x = (*src)++; + if(*src < start) + *src = start; + return x; +} + +// starts at 0 +static int get_next_qid(jdns_session_t *s) +{ + int n, id; + id = -1; + while(id == -1) + { + id = _int_wrap(&s->next_qid, 0); + for(n = 0; n < s->queries->count; ++n) + { + if(((query_t *)s->queries->item[n])->id == id) + { + id = -1; + break; + } + } + } + return id; +} + +// starts at 1 +static int get_next_req_id(jdns_session_t *s) +{ + int n, k, id; + id = -1; + while(id == -1) + { + id = _int_wrap(&s->next_req_id, 1); + + // no query using this? + for(n = 0; n < s->queries->count; ++n) + { + query_t *q = (query_t *)s->queries->item[n]; + for(k = 0; k < q->req_ids_count; ++k) + { + if(q->req_ids[k] == id) + { + id = -1; + break; + } + } + if(id == -1) + break; + } + + // no publish using this? + for(n = 0; n < s->published->count; ++n) + { + if(((published_item_t *)s->published->item[n])->id == id) + { + id = -1; + break; + } + } + + // successful unicast queries or any kind of error result in + // events for actions that are no longer active. we need + // to make sure ids for these actions are not reassigned + // until the user explicitly releases them + for(n = 0; n < s->held_req_ids_count; ++n) + { + if(s->held_req_ids[n] == id) + { + id = -1; + break; + } + } + } + return id; +} + +// random number fitting in 16 bits +static int get_next_dns_id(jdns_session_t *s) +{ + int n, id, active_ids; + active_ids = 0; + id = -1; + while(id == -1) + { + // use random number for dns id + id = s->cb.rand_int(s, s->cb.app) & 0xffff; + + for(n = 0; n < s->queries->count; ++n) + { + query_t *q = (query_t *)s->queries->item[n]; + if(q->dns_id != -1) + { + ++active_ids; + if(active_ids >= JDNS_QUERY_MAX) + return -1; + + if(q->dns_id == id) + { + id = -1; + break; + } + } + } + } + return id; +} + +// starts at 0 +static int get_next_name_server_id(jdns_session_t *s) +{ + int n, id; + id = -1; + while(id == -1) + { + id = _int_wrap(&s->next_name_server_id, 0); + for(n = 0; n < s->name_servers->count; ++n) + { + if(((name_server_t *)s->name_servers->item[n])->id == id) + { + id = -1; + break; + } + } + } + return id; +} + +int jdns_init_unicast(jdns_session_t *s, const jdns_address_t *addr, int port) +{ + int ret; + s->mode = 0; + ret = s->cb.udp_bind(s, s->cb.app, addr, port, 0); + if(ret <= 0) + return 0; + s->handle = ret; + s->port = port; + return 1; +} + +int jdns_init_multicast(jdns_session_t *s, const jdns_address_t *addr, int port, const jdns_address_t *maddr) +{ + int ret; + s->mode = 1; + ret = s->cb.udp_bind(s, s->cb.app, addr, port, maddr); + if(ret <= 0) + return 0; + s->handle = ret; + s->port = port; + s->maddr = jdns_address_copy(maddr); + + // class 1. note: frame size is ignored by the jdns version of mdnsd + s->mdns = mdnsd_new(0x0001, 1000, s->port, _callback_time_now, _callback_rand_int, s); + return 1; +} + +void jdns_shutdown(jdns_session_t *s) +{ + if(s->shutdown == 0) + s->shutdown = 1; // request shutdown +} + +void jdns_set_nameservers(jdns_session_t *s, const jdns_nameserverlist_t *nslist) +{ + int n, k; + + // removed? + for(k = 0; k < s->name_servers->count; ++k) + { + name_server_t *ns = (name_server_t *)(s->name_servers->item[k]); + int found = 0; + for(n = 0; n < nslist->count; ++n) + { + jdns_nameserver_t *i = (jdns_nameserver_t *)nslist->item[n]; + if(jdns_address_cmp(ns->address, i->address) && ns->port == i->port) + { + found = 1; + break; + } + } + if(!found) + { + int i; + int ns_id; + + // remove any pending packets to this nameserver + _remove_name_server_datagrams(s, ns->id); + + _debug_line(s, "ns [%s:%d] (id=%d) removed", ns->address->c_str, ns->port, ns->id); + ns_id = ns->id; + list_remove(s->name_servers, ns); + --k; // adjust position + for(i = 0; i < s->queries->count; ++i) + query_name_server_gone((query_t *)s->queries->item[i], ns_id); + } + } + + // added? + for(n = 0; n < nslist->count; ++n) + { + name_server_t *ns; + jdns_nameserver_t *i; + int found; + + i = (jdns_nameserver_t *)nslist->item[n]; + found = 0; + for(k = 0; k < s->name_servers->count; ++k) + { + ns = (name_server_t *)(s->name_servers->item[k]); + if(jdns_address_cmp(ns->address, i->address) && ns->port == i->port) + { + found = 1; + break; + } + } + if(found) + { + _debug_line(s, "ns [%s:%d] (id=%d) still present", ns->address->c_str, ns->port, ns->id); + } + else + { + ns = name_server_new(); + ns->id = get_next_name_server_id(s); + ns->address = jdns_address_copy(i->address); + ns->port = i->port; + list_insert(s->name_servers, ns, -1); + _debug_line(s, "ns [%s:%d] (id=%d) added", ns->address->c_str, ns->port, ns->id); + } + } + + // no nameservers? + if(nslist->count == 0) + { + _debug_line(s, "nameserver count is zero, invalidating any queries"); + + // invalidate all of the queries! + for(n = 0; n < s->queries->count; ++n) + { + query_t *q = (query_t *)s->queries->item[n]; + + // report event to any requests listening + for(k = 0; k < q->req_ids_count; ++k) + { + jdns_event_t *event = jdns_event_new(); + event->type = JDNS_EVENT_RESPONSE; + event->id = q->req_ids[k]; + event->status = JDNS_STATUS_TIMEOUT; + _append_event_and_hold_id(s, event); + } + + // this line is probably redundant, but just for + // consistency we'll do it... + _remove_query_datagrams(s, q); + + list_remove(s->queries, q); + --n; // adjust position + } + } +} + +void jdns_probe(jdns_session_t *s) +{ + if(s->mode != 1) + return; + + _multicast_flush(s); +} + +int jdns_query(jdns_session_t *s, const unsigned char *name, int rtype) +{ + if(s->mode == 0) + return _unicast_query(s, name, rtype); + else + return _multicast_query(s, name, rtype); +} + +static void _remove_events(jdns_session_t *s, int event_type, int id) +{ + int n; + for(n = 0; n < s->events->count; ++n) + { + event_t *e = (event_t *)s->events->item[n]; + if(e->event->type == event_type && e->event->id == id) + { + list_remove(s->events, e); + --n; // adjust position + } + } +} + +void jdns_cancel_query(jdns_session_t *s, int id) +{ + int n; + + _unhold_req_id(s, id); + + // remove any events associated with the query. this avoids any + // possibility that stale events from one query are mistaken to be + // events resulting from a later query that happened to reuse the + // id. it also means we don't deliver events for cancelled queries, + // which can simplify application logic. + _remove_events(s, JDNS_EVENT_RESPONSE, id); + + // multicast + if(s->mode == 1) + { + _multicast_cancel(s, id); + return; + } + + // unicast + for(n = 0; n < s->queries->count; ++n) + { + query_t *q = (query_t *)s->queries->item[n]; + if(query_have_req_id(q, id)) + { + query_remove_req_id(q, id); + + // note: calling _unicast_cancel might remove an item + // from s->queries, thereby screwing up our iterator + // position, but that's ok because we just break + // anyway. + + // if no one else is depending on this request, then take action + if(q->req_ids_count == 0 && !q->cname_parent) + { + // remove a possible cname child + if(q->cname_child && q->cname_child->req_ids_count == 0) + { + q->cname_child->cname_parent = 0; + _unicast_cancel(s, q->cname_child); + q->cname_child = 0; + } + + _unicast_cancel(s, q); + } + break; + } + } +} + +int jdns_publish(jdns_session_t *s, int mode, const jdns_rr_t *rr) +{ + return _multicast_publish(s, mode, rr); +} + +void jdns_update_publish(jdns_session_t *s, int id, const jdns_rr_t *rr) +{ + _multicast_update_publish(s, id, rr); +} + +void jdns_cancel_publish(jdns_session_t *s, int id) +{ + _unhold_req_id(s, id); + + _remove_events(s, JDNS_EVENT_PUBLISH, id); + + _multicast_cancel_publish(s, id); +} + +int jdns_step(jdns_session_t *s) +{ + int now, passed; + int ret; + + // session is shut down + if(s->shutdown == 2) + return 0; + + now = s->cb.time_now(s, s->cb.app); + passed = now - s->last_time; + + _debug_line(s, "passed: %d", passed); + + if(s->mode == 0) + ret = jdns_step_unicast(s, now); + else + ret = jdns_step_multicast(s, now); + + s->last_time = now; + return ret; +} + +int jdns_next_timer(jdns_session_t *s) +{ + return s->next_timer; +} + +void jdns_set_handle_readable(jdns_session_t *s, int handle) +{ + (void)handle; + s->handle_readable = 1; +} + +void jdns_set_handle_writable(jdns_session_t *s, int handle) +{ + (void)handle; + s->handle_writable = 1; +} + +jdns_event_t *jdns_next_event(jdns_session_t *s) +{ + jdns_event_t *event = 0; + if(s->events->count > 0) + { + event_t *e = (event_t *)s->events->item[0]; + event = e->event; + e->event = 0; + list_remove(s->events, e); + } + return event; +} + +void jdns_set_hold_ids_enabled(jdns_session_t *s, int enabled) +{ + _set_hold_ids_enabled(s, enabled); +} + +//---------------------------------------------------------------------------- +// jdns - internal functions +//---------------------------------------------------------------------------- + +// we don't have vsnprintf on windows, so don't pass anything enormous to +// this function. the plan is that no line should exceed 1000 bytes, +// although _print_rr() might get close. a 2048 byte buffer should be +// plenty then. +void _debug_line(jdns_session_t *s, const char *format, ...) +{ + char *buf = (char *)malloc(2048); + va_list ap; + va_start(ap, format); + jdns_vsprintf_s(buf, 2048, format, ap); + va_end(ap); + s->cb.debug_line(s, s->cb.app, buf); + free(buf); +} + +int _callback_time_now(mdnsd d, void *arg) +{ + jdns_session_t *s = (jdns_session_t *)arg; + (void)d; + // offset the time, mdnsd doesn't like starting at 0 + return s->cb.time_now(s, s->cb.app) + 120 * 1000; +} + +int _callback_rand_int(mdnsd d, void *arg) +{ + jdns_session_t *s = (jdns_session_t *)arg; + (void)d; + return s->cb.rand_int(s, s->cb.app); +} + +void _append_event(jdns_session_t *s, jdns_event_t *event) +{ + event_t *e = event_new(); + e->event = event; + list_insert(s->events, e, -1); +} + +void _append_event_and_hold_id(jdns_session_t *s, jdns_event_t *event) +{ + if(s->do_hold_req_ids) + _hold_req_id(s, event->id); + _append_event(s, event); +} + +void _remove_name_server_datagrams(jdns_session_t *s, int ns_id) +{ + int n; + for(n = 0; n < s->outgoing->count; ++n) + { + datagram_t *a = (datagram_t *)s->outgoing->item[n]; + if(a->ns_id == ns_id) + { + list_remove(s->outgoing, a); + --n; // adjust position + } + } +} + +void _remove_query_datagrams(jdns_session_t *s, const query_t *q) +{ + int n; + for(n = 0; n < s->outgoing->count; ++n) + { + datagram_t *a = (datagram_t *)s->outgoing->item[n]; + if(a->query == q) + { + list_remove(s->outgoing, a); + --n; // adjust position + } + } +} + +void _process_message(jdns_session_t *s, jdns_packet_t *p, int now, query_t *q, name_server_t *ns); + +// return 1 if 'q' should be deleted, 0 if not +int _process_response(jdns_session_t *s, jdns_response_t *r, int nxdomain, int now, query_t *q); + +jdns_response_t *_cache_get_response(jdns_session_t *s, const unsigned char *qname, int qtype, int *_lowest_timeleft) +{ + int n; + int lowest_timeleft = -1; + int now = s->cb.time_now(s, s->cb.app); + jdns_response_t *r = 0; + for(n = 0; n < s->cache->count; ++n) + { + cache_item_t *i = (cache_item_t *)s->cache->item[n]; + if(jdns_domain_cmp(i->qname, qname) && i->qtype == qtype) + { + int passed, timeleft; + + if(!r) + r = jdns_response_new(); + + if(i->record) + jdns_response_append_answer(r, i->record); + + passed = now - i->time_start; + timeleft = (i->ttl * 1000) - passed; + if(lowest_timeleft == -1 || timeleft < lowest_timeleft) + lowest_timeleft = timeleft; + } + } + if(_lowest_timeleft) + *_lowest_timeleft = lowest_timeleft; + return r; +} + +query_t *_find_first_active_query(jdns_session_t *s, const unsigned char *qname, int qtype) +{ + int n; + query_t *q; + + for(n = 0; n < s->queries->count; ++n) + { + q = (query_t *)s->queries->item[n]; + if(jdns_domain_cmp(q->qname, qname) && q->qtype == qtype && q->step != -1) + return q; + } + + return 0; +} + +query_t *_get_query(jdns_session_t *s, const unsigned char *qname, int qtype, int unique) +{ + query_t *q; + jdns_string_t *str; + + if(!unique) + { + q = _find_first_active_query(s, qname, qtype); + if(q) + { + str = _make_printable_cstr((const char *)q->qname); + _debug_line(s, "[%d] reusing query for: [%s] [%s]", q->id, _qtype2str(qtype), str->data); + jdns_string_delete(str); + return q; + } + } + + q = query_new(); + q->id = get_next_qid(s); + q->qname = _ustrdup(qname); + q->qtype = qtype; + q->step = 0; + q->dns_id = -1; + q->time_start = 0; + q->time_next = 0; + q->trycache = 1; + q->retrying = 0; + list_insert(s->queries, q, -1); + + str = _make_printable_cstr((const char *)q->qname); + _debug_line(s, "[%d] querying: [%s] [%s]", q->id, _qtype2str(qtype), str->data); + jdns_string_delete(str); + return q; +} + +int _unicast_query(jdns_session_t *s, const unsigned char *name, int qtype) +{ + unsigned char *qname; + query_t *q; + int req_id; + jdns_string_t *str; + + str = _make_printable_cstr((const char *)name); + _debug_line(s, "query input: [%s]", str->data); + jdns_string_delete(str); + + qname = _fix_input(name); + + q = _get_query(s, qname, qtype, 0); + req_id = get_next_req_id(s); + query_add_req_id(q, req_id); + free(qname); + return req_id; +} + +void _unicast_cancel(jdns_session_t *s, query_t *q) +{ + // didn't even do a step yet? just remove it + if(q->step == 0) + { + _remove_query_datagrams(s, q); + list_remove(s->queries, q); + } + // otherwise, just deactivate + else + { + // deactivate and remain in the background for + // 1 minute. this will allow us to cache a + // reply, even if the user is not currently + // interested. + q->step = -1; + q->time_start = s->cb.time_now(s, s->cb.app); + q->time_next = 60000; + } +} + +void _queue_packet(jdns_session_t *s, query_t *q, const name_server_t *ns, int recurse, int query_send_type) +{ + jdns_packet_t *packet; + datagram_t *a; + + packet = jdns_packet_new(); + packet->id = q->dns_id; + packet->opts.rd = recurse; // recursion desired + { + jdns_packet_question_t *question = jdns_packet_question_new(); + question->qname = jdns_string_new(); + jdns_string_set_cstr(question->qname, (const char *)q->qname); + question->qtype = q->qtype; + question->qclass = 0x0001; + jdns_list_insert(packet->questions, question, -1); + jdns_packet_question_delete(question); + } + if(!jdns_packet_export(packet, JDNS_UDP_UNI_OUT_MAX)) + { + _debug_line(s, "outgoing packet export error, not sending"); + jdns_packet_delete(packet); + return; + } + + a = datagram_new(); + a->handle = s->handle; + a->dest_address = jdns_address_copy(ns->address); + a->dest_port = ns->port; + a->data = jdns_copy_array(packet->raw_data, packet->raw_size); + a->size = packet->raw_size; + a->query = q; + a->query_send_type = query_send_type; + a->ns_id = ns->id; + + jdns_packet_delete(packet); + + list_insert(s->outgoing, a, -1); +} + +// return 1 if packets still need to be written +int _unicast_do_writes(jdns_session_t *s, int now); + +// return 1 if packets still need to be read +int _unicast_do_reads(jdns_session_t *s, int now); + +int jdns_step_unicast(jdns_session_t *s, int now) +{ + int n; + int need_read = 0; + int need_write = 0; + int smallest_time = -1; + int flags; + + if(s->shutdown == 1) + { + jdns_event_t *event = jdns_event_new(); + event->type = JDNS_EVENT_SHUTDOWN; + _append_event(s, event); + s->shutdown = 2; + return 0; + } + + // expire cached items + for(n = 0; n < s->cache->count; ++n) + { + cache_item_t *i = (cache_item_t *)s->cache->item[n]; + if(now >= i->time_start + (i->ttl * 1000)) + { + jdns_string_t *str = _make_printable_cstr((const char *)i->qname); + _debug_line(s, "cache exp [%s]", str->data); + jdns_string_delete(str); + list_remove(s->cache, i); + --n; // adjust position + } + } + + need_write = _unicast_do_writes(s, now); + need_read = _unicast_do_reads(s, now); + + // calculate next timer (based on queries and cache) + for(n = 0; n < s->queries->count; ++n) + { + query_t *q = (query_t *)(s->queries->item[n]); + if(q->time_start != -1) + { + int qpassed = now - q->time_start; + int timeleft = q->time_next - qpassed; + if(timeleft < 0) + timeleft = 0; + + if(smallest_time == -1 || timeleft < smallest_time) + smallest_time = timeleft; + } + } + for(n = 0; n < s->cache->count; ++n) + { + cache_item_t *i = (cache_item_t *)(s->cache->item[n]); + int passed = now - i->time_start; + int timeleft = (i->ttl * 1000) - passed; + if(timeleft < 0) + timeleft = 0; + + if(smallest_time == -1 || timeleft < smallest_time) + smallest_time = timeleft; + } + + flags = 0; + if(smallest_time != -1) + { + flags |= JDNS_STEP_TIMER; + s->next_timer = smallest_time; + + // offset it a little bit, so that the user doesn't call + // us too early, resulting in a no-op and another timer + // of 1 millisecond. + s->next_timer += 2; + } + if(need_read || need_write) + flags |= JDNS_STEP_HANDLE; + return flags; +} + +int _unicast_do_writes(jdns_session_t *s, int now) +{ + int need_write = 0; + int n, k; + + for(n = 0; n < s->queries->count; ++n) + { + query_t *q; + int qpassed, timeleft; + int giveup; + name_server_t *ns; + int already_sending; + + q = (query_t *)s->queries->item[n]; + + // nothing to do + if(q->time_start == -1) + continue; + + qpassed = now - q->time_start; + timeleft = q->time_next - qpassed; + if(timeleft < 0) + timeleft = 0; + _debug_line(s, "[%d] time_start/next=%d/%d (left=%d)", q->id, q->time_start, q->time_next, timeleft); + if(timeleft > 0) + continue; + + if(q->trycache) + { + // is it cached? + int lowest_timeleft; + int qtype = q->qtype; + jdns_response_t *r; + + r = _cache_get_response(s, q->qname, qtype, &lowest_timeleft); + + // not found? try cname + if(!r) + { + qtype = JDNS_RTYPE_CNAME; + r = _cache_get_response(s, q->qname, qtype, &lowest_timeleft); + } + + if(r) + { + int nxdomain; + + _debug_line(s, "[%d] using cached answer", q->id); + + // are any of the records about to expire in 3 minutes? + // assume the client is interested in this record and + // query it again "in the background" (but only + // if we are not already doing so) + if(lowest_timeleft < (3 * 60 * 1000) && !_find_first_active_query(s, q->qname, q->qtype)) + { + query_t *new_q; + + _debug_line(s, "requerying for cached item about to expire"); + + new_q = _get_query(s, q->qname, q->qtype, 1); + new_q->retrying = 1; // slow it down + new_q->trycache = 0; // don't use the cache for this + } + + nxdomain = r->answerCount == 0 ? 1 : 0; + if(_process_response(s, r, nxdomain, -1, q)) + { + _remove_query_datagrams(s, q); + list_remove(s->queries, q); + --n; // adjust position + } + + jdns_response_delete(r); + continue; + } + } + + // inactive + if(q->step == -1) + { + // time up on an inactive query? remove it + _debug_line(s, "removing inactive query"); + _remove_query_datagrams(s, q); + list_remove(s->queries, q); + --n; // adjust position + continue; + } + + giveup = 0; + + // too many tries, give up + if(q->step == 8) + giveup = 1; + + // no nameservers, give up + // (this would happen if someone removed all nameservers + // during a query) + if(s->name_servers->count == 0) + giveup = 1; + + if(giveup) + { + // report event to any requests listening + for(k = 0; k < q->req_ids_count; ++k) + { + jdns_event_t *event = jdns_event_new(); + event->type = JDNS_EVENT_RESPONSE; + event->id = q->req_ids[k]; + event->status = JDNS_STATUS_TIMEOUT; + _append_event_and_hold_id(s, event); + } + + // report error to parent + if(q->cname_parent) + { + // report event to any requests listening + query_t *cq = q->cname_parent; + for(k = 0; k < cq->req_ids_count; ++k) + { + jdns_event_t *event = jdns_event_new(); + event->type = JDNS_EVENT_RESPONSE; + event->id = cq->req_ids[k]; + event->status = JDNS_STATUS_TIMEOUT; + _append_event_and_hold_id(s, event); + } + list_remove(s->queries, cq); + } + + _remove_query_datagrams(s, q); + list_remove(s->queries, q); + --n; // adjust position + continue; + } + + // assign a packet id if we don't have one yet + if(q->dns_id == -1) + { + q->dns_id = get_next_dns_id(s); + + // couldn't get an id? + if(q->dns_id == -1) + { + _debug_line(s, "unable to reserve packet id"); + + // report event to any requests listening + for(k = 0; k < q->req_ids_count; ++k) + { + jdns_event_t *event = jdns_event_new(); + event->type = JDNS_EVENT_RESPONSE; + event->id = q->req_ids[k]; + event->status = JDNS_STATUS_ERROR; + _append_event_and_hold_id(s, event); + } + + // report error to parent + if(q->cname_parent) + { + // report event to any requests listening + query_t *cq = q->cname_parent; + for(k = 0; k < cq->req_ids_count; ++k) + { + jdns_event_t *event = jdns_event_new(); + event->type = JDNS_EVENT_RESPONSE; + event->id = cq->req_ids[k]; + event->status = JDNS_STATUS_ERROR; + _append_event_and_hold_id(s, event); + } + list_remove(s->queries, cq); + } + + _remove_query_datagrams(s, q); + list_remove(s->queries, q); + --n; // adjust position + continue; + } + } + + // out of name servers? + if(q->servers_tried_count == s->name_servers->count) + { + // clear the 'tried' list, and start over in retry mode + query_clear_servers_tried(q); + q->retrying = 1; + } + + // find a nameserver that has not been tried + ns = 0; + for(k = 0; k < s->name_servers->count; ++k) + { + name_server_t *i = (name_server_t *)s->name_servers->item[k]; + if(!query_server_tried(q, i->id)) + { + ns = i; + break; + } + } + + // in theory, it is not possible for 'ns' to be null here + + // don't send the packet if there is already one in the queue + already_sending = 0; + for(k = 0; k < s->outgoing->count; ++k) + { + datagram_t *a = (datagram_t *)s->outgoing->item[k]; + if(a->query == q && a->query_send_type == 0) + { + already_sending = 1; + break; + } + } + + // send the query, with recursion desired, normal query_send_type + if(!already_sending) + _queue_packet(s, q, ns, 1, 0); + + query_add_server_tried(q, ns->id); + + // if there is one query, then do a trick on the first step + /*if(s->queries->count == 1 && q->step == 0 && !q->retrying) + { + // query all other servers non-recursively + // note: if sending fails, there is no retry + for(k = 0; k < s->name_servers->count; ++k) + { + name_server_t *i = (name_server_t *)s->name_servers->item[k]; + if(!query_server_tried(q, i->id)) + { + // last arg sets first-step query_send_type + _queue_packet(s, q, i, 0, 1); + } + } + }*/ + + // out of name servers? + if(q->servers_tried_count == s->name_servers->count) + { + // clear the 'tried' list, and start over in retry mode + query_clear_servers_tried(q); + q->retrying = 1; + } + + q->time_start = now; + q->time_next = q->retrying ? 1500 : 800; + ++q->step; + } + + // try to send queued outgoing packets + for(n = 0; n < s->outgoing->count; ++n) + { + datagram_t *a = (datagram_t *)s->outgoing->item[n]; + int ret; + + if(!s->handle_writable) + { + need_write = 1; + break; + } + + _debug_line(s, "SEND %s:%d (size=%d)", a->dest_address->c_str, a->dest_port, a->size); + _print_hexdump(s, a->data, a->size); + + ret = s->cb.udp_write(s, s->cb.app, a->handle, a->dest_address, a->dest_port, a->data, a->size); + if(ret == 0) + { + s->handle_writable = 0; + need_write = 1; + break; + } + + list_remove(s->outgoing, a); + --n; // adjust position + } + + return need_write; +} + +void _cache_add(jdns_session_t *s, const unsigned char *qname, int qtype, int time_start, int ttl, const jdns_rr_t *record) +{ + cache_item_t *i; + jdns_string_t *str; + if(ttl == 0) + return; + if(s->cache->count >= JDNS_CACHE_MAX) + return; + i = cache_item_new(); + i->qname = _ustrdup(qname); + i->qtype = qtype; + i->time_start = time_start; + i->ttl = ttl; + if(record) + i->record = jdns_rr_copy(record); + list_insert(s->cache, i, -1); + + str = _make_printable_cstr((const char *)i->qname); + _debug_line(s, "cache add [%s] for %d seconds", str->data, i->ttl); + jdns_string_delete(str); +} + +void _cache_remove_all_of_kind(jdns_session_t *s, const unsigned char *qname, int qtype) +{ + int n; + for(n = 0; n < s->cache->count; ++n) + { + cache_item_t *i = (cache_item_t *)s->cache->item[n]; + if(jdns_domain_cmp(i->qname, qname) && i->qtype == qtype) + { + jdns_string_t *str = _make_printable_cstr((const char *)i->qname); + _debug_line(s, "cache del [%s]", str->data); + jdns_string_delete(str); + list_remove(s->cache, i); + --n; // adjust position + } + } +} + +void _cache_remove_all_of_record(jdns_session_t *s, const jdns_rr_t *record) +{ + int n; + for(n = 0; n < s->cache->count; ++n) + { + cache_item_t *i = (cache_item_t *)s->cache->item[n]; + if(i->record && _cmp_rr(i->record, record)) + { + jdns_string_t *str = _make_printable_cstr((const char *)i->qname); + _debug_line(s, "cache del [%s]", str->data); + jdns_string_delete(str); + list_remove(s->cache, i); + --n; // adjust position + } + } +} + +// same as _cache_add, but make sure the exact same record (name AND value) +// isn't stored twice, and make sure no more than one cname record per name +// is stored. +void _cache_add_no_dups(jdns_session_t *s, const unsigned char *qname, int qtype, int time_start, int ttl, const jdns_rr_t *record) +{ + if(qtype == JDNS_RTYPE_CNAME) + _cache_remove_all_of_kind(s, qname, qtype); + else + _cache_remove_all_of_record(s, record); + + _cache_add(s, qname, qtype, time_start, ttl, record); +} + +int _unicast_do_reads(jdns_session_t *s, int now) +{ + int need_read; + int n, k; + + // let's always ask for reads, just so the user doesn't have to + // worry about what should happen to incoming packets otherwise + need_read = 1; + + if(!s->handle_readable) + return need_read; + + while(1) + { + unsigned char buf[JDNS_UDP_UNI_IN_MAX]; + int bufsize = JDNS_UDP_UNI_IN_MAX; + int ret; + jdns_packet_t *packet; + jdns_address_t *addr; + int port; + query_t *q; + name_server_t *ns; + + addr = jdns_address_new(); + ret = s->cb.udp_read(s, s->cb.app, s->handle, addr, &port, buf, &bufsize); + + // no packet? + if(ret == 0) + { + s->handle_readable = 0; + jdns_address_delete(addr); + break; + } + + _debug_line(s, "RECV %s:%d (size=%d)", addr->c_str, port, bufsize); + _print_hexdump(s, buf, bufsize); + + if(!jdns_packet_import(&packet, buf, bufsize)) + { + _debug_line(s, "error parsing packet / too large"); + + jdns_address_delete(addr); + continue; + } + + _print_packet(s, packet); + + if(s->queries->count == 0) + { + _debug_line(s, "we have no queries"); + + jdns_address_delete(addr); + jdns_packet_delete(packet); + continue; + } + + // who does it belong to? + q = 0; + ns = 0; + for(n = 0; n < s->queries->count; ++n) + { + query_t *i = (query_t *)s->queries->item[n]; + if(i->dns_id == -1) + continue; + + if(i->dns_id == packet->id) + { + q = i; + break; + } + } + + if(q) + { + // what name server did it come from? + for(k = 0; k < s->name_servers->count; ++k) + { + name_server_t *i = (name_server_t *)s->name_servers->item[k]; + if(jdns_address_cmp(i->address, addr) && i->port == port) + { + ns = i; + break; + } + } + + // none? maybe that's because we're using unicast + // over multicast, where responses always come + // from an unexpected address + if(!ns && s->name_servers->count > 0) + { + name_server_t *i; + jdns_address_t *m4, *m6; + + i = (name_server_t *)s->name_servers->item[0]; + m4 = jdns_address_multicast4_new(); + m6 = jdns_address_multicast6_new(); + if(jdns_address_cmp(i->address, m4) || jdns_address_cmp(i->address, m6)) + ns = i; + jdns_address_delete(m4); + jdns_address_delete(m6); + } + + // no suitable name server + if(!ns) + { + // setting q = 0 causes the response to be + // ignored. earlier versions of jdns would + // do this, but now we comment it out because + // the behavior is too strict. + //q = 0; + + // instead we'll just print a warning + _debug_line(s, "warning: response from unexpected nameserver"); + } + } + + jdns_address_delete(addr); + + // no queries? eat the packet + if(!q) + { + _debug_line(s, "no such query for packet"); + jdns_packet_delete(packet); + continue; + } + + _process_message(s, packet, now, q, ns); + jdns_packet_delete(packet); + } + + return need_read; +} + +void _process_message(jdns_session_t *s, jdns_packet_t *packet, int now, query_t *q, name_server_t *ns) +{ + int n; + int authoritative; + int truncated; + int recursion_desired; + int answer_section_ok; + jdns_response_t *r; + + if(packet->opts.opcode != 0) + { + _debug_line(s, "opcode != 0, discarding"); + return; + } + + // we don't test RA (recursion available) + // we don't test the extra Z fields + + authoritative = packet->opts.aa; + truncated = packet->opts.tc; + recursion_desired = packet->opts.rd; + answer_section_ok = 0; + if(packet->qdcount == packet->questions->count && packet->ancount == packet->answerRecords->count) + answer_section_ok = 1; + + r = 0; + + // nxdomain + if(packet->opts.rcode == 3) + { + // treat nxdomain as a generic error, but at the same time flag + // the fact that it was received. this ensures that + // resolving keeps going, in case the user has multiple dns + // servers and one of them reports nxdomain when a later one + // would succeed. if all of the servers fail then this flag + // can be used at the end to report nxdomain instead of a + // generic error. + q->nxdomain = 1; + } + // normal + else if(packet->opts.rcode == 0) + { + int at_least_something; + int success; + + r = _packet2response(packet, q->qname, q->qtype, 0xffff); + at_least_something = 0; + if(r->answerCount > 0) + at_least_something = 1; + _print_records(s, r, q->qname); + + success = 0; + if(at_least_something) + { + success = 1; + } + else + { + // note: why does qdns care about recursion_desired here? + if(authoritative && recursion_desired) + success = 1; + } + + if(!success) + { + jdns_response_delete(r); + r = 0; + } + } + + // caching + if(r) + { + int cache_answers; + int cache_additional; + + // clear past items + _cache_remove_all_of_kind(s, q->qname, q->qtype); + + cache_answers = 1; + cache_additional = 1; + + // if truncated, we may not want to cache + if(truncated) + { + cache_additional = 0; + if(!answer_section_ok) + cache_answers = 0; + } + + if(cache_answers) + { + for(n = 0; n < r->answerCount; ++n) + { + jdns_rr_t *record = r->answerRecords[n]; + _cache_add_no_dups(s, q->qname, record->type, now, _min(record->ttl, JDNS_TTL_MAX), record); + } + } + + if(cache_additional) + { + for(n = 0; n < r->additionalCount; ++n) + { + jdns_rr_t *record = r->additionalRecords[n]; + _cache_add_no_dups(s, record->owner, record->type, now, _min(record->ttl, JDNS_TTL_MAX), record); + } + } + } + + // don't pass authority/additional records upwards + if(r) + jdns_response_remove_extra(r); + + // this server returned an error? + if(!r && ns) + { + // all failed servers must also be considered tried servers, + // so mark as tried if necessary. this can happen if the + // tried list is cleared (to perform retrying) and then an + // error is received + if(!query_server_tried(q, ns->id)) + query_add_server_tried(q, ns->id); + + query_add_server_failed(q, ns->id); + } + + if(_process_response(s, r, 0, now, q)) + { + _remove_query_datagrams(s, q); + list_remove(s->queries, q); + } + + jdns_response_delete(r); +} + +// 'r' can be null, for processing an error +// 'now' can be -1, if processing a cached response ('r' always non-null) +int _process_response(jdns_session_t *s, jdns_response_t *r, int nxdomain, int now, query_t *q) +{ + int k; + int do_error = 0; + int do_nxdomain = 0; + + // error + if(!r) + { + int all_errored; + + // if not all servers have errored, ignore error + all_errored = 1; + for(k = 0; k < s->name_servers->count; ++k) + { + name_server_t *ns = (name_server_t *)s->name_servers->item[k]; + if(!query_server_failed(q, ns->id)) + { + all_errored = 0; + break; + } + } + if(!all_errored) + return 0; + + do_error = 1; + + // if we picked up an nxdomain along the way, act on it now + if(q->nxdomain) + { + do_nxdomain = 1; + + // cache nxdomain for 1 minute + if(q->qtype != JDNS_RTYPE_ANY && now != -1) + { + _cache_remove_all_of_kind(s, q->qname, q->qtype); + _cache_add(s, q->qname, q->qtype, now, 60, 0); + } + } + } + else if(nxdomain) + { + do_error = 1; + do_nxdomain = 1; + } + + if(do_error) + { + // report event to any requests listening + for(k = 0; k < q->req_ids_count; ++k) + { + jdns_event_t *event = jdns_event_new(); + event->type = JDNS_EVENT_RESPONSE; + event->id = q->req_ids[k]; + if(do_nxdomain) + event->status = JDNS_STATUS_NXDOMAIN; + else + event->status = JDNS_STATUS_ERROR; + _append_event_and_hold_id(s, event); + } + + // report error to parent + if(q->cname_parent) + { + // report event to any requests listening + query_t *cq = q->cname_parent; + for(k = 0; k < cq->req_ids_count; ++k) + { + jdns_event_t *event = jdns_event_new(); + event->type = JDNS_EVENT_RESPONSE; + event->id = cq->req_ids[k]; + event->status = JDNS_STATUS_ERROR; + _append_event_and_hold_id(s, event); + } + list_remove(s->queries, cq); + } + + return 1; + } + + // all we got was a cname that we didn't ask for? + if(r->answerCount == 1 && r->answerRecords[0]->type == JDNS_RTYPE_CNAME && q->qtype != JDNS_RTYPE_CNAME) + { + query_t *new_q; + + _debug_line(s, "all we got was a cname, following the chain ..."); + + // max chain count, bail + if(q->cname_chain_count >= JDNS_CNAME_MAX) + { + // report event to any requests listening + for(k = 0; k < q->req_ids_count; ++k) + { + jdns_event_t *event = jdns_event_new(); + event->type = JDNS_EVENT_RESPONSE; + event->id = q->req_ids[k]; + event->status = JDNS_STATUS_ERROR; + _append_event_and_hold_id(s, event); + } + + // report error to parent + if(q->cname_parent) + { + // report event to any requests listening + query_t *cq = q->cname_parent; + for(k = 0; k < cq->req_ids_count; ++k) + { + jdns_event_t *event = jdns_event_new(); + event->type = JDNS_EVENT_RESPONSE; + event->id = cq->req_ids[k]; + event->status = JDNS_STATUS_ERROR; + _append_event_and_hold_id(s, event); + } + list_remove(s->queries, cq); + } + + return 1; + } + + new_q = _get_query(s, r->answerRecords[0]->data.name, q->qtype, 1); + + // is the current query a child query? (has a parent) + if(q->cname_parent) + { + // if so, then set new_q as the new child + new_q->cname_chain_count = q->cname_chain_count + 1; + new_q->cname_parent = q->cname_parent; + new_q->cname_parent->cname_child = new_q; + + // and delete the current query + return 1; + } + else + { + // otherwise, the current query becomes a parent, with + // new_q set as the child + new_q->cname_chain_count = q->cname_chain_count + 1; + new_q->cname_parent = q; + q->cname_child = new_q; + q->time_start = -1; + q->dns_id = -1; // don't handle responses + } + } + + // if this query now has a child, then don't report events or delete + if(q->cname_child) + return 0; + + // report event to any requests listening + for(k = 0; k < q->req_ids_count; ++k) + { + jdns_event_t *event = jdns_event_new(); + event->type = JDNS_EVENT_RESPONSE; + event->id = q->req_ids[k]; + event->status = JDNS_STATUS_SUCCESS; + event->response = jdns_response_copy(r); + _append_event_and_hold_id(s, event); + } + + // report to parent + if(q->cname_parent) + { + // report event to any requests listening + query_t *cq = q->cname_parent; + for(k = 0; k < cq->req_ids_count; ++k) + { + jdns_event_t *event = jdns_event_new(); + event->type = JDNS_EVENT_RESPONSE; + event->id = cq->req_ids[k]; + event->status = JDNS_STATUS_SUCCESS; + event->response = jdns_response_copy(r); + _append_event_and_hold_id(s, event); + } + list_remove(s->queries, cq); + } + + return 1; +} + +//---------------------------------------------------------------------------- +// jdns - multicast +//---------------------------------------------------------------------------- +static jdns_rr_t *_mdnsda2rr(mdnsda a) +{ + jdns_rr_t *rr; + + if(a->type == JDNS_RTYPE_ANY) + return 0; + + // for AAAA, TXT and HINFO, run the raw rdata through jdns_rr's parser + if(a->type == JDNS_RTYPE_AAAA || a->type == JDNS_RTYPE_TXT || a->type == JDNS_RTYPE_HINFO) + { + jdns_packet_resource_t *pr = jdns_packet_resource_new(); + pr->qname = jdns_string_new(); + jdns_string_set_cstr(pr->qname, (const char *)a->name); + pr->qtype = a->type; + pr->qclass = 0x0001; // class is always 1 for us + if(a->ttl == 0) + pr->ttl = 0; + else + pr->ttl = a->real_ttl; + pr->rdata = jdns_copy_array(a->rdata, a->rdlen); + pr->rdlength = a->rdlen; + + // we don't need a reference for these types + rr = jdns_rr_from_resource(pr, 0); + } + // else, pull the values out of 'a' directly + else + { + rr = jdns_rr_new(); + rr->owner = _ustrdup(a->name); + rr->qclass = 0x0001; // class is always 1 for us + if(a->ttl == 0) + rr->ttl = 0; + else + rr->ttl = a->real_ttl; + + switch(a->type) + { + case JDNS_RTYPE_A: + { + jdns_address_t *addr = jdns_address_new(); + jdns_address_set_ipv4(addr, a->ip); + jdns_rr_set_A(rr, addr); + jdns_address_delete(addr); + break; + } + case JDNS_RTYPE_AAAA: + { + // covered earlier + break; + } + case JDNS_RTYPE_MX: + { + // don't care about MX + jdns_rr_delete(rr); + rr = 0; + break; + } + case JDNS_RTYPE_SRV: + { + jdns_rr_set_SRV(rr, a->rdname, a->srv.port, a->srv.priority, a->srv.weight); + break; + } + case JDNS_RTYPE_CNAME: + { + jdns_rr_set_CNAME(rr, a->rdname); + break; + } + case JDNS_RTYPE_PTR: + { + jdns_rr_set_PTR(rr, a->rdname); + break; + } + case JDNS_RTYPE_TXT: + { + // covered earlier + break; + } + case JDNS_RTYPE_HINFO: + { + // covered earlier + break; + } + case JDNS_RTYPE_NS: + { + // don't care about NS + jdns_rr_delete(rr); + rr = 0; + break; + } + default: + { + jdns_rr_set_record(rr, a->type, a->rdata, a->rdlen); + break; + } + } + } + + return rr; +} + +int _multicast_query_ans(mdnsda a, void *arg) +{ + int n; + jdns_session_t *s; + query_t *q; + jdns_response_t *r; + jdns_rr_t *rr; + jdns_event_t *event; + + s = (jdns_session_t *)arg; + + // what query is this for? + q = 0; + for(n = 0; n < s->queries->count; ++n) + { + query_t *i = (query_t *)s->queries->item[n]; + if((i->qtype == JDNS_RTYPE_ANY || i->qtype == a->type) && jdns_domain_cmp(i->qname, a->name)) + { + q = i; + break; + } + } + + // note: this can't happen, but we'll check anyway + if(!q) + { + _debug_line(s, "no such multicast query"); + return 0; + } + + rr = _mdnsda2rr(a); + if(!rr) + return 0; + + // add/remove as a known + if(rr->ttl == 0) + { + for(n = 0; n < q->mul_known->answerCount; ++n) + { + jdns_rr_t *k = q->mul_known->answerRecords[n]; + if(_cmp_rr(k, rr)) + { + jdns_response_remove_answer(q->mul_known, n); + break; + } + } + } + else + jdns_response_append_answer(q->mul_known, rr); + + r = jdns_response_new(); + jdns_response_append_answer(r, rr); + jdns_rr_delete(rr); + + // report event to any requests listening + for(n = 0; n < q->req_ids_count; ++n) + { + event = jdns_event_new(); + event->type = JDNS_EVENT_RESPONSE; + event->id = q->req_ids[n]; + event->status = JDNS_STATUS_SUCCESS; + event->response = jdns_response_copy(r); + _append_event(s, event); + } + + jdns_response_delete(r); + return 0; +} + +query_t *_get_multicast_query(jdns_session_t *s, const unsigned char *qname, int qtype) +{ + int n; + query_t *q; + jdns_string_t *str; + + // check for existing queries + for(n = 0; n < s->queries->count; ++n) + { + q = (query_t *)s->queries->item[n]; + if(jdns_domain_cmp(q->qname, qname) && q->qtype == qtype) + { + str = _make_printable_cstr((const char *)q->qname); + _debug_line(s, "[%d] reusing query for: [%s] [%s]", q->id, _qtype2str(qtype), str->data); + jdns_string_delete(str); + return q; + } + } + + q = query_new(); + q->id = get_next_qid(s); + q->qname = _ustrdup(qname); + q->qtype = qtype; + q->step = 0; + q->mul_known = jdns_response_new(); + list_insert(s->queries, q, -1); + + str = _make_printable_cstr((const char *)q->qname); + _debug_line(s, "[%d] querying: [%s] [%s]", q->id, _qtype2str(qtype), str->data); + jdns_string_delete(str); + return q; +} + +int _multicast_query(jdns_session_t *s, const unsigned char *name, int qtype) +{ + unsigned char *qname; + query_t *q; + int req_id; + jdns_string_t *str; + + str = _make_printable_cstr((const char *)name); + _debug_line(s, "query input: [%s]", str->data); + jdns_string_delete(str); + + // add a dot to the end if needed + qname = _fix_input(name); + + q = _get_multicast_query(s, qname, qtype); + req_id = get_next_req_id(s); + query_add_req_id(q, req_id); + free(qname); + + // start the mdnsd_query if necessary + if(q->step == 0) + { + q->step = 1; + mdnsd_query(s->mdns, (char *)q->qname, q->qtype, _multicast_query_ans, s); + } + else + { + int n; + + // report the knowns + for(n = 0; n < q->mul_known->answerCount; ++n) + { + const jdns_rr_t *rr; + jdns_response_t *r; + jdns_event_t *event; + + rr = q->mul_known->answerRecords[n]; + r = jdns_response_new(); + jdns_response_append_answer(r, rr); + + event = jdns_event_new(); + event->type = JDNS_EVENT_RESPONSE; + event->id = req_id; + event->status = JDNS_STATUS_SUCCESS; + event->response = r; + _append_event(s, event); + } + } + return req_id; +} + +void _multicast_cancel(jdns_session_t *s, int req_id) +{ + int n; + for(n = 0; n < s->queries->count; ++n) + { + query_t *q = (query_t *)s->queries->item[n]; + if(query_have_req_id(q, req_id)) + { + query_remove_req_id(q, req_id); + + // if no one else is depending on this request, then take action + if(q->req_ids_count == 0) + { + mdnsd_query(s->mdns, (char *)q->qname, q->qtype, NULL, 0); + list_remove(s->queries, q); + } + break; + } + } +} + +void _multicast_pubresult(int result, char *name, int type, void *arg) +{ + jdns_session_t *s; + published_item_t *pub; + jdns_event_t *event; + int n; + + s = (jdns_session_t *)arg; + + // find the associated pub item + pub = 0; + for(n = 0; n < s->published->count; ++n) + { + published_item_t *i = (published_item_t *)s->published->item[n]; + if(strcmp((char *)i->qname, name) == 0 && i->qtype == type) + { + pub = i; + break; + } + } + + // note: this can't happen, but we'll check anyway + if(!pub) + { + _debug_line(s, "no such multicast published item"); + return; + } + + if(result == 1) + { + jdns_string_t *str = _make_printable_cstr(name); + _debug_line(s, "published name %s for type %d", str->data, type); + jdns_string_delete(str); + + event = jdns_event_new(); + event->type = JDNS_EVENT_PUBLISH; + event->id = pub->id; + event->status = JDNS_STATUS_SUCCESS; + _append_event(s, event); + } + else + { + jdns_string_t *str = _make_printable_cstr(name); + _debug_line(s, "conflicting name detected %s for type %d", str->data, type); + jdns_string_delete(str); + + event = jdns_event_new(); + event->type = JDNS_EVENT_PUBLISH; + event->id = pub->id; + event->status = JDNS_STATUS_CONFLICT; + _append_event_and_hold_id(s, event); + + // remove the item + list_remove(s->published, pub); + } +} + +static jdns_string_t *_create_text(const jdns_stringlist_t *texts) +{ + jdns_string_t *out; + int n; + int total; + unsigned char *buf; + + buf = 0; + total = 0; + for(n = 0; n < texts->count; ++n) + total += texts->item[n]->size + 1; + if(total > 0) + { + int at = 0; + buf = (unsigned char *)malloc(total); + for(n = 0; n < texts->count; ++n) + { + unsigned int len = texts->item[n]->size; + buf[at++] = len; + memcpy(buf + at, texts->item[n]->data, len); + at += len; + } + } + + out = jdns_string_new(); + if(buf) + { + out->data = buf; + out->size = total; + } + else + jdns_string_set_cstr(out, ""); + return out; +} + +static void _publish_applyrr_unknown(jdns_session_t *s, mdnsdr r, const jdns_rr_t *rr) +{ + // for unsupported/unknown, just take the rdata + // note: for this to work, the app must explicitly set the rdata. + // if the record is MX or some other known but unsupported record + // type, setting the known fields is not enough + mdnsd_set_raw(s->mdns, r, (char *)rr->rdata, rr->rdlength); +} + +static int _publish_applyrr(jdns_session_t *s, mdnsdr r, const jdns_rr_t *rr) +{ + if(!rr->haveKnown) + { + _publish_applyrr_unknown(s, r, rr); + return 1; + } + + // jdns_mdnsd supports: A, AAAA, SRV, CNAME, PTR, TXT, and HINFO + switch(rr->type) + { + case JDNS_RTYPE_A: + { + unsigned long int ip_net = htonl(rr->data.address->addr.v4); + mdnsd_set_raw(s->mdns, r, (char *)&ip_net, 4); + break; + } + case JDNS_RTYPE_AAAA: + { + mdnsd_set_raw(s->mdns, r, (char *)rr->data.address->addr.v6, 16); + break; + } + case JDNS_RTYPE_SRV: + { + mdnsd_set_srv(s->mdns, r, rr->data.server->priority, rr->data.server->weight, rr->data.server->port, (char *)rr->data.server->name); + break; + } + case JDNS_RTYPE_CNAME: + { + mdnsd_set_host(s->mdns, r, (char *)rr->data.name); + break; + } + case JDNS_RTYPE_PTR: + { + mdnsd_set_host(s->mdns, r, (char *)rr->data.name); + break; + } + case JDNS_RTYPE_TXT: + { + jdns_string_t *out = _create_text(rr->data.texts); + mdnsd_set_raw(s->mdns, r, (char *)out->data, out->size); + jdns_string_delete(out); + break; + } + case JDNS_RTYPE_HINFO: + { + jdns_string_t *out; + jdns_stringlist_t *list; + + list = jdns_stringlist_new(); + jdns_stringlist_append(list, rr->data.hinfo.cpu); + jdns_stringlist_append(list, rr->data.hinfo.os); + out = _create_text(list); + jdns_stringlist_delete(list); + + mdnsd_set_raw(s->mdns, r, (char *)out->data, out->size); + jdns_string_delete(out); + break; + } + default: + { + _publish_applyrr_unknown(s, r, rr); + break; + } + } + + return 1; +} + +static void report_published(jdns_session_t *s, published_item_t *pub) +{ + jdns_event_t *event; + jdns_string_t *str; + + str = _make_printable_cstr((char *)pub->qname); + _debug_line(s, "published name %s for type %d", str->data, pub->qtype); + jdns_string_delete(str); + + event = jdns_event_new(); + event->type = JDNS_EVENT_PUBLISH; + event->id = pub->id; + event->status = JDNS_STATUS_SUCCESS; + _append_event(s, event); +} + +int _multicast_publish(jdns_session_t *s, int mode, const jdns_rr_t *rr) +{ + mdnsdr r; + published_item_t *pub; + int next_id; + jdns_event_t *event; + int n; + + r = 0; + next_id = get_next_req_id(s); + + // see if we have an item with this name+type combination already + pub = 0; + for(n = 0; n < s->published->count; ++n) + { + published_item_t *i = (published_item_t *)s->published->item[n]; + if(i->qtype == rr->type && jdns_domain_cmp(i->qname, rr->owner)) + { + pub = i; + break; + } + } + if(pub) + goto error; + + if(!jdns_rr_verify(rr)) + goto error; + + if(mode == JDNS_PUBLISH_UNIQUE) + r = mdnsd_unique(s->mdns, (char *)rr->owner, rr->type, rr->ttl, _multicast_pubresult, s); + else + r = mdnsd_shared(s->mdns, (char *)rr->owner, rr->type, rr->ttl); + + if(!_publish_applyrr(s, r, rr)) + goto error; + + pub = published_item_new(); + pub->id = next_id; + pub->mode = mode; + pub->qname = _ustrdup(rr->owner); + pub->qtype = rr->type; + pub->rec = r; + pub->rr = jdns_rr_copy(rr); + list_insert(s->published, pub, -1); + + // mdnsd doesn't report publish events for shared, so do that here + if(mode == JDNS_PUBLISH_SHARED) + report_published(s, pub); + + return pub->id; + +error: + _debug_line(s, "attempt to publish record, malformed, unsupported, or duplicate type"); + + if(r) + { + // don't publish + mdnsd_done(s->mdns, r); + } + + // send an error to the app + event = jdns_event_new(); + event->type = JDNS_EVENT_PUBLISH; + event->id = next_id; + event->status = JDNS_STATUS_ERROR; + _append_event_and_hold_id(s, event); + + return next_id; +} + +void _multicast_update_publish(jdns_session_t *s, int id, const jdns_rr_t *rr) +{ + mdnsdr r; + published_item_t *pub; + int qtype; + int n; + + pub = 0; + for(n = 0; n < s->published->count; ++n) + { + published_item_t *i = (published_item_t *)s->published->item[n]; + if(i->id == id) + { + pub = i; + break; + } + } + if(!pub) + return; + + qtype = pub->qtype; + r = pub->rec; + + // expire existing record. this is mostly needed for shared records + // since unique records already have the cache flush bit and that + // should achieve the same result. however, since Apple expires + // unique records before updates, so will we. + mdnsd_done(s->mdns, r); + if(pub->mode == JDNS_PUBLISH_UNIQUE) + r = mdnsd_unique(s->mdns, (char *)pub->rr->owner, pub->rr->type, rr->ttl, _multicast_pubresult, s); + else + r = mdnsd_shared(s->mdns, (char *)pub->rr->owner, pub->rr->type, rr->ttl); + pub->rec = r; + + if(!_publish_applyrr(s, r, rr)) + { + _debug_line(s, "attempt to update_publish an unsupported type"); + return; + } +} + +void _multicast_cancel_publish(jdns_session_t *s, int id) +{ + int n; + for(n = 0; n < s->published->count; ++n) + { + published_item_t *i = (published_item_t *)s->published->item[n]; + if(i->id == id) + { + mdnsd_done(s->mdns, i->rec); + list_remove(s->published, i); + break; + } + } +} + +void _multicast_flush(jdns_session_t *s) +{ + int n; + + // to flush, we make like our queries and published items are all new. + // we'll do this by destroying/creating the mdnsd object again (so it + // is fresh) and then reapply all queries and published items to it. + + // start over with mdnsd + mdnsd_free(s->mdns); + s->mdns = mdnsd_new(0x0001, 1000, s->port, _callback_time_now, _callback_rand_int, s); + + // attempt to publish again + for(n = 0; n < s->published->count; ++n) + { + published_item_t *i; + mdnsdr r; + + i = (published_item_t *)s->published->item[n]; + if(i->mode == JDNS_PUBLISH_UNIQUE) + r = mdnsd_unique(s->mdns, (char *)i->rr->owner, i->rr->type, i->rr->ttl, _multicast_pubresult, s); + else + r = mdnsd_shared(s->mdns, (char *)i->rr->owner, i->rr->type, i->rr->ttl); + _publish_applyrr(s, r, i->rr); + i->rec = r; + } + + // restore the queries + for(n = 0; n < s->queries->count; ++n) + { + query_t *q = (query_t *)s->queries->item[n]; + + // issue the query + mdnsd_query(s->mdns, (char *)q->qname, q->qtype, _multicast_query_ans, s); + } +} + +int jdns_step_multicast(jdns_session_t *s, int now) +{ + int need_read, need_write, smallest_time; + struct mytimeval *tv; + jdns_packet_t *packet; + int flags; + + // not used + (void)now; + + need_read = 0; + need_write = 0; + + if(s->shutdown == 1) + mdnsd_shutdown(s->mdns); + + while(1) + { + jdns_address_t *addr; + unsigned short int port; + int ret; + unsigned char *buf; + int buf_len; + + if(!mdnsd_out(s->mdns, &packet, &addr, &port)) + break; + + if(!s->handle_writable) + { + need_write = 1; + jdns_address_delete(addr); + break; + } + + if(!jdns_packet_export(packet, JDNS_UDP_MUL_OUT_MAX)) + { + _debug_line(s, "outgoing packet export error, not sending"); + jdns_packet_delete(packet); + continue; + } + + buf = packet->raw_data; + buf_len = packet->raw_size; + + // multicast + if(!addr) + { + addr = jdns_address_copy(s->maddr); + port = s->port; + } + + _debug_line(s, "SEND %s:%d (size=%d)", addr->c_str, port, buf_len); + _print_hexdump(s, buf, buf_len); + + ret = s->cb.udp_write(s, s->cb.app, s->handle, addr, port, buf, buf_len); + + jdns_address_delete(addr); + jdns_packet_delete(packet); + + // if we can't write the packet, oh well + if(ret == 0) + { + s->handle_writable = 0; + need_write = 1; + break; + } + } + + if(s->shutdown == 1) + { + jdns_event_t *event = jdns_event_new(); + event->type = JDNS_EVENT_SHUTDOWN; + _append_event(s, event); + s->shutdown = 2; + return 0; + } + + // let's always ask for reads, just so the user doesn't have to + // worry about what should happen to incoming packets otherwise + need_read = 1; + + if(s->handle_readable) + { + while(1) + { + unsigned char buf[JDNS_UDP_MUL_IN_MAX]; + int bufsize = JDNS_UDP_MUL_IN_MAX; + int ret; + jdns_address_t *addr; + int port; + jdns_response_t *r; + + addr = jdns_address_new(); + ret = s->cb.udp_read(s, s->cb.app, s->handle, addr, &port, buf, &bufsize); + + // no packet? + if(ret == 0) + { + s->handle_readable = 0; + jdns_address_delete(addr); + break; + } + + _debug_line(s, "RECV %s:%d (size=%d)", addr->c_str, port, bufsize); + _print_hexdump(s, buf, bufsize); + + if(!jdns_packet_import(&packet, buf, bufsize)) + { + _debug_line(s, "error parsing packet / too large"); + + jdns_address_delete(addr); + continue; + } + + _print_packet(s, packet); + + r = _packet2response(packet, 0, 0, 0x7fff); + _print_records(s, r, 0); + + mdnsd_in(s->mdns, packet, r, addr, (unsigned short)port); + + jdns_address_delete(addr); + jdns_packet_delete(packet); + jdns_response_delete(r); + } + } + + tv = mdnsd_sleep(s->mdns); + smallest_time = tv->tv_sec * 1000 + tv->tv_usec / 1000; + + flags = 0; + if(smallest_time != -1) + { + flags |= JDNS_STEP_TIMER; + s->next_timer = smallest_time; + + // offset it a little bit, so that the user doesn't call + // us too early, resulting in a no-op and another timer + // of 1 millisecond. + s->next_timer += 2; + } + if(need_read || need_write) + flags |= JDNS_STEP_HANDLE; + return flags; +} diff --git a/thirdparty/jdns/jdns.h b/thirdparty/jdns/jdns.h new file mode 100644 index 000000000..d849d8d92 --- /dev/null +++ b/thirdparty/jdns/jdns.h @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2005,2006 Justin Karneges + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef JDNS_H +#define JDNS_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*jdns_object_dtor_func)(void *); +typedef void *(*jdns_object_cctor_func)(const void *); + +#define JDNS_OBJECT \ + jdns_object_dtor_func dtor; \ + jdns_object_cctor_func cctor; + +#define JDNS_OBJECT_NEW(name) \ + (name##_t *)jdns_object_new(sizeof(name##_t), \ + (jdns_object_dtor_func)name##_delete, \ + (jdns_object_cctor_func)name##_copy); + +typedef struct jdns_object +{ + JDNS_OBJECT +} jdns_object_t; + +void *jdns_object_new(int size, void (*dtor)(void *), + void *(*cctor)(const void *)); +void *jdns_object_copy(const void *a); +void jdns_object_delete(void *a); +void jdns_object_free(void *a); + +#define JDNS_LIST_DECLARE(name) \ + JDNS_OBJECT \ + int count; \ + name##_t **item; + +typedef struct jdns_list +{ + JDNS_OBJECT + int count; + void **item; + int valueList; + int autoDelete; +} jdns_list_t; + +jdns_list_t *jdns_list_new(); +jdns_list_t *jdns_list_copy(const jdns_list_t *a); +void jdns_list_delete(jdns_list_t *a); +void jdns_list_clear(jdns_list_t *a); +void jdns_list_insert(jdns_list_t *a, void *item, int pos); +void jdns_list_insert_value(jdns_list_t *a, const void *item, int pos); +void jdns_list_remove(jdns_list_t *a, void *item); +void jdns_list_remove_at(jdns_list_t *a, int pos); + +typedef struct jdns_string +{ + JDNS_OBJECT + unsigned char *data; + int size; +} jdns_string_t; + +jdns_string_t *jdns_string_new(); +jdns_string_t *jdns_string_copy(const jdns_string_t *s); +void jdns_string_delete(jdns_string_t *s); +void jdns_string_set(jdns_string_t *s, const unsigned char *str, + int str_len); +void jdns_string_set_cstr(jdns_string_t *s, const char *str); + + // overlays jdns_list +typedef struct jdns_stringlist +{ + JDNS_OBJECT + int count; + jdns_string_t **item; +} jdns_stringlist_t; + +jdns_stringlist_t *jdns_stringlist_new(); +jdns_stringlist_t *jdns_stringlist_copy(const jdns_stringlist_t *a); +void jdns_stringlist_delete(jdns_stringlist_t *a); +void jdns_stringlist_append(jdns_stringlist_t *a, const jdns_string_t *str); + +typedef struct jdns_address +{ + int isIpv6; + union + { + unsigned long int v4; + unsigned char *v6; // 16 bytes + } addr; + char *c_str; +} jdns_address_t; + +jdns_address_t *jdns_address_new(); +jdns_address_t *jdns_address_copy(const jdns_address_t *a); +void jdns_address_delete(jdns_address_t *a); +void jdns_address_set_ipv4(jdns_address_t *a, unsigned long int ipv4); +void jdns_address_set_ipv6(jdns_address_t *a, const unsigned char *ipv6); +// return 1 if string was ok, else 0. Note: IPv4 addresses only! +int jdns_address_set_cstr(jdns_address_t *a, const char *str); +// return 1 if the same, else 0 +int jdns_address_cmp(const jdns_address_t *a, const jdns_address_t *b); + +// convenient predefined addresses/ports +#define JDNS_UNICAST_PORT 53 +#define JDNS_MULTICAST_PORT 5353 +jdns_address_t *jdns_address_multicast4_new(); // 224.0.0.251 +jdns_address_t *jdns_address_multicast6_new(); // FF02::FB + +typedef struct jdns_server +{ + unsigned char *name; + int port; // SRV only + int priority; + int weight; // SRV only +} jdns_server_t; + +jdns_server_t *jdns_server_new(); +jdns_server_t *jdns_server_copy(const jdns_server_t *s); +void jdns_server_delete(jdns_server_t *s); +void jdns_server_set_name(jdns_server_t *s, const unsigned char *name); + +typedef struct jdns_nameserver +{ + jdns_address_t *address; + int port; +} jdns_nameserver_t; + +jdns_nameserver_t *jdns_nameserver_new(); +jdns_nameserver_t *jdns_nameserver_copy(const jdns_nameserver_t *a); +void jdns_nameserver_delete(jdns_nameserver_t *a); +void jdns_nameserver_set(jdns_nameserver_t *a, const jdns_address_t *addr, + int port); + +typedef struct jdns_nameserverlist +{ + int count; + jdns_nameserver_t **item; +} jdns_nameserverlist_t; + +jdns_nameserverlist_t *jdns_nameserverlist_new(); +jdns_nameserverlist_t *jdns_nameserverlist_copy( + const jdns_nameserverlist_t *a); +void jdns_nameserverlist_delete(jdns_nameserverlist_t *a); +void jdns_nameserverlist_append(jdns_nameserverlist_t *a, + const jdns_address_t *addr, int port); + +typedef struct jdns_dnshost +{ + jdns_string_t *name; + jdns_address_t *address; +} jdns_dnshost_t; + +typedef struct jdns_dnshostlist +{ + int count; + jdns_dnshost_t **item; +} jdns_dnshostlist_t; + +typedef struct jdns_dnsparams +{ + jdns_nameserverlist_t *nameservers; + jdns_stringlist_t *domains; + jdns_dnshostlist_t *hosts; +} jdns_dnsparams_t; + +jdns_dnsparams_t *jdns_dnsparams_new(); +jdns_dnsparams_t *jdns_dnsparams_copy(jdns_dnsparams_t *a); +void jdns_dnsparams_delete(jdns_dnsparams_t *a); +void jdns_dnsparams_append_nameserver(jdns_dnsparams_t *a, + const jdns_address_t *addr, int port); +void jdns_dnsparams_append_domain(jdns_dnsparams_t *a, + const jdns_string_t *domain); +void jdns_dnsparams_append_host(jdns_dnsparams_t *a, + const jdns_string_t *name, const jdns_address_t *address); + +#define JDNS_RTYPE_A 1 +#define JDNS_RTYPE_AAAA 28 +#define JDNS_RTYPE_MX 15 +#define JDNS_RTYPE_SRV 33 +#define JDNS_RTYPE_CNAME 5 +#define JDNS_RTYPE_PTR 12 +#define JDNS_RTYPE_TXT 16 +#define JDNS_RTYPE_HINFO 13 +#define JDNS_RTYPE_NS 2 +#define JDNS_RTYPE_ANY 255 + +typedef struct jdns_rr +{ + unsigned char *owner; + int ttl; + int type; + int qclass; + int rdlength; + unsigned char *rdata; + int haveKnown; + + union + { + jdns_address_t *address; // for A, AAAA + jdns_server_t *server; // for MX, SRV + unsigned char *name; // for CNAME, PTR, NS + jdns_stringlist_t *texts; // for TXT + struct + { + jdns_string_t *cpu; + jdns_string_t *os; + } hinfo; // for HINFO + } data; +} jdns_rr_t; + +jdns_rr_t *jdns_rr_new(); +jdns_rr_t *jdns_rr_copy(const jdns_rr_t *r); +void jdns_rr_delete(jdns_rr_t *r); +void jdns_rr_set_owner(jdns_rr_t *r, const unsigned char *name); +void jdns_rr_set_record(jdns_rr_t *r, int type, const unsigned char *rdata, + int rdlength); +void jdns_rr_set_A(jdns_rr_t *r, const jdns_address_t *address); +void jdns_rr_set_AAAA(jdns_rr_t *r, const jdns_address_t *address); +void jdns_rr_set_MX(jdns_rr_t *r, const unsigned char *name, int priority); +void jdns_rr_set_SRV(jdns_rr_t *r, const unsigned char *name, int port, + int priority, int weight); +void jdns_rr_set_CNAME(jdns_rr_t *r, const unsigned char *name); +void jdns_rr_set_PTR(jdns_rr_t *r, const unsigned char *name); +void jdns_rr_set_TXT(jdns_rr_t *r, const jdns_stringlist_t *texts); +void jdns_rr_set_HINFO(jdns_rr_t *r, const jdns_string_t *cpu, + const jdns_string_t *os); +void jdns_rr_set_NS(jdns_rr_t *r, const unsigned char *name); +// note: only works on known types +int jdns_rr_verify(const jdns_rr_t *r); + +typedef struct jdns_response +{ + int answerCount; + jdns_rr_t **answerRecords; + int authorityCount; + jdns_rr_t **authorityRecords; + int additionalCount; + jdns_rr_t **additionalRecords; +} jdns_response_t; + +jdns_response_t *jdns_response_new(); +jdns_response_t *jdns_response_copy(const jdns_response_t *r); +void jdns_response_delete(jdns_response_t *r); +void jdns_response_append_answer(jdns_response_t *r, const jdns_rr_t *rr); +void jdns_response_append_authority(jdns_response_t *r, const jdns_rr_t *rr); +void jdns_response_append_additional(jdns_response_t *r, + const jdns_rr_t *rr); + +#define JDNS_PUBLISH_SHARED 0x0001 +#define JDNS_PUBLISH_UNIQUE 0x0002 + +#define JDNS_STEP_TIMER 0x0001 +#define JDNS_STEP_HANDLE 0x0002 + +#define JDNS_EVENT_RESPONSE 0x0001 +#define JDNS_EVENT_PUBLISH 0x0002 +#define JDNS_EVENT_SHUTDOWN 0x0003 + +#define JDNS_STATUS_SUCCESS 0x0001 +#define JDNS_STATUS_NXDOMAIN 0x0002 +#define JDNS_STATUS_ERROR 0x0003 +#define JDNS_STATUS_TIMEOUT 0x0004 +#define JDNS_STATUS_CONFLICT 0x0005 + +typedef struct jdns_session jdns_session_t; + +typedef struct jdns_callbacks +{ + void *app; // user-supplied context + + // time_now: + // s: session + // app: user-supplied context + // return: milliseconds since session started + int (*time_now)(jdns_session_t *s, void *app); + + // rand_int: + // s: session + // app: user-supplied context + // return: random integer between 0-65535 + int (*rand_int)(jdns_session_t *s, void *app); + + // debug_line: + // s: session + // app: user-supplied context + // str: a line of debug text + // return: nothing + void (*debug_line)(jdns_session_t *s, void *app, const char *str); + + // udp_bind: + // s: session + // app: user-supplied context + // addr: ip address of interface to bind to. 0 for all + // port: port of interface to bind to. 0 for any + // maddr: multicast address. 0 if not using multicast + // return: handle (>0) of bound socket, or 0 on error + // note: for multicast, the following must be done: + // use SO_REUSEPORT to share with other mdns programs + // use IP_ADD_MEMBERSHIP to associate addr and maddr + // set IP_MULTICAST_TTL to 255 + int (*udp_bind)(jdns_session_t *s, void *app, + const jdns_address_t *addr, int port, + const jdns_address_t *maddr); + + // udp_unbind: + // s: session + // app: user-supplied context + // handle: handle of socket obtained with udp_bind + // return: nothing + void (*udp_unbind)(jdns_session_t *s, void *app, int handle); + + // udp_read: + // s: session + // app: user-supplied context + // handle: handle of socket obtained with udp_bind + // addr: store ip address of sender + // port: store port of sender + // buf: store packet content + // bufsize: value contains max size, to be changed to real size + // return: 1 if packet read, 0 if none available + int (*udp_read)(jdns_session_t *s, void *app, int handle, + jdns_address_t *addr, int *port, unsigned char *buf, + int *bufsize); + + // udp_write: + // s: session + // app: user-supplied context + // handle: handle of socket obtained with udp_bind + // addr: ip address of recipient + // port: port of recipient + // buf: packet content + // bufsize: size of packet + // return: 1 if packet taken for writing, 0 if this is a bad time + int (*udp_write)(jdns_session_t *s, void *app, int handle, + const jdns_address_t *addr, int port, unsigned char *buf, + int bufsize); +} jdns_callbacks_t; + +typedef struct jdns_event +{ + int type; // JDNS_EVENT + int id; // query id or publish id + + // for query, this can be SUCCESS, NXDOMAIN, ERROR, or TIMEOUT + // for publish, this can be SUCCESS, ERROR, or CONFLICT + int status; + + // for query + jdns_response_t *response; +} jdns_event_t; + +void jdns_event_delete(jdns_event_t *e); + +// jdns_session_new: +// callbacks: the struct of callbacks +// return: newly allocated session +jdns_session_t *jdns_session_new(jdns_callbacks_t *callbacks); + +// jdns_session_delete: +// s: session to free +// return: nothing +void jdns_session_delete(jdns_session_t *s); + +// jdns_init_unicast: +// s: session +// addr: ip address of interface to bind to. NULL for all +// port: port of interface to bind to. 0 for any +// return: 1 on success, 0 on failure +int jdns_init_unicast(jdns_session_t *s, const jdns_address_t *addr, + int port); + +// jdns_init_multicast: +// s: session +// addr: ip address of interface to bind to. NULL for all +// port: port of interface to bind to. 0 for any +// addr: multicast address to associate with. cannot be NULL +// return: 1 on success, 0 on failure +int jdns_init_multicast(jdns_session_t *s, const jdns_address_t *addr, + int port, const jdns_address_t *maddr); + +// jdns_shutdown: +// s: session +// return: nothing +void jdns_shutdown(jdns_session_t *s); + +// jdns_set_nameservers: +// s: session +// nslist: list of nameservers +// return nothing +void jdns_set_nameservers(jdns_session_t *s, + const jdns_nameserverlist_t *nslist); + +// jdns_probe: +// s: session +// return: nothing +void jdns_probe(jdns_session_t *s); + +// jdns_query: +// s: session +// name: the name to look up +// rtype: the record type +// return: id of this operation +int jdns_query(jdns_session_t *s, const unsigned char *name, int rtype); + +// jdns_cancel_query: +// s: session +// id: the operation id to cancel +// return: nothing +void jdns_cancel_query(jdns_session_t *s, int id); + +// jdns_publish: +// s: session +// mode: JDNS_PUBLISH shared or unique +// rec: the record data +// return: id of this operation +// note: supported record types: A, AAAA, SRV, CNAME, PTR, TXT, and HINFO. +// if the published type is not one of these, raw rdata must be set. +int jdns_publish(jdns_session_t *s, int mode, const jdns_rr_t *rec); + +// jdns_update_publish: +// s: session +// id: the operation id to update +// rec: the record data +// return: nothing +// note: update only works on successfully published records, and no event +// is generated for a successful update. +void jdns_update_publish(jdns_session_t *s, int id, const jdns_rr_t *rec); + +// jdns_cancel_publish: +// s: session +// id: the operation id to cancel +// return: nothing +void jdns_cancel_publish(jdns_session_t *s, int id); + +// jdns_step: +// s: session +// return: JDNS_STEP flags OR'd together +int jdns_step(jdns_session_t *s); + +// jdns_next_timer: +// s: session +// return: milliseconds until timeout +int jdns_next_timer(jdns_session_t *s); + +// jdns_set_handle_readable: +// s: session +// handle: handle that is now readable +// return: nothing +void jdns_set_handle_readable(jdns_session_t *s, int handle); + +// jdns_set_handle_writable: +// s: session +// handle: handle that is now writable +// return: nothing +void jdns_set_handle_writable(jdns_session_t *s, int handle); + +// jdns_next_event: +// s: session +// return: newly allocated event, or zero if none are ready +jdns_event_t *jdns_next_event(jdns_session_t *s); + +// jdns_system_dnsparams: +// return: newly allocated dnsparams from the system +jdns_dnsparams_t *jdns_system_dnsparams(); + +// jdns_set_hold_ids_enabled +// s: session +// enabled: whether to enable id holding. default is 0 (disabled) +// return: nothing +// normally, when a unicast query completes or any kind of query or publish +// operation results in an error, the operation is automatically "canceled". +// when id holding is enabled, the operation still stops internally, but the +// id value used by that operation is "held" until the application +// explicitly calls jdns_cancel_query() or jdns_cancel_publish() to release +// it. this allows the application to ensure there is no ambiguity when +// determining which operation a particular event belongs to. it is disabled +// be default so as to not introduce memory leaks in existing applications, +// however new applications really should use it. +void jdns_set_hold_ids_enabled(jdns_session_t *s, int enabled); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thirdparty/jdns/jdns.pri b/thirdparty/jdns/jdns.pri new file mode 100644 index 000000000..fb2e3dced --- /dev/null +++ b/thirdparty/jdns/jdns.pri @@ -0,0 +1,27 @@ +# qmake project include file + +QT *= network + +windows:{ + LIBS += -lWs2_32 -lAdvapi32 +} +unix:{ + #QMAKE_CFLAGS += -pedantic +} + +HEADERS += \ + $$PWD/jdns_packet.h \ + $$PWD/jdns_mdnsd.h \ + $$PWD/jdns_p.h \ + $$PWD/jdns.h \ + $$PWD/qjdns_sock.h \ + $$PWD/qjdns.h + +SOURCES += \ + $$PWD/jdns_util.c \ + $$PWD/jdns_packet.c \ + $$PWD/jdns_mdnsd.c \ + $$PWD/jdns_sys.c \ + $$PWD/jdns.c \ + $$PWD/qjdns_sock.cpp \ + $$PWD/qjdns.cpp diff --git a/thirdparty/jdns/jdns.pro b/thirdparty/jdns/jdns.pro new file mode 100644 index 000000000..610dab875 --- /dev/null +++ b/thirdparty/jdns/jdns.pro @@ -0,0 +1,9 @@ +CONFIG += console +CONFIG -= app_bundle +QT -= gui +QT += network + +include(jdns.pri) + +SOURCES += \ + main.cpp diff --git a/thirdparty/jdns/jdns_mdnsd.c b/thirdparty/jdns/jdns_mdnsd.c new file mode 100644 index 000000000..df6326aa3 --- /dev/null +++ b/thirdparty/jdns/jdns_mdnsd.c @@ -0,0 +1,1125 @@ +/* + * Copyright (C) 2005 Jeremie Miller + * Copyright (C) 2005,2006 Justin Karneges + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "jdns_mdnsd.h" + +#include +#include + +#define QTYPE_A JDNS_RTYPE_A +#define QTYPE_AAAA JDNS_RTYPE_AAAA +#define QTYPE_MX JDNS_RTYPE_MX +#define QTYPE_SRV JDNS_RTYPE_SRV +#define QTYPE_CNAME JDNS_RTYPE_CNAME +#define QTYPE_PTR JDNS_RTYPE_PTR +#define QTYPE_TXT JDNS_RTYPE_TXT +#define QTYPE_HINFO JDNS_RTYPE_HINFO +#define QTYPE_NS JDNS_RTYPE_NS +#define QTYPE_ANY JDNS_RTYPE_ANY + +// size of query/publish hashes +#define SPRIME 108 +// size of cache hash +#define LPRIME 1009 +// brute force garbage cleanup frequency, rarely needed (daily default) +#define GC 86400 + +// maximum number of items to cache. if an attacker on the LAN advertises +// a million services, we don't want to crash our program trying to collect +// them all. any dns records received beyond this max are ignored. this +// means the attacker would succeed in DoS'ing the multicast dns network, +// but he wouldn't succeed in running our program out of memory. +#define MAX_CACHE 16384 + +#define bzero(p, size) memset(p, 0, size) + +/* messy, but it's the best/simplest balance I can find at the moment +Some internal data types, and a few hashes: querys, answers, cached, and records (published, unique and shared) +Each type has different semantics for processing, both for timeouts, incoming, and outgoing I/O +They inter-relate too, like records affect the querys they are relevant to +Nice things about MDNS: we only publish once (and then ask asked), and only query once, then just expire records we've got cached +*/ + +struct query +{ + char *name; + int type; + unsigned long int nexttry; + int tries; + int (*answer)(mdnsda, void *); + void *arg; + struct query *next, *list; +}; + +struct unicast +{ + int id; + char ipv6; + unsigned long int to; + unsigned char to6[16]; + unsigned short int port; + mdnsdr r; + struct unicast *next; +}; + +struct cached +{ + struct mdnsda_struct rr; + struct query *q; + struct cached *next; +}; + +struct mdnsdr_struct +{ + struct mdnsda_struct rr; + char unique; // # of checks performed to ensure + int tries; + void (*pubresult)(int, char *, int, void *); + void *arg; + struct mdnsdr_struct *next, *list; +}; + +struct mdnsd_struct +{ + char shutdown; + unsigned long int expireall, checkqlist; + struct mytimeval now, sleep, pause, probe, publish; + int class, frame; + struct cached *cache[LPRIME]; + int cache_count; + struct mdnsdr_struct *published[SPRIME], *probing, *a_now, *a_pause, *a_publish; + struct unicast *uanswers; + struct query *queries[SPRIME], *qlist; + int (*cb_time_now)(struct mdnsd_struct *dp, void *arg); + int (*cb_rand_int)(struct mdnsd_struct *dp, void *arg); + void *cb_arg; + int port; +}; + +void mygettimeofday(mdnsd d, struct mytimeval *tv) +{ + //struct timeval t; + //gettimeofday(&t, 0); + //tv->tv_sec = t.tv_sec; + //tv->tv_usec = t.tv_usec; + + int msec = d->cb_time_now(d, d->cb_arg); + tv->tv_sec = msec / 1000; + tv->tv_usec = (msec % 1000) * 1000; +} + +void query_free(struct query *q) +{ + jdns_free(q->name); + jdns_free(q); +} + +void mdnsda_content_free(struct mdnsda_struct *rr) +{ + if(rr->name) + jdns_free(rr->name); + if(rr->rdata) + jdns_free(rr->rdata); + if(rr->rdname) + jdns_free(rr->rdname); +} + +int _namehash(const char *s) +{ + const unsigned char *name = (const unsigned char *)s; + unsigned long h = 0, g; + + while (*name) + { /* do some fancy bitwanking on the string */ + h = (h << 4) + (unsigned long)(*name++); + if ((g = (h & 0xF0000000UL))!=0) + h ^= (g >> 24); + h &= ~g; + } + + return (int)h; +} + +// case-insensitive hash +int _namehash_nocase(const char *s) +{ + int n, len; + char *low = jdns_strdup(s); + len = strlen(low); + for(n = 0; n < len; ++n) + low[n] = tolower(low[n]); + n = _namehash(low); + jdns_free(low); + return n; +} + +// basic linked list and hash primitives +struct query *_q_next(mdnsd d, struct query *q, char *host, int type) +{ + if(q == 0) q = d->queries[_namehash_nocase(host) % SPRIME]; + else q = q->next; + for(;q != 0; q = q->next) + if(q->type == type && jdns_domain_cmp((unsigned char *)q->name, (unsigned char *)host)) + return q; + return 0; +} +struct cached *_c_next(mdnsd d, struct cached *c, char *host, int type) +{ + if(c == 0) c = d->cache[_namehash_nocase(host) % LPRIME]; + else c = c->next; + for(;c != 0; c = c->next) + if((type == c->rr.type || type == 255) && jdns_domain_cmp(c->rr.name, (unsigned char *)host)) + return c; + return 0; +} +mdnsdr _r_next(mdnsd d, mdnsdr r, char *host, int type) +{ + if(r == 0) r = d->published[_namehash_nocase(host) % SPRIME]; + else r = r->next; + for(;r != 0; r = r->next) + if(type == r->rr.type && jdns_domain_cmp(r->rr.name, (unsigned char *)host)) + return r; + return 0; +} + +/* +int _rr_len(mdnsda rr) +{ + int len = 12; // name is always compressed (dup of earlier), plus normal stuff + if(rr->rdata) len += rr->rdlen; + if(rr->rdname) len += strlen((char *)rr->rdname); // worst case + if(rr->ip) len += 4; + if(rr->type == QTYPE_PTR) len += 6; // srv record stuff + return len; +} +*/ + +/* +int _a_match(struct resource *r, mdnsda a) +{ // compares new rdata with known a, painfully + if(strcmp((char *)r->name,(char *)a->name) || r->type != a->type) return 0; + if(r->type == QTYPE_SRV && !strcmp((char *)r->known.srv.name,(char *)a->rdname) && a->srv.port == r->known.srv.port && a->srv.weight == r->known.srv.weight && a->srv.priority == r->known.srv.priority) return 1; + if((r->type == QTYPE_PTR || r->type == QTYPE_NS || r->type == QTYPE_CNAME) && !strcmp((char *)a->rdname,(char *)r->known.ns.name)) return 1; + if(r->rdlength == a->rdlen && !memcmp(r->rdata,a->rdata,r->rdlength)) return 1; + return 0; +} +*/ + +int _a_match(const jdns_rr_t *r, mdnsda a) +{ + if(r->type != a->type || !jdns_domain_cmp(r->owner, a->name)) + return 0; + if(r->type == JDNS_RTYPE_SRV) + { + if(jdns_domain_cmp(r->data.server->name, a->rdname) + && r->data.server->port == a->srv.port + && r->data.server->priority == a->srv.priority + && r->data.server->weight == a->srv.weight + ) + return 1; + } + else if(r->type == JDNS_RTYPE_PTR || r->type == JDNS_RTYPE_NS || r->type == JDNS_RTYPE_CNAME) + { + if(jdns_domain_cmp(r->data.name, a->rdname)) + return 1; + } + else if(r->rdlength == a->rdlen && !memcmp(r->rdata, a->rdata, r->rdlength)) + return 1; + + return 0; +} + +// compare time values easily +int _tvdiff(struct mytimeval old, struct mytimeval new) +{ + int udiff = 0; + if(old.tv_sec != new.tv_sec) udiff = (new.tv_sec - old.tv_sec) * 1000000; + return (new.tv_usec - old.tv_usec) + udiff; +} + +// make sure not already on the list, then insert +void _r_push(mdnsdr *list, mdnsdr r) +{ + mdnsdr cur; + for(cur = *list; cur != 0; cur = cur->list) + if(cur == r) return; + r->list = *list; + *list = r; +} + +// set this r to probing, set next probe time +void _r_probe(mdnsd d, mdnsdr r) +{ + (void)d; + (void)r; +} + +// force any r out right away, if valid +void _r_publish(mdnsd d, mdnsdr r) +{ + if(r->unique && r->unique < 5) return; // probing already + r->tries = 0; + d->publish.tv_sec = d->now.tv_sec; d->publish.tv_usec = d->now.tv_usec; + _r_push(&d->a_publish,r); +} + +// send r out asap +void _r_send(mdnsd d, mdnsdr r) +{ + // removing record + if(r->rr.ttl == 0) + { + if(d->a_publish == r) + d->a_publish = r->list; + _r_push(&d->a_now, r); + return; + } + + if(r->tries < 4) + { // being published, make sure that happens soon + d->publish.tv_sec = d->now.tv_sec; d->publish.tv_usec = d->now.tv_usec; + return; + } + if(r->unique) + { // known unique ones can be sent asap + _r_push(&d->a_now,r); + return; + } + // set d->pause.tv_usec to random 20-120 msec + d->pause.tv_sec = d->now.tv_sec; + //d->pause.tv_usec = d->now.tv_usec + ((d->now.tv_usec % 100) + 20) * 1000; + d->pause.tv_usec = d->now.tv_usec; + d->pause.tv_usec += ((d->cb_rand_int(d, d->cb_arg) % 100) + 20) * 1000; + _r_push(&d->a_pause,r); +} + +// create generic unicast response struct +void _u_push(mdnsd d, mdnsdr r, int id, const jdns_address_t *addr, unsigned short int port) +{ + struct unicast *u; + u = (struct unicast *)jdns_alloc(sizeof(struct unicast)); + bzero(u,sizeof(struct unicast)); + u->r = r; + u->id = id; + if(addr->isIpv6) + { + u->ipv6 = 1; + memcpy(u->to6, addr->addr.v6, 16); + } + else + { + u->ipv6 = 0; + u->to = addr->addr.v4; + } + u->port = port; + u->next = d->uanswers; + d->uanswers = u; +} + +void _q_reset(mdnsd d, struct query *q) +{ + struct cached *cur = 0; + q->nexttry = 0; + q->tries = 0; + while((cur = _c_next(d,cur,q->name,q->type))) + if(q->nexttry == 0 || cur->rr.ttl - 7 < q->nexttry) q->nexttry = cur->rr.ttl - 7; + if(q->nexttry != 0 && q->nexttry < d->checkqlist) d->checkqlist = q->nexttry; +} + +void _q_done(mdnsd d, struct query *q) +{ // no more query, update all it's cached entries, remove from lists + struct cached *c = 0; + struct query *cur; + int i = _namehash_nocase(q->name) % SPRIME; + while((c = _c_next(d,c,q->name,q->type))) c->q = 0; + if(d->qlist == q) d->qlist = q->list; + else { + for(cur=d->qlist;cur->list != q;cur = cur->list); + cur->list = q->list; + } + if(d->queries[i] == q) d->queries[i] = q->next; + else { + for(cur=d->queries[i];cur->next != q;cur = cur->next); + cur->next = q->next; + } + query_free(q); +} + +void _r_done(mdnsd d, mdnsdr r) +{ // buh-bye, remove from hash and free + mdnsdr cur = 0; + int i = _namehash_nocase((char *)r->rr.name) % SPRIME; + if(d->a_now == r) + d->a_now = r->list; + if(d->a_pause == r) + d->a_pause = r->list; + if(d->a_publish == r) + d->a_publish = r->list; + if(d->published[i] == r) d->published[i] = r->next; + else { + for(cur=d->published[i];cur && cur->next != r;cur = cur->next); + if(cur) cur->next = r->next; + } + mdnsda_content_free(&r->rr); + jdns_free(r); +} + +void _q_answer(mdnsd d, struct cached *c) +{ // call the answer function with this cached entry + if(c->rr.ttl <= d->now.tv_sec) c->rr.ttl = 0; + if(c->q->answer(&c->rr,c->q->arg) == -1) _q_done(d, c->q); +} + +void _conflict(mdnsd d, mdnsdr r) +{ + r->pubresult(0, (char *)r->rr.name,r->rr.type,r->arg); + mdnsd_done(d,r); +} + +void _published(mdnsd d, mdnsdr r) +{ + (void)d; + r->pubresult(1, (char *)r->rr.name,r->rr.type,r->arg); +} + +void _c_expire(mdnsd d, struct cached **list) +{ // expire any old entries in this list + struct cached *next, *cur = *list, *last = 0; + while(cur != 0) + { + next = cur->next; + if(d->now.tv_sec >= cur->rr.ttl) + { + if(last) last->next = next; + if(*list == cur) *list = next; // update list pointer if the first one expired + --(d->cache_count); + if(cur->q) _q_answer(d,cur); + mdnsda_content_free(&cur->rr); + jdns_free(cur); + }else{ + last = cur; + } + cur = next; + } +} + +// brute force expire any old cached records +void _gc(mdnsd d) +{ + int i; + for(i=0;icache[i]) _c_expire(d,&d->cache[i]); + d->expireall = d->now.tv_sec + GC; +} + +struct cached *_find_exact(mdnsd d, const jdns_rr_t *r) +{ + struct cached *c = 0; + while(1) + { + c = _c_next(d, c, (char *)r->owner, r->type); + if(!c) + break; + if(_a_match(r, &c->rr)) + return c; + } + return 0; +} + +void _cache(mdnsd d, const jdns_rr_t *r) +{ + struct cached *c; + int i = _namehash_nocase((char *)r->owner) % LPRIME; + struct cached *same_value; + + // do we already have it? + //printf("cache: checking for entry: [%s] [%d]\n", r->owner, r->type); + same_value = _find_exact(d, r); + if(same_value) + { + //printf("already have entry of same value\n"); + } + + if(r->qclass == 32768 + d->class) + { // cache flush + // simulate removal of all records for this question, + // except if the value hasn't changed + c = 0; + while((c = _c_next(d,c,(char *)r->owner,r->type))) + { + if(c != same_value) + c->rr.ttl = 0; + } + _c_expire(d,&d->cache[i]); + + // we may have expired same_value here, so check for it again + same_value = _find_exact(d, r); + } + + if(r->ttl == 0) + { // process deletes + if(same_value) + same_value->rr.ttl = 0; + _c_expire(d,&d->cache[i]); + return; + } + + if(same_value) + { + //printf("updating ttl only\n"); + + // only update ttl (this code directly copied from below) + same_value->rr.ttl = d->now.tv_sec + (r->ttl / 2) + 8; + same_value->rr.real_ttl = r->ttl; + return; + } + + //printf("cache: inserting entry: [%s] [%d]\n", r->owner, r->type); + if(d->cache_count >= MAX_CACHE) + return; + + c = (struct cached *)jdns_alloc(sizeof(struct cached)); + bzero(c,sizeof(struct cached)); + c->rr.name = (unsigned char *)jdns_strdup((char *)r->owner); + c->rr.type = r->type; + c->rr.ttl = d->now.tv_sec + (r->ttl / 2) + 8; // XXX hack for now, BAD SPEC, start retrying just after half-waypoint, then expire + c->rr.real_ttl = r->ttl; + c->rr.rdlen = r->rdlength; + c->rr.rdata = jdns_copy_array(r->rdata, r->rdlength); + switch(r->type) + { + case QTYPE_A: + c->rr.ip = r->data.address->addr.v4; + break; + case QTYPE_NS: + case QTYPE_CNAME: + case QTYPE_PTR: + c->rr.rdname = (unsigned char *)jdns_strdup((const char *)r->data.name); + break; + case QTYPE_SRV: + c->rr.rdname = (unsigned char *)jdns_strdup((const char *)r->data.server->name); + c->rr.srv.port = r->data.server->port; + c->rr.srv.weight = r->data.server->weight; + c->rr.srv.priority = r->data.server->priority; + break; + } + c->next = d->cache[i]; + d->cache[i] = c; + if((c->q = _q_next(d, 0, (char *)r->owner, r->type))) + _q_answer(d,c); + if(c->q && c->q->nexttry == 0) + { + //printf("cache insert, but nexttry == 0\n"); + _q_reset(d,c->q); + if(d->checkqlist == 0) + d->checkqlist = c->q->nexttry; + //printf("after reset: q->nexttry=%d d->checkqlist=%d\n", c->q->nexttry, d->checkqlist); + } +} + +/* +void _a_copy(struct message *m, mdnsda a) +{ // copy the data bits only + if(a->rdata) { message_rdata_raw(m, a->rdata, a->rdlen); return; } + if(a->ip) message_rdata_long(m, a->ip); + if(a->type == QTYPE_SRV) message_rdata_srv(m, a->srv.priority, a->srv.weight, a->srv.port, a->rdname); + else if(a->rdname) message_rdata_name(m, a->rdname); +} +*/ + +void _a_copyq(jdns_list_t *dest, unsigned char *name, unsigned short type, unsigned short class) +{ + jdns_packet_question_t *q = jdns_packet_question_new(); + q->qname = jdns_string_new(); + jdns_string_set_cstr(q->qname, (char *)name); + q->qtype = type; + q->qclass = class; + jdns_list_insert(dest, q, -1); + jdns_packet_question_delete(q); +} + +void _a_copy(jdns_list_t *dest, unsigned char *name, unsigned short type, unsigned short class, unsigned long int ttl, mdnsda a) +{ + jdns_packet_resource_t *r = jdns_packet_resource_new(); + r->qname = jdns_string_new(); + jdns_string_set_cstr(r->qname, (char *)name); + r->qtype = type; + r->qclass = class; + r->ttl = ttl; + if(a->rdata) + jdns_packet_resource_add_bytes(r, a->rdata, a->rdlen); + else if(a->ip) + { + unsigned long int ip; + ip = htonl(a->ip); + jdns_packet_resource_add_bytes(r, (unsigned char *)&ip, 4); + } + else if(a->type == QTYPE_SRV) + { + unsigned short priority, weight, port; + jdns_string_t *name; + priority = htons(a->srv.priority); + weight = htons(a->srv.weight); + port = htons(a->srv.port); + name = jdns_string_new(); + jdns_string_set_cstr(name, (const char *)a->rdname); + jdns_packet_resource_add_bytes(r, (unsigned char *)&priority, 2); + jdns_packet_resource_add_bytes(r, (unsigned char *)&weight, 2); + jdns_packet_resource_add_bytes(r, (unsigned char *)&port, 2); + jdns_packet_resource_add_name(r, name); + jdns_string_delete(name); + } + else if(a->rdname) + { + jdns_string_t *name; + name = jdns_string_new(); + jdns_string_set_cstr(name, (const char *)a->rdname); + jdns_packet_resource_add_name(r, name); + jdns_string_delete(name); + } + jdns_list_insert(dest, r, -1); + jdns_packet_resource_delete(r); +} + +/* +int _r_out(mdnsd d, struct message *m, mdnsdr *list) +{ // copy a published record into an outgoing message + mdnsdr r; //, next; + int ret = 0; + while((r = *list) != 0 && message_packet_len(m) + _rr_len(&r->rr) < d->frame) + { + *list = r->list; + ret++; + if(r->unique) + message_an(m, r->rr.name, r->rr.type, (unsigned short)(d->class + 32768), r->rr.ttl); + else + message_an(m, r->rr.name, r->rr.type, (unsigned short)d->class, r->rr.ttl); + _a_copy(m, &r->rr); + if(r->rr.ttl == 0) _r_done(d,r); + } + return ret; +} +*/ + +int _r_out(mdnsd d, jdns_packet_t *m, mdnsdr *list) +{ // copy a published record into an outgoing message + mdnsdr r; //, next; + unsigned short class; + int ret = 0; + while((r = *list) != 0) + { + *list = r->list; + ret++; + class = r->unique ? d->class | 0x8000 : d->class; + _a_copy(m->answerRecords, r->rr.name, r->rr.type, class, r->rr.ttl, &r->rr); + if(r->rr.ttl == 0) _r_done(d,r); + } + return ret; +} + + +mdnsd mdnsd_new(int class, int frame, int port, int (*time_now)(mdnsd d, void *arg), int (*rand_int)(mdnsd d, void *arg), void *arg) +{ + //int i; + mdnsd d; + d = (mdnsd)jdns_alloc(sizeof(struct mdnsd_struct)); + bzero(d,sizeof(struct mdnsd_struct)); + d->cb_time_now = time_now; + d->cb_rand_int = rand_int; + d->cb_arg = arg; + mygettimeofday(d, &d->now); + d->expireall = d->now.tv_sec + GC; + d->class = class; + d->frame = frame; + d->cache_count = 0; + d->port = port; + return d; +} + +void mdnsd_shutdown(mdnsd d) +{ // shutting down, zero out ttl and push out all records + int i; + mdnsdr cur,next; + d->a_now = 0; + for(i=0;ipublished[i]; cur != 0;) + { + next = cur->next; + cur->rr.ttl = 0; + cur->list = d->a_now; + d->a_now = cur; + cur = next; + } + d->shutdown = 1; +} + +void mdnsd_flush(mdnsd d) +{ + // set all querys to 0 tries + // free whole cache + // set all mdnsdr to probing + // reset all answer lists + + (void)d; +} + +void mdnsd_free(mdnsd d) +{ + int i; + + // loop through all hashes, free everything + // free answers if any + + for(i = 0; i < LPRIME; ++i) + { + while(d->cache[i]) + { + struct cached *cur = d->cache[i]; + d->cache[i] = cur->next; + mdnsda_content_free(&cur->rr); + jdns_free(cur); + } + } + + for(i = 0; i < SPRIME; ++i) + { + while(d->published[i]) + { + struct mdnsdr_struct *cur = d->published[i]; + d->published[i] = cur->next; + mdnsda_content_free(&cur->rr); + jdns_free(cur); + } + } + + while(d->uanswers) + { + struct unicast *u = d->uanswers; + d->uanswers = u->next; + jdns_free(u); + } + + for(i = 0; i < SPRIME; ++i) + { + while(d->queries[i]) + { + struct query *cur = d->queries[i]; + d->queries[i] = cur->next; + query_free(cur); + } + } + + jdns_free(d); +} + +void mdnsd_in(mdnsd d, const jdns_packet_t *m, const jdns_response_t *resp, const jdns_address_t *addr, unsigned short int port) +{ + int i, j; + mdnsdr r = 0; + + if(d->shutdown) return; + + mygettimeofday(d, &d->now); + + if(m->opts.qr == 0) + { + for(i=0;iquestions->count;i++) + { // process each query + jdns_packet_question_t *pq = (jdns_packet_question_t *)m->questions->item[i]; + + if(pq->qclass != d->class || (r = _r_next(d,0,(char *)pq->qname->data,pq->qtype)) == 0) continue; + + // send the matching unicast reply + if(port != d->port) _u_push(d,r,m->id,addr,port); + + for(;r != 0; r = _r_next(d,r,(char *)pq->qname->data,pq->qtype)) + { // check all of our potential answers + if(r->unique && r->unique < 5) + { // probing state, check for conflicts + for(j=0;jauthorityCount;j++) + { // check all to-be answers against our own + jdns_rr_t *ns = resp->authorityRecords[j]; + if(pq->qtype != ns->type || !jdns_domain_cmp(pq->qname->data, ns->owner)) continue; + if(!_a_match(ns,&r->rr)) + { + _conflict(d,r); // answer isn't ours, conflict! + + // r is invalid after conflict, start all over + r = 0; + break; + } + } + continue; + } + for(j=0;janswerCount;j++) + { // check the known answers for this question + jdns_rr_t *an = resp->answerRecords[j]; + if(pq->qtype != an->type || !jdns_domain_cmp(pq->qname->data, an->owner)) continue; + if(_a_match(an,&r->rr)) break; // they already have this answer + } + if(j == resp->answerCount) _r_send(d,r); + } + } + return; + } + + for(i=0;ianswerCount;i++) + { // process each answer, check for a conflict, and cache + jdns_rr_t *an = resp->answerRecords[i]; + if((r = _r_next(d,0,(char *)an->owner,an->type)) != 0 && r->unique && _a_match(an,&r->rr) == 0) _conflict(d,r); + _cache(d,an); + } + + // cache additional records + for(i=0;iadditionalCount;i++) + { + jdns_rr_t *an = resp->additionalRecords[i]; + _cache(d,an); + } +} + +int mdnsd_out(mdnsd d, jdns_packet_t **_m, jdns_address_t **addr, unsigned short int *port) +{ + mdnsdr r; + int ret = 0; + jdns_packet_t *m; + + mygettimeofday(d, &d->now); + //bzero(m,sizeof(struct message)); + m = jdns_packet_new(); + + // defaults, multicast + *port = 0; //htons(5353); + *addr = 0; + // *ip = 0; //inet_addr("224.0.0.251"); + m->opts.qr = 1; + m->opts.aa = 1; + + if(d->uanswers) + { // send out individual unicast answers + struct unicast *u = d->uanswers; + d->uanswers = u->next; + *port = u->port; + // *ip = u->to; + *addr = jdns_address_new(); + if(u->ipv6) + jdns_address_set_ipv6(*addr, u->to6); + else + jdns_address_set_ipv4(*addr, u->to); + m->id = u->id; + _a_copyq(m->questions, u->r->rr.name, u->r->rr.type, (unsigned short)d->class); + _a_copy(m->answerRecords, u->r->rr.name, u->r->rr.type, (unsigned short)d->class, u->r->rr.ttl, &u->r->rr); + jdns_free(u); + ret = 1; + goto end; + } + +//printf("OUT: probing %X now %X pause %X publish %X\n",d->probing,d->a_now,d->a_pause,d->a_publish); + + // accumulate any immediate responses + if(d->a_now) { ret += _r_out(d, m, &d->a_now); } + + if(d->a_publish && _tvdiff(d->now,d->publish) <= 0) + { // check to see if it's time to send the publish retries (and unlink if done) + mdnsdr next, cur = d->a_publish, last = 0; + unsigned short class; + while(cur /*&& message_packet_len(m) + _rr_len(&cur->rr) < d->frame*/ ) + { + next = cur->list; + ret++; cur->tries++; + class = cur->unique ? d->class | 0x8000 : d->class; + _a_copy(m->answerRecords, cur->rr.name, cur->rr.type, class, cur->rr.ttl, &cur->rr); + + if(cur->rr.ttl != 0 && cur->tries < 4) + { + last = cur; + cur = next; + continue; + } + if(d->a_publish == cur) d->a_publish = next; + if(last) last->list = next; + if(cur->rr.ttl == 0) _r_done(d,cur); + cur = next; + } + if(d->a_publish) + { + d->publish.tv_sec = d->now.tv_sec + 2; + d->publish.tv_usec = d->now.tv_usec; + } + } + + // if we're in shutdown, we're done + if(d->shutdown) + goto end; + + // check if a_pause is ready + if(d->a_pause && _tvdiff(d->now, d->pause) <= 0) ret += _r_out(d, m, &d->a_pause); + + // now process questions + if(ret) + goto end; + m->opts.qr = 0; + m->opts.aa = 0; + + if(d->probing && _tvdiff(d->now,d->probe) <= 0) + { + mdnsdr last = 0; + for(r = d->probing; r != 0;) + { // scan probe list to ask questions and process published + if(r->unique == 4) + { // done probing, publish + mdnsdr next = r->list; + if(d->probing == r) + d->probing = r->list; + else + last->list = r->list; + r->list = 0; + r->unique = 5; + _r_publish(d,r); + _published(d,r); + r = next; + continue; + } + //message_qd(m, r->rr.name, r->rr.type, (unsigned short)d->class); + _a_copyq(m->questions, r->rr.name, r->rr.type, (unsigned short)d->class); + last = r; + r = r->list; + } + for(r = d->probing; r != 0; last = r, r = r->list) + { // scan probe list again to append our to-be answers + r->unique++; + _a_copy(m->authorityRecords, r->rr.name, r->rr.type, (unsigned short)d->class, r->rr.ttl, &r->rr); + ret++; + } + if(ret) + { // process probes again in the future + d->probe.tv_sec = d->now.tv_sec; + d->probe.tv_usec = d->now.tv_usec + 250000; + goto end; + } + } + + if(d->checkqlist && d->now.tv_sec >= d->checkqlist) + { // process qlist for retries or expirations + struct query *q; + struct cached *c; + unsigned long int nextbest = 0; + + // ask questions first, track nextbest time + for(q = d->qlist; q != 0; q = q->list) + if(q->nexttry > 0 && q->nexttry <= d->now.tv_sec && q->tries < 3) + _a_copyq(m->questions, (unsigned char *)q->name, (unsigned short)q->type, (unsigned short)d->class); + else if(q->nexttry > 0 && (nextbest == 0 || q->nexttry < nextbest)) + nextbest = q->nexttry; + + // include known answers, update questions + for(q = d->qlist; q != 0; q = q->list) + { + if(q->nexttry == 0 || q->nexttry > d->now.tv_sec) continue; + if(q->tries == 3) + { // done retrying, expire and reset + _c_expire(d,&d->cache[_namehash_nocase(q->name) % LPRIME]); + _q_reset(d,q); + continue; + } + ret++; + q->nexttry = d->now.tv_sec + ++q->tries; + if(nextbest == 0 || q->nexttry < nextbest) + nextbest = q->nexttry; + // if room, add all known good entries + c = 0; + while((c = _c_next(d,c,q->name,q->type)) != 0 && c->rr.ttl > d->now.tv_sec + 8 /* && message_packet_len(m) + _rr_len(&c->rr) < d->frame */) + { + _a_copy(m->answerRecords, (unsigned char *)q->name, (unsigned short)q->type, (unsigned short)d->class, (unsigned long int)(c->rr.ttl - d->now.tv_sec), &c->rr); + } + } + d->checkqlist = nextbest; + } + + if(d->now.tv_sec > d->expireall) + _gc(d); + +end: + if(ret) + *_m = m; + else + jdns_packet_delete(m); + + return ret; +} + +struct mytimeval *mdnsd_sleep(mdnsd d) +{ + int sec, usec; + //mdnsdr r; + d->sleep.tv_sec = d->sleep.tv_usec = 0; + #define RET while(d->sleep.tv_usec > 1000000) {d->sleep.tv_sec++;d->sleep.tv_usec -= 1000000;} return &d->sleep; + + // first check for any immediate items to handle + if(d->uanswers || d->a_now) return &d->sleep; + + mygettimeofday(d, &d->now); + + if(d->a_pause) + { // then check for paused answers + if((usec = _tvdiff(d->now,d->pause)) > 0) d->sleep.tv_usec = usec; + RET; + } + + if(d->probing) + { // now check for probe retries + if((usec = _tvdiff(d->now,d->probe)) > 0) d->sleep.tv_usec = usec; + RET; + } + + if(d->a_publish) + { // now check for publish retries + if((usec = _tvdiff(d->now,d->publish)) > 0) d->sleep.tv_usec = usec; + RET; + } + + if(d->checkqlist) + { // also check for queries with known answer expiration/retry + if((sec = d->checkqlist - d->now.tv_sec) > 0) d->sleep.tv_sec = sec; + RET; + } + + // last resort, next gc expiration + if((sec = d->expireall - d->now.tv_sec) > 0) d->sleep.tv_sec = sec; + RET; +} + +void mdnsd_query(mdnsd d, char *host, int type, int (*answer)(mdnsda a, void *arg), void *arg) +{ + struct query *q; + struct cached *cur = 0; + int i = _namehash_nocase(host) % SPRIME; + if(!(q = _q_next(d,0,host,type))) + { + if(!answer) return; + q = (struct query *)jdns_alloc(sizeof(struct query)); + bzero(q,sizeof(struct query)); + q->name = jdns_strdup(host); + q->type = type; + q->next = d->queries[i]; + q->list = d->qlist; + d->qlist = d->queries[i] = q; + q->answer = answer; + q->arg = arg; + while((cur = _c_next(d,cur,q->name,q->type))) + { + cur->q = q; // any cached entries should be associated + _q_answer(d,cur); // and reported! + } + _q_reset(d,q); + q->nexttry = d->checkqlist = d->now.tv_sec; // new questin, immediately send out + return; + } + if(!answer) + { // no answer means we don't care anymore + _q_done(d,q); + return; + } + q->answer = answer; + q->arg = arg; +} + +mdnsda mdnsd_list(mdnsd d, char *host, int type, mdnsda last) +{ + return (mdnsda)_c_next(d,(struct cached *)last,host,type); +} + +mdnsdr mdnsd_shared(mdnsd d, char *host, int type, long int ttl) +{ + int i = _namehash_nocase(host) % SPRIME; + mdnsdr r; + r = (mdnsdr)jdns_alloc(sizeof(struct mdnsdr_struct)); + bzero(r,sizeof(struct mdnsdr_struct)); + r->rr.name = (unsigned char *)jdns_strdup(host); + r->rr.type = type; + r->rr.ttl = ttl; + r->next = d->published[i]; + d->published[i] = r; + return r; +} + +mdnsdr mdnsd_unique(mdnsd d, char *host, int type, long int ttl, void (*pubresult)(int result, char *host, int type, void *arg), void *arg) +{ + mdnsdr r; + r = mdnsd_shared(d,host,type,ttl); + r->pubresult = pubresult; + r->arg = arg; + r->unique = 1; + _r_push(&d->probing,r); + d->probe.tv_sec = d->now.tv_sec; + d->probe.tv_usec = d->now.tv_usec; + return r; +} + +void mdnsd_done(mdnsd d, mdnsdr r) +{ + mdnsdr cur; + if(r->unique && r->unique < 5) + { // probing yet, zap from that list first! + if(d->probing == r) d->probing = r->list; + else { + for(cur=d->probing;cur->list != r;cur = cur->list); + cur->list = r->list; + } + _r_done(d,r); + return; + } + r->rr.ttl = 0; + _r_send(d,r); +} + +void mdnsd_set_raw(mdnsd d, mdnsdr r, char *data, int len) +{ + if(r->rr.rdata) + jdns_free(r->rr.rdata); + r->rr.rdata = jdns_copy_array((unsigned char*)data, len); + r->rr.rdlen = len; + _r_publish(d,r); +} + +void mdnsd_set_host(mdnsd d, mdnsdr r, char *name) +{ + jdns_free(r->rr.rdname); + r->rr.rdname = (unsigned char *)jdns_strdup(name); + _r_publish(d,r); +} + +void mdnsd_set_ip(mdnsd d, mdnsdr r, unsigned long int ip) +{ + r->rr.ip = ip; + _r_publish(d,r); +} + +void mdnsd_set_srv(mdnsd d, mdnsdr r, int priority, int weight, int port, char *name) +{ + r->rr.srv.priority = priority; + r->rr.srv.weight = weight; + r->rr.srv.port = port; + mdnsd_set_host(d,r,name); +} diff --git a/thirdparty/jdns/jdns_mdnsd.h b/thirdparty/jdns/jdns_mdnsd.h new file mode 100644 index 000000000..fd9d28c5b --- /dev/null +++ b/thirdparty/jdns/jdns_mdnsd.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2005 Jeremie Miller + * Copyright (C) 2005,2006 Justin Karneges + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef JDNS_MDNSD_H +#define JDNS_MDNSD_H + +#include "jdns_p.h" + +struct mytimeval +{ + unsigned long int tv_sec; /* seconds */ + unsigned long int tv_usec; /* microseconds */ +}; + +typedef struct mdnsd_struct *mdnsd; // main daemon data +typedef struct mdnsdr_struct *mdnsdr; // record entry +// answer data +typedef struct mdnsda_struct +{ + unsigned char *name; + unsigned short int type; + unsigned long int ttl; + unsigned long int real_ttl; + unsigned short int rdlen; + unsigned char *rdata; + unsigned long int ip; // A + unsigned char *rdname; // NS/CNAME/PTR/SRV + struct { unsigned short int priority, weight, port; } srv; // SRV +} *mdnsda; + +/////////// +// Global functions +// +// create a new mdns daemon for the given class of names (usually 1) and maximum frame size +mdnsd mdnsd_new(int class, int frame, int port, int (*time_now)(mdnsd d, void *arg), int (*rand_int)(mdnsd d, void *arg), void *arg); +// +// gracefully shutdown the daemon, use mdnsd_out() to get the last packets +void mdnsd_shutdown(mdnsd d); +// +// flush all cached records (network/interface changed) +void mdnsd_flush(mdnsd d); +// +// free given mdnsd (should have used mdnsd_shutdown() first!) +void mdnsd_free(mdnsd d); +// +/////////// + +/////////// +// I/O functions +// +// incoming message from host (to be cached/processed) +void mdnsd_in(mdnsd d, const jdns_packet_t *m, const jdns_response_t *resp, const jdns_address_t *addr, unsigned short int port); +// +// outgoing messge to be delivered to host, returns >0 if one was returned and m/ip/port set +int mdnsd_out(mdnsd d, jdns_packet_t **m, jdns_address_t **addr, unsigned short int *port); +// +// returns the max wait-time until mdnsd_out() needs to be called again +struct mytimeval *mdnsd_sleep(mdnsd d); +// +//////////// + +/////////// +// Q/A functions +// +// register a new query +// answer(record, arg) is called whenever one is found/changes/expires (immediate or anytime after, mdnsda valid until ->ttl==0) +// either answer returns -1, or another mdnsd_query with a NULL answer will remove/unregister this query +void mdnsd_query(mdnsd d, char *host, int type, int (*answer)(mdnsda a, void *arg), void *arg); +// +// returns the first (if last == NULL) or next answer after last from the cache +// mdnsda only valid until an I/O function is called +mdnsda mdnsd_list(mdnsd d, char *host, int type, mdnsda last); +// +/////////// + +/////////// +// Publishing functions +// +// create a new unique record (try mdnsda_list first to make sure it's not used) +// conflict(arg) called at any point when one is detected and unable to recover +// after the first data is set_*(), any future changes effectively expire the old one and attempt to create a new unique record +mdnsdr mdnsd_unique(mdnsd d, char *host, int type, long int ttl, void (*pubresult)(int result, char *host, int type, void *arg), void *arg); +// +// create a new shared record +mdnsdr mdnsd_shared(mdnsd d, char *host, int type, long int ttl); +// +// de-list the given record +void mdnsd_done(mdnsd d, mdnsdr r); +// +// these all set/update the data for the given record, nothing is published until they are called +void mdnsd_set_raw(mdnsd d, mdnsdr r, char *data, int len); +void mdnsd_set_host(mdnsd d, mdnsdr r, char *name); +void mdnsd_set_ip(mdnsd d, mdnsdr r, unsigned long int ip); +void mdnsd_set_srv(mdnsd d, mdnsdr r, int priority, int weight, int port, char *name); +// +/////////// + + +#endif diff --git a/thirdparty/jdns/jdns_p.h b/thirdparty/jdns/jdns_p.h new file mode 100644 index 000000000..02d90e2e2 --- /dev/null +++ b/thirdparty/jdns/jdns_p.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2005-2008 Justin Karneges + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef JDNS_P_H +#define JDNS_P_H + +#include +#include +#include +#include +#include + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) +# define JDNS_OS_WIN +#else +# define JDNS_OS_UNIX +#endif + +#if defined(__FreeBSD__) || defined(__DragonFly__) +# define JDNS_OS_FREEBSD +#elif defined(__NetBSD__) +# define JDNS_OS_NETBSD +#elif defined(sun) || defined(__sun) +# define JDNS_OS_SOLARIS +#elif defined(__APPLE__) && (defined(__GNUC__) || defined(__xlC__) || defined(__xlc__)) +# define JDNS_OS_MAC +#endif + +#ifdef JDNS_OS_WIN +# include +#endif + +#ifdef JDNS_OS_UNIX +# include +# include +#endif + +#include "jdns.h" +#include "jdns_packet.h" + +// jdns_util.c +void *jdns_alloc(int size); +void *jdns_realloc(void *p, int size); +void jdns_free(void *p); +char *jdns_strdup(const char *s); +unsigned char *jdns_copy_array(const unsigned char *src, int size); +int jdns_domain_cmp(const unsigned char *a, const unsigned char *b); + +int jdns_sprintf_s(char *str, int n, const char *format, ...); +int jdns_vsprintf_s(char *str, int n, const char *format, va_list ap); +FILE *jdns_fopen(const char *path, const char *mode); +jdns_string_t *jdns_getenv(const char *name); +char *jdns_strcpy(char *dst, const char *src); + +int jdns_string_indexOf(const jdns_string_t *s, unsigned char c, int pos); +jdns_stringlist_t *jdns_string_split(const jdns_string_t *s, unsigned char sep); + +jdns_dnshost_t *jdns_dnshost_new(); +jdns_dnshost_t *jdns_dnshost_copy(const jdns_dnshost_t *a); +void jdns_dnshost_delete(jdns_dnshost_t *a); +jdns_dnshostlist_t *jdns_dnshostlist_new(); +jdns_dnshostlist_t *jdns_dnshostlist_copy(const jdns_dnshostlist_t *a); +void jdns_dnshostlist_delete(jdns_dnshostlist_t *a); +void jdns_dnshostlist_append(jdns_dnshostlist_t *a, const jdns_dnshost_t *host); + +jdns_rr_t *jdns_rr_from_resource(const jdns_packet_resource_t *pr, const jdns_packet_t *ref); +void jdns_response_remove_extra(jdns_response_t *r); +void jdns_response_remove_answer(jdns_response_t *r, int pos); + +#define alloc_type(type) (type *)jdns_alloc(sizeof(type)) +#define _ustrdup(str) (unsigned char *)jdns_strdup((const char *)str) +#define _ustrlen(str) strlen((const char *)str) +#define _ustrcmp(a, b) strcmp((const char *)a, (const char *)b) + +#endif diff --git a/thirdparty/jdns/jdns_packet.c b/thirdparty/jdns/jdns_packet.c new file mode 100644 index 000000000..1f10b92b1 --- /dev/null +++ b/thirdparty/jdns/jdns_packet.c @@ -0,0 +1,1008 @@ +/* + * Copyright (C) 2006 Justin Karneges + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "jdns_packet.h" + +#include "jdns_p.h" + +// maximum length of a sublabel +#define MAX_SUBLABEL_LENGTH 63 + +// maximum length of a label, including final terminating zero (root sublabel) +// according to RFC 2181, the maximum length is 255, not counting the root +// sublabel. so, with the root sublabel, that means a max length of 256. +#define MAX_LABEL_LENGTH 256 + +// jer's endian functions +static unsigned short int net2short(const unsigned char **bufp) +{ + unsigned short int i; + i = **bufp; + i <<= 8; + i |= *(*bufp + 1); + *bufp += 2; + return i; +} + +static unsigned long int net2long(const unsigned char **bufp) +{ + unsigned long int l; + l = **bufp; + l <<= 8; + l |= *(*bufp + 1); + l <<= 8; + l |= *(*bufp + 2); + l <<= 8; + l |= *(*bufp + 3); + *bufp += 4; + return l; +} + +static void short2net(unsigned short int i, unsigned char **bufp) +{ + *(*bufp + 1) = (unsigned char)i; + i >>= 8; + **bufp = (unsigned char)i; + *bufp += 2; +} + +static void long2net(unsigned long int l, unsigned char **bufp) +{ + *(*bufp + 3) = (unsigned char)l; + l >>= 8; + *(*bufp + 2) = (unsigned char)l; + l >>= 8; + *(*bufp + 1) = (unsigned char)l; + l >>= 8; + **bufp = (unsigned char)l; + *bufp += 4; +} + +// label stuff +typedef struct jdns_packet_label +{ + JDNS_OBJECT + int offset; + jdns_string_t *value; +} jdns_packet_label_t; + +static void jdns_packet_label_delete(jdns_packet_label_t *a); +static jdns_packet_label_t *jdns_packet_label_copy(const jdns_packet_label_t *a); + +static jdns_packet_label_t *jdns_packet_label_new() +{ + jdns_packet_label_t *a = JDNS_OBJECT_NEW(jdns_packet_label); + a->offset = 0; + a->value = 0; + return a; +} + +jdns_packet_label_t *jdns_packet_label_copy(const jdns_packet_label_t *a) +{ + jdns_packet_label_t *c = jdns_packet_label_new(); + c->offset = a->offset; + if(a->value) + c->value = jdns_string_copy(a->value); + return c; +} + +void jdns_packet_label_delete(jdns_packet_label_t *a) +{ + if(!a) + return; + jdns_string_delete(a->value); + jdns_object_free(a); +} + +// gets an offset for decompression. does range and hop count checking also +static int getoffset(const unsigned char *str, int refsize, int *hopsleft) +{ + unsigned short int x; + if(*hopsleft <= 0) + return -1; + --(*hopsleft); + x = str[0] & 0x3f; + x <<= 8; + x |= str[1]; + // stay in bounds + if(x >= refsize) + return -1; + return x; +} + +static int readlabel(const unsigned char *in, int insize, const unsigned char *ref, int refsize, int *_at, jdns_string_t **name) +{ + int at; + // string format is one character smaller than dns format. e.g.: + // dns: [7] affinix [3] com [0] = 13 bytes + // string: "affinix.com." = 12 bytes + // only exception is '.' itself, but that won't influence the max. + unsigned char out[MAX_LABEL_LENGTH - 1]; + int out_size; + const unsigned char *label, *last; + int hopped_yet; + int hopsleft; + int label_size; + + at = *_at; + + // stay in range + if(at < 0 || at >= insize) + return 0; + + out_size = 0; + label = in + at; + hopped_yet = 0; + last = in + insize; + while(1) + { + // need a byte + if(label + 1 > last) + goto error; + + // we make this a while loop instead of an 'if', in case + // there's a pointer to a pointer. as a precaution, + // we will hop no more than 8 times + hopsleft = 8; + while(*label & 0xc0) + { + int offset; + + // need the next byte, too + if(label + 2 > last) + goto error; + + offset = getoffset(label, refsize, &hopsleft); + if(offset == -1) + goto error; + + label = ref + offset; + if(!hopped_yet) + { + at += 2; + hopped_yet = 1; + last = ref + refsize; + } + + // need a byte + if(label + 1 > last) + goto error; + } + + label_size = *label & 0x3f; + + // null label? then we're done + if(label_size == 0) + { + if(!hopped_yet) + ++at; + break; + } + + // enough source bytes? (length byte + length) + if(label + label_size + 1 > last) + goto error; + + // enough dest bytes? (length + dot) + if(out_size + label_size + 1 > MAX_LABEL_LENGTH - 1) + goto error; + + memcpy(out + out_size, label + 1, label_size); + out_size += label_size; + out[out_size] = '.'; + ++out_size; + + if(!hopped_yet) + at += label_size + 1; + + label += label_size + 1; + } + + *_at = at; + *name = jdns_string_new(); + jdns_string_set(*name, out, out_size); + return 1; + +error: + return 0; +} + +// this function compares labels in label format: +// [length] [value ...] [length] [value ...] [0] +static int matchlabel(const unsigned char *a, int asize, const unsigned char *b, int bsize, const unsigned char *ref, int refsize, int ahopsleft, int bhopsleft) +{ + int n, alen, blen, offset; + + // same pointer? + if(a == b) + return 1; + + if(asize < 1 || bsize < 1) + return 0; + + // always ensure we get called without a pointer + if(*a & 0xc0) + { + if(asize < 2) + return 0; + offset = getoffset(a, refsize, &ahopsleft); + if(offset == -1) + return 0; + return matchlabel(ref + offset, refsize - offset, b, bsize, ref, refsize, ahopsleft, bhopsleft); + } + if(*b & 0xc0) + { + if(bsize < 2) + return 0; + offset = getoffset(b, refsize, &bhopsleft); + if(offset == -1) + return 0; + return matchlabel(a, asize, ref + offset, refsize - offset, ref, refsize, ahopsleft, bhopsleft); + } + + alen = *a & 0x3f; + blen = *b & 0x3f; + + // must be same length + if(alen != blen) + return 0; + + // done? + if(alen == 0) + return 1; + + // length byte + length + first byte of next label + if(asize < alen + 2) + return 0; + if(bsize < blen + 2) + return 0; + + // compare the value + for(n = 1; n < alen + 1; ++n) + { + if(a[n] != b[n]) + return 0; + } + + // try next labels + n = alen + 1; + return matchlabel(a + n, asize - n, b + n, bsize - n, ref, refsize, ahopsleft, bhopsleft); +} + +int jdns_packet_name_isvalid(const unsigned char *name, int size) +{ + int n, at, len; + + // at least one byte, no larger than MAX_LABEL_LENGTH - 1 (one byte is + // gained when converting to a label) + if(size < 1 || size > (MAX_LABEL_LENGTH - 1)) + return 0; + + // last byte must be a dot + if(name[size - 1] != '.') + return 0; + + // first byte can't be a dot if there are characters after + if(size > 1 && name[0] == '.') + return 0; + + // each sublabel must be between 1 and MAX_SUBLABEL_LENGTH in length + at = 0; + while(1) + { + // search for dot or end + for(n = at; n < size; ++n) + { + if(name[n] == '.') + break; + } + // length of last one is always zero + if(n >= size) + break; + + len = n - at; + if(len < 1 || len > MAX_SUBLABEL_LENGTH) + return 0; + at = n + 1; // skip over the dot + } + + return 1; +} + +// this function assumes label is pointing to a MAX_LABEL_LENGTH byte buffer +static int name_to_label(const jdns_string_t *name, unsigned char *label) +{ + int n, i, at, len; + + if(!jdns_packet_name_isvalid(name->data, name->size)) + return -1; + + if(name->size == 1) + { + label[0] = 0; + return 1; + } + + at = 0; + i = 0; + while(1) + { + // search for dot or end + for(n = at; n < name->size; ++n) + { + if(name->data[n] == '.') + break; + } + len = n - at; + if(i + (len + 1) > MAX_LABEL_LENGTH) // length byte + length + return 0; + + label[i++] = len; + memcpy(label + i, name->data + at, len); + i += len; + + if(n >= name->size) // end? + break; + at = n + 1; // skip over the dot + } + + return i; +} + +// lookup list is made of jdns_packet_labels +static int writelabel(const jdns_string_t *name, int at, int left, unsigned char **bufp, jdns_list_t *lookup) +{ + unsigned char label[MAX_LABEL_LENGTH]; + int n, i, len; + unsigned char *l; + unsigned char *ref; + int refsize; + + len = name_to_label(name, label); + if(len == -1) + return 0; + + ref = *bufp - at; + refsize = at + left; + for(n = 0; label[n]; n += label[n] + 1) + { + for(i = 0; i < lookup->count; ++i) + { + jdns_packet_label_t *pl = (jdns_packet_label_t *)lookup->item[i]; + + if(matchlabel(label + n, len - n, pl->value->data, pl->value->size, ref, refsize, 8, 8)) + { + // set up a pointer right here, overwriting + // the length byte and the first content + // byte of this section within 'label'. + // this is safe, because the length value + // will always be greater than zero, + // ensuring we have two bytes available to + // use. + l = label + n; + short2net((unsigned short int)pl->offset, &l); + label[n] |= 0xc0; + len = n + 2; // cut things short + break; + } + } + if(label[n] & 0xc0) // double loop, so break again + break; + } + + if(left < len) + return 0; + + // copy into buffer, point there now + memcpy(*bufp, label, len); + l = *bufp; + *bufp += len; + + // for each new label, store its location for future compression + for(n = 0; l[n]; n += l[n] + 1) + { + jdns_string_t *str; + jdns_packet_label_t *pl; + if(l[n] & 0xc0) + break; + + pl = jdns_packet_label_new(); + str = jdns_string_new(); + jdns_string_set(str, l + n, len - n); + pl->offset = l + n - ref; + pl->value = str; + jdns_list_insert(lookup, pl, -1); + } + + return 1; +} + +//---------------------------------------------------------------------------- +// jdns_packet_write +//---------------------------------------------------------------------------- +#define JDNS_PACKET_WRITE_RAW 0 +#define JDNS_PACKET_WRITE_NAME 1 + +struct jdns_packet_write +{ + JDNS_OBJECT + int type; + jdns_string_t *value; +}; + +void jdns_packet_write_delete(jdns_packet_write_t *a); +jdns_packet_write_t *jdns_packet_write_copy(const jdns_packet_write_t *a); + +jdns_packet_write_t *jdns_packet_write_new() +{ + jdns_packet_write_t *a = JDNS_OBJECT_NEW(jdns_packet_write); + a->type = 0; + a->value = 0; + return a; +} + +jdns_packet_write_t *jdns_packet_write_copy(const jdns_packet_write_t *a) +{ + jdns_packet_write_t *c = jdns_packet_write_new(); + c->type = a->type; + if(a->value) + c->value = jdns_string_copy(a->value); + return c; +} + +void jdns_packet_write_delete(jdns_packet_write_t *a) +{ + if(!a) + return; + jdns_string_delete(a->value); + jdns_object_free(a); +} + +//---------------------------------------------------------------------------- +// jdns_packet_question +//---------------------------------------------------------------------------- +jdns_packet_question_t *jdns_packet_question_new() +{ + jdns_packet_question_t *a = JDNS_OBJECT_NEW(jdns_packet_question); + a->qname = 0; + a->qtype = 0; + a->qclass = 0; + return a; +} + +jdns_packet_question_t *jdns_packet_question_copy(const jdns_packet_question_t *a) +{ + jdns_packet_question_t *c = jdns_packet_question_new(); + if(a->qname) + c->qname = jdns_string_copy(a->qname); + c->qtype = a->qtype; + c->qclass = a->qclass; + return c; +} + +void jdns_packet_question_delete(jdns_packet_question_t *a) +{ + if(!a) + return; + jdns_string_delete(a->qname); + jdns_object_free(a); +} + +//---------------------------------------------------------------------------- +// jdns_packet_resource +//---------------------------------------------------------------------------- +jdns_packet_resource_t *jdns_packet_resource_new() +{ + jdns_packet_resource_t *a = JDNS_OBJECT_NEW(jdns_packet_resource); + a->qname = 0; + a->qtype = 0; + a->qclass = 0; + a->ttl = 0; + a->rdlength = 0; + a->rdata = 0; + + a->writelog = jdns_list_new(); + a->writelog->valueList = 1; + return a; +} + +jdns_packet_resource_t *jdns_packet_resource_copy(const jdns_packet_resource_t *a) +{ + jdns_packet_resource_t *c = jdns_packet_resource_new(); + if(a->qname) + c->qname = jdns_string_copy(a->qname); + c->qtype = a->qtype; + c->qclass = a->qclass; + c->ttl = a->ttl; + c->rdlength = a->rdlength; + c->rdata = jdns_copy_array(a->rdata, a->rdlength); + + jdns_list_delete(c->writelog); + c->writelog = jdns_list_copy(a->writelog); + return c; +} + +void jdns_packet_resource_delete(jdns_packet_resource_t *a) +{ + if(!a) + return; + jdns_string_delete(a->qname); + if(a->rdata) + jdns_free(a->rdata); + jdns_list_delete(a->writelog); + jdns_object_free(a); +} + +void jdns_packet_resource_add_bytes(jdns_packet_resource_t *a, const unsigned char *data, int size) +{ + jdns_packet_write_t *write = jdns_packet_write_new(); + write->type = JDNS_PACKET_WRITE_RAW; + write->value = jdns_string_new(); + jdns_string_set(write->value, data, size); + jdns_list_insert_value(a->writelog, write, -1); + jdns_packet_write_delete(write); +} + +void jdns_packet_resource_add_name(jdns_packet_resource_t *a, const jdns_string_t *name) +{ + jdns_packet_write_t *write = jdns_packet_write_new(); + write->type = JDNS_PACKET_WRITE_NAME; + write->value = jdns_string_copy(name); + jdns_list_insert_value(a->writelog, write, -1); + jdns_packet_write_delete(write); +} + +int jdns_packet_resource_read_name(const jdns_packet_resource_t *a, const jdns_packet_t *p, int *at, jdns_string_t **name) +{ + return readlabel(a->rdata, a->rdlength, p->raw_data, p->raw_size, at, name); +} + +//---------------------------------------------------------------------------- +// jdns_packet +//---------------------------------------------------------------------------- + +// note: both process_qsection and process_rrsection modify the 'dest' list, +// even if later items cause an error. this turns out to be convenient +// for handling truncated dns packets + +static int process_qsection(jdns_list_t *dest, int count, const unsigned char *data, int size, const unsigned char **bufp) +{ + int n; + int offset, at; + jdns_string_t *name = 0; + const unsigned char *buf; + + buf = *bufp; + for(n = 0; n < count; ++n) + { + jdns_packet_question_t *q; + + offset = buf - data; + at = 0; + + if(!readlabel(data + offset, size - offset, data, size, &at, &name)) + goto error; + + offset += at; + + // need 4 more bytes + if(size - offset < 4) + goto error; + + buf = data + offset; + + q = jdns_packet_question_new(); + q->qname = name; + name = 0; + q->qtype = net2short(&buf); + q->qclass = net2short(&buf); + + jdns_list_insert_value(dest, q, -1); + jdns_packet_question_delete(q); + } + + *bufp = buf; + return 1; + +error: + jdns_string_delete(name); + return 0; +} + +static int process_rrsection(jdns_list_t *dest, int count, const unsigned char *data, int size, const unsigned char **bufp) +{ + int n; + int offset, at; + jdns_string_t *name = 0; + const unsigned char *buf; + + buf = *bufp; + for(n = 0; n < count; ++n) + { + jdns_packet_resource_t *r; + + offset = buf - data; + at = 0; + + if(!readlabel(data + offset, size - offset, data, size, &at, &name)) + goto error; + + offset += at; + + // need 10 more bytes + if(offset + 10 > size) + goto error; + + buf = data + offset; + + r = jdns_packet_resource_new(); + r->qname = name; + name = 0; + r->qtype = net2short(&buf); + r->qclass = net2short(&buf); + r->ttl = net2long(&buf); + + // per RFC 2181, ttl is supposed to be a 31 bit number. if + // the top bit of the 32 bit field is 1, then entire ttl is + // to be considered 0. + if(r->ttl & 0x80000000) + r->ttl = 0; + + r->rdlength = net2short(&buf); + + offset = buf - data; + + // make sure we have enough for the rdata + if(size - offset < r->rdlength) + { + jdns_packet_resource_delete(r); + goto error; + } + + r->rdata = jdns_copy_array(buf, r->rdlength); + buf += r->rdlength; + + jdns_list_insert_value(dest, r, -1); + jdns_packet_resource_delete(r); + } + + *bufp = buf; + return 1; + +error: + jdns_string_delete(name); + return 0; +} + +static int append_qsection(const jdns_list_t *src, int at, int left, unsigned char **bufp, jdns_list_t *lookup) +{ + unsigned char *buf, *start, *last; + int n; + + buf = *bufp; + start = buf - at; + last = buf + left; + for(n = 0; n < src->count; ++n) + { + jdns_packet_question_t *q = (jdns_packet_question_t *)src->item[n]; + + if(!writelabel(q->qname, buf - start, last - buf, &buf, lookup)) + goto error; + + if(buf + 4 > last) + goto error; + + short2net(q->qtype, &buf); + short2net(q->qclass, &buf); + } + + *bufp = buf; + return 1; + +error: + return 0; +} + +static int append_rrsection(const jdns_list_t *src, int at, int left, unsigned char **bufp, jdns_list_t *lookup) +{ + unsigned char *buf, *start, *last, *rdlengthp; + int n, i, rdlength; + + buf = *bufp; + start = buf - at; + last = buf + left; + for(n = 0; n < src->count; ++n) + { + jdns_packet_resource_t *r = (jdns_packet_resource_t *)src->item[n]; + + if(!writelabel(r->qname, buf - start, last - buf, &buf, lookup)) + goto error; + + if(buf + 10 > last) + goto error; + + short2net(r->qtype, &buf); + short2net(r->qclass, &buf); + long2net(r->ttl, &buf); + + // skip over rdlength + rdlengthp = buf; + buf += 2; + + // play write log + rdlength = 0; + for(i = 0; i < r->writelog->count; ++i) + { + jdns_packet_write_t *write = (jdns_packet_write_t *)r->writelog->item[i]; + if(write->type == JDNS_PACKET_WRITE_RAW) + { + if(buf + write->value->size > last) + goto error; + + memcpy(buf, write->value->data, write->value->size); + buf += write->value->size; + } + else // JDNS_PACKET_WRITE_NAME + { + if(!writelabel(write->value, buf - start, last - buf, &buf, lookup)) + goto error; + } + } + + i = buf - rdlengthp; // should be rdata size + 2 + short2net((unsigned short int)(i - 2), &rdlengthp); + } + + *bufp = buf; + return 1; + +error: + return 0; +} + +jdns_packet_t *jdns_packet_new() +{ + jdns_packet_t *a = JDNS_OBJECT_NEW(jdns_packet); + a->id = 0; + a->opts.qr = 0; + a->opts.opcode = 0; + a->opts.aa = 0; + a->opts.tc = 0; + a->opts.rd = 0; + a->opts.ra = 0; + a->opts.z = 0; + a->opts.rcode = 0; + + a->questions = jdns_list_new(); + a->answerRecords = jdns_list_new(); + a->authorityRecords = jdns_list_new(); + a->additionalRecords = jdns_list_new(); + + a->questions->valueList = 1; + a->answerRecords->valueList = 1; + a->authorityRecords->valueList = 1; + a->additionalRecords->valueList = 1; + + a->fully_parsed = 0; + + a->raw_size = 0; + a->raw_data = 0; + return a; +} + +jdns_packet_t *jdns_packet_copy(const jdns_packet_t *a) +{ + jdns_packet_t *c = jdns_packet_new(); + c->id = a->id; + c->opts.qr = a->opts.qr; + c->opts.opcode = a->opts.opcode; + c->opts.aa = a->opts.aa; + c->opts.tc = a->opts.tc; + c->opts.rd = a->opts.rd; + c->opts.ra = a->opts.ra; + c->opts.z = a->opts.z; + c->opts.rcode = a->opts.rcode; + + jdns_list_delete(c->questions); + jdns_list_delete(c->answerRecords); + jdns_list_delete(c->authorityRecords); + jdns_list_delete(c->additionalRecords); + c->questions = jdns_list_copy(a->questions); + c->answerRecords = jdns_list_copy(a->answerRecords); + c->authorityRecords = jdns_list_copy(a->authorityRecords); + c->additionalRecords = jdns_list_copy(a->additionalRecords); + + c->fully_parsed = a->fully_parsed; + + c->raw_size = a->raw_size; + c->raw_data = jdns_copy_array(a->raw_data, a->raw_size); + + return c; +} + +void jdns_packet_delete(jdns_packet_t *a) +{ + if(!a) + return; + jdns_list_delete(a->questions); + jdns_list_delete(a->answerRecords); + jdns_list_delete(a->authorityRecords); + jdns_list_delete(a->additionalRecords); + if(a->raw_data) + jdns_free(a->raw_data); + jdns_object_free(a); +} + +int jdns_packet_import(jdns_packet_t **a, const unsigned char *data, int size) +{ + jdns_packet_t *tmp = 0; + const unsigned char *buf; + + // need at least some data + if(!data || size == 0) + return 0; + + // header (id + options + item counts) is 12 bytes + if(size < 12) + goto error; + + tmp = jdns_packet_new(); + buf = data; + + // id + tmp->id = net2short(&buf); + + // options + if(buf[0] & 0x80) // qr is bit 7 + tmp->opts.qr = 1; + tmp->opts.opcode = (buf[0] & 0x78) >> 3; // opcode is bits 6,5,4,3 + if(buf[0] & 0x04) // aa is bit 2 + tmp->opts.aa = 1; + if(buf[0] & 0x02) // tc is bit 1 + tmp->opts.tc = 1; + if(buf[0] & 0x01) // rd is bit 0 + tmp->opts.rd = 1; + if(buf[1] & 0x80) // ra is bit 7 (second byte) + tmp->opts.ra = 1; + tmp->opts.z = (buf[1] & 0x70) >> 4; // z is bits 6,5,4 + tmp->opts.rcode = buf[1] & 0x0f; // rcode is bits 3,2,1,0 + buf += 2; + + // item counts + tmp->qdcount = net2short(&buf); + tmp->ancount = net2short(&buf); + tmp->nscount = net2short(&buf); + tmp->arcount = net2short(&buf); + + // if these fail, we don't count them as errors, since the packet + // might have been truncated + if(!process_qsection(tmp->questions, tmp->qdcount, data, size, &buf)) + goto skip; + if(!process_rrsection(tmp->answerRecords, tmp->ancount, data, size, &buf)) + goto skip; + if(!process_rrsection(tmp->authorityRecords, tmp->nscount, data, size, &buf)) + goto skip; + if(!process_rrsection(tmp->additionalRecords, tmp->arcount, data, size, &buf)) + goto skip; + + tmp->fully_parsed = 1; + +skip: + // keep the raw data for reference during rdata parsing + tmp->raw_size = size; + tmp->raw_data = jdns_copy_array(data, size); + + *a = tmp; + return 1; + +error: + jdns_packet_delete(tmp); + return 0; +} + +int jdns_packet_export(jdns_packet_t *a, int maxsize) +{ + unsigned char *block = 0; + unsigned char *buf, *last; + unsigned char c; + int size; + jdns_list_t *lookup = 0; // to hold jdns_packet_label_t + + // clear out any existing raw data before we begin + if(a->raw_data) + { + jdns_free(a->raw_data); + a->raw_data = 0; + a->raw_size = 0; + } + + // preallocate + size = maxsize; + block = (unsigned char *)jdns_alloc(size); + memset(block, 0, size); + + buf = block; + last = block + size; + + if(size < 12) + goto error; + + short2net(a->id, &buf); + if(a->opts.qr) + buf[0] |= 0x80; + c = (unsigned char)a->opts.opcode; + buf[0] |= c << 3; + if(a->opts.aa) + buf[0] |= 0x04; + if(a->opts.tc) + buf[0] |= 0x02; + if(a->opts.rd) + buf[0] |= 0x01; + if(a->opts.ra) + buf[1] |= 0x80; + c = (unsigned char)a->opts.z; + buf[1] |= c << 4; + c = (unsigned char)a->opts.rcode; + buf[1] |= c; + buf += 2; + short2net((unsigned short int)a->questions->count, &buf); + short2net((unsigned short int)a->answerRecords->count, &buf); + short2net((unsigned short int)a->authorityRecords->count, &buf); + short2net((unsigned short int)a->additionalRecords->count, &buf); + + // append sections + lookup = jdns_list_new(); + lookup->autoDelete = 1; + + if(!append_qsection(a->questions, buf - block, last - buf, &buf, lookup)) + goto error; + if(!append_rrsection(a->answerRecords, buf - block, last - buf, &buf, lookup)) + goto error; + if(!append_rrsection(a->authorityRecords, buf - block, last - buf, &buf, lookup)) + goto error; + if(!append_rrsection(a->additionalRecords, buf - block, last - buf, &buf, lookup)) + goto error; + + // done with all sections + jdns_list_delete(lookup); + + // condense + size = buf - block; + block = (unsigned char *)jdns_realloc(block, size); + + // finalize + a->qdcount = a->questions->count; + a->ancount = a->answerRecords->count; + a->nscount = a->authorityRecords->count; + a->arcount = a->additionalRecords->count; + a->raw_data = block; + a->raw_size = size; + + return 1; + +error: + jdns_list_delete(lookup); + if(block) + jdns_free(block); + return 0; +} diff --git a/thirdparty/jdns/jdns_packet.h b/thirdparty/jdns/jdns_packet.h new file mode 100644 index 000000000..1c9e3b5f1 --- /dev/null +++ b/thirdparty/jdns/jdns_packet.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2006 Justin Karneges + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef JDNS_PACKET_H +#define JDNS_PACKET_H + +#include "jdns.h" + +// -- howto -- +// +// writing packets: +// 1) call jdns_packet_new() +// 2) populate the jdns_packet_t structure, using the functions +// as necessary +// 3) call jdns_packet_export() to populate the raw data of the packet +// +// reading packets: +// 1) call jdns_packet_new() +// 2) call jdns_packet_import() with the raw data +// 3) the jdns_packet_t structure is now populated +// +// IMPORTANT: all names must be valid. that is, ending in a dot character + +int jdns_packet_name_isvalid(const unsigned char *name, int size); // 0 if not valid + +typedef struct jdns_packet_question +{ + JDNS_OBJECT + jdns_string_t *qname; + unsigned short int qtype, qclass; +} jdns_packet_question_t; + +jdns_packet_question_t *jdns_packet_question_new(); +jdns_packet_question_t *jdns_packet_question_copy(const jdns_packet_question_t *a); +void jdns_packet_question_delete(jdns_packet_question_t *a); + +typedef struct jdns_packet_write jdns_packet_write_t; +typedef struct jdns_packet jdns_packet_t; + +typedef struct jdns_packet_resource +{ + JDNS_OBJECT + jdns_string_t *qname; + unsigned short int qtype, qclass; + unsigned long int ttl; // 31-bit number, top bit always 0 + unsigned short int rdlength; + unsigned char *rdata; + + // private + jdns_list_t *writelog; // jdns_packet_write_t +} jdns_packet_resource_t; + +jdns_packet_resource_t *jdns_packet_resource_new(); +jdns_packet_resource_t *jdns_packet_resource_copy(const jdns_packet_resource_t *a); +void jdns_packet_resource_delete(jdns_packet_resource_t *a); +void jdns_packet_resource_add_bytes(jdns_packet_resource_t *a, const unsigned char *data, int size); +void jdns_packet_resource_add_name(jdns_packet_resource_t *a, const jdns_string_t *name); +int jdns_packet_resource_read_name(const jdns_packet_resource_t *a, const jdns_packet_t *p, int *at, jdns_string_t **name); + +struct jdns_packet +{ + JDNS_OBJECT + unsigned short int id; + struct + { + unsigned short qr, opcode, aa, tc, rd, ra, z, rcode; + } opts; + + // item counts as specified by the packet. do not use these + // for iteration over the item lists, since they can be wrong + // if the packet is truncated. + int qdcount, ancount, nscount, arcount; + + // value lists + jdns_list_t *questions; // jdns_packet_question_t + jdns_list_t *answerRecords; // jdns_packet_resource_t + jdns_list_t *authorityRecords; // jdns_packet_resource_t + jdns_list_t *additionalRecords; // jdns_packet_resource_t + + // since dns packets are allowed to be truncated, it is possible + // for a packet to not get fully parsed yet still be considered + // successfully parsed. this flag means the packet was fully + // parsed also. + int fully_parsed; + + int raw_size; + unsigned char *raw_data; +}; + +jdns_packet_t *jdns_packet_new(); +jdns_packet_t *jdns_packet_copy(const jdns_packet_t *a); +void jdns_packet_delete(jdns_packet_t *a); +int jdns_packet_import(jdns_packet_t **a, const unsigned char *data, int size); // 0 on fail +int jdns_packet_export(jdns_packet_t *a, int maxsize); // 0 on fail + +#endif diff --git a/thirdparty/jdns/jdns_sys.c b/thirdparty/jdns/jdns_sys.c new file mode 100644 index 000000000..21a846139 --- /dev/null +++ b/thirdparty/jdns/jdns_sys.c @@ -0,0 +1,850 @@ +/* + * Copyright (C) 2005-2008 Justin Karneges + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* +this code probes the system for dns settings. blah. + +q3dns strategies +---------------- + +windows: + +domain name, name server, "search list" found in windows registry here: + + HKEY_LOCAL_MACHINE + System\CurrentControlSet\Services\Tcpip\Parameters <-- win nt+ + System\CurrentControlSet\Services\VxD\MSTCP <-- win 98 + + for domain, try DhcpDomain else Domain + for name servers, try DhcpNameServer, else NameServer + for search list, try SearchList + +iphlpapi.dll : GetNetworkParams(PFIXED_INFO, PULONG); + + info->DomainName + info->DnsServerList (if not null, use it, and loop through ->Next until + null) + no search list + +first try getnetworkparams. if that fails, try the nt regkey then the 98 + regkey. it seems that search list can only come from the registry, so + maybe we need to grab that entry even if getnetworkparams works. + +in the case of the registry, the nameserver and searchlist entries are + delimited by spaces on win nt and commas on win 98. probably a good + idea to simplify white space first (chop away space at start and end, + reduce all sections of spaces to one char). also, lowercase the search + list. + +qt doesn't read the hosts file on windows. this might be a good idea, but + probably not worth it. + +unix: + +read /etc/resolv.conf manually: + for each line, split at spaces + if the first item is "nameserver", then there should be an IP address + following it. note: may contain mixed ipv4 and ipv6 addresses + if the first item is "search", all other items are added to the domain + list + if the first item is "domain", then the next item should be added to the + domain list. + do case-insensitive matching for the item types + for search/domain, the items are in the 8-bit system locale + +info can also be fetched using system calls. we use the res_* stuff here. + first we should detect for a "modern res api". this is available from + glibc 2.3 and onward. use the following scheme to check for it: + +#if defined(__GLIBC__) && ((__GLIBC__ > 2) || ((__GLIBC__ == 2) + && (__GLIBC_MINOR__ >= 3))) + // modern res api +#endif + +on mac we should look up res_init in the system library. see: + qt_mac_resolve_sys(RTLD_NEXT, "res_init"); for a hint. +otherwise we can just use res_init() straight. + +under a modern res api, we do: + struct __res_state res; + res_ninit(&res); +otherwise, we simply call res_init(). for the modern api, we use the "res" + struct that we made. otherwise, we use the global "_res" struct. + +read the res struct to obtain the name servers, search list, and domain. + lowercase the search list and domain. + +qt tries the file, and if that fails it tries the syscalls. we may want to + do the syscalls first, or even just do both all the time. + +read /etc/hosts manually: + for each line + if there is a '#' character in the line, remove it and everything to + the right + simplify white space + convert to lowercase + split the line at spaces + first item is the ip address + all remaining items are hostnames + + note: these hosts could also be used for reverse-dns too + note2: Windows has a hosts file as well (like C:\WINDOWS\hosts) +*/ + +#include "jdns_p.h" + +#ifdef JDNS_OS_WIN +# include +#endif + +#ifdef JDNS_OS_UNIX +# include +# include +# include +# include +#endif + +#define string_indexOf jdns_string_indexOf +#define string_split jdns_string_split + +static int char_isspace(unsigned char c) +{ + if(c == ' ' || c == '\t' || c == '\n' || c == '\r') + return 1; + return 0; +} + +static unsigned char *string_getnextword(unsigned char *in, int size, int pos, int *newpos) +{ + int n; + int at; + int len; + unsigned char *out; + + at = pos; + + // skip any space at the start + while(at < size && char_isspace(in[at])) + ++at; + + // all space? no word then + if(at >= size) + return 0; + + // skip until a space or end + n = at; + while(n < size && !char_isspace(in[n])) + ++n; + len = n - at; + + // allocate length + zero byte + out = (unsigned char *)jdns_alloc(len + 1); + if(!out) + return 0; + memcpy(out, in + at, len); + out[len] = 0; + *newpos = at + len; + return out; +} + +static jdns_string_t *string_simplify(const jdns_string_t *in) +{ + int n; + int pos; + int total; + unsigned char *out; + int outlen; + jdns_string_t *outstr; + jdns_stringlist_t *wordlist; + + // gather words and total of lengths + pos = 0; + total = 0; + wordlist = jdns_stringlist_new(); + while(1) + { + jdns_string_t *word; + unsigned char *str = string_getnextword(in->data, in->size, pos, &pos); + if(!str) + break; + word = jdns_string_new(); + jdns_string_set_cstr(word, (char *)str); + jdns_free(str); + jdns_stringlist_append(wordlist, word); + total += word->size; + jdns_string_delete(word); + } + + if(total == 0) + { + jdns_stringlist_delete(wordlist); + + outstr = jdns_string_new(); + jdns_string_set_cstr(outstr, ""); + return outstr; + } + + // we need to allocate space for total lengths and wordcount-1 spaces + outlen = total + (wordlist->count - 1); + out = (unsigned char *)jdns_alloc(outlen); + + // lay out the words + pos = 0; + for(n = 0; n < wordlist->count; ++n) + { + unsigned char *data = wordlist->item[n]->data; + int size = wordlist->item[n]->size; + memcpy(out + pos, data, size); + pos += size; + + // if this is not the last word, append a space + if(n + 1 < wordlist->count) + out[pos++] = ' '; + } + jdns_stringlist_delete(wordlist); + + outstr = jdns_string_new(); + jdns_string_set(outstr, out, outlen); + jdns_free(out); + return outstr; +} + +static jdns_string_t *string_tolower(const jdns_string_t *in) +{ + int n; + jdns_string_t *out = jdns_string_copy(in); + for(n = 0; n < out->size; ++n) + out->data[n] = tolower(out->data[n]); + return out; +} + +static jdns_string_t *file_nextline(FILE *f) +{ + int at, size; + unsigned char *buf; + jdns_string_t *str; + + size = 1023; + buf = (unsigned char *)jdns_alloc(size); + at = 0; + while(1) + { + unsigned char c = fgetc(f); + if(feof(f)) + { + if(at > 0) + { + // if we read at least one char, take it as a + // line + break; + } + else + { + jdns_free(buf); + return 0; + } + } + if(c == '\n') + break; + if(c == '\r') + continue; + if(at < 1023) + buf[at++] = c; + } + + str = jdns_string_new(); + jdns_string_set(str, buf, at); + jdns_free(buf); + return str; +} + +static jdns_dnshostlist_t *read_hosts_file(const char *path) +{ + jdns_dnshostlist_t *out; + FILE *f; + jdns_string_t *line, *simp; + jdns_stringlist_t *parts; + jdns_address_t *addr; + int n; + + out = jdns_dnshostlist_new(); + + f = jdns_fopen(path, "r"); + if(!f) + return out; + while(1) + { + line = file_nextline(f); + if(!line) + break; + + // truncate at comment + n = string_indexOf(line, '#', 0); + if(n != -1) + { + line->size = n; + line->data[n] = 0; + } + + simp = string_simplify(line); + jdns_string_delete(line); + + parts = string_split(simp, ' '); + jdns_string_delete(simp); + + if(parts->count < 2) + { + jdns_stringlist_delete(parts); + continue; + } + + addr = jdns_address_new(); + if(!jdns_address_set_cstr(addr, (const char *)parts->item[0]->data)) + { + jdns_address_delete(addr); + jdns_stringlist_delete(parts); + continue; + } + + for(n = 1; n < parts->count; ++n) + { + jdns_dnshost_t *h = jdns_dnshost_new(); + h->name = jdns_string_copy(parts->item[n]); + h->address = jdns_address_copy(addr); + jdns_dnshostlist_append(out, h); + jdns_dnshost_delete(h); + } + + jdns_address_delete(addr); + jdns_stringlist_delete(parts); + } + fclose(f); + return out; +} + +static void apply_hosts_file(jdns_dnsparams_t *a, const char *path) +{ + int n; + jdns_dnshostlist_t *list; + + list = read_hosts_file(path); + for(n = 0; n < list->count; ++n) + jdns_dnshostlist_append(a->hosts, list->item[n]); + jdns_dnshostlist_delete(list); +} + +static int dnsparams_have_domain(const jdns_dnsparams_t *a, const jdns_string_t *domain) +{ + int n; + for(n = 0; n < a->domains->count; ++n) + { + jdns_string_t *str = a->domains->item[n]; + if(strcmp((const char *)str->data, (const char *)domain->data) == 0) + return 1; + } + return 0; +} + +#ifdef JDNS_OS_WIN + +// from Microsoft IPTypes.h +#ifndef IP_TYPES_INCLUDED +#define MAX_HOSTNAME_LEN 128 +#define MAX_DOMAIN_NAME_LEN 128 +#define MAX_SCOPE_ID_LEN 256 +typedef struct { + char String[4 * 4]; +} IP_ADDRESS_STRING, *PIP_ADDRESS_STRING, IP_MASK_STRING, *PIP_MASK_STRING; +typedef struct _IP_ADDR_STRING { + struct _IP_ADDR_STRING* Next; + IP_ADDRESS_STRING IpAddress; + IP_MASK_STRING IpMask; + DWORD Context; +} IP_ADDR_STRING, *PIP_ADDR_STRING; +typedef struct { + char HostName[MAX_HOSTNAME_LEN + 4] ; + char DomainName[MAX_DOMAIN_NAME_LEN + 4]; + PIP_ADDR_STRING CurrentDnsServer; + IP_ADDR_STRING DnsServerList; + UINT NodeType; + char ScopeId[MAX_SCOPE_ID_LEN + 4]; + UINT EnableRouting; + UINT EnableProxy; + UINT EnableDns; +} FIXED_INFO, *PFIXED_INFO; +#endif + +typedef DWORD (WINAPI *GetNetworkParamsFunc)(PFIXED_INFO, PULONG); + +static jdns_string_t *reg_readString(HKEY hk, const char *subkey) +{ + char *buf; + DWORD bufsize; + int ret; + jdns_string_t *str = 0; + + bufsize = 1024; + buf = (char *)jdns_alloc((int)bufsize); + if(!buf) + return 0; + ret = RegQueryValueExA(hk, subkey, 0, 0, (LPBYTE)buf, &bufsize); + if(ret == ERROR_MORE_DATA) + { + buf = (char *)jdns_realloc(buf, bufsize); + if(!buf) + { + jdns_free(buf); + return 0; + } + ret = RegQueryValueExA(hk, subkey, 0, 0, (LPBYTE)buf, &bufsize); + } + if(ret == ERROR_SUCCESS) + { + str = jdns_string_new(); + jdns_string_set_cstr(str, (char *)buf); + } + jdns_free(buf); + return str; +} + +static jdns_dnsparams_t *dnsparams_get_winreg() +{ + int n; + jdns_dnsparams_t *params; + HKEY key; + int ret; + char sep; + jdns_string_t *str_domain, *str_nameserver, *str_searchlist; + jdns_stringlist_t *list_nameserver, *list_searchlist; + + sep = ' '; + ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, + "System\\CurrentControlSet\\Services\\Tcpip\\Parameters", + 0, KEY_READ, &key); + if(ret != ERROR_SUCCESS) + { + sep = ','; + ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, + "System\\CurrentControlSet\\Services\\VxD\\MSTCP", + 0, KEY_READ, &key); + if(ret != ERROR_SUCCESS) + return 0; + } + + str_domain = reg_readString(key, "DhcpDomain"); + if(!str_domain) + str_domain = reg_readString(key, "Domain"); + str_nameserver = reg_readString(key, "DhcpNameServer"); + if(!str_nameserver) + str_nameserver = reg_readString(key, "NameServer"); + str_searchlist = reg_readString(key, "SearchList"); + + RegCloseKey(key); + + list_nameserver = 0; + if(str_nameserver) + { + list_nameserver = string_split(str_nameserver, sep); + jdns_string_delete(str_nameserver); + } + list_searchlist = 0; + if(str_searchlist) + { + // lowercase the string + jdns_string_t *p = string_tolower(str_searchlist); + jdns_string_delete(str_searchlist); + str_searchlist = p; + + list_searchlist = string_split(str_searchlist, sep); + jdns_string_delete(str_searchlist); + } + + params = jdns_dnsparams_new(); + if(list_nameserver) + { + // qt seems to do a strange thing here by running each name + // server address through the q3dns setLabel function, and + // then pulls the result as a list of addresses. i have + // no idea why they do this, or how one IP address would + // turn into anything else, let alone several addresses. + // so, uh, we're not going to do that. + for(n = 0; n < list_nameserver->count; ++n) + { + jdns_address_t *addr = jdns_address_new(); + if(jdns_address_set_cstr(addr, (char *)list_nameserver->item[n]->data)) + jdns_dnsparams_append_nameserver(params, addr, JDNS_UNICAST_PORT); + jdns_address_delete(addr); + } + jdns_stringlist_delete(list_nameserver); + } + if(str_domain) + { + if(str_domain->size > 0) + jdns_dnsparams_append_domain(params, str_domain); + jdns_string_delete(str_domain); + } + if(list_searchlist) + { + for(n = 0; n < list_searchlist->count; ++n) + { + if(list_searchlist->item[n]->size > 0) + jdns_dnsparams_append_domain(params, list_searchlist->item[n]); + } + jdns_stringlist_delete(list_searchlist); + } + + return params; +} + +static jdns_dnsparams_t *dnsparams_get_winsys() +{ + jdns_dnsparams_t *params; + GetNetworkParamsFunc myGetNetworkParams; + DWORD ret; + HINSTANCE lib; + jdns_address_t *addr; + jdns_string_t *str; + IP_ADDR_STRING *ipstr; + + lib = LoadLibraryA("iphlpapi"); + if(!lib) + return 0; + + params = 0; + myGetNetworkParams = (GetNetworkParamsFunc)GetProcAddress(lib, "GetNetworkParams"); + if(myGetNetworkParams) + { + ULONG bufsize = 0; + ret = myGetNetworkParams(0, &bufsize); + if(ret == ERROR_BUFFER_OVERFLOW) + { + FIXED_INFO *info = (FIXED_INFO *)jdns_alloc((int)bufsize); + ret = myGetNetworkParams(info, &bufsize); + if(ret == ERROR_SUCCESS) + { + params = jdns_dnsparams_new(); + ipstr = &info->DnsServerList; + while(ipstr) + { + addr = jdns_address_new(); + if(jdns_address_set_cstr(addr, (char *)ipstr->IpAddress.String)) + jdns_dnsparams_append_nameserver(params, addr, JDNS_UNICAST_PORT); + jdns_address_delete(addr); + ipstr = ipstr->Next; + } + str = jdns_string_new(); + jdns_string_set_cstr(str, info->DomainName); + if(str->size > 0) + jdns_dnsparams_append_domain(params, str); + jdns_string_delete(str); + } + jdns_free(info); + } + } + FreeLibrary(lib); + return params; +} + +static void apply_hosts_var_filepath(jdns_dnsparams_t *a, const char *envvar, const char *path) +{ + jdns_string_t *e; + char *str; + int elen, plen; + + e = jdns_getenv(envvar); + if(!e) + return; + elen = strlen((char *)e->data); + plen = strlen(path); + str = (char *)jdns_alloc(elen + plen + 1); + memcpy(str, e->data, elen); + jdns_string_delete(e); + + jdns_strcpy(str + elen, path); + apply_hosts_file(a, str); + jdns_free(str); +} + +static void apply_win_hosts_file(jdns_dnsparams_t *a) +{ + // windows 64-bit + apply_hosts_var_filepath(a, "SystemRoot", "\\SysWOW64\\drivers\\etc\\hosts"); + + // winnt+ + apply_hosts_var_filepath(a, "SystemRoot", "\\system32\\drivers\\etc\\hosts"); + + // win9x + apply_hosts_var_filepath(a, "WINDIR", "\\hosts"); +} + +static jdns_dnsparams_t *dnsparams_get_win() +{ + int n; + jdns_dnsparams_t *sys_params, *reg_params; + + reg_params = dnsparams_get_winreg(); + sys_params = dnsparams_get_winsys(); + + // no sys params? take the reg params then + if(!sys_params) + { + apply_win_hosts_file(reg_params); + return reg_params; + } + + // sys params don't have a search list, so merge the domains from + // the registry if possible + if(reg_params) + { + for(n = 0; n < reg_params->domains->count; ++n) + { + jdns_string_t *reg_str = reg_params->domains->item[n]; + + // don't add dups + if(!dnsparams_have_domain(sys_params, reg_str)) + jdns_dnsparams_append_domain(sys_params, reg_str); + } + jdns_dnsparams_delete(reg_params); + } + apply_win_hosts_file(sys_params); + return sys_params; +} + +#endif + +#ifdef JDNS_OS_UNIX + +static jdns_dnsparams_t *dnsparams_get_unixfiles() +{ + FILE *f; + int n; + jdns_dnsparams_t *params; + jdns_string_t *line, *simp; + jdns_stringlist_t *parts; + + params = jdns_dnsparams_new(); + + f = jdns_fopen("/etc/resolv.conf", "r"); + if(!f) + return params; + while(1) + { + line = file_nextline(f); + if(!line) + break; + + // truncate at comment + n = string_indexOf(line, '#', 0); + if(n != -1) + { + line->size = n; + line->data[n] = 0; + } + + simp = string_simplify(line); + jdns_string_delete(line); + + parts = string_split(simp, ' '); + jdns_string_delete(simp); + + if(parts->count < 2) + { + jdns_stringlist_delete(parts); + continue; + } + + simp = string_tolower(parts->item[0]); + if(strcmp((char *)simp->data, "nameserver") == 0) + { + jdns_address_t *addr = jdns_address_new(); + jdns_address_set_cstr(addr, (const char *)parts->item[1]->data); + jdns_dnsparams_append_nameserver(params, addr, JDNS_UNICAST_PORT); + jdns_address_delete(addr); + } + else if(strcmp((char *)simp->data, "search") == 0) + { + for(n = 1; n < parts->count; ++n) + { + jdns_dnsparams_append_domain(params, parts->item[n]); + } + } + else if(strcmp((char *)simp->data, "domain") == 0) + { + jdns_dnsparams_append_domain(params, parts->item[1]); + } + jdns_string_delete(simp); + + jdns_stringlist_delete(parts); + } + fclose(f); + return params; +} + +#if defined(__GLIBC__) && ((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 3))) +# define JDNS_MODERN_RES_API +#endif + +#ifndef JDNS_MODERN_RES_API +typedef int (*res_init_func)(); +static int my_res_init() +{ +#ifdef JDNS_OS_MAC + res_init_func mac_res_init; + + // look up res_init in the system library (qt does this, not sure why) + mac_res_init = (res_init_func)dlsym(RTLD_NEXT, "res_init"); + if(!mac_res_init) + return -1; + return mac_res_init(); +#else + return res_init(); +#endif +} +#endif + +// on some platforms, __res_state_ext exists as a struct but it is not +// a define, so the #ifdef doesn't work. as a workaround, we'll explicitly +// specify the platforms that have __res_state_ext +//#ifdef __res_state_ext +#if defined(JDNS_OS_MAC) || defined(JDNS_OS_FREEBSD) || \ + defined(JDNS_OS_NETBSD) || defined (JDNS_OS_SOLARIS) +# define USE_EXTEXT +#endif + +static jdns_dnsparams_t *dnsparams_get_unixsys() +{ + int n; + jdns_dnsparams_t *params; + +#ifdef JDNS_MODERN_RES_API + struct __res_state res; + memset(&res, 0, sizeof(struct __res_state)); + n = res_ninit(&res); +#define RESVAR res +#else + n = my_res_init(); +#define RESVAR _res +#endif + + params = jdns_dnsparams_new(); + + // error initializing? + if(n == -1) + return params; + + // nameservers - ipv6 + for(n = 0; n < MAXNS && n < RESVAR._u._ext.nscount; ++n) + { + jdns_address_t *addr; + struct sockaddr_in6 *sa6; + +#ifdef USE_EXTEXT + sa6 = ((struct sockaddr_in6 *)RESVAR._u._ext.ext) + n; +#else + sa6 = RESVAR._u._ext.nsaddrs[n]; +#endif + + if(sa6 == NULL) + continue; + addr = jdns_address_new(); + jdns_address_set_ipv6(addr, sa6->sin6_addr.s6_addr); + jdns_dnsparams_append_nameserver(params, addr, JDNS_UNICAST_PORT); + jdns_address_delete(addr); + } + + // nameservers - ipv4 + for(n = 0; n < MAXNS && n < RESVAR.nscount; ++n) + { + jdns_address_t *addr = jdns_address_new(); + jdns_address_set_ipv4(addr, ntohl(RESVAR.nsaddr_list[n].sin_addr.s_addr)); + jdns_dnsparams_append_nameserver(params, addr, JDNS_UNICAST_PORT); + jdns_address_delete(addr); + } + + // domain name + if(strlen(RESVAR.defdname) > 0) + { + jdns_string_t *str; + jdns_string_t *p; + str = jdns_string_new(); + jdns_string_set_cstr(str, RESVAR.defdname); + p = string_tolower(str); + jdns_string_delete(str); + str = p; + jdns_dnsparams_append_domain(params, str); + jdns_string_delete(str); + } + + // search list +#ifdef MAXDFLSRCH + for(n = 0; n < MAXDFLSRCH && RESVAR.dnsrch[n]; ++n) + { + if(strlen(RESVAR.dnsrch[n]) > 0) + { + jdns_string_t *str; + jdns_string_t *p; + str = jdns_string_new(); + jdns_string_set_cstr(str, RESVAR.dnsrch[n]); + p = string_tolower(str); + jdns_string_delete(str); + str = p; + + // don't add dups + if(!dnsparams_have_domain(params, str)) + jdns_dnsparams_append_domain(params, str); + + jdns_string_delete(str); + } + } +#endif + + return params; +} + +static jdns_dnsparams_t *dnsparams_get_unix() +{ + jdns_dnsparams_t *params; + + // prefer system calls over files + params = dnsparams_get_unixsys(); + if(params->nameservers->count == 0) + { + jdns_dnsparams_delete(params); + params = dnsparams_get_unixfiles(); + } + + apply_hosts_file(params, "/etc/hosts"); + + return params; +} + +#endif + +jdns_dnsparams_t *jdns_system_dnsparams() +{ +#ifdef JDNS_OS_WIN + return dnsparams_get_win(); +#else + return dnsparams_get_unix(); +#endif +} diff --git a/thirdparty/jdns/jdns_util.c b/thirdparty/jdns/jdns_util.c new file mode 100644 index 000000000..0686583fb --- /dev/null +++ b/thirdparty/jdns/jdns_util.c @@ -0,0 +1,1553 @@ +/* + * Copyright (C) 2005-2008 Justin Karneges + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "jdns_p.h" + +#include "jdns_packet.h" + +//---------------------------------------------------------------------------- +// misc +//---------------------------------------------------------------------------- +void *jdns_alloc(int size) +{ + return malloc(size); +} + +void *jdns_realloc(void *p, int size) +{ + return realloc(p, size); +} + +void jdns_free(void *p) +{ + free(p); +} + +char *jdns_strdup(const char *s) +{ + char *p; + int len; + + len = strlen(s) + 1; // the zero + p = (char *)jdns_alloc(len); + memcpy(p, s, len); + return p; +} + +unsigned char *jdns_copy_array(const unsigned char *src, int size) +{ + unsigned char *out; + if(size <= 0) + return 0; + out = (unsigned char *)jdns_alloc(size); + memcpy(out, src, size); + return out; +} + +int jdns_domain_cmp(const unsigned char *a, const unsigned char *b) +{ + int n; + int len_a; + + // case-insensitive compare + len_a = _ustrlen(a); + if(len_a != (int)_ustrlen(b)) + return 0; + + for(n = 0; n < len_a; ++n) + { + if(tolower(a[n]) != tolower(b[n])) + return 0; + } + return 1; +} + +int jdns_sprintf_s(char *str, int n, const char *format, ...) +{ + int ret; + va_list ap; + va_start(ap, format); + ret = jdns_vsprintf_s(str, n, format, ap); + va_end(ap); + return ret; +} + +int jdns_vsprintf_s(char *str, int n, const char *format, va_list ap) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + return vsprintf_s(str, n, format, ap); +#else + (void)n; + return vsprintf(str, format, ap); +#endif +} + +FILE *jdns_fopen(const char *path, const char *mode) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + FILE *fp; + if(fopen_s(&fp, path, mode) != 0) + return 0; + return fp; +#else + return fopen(path, mode); +#endif +} + +jdns_string_t *jdns_getenv(const char *name) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + jdns_string_t *out; + char *dest; + size_t size; + int sizei; + errno_t ret; + ret = getenv_s(&size, 0, 0, name); + if(ret != 0 || size == 0) + return 0; + sizei = (int)size; + dest = (char *)jdns_alloc(sizei); + ret = getenv_s(&size, dest, size, name); + if(ret != 0) + { + free(dest); + return 0; + } + out = jdns_string_new(); + out->size = sizei - 1; + out->data = dest; // must be zero-terminated, which it is + return out; +#else + jdns_string_t *out; + char *val; + val = getenv(name); + if(!val) + return 0; + out = jdns_string_new(); + jdns_string_set_cstr(out, val); + return out; +#endif +} + +char *jdns_strcpy(char *dst, const char *src) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + int len; + // deliberately unsafe + len = strlen(src); + if(strcpy_s(dst, len + 1, src) != 0) + return 0; + return dst; +#else + return strcpy(dst, src); +#endif +} + +//---------------------------------------------------------------------------- +// jdns_object +//---------------------------------------------------------------------------- +void *jdns_object_new(int size, void (*dtor)(void *), void *(*cctor)(const void *)) +{ + jdns_object_t *a = (jdns_object_t *)jdns_alloc(size); + memset(a, 0, size); + a->dtor = dtor; + a->cctor = cctor; + return a; +} + +void *jdns_object_copy(const void *a) +{ + return ((const jdns_object_t *)a)->cctor(a); +} + +void jdns_object_delete(void *a) +{ + ((jdns_object_t *)a)->dtor(a); +} + +void jdns_object_free(void *a) +{ + jdns_free(a); +} + +//---------------------------------------------------------------------------- +// jdns_list +//---------------------------------------------------------------------------- +jdns_list_t *jdns_list_new() +{ + jdns_list_t *a = JDNS_OBJECT_NEW(jdns_list); + a->count = 0; + a->item = 0; + a->valueList = 0; + a->autoDelete = 0; + return a; +} + +jdns_list_t *jdns_list_copy(const jdns_list_t *a) +{ + jdns_list_t *c = jdns_list_new(); + + // note: copying a list with autoDelete should not ever be done. + // heck, let's not even allow it. return an empty list. + if(a->autoDelete) + return c; + + c->valueList = a->valueList; + + // copy the items + if(a->item) + { + int n; + c->count = a->count; + c->item = (void **)jdns_alloc(sizeof(void *) * c->count); + if(a->valueList) + { + // deep copy + for(n = 0; n < c->count; ++n) + c->item[n] = jdns_object_copy(a->item[n]); + } + else + { + // just the pointer + for(n = 0; n < c->count; ++n) + c->item[n] = a->item[n]; + } + } + return c; +} + +void jdns_list_delete(jdns_list_t *a) +{ + if(!a) + return; + jdns_list_clear(a); + jdns_object_free(a); +} + +void jdns_list_clear(jdns_list_t *a) +{ + if(a->item) + { + // delete the items if necessary + if(a->valueList || a->autoDelete) + { + int n; + for(n = 0; n < a->count; ++n) + jdns_object_delete(a->item[n]); + } + jdns_free(a->item); + a->item = 0; + a->count = 0; + } +} + +void jdns_list_insert(jdns_list_t *a, void *item, int pos) +{ + // make memory + if(!a->item) + a->item = (void **)jdns_alloc(sizeof(void *)); + else + a->item = (void **)jdns_realloc(a->item, sizeof(void *) * (a->count + 1)); + + // prepare position + if(pos != -1) + memmove(a->item + pos + 1, a->item + pos, (a->count - pos) * sizeof(void *)); + else + pos = a->count; + + // insert it + if(a->valueList) + a->item[pos] = jdns_object_copy(item); + else + a->item[pos] = item; + ++a->count; +} + +void jdns_list_insert_value(jdns_list_t *a, const void *item, int pos) +{ + jdns_list_insert(a, (void *)item, pos); +} + +void jdns_list_remove(jdns_list_t *a, void *item) +{ + int n; + int pos = -1; + for(n = 0; n < a->count; ++n) + { + if(a->item[n] == item) + { + pos = n; + break; + } + } + if(pos == -1) + return; + + jdns_list_remove_at(a, pos); +} + +void jdns_list_remove_at(jdns_list_t *a, int pos) +{ + if(pos < 0 || pos >= a->count) + return; + + // delete the item if necessary + if(a->valueList || a->autoDelete) + jdns_object_delete(a->item[pos]); + + // free the position + if(a->count > 1) + { + memmove(a->item + pos, a->item + pos + 1, (a->count - pos - 1) * sizeof(void *)); + --a->count; + } + else + { + jdns_free(a->item); + a->item = 0; + a->count = 0; + } +} + +//---------------------------------------------------------------------------- +// jdns_string +//---------------------------------------------------------------------------- +jdns_string_t *jdns_string_new() +{ + jdns_string_t *s = JDNS_OBJECT_NEW(jdns_string); + s->data = 0; + s->size = 0; + return s; +} + +jdns_string_t *jdns_string_copy(const jdns_string_t *s) +{ + jdns_string_t *c = jdns_string_new(); + if(s->data) + jdns_string_set(c, s->data, s->size); + return c; +} + +void jdns_string_delete(jdns_string_t *s) +{ + if(!s) + return; + if(s->data) + jdns_free(s->data); + jdns_object_free(s); +} + +void jdns_string_set(jdns_string_t *s, const unsigned char *str, int str_len) +{ + if(s->data) + jdns_free(s->data); + s->data = (unsigned char *)jdns_alloc(str_len + 1); + memcpy(s->data, str, str_len); + s->data[str_len] = 0; + s->size = str_len; +} + +void jdns_string_set_cstr(jdns_string_t *s, const char *str) +{ + jdns_string_set(s, (const unsigned char *)str, strlen(str)); +} + +int jdns_string_indexOf(const jdns_string_t *s, unsigned char c, int pos) +{ + int n; + for(n = pos; n < s->size; ++n) + { + if(s->data[n] == c) + return n; + } + return -1; +} + +jdns_stringlist_t *jdns_string_split(const jdns_string_t *s, unsigned char sep) +{ + int at, n, len; + jdns_string_t *str; + jdns_stringlist_t *out; + + at = 0; + out = jdns_stringlist_new(); + while(at < s->size) + { + n = jdns_string_indexOf(s, sep, at); + if(n == -1) + n = s->size; + len = n - at; + // FIXME: should we allow empty items? + //if(len == 0) + // break; + str = jdns_string_new(); + jdns_string_set(str, s->data + at, len); + jdns_stringlist_append(out, str); + jdns_string_delete(str); + at = n + 1; // skip over separator + } + return out; +} + +//---------------------------------------------------------------------------- +// jdns_stringlist +//---------------------------------------------------------------------------- +jdns_stringlist_t *jdns_stringlist_new() +{ + jdns_list_t *a = jdns_list_new(); + a->valueList = 1; + return (jdns_stringlist_t *)a; +} + +jdns_stringlist_t *jdns_stringlist_copy(const jdns_stringlist_t *a) +{ + return (jdns_stringlist_t *)jdns_list_copy((const jdns_list_t *)a); +} + +void jdns_stringlist_delete(jdns_stringlist_t *a) +{ + jdns_list_delete((jdns_list_t *)a); + // note: no need to call jdns_object_free() here +} + +void jdns_stringlist_append(jdns_stringlist_t *a, const jdns_string_t *str) +{ + jdns_list_insert_value((jdns_list_t *)a, str, -1); +} + +//---------------------------------------------------------------------------- +// jdns_address +//---------------------------------------------------------------------------- +jdns_address_t *jdns_address_new() +{ + jdns_address_t *a = alloc_type(jdns_address_t); + a->isIpv6 = 0; + a->addr.v4 = 0; + a->c_str = jdns_strdup(""); + return a; +} + +jdns_address_t *jdns_address_copy(const jdns_address_t *a) +{ + jdns_address_t *c = jdns_address_new(); + if(a->isIpv6) + jdns_address_set_ipv6(c, a->addr.v6); + else + jdns_address_set_ipv4(c, a->addr.v4); + return c; +} + +void jdns_address_delete(jdns_address_t *a) +{ + if(!a) + return; + if(a->isIpv6) + jdns_free(a->addr.v6); + jdns_free(a->c_str); + jdns_free(a); +} + +void jdns_address_set_ipv4(jdns_address_t *a, unsigned long int ipv4) +{ + if(a->isIpv6) + jdns_free(a->addr.v6); + jdns_free(a->c_str); + a->isIpv6 = 0; + a->addr.v4 = ipv4; + a->c_str = (char *)jdns_alloc(16); // max size (3 * 4 + 3 + 1) + jdns_sprintf_s(a->c_str, 16, "%d.%d.%d.%d", + (unsigned char)((ipv4 >> 24) & 0xff), + (unsigned char)((ipv4 >> 16) & 0xff), + (unsigned char)((ipv4 >> 8) & 0xff), + (unsigned char)((ipv4) & 0xff)); +} + +void jdns_address_set_ipv6(jdns_address_t *a, const unsigned char *ipv6) +{ + int n; + unsigned char *p; + unsigned short word[8]; + if(a->isIpv6) + jdns_free(a->addr.v6); + jdns_free(a->c_str); + a->isIpv6 = 1; + a->addr.v6 = (unsigned char *)jdns_alloc(16); + memcpy(a->addr.v6, ipv6, 16); + p = (unsigned char *)a->addr.v6; + a->c_str = (char *)jdns_alloc(40); // max size (8 * 4 + 7 + 1) + // each word in a 16-byte ipv6 address is network byte order + for(n = 0; n < 8; ++n) + word[n] = ((unsigned short)(p[n * 2]) << 8) + (unsigned short)(p[n * 2 + 1]); + jdns_sprintf_s(a->c_str, 40, "%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X", word[0], word[1], word[2], word[3], word[4], word[5], word[6], word[7]); +} + +int jdns_address_set_cstr(jdns_address_t *a, const char *str) +{ + int slen = strlen(str); + + // ipv6 + if(strchr(str, ':')) + { + jdns_string_t *in; + jdns_stringlist_t *list; + unsigned char ipv6[16]; + int n, at, count, fill; + + in = jdns_string_new(); + jdns_string_set_cstr(in, str); + list = jdns_string_split(in, ':'); + jdns_string_delete(in); + + // a confusing outputting-backwards parser adapted from qt + + count = list->count; + + if(count < 3 || count > 8) + goto error; + + at = 16; + fill = 9 - count; + for(n = count - 1; n >= 0; --n) + { + if(at <= 0) + goto error; + + if(list->item[n]->size == 0) + { + if(n == count - 1) + { + if(list->item[n - 1]->size != 0) + goto error; + ipv6[--at] = 0; + ipv6[--at] = 0; + } + else if(n == 0) + { + if(list->item[n + 1]->size != 0) + goto error; + ipv6[--at] = 0; + ipv6[--at] = 0; + } + else + { + int i; + for(i = 0; i < fill; ++i) + { + if(at <= 0) + goto error; + ipv6[--at] = 0; + ipv6[--at] = 0; + } + } + } + else + { + if(jdns_string_indexOf(list->item[n], '.', 0) == -1) + { + int x; + x = strtol((const char *)list->item[n]->data, NULL, 16); + if(x < 0 || x > 0xffff) + goto error; + ipv6[--at] = x & 0xff; + ipv6[--at] = (x >> 8) & 0xff; + } + else + { + jdns_address_t *v4; + + if(n != count - 1) + goto error; + + v4 = jdns_address_new(); + if(!jdns_address_set_cstr(v4, (char *)list->item[n]->data)) + { + jdns_address_delete(v4); + goto error; + } + + ipv6[--at] = (unsigned char)(v4->addr.v4 & 0xff); + ipv6[--at] = (unsigned char)((v4->addr.v4 >> 8) & 0xff); + ipv6[--at] = (unsigned char)((v4->addr.v4 >> 16) & 0xff); + ipv6[--at] = (unsigned char)((v4->addr.v4 >> 24) & 0xff); + jdns_address_delete(v4); + --fill; + } + } + } + jdns_stringlist_delete(list); + + jdns_address_set_ipv6(a, ipv6); + return 1; + +error: + jdns_stringlist_delete(list); + return 0; + } + else if(strchr(str, '.')) + { + unsigned char b[4]; + int x; + unsigned long int ipv4; + int at; + char *part; + int len; + const char *p, *p2; + + p = str; + at = 0; + while(1) + { + p2 = strchr(p, '.'); + if(!p2) + p2 = str + slen; + len = p2 - p; + + // convert the section into a byte + part = (char *)jdns_alloc(len + 1); + memcpy(part, p, len); + part[len] = 0; + x = strtol(part, NULL, 10); + jdns_free(part); + if(x < 0 || x > 0xff) + break; + b[at++] = (unsigned char)x; + + // done? + if(p2 >= str + slen) + break; + + // skip over the separator + p = p2 + 1; + } + if(at != 4) + return 0; + + ipv4 = 0; + ipv4 += b[0]; + ipv4 <<= 8; + ipv4 += b[1]; + ipv4 <<= 8; + ipv4 += b[2]; + ipv4 <<= 8; + ipv4 += b[3]; + + jdns_address_set_ipv4(a, ipv4); + return 1; + } + else + return 0; +} + +int jdns_address_cmp(const jdns_address_t *a, const jdns_address_t *b) +{ + // same protocol? + if(a->isIpv6 != b->isIpv6) + return 0; + if(a->isIpv6) + { + int n; + for(n = 0; n < 16; ++n) + { + if(a->addr.v6[n] != b->addr.v6[n]) + break; + } + if(n == 16) + return 1; + } + else + { + if(a->addr.v4 == b->addr.v4) + return 1; + } + return 0; +} + +// FF02::FB +unsigned char jdns_multicast_addr6_value_v6[] = +{ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb +}; + +jdns_address_t *jdns_address_multicast4_new() +{ + jdns_address_t *a = jdns_address_new(); + jdns_address_set_ipv4(a, 0xe00000fb); + return a; +} + +jdns_address_t *jdns_address_multicast6_new() +{ + jdns_address_t *a = jdns_address_new(); + jdns_address_set_ipv6(a, jdns_multicast_addr6_value_v6); + return a; +} + +//---------------------------------------------------------------------------- +// jdns_server +//---------------------------------------------------------------------------- +jdns_server_t *jdns_server_new() +{ + jdns_server_t *s = alloc_type(jdns_server_t); + s->name = 0; + s->port = 0; + s->priority = 0; + s->weight = 0; + return s; +} + +jdns_server_t *jdns_server_copy(const jdns_server_t *s) +{ + jdns_server_t *c = jdns_server_new(); + if(s->name) + c->name = _ustrdup(s->name); + c->port = s->port; + c->priority = s->priority; + c->weight = s->weight; + return c; +} + +void jdns_server_delete(jdns_server_t *s) +{ + if(!s) + return; + if(s->name) + jdns_free(s->name); + jdns_object_free(s); +} + +void jdns_server_set_name(jdns_server_t *s, const unsigned char *name) +{ + if(s->name) + jdns_free(s->name); + s->name = _ustrdup(name); +} + +//---------------------------------------------------------------------------- +// jdns_nameserver +//---------------------------------------------------------------------------- +jdns_nameserver_t *jdns_nameserver_new() +{ + jdns_nameserver_t *a = alloc_type(jdns_nameserver_t); + a->address = 0; + a->port = -1; + return a; +} + +jdns_nameserver_t *jdns_nameserver_copy(const jdns_nameserver_t *a) +{ + jdns_nameserver_t *c = jdns_nameserver_new(); + if(a->address) + c->address = jdns_address_copy(a->address); + c->port = a->port; + return c; +} + +void jdns_nameserver_delete(jdns_nameserver_t *a) +{ + if(!a) + return; + jdns_address_delete(a->address); + jdns_free(a); +} + +void jdns_nameserver_set(jdns_nameserver_t *a, const jdns_address_t *addr, int port) +{ + if(a->address) + jdns_address_delete(a->address); + a->address = jdns_address_copy(addr); + a->port = port; +} + +//---------------------------------------------------------------------------- +// jdns_nameserverlist +//---------------------------------------------------------------------------- +jdns_nameserverlist_t *jdns_nameserverlist_new() +{ + jdns_nameserverlist_t *a = alloc_type(jdns_nameserverlist_t); + a->count = 0; + a->item = 0; + return a; +} + +jdns_nameserverlist_t *jdns_nameserverlist_copy(const jdns_nameserverlist_t *a) +{ + int n; + jdns_nameserverlist_t *c = jdns_nameserverlist_new(); + if(a->item) + { + c->item = (jdns_nameserver_t **)jdns_alloc(sizeof(jdns_nameserver_t *) * a->count); + c->count = a->count; + for(n = 0; n < c->count; ++n) + c->item[n] = jdns_nameserver_copy(a->item[n]); + } + return c; +} + +void jdns_nameserverlist_delete(jdns_nameserverlist_t *a) +{ + int n; + if(!a) + return; + if(a->item) + { + for(n = 0; n < a->count; ++n) + jdns_nameserver_delete(a->item[n]); + jdns_free(a->item); + } + jdns_free(a); +} + +void jdns_nameserverlist_append(jdns_nameserverlist_t *a, const jdns_address_t *addr, int port) +{ + if(!a->item) + a->item = (jdns_nameserver_t **)jdns_alloc(sizeof(jdns_nameserver_t *)); + else + a->item = (jdns_nameserver_t **)jdns_realloc(a->item, sizeof(jdns_nameserver_t *) * (a->count + 1)); + a->item[a->count] = jdns_nameserver_new(); + jdns_nameserver_set(a->item[a->count], addr, port); + ++a->count; +} + +//---------------------------------------------------------------------------- +// jdns_dnshost +//---------------------------------------------------------------------------- +jdns_dnshost_t *jdns_dnshost_new() +{ + jdns_dnshost_t *a = alloc_type(jdns_dnshost_t); + a->name = 0; + a->address = 0; + return a; +} + +jdns_dnshost_t *jdns_dnshost_copy(const jdns_dnshost_t *a) +{ + jdns_dnshost_t *c = jdns_dnshost_new(); + if(a->name) + c->name = jdns_string_copy(a->name); + if(a->address) + c->address = jdns_address_copy(a->address); + return c; +} + +void jdns_dnshost_delete(jdns_dnshost_t *a) +{ + if(!a) + return; + jdns_string_delete(a->name); + jdns_address_delete(a->address); + jdns_free(a); +} + +//---------------------------------------------------------------------------- +// jdns_dnshostlist +//---------------------------------------------------------------------------- +jdns_dnshostlist_t *jdns_dnshostlist_new() +{ + jdns_dnshostlist_t *a = alloc_type(jdns_dnshostlist_t); + a->count = 0; + a->item = 0; + return a; +} + +jdns_dnshostlist_t *jdns_dnshostlist_copy(const jdns_dnshostlist_t *a) +{ + int n; + jdns_dnshostlist_t *c = jdns_dnshostlist_new(); + if(a->item) + { + c->item = (jdns_dnshost_t **)jdns_alloc(sizeof(jdns_dnshost_t *) * a->count); + c->count = a->count; + for(n = 0; n < c->count; ++n) + c->item[n] = jdns_dnshost_copy(a->item[n]); + } + return c; +} + +void jdns_dnshostlist_delete(jdns_dnshostlist_t *a) +{ + int n; + if(!a) + return; + if(a->item) + { + for(n = 0; n < a->count; ++n) + jdns_dnshost_delete(a->item[n]); + jdns_free(a->item); + } + jdns_free(a); +} + +void jdns_dnshostlist_append(jdns_dnshostlist_t *a, const jdns_dnshost_t *host) +{ + if(!a->item) + a->item = (jdns_dnshost_t **)jdns_alloc(sizeof(jdns_dnshost_t *)); + else + a->item = (jdns_dnshost_t **)jdns_realloc(a->item, sizeof(jdns_dnshost_t *) * (a->count + 1)); + a->item[a->count] = jdns_dnshost_copy(host); + ++a->count; +} + +//---------------------------------------------------------------------------- +// jdns_dnsparams +//---------------------------------------------------------------------------- +jdns_dnsparams_t *jdns_dnsparams_new() +{ + jdns_dnsparams_t *a = alloc_type(jdns_dnsparams_t); + a->nameservers = jdns_nameserverlist_new(); + a->domains = jdns_stringlist_new(); + a->hosts = jdns_dnshostlist_new(); + return a; +} + +jdns_dnsparams_t *jdns_dnsparams_copy(jdns_dnsparams_t *a) +{ + jdns_dnsparams_t *c = jdns_dnsparams_new(); + c->nameservers = jdns_nameserverlist_copy(a->nameservers); + c->domains = jdns_stringlist_copy(a->domains); + c->hosts = jdns_dnshostlist_copy(a->hosts); + return c; +} + +void jdns_dnsparams_delete(jdns_dnsparams_t *a) +{ + if(!a) + return; + jdns_nameserverlist_delete(a->nameservers); + jdns_stringlist_delete(a->domains); + jdns_dnshostlist_delete(a->hosts); + jdns_free(a); +} + +void jdns_dnsparams_append_nameserver(jdns_dnsparams_t *a, const jdns_address_t *addr, int port) +{ + jdns_nameserverlist_append(a->nameservers, addr, port); +} + +void jdns_dnsparams_append_domain(jdns_dnsparams_t *a, const jdns_string_t *domain) +{ + jdns_stringlist_append(a->domains, domain); +} + +void jdns_dnsparams_append_host(jdns_dnsparams_t *a, const jdns_string_t *name, const jdns_address_t *address) +{ + jdns_dnshost_t *h = jdns_dnshost_new(); + h->name = jdns_string_copy(name); + h->address = jdns_address_copy(address); + jdns_dnshostlist_append(a->hosts, h); + jdns_dnshost_delete(h); +} + +//---------------------------------------------------------------------------- +// jdns_rr +//---------------------------------------------------------------------------- +void _jdns_rr_data_reset(jdns_rr_t *r) +{ + if(r->rdata) + { + jdns_free(r->rdata); + r->rdata = 0; + } + r->rdlength = 0; + + if(r->haveKnown) + { + switch(r->type) + { + case JDNS_RTYPE_A: + case JDNS_RTYPE_AAAA: + jdns_address_delete(r->data.address); + break; + case JDNS_RTYPE_MX: + case JDNS_RTYPE_SRV: + jdns_server_delete(r->data.server); + break; + case JDNS_RTYPE_CNAME: + case JDNS_RTYPE_PTR: + case JDNS_RTYPE_NS: + jdns_free(r->data.name); + break; + case JDNS_RTYPE_TXT: + jdns_stringlist_delete(r->data.texts); + break; + case JDNS_RTYPE_HINFO: + jdns_string_delete(r->data.hinfo.cpu); + jdns_string_delete(r->data.hinfo.os); + break; + default: + break; + }; + r->haveKnown = 0; + } + r->type = -1; +} + +void _jdns_rr_data_copy(const jdns_rr_t *r, jdns_rr_t *c) +{ + c->type = r->type; + c->qclass = r->qclass; + c->rdlength = r->rdlength; + c->rdata = jdns_copy_array(r->rdata, r->rdlength); + + if(r->haveKnown) + { + switch(r->type) + { + case JDNS_RTYPE_A: + case JDNS_RTYPE_AAAA: + c->data.address = jdns_address_copy(r->data.address); + break; + case JDNS_RTYPE_MX: + case JDNS_RTYPE_SRV: + c->data.server = jdns_server_copy(r->data.server); + break; + case JDNS_RTYPE_CNAME: + case JDNS_RTYPE_PTR: + case JDNS_RTYPE_NS: + c->data.name = _ustrdup(r->data.name); + break; + case JDNS_RTYPE_TXT: + c->data.texts = jdns_stringlist_copy(r->data.texts); + break; + case JDNS_RTYPE_HINFO: + c->data.hinfo.cpu = jdns_string_copy(r->data.hinfo.cpu); + c->data.hinfo.os = jdns_string_copy(r->data.hinfo.os); + break; + default: + break; + }; + c->haveKnown = 1; + } +} + +jdns_rr_t *jdns_rr_new() +{ + jdns_rr_t *r = alloc_type(jdns_rr_t); + r->owner = 0; + r->ttl = 0; + r->type = -1; + r->qclass = 0; + r->rdata = 0; + r->rdlength = 0; + r->haveKnown = 0; + return r; +} + +jdns_rr_t *jdns_rr_copy(const jdns_rr_t *r) +{ + jdns_rr_t *c = jdns_rr_new(); + if(r->owner) + c->owner = _ustrdup(r->owner); + c->ttl = r->ttl; + _jdns_rr_data_copy(r, c); + return c; +} + +void jdns_rr_delete(jdns_rr_t *r) +{ + if(!r) + return; + if(r->owner) + jdns_free(r->owner); + _jdns_rr_data_reset(r); + jdns_free(r); +} + +void jdns_rr_set_owner(jdns_rr_t *r, const unsigned char *name) +{ + if(r->owner) + jdns_free(r->owner); + r->owner = _ustrdup(name); +} + +void jdns_rr_set_record(jdns_rr_t *r, int type, const unsigned char *rdata, int rdlength) +{ + _jdns_rr_data_reset(r); + r->type = type; + r->rdlength = rdlength; + r->rdata = jdns_copy_array(rdata, rdlength); +} + +void jdns_rr_set_A(jdns_rr_t *r, const jdns_address_t *address) +{ + _jdns_rr_data_reset(r); + r->type = JDNS_RTYPE_A; + r->haveKnown = 1; + r->data.address = jdns_address_copy(address); +} + +void jdns_rr_set_AAAA(jdns_rr_t *r, const jdns_address_t *address) +{ + _jdns_rr_data_reset(r); + r->type = JDNS_RTYPE_AAAA; + r->haveKnown = 1; + r->data.address = jdns_address_copy(address); +} + +void jdns_rr_set_MX(jdns_rr_t *r, const unsigned char *name, int priority) +{ + jdns_server_t *s = jdns_server_new(); + jdns_server_set_name(s, name); + s->priority = priority; + + _jdns_rr_data_reset(r); + r->type = JDNS_RTYPE_MX; + r->haveKnown = 1; + r->data.server = s; +} + +void jdns_rr_set_SRV(jdns_rr_t *r, const unsigned char *name, int port, int priority, int weight) +{ + jdns_server_t *s = jdns_server_new(); + jdns_server_set_name(s, name); + s->port = port; + s->priority = priority; + s->weight = weight; + + _jdns_rr_data_reset(r); + r->type = JDNS_RTYPE_SRV; + r->haveKnown = 1; + r->data.server = s; +} + +void jdns_rr_set_CNAME(jdns_rr_t *r, const unsigned char *name) +{ + _jdns_rr_data_reset(r); + r->type = JDNS_RTYPE_CNAME; + r->haveKnown = 1; + r->data.name = _ustrdup(name); +} + +void jdns_rr_set_PTR(jdns_rr_t *r, const unsigned char *name) +{ + _jdns_rr_data_reset(r); + r->type = JDNS_RTYPE_PTR; + r->haveKnown = 1; + r->data.name = _ustrdup(name); +} + +void jdns_rr_set_TXT(jdns_rr_t *r, const jdns_stringlist_t *texts) +{ + _jdns_rr_data_reset(r); + r->type = JDNS_RTYPE_TXT; + r->haveKnown = 1; + r->data.texts = jdns_stringlist_copy(texts); +} + +void jdns_rr_set_HINFO(jdns_rr_t *r, const jdns_string_t *cpu, const jdns_string_t *os) +{ + _jdns_rr_data_reset(r); + r->type = JDNS_RTYPE_HINFO; + r->haveKnown = 1; + r->data.hinfo.cpu = jdns_string_copy(cpu); + r->data.hinfo.os = jdns_string_copy(os); +} + +void jdns_rr_set_NS(jdns_rr_t *r, const unsigned char *name) +{ + _jdns_rr_data_reset(r); + r->type = JDNS_RTYPE_NS; + r->haveKnown = 1; + r->data.name = _ustrdup(name); +} + +int jdns_rr_verify(const jdns_rr_t *r) +{ + if(r->type == -1) + return 0; + + if(!jdns_packet_name_isvalid(r->owner, _ustrlen(r->owner))) + return 0; + + switch(r->type) + { + case JDNS_RTYPE_MX: + case JDNS_RTYPE_SRV: + { + // consider it valid if we don't have a known to check + if(!r->haveKnown) + return 1; + if(!jdns_packet_name_isvalid(r->data.server->name, _ustrlen(r->data.server->name))) + return 0; + break; + } + case JDNS_RTYPE_CNAME: + case JDNS_RTYPE_PTR: + case JDNS_RTYPE_NS: + { + if(!r->haveKnown) + return 1; + if(!jdns_packet_name_isvalid(r->data.name, _ustrlen(r->data.name))) + return 0; + break; + } + case JDNS_RTYPE_TXT: + { + int n; + if(!r->haveKnown) + return 1; + for(n = 0; n < r->data.texts->count; ++n) + { + if(r->data.texts->item[n]->size > 255) + return 0; + } + break; + } + case JDNS_RTYPE_HINFO: + { + if(!r->haveKnown) + return 1; + if(r->data.hinfo.cpu->size > 255) + return 0; + if(r->data.hinfo.os->size > 255) + return 0; + break; + } + } + + return 1; +} + +static jdns_string_t *read_name_at_end(const jdns_packet_resource_t *pr, const jdns_packet_t *ref, int _at) +{ + jdns_string_t *name; + int at; + at = _at; + if(!jdns_packet_resource_read_name(pr, ref, &at, &name)) + return 0; + if(at != pr->rdlength) + { + jdns_string_delete(name); + return 0; + } + return name; +} + +static jdns_string_t *read_text_string(const jdns_packet_resource_t *pr, int *_at) +{ + jdns_string_t *out; + int at, len; + at = *_at; + if(at + 1 > pr->rdlength) + return 0; + len = pr->rdata[at++]; + if(at + len > pr->rdlength) + return 0; + out = jdns_string_new(); + jdns_string_set(out, pr->rdata + at, len); + at += len; + *_at = at; + return out; +} + +// if the type is known, then it must be parsed properly +// if the type is unknown, then that's ok +// rdata is always copied, known or not +jdns_rr_t *jdns_rr_from_resource(const jdns_packet_resource_t *pr, const jdns_packet_t *ref) +{ + jdns_rr_t *rr = 0; + + if(pr->qtype == JDNS_RTYPE_ANY) + return 0; + + switch(pr->qtype) + { + case JDNS_RTYPE_A: + { + jdns_address_t *addr; + unsigned long int ip; + if(pr->rdlength != 4) + break; + memcpy(&ip, pr->rdata, 4); + ip = ntohl(ip); + addr = jdns_address_new(); + jdns_address_set_ipv4(addr, ip); + rr = jdns_rr_new(); + jdns_rr_set_A(rr, addr); + jdns_address_delete(addr); + break; + } + case JDNS_RTYPE_AAAA: + { + jdns_address_t *addr; + if(pr->rdlength != 16) + break; + addr = jdns_address_new(); + jdns_address_set_ipv6(addr, pr->rdata); + rr = jdns_rr_new(); + jdns_rr_set_AAAA(rr, addr); + jdns_address_delete(addr); + break; + } + case JDNS_RTYPE_MX: + { + unsigned short priority; + jdns_string_t *name; + if(pr->rdlength < 2) + break; + memcpy(&priority, pr->rdata, 2); + priority = ntohs(priority); + name = read_name_at_end(pr, ref, 2); + if(!name) + break; + rr = jdns_rr_new(); + jdns_rr_set_MX(rr, name->data, priority); + jdns_string_delete(name); + break; + } + case JDNS_RTYPE_SRV: + { + unsigned short priority, weight, port; + jdns_string_t *name; + if(pr->rdlength < 6) + break; + memcpy(&priority, pr->rdata, 2); + priority = ntohs(priority); + memcpy(&weight, pr->rdata + 2, 2); + weight = ntohs(weight); + memcpy(&port, pr->rdata + 4, 2); + port = ntohs(port); + name = read_name_at_end(pr, ref, 6); + if(!name) + break; + rr = jdns_rr_new(); + jdns_rr_set_SRV(rr, name->data, port, priority, weight); + jdns_string_delete(name); + break; + } + case JDNS_RTYPE_CNAME: + { + jdns_string_t *name; + name = read_name_at_end(pr, ref, 0); + if(!name) + break; + rr = jdns_rr_new(); + jdns_rr_set_CNAME(rr, name->data); + jdns_string_delete(name); + break; + } + case JDNS_RTYPE_PTR: + { + jdns_string_t *name; + name = read_name_at_end(pr, ref, 0); + if(!name) + break; + rr = jdns_rr_new(); + jdns_rr_set_PTR(rr, name->data); + jdns_string_delete(name); + break; + } + case JDNS_RTYPE_TXT: + { + jdns_stringlist_t *texts; + jdns_string_t *str; + int at, error; + texts = jdns_stringlist_new(); + at = 0; + error = 0; + while(at < pr->rdlength) + { + str = read_text_string(pr, &at); + if(!str) + { + error = 1; + break; + } + jdns_stringlist_append(texts, str); + jdns_string_delete(str); + } + if(error) + { + jdns_stringlist_delete(texts); + break; + } + rr = jdns_rr_new(); + jdns_rr_set_TXT(rr, texts); + jdns_stringlist_delete(texts); + break; + } + case JDNS_RTYPE_HINFO: + { + jdns_string_t *cpu, *os; + int at; + at = 0; + cpu = read_text_string(pr, &at); + if(!cpu) + break; + os = read_text_string(pr, &at); + if(!os) + { + jdns_string_delete(cpu); + break; + } + if(at != pr->rdlength) + { + jdns_string_delete(cpu); + jdns_string_delete(os); + break; + } + rr = jdns_rr_new(); + jdns_rr_set_HINFO(rr, cpu, os); + jdns_string_delete(cpu); + jdns_string_delete(os); + break; + } + case JDNS_RTYPE_NS: + { + jdns_string_t *name; + name = read_name_at_end(pr, ref, 0); + if(!name) + break; + rr = jdns_rr_new(); + jdns_rr_set_NS(rr, name->data); + jdns_string_delete(name); + break; + } + default: + { + rr = jdns_rr_new(); + rr->type = pr->qtype; + break; + } + } + + if(rr) + { + rr->qclass = pr->qclass; + rr->owner = _ustrdup(pr->qname->data); + rr->ttl = (int)pr->ttl; // pr->ttl is 31-bits, cast is safe + rr->rdlength = pr->rdlength; + rr->rdata = jdns_copy_array(pr->rdata, pr->rdlength); + } + + return rr; +} + +//---------------------------------------------------------------------------- +// jdns_response +//---------------------------------------------------------------------------- +#define ARRAY_DELETE(array, count, dtor) \ + { \ + if(count > 0) \ + { \ + int n; \ + for(n = 0; n < count; ++n) \ + dtor(array[n]); \ + } \ + jdns_free(array); \ + array = 0; \ + count = 0; \ + } + +#define ARRAY_COPY(type, array_src, count_src, array_dest, count_dest, cctor) \ + { \ + if(count_src > 0) \ + { \ + int n; \ + count_dest = count_src; \ + array_dest = (type **)jdns_alloc(sizeof(type *) * count_dest); \ + for(n = 0; n < count_dest; ++n) \ + array_dest[n] = cctor(array_src[n]); \ + } \ + } + +#define ARRAY_APPEND(type, array, count, item) \ + { \ + if(!array) \ + array = (type **)jdns_alloc(sizeof(type *)); \ + else \ + array = (type **)jdns_realloc(array, sizeof(type *) * (count + 1)); \ + array[count] = item; \ + ++count; \ + } + +jdns_response_t *jdns_response_new() +{ + jdns_response_t *r = alloc_type(jdns_response_t); + r->answerCount = 0; + r->answerRecords = 0; + r->authorityCount = 0; + r->authorityRecords = 0; + r->additionalCount = 0; + r->additionalRecords = 0; + return r; +} + +jdns_response_t *jdns_response_copy(const jdns_response_t *r) +{ + jdns_response_t *c = jdns_response_new(); + ARRAY_COPY(jdns_rr_t, r->answerRecords, r->answerCount, c->answerRecords, c->answerCount, jdns_rr_copy); + ARRAY_COPY(jdns_rr_t, r->authorityRecords, r->authorityCount, c->authorityRecords, c->authorityCount, jdns_rr_copy); + ARRAY_COPY(jdns_rr_t, r->additionalRecords, r->additionalCount, c->additionalRecords, c->additionalCount, jdns_rr_copy); + return c; +} + +void jdns_response_delete(jdns_response_t *r) +{ + if(!r) + return; + ARRAY_DELETE(r->answerRecords, r->answerCount, jdns_rr_delete); + ARRAY_DELETE(r->authorityRecords, r->authorityCount, jdns_rr_delete); + ARRAY_DELETE(r->additionalRecords, r->additionalCount, jdns_rr_delete); + jdns_free(r); +} + +void jdns_response_append_answer(jdns_response_t *r, const jdns_rr_t *rr) +{ + ARRAY_APPEND(jdns_rr_t, r->answerRecords, r->answerCount, jdns_rr_copy(rr)); +} + +void jdns_response_append_authority(jdns_response_t *r, const jdns_rr_t *rr) +{ + ARRAY_APPEND(jdns_rr_t, r->authorityRecords, r->authorityCount, jdns_rr_copy(rr)); +} + +void jdns_response_append_additional(jdns_response_t *r, const jdns_rr_t *rr) +{ + ARRAY_APPEND(jdns_rr_t, r->additionalRecords, r->additionalCount, jdns_rr_copy(rr)); +} + +void jdns_response_remove_extra(jdns_response_t *r) +{ + ARRAY_DELETE(r->authorityRecords, r->authorityCount, jdns_rr_delete); + ARRAY_DELETE(r->additionalRecords, r->additionalCount, jdns_rr_delete); +} + +void jdns_response_remove_answer(jdns_response_t *r, int pos) +{ + jdns_rr_t *rr = r->answerRecords[pos]; + jdns_rr_delete(rr); + + // free the position + if(r->answerCount > 1) + { + memmove(r->answerRecords + pos, r->answerRecords + pos + 1, (r->answerCount - pos - 1) * sizeof(void *)); + --r->answerCount; + } + else + { + jdns_free(r->answerRecords); + r->answerRecords = 0; + r->answerCount = 0; + } +} diff --git a/thirdparty/jdns/main.cpp b/thirdparty/jdns/main.cpp new file mode 100644 index 000000000..7ecdef823 --- /dev/null +++ b/thirdparty/jdns/main.cpp @@ -0,0 +1,596 @@ +/* + * Copyright (C) 2005 Justin Karneges + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include "qjdns.h" + +QString dataToString(const QByteArray &buf) +{ + QString out; + for(int n = 0; n < buf.size(); ++n) + { + unsigned char c = (unsigned char)buf[n]; + if(c == '\\') + out += "\\\\"; + else if(c >= 0x20 && c < 0x7f) + out += c; + else + out += QString().sprintf("\\x%02x", (unsigned int)c); + } + return out; +} + +void print_record(const QJDns::Record &r) +{ + switch(r.type) + { + case QJDns::A: + printf(" A: [%s] (ttl=%d)\n", qPrintable(r.address.toString()), r.ttl); + break; + case QJDns::Aaaa: + printf(" AAAA: [%s] (ttl=%d)\n", qPrintable(r.address.toString()), r.ttl); + break; + case QJDns::Mx: + printf(" MX: [%s] priority=%d (ttl=%d)\n", r.name.data(), r.priority, r.ttl); + break; + case QJDns::Srv: + printf(" SRV: [%s] port=%d priority=%d weight=%d (ttl=%d)\n", r.name.data(), r.port, r.priority, r.weight, r.ttl); + break; + case QJDns::Cname: + printf(" CNAME: [%s] (ttl=%d)\n", r.name.data(), r.ttl); + break; + case QJDns::Ptr: + printf(" PTR: [%s] (ttl=%d)\n", r.name.data(), r.ttl); + break; + case QJDns::Txt: + { + printf(" TXT: count=%d (ttl=%d)\n", r.texts.count(), r.ttl); + for(int n = 0; n < r.texts.count(); ++n) + printf(" len=%d [%s]\n", r.texts[n].size(), qPrintable(dataToString(r.texts[n]))); + break; + } + case QJDns::Hinfo: + printf(" HINFO: [%s] [%s] (ttl=%d)\n", r.cpu.data(), r.os.data(), r.ttl); + break; + case QJDns::Ns: + printf(" NS: [%s] (ttl=%d)\n", r.name.data(), r.ttl); + break; + default: + printf(" (Unknown): type=%d, size=%d (ttl=%d)\n", r.type, r.rdata.size(), r.ttl); + break; + } +} + +class App : public QObject +{ + Q_OBJECT +public: + bool opt_debug, opt_ipv6, opt_quit; + int quit_time; + QString mode, type, name, ipaddr; + QStringList nslist; + QList pubitems; + QJDns jdns; + int req_id; + + App() + { + connect(&jdns, SIGNAL(resultsReady(int, const QJDns::Response &)), SLOT(jdns_resultsReady(int, const QJDns::Response &))); + connect(&jdns, SIGNAL(published(int)), SLOT(jdns_published(int))); + connect(&jdns, SIGNAL(error(int, QJDns::Error)), SLOT(jdns_error(int, QJDns::Error))); + connect(&jdns, SIGNAL(shutdownFinished()), SLOT(jdns_shutdownFinished())); + connect(&jdns, SIGNAL(debugLinesReady()), SLOT(jdns_debugLinesReady())); + } + + ~App() + { + } + +public slots: + void start() + { + if(mode == "uni") + { + if(!jdns.init(QJDns::Unicast, opt_ipv6 ? QHostAddress::AnyIPv6 : QHostAddress::Any)) + { + jdns_debugLinesReady(); + printf("unable to bind\n"); + emit quit(); + return; + } + + QList addrs; + for(int n = 0; n < nslist.count(); ++n) + { + QJDns::NameServer host; + QString str = nslist[n]; + if(str == "mul") + { + if(opt_ipv6) + host.address = QHostAddress("FF02::FB"); + else + host.address = QHostAddress("224.0.0.251"); + host.port = 5353; + } + else + { + int at = str.indexOf(';'); + if(at != -1) + { + host.address = QHostAddress(str.mid(0, at)); + host.port = str.mid(at + 1).toInt(); + } + else + { + host.address = QHostAddress(str); + } + } + + if(host.address.isNull() || host.port <= 0) + { + printf("bad nameserver: [%s]\n", qPrintable(nslist[n])); + emit quit(); + return; + } + addrs += host; + } + + if(addrs.isEmpty()) + addrs = QJDns::systemInfo().nameServers; + + if(addrs.isEmpty()) + { + printf("no nameservers were detected or specified\n"); + emit quit(); + return; + } + + jdns.setNameServers(addrs); + } + else + { + if(!jdns.init(QJDns::Multicast, opt_ipv6 ? QHostAddress::AnyIPv6 : QHostAddress::Any)) + { + jdns_debugLinesReady(); + printf("unable to bind\n"); + emit quit(); + return; + } + } + + if(mode == "uni" || mode == "mul") + { + int x = QJDns::A; + if(type == "ptr") + x = QJDns::Ptr; + else if(type == "srv") + x = QJDns::Srv; + else if(type == "a") + x = QJDns::A; + else if(type == "aaaa") + x = QJDns::Aaaa; + else if(type == "mx") + x = QJDns::Mx; + else if(type == "txt") + x = QJDns::Txt; + else if(type == "hinfo") + x = QJDns::Hinfo; + else if(type == "cname") + x = QJDns::Cname; + else if(type == "any") + x = QJDns::Any; + else + { + bool ok; + int y = type.toInt(&ok); + if(ok) + x = y; + } + + req_id = jdns.queryStart(name.toLatin1(), x); + printf("[%d] Querying for [%s] type=%d ...\n", req_id, qPrintable(name), x); + } + else // publish + { + for(int n = 0; n < pubitems.count(); ++n) + { + const QJDns::Record &rr = pubitems[n]; + QJDns::PublishMode m = QJDns::Unique; + if(rr.type == QJDns::Ptr) + m = QJDns::Shared; + int id = jdns.publishStart(m, rr); + printf("[%d] Publishing [%s] type=%d ...\n", id, rr.owner.data(), rr.type); + } + } + + if(opt_quit) + QTimer::singleShot(quit_time * 1000, this, SLOT(doShutdown())); + } + +signals: + void quit(); + +private slots: + void jdns_resultsReady(int id, const QJDns::Response &results) + { + printf("[%d] Results\n", id); + for(int n = 0; n < results.answerRecords.count(); ++n) + print_record(results.answerRecords[n]); + + if(mode == "uni") + jdns.shutdown(); + } + + void jdns_published(int id) + { + printf("[%d] Published\n", id); + } + + void jdns_error(int id, QJDns::Error e) + { + QString str; + if(e == QJDns::ErrorGeneric) + str = "Generic"; + else if(e == QJDns::ErrorNXDomain) + str = "NXDomain"; + else if(e == QJDns::ErrorTimeout) + str = "Timeout"; + else if(e == QJDns::ErrorConflict) + str = "Conflict"; + printf("[%d] Error: %s\n", id, qPrintable(str)); + jdns.shutdown(); + } + + void jdns_shutdownFinished() + { + emit quit(); + } + + void jdns_debugLinesReady() + { + QStringList lines = jdns.debugLines(); + if(opt_debug) + { + for(int n = 0; n < lines.count(); ++n) + printf("jdns: %s\n", qPrintable(lines[n])); + } + } + + void doShutdown() + { + jdns.shutdown(); + } +}; + +#include "main.moc" + +void usage() +{ + printf("usage: jdns (options) uni [type] [name] (nameserver(;port)|mul ...)\n"); + printf(" jdns (options) mul [type] [name]\n"); + printf(" jdns (options) pub [items ...]\n"); + printf(" jdns sys\n"); + printf("\n"); + printf("options:\n"); + printf(" -d show debug output\n"); + printf(" -6 use ipv6\n"); + printf(" -q x quit x seconds after starting\n"); + printf("\n"); + printf("uni/mul types: a aaaa ptr srv mx txt hinfo cname any\n"); + printf("pub items: ptr:name,answer srv:name,answer,port a:name,ipaddr\n"); + printf(" txt:name,str0,...,strn aaaa:name,ipaddr\n"); + printf("\n"); + printf("examples:\n"); + printf(" jdns uni a jabber.org 192.168.0.1\n"); + printf(" jdns uni srv _xmpp-client._tcp.jabber.org 192.168.0.1;53\n"); + printf(" jdns uni 10 user@host._presence._tcp.local mul\n"); + printf(" jdns mul a foobar.local\n"); + printf(" jdns mul ptr _services._dns-sd._udp.local\n"); + printf(" jdns pub a:mybox.local.,192.168.0.55\n"); + printf("\n"); +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + if(argc < 2) + { + usage(); + return 1; + } + + // get args + QStringList args; + for(int n = 1; n < argc; ++n) + args += QString(argv[n]); + + bool opt_debug = false; + bool opt_ipv6 = false; + bool opt_quit = false; + int quit_time = 0; + QString mode, type, name, ipaddr; + QStringList nslist; + QList pubitems; + + // options + for(int n = 0; n < args.count(); ++n) + { + if(args[n].left(1) == "-") + { + if(args[n] == "-d") + opt_debug = true; + else if(args[n] == "-6") + opt_ipv6 = true; + else if(args[n] == "-q") + { + if(n + 1 >= args.count()) + { + printf("need to specify number of seconds\n"); + usage(); + return 1; + } + + int x = args[n + 1].toInt(); + if(x < 1) + x = 30; + + opt_quit = true; + quit_time = x; + + args.removeAt(n + 1); + } + else + { + printf("bad option\n"); + usage(); + return 1; + } + args.removeAt(n); + --n; // adjust position + } + } + + mode = args[0]; + if(mode == "uni" || mode == "mul") + { + if(args.count() < 3) + { + printf("not enough args\n"); + usage(); + return 1; + } + type = args[1]; + name = args[2]; + if(mode == "uni") + { + for(int n = 3; n < args.count(); ++n) + nslist += QString(args[n]); + } + } + else if(mode == "pub") + { + if(args.count() < 2) + { + printf("not enough args\n"); + usage(); + return 1; + } + for(int n = 1; n < args.count(); ++n) + { + QString arg = args[n]; + int at = arg.indexOf(':'); + if(at == -1) + { + printf("missing colon\n"); + usage(); + return 1; + } + QString type = arg.mid(0, at).toLower(); + QString val = arg.mid(at + 1); + if(type == "a") + { + QStringList list = val.split(','); + if(list.count() != 2) + { + printf("bad format for A type\n"); + usage(); + return 1; + } + QHostAddress host(list[1]); + if(host.isNull() || host.protocol() != QAbstractSocket::IPv4Protocol) + { + printf("bad format for A type IP address\n"); + usage(); + return 1; + } + + QJDns::Record rec; + rec.owner = list[0].toLatin1(); + rec.type = QJDns::A; + rec.ttl = 120; + rec.haveKnown = true; + rec.address = host; + pubitems += rec; + } + else if(type == "aaaa") + { + QStringList list = val.split(','); + if(list.count() != 2) + { + printf("bad format for AAAA type\n"); + usage(); + return 1; + } + QHostAddress host(list[1]); + if(host.isNull() || host.protocol() != QAbstractSocket::IPv6Protocol) + { + printf("bad format for AAAA type IP address\n"); + usage(); + return 1; + } + + QJDns::Record rec; + rec.owner = list[0].toLatin1(); + rec.type = QJDns::Aaaa; + rec.ttl = 120; + rec.haveKnown = true; + rec.address = host; + pubitems += rec; + } + else if(type == "srv") + { + QStringList list = val.split(','); + if(list.count() != 3) + { + printf("bad format for SRV type\n"); + usage(); + return 1; + } + + QJDns::Record rec; + rec.owner = list[0].toLatin1(); + rec.type = QJDns::Srv; + rec.ttl = 120; + rec.haveKnown = true; + rec.name = list[1].toLatin1(); + rec.priority = 0; + rec.weight = 0; + rec.port = list[2].toInt(); + pubitems += rec; + } + else if(type == "ptr") + { + QStringList list = val.split(','); + if(list.count() != 2) + { + printf("bad format for PTR type\n"); + usage(); + return 1; + } + + QJDns::Record rec; + rec.owner = list[0].toLatin1(); + rec.type = QJDns::Ptr; + rec.ttl = 120; + rec.haveKnown = true; + rec.name = list[1].toLatin1(); + pubitems += rec; + } + else if(type == "txt") + { + QStringList list = val.split(','); + QList texts; + for(int n = 1; n < list.count(); ++n) + texts += list[n].toLatin1(); + + QJDns::Record rec; + rec.owner = list[0].toLatin1(); + rec.type = QJDns::Txt; + rec.ttl = 120; + rec.haveKnown = true; + rec.texts = texts; + pubitems += rec; + } + else + { + printf("bad record type [%s]\n", qPrintable(type)); + usage(); + return 1; + } + } + } + else if(mode == "sys") + { + QJDns::SystemInfo info = QJDns::systemInfo(); + + printf("DNS System Information\n"); + printf(" Name Servers:\n"); + if(!info.nameServers.isEmpty()) + { + for(int n = 0; n < info.nameServers.count(); ++n) + printf(" %s\n", qPrintable(info.nameServers[n].address.toString())); + } + else + printf(" (None)\n"); + + printf(" Domains:\n"); + if(!info.domains.isEmpty()) + { + for(int n = 0; n < info.domains.count(); ++n) + printf(" [%s]\n", info.domains[n].data()); + } + else + printf(" (None)\n"); + + printf(" Hosts:\n"); + if(!info.hosts.isEmpty()) + { + for(int n = 0; n < info.hosts.count(); ++n) + { + const QJDns::DnsHost &h = info.hosts[n]; + printf(" [%s] -> %s\n", h.name.data(), qPrintable(h.address.toString())); + } + } + else + printf(" (None)\n"); + + QHostAddress addr; + printf("Primary IPv4 Multicast Address: "); + addr = QJDns::detectPrimaryMulticast(QHostAddress::Any); + if(!addr.isNull()) + printf("%s\n", qPrintable(addr.toString())); + else + printf("(None)\n"); + printf("Primary IPv6 Multicast Address: "); + addr = QJDns::detectPrimaryMulticast(QHostAddress::AnyIPv6); + if(!addr.isNull()) + printf("%s\n", qPrintable(addr.toString())); + else + printf("(None)\n"); + + return 0; + } + else + { + usage(); + return 1; + } + + App a; + a.opt_debug = opt_debug; + a.opt_ipv6 = opt_ipv6; + a.opt_quit = opt_quit; + a.quit_time = quit_time; + a.mode = mode; + a.type = type.toLower(); + a.name = name; + a.ipaddr = ipaddr; + a.nslist = nslist; + a.pubitems = pubitems; + QObject::connect(&a, SIGNAL(quit()), &app, SLOT(quit())); + QTimer::singleShot(0, &a, SLOT(start())); + app.exec(); + return 0; +} diff --git a/thirdparty/jdns/qjdns.cpp b/thirdparty/jdns/qjdns.cpp new file mode 100644 index 000000000..158068536 --- /dev/null +++ b/thirdparty/jdns/qjdns.cpp @@ -0,0 +1,1047 @@ +/* + * Copyright (C) 2005-2008 Justin Karneges + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "qjdns.h" + +#include +#include "qjdns_sock.h" +#include "jdns.h" + +// for fprintf +#include + +namespace { + +// safeobj stuff, from qca + +void releaseAndDeleteLater(QObject *owner, QObject *obj) +{ + obj->disconnect(owner); + obj->setParent(0); + obj->deleteLater(); +} + +class SafeTimer : public QObject +{ + Q_OBJECT +public: + SafeTimer(QObject *parent = 0) : + QObject(parent) + { + t = new QTimer(this); + connect(t, SIGNAL(timeout()), SIGNAL(timeout())); + } + + ~SafeTimer() + { + releaseAndDeleteLater(this, t); + } + + int interval() const { return t->interval(); } + bool isActive() const { return t->isActive(); } + bool isSingleShot() const { return t->isSingleShot(); } + void setInterval(int msec) { t->setInterval(msec); } + void setSingleShot(bool singleShot) { t->setSingleShot(singleShot); } + int timerId() const { return t->timerId(); } + +public slots: + void start(int msec) { t->start(msec); } + void start() { t->start(); } + void stop() { t->stop(); } + +signals: + void timeout(); + +private: + QTimer *t; +}; + +} + +static jdns_string_t *qt2str(const QByteArray &in) +{ + jdns_string_t *out = jdns_string_new(); + jdns_string_set(out, (const unsigned char *)in.data(), in.size()); + return out; +} + +static QByteArray str2qt(const jdns_string_t *in) +{ + return QByteArray((const char *)in->data, in->size); +} + +static void qt2addr_set(jdns_address_t *addr, const QHostAddress &host) +{ + if(host.protocol() == QAbstractSocket::IPv6Protocol) + jdns_address_set_ipv6(addr, host.toIPv6Address().c); + else + jdns_address_set_ipv4(addr, host.toIPv4Address()); +} + +static jdns_address_t *qt2addr(const QHostAddress &host) +{ + jdns_address_t *addr = jdns_address_new(); + qt2addr_set(addr, host); + return addr; +} + +static QHostAddress addr2qt(const jdns_address_t *addr) +{ + if(addr->isIpv6) + return QHostAddress(addr->addr.v6); + else + return QHostAddress(addr->addr.v4); +} + +static QJDns::Record import_record(const jdns_rr_t *in) +{ + QJDns::Record out; + + out.owner = QByteArray((const char *)in->owner); + out.ttl = in->ttl; + out.type = in->type; + out.rdata = QByteArray((const char *)in->rdata, in->rdlength); + + // known + if(in->haveKnown) + { + int type = in->type; + + if(type == QJDns::A || type == QJDns::Aaaa) + { + out.haveKnown = true; + out.address = addr2qt(in->data.address); + } + else if(type == QJDns::Mx) + { + out.haveKnown = true; + out.name = QByteArray((const char *)in->data.server->name); + out.priority = in->data.server->priority; + } + else if(type == QJDns::Srv) + { + out.haveKnown = true; + out.name = QByteArray((const char *)in->data.server->name); + out.priority = in->data.server->priority; + out.weight = in->data.server->weight; + out.port = in->data.server->port; + } + else if(type == QJDns::Cname || type == QJDns::Ptr || type == QJDns::Ns) + { + out.haveKnown = true; + out.name = QByteArray((const char *)in->data.name); + } + else if(type == QJDns::Txt) + { + out.haveKnown = true; + out.texts.clear(); + for(int n = 0; n < in->data.texts->count; ++n) + out.texts += str2qt(in->data.texts->item[n]); + } + else if(type == QJDns::Hinfo) + { + out.haveKnown = true; + out.cpu = str2qt(in->data.hinfo.cpu); + out.os = str2qt(in->data.hinfo.os); + } + } + + return out; +} + +static jdns_rr_t *export_record(const QJDns::Record &in) +{ + jdns_rr_t *out = jdns_rr_new(); + + jdns_rr_set_owner(out, (const unsigned char *)in.owner.data()); + out->ttl = in.ttl; + + // if we have known, use that + if(in.haveKnown) + { + int type = in.type; + + if(type == QJDns::A) + { + jdns_address_t *addr = qt2addr(in.address); + jdns_rr_set_A(out, addr); + jdns_address_delete(addr); + } + else if(type == QJDns::Aaaa) + { + jdns_address_t *addr = qt2addr(in.address); + jdns_rr_set_AAAA(out, addr); + jdns_address_delete(addr); + } + else if(type == QJDns::Mx) + { + jdns_rr_set_MX(out, (const unsigned char *)in.name.data(), in.priority); + } + else if(type == QJDns::Srv) + { + jdns_rr_set_SRV(out, (const unsigned char *)in.name.data(), in.port, in.priority, in.weight); + } + else if(type == QJDns::Cname) + { + jdns_rr_set_CNAME(out, (const unsigned char *)in.name.data()); + } + else if(type == QJDns::Ptr) + { + jdns_rr_set_PTR(out, (const unsigned char *)in.name.data()); + } + else if(type == QJDns::Txt) + { + jdns_stringlist_t *list = jdns_stringlist_new(); + for(int n = 0; n < in.texts.count(); ++n) + { + jdns_string_t *str = qt2str(in.texts[n]); + jdns_stringlist_append(list, str); + jdns_string_delete(str); + } + jdns_rr_set_TXT(out, list); + jdns_stringlist_delete(list); + } + else if(type == QJDns::Hinfo) + { + jdns_string_t *cpu = qt2str(in.cpu); + jdns_string_t *os = qt2str(in.os); + jdns_rr_set_HINFO(out, cpu, os); + jdns_string_delete(cpu); + jdns_string_delete(os); + } + else if(type == QJDns::Ns) + { + jdns_rr_set_NS(out, (const unsigned char *)in.name.data()); + } + } + else + jdns_rr_set_record(out, in.type, (const unsigned char *)in.rdata.data(), in.rdata.size()); + + return out; +} + +//---------------------------------------------------------------------------- +// QJDns::NameServer +//---------------------------------------------------------------------------- +QJDns::NameServer::NameServer() +{ + port = JDNS_UNICAST_PORT; +} + +//---------------------------------------------------------------------------- +// QJDns::Record +//---------------------------------------------------------------------------- +QJDns::Record::Record() +{ + ttl = 0; + type = -1; + haveKnown = false; +} + +bool QJDns::Record::verify() const +{ + jdns_rr_t *rr = export_record(*this); + int ok = jdns_rr_verify(rr); + jdns_rr_delete(rr); + return (ok ? true : false); +} + +//---------------------------------------------------------------------------- +// QJDns +//---------------------------------------------------------------------------- +static int my_srand_done = 0; + +static void my_srand() +{ + if(my_srand_done) + return; + + // lame attempt at randomizing without srand + int count = ::time(NULL) % 128; + for(int n = 0; n < count; ++n) + rand(); + + my_srand_done = 1; +} + +class QJDns::Private : public QObject +{ + Q_OBJECT +public: + class LateError + { + public: + int source_type; // 0 for query, 1 for publish + int id; + Error error; + }; + + class LateResponse + { + public: + int id; + QJDns::Response response; + bool do_cancel; + }; + + QJDns *q; + QJDns::Mode mode; + jdns_session_t *sess; + bool shutting_down; + SafeTimer stepTrigger, debugTrigger; + SafeTimer stepTimeout; + QTime clock; + QStringList debug_strings; + bool new_debug_strings; + int next_handle; + bool need_handle; + QHash socketForHandle; + QHash handleForSocket; + int pending; + bool pending_wait; + bool complete_shutdown; + + // pointers that will point to things we are currently signalling + // about. when a query or publish is cancelled, we can use these + // pointers to extract anything we shouldn't signal. + QList *pErrors; + QList *pPublished; + QList *pResponses; + + Private(QJDns *_q) : + QObject(_q), + q(_q), + stepTrigger(this), + debugTrigger(this), + stepTimeout(this), + pErrors(0), + pPublished(0), + pResponses(0) + { + sess = 0; + shutting_down = false; + new_debug_strings = false; + pending = 0; + + connect(&stepTrigger, SIGNAL(timeout()), SLOT(doNextStepSlot())); + stepTrigger.setSingleShot(true); + + connect(&debugTrigger, SIGNAL(timeout()), SLOT(doDebug())); + debugTrigger.setSingleShot(true); + + connect(&stepTimeout, SIGNAL(timeout()), SLOT(st_timeout())); + stepTimeout.setSingleShot(true); + + my_srand(); + + clock.start(); + } + + ~Private() + { + cleanup(); + } + + void cleanup() + { + if(sess) + { + jdns_session_delete(sess); + sess = 0; + } + + shutting_down = false; + pending = 0; + + // it is safe to delete the QUdpSocket objects here without + // deleteLater, since this code path never occurs when + // a signal from those objects is on the stack + qDeleteAll(socketForHandle); + socketForHandle.clear(); + handleForSocket.clear(); + + stepTrigger.stop(); + stepTimeout.stop(); + need_handle = 0; + } + + bool init(QJDns::Mode _mode, const QHostAddress &address) + { + mode = _mode; + + jdns_callbacks_t callbacks; + callbacks.app = this; + callbacks.time_now = cb_time_now; + callbacks.rand_int = cb_rand_int; + callbacks.debug_line = cb_debug_line; + callbacks.udp_bind = cb_udp_bind; + callbacks.udp_unbind = cb_udp_unbind; + callbacks.udp_read = cb_udp_read; + callbacks.udp_write = cb_udp_write; + sess = jdns_session_new(&callbacks); + jdns_set_hold_ids_enabled(sess, 1); + next_handle = 1; + need_handle = false; + + int ret; + + jdns_address_t *baddr = qt2addr(address); + if(mode == Unicast) + { + ret = jdns_init_unicast(sess, baddr, 0); + } + else + { + jdns_address_t *maddr; + if(address.protocol() == QAbstractSocket::IPv6Protocol) + maddr = jdns_address_multicast6_new(); + else + maddr = jdns_address_multicast4_new(); + ret = jdns_init_multicast(sess, baddr, JDNS_MULTICAST_PORT, maddr); + jdns_address_delete(maddr); + } + jdns_address_delete(baddr); + + if(!ret) + { + jdns_session_delete(sess); + sess = 0; + return false; + } + return true; + } + + void setNameServers(const QList &nslist) + { + jdns_nameserverlist_t *addrs = jdns_nameserverlist_new(); + for(int n = 0; n < nslist.count(); ++n) + { + jdns_address_t *addr = qt2addr(nslist[n].address); + jdns_nameserverlist_append(addrs, addr, nslist[n].port); + jdns_address_delete(addr); + } + jdns_set_nameservers(sess, addrs); + jdns_nameserverlist_delete(addrs); + } + + void process() + { + if(!stepTrigger.isActive()) + { + stepTimeout.stop(); + stepTrigger.start(); + } + } + + void processDebug() + { + new_debug_strings = true; + if(!debugTrigger.isActive()) + debugTrigger.start(); + } + + void doNextStep() + { + if(shutting_down && complete_shutdown) + { + cleanup(); + emit q->shutdownFinished(); + return; + } + + QPointer self = this; + + int ret = jdns_step(sess); + + QList errors; + QList published; + QList responses; + bool finish_shutdown = false; + + pErrors = &errors; + pPublished = &published; + pResponses = &responses; + + while(1) + { + jdns_event_t *e = jdns_next_event(sess); + if(!e) + break; + + if(e->type == JDNS_EVENT_SHUTDOWN) + { + finish_shutdown = true; + } + else if(e->type == JDNS_EVENT_PUBLISH) + { + if(e->status != JDNS_STATUS_SUCCESS) + { + QJDns::Error error; + if(e->status == JDNS_STATUS_CONFLICT) + error = QJDns::ErrorConflict; + else + error = QJDns::ErrorGeneric; + LateError le; + le.source_type = 1; + le.id = e->id; + le.error = error; + errors += le; + } + else + { + published += e->id; + } + } + else if(e->type == JDNS_EVENT_RESPONSE) + { + if(e->status != JDNS_STATUS_SUCCESS) + { + QJDns::Error error; + if(e->status == JDNS_STATUS_NXDOMAIN) + error = QJDns::ErrorNXDomain; + else if(e->status == JDNS_STATUS_TIMEOUT) + error = QJDns::ErrorTimeout; + else + error = QJDns::ErrorGeneric; + LateError le; + le.source_type = 0; + le.id = e->id; + le.error = error; + errors += le; + } + else + { + QJDns::Response out_response; + for(int n = 0; n < e->response->answerCount; ++n) + out_response.answerRecords += import_record(e->response->answerRecords[n]); + LateResponse lr; + lr.id = e->id; + lr.response = out_response; + if(mode == Unicast) + lr.do_cancel = true; + else + lr.do_cancel = false; + responses += lr; + } + } + + jdns_event_delete(e); + } + + if(ret & JDNS_STEP_TIMER) + stepTimeout.start(jdns_next_timer(sess)); + else + stepTimeout.stop(); + + need_handle = (ret & JDNS_STEP_HANDLE); + + // read the lists safely enough so that items can be deleted + // behind our back + + while(!errors.isEmpty()) + { + LateError i = errors.takeFirst(); + if(i.source_type == 0) + jdns_cancel_query(sess, i.id); + else + jdns_cancel_publish(sess, i.id); + emit q->error(i.id, i.error); + if(!self) + return; + } + + while(!published.isEmpty()) + { + int i = published.takeFirst(); + emit q->published(i); + if(!self) + return; + } + + while(!responses.isEmpty()) + { + LateResponse i = responses.takeFirst(); + if(i.do_cancel) + jdns_cancel_query(sess, i.id); + emit q->resultsReady(i.id, i.response); + if(!self) + return; + } + + if(finish_shutdown) + { + // if we have pending udp packets to write, stick around + if(pending > 0) + { + pending_wait = true; + } + else + { + complete_shutdown = true; + process(); + } + } + + pErrors = 0; + pPublished = 0; + pResponses = 0; + } + + void removeCancelled(int id) + { + if(pErrors) + { + for(int n = 0; n < pErrors->count(); ++n) + { + if(pErrors->at(n).id == id) + { + pErrors->removeAt(n); + --n; // adjust position + } + } + } + + if(pPublished) + { + for(int n = 0; n < pPublished->count(); ++n) + { + if(pPublished->at(n) == id) + { + pPublished->removeAt(n); + --n; // adjust position + } + } + } + + if(pResponses) + { + for(int n = 0; n < pResponses->count(); ++n) + { + if(pResponses->at(n).id == id) + { + pResponses->removeAt(n); + --n; // adjust position + } + } + } + } + +private slots: + void udp_readyRead() + { + QUdpSocket *sock = (QUdpSocket *)sender(); + int handle = handleForSocket.value(sock); + + if(need_handle) + { + jdns_set_handle_readable(sess, handle); + process(); + } + else + { + // eat packet + QByteArray buf(4096, 0); + QHostAddress from_addr; + quint16 from_port; + sock->readDatagram(buf.data(), buf.size(), &from_addr, &from_port); + } + } + + void udp_bytesWritten(qint64) + { + if(pending > 0) + { + --pending; + if(shutting_down && pending_wait && pending == 0) + { + pending_wait = false; + complete_shutdown = true; + process(); + } + } + } + + void st_timeout() + { + doNextStep(); + } + + void doNextStepSlot() + { + doNextStep(); + } + + void doDebug() + { + if(new_debug_strings) + { + new_debug_strings = false; + if(!debug_strings.isEmpty()) + emit q->debugLinesReady(); + } + } + +private: + // jdns callbacks + static int cb_time_now(jdns_session_t *, void *app) + { + QJDns::Private *self = (QJDns::Private *)app; + + return self->clock.elapsed(); + } + + static int cb_rand_int(jdns_session_t *, void *) + { + return rand() % 65536; + } + + static void cb_debug_line(jdns_session_t *, void *app, const char *str) + { + QJDns::Private *self = (QJDns::Private *)app; + + self->debug_strings += QString::fromLatin1(str); + self->processDebug(); + } + + static int cb_udp_bind(jdns_session_t *, void *app, const jdns_address_t *addr, int port, const jdns_address_t *maddr) + { + QJDns::Private *self = (QJDns::Private *)app; + + // we always pass non-null to jdns_init, so this should be a valid address + QHostAddress host = addr2qt(addr); + + QUdpSocket *sock = new QUdpSocket(self); + self->connect(sock, SIGNAL(readyRead()), SLOT(udp_readyRead())); + + // use queued for bytesWritten, since qt is evil and emits before writeDatagram returns + qRegisterMetaType("qint64"); + self->connect(sock, SIGNAL(bytesWritten(qint64)), SLOT(udp_bytesWritten(qint64)), Qt::QueuedConnection); + + QUdpSocket::BindMode mode; + mode |= QUdpSocket::ShareAddress; + mode |= QUdpSocket::ReuseAddressHint; + if(!sock->bind(host, port, mode)) + { + delete sock; + return 0; + } + + if(maddr) + { + int sd = sock->socketDescriptor(); + bool ok; + int errorCode; + if(maddr->isIpv6) + ok = qjdns_sock_setMulticast6(sd, maddr->addr.v6, &errorCode); + else + ok = qjdns_sock_setMulticast4(sd, maddr->addr.v4, &errorCode); + + if(!ok) + { + delete sock; + + self->debug_strings += QString("failed to setup multicast on the socket (errorCode=%1)").arg(errorCode); + self->processDebug(); + return 0; + } + + if(maddr->isIpv6) + { + qjdns_sock_setTTL6(sd, 255); + qjdns_sock_setIPv6Only(sd); + } + else + qjdns_sock_setTTL4(sd, 255); + } + + int handle = self->next_handle++; + self->socketForHandle.insert(handle, sock); + self->handleForSocket.insert(sock, handle); + return handle; + } + + static void cb_udp_unbind(jdns_session_t *, void *app, int handle) + { + QJDns::Private *self = (QJDns::Private *)app; + + QUdpSocket *sock = self->socketForHandle.value(handle); + if(!sock) + return; + + self->socketForHandle.remove(handle); + self->handleForSocket.remove(sock); + delete sock; + } + + static int cb_udp_read(jdns_session_t *, void *app, int handle, jdns_address_t *addr, int *port, unsigned char *buf, int *bufsize) + { + QJDns::Private *self = (QJDns::Private *)app; + + QUdpSocket *sock = self->socketForHandle.value(handle); + if(!sock) + return 0; + + // nothing to read? + if(!sock->hasPendingDatagrams()) + return 0; + + QHostAddress from_addr; + quint16 from_port; + int ret = sock->readDatagram((char *)buf, *bufsize, &from_addr, &from_port); + if(ret == -1) + return 0; + + qt2addr_set(addr, from_addr); + *port = (int)from_port; + *bufsize = ret; + return 1; + } + + static int cb_udp_write(jdns_session_t *, void *app, int handle, const jdns_address_t *addr, int port, unsigned char *buf, int bufsize) + { + QJDns::Private *self = (QJDns::Private *)app; + + QUdpSocket *sock = self->socketForHandle.value(handle); + if(!sock) + return 0; + + QHostAddress host = addr2qt(addr); + int ret = sock->writeDatagram((const char *)buf, bufsize, host, port); + if(ret == -1) + { + // this can happen if the datagram to send is too big. i'm not sure what else + // may cause this. if we return 0, then jdns may try to resend the packet, + // which might not work if it is too large (causing the same error over and + // over). we'll return success to jdns, so the result is as if the packet + // was dropped. + return 1; + } + + ++self->pending; + return 1; + } +}; + +QJDns::QJDns(QObject *parent) +:QObject(parent) +{ + d = new Private(this); +} + +QJDns::~QJDns() +{ + delete d; +} + +bool QJDns::init(Mode mode, const QHostAddress &address) +{ + return d->init(mode, address); +} + +void QJDns::shutdown() +{ + d->shutting_down = true; + d->pending_wait = false; + d->complete_shutdown = false; + jdns_shutdown(d->sess); + d->process(); +} + +QStringList QJDns::debugLines() +{ + QStringList tmp = d->debug_strings; + d->debug_strings.clear(); + return tmp; +} + +QJDns::SystemInfo QJDns::systemInfo() +{ + SystemInfo out; + jdns_dnsparams_t *params = jdns_system_dnsparams(); + for(int n = 0; n < params->nameservers->count; ++n) + { + NameServer ns; + ns.address = addr2qt(params->nameservers->item[n]->address); + out.nameServers += ns; + } + for(int n = 0; n < params->domains->count; ++n) + out.domains += str2qt(params->domains->item[n]); + for(int n = 0; n < params->hosts->count; ++n) + { + DnsHost h; + h.name = str2qt(params->hosts->item[n]->name); + h.address = addr2qt(params->hosts->item[n]->address); + out.hosts += h; + } + jdns_dnsparams_delete(params); + return out; +} + +#define PROBE_BASE 20000 +#define PROBE_RANGE 100 + +QHostAddress QJDns::detectPrimaryMulticast(const QHostAddress &address) +{ + my_srand(); + + QUdpSocket *sock = new QUdpSocket; + QUdpSocket::BindMode mode; + mode |= QUdpSocket::ShareAddress; + mode |= QUdpSocket::ReuseAddressHint; + int port = -1; + for(int n = 0; n < PROBE_RANGE; ++n) + { + if(sock->bind(address, PROBE_BASE + n, mode)) + { + port = PROBE_BASE + n; + break; + } + } + if(port == -1) + { + delete sock; + return QHostAddress(); + } + + jdns_address_t *a; + if(address.protocol() == QAbstractSocket::IPv6Protocol) + a = jdns_address_multicast6_new(); + else + a = jdns_address_multicast4_new(); + QHostAddress maddr = addr2qt(a); + jdns_address_delete(a); + + if(address.protocol() == QAbstractSocket::IPv6Protocol) + { + int x; + if(!qjdns_sock_setMulticast6(sock->socketDescriptor(), maddr.toIPv6Address().c, &x)) + { + delete sock; + return QHostAddress(); + } + qjdns_sock_setTTL6(sock->socketDescriptor(), 0); + } + else + { + int x; + if(!qjdns_sock_setMulticast4(sock->socketDescriptor(), maddr.toIPv4Address(), &x)) + { + delete sock; + return QHostAddress(); + } + qjdns_sock_setTTL4(sock->socketDescriptor(), 0); + } + + QHostAddress result; + QByteArray out(128, 0); + for(int n = 0; n < out.size(); ++n) + out[n] = rand(); + if(sock->writeDatagram(out.data(), out.size(), maddr, port) == -1) + { + delete sock; + return QHostAddress(); + } + while(1) + { + if(!sock->waitForReadyRead(1000)) + { + fprintf(stderr, "QJDns::detectPrimaryMulticast: timeout while checking %s\n", qPrintable(address.toString())); + delete sock; + return QHostAddress(); + } + QByteArray in(128, 0); + QHostAddress from_addr; + quint16 from_port; + int ret = sock->readDatagram(in.data(), in.size(), &from_addr, &from_port); + if(ret == -1) + { + delete sock; + return QHostAddress(); + } + + if(from_port != port) + continue; + in.resize(ret); + if(in != out) + continue; + + result = from_addr; + break; + } + delete sock; + + return result; +} + +void QJDns::setNameServers(const QList &list) +{ + d->setNameServers(list); +} + +int QJDns::queryStart(const QByteArray &name, int type) +{ + int id = jdns_query(d->sess, (const unsigned char *)name.data(), type); + d->process(); + return id; +} + +void QJDns::queryCancel(int id) +{ + jdns_cancel_query(d->sess, id); + d->removeCancelled(id); + d->process(); +} + +int QJDns::publishStart(PublishMode m, const Record &record) +{ + jdns_rr_t *rr = export_record(record); + + int pubmode; + if(m == QJDns::Unique) + pubmode = JDNS_PUBLISH_UNIQUE; + else + pubmode = JDNS_PUBLISH_SHARED; + + int id = jdns_publish(d->sess, pubmode, rr); + jdns_rr_delete(rr); + d->process(); + return id; +} + +void QJDns::publishUpdate(int id, const Record &record) +{ + jdns_rr_t *rr = export_record(record); + + jdns_update_publish(d->sess, id, rr); + jdns_rr_delete(rr); + d->process(); +} + +void QJDns::publishCancel(int id) +{ + jdns_cancel_publish(d->sess, id); + d->removeCancelled(id); + d->process(); +} + +//#include "qjdns.moc" diff --git a/thirdparty/jdns/qjdns.h b/thirdparty/jdns/qjdns.h new file mode 100644 index 000000000..6db0ccc31 --- /dev/null +++ b/thirdparty/jdns/qjdns.h @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2005,2006 Justin Karneges + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// this is the Qt wrapper to jdns. it requires Qt 4.1+ + +#ifndef QJDNS_H +#define QJDNS_H + +#include +#include + +class QJDns : public QObject +{ + Q_OBJECT +public: + enum Mode + { + Unicast, + Multicast + }; + + enum PublishMode + { + Unique, + Shared + }; + + enum Type + { + A = 1, + Aaaa = 28, + Mx = 15, + Srv = 33, + Cname = 5, + Ptr = 12, + Txt = 16, + Hinfo = 13, + Ns = 2, + Any = 255 + }; + + enum Error + { + ErrorGeneric, + ErrorNXDomain, // query only + ErrorTimeout, // query only + ErrorConflict // publish only + }; + + class NameServer + { + public: + QHostAddress address; + int port; + + NameServer(); + }; + + class DnsHost + { + public: + QByteArray name; + QHostAddress address; + }; + + class SystemInfo + { + public: + QList nameServers; + QList domains; + QList hosts; + }; + + class Record + { + public: + QByteArray owner; + int ttl; + int type; + QByteArray rdata; + bool haveKnown; + + // known + QHostAddress address; // for A, Aaaa + QByteArray name; // for Mx, Srv, Cname, Ptr, Ns + int priority; // for Mx, Srv + int weight; // for Srv + int port; // for Srv + QList texts; // for Txt + QByteArray cpu; // for Hinfo + QByteArray os; // for Hinfo + + Record(); + bool verify() const; + }; + + class Response + { + public: + QList answerRecords; + QList authorityRecords; + QList additionalRecords; + }; + + QJDns(QObject *parent = 0); + ~QJDns(); + + bool init(Mode mode, const QHostAddress &address); + void shutdown(); + QStringList debugLines(); + + static SystemInfo systemInfo(); + static QHostAddress detectPrimaryMulticast(const QHostAddress &address); + + void setNameServers(const QList &list); + + int queryStart(const QByteArray &name, int type); + void queryCancel(int id); + + // for multicast mode only + int publishStart(PublishMode m, const Record &record); + void publishUpdate(int id, const Record &record); + void publishCancel(int id); + +signals: + void resultsReady(int id, const QJDns::Response &results); + void published(int id); + void error(int id, QJDns::Error e); + void shutdownFinished(); + void debugLinesReady(); + +private: + class Private; + friend class Private; + Private *d; +}; + +#endif diff --git a/thirdparty/jdns/qjdns_sock.cpp b/thirdparty/jdns/qjdns_sock.cpp new file mode 100644 index 000000000..54620bccc --- /dev/null +++ b/thirdparty/jdns/qjdns_sock.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2005,2006 Justin Karneges + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "qjdns_sock.h" + +#include +#include +#include +#include + +#ifdef Q_OS_WIN +# include +# include +#endif + +#ifdef Q_OS_UNIX +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#ifndef QT_NO_IPV6 +# define HAVE_IPV6 +# ifndef s6_addr +# define IPPROTO_IPV6 41 + struct in6_addr + { + union + { + unsigned char _S6_u8[16]; + unsigned short _S6_u16[8]; + unsigned long _S6_u32[4]; + } _S6_un; + }; +# define s6_addr _S6_un._S6_u8 +# endif +# ifndef IPV6_JOIN_GROUP +# define IPV6_JOIN_GROUP 12 +# define IPV6_MULTICAST_HOPS 10 + struct ipv6_mreq + { + struct in6_addr ipv6mr_multiaddr; + unsigned int ipv6mr_interface; + }; +# endif +#endif + +static int get_last_error() +{ + int x; +#ifdef Q_OS_WIN + x = WSAGetLastError(); +#else + x = errno; +#endif + return x; +} + +bool qjdns_sock_setMulticast4(int s, unsigned long int addr, int *errorCode) +{ + int ret; + struct ip_mreq mc; + + memset(&mc, 0, sizeof(mc)); + mc.imr_multiaddr.s_addr = htonl(addr); + mc.imr_interface.s_addr = INADDR_ANY; + + ret = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&mc, sizeof(mc)); + if(ret != 0) + { + if(errorCode) + *errorCode = get_last_error(); + return false; + } + return true; +} + +bool qjdns_sock_setMulticast6(int s, unsigned char *addr, int *errorCode) +{ +#ifdef HAVE_IPV6 + int ret; + struct ipv6_mreq mc; + + memset(&mc, 0, sizeof(mc)); + memcpy(mc.ipv6mr_multiaddr.s6_addr, addr, 16); + mc.ipv6mr_interface = 0; + + ret = setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, (const char *)&mc, sizeof(mc)); + if(ret != 0) + { + if(errorCode) + *errorCode = get_last_error(); + return false; + } + return true; +#else + Q_UNUSED(s); + Q_UNUSED(addr); + Q_UNUSED(errorCode); + return false; +#endif +} + +bool qjdns_sock_setTTL4(int s, int ttl) +{ + unsigned char cttl; + int ret, ittl; + + cttl = ttl; + ittl = ttl; + + // IP_MULTICAST_TTL might take 1 byte or 4, try both + ret = setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (const char *)&cttl, sizeof(cttl)); + if(ret != 0) + { + ret = setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (const char *)&ittl, sizeof(ittl)); + if(ret != 0) + return false; + } + return true; +} + +bool qjdns_sock_setTTL6(int s, int ttl) +{ +#ifdef HAVE_IPV6 + unsigned char cttl; + int ret, ittl; + + cttl = ttl; + ittl = ttl; + + // IPV6_MULTICAST_HOPS might take 1 byte or 4, try both + ret = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (const char *)&cttl, sizeof(cttl)); + if(ret != 0) + { + ret = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (const char *)&ittl, sizeof(ittl)); + if(ret != 0) + return false; + } + return true; +#else + Q_UNUSED(s); + Q_UNUSED(ttl); + return false; +#endif +} + +bool qjdns_sock_setIPv6Only(int s) +{ +#if defined(HAVE_IPV6) && defined(IPV6_V6ONLY) + int x = 1; + if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&x, sizeof(x)) != 0) + return false; + return true; +#else + Q_UNUSED(s); + return false; +#endif +} diff --git a/thirdparty/jdns/qjdns_sock.h b/thirdparty/jdns/qjdns_sock.h new file mode 100644 index 000000000..13d388d6f --- /dev/null +++ b/thirdparty/jdns/qjdns_sock.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2005,2006 Justin Karneges + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef QJDNS_SOCK_H +#define QJDNS_SOCK_H + +bool qjdns_sock_setMulticast4(int s, unsigned long int addr, int *errorCode); +bool qjdns_sock_setMulticast6(int s, unsigned char *addr, int *errorCode); +bool qjdns_sock_setTTL4(int s, int ttl); +bool qjdns_sock_setTTL6(int s, int ttl); +bool qjdns_sock_setIPv6Only(int s); + +#endif