File: 1.13.3b/server/base/tests/dns.inc.php (View as HTML)

  1: <?php // FreeNATS DNS Test Module
  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; $a<strlen($data); $a++)
188: 			{
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; $a<count($labels); $a++)
353: 			{
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: ?>