/*
* Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
*
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3.0 only,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* version 3.0 for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3.0 along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#import "OFApplication.h"
#import "OFArray.h"
#import "OFDNSResolver.h"
#import "OFIRI.h"
#import "OFLocale.h"
#import "OFOptionsParser.h"
#import "OFSandbox.h"
#import "OFStdIOStream.h"
@interface OFDNS: OFObject <OFApplicationDelegate, OFDNSResolverQueryDelegate>
{
size_t _inFlight;
int _errors;
}
@end
OF_APPLICATION_DELEGATE(OFDNS)
static void
help(OFStream *stream, bool full, int status)
{
[OFStdErr writeLine: OF_LOCALIZED(@"usage",
@"Usage: %[prog] -[chst] domain1 [domain2 ...]",
@"prog", [OFApplication programName])];
if (full) {
[stream writeString: @"\n"];
[stream writeLine: OF_LOCALIZED(@"full_usage",
@"Options:\n "
@"-c --class= "
@" The DNS class to query (defaults to IN)\n "
@"-h --help "
@" Show this help\n "
@"-s --server="
@" The server to query\n "
@"-t --type= "
@" The record type to query (defaults to AAAA and A,\n"
@" can be repeated)\n "
@" --tcp "
@" Force using TCP for the query")];
}
[OFApplication terminateWithStatus: status];
}
@implementation OFDNS
- (void)resolver: (OFDNSResolver *)resolver
didPerformQuery: (OFDNSQuery *)query
response: (OFDNSResponse *)response
exception: (id)exception
{
_inFlight--;
if (exception == nil)
[OFStdOut writeFormat: @"%@\n", response];
else {
[OFStdErr writeLine: OF_LOCALIZED(@"failed_to_resolve",
@"Failed to resolve: %[exception]",
@"exception", exception)];
_errors++;
}
if (_inFlight == 0)
[OFApplication terminateWithStatus: _errors];
}
- (void)applicationDidFinishLaunching: (OFNotification *)notification
{
OFString *DNSClassString, *server;
bool forceTCP;
const OFOptionsParserOption options[] = {
{ 'c', @"class", 1, NULL, &DNSClassString },
{ 'h', @"help", 0, NULL, NULL },
{ 's', @"server", 1, NULL, &server },
{ 't', @"type", 1, NULL, NULL },
{ '\0', @"tcp", 0, &forceTCP, NULL },
{ '\0', nil, 0, NULL, NULL }
};
OFMutableArray OF_GENERIC(OFString *) *recordTypes;
OFOptionsParser *optionsParser;
OFUnichar option;
OFArray OF_GENERIC(OFString *) *remainingArguments;
OFDNSResolver *resolver;
OFDNSClass DNSClass;
#ifdef OF_HAVE_FILES
# ifndef OF_AMIGAOS
[OFLocale addLocalizationDirectoryIRI:
[OFIRI fileIRIWithPath: @LOCALIZATION_DIR]];
# else
[OFLocale addLocalizationDirectoryIRI:
[OFIRI fileIRIWithPath: @"PROGDIR:/share/ofdns/localization"]];
# endif
#endif
#ifdef OF_HAVE_SANDBOX
OFSandbox *sandbox = [[OFSandbox alloc] init];
@try {
sandbox.allowsStdIO = true;
sandbox.allowsDNS = true;
[OFApplication of_activateSandbox: sandbox];
} @finally {
[sandbox release];
}
#endif
recordTypes = [OFMutableArray array];
optionsParser = [OFOptionsParser parserWithOptions: options];
while ((option = [optionsParser nextOption]) != '\0') {
switch (option) {
case 't':
[recordTypes addObject: optionsParser.argument];
break;
case 'h':
help(OFStdOut, true, 0);
break;
case ':':
if (optionsParser.lastLongOption != nil)
[OFStdErr writeLine: OF_LOCALIZED(
@"long_option_required_argument",
@"%[prog]: Option --%[opt] requires an "
@"argument",
@"prog", [OFApplication programName],
@"opt", optionsParser.lastLongOption)];
else {
OFString *optStr = [OFString
stringWithFormat: @"%C",
optionsParser.lastOption];
[OFStdErr writeLine: OF_LOCALIZED(
@"option_requires_argument",
@"%[prog]: Option -%[opt] requires an "
@"argument",
@"prog", [OFApplication programName],
@"opt", optStr)];
}
[OFApplication terminateWithStatus: 1];
break;
case '?':
if (optionsParser.lastLongOption != nil)
[OFStdErr writeLine: OF_LOCALIZED(
@"unknown_long_option",
@"%[prog]: Unknown option: --%[opt]",
@"prog", [OFApplication programName],
@"opt", optionsParser.lastLongOption)];
else {
OFString *optStr = [OFString
stringWithFormat: @"%C",
optionsParser.lastOption];
[OFStdErr writeLine: OF_LOCALIZED(
@"Unknown_option",
@"%[prog]: Unknown option: -%[opt]",
@"prog", [OFApplication programName],
@"opt", optStr)];
}
[OFApplication terminateWithStatus: 1];
break;
}
}
remainingArguments = optionsParser.remainingArguments;
if (remainingArguments.count < 1)
help(OFStdErr, false, 1);
resolver = [OFDNSResolver resolver];
resolver.configReloadInterval = 0;
resolver.forcesTCP = forceTCP;
DNSClass = (DNSClassString != nil
? OFDNSClassParseName(DNSClassString) : OFDNSClassIN);
if (recordTypes.count == 0) {
[recordTypes addObject: @"AAAA"];
[recordTypes addObject: @"A"];
}
if (server != nil)
resolver.nameServers = [OFArray arrayWithObject: server];
for (OFString *domainName in remainingArguments) {
for (OFString *recordTypeString in recordTypes) {
OFDNSRecordType recordType =
OFDNSRecordTypeParseName(recordTypeString);
OFDNSQuery *query =
[OFDNSQuery queryWithDomainName: domainName
DNSClass: DNSClass
recordType: recordType];
_inFlight++;
[resolver asyncPerformQuery: query delegate: self];
}
}
}
@end