/* * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im> * * All rights reserved. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3.0 only, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * version 3.0 for more details. * * You should have received a copy of the GNU Lesser General Public License * version 3.0 along with this program. If not, see * <https://www.gnu.org/licenses/>. */ #include "config.h" #include <string.h> #import "OFDNSResolver.h" #import "OFArray.h" #import "OFDNSQuery.h" #import "OFDNSResolverSettings.h" #import "OFDNSResponse.h" #import "OFData.h" #import "OFDate.h" #import "OFDictionary.h" #import "OFHostAddressResolver.h" #import "OFNumber.h" #import "OFPair.h" #import "OFString.h" #import "OFTCPSocket.h" #import "OFTimer.h" #import "OFUDPSocket.h" #import "OFUDPSocket+Private.h" #import "OFDNSQueryFailedException.h" #import "OFInitializationFailedException.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFInvalidServerResponseException.h" #import "OFNotImplementedException.h" #import "OFOutOfRangeException.h" #import "OFTruncatedDataException.h" #ifndef SOCK_DNS # define SOCK_DNS 0 #endif static const size_t bufferLength = OFDNSResolverBufferLength; static const size_t maxDNSResponseLength = 65536; /* * RFC 1035 doesn't specify if pointers to pointers are allowed, and if so how * many. Since it's unspecified, we have to assume that it might happen, but we * also want to limit it to avoid DoS. Limiting it to 16 levels of pointers and * immediately rejecting pointers to itself seems like a fair balance. */ static const uint_fast8_t maxAllowedPointers = 16; @interface OFDNSResolver () <OFUDPSocketDelegate, OFTCPSocketDelegate> - (void)of_contextTimedOut: (OFDNSResolverContext *)context; @end OF_DIRECT_MEMBERS @interface OFDNSResolverContext: OFObject { @public OFDNSQuery *_query; OFNumber *_ID; OFDNSResolverSettings *_settings; size_t _nameServersIndex; unsigned int _attempt; id <OFDNSResolverQueryDelegate> _delegate; OFData *_queryData; OFSocketAddress _usedNameServer; OFTCPSocket *_TCPSocket; OFMutableData *_TCPQueryData; void *_TCPBuffer; size_t _responseLength; OFTimer *_cancelTimer; } - (instancetype)initWithQuery: (OFDNSQuery *)query ID: (OFNumber *)ID settings: (OFDNSResolverSettings *)settings delegate: (id <OFDNSResolverQueryDelegate>)delegate; @end static OFString * parseString(const unsigned char *buffer, size_t length, size_t *i) { uint8_t stringLength; OFString *string; if (*i >= length) @throw [OFTruncatedDataException exception]; stringLength = buffer[(*i)++]; if (*i + stringLength > length) @throw [OFTruncatedDataException exception]; string = [OFString stringWithUTF8String: (char *)&buffer[*i] length: stringLength]; *i += stringLength; return string; } static OFString * parseName(const unsigned char *buffer, size_t length, size_t *i, uint_fast8_t pointerLevel) { OFMutableArray *components = [OFMutableArray array]; uint8_t componentLength; do { OFString *component; if (*i >= length) @throw [OFTruncatedDataException exception]; componentLength = buffer[(*i)++]; if (componentLength & 0xC0) { size_t j; OFString *suffix; if (pointerLevel == 0) @throw [OFInvalidServerResponseException exception]; if (*i >= length) @throw [OFTruncatedDataException exception]; j = ((componentLength & 0x3F) << 8) | buffer[(*i)++]; if (j == *i - 2) /* Pointing to itself?! */ @throw [OFInvalidServerResponseException exception]; suffix = parseName(buffer, length, &j, pointerLevel - 1); if (components.count == 0) return suffix; else { [components addObject: suffix]; return [components componentsJoinedByString: @"."]; } } if (*i + componentLength > length) @throw [OFTruncatedDataException exception]; component = [OFString stringWithUTF8String: (char *)&buffer[*i] length: componentLength]; *i += componentLength; [components addObject: component]; } while (componentLength > 0); return [components componentsJoinedByString: @"."]; } static OF_KINDOF(OFDNSResourceRecord *) parseResourceRecord(OFString *name, OFDNSClass DNSClass, OFDNSRecordType recordType, uint32_t TTL, const unsigned char *buffer, size_t length, size_t i, uint16_t dataLength) { if (recordType == OFDNSRecordTypeA && DNSClass == OFDNSClassIN) { OFSocketAddress address; if (dataLength != 4) @throw [OFInvalidServerResponseException exception]; memset(&address, 0, sizeof(address)); address.family = OFSocketAddressFamilyIPv4; address.length = sizeof(address.sockaddr.in); address.sockaddr.in.sin_family = AF_INET; memcpy(&address.sockaddr.in.sin_addr.s_addr, buffer + i, 4); return [[[OFADNSResourceRecord alloc] initWithName: name address: &address TTL: TTL] autorelease]; } else if (recordType == OFDNSRecordTypeNS) { size_t j = i; OFString *authoritativeHost = parseName(buffer, length, &j, maxAllowedPointers); if (j != i + dataLength) @throw [OFInvalidServerResponseException exception]; return [[[OFNSDNSResourceRecord alloc] initWithName: name DNSClass: DNSClass authoritativeHost: authoritativeHost TTL: TTL] autorelease]; } else if (recordType == OFDNSRecordTypeCNAME) { size_t j = i; OFString *alias = parseName(buffer, length, &j, maxAllowedPointers); if (j != i + dataLength) @throw [OFInvalidServerResponseException exception]; return [[[OFCNAMEDNSResourceRecord alloc] initWithName: name DNSClass: DNSClass alias: alias TTL: TTL] autorelease]; } else if (recordType == OFDNSRecordTypeSOA) { size_t j = i; OFString *primaryNameServer = parseName(buffer, length, &j, maxAllowedPointers); OFString *responsiblePerson; uint32_t serialNumber, refreshInterval, retryInterval; uint32_t expirationInterval, minTTL; if (j > i + dataLength) @throw [OFInvalidServerResponseException exception]; responsiblePerson = parseName(buffer, length, &j, maxAllowedPointers); if (dataLength - (j - i) != 20) @throw [OFInvalidServerResponseException exception]; serialNumber = (buffer[j] << 24) | (buffer[j + 1] << 16) | (buffer[j + 2] << 8) | buffer[j + 3]; refreshInterval = (buffer[j + 4] << 24) | (buffer[j + 5] << 16) | (buffer[j + 6] << 8) | buffer[j + 7]; retryInterval = (buffer[j + 8] << 24) | (buffer[j + 9] << 16) | (buffer[j + 10] << 8) | buffer[j + 11]; expirationInterval = (buffer[j + 12] << 24) | (buffer[j + 13] << 16) | (buffer[j + 14] << 8) | buffer[j + 15]; minTTL = (buffer[j + 16] << 24) | (buffer[j + 17] << 16) | (buffer[j + 18] << 8) | buffer[j + 19]; return [[[OFSOADNSResourceRecord alloc] initWithName: name DNSClass: DNSClass primaryNameServer: primaryNameServer responsiblePerson: responsiblePerson serialNumber: serialNumber refreshInterval: refreshInterval retryInterval: retryInterval expirationInterval: expirationInterval minTTL: minTTL TTL: TTL] autorelease]; } else if (recordType == OFDNSRecordTypePTR) { size_t j = i; OFString *domainName = parseName(buffer, length, &j, maxAllowedPointers); if (j != i + dataLength) @throw [OFInvalidServerResponseException exception]; return [[[OFPTRDNSResourceRecord alloc] initWithName: name DNSClass: DNSClass domainName: domainName TTL: TTL] autorelease]; } else if (recordType == OFDNSRecordTypeHINFO) { size_t j = i; OFString *CPU = parseString(buffer, length, &j); OFString *OS; if (j > i + dataLength) @throw [OFInvalidServerResponseException exception]; OS = parseString(buffer, length, &j); if (j != i + dataLength) @throw [OFInvalidServerResponseException exception]; return [[[OFHINFODNSResourceRecord alloc] initWithName: name DNSClass: DNSClass CPU: CPU OS: OS TTL: TTL] autorelease]; } else if (recordType == OFDNSRecordTypeMX) { uint16_t preference; size_t j; OFString *mailExchange; if (dataLength < 2) @throw [OFInvalidServerResponseException exception]; preference = (buffer[i] << 8) | buffer[i + 1]; j = i + 2; mailExchange = parseName(buffer, length, &j, maxAllowedPointers); if (j != i + dataLength) @throw [OFInvalidServerResponseException exception]; return [[[OFMXDNSResourceRecord alloc] initWithName: name DNSClass: DNSClass preference: preference mailExchange: mailExchange TTL: TTL] autorelease]; } else if (recordType == OFDNSRecordTypeTXT) { OFMutableArray *textStrings = [OFMutableArray array]; while (dataLength > 0) { uint_fast8_t stringLength = buffer[i++]; dataLength--; if (stringLength > dataLength) @throw [OFInvalidServerResponseException exception]; [textStrings addObject: [OFData dataWithItems: buffer + i count: stringLength]]; i += stringLength; dataLength -= stringLength; } [textStrings makeImmutable]; return [[[OFTXTDNSResourceRecord alloc] initWithName: name DNSClass: DNSClass textStrings: textStrings TTL: TTL] autorelease]; } else if (recordType == OFDNSRecordTypeRP) { size_t j = i; OFString *mailbox = parseName(buffer, length, &j, maxAllowedPointers); OFString *TXTDomainName; if (j > i + dataLength) @throw [OFInvalidServerResponseException exception]; TXTDomainName = parseName(buffer, length, &j, maxAllowedPointers); if (j != i + dataLength) @throw [OFInvalidServerResponseException exception]; return [[[OFRPDNSResourceRecord alloc] initWithName: name DNSClass: DNSClass mailbox: mailbox TXTDomainName: TXTDomainName TTL: TTL] autorelease]; } else if (recordType == OFDNSRecordTypeAAAA && DNSClass == OFDNSClassIN) { OFSocketAddress address; if (dataLength != 16) @throw [OFInvalidServerResponseException exception]; memset(&address, 0, sizeof(address)); address.family = OFSocketAddressFamilyIPv6; address.length = sizeof(address.sockaddr.in6); #ifdef AF_INET6 address.sockaddr.in6.sin6_family = AF_INET6; #else address.sockaddr.in6.sin6_family = AF_UNSPEC; #endif memcpy(address.sockaddr.in6.sin6_addr.s6_addr, buffer + i, 16); return [[[OFAAAADNSResourceRecord alloc] initWithName: name address: &address TTL: TTL] autorelease]; } else if (recordType == OFDNSRecordTypeLOC) { uint8_t size, horizontalPrecision, verticalPrecision; uint32_t latitude, longitude, altitude; if (dataLength < 16 || buffer[i] != 0) @throw [OFInvalidServerResponseException exception]; size = buffer[i + 1]; horizontalPrecision = buffer[i + 2]; verticalPrecision = buffer[i + 3]; latitude = (buffer[i + 4] << 24) | (buffer[i + 5] << 16) | (buffer[i + 6] << 8) | buffer[i + 7]; longitude = (buffer[i + 8] << 24) | (buffer[i + 9] << 16) | (buffer[i + 10] << 8) | buffer[i + 11]; altitude = (buffer[i + 12] << 24) | (buffer[i + 13] << 16) | (buffer[i + 14] << 8) | buffer[i + 15]; return [[[OFLOCDNSResourceRecord alloc] initWithName: name DNSClass: DNSClass size: size horizontalPrecision: horizontalPrecision verticalPrecision: verticalPrecision latitude: latitude longitude: longitude altitude: altitude TTL: TTL] autorelease]; } else if (recordType == OFDNSRecordTypeSRV && DNSClass == OFDNSClassIN) { uint16_t priority, weight, port; size_t j; OFString *target; if (dataLength < 6) @throw [OFInvalidServerResponseException exception]; priority = (buffer[i] << 8) | buffer[i + 1]; weight = (buffer[i + 2] << 8) | buffer[i + 3]; port = (buffer[i + 4] << 8) | buffer[i + 5]; j = i + 6; target = parseName(buffer, length, &j, maxAllowedPointers); if (j != i + dataLength) @throw [OFInvalidServerResponseException exception]; return [[[OFSRVDNSResourceRecord alloc] initWithName: name priority: priority weight: weight target: target port: port TTL: TTL] autorelease]; } else if (recordType == OFDNSRecordTypeURI) { uint16_t priority, weight; OFString *target; if (dataLength < 4) @throw [OFInvalidServerResponseException exception]; priority = (buffer[i] << 8) | buffer[i + 1]; weight = (buffer[i + 2] << 8) | buffer[i + 3]; target = [OFString stringWithUTF8String: (char *)buffer + i + 4 length: dataLength - 4]; return [[[OFURIDNSResourceRecord alloc] initWithName: name DNSClass: DNSClass priority: priority weight: weight target: target TTL: TTL] autorelease]; } else return [[[OFDNSResourceRecord alloc] initWithName: name DNSClass: DNSClass recordType: recordType TTL: TTL] autorelease]; } static OFDictionary * parseSection(const unsigned char *buffer, size_t length, size_t *i, uint_fast16_t count) { OFMutableDictionary *ret = [OFMutableDictionary dictionary]; OFEnumerator OF_GENERIC(OFMutableArray *) *objectEnumerator; OFMutableArray *array; for (uint_fast16_t j = 0; j < count; j++) { OFString *name = parseName(buffer, length, i, maxAllowedPointers).lowercaseString; OFDNSClass DNSClass; OFDNSRecordType recordType; uint32_t TTL; uint16_t dataLength; OFDNSResourceRecord *record; if (*i + 10 > length) @throw [OFTruncatedDataException exception]; recordType = (buffer[*i] << 8) | buffer[*i + 1]; DNSClass = (buffer[*i + 2] << 8) | buffer[*i + 3]; TTL = (buffer[*i + 4] << 24) | (buffer[*i + 5] << 16) | (buffer[*i + 6] << 8) | buffer[*i + 7]; dataLength = (buffer[*i + 8] << 8) | buffer[*i + 9]; *i += 10; if (*i + dataLength > length) @throw [OFTruncatedDataException exception]; record = parseResourceRecord(name, DNSClass, recordType, TTL, buffer, length, *i, dataLength); *i += dataLength; array = [ret objectForKey: name]; if (array == nil) { array = [OFMutableArray array]; [ret setObject: array forKey: name]; } [array addObject: record]; } objectEnumerator = [ret objectEnumerator]; while ((array = [objectEnumerator nextObject]) != nil) [array makeImmutable]; [ret makeImmutable]; return ret; } static bool containsExpiredRecord(OFDNSResponseRecords responseRecords, uint32_t age) { OFEnumerator *enumerator = [responseRecords objectEnumerator]; OFArray OF_GENERIC(OFDNSResourceRecord *) *records; while ((records = [enumerator nextObject]) != nil) for (OFDNSResourceRecord *record in records) if (record.TTL < age) return true; return false; } @implementation OFDNSResolverContext - (instancetype)initWithQuery: (OFDNSQuery *)query ID: (OFNumber *)ID settings: (OFDNSResolverSettings *)settings delegate: (id <OFDNSResolverQueryDelegate>)delegate { self = [super init]; @try { void *pool = objc_autoreleasePoolPush(); OFMutableData *queryData; uint16_t tmp; _query = [query copy]; _ID = [ID retain]; _settings = [settings copy]; _delegate = [delegate retain]; queryData = [OFMutableData dataWithCapacity: 512]; /* Header */ tmp = OFToBigEndian16(_ID.unsignedShortValue); [queryData addItems: &tmp count: 2]; /* RD */ tmp = OFToBigEndian16(1u << 8); [queryData addItems: &tmp count: 2]; /* QDCOUNT */ tmp = OFToBigEndian16(1); [queryData addItems: &tmp count: 2]; /* ANCOUNT, NSCOUNT and ARCOUNT */ [queryData increaseCountBy: 6]; /* Question */ /* QNAME */ for (OFString *component in [_query.domainName componentsSeparatedByString: @"."]) { size_t length = component.UTF8StringLength; uint8_t length8; if (length > 63 || queryData.count + length > 512) @throw [OFOutOfRangeException exception]; length8 = (uint8_t)length; [queryData addItem: &length8]; [queryData addItems: component.UTF8String count: length]; } /* QTYPE */ tmp = OFToBigEndian16(_query.recordType); [queryData addItems: &tmp count: 2]; /* QCLASS */ tmp = OFToBigEndian16(_query.DNSClass); [queryData addItems: &tmp count: 2]; [queryData makeImmutable]; _queryData = [queryData copy]; objc_autoreleasePoolPop(pool); } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [_query release]; [_ID release]; [_settings release]; [_delegate release]; [_queryData release]; [_TCPSocket release]; [_TCPQueryData release]; OFFreeMemory(_TCPBuffer); [_cancelTimer release]; [super dealloc]; } @end @implementation OFDNSResolver #ifdef OF_AMIGAOS + (void)initialize { if (self != [OFDNSResolver class]) return; if (!OFSocketInit()) @throw [OFInitializationFailedException exceptionWithClass: self]; } #endif + (instancetype)resolver { return [[[self alloc] init] autorelease]; } - (instancetype)init { self = [super init]; @try { _settings = [[OFDNSResolverSettings alloc] init]; _queries = [[OFMutableDictionary alloc] init]; _TCPQueries = [[OFMutableDictionary alloc] init]; _cache = [[OFMutableDictionary alloc] init]; [_settings reload]; } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [self close]; [_settings release]; [_IPv4Socket cancelAsyncRequests]; [_IPv4Socket release]; #ifdef OF_HAVE_IPV6 [_IPv6Socket cancelAsyncRequests]; [_IPv6Socket release]; #endif [_queries release]; [_TCPQueries release]; [_cache release]; [_lastNameServers release]; [super dealloc]; } - (OFDictionary *)staticHosts { return _settings->_staticHosts; } - (void)setStaticHosts: (OFDictionary *)staticHosts { OFDictionary *old = _settings->_staticHosts; _settings->_staticHosts = [staticHosts copy]; [old release]; } - (OFArray *)nameServers { return _settings->_nameServers; } - (void)setNameServers: (OFArray *)nameServers { OFArray *old = _settings->_nameServers; _settings->_nameServers = [nameServers copy]; [old release]; } - (OFString *)localDomain { return _settings->_localDomain; } - (OFArray *)searchDomains { return _settings->_searchDomains; } - (void)setSearchDomains: (OFArray *)searchDomains { OFArray *old = _settings->_searchDomains; _settings->_searchDomains = [searchDomains copy]; [old release]; } - (OFTimeInterval)timeout { return _settings->_timeout; } - (void)setTimeout: (OFTimeInterval)timeout { _settings->_timeout = timeout; } - (unsigned int)maxAttempts { return _settings->_maxAttempts; } - (void)setMaxAttempts: (unsigned int)maxAttempts { _settings->_maxAttempts = maxAttempts; } - (unsigned int)minNumberOfDotsInAbsoluteName { return _settings->_minNumberOfDotsInAbsoluteName; } - (void)setMinNumberOfDotsInAbsoluteName: (unsigned int)minNumberOfDotsInAbsoluteName { _settings->_minNumberOfDotsInAbsoluteName = minNumberOfDotsInAbsoluteName; } - (bool)forcesTCP { return _settings->_forcesTCP; } - (void)setForcesTCP: (bool)forcesTCP { _settings->_forcesTCP = forcesTCP; } - (OFTimeInterval)configReloadInterval { return _settings->_configReloadInterval; } - (void)setConfigReloadInterval: (OFTimeInterval)configReloadInterval { _settings->_configReloadInterval = configReloadInterval; } - (void)of_sendQueryForContext: (OFDNSResolverContext *)context runLoopMode: (OFRunLoopMode)runLoopMode { OFUDPSocket *sock; OFString *nameServer; [_queries setObject: context forKey: context->_ID]; [context->_cancelTimer invalidate]; [context->_cancelTimer release]; context->_cancelTimer = nil; context->_cancelTimer = [[OFTimer alloc] initWithFireDate: [OFDate dateWithTimeIntervalSinceNow: context->_settings->_timeout] interval: context->_settings->_timeout target: self selector: @selector(of_contextTimedOut:) object: context repeats: false]; [[OFRunLoop currentRunLoop] addTimer: context->_cancelTimer forMode: runLoopMode]; nameServer = [context->_settings->_nameServers objectAtIndex: context->_nameServersIndex]; if (context->_settings->_forcesTCP) { OFEnsure(context->_TCPSocket == nil); context->_TCPSocket = [[OFTCPSocket alloc] init]; [_TCPQueries setObject: context forKey: context->_TCPSocket]; context->_TCPSocket.delegate = self; [context->_TCPSocket asyncConnectToHost: nameServer port: 53 runLoopMode: runLoopMode]; return; } context->_usedNameServer = OFSocketAddressParseIP(nameServer, 53); switch (context->_usedNameServer.family) { #ifdef OF_HAVE_IPV6 case OFSocketAddressFamilyIPv6: if (_IPv6Socket == nil) { OFSocketAddress address = OFSocketAddressParseIPv6(@"::", 0); _IPv6Socket = [[OFUDPSocket alloc] init]; [_IPv6Socket of_bindToAddress: &address extraType: SOCK_DNS]; @try { _IPv6Socket.canBlock = false; } @catch (OFNotImplementedException *e) { /* Can't do anything about it... */ } _IPv6Socket.delegate = self; } sock = _IPv6Socket; break; #endif case OFSocketAddressFamilyIPv4: if (_IPv4Socket == nil) { OFSocketAddress address = OFSocketAddressParseIPv4(@"0.0.0.0", 0); _IPv4Socket = [[OFUDPSocket alloc] init]; [_IPv4Socket of_bindToAddress: &address extraType: SOCK_DNS]; @try { _IPv4Socket.canBlock = false; } @catch (OFNotImplementedException *e) { /* Can't do anything about it... */ } _IPv4Socket.delegate = self; } sock = _IPv4Socket; break; default: @throw [OFInvalidArgumentException exception]; } [sock asyncSendData: context->_queryData receiver: &context->_usedNameServer runLoopMode: runLoopMode]; [sock asyncReceiveIntoBuffer: _buffer length: bufferLength runLoopMode: runLoopMode]; } - (void)of_cleanUpCache { OFTimeInterval now = [[OFDate date] timeIntervalSince1970]; OFMutableArray *removeList; if (_lastNameServers != _settings->_nameServers && ![_lastNameServers isEqual: _settings->_nameServers]) { OFArray *old = _lastNameServers; _lastNameServers = [_settings->_nameServers copy]; [old release]; [_cache removeAllObjects]; return; } if (now - _lastCacheCleanup < 1) return; _lastCacheCleanup = now; removeList = [OFMutableArray arrayWithCapacity: _cache.count]; for (OFDNSQuery *query in _cache) { OFPair OF_GENERIC(OFDate *, OFDNSResponse *) *entry = [_cache objectForKey: query]; uint32_t age = (uint32_t)now - (uint32_t)[entry.firstObject timeIntervalSince1970]; OFDNSResponse *response = entry.secondObject; if (containsExpiredRecord(response.answerRecords, age) || containsExpiredRecord(response.authorityRecords, age) || containsExpiredRecord(response.additionalRecords, age)) [removeList addObject: query]; } for (OFDNSQuery *query in removeList) [_cache removeObjectForKey: query]; } - (void)asyncPerformQuery: (OFDNSQuery *)query delegate: (id <OFDNSResolverQueryDelegate>)delegate { [self asyncPerformQuery: query runLoopMode: OFDefaultRunLoopMode delegate: delegate]; } - (void)asyncPerformQuery: (OFDNSQuery *)query runLoopMode: (OFRunLoopMode)runLoopMode delegate: (id <OFDNSResolverQueryDelegate>)delegate { void *pool = objc_autoreleasePoolPush(); OFNumber *ID; OFDNSResolverContext *context; OFPair OF_GENERIC(OFDate *, OFDNSResponse *) *cacheEntry; [self of_cleanUpCache]; if ((cacheEntry = [_cache objectForKey: query]) != nil) { uint32_t age = (uint32_t)-[cacheEntry.firstObject timeIntervalSinceNow]; OFDNSResponse *response = cacheEntry.secondObject; if (!containsExpiredRecord(response.answerRecords, age) && !containsExpiredRecord(response.authorityRecords, age) && !containsExpiredRecord(response.additionalRecords, age)) { OFTimer *timer = [OFTimer timerWithTimeInterval: 0 target: delegate selector: @selector(resolver: didPerformQuery:response: exception:) object: self object: query object: response object: nil repeats: false]; [[OFRunLoop currentRunLoop] addTimer: timer forMode: runLoopMode]; objc_autoreleasePoolPop(pool); return; } } /* Random, unused ID */ do { ID = [OFNumber numberWithUnsignedShort: OFRandom16()]; } while ([_queries objectForKey: ID] != nil); if (query.domainName.UTF8StringLength > 253) @throw [OFOutOfRangeException exception]; if (_settings->_nameServers.count == 0) { id exception = [OFDNSQueryFailedException exceptionWithQuery: query errorCode: OFDNSResolverErrorCodeNoNameServer]; [delegate resolver: self didPerformQuery: query response: nil exception: exception]; return; } context = [[[OFDNSResolverContext alloc] initWithQuery: query ID: ID settings: _settings delegate: delegate] autorelease]; [self of_sendQueryForContext: context runLoopMode: runLoopMode]; objc_autoreleasePoolPop(pool); } - (void)of_contextTimedOut: (OFDNSResolverContext *)context { OFRunLoopMode runLoopMode = [OFRunLoop currentRunLoop].currentMode; OFDNSQueryFailedException *exception; if (context->_TCPSocket != nil) { context->_TCPSocket.delegate = nil; [context->_TCPSocket cancelAsyncRequests]; [_TCPQueries removeObjectForKey: context->_TCPSocket]; [context->_TCPSocket release]; context->_TCPSocket = nil; context->_responseLength = 0; } if (context->_nameServersIndex + 1 < context->_settings->_nameServers.count) { context->_nameServersIndex++; [self of_sendQueryForContext: context runLoopMode: runLoopMode]; return; } if (++context->_attempt < context->_settings->_maxAttempts) { context->_nameServersIndex = 0; [self of_sendQueryForContext: context runLoopMode: runLoopMode]; return; } context = [[context retain] autorelease]; [_queries removeObjectForKey: context->_ID]; /* * Cancel any pending queries, to avoid a send being still pending and * trying to access the query once it no longer exists. */ [_IPv4Socket cancelAsyncRequests]; [_IPv4Socket asyncReceiveIntoBuffer: _buffer length: bufferLength]; #ifdef OF_HAVE_IPV6 [_IPv6Socket cancelAsyncRequests]; [_IPv6Socket asyncReceiveIntoBuffer: _buffer length: bufferLength]; #endif exception = [OFDNSQueryFailedException exceptionWithQuery: context->_query errorCode: OFDNSResolverErrorCodeTimeout]; [context->_delegate resolver: self didPerformQuery: context->_query response: nil exception: exception]; } - (bool)of_handleResponseBuffer: (unsigned char *)buffer length: (size_t)length sender: (const OFSocketAddress *)sender { OFDictionary *answerRecords = nil, *authorityRecords = nil; OFDictionary *additionalRecords = nil; OFDNSResponse *response = nil; id exception = nil; OFNumber *ID; OFDNSResolverContext *context; if (length < 2) /* We can't get the ID to get the context. Ignore packet. */ return true; ID = [OFNumber numberWithUnsignedShort: (buffer[0] << 8) | buffer[1]]; context = [[[_queries objectForKey: ID] retain] autorelease]; if (context == nil) return true; if (context->_TCPSocket != nil) { if ([_TCPQueries objectForKey: context->_TCPSocket] != context) return true; } else if (sender == NULL || !OFSocketAddressEqual(sender, &context->_usedNameServer)) return true; [context->_cancelTimer invalidate]; [context->_cancelTimer release]; context->_cancelTimer = nil; [_queries removeObjectForKey: ID]; @try { OFDNSResolverErrorCode errorCode = 0; bool tryNextNameServer = false; const unsigned char *queryDataBuffer; size_t i; uint16_t numQuestions, numAnswers, numAuthorityRecords; uint16_t numAdditionalRecords; if (length < 12) @throw [OFTruncatedDataException exception]; if (context->_queryData.itemSize != 1 || context->_queryData.count < 12) @throw [OFInvalidArgumentException exception]; queryDataBuffer = context->_queryData.items; /* QR */ if ((buffer[2] & 0x80) == 0) @throw [OFInvalidServerResponseException exception]; /* Opcode */ if ((buffer[2] & 0x78) != (queryDataBuffer[2] & 0x78)) @throw [OFInvalidServerResponseException exception]; /* TC */ if (buffer[2] & 0x02) { OFRunLoopMode runLoopMode; if (context->_settings->_forcesTCP) @throw [OFTruncatedDataException exception]; context->_settings->_forcesTCP = true; runLoopMode = [OFRunLoop currentRunLoop].currentMode; [self of_sendQueryForContext: context runLoopMode: runLoopMode]; return false; } /* RCODE */ switch (buffer[3] & 0x0F) { case 0: break; case 1: errorCode = OFDNSResolverErrorCodeServerInvalidFormat; break; case 2: errorCode = OFDNSResolverErrorCodeServerFailure; tryNextNameServer = true; break; case 3: errorCode = OFDNSResolverErrorCodeServerNameError; break; case 4: errorCode = OFDNSResolverErrorCodeServerNotImplemented; tryNextNameServer = true; break; case 5: errorCode = OFDNSResolverErrorCodeServerRefused; tryNextNameServer = true; break; default: errorCode = OFDNSResolverErrorCodeUnknown; tryNextNameServer = true; break; } if (tryNextNameServer) { if (context->_nameServersIndex + 1 < context->_settings->_nameServers.count) { OFRunLoopMode runLoopMode = [OFRunLoop currentRunLoop].currentMode; context->_nameServersIndex++; [self of_sendQueryForContext: context runLoopMode: runLoopMode]; return false; } } if (buffer[3] & 0x0F) @throw [OFDNSQueryFailedException exceptionWithQuery: context->_query errorCode: errorCode]; numQuestions = (buffer[4] << 8) | buffer[5]; numAnswers = (buffer[6] << 8) | buffer[7]; numAuthorityRecords = (buffer[8] << 8) | buffer[9]; numAdditionalRecords = (buffer[10] << 8) | buffer[11]; i = 12; /* * Skip over the questions - we use the ID to identify the * query. * * TODO: Compare to our query, just in case? */ for (uint_fast16_t j = 0; j < numQuestions; j++) { parseName(buffer, length, &i, maxAllowedPointers); i += 4; } answerRecords = parseSection(buffer, length, &i, numAnswers); authorityRecords = parseSection(buffer, length, &i, numAuthorityRecords); additionalRecords = parseSection(buffer, length, &i, numAdditionalRecords); response = [OFDNSResponse responseWithDomainName: context->_query.domainName answerRecords: answerRecords authorityRecords: authorityRecords additionalRecords: additionalRecords]; } @catch (id e) { exception = e; } if (exception != nil) response = nil; [self of_cleanUpCache]; if (response != nil) [_cache setObject: [OFPair pairWithFirstObject: [OFDate date] secondObject: response] forKey: context->_query]; else [_cache removeObjectForKey: context->_query]; [context->_delegate resolver: self didPerformQuery: context->_query response: response exception: exception]; return false; } - (bool)socket: (OFDatagramSocket *)sock didReceiveIntoBuffer: (void *)buffer length: (size_t)length sender: (const OFSocketAddress *)sender exception: (id)exception { if (exception != nil) return true; return [self of_handleResponseBuffer: buffer length: length sender: sender]; } - (void)socket: (OFTCPSocket *)sock didConnectToHost: (OFString *)host port: (uint16_t)port exception: (id)exception { OFDNSResolverContext *context = [_TCPQueries objectForKey: sock]; OFEnsure(context != nil); if (exception != nil) { /* * TODO: Handle error immediately instead of waiting for the * timer to try the next nameserver or to retry. */ [_TCPQueries removeObjectForKey: context->_TCPSocket]; [context->_TCPSocket release]; context->_TCPSocket = nil; context->_responseLength = 0; return; } if (context->_TCPQueryData == nil) { size_t queryDataCount = context->_queryData.count; uint16_t tmp; if (queryDataCount > UINT16_MAX) @throw [OFOutOfRangeException exception]; context->_TCPQueryData = [[OFMutableData alloc] initWithCapacity: queryDataCount + 2]; tmp = OFToBigEndian16(queryDataCount); [context->_TCPQueryData addItems: &tmp count: sizeof(tmp)]; [context->_TCPQueryData addItems: context->_queryData.items count: queryDataCount]; } [sock asyncWriteData: context->_TCPQueryData]; } - (OFData *)stream: (OFStream *)stream didWriteData: (OFData *)data bytesWritten: (size_t)bytesWritten exception: (id)exception { OFTCPSocket *sock = (OFTCPSocket *)stream; OFDNSResolverContext *context = [_TCPQueries objectForKey: sock]; OFEnsure(context != nil); if (exception != nil) { /* * TODO: Handle error immediately instead of waiting for the * timer to try the next nameserver or to retry. */ [_TCPQueries removeObjectForKey: context->_TCPSocket]; [context->_TCPSocket release]; context->_TCPSocket = nil; context->_responseLength = 0; return nil; } if (context->_TCPBuffer == nil) context->_TCPBuffer = OFAllocMemory(maxDNSResponseLength, 1); [sock asyncReadIntoBuffer: context->_TCPBuffer exactLength: 2]; return nil; } - (bool)stream: (OFStream *)stream didReadIntoBuffer: (void *)buffer length: (size_t)length exception: (id)exception { OFTCPSocket *sock = (OFTCPSocket *)stream; OFDNSResolverContext *context = [_TCPQueries objectForKey: sock]; OFEnsure(context != nil); if (exception != nil) { /* * TODO: Handle error immediately instead of waiting for the * timer to try the next nameserver or to retry. */ goto done; } if (context->_responseLength == 0) { unsigned char *ucBuffer = buffer; OFEnsure(length == 2); context->_responseLength = (ucBuffer[0] << 8) | ucBuffer[1]; if (context->_responseLength > maxDNSResponseLength) @throw [OFOutOfRangeException exception]; if (context->_responseLength == 0) goto done; [sock asyncReadIntoBuffer: context->_TCPBuffer exactLength: context->_responseLength]; return false; } if (length != context->_responseLength) /* * The connection was closed before we received the entire * response. */ goto done; [self of_handleResponseBuffer: buffer length: length sender: NULL]; done: [_TCPQueries removeObjectForKey: context->_TCPSocket]; [context->_TCPSocket release]; context->_TCPSocket = nil; context->_responseLength = 0; return false; } - (void)asyncResolveAddressesForHost: (OFString *)host delegate: (id <OFDNSResolverHostDelegate>)delegate { [self asyncResolveAddressesForHost: host addressFamily: OFSocketAddressFamilyAny runLoopMode: OFDefaultRunLoopMode delegate: delegate]; } - (void)asyncResolveAddressesForHost: (OFString *)host addressFamily: (OFSocketAddressFamily)addressFamily delegate: (id <OFDNSResolverHostDelegate>)delegate { [self asyncResolveAddressesForHost: host addressFamily: addressFamily runLoopMode: OFDefaultRunLoopMode delegate: delegate]; } - (void)asyncResolveAddressesForHost: (OFString *)host addressFamily: (OFSocketAddressFamily)addressFamily runLoopMode: (OFRunLoopMode)runLoopMode delegate: (id <OFDNSResolverHostDelegate>)delegate { void *pool = objc_autoreleasePoolPush(); OFHostAddressResolver *resolver = [[[OFHostAddressResolver alloc] initWithHost: host addressFamily: addressFamily resolver: self settings: _settings runLoopMode: runLoopMode delegate: delegate] autorelease]; [resolver asyncResolve]; objc_autoreleasePoolPop(pool); } - (OFData *)resolveAddressesForHost: (OFString *)host addressFamily: (OFSocketAddressFamily)addressFamily { void *pool = objc_autoreleasePoolPush(); OFHostAddressResolver *resolver = [[[OFHostAddressResolver alloc] initWithHost: host addressFamily: addressFamily resolver: self settings: _settings runLoopMode: nil delegate: nil] autorelease]; OFData *addresses = [resolver resolve]; [addresses retain]; objc_autoreleasePoolPop(pool); return [addresses autorelease]; } - (void)close { void *pool = objc_autoreleasePoolPush(); OFEnumerator OF_GENERIC(OFDNSResolverContext *) *enumerator; OFDNSResolverContext *context; [_IPv4Socket cancelAsyncRequests]; [_IPv4Socket release]; _IPv4Socket = nil; #ifdef OF_HAVE_IPV6 [_IPv6Socket cancelAsyncRequests]; [_IPv6Socket release]; _IPv6Socket = nil; #endif enumerator = [_queries objectEnumerator]; while ((context = [enumerator nextObject]) != nil) { OFDNSQueryFailedException *exception; exception = [OFDNSQueryFailedException exceptionWithQuery: context->_query errorCode: OFDNSResolverErrorCodeCanceled]; [context->_delegate resolver: self didPerformQuery: context->_query response: nil exception: exception]; } [_queries removeAllObjects]; objc_autoreleasePoolPop(pool); } @end