File:
0.04.29a/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)
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: $tmp=0; // PHP4 fix for pass-by-ref
155: $nextitem=$this->ReadResponse(1,$offset++);
156: $pointer_offset = ( ($label_len & 0x3f) << 8 ) + ord($nextitem);
157: // Branch Back Upon Ourselves...
158: $this->Debug("Label Offset: ".$pointer_offset);
159: $pointer_labels=$this->ReadDomainLabels($pointer_offset,$tmp);
160: foreach($pointer_labels as $ptr_label)
161: $labels[]=$ptr_label;
162: $return=true;
163: }
164: }
165: $counter=$offset-$startoffset;
166: return $labels;
167: }
168:
169: function ReadDomainLabel()
170: {
171: $count=0;
172: $labels=$this->ReadDomainLabels($this->responsecounter,$count);
173: $domain=implode(".",$labels);
174: $this->responsecounter+=$count;
175: $this->Debug("Label ".$domain." len ".$count);
176: return $domain;
177: }
178:
179: function Debug($text)
180: {
181: if ($this->debug) echo $text."\n";
182: }
183:
184: function DebugBinary($data)
185: {
186: if ($this->binarydebug)
187: {
188: for ($a=0; $a189: {
190: echo $a;
191: echo "\t";
192: printf("%d",$data[$a]);
193: echo "\t";
194: $hex=bin2hex($data[$a]);
195: echo "0x".$hex;
196: echo "\t";
197: $dec=hexdec($hex);
198: echo $dec;
199: echo "\t";
200: if (($dec>30)&&($dec<150)) echo $data[$a];
201: echo "\n";
202: }
203: }
204: }
205:
206: function SetError($text)
207: {
208: $this->error=true;
209: $this->lasterror=$text;
210: $this->Debug("Error: ".$text);
211: }
212:
213: function ClearError()
214: {
215: $this->error=false;
216: $this->lasterror="";
217: }
218:
219: function DNSQuery($server,$port=53,$timeout=60,$udp=true,$debug=false)
220: {
221: $this->server=$server;
222: $this->port=$port;
223: $this->timeout=$timeout;
224: $this->udp=$udp;
225: $this->debug=$debug;
226:
227: $this->types=new DNSTypes();
228: $this->Debug("DNSQuery Class Initialised");
229: }
230:
231:
232: function ReadRecord()
233: {
234: // First the pesky domain names - maybe not so pesky though I suppose
235:
236: $domain=$this->ReadDomainLabel();
237:
238:
239:
240: $ans_header_bin=$this->ReadResponse(10); // 10 byte header
241: $ans_header=unpack("ntype/nclass/Nttl/nlength",$ans_header_bin);
242: $this->Debug("Record Type ".$ans_header['type']." Class ".$ans_header['class']." TTL ".$ans_header['ttl']." Length ".$ans_header['length']);
243:
244: $typeid=$this->types->GetById($ans_header['type']);
245: $extras=array();
246: $data="";
247: $string="";
248:
249: switch($typeid)
250: {
251: case "A":
252: $ip=implode(".",unpack("Ca/Cb/Cc/Cd",$this->ReadResponse(4)));
253: $data=$ip;
254: $string=$domain." has address ".$ip;
255: break;
256:
257: case "NS":
258: $nameserver=$this->ReadDomainLabel();
259: $data=$nameserver;
260: $string=$domain." nameserver ".$nameserver;
261: break;
262:
263: case "PTR":
264: $data=$this->ReadDomainLabel();
265: $string=$domain." points to ".$data;
266: break;
267:
268: case "CNAME":
269: $data=$this->ReadDomainLabel();
270: $string=$domain." alias of ".$data;
271: break;
272:
273: case "MX":
274: $prefs=$this->ReadResponse(2);
275: $prefs=unpack("nlevel",$prefs);
276: $extras['level']=$prefs['level'];
277: $data=$this->ReadDomainLabel();
278: $string=$domain." mailserver ".$data." (pri=".$extras['level'].")";
279: break;
280:
281: case "SOA":
282: // Label First
283: $data=$this->ReadDomainLabel();
284: $responsible=$this->ReadDomainLabel();
285:
286: $buffer=$this->ReadResponse(20);
287: $extras=unpack("nserial/nrefresh/Nretry/Nexpiry/Nminttl",$buffer);
288: $dot=strpos($responsible,".");
289: $responsible[$dot]="@";
290: $extras['responsible']=$responsible;
291: $string=$domain." SOA ".$data." Serial ".$extras['serial'];
292: break;
293:
294: case "TXT":
295: $data=$this->ReadResponse($ans_header['length']);
296: $string=$domain." TEXT ".$data;
297: break;
298:
299: default: // something we can't deal with
300: $stuff=$this->ReadResponse($ans_header['length']);
301: break;
302:
303: }
304:
305: //$dns_answer->AddResult($ans_header['type'],$typeid,$ans_header['class'],$ans_header['ttl'],$data,$domain,$string,$extras);
306: $return=array(
307: "header" => $ans_header,
308: "typeid" => $typeid,
309: "data" => $data,
310: "domain" => $domain,
311: "string" => $string,
312: "extras" => $extras );
313: return $return;
314: }
315:
316:
317:
318:
319:
320:
321: function Query($question,$type="A")
322: {
323: $this->ClearError();
324: $typeid=$this->types->GetByName($type);
325: if ($typeid===false)
326: {
327: $this->SetError("Invalid Query Type ".$type);
328: return false;
329: }
330:
331: if ($this->udp) $host="udp://".$this->server;
332: else $host=$this->server;
333:
334: if (!$socket=fsockopen($host,$this->port,$this->timeout))
335: {
336: $this->SetError("Failed to Open Socket");
337: return false;
338: }
339:
340: // Split Into Labels
341: if (preg_match("/[a-z|A-Z]/",$question)==0) // IP Address
342: {
343: $labeltmp=explode(".",$question); // reverse ARPA format
344: for ($i=count($labeltmp)-1; $i>=0; $i--)
345: $labels[]=$labeltmp[$i];
346: $labels[]="IN-ADDR";
347: $labels[]="ARPA";
348: }
349: else // hostname
350: $labels=explode(".",$question);
351:
352: $question_binary="";
353: for ($a=0; $a354: {
355: $size=strlen($labels[$a]);
356: $question_binary.=pack("C",$size); // size byte first
357: $question_binary.=$labels[$a]; // then the label
358: }
359: $question_binary.=pack("C",0); // end it off
360:
361: $this->Debug("Question: ".$question." (type=".$type."/".$typeid.")");
362:
363: $id=rand(1,255)|(rand(0,255)<<8); // generate the ID
364:
365: // Set standard codes and flags
366: $flags=0x0100 & 0x0300; // recursion & queryspecmask
367: $opcode=0x0000; // opcode
368:
369: // Build the header
370: $header="";
371: $header.=pack("n",$id);
372: $header.=pack("n",$opcode | $flags);
373: $header.=pack("nnnn",1,0,0,0);
374: $header.=$question_binary;
375: $header.=pack("n",$typeid);
376: $header.=pack("n",0x0001); // internet class
377: $headersize=strlen($header);
378: $headersizebin=pack("n",$headersize);
379:
380: $this->Debug("Header Length: ".$headersize." Bytes");
381: $this->DebugBinary($header);
382:
383: if ( ($this->udp) && ($headersize>=512) )
384: {
385: $this->SetError("Question too big for UDP (".$headersize." bytes)");
386: fclose($socket);
387: return false;
388: }
389:
390: if ($this->udp) // UDP method
391: {
392: if (!fwrite($socket,$header,$headersize))
393: {
394: $this->SetError("Failed to write question to socket");
395: fclose($socket);
396: return false;
397: }
398: if (!$this->rawbuffer=fread($socket,4096)) // read until the end with UDP
399: {
400: $this->SetError("Failed to write read data buffer");
401: fclose($socket);
402: return false;
403: }
404: }
405: else // TCP
406: {
407: if (!fwrite($socket,$headersizebin)) // write the socket
408: {
409: $this->SetError("Failed to write question length to TCP socket");
410: fclose($socket);
411: return false;
412: }
413: if (!fwrite($socket,$header,$headersize))
414: {
415: $this->SetError("Failed to write question to TCP socket");
416: fclose($socket);
417: return false;
418: }
419: if (!$returnsize=fread($socket,2))
420: {
421: $this->SetError("Failed to read size from TCP socket");
422: fclose($socket);
423: return false;
424: }
425: $tmplen=unpack("nlength",$returnsize);
426: $datasize=$tmplen['length'];
427: $this->Debug("TCP Stream Length Limit ".$datasize);
428: if (!$this->rawbuffer=fread($socket,$datasize))
429: {
430: $this->SetError("Failed to read data buffer");
431: fclose($socket);
432: return false;
433: }
434: }
435: fclose($socket);
436:
437: $buffersize=strlen($this->rawbuffer);
438: $this->Debug("Read Buffer Size ".$buffersize);
439:
440: if ($buffersize<12)
441: {
442: $this->SetError("Return Buffer too Small");
443: return false;
444: }
445:
446: $this->rawheader=substr($this->rawbuffer,0,12); // first 12 bytes is the header
447: $this->rawresponse=substr($this->rawbuffer,12); // after that the response
448:
449: $this->responsecounter=12; // start parsing response counter from 12 - no longer using response so can do pointers
450:
451: $this->DebugBinary($this->rawbuffer);
452:
453: $this->header=unpack("nid/nspec/nqdcount/nancount/nnscount/narcount",$this->rawheader);
454:
455: $answers=$this->header['ancount'];
456: $this->Debug("Query Returned ".$answers." Answers");
457:
458: $dns_answer=new DNSAnswer();
459:
460: // Deal with the header question data
461: if ($this->header['qdcount']>0)
462: {
463: $this->Debug("Found ".$this->header['qdcount']." Questions");
464: for ($a=0; $a<$this->header['qdcount']; $a++)
465: {
466: $c=1;
467: while ($c!=0)
468: {
469: $c=hexdec(bin2hex($this->ReadResponse(1)));
470: }
471: $extradata=$this->ReadResponse(4);
472: }
473: }
474:
475: // New Functional Method
476: for ($a=0; $a<$this->header['ancount']; $a++)
477: {
478: $record=$this->ReadRecord();
479: $dns_answer->AddResult($record['header']['type'],$record['typeid'],$record['header']['class'],$record['header']['ttl'],
480: $record['data'],$record['domain'],$record['string'],$record['extras']);
481: }
482:
483: $this->lastnameservers=new DNSAnswer();
484: for ($a=0; $a<$this->header['nscount']; $a++)
485: {
486: $record=$this->ReadRecord();
487: $this->lastnameservers->AddResult($record['header']['type'],$record['typeid'],$record['header']['class'],$record['header']['ttl'],
488: $record['data'],$record['domain'],$record['string'],$record['extras']);
489: }
490:
491: $this->lastadditional=new DNSAnswer();
492: for ($a=0; $a<$this->header['arcount']; $a++)
493: {
494: $record=$this->ReadRecord();
495: $this->lastadditional->AddResult($record['header']['type'],$record['typeid'],$record['header']['class'],$record['header']['ttl'],
496: $record['data'],$record['domain'],$record['string'],$record['extras']);
497: }
498:
499:
500:
501:
502: return $dns_answer;
503: }
504:
505:
506: function SmartALookup($hostname,$depth=0)
507: {
508: $this->Debug("SmartALookup for ".$hostname." depth ".$depth);
509: if ($depth>5) return ""; // avoid recursive lookups
510: // The SmartALookup function will resolve CNAMES using the additional properties if possible
511: $answer=$this->Query($hostname,"A");
512:
513: if ($answer===false) return ""; // failed totally
514: if ($answer->count<=0) return ""; // no records at all returned
515:
516: $best_answer="";
517: $best_answer_typeid=0;
518:
519: $records=$answer->count;
520: for ($a=0; $a<$records; $a++)
521: {
522: $data=$answer->results[$a]->data;
523: $answer_typeid=$answer->results[$a]->typeid;
524:
525: if ($answer_typeid=="A") // found it
526: {
527: $best_answer=$data;
528: $best_answer_typeid="A";
529: $a=$records+10;
530: }
531: else if ($answer_typeid=="CNAME") // alias
532: {
533: $best_answer=$data;
534: $best_answer_typeid="CNAME";
535: // and keep going
536: }
537:
538: }
539:
540: if ( ($best_answer=="") || ($best_answer_typeid=="") ) return "";
541:
542: if ($best_answer_typeid=="A") return $best_answer; // got an IP ok
543:
544: if ($best_answer_typeid!="CNAME") return ""; // shouldn't ever happen
545:
546: $newtarget=$best_answer; // this is what we now need to resolve
547:
548: // First is it in the additional section
549: for ($a=0; $a<$this->lastadditional->count; $a++)
550: {
551: if ( ($this->lastadditional->results[$a]->domain==$hostname) &&
552: ($this->lastadditional->results[$a]->typeid=="A") )
553: return $this->lastadditional->results[$a]->data;
554: }
555:
556: // Not in the results
557:
558: return $this->SmartALookup($newtarget,++$depth);
559: }
560:
561: }
562:
563: ?>