File:
0.04.20a/server/base/tests/dns.inc.php (
View as Code)
1: 2: /* -------------------------------------------------------------
3: This file is the PurplePixie PHP DNS Query Classes
4:
5: The software is (C) Copyright 2008 PurplePixie Systems
6:
7: This is free software: you can redistribute it and/or modify
8: it under the terms of the GNU General Public License as published by
9: the Free Software Foundation, either version 3 of the License, or
10: (at your option) any later version.
11:
12: The software is distributed in the hope that it will be useful,
13: but WITHOUT ANY WARRANTY; without even the implied warranty of
14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15: GNU General Public License for more details.
16:
17: You should have received a copy of the GNU General Public License
18: along with this software. If not, see www.gnu.org/licenses
19:
20: For more information see www.purplepixie.org/phpdns
21: -------------------------------------------------------------- */
22:
23: //
24: // Version 0.02 20th June 2008
25: // David Cutting -- dcutting (some_sort_of_at_sign) purplepixie dot org
26: //
27:
28: class DNSTypes
29: {
30: var $types_by_id;
31: var $types_by_name;
32:
33: function AddType($id,$name)
34: {
35: $this->types_by_id[$id]=$name;
36: $this->types_by_name[$name]=$id;
37: }
38:
39: function DNSTypes()
40: {
41: $this->types_by_id=array();
42: $this->types_by_name=array();
43:
44: $this->AddType(1,"A");
45: $this->AddType(2,"NS");
46: $this->AddType(5,"CNAME");
47: $this->AddType(6,"SOA");
48: $this->AddType(12,"PTR");
49: $this->AddType(15,"MX");
50: $this->AddType(16,"TXT");
51: $this->AddType(255,"ANY");
52: }
53:
54: function GetByName($name)
55: {
56: if (isset($this->types_by_name[$name])) return $this->types_by_name[$name];
57: return 0;
58: }
59:
60: function GetById($id)
61: {
62: if (isset($this->types_by_id[$id])) return $this->types_by_id[$id];
63: return "";
64: }
65: }
66:
67: class DNSResult
68: {
69: var $type;
70: var $typeid;
71: var $class;
72: var $ttl;
73: var $data;
74: var $domain;
75: var $string;
76: var $extas=array();
77: }
78:
79: class DNSAnswer
80: {
81: var $count=0;
82: var $results=array();
83:
84: function AddResult($type,$typeid,$class,$ttl,$data,$domain="",$string="",$extras=array())
85: {
86: $this->results[$this->count]=new DNSResult();
87: $this->results[$this->count]->type=$type;
88: $this->results[$this->count]->typeid=$typeid;
89: $this->results[$this->count]->class=$class;
90: $this->results[$this->count]->ttl=$ttl;
91: $this->results[$this->count]->data=$data;
92: $this->results[$this->count]->domain=$domain;
93: $this->results[$this->count]->string=$string;
94: $this->results[$this->count]->extras=$extras;
95: $this->count++;
96: return ($this->count-1);
97: }
98: }
99:
100:
101: class DNSQuery
102: {
103: var $server="";
104: var $port;
105: var $timeout;
106: var $udp;
107: var $debug;
108: var $binarydebug=false;
109:
110: var $types;
111:
112: var $rawbuffer="";
113: var $rawheader="";
114: var $rawresponse="";
115: var $header;
116: var $responsecounter=0;
117:
118: var $lastnameservers;
119: var $lastadditional;
120:
121: var $error=false;
122: var $lasterror="";
123:
124: function ReadResponse($count=1,$offset="")
125: {
126: if ($offset=="") // no offset so use and increment the ongoing counter
127: {
128: $return=substr($this->rawbuffer,$this->responsecounter,$count);
129: $this->responsecounter+=$count;
130: }
131: else
132: {
133: $return=substr($this->rawbuffer,$offset,$count);
134: }
135: return $return;
136: }
137:
138: function ReadDomainLabels($offset,&$counter=0)
139: {
140: $labels=array();
141: $startoffset=$offset;
142: $return=false;
143: while (!$return)
144: {
145: $label_len=ord($this->ReadResponse(1,$offset++));
146: if ($label_len<=0) $return=true; // end of data
147: else if ($label_len<64) // uncompressed data
148: {
149: $labels[]=$this->ReadResponse($label_len,$offset);
150: $offset+=$label_len;
151: }
152: else // label_len>=64 -- pointer
153: {
154: $nextitem=$this->ReadResponse(1,$offset++);
155: $pointer_offset = ( ($label_len & 0x3f) << 8 ) + ord($nextitem);
156: // Branch Back Upon Ourselves...
157: $this->Debug("Label Offset: ".$pointer_offset);
158: $pointer_labels=$this->ReadDomainLabels($pointer_offset);
159: foreach($pointer_labels as $ptr_label)
160: $labels[]=$ptr_label;
161: $return=true;
162: }
163: }
164: $counter=$offset-$startoffset;
165: return $labels;
166: }
167:
168: function ReadDomainLabel()
169: {
170: $count=0;
171: $labels=$this->ReadDomainLabels($this->responsecounter,$count);
172: $domain=implode(".",$labels);
173: $this->responsecounter+=$count;
174: $this->Debug("Label ".$domain." len ".$count);
175: return $domain;
176: }
177:
178: function Debug($text)
179: {
180: if ($this->debug) echo $text."\n";
181: }
182:
183: function DebugBinary($data)
184: {
185: if ($this->binarydebug)
186: {
187: for ($a=0; $a188: {
189: echo $a;
190: echo "\t";
191: printf("%d",$data[$a]);
192: echo "\t";
193: $hex=bin2hex($data[$a]);
194: echo "0x".$hex;
195: echo "\t";
196: $dec=hexdec($hex);
197: echo $dec;
198: echo "\t";
199: if (($dec>30)&&($dec<150)) echo $data[$a];
200: echo "\n";
201: }
202: }
203: }
204:
205: function SetError($text)
206: {
207: $this->error=true;
208: $this->lasterror=$text;
209: $this->Debug("Error: ".$text);
210: }
211:
212: function ClearError()
213: {
214: $this->error=false;
215: $this->lasterror="";
216: }
217:
218: function DNSQuery($server,$port=53,$timeout=60,$udp=true,$debug=false)
219: {
220: $this->server=$server;
221: $this->port=$port;
222: $this->timeout=$timeout;
223: $this->udp=$udp;
224: $this->debug=$debug;
225:
226: $this->types=new DNSTypes();
227: $this->Debug("DNSQuery Class Initialised");
228: }
229:
230:
231: function ReadRecord()
232: {
233: // First the pesky domain names - maybe not so pesky though I suppose
234:
235: $domain=$this->ReadDomainLabel();
236:
237:
238:
239: $ans_header_bin=$this->ReadResponse(10); // 10 byte header
240: $ans_header=unpack("ntype/nclass/Nttl/nlength",$ans_header_bin);
241: $this->Debug("Record Type ".$ans_header['type']." Class ".$ans_header['class']." TTL ".$ans_header['ttl']." Length ".$ans_header['length']);
242:
243: $typeid=$this->types->GetById($ans_header['type']);
244: $extras=array();
245: $data="";
246: $string="";
247:
248: switch($typeid)
249: {
250: case "A":
251: $ip=implode(".",unpack("Ca/Cb/Cc/Cd",$this->ReadResponse(4)));
252: $data=$ip;
253: $string=$domain." has address ".$ip;
254: break;
255:
256: case "NS":
257: $nameserver=$this->ReadDomainLabel();
258: $data=$nameserver;
259: $string=$domain." nameserver ".$nameserver;
260: break;
261:
262: case "PTR":
263: $data=$this->ReadDomainLabel();
264: $string=$domain." points to ".$data;
265: break;
266:
267: case "CNAME":
268: $data=$this->ReadDomainLabel();
269: $string=$domain." alias of ".$data;
270: break;
271:
272: case "MX":
273: $prefs=$this->ReadResponse(2);
274: $prefs=unpack("nlevel",$prefs);
275: $extras['level']=$prefs['level'];
276: $data=$this->ReadDomainLabel();
277: $string=$domain." mailserver ".$data." (pri=".$extras['level'].")";
278: break;
279:
280: case "SOA":
281: // Label First
282: $data=$this->ReadDomainLabel();
283: $responsible=$this->ReadDomainLabel();
284:
285: $buffer=$this->ReadResponse(20);
286: $extras=unpack("nserial/nrefresh/Nretry/Nexpiry/Nminttl",$buffer);
287: $dot=strpos($responsible,".");
288: $responsible[$dot]="@";
289: $extras['responsible']=$responsible;
290: $string=$domain." SOA ".$data." Serial ".$extras['serial'];
291: break;
292:
293: case "TXT":
294: $data=$this->ReadResponse($ans_header['length']);
295: $string=$domain." TEXT ".$data;
296: break;
297:
298: default: // something we can't deal with
299: $stuff=$this->ReadResponse($ans_header['length']);
300: break;
301:
302: }
303:
304: //$dns_answer->AddResult($ans_header['type'],$typeid,$ans_header['class'],$ans_header['ttl'],$data,$domain,$string,$extras);
305: $return=array(
306: "header" => $ans_header,
307: "typeid" => $typeid,
308: "data" => $data,
309: "domain" => $domain,
310: "string" => $string,
311: "extras" => $extras );
312: return $return;
313: }
314:
315:
316:
317:
318:
319:
320: function Query($question,$type="A")
321: {
322: $this->ClearError();
323: $typeid=$this->types->GetByName($type);
324: if ($typeid===false)
325: {
326: $this->SetError("Invalid Query Type ".$type);
327: return false;
328: }
329:
330: if ($this->udp) $host="udp://".$this->server;
331: else $host=$this->server;
332:
333: if (!$socket=fsockopen($host,$this->port,$this->timeout))
334: {
335: $this->SetError("Failed to Open Socket");
336: return false;
337: }
338:
339: // Split Into Labels
340: if (preg_match("/[a-z|A-Z]/",$question)==0) // IP Address
341: {
342: $labeltmp=explode(".",$question); // reverse ARPA format
343: for ($i=count($labeltmp)-1; $i>=0; $i--)
344: $labels[]=$labeltmp[$i];
345: $labels[]="IN-ADDR";
346: $labels[]="ARPA";
347: }
348: else // hostname
349: $labels=explode(".",$question);
350:
351: $question_binary="";
352: for ($a=0; $a353: {
354: $size=strlen($labels[$a]);
355: $question_binary.=pack("C",$size); // size byte first
356: $question_binary.=$labels[$a]; // then the label
357: }
358: $question_binary.=pack("C",0); // end it off
359:
360: $this->Debug("Question: ".$question." (type=".$type."/".$typeid.")");
361:
362: $id=rand(1,255)|(rand(0,255)<<8); // generate the ID
363:
364: // Set standard codes and flags
365: $flags=0x0100 & 0x0300; // recursion & queryspecmask
366: $opcode=0x0000; // opcode
367:
368: // Build the header
369: $header="";
370: $header.=pack("n",$id);
371: $header.=pack("n",$opcode | $flags);
372: $header.=pack("nnnn",1,0,0,0);
373: $header.=$question_binary;
374: $header.=pack("n",$typeid);
375: $header.=pack("n",0x0001); // internet class
376: $headersize=strlen($header);
377: $headersizebin=pack("n",$headersize);
378:
379: $this->Debug("Header Length: ".$headersize." Bytes");
380: $this->DebugBinary($header);
381:
382: if ( ($this->udp) && ($headersize>=512) )
383: {
384: $this->SetError("Question too big for UDP (".$headersize." bytes)");
385: fclose($socket);
386: return false;
387: }
388:
389: if ($this->udp) // UDP method
390: {
391: if (!fwrite($socket,$header,$headersize))
392: {
393: $this->SetError("Failed to write question to socket");
394: fclose($socket);
395: return false;
396: }
397: if (!$this->rawbuffer=fread($socket,4096)) // read until the end with UDP
398: {
399: $this->SetError("Failed to write read data buffer");
400: fclose($socket);
401: return false;
402: }
403: }
404: else // TCP
405: {
406: if (!fwrite($socket,$headersizebin)) // write the socket
407: {
408: $this->SetError("Failed to write question length to TCP socket");
409: fclose($socket);
410: return false;
411: }
412: if (!fwrite($socket,$header,$headersize))
413: {
414: $this->SetError("Failed to write question to TCP socket");
415: fclose($socket);
416: return false;
417: }
418: if (!$returnsize=fread($socket,2))
419: {
420: $this->SetError("Failed to read size from TCP socket");
421: fclose($socket);
422: return false;
423: }
424: $tmplen=unpack("nlength",$returnsize);
425: $datasize=$tmplen['length'];
426: $this->Debug("TCP Stream Length Limit ".$datasize);
427: if (!$this->rawbuffer=fread($socket,$datasize))
428: {
429: $this->SetError("Failed to read data buffer");
430: fclose($socket);
431: return false;
432: }
433: }
434: fclose($socket);
435:
436: $buffersize=strlen($this->rawbuffer);
437: $this->Debug("Read Buffer Size ".$buffersize);
438:
439: if ($buffersize<12)
440: {
441: $this->SetError("Return Buffer too Small");
442: return false;
443: }
444:
445: $this->rawheader=substr($this->rawbuffer,0,12); // first 12 bytes is the header
446: $this->rawresponse=substr($this->rawbuffer,12); // after that the response
447:
448: $this->responsecounter=12; // start parsing response counter from 12 - no longer using response so can do pointers
449:
450: $this->DebugBinary($this->rawbuffer);
451:
452: $this->header=unpack("nid/nspec/nqdcount/nancount/nnscount/narcount",$this->rawheader);
453:
454: $answers=$this->header['ancount'];
455: $this->Debug("Query Returned ".$answers." Answers");
456:
457: $dns_answer=new DNSAnswer();
458:
459: // Deal with the header question data
460: if ($this->header['qdcount']>0)
461: {
462: $this->Debug("Found ".$this->header['qdcount']." Questions");
463: for ($a=0; $a<$this->header['qdcount']; $a++)
464: {
465: $c=1;
466: while ($c!=0)
467: {
468: $c=hexdec(bin2hex($this->ReadResponse(1)));
469: }
470: $extradata=$this->ReadResponse(4);
471: }
472: }
473:
474: // New Functional Method
475: for ($a=0; $a<$this->header['ancount']; $a++)
476: {
477: $record=$this->ReadRecord();
478: $dns_answer->AddResult($record['header']['type'],$record['typeid'],$record['header']['class'],$record['header']['ttl'],
479: $record['data'],$record['domain'],$record['string'],$record['extras']);
480: }
481:
482: $this->lastnameservers=new DNSAnswer();
483: for ($a=0; $a<$this->header['nscount']; $a++)
484: {
485: $record=$this->ReadRecord();
486: $this->lastnameservers->AddResult($record['header']['type'],$record['typeid'],$record['header']['class'],$record['header']['ttl'],
487: $record['data'],$record['domain'],$record['string'],$record['extras']);
488: }
489:
490: $this->lastadditional=new DNSAnswer();
491: for ($a=0; $a<$this->header['arcount']; $a++)
492: {
493: $record=$this->ReadRecord();
494: $this->lastadditional->AddResult($record['header']['type'],$record['typeid'],$record['header']['class'],$record['header']['ttl'],
495: $record['data'],$record['domain'],$record['string'],$record['extras']);
496: }
497:
498:
499:
500:
501: return $dns_answer;
502: }
503:
504:
505: function SmartALookup($hostname,$depth=0)
506: {
507: $this->Debug("SmartALookup for ".$hostname." depth ".$depth);
508: if ($depth>5) return ""; // avoid recursive lookups
509: // The SmartALookup function will resolve CNAMES using the additional properties if possible
510: $answer=$this->Query($hostname,"A");
511:
512: if ($answer===false) return ""; // failed totally
513: if ($answer->count<=0) return ""; // no records at all returned
514:
515: $best_answer="";
516: $best_answer_typeid=0;
517:
518: $records=$answer->count;
519: for ($a=0; $a<$records; $a++)
520: {
521: $data=$answer->results[$a]->data;
522: $answer_typeid=$answer->results[$a]->typeid;
523:
524: if ($answer_typeid=="A") // found it
525: {
526: $best_answer=$data;
527: $best_answer_typeid="A";
528: $a=$records+10;
529: }
530: else if ($answer_typeid=="CNAME") // alias
531: {
532: $best_answer=$data;
533: $best_answer_typeid="CNAME";
534: // and keep going
535: }
536:
537: }
538:
539: if ( ($best_answer=="") || ($best_answer_typeid=="") ) return "";
540:
541: if ($best_answer_typeid=="A") return $best_answer; // got an IP ok
542:
543: if ($best_answer_typeid!="CNAME") return ""; // shouldn't ever happen
544:
545: $newtarget=$best_answer; // this is what we now need to resolve
546:
547: // First is it in the additional section
548: for ($a=0; $a<$this->lastadditional->count; $a++)
549: {
550: if ( ($this->lastadditional->results[$a]->domain==$hostname) &&
551: ($this->lastadditional->results[$a]->typeid=="A") )
552: return $this->lastadditional->results[$a]->data;
553: }
554:
555: // Not in the results
556:
557: return $this->SmartALookup($newtarget,++$depth);
558: }
559:
560: }
561:
562: ?>