/* * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, * 2018, 2019 * Jonathan Schleifer * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in * the packaging of this file. * * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #include "config.h" #include #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 "OFNumber.h" #import "OFPair.h" #import "OFString.h" #import "OFTimer.h" #import "OFUDPSocket.h" #import "OFUDPSocket+Private.h" #import "OFDNSQueryFailedException.h" #import "OFInitializationFailedException.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFInvalidServerReplyException.h" #import "OFOutOfRangeException.h" #import "OFTruncatedDataException.h" #ifndef SOCK_DNS # define SOCK_DNS 0 #endif #define BUFFER_LENGTH OF_DNS_RESOLVER_BUFFER_LENGTH /* * 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. */ #define MAX_ALLOWED_POINTERS 16 #define CNAME_RECURSION 3 /* * TODO: * * - Fallback to TCP */ static const of_run_loop_mode_t resolveRunLoopMode = @"of_dns_resolver_resolve_mode"; @interface OFDNSResolverQuery: OFObject { @public OFDNSQuery *_query; OFString *_domainName; OFNumber *_ID; OFDNSResolverSettings *_settings; size_t _nameServersIndex, _searchDomainsIndex; unsigned int _attempt; id _target; SEL _selector; id _context; OFData *_queryData; of_socket_address_t _usedNameServer; OFTimer *_cancelTimer; } - (instancetype)initWithQuery: (OFDNSQuery *)query domainName: (OFString *)domainName ID: (OFNumber *)ID settings: (OFDNSResolverSettings *)settings nameServersIndex: (size_t)nameServersIndex searchDomainsIndex: (size_t)searchDomainsIndex target: (id)target selector: (SEL)selector context: (id)context; @end @interface OFDNSResolverAsyncResolveSocketAddressesContext: OFObject { OFString *_host; id _delegate; OFMutableArray OF_GENERIC(OF_KINDOF(OFDNSResourceRecord *)) *_records; OFDNSResolver *_resolver; OFString *_domainName; @public unsigned int _expectedResponses; } - (instancetype)initWithHost: (OFString *)host delegate: (id)delegate; - (bool)parseRecords: (OFArray *)records response: (OFDNSResponse *)response recordType: (of_dns_resource_record_type_t)recordType recursion: (unsigned int)recursion result: (OFMutableArray *)result; - (void)resolveCNAME: (OFCNAMEDNSResourceRecord *)CNAME response: (OFDNSResponse *)response recordType: (of_dns_resource_record_type_t)recordType recursion: (unsigned int)recursion result: (OFMutableArray *)result; - (void)resolver: (OFDNSResolver *)resolver didResolveCNAME: (OFString *)CNAME response: (OFDNSResponse *)response context: (OFNumber *)context exception: (id)exception; - (void)done; - (void)resolver: (OFDNSResolver *)resolver didResolveDomainName: (OFString *)domainName response: (OFDNSResponse *)response context: (OFNumber *)context exception: (id)exception; @end @interface OFDNSResolverResolveSocketAddressesDelegate: OFObject { @public bool _done; OFData *_socketAddresses; id _exception; } @end @interface OFDNSResolver () - (void)of_asyncPerformQuery: (OFDNSQuery *)query settings: (OFDNSResolverSettings *)settings nameServersIndex: (size_t)nameServersIndex searchDomainsIndex: (size_t)searchDomainsIndex runLoopMode: (of_run_loop_mode_t)runLoopMode target: (id)target selector: (SEL)selector context: (id)context; - (void)of_sendQuery: (OFDNSResolverQuery *)query runLoopMode: (of_run_loop_mode_t)runLoopMode; - (void)of_queryWithIDTimedOut: (OFDNSResolverQuery *)query; @end static bool isFQDN(OFString *host, OFDNSResolverSettings *settings) { const char *UTF8String = host.UTF8String; size_t length = host.UTF8StringLength; unsigned int dots = 0; if ([host hasSuffix: @"."]) return true; for (size_t i = 0; i < length; i++) if (UTF8String[i] == '.') dots++; return (dots >= settings->_minNumberOfDotsInAbsoluteName); } 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 [OFInvalidServerReplyException exception]; if (*i >= length) @throw [OFTruncatedDataException exception]; j = ((componentLength & 0x3F) << 8) | buffer[(*i)++]; if (j == *i - 2) /* Pointing to itself?! */ @throw [OFInvalidServerReplyException 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, of_dns_resource_record_class_t recordClass, of_dns_resource_record_type_t recordType, uint32_t TTL, const unsigned char *buffer, size_t length, size_t i, uint16_t dataLength) { if (recordType == OF_DNS_RESOURCE_RECORD_TYPE_A && recordClass == OF_DNS_RESOURCE_RECORD_CLASS_IN) { of_socket_address_t address; if (dataLength != 4) @throw [OFInvalidServerReplyException exception]; memset(&address, 0, sizeof(address)); address.family = OF_SOCKET_ADDRESS_FAMILY_IPV4; 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 == OF_DNS_RESOURCE_RECORD_TYPE_NS) { size_t j = i; OFString *authoritativeHost = parseName(buffer, length, &j, MAX_ALLOWED_POINTERS); if (j != i + dataLength) @throw [OFInvalidServerReplyException exception]; return [[[OFNSDNSResourceRecord alloc] initWithName: name recordClass: recordClass authoritativeHost: authoritativeHost TTL: TTL] autorelease]; } else if (recordType == OF_DNS_RESOURCE_RECORD_TYPE_CNAME) { size_t j = i; OFString *alias = parseName(buffer, length, &j, MAX_ALLOWED_POINTERS); if (j != i + dataLength) @throw [OFInvalidServerReplyException exception]; return [[[OFCNAMEDNSResourceRecord alloc] initWithName: name recordClass: recordClass alias: alias TTL: TTL] autorelease]; } else if (recordType == OF_DNS_RESOURCE_RECORD_TYPE_SOA) { size_t j = i; OFString *primaryNameServer = parseName(buffer, length, &j, MAX_ALLOWED_POINTERS); OFString *responsiblePerson; uint32_t serialNumber, refreshInterval, retryInterval; uint32_t expirationInterval, minTTL; if (j > i + dataLength) @throw [OFInvalidServerReplyException exception]; responsiblePerson = parseName(buffer, length, &j, MAX_ALLOWED_POINTERS); if (dataLength - (j - i) != 20) @throw [OFInvalidServerReplyException 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 recordClass: recordClass primaryNameServer: primaryNameServer responsiblePerson: responsiblePerson serialNumber: serialNumber refreshInterval: refreshInterval retryInterval: retryInterval expirationInterval: expirationInterval minTTL: minTTL TTL: TTL] autorelease]; } else if (recordType == OF_DNS_RESOURCE_RECORD_TYPE_PTR) { size_t j = i; OFString *domainName = parseName(buffer, length, &j, MAX_ALLOWED_POINTERS); if (j != i + dataLength) @throw [OFInvalidServerReplyException exception]; return [[[OFPTRDNSResourceRecord alloc] initWithName: name recordClass: recordClass domainName: domainName TTL: TTL] autorelease]; } else if (recordType == OF_DNS_RESOURCE_RECORD_TYPE_HINFO) { size_t j = i; OFString *CPU = parseString(buffer, length, &j); OFString *OS; if (j > i + dataLength) @throw [OFInvalidServerReplyException exception]; OS = parseString(buffer, length, &j); if (j != i + dataLength) @throw [OFInvalidServerReplyException exception]; return [[[OFHINFODNSResourceRecord alloc] initWithName: name recordClass: recordClass CPU: CPU OS: OS TTL: TTL] autorelease]; } else if (recordType == OF_DNS_RESOURCE_RECORD_TYPE_MX) { uint16_t preference; size_t j; OFString *mailExchange; if (dataLength < 2) @throw [OFInvalidServerReplyException exception]; preference = (buffer[i] << 8) | buffer[i + 1]; j = i + 2; mailExchange = parseName(buffer, length, &j, MAX_ALLOWED_POINTERS); if (j != i + dataLength) @throw [OFInvalidServerReplyException exception]; return [[[OFMXDNSResourceRecord alloc] initWithName: name recordClass: recordClass preference: preference mailExchange: mailExchange TTL: TTL] autorelease]; } else if (recordType == OF_DNS_RESOURCE_RECORD_TYPE_TXT) { OFData *textData = [OFData dataWithItems: &buffer[i] count: dataLength]; return [[[OFTXTDNSResourceRecord alloc] initWithName: name recordClass: recordClass textData: textData TTL: TTL] autorelease]; } else if (recordType == OF_DNS_RESOURCE_RECORD_TYPE_RP) { size_t j = i; OFString *mailbox = parseName(buffer, length, &j, MAX_ALLOWED_POINTERS); OFString *TXTDomainName; if (j > i + dataLength) @throw [OFInvalidServerReplyException exception]; TXTDomainName = parseName(buffer, length, &j, MAX_ALLOWED_POINTERS); if (j != i + dataLength) @throw [OFInvalidServerReplyException exception]; return [[[OFRPDNSResourceRecord alloc] initWithName: name recordClass: recordClass mailbox: mailbox TXTDomainName: TXTDomainName TTL: TTL] autorelease]; } else if (recordType == OF_DNS_RESOURCE_RECORD_TYPE_AAAA && recordClass == OF_DNS_RESOURCE_RECORD_CLASS_IN) { of_socket_address_t address; if (dataLength != 16) @throw [OFInvalidServerReplyException exception]; memset(&address, 0, sizeof(address)); address.family = OF_SOCKET_ADDRESS_FAMILY_IPV6; 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 == OF_DNS_RESOURCE_RECORD_TYPE_SRV && recordClass == OF_DNS_RESOURCE_RECORD_CLASS_IN) { uint16_t priority, weight, port; size_t j; OFString *target; if (dataLength < 6) @throw [OFInvalidServerReplyException 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, MAX_ALLOWED_POINTERS); if (j != i + dataLength) @throw [OFInvalidServerReplyException exception]; return [[[OFSRVDNSResourceRecord alloc] initWithName: name priority: priority weight: weight target: target port: port TTL: TTL] autorelease]; } else return [[[OFDNSResourceRecord alloc] initWithName: name recordClass: recordClass 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, MAX_ALLOWED_POINTERS); of_dns_resource_record_class_t recordClass; of_dns_resource_record_type_t recordType; uint32_t TTL; uint16_t dataLength; OFDNSResourceRecord *record; if (*i + 10 > length) @throw [OFTruncatedDataException exception]; recordType = (buffer[*i] << 16) | buffer[*i + 1]; recordClass = (buffer[*i + 2] << 16) | buffer[*i + 3]; TTL = (buffer[*i + 4] << 24) | (buffer[*i + 5] << 16) | (buffer[*i + 6] << 8) | buffer[*i + 7]; dataLength = (buffer[*i + 8] << 16) | buffer[*i + 9]; *i += 10; if (*i + dataLength > length) @throw [OFTruncatedDataException exception]; record = parseResourceRecord(name, recordClass, 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 void callback(id target, SEL selector, OFDNSResolver *resolver, OFString *domainName, OFDNSResponse *response, id context, id exception) { void (*method)(id, SEL, OFDNSResolver *, OFString *, OFDNSResponse *, id, id) = (void (*)(id, SEL, OFDNSResolver *, OFString *, OFDNSResponse *, id, id))[target methodForSelector: selector]; method(target, selector, resolver, domainName, response, context, exception); } @implementation OFDNSResolverQuery - (instancetype)initWithQuery: (OFDNSQuery *)query domainName: (OFString *)domainName ID: (OFNumber *)ID settings: (OFDNSResolverSettings *)settings nameServersIndex: (size_t)nameServersIndex searchDomainsIndex: (size_t)searchDomainsIndex target: (id)target selector: (SEL)selector context: (id)context { self = [super init]; @try { void *pool = objc_autoreleasePoolPush(); OFMutableData *queryData; uint16_t tmp; _query = [query copy]; _domainName = [domainName copy]; _ID = [ID retain]; _settings = [settings copy]; _nameServersIndex = nameServersIndex; _searchDomainsIndex = searchDomainsIndex; _target = [target retain]; _selector = selector; _context = [context retain]; queryData = [OFMutableData dataWithCapacity: 512]; /* Header */ tmp = OF_BSWAP16_IF_LE(_ID.uInt16Value); [queryData addItems: &tmp count: 2]; /* RD */ tmp = OF_BSWAP16_IF_LE(1u << 8); [queryData addItems: &tmp count: 2]; /* QDCOUNT */ tmp = OF_BSWAP16_IF_LE(1); [queryData addItems: &tmp count: 2]; /* ANCOUNT, NSCOUNT and ARCOUNT */ [queryData increaseCountBy: 6]; /* Question */ /* QNAME */ for (OFString *component in [_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 = OF_BSWAP16_IF_LE(_query.recordType); [queryData addItems: &tmp count: 2]; /* QCLASS */ tmp = OF_BSWAP16_IF_LE(_query.recordClass); [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]; [_domainName release]; [_ID release]; [_settings release]; [_target release]; [_context release]; [_queryData release]; [_cancelTimer release]; [super dealloc]; } @end @implementation OFDNSResolverAsyncResolveSocketAddressesContext - (instancetype)initWithHost: (OFString *)host delegate: (id)delegate { self = [super init]; @try { _host = [host copy]; _delegate = [delegate retain]; _records = [[OFMutableArray alloc] init]; } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [_host release]; [_delegate release]; [_records release]; [_resolver release]; [_domainName release]; [super dealloc]; } - (bool)parseRecords: (OFArray *)records response: (OFDNSResponse *)response recordType: (of_dns_resource_record_type_t)recordType recursion: (unsigned int)recursion result: (OFMutableArray *)result { bool found = false; for (OF_KINDOF(OFDNSResourceRecord *) record in records) { if ([record recordClass] != OF_DNS_RESOURCE_RECORD_CLASS_IN) continue; if ([record recordType] == recordType) { [result addObject: record]; found = true; } else if ([record recordType] == OF_DNS_RESOURCE_RECORD_TYPE_CNAME) { [self resolveCNAME: record response: response recordType: recordType recursion: recursion result: result]; found = true; } } return found; } - (void)resolveCNAME: (OFCNAMEDNSResourceRecord *)CNAME response: (OFDNSResponse *)response recordType: (of_dns_resource_record_type_t)recordType recursion: (unsigned int)recursion result: (OFMutableArray *)result { OFString *alias = CNAME.alias; bool found = false; if (recursion == 0) return; if ([self parseRecords: [response.answerRecords objectForKey: alias] response: response recordType: recordType recursion: recursion - 1 result: result]) found = true; if ([self parseRecords: [response.additionalRecords objectForKey: alias] response: response recordType: recordType recursion: recursion - 1 result: result]) found = true; if (!found) { of_run_loop_mode_t runLoopMode = [OFRunLoop currentRunLoop].currentMode; OFNumber *recordTypeNumber = [OFNumber numberWithInt: recordType]; OFDNSQuery *query; _expectedResponses++; [result addObject: [OFPair pairWithFirstObject: CNAME secondObject: recordTypeNumber]]; query = [OFDNSQuery queryWithHost: alias recordClass: OF_DNS_RESOURCE_RECORD_CLASS_IN recordType: recordType]; [_resolver of_asyncPerformQuery: query settings: nil nameServersIndex: 0 searchDomainsIndex: 0 runLoopMode: runLoopMode target: self selector: @selector(resolver: didResolveCNAME:response: context:exception:) context: recordTypeNumber]; } } - (void)resolver: (OFDNSResolver *)resolver didResolveCNAME: (OFString *)CNAME response: (OFDNSResponse *)response context: (OFNumber *)context exception: (id)exception { /* * TODO: Error handling could be improved. Ignore error if there are * responses, otherwise propagate error. */ of_dns_resource_record_type_t recordType = context.unsignedIntValue; bool found = false; OFMutableArray *records; size_t count; OF_ENSURE(resolver == _resolver); _expectedResponses--; if (exception != nil) { if (_expectedResponses == 0) [self done]; return; } records = [OFMutableArray array]; if ([self parseRecords: [response.answerRecords objectForKey: CNAME] response: response recordType: recordType recursion: CNAME_RECURSION result: records]) found = true; if ([self parseRecords: [response.additionalRecords objectForKey: CNAME] response: response recordType: recordType recursion: CNAME_RECURSION result: records]) found = true; if (!found) { if (_expectedResponses == 0) [self done]; return; } count = _records.count; for (size_t i = 0; i < count; i++) { id object = [_records objectAtIndex: i]; if (![object isKindOfClass: [OFPair class]]) continue; if (![[[object firstObject] alias] isEqual: CNAME]) continue; if ([[object secondObject] unsignedIntValue] != recordType) continue; [_records removeObjectAtIndex: i]; [_records insertObjectsFromArray: records atIndex: i]; i += records.count - 1; } if (_expectedResponses == 0) [self done]; } - (void)done { OFMutableData *addresses = [OFMutableData dataWithItemSize: sizeof(of_socket_address_t)]; id exception = nil; for (id record in _records) { if (![record isKindOfClass: [OFDNSResourceRecord class]]) continue; switch ([record recordType]) { case OF_DNS_RESOURCE_RECORD_TYPE_A: case OF_DNS_RESOURCE_RECORD_TYPE_AAAA: [addresses addItem: [record address]]; break; default: break; } } [addresses makeImmutable]; if (addresses.count == 0) { OFDNSQuery *query = [OFDNSQuery queryWithHost: _host recordClass: OF_DNS_RESOURCE_RECORD_CLASS_IN recordType: 0]; exception = [OFDNSQueryFailedException exceptionWithQuery: query error: OF_DNS_RESOLVER_ERROR_UNKNOWN]; } if ([_delegate respondsToSelector: @selector( resolver:didResolveDomainName:socketAddresses:exception:)]) [_delegate resolver: _resolver didResolveDomainName: _domainName socketAddresses: (exception == nil ? addresses : nil) exception: exception]; } - (void)resolver: (OFDNSResolver *)resolver didResolveDomainName: (OFString *)domainName response: (OFDNSResponse *)response context: (OFNumber *)context exception: (id)exception { /* * TODO: Error handling could be improved. Ignore error if there are * responses, otherwise propagate error. */ of_dns_resource_record_type_t recordType = context.unsignedIntValue; if (_resolver != nil) OF_ENSURE(resolver == _resolver); else _resolver = [resolver retain]; _expectedResponses--; if (_domainName != nil) { if (![domainName isEqual: _domainName]) /* Did the config change in between? */ return; } else _domainName = [domainName copy]; if (exception != nil) { if (_expectedResponses == 0) [self done]; return; } [self parseRecords: [response.answerRecords objectForKey: _domainName] response: response recordType: recordType recursion: CNAME_RECURSION result: _records]; if (_expectedResponses == 0) [self done]; } @end @implementation OFDNSResolverResolveSocketAddressesDelegate - (void)dealloc { [_socketAddresses release]; [_exception release]; [super dealloc]; } - (void)resolver: (OFDNSResolver *)resolver didResolveDomainName: (OFString *)domainName socketAddresses: (OFData *)socketAddresses exception: (id)exception { _socketAddresses = [socketAddresses retain]; _exception = [exception retain]; _done = true; } @end @implementation OFDNSResolver #ifdef OF_AMIGAOS + (void)initialize { if (self != [OFDNSResolver class]) return; if (!of_socket_init()) @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]; [_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]; [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]; } - (of_time_interval_t)timeout { return _settings->_timeout; } - (void)setTimeout: (of_time_interval_t)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)usesTCP { return _settings->_usesTCP; } - (void)setUsesTCP: (bool)usesTCP { _settings->_usesTCP = usesTCP; } - (of_time_interval_t)configReloadInterval { return _settings->_configReloadInterval; } - (void)setConfigReloadInterval: (of_time_interval_t)configReloadInterval { _settings->_configReloadInterval = configReloadInterval; } - (void)of_asyncPerformQuery: (OFDNSQuery *)query settings: (OFDNSResolverSettings *)settings nameServersIndex: (size_t)nameServersIndex searchDomainsIndex: (size_t)searchDomainsIndex runLoopMode: (of_run_loop_mode_t)runLoopMode target: (id)target selector: (SEL)selector context: (id)context { void *pool = objc_autoreleasePoolPush(); OFNumber *ID; OFString *host, *domainName; OFDNSResolverQuery *resolverQuery; if (settings == nil) { [_settings reload]; settings = _settings; } /* Random, unused ID */ do { ID = [OFNumber numberWithUInt16: (uint16_t)of_random()]; } while ([_queries objectForKey: ID] != nil); host = query.host; if (isFQDN(host, settings)) { domainName = host; if (![domainName hasSuffix: @"."]) domainName = [domainName stringByAppendingString: @"."]; } else { OFString *searchDomain = [settings->_searchDomains objectAtIndex: searchDomainsIndex]; domainName = [OFString stringWithFormat: @"%@.%@.", host, searchDomain]; } if (domainName.UTF8StringLength > 253) @throw [OFOutOfRangeException exception]; resolverQuery = [[[OFDNSResolverQuery alloc] initWithQuery: query domainName: domainName ID: ID settings: settings nameServersIndex: nameServersIndex searchDomainsIndex: searchDomainsIndex target: target selector: selector context: context] autorelease]; [_queries setObject: resolverQuery forKey: ID]; [self of_sendQuery: resolverQuery runLoopMode: runLoopMode]; objc_autoreleasePoolPop(pool); } - (void)of_resolver: (OFDNSResolver *)resolver didResolveDomainName: (OFString *)domainName response: (OFDNSResponse *)response context: (id)delegate exception: (id)exception { if ([delegate respondsToSelector: @selector(resolver: didResolveDomainName:response:exception:)]) [delegate resolver: resolver didResolveDomainName: domainName response: response exception: exception]; } - (void)asyncPerformQuery: (OFDNSQuery *)query delegate: (id )delegate { [self of_asyncPerformQuery: query settings: nil nameServersIndex: 0 searchDomainsIndex: 0 runLoopMode: of_run_loop_mode_default target: self selector: @selector(of_resolver:didResolveDomainName: response:context:exception:) context: delegate]; } - (void)asyncPerformQuery: (OFDNSQuery *)query runLoopMode: (of_run_loop_mode_t)runLoopMode delegate: (id )delegate { [self of_asyncPerformQuery: query settings: nil nameServersIndex: 0 searchDomainsIndex: 0 runLoopMode: runLoopMode target: self selector: @selector(of_resolver:didResolveDomainName: response:context:exception:) context: delegate]; } - (void)of_sendQuery: (OFDNSResolverQuery *)query runLoopMode: (of_run_loop_mode_t)runLoopMode { OFUDPSocket *sock; OFString *nameServer; [query->_cancelTimer invalidate]; [query->_cancelTimer release]; query->_cancelTimer = nil; query->_cancelTimer = [[OFTimer alloc] initWithFireDate: [OFDate dateWithTimeIntervalSinceNow: query->_settings->_timeout] interval: query->_settings->_timeout target: self selector: @selector(of_queryWithIDTimedOut:) object: query repeats: false]; [[OFRunLoop currentRunLoop] addTimer: query->_cancelTimer forMode: runLoopMode]; nameServer = [query->_settings->_nameServers objectAtIndex: query->_nameServersIndex]; query->_usedNameServer = of_socket_address_parse_ip(nameServer, 53); switch (query->_usedNameServer.family) { #ifdef OF_HAVE_IPV6 case OF_SOCKET_ADDRESS_FAMILY_IPV6: if (_IPv6Socket == nil) { of_socket_address_t address = of_socket_address_parse_ip(@"::", 0); _IPv6Socket = [[OFUDPSocket alloc] init]; [_IPv6Socket of_bindToAddress: &address extraType: SOCK_DNS]; _IPv6Socket.blocking = false; _IPv6Socket.delegate = self; [_IPv6Socket asyncReceiveIntoBuffer: _buffer length: BUFFER_LENGTH]; } sock = _IPv6Socket; break; #endif case OF_SOCKET_ADDRESS_FAMILY_IPV4: if (_IPv4Socket == nil) { of_socket_address_t address = of_socket_address_parse_ip(@"0.0.0.0", 0); _IPv4Socket = [[OFUDPSocket alloc] init]; [_IPv4Socket of_bindToAddress: &address extraType: SOCK_DNS]; _IPv4Socket.blocking = false; _IPv4Socket.delegate = self; [_IPv4Socket asyncReceiveIntoBuffer: _buffer length: BUFFER_LENGTH]; } sock = _IPv4Socket; break; default: @throw [OFInvalidArgumentException exception]; } [sock asyncSendData: query->_queryData receiver: &query->_usedNameServer runLoopMode: runLoopMode]; } - (void)of_queryWithIDTimedOut: (OFDNSResolverQuery *)query { OFDNSQueryFailedException *exception; if (query == nil) return; if (query->_nameServersIndex + 1 < query->_settings->_nameServers.count) { query->_nameServersIndex++; [self of_sendQuery: query runLoopMode: [OFRunLoop currentRunLoop].currentMode]; return; } if (query->_attempt < query->_settings->_maxAttempts) { query->_attempt++; query->_nameServersIndex = 0; [self of_sendQuery: query runLoopMode: [OFRunLoop currentRunLoop].currentMode]; return; } query = [[query retain] autorelease]; [_queries removeObjectForKey: query->_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: BUFFER_LENGTH]; #ifdef OF_HAVE_IPV6 [_IPv6Socket cancelAsyncRequests]; [_IPv6Socket asyncReceiveIntoBuffer: _buffer length: BUFFER_LENGTH]; #endif exception = [OFDNSQueryFailedException exceptionWithQuery: query->_query error: OF_DNS_RESOLVER_ERROR_TIMEOUT]; callback(query->_target, query->_selector, self, query->_domainName, nil, query->_context, exception); } - (bool)socket: (OFUDPSocket *)sock didReceiveIntoBuffer: (void *)buffer_ length: (size_t)length sender: (const of_socket_address_t *)sender exception: (id)exception { unsigned char *buffer = buffer_; OFDictionary *answerRecords = nil, *authorityRecords = nil; OFDictionary *additionalRecords = nil; OFDNSResponse *response = nil; OFNumber *ID; OFDNSResolverQuery *query; if (exception != nil) return true; if (length < 2) /* We can't get the ID to get the query. Ignore packet. */ return true; ID = [OFNumber numberWithUInt16: (buffer[0] << 8) | buffer[1]]; query = [[[_queries objectForKey: ID] retain] autorelease]; if (query == nil) return true; if (!of_socket_address_equal(sender, &query->_usedNameServer)) return true; [query->_cancelTimer invalidate]; [query->_cancelTimer release]; query->_cancelTimer = nil; [_queries removeObjectForKey: ID]; @try { bool tryNextNameServer = false; const unsigned char *queryDataBuffer; size_t i; of_dns_resolver_error_t error; uint16_t numQuestions, numAnswers, numAuthorityRecords; uint16_t numAdditionalRecords; if (length < 12) @throw [OFTruncatedDataException exception]; if (query->_queryData.itemSize != 1 || query->_queryData.count < 12) @throw [OFInvalidArgumentException exception]; queryDataBuffer = query->_queryData.items; /* QR */ if ((buffer[2] & 0x80) == 0) @throw [OFInvalidServerReplyException exception]; /* Opcode */ if ((buffer[2] & 0x78) != (queryDataBuffer[2] & 0x78)) @throw [OFInvalidServerReplyException exception]; /* TC */ if (buffer[2] & 0x02) @throw [OFTruncatedDataException exception]; /* RCODE */ switch (buffer[3] & 0x0F) { case 0: break; case 1: error = OF_DNS_RESOLVER_ERROR_SERVER_INVALID_FORMAT; break; case 2: error = OF_DNS_RESOLVER_ERROR_SERVER_FAILURE; tryNextNameServer = true; break; case 3: error = OF_DNS_RESOLVER_ERROR_SERVER_NAME_ERROR; break; case 4: error = OF_DNS_RESOLVER_ERROR_SERVER_NOT_IMPLEMENTED; tryNextNameServer = true; break; case 5: error = OF_DNS_RESOLVER_ERROR_SERVER_REFUSED; tryNextNameServer = true; break; default: error = OF_DNS_RESOLVER_ERROR_UNKNOWN; tryNextNameServer = true; break; } if (tryNextNameServer) { if (query->_searchDomainsIndex + 1 < query->_settings->_searchDomains.count) { of_run_loop_mode_t runLoopMode = [OFRunLoop currentRunLoop].currentMode; size_t nameServersIndex = query->_nameServersIndex + 1; size_t searchDomainsIndex = query->_searchDomainsIndex + 1; query->_searchDomainsIndex++; [self of_asyncPerformQuery: query->_query settings: query->_settings nameServersIndex: nameServersIndex searchDomainsIndex: searchDomainsIndex runLoopMode: runLoopMode target: query->_target selector: query->_selector context: query->_context]; return true; } } if (buffer[3] & 0x0F) @throw [OFDNSQueryFailedException exceptionWithQuery: query->_query error: error]; 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, MAX_ALLOWED_POINTERS); i += 4; } answerRecords = parseSection(buffer, length, &i, numAnswers); authorityRecords = parseSection(buffer, length, &i, numAuthorityRecords); additionalRecords = parseSection(buffer, length, &i, numAdditionalRecords); response = [OFDNSResponse responseWithAnswerRecords: answerRecords authorityRecords: authorityRecords additionalRecords: additionalRecords]; } @catch (id e) { callback(query->_target, query->_selector, self, query->_domainName, nil, query->_context, e); return true; } callback(query->_target, query->_selector, self, query->_domainName, response, query->_context, nil); return true; } - (void)asyncResolveSocketAddressesForHost: (OFString *)host delegate: (id )delegate { [self asyncResolveSocketAddressesForHost: host addressFamily: OF_SOCKET_ADDRESS_FAMILY_ANY runLoopMode: of_run_loop_mode_default delegate: delegate]; } - (void)asyncResolveSocketAddressesForHost: (OFString *)host addressFamily: (of_socket_address_family_t) addressFamily delegate: (id )delegate { [self asyncResolveSocketAddressesForHost: host addressFamily: addressFamily runLoopMode: of_run_loop_mode_default delegate: delegate]; } - (void)asyncResolveSocketAddressesForHost: (OFString *)host addressFamily: (of_socket_address_family_t) addressFamily runLoopMode: (of_run_loop_mode_t)runLoopMode delegate: (id )delegate { OFArray OF_GENERIC(OFString *) *aliases; void *pool; OFDNSResolverAsyncResolveSocketAddressesContext *context; @try { of_socket_address_t address = of_socket_address_parse_ip(host, 0); OFData *addresses = nil; id exception = nil; if (addressFamily == OF_SOCKET_ADDRESS_FAMILY_ANY || addressFamily == address.family) addresses = [OFData dataWithItems: &address itemSize: sizeof(address) count: 1]; else exception = [OFInvalidArgumentException exception]; if ([delegate respondsToSelector: @selector(resolver: didResolveDomainName:socketAddresses:exception:)]) { OFTimer *timer = [OFTimer timerWithTimeInterval: 0 target: delegate selector: @selector(resolver: didResolveDomainName: socketAddresses: exception:) object: self object: host object: addresses object: exception repeats: false]; [[OFRunLoop currentRunLoop] addTimer: timer forMode: runLoopMode]; } return; } @catch (OFInvalidFormatException *e) { } if ((aliases = [_settings->_staticHosts objectForKey: host]) != nil) { OFMutableData *addresses = [OFMutableData dataWithItemSize: sizeof(of_socket_address_t)]; id exception = nil; for (OFString *alias in aliases) { of_socket_address_t address; @try { address = of_socket_address_parse_ip(alias, 0); } @catch (OFInvalidFormatException *e) { continue; } if (addressFamily != OF_SOCKET_ADDRESS_FAMILY_ANY && address.family != addressFamily) continue; [addresses addItem: &address]; } [addresses makeImmutable]; if (addresses.count == 0) { of_dns_resource_record_type_t recordType = 0; addresses = nil; switch (addressFamily) { case OF_SOCKET_ADDRESS_FAMILY_ANY: recordType = OF_DNS_RESOURCE_RECORD_TYPE_ALL; break; case OF_SOCKET_ADDRESS_FAMILY_IPV4: recordType = OF_DNS_RESOURCE_RECORD_TYPE_A; break; case OF_SOCKET_ADDRESS_FAMILY_IPV6: recordType = OF_DNS_RESOURCE_RECORD_TYPE_AAAA; break; default: exception = [OFInvalidArgumentException exception]; break; } if (exception == nil) { of_dns_resource_record_class_t recordClass = OF_DNS_RESOURCE_RECORD_CLASS_IN; of_dns_resolver_error_t error = OF_DNS_RESOLVER_ERROR_NO_RESULT; OFDNSQuery *query = [OFDNSQuery queryWithHost: host recordClass: recordClass recordType: recordType]; exception = [OFDNSQueryFailedException exceptionWithQuery: query error: error]; } } if ([delegate respondsToSelector: @selector(resolver: didResolveDomainName:socketAddresses:exception:)]) { OFTimer *timer = [OFTimer timerWithTimeInterval: 0 target: delegate selector: @selector(resolver: didResolveDomainName: socketAddresses: exception:) object: self object: host object: addresses object: exception repeats: false]; [[OFRunLoop currentRunLoop] addTimer: timer forMode: runLoopMode]; } return; } pool = objc_autoreleasePoolPush(); context = [[[OFDNSResolverAsyncResolveSocketAddressesContext alloc] initWithHost: host delegate: delegate] autorelease]; switch (addressFamily) { case OF_SOCKET_ADDRESS_FAMILY_IPV4: #ifdef OF_HAVE_IPV6 case OF_SOCKET_ADDRESS_FAMILY_IPV6: #endif context->_expectedResponses = 1; break; case OF_SOCKET_ADDRESS_FAMILY_ANY: #ifdef OF_HAVE_IPV6 context->_expectedResponses = 2; #else context->_expectedResponses = 1; #endif break; default: @throw [OFInvalidArgumentException exception]; } #ifdef OF_HAVE_IPV6 if (addressFamily == OF_SOCKET_ADDRESS_FAMILY_IPV6 || addressFamily == OF_SOCKET_ADDRESS_FAMILY_ANY) { OFDNSQuery *query = [OFDNSQuery queryWithHost: host recordClass: OF_DNS_RESOURCE_RECORD_CLASS_IN recordType: OF_DNS_RESOURCE_RECORD_TYPE_AAAA]; OFNumber *recordTypeNumber = [OFNumber numberWithInt: OF_DNS_RESOURCE_RECORD_TYPE_AAAA]; [self of_asyncPerformQuery: query settings: nil nameServersIndex: 0 searchDomainsIndex: 0 runLoopMode: runLoopMode target: context selector: @selector(resolver: didResolveDomainName:response: context:exception:) context: recordTypeNumber]; } #endif if (addressFamily == OF_SOCKET_ADDRESS_FAMILY_IPV4 || addressFamily == OF_SOCKET_ADDRESS_FAMILY_ANY) { OFDNSQuery *query = [OFDNSQuery queryWithHost: host recordClass: OF_DNS_RESOURCE_RECORD_CLASS_IN recordType: OF_DNS_RESOURCE_RECORD_TYPE_A]; OFNumber *recordTypeNumber = [OFNumber numberWithInt: OF_DNS_RESOURCE_RECORD_TYPE_A]; [self of_asyncPerformQuery: query settings: nil nameServersIndex: 0 searchDomainsIndex: 0 runLoopMode: runLoopMode target: context selector: @selector(resolver: didResolveDomainName:response: context:exception:) context: recordTypeNumber]; } objc_autoreleasePoolPop(pool); } - (OFData *)resolveSocketAddressesForHost: (OFString *)host addressFamily: (of_socket_address_family_t) addressFamily { void *pool = objc_autoreleasePoolPush(); OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; OFDNSResolverResolveSocketAddressesDelegate *delegate; OFData *ret; delegate = [[[OFDNSResolverResolveSocketAddressesDelegate alloc] init] autorelease]; [self asyncResolveSocketAddressesForHost: host addressFamily: addressFamily runLoopMode: resolveRunLoopMode delegate: delegate]; while (!delegate->_done) [runLoop runMode: resolveRunLoopMode beforeDate: nil]; /* Cleanup */ [runLoop runMode: resolveRunLoopMode beforeDate: [OFDate date]]; if (delegate->_exception != nil) @throw delegate->_exception; ret = [delegate->_socketAddresses retain]; objc_autoreleasePoolPop(pool); return [ret autorelease]; } - (void)close { void *pool = objc_autoreleasePoolPush(); OFEnumerator OF_GENERIC(OFDNSResolverQuery *) *enumerator; OFDNSResolverQuery *query; [_IPv4Socket cancelAsyncRequests]; [_IPv4Socket release]; _IPv4Socket = nil; #ifdef OF_HAVE_IPV6 [_IPv6Socket cancelAsyncRequests]; [_IPv6Socket release]; _IPv6Socket = nil; #endif enumerator = [_queries objectEnumerator]; while ((query = [enumerator nextObject]) != nil) { OFDNSQueryFailedException *exception; exception = [OFDNSQueryFailedException exceptionWithQuery: query->_query error: OF_DNS_RESOLVER_ERROR_CANCELED]; callback(query->_target, query->_selector, self, query->_domainName, nil, query->_context, exception); } [_queries removeAllObjects]; objc_autoreleasePoolPop(pool); } @end