dnssend.pas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. {==============================================================================|
  2. | Project : Ararat Synapse | 002.007.004 |
  3. |==============================================================================|
  4. | Content: DNS client |
  5. |==============================================================================|
  6. | Copyright (c)1999-2007, Lukas Gebauer |
  7. | All rights reserved. |
  8. | |
  9. | Redistribution and use in source and binary forms, with or without |
  10. | modification, are permitted provided that the following conditions are met: |
  11. | |
  12. | Redistributions of source code must retain the above copyright notice, this |
  13. | list of conditions and the following disclaimer. |
  14. | |
  15. | Redistributions in binary form must reproduce the above copyright notice, |
  16. | this list of conditions and the following disclaimer in the documentation |
  17. | and/or other materials provided with the distribution. |
  18. | |
  19. | Neither the name of Lukas Gebauer nor the names of its contributors may |
  20. | be used to endorse or promote products derived from this software without |
  21. | specific prior written permission. |
  22. | |
  23. | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
  24. | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
  25. | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
  26. | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR |
  27. | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
  28. | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
  29. | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
  30. | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
  31. | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
  32. | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
  33. | DAMAGE. |
  34. |==============================================================================|
  35. | The Initial Developer of the Original Code is Lukas Gebauer (Czech Republic).|
  36. | Portions created by Lukas Gebauer are Copyright (c)2000-2007. |
  37. | All Rights Reserved. |
  38. |==============================================================================|
  39. | Contributor(s): |
  40. |==============================================================================|
  41. | History: see HISTORY.HTM from distribution package |
  42. | (Found at URL: http://www.ararat.cz/synapse/) |
  43. |==============================================================================}
  44. {: @abstract(DNS client by UDP or TCP)
  45. Support for sending DNS queries by UDP or TCP protocol. It can retrieve zone
  46. transfers too!
  47. Used RFC: RFC-1035, RFC-1183, RFC1706, RFC1712, RFC2163, RFC2230
  48. }
  49. {$IFDEF FPC}
  50. {$MODE DELPHI}
  51. {$ENDIF}
  52. {$Q-}
  53. {$H+}
  54. unit dnssend;
  55. interface
  56. uses
  57. SysUtils, Classes,
  58. blcksock, synautil, synaip, synsock;
  59. const
  60. cDnsProtocol = '53';
  61. QTYPE_A = 1;
  62. QTYPE_NS = 2;
  63. QTYPE_MD = 3;
  64. QTYPE_MF = 4;
  65. QTYPE_CNAME = 5;
  66. QTYPE_SOA = 6;
  67. QTYPE_MB = 7;
  68. QTYPE_MG = 8;
  69. QTYPE_MR = 9;
  70. QTYPE_NULL = 10;
  71. QTYPE_WKS = 11; //
  72. QTYPE_PTR = 12;
  73. QTYPE_HINFO = 13;
  74. QTYPE_MINFO = 14;
  75. QTYPE_MX = 15;
  76. QTYPE_TXT = 16;
  77. QTYPE_RP = 17;
  78. QTYPE_AFSDB = 18;
  79. QTYPE_X25 = 19;
  80. QTYPE_ISDN = 20;
  81. QTYPE_RT = 21;
  82. QTYPE_NSAP = 22;
  83. QTYPE_NSAPPTR = 23;
  84. QTYPE_SIG = 24; // RFC-2065
  85. QTYPE_KEY = 25; // RFC-2065
  86. QTYPE_PX = 26;
  87. QTYPE_GPOS = 27;
  88. QTYPE_AAAA = 28;
  89. QTYPE_LOC = 29; // RFC-1876
  90. QTYPE_NXT = 30; // RFC-2065
  91. QTYPE_SRV = 33;
  92. QTYPE_NAPTR = 35; // RFC-2168
  93. QTYPE_KX = 36;
  94. QTYPE_SPF = 99;
  95. QTYPE_AXFR = 252;
  96. QTYPE_MAILB = 253; //
  97. QTYPE_MAILA = 254; //
  98. QTYPE_ALL = 255;
  99. type
  100. {:@abstract(Implementation of DNS protocol by UDP or TCP protocol.)
  101. Note: Are you missing properties for specify server address and port? Look to
  102. parent @link(TSynaClient) too!}
  103. TDNSSend = class(TSynaClient)
  104. private
  105. FID: Word;
  106. FRCode: Integer;
  107. FBuffer: AnsiString;
  108. FSock: TUDPBlockSocket;
  109. FTCPSock: TTCPBlockSocket;
  110. FUseTCP: Boolean;
  111. FAnswerInfo: TStringList;
  112. FNameserverInfo: TStringList;
  113. FAdditionalInfo: TStringList;
  114. FAuthoritative: Boolean;
  115. FTruncated: Boolean;
  116. function CompressName(const Value: AnsiString): AnsiString;
  117. function CodeHeader: AnsiString;
  118. function CodeQuery(const Name: AnsiString; QType: Integer): AnsiString;
  119. function DecodeLabels(var From: Integer): AnsiString;
  120. function DecodeString(var From: Integer): AnsiString;
  121. function DecodeResource(var i: Integer; const Info: TStringList;
  122. QType: Integer): AnsiString;
  123. function RecvTCPResponse(const WorkSock: TBlockSocket): AnsiString;
  124. function DecodeResponse(const Buf: AnsiString; const Reply: TStrings;
  125. QType: Integer):boolean;
  126. public
  127. constructor Create;
  128. destructor Destroy; override;
  129. {:Query a DNSHost for QType resources correspond to a name. Supported QType
  130. values are: Qtype_A, Qtype_NS, Qtype_MD, Qtype_MF, Qtype_CNAME, Qtype_SOA,
  131. Qtype_MB, Qtype_MG, Qtype_MR, Qtype_NULL, Qtype_PTR, Qtype_HINFO,
  132. Qtype_MINFO, Qtype_MX, Qtype_TXT, Qtype_RP, Qtype_AFSDB, Qtype_X25,
  133. Qtype_ISDN, Qtype_RT, Qtype_NSAP, Qtype_NSAPPTR, Qtype_PX, Qtype_GPOS,
  134. Qtype_KX.
  135. Type for zone transfers QTYPE_AXFR is supported too, but only in TCP mode!
  136. "Name" is domain name or host name for queried resource. If "name" is
  137. IP address, automatically convert to reverse domain form (.in-addr.arpa).
  138. If result is @true, Reply contains resource records. One record on one line.
  139. If Resource record have multiple fields, they are stored on line divided by
  140. comma. (example: MX record contains value 'rs.cesnet.cz' with preference
  141. number 10, string in Reply is: '10,rs.cesnet.cz'). All numbers or IP address
  142. in resource are converted to string form.}
  143. function DNSQuery(Name: AnsiString; QType: Integer;
  144. const Reply: TStrings): Boolean;
  145. published
  146. {:Socket object used for UDP operation. Good for seting OnStatus hook, etc.}
  147. property Sock: TUDPBlockSocket read FSock;
  148. {:Socket object used for TCP operation. Good for seting OnStatus hook, etc.}
  149. property TCPSock: TTCPBlockSocket read FTCPSock;
  150. {:if @true, then is used TCP protocol instead UDP. It is needed for zone
  151. transfers, etc.}
  152. property UseTCP: Boolean read FUseTCP Write FUseTCP;
  153. {:After DNS operation contains ResultCode of DNS operation.
  154. Values are: 0-no error, 1-format error, 2-server failure, 3-name error,
  155. 4-not implemented, 5-refused.}
  156. property RCode: Integer read FRCode;
  157. {:@True, if answer is authoritative.}
  158. property Authoritative: Boolean read FAuthoritative;
  159. {:@True, if answer is truncated to 512 bytes.}
  160. property Truncated: Boolean read FTRuncated;
  161. {:Detailed informations from name server reply. One record per line. Record
  162. have comma delimited entries with type number, TTL and data filelds.
  163. This information contains detailed information about query reply.}
  164. property AnswerInfo: TStringList read FAnswerInfo;
  165. {:Detailed informations from name server reply. One record per line. Record
  166. have comma delimited entries with type number, TTL and data filelds.
  167. This information contains detailed information about nameserver.}
  168. property NameserverInfo: TStringList read FNameserverInfo;
  169. {:Detailed informations from name server reply. One record per line. Record
  170. have comma delimited entries with type number, TTL and data filelds.
  171. This information contains detailed additional information.}
  172. property AdditionalInfo: TStringList read FAdditionalInfo;
  173. end;
  174. {:A very useful function, and example of it's use is found in the TDNSSend object.
  175. This function is used to get mail servers for a domain and sort them by
  176. preference numbers. "Servers" contains only the domain names of the mail
  177. servers in the right order (without preference number!). The first domain name
  178. will always be the highest preferenced mail server. Returns boolean @TRUE if
  179. all went well.}
  180. function GetMailServers(const DNSHost, Domain: AnsiString;
  181. const Servers: TStrings): Boolean;
  182. implementation
  183. constructor TDNSSend.Create;
  184. begin
  185. inherited Create;
  186. FSock := TUDPBlockSocket.Create;
  187. FTCPSock := TTCPBlockSocket.Create;
  188. FUseTCP := False;
  189. FTimeout := 10000;
  190. FTargetPort := cDnsProtocol;
  191. FAnswerInfo := TStringList.Create;
  192. FNameserverInfo := TStringList.Create;
  193. FAdditionalInfo := TStringList.Create;
  194. Randomize;
  195. end;
  196. destructor TDNSSend.Destroy;
  197. begin
  198. FAnswerInfo.Free;
  199. FNameserverInfo.Free;
  200. FAdditionalInfo.Free;
  201. FTCPSock.Free;
  202. FSock.Free;
  203. inherited Destroy;
  204. end;
  205. function TDNSSend.CompressName(const Value: AnsiString): AnsiString;
  206. var
  207. n: Integer;
  208. s: AnsiString;
  209. begin
  210. Result := '';
  211. if Value = '' then
  212. Result := #0
  213. else
  214. begin
  215. s := '';
  216. for n := 1 to Length(Value) do
  217. if Value[n] = '.' then
  218. begin
  219. Result := Result + Char(Length(s)) + s;
  220. s := '';
  221. end
  222. else
  223. s := s + Value[n];
  224. if s <> '' then
  225. Result := Result + Char(Length(s)) + s;
  226. Result := Result + #0;
  227. end;
  228. end;
  229. function TDNSSend.CodeHeader: AnsiString;
  230. begin
  231. FID := Random(32767);
  232. Result := CodeInt(FID); // ID
  233. Result := Result + CodeInt($0100); // flags
  234. Result := Result + CodeInt(1); // QDCount
  235. Result := Result + CodeInt(0); // ANCount
  236. Result := Result + CodeInt(0); // NSCount
  237. Result := Result + CodeInt(0); // ARCount
  238. end;
  239. function TDNSSend.CodeQuery(const Name: AnsiString; QType: Integer): AnsiString;
  240. begin
  241. Result := CompressName(Name);
  242. Result := Result + CodeInt(QType);
  243. Result := Result + CodeInt(1); // Type INTERNET
  244. end;
  245. function TDNSSend.DecodeString(var From: Integer): AnsiString;
  246. var
  247. Len: integer;
  248. begin
  249. Len := Ord(FBuffer[From]);
  250. Inc(From);
  251. Result := Copy(FBuffer, From, Len);
  252. Inc(From, Len);
  253. end;
  254. function TDNSSend.DecodeLabels(var From: Integer): AnsiString;
  255. var
  256. l, f: Integer;
  257. begin
  258. Result := '';
  259. while True do
  260. begin
  261. if From >= Length(FBuffer) then
  262. Break;
  263. l := Ord(FBuffer[From]);
  264. Inc(From);
  265. if l = 0 then
  266. Break;
  267. if Result <> '' then
  268. Result := Result + '.';
  269. if (l and $C0) = $C0 then
  270. begin
  271. f := l and $3F;
  272. f := f * 256 + Ord(FBuffer[From]) + 1;
  273. Inc(From);
  274. Result := Result + DecodeLabels(f);
  275. Break;
  276. end
  277. else
  278. begin
  279. Result := Result + Copy(FBuffer, From, l);
  280. Inc(From, l);
  281. end;
  282. end;
  283. end;
  284. function TDNSSend.DecodeResource(var i: Integer; const Info: TStringList;
  285. QType: Integer): AnsiString;
  286. var
  287. Rname: AnsiString;
  288. RType, Len, j, x, y, z, n: Integer;
  289. R: AnsiString;
  290. t1, t2, ttl: integer;
  291. ip6: TIp6bytes;
  292. begin
  293. Result := '';
  294. R := '';
  295. Rname := DecodeLabels(i);
  296. RType := DecodeInt(FBuffer, i);
  297. Inc(i, 4);
  298. t1 := DecodeInt(FBuffer, i);
  299. Inc(i, 2);
  300. t2 := DecodeInt(FBuffer, i);
  301. Inc(i, 2);
  302. ttl := t1 * 65536 + t2;
  303. Len := DecodeInt(FBuffer, i);
  304. Inc(i, 2); // i point to begin of data
  305. j := i;
  306. i := i + len; // i point to next record
  307. if Length(FBuffer) >= (i - 1) then
  308. case RType of
  309. QTYPE_A:
  310. begin
  311. R := IntToStr(Ord(FBuffer[j]));
  312. Inc(j);
  313. R := R + '.' + IntToStr(Ord(FBuffer[j]));
  314. Inc(j);
  315. R := R + '.' + IntToStr(Ord(FBuffer[j]));
  316. Inc(j);
  317. R := R + '.' + IntToStr(Ord(FBuffer[j]));
  318. end;
  319. QTYPE_AAAA:
  320. begin
  321. for n := 0 to 15 do
  322. ip6[n] := ord(FBuffer[j + n]);
  323. R := IP6ToStr(ip6);
  324. end;
  325. QTYPE_NS, QTYPE_MD, QTYPE_MF, QTYPE_CNAME, QTYPE_MB,
  326. QTYPE_MG, QTYPE_MR, QTYPE_PTR, QTYPE_X25, QTYPE_NSAP,
  327. QTYPE_NSAPPTR:
  328. R := DecodeLabels(j);
  329. QTYPE_SOA:
  330. begin
  331. R := DecodeLabels(j);
  332. R := R + ',' + DecodeLabels(j);
  333. for n := 1 to 5 do
  334. begin
  335. x := DecodeInt(FBuffer, j) * 65536 + DecodeInt(FBuffer, j + 2);
  336. Inc(j, 4);
  337. R := R + ',' + IntToStr(x);
  338. end;
  339. end;
  340. QTYPE_NULL:
  341. begin
  342. end;
  343. QTYPE_WKS:
  344. begin
  345. end;
  346. QTYPE_HINFO:
  347. begin
  348. R := DecodeString(j);
  349. R := R + ',' + DecodeString(j);
  350. end;
  351. QTYPE_MINFO, QTYPE_RP, QTYPE_ISDN:
  352. begin
  353. R := DecodeLabels(j);
  354. R := R + ',' + DecodeLabels(j);
  355. end;
  356. QTYPE_MX, QTYPE_AFSDB, QTYPE_RT, QTYPE_KX:
  357. begin
  358. x := DecodeInt(FBuffer, j);
  359. Inc(j, 2);
  360. R := IntToStr(x);
  361. R := R + ',' + DecodeLabels(j);
  362. end;
  363. QTYPE_TXT, QTYPE_SPF:
  364. begin
  365. R := '';
  366. while j < i do
  367. R := R + DecodeString(j);
  368. end;
  369. QTYPE_GPOS:
  370. begin
  371. R := DecodeLabels(j);
  372. R := R + ',' + DecodeLabels(j);
  373. R := R + ',' + DecodeLabels(j);
  374. end;
  375. QTYPE_PX:
  376. begin
  377. x := DecodeInt(FBuffer, j);
  378. Inc(j, 2);
  379. R := IntToStr(x);
  380. R := R + ',' + DecodeLabels(j);
  381. R := R + ',' + DecodeLabels(j);
  382. end;
  383. QTYPE_SRV:
  384. // Author: Dan <ml@mutox.org>
  385. begin
  386. x := DecodeInt(FBuffer, j);
  387. Inc(j, 2);
  388. y := DecodeInt(FBuffer, j);
  389. Inc(j, 2);
  390. z := DecodeInt(FBuffer, j);
  391. Inc(j, 2);
  392. R := IntToStr(x); // Priority
  393. R := R + ',' + IntToStr(y); // Weight
  394. R := R + ',' + IntToStr(z); // Port
  395. R := R + ',' + DecodeLabels(j); // Server DNS Name
  396. end;
  397. end;
  398. if R <> '' then
  399. Info.Add(RName + ',' + IntToStr(RType) + ',' + IntToStr(ttl) + ',' + R);
  400. if QType = RType then
  401. Result := R;
  402. end;
  403. function TDNSSend.RecvTCPResponse(const WorkSock: TBlockSocket): AnsiString;
  404. var
  405. l: integer;
  406. begin
  407. Result := '';
  408. l := WorkSock.recvbyte(FTimeout) * 256 + WorkSock.recvbyte(FTimeout);
  409. if l > 0 then
  410. Result := WorkSock.RecvBufferStr(l, FTimeout);
  411. end;
  412. function TDNSSend.DecodeResponse(const Buf: AnsiString; const Reply: TStrings;
  413. QType: Integer):boolean;
  414. var
  415. n, i: Integer;
  416. flag, qdcount, ancount, nscount, arcount: Integer;
  417. s: AnsiString;
  418. begin
  419. Result := False;
  420. Reply.Clear;
  421. FAnswerInfo.Clear;
  422. FNameserverInfo.Clear;
  423. FAdditionalInfo.Clear;
  424. FAuthoritative := False;
  425. if (Length(Buf) > 13) and (FID = DecodeInt(Buf, 1)) then
  426. begin
  427. Result := True;
  428. flag := DecodeInt(Buf, 3);
  429. FRCode := Flag and $000F;
  430. FAuthoritative := (Flag and $0400) > 0;
  431. FTruncated := (Flag and $0200) > 0;
  432. if FRCode = 0 then
  433. begin
  434. qdcount := DecodeInt(Buf, 5);
  435. ancount := DecodeInt(Buf, 7);
  436. nscount := DecodeInt(Buf, 9);
  437. arcount := DecodeInt(Buf, 11);
  438. i := 13; //begin of body
  439. if (qdcount > 0) and (Length(Buf) > i) then //skip questions
  440. for n := 1 to qdcount do
  441. begin
  442. while (Buf[i] <> #0) and ((Ord(Buf[i]) and $C0) <> $C0) do
  443. Inc(i);
  444. Inc(i, 5);
  445. end;
  446. if (ancount > 0) and (Length(Buf) > i) then // decode reply
  447. for n := 1 to ancount do
  448. begin
  449. s := DecodeResource(i, FAnswerInfo, QType);
  450. if s <> '' then
  451. Reply.Add(s);
  452. end;
  453. if (nscount > 0) and (Length(Buf) > i) then // decode nameserver info
  454. for n := 1 to nscount do
  455. DecodeResource(i, FNameserverInfo, QType);
  456. if (arcount > 0) and (Length(Buf) > i) then // decode additional info
  457. for n := 1 to arcount do
  458. DecodeResource(i, FAdditionalInfo, QType);
  459. end;
  460. end;
  461. end;
  462. function TDNSSend.DNSQuery(Name: AnsiString; QType: Integer;
  463. const Reply: TStrings): Boolean;
  464. var
  465. WorkSock: TBlockSocket;
  466. t: TStringList;
  467. b: boolean;
  468. begin
  469. Result := False;
  470. if IsIP(Name) then
  471. Name := ReverseIP(Name) + '.in-addr.arpa';
  472. if IsIP6(Name) then
  473. Name := ReverseIP6(Name) + '.ip6.arpa';
  474. FBuffer := CodeHeader + CodeQuery(Name, QType);
  475. if FUseTCP then
  476. WorkSock := FTCPSock
  477. else
  478. WorkSock := FSock;
  479. WorkSock.Bind(FIPInterface, cAnyPort);
  480. WorkSock.Connect(FTargetHost, FTargetPort);
  481. if FUseTCP then
  482. FBuffer := Codeint(length(FBuffer)) + FBuffer;
  483. WorkSock.SendString(FBuffer);
  484. if FUseTCP then
  485. FBuffer := RecvTCPResponse(WorkSock)
  486. else
  487. FBuffer := WorkSock.RecvPacket(FTimeout);
  488. if FUseTCP and (QType = QTYPE_AXFR) then //zone transfer
  489. begin
  490. t := TStringList.Create;
  491. try
  492. repeat
  493. b := DecodeResponse(FBuffer, Reply, QType);
  494. if (t.Count > 1) and (AnswerInfo.Count > 0) then //find end of transfer
  495. b := b and (t[0] <> AnswerInfo[AnswerInfo.count - 1]);
  496. if b then
  497. begin
  498. t.AddStrings(AnswerInfo);
  499. FBuffer := RecvTCPResponse(WorkSock);
  500. if FBuffer = '' then
  501. Break;
  502. if WorkSock.LastError <> 0 then
  503. Break;
  504. end;
  505. until not b;
  506. Reply.Assign(t);
  507. Result := True;
  508. finally
  509. t.free;
  510. end;
  511. end
  512. else //normal query
  513. if WorkSock.LastError = 0 then
  514. Result := DecodeResponse(FBuffer, Reply, QType);
  515. end;
  516. {==============================================================================}
  517. function GetMailServers(const DNSHost, Domain: AnsiString;
  518. const Servers: TStrings): Boolean;
  519. var
  520. DNS: TDNSSend;
  521. t: TStringList;
  522. n, m, x: Integer;
  523. begin
  524. Result := False;
  525. Servers.Clear;
  526. t := TStringList.Create;
  527. DNS := TDNSSend.Create;
  528. try
  529. DNS.TargetHost := DNSHost;
  530. if DNS.DNSQuery(Domain, QType_MX, t) then
  531. begin
  532. { normalize preference number to 5 digits }
  533. for n := 0 to t.Count - 1 do
  534. begin
  535. x := Pos(',', t[n]);
  536. if x > 0 then
  537. for m := 1 to 6 - x do
  538. t[n] := '0' + t[n];
  539. end;
  540. { sort server list }
  541. t.Sorted := True;
  542. { result is sorted list without preference numbers }
  543. for n := 0 to t.Count - 1 do
  544. begin
  545. x := Pos(',', t[n]);
  546. Servers.Add(Copy(t[n], x + 1, Length(t[n]) - x));
  547. end;
  548. Result := True;
  549. end;
  550. finally
  551. DNS.Free;
  552. t.Free;
  553. end;
  554. end;
  555. end.