/* * Copyright (c) 2008-2023 Jonathan Schleifer <js@nil.im> * * 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" #import "OFHostAddressResolver.h" #import "OFArray.h" #import "OFDNSResolver.h" #import "OFDNSResolverSettings.h" #import "OFData.h" #import "OFDate.h" #import "OFDictionary.h" #import "OFRunLoop.h" #import "OFString.h" #import "OFTimer.h" #import "OFDNSQueryFailedException.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFResolveHostFailedException.h" @interface OFHostAddressResolverDelegate: OFObject <OFDNSResolverHostDelegate> { @public bool _done; OFData *_addresses; id _exception; } @end static const OFRunLoopMode resolveRunLoopMode = @"OFHostAddressResolverResolveRunLoopMode"; static bool isFQDN(OFString *host, unsigned int minNumberOfDotsInAbsoluteName) { const char *UTF8String; size_t length; unsigned int dots; if ([host hasSuffix: @"."]) return true; UTF8String = host.UTF8String; length = host.UTF8StringLength; dots = 0; for (size_t i = 0; i < length; i++) if (UTF8String[i] == '.') dots++; return (dots >= minNumberOfDotsInAbsoluteName); } static bool addressForRecord(OF_KINDOF(OFDNSResourceRecord *) record, const OFSocketAddress **address, OFSocketAddressFamily addressFamily) { switch ([record recordType]) { #ifdef OF_HAVE_IPV6 case OFDNSRecordTypeAAAA: if (addressFamily != OFSocketAddressFamilyIPv6 && addressFamily != OFSocketAddressFamilyAny) return false; break; #endif case OFDNSRecordTypeA: if (addressFamily != OFSocketAddressFamilyIPv4 && addressFamily != OFSocketAddressFamilyAny) return false; break; default: return false; } *address = [record address]; return true; } static void callDelegateInMode(OFRunLoopMode runLoopMode, id <OFDNSResolverHostDelegate> delegate, OFDNSResolver *resolver, OFString *host, OFData *addresses, id exception) { SEL selector = @selector(resolver:didResolveHost:addresses:exception:); if ([delegate respondsToSelector: selector]) { OFTimer *timer = [OFTimer timerWithTimeInterval: 0 target: delegate selector: selector object: resolver object: host object: addresses object: exception repeats: false]; [[OFRunLoop currentRunLoop] addTimer: timer forMode: runLoopMode]; } } @implementation OFHostAddressResolver: OFObject - (instancetype)initWithHost: (OFString *)host addressFamily: (OFSocketAddressFamily)addressFamily resolver: (OFDNSResolver *)resolver settings: (OFDNSResolverSettings *)settings runLoopMode: (OFRunLoopMode)runLoopMode delegate: (id <OFDNSResolverHostDelegate>)delegate { self = [super init]; @try { _host = [host copy]; _addressFamily = addressFamily; _resolver = [resolver retain]; _settings = [settings copy]; _runLoopMode = [runLoopMode copy]; _delegate = [delegate retain]; } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [_host release]; [_resolver release]; [_settings release]; [_runLoopMode release]; [_delegate release]; [_addresses release]; [super dealloc]; } - (void)sendQueries { OFString *domainName; if (!_isFQDN) { OFString *searchDomain = @""; if (_searchDomainIndex < _settings->_searchDomains.count) searchDomain = [_settings->_searchDomains objectAtIndex: _searchDomainIndex]; domainName = [OFString stringWithFormat: @"%@.%@", _host, searchDomain]; } else domainName = _host; #ifdef OF_HAVE_IPV6 if (_addressFamily == OFSocketAddressFamilyIPv6 || _addressFamily == OFSocketAddressFamilyAny) { OFDNSQuery *query = [OFDNSQuery queryWithDomainName: domainName DNSClass: OFDNSClassIN recordType: OFDNSRecordTypeAAAA]; _numExpectedResponses++; [_resolver asyncPerformQuery: query runLoopMode: _runLoopMode delegate: self]; } #endif if (_addressFamily == OFSocketAddressFamilyIPv4 || _addressFamily == OFSocketAddressFamilyAny) { OFDNSQuery *query = [OFDNSQuery queryWithDomainName: domainName DNSClass: OFDNSClassIN recordType: OFDNSRecordTypeA]; _numExpectedResponses++; [_resolver asyncPerformQuery: query runLoopMode: _runLoopMode delegate: self]; } } - (void)resolver: (OFDNSResolver *)resolver didPerformQuery: (OFDNSQuery *)query response: (OFDNSResponse *)response exception: (id)exception { _numExpectedResponses--; if ([exception isKindOfClass: [OFDNSQueryFailedException class]] && [exception errorCode] == OFDNSResolverErrorCodeServerNameError && !_isFQDN && _numExpectedResponses == 0 && _addresses.count == 0 && _searchDomainIndex + 1 < _settings->_searchDomains.count) { _searchDomainIndex++; [self sendQueries]; return; } for (OF_KINDOF(OFDNSResourceRecord *) record in [response.answerRecords objectForKey: query.domainName]) { const OFSocketAddress *address = NULL; OFDNSQuery *CNAMEQuery; if ([record DNSClass] != OFDNSClassIN) continue; if (addressForRecord(record, &address, _addressFamily)) { [_addresses addItem: address]; continue; } if ([record recordType] != OFDNSRecordTypeCNAME) continue; /* FIXME: Check if it's already in answers */ CNAMEQuery = [OFDNSQuery queryWithDomainName: [record alias] DNSClass: OFDNSClassIN recordType: query.recordType]; _numExpectedResponses++; [_resolver asyncPerformQuery: CNAMEQuery runLoopMode: _runLoopMode delegate: self]; } if (_numExpectedResponses > 0) return; [_addresses makeImmutable]; if (_addresses.count == 0) { [_addresses release]; _addresses = nil; if ([exception isKindOfClass: [OFDNSQueryFailedException class]]) exception = [OFResolveHostFailedException exceptionWithHost: _host addressFamily: _addressFamily errorCode: [exception errorCode]]; if (exception == nil) exception = [OFResolveHostFailedException exceptionWithHost: _host addressFamily: _addressFamily errorCode: OFDNSResolverErrorCodeNoResult]; } else exception = nil; if ([_delegate respondsToSelector: @selector(resolver:didResolveHost:addresses:exception:)]) [_delegate resolver: _resolver didResolveHost: _host addresses: _addresses exception: exception]; } - (void)asyncResolve { void *pool = objc_autoreleasePoolPush(); OFArray OF_GENERIC(OFString *) *aliases; @try { OFSocketAddress address = OFSocketAddressParseIP(_host, 0); OFData *addresses = nil; id exception = nil; if (_addressFamily == address.family || _addressFamily == OFSocketAddressFamilyAny) addresses = [OFData dataWithItems: &address count: 1 itemSize: sizeof(address)]; else exception = [OFInvalidArgumentException exception]; callDelegateInMode(_runLoopMode, _delegate, _resolver, _host, addresses, exception); objc_autoreleasePoolPop(pool); return; } @catch (OFInvalidFormatException *e) { } if ((aliases = [_settings->_staticHosts objectForKey: _host]) != nil) { OFMutableData *addresses = [OFMutableData dataWithItemSize: sizeof(OFSocketAddress)]; id exception = nil; for (OFString *alias in aliases) { OFSocketAddress address; @try { address = OFSocketAddressParseIP(alias, 0); } @catch (OFInvalidFormatException *e) { continue; } if (_addressFamily != address.family && _addressFamily != OFSocketAddressFamilyAny) continue; [addresses addItem: &address]; } [addresses makeImmutable]; if (addresses.count == 0) { addresses = nil; exception = [OFResolveHostFailedException exceptionWithHost: _host addressFamily: _addressFamily errorCode: OFDNSResolverErrorCodeNoResult]; } callDelegateInMode(_runLoopMode, _delegate, _resolver, _host, addresses, exception); objc_autoreleasePoolPop(pool); return; } _isFQDN = isFQDN(_host, _settings->_minNumberOfDotsInAbsoluteName); _addresses = [[OFMutableData alloc] initWithItemSize: sizeof(OFSocketAddress)]; [self sendQueries]; objc_autoreleasePoolPop(pool); } - (OFData *)resolve { void *pool = objc_autoreleasePoolPush(); OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; OFHostAddressResolverDelegate *delegate; OFData *ret; delegate = [[[OFHostAddressResolverDelegate alloc] init] autorelease]; _runLoopMode = [resolveRunLoopMode copy]; _delegate = [delegate retain]; [self asyncResolve]; while (!delegate->_done) [runLoop runMode: resolveRunLoopMode beforeDate: nil]; /* Cleanup */ [runLoop runMode: resolveRunLoopMode beforeDate: [OFDate date]]; if (delegate->_exception != nil) @throw delegate->_exception; ret = [delegate->_addresses copy]; objc_autoreleasePoolPop(pool); return [ret autorelease]; } @end @implementation OFHostAddressResolverDelegate - (void)dealloc { [_addresses release]; [_exception release]; [super dealloc]; } - (void)resolver: (OFDNSResolver *)resolver didResolveHost: (OFString *)host addresses: (OFData *)addresses exception: (id)exception { _addresses = [addresses copy]; _exception = [exception retain]; _done = true; } @end