diff options
Diffstat (limited to 'mDNSResponder/mDNSShared/dnsextd.c')
-rw-r--r-- | mDNSResponder/mDNSShared/dnsextd.c | 3150 |
1 files changed, 3150 insertions, 0 deletions
diff --git a/mDNSResponder/mDNSShared/dnsextd.c b/mDNSResponder/mDNSShared/dnsextd.c new file mode 100644 index 00000000..aa06650a --- /dev/null +++ b/mDNSResponder/mDNSShared/dnsextd.c @@ -0,0 +1,3150 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if __APPLE__ +// In Mac OS X 10.5 and later trying to use the daemon function gives a “‘daemon’ is deprecated” +// error, which prevents compilation because we build with "-Werror". +// Since this is supposed to be portable cross-platform code, we don't care that daemon is +// deprecated on Mac OS X 10.5, so we use this preprocessor trick to eliminate the error message. +#define daemon yes_we_know_that_daemon_is_deprecated_in_os_x_10_5_thankyou +#endif + +#include <signal.h> +#include <pthread.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <syslog.h> +#include <string.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <time.h> +#include <errno.h> + +#if __APPLE__ +#undef daemon +extern int daemon(int, int); +#endif + +// Solaris doesn't have daemon(), so we define it here +#ifdef NOT_HAVE_DAEMON +#include "../mDNSPosix/mDNSUNP.h" // For daemon() +#endif // NOT_HAVE_DAEMON + +#include "dnsextd.h" +#include "../mDNSShared/uds_daemon.h" +#include "../mDNSShared/dnssd_ipc.h" +#include "../mDNSCore/uDNS.h" +#include "../mDNSShared/DebugServices.h" + +// Compatibility workaround +#ifndef AF_LOCAL +#define AF_LOCAL AF_UNIX +#endif + +// +// Constants +// +mDNSexport const char ProgramName[] = "dnsextd"; + +#define LOOPBACK "127.0.0.1" +#if !defined(LISTENQ) +# define LISTENQ 128 // tcp connection backlog +#endif +#define RECV_BUFLEN 9000 +#define LEASETABLE_INIT_NBUCKETS 256 // initial hashtable size (doubles as table fills) +#define EXPIRATION_INTERVAL 300 // check for expired records every 5 minutes +#define SRV_TTL 7200 // TTL For _dns-update SRV records +#define CONFIG_FILE "/etc/dnsextd.conf" +#define TCP_SOCKET_FLAGS kTCPSocketFlags_UseTLS + +// LLQ Lease bounds (seconds) +#define LLQ_MIN_LEASE (15 * 60) +#define LLQ_MAX_LEASE (120 * 60) +#define LLQ_LEASE_FUDGE 60 + +// LLQ SOA poll interval (microseconds) +#define LLQ_MONITOR_ERR_INTERVAL (60 * 1000000) +#define LLQ_MONITOR_INTERVAL 250000 +#ifdef SIGINFO +#define INFO_SIGNAL SIGINFO +#else +#define INFO_SIGNAL SIGUSR1 +#endif + +#define SAME_INADDR(x,y) (*((mDNSu32 *)&x) == *((mDNSu32 *)&y)) + +// +// Data Structures +// Structs/fields that must be locked for thread safety are explicitly commented +// + +// args passed to UDP request handler thread as void* + +typedef struct +{ + PktMsg pkt; + struct sockaddr_in cliaddr; + DaemonInfo *d; + int sd; +} UDPContext; + +// args passed to TCP request handler thread as void* +typedef struct +{ + PktMsg pkt; + struct sockaddr_in cliaddr; + TCPSocket *sock; // socket connected to client + DaemonInfo *d; +} TCPContext; + +// args passed to UpdateAnswerList thread as void* +typedef struct +{ + DaemonInfo *d; + AnswerListElem *a; +} UpdateAnswerListArgs; + +// +// Global Variables +// + +// booleans to determine runtime output +// read-only after initialization (no mutex protection) +static mDNSBool foreground = 0; +static mDNSBool verbose = 0; + +// globals set via signal handler (accessed exclusively by main select loop and signal handler) +static mDNSBool terminate = 0; +static mDNSBool dumptable = 0; +static mDNSBool hangup = 0; + +// global for config file location +static char * cfgfile = NULL; + +// +// Logging Routines +// Log messages are delivered to syslog unless -f option specified +// + +// common message logging subroutine +mDNSlocal void PrintLog(const char *buffer) +{ + if (foreground) + { + fprintf(stderr,"%s\n", buffer); + fflush(stderr); + } + else + { + openlog("dnsextd", LOG_CONS, LOG_DAEMON); + syslog(LOG_ERR, "%s", buffer); + closelog(); + } +} + +// Verbose Logging (conditional on -v option) +mDNSlocal void VLog(const char *format, ...) +{ + char buffer[512]; + va_list ptr; + + if (!verbose) return; + va_start(ptr,format); + buffer[mDNS_vsnprintf((char *)buffer, sizeof(buffer), format, ptr)] = 0; + va_end(ptr); + PrintLog(buffer); +} + +// Unconditional Logging +mDNSlocal void Log(const char *format, ...) +{ + char buffer[512]; + va_list ptr; + + va_start(ptr,format); + buffer[mDNS_vsnprintf((char *)buffer, sizeof(buffer), format, ptr)] = 0; + va_end(ptr); + PrintLog(buffer); +} + +// Error Logging +// prints message "dnsextd <function>: <operation> - <error message>" +// must be compiled w/ -D_REENTRANT for thread-safe errno usage +mDNSlocal void LogErr(const char *fn, const char *operation) +{ + char buf[512], errbuf[256]; + strerror_r(errno, errbuf, sizeof(errbuf)); + snprintf(buf, sizeof(buf), "%s: %s - %s", fn, operation, errbuf); + PrintLog(buf); +} + +// +// Networking Utility Routines +// + +// Convert DNS Message Header from Network to Host byte order +mDNSlocal void HdrNToH(PktMsg *pkt) +{ + // Read the integer parts which are in IETF byte-order (MSB first, LSB second) + mDNSu8 *ptr = (mDNSu8 *)&pkt->msg.h.numQuestions; + pkt->msg.h.numQuestions = (mDNSu16)((mDNSu16)ptr[0] << 8 | ptr[1]); + pkt->msg.h.numAnswers = (mDNSu16)((mDNSu16)ptr[2] << 8 | ptr[3]); + pkt->msg.h.numAuthorities = (mDNSu16)((mDNSu16)ptr[4] << 8 | ptr[5]); + pkt->msg.h.numAdditionals = (mDNSu16)((mDNSu16)ptr[6] << 8 | ptr[7]); +} + +// Convert DNS Message Header from Host to Network byte order +mDNSlocal void HdrHToN(PktMsg *pkt) +{ + mDNSu16 numQuestions = pkt->msg.h.numQuestions; + mDNSu16 numAnswers = pkt->msg.h.numAnswers; + mDNSu16 numAuthorities = pkt->msg.h.numAuthorities; + mDNSu16 numAdditionals = pkt->msg.h.numAdditionals; + mDNSu8 *ptr = (mDNSu8 *)&pkt->msg.h.numQuestions; + + // Put all the integer values in IETF byte-order (MSB first, LSB second) + *ptr++ = (mDNSu8)(numQuestions >> 8); + *ptr++ = (mDNSu8)(numQuestions & 0xFF); + *ptr++ = (mDNSu8)(numAnswers >> 8); + *ptr++ = (mDNSu8)(numAnswers & 0xFF); + *ptr++ = (mDNSu8)(numAuthorities >> 8); + *ptr++ = (mDNSu8)(numAuthorities & 0xFF); + *ptr++ = (mDNSu8)(numAdditionals >> 8); + *ptr++ = (mDNSu8)(numAdditionals & 0xFF); +} + + +// Add socket to event loop + +mDNSlocal mStatus AddSourceToEventLoop( DaemonInfo * self, TCPSocket *sock, EventCallback callback, void *context ) +{ + EventSource * newSource; + mStatus err = mStatus_NoError; + + if ( self->eventSources.LinkOffset == 0 ) + { + InitLinkedList( &self->eventSources, offsetof( EventSource, next)); + } + + newSource = ( EventSource*) malloc( sizeof *newSource ); + if ( newSource == NULL ) + { + err = mStatus_NoMemoryErr; + goto exit; + } + + newSource->callback = callback; + newSource->context = context; + newSource->sock = sock; + newSource->fd = mDNSPlatformTCPGetFD( sock ); + + AddToTail( &self->eventSources, newSource ); + +exit: + + return err; +} + + +// Remove socket from event loop + +mDNSlocal mStatus RemoveSourceFromEventLoop( DaemonInfo * self, TCPSocket *sock ) +{ + EventSource * source; + mStatus err; + + for ( source = ( EventSource* ) self->eventSources.Head; source; source = source->next ) + { + if ( source->sock == sock ) + { + RemoveFromList( &self->eventSources, source ); + + free( source ); + err = mStatus_NoError; + goto exit; + } + } + + err = mStatus_NoSuchNameErr; + +exit: + + return err; +} + +// create a socket connected to nameserver +// caller terminates connection via close() +mDNSlocal TCPSocket *ConnectToServer(DaemonInfo *d) +{ + int ntries = 0, retry = 0; + + while (1) + { + mDNSIPPort port = zeroIPPort; + int fd; + + TCPSocket *sock = mDNSPlatformTCPSocket( NULL, 0, &port, mDNSfalse ); + if ( !sock ) { LogErr("ConnectToServer", "socket"); return NULL; } + fd = mDNSPlatformTCPGetFD( sock ); + if (!connect( fd, (struct sockaddr *)&d->ns_addr, sizeof(d->ns_addr))) return sock; + mDNSPlatformTCPCloseConnection( sock ); + if (++ntries < 10) + { + LogErr("ConnectToServer", "connect"); + Log("ConnectToServer - retrying connection"); + if (!retry) retry = 500000 + random() % 500000; + usleep(retry); + retry *= 2; + } + else { Log("ConnectToServer - %d failed attempts. Aborting.", ntries); return NULL; } + } +} + +// send an entire block of data over a connected socket +mDNSlocal int MySend(TCPSocket *sock, const void *msg, int len) +{ + int selectval, n, nsent = 0; + fd_set wset; + struct timeval timeout = { 3, 0 }; // until we remove all calls from main thread, keep timeout short + + while (nsent < len) + { + int fd; + + FD_ZERO(&wset); + + fd = mDNSPlatformTCPGetFD( sock ); + + FD_SET( fd, &wset ); + selectval = select( fd+1, NULL, &wset, NULL, &timeout); + if (selectval < 0) { LogErr("MySend", "select"); return -1; } + if (!selectval || !FD_ISSET(fd, &wset)) { Log("MySend - timeout"); return -1; } + + n = mDNSPlatformWriteTCP( sock, ( char* ) msg + nsent, len - nsent); + + if (n < 0) { LogErr("MySend", "send"); return -1; } + nsent += n; + } + return 0; +} + +// Transmit a DNS message, prefixed by its length, over TCP, blocking if necessary +mDNSlocal int SendPacket(TCPSocket *sock, PktMsg *pkt) +{ + // send the lenth, in network byte order + mDNSu16 len = htons((mDNSu16)pkt->len); + if (MySend(sock, &len, sizeof(len)) < 0) return -1; + + // send the message + VLog("SendPacket Q:%d A:%d A:%d A:%d ", + ntohs(pkt->msg.h.numQuestions), + ntohs(pkt->msg.h.numAnswers), + ntohs(pkt->msg.h.numAuthorities), + ntohs(pkt->msg.h.numAdditionals)); + return MySend(sock, &pkt->msg, pkt->len); +} + +// Receive len bytes, waiting until we have all of them. +// Returns number of bytes read (which should always be the number asked for). +static int my_recv(TCPSocket *sock, void *const buf, const int len, mDNSBool * closed) +{ + // Don't use "MSG_WAITALL"; it returns "Invalid argument" on some Linux versions; + // use an explicit while() loop instead. + // Also, don't try to do '+=' arithmetic on the original "void *" pointer -- + // arithmetic on "void *" pointers is compiler-dependent. + + fd_set rset; + struct timeval timeout = { 3, 0 }; // until we remove all calls from main thread, keep timeout short + int selectval, remaining = len; + char *ptr = (char *)buf; + ssize_t num_read; + + while (remaining) + { + int fd; + + fd = mDNSPlatformTCPGetFD( sock ); + + FD_ZERO(&rset); + FD_SET(fd, &rset); + selectval = select(fd+1, &rset, NULL, NULL, &timeout); + if (selectval < 0) { LogErr("my_recv", "select"); return -1; } + if (!selectval || !FD_ISSET(fd, &rset)) + { + Log("my_recv - timeout"); + return -1; + } + + num_read = mDNSPlatformReadTCP( sock, ptr, remaining, closed ); + + if (((num_read == 0) && *closed) || (num_read < 0) || (num_read > remaining)) return -1; + if (num_read == 0) return 0; + ptr += num_read; + remaining -= num_read; + } + return(len); +} + +// Return a DNS Message read off of a TCP socket, or NULL on failure +// If storage is non-null, result is placed in that buffer. Otherwise, +// returned value is allocated with Malloc, and contains sufficient extra +// storage for a Lease OPT RR + +mDNSlocal PktMsg* +RecvPacket +( + TCPSocket * sock, + PktMsg * storage, + mDNSBool * closed +) +{ + int nread; + int allocsize; + mDNSu16 msglen = 0; + PktMsg * pkt = NULL; + unsigned int srclen; + int fd; + mStatus err = 0; + + fd = mDNSPlatformTCPGetFD( sock ); + + nread = my_recv( sock, &msglen, sizeof( msglen ), closed ); + + require_action_quiet( nread != -1, exit, err = mStatus_UnknownErr ); + require_action_quiet( nread > 0, exit, err = mStatus_NoError ); + + msglen = ntohs( msglen ); + require_action_quiet( nread == sizeof( msglen ), exit, err = mStatus_UnknownErr; Log( "Could not read length field of message") ); + + if ( storage ) + { + require_action_quiet( msglen <= sizeof( storage->msg ), exit, err = mStatus_UnknownErr; Log( "RecvPacket: provided buffer too small." ) ); + pkt = storage; + } + else + { + // buffer extra space to add an OPT RR + + if ( msglen > sizeof(DNSMessage)) + { + allocsize = sizeof(PktMsg) - sizeof(DNSMessage) + msglen; + } + else + { + allocsize = sizeof(PktMsg); + } + + pkt = malloc(allocsize); + require_action_quiet( pkt, exit, err = mStatus_NoMemoryErr; LogErr( "RecvPacket", "malloc" ) ); + mDNSPlatformMemZero( pkt, sizeof( *pkt ) ); + } + + pkt->len = msglen; + srclen = sizeof(pkt->src); + + if ( getpeername( fd, ( struct sockaddr* ) &pkt->src, &srclen ) || ( srclen != sizeof( pkt->src ) ) ) + { + LogErr("RecvPacket", "getpeername"); + mDNSPlatformMemZero(&pkt->src, sizeof(pkt->src)); + } + + nread = my_recv(sock, &pkt->msg, msglen, closed ); + require_action_quiet( nread >= 0, exit, err = mStatus_UnknownErr ; LogErr( "RecvPacket", "recv" ) ); + require_action_quiet( nread == msglen, exit, err = mStatus_UnknownErr ; Log( "Could not read entire message" ) ); + require_action_quiet( pkt->len >= sizeof( DNSMessageHeader ), exit, err = mStatus_UnknownErr ; Log( "RecvPacket: Message too short (%d bytes)", pkt->len ) ); + +exit: + + if ( err && pkt ) + { + if ( pkt != storage ) + { + free(pkt); + } + + pkt = NULL; + } + + return pkt; +} + + +mDNSlocal DNSZone* +FindZone +( + DaemonInfo * self, + domainname * name +) +{ + DNSZone * zone; + + for ( zone = self->zones; zone; zone = zone->next ) + { + if ( SameDomainName( &zone->name, name ) ) + { + break; + } + } + + return zone; +} + + +mDNSlocal mDNSBool +ZoneHandlesName +( + const domainname * zname, + const domainname * dname +) +{ + mDNSu16 i = DomainNameLength( zname ); + mDNSu16 j = DomainNameLength( dname ); + + if ( ( i == ( MAX_DOMAIN_NAME + 1 ) ) || ( j == ( MAX_DOMAIN_NAME + 1 ) ) || ( i > j ) || ( memcmp( zname->c, dname->c + ( j - i ), i ) != 0 ) ) + { + return mDNSfalse; + } + + return mDNStrue; +} + + +mDNSlocal mDNSBool IsQuery( PktMsg * pkt ) +{ + return ( pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ) == (mDNSu8) ( kDNSFlag0_QR_Query | kDNSFlag0_OP_StdQuery ); +} + + +mDNSlocal mDNSBool IsUpdate( PktMsg * pkt ) +{ + return ( pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ) == (mDNSu8) ( kDNSFlag0_OP_Update ); +} + + +mDNSlocal mDNSBool IsNotify(PktMsg *pkt) +{ + return ( pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ) == ( mDNSu8) ( kDNSFlag0_OP_Notify ); +} + + +mDNSlocal mDNSBool IsLLQRequest(PktMsg *pkt) +{ + const mDNSu8 *ptr = NULL, *end = (mDNSu8 *)&pkt->msg + pkt->len; + LargeCacheRecord lcr; + int i; + mDNSBool result = mDNSfalse; + + HdrNToH(pkt); + if ((mDNSu8)(pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask) != (mDNSu8)(kDNSFlag0_QR_Query | kDNSFlag0_OP_StdQuery)) goto end; + + if (!pkt->msg.h.numAdditionals) goto end; + ptr = LocateAdditionals(&pkt->msg, end); + if (!ptr) goto end; + + // find last Additional info. + for (i = 0; i < pkt->msg.h.numAdditionals; i++) + { + ptr = GetLargeResourceRecord(NULL, &pkt->msg, ptr, end, 0, kDNSRecordTypePacketAdd, &lcr); + if (!ptr) { Log("Unable to read additional record"); goto end; } + } + + if ( lcr.r.resrec.rrtype == kDNSType_OPT && lcr.r.resrec.rdlength >= DNSOpt_LLQData_Space && lcr.r.resrec.rdata->u.opt[0].opt == kDNSOpt_LLQ ) + { + result = mDNStrue; + } + +end: + HdrHToN(pkt); + return result; +} + +// !!!KRS implement properly +mDNSlocal mDNSBool IsLLQAck(PktMsg *pkt) +{ + if ((pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ) == (mDNSu8) ( kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery ) && + pkt->msg.h.numQuestions && !pkt->msg.h.numAnswers && !pkt->msg.h.numAuthorities) return mDNStrue; + return mDNSfalse; +} + + +mDNSlocal mDNSBool +IsPublicSRV +( + DaemonInfo * self, + DNSQuestion * q +) +{ + DNameListElem * elem; + mDNSBool ret = mDNSfalse; + int i = ( int ) DomainNameLength( &q->qname ) - 1; + + for ( elem = self->public_names; elem; elem = elem->next ) + { + int j = ( int ) DomainNameLength( &elem->name ) - 1; + + if ( i > j ) + { + for ( ; i >= 0; i--, j-- ) + { + if ( q->qname.c[ i ] != elem->name.c[ j ] ) + { + ret = mDNStrue; + goto exit; + } + } + } + } + +exit: + + return ret; +} + + +mDNSlocal void +SetZone +( + DaemonInfo * self, + PktMsg * pkt +) +{ + domainname zname; + mDNSu8 QR_OP; + const mDNSu8 * ptr = pkt->msg.data; + mDNSBool exception = mDNSfalse; + + // Initialize + + pkt->zone = NULL; + pkt->isZonePublic = mDNStrue; + zname.c[0] = '\0'; + + // Figure out what type of packet this is + + QR_OP = ( mDNSu8 ) ( pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ); + + if ( IsQuery( pkt ) ) + { + DNSQuestion question; + + // It's a query + + ptr = getQuestion( &pkt->msg, ptr, ( ( mDNSu8* ) &pkt->msg ) + pkt->len, NULL, &question ); + + AppendDomainName( &zname, &question.qname ); + + exception = ( ( question.qtype == kDNSType_SOA ) || ( question.qtype == kDNSType_NS ) || ( ( question.qtype == kDNSType_SRV ) && IsPublicSRV( self, &question ) ) ); + } + else if ( IsUpdate( pkt ) ) + { + DNSQuestion question; + + // It's an update. The format of the zone section is the same as the format for the question section + // according to RFC 2136, so we'll just treat this as a question so we can get at the zone. + + ptr = getQuestion( &pkt->msg, ptr, ( ( mDNSu8* ) &pkt->msg ) + pkt->len, NULL, &question ); + + AppendDomainName( &zname, &question.qname ); + + exception = mDNSfalse; + } + + if ( zname.c[0] != '\0' ) + { + // Find the right zone + + for ( pkt->zone = self->zones; pkt->zone; pkt->zone = pkt->zone->next ) + { + if ( ZoneHandlesName( &pkt->zone->name, &zname ) ) + { + VLog( "found correct zone %##s for query", pkt->zone->name.c ); + + pkt->isZonePublic = ( ( pkt->zone->type == kDNSZonePublic ) || exception ); + + VLog( "zone %##s is %s", pkt->zone->name.c, ( pkt->isZonePublic ) ? "public" : "private" ); + + break; + } + } + } +} + + +mDNSlocal int +UDPServerTransaction(const DaemonInfo *d, const PktMsg *request, PktMsg *reply, mDNSBool *trunc) +{ + fd_set rset; + struct timeval timeout = { 3, 0 }; // until we remove all calls from main thread, keep timeout short + int sd; + int res; + mStatus err = mStatus_NoError; + + // Initialize + + *trunc = mDNSfalse; + + // Create a socket + + sd = socket( AF_INET, SOCK_DGRAM, 0 ); + require_action( sd >= 0, exit, err = mStatus_UnknownErr; LogErr( "UDPServerTransaction", "socket" ) ); + + // Send the packet to the nameserver + + VLog("UDPServerTransaction Q:%d A:%d A:%d A:%d ", + ntohs(request->msg.h.numQuestions), + ntohs(request->msg.h.numAnswers), + ntohs(request->msg.h.numAuthorities), + ntohs(request->msg.h.numAdditionals)); + res = sendto( sd, (char *)&request->msg, request->len, 0, ( struct sockaddr* ) &d->ns_addr, sizeof( d->ns_addr ) ); + require_action( res == (int) request->len, exit, err = mStatus_UnknownErr; LogErr( "UDPServerTransaction", "sendto" ) ); + + // Wait for reply + + FD_ZERO( &rset ); + FD_SET( sd, &rset ); + res = select( sd + 1, &rset, NULL, NULL, &timeout ); + require_action( res >= 0, exit, err = mStatus_UnknownErr; LogErr( "UDPServerTransaction", "select" ) ); + require_action( ( res > 0 ) && FD_ISSET( sd, &rset ), exit, err = mStatus_UnknownErr; Log( "UDPServerTransaction - timeout" ) ); + + // Receive reply + + reply->len = recvfrom( sd, &reply->msg, sizeof(reply->msg), 0, NULL, NULL ); + require_action( ( ( int ) reply->len ) >= 0, exit, err = mStatus_UnknownErr; LogErr( "UDPServerTransaction", "recvfrom" ) ); + require_action( reply->len >= sizeof( DNSMessageHeader ), exit, err = mStatus_UnknownErr; Log( "UDPServerTransaction - Message too short (%d bytes)", reply->len ) ); + + // Check for truncation bit + + if ( reply->msg.h.flags.b[0] & kDNSFlag0_TC ) + { + *trunc = mDNStrue; + } + +exit: + + if ( sd >= 0 ) + { + close( sd ); + } + + return err; +} + +// +// Dynamic Update Utility Routines +// + +// check if a request and server response complete a successful dynamic update +mDNSlocal mDNSBool SuccessfulUpdateTransaction(PktMsg *request, PktMsg *reply) +{ + char buf[32]; + char *vlogmsg = NULL; + + // check messages + if (!request || !reply) { vlogmsg = "NULL message"; goto failure; } + if (request->len < sizeof(DNSMessageHeader) || reply->len < sizeof(DNSMessageHeader)) { vlogmsg = "Malformatted message"; goto failure; } + + // check request operation + if ((request->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask) != (request->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask)) + { vlogmsg = "Request opcode not an update"; goto failure; } + + // check result + if ((reply->msg.h.flags.b[1] & kDNSFlag1_RC_Mask)) { vlogmsg = "Reply contains non-zero rcode"; goto failure; } + if ((reply->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask) != (kDNSFlag0_OP_Update | kDNSFlag0_QR_Response)) + { vlogmsg = "Reply opcode not an update response"; goto failure; } + + VLog("Successful update from %s", inet_ntop(AF_INET, &request->src.sin_addr, buf, 32)); + return mDNStrue; + +failure: + VLog("Request %s: %s", inet_ntop(AF_INET, &request->src.sin_addr, buf, 32), vlogmsg); + return mDNSfalse; +} + +// Allocate an appropriately sized CacheRecord and copy data from original. +// Name pointer in CacheRecord object is set to point to the name specified +// +mDNSlocal CacheRecord *CopyCacheRecord(const CacheRecord *orig, domainname *name) +{ + CacheRecord *cr; + size_t size = sizeof(*cr); + if (orig->resrec.rdlength > InlineCacheRDSize) size += orig->resrec.rdlength - InlineCacheRDSize; + cr = malloc(size); + if (!cr) { LogErr("CopyCacheRecord", "malloc"); return NULL; } + memcpy(cr, orig, size); + cr->resrec.rdata = (RData*)&cr->smallrdatastorage; + cr->resrec.name = name; + + return cr; +} + + +// +// Lease Hashtable Utility Routines +// + +// double hash table size +// caller must lock table prior to invocation +mDNSlocal void RehashTable(DaemonInfo *d) +{ + RRTableElem *ptr, *tmp, **new; + int i, bucket, newnbuckets = d->nbuckets * 2; + + VLog("Rehashing lease table (new size %d buckets)", newnbuckets); + new = malloc(sizeof(RRTableElem *) * newnbuckets); + if (!new) { LogErr("RehashTable", "malloc"); return; } + mDNSPlatformMemZero(new, newnbuckets * sizeof(RRTableElem *)); + + for (i = 0; i < d->nbuckets; i++) + { + ptr = d->table[i]; + while (ptr) + { + bucket = ptr->rr.resrec.namehash % newnbuckets; + tmp = ptr; + ptr = ptr->next; + tmp->next = new[bucket]; + new[bucket] = tmp; + } + } + d->nbuckets = newnbuckets; + free(d->table); + d->table = new; +} + +// print entire contents of hashtable, invoked via SIGINFO +mDNSlocal void PrintLeaseTable(DaemonInfo *d) +{ + int i; + RRTableElem *ptr; + char rrbuf[MaxMsg], addrbuf[16]; + struct timeval now; + int hr, min, sec; + + if (gettimeofday(&now, NULL)) { LogErr("PrintTable", "gettimeofday"); return; } + if (pthread_mutex_lock(&d->tablelock)) { LogErr("PrintTable", "pthread_mutex_lock"); return; } + + Log("Dumping Lease Table Contents (table contains %d resource records)", d->nelems); + for (i = 0; i < d->nbuckets; i++) + { + for (ptr = d->table[i]; ptr; ptr = ptr->next) + { + hr = ((ptr->expire - now.tv_sec) / 60) / 60; + min = ((ptr->expire - now.tv_sec) / 60) % 60; + sec = (ptr->expire - now.tv_sec) % 60; + Log("Update from %s, Expires in %d:%d:%d\n\t%s", inet_ntop(AF_INET, &ptr->cli.sin_addr, addrbuf, 16), hr, min, sec, + GetRRDisplayString_rdb(&ptr->rr.resrec, &ptr->rr.resrec.rdata->u, rrbuf)); + } + } + pthread_mutex_unlock(&d->tablelock); +} + +// +// Startup SRV Registration Routines +// Register _dns-update._udp/_tcp.<zone> SRV records indicating the port on which +// the daemon accepts requests +// + +// delete all RRS of a given name/type +mDNSlocal mDNSu8 *putRRSetDeletion(DNSMessage *msg, mDNSu8 *ptr, mDNSu8 *limit, ResourceRecord *rr) +{ + ptr = putDomainNameAsLabels(msg, ptr, limit, rr->name); + if (!ptr || ptr + 10 >= limit) return NULL; // out of space + ptr[0] = (mDNSu8)(rr->rrtype >> 8); + ptr[1] = (mDNSu8)(rr->rrtype & 0xFF); + ptr[2] = (mDNSu8)((mDNSu16)kDNSQClass_ANY >> 8); + ptr[3] = (mDNSu8)((mDNSu16)kDNSQClass_ANY & 0xFF); + mDNSPlatformMemZero(ptr+4, sizeof(rr->rroriginalttl) + sizeof(rr->rdlength)); // zero ttl/rdata + msg->h.mDNS_numUpdates++; + return ptr + 10; +} + +mDNSlocal mDNSu8 *PutUpdateSRV(DaemonInfo *d, DNSZone * zone, PktMsg *pkt, mDNSu8 *ptr, char *regtype, mDNSIPPort port, mDNSBool registration) +{ + AuthRecord rr; + char hostname[1024], buf[MaxMsg]; + mDNSu8 *end = (mDNSu8 *)&pkt->msg + sizeof(DNSMessage); + + ( void ) d; + + mDNS_SetupResourceRecord(&rr, NULL, 0, kDNSType_SRV, SRV_TTL, kDNSRecordTypeUnique, AuthRecordAny, NULL, NULL); + rr.resrec.rrclass = kDNSClass_IN; + rr.resrec.rdata->u.srv.priority = 0; + rr.resrec.rdata->u.srv.weight = 0; + rr.resrec.rdata->u.srv.port = port; + if (gethostname(hostname, 1024) < 0 || !MakeDomainNameFromDNSNameString(&rr.resrec.rdata->u.srv.target, hostname)) + rr.resrec.rdata->u.srv.target.c[0] = '\0'; + + MakeDomainNameFromDNSNameString(&rr.namestorage, regtype); + AppendDomainName(&rr.namestorage, &zone->name); + VLog("%s %s", registration ? "Registering SRV record" : "Deleting existing RRSet", + GetRRDisplayString_rdb(&rr.resrec, &rr.resrec.rdata->u, buf)); + if (registration) ptr = PutResourceRecord(&pkt->msg, ptr, &pkt->msg.h.mDNS_numUpdates, &rr.resrec); + else ptr = putRRSetDeletion(&pkt->msg, ptr, end, &rr.resrec); + return ptr; +} + + +// perform dynamic update. +// specify deletion by passing false for the register parameter, otherwise register the records. +mDNSlocal int UpdateSRV(DaemonInfo *d, mDNSBool registration) +{ + TCPSocket *sock = NULL; + DNSZone * zone; + int err = mStatus_NoError; + + sock = ConnectToServer( d ); + require_action( sock, exit, err = mStatus_UnknownErr; Log( "UpdateSRV: ConnectToServer failed" ) ); + + for ( zone = d->zones; zone; zone = zone->next ) + { + PktMsg pkt; + mDNSu8 *ptr = pkt.msg.data; + mDNSu8 *end = (mDNSu8 *)&pkt.msg + sizeof(DNSMessage); + PktMsg *reply = NULL; + mDNSBool closed; + mDNSBool ok; + + // Initialize message + InitializeDNSMessage(&pkt.msg.h, zeroID, UpdateReqFlags); + pkt.src.sin_addr.s_addr = zerov4Addr.NotAnInteger; // address field set solely for verbose logging in subroutines + pkt.src.sin_family = AF_INET; + + // format message body + ptr = putZone(&pkt.msg, ptr, end, &zone->name, mDNSOpaque16fromIntVal(kDNSClass_IN)); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + + if ( zone->type == kDNSZonePrivate ) + { + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-update-tls._tcp.", d->private_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-query-tls._tcp.", d->private_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-llq-tls._tcp.", d->private_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + + if ( !registration ) + { + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-update._udp.", d->llq_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-llq._udp.", d->llq_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + } + } + else + { + if ( !registration ) + { + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-update-tls.", d->private_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-query-tls.", d->private_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-llq-tls.", d->private_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + } + + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-update._udp.", d->llq_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-llq._udp.", d->llq_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + } + + HdrHToN(&pkt); + + if ( zone->updateKeys ) + { + DNSDigest_SignMessage( &pkt.msg, &ptr, zone->updateKeys, 0 ); + require_action( ptr, exit, Log("UpdateSRV: Error constructing lease expiration update" ) ); + } + + pkt.len = ptr - (mDNSu8 *)&pkt.msg; + + // send message, receive reply + + err = SendPacket( sock, &pkt ); + require_action( !err, exit, Log( "UpdateSRV: SendPacket failed" ) ); + + reply = RecvPacket( sock, NULL, &closed ); + require_action( reply, exit, err = mStatus_UnknownErr; Log( "UpdateSRV: RecvPacket returned NULL" ) ); + + ok = SuccessfulUpdateTransaction( &pkt, reply ); + + if ( !ok ) + { + Log("SRV record registration failed with rcode %d", reply->msg.h.flags.b[1] & kDNSFlag1_RC_Mask); + } + + free( reply ); + } + +exit: + + if ( sock ) + { + mDNSPlatformTCPCloseConnection( sock ); + } + + return err; +} + +// wrapper routines/macros +#define ClearUpdateSRV(d) UpdateSRV(d, 0) + +// clear any existing records prior to registration +mDNSlocal int SetUpdateSRV(DaemonInfo *d) +{ + int err; + + err = ClearUpdateSRV(d); // clear any existing record + if (!err) err = UpdateSRV(d, 1); + return err; +} + +// +// Argument Parsing and Configuration +// + +mDNSlocal void PrintUsage(void) +{ + fprintf(stderr, "Usage: dnsextd [-f <config file>] [-vhd] ...\n" + "Use \"dnsextd -h\" for help\n"); +} + +mDNSlocal void PrintHelp(void) +{ + fprintf(stderr, "\n\n"); + PrintUsage(); + + fprintf(stderr, + "dnsextd is a daemon that implements DNS extensions supporting Dynamic DNS Update Leases\n" + "and Long Lived Queries, used in Wide-Area DNS Service Discovery, on behalf of name servers\n" + "that do not natively support these extensions. (See dns-sd.org for more info on DNS Service\n" + "Discovery, Update Leases, and Long Lived Queries.)\n\n" + + "dnsextd requires one argument,the zone, which is the domain for which Update Leases\n" + "and Long Lived Queries are to be administered. dnsextd communicates directly with the\n" + "primary master server for this zone.\n\n" + + "The options are as follows:\n\n" + + "-f Specify configuration file. The default is /etc/dnsextd.conf.\n\n" + + "-d Run daemon in foreground.\n\n" + + "-h Print help.\n\n" + + "-v Verbose output.\n\n" + ); +} + + +// Note: ProcessArgs called before process is daemonized, and therefore must open no descriptors +// returns 0 (success) if program is to continue execution +// output control arguments (-f, -v) do not affect this routine +mDNSlocal int ProcessArgs(int argc, char *argv[], DaemonInfo *d) +{ + DNSZone * zone; + int opt; + int err = 0; + + cfgfile = strdup( CONFIG_FILE ); + require_action( cfgfile, arg_error, err = mStatus_NoMemoryErr ); + + // defaults, may be overriden by command option + + // setup our sockaddr + + mDNSPlatformMemZero( &d->addr, sizeof( d->addr ) ); + d->addr.sin_addr.s_addr = zerov4Addr.NotAnInteger; + d->addr.sin_port = UnicastDNSPort.NotAnInteger; + d->addr.sin_family = AF_INET; +#ifndef NOT_HAVE_SA_LEN + d->addr.sin_len = sizeof( d->addr ); +#endif + + // setup nameserver's sockaddr + + mDNSPlatformMemZero(&d->ns_addr, sizeof(d->ns_addr)); + d->ns_addr.sin_family = AF_INET; + inet_pton( AF_INET, LOOPBACK, &d->ns_addr.sin_addr ); + d->ns_addr.sin_port = NSIPCPort.NotAnInteger; +#ifndef NOT_HAVE_SA_LEN + d->ns_addr.sin_len = sizeof( d->ns_addr ); +#endif + + // setup our ports + + d->private_port = PrivateDNSPort; + d->llq_port = DNSEXTPort; + + while ((opt = getopt(argc, argv, "f:hdv")) != -1) + { + switch(opt) + { + case 'f': free( cfgfile ); cfgfile = strdup( optarg ); require_action( cfgfile, arg_error, err = mStatus_NoMemoryErr ); break; + case 'h': PrintHelp(); return -1; + case 'd': foreground = 1; break; // Also used when launched via OS X's launchd mechanism + case 'v': verbose = 1; break; + default: goto arg_error; + } + } + + err = ParseConfig( d, cfgfile ); + require_noerr( err, arg_error ); + + // Make sure we've specified some zones + + require_action( d->zones, arg_error, err = mStatus_UnknownErr ); + + // if we have a shared secret, use it for the entire zone + + for ( zone = d->zones; zone; zone = zone->next ) + { + if ( zone->updateKeys ) + { + AssignDomainName( &zone->updateKeys->domain, &zone->name ); + } + } + + return 0; + +arg_error: + + PrintUsage(); + return -1; +} + + +// +// Initialization Routines +// + +// Allocate memory, initialize locks and bookkeeping variables +mDNSlocal int InitLeaseTable(DaemonInfo *d) +{ + if (pthread_mutex_init(&d->tablelock, NULL)) { LogErr("InitLeaseTable", "pthread_mutex_init"); return -1; } + d->nbuckets = LEASETABLE_INIT_NBUCKETS; + d->nelems = 0; + d->table = malloc(sizeof(RRTableElem *) * LEASETABLE_INIT_NBUCKETS); + if (!d->table) { LogErr("InitLeaseTable", "malloc"); return -1; } + mDNSPlatformMemZero(d->table, sizeof(RRTableElem *) * LEASETABLE_INIT_NBUCKETS); + return 0; +} + + +mDNSlocal int +SetupSockets +( + DaemonInfo * self +) +{ + static const int kOn = 1; + int sockpair[2]; + mDNSBool private = mDNSfalse; + struct sockaddr_in daddr; + DNSZone * zone; + mStatus err = 0; + + // set up sockets on which we all ns requests + + self->tcpsd = socket( AF_INET, SOCK_STREAM, 0 ); + require_action( dnssd_SocketValid(self->tcpsd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) ); + +#if defined(SO_REUSEADDR) + err = setsockopt(self->tcpsd, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn)); + require_action( !err, exit, LogErr( "SetupSockets", "SO_REUSEADDR self->tcpsd" ) ); +#endif + + err = bind( self->tcpsd, ( struct sockaddr* ) &self->addr, sizeof( self->addr ) ); + require_action( !err, exit, LogErr( "SetupSockets", "bind self->tcpsd" ) ); + + err = listen( self->tcpsd, LISTENQ ); + require_action( !err, exit, LogErr( "SetupSockets", "listen" ) ); + + self->udpsd = socket( AF_INET, SOCK_DGRAM, 0 ); + require_action( dnssd_SocketValid(self->udpsd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) ); + +#if defined(SO_REUSEADDR) + err = setsockopt(self->udpsd, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn)); + require_action( !err, exit, LogErr( "SetupSockets", "SO_REUSEADDR self->udpsd" ) ); +#endif + + err = bind( self->udpsd, ( struct sockaddr* ) &self->addr, sizeof( self->addr ) ); + require_action( !err, exit, LogErr( "SetupSockets", "bind self->udpsd" ) ); + + // set up sockets on which we receive llq requests + + mDNSPlatformMemZero(&self->llq_addr, sizeof(self->llq_addr)); + self->llq_addr.sin_family = AF_INET; + self->llq_addr.sin_addr.s_addr = zerov4Addr.NotAnInteger; + self->llq_addr.sin_port = ( self->llq_port.NotAnInteger ) ? self->llq_port.NotAnInteger : DNSEXTPort.NotAnInteger; + + if (self->llq_addr.sin_port == self->addr.sin_port) + { + self->llq_tcpsd = self->tcpsd; + self->llq_udpsd = self->udpsd; + } + else + { + self->llq_tcpsd = socket( AF_INET, SOCK_STREAM, 0 ); + require_action( dnssd_SocketValid(self->llq_tcpsd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) ); + +#if defined(SO_REUSEADDR) + err = setsockopt(self->llq_tcpsd, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn)); + require_action( !err, exit, LogErr( "SetupSockets", "SO_REUSEADDR self->llq_tcpsd" ) ); +#endif + + err = bind( self->llq_tcpsd, ( struct sockaddr* ) &self->llq_addr, sizeof( self->llq_addr ) ); + require_action( !err, exit, LogErr( "SetupSockets", "bind self->llq_tcpsd" ) ); + + err = listen( self->llq_tcpsd, LISTENQ ); + require_action( !err, exit, LogErr( "SetupSockets", "listen" ) ); + + self->llq_udpsd = socket( AF_INET, SOCK_DGRAM, 0 ); + require_action( dnssd_SocketValid(self->llq_udpsd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) ); + +#if defined(SO_REUSEADDR) + err = setsockopt(self->llq_udpsd, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn)); + require_action( !err, exit, LogErr( "SetupSockets", "SO_REUSEADDR self->llq_udpsd" ) ); +#endif + + err = bind(self->llq_udpsd, ( struct sockaddr* ) &self->llq_addr, sizeof( self->llq_addr ) ); + require_action( !err, exit, LogErr( "SetupSockets", "bind self->llq_udpsd" ) ); + } + + // set up Unix domain socket pair for LLQ polling thread to signal main thread that a change to the zone occurred + + err = socketpair( AF_LOCAL, SOCK_STREAM, 0, sockpair ); + require_action( !err, exit, LogErr( "SetupSockets", "socketpair" ) ); + + self->LLQEventListenSock = sockpair[0]; + self->LLQEventNotifySock = sockpair[1]; + + // set up socket on which we receive private requests + + self->llq_tcpsd = socket( AF_INET, SOCK_STREAM, 0 ); + require_action( dnssd_SocketValid(self->tlssd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) ); + mDNSPlatformMemZero(&daddr, sizeof(daddr)); + daddr.sin_family = AF_INET; + daddr.sin_addr.s_addr = zerov4Addr.NotAnInteger; + daddr.sin_port = ( self->private_port.NotAnInteger ) ? self->private_port.NotAnInteger : PrivateDNSPort.NotAnInteger; + + self->tlssd = socket( AF_INET, SOCK_STREAM, 0 ); + require_action( dnssd_SocketValid(self->tlssd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) ); + +#if defined(SO_REUSEADDR) + err = setsockopt(self->tlssd, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn)); + require_action( !err, exit, LogErr( "SetupSockets", "SO_REUSEADDR self->tlssd" ) ); +#endif + + err = bind( self->tlssd, ( struct sockaddr* ) &daddr, sizeof( daddr ) ); + require_action( !err, exit, LogErr( "SetupSockets", "bind self->tlssd" ) ); + + err = listen( self->tlssd, LISTENQ ); + require_action( !err, exit, LogErr( "SetupSockets", "listen" ) ); + + // Do we have any private zones? + + for ( zone = self->zones; zone; zone = zone->next ) + { + if ( zone->type == kDNSZonePrivate ) + { + private = mDNStrue; + break; + } + } + + if ( private ) + { + err = mDNSPlatformTLSSetupCerts(); + require_action( !err, exit, LogErr( "SetupSockets", "mDNSPlatformTLSSetupCerts" ) ); + } + +exit: + + return err; +} + +// +// periodic table updates +// + +// Delete a resource record from the nameserver via a dynamic update +// sd is a socket already connected to the server +mDNSlocal void DeleteOneRecord(DaemonInfo *d, CacheRecord *rr, domainname *zname, TCPSocket *sock) +{ + DNSZone * zone; + PktMsg pkt; + mDNSu8 *ptr = pkt.msg.data; + mDNSu8 *end = (mDNSu8 *)&pkt.msg + sizeof(DNSMessage); + char buf[MaxMsg]; + mDNSBool closed; + PktMsg *reply = NULL; + + VLog("Expiring record %s", GetRRDisplayString_rdb(&rr->resrec, &rr->resrec.rdata->u, buf)); + + InitializeDNSMessage(&pkt.msg.h, zeroID, UpdateReqFlags); + + ptr = putZone(&pkt.msg, ptr, end, zname, mDNSOpaque16fromIntVal(rr->resrec.rrclass)); + if (!ptr) goto end; + ptr = putDeletionRecord(&pkt.msg, ptr, &rr->resrec); + if (!ptr) goto end; + + HdrHToN(&pkt); + + zone = FindZone( d, zname ); + + if ( zone && zone->updateKeys) + { + DNSDigest_SignMessage(&pkt.msg, &ptr, zone->updateKeys, 0 ); + if (!ptr) goto end; + } + + pkt.len = ptr - (mDNSu8 *)&pkt.msg; + pkt.src.sin_addr.s_addr = zerov4Addr.NotAnInteger; // address field set solely for verbose logging in subroutines + pkt.src.sin_family = AF_INET; + if (SendPacket( sock, &pkt)) { Log("DeleteOneRecord: SendPacket failed"); } + reply = RecvPacket( sock, NULL, &closed ); + if (reply) HdrNToH(reply); + require_action( reply, end, Log( "DeleteOneRecord: RecvPacket returned NULL" ) ); + + if (!SuccessfulUpdateTransaction(&pkt, reply)) + Log("Expiration update failed with rcode %d", reply ? reply->msg.h.flags.b[1] & kDNSFlag1_RC_Mask : -1); + +end: + if (!ptr) { Log("DeleteOneRecord: Error constructing lease expiration update"); } + if (reply) free(reply); +} + +// iterate over table, deleting expired records (or all records if DeleteAll is true) +mDNSlocal void DeleteRecords(DaemonInfo *d, mDNSBool DeleteAll) +{ + struct timeval now; + int i; + TCPSocket *sock = ConnectToServer(d); + if (!sock) { Log("DeleteRecords: ConnectToServer failed"); return; } + if (gettimeofday(&now, NULL)) { LogErr("DeleteRecords ", "gettimeofday"); return; } + if (pthread_mutex_lock(&d->tablelock)) { LogErr("DeleteRecords", "pthread_mutex_lock"); return; } + + for (i = 0; i < d->nbuckets; i++) + { + RRTableElem **ptr = &d->table[i]; + while (*ptr) + { + if (DeleteAll || (*ptr)->expire - now.tv_sec < 0) + { + RRTableElem *fptr; + // delete record from server + DeleteOneRecord(d, &(*ptr)->rr, &(*ptr)->zone, sock); + fptr = *ptr; + *ptr = (*ptr)->next; + free(fptr); + d->nelems--; + } + else ptr = &(*ptr)->next; + } + } + pthread_mutex_unlock(&d->tablelock); + mDNSPlatformTCPCloseConnection( sock ); +} + +// +// main update request handling +// + +// Add, delete, or refresh records in table based on contents of a successfully completed dynamic update +mDNSlocal void UpdateLeaseTable(PktMsg *pkt, DaemonInfo *d, mDNSs32 lease) +{ + RRTableElem **rptr, *tmp; + int i, allocsize, bucket; + LargeCacheRecord lcr; + ResourceRecord *rr = &lcr.r.resrec; + const mDNSu8 *ptr, *end; + struct timeval tv; + DNSQuestion zone; + char buf[MaxMsg]; + + if (pthread_mutex_lock(&d->tablelock)) { LogErr("UpdateLeaseTable", "pthread_mutex_lock"); return; } + HdrNToH(pkt); + ptr = pkt->msg.data; + end = (mDNSu8 *)&pkt->msg + pkt->len; + ptr = getQuestion(&pkt->msg, ptr, end, 0, &zone); + if (!ptr) { Log("UpdateLeaseTable: cannot read zone"); goto cleanup; } + ptr = LocateAuthorities(&pkt->msg, end); + if (!ptr) { Log("UpdateLeaseTable: Format error"); goto cleanup; } + + for (i = 0; i < pkt->msg.h.mDNS_numUpdates; i++) + { + mDNSBool DeleteAllRRSets = mDNSfalse, DeleteOneRRSet = mDNSfalse, DeleteOneRR = mDNSfalse; + + ptr = GetLargeResourceRecord(NULL, &pkt->msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); + if (!ptr || lcr.r.resrec.RecordType == kDNSRecordTypePacketNegative) { Log("UpdateLeaseTable: GetLargeResourceRecord failed"); goto cleanup; } + bucket = rr->namehash % d->nbuckets; + rptr = &d->table[bucket]; + + // handle deletions + if (rr->rrtype == kDNSQType_ANY && !rr->rroriginalttl && rr->rrclass == kDNSQClass_ANY && !rr->rdlength) + DeleteAllRRSets = mDNStrue; // delete all rrsets for a name + else if (!rr->rroriginalttl && rr->rrclass == kDNSQClass_ANY && !rr->rdlength) + DeleteOneRRSet = mDNStrue; + else if (!rr->rroriginalttl && rr->rrclass == kDNSClass_NONE) + DeleteOneRR = mDNStrue; + + if (DeleteAllRRSets || DeleteOneRRSet || DeleteOneRR) + { + while (*rptr) + { + if (SameDomainName((*rptr)->rr.resrec.name, rr->name) && + (DeleteAllRRSets || + (DeleteOneRRSet && (*rptr)->rr.resrec.rrtype == rr->rrtype) || + (DeleteOneRR && IdenticalResourceRecord(&(*rptr)->rr.resrec, rr)))) + { + tmp = *rptr; + VLog("Received deletion update for %s", GetRRDisplayString_rdb(&tmp->rr.resrec, &tmp->rr.resrec.rdata->u, buf)); + *rptr = (*rptr)->next; + free(tmp); + d->nelems--; + } + else rptr = &(*rptr)->next; + } + } + else if (lease > 0) + { + // see if add or refresh + while (*rptr && !IdenticalResourceRecord(&(*rptr)->rr.resrec, rr)) rptr = &(*rptr)->next; + if (*rptr) + { + // refresh + if (gettimeofday(&tv, NULL)) { LogErr("UpdateLeaseTable", "gettimeofday"); goto cleanup; } + (*rptr)->expire = tv.tv_sec + (unsigned)lease; + VLog("Refreshing lease for %s", GetRRDisplayString_rdb(&lcr.r.resrec, &lcr.r.resrec.rdata->u, buf)); + } + else + { + // New record - add to table + if (d->nelems > d->nbuckets) + { + RehashTable(d); + bucket = rr->namehash % d->nbuckets; + rptr = &d->table[bucket]; + } + if (gettimeofday(&tv, NULL)) { LogErr("UpdateLeaseTable", "gettimeofday"); goto cleanup; } + allocsize = sizeof(RRTableElem); + if (rr->rdlength > InlineCacheRDSize) allocsize += (rr->rdlength - InlineCacheRDSize); + tmp = malloc(allocsize); + if (!tmp) { LogErr("UpdateLeaseTable", "malloc"); goto cleanup; } + memcpy(&tmp->rr, &lcr.r, sizeof(CacheRecord) + rr->rdlength - InlineCacheRDSize); + tmp->rr.resrec.rdata = (RData *)&tmp->rr.smallrdatastorage; + AssignDomainName(&tmp->name, rr->name); + tmp->rr.resrec.name = &tmp->name; + tmp->expire = tv.tv_sec + (unsigned)lease; + tmp->cli.sin_addr = pkt->src.sin_addr; + AssignDomainName(&tmp->zone, &zone.qname); + tmp->next = d->table[bucket]; + d->table[bucket] = tmp; + d->nelems++; + VLog("Adding update for %s to lease table", GetRRDisplayString_rdb(&lcr.r.resrec, &lcr.r.resrec.rdata->u, buf)); + } + } + } + +cleanup: + pthread_mutex_unlock(&d->tablelock); + HdrHToN(pkt); +} + +// Given a successful reply from a server, create a new reply that contains lease information +// Replies are currently not signed !!!KRS change this +mDNSlocal PktMsg *FormatLeaseReply(DaemonInfo *d, PktMsg *orig, mDNSu32 lease) +{ + PktMsg *reply; + mDNSu8 *ptr, *end; + mDNSOpaque16 flags; + + (void)d; //unused + reply = malloc(sizeof(*reply)); + if (!reply) { LogErr("FormatLeaseReply", "malloc"); return NULL; } + flags.b[0] = kDNSFlag0_QR_Response | kDNSFlag0_OP_Update; + flags.b[1] = 0; + + InitializeDNSMessage(&reply->msg.h, orig->msg.h.id, flags); + reply->src.sin_addr.s_addr = zerov4Addr.NotAnInteger; // unused except for log messages + reply->src.sin_family = AF_INET; + ptr = reply->msg.data; + end = (mDNSu8 *)&reply->msg + sizeof(DNSMessage); + ptr = putUpdateLease(&reply->msg, ptr, lease); + if (!ptr) { Log("FormatLeaseReply: putUpdateLease failed"); free(reply); return NULL; } + reply->len = ptr - (mDNSu8 *)&reply->msg; + HdrHToN(reply); + return reply; +} + + +// pkt is thread-local, not requiring locking + +mDNSlocal PktMsg* +HandleRequest +( + DaemonInfo * self, + PktMsg * request +) +{ + PktMsg * reply = NULL; + PktMsg * leaseReply; + PktMsg buf; + char addrbuf[32]; + TCPSocket * sock = NULL; + mStatus err; + mDNSs32 lease = 0; + if ((request->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask) == kDNSFlag0_OP_Update) + { + int i, adds = 0, dels = 0; + const mDNSu8 *ptr, *end = (mDNSu8 *)&request->msg + request->len; + HdrNToH(request); + lease = GetPktLease(&mDNSStorage, &request->msg, end); + ptr = LocateAuthorities(&request->msg, end); + for (i = 0; i < request->msg.h.mDNS_numUpdates; i++) + { + LargeCacheRecord lcr; + ptr = GetLargeResourceRecord(NULL, &request->msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); + if (lcr.r.resrec.RecordType != kDNSRecordTypePacketNegative && lcr.r.resrec.rroriginalttl) adds++;else dels++; + } + HdrHToN(request); + if (adds && !lease) + { + static const mDNSOpaque16 UpdateRefused = { { kDNSFlag0_QR_Response | kDNSFlag0_OP_Update, kDNSFlag1_RC_Refused } }; + Log("Rejecting Update Request with %d additions but no lease", adds); + reply = malloc(sizeof(*reply)); + mDNSPlatformMemZero(&reply->src, sizeof(reply->src)); + reply->len = sizeof(DNSMessageHeader); + reply->zone = NULL; + reply->isZonePublic = 0; + InitializeDNSMessage(&reply->msg.h, request->msg.h.id, UpdateRefused); + return(reply); + } + if (lease > 7200) // Don't allow lease greater than two hours; typically 90-minute renewal period + lease = 7200; + } + // Send msg to server, read reply + + if ( request->len <= 512 ) + { + mDNSBool trunc; + + if ( UDPServerTransaction( self, request, &buf, &trunc) < 0 ) + { + Log("HandleRequest - UDPServerTransaction failed. Trying TCP"); + } + else if ( trunc ) + { + VLog("HandleRequest - answer truncated. Using TCP"); + } + else + { + reply = &buf; // success + } + } + + if ( !reply ) + { + mDNSBool closed; + int res; + + sock = ConnectToServer( self ); + require_action_quiet( sock, exit, err = mStatus_UnknownErr ; Log( "Discarding request from %s due to connection errors", inet_ntop( AF_INET, &request->src.sin_addr, addrbuf, 32 ) ) ); + + res = SendPacket( sock, request ); + require_action_quiet( res >= 0, exit, err = mStatus_UnknownErr ; Log( "Couldn't relay message from %s to server. Discarding.", inet_ntop(AF_INET, &request->src.sin_addr, addrbuf, 32 ) ) ); + + reply = RecvPacket( sock, &buf, &closed ); + } + + // IMPORTANT: reply is in network byte order at this point in the code + // We keep it this way because we send it back to the client in the same form + + // Is it an update? + + if ( reply && ( ( reply->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ) == ( kDNSFlag0_OP_Update | kDNSFlag0_QR_Response ) ) ) + { + char pingmsg[4]; + mDNSBool ok = SuccessfulUpdateTransaction( request, reply ); + require_action( ok, exit, err = mStatus_UnknownErr; VLog( "Message from %s not a successful update.", inet_ntop(AF_INET, &request->src.sin_addr, addrbuf, 32 ) ) ); + + UpdateLeaseTable( request, self, lease ); + + if ( lease > 0 ) + { + leaseReply = FormatLeaseReply( self, reply, lease ); + + if ( !leaseReply ) + { + Log("HandleRequest - unable to format lease reply"); + } + + // %%% Looks like a potential memory leak -- who frees the original reply? + reply = leaseReply; + } + + // tell the main thread there was an update so it can send LLQs + + if ( send( self->LLQEventNotifySock, pingmsg, sizeof( pingmsg ), 0 ) != sizeof( pingmsg ) ) + { + LogErr("HandleRequest", "send"); + } + } + +exit: + + if ( sock ) + { + mDNSPlatformTCPCloseConnection( sock ); + } + + if ( reply == &buf ) + { + reply = malloc( sizeof( *reply ) ); + + if ( reply ) + { + reply->len = buf.len; + memcpy(&reply->msg, &buf.msg, buf.len); + } + else + { + LogErr("HandleRequest", "malloc"); + } + } + + return reply; +} + + +// +// LLQ Support Routines +// + +// Set fields of an LLQ OPT Resource Record +mDNSlocal void FormatLLQOpt(AuthRecord *opt, int opcode, const mDNSOpaque64 *const id, mDNSs32 lease) +{ + mDNSPlatformMemZero(opt, sizeof(*opt)); + mDNS_SetupResourceRecord(opt, mDNSNULL, mDNSInterface_Any, kDNSType_OPT, kStandardTTL, kDNSRecordTypeKnownUnique, AuthRecordAny, mDNSNULL, mDNSNULL); + opt->resrec.rrclass = NormalMaxDNSMessageData; + opt->resrec.rdlength = sizeof(rdataOPT); // One option in this OPT record + opt->resrec.rdestimate = sizeof(rdataOPT); + opt->resrec.rdata->u.opt[0].opt = kDNSOpt_LLQ; + opt->resrec.rdata->u.opt[0].u.llq.vers = kLLQ_Vers; + opt->resrec.rdata->u.opt[0].u.llq.llqOp = opcode; + opt->resrec.rdata->u.opt[0].u.llq.err = LLQErr_NoError; + opt->resrec.rdata->u.opt[0].u.llq.id = *id; + opt->resrec.rdata->u.opt[0].u.llq.llqlease = lease; +} + +// Calculate effective remaining lease of an LLQ +mDNSlocal mDNSu32 LLQLease(LLQEntry *e) +{ + struct timeval t; + + gettimeofday(&t, NULL); + if (e->expire < t.tv_sec) return 0; + else return e->expire - t.tv_sec; +} + +mDNSlocal void DeleteLLQ(DaemonInfo *d, LLQEntry *e) +{ + int bucket = DomainNameHashValue(&e->qname) % LLQ_TABLESIZE; + LLQEntry **ptr = &d->LLQTable[bucket]; + AnswerListElem *a = e->AnswerList; + char addr[32]; + + inet_ntop(AF_INET, &e->cli.sin_addr, addr, 32); + VLog("Deleting LLQ table entry for %##s client %s", e->qname.c, addr); + + if (a && !(--a->refcount) && d->AnswerTableCount >= LLQ_TABLESIZE) + { + // currently, generating initial answers blocks the main thread, so we keep the answer list + // even if the ref count drops to zero. To prevent unbounded table growth, we free shared answers + // if the ref count drops to zero AND there are more table elements than buckets + // !!!KRS update this when we make the table dynamically growable + + CacheRecord *cr = a->KnownAnswers, *tmp; + AnswerListElem **tbl = &d->AnswerTable[bucket]; + + while (cr) + { + tmp = cr; + cr = cr->next; + free(tmp); + } + + while (*tbl && *tbl != a) tbl = &(*tbl)->next; + if (*tbl) { *tbl = (*tbl)->next; free(a); d->AnswerTableCount--; } + else Log("Error: DeleteLLQ - AnswerList not found in table"); + } + + // remove LLQ from table, free memory + while(*ptr && *ptr != e) ptr = &(*ptr)->next; + if (!*ptr) { Log("Error: DeleteLLQ - LLQ not in table"); return; } + *ptr = (*ptr)->next; + free(e); +} + +mDNSlocal int SendLLQ(DaemonInfo *d, PktMsg *pkt, struct sockaddr_in dst, TCPSocket *sock) +{ + char addr[32]; + int err = -1; + + HdrHToN(pkt); + + if ( sock ) + { + if ( SendPacket( sock, pkt ) != 0 ) + { + LogErr("DaemonInfo", "MySend"); + Log("Could not send response to client %s", inet_ntop(AF_INET, &dst.sin_addr, addr, 32)); + } + } + else + { + if (sendto(d->llq_udpsd, &pkt->msg, pkt->len, 0, (struct sockaddr *)&dst, sizeof(dst)) != (int)pkt->len) + { + LogErr("DaemonInfo", "sendto"); + Log("Could not send response to client %s", inet_ntop(AF_INET, &dst.sin_addr, addr, 32)); + } + } + + err = 0; + HdrNToH(pkt); + return err; +} + +mDNSlocal CacheRecord *AnswerQuestion(DaemonInfo *d, AnswerListElem *e) +{ + PktMsg q; + int i; + TCPSocket *sock = NULL; + const mDNSu8 *ansptr; + mDNSu8 *end = q.msg.data; + PktMsg buf, *reply = NULL; + LargeCacheRecord lcr; + CacheRecord *AnswerList = NULL; + mDNSu8 rcode; + + VLog("Querying server for %##s type %d", e->name.c, e->type); + + InitializeDNSMessage(&q.msg.h, zeroID, uQueryFlags); + + end = putQuestion(&q.msg, end, end + AbsoluteMaxDNSMessageData, &e->name, e->type, kDNSClass_IN); + if (!end) { Log("Error: AnswerQuestion - putQuestion returned NULL"); goto end; } + q.len = (int)(end - (mDNSu8 *)&q.msg); + + HdrHToN(&q); + + if (!e->UseTCP) + { + mDNSBool trunc; + + if (UDPServerTransaction(d, &q, &buf, &trunc) < 0) + Log("AnswerQuestion %##s - UDPServerTransaction failed. Trying TCP", e->name.c); + else if (trunc) + { VLog("AnswerQuestion %##s - answer truncated. Using TCP", e->name.c); e->UseTCP = mDNStrue; } + else reply = &buf; // success + } + + if (!reply) + { + mDNSBool closed; + + sock = ConnectToServer(d); + if (!sock) { Log("AnswerQuestion: ConnectToServer failed"); goto end; } + if (SendPacket( sock, &q)) { Log("AnswerQuestion: SendPacket failed"); mDNSPlatformTCPCloseConnection( sock ); goto end; } + reply = RecvPacket( sock, NULL, &closed ); + mDNSPlatformTCPCloseConnection( sock ); + require_action( reply, end, Log( "AnswerQuestion: RecvPacket returned NULL" ) ); + } + + HdrNToH(&q); + if (reply) HdrNToH(reply); + + if ((reply->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask) != (kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery)) + { Log("AnswerQuestion: %##s type %d - Invalid response flags from server"); goto end; } + rcode = (mDNSu8)(reply->msg.h.flags.b[1] & kDNSFlag1_RC_Mask); + if (rcode && rcode != kDNSFlag1_RC_NXDomain) { Log("AnswerQuestion: %##s type %d - non-zero rcode %d from server", e->name.c, e->type, rcode); goto end; } + + end = (mDNSu8 *)&reply->msg + reply->len; + ansptr = LocateAnswers(&reply->msg, end); + if (!ansptr) { Log("Error: AnswerQuestion - LocateAnswers returned NULL"); goto end; } + + for (i = 0; i < reply->msg.h.numAnswers; i++) + { + ansptr = GetLargeResourceRecord(NULL, &reply->msg, ansptr, end, 0, kDNSRecordTypePacketAns, &lcr); + if (!ansptr) { Log("AnswerQuestions: GetLargeResourceRecord returned NULL"); goto end; } + if (lcr.r.resrec.RecordType != kDNSRecordTypePacketNegative) + { + if (lcr.r.resrec.rrtype != e->type || lcr.r.resrec.rrclass != kDNSClass_IN || !SameDomainName(lcr.r.resrec.name, &e->name)) + { + Log("AnswerQuestion: response %##s type #d does not answer question %##s type #d. Discarding", + lcr.r.resrec.name->c, lcr.r.resrec.rrtype, e->name.c, e->type); + } + else + { + CacheRecord *cr = CopyCacheRecord(&lcr.r, &e->name); + if (!cr) { Log("Error: AnswerQuestion - CopyCacheRecord returned NULL"); goto end; } + cr->next = AnswerList; + AnswerList = cr; + } + } + } + +end: + if (reply && reply != &buf) free(reply); + return AnswerList; +} + +// Routine forks a thread to set EventList to contain Add/Remove events, and deletes any removes from the KnownAnswer list +mDNSlocal void *UpdateAnswerList(void *args) +{ + CacheRecord *cr, *NewAnswers, **na, **ka; // "new answer", "known answer" + DaemonInfo *d = ((UpdateAnswerListArgs *)args)->d; + AnswerListElem *a = ((UpdateAnswerListArgs *)args)->a; + + free(args); + args = NULL; + + // get up to date answers + NewAnswers = AnswerQuestion(d, a); + + // first pass - mark all answers for deletion + for (ka = &a->KnownAnswers; *ka; ka = &(*ka)->next) + (*ka)->resrec.rroriginalttl = (unsigned)-1; // -1 means delete + + // second pass - mark answers pre-existent + for (ka = &a->KnownAnswers; *ka; ka = &(*ka)->next) + { + for (na = &NewAnswers; *na; na = &(*na)->next) + { + if (IdenticalResourceRecord(&(*ka)->resrec, &(*na)->resrec)) + { (*ka)->resrec.rroriginalttl = 0; break; } // 0 means no change + } + } + + // third pass - add new records to Event list + na = &NewAnswers; + while (*na) + { + for (ka = &a->KnownAnswers; *ka; ka = &(*ka)->next) + if (IdenticalResourceRecord(&(*ka)->resrec, &(*na)->resrec)) break; + if (!*ka) + { + // answer is not in list - splice from NewAnswers list, add to Event list + cr = *na; + *na = (*na)->next; // splice from list + cr->next = a->EventList; // add spliced record to event list + a->EventList = cr; + cr->resrec.rroriginalttl = 1; // 1 means add + } + else na = &(*na)->next; + } + + // move all the removes from the answer list to the event list + ka = &a->KnownAnswers; + while (*ka) + { + if ((*ka)->resrec.rroriginalttl == (unsigned)-1) + { + cr = *ka; + *ka = (*ka)->next; + cr->next = a->EventList; + a->EventList = cr; + } + else ka = &(*ka)->next; + } + + // lastly, free the remaining records (known answers) in NewAnswers list + while (NewAnswers) + { + cr = NewAnswers; + NewAnswers = NewAnswers->next; + free(cr); + } + + return NULL; +} + +mDNSlocal void SendEvents(DaemonInfo *d, LLQEntry *e) +{ + PktMsg response; + CacheRecord *cr; + mDNSu8 *end = (mDNSu8 *)&response.msg.data; + mDNSOpaque16 msgID; + char rrbuf[MaxMsg], addrbuf[32]; + AuthRecord opt; + + // Should this really be random? Do we use the msgID on the receiving end? + msgID.NotAnInteger = random(); + if (verbose) inet_ntop(AF_INET, &e->cli.sin_addr, addrbuf, 32); + InitializeDNSMessage(&response.msg.h, msgID, ResponseFlags); + end = putQuestion(&response.msg, end, end + AbsoluteMaxDNSMessageData, &e->qname, e->qtype, kDNSClass_IN); + if (!end) { Log("Error: SendEvents - putQuestion returned NULL"); return; } + + // put adds/removes in packet + for (cr = e->AnswerList->EventList; cr; cr = cr->next) + { + if (verbose) GetRRDisplayString_rdb(&cr->resrec, &cr->resrec.rdata->u, rrbuf); + VLog("%s (%s): %s", addrbuf, (mDNSs32)cr->resrec.rroriginalttl < 0 ? "Remove" : "Add", rrbuf); + end = PutResourceRecordTTLJumbo(&response.msg, end, &response.msg.h.numAnswers, &cr->resrec, cr->resrec.rroriginalttl); + if (!end) { Log("Error: SendEvents - PutResourceRecordTTLJumbo returned NULL"); return; } + } + + FormatLLQOpt(&opt, kLLQOp_Event, &e->id, LLQLease(e)); + end = PutResourceRecordTTLJumbo(&response.msg, end, &response.msg.h.numAdditionals, &opt.resrec, 0); + if (!end) { Log("Error: SendEvents - PutResourceRecordTTLJumbo"); return; } + + response.len = (int)(end - (mDNSu8 *)&response.msg); + if (SendLLQ(d, &response, e->cli, NULL ) < 0) LogMsg("Error: SendEvents - SendLLQ"); +} + +mDNSlocal void PrintLLQAnswers(DaemonInfo *d) +{ + int i; + char rrbuf[MaxMsg]; + + Log("Printing LLQ Answer Table contents"); + + for (i = 0; i < LLQ_TABLESIZE; i++) + { + AnswerListElem *a = d->AnswerTable[i]; + while(a) + { + int ancount = 0; + const CacheRecord *rr = a->KnownAnswers; + while (rr) { ancount++; rr = rr->next; } + Log("%p : Question %##s; type %d; referenced by %d LLQs; %d answers:", a, a->name.c, a->type, a->refcount, ancount); + for (rr = a->KnownAnswers; rr; rr = rr->next) Log("\t%s", GetRRDisplayString_rdb(&rr->resrec, &rr->resrec.rdata->u, rrbuf)); + a = a->next; + } + } +} + +mDNSlocal void PrintLLQTable(DaemonInfo *d) +{ + LLQEntry *e; + char addr[32]; + int i; + + Log("Printing LLQ table contents"); + + for (i = 0; i < LLQ_TABLESIZE; i++) + { + e = d->LLQTable[i]; + while(e) + { + char *state; + + switch (e->state) + { + case RequestReceived: state = "RequestReceived"; break; + case ChallengeSent: state = "ChallengeSent"; break; + case Established: state = "Established"; break; + default: state = "unknown"; + } + inet_ntop(AF_INET, &e->cli.sin_addr, addr, 32); + + Log("LLQ from %s in state %s; %##s; type %d; orig lease %d; remaining lease %d; AnswerList %p)", + addr, state, e->qname.c, e->qtype, e->lease, LLQLease(e), e->AnswerList); + e = e->next; + } + } +} + +// Send events to clients as a result of a change in the zone +mDNSlocal void GenLLQEvents(DaemonInfo *d) +{ + LLQEntry **e; + int i; + struct timeval t; + UpdateAnswerListArgs *args; + + VLog("Generating LLQ Events"); + + gettimeofday(&t, NULL); + + // get all answers up to date + for (i = 0; i < LLQ_TABLESIZE; i++) + { + AnswerListElem *a = d->AnswerTable[i]; + while(a) + { + args = malloc(sizeof(*args)); + if (!args) { LogErr("GenLLQEvents", "malloc"); return; } + args->d = d; + args->a = a; + if (pthread_create(&a->tid, NULL, UpdateAnswerList, args) < 0) { LogErr("GenLLQEvents", "pthread_create"); return; } + usleep(1); + a = a->next; + } + } + + for (i = 0; i < LLQ_TABLESIZE; i++) + { + AnswerListElem *a = d->AnswerTable[i]; + while(a) + { + if (pthread_join(a->tid, NULL)) LogErr("GenLLQEvents", "pthread_join"); + a = a->next; + } + } + + // for each established LLQ, send events + for (i = 0; i < LLQ_TABLESIZE; i++) + { + e = &d->LLQTable[i]; + while(*e) + { + if ((*e)->expire < t.tv_sec) DeleteLLQ(d, *e); + else + { + if ((*e)->state == Established && (*e)->AnswerList->EventList) SendEvents(d, *e); + e = &(*e)->next; + } + } + } + + // now that all LLQs are updated, we move Add events from the Event list to the Known Answer list, and free Removes + for (i = 0; i < LLQ_TABLESIZE; i++) + { + AnswerListElem *a = d->AnswerTable[i]; + while(a) + { + if (a->EventList) + { + CacheRecord *cr = a->EventList, *tmp; + while (cr) + { + tmp = cr; + cr = cr->next; + if ((signed)tmp->resrec.rroriginalttl < 0) free(tmp); + else + { + tmp->next = a->KnownAnswers; + a->KnownAnswers = tmp; + tmp->resrec.rroriginalttl = 0; + } + } + a->EventList = NULL; + } + a = a->next; + } + } +} + +mDNSlocal void SetAnswerList(DaemonInfo *d, LLQEntry *e) +{ + int bucket = DomainNameHashValue(&e->qname) % LLQ_TABLESIZE; + AnswerListElem *a = d->AnswerTable[bucket]; + while (a && (a->type != e->qtype ||!SameDomainName(&a->name, &e->qname))) a = a->next; + if (!a) + { + a = malloc(sizeof(*a)); + if (!a) { LogErr("SetAnswerList", "malloc"); return; } + AssignDomainName(&a->name, &e->qname); + a->type = e->qtype; + a->refcount = 0; + a->EventList = NULL; + a->UseTCP = mDNSfalse; + a->next = d->AnswerTable[bucket]; + d->AnswerTable[bucket] = a; + d->AnswerTableCount++; + a->KnownAnswers = AnswerQuestion(d, a); + } + + e->AnswerList = a; + a->refcount++; +} + +// Allocate LLQ entry, insert into table +mDNSlocal LLQEntry *NewLLQ(DaemonInfo *d, struct sockaddr_in cli, domainname *qname, mDNSu16 qtype, mDNSu32 lease ) +{ + char addr[32]; + struct timeval t; + int bucket = DomainNameHashValue(qname) % LLQ_TABLESIZE; + LLQEntry *e; + + e = malloc(sizeof(*e)); + if (!e) { LogErr("NewLLQ", "malloc"); return NULL; } + + inet_ntop(AF_INET, &cli.sin_addr, addr, 32); + VLog("Allocating LLQ entry for client %s question %##s type %d", addr, qname->c, qtype); + + // initialize structure + e->cli = cli; + AssignDomainName(&e->qname, qname); + e->qtype = qtype; + e->id = zeroOpaque64; + e->state = RequestReceived; + e->AnswerList = NULL; + + if (lease < LLQ_MIN_LEASE) lease = LLQ_MIN_LEASE; + else if (lease > LLQ_MAX_LEASE) lease = LLQ_MAX_LEASE; + + gettimeofday(&t, NULL); + e->expire = t.tv_sec + (int)lease; + e->lease = lease; + + // add to table + e->next = d->LLQTable[bucket]; + d->LLQTable[bucket] = e; + + return e; +} + +// Handle a refresh request from client +mDNSlocal void LLQRefresh(DaemonInfo *d, LLQEntry *e, LLQOptData *llq, mDNSOpaque16 msgID, TCPSocket *sock ) +{ + AuthRecord opt; + PktMsg ack; + mDNSu8 *end = (mDNSu8 *)&ack.msg.data; + char addr[32]; + + inet_ntop(AF_INET, &e->cli.sin_addr, addr, 32); + VLog("%s LLQ for %##s from %s", llq->llqlease ? "Refreshing" : "Deleting", e->qname.c, addr); + + if (llq->llqlease) + { + struct timeval t; + if (llq->llqlease < LLQ_MIN_LEASE) llq->llqlease = LLQ_MIN_LEASE; + else if (llq->llqlease > LLQ_MAX_LEASE) llq->llqlease = LLQ_MIN_LEASE; + gettimeofday(&t, NULL); + e->expire = t.tv_sec + llq->llqlease; + } + + ack.src.sin_addr.s_addr = 0; // unused + InitializeDNSMessage(&ack.msg.h, msgID, ResponseFlags); + end = putQuestion(&ack.msg, end, end + AbsoluteMaxDNSMessageData, &e->qname, e->qtype, kDNSClass_IN); + if (!end) { Log("Error: putQuestion"); return; } + + FormatLLQOpt(&opt, kLLQOp_Refresh, &e->id, llq->llqlease ? LLQLease(e) : 0); + end = PutResourceRecordTTLJumbo(&ack.msg, end, &ack.msg.h.numAdditionals, &opt.resrec, 0); + if (!end) { Log("Error: PutResourceRecordTTLJumbo"); return; } + + ack.len = (int)(end - (mDNSu8 *)&ack.msg); + if (SendLLQ(d, &ack, e->cli, sock)) Log("Error: LLQRefresh"); + + if (llq->llqlease) e->state = Established; + else DeleteLLQ(d, e); +} + +// Complete handshake with Ack an initial answers +mDNSlocal void LLQCompleteHandshake(DaemonInfo *d, LLQEntry *e, LLQOptData *llq, mDNSOpaque16 msgID, TCPSocket *sock) +{ + char addr[32]; + CacheRecord *ptr; + AuthRecord opt; + PktMsg ack; + mDNSu8 *end = (mDNSu8 *)&ack.msg.data; + char rrbuf[MaxMsg], addrbuf[32]; + + inet_ntop(AF_INET, &e->cli.sin_addr, addr, 32); + + if (!mDNSSameOpaque64(&llq->id, &e->id) || + llq->vers != kLLQ_Vers || + llq->llqOp != kLLQOp_Setup || + llq->err != LLQErr_NoError || + llq->llqlease > e->lease + LLQ_LEASE_FUDGE || + llq->llqlease < e->lease - LLQ_LEASE_FUDGE) + { + Log("Incorrect challenge response from %s", addr); + return; + } + + if (e->state == Established) VLog("Retransmitting LLQ ack + answers for %##s", e->qname.c); + else VLog("Delivering LLQ ack + answers for %##s", e->qname.c); + + // format ack + answers + ack.src.sin_addr.s_addr = 0; // unused + InitializeDNSMessage(&ack.msg.h, msgID, ResponseFlags); + end = putQuestion(&ack.msg, end, end + AbsoluteMaxDNSMessageData, &e->qname, e->qtype, kDNSClass_IN); + if (!end) { Log("Error: putQuestion"); return; } + + if (e->state != Established) { SetAnswerList(d, e); e->state = Established; } + + if (verbose) inet_ntop(AF_INET, &e->cli.sin_addr, addrbuf, 32); + for (ptr = e->AnswerList->KnownAnswers; ptr; ptr = ptr->next) + { + if (verbose) GetRRDisplayString_rdb(&ptr->resrec, &ptr->resrec.rdata->u, rrbuf); + VLog("%s Intitial Answer - %s", addr, rrbuf); + end = PutResourceRecordTTLJumbo(&ack.msg, end, &ack.msg.h.numAnswers, &ptr->resrec, 1); + if (!end) { Log("Error: PutResourceRecordTTLJumbo"); return; } + } + + FormatLLQOpt(&opt, kLLQOp_Setup, &e->id, LLQLease(e)); + end = PutResourceRecordTTLJumbo(&ack.msg, end, &ack.msg.h.numAdditionals, &opt.resrec, 0); + if (!end) { Log("Error: PutResourceRecordTTLJumbo"); return; } + + ack.len = (int)(end - (mDNSu8 *)&ack.msg); + if (SendLLQ(d, &ack, e->cli, sock)) Log("Error: LLQCompleteHandshake"); +} + +mDNSlocal void LLQSetupChallenge(DaemonInfo *d, LLQEntry *e, LLQOptData *llq, mDNSOpaque16 msgID) +{ + struct timeval t; + PktMsg challenge; + mDNSu8 *end = challenge.msg.data; + AuthRecord opt; + + if (e->state == ChallengeSent) VLog("Retransmitting LLQ setup challenge for %##s", e->qname.c); + else VLog("Sending LLQ setup challenge for %##s", e->qname.c); + + if (!mDNSOpaque64IsZero(&llq->id)) { Log("Error: LLQSetupChallenge - nonzero ID"); return; } // server bug + if (llq->llqOp != kLLQOp_Setup) { Log("LLQSetupChallenge - incorrrect operation from client"); return; } // client error + + if (mDNSOpaque64IsZero(&e->id)) // don't regenerate random ID for retransmissions + { + // construct ID <time><random> + gettimeofday(&t, NULL); + e->id.l[0] = t.tv_sec; + e->id.l[1] = random(); + } + + // format response (query + LLQ opt rr) + challenge.src.sin_addr.s_addr = 0; // unused + InitializeDNSMessage(&challenge.msg.h, msgID, ResponseFlags); + end = putQuestion(&challenge.msg, end, end + AbsoluteMaxDNSMessageData, &e->qname, e->qtype, kDNSClass_IN); + if (!end) { Log("Error: putQuestion"); return; } + FormatLLQOpt(&opt, kLLQOp_Setup, &e->id, LLQLease(e)); + end = PutResourceRecordTTLJumbo(&challenge.msg, end, &challenge.msg.h.numAdditionals, &opt.resrec, 0); + if (!end) { Log("Error: PutResourceRecordTTLJumbo"); return; } + challenge.len = (int)(end - (mDNSu8 *)&challenge.msg); + if (SendLLQ(d, &challenge, e->cli, NULL)) { Log("Error: LLQSetupChallenge"); return; } + e->state = ChallengeSent; +} + +// Take action on an LLQ message from client. Entry must be initialized and in table +mDNSlocal void UpdateLLQ(DaemonInfo *d, LLQEntry *e, LLQOptData *llq, mDNSOpaque16 msgID, TCPSocket *sock ) +{ + switch(e->state) + { + case RequestReceived: + if ( sock ) + { + struct timeval t; + gettimeofday(&t, NULL); + e->id.l[0] = t.tv_sec; // construct ID <time><random> + e->id.l[1] = random(); + llq->id = e->id; + LLQCompleteHandshake( d, e, llq, msgID, sock ); + + // Set the state to established because we've just set the LLQ up using TCP + e->state = Established; + } + else + { + LLQSetupChallenge(d, e, llq, msgID); + } + return; + case ChallengeSent: + if (mDNSOpaque64IsZero(&llq->id)) LLQSetupChallenge(d, e, llq, msgID); // challenge sent and lost + else LLQCompleteHandshake(d, e, llq, msgID, sock ); + return; + case Established: + if (mDNSOpaque64IsZero(&llq->id)) + { + // client started over. reset state. + LLQEntry *newe = NewLLQ(d, e->cli, &e->qname, e->qtype, llq->llqlease ); + if (!newe) return; + DeleteLLQ(d, e); + LLQSetupChallenge(d, newe, llq, msgID); + return; + } + else if (llq->llqOp == kLLQOp_Setup) + { LLQCompleteHandshake(d, e, llq, msgID, sock); return; } // Ack lost + else if (llq->llqOp == kLLQOp_Refresh) + { LLQRefresh(d, e, llq, msgID, sock); return; } + else { Log("Unhandled message for established LLQ"); return; } + } +} + +mDNSlocal LLQEntry *LookupLLQ(DaemonInfo *d, struct sockaddr_in cli, domainname *qname, mDNSu16 qtype, const mDNSOpaque64 *const id) +{ + int bucket = bucket = DomainNameHashValue(qname) % LLQ_TABLESIZE; + LLQEntry *ptr = d->LLQTable[bucket]; + + while(ptr) + { + if (((ptr->state == ChallengeSent && mDNSOpaque64IsZero(id) && (cli.sin_port == ptr->cli.sin_port)) || // zero-id due to packet loss OK in state ChallengeSent + mDNSSameOpaque64(id, &ptr->id)) && // id match + (cli.sin_addr.s_addr == ptr->cli.sin_addr.s_addr) && (qtype == ptr->qtype) && SameDomainName(&ptr->qname, qname)) // same source, type, qname + return ptr; + ptr = ptr->next; + } + return NULL; +} + +mDNSlocal int +RecvNotify +( + DaemonInfo * d, + PktMsg * pkt +) +{ + int res; + int err = 0; + + pkt->msg.h.flags.b[0] |= kDNSFlag0_QR_Response; + + res = sendto( d->udpsd, &pkt->msg, pkt->len, 0, ( struct sockaddr* ) &pkt->src, sizeof( pkt->src ) ); + require_action( res == ( int ) pkt->len, exit, err = mStatus_UnknownErr; LogErr( "RecvNotify", "sendto" ) ); + +exit: + + return err; +} + + +mDNSlocal int RecvLLQ( DaemonInfo *d, PktMsg *pkt, TCPSocket *sock ) +{ + DNSQuestion q; + LargeCacheRecord opt; + int i, err = -1; + char addr[32]; + const mDNSu8 *qptr = pkt->msg.data; + const mDNSu8 *end = (mDNSu8 *)&pkt->msg + pkt->len; + const mDNSu8 *aptr; + LLQOptData *llq = NULL; + LLQEntry *e = NULL; + + HdrNToH(pkt); + aptr = LocateAdditionals(&pkt->msg, end); // Can't do this until after HdrNToH(pkt); + inet_ntop(AF_INET, &pkt->src.sin_addr, addr, 32); + + VLog("Received LLQ msg from %s", addr); + // sanity-check packet + if (!pkt->msg.h.numQuestions || !pkt->msg.h.numAdditionals) + { + Log("Malformatted LLQ from %s with %d questions, %d additionals", addr, pkt->msg.h.numQuestions, pkt->msg.h.numAdditionals); + goto end; + } + + // Locate the OPT record. + // According to RFC 2671, "One OPT pseudo-RR can be added to the additional data section of either a request or a response." + // This implies that there may be *at most* one OPT record per DNS message, in the Additional Section, + // but not necessarily the *last* entry in the Additional Section. + for (i = 0; i < pkt->msg.h.numAdditionals; i++) + { + aptr = GetLargeResourceRecord(NULL, &pkt->msg, aptr, end, 0, kDNSRecordTypePacketAdd, &opt); + if (!aptr) { Log("Malformatted LLQ from %s: could not get Additional record %d", addr, i); goto end; } + if (opt.r.resrec.RecordType != kDNSRecordTypePacketNegative && opt.r.resrec.rrtype == kDNSType_OPT) break; + } + + // validate OPT + if (opt.r.resrec.rrtype != kDNSType_OPT) { Log("Malformatted LLQ from %s: last Additional not an OPT RR", addr); goto end; } + if (opt.r.resrec.rdlength < pkt->msg.h.numQuestions * DNSOpt_LLQData_Space) { Log("Malformatted LLQ from %s: OPT RR to small (%d bytes for %d questions)", addr, opt.r.resrec.rdlength, pkt->msg.h.numQuestions); } + + // dispatch each question + for (i = 0; i < pkt->msg.h.numQuestions; i++) + { + qptr = getQuestion(&pkt->msg, qptr, end, 0, &q); + if (!qptr) { Log("Malformatted LLQ from %s: cannot read question %d", addr, i); goto end; } + llq = (LLQOptData *)&opt.r.resrec.rdata->u.opt[0].u.llq + i; // point into OptData at index i + if (llq->vers != kLLQ_Vers) { Log("LLQ from %s contains bad version %d (expected %d)", addr, llq->vers, kLLQ_Vers); goto end; } + + e = LookupLLQ(d, pkt->src, &q.qname, q.qtype, &llq->id); + if (!e) + { + // no entry - if zero ID, create new + e = NewLLQ(d, pkt->src, &q.qname, q.qtype, llq->llqlease ); + if (!e) goto end; + } + UpdateLLQ(d, e, llq, pkt->msg.h.id, sock); + } + err = 0; + +end: + HdrHToN(pkt); + return err; +} + + +mDNSlocal mDNSBool IsAuthorized( DaemonInfo * d, PktMsg * pkt, DomainAuthInfo ** key, mDNSu16 * rcode, mDNSu16 * tcode ) +{ + const mDNSu8 * lastPtr = NULL; + const mDNSu8 * ptr = NULL; + DomainAuthInfo * keys; + mDNSu8 * end = ( mDNSu8* ) &pkt->msg + pkt->len; + LargeCacheRecord lcr; + mDNSBool hasTSIG = mDNSfalse; + mDNSBool strip = mDNSfalse; + mDNSBool ok = mDNSfalse; + int i; + + // Unused parameters + + ( void ) d; + + HdrNToH(pkt); + + *key = NULL; + + if ( pkt->msg.h.numAdditionals ) + { + ptr = LocateAdditionals(&pkt->msg, end); + if (ptr) + { + for (i = 0; i < pkt->msg.h.numAdditionals; i++) + { + lastPtr = ptr; + ptr = GetLargeResourceRecord(NULL, &pkt->msg, ptr, end, 0, kDNSRecordTypePacketAdd, &lcr); + if (!ptr) + { + Log("Unable to read additional record"); + lastPtr = NULL; + break; + } + } + + hasTSIG = ( ptr && lcr.r.resrec.RecordType != kDNSRecordTypePacketNegative && lcr.r.resrec.rrtype == kDNSType_TSIG ); + } + else + { + LogMsg( "IsAuthorized: unable to find Additional section" ); + } + } + + // If we don't know what zone this is, then it's authorized. + + if ( !pkt->zone ) + { + ok = mDNStrue; + strip = mDNSfalse; + goto exit; + } + + if ( IsQuery( pkt ) ) + { + keys = pkt->zone->queryKeys; + strip = mDNStrue; + } + else if ( IsUpdate( pkt ) ) + { + keys = pkt->zone->updateKeys; + strip = mDNSfalse; + } + else + { + ok = mDNStrue; + strip = mDNSfalse; + goto exit; + } + + if ( pkt->isZonePublic ) + { + ok = mDNStrue; + goto exit; + } + + // If there are no keys, then we're authorized + + if ( ( hasTSIG && !keys ) || ( !hasTSIG && keys ) ) + { + Log( "Invalid TSIG spec %##s for zone %##s", lcr.r.resrec.name->c, pkt->zone->name.c ); + *rcode = kDNSFlag1_RC_NotAuth; + *tcode = TSIG_ErrBadKey; + strip = mDNStrue; + ok = mDNSfalse; + goto exit; + } + + // Find the right key + + for ( *key = keys; *key; *key = (*key)->next ) + { + if ( SameDomainName( lcr.r.resrec.name, &(*key)->keyname ) ) + { + break; + } + } + + if ( !(*key) ) + { + Log( "Invalid TSIG name %##s for zone %##s", lcr.r.resrec.name->c, pkt->zone->name.c ); + *rcode = kDNSFlag1_RC_NotAuth; + *tcode = TSIG_ErrBadKey; + strip = mDNStrue; + ok = mDNSfalse; + goto exit; + } + + // Okay, we have the correct key and a TSIG record. DNSDigest_VerifyMessage does the heavy + // lifting of message verification + + pkt->msg.h.numAdditionals--; + + HdrHToN( pkt ); + + ok = DNSDigest_VerifyMessage( &pkt->msg, ( mDNSu8* ) lastPtr, &lcr, (*key), rcode, tcode ); + + HdrNToH( pkt ); + + pkt->msg.h.numAdditionals++; + +exit: + + if ( hasTSIG && strip ) + { + // Strip the TSIG from the message + + pkt->msg.h.numAdditionals--; + pkt->len = lastPtr - ( mDNSu8* ) ( &pkt->msg ); + } + + HdrHToN(pkt); + + return ok; +} + +// request handler wrappers for TCP and UDP requests +// (read message off socket, fork thread that invokes main processing routine and handles cleanup) + +mDNSlocal void* +UDPMessageHandler +( + void * vptr +) +{ + UDPContext * context = ( UDPContext* ) vptr; + PktMsg * reply = NULL; + int res; + mStatus err; + + // !!!KRS strictly speaking, we shouldn't use TCP for a UDP request because the server + // may give us a long answer that would require truncation for UDP delivery to client + + reply = HandleRequest( context->d, &context->pkt ); + require_action( reply, exit, err = mStatus_UnknownErr ); + + res = sendto( context->sd, &reply->msg, reply->len, 0, ( struct sockaddr* ) &context->pkt.src, sizeof( context->pkt.src ) ); + require_action_quiet( res == ( int ) reply->len, exit, LogErr( "UDPMessageHandler", "sendto" ) ); + +exit: + + if ( reply ) + { + free( reply ); + } + + free( context ); + + pthread_exit( NULL ); + + return NULL; +} + + +mDNSlocal int +RecvUDPMessage +( + DaemonInfo * self, + int sd +) +{ + UDPContext * context = NULL; + pthread_t tid; + mDNSu16 rcode; + mDNSu16 tcode; + DomainAuthInfo * key; + unsigned int clisize = sizeof( context->cliaddr ); + int res; + mStatus err = mStatus_NoError; + + context = malloc( sizeof( UDPContext ) ); + require_action( context, exit, err = mStatus_NoMemoryErr ; LogErr( "RecvUDPMessage", "malloc" ) ); + + mDNSPlatformMemZero( context, sizeof( *context ) ); + context->d = self; + context->sd = sd; + + res = recvfrom(sd, &context->pkt.msg, sizeof(context->pkt.msg), 0, (struct sockaddr *)&context->cliaddr, &clisize); + + require_action( res >= 0, exit, err = mStatus_UnknownErr ; LogErr( "RecvUDPMessage", "recvfrom" ) ); + context->pkt.len = res; + require_action( clisize == sizeof( context->cliaddr ), exit, err = mStatus_UnknownErr ; Log( "Client address of unknown size %d", clisize ) ); + context->pkt.src = context->cliaddr; + + // Set the zone in the packet + + SetZone( context->d, &context->pkt ); + + // Notify messages handled by main thread + + if ( IsNotify( &context->pkt ) ) + { + int e = RecvNotify( self, &context->pkt ); + free(context); + return e; + } + else if ( IsAuthorized( context->d, &context->pkt, &key, &rcode, &tcode ) ) + { + if ( IsLLQRequest( &context->pkt ) ) + { + // LLQ messages handled by main thread + int e = RecvLLQ( self, &context->pkt, NULL ); + free(context); + return e; + } + + if ( IsLLQAck(&context->pkt ) ) + { + // !!!KRS need to do acks + retrans + + free(context); + return 0; + } + + err = pthread_create( &tid, NULL, UDPMessageHandler, context ); + require_action( !err, exit, LogErr( "RecvUDPMessage", "pthread_create" ) ); + + pthread_detach(tid); + } + else + { + PktMsg reply; + int e; + + memcpy( &reply, &context->pkt, sizeof( PktMsg ) ); + + reply.msg.h.flags.b[0] = kDNSFlag0_QR_Response | kDNSFlag0_AA | kDNSFlag0_RD; + reply.msg.h.flags.b[1] = kDNSFlag1_RA | kDNSFlag1_RC_NXDomain; + + e = sendto( sd, &reply.msg, reply.len, 0, ( struct sockaddr* ) &context->pkt.src, sizeof( context->pkt.src ) ); + require_action_quiet( e == ( int ) reply.len, exit, LogErr( "RecvUDPMessage", "sendto" ) ); + + err = mStatus_NoAuth; + } + +exit: + + if ( err && context ) + { + free( context ); + } + + return err; +} + + +mDNSlocal void +FreeTCPContext +( + TCPContext * context +) +{ + if ( context ) + { + if ( context->sock ) + { + mDNSPlatformTCPCloseConnection( context->sock ); + } + + free( context ); + } +} + + +mDNSlocal void* +TCPMessageHandler +( + void * vptr +) +{ + TCPContext * context = ( TCPContext* ) vptr; + PktMsg * reply = NULL; + int res; + char buf[32]; + + //!!!KRS if this read blocks indefinitely, we can run out of threads + // read the request + + reply = HandleRequest( context->d, &context->pkt ); + require_action_quiet( reply, exit, LogMsg( "TCPMessageHandler: No reply for client %s", inet_ntop( AF_INET, &context->cliaddr.sin_addr, buf, 32 ) ) ); + + // deliver reply to client + + res = SendPacket( context->sock, reply ); + require_action( res >= 0, exit, LogMsg("TCPMessageHandler: Unable to send reply to client %s", inet_ntop(AF_INET, &context->cliaddr.sin_addr, buf, 32 ) ) ); + +exit: + + FreeTCPContext( context ); + + if ( reply ) + { + free( reply ); + } + + pthread_exit(NULL); +} + + +mDNSlocal void +RecvTCPMessage +( + void * param +) +{ + TCPContext * context = ( TCPContext* ) param; + mDNSu16 rcode; + mDNSu16 tcode; + pthread_t tid; + DomainAuthInfo * key; + PktMsg * pkt; + mDNSBool closed; + mDNSBool freeContext = mDNStrue; + mStatus err = mStatus_NoError; + + // Receive a packet. It's okay if we don't actually read a packet, as long as the closed flag is + // set to false. This is because SSL/TLS layer might gobble up the first packet that we read off the + // wire. We'll let it do that, and wait for the next packet which will be ours. + + pkt = RecvPacket( context->sock, &context->pkt, &closed ); + if (pkt) HdrNToH(pkt); + require_action( pkt || !closed, exit, err = mStatus_UnknownErr; LogMsg( "client disconnected" ) ); + + if ( pkt ) + { + // Always do this, regardless of what kind of packet it is. If we wanted LLQ events to be sent over TCP, + // we would change this line of code. As it is now, we will reply to an LLQ via TCP, but then events + // are sent over UDP + + RemoveSourceFromEventLoop( context->d, context->sock ); + + // Set's the DNS Zone that is associated with this message + + SetZone( context->d, &context->pkt ); + + // IsAuthorized will make sure the message is authorized for the designated zone. + // After verifying the signature, it will strip the TSIG from the message + + if ( IsAuthorized( context->d, &context->pkt, &key, &rcode, &tcode ) ) + { + if ( IsLLQRequest( &context->pkt ) ) + { + // LLQ messages handled by main thread + RecvLLQ( context->d, &context->pkt, context->sock); + } + else + { + err = pthread_create( &tid, NULL, TCPMessageHandler, context ); + + if ( err ) + { + LogErr( "RecvTCPMessage", "pthread_create" ); + err = mStatus_NoError; + goto exit; + } + + // Let the thread free the context + + freeContext = mDNSfalse; + + pthread_detach(tid); + } + } + else + { + PktMsg reply; + + LogMsg( "Client %s Not authorized for zone %##s", inet_ntoa( context->pkt.src.sin_addr ), pkt->zone->name.c ); + + memcpy( &reply, &context->pkt, sizeof( PktMsg ) ); + + reply.msg.h.flags.b[0] = kDNSFlag0_QR_Response | kDNSFlag0_AA | kDNSFlag0_RD; + reply.msg.h.flags.b[1] = kDNSFlag1_RA | kDNSFlag1_RC_Refused; + + SendPacket( context->sock, &reply ); + } + } + else + { + freeContext = mDNSfalse; + } + +exit: + + if ( err ) + { + RemoveSourceFromEventLoop( context->d, context->sock ); + } + + if ( freeContext ) + { + FreeTCPContext( context ); + } +} + + +mDNSlocal int +AcceptTCPConnection +( + DaemonInfo * self, + int sd, + TCPSocketFlags flags +) +{ + TCPContext * context = NULL; + unsigned int clilen = sizeof( context->cliaddr); + int newSock; + mStatus err = mStatus_NoError; + + context = ( TCPContext* ) malloc( sizeof( TCPContext ) ); + require_action( context, exit, err = mStatus_NoMemoryErr; LogErr( "AcceptTCPConnection", "malloc" ) ); + mDNSPlatformMemZero( context, sizeof( sizeof( TCPContext ) ) ); + context->d = self; + newSock = accept( sd, ( struct sockaddr* ) &context->cliaddr, &clilen ); + require_action( newSock != -1, exit, err = mStatus_UnknownErr; LogErr( "AcceptTCPConnection", "accept" ) ); + + context->sock = mDNSPlatformTCPAccept( flags, newSock ); + require_action( context->sock, exit, err = mStatus_UnknownErr; LogErr( "AcceptTCPConnection", "mDNSPlatformTCPAccept" ) ); + + err = AddSourceToEventLoop( self, context->sock, RecvTCPMessage, context ); + require_action( !err, exit, LogErr( "AcceptTCPConnection", "AddSourceToEventLoop" ) ); + +exit: + + if ( err && context ) + { + free( context ); + context = NULL; + } + + return err; +} + + +// main event loop +// listen for incoming requests, periodically check table for expired records, respond to signals +mDNSlocal int Run(DaemonInfo *d) +{ + int staticMaxFD, nfds; + fd_set rset; + struct timeval timenow, timeout, EventTS, tablecheck = { 0, 0 }; + mDNSBool EventsPending = mDNSfalse; + + VLog("Listening for requests..."); + + staticMaxFD = 0; + + if ( d->tcpsd + 1 > staticMaxFD ) staticMaxFD = d->tcpsd + 1; + if ( d->udpsd + 1 > staticMaxFD ) staticMaxFD = d->udpsd + 1; + if ( d->tlssd + 1 > staticMaxFD ) staticMaxFD = d->tlssd + 1; + if ( d->llq_tcpsd + 1 > staticMaxFD ) staticMaxFD = d->llq_tcpsd + 1; + if ( d->llq_udpsd + 1 > staticMaxFD ) staticMaxFD = d->llq_udpsd + 1; + if ( d->LLQEventListenSock + 1 > staticMaxFD ) staticMaxFD = d->LLQEventListenSock + 1; + + while(1) + { + EventSource * source; + int maxFD; + + // set timeout + timeout.tv_sec = timeout.tv_usec = 0; + if (gettimeofday(&timenow, NULL)) { LogErr("Run", "gettimeofday"); return -1; } + + if (EventsPending) + { + if (timenow.tv_sec - EventTS.tv_sec >= 5) // if we've been waiting 5 seconds for a "quiet" period to send + { GenLLQEvents(d); EventsPending = mDNSfalse; } // events, we go ahead and do it now + else timeout.tv_usec = 500000; // else do events after 1/2 second with no new events or LLQs + } + if (!EventsPending) + { + // if no pending events, timeout when we need to check for expired records + if (tablecheck.tv_sec && timenow.tv_sec - tablecheck.tv_sec >= 0) + { DeleteRecords(d, mDNSfalse); tablecheck.tv_sec = 0; } // table check overdue + if (!tablecheck.tv_sec) tablecheck.tv_sec = timenow.tv_sec + EXPIRATION_INTERVAL; + timeout.tv_sec = tablecheck.tv_sec - timenow.tv_sec; + } + + FD_ZERO(&rset); + FD_SET( d->tcpsd, &rset ); + FD_SET( d->udpsd, &rset ); + FD_SET( d->tlssd, &rset ); + FD_SET( d->llq_tcpsd, &rset ); + FD_SET( d->llq_udpsd, &rset ); + FD_SET( d->LLQEventListenSock, &rset ); + + maxFD = staticMaxFD; + + for ( source = ( EventSource* ) d->eventSources.Head; source; source = source->next ) + { + FD_SET( source->fd, &rset ); + + if ( source->fd > maxFD ) + { + maxFD = source->fd; + } + } + + nfds = select( maxFD + 1, &rset, NULL, NULL, &timeout); + if (nfds < 0) + { + if (errno == EINTR) + { + if (terminate) + { + // close sockets to prevent clients from making new requests during shutdown + close( d->tcpsd ); + close( d->udpsd ); + close( d->tlssd ); + close( d->llq_tcpsd ); + close( d->llq_udpsd ); + d->tcpsd = d->udpsd = d->tlssd = d->llq_tcpsd = d->llq_udpsd = -1; + DeleteRecords(d, mDNStrue); + return 0; + } + else if (dumptable) + { + Log( "Received SIGINFO" ); + + PrintLeaseTable(d); + PrintLLQTable(d); + PrintLLQAnswers(d); + dumptable = 0; + } + else if (hangup) + { + int err; + + Log( "Received SIGHUP" ); + + err = ParseConfig( d, cfgfile ); + + if ( err ) + { + LogErr( "Run", "ParseConfig" ); + return -1; + } + + hangup = 0; + } + else + { + Log("Received unhandled signal - continuing"); + } + } + else + { + LogErr("Run", "select"); return -1; + } + } + else if (nfds) + { + if (FD_ISSET(d->udpsd, &rset)) RecvUDPMessage( d, d->udpsd ); + if (FD_ISSET(d->llq_udpsd, &rset)) RecvUDPMessage( d, d->llq_udpsd ); + if (FD_ISSET(d->tcpsd, &rset)) AcceptTCPConnection( d, d->tcpsd, 0 ); + if (FD_ISSET(d->llq_tcpsd, &rset)) AcceptTCPConnection( d, d->llq_tcpsd, 0 ); + if (FD_ISSET(d->tlssd, &rset)) AcceptTCPConnection( d, d->tlssd, TCP_SOCKET_FLAGS ); + if (FD_ISSET(d->LLQEventListenSock, &rset)) + { + // clear signalling data off socket + char buf[256]; + recv(d->LLQEventListenSock, buf, 256, 0); + if (!EventsPending) + { + EventsPending = mDNStrue; + if (gettimeofday(&EventTS, NULL)) { LogErr("Run", "gettimeofday"); return -1; } + } + } + + for ( source = ( EventSource* ) d->eventSources.Head; source; source = source->next ) + { + if ( FD_ISSET( source->fd, &rset ) ) + { + source->callback( source->context ); + break; // in case we removed this guy from the event loop + } + } + } + else + { + // timeout + if (EventsPending) { GenLLQEvents(d); EventsPending = mDNSfalse; } + else { DeleteRecords(d, mDNSfalse); tablecheck.tv_sec = 0; } + } + } + return 0; +} + +// signal handler sets global variables, which are inspected by main event loop +// (select automatically returns due to the handled signal) +mDNSlocal void HndlSignal(int sig) +{ + if (sig == SIGTERM || sig == SIGINT ) { terminate = 1; return; } + if (sig == INFO_SIGNAL) { dumptable = 1; return; } + if (sig == SIGHUP) { hangup = 1; return; } +} + +mDNSlocal mStatus +SetPublicSRV +( + DaemonInfo * d, + const char * name +) +{ + DNameListElem * elem; + mStatus err = mStatus_NoError; + + elem = ( DNameListElem* ) malloc( sizeof( DNameListElem ) ); + require_action( elem, exit, err = mStatus_NoMemoryErr ); + MakeDomainNameFromDNSNameString( &elem->name, name ); + elem->next = d->public_names; + d->public_names = elem; + +exit: + + return err; +} + + +int main(int argc, char *argv[]) +{ + int started_via_launchd = 0; + DaemonInfo *d; + struct rlimit rlim; + + Log("dnsextd starting"); + + d = malloc(sizeof(*d)); + if (!d) { LogErr("main", "malloc"); exit(1); } + mDNSPlatformMemZero(d, sizeof(DaemonInfo)); + + // Setup the public SRV record names + + SetPublicSRV(d, "_dns-update._udp."); + SetPublicSRV(d, "_dns-llq._udp."); + SetPublicSRV(d, "_dns-update-tls._tcp."); + SetPublicSRV(d, "_dns-query-tls._tcp."); + SetPublicSRV(d, "_dns-llq-tls._tcp."); + + // Setup signal handling + + if (signal(SIGHUP, HndlSignal) == SIG_ERR) perror("Can't catch SIGHUP"); + if (signal(SIGTERM, HndlSignal) == SIG_ERR) perror("Can't catch SIGTERM"); + if (signal(INFO_SIGNAL, HndlSignal) == SIG_ERR) perror("Can't catch SIGINFO"); + if (signal(SIGINT, HndlSignal) == SIG_ERR) perror("Can't catch SIGINT"); + if (signal(SIGPIPE, SIG_IGN ) == SIG_ERR) perror("Can't ignore SIGPIPE"); + + // remove open file limit + rlim.rlim_max = RLIM_INFINITY; + rlim.rlim_cur = RLIM_INFINITY; + if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) + { + LogErr("main", "setrlimit"); + Log("Using default file descriptor resource limit"); + } + + if (argc > 1 && !strcasecmp(argv[1], "-launchd")) + { + Log("started_via_launchd"); + started_via_launchd = 1; + argv++; + argc--; + } + if (ProcessArgs(argc, argv, d) < 0) { LogErr("main", "ProcessArgs"); exit(1); } + + if (!foreground && !started_via_launchd) + { + if (daemon(0,0)) + { + LogErr("main", "daemon"); + foreground = 1; + } + } + + if (InitLeaseTable(d) < 0) { LogErr("main", "InitLeaseTable"); exit(1); } + if (SetupSockets(d) < 0) { LogErr("main", "SetupSockets"); exit(1); } + if (SetUpdateSRV(d) < 0) { LogErr("main", "SetUpdateSRV"); exit(1); } + + Run(d); + + Log("dnsextd stopping"); + + if (ClearUpdateSRV(d) < 0) { LogErr("main", "ClearUpdateSRV"); exit(1); } // clear update srv's even if Run or pthread_create returns an error + free(d); + exit(0); +} + + +// These are stubbed out implementations of up-call routines that the various platform support layers +// call. These routines are fully implemented in both mDNS.c and uDNS.c, but dnsextd doesn't +// link this code in. +// +// It's an error for these routines to actually be called, so perhaps we should log any call +// to them. +void mDNSCoreInitComplete( mDNS * const m, mStatus result) { ( void ) m; ( void ) result; } +void mDNS_ConfigChanged(mDNS *const m) { ( void ) m; } +void mDNSCoreMachineSleep(mDNS * const m, mDNSBool wake) { ( void ) m; ( void ) wake; } +void mDNSCoreReceive(mDNS *const m, void *const msg, const mDNSu8 *const end, + const mDNSAddr *const srcaddr, const mDNSIPPort srcport, + const mDNSAddr *const dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID iid) +{ ( void ) m; ( void ) msg; ( void ) end; ( void ) srcaddr; ( void ) srcport; ( void ) dstaddr; ( void ) dstport; ( void ) iid; } +DNSServer *mDNS_AddDNSServer(mDNS *const m, const domainname *d, const mDNSInterfaceID interface, const int serviceID, const mDNSAddr *addr, const mDNSIPPort port, + mDNSu32 scoped, mDNSu32 timeout, mDNSBool cellIntf, mDNSu16 resGroupID, mDNSBool reqA, mDNSBool reqAAAA, mDNSBool reqDO) +{ ( void ) m; ( void ) d; ( void ) interface; ( void ) serviceID; ( void ) addr; ( void ) port; ( void ) scoped; ( void ) timeout; (void) cellIntf; + (void) resGroupID; (void) reqA; (void) reqAAAA; (void) reqDO; return(NULL); } +void mDNS_AddSearchDomain(const domainname *const domain, mDNSInterfaceID InterfaceID) { (void)domain; (void) InterfaceID;} +void mDNS_AddDynDNSHostName(mDNS *m, const domainname *fqdn, mDNSRecordCallback *StatusCallback, const void *StatusContext) +{ ( void ) m; ( void ) fqdn; ( void ) StatusCallback; ( void ) StatusContext; } +mDNSs32 mDNS_Execute (mDNS *const m) { ( void ) m; return 0; } +mDNSs32 mDNS_TimeNow(const mDNS *const m) { ( void ) m; return 0; } +mStatus mDNS_Deregister(mDNS *const m, AuthRecord *const rr) { ( void ) m; ( void ) rr; return 0; } +void mDNS_DeregisterInterface(mDNS *const m, NetworkInterfaceInfo *set, mDNSBool flapping) +{ ( void ) m; ( void ) set; ( void ) flapping; } +const char * const mDNS_DomainTypeNames[1] = {}; +mStatus mDNS_GetDomains(mDNS *const m, DNSQuestion *const question, mDNS_DomainType DomainType, const domainname *dom, + const mDNSInterfaceID InterfaceID, mDNSQuestionCallback *Callback, void *Context) +{ ( void ) m; ( void ) question; ( void ) DomainType; ( void ) dom; ( void ) InterfaceID; ( void ) Callback; ( void ) Context; return 0; } +mStatus mDNS_Register(mDNS *const m, AuthRecord *const rr) { ( void ) m; ( void ) rr; return 0; } +mStatus mDNS_RegisterInterface(mDNS *const m, NetworkInterfaceInfo *set, mDNSBool flapping) +{ ( void ) m; ( void ) set; ( void ) flapping; return 0; } +void mDNS_RemoveDynDNSHostName(mDNS *m, const domainname *fqdn) { ( void ) m; ( void ) fqdn; } +void mDNS_SetFQDN(mDNS * const m) { ( void ) m; } +void mDNS_SetPrimaryInterfaceInfo(mDNS *m, const mDNSAddr *v4addr, const mDNSAddr *v6addr, const mDNSAddr *router) +{ ( void ) m; ( void ) v4addr; ( void ) v6addr; ( void ) router; } +mStatus uDNS_SetupDNSConfig( mDNS *const m ) { ( void ) m; return 0; } +mStatus mDNS_SetSecretForDomain(mDNS *m, DomainAuthInfo *info, + const domainname *domain, const domainname *keyname, const char *b64keydata, const domainname *hostname, mDNSIPPort *port, mDNSBool autoTunnel) +{ ( void ) m; ( void ) info; ( void ) domain; ( void ) keyname; ( void ) b64keydata; ( void ) hostname; (void) port; ( void ) autoTunnel; return 0; } +mStatus mDNS_StopQuery(mDNS *const m, DNSQuestion *const question) { ( void ) m; ( void ) question; return 0; } +void TriggerEventCompletion(void); +void TriggerEventCompletion() {} +int AnonInfoAnswersQuestion(const ResourceRecord *const rr, const DNSQuestion *const q); +int AnonInfoAnswersQuestion(const ResourceRecord *const rr, const DNSQuestion *const q) { ( void ) rr; ( void ) q; return 1;} +mDNS mDNSStorage; + + +// For convenience when using the "strings" command, this is the last thing in the file +// The "@(#) " pattern is a special prefix the "what" command looks for +const char mDNSResponderVersionString_SCCS[] = "@(#) dnsextd " STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")"; + +#if _BUILDING_XCODE_PROJECT_ +// If the process crashes, then this string will be magically included in the automatically-generated crash log +const char *__crashreporter_info__ = mDNSResponderVersionString_SCCS + 5; +asm (".desc ___crashreporter_info__, 0x10"); +#endif |