PageRenderTime 126ms CodeModel.GetById 40ms app.highlight 41ms RepoModel.GetById 39ms app.codeStats 0ms

/code/classes/Daemon/DNSd/Engine.class.php

https://github.com/blekkzor/pinetd2
PHP | 319 lines | 254 code | 52 blank | 13 comment | 69 complexity | 39959512f71523974269592270a66b15 MD5 | raw file
  1<?php
  2
  3namespace Daemon\DNSd;
  4
  5use Daemon\DNSd\Type\RFC1035;
  6use pinetd\SQL;
  7use pinetd\Logger;
  8
  9class Engine {
 10	const DNS_CLASS_IN = 1; // Teh Internet
 11	const DNS_CLASS_CS = 2; // CSNET class (obsolete, used only for examples in some obsolete RFCs)
 12	const DNS_CLASS_CH = 3; // CHAOS
 13	const DNS_CLASS_HS = 4; // Hesiod [Dyer 87]
 14
 15	protected $parent;
 16	protected $IPC;
 17	protected $packet_class;
 18	protected $localConfig;
 19	protected $sql;
 20	protected $sql_stmts = NULL;
 21	protected $sql_stmts_tmp = array();
 22
 23	public function __construct($parent, $IPC, $localConfig) {
 24		$this->parent = $parent;
 25		$this->IPC = $IPC;
 26		$this->packet_class = relativeclass($this, 'Packet');
 27
 28		$this->localConfig = $localConfig;
 29		// connect to SQL
 30		$this->sql = SQL::Factory($this->localConfig['Storage']);
 31
 32		// check if tables exists
 33		// TODO: make this look better
 34		while(!$this->sql->query('SELECT 1 FROM `status` LIMIT 1')) usleep(500000);
 35	}
 36
 37	protected function prepareStatements() {
 38		$stmts = array(
 39			'get_domain' => 'SELECT `zone`, `pzc_stamp`, `pzc_zone` FROM `domains` WHERE `domain` = ?',
 40			'get_zone' => 'SELECT `zone_id` FROM `zones` WHERE `zone` = ?',
 41			'get_record_any' => 'SELECT * FROM `zone_records` WHERE `zone` = ? AND `host` = ?',
 42			'get_record_nsonly' => 'SELECT * FROM `zone_records` WHERE `zone` = ? AND `host` = ? AND `type` IN (\'NS\',\'ZONE\')',
 43			'get_authority' => 'SELECT * FROM `zone_records` WHERE `zone` = ? AND `host` = \'\' AND `type` IN (?)',
 44		);
 45
 46		foreach($stmts as $name => $query) {
 47			if (isset($this->sql_stmts_tmp[$name])) continue;
 48
 49			$stmt = $this->sql->prepare($query);
 50			if (!$stmt) return false;
 51
 52			$this->sql_stmts_tmp[$name] = $stmt;
 53		}
 54
 55		$this->sql_stmts = $this->sql_stmts_tmp;
 56
 57		return true;
 58	}
 59
 60	protected function handleQuestion($pkt, $question) {
 61		switch($question['qclass']) {
 62			case self::DNS_CLASS_CH:
 63				$handler = 'handleChaosQuestion';
 64				break;
 65			case self::DNS_CLASS_IN:
 66			default:
 67				$handler = 'handleInternetQuestion';
 68				break;
 69		}
 70		return $this->$handler($pkt, $question['qname'], $question['qtype']);
 71	}
 72
 73	protected function buildInternetQuestionReply($pkt, $host, $zone, $domain, $type, $subquery = 0, $initial_query = NULL, $max_exp = NULL) {
 74		$ohost = $host;
 75		if ($ohost != '') $ohost .= '.';
 76		$typestr = Type::typeToString($type);
 77		$nsonly = false;
 78
 79		while(1) {
 80			// got host & domain, lookup...
 81			if ($nsonly) {
 82				$res = $this->sql_stmts['get_record_nsonly']->run(array($zone, strtolower($host)));
 83			} else {
 84				$res = $this->sql_stmts['get_record_any']->run(array($zone, strtolower($host)));
 85			}
 86
 87			$found = 0;
 88			$add_lookup = array();
 89			$res_list = array();
 90			while($row = $res->fetch_assoc())
 91				$res_list[] = $row;
 92			foreach($res_list as $row) {
 93				++$found;
 94
 95				if (strtolower($row['type']) == 'zone') {
 96					// special type: linking to another zone
 97					$link_zone = $this->sql_stmts['get_zone']->run(array(strtolower($row['data'])))->fetch_assoc();
 98					if ($link_zone) {
 99						$this->buildInternetQuestionReply($pkt, substr($ohost, 0, -1), $link_zone['zone_id'], $domain, $type, $subquery, $initial_query, $max_exp);
100					}
101					continue;
102				}
103				
104				if ((!is_null($max_exp)) && ($row['ttl'] > $max_exp)) $row['ttl'] = $max_exp;
105
106				$answer = $this->makeResponse($row, $pkt);
107				if (is_null($answer)) continue;
108
109				if ($answer->getType() == Type\RFC1035::TYPE_CNAME) {
110					$aname = $row['data'];
111					if (substr($aname, -1) != '.') $aname .= '.' . $domain . '.';
112					if (strtolower($aname) != strtolower($ohost . $domain. '.')) {
113						$add_lookup[strtolower($aname)] = $aname;
114						$pkt->addAnswer($ohost. $domain. '.', $answer, $row['ttl']);
115					}
116				} elseif (($type != Type\RFC1035::TYPE_ANY) && ($answer->getType() == Type\RFC1035::TYPE_NS) && ($answer->getType() != $type) && ($host[0] != '*')) {
117					if ($host != '')
118						$pkt->addAuthority($host.'.'. $domain. '.', $answer, $row['ttl']);
119				} elseif (($type == Type\RFC1035::TYPE_ANY) || ($answer->getType() == $type)) {
120					$pkt->addAnswer($ohost. $domain. '.', $answer, $row['ttl']);
121				}
122			}
123			if ($found) break;
124			if ($host == '') break;
125			if ($host == '*') break; // can't lookup more
126			if ($host[0] == '*') {
127				$host = (string)substr($host, 2);
128				$nsonly = true;
129				continue;
130			}
131
132			$nsonly = false;
133
134			$pos = strpos($host, '.');
135			if ($pos === false) {
136				$host = '*';
137			} else {
138				$host = '*' . substr($host, $pos);
139			}
140		}
141
142		if ($subquery < 5) {
143			foreach($add_lookup as $aname) {
144				$this->handleInternetQuestion($pkt, $aname, $type, $subquery + 1, $initial_query);
145			}
146		} elseif($add_lookup) {
147			Logger::log(Logger::LOG_WARN, 'Query reached recursivity limit (query against '.$initial_query.' reaching '.$name.' and with lookup of '.implode(', ', $add_lookup).')');
148		}
149
150		if ($subquery) return;
151
152		if ($pkt->hasAuthority()) return;
153
154		// add authority
155		$res = $this->sql_stmts['get_authority']->run(array($zone, $pkt->hasAnswer()?'NS':'SOA'));
156
157		while($row = $res->fetch_assoc()) {
158			if ((!is_null($max_exp)) && ($row['ttl'] > $max_exp)) $row['ttl'] = $max_exp;
159			$answer = $this->makeResponse($row, $pkt);
160			if (is_null($answer)) continue;
161			if (!$pkt->hasAnswer()) $row['ttl'] = 0; // trick to avoid remote dns daemon caching the fact that this doesn't exists
162			$pkt->addAuthority($domain . '.', $answer, $row['ttl']);
163			$pkt->setFlag('aa', 1);
164		}
165	}
166
167	protected function handleInternetQuestion($pkt, $name, $type, $subquery = 0, $initial_query = NULL) {
168		// strip ending "."
169		if (substr($name, -1) == '.') $name = substr($name, 0, -1);
170
171		if (is_null($initial_query)) $initial_query = $name;
172
173		// check sql statements
174		if (is_null($this->sql_stmts)) {
175			if (!$this->prepareStatements()) return;
176		}
177
178		$typestr = Type::typeToString($type);
179		if (is_null($typestr)) {
180			$pkt->setFlag('rcode', Packet::RCODE_NOTIMP);
181			return;
182		}
183
184		if (strtolower($name) == 'my.dns.st') {
185			// HACK HACK HACK
186			$pkt->setFlag('aa', 1);
187			$pkt->setFlag('ra', 0);
188			
189			$peer = $pkt->getPeer();
190			if (!is_array($peer)) $peer = explode(':', $peer);
191			switch($type) {
192				case Type\RFC1035::TYPE_A: 
193					$answer = $this->makeResponse(array('type' => 'A', 'data' => $peer[0]), $pkt);
194					$pkt->addAnswer($name.'.', $answer, 600);
195					break;
196				case Type\RFC1035::TYPE_TXT:
197					$answer = $this->makeResponse(array('type' => 'TXT', 'data' => implode(' ', $peer)), $pkt);
198					$pkt->addAnswer($name.'.', $answer, 600);
199					break;
200				case Type\RFC1035::TYPE_ANY:
201					$answer = $this->makeResponse(array('type' => 'A', 'data' => $peer[0]), $pkt);
202					$pkt->addAnswer($name.'.', $answer, 600);
203					$answer = $this->makeResponse(array('type' => 'TXT', 'data' => implode(' ', $peer)), $pkt);
204					$pkt->addAnswer($name.'.', $answer, 600);
205					break;
206			}
207			return;
208		}
209
210		// search this domain
211		$domain = $name;
212		$host = '';
213		while(1) {
214			$res = $this->sql_stmts['get_domain']->run(array(strtolower($domain)))->fetch_assoc();
215			if (!$res) {
216				$pos = strpos($domain, '.');
217				if ($pos === false) {
218					if (!$subquery) {
219						$pkt->setFlag('rcode', Packet::RCODE_REFUSED); // We do not want to resolve you (won't recursive resolve)
220						$pkt->setFlag('ra', 0);
221					}
222					return;
223				}
224				$host .= ($host==''?'':'.').substr($domain, 0, $pos);
225				$domain = substr($domain, $pos + 1);
226				continue;
227			}
228
229			break;
230		}
231
232		$pkt->setFlag('ra', 0);
233		$this->IPC->callPort('DNSd::DbEngine::'.$this->sql->unique(), 'domainHit', array($domain), false); // do not wait for reply
234
235		$max_exp = null;
236		$zone = $res['zone'];
237		if ($res['pzc_zone']) {
238			if ($res['pzc_stamp'] > time()) {
239				$max_exp = $res['pzc_stamp'] - time();
240			} else {
241				$zone = $res['pzc_zone'];
242			}
243		}
244
245		$pkt->setDefaultDomain($domain);
246
247		$this->buildInternetQuestionReply($pkt, $host, $zone, $domain, $type, $subquery, $initial_query, $max_exp);
248	}
249
250	protected function makeResponse($row, $pkt) {
251		$atype = Type::stringToType($row['type']);
252		if (is_null($atype)) return NULL;
253
254		$answer = Type::factory($pkt, $atype);
255		switch($atype) {
256			case Type\RFC1035::TYPE_MX:
257				$answer->setValue(array('priority' => $row['mx_priority'], 'host' => $row['data']));
258				break;
259			case Type\RFC1035::TYPE_SOA:
260				$answer->setValue(array(
261					'mname' => $row['data'],
262					'rname' => $row['resp_person'],
263					'serial' => $row['serial'],
264					'refresh' => $row['refresh'],
265					'retry' => $row['retry'],
266					'expire' => $row['expire'],
267					'minimum' => $row['minimum'],
268				));
269				break;
270			case Type\RFC2782::TYPE_SRV:
271				$answer->setValue(array(
272					'priority' => $row['mx_priority'],
273					'host' => $row['data'],
274					'refresh' => $row['refresh'],
275					'retry' => $row['retry'],
276				));
277				break;
278			default:
279				$answer->setValue($row['data']);
280		}
281		if ($row['host']) $row['host'].='.';
282
283		return $answer;
284	}
285
286	protected function handleChaosQuestion($pkt, $name, $type) {
287		if ((strtolower($name) == 'version.dnsd.') && ($type == RFC1035::TYPE_TXT)) {
288			$txt = Type::factory($pkt, RFC1035::TYPE_TXT);
289			$txt->setValue('DNSd PHP daemon (PHP/'.PHP_VERSION.')');
290			$pkt->addAnswer('version.dnsd.', $txt, 0, self::DNS_CLASS_CH);
291
292			$auth = Type::factory($pkt, RFC1035::TYPE_NS);
293			$auth->setValue('version.dnsd.');
294			$pkt->addAuthority('version.dnsd.', $auth, 0, self::DNS_CLASS_CH);
295			return;
296		}
297	}
298
299	public function handlePacket($data, $peer_info) {
300		$pkt = new $this->packet_class($peer_info);
301		if (!$pkt->decode($data)) return;
302
303		$pkt->resetAnswer();
304
305		foreach($pkt->getQuestions() as $question) {
306			$this->handleQuestion($pkt, $question);
307		}
308
309		$pkt->setFlag('qr', 1);
310
311		$pkt = $pkt->encode();
312		//$test = new $this->packet_class();
313		//$test->decode($pkt);
314		//var_dump($test);
315
316		if (!is_null($pkt)) $this->parent->sendReply($pkt, $peer_info);
317	}
318}
319