File: 0.04.30a/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)
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; $a<strlen($data); $a++)
189: 			{
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; $a<count($labels); $a++)
354: 			{
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: ?>