{ $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.