Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -212,11 +212,12 @@ ${AUTORELEASE_M} \ ${INSTANCE_M} \ ${LIBBASES_M} SRCS_FILES += OFFileURLHandler.m \ OFINIFileSettings.m -SRCS_SOCKETS += OFHTTPURLHandler.m \ +SRCS_SOCKETS += OFDNSResolverSettings.m \ + OFHTTPURLHandler.m \ OFKernelEventObserver.m \ ${OFEPOLLKERNELEVENTOBSERVER_M} \ ${OFKQUEUEKERNELEVENTOBSERVER_M} \ ${OFPOLLKERNELEVENTOBSERVER_M} \ ${OFSELECTKERNELEVENTOBSERVER_M} Index: src/OFDNSResolver.h ================================================================== --- src/OFDNSResolver.h +++ src/OFDNSResolver.h @@ -27,10 +27,11 @@ #define OF_DNS_RESOLVER_BUFFER_LENGTH 512 @class OFArray OF_GENERIC(ObjectType); @class OFDNSResolver; @class OFDNSResolverQuery; +@class OFDNSResolverSettings; @class OFDate; @class OFDictionary OF_GENERIC(KeyType, ObjectType); @class OFMutableDictionary OF_GENERIC(KeyType, ObjectType); @class OFNumber; @class OFUDPSocket; @@ -114,20 +115,11 @@ * reload. */ OF_SUBCLASSING_RESTRICTED @interface OFDNSResolver: OFObject { - OFDictionary OF_GENERIC(OFString *, OFArray OF_GENERIC(OFString *) *) - *_staticHosts; - OFArray OF_GENERIC(OFString *) *_nameServers; - OFString *_Nullable _localDomain; - OFArray OF_GENERIC(OFString *) *_searchDomains; - of_time_interval_t _timeout; - unsigned int _maxAttempts, _minNumberOfDotsInAbsoluteName; - bool _usesTCP; - of_time_interval_t _configReloadInterval; - OFDate *_lastConfigReload; + OFDNSResolverSettings *_settings; OFUDPSocket *_IPv4Socket; #ifdef OF_HAVE_IPV6 OFUDPSocket *_IPv6Socket; #endif char _buffer[OF_DNS_RESOLVER_BUFFER_LENGTH]; Index: src/OFDNSResolver.m ================================================================== --- src/OFDNSResolver.m +++ src/OFDNSResolver.m @@ -15,56 +15,35 @@ * file. */ #include "config.h" -#include #include -#include "unistd_wrapper.h" #import "OFDNSResolver.h" #import "OFArray.h" -#import "OFCharacterSet.h" #import "OFDNSRequest.h" +#import "OFDNSResolverSettings.h" #import "OFDNSResponse.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 "OFDNSRequestFailedException.h" #import "OFInitializationFailedException.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFInvalidServerReplyException.h" -#import "OFOpenItemFailedException.h" -#import "OFOutOfMemoryException.h" #import "OFOutOfRangeException.h" #import "OFTruncatedDataException.h" -#ifdef OF_WINDOWS -# define interface struct -# include -# undef interface -#endif - -#import "socket_helpers.h" - -#ifdef OF_NINTENDO_3DS -# include <3ds.h> -#endif - #ifndef SOCK_DNS # define SOCK_DNS 0 #endif #define BUFFER_LENGTH OF_DNS_RESOLVER_BUFFER_LENGTH @@ -77,51 +56,19 @@ */ #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 - /* * 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 OFDNSRequest *_request; OFString *_domainName; @@ -193,81 +140,23 @@ id _exception; } @end @interface OFDNSResolver () -- (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_asyncPerformRequest: (OFDNSRequest *)request 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_asyncPerformRequest: (OFDNSRequest *)request - 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; @@ -658,42 +547,10 @@ method(target, selector, resolver, domainName, response, 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)initWithRequest: (OFDNSRequest *)request domainName: (OFString *)domainName ID: (OFNumber *)ID settings: (OFDNSResolverSettings *)settings @@ -711,11 +568,11 @@ uint16_t tmp; _request = [request copy]; _domainName = [domainName copy]; _ID = [ID retain]; - _settings = [settings retain]; + _settings = [settings copy]; _nameServersIndex = nameServersIndex; _searchDomainsIndex = searchDomainsIndex; _target = [target retain]; _selector = selector; _context = [context retain]; @@ -897,10 +754,13 @@ request = [OFDNSRequest requestWithHost: alias recordClass: OF_DNS_RESOURCE_RECORD_CLASS_IN recordType: recordType]; [_resolver of_asyncPerformRequest: request + settings: nil + nameServersIndex: 0 + searchDomainsIndex: 0 runLoopMode: runLoopMode target: self selector: @selector(resolver: didResolveCNAME:response: context:exception:) @@ -1086,16 +946,10 @@ _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_AMIGAOS + (void)initialize { if (self != [OFDNSResolver class]) return; @@ -1114,114 +968,27 @@ - (instancetype)init { self = [super init]; @try { + _settings = [[OFDNSResolverSettings alloc] init]; _queries = [[OFMutableDictionary alloc] init]; - [self of_obtainSystemConfig]; + [_settings reload]; } @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]; + [_settings release]; [_IPv4Socket cancelAsyncRequests]; [_IPv4Socket release]; #ifdef OF_HAVE_IPV6 [_IPv6Socket cancelAsyncRequests]; [_IPv6Socket release]; @@ -1229,344 +996,101 @@ [_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 time, 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]; +- (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_asyncPerformRequest: (OFDNSRequest *)request settings: (OFDNSResolverSettings *)settings nameServersIndex: (size_t)nameServersIndex @@ -1579,11 +1103,14 @@ void *pool = objc_autoreleasePoolPush(); OFNumber *ID; OFString *host, *domainName; OFDNSResolverQuery *query; - [self of_reloadSystemConfig]; + if (settings == nil) { + [_settings reload]; + settings = _settings; + } /* Random, unused ID */ do { ID = [OFNumber numberWithUInt16: (uint16_t)of_random()]; } while ([_queries objectForKey: ID] != nil); @@ -1640,10 +1167,13 @@ - (void)asyncPerformRequest: (OFDNSRequest *)request delegate: (id )delegate { [self of_asyncPerformRequest: request + settings: nil + nameServersIndex: 0 + searchDomainsIndex: 0 runLoopMode: of_run_loop_mode_default target: self selector: @selector(of_resolver: didResolveDomainName:response:context: exception:) @@ -1653,45 +1183,21 @@ - (void)asyncPerformRequest: (OFDNSRequest *)request runLoopMode: (of_run_loop_mode_t)runLoopMode delegate: (id )delegate { [self of_asyncPerformRequest: request + settings: nil + nameServersIndex: 0 + searchDomainsIndex: 0 runLoopMode: runLoopMode target: self selector: @selector(of_resolver: didResolveDomainName:response:context: exception:) context: delegate]; } -- (void)of_asyncPerformRequest: (OFDNSRequest *)request - 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_asyncPerformRequest: request - 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; @@ -2022,11 +1528,11 @@ return; } @catch (OFInvalidFormatException *e) { } - if ((aliases = [_staticHosts objectForKey: host]) != nil) { + if ((aliases = [_settings->_staticHosts objectForKey: host]) != nil) { OFMutableData *addresses = [OFMutableData dataWithItemSize: sizeof(of_socket_address_t)]; id exception = nil; for (OFString *alias in aliases) { @@ -2138,10 +1644,13 @@ recordType: OF_DNS_RESOURCE_RECORD_TYPE_AAAA]; OFNumber *recordTypeNumber = [OFNumber numberWithInt: OF_DNS_RESOURCE_RECORD_TYPE_AAAA]; [self of_asyncPerformRequest: request + settings: nil + nameServersIndex: 0 + searchDomainsIndex: 0 runLoopMode: runLoopMode target: context selector: @selector(resolver: didResolveDomainName:response: context:exception:) @@ -2157,10 +1666,13 @@ recordType: OF_DNS_RESOURCE_RECORD_TYPE_A]; OFNumber *recordTypeNumber = [OFNumber numberWithInt: OF_DNS_RESOURCE_RECORD_TYPE_A]; [self of_asyncPerformRequest: request + settings: nil + nameServersIndex: 0 + searchDomainsIndex: 0 runLoopMode: runLoopMode target: context selector: @selector(resolver: didResolveDomainName:response: context:exception:) ADDED src/OFDNSResolverSettings.h Index: src/OFDNSResolverSettings.h ================================================================== --- src/OFDNSResolverSettings.h +++ src/OFDNSResolverSettings.h @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#import "OFObject.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFArray OF_GENERIC(ObjectType); +@class OFDate; +@class OFDictionary OF_GENERIC(KeyType, ObjectType); + +@interface OFDNSResolverSettings: OFObject +{ +@public + OFDictionary OF_GENERIC(OFString *, OFArray OF_GENERIC(OFString *) *) + *_staticHosts; + OFArray OF_GENERIC(OFString *) *_nameServers; + OFString *_Nullable _localDomain; + OFArray OF_GENERIC(OFString *) *_searchDomains; + of_time_interval_t _timeout; + unsigned int _maxAttempts, _minNumberOfDotsInAbsoluteName; + bool _usesTCP; + of_time_interval_t _configReloadInterval; +@protected + OFDate *_lastConfigReload; +} + +- (void)reload; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFDNSResolverSettings.m Index: src/OFDNSResolverSettings.m ================================================================== --- src/OFDNSResolverSettings.m +++ src/OFDNSResolverSettings.m @@ -0,0 +1,545 @@ +/* + * 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 "unistd_wrapper.h" + +#import "OFDNSResolverSettings.h" +#import "OFArray.h" +#import "OFCharacterSet.h" +#import "OFDate.h" +#import "OFDictionary.h" +#import "OFFile.h" +#import "OFLocale.h" +#import "OFString.h" +#ifdef OF_WINDOWS +# import "OFWindowsRegistryKey.h" +#endif + +#import "OFInvalidFormatException.h" +#import "OFOpenItemFailedException.h" +#import "OFOutOfMemoryException.h" + +#ifdef OF_WINDOWS +# define interface struct +# include +# undef interface +#endif + +#ifdef OF_NINTENDO_3DS +# include <3ds.h> +#endif + +#import "socket_helpers.h" + +#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 + +#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 + +@implementation OFDNSResolverSettings +- (void)dealloc +{ + [_staticHosts release]; + [_nameServers release]; + [_localDomain release]; + [_searchDomains release]; + [_lastConfigReload release]; + + [super dealloc]; +} + +- (id)copy +{ + OFDNSResolverSettings *copy = [[OFDNSResolverSettings alloc] init]; + + @try { + copy->_staticHosts = [_staticHosts copy]; + copy->_nameServers = [_nameServers copy]; + copy->_localDomain = [_localDomain copy]; + copy->_searchDomains = [_searchDomains copy]; + copy->_timeout = _timeout; + copy->_maxAttempts = _maxAttempts; + copy->_minNumberOfDotsInAbsoluteName = + _minNumberOfDotsInAbsoluteName; + copy->_usesTCP = _usesTCP; + copy->_configReloadInterval = _configReloadInterval; + copy->_lastConfigReload = [_lastConfigReload copy]; + } @catch (id e) { + [copy release]; + @throw e; + } + + return copy; +} + +- (void)setDefaults +{ + [_staticHosts release]; + _staticHosts = nil; + + [_nameServers release]; + _nameServers = nil; + + [_localDomain release]; + _localDomain = nil; + + [_searchDomains release]; + _searchDomains = nil; + + _timeout = 2; + _maxAttempts = 3; + _minNumberOfDotsInAbsoluteName = 1; + _usesTCP = false; +#ifndef OF_NINTENDO_3DS + _configReloadInterval = 2; +#else + _configReloadInterval = 0; +#endif +} + +#if defined(OF_HAVE_FILES) && !defined(OF_NINTENDO_3DS) +- (void)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)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) { + } +} + +- (void)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 parseResolvConfOption: argument]; + + objc_autoreleasePoolPop(pool2); + } + + [nameServers makeImmutable]; + + [_nameServers release]; + _nameServers = [nameServers copy]; + + objc_autoreleasePoolPop(pool); +} +# endif +#endif + +#ifdef OF_WINDOWS +- (void)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)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)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)reload +{ + void *pool; +#ifdef OF_WINDOWS + OFString *path; +#endif + + /* + * TODO: Rather than reparsing every time, check what actually changed + * (mtime) and only reset those. + */ + + if (_lastConfigReload != nil && _configReloadInterval > 0 && + _lastConfigReload.timeIntervalSinceNow < _configReloadInterval) + return; + + pool = objc_autoreleasePoolPush(); + + [self 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 parseHosts: path]; +# endif + + [self obtainWindowsSystemConfig]; +#elif defined(OF_AMIGAOS4) + [self parseHosts: HOSTS_PATH]; + [self obtainAmigaOS4SystemConfig]; +#elif defined(OF_NINTENDO_3DS) + [self obtainNintendo3DSSytemConfig]; +#elif defined(OF_HAVE_FILES) + [self parseHosts: HOSTS_PATH]; + [self 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]; + } + + objc_autoreleasePoolPop(pool); +} +@end