restemplate/indy/Protocols/IdDNSResolver.pas

1722 lines
51 KiB
Plaintext

{
$Project$
$Workfile$
$Revision$
$DateUTC$
$Id$
This file is part of the Indy (Internet Direct) project, and is offered
under the dual-licensing agreement described on the Indy website.
(http://www.indyproject.org/)
Copyright:
(c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
$Log$
4/19/2005 BTaylor
Added support for SVR and NAPTR records. (Used for SIP/VOIP) (parts by Frank Shearar)
Added TResultRecord.Section, .FilterBySection , .FilterByClass
DNS lookups can now be generated exactly the same as NsLookup.
Improved .Assign support on many objects. QueryResult object+items can now be properly cloned.
TIdDNSResolver.FDNSHeader was a public field, now it's a public readonly property, TIdDNSResolver.DNSHeader
fixed TMXRecord.Parse bug, .Preference will now contain correct value.
fixed TTextRecord.Parse issue. DomainKeys (yahoo's anti-spam method) can now be used.
Minor cleanups/spelling errors fixed.
Rev 1.26 3/21/2005 10:36:20 PM VVassiliev
NextDNSLabel fix
TTextRecord.Parse fix
ClearInternalQuery before resolving
Rev 1.25 2/9/05 2:10:34 AM RLebeau
Removed compiler hint
Rev 1.24 2/8/05 6:17:14 PM RLebeau
Updated CreateQuery() to use Fetch() and AppendString() instead of Pos(),
ToBytes(), and AppendBytes()
Rev 1.23 10/26/2004 9:06:30 PM JPMugaas
Updated references.
Rev 1.22 2004.10.25 10:18:38 PM czhower
Removed unused var.
Rev 1.21 25/10/2004 15:55:28 ANeillans
Bug fix:
http://apps.atozedsoftware.com/cgi-bin/BBGIndy/BugBeGoneISAPI.dll/?item=122
Checked in for Dennies Chang
Rev 1.20 2004/7/19 ¤U¤È 09:40:52 DChang
1. fix the TIdResolver.ParseAnswers, add 2 parameters for the function to
check if QueryResult should be clear or not, TIdResolver.FillResult is
modified at the same time.
Fix AXFR procedure, fully support BIND 8 AXFR procedures.
2. Replace the original type indicator in TQueryResult.Add.
It can understand AAAA type correctly.
3. Add qtIXFR type for TIdDNSResover, add 2 parameters for
TIdDNSResolver.Resolver, add one parameter for TIdDNSResolver.CreateHeader.
4. Support query type CHAOS, but only for checking version.bind. (Check DNS
server version.)
Rev 1.19 7/12/2004 9:42:26 PM DSiders
Removed TODO for Address property.
Rev 1.18 7/12/2004 9:24:04 PM DSiders
Added TODOs for property name inconsistencies.
Rev 1.17 7/8/04 11:48:28 PM RLebeau
Tweaked TQueryResult.NextDNSLabel()
Rev 1.16 2004.05.20 1:39:30 PM czhower
Last of the IdStream updates
Rev 1.15 2004.04.08 3:57:28 PM czhower
Removal of bytes from buffer.
Rev 1.14 2004.03.01 9:37:04 PM czhower
Fixed name conflicts for .net
Rev 1.13 2/11/2004 5:47:26 AM JPMugaas
Can now assign a port for the DNS host as well as IPVersion.
In addition, you can now use socks with TCP zone transfers.
Rev 1.12 2/11/2004 5:21:16 AM JPMugaas
Vladimir Vassiliev changes for removal of byte flipping. Network conversion
order conversion functions are used instead.
IPv6 addresses are returned in the standard form.
In WKS records, Address was changed to IPAddress to be consistant with other
record types. Address can also imply a hostname.
Rev 1.11 2/9/2004 11:27:36 AM JPMugaas
Some functions weren't working as expected. Renamed them to describe them
better.
Rev 1.10 2004.02.03 5:45:58 PM czhower
Name changes
Rev 1.9 11/13/2003 5:46:54 PM VVassiliev
DotNet
AAAA record fix
Add PTR for IPV6
Rev 1.8 10/25/2003 06:51:54 AM JPMugaas
Updated for new API changes and tried to restore some functionality.
Rev 1.7 10/19/2003 11:57:32 AM DSiders
Added localization comments.
Rev 1.6 2003.10.12 3:50:38 PM czhower
Compile todos
Rev 1.5 2003/4/30 ¤U¤È 12:39:54 DChang
fix the TIdResolver.ParseAnswers, add 2 parameters for the function
to check if QueryResult should be clear or not, TIdResolver.FillResult
is modified at the same time.
fix AXFR procedure, fully support BIND 8 AXFR procedures.
Rev 1.4 4/28/2003 02:30:50 PM JPMugaas
reverted back to the old one as the new one checked will not compile, has
problametic dependancies on Contrs and Dialogs (both not permitted).
Rev 1.2 4/28/2003 07:00:10 AM JPMugaas
Should now compile.
Rev 1.0 11/14/2002 02:18:34 PM JPMugaas
Rev 1.3 04/26/2003 02:30:10 PM DenniesChang
IdDNSResolver.
Started: sometime.
Finished: 2003/04/26
IdDNSResolver has integrate UDP and TCP tunnel to resolve then types defined in RFC 1035,
and AAAA, which is defined in RFC 1884, 1886.
AXFR command, which is defined in RFC 1995, is also implemented in 2003/04/26
The resolver also does not support Chaos RR. Only IN RR are supported as of this time.
Part of code from Ray Malone
// Dennies Chang : Combine TIdDNSSyncResolver and TIdDNSCommResolver as TIdDNSResolver.
// 2003/04/26.
// Dennies Chang : Rename TIdDNSResolver as TIdDNSCommonResolver. 2003/04/23
// Dennies Chang : Add TIdDNSSyncClient to implement AXFR command. 2003/04/15
// Dennies Chang : Add atAAAA and TAAAARecord (2002 Oct.)
// Dennies Chang : Add TDNSHeader for IDHeader to maintain DNS Header, but not complete yet.
// SG 28/1/02: Changed the DNSStrToDomain function according to original Author of the old comp: Ray Malone
SG 10/07/01 Added support for qrStar query
VV 12/09/01 Added construction of reverse query (PTR)
DS 12/31/01 Corrected ReponsiblePerson spelling
VV 01/02/03 TQueryResult.DNSStrToDomain fix
TODO : Add structure of IDHEADER IN FIGURE }
unit IdDNSResolver;
interface
{$i IdCompilerDefines.inc}
uses
Classes,
IdAssignedNumbers,
IdBuffer,
IdComponent,
IdGlobal, IdExceptionCore,
IdNetworkCalculator,
IdGlobalProtocols,
IdDNSCommon,
IdTCPClient,
IdTCPConnection,
IdUDPClient;
(*$HPPEMIT '#if defined(_VCL_ALIAS_RECORDS)' *)
(*$HPPEMIT '#if !defined(UNICODE)' *)
(*$HPPEMIT '#pragma alias "@Iddnsresolver@TIdDNSResolver@SetPortA$qqrxus"="@Iddnsresolver@TIdDNSResolver@SetPort$qqrxus"' *)
(*$HPPEMIT '#else' *)
(*$HPPEMIT '#pragma alias "@Iddnsresolver@TIdDNSResolver@SetPortW$qqrxus"="@Iddnsresolver@TIdDNSResolver@SetPort$qqrxus"' *)
(*$HPPEMIT '#endif' *)
(*$HPPEMIT '#endif' *)
type
{ TODO : Solve problem with obsolete records }
TQueryRecordTypes = (
qtA, qtNS, qtMD, qtMF,
qtName, qtSOA, qtMB, qtMG,
qtMR, qtNull, qtWKS, qtPTR,
qtHINFO, qtMINFO, qtMX, qtTXT,
//qtRP, qtAfsdb, qtX25, qtISDN,
qtRT, qtNSAP, qtNSAP_PTR, qtSIG,
//qtKEY, qtPX, qtQPOS,
qtAAAA,
//qtLOC, qtNXT, qtR31, qtR32,
qtService,
//qtR34,
qtNAPTR,
//qtKX,
qtCERT, qtV6Addr, qtDName, qtR40,
qtOptional, qtIXFR, qtAXFR, qtSTAR);
{Marked by Dennies Chang at 2004/7/14.
{TXFRTypes = (xtAXFR, xtIXFR);
}
const
// Lookup table for query record values.
QueryRecordCount = 30;
QueryRecordValues: array [0..QueryRecordCount] of UInt16 = (
1,2,3,4,
5,6,7,8,
9,10,11,12,
13,14,15,16,
//17,18,19,20,
21,22,23,24,
//25,26,27,
28,
//29,30,31,32,
33,
//34,
35,
//36,
37,38,39,40,
41, 251, 252, 255);
QueryRecordTypes: Array [0..QueryRecordCount] of TQueryRecordTypes = (
qtA, qtNS, qtMD, qtMF,
qtName, qtSOA, qtMB, qtMG,
qtMR, qtNull, qtWKS, qtPTR,
qtHINFO, qtMINFO, qtMX, qtTXT,
//qtRP, qtAfsdb, qtX25, qtISDN,
qtRT, qtNSAP, qtNSAP_PTR, qtSIG,
//qtKEY, qtPX, qtQPOS,
qtAAAA,
//qtLOC, qtNXT, qtR31, qtR32,
qtService,
//qtR34,
qtNAPTR,
//qtKX,
qtCERT, qtV6Addr, qtDName, qtR40,
qtOptional, qtIXFR, qtAXFR, qtSTAR);
type
TQueryType = set of TQueryRecordTypes;
TResultSection = (rsAnswer, rsNameServer, rsAdditional);
TResultSections = set of TResultSection;
TResultRecord = class(TCollectionItem) // Rename to REsourceRecord
protected
FRecType: TQueryRecordTypes;
FRecClass: UInt16;
FName: string;
FTTL: UInt32;
FRDataLength: Integer;
FRData: TIdBytes;
FSection: TResultSection;
public
procedure Assign(Source: TPersistent); override;
// Parse the data (descendants only)
procedure Parse(CompleteMessage: TIdBytes; APos: Integer); virtual;
{ TODO : This needs to change (to what? why?) }
property RecType: TQueryRecordTypes read FRecType;
property RecClass: UInt16 read FRecClass;
property Name: string read FName;
property TTL: UInt32 read FTTL;
property RDataLength: Integer read FRDataLength;
property RData: TIdBytes read FRData;
property Section: TResultSection read FSection;
end;
TResultRecordClass = class of TResultRecord;
TRDATARecord = class(TResultRecord)
protected
FIPAddress: String;
public
procedure Parse(CompleteMessage: TIdBytes; APos: Integer); override;
procedure Assign(Source: TPersistent); override;
property IPAddress: string read FIPAddress;
end;
TARecord = class(TRDATARecord)
end;
TAAAARecord = class (TResultRecord)
protected
FAddress: string;
public
//TODO: implement AssignTo instead of Assign. (why?)
procedure Assign(Source: TPersistent); override;
procedure Parse(CompleteMessage: TIdBytes; APos: Integer); override;
//
property Address : string read FAddress;
end;
TWKSRecord = Class(TResultRecord)
protected
FByteCount: integer;
FData: TIdBytes;
FIPAddress: String;
FProtocol: UInt16;
//
function GetABit(AIndex: Integer): UInt8;
public
procedure Parse(CompleteMessage: TIdBytes; APos: Integer); override;
procedure Assign(Source: TPersistent); override;
//
property IPAddress: String read FIPAddress;
property Protocol: UInt16 read FProtocol;
property BitMap[index: integer]: UInt8 read GetABit;
property ByteCount: integer read FByteCount;
end;
TMXRecord = class(TResultRecord)
protected
FExchangeServer: string;
FPreference: UInt16;
public
procedure Parse(CompleteMessage: TIdBytes; APos: Integer); override;
procedure Assign(Source: TPersistent); override;
property ExchangeServer: string read FExchangeServer;
property Preference: UInt16 read FPreference;
end;
TTextRecord = class(TResultRecord)
protected
FText: TStrings;
public
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
procedure Parse(CompleteMessage: TIdBytes; APos: Integer); override;
Property Text: TStrings read FText;
end;
TErrorRecord = class(TResultRecord)
end;
THINFORecord = Class(TTextRecord)
protected
FCPU: String;
FOS: String;
public
procedure Assign(Source: TPersistent); override;
procedure Parse(CompleteMessage: TIdBytes; APos: Integer); override;
property CPU: String read FCPU;
property OS: String read FOS;
end;
TMINFORecord = Class(TResultRecord)
protected
FResponsiblePerson: String;
FErrorMailbox: String;
public
procedure Parse(CompleteMessage: TIdBytes; APos: Integer); override;
procedure Assign(Source: TPersistent); override;
property ResponsiblePersonMailbox: String read FResponsiblePerson;
property ErrorMailbox: String read FErrorMailbox;
end;
TSOARecord = class(TResultRecord)
protected
FSerial: UInt32;
FMinimumTTL: UInt32;
FRefresh: UInt32;
FRetry: UInt32;
FMNAME: string;
FRNAME: string;
FExpire: UInt32;
public
procedure Parse(CompleteMessage: TIdBytes; APos: Integer); override;
procedure Assign(Source: TPersistent); override;
property Primary: string read FMNAME;
property ResponsiblePerson: string read FRNAME;
property Serial: UInt32 read FSerial;
property Refresh: UInt32 read FRefresh;
property Retry: UInt32 read FRetry;
property Expire: UInt32 read FExpire;
property MinimumTTL: UInt32 read FMinimumTTL;
end;
TNAMERecord = class(TResultRecord)
protected
FHostName: string;
public
procedure Parse(CompleteMessage: TIdBytes; APos: Integer); override;
procedure Assign(Source: TPersistent); override;
property HostName: string read FHostName;
end;
TNSRecord = class(TNAMERecord)
end;
TCNRecord = class(TNAMERecord)
end;
TSRVRecord = class(TResultRecord)
private
FService: string;
FProtocol: string;
FPriority: integer;
FWeight: integer;
FPort: integer;
FTarget: string;
FOriginalName: string;
function IsValidIdent(const aStr:string):Boolean;
function CleanIdent(const aStr:string):string;
public
procedure Parse(CompleteMessage: TIdBytes; APos: Integer); override;
procedure Assign(Source: TPersistent); override;
property OriginalName:string read FOriginalName;
property Service: string read FService;
property Protocol: string read FProtocol;
property Priority: integer read FPriority;
property Weight: integer read FWeight;
property Port: integer read FPort;
property Target: string read FTarget;
end;
TNAPTRRecord = class(TResultRecord)
private
FOrder: integer;
FPreference: integer;
FFlags: string;
FService: string;
FRegExp: string;
FReplacement: string;
public
procedure Parse(CompleteMessage: TIdBytes; APos: Integer); override;
procedure Assign(Source: TPersistent); override;
property Order:integer read fOrder;
property Preference:integer read fPreference;
property Flags:string read fFlags;
property Service:string read fService;
property RegExp:string read fRegExp;
property Replacement:string read fReplacement;
end;
TQueryResult = class(TCollection)
protected
FDomainName: String;
FQueryClass: UInt16;
FQueryType: UInt16;
FQueryPointerList: TStringList;
procedure SetItem(Index: Integer; Value: TResultRecord);
function GetItem(Index: Integer): TResultRecord;
public
constructor Create;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
function Add(Answer: TIdBytes; var APos: Integer): TResultRecord;
procedure Clear; reintroduce;
procedure FilterBySection(const AKeep: TResultSections=[rsAnswer]);
procedure FilterByClass(const AKeep: TResultRecordClass);
Property QueryClass: UInt16 read FQueryClass;
Property QueryType: UInt16 read FQueryType;
Property DomainName: String read FDomainName;
property Items[Index: Integer]: TResultRecord read GetItem write SetItem; default;
end;
TPTRRecord = Class(TNAMERecord)
end;
//TIdTCPConnection looks odd for something that's supposed to be UDP.
//However, DNS uses TCP for zone-transfers.
TIdDNSResolver = class(TIdTCPConnection)
protected
FAllowRecursiveQueries: boolean;
FInternalQuery: TIdBytes;
FQuestionLength: Integer;
FHost: string;
FIPVersion: TIdIPVersion;
FPort: TIdPort;
FQueryResult: TQueryResult;
FQueryType: TQueryType;
FWaitingTime: integer;
FPlainTextResult: TIdBytes;
FDNSHeader : TDNSHeader;
procedure SetInternalQuery(const Value: TIdBytes);
procedure SetPlainTextResult(const Value: TIdBytes);
procedure InitComponent; override;
procedure SetIPVersion(const AValue: TIdIPVersion); virtual;
procedure SetPort(const AValue: TIdPort); virtual;
public
property DNSHeader:TDNSHeader read FDNSHeader;
procedure ClearInternalQuery;
destructor Destroy; override;
procedure ParseAnswers(DNSHeader: TDNSHeader; Answer: TIdBytes; ResetResult: Boolean = True);
procedure CreateQuery(ADomain: string; SOARR : TIdRR_SOA; QueryClass:integer = Class_IN);
procedure FillResult(AResult: TIdBytes; checkID : boolean = true;
ResetResult : boolean = true);
procedure FillResultWithOutCheckId(AResult: TIdBytes); {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use FillResult() with checkID=False'{$ENDIF};{$ENDIF}
procedure Resolve(ADomain: string; SOARR : TIdRR_SOA = nil; QClass: integer = Class_IN);
property QueryResult: TQueryResult read FQueryResult;
property InternalQuery: TIdBytes read FInternalQuery write SetInternalQuery;
property PlainTextResult: TIdBytes read FPlainTextResult write SetPlainTextResult;
published
property QueryType : TQueryType read FQueryType write FQueryType;
// TODO: rename to ReadTimeout?
// Dennies's comment : it's ok, that's just a name.
property WaitingTime : integer read FWaitingTime write FWaitingTime;
property AllowRecursiveQueries : boolean read FAllowRecursiveQueries write FAllowRecursiveQueries;
property Host : string read FHost write FHost;
property Port : TIdPort read FPort write SetPort default IdPORT_DOMAIN;
property IPVersion: TIdIPVersion read FIPVersion write SetIPVersion;
end;
function DNSStrToDomain(const DNSStr: TIdBytes; var VPos: Integer): string;
function NextDNSLabel(const DNSStr: TIdBytes; var VPos: Integer): string;
implementation
uses
IdBaseComponent,
IdResourceStringsProtocols,
IdStack, SysUtils;
// SG 28/1/02: Changed that function according to original Author of the old comp: Ray Malone
function DNSStrToDomain(const DNSStr: TIdBytes; var VPos: Integer): string;
var
LabelStr : String;
Len : Integer;
SavedIdx : Integer;
B : Byte;
PackSize: Integer;
begin
Result := ''; {Do not Localize}
PackSize := Length(DNSStr);
SavedIdx := -1;
while VPos < PackSize do // name field ends with nul byte
begin
Len := DNSStr[VPos];
// RLebeau 5/4/2009: sometimes the first entry of a domain's record is
// not defined, so account for that here at the top of the loop instead
// of at the bottom, otherwise a Range Check error can occur when
// trying to access the non-existant data...
if Len = 0 then begin
Break;
end;
while (Len and $C0) = $C0 do // {!!0.01} added loop for pointer
begin // that points to a pointer. Removed >63 hack. Am I really that stupid?
if SavedIdx < 0 then begin
SavedIdx := Succ(VPos); // it is important to return to original index spot
end;
// when we go down more than 1 level.
B := Len and $3F; // strip first two bits ($C) from first byte of offset pos
VPos := GStack.NetworkToHost(TwoByteToUInt16(B, DNSStr[VPos + 1]));// + 1; // add one to index for delphi string index //VV
Len := DNSStr[VPos]; // if len is another $Cx we will (while) loop again
end;
Assert(VPos < PackSize, GetErrorStr(2, 2)); // loop screwed up. This very very unlikely now could be removed.
LabelStr := BytesToString(DNSStr, VPos+1, Len);
Inc(VPos, 1+Len);
if Pred(VPos) > PackSize then begin // len byte was corrupted puting us past end of packet
raise EIdDnsResolverError.Create(GetErrorStr(2, 3));
end;
Result := Result + LabelStr + '.'; // concat and add period. {Do not Localize}
end;
if TextEndsWith(Result, '.') then begin // remove final period {Do not Localize}
SetLength(Result, Length(Result) - 1);
end;
if SavedIdx >= 0 then begin
VPos := SavedIdx; // restore original Idx +1
end;
Inc(VPos); // set to first char of next item in the resource
end;
function NextDNSLabel(const DNSStr: TIdBytes; var VPos: Integer): string;
var
LabelLength: Byte;
begin
if Length(DNSStr) > VPos then begin
LabelLength := DNSStr[VPos];
Inc(VPos);
//VV Shouldn't be pointers in Text messages
if LabelLength > 0 then begin
Result := BytesToString(DNSStr, VPos, LabelLength);
Inc(VPos, LabelLength);
Exit;
end;
end;
Result := ''; {Do not Localize}
end;
{ TARecord }
procedure TRDATARecord.Assign(Source: TPersistent);
begin
inherited Assign(Source);
if Source is TRDATARecord then begin
FIPAddress := TRDATARecord(Source).IPAddress;
end;
end;
procedure TRDATARecord.Parse(CompleteMessage: TIdBytes; APos: Integer);
begin
inherited Parse(CompleteMessage, APos);
if Length(RData) > 0 then begin
FIPAddress := MakeUInt32IntoIPv4Address(GStack.NetworkToHost(OrdFourByteToUInt32(RData[0], RData[1], RData[2], RData[3])));
end;
end;
{ TMXRecord }
procedure TMXRecord.Assign(Source: TPersistent);
var
LSource: TMXRecord;
begin
inherited Assign(Source);
if Source is TMXRecord then
begin
LSource := TMXRecord(Source);
FExchangeServer := LSource.ExchangeServer;
FPreference := LSource.Preference;
end;
end;
{ TCNAMERecord }
procedure TNAMERecord.Assign(Source: TPersistent);
begin
inherited Assign(Source);
if Source is TNAMERecord then begin
FHostName := TNAMERecord(Source).HostName;
end;
end;
{ TQueryResult }
function TQueryResult.Add(Answer: TIdBytes; var APos: Integer): TResultRecord;
var
RRName: String;
RR_type, RR_Class: UInt16;
RR_TTL: UInt32;
RD_Length: UInt16;
RData: TIdBytes;
begin
// extract the RR data
RRName := DNSStrToDomain(Answer, APos);
RR_Type := GStack.NetworkToHost( TwoByteToUInt16(Answer[APos], Answer[APos + 1]));
RR_Class := GStack.NetworkToHost(TwoByteToUInt16(Answer[APos + 2], Answer[APos + 3]));
RR_TTL := GStack.NetworkToHost(OrdFourByteToUInt32(Answer[APos + 4], Answer[APos + 5], Answer[APos + 6], Answer[APos + 7]));
RD_Length := GStack.NetworkToHost(TwoByteToUInt16(Answer[APos + 8], Answer[APos + 9]));
RData := Copy(Answer, APos + 10, RD_Length);
// remove what we have read from the buffer
// Read the record type
// Dennies Chang had modified this part to indicate type by RR_type
// because RR_type is integer, we can use TypeCode which is defined
// in IdDNSCommon to select all record type.
case RR_Type of
TypeCode_A ://qtA:
begin
Result := TARecord.Create(Self);
end;
TypeCode_NS : //qtNS:
begin
Result := TNSRecord.Create(Self);
end;
TypeCode_MX ://qtMX:
begin
Result := TMXRecord.Create(Self);
end;
TypeCode_CName : // qtName:
begin
Result := TNAMERecord.Create(Self);
end;
TypeCode_SOA : //qtSOA:
begin
Result := TSOARecord.Create(Self);
end;
TypeCode_HINFO : //qtHINFO:
begin
Result := THINFORecord.Create(Self);
end;
TypeCode_TXT ://qtTXT:
begin
Result := TTextRecord.Create(Self);
end;
TypeCode_WKS ://qtWKS:
begin
Result := TWKSRecord.Create(Self);
end;
TypeCode_PTR :// qtPTR:
begin
Result := TPTRRecord.Create(Self);
end;
TypeCode_MINFO ://qtMINFO:
begin
Result := TMINFORecord.Create(Self);
end;
TypeCode_AAAA : //qtAAAA:
begin
Result := TAAAARecord.Create(Self);
end;
TypeCode_Service : //qtService
begin
Result := TSRVRecord.Create(Self);
end;
TypeCode_NAPTR : //qtNAPTR
begin
Result := TNAPTRRecord.Create(Self);
end;
else begin
// Unsupported query type, return generic record
Result := TResultRecord.Create(Self);
end;
end; // case
// Set the "general purprose" options
if Assigned(Result) then
begin
//if RR_Type <= High(QueryRecordTypes) then
// modified in 2004 7/15.
case RR_Type of
TypeCode_A: Result.FRecType := qtA;
TypeCode_NS: Result.FRecType := qtNS;
TypeCode_MD: Result.FRecType := qtMD;
TypeCode_MF: Result.FRecType := qtMF;
TypeCode_CName: Result.FRecType := qtName;
TypeCode_SOA: Result.FRecType := qtSOA;
TypeCode_MB: Result.FRecType := qtMB;
TypeCode_MG: Result.FRecType := qtMG;
TypeCode_MR: Result.FRecType := qtMR;
TypeCode_NULL: Result.FRecType := qtNull;
TypeCode_WKS: Result.FRecType := qtWKS;
TypeCode_PTR: Result.FRecType := qtPTR;
TypeCode_HINFO: Result.FRecType := qtHINFO;
TypeCode_MINFO: Result.FRecType := qtMINFO;
TypeCode_MX: Result.FRecType := qtMX;
TypeCode_TXT: Result.FRecType := qtTXT;
//TypeCode_NSAP: Result.FRecType := QueryRecordTypes[Ord(RR_Type) - 1];
//TypeCode_NSAP_PTR: Result.FRecType := QueryRecordTypes[Ord(RR_Type) - 1];
TypeCode_AAAA: Result.FRecType := qtAAAA;
//TypeCode_LOC: Result.FRecType := QueryRecordTypes[Ord(RR_Type) - 1];
TypeCode_Service:Result.FRecType := qtService;
TypeCode_NAPTR: Result.FRecType := qtNAPTR;
TypeCode_AXFR: Result.FRecType := qtAXFR;
//TypeCode_STAR: Result.FRecType := qtSTAR;
end;
result.FRecClass := RR_Class;
result.FName := RRName;
result.FTTL := RR_TTL;
Result.FRData := Copy(RData, 0{1}, RD_Length);
Result.FRDataLength := RD_Length;
// Parse the result
// Since the DNS message can be compressed, we need to have the whole message to parse it, in case
// we encounter a pointer
//Result.Parse(Copy(Answer, 0{1}, APos + 9 + RD_Length), APos + 10);
Result.Parse(Answer, APos + 10);
end;
// Set the new position
inc(APos, RD_Length + 10);
end;
constructor TQueryResult.Create;
begin
inherited Create(TResultRecord);
FQueryPointerList := TStringList.Create;
end;
destructor TQueryResult.Destroy;
begin
FreeAndNil(FQueryPointerList);
inherited Destroy;
end;
function TQueryResult.GetItem(Index: Integer): TResultRecord;
begin
Result := TResultRecord(inherited GetItem(Index));
end;
procedure TQueryResult.SetItem(Index: Integer; Value: TResultRecord);
begin
inherited SetItem(Index, Value);
end;
{ TResultRecord }
procedure TResultRecord.Assign(Source: TPersistent);
var
LSource: TResultRecord;
begin
if Source is TResultRecord then
begin
LSource := TResultRecord(Source);
FRecType := LSource.RecType;
FRecClass := LSource.RecClass;
FName := LSource.Name;
FTTL := LSource.TTL;
FRDataLength := LSource.RDataLength;
FRData := Copy(LSource.RData, 0, Length(LSource.RData));
FSection := LSource.Section;
end else begin
inherited Assign(Source);
end;
end;
procedure TResultRecord.Parse;
begin
end;
{ TNAMERecord }
procedure TNAMERecord.Parse(CompleteMessage: TIdBytes; APos: Integer);
begin
inherited Parse(CompleteMessage, APos);
FHostName := DNSStrToDomain(CompleteMessage, APos);
end;
{ TQueryResult }
procedure TQueryResult.Clear;
begin
inherited Clear;
FQueryPointerList.Clear;
end;
procedure TQueryResult.Assign(Source: TPersistent);
//TCollection.Assign doesn't create correct Item class.
var
i: Integer;
LRec: TResultRecord;
LNew: TResultRecord;
begin
if Source is TQueryResult then
begin
BeginUpdate;
try
Clear;
for i := 0 to TQueryResult(Source).Count-1 do
begin
LRec := TQueryResult(Source).Items[i];
LNew := TResultRecordClass(LRec.ClassType).Create(Self);
try
LNew.Assign(LRec);
except
FreeAndNil(LNew);
raise;
end;
end;
finally
EndUpdate;
end;
end else begin
inherited Assign(Source);
end;
end;
{ TMXRecord }
procedure TMXRecord.Parse(CompleteMessage: TIdBytes; APos: Integer);
begin
inherited Parse(CompleteMessage, APos);
FPreference := GStack.NetworkToHost(TwoByteToUInt16(CompleteMessage[APos], CompleteMessage[APos + 1]));
Inc(APos, 2);
FExchangeServer := DNSStrToDomain(CompleteMessage, APos);
end;
{ TTextRecord }
procedure TTextRecord.Assign(Source: TPersistent);
begin
inherited Assign(Source);
if Source is TTextRecord then begin
FText.Assign(TTextRecord(Source).Text);
end;
end;
constructor TTextRecord.Create(Collection: TCollection);
begin
inherited Create(Collection);
FText := TStringList.Create;
end;
destructor TTextRecord.Destroy;
begin
FreeAndNil(FText);
inherited Destroy;
end;
//the support for long text values is required for DomainKeys,
//which has an encoded public key
procedure TTextRecord.Parse(CompleteMessage: TIdBytes; APos: Integer);
var
LStart: Integer;
Buffer: string;
begin
FText.Clear;
LStart := APos;
while APos < (LStart+RDataLength) do
begin
Buffer := NextDNSLabel(CompleteMessage, APos);
if Buffer <> '' then begin {Do not Localize}
FText.Add(Buffer);
end;
end;
inherited Parse(CompleteMessage, APos);
end;
{ TSOARecord }
procedure TSOARecord.Assign(Source: TPersistent);
var
LSource: TSOARecord;
begin
inherited Assign(Source);
if Source is TSOARecord then begin
LSource := TSOARecord(Source);
FSerial := LSource.Serial;
FMinimumTTL := LSource.MinimumTTL;
FRefresh := LSource.Refresh;
FRetry := LSource.Retry;
FMNAME := LSource.FMNAME;
FRNAME := LSource.FRNAME;
FExpire := LSource.Expire;
end;
end;
procedure TSOARecord.Parse(CompleteMessage: TIdBytes; APos: Integer);
begin
inherited Parse(CompleteMessage, APos);
FMNAME := DNSStrToDomain(CompleteMessage, APos);
FRNAME := DNSStrToDomain(CompleteMessage, APos);
FSerial := GStack.NetworkToHost(OrdFourByteToUInt32(CompleteMessage[APos], CompleteMessage[APos + 1], CompleteMessage[APos + 2], CompleteMessage[APos + 3]));
Inc(APos, 4);
FRefresh := GStack.NetworkToHost( OrdFourByteToUInt32(CompleteMessage[APos], CompleteMessage[APos + 1], CompleteMessage[APos + 2], CompleteMessage[APos + 3]));
Inc(APos, 4);
FRetry := GStack.NetworkToHost( OrdFourByteToUInt32(CompleteMessage[APos], CompleteMessage[APos + 1], CompleteMessage[APos + 2], CompleteMessage[APos + 3]));
Inc(APos, 4);
FExpire := GStack.NetworkToHost( OrdFourByteToUInt32(CompleteMessage[APos], CompleteMessage[APos + 1], CompleteMessage[APos + 2], CompleteMessage[APos + 3]));
Inc(APos, 4);
FMinimumTTL := GStack.NetworkToHost( OrdFourByteToUInt32(CompleteMessage[APos], CompleteMessage[APos + 1], CompleteMessage[APos + 2], CompleteMessage[APos + 3]));
end;
{ TWKSRecord }
procedure TWKSRecord.Assign(Source: TPersistent);
var
LSource: TWKSRecord;
begin
inherited Assign(Source);
if Source is TWKSRecord then begin
LSource := TWKSRecord(Source);
FIPAddress := LSource.IPAddress;
FProtocol := LSource.Protocol;
FByteCount := LSource.ByteCount;
FData := Copy(LSource.FData, 0, Length(LSource.FData));
end;
end;
function TWKSRecord.GetABit(AIndex: Integer): UInt8;
begin
Result := FData[AIndex];
end;
procedure TWKSRecord.Parse(CompleteMessage: TIdBytes; APos: Integer);
begin
inherited Parse(CompleteMessage, APos);
FIPAddress := MakeUInt32IntoIPv4Address(GStack.NetworkToHost(OrdFourByteToUInt32(RData[0], RData[1], RData[2], RData[3])));
FProtocol := UInt16(RData[4]);
FData := ToBytes(RData, Length(RData)-5, 5);
end;
{ TMINFORecord }
procedure TMINFORecord.Assign(Source: TPersistent);
var
LSource: TMINFORecord;
begin
inherited Assign(Source);
if Source is TMINFORecord then
begin
LSource := TMINFORecord(Source);
FResponsiblePerson := LSource.ResponsiblePersonMailbox;
FErrorMailbox := LSource.ErrorMailbox;
end;
end;
procedure TMINFORecord.Parse(CompleteMessage: TIdBytes; APos: Integer);
begin
inherited Parse(CompleteMessage, APos);
FResponsiblePerson := DNSStrToDomain(CompleteMessage, APos);
FErrorMailbox := DNSStrToDomain(CompleteMessage, APos);
end;
{ THINFORecord }
procedure THINFORecord.Assign(Source: TPersistent);
var
LSource: THINFORecord;
begin
inherited Assign(Source);
if Source is THINFORecord then
begin
LSource := THINFORecord(Source);
FCPU := LSource.CPU;
FOS := LSource.OS;
end;
end;
procedure THINFORecord.Parse(CompleteMessage: TIdBytes; APos: Integer);
begin
inherited Parse(CompleteMessage, APos);
FCPU := NextDNSLabel(CompleteMessage, APos);
FOS := NextDNSLabel(CompleteMessage, APos);
end;
{ TAAAARecord }
procedure TAAAARecord.Assign(Source: TPersistent);
begin
inherited Assign(Source);
if Source is TAAAARecord then begin
FAddress := TAAAARecord(Source).Address;
end;
end;
procedure TAAAARecord.Parse(CompleteMessage: TIdBytes; APos: Integer);
var
FIP6 : TIdIPv6Address;
i : Integer;
begin
inherited Parse(CompleteMessage, APos);
if Length(RData) >= 15 then begin
BytesToIPv6(RData, FIP6);
for i := 0 to 7 do begin
FIP6[i] := GStack.NetworkToHost(FIP6[i]);
end;
FAddress := IPv6AddressToStr(FIP6);
end;
end;
{ TIdDNSResolver }
procedure TIdDNSResolver.ClearInternalQuery;
begin
SetLength(FInternalQuery, 0);
FQuestionLength := 0;
end;
procedure TIdDNSResolver.CreateQuery(ADomain: string; SOARR : TIdRR_SOA;
QueryClass:integer=1);
function DoDomainName(ADNS : String): TIdBytes;
var
BufStr : String;
LLen : Byte;
begin
SetLength(Result, 0);
while Length(ADNS) > 0 do begin
BufStr := Fetch(ADNS, '.'); {Do not Localize}
LLen := Length(BufStr);
AppendByte(Result, LLen);
AppendString(Result, BufStr, LLen);
end;
end;
function DoHostAddressV6(const ADNS: String): TIdBytes;
var
IPV6Str, IPV6Ptr: string;
i: Integer;
begin
if not IsValidIPv6(ADNS) then begin
raise EIdDnsResolverError.CreateFmt(RSQueryInvalidIpV6, [aDNS]);
end;
IPV6Str := ConvertToCanonical6IP(ADNS);
IPV6Ptr := ''; {Do not Localize}
for i := Length(IPV6Str) downto 1 do begin
if IPV6Str[i] <> ':' then begin {Do not Localize}
IPV6Ptr := IPV6Ptr + IPV6Str[i] + '.'; {Do not Localize}
end;
end;
IPV6Ptr := IPV6Ptr + 'IP6.INT'; {Do not Localize}
Result := DoDomainName(IPV6Ptr);
end;
function DoHostAddress(const ADNS: String): TIdBytes;
var
BufStr, First, Second, Third, Fourth: String;
LLen: Byte;
begin { DoHostAddress }
if Pos(':', ADNS) > 0 then begin {Do not Localize}
Result := DoHostAddressV6(ADNS);
end else begin
SetLength(Result, 0);
BufStr := ADNS;
First := Fetch(BufStr, '.');
Second := Fetch(BufStr, '.');
Third := Fetch(BufStr, '.');
Fourth := BufStr;
LLen := Length(Fourth);
AppendByte(Result, LLen);
AppendString(Result, Fourth, LLen);
LLen := Length(Third);
AppendByte(Result, LLen);
AppendString(Result, Third, LLen);
LLen := Length(Second);
AppendByte(Result, LLen);
AppendString(Result, Second, LLen);
LLen := Length(First);
AppendByte(Result, LLen);
AppendString(Result, First, LLen);
AppendByte(Result, 7);
AppendString(Result, 'in-addr', 7); {do not localize}
AppendByte(Result, 4);
AppendString(Result, 'arpa', 4); {do not localize}
end;
end;
var
ARecType: TQueryRecordTypes;
iQ: Integer;
AQuestion, AAuthority: TIdBytes;
TempBytes: TIdBytes;
w : UInt16;
begin
SetLength(TempBytes, 2);
SetLength(AAuthority, 0);
FDNSHeader.ID := Random(65535);
FDNSHeader.ClearByteCode;
FDNSHeader.Qr := 0;
FDNSHeader.OpCode := 0;
FDNSHeader.ANCount := 0;
FDNSHeader.NSCount := 0;
FDNSHeader.ARCount := 0;
//do not reverse the bytes because this is a bit set
FDNSHeader.RD := UInt16(FAllowRecursiveQueries);
// Iterate thru questions
{ TODO : Optimize for non-double loop }
if (QueryType * [qtAXFR, qtIXFR]) <> [] then
begin
iQ := 1; // if exec AXFR, there can be only one Question.
if qtIXFR in QueryType then begin
// if exec IXFR, we must include a SOA record in Authority Section (RFC 1995)
if not Assigned(SOARR) then begin
raise EIdDnsResolverError.Create(GetErrorStr(7, 3));
end;
AAuthority := SOARR.BinQueryRecord('');
FDNSHeader.AA := 1;
end;
end else
begin
iQ := 0;
for ARecType := Low(TQueryRecordTypes) to High(TQueryRecordTypes) do begin
if ARecType in QueryType then begin
Inc(iQ);
end;
end;
end;
FDNSHeader.QDCount := iQ;
if FDNSHeader.QDCount = 0 then begin
ClearInternalQuery;
Exit;
end;
InternalQuery := FDNSHeader.GenerateBinaryHeader;
if qtAXFR in QueryType then begin
if (IndyPos('IN-ADDR', UpperCase(ADomain)) > 0) or {Do not Localize}
(IndyPos('IP6.INT', UpperCase(ADomain)) > 0) then {do not localize}
begin
AppendBytes(AQuestion, DoHostAddress(ADomain));
AppendByte(AQuestion, 0);
end else
begin
AppendBytes(AQuestion, DoDomainName(ADomain));
AppendByte(AQuestion, 0);
end;
//we do this in a round about manner because HostToNetwork will not always
//work the same
w := 252;
w := GStack.HostToNetwork(w);
UInt16ToTwoBytes(w, TempBytes, 0);
AppendBytes(AQuestion, TempBytes); // Type = AXFR
w := QueryClass;
w := GStack.HostToNetwork(w);
UInt16ToTwoBytes(w, TempBytes, 0);
AppendBytes(AQuestion, TempBytes);
end
else if qtIXFR in QueryType then begin
if (IndyPos('IN-ADDR', UpperCase(ADomain)) > 0) or {Do not Localize}
(IndyPos('IP6.INT', UpperCase(ADomain)) > 0) then {do not localize}
begin
AppendBytes(AQuestion, DoHostAddress(ADomain));
AppendByte(AQuestion, 0);
end else
begin
AppendBytes(AQuestion, DoDomainName(ADomain));
AppendByte(AQuestion, 0);
end;
//we do this in a round about manner because HostToNetwork will not always
//work the same
w := 251;
w := GStack.HostToNetwork(w);
UInt16ToTwoBytes(w, TempBytes, 0);
AppendBytes(AQuestion, TempBytes); // Type = IXFR
w := QueryClass;
w := GStack.HostToNetwork(w);
UInt16ToTwoBytes(w, TempBytes, 0);
AppendBytes(AQuestion, TempBytes);
end else
begin
for ARecType := Low(TQueryRecordTypes) to High(TQueryRecordTypes) do begin
if ARecType in QueryType then begin
// Create the question
if (ARecType = qtPTR) and
(IndyPos('IN-ADDR', UpperCase(ADomain)) = 0) and {Do not Localize}
(IndyPos('IP6.INT', UpperCase(ADomain)) = 0) then {do not localize}
begin
AppendBytes(AQuestion, DoHostAddress(ADomain));
end else begin
AppendBytes(AQuestion, DoDomainName(ADomain));
end;
AppendByte(AQuestion, 0);
w := QueryRecordValues[Ord(ARecType)];
w := GStack.HostToNetwork(w);
UInt16ToTwoBytes(w, TempBytes, 0);
AppendBytes(AQuestion, TempBytes);
w := QueryClass;
w := GStack.HostToNetwork(w);
UInt16ToTwoBytes(w, TempBytes, 0);
AppendBytes(AQuestion, TempBytes);
end;
end;
end;
AppendBytes(FInternalQuery, AQuestion);
FQuestionLength := Length(FInternalQuery);
FDNSHeader.ParseQuery(FInternalQuery);
end;
destructor TIdDNSResolver.Destroy;
begin
FreeAndNil(FQueryResult);
FreeAndNil(FDNSHeader);
inherited Destroy;
end;
procedure TIdDNSResolver.FillResult(AResult: TIdBytes; CheckID: Boolean = True;
ResetResult: Boolean = True);
var
ReplyId: UInt16;
NAnswers: UInt16;
begin
{ TODO : Check bytes received }
// Check to see if the reply is the one waited for
if Length(AResult) < 12 then begin
raise EIdDnsResolverError.Create(GetErrorStr(5, 29));
end;
{
if Length(AResult) < Self.FQuestionLength then begin
raise EIdDnsResolverError.Create(GetErrorStr(5, 30));
end;
}
if CheckID then begin
ReplyId := GStack.NetworkToHost(TwoByteToUInt16(AResult[0], AResult[1]));
if ReplyId <> FDNSHeader.Id then begin
raise EIdDnsResolverError.Create(GetErrorStr(4, FDNSHeader.id));
end;
end;
FDNSHeader.ParseQuery(AResult);
if FDNSHeader.RCode <> 0 then begin
raise EIdDnsResolverError.Create(GetRCodeStr(FDNSHeader.RCode));
end;
NAnswers := FDNSHeader.ANCount + FDNSHeader.NSCount + FDNSHeader.ARCount;
if NAnswers > 0 then begin
// Move Pointer to Start of answers
if Length(AResult) > 12 then begin
ParseAnswers(FDNSHeader, AResult, ResetResult);
end;
end;
end;
{$I IdDeprecatedImplBugOff.inc}
procedure TIdDNSResolver.FillResultWithOutCheckId(AResult: TIdBytes);
{$I IdDeprecatedImplBugOn.inc}
var
NAnswers: UInt16;
begin
if FDNSHeader.ParseQuery(AResult) <> 0 then begin
raise EIdDnsResolverError.Create(GetErrorStr(5, 29));
end;
{
if FDNSHeader.RCode <> 0 then begin
raise EIdDnsResolverError.Create(GetRCodeStr(FDNSHeader.RCode));
end;
}
NAnswers := FDNSHeader.ANCount + FDNSHeader.NSCount + FDNSHeader.ARCount;
if NAnswers > 0 then begin
// Move Pointer to Start of answers
if Length(AResult) > 12 then begin
ParseAnswers(FDNSHeader, AResult);
end;
end;
end;
procedure TQueryResult.FilterBySection(const AKeep: TResultSections);
var
i: Integer;
begin
for i := Count-1 downto 0 do
begin
if not (Items[i].Section in AKeep) then begin
Delete(i);
end;
end;
end;
procedure TQueryResult.FilterByClass(const AKeep: TResultRecordClass);
var
i: Integer;
begin
for i := Count-1 downto 0 do
begin
if not (Items[i] is AKeep) then begin
Delete(i);
end;
end;
end;
procedure TIdDNSResolver.InitComponent;
begin
inherited InitComponent;
Port := IdPORT_DOMAIN;
FQueryResult := TQueryResult.Create;
FDNSHeader := TDNSHeader.Create;
FAllowRecursiveQueries := true;
Self.WaitingTime := 5000;
end;
procedure TIdDNSResolver.ParseAnswers(DNSHeader: TDNSHeader; Answer: TIdBytes;
ResetResult: Boolean = True);
var
i: integer;
APos: Integer;
begin
if ResetResult then begin
QueryResult.Clear;
end;
APos := 12; //13; // Header is 12 byte long we need next byte
// if QDCount = 1, we need to process Question first.
if DNSHeader.QDCount = 1 then
begin
// first, get the question
// extract the domain name
QueryResult.FDomainName := DNSStrToDomain(Answer, APos);
// get the query type
QueryResult.FQueryType := TwoByteToUInt16(Answer[APos], Answer[APos + 1]);
Inc(APos, 2);
// get the Query Class
QueryResult.FQueryClass := TwoByteToUInt16(Answer[APos], Answer[APos + 1]);
Inc(APos, 2);
end;
for i := 1 to DNSHeader.ANCount do begin
QueryResult.Add(Answer, APos).FSection := rsAnswer;
end;
for i := 1 to DNSHeader.NSCount do begin
QueryResult.Add(Answer, APos).FSection := rsNameServer;
end;
for i := 1 to DNSHeader.ARCount do begin
QueryResult.Add(Answer, APos).FSection := rsAdditional;
end;
end;
procedure TIdDNSResolver.Resolve(ADomain: string; SOARR : TIdRR_SOA = nil;
QClass: integer = Class_IN);
var
UDP_Tunnel : TIdUDPClient;
TCP_Tunnel : TIdTCPClient;
LRet: Integer;
LResult: TIdBytes;
BytesReceived: Integer;
begin
if ADomain <> '' then begin
ClearInternalQuery;
end;
// Resolve queries the DNS for the records contained in the
if FQuestionLength = 0 then begin
if qtIXFR in QueryType then begin
CreateQuery(ADomain, SOARR, QClass);
end else begin
CreateQuery(ADomain, nil, QClass)
end;
end;
if FQuestionLength = 0 then begin
raise EIdDnsResolverError.CreateFmt(RSQueryInvalidQueryCount, [0]);
end;
if qtAXFR in QueryType then begin
// AXFR
TCP_Tunnel := TIdTCPClient.Create;
try
TCP_Tunnel.Host := Host;
TCP_Tunnel.Port := Port;
TCP_Tunnel.IPVersion := IPVersion;
TCP_Tunnel.IOHandler := IOHandler;
try
TCP_Tunnel.Connect;
try
TCP_Tunnel.IOHandler.Write(Int16(FQuestionLength));
TCP_Tunnel.IOHandler.Write(InternalQuery);
QueryResult.Clear;
LRet := TCP_Tunnel.IOHandler.ReadInt16;
SetLength(LResult, LRet);
TCP_Tunnel.IOHandler.ReadBytes(LResult, LRet);
PlainTextResult := LResult;
if LRet > 4 then begin
FillResult(LResult, False, False);
if QueryResult.Count = 0 then begin
raise EIdDnsResolverError.Create(GetErrorStr(2,3));
end;
end else begin
raise EIdDnsResolverError.Create(RSDNSTimeout);
end;
finally
TCP_Tunnel.Disconnect;
end;
except
on EIdConnectTimeout do begin
SetLength(FPlainTextResult, 0);
IndyRaiseOuterException(EIdDNSResolverError.Create(RSDNSTimeout));
end;
on EIdConnectException do begin
SetLength(FPlainTextResult, 0);
IndyRaiseOuterException(EIdDNSResolverError.Create(RSTunnelConnectToMasterFailed));
end;
end;
finally
FreeAndNil(TCP_Tunnel);
end;
end
else if qtIXFR in QueryType then begin
// IXFR
TCP_Tunnel := TIdTCPClient.Create;
try
TCP_Tunnel.Host := Host;
TCP_Tunnel.Port := Port;
TCP_Tunnel.IPVersion := IPVersion;
TCP_Tunnel.IOHandler := IOHandler;
{ Thanks RLebeau, you fix a lot of codes which I do not spend time to do - Dennies Chang. }
try
TCP_Tunnel.Connect;
try
TCP_Tunnel.IOHandler.Write(Int16(FQuestionLength));
TCP_Tunnel.IOHandler.Write(InternalQuery);
QueryResult.Clear;
LRet := TCP_Tunnel.IOHandler.ReadInt16;
SetLength(LResult, LRet);
TCP_Tunnel.IOHandler.ReadBytes(LResult, LRet);
PlainTextResult := LResult;
if LRet > 4 then begin
FillResult(LResult, False, False);
if QueryResult.Count = 0 then begin
raise EIdDnsResolverError.Create(GetErrorStr(2,3));
end;
end else begin
raise EIdDnsResolverError.Create(RSDNSTimeout);
end;
finally
TCP_Tunnel.Disconnect;
end;
except
on EIdConnectTimeout do begin
SetLength(FPlainTextResult, 0);
IndyRaiseOuterException(EIdDNSResolverError.Create(RSDNSTimeout));
end;
on EIdConnectException do begin
SetLength(FPlainTextResult, 0);
IndyRaiseOuterException(EIdDNSResolverError.Create(RSTunnelConnectToMasterFailed));
end;
end;
finally
FreeAndNil(TCP_Tunnel);
end;
end
else begin
UDP_Tunnel := TIdUDPClient.Create;
try
UDP_Tunnel.Host := Host;
UDP_Tunnel.Port := Port;
UDP_Tunnel.IPVersion := IPVersion;
UDP_Tunnel.SendBuffer(InternalQuery);
SetLength(LResult, 8192);
BytesReceived := UDP_Tunnel.ReceiveBuffer(LResult, WaitingTime);
SetLength(LResult, BytesReceived);
if Length(LResult) > 0 then begin
PlainTextResult := LResult;
end else begin
SetLength(FPlainTextResult, 0);
end;
finally
FreeAndNil(UDP_Tunnel);
end;
if Length(LResult) > 4 then begin
FillResult(LResult);
if QueryResult.Count = 0 then begin
raise EIdDnsResolverError.Create(GetErrorStr(2,3));
end;
end else begin
raise EIdDnsResolverError.Create(RSDNSTimeout);
end;
end;
end;
procedure TIdDNSResolver.SetInternalQuery(const Value: TIdBytes);
begin
FQuestionLength := Length(Value);
FInternalQuery := Copy(Value, 0, FQuestionLength);
Self.FDNSHeader.ParseQuery(Value);
end;
procedure TIdDNSResolver.SetIPVersion(const AValue: TIdIPVersion);
begin
FIPVersion := AValue;
end;
procedure TIdDNSResolver.SetPlainTextResult(const Value: TIdBytes);
begin
FPlainTextResult := Copy(Value, 0, Length(Value));
end;
procedure TIdDNSResolver.SetPort(const AValue: TIdPort);
begin
FPort := AValue;
end;
procedure TSRVRecord.Assign(Source: TPersistent);
var
LSource: TSRVRecord;
begin
inherited Assign(Source);
if Source is TSRVRecord then
begin
LSource := TSRVRecord(Source);
FService := LSource.Service;
FProtocol := LSource.Protocol;
FPriority := LSource.Priority;
FWeight := LSource.Weight;
FPort := LSource.Port;
FTarget := LSource.Target;
end;
end;
function TSRVRecord.CleanIdent(const aStr: string): string;
begin
Result := Copy(aStr, 2, MaxInt);
end;
function TSRVRecord.IsValidIdent(const AStr: string): Boolean;
begin
Result := (Length(AStr) > 1) and TextStartsWith(AStr, '_'); {Do not Localize}
end;
procedure TSRVRecord.Parse(CompleteMessage: TIdBytes; APos: Integer);
var
LName, LService, LProtocol: string;
begin
inherited Parse(CompleteMessage, APos);
FOriginalName := FName;
//this is to split: _sip._udp.example.com
LName := FName;
LService := Fetch(LName, '.', True, False);
LProtocol := Fetch(LName,'.', True, False);
if IsValidIdent(LService) and IsValidIdent(LProtocol) and (LName <> '') then
begin
FService := CleanIdent(LService);
FProtocol := CleanIdent(LProtocol);
FName := LName;
end;
FPriority := GStack.NetworkToHost(TwoByteToUInt16(CompleteMessage[APos], CompleteMessage[APos+1]));
Inc(APos, 2);
FWeight := GStack.NetworkToHost(TwoByteToUInt16(CompleteMessage[APos], CompleteMessage[APos+1]));
Inc(APos, 2);
FPort := GStack.NetworkToHost(TwoByteToUInt16(CompleteMessage[APos], CompleteMessage[APos+1]));
Inc(APos, 2);
FTarget := DNSStrToDomain(CompleteMessage, APos);
end;
procedure TNAPTRRecord.Assign(Source: TPersistent);
var
LSource: TNAPTRRecord;
begin
inherited Assign(Source);
if Source is TNAPTRRecord then
begin
LSource := TNAPTRRecord(Source);
FOrder := LSource.Order;
FPreference := LSource.Preference;
FFlags := LSource.FFlags;
FService := LSource.Service;
FRegExp := LSource.RegExp;
FReplacement := LSource.Replacement;
end;
end;
procedure TNAPTRRecord.Parse(CompleteMessage: TIdBytes; APos: Integer);
begin
inherited Parse(CompleteMessage, APos);
FOrder := GStack.NetworkToHost(TwoByteToUInt16(CompleteMessage[APos], CompleteMessage[APos+1]));
Inc(APos, 2);
FPreference := GStack.NetworkToHost(TwoByteToUInt16(CompleteMessage[APos], CompleteMessage[APos+1]));
Inc(APos, 2);
FFlags := NextDNSLabel(CompleteMessage, APos);
FService := NextDNSLabel(CompleteMessage, APos);
FRegExp := NextDNSLabel(CompleteMessage, APos);
FReplacement := DNSStrToDomain(CompleteMessage, APos);
end;
end.