/*
* Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
* 2018, 2019
* Jonathan Schleifer <js@heap.zone>
*
* 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 <errno.h>
#include <string.h>
#include "unistd_wrapper.h"
#import "OFDNSResolver.h"
#import "OFArray.h"
#import "OFCharacterSet.h"
#import "OFData.h"
#import "OFDate.h"
#import "OFDictionary.h"
#import "OFFile.h"
#import "OFLocale.h"
#import "OFNumber.h"
#import "OFPair.h"
#import "OFString.h"
#import "OFTimer.h"
#import "OFUDPSocket.h"
#import "OFUDPSocket+Private.h"
#ifdef OF_WINDOWS
# import "OFWindowsRegistryKey.h"
#endif
#import "OFInitializationFailedException.h"
#import "OFInvalidArgumentException.h"
#import "OFInvalidFormatException.h"
#import "OFInvalidServerReplyException.h"
#import "OFOpenItemFailedException.h"
#import "OFOutOfMemoryException.h"
#import "OFOutOfRangeException.h"
#import "OFResolveHostFailedException.h"
#import "OFTruncatedDataException.h"
#ifdef OF_WINDOWS
# define interface struct
# include <iphlpapi.h>
# undef interface
#endif
#ifdef OF_AMIGAOS4
# define __USE_INLINE__
# define __NOLIBBASE__
# define __NOGLOBALIFACE__
# include <proto/exec.h>
# include <proto/bsdsocket.h>
#endif
#ifdef OF_NINTENDO_3DS
# include <3ds.h>
#endif
#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
#if defined(OF_HAIKU)
# define HOSTS_PATH @"/system/settings/network/hosts"
# define RESOLV_CONF_PATH @"/system/settings/network/resolv.conf"
#elif defined(OF_MORPHOS)
# define HOSTS_PATH @"ENV:sys/net/hosts"
# define RESOLV_CONF_PATH @"ENV:sys/net/resolv.conf"
#elif defined(OF_AMIGAOS4)
# define HOSTS_PATH @"DEVS:Internet/hosts"
#elif defined(OF_AMIGAOS)
# define HOSTS_PATH @"AmiTCP:db/hosts"
# define RESOLV_CONF_PATH @"AmiTCP:db/resolv.conf"
#else
# define HOSTS_PATH @"/etc/hosts"
# define RESOLV_CONF_PATH @"/etc/resolv.conf"
#endif
#ifdef OF_AMIGAOS4
extern struct ExecIFace *IExec;
static struct Library *SocketBase = NULL;
static struct SocketIFace *ISocket = NULL;
OF_DESTRUCTOR()
{
if (ISocket != NULL)
DropInterface((struct Interface *)ISocket);
if (SocketBase != NULL)
CloseLibrary(SocketBase);
}
#endif
/*
* TODO:
*
* - Fallback to TCP
*/
static const of_run_loop_mode_t resolveRunLoopMode =
@"of_dns_resolver_resolve_mode";
@interface OFDNSResolverSettings: OFObject
{
@public
OFArray OF_GENERIC(OFString *) *_nameServers, *_searchDomains;
of_time_interval_t _timeout;
unsigned int _maxAttempts, _minNumberOfDotsInAbsoluteName;
}
- (instancetype)
initWithNameServers: (OFArray *)nameServers
searchDomains: (OFArray *)searchDomains
timeout: (of_time_interval_t)timeout
maxAttempts: (unsigned int)maxAttempts
minNumberOfDotsInAbsoluteName: (unsigned int)minNumberOfDotsInAbsoluteName;
@end
@interface OFDNSResolverQuery: OFObject
{
@public
OFString *_host, *_domainName;
of_dns_resource_record_class_t _recordClass;
of_dns_resource_record_type_t _recordType;
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)initWithHost: (OFString *)host
domainName: (OFString *)domainName
recordClass: (of_dns_resource_record_class_t)recordClass
recordType: (of_dns_resource_record_type_t)recordType
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 OFDNSResolver_AsyncResolveSocketAddressesContext: 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
answerRecords: (OFDictionary *)answerRecords
additionalRecords: (OFDictionary *)additionalRecords
recordType: (of_dns_resource_record_type_t)recordType
recursion: (unsigned int)recursion
result: (OFMutableArray *)result;
- (void)resolveCNAME: (OFCNAMEDNSResourceRecord *)CNAME
answerRecords: (OFDictionary *)answerRecords
additionalRecords: (OFDictionary *)additionalRecords
recordType: (of_dns_resource_record_type_t)recordType
recursion: (unsigned int)recursion
result: (OFMutableArray *)result;
- (void)resolver: (OFDNSResolver *)resolver
didResolveCNAME: (OFString *)CNAME
answerRecords: (OFDictionary *)answerRecords
authorityRecords: (OFDictionary *)authorityRecords
additionalRecords: (OFDictionary *)additionalRecords
context: (OFNumber *)context
exception: (id)exception;
- (void)done;
- (void)resolver: (OFDNSResolver *)resolver
didResolveDomainName: (OFString *)domainName
answerRecords: (OFDictionary *)answerRecords
authorityRecords: (OFDictionary *)authorityRecords
additionalRecords: (OFDictionary *)additionalRecords
context: (OFNumber *)context
exception: (id)exception;
@end
@interface OFDNSResolver_ResolveSocketAddressesDelegate: OFObject
<OFDNSResolverDelegate>
{
@public
bool _done;
OFData *_socketAddresses;
id _exception;
}
@end
@interface OFDNSResolver () <OFUDPSocketDelegate>
- (void)of_setDefaults;
- (void)of_obtainSystemConfig;
#if defined(OF_HAVE_FILES) && !defined(OF_NINTENDO_3DS)
- (void)of_parseHosts: (OFString *)path;
# if !defined(OF_WINDOWS) && !defined(OF_AMIGAOS4)
- (void)of_parseResolvConf: (OFString *)path;
- (void)of_parseResolvConfOption: (OFString *)option;
# endif
#endif
#ifdef OF_WINDOWS
- (void)of_obtainWindowsSystemConfig;
#endif
#ifdef OF_AMIGAOS4
- (void)of_obtainAmigaOS4SystemConfig;
#endif
#ifdef OF_NINTENDO_3DS
- (void)of_obtainNintendo3DSSytemConfig;
#endif
- (void)of_reloadSystemConfig;
- (void)of_resolveHost: (OFString *)host
recordClass: (of_dns_resource_record_class_t)recordClass
recordType: (of_dns_resource_record_type_t)recordType
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_asyncResolveHost: (OFString *)host
recordClass: (of_dns_resource_record_class_t)recordClass
recordType: (of_dns_resource_record_type_t)recordType
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
#ifndef OF_WII
static OFString *
domainFromHostname(void)
{
char hostname[256];
OFString *domain;
if (gethostname(hostname, 256) != 0)
return nil;
domain = [OFString stringWithCString: hostname
encoding: [OFLocale encoding]];
@try {
of_socket_address_parse_ip(domain, 0);
/*
* If we are still here, the host name is a valid IP address.
* We can't use that as local domain.
*/
return nil;
} @catch (OFInvalidFormatException *e) {
/* Not an IP address -> we can use it if it contains a dot. */
size_t pos = [domain rangeOfString: @"."].location;
if (pos == OF_NOT_FOUND)
return nil;
return [domain substringWithRange:
of_range(pos + 1, domain.length - pos - 1)];
}
}
#endif
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, OFDictionary *answerRecords,
OFDictionary *authorityRecords, OFDictionary *additionalRecords, id context,
id exception)
{
void (*method)(id, SEL, OFDNSResolver *, OFString *, OFDictionary *,
OFDictionary *, OFDictionary *, id, id) = (void (*)(id, SEL,
OFDNSResolver *, OFString *, OFDictionary *, OFDictionary *,
OFDictionary *, id, id))[target methodForSelector: selector];
method(target, selector, resolver, domainName, answerRecords,
authorityRecords, additionalRecords, context, exception);
}
@implementation OFDNSResolverSettings
- (instancetype)initWithNameServers: (OFArray *)nameServers
searchDomains: (OFArray *)searchDomains
timeout: (of_time_interval_t)timeout
maxAttempts: (unsigned int)maxAttempts
minNumberOfDotsInAbsoluteName: (unsigned int)minNumberOfDotsInAbsoluteName
{
self = [super init];
@try {
_nameServers = [nameServers copy];
_searchDomains = [searchDomains copy];
_timeout = timeout;
_maxAttempts = maxAttempts;
_minNumberOfDotsInAbsoluteName = minNumberOfDotsInAbsoluteName;
} @catch (id e) {
[self release];
@throw e;
}
return self;
}
- (void)dealloc
{
[_nameServers release];
[_searchDomains release];
[super dealloc];
}
@end
@implementation OFDNSResolverQuery
- (instancetype)initWithHost: (OFString *)host
domainName: (OFString *)domainName
recordClass: (of_dns_resource_record_class_t)recordClass
recordType: (of_dns_resource_record_type_t)recordType
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;
_host = [host copy];
_domainName = [domainName copy];
_recordClass = recordClass;
_recordType = recordType;
_ID = [ID retain];
_settings = [settings retain];
_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(1 << 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(recordType);
[queryData addItems: &tmp
count: 2];
/* QCLASS */
tmp = OF_BSWAP16_IF_LE(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
{
[_host release];
[_domainName release];
[_ID release];
[_settings release];
[_target release];
[_context release];
[_queryData release];
[_cancelTimer release];
[super dealloc];
}
@end
@implementation OFDNSResolver_AsyncResolveSocketAddressesContext
- (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
answerRecords: (OFDictionary *)answerRecords
additionalRecords: (OFDictionary *)additionalRecords
recordType: (of_dns_resource_record_type_t)recordType
recursion: (unsigned int)recursion
result: (OFMutableArray *)result
{
bool found = false;
for (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: (OFCNAMEDNSResourceRecord *)record
answerRecords: answerRecords
additionalRecords: additionalRecords
recordType: recordType
recursion: recursion
result: result];
found = true;
}
}
return found;
}
- (void)resolveCNAME: (OFCNAMEDNSResourceRecord *)CNAME
answerRecords: (OFDictionary *)answerRecords
additionalRecords: (OFDictionary *)additionalRecords
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: [answerRecords objectForKey: alias]
answerRecords: answerRecords
additionalRecords: additionalRecords
recordType: recordType
recursion: recursion - 1
result: result])
found = true;
if ([self parseRecords: [additionalRecords objectForKey: alias]
answerRecords: answerRecords
additionalRecords: additionalRecords
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];
_expectedResponses++;
[result addObject:
[OFPair pairWithFirstObject: CNAME
secondObject: recordTypeNumber]];
[_resolver of_asyncResolveHost: alias
recordClass: OF_DNS_RESOURCE_RECORD_CLASS_IN
recordType: recordType
runLoopMode: runLoopMode
target: self
selector: @selector(resolver:
didResolveCNAME:
answerRecords:
authorityRecords:
additionalRecords:context:
exception:)
context: recordTypeNumber];
}
}
- (void)resolver: (OFDNSResolver *)resolver
didResolveCNAME: (OFString *)CNAME
answerRecords: (OFDictionary *)answerRecords
authorityRecords: (OFDictionary *)authorityRecords
additionalRecords: (OFDictionary *)additionalRecords
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: [answerRecords objectForKey: CNAME]
answerRecords: answerRecords
additionalRecords: additionalRecords
recordType: recordType
recursion: CNAME_RECURSION
result: records])
found = true;
if ([self parseRecords: [additionalRecords objectForKey: CNAME]
answerRecords: answerRecords
additionalRecords: additionalRecords
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)
exception = [OFResolveHostFailedException
exceptionWithHost: _host
recordClass: OF_DNS_RESOURCE_RECORD_CLASS_IN
recordType: 0
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
answerRecords: (OFDictionary *)answerRecords
authorityRecords: (OFDictionary *)authorityRecords
additionalRecords: (OFDictionary *)additionalRecords
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 between requests? */
return;
} else
_domainName = [domainName copy];
if (exception != nil) {
if (_expectedResponses == 0)
[self done];
return;
}
[self parseRecords: [answerRecords objectForKey: _domainName]
answerRecords: answerRecords
additionalRecords: additionalRecords
recordType: recordType
recursion: CNAME_RECURSION
result: _records];
if (_expectedResponses == 0)
[self done];
}
@end
@implementation OFDNSResolver_ResolveSocketAddressesDelegate
- (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
@synthesize staticHosts = _staticHosts, nameServers = _nameServers;
@synthesize localDomain = _localDomain, searchDomains = _searchDomains;
@synthesize timeout = _timeout, maxAttempts = _maxAttempts;
@synthesize minNumberOfDotsInAbsoluteName = _minNumberOfDotsInAbsoluteName;
@synthesize usesTCP = _usesTCP, configReloadInterval = _configReloadInterval;
#ifdef OF_AMIGAOS4
+ (void)initialize
{
if (self != [OFDNSResolver class])
return;
if ((SocketBase = OpenLibrary("bsdsocket.library", 4)) == NULL)
@throw [OFInitializationFailedException
exceptionWithClass: self];
if ((ISocket = (struct SocketIFace *)
GetInterface(SocketBase, "main", 1, NULL)) == NULL)
@throw [OFInitializationFailedException
exceptionWithClass: self];
}
#endif
+ (instancetype)resolver
{
return [[[self alloc] init] autorelease];
}
- (instancetype)init
{
self = [super init];
@try {
_queries = [[OFMutableDictionary alloc] init];
[self of_obtainSystemConfig];
} @catch (id e) {
[self release];
@throw e;
}
return self;
}
- (void)of_setDefaults
{
_timeout = 2;
_maxAttempts = 3;
_minNumberOfDotsInAbsoluteName = 1;
_usesTCP = false;
#ifndef OF_NINTENDO_3DS
_configReloadInterval = 2;
#else
_configReloadInterval = 0;
#endif
}
- (void)of_obtainSystemConfig
{
void *pool = objc_autoreleasePoolPush();
#ifdef OF_WINDOWS
OFString *path;
#endif
[self of_setDefaults];
#if defined(OF_WINDOWS)
# ifdef OF_HAVE_FILES
path = [[OFWindowsRegistryKey localMachineKey]
stringForValue: @"DataBasePath"
subkeyPath: @"SYSTEM\\CurrentControlSet\\Services\\"
@"Tcpip\\Parameters"];
path = [path stringByAppendingPathComponent: @"hosts"];
if (path != nil)
[self of_parseHosts: path];
# endif
[self of_obtainWindowsSystemConfig];
#elif defined(OF_AMIGAOS4)
[self of_parseHosts: HOSTS_PATH];
[self of_obtainAmigaOS4SystemConfig];
#elif defined(OF_NINTENDO_3DS)
[self of_obtainNintendo3DSSytemConfig];
#elif defined(OF_HAVE_FILES)
[self of_parseHosts: HOSTS_PATH];
[self of_parseResolvConf: RESOLV_CONF_PATH];
#endif
if (_staticHosts == nil) {
OFArray *localhost =
#ifdef OF_HAVE_IPV6
[OFArray arrayWithObjects: @"::1", @"127.0.0.1", nil];
#else
[OFArray arrayWithObject: @"127.0.0.1"];
#endif
_staticHosts = [[OFDictionary alloc]
initWithObject: localhost
forKey: @"localhost"];
}
if (_nameServers == nil)
#ifdef OF_HAVE_IPV6
_nameServers = [[OFArray alloc]
initWithObjects: @"127.0.0.1", @"::1", nil];
#else
_nameServers = [[OFArray alloc] initWithObject: @"127.0.0.1"];
#endif
#ifndef OF_WII
if (_localDomain == nil)
_localDomain = [domainFromHostname() copy];
#endif
if (_searchDomains == nil) {
if (_localDomain != nil)
_searchDomains = [[OFArray alloc]
initWithObject: _localDomain];
else
_searchDomains = [[OFArray alloc] init];
}
_lastConfigReload = [[OFDate alloc] init];
objc_autoreleasePoolPop(pool);
}
- (void)dealloc
{
[self close];
[_staticHosts release];
[_nameServers release];
[_localDomain release];
[_searchDomains release];
[_lastConfigReload release];
[_IPv4Socket cancelAsyncRequests];
[_IPv4Socket release];
#ifdef OF_HAVE_IPV6
[_IPv6Socket cancelAsyncRequests];
[_IPv6Socket release];
#endif
[_queries release];
[super dealloc];
}
#if defined(OF_HAVE_FILES) && !defined(OF_NINTENDO_3DS)
- (void)of_parseHosts: (OFString *)path
{
void *pool = objc_autoreleasePoolPush();
OFCharacterSet *whitespaceCharacterSet =
[OFCharacterSet whitespaceCharacterSet];
OFMutableDictionary *staticHosts;
OFFile *file;
OFString *line;
OFEnumerator *enumerator;
OFMutableArray *addresses;
@try {
file = [OFFile fileWithPath: path
mode: @"r"];
} @catch (OFOpenItemFailedException *e) {
objc_autoreleasePoolPop(pool);
return;
}
staticHosts = [OFMutableDictionary dictionary];
while ((line = [file readLine]) != nil) {
void *pool2 = objc_autoreleasePoolPush();
OFArray *components, *hosts;
size_t pos;
OFString *address;
pos = [line rangeOfString: @"#"].location;
if (pos != OF_NOT_FOUND)
line = [line substringWithRange: of_range(0, pos)];
components = [line
componentsSeparatedByCharactersInSet: whitespaceCharacterSet
options: OF_STRING_SKIP_EMPTY];
if (components.count < 2) {
objc_autoreleasePoolPop(pool2);
continue;
}
address = components.firstObject;
hosts = [components objectsInRange:
of_range(1, components.count - 1)];
for (OFString *host in hosts) {
addresses = [staticHosts objectForKey: host];
if (addresses == nil) {
addresses = [OFMutableArray array];
[staticHosts setObject: addresses
forKey: host];
}
[addresses addObject: address];
}
objc_autoreleasePoolPop(pool2);
}
enumerator = [staticHosts objectEnumerator];
while ((addresses = [enumerator nextObject]) != nil)
[addresses makeImmutable];
[staticHosts makeImmutable];
[_staticHosts release];
_staticHosts = [staticHosts copy];
objc_autoreleasePoolPop(pool);
}
# if !defined(OF_WINDOWS) && !defined(OF_AMIGAOS4)
- (void)of_parseResolvConf: (OFString *)path
{
void *pool = objc_autoreleasePoolPush();
OFCharacterSet *whitespaceCharacterSet =
[OFCharacterSet whitespaceCharacterSet];
OFCharacterSet *commentCharacters = [OFCharacterSet
characterSetWithCharactersInString: @"#;"];
OFMutableArray *nameServers = [[_nameServers mutableCopy] autorelease];
OFFile *file;
OFString *line;
@try {
file = [OFFile fileWithPath: path
mode: @"r"];
} @catch (OFOpenItemFailedException *e) {
objc_autoreleasePoolPop(pool);
return;
}
if (nameServers == nil)
nameServers = [OFMutableArray array];
while ((line = [file readLine]) != nil) {
void *pool2 = objc_autoreleasePoolPush();
size_t pos;
OFArray *components, *arguments;
OFString *option;
pos = [line indexOfCharacterFromSet: commentCharacters];
if (pos != OF_NOT_FOUND)
line = [line substringWithRange: of_range(0, pos)];
components = [line
componentsSeparatedByCharactersInSet: whitespaceCharacterSet
options: OF_STRING_SKIP_EMPTY];
if (components.count < 2) {
objc_autoreleasePoolPop(pool2);
continue;
}
option = components.firstObject;
arguments = [components objectsInRange:
of_range(1, components.count - 1)];
if ([option isEqual: @"nameserver"]) {
if (arguments.count != 1) {
objc_autoreleasePoolPop(pool2);
continue;
}
[nameServers addObject: [arguments firstObject]];
} else if ([option isEqual: @"domain"]) {
if (arguments.count != 1) {
objc_autoreleasePoolPop(pool2);
continue;
}
[_localDomain release];
_localDomain = [arguments.firstObject copy];
} else if ([option isEqual: @"search"]) {
[_searchDomains release];
_searchDomains = [arguments copy];
} else if ([option isEqual: @"options"])
for (OFString *argument in arguments)
[self of_parseResolvConfOption: argument];
objc_autoreleasePoolPop(pool2);
}
[nameServers makeImmutable];
[_nameServers release];
_nameServers = [nameServers copy];
objc_autoreleasePoolPop(pool);
}
- (void)of_parseResolvConfOption: (OFString *)option
{
@try {
if ([option hasPrefix: @"ndots:"]) {
option = [option substringWithRange:
of_range(6, option.length - 6)];
_minNumberOfDotsInAbsoluteName =
(unsigned int)option.decimalValue;
} else if ([option hasPrefix: @"timeout:"]) {
option = [option substringWithRange:
of_range(8, option.length - 8)];
_timeout = option.decimalValue;
} else if ([option hasPrefix: @"attempts:"]) {
option = [option substringWithRange:
of_range(9, option.length - 9)];
_maxAttempts = (unsigned int)option.decimalValue;
} else if ([option hasPrefix: @"reload-period:"]) {
option = [option substringWithRange:
of_range(14, option.length - 14)];
_configReloadInterval = option.decimalValue;
} else if ([option isEqual: @"tcp"])
_usesTCP = true;
} @catch (OFInvalidFormatException *e) {
}
}
# endif
#endif
#ifdef OF_WINDOWS
- (void)of_obtainWindowsSystemConfig
{
of_string_encoding_t encoding = [OFLocale encoding];
OFMutableArray *nameServers;
/*
* We need more space than FIXED_INFO in case we have more than one
* name server, but we also want it to be properly aligned, meaning we
* can't just get a buffer of bytes. Thus, we just get space for 8.
*/
FIXED_INFO fixedInfo[8];
ULONG length = sizeof(fixedInfo);
PIP_ADDR_STRING iter;
if (GetNetworkParams(fixedInfo, &length) != ERROR_SUCCESS)
return;
nameServers = [OFMutableArray array];
for (iter = &fixedInfo->DnsServerList; iter != NULL; iter = iter->Next)
[nameServers addObject:
[OFString stringWithCString: iter->IpAddress.String
encoding: encoding]];
if (nameServers.count > 0) {
[nameServers makeImmutable];
_nameServers = [nameServers copy];
}
if (fixedInfo->DomainName[0] != '\0')
_localDomain = [[OFString alloc]
initWithCString: fixedInfo->DomainName
encoding: encoding];
}
#endif
#ifdef OF_AMIGAOS4
- (void)of_obtainAmigaOS4SystemConfig
{
OFMutableArray *nameServers = [OFMutableArray array];
of_string_encoding_t encoding = [OFLocale encoding];
struct List *nameServerList = ObtainDomainNameServerList();
char buffer[MAXHOSTNAMELEN];
if (nameServerList == NULL)
@throw [OFOutOfMemoryException exception];
@try {
struct DomainNameServerNode *iter =
(struct DomainNameServerNode *)&nameServerList->lh_Head;
while (iter->dnsn_MinNode.mln_Succ != NULL) {
if (iter->dnsn_UseCount != 0 &&
iter->dnsn_Address != NULL) {
OFString *address = [OFString
stringWithCString: iter->dnsn_Address
encoding: encoding];
[nameServers addObject: address];
}
iter = (struct DomainNameServerNode *)
iter->dnsn_MinNode.mln_Succ;
}
} @finally {
ReleaseDomainNameServerList(nameServerList);
}
if (nameServers.count > 0) {
[nameServers makeImmutable];
_nameServers = [nameServers copy];
}
if (GetDefaultDomainName(buffer, sizeof(buffer)))
_localDomain = [[OFString alloc] initWithCString: buffer
encoding: encoding];
}
#endif
#ifdef OF_NINTENDO_3DS
- (void)of_obtainNintendo3DSSytemConfig
{
OFMutableArray *nameServers = [OFMutableArray array];
union {
/*
* For some unknown reason, this needs a 336 bytes buffer and
* always returns 336 bytes.
*/
char bytes[336];
SOCU_DNSTableEntry entries[2];
} buffer;
socklen_t optLen = sizeof(buffer);
if (SOCU_GetNetworkOpt(SOL_CONFIG, NETOPT_DNS_TABLE,
&buffer, &optLen) != 0)
return;
/*
* We're fine if this gets smaller in a future release (unlikely), as
* long as two entries still fit.
*/
if (optLen < sizeof(buffer.entries))
return;
for (uint_fast8_t i = 0; i < 2; i++) {
uint32_t ip = OF_BSWAP32_IF_LE(buffer.entries[i].ip.s_addr);
if (ip == 0)
continue;
[nameServers addObject: [OFString stringWithFormat:
@"%u.%u.%u.%u", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF,
(ip >> 8) & 0xFF, ip & 0xFF]];
}
if (nameServers.count > 0) {
[nameServers makeImmutable];
_nameServers = [nameServers copy];
}
}
#endif
- (void)of_reloadSystemConfig
{
/*
* TODO: Rather than reparsing every, check what actually changed
* (mtime) and only reset those.
*/
if (_lastConfigReload != nil && _configReloadInterval > 0 &&
_lastConfigReload.timeIntervalSinceNow < _configReloadInterval)
return;
[_staticHosts release];
_staticHosts = nil;
[_nameServers release];
_nameServers = nil;
[_localDomain release];
_localDomain = nil;
[_searchDomains release];
_searchDomains = nil;
[self of_setDefaults];
[_lastConfigReload release];
_lastConfigReload = nil;
[self of_obtainSystemConfig];
}
- (void)of_resolveHost: (OFString *)host
recordClass: (of_dns_resource_record_class_t)recordClass
recordType: (of_dns_resource_record_type_t)recordType
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 *domainName;
OFDNSResolverQuery *query;
[self of_reloadSystemConfig];
/* Random, unused ID */
do {
ID = [OFNumber numberWithUInt16: (uint16_t)of_random()];
} while ([_queries objectForKey: ID] != nil);
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];
query = [[[OFDNSResolverQuery alloc]
initWithHost: host
domainName: domainName
recordClass: recordClass
recordType: recordType
ID: ID
settings: settings
nameServersIndex: nameServersIndex
searchDomainsIndex: searchDomainsIndex
target: target
selector: selector
context: context] autorelease];
[_queries setObject: query
forKey: ID];
[self of_sendQuery: query
runLoopMode: runLoopMode];
objc_autoreleasePoolPop(pool);
}
- (void)of_resolver: (OFDNSResolver *)resolver
didResolveDomainName: (OFString *)domainName
answerRecords: (of_dns_resolver_records_t)answerRecords
authorityRecords: (of_dns_resolver_records_t)authorityRecords
additionalRecords: (of_dns_resolver_records_t)additionalRecords
context: (id)delegate
exception: (id)exception
{
if ([delegate respondsToSelector: @selector(resolver:
didResolveDomainName:answerRecords:authorityRecords:
additionalRecords:exception:)])
[delegate resolver: resolver
didResolveDomainName: domainName
answerRecords: answerRecords
authorityRecords: authorityRecords
additionalRecords: additionalRecords
exception: exception];
}
- (void)asyncResolveHost: (OFString *)host
delegate: (id <OFDNSResolverDelegate>)delegate
{
[self of_asyncResolveHost: host
recordClass: OF_DNS_RESOURCE_RECORD_CLASS_IN
recordType: OF_DNS_RESOURCE_RECORD_TYPE_ALL
runLoopMode: of_run_loop_mode_default
target: self
selector: @selector(of_resolver:didResolveDomainName:
answerRecords:authorityRecords:
additionalRecords:context:exception:)
context: delegate];
}
- (void)asyncResolveHost: (OFString *)host
recordClass: (of_dns_resource_record_class_t)recordClass
recordType: (of_dns_resource_record_type_t)recordType
delegate: (id <OFDNSResolverDelegate>)delegate
{
[self of_asyncResolveHost: host
recordClass: recordClass
recordType: recordType
runLoopMode: of_run_loop_mode_default
target: self
selector: @selector(of_resolver:didResolveDomainName:
answerRecords:authorityRecords:
additionalRecords:context:exception:)
context: delegate];
}
- (void)asyncResolveHost: (OFString *)host
recordClass: (of_dns_resource_record_class_t)recordClass
recordType: (of_dns_resource_record_type_t)recordType
runLoopMode: (of_run_loop_mode_t)runLoopMode
delegate: (id <OFDNSResolverDelegate>)delegate
{
[self of_asyncResolveHost: host
recordClass: recordClass
recordType: recordType
runLoopMode: runLoopMode
target: self
selector: @selector(of_resolver:didResolveDomainName:
answerRecords:authorityRecords:
additionalRecords:context:exception:)
context: delegate];
}
- (void)of_asyncResolveHost: (OFString *)host
recordClass: (of_dns_resource_record_class_t)recordClass
recordType: (of_dns_resource_record_type_t)recordType
runLoopMode: (of_run_loop_mode_t)runLoopMode
target: (id)target
selector: (SEL)selector
context: (id)context
{
void *pool = objc_autoreleasePoolPush();
OFDNSResolverSettings *settings = [[[OFDNSResolverSettings alloc]
initWithNameServers: _nameServers
searchDomains: _searchDomains
timeout: _timeout
maxAttempts: _maxAttempts
minNumberOfDotsInAbsoluteName: _minNumberOfDotsInAbsoluteName]
autorelease];
[self of_resolveHost: host
recordClass: recordClass
recordType: recordType
settings: settings
nameServersIndex: 0
searchDomainsIndex: 0
runLoopMode: runLoopMode
target: target
selector: selector
context: context];
objc_autoreleasePoolPop(pool);
}
- (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
{
OFResolveHostFailedException *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 requests, 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 = [OFResolveHostFailedException
exceptionWithHost: query->_host
recordClass: query->_recordClass
recordType: query->_recordType
error: OF_DNS_RESOLVER_ERROR_TIMEOUT];
callback(query->_target, query->_selector, self, query->_domainName,
nil, nil, 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;
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 {
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;
break;
case 3:
if (query->_searchDomainsIndex + 1 <
query->_settings->_searchDomains.count) {
of_run_loop_mode_t runLoopMode =
[OFRunLoop currentRunLoop].currentMode;
query->_searchDomainsIndex++;
[self of_resolveHost: query->_host
recordClass: query->_recordClass
recordType: query->_recordType
settings: query->_settings
nameServersIndex: query->_nameServersIndex
searchDomainsIndex: query->_searchDomainsIndex
runLoopMode: runLoopMode
target: query->_target
selector: query->_selector
context: query->_context];
return true;
}
error = OF_DNS_RESOLVER_ERROR_SERVER_NAME_ERROR;
break;
case 4:
error = OF_DNS_RESOLVER_ERROR_SERVER_NOT_IMPLEMENTED;
break;
case 5:
error = OF_DNS_RESOLVER_ERROR_SERVER_REFUSED;
break;
default:
error = OF_DNS_RESOLVER_ERROR_UNKNOWN;
break;
}
if (buffer[3] & 0x0F)
@throw [OFResolveHostFailedException
exceptionWithHost: query->_host
recordClass: query->_recordClass
recordType: query->_recordType
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);
} @catch (id e) {
callback(query->_target, query->_selector, self,
query->_domainName, nil, nil, nil, query->_context, e);
return true;
}
callback(query->_target, query->_selector, self, query->_domainName,
answerRecords, authorityRecords, additionalRecords,
query->_context, nil);
return true;
}
- (void)asyncResolveSocketAddressesForHost: (OFString *)host
delegate: (id <OFDNSResolverDelegate>)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 <OFDNSResolverDelegate>)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 <OFDNSResolverDelegate>)delegate
{
OFArray OF_GENERIC(OFString *) *aliases;
void *pool;
OFDNSResolver_AsyncResolveSocketAddressesContext *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 = [_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;
exception = [OFResolveHostFailedException
exceptionWithHost: host
recordClass: recordClass
recordType: recordType
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 = [[[OFDNSResolver_AsyncResolveSocketAddressesContext 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) {
OFNumber *recordTypeNumber =
[OFNumber numberWithInt: OF_DNS_RESOURCE_RECORD_TYPE_AAAA];
[self of_asyncResolveHost: host
recordClass: OF_DNS_RESOURCE_RECORD_CLASS_IN
recordType: OF_DNS_RESOURCE_RECORD_TYPE_AAAA
runLoopMode: runLoopMode
target: context
selector: @selector(resolver:
didResolveDomainName:
answerRecords:authorityRecords:
additionalRecords:context:
exception:)
context: recordTypeNumber];
}
#endif
if (addressFamily == OF_SOCKET_ADDRESS_FAMILY_IPV4 ||
addressFamily == OF_SOCKET_ADDRESS_FAMILY_ANY)
[self of_asyncResolveHost: host
recordClass: OF_DNS_RESOURCE_RECORD_CLASS_IN
recordType: OF_DNS_RESOURCE_RECORD_TYPE_A
runLoopMode: runLoopMode
target: context
selector: @selector(resolver:
didResolveDomainName:
answerRecords:authorityRecords:
additionalRecords:context:
exception:)
context: [OFNumber numberWithInt:
OF_DNS_RESOURCE_RECORD_TYPE_A]];
objc_autoreleasePoolPop(pool);
}
- (OFData *)resolveSocketAddressesForHost: (OFString *)host
addressFamily: (of_socket_address_family_t)
addressFamily
{
void *pool = objc_autoreleasePoolPush();
OFRunLoop *runLoop = [OFRunLoop currentRunLoop];
OFDNSResolver_ResolveSocketAddressesDelegate *delegate;
OFData *ret;
delegate = [[[OFDNSResolver_ResolveSocketAddressesDelegate
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) {
OFResolveHostFailedException *exception;
exception = [OFResolveHostFailedException
exceptionWithHost: query->_host
recordClass: query->_recordClass
recordType: query->_recordType
error: OF_DNS_RESOLVER_ERROR_CANCELED];
callback(query->_target, query->_selector, self,
query->_domainName, nil, nil, nil, query->_context,
exception);
}
[_queries removeAllObjects];
objc_autoreleasePoolPop(pool);
}
@end