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