Mercurial > hg > marc_php
comparison marns.php @ 1:caa68b502313 draft
Added the MARC DNS server (and small fixes in marcus and anoclaims)
author | Ivo Smits <Ivo@UCIS.nl> |
---|---|
date | Thu, 13 Nov 2014 17:22:12 +0100 |
parents | |
children | f6954b464d2f |
comparison
equal
deleted
inserted
replaced
0:3ac7bd7495fd | 1:caa68b502313 |
---|---|
1 <?php | |
2 require_once './marccore.php'; | |
3 | |
4 $dns_opcode = array(0 => 'QUERY', 1 => 'IQUERY', 2 => 'STATUS'); | |
5 $dns_responsecode = array(0 => 'NOERROR', 1 => 'FORMERR', 2 => 'SERVFAIL', 3 => 'NXDOMAIN', 4 => 'NOTIMPL', 5 => 'REFUSED'); | |
6 $dns_rr_class = array(1 => 'IN', 2 => 'CS', 3 => 'CH', 4 => 'HS', 255 => 'ANY'); | |
7 $dns_rr_type = array(1 => 'A', 2 => 'NS', 5 => 'CNAME', 6 => 'SOA', 10 => 'NULL', 12 => 'PTR', 15 => 'MX', 16 => 'TXT', 28 => 'AAAA', 252 => 'AXFR', 255 => 'ANY'); | |
8 $dns_opcode_rev = array_flip($dns_opcode); | |
9 $dns_responsecode_rev = array_flip($dns_responsecode); | |
10 $dns_rr_class_rev = array_flip($dns_rr_class); | |
11 $dns_rr_type_rev = array_flip($dns_rr_type); | |
12 | |
13 error_reporting(E_ALL); | |
14 | |
15 if (!isset($argv)) $argv = $_SERVER['argv']; | |
16 if (count($argv) < 3) { | |
17 echo "Usage: ".$argv[0]." [marc-flatfile-database-filename] [listen-ip-address] [listen-port-number]\n"; | |
18 die(); | |
19 } | |
20 | |
21 $dbfile = $argv[1]; | |
22 $listenaddress = $argv[2]; | |
23 $listenport = $argv[3]; | |
24 | |
25 $database = new MARCDatabaseFlatFile(); | |
26 | |
27 function dbreload() { | |
28 global $database, $dbfile; | |
29 $database->Open($dbfile); | |
30 $database->Close(); | |
31 } | |
32 | |
33 dbreload(); | |
34 | |
35 $socket = socket_create(strpos($listenaddress, ':') === FALSE ? AF_INET : AF_INET6, SOCK_DGRAM, SOL_UDP); | |
36 socket_bind($socket, $listenaddress, $listenport); | |
37 | |
38 while (true) { | |
39 $buffer = $remoteaddr = $remoteport = NULL; | |
40 $readlen = socket_recvfrom($socket, $buffer, 0xFFFF, 0, $remoteaddr, $remoteport); | |
41 if ($readlen === FALSE) continue; | |
42 $buffer = dns_process($buffer, $remoteaddr); | |
43 if ($buffer === FALSE) continue; | |
44 socket_sendto($socket, $buffer, strlen($buffer), 0, $remoteaddr, $remoteport); | |
45 } | |
46 | |
47 function dns_process($reqbuffer, $remoteaddr) { | |
48 $req = dns_decode($reqbuffer); | |
49 if ($req['isresponse']) return; | |
50 $res = array('id' => $req['id'], 'isresponse' => true, 'opcode' => $req['opcode'], 'question' => $req['question'], 'responsecode' => 'NOERROR', 'answer' => array(), 'authority' => array(), 'additional' => array()); | |
51 if ($req['opcode'] != 'QUERY') { | |
52 $res['responsecode'] = 'NOTIMPL'; | |
53 } else { | |
54 foreach ($req['question'] as $q) { | |
55 if ($q['class'] == 'IN') { | |
56 if (!dns_get_response($res, $q)) $res['responsecode'] = 'NXDOMAIN'; | |
57 } else if ($q['class'] == 'CH' && $q['type'] == 'TXT') { | |
58 switch ($q['name']) { | |
59 case 'version.bind': $res['answer'][] = array('name' => $q['name'], 'class' => $q['class'], 'type' => $q['type'], 'value' => 'UCIS MARNS - http://www.ucis.nl'); break; | |
60 case 'reload.marns': | |
61 if ($remoteaddr == '127.0.0.1' || $remoteaddr == '::1' || $remoteaddr == $GLOBALS['listenaddress']) { | |
62 dbreload(); | |
63 $res['answer'][] = array('name' => $q['name'], 'class' => $q['class'], 'type' => $q['type'], 'value' => 'RELOAD OK'); | |
64 } else { | |
65 $res['responsecode'] = 'REFUSED'; | |
66 } | |
67 break; | |
68 default: $res['responsecode'] = 'NOTIMPL'; break; | |
69 } | |
70 } else { | |
71 $res['responsecode'] = 'NOTIMPL'; | |
72 } | |
73 } | |
74 } | |
75 return dns_encode($res); | |
76 } | |
77 | |
78 function dns_find_zone(&$name, &$zone) { | |
79 global $database; | |
80 $zone = strtolower(rtrim($name, '.')); | |
81 $name = ''; | |
82 while (strlen($zone)) { | |
83 $dbzone = $database->GetResource(chr(4).$zone); | |
84 if ($dbzone != NULL) return $dbzone; | |
85 $i = strpos($zone, '.'); | |
86 if ($i === FALSE) break; | |
87 if (strlen($name)) $name .= '.'; | |
88 $name .= substr($zone, 0, $i); | |
89 $zone = substr($zone, $i + 1); | |
90 } | |
91 return NULL; | |
92 } | |
93 function dns_convert_record($item, $realname, $zonename) { | |
94 if (isset($item['class']) && strcasecmp($item['class'], 'IN')) return FALSE; | |
95 if (!is_array($item) || !isset($item['type'])) return FALSE; | |
96 if (strlen($realname)) $realname .= '.'; | |
97 $realname .= $zonename; | |
98 $type = strtoupper($item['type']); | |
99 $rr = array('name' => $realname, 'type' => $type); | |
100 if (($type == 'A' || $type == 'AAAA') && isset($item['target'])) $rr['address'] = $item['target']; | |
101 elseif (($type == 'CNAME' || $type == 'NS' || $type == 'PTR') && isset($item['target'])) $rr['target'] = $item['target']; | |
102 elseif ($type == 'MX' && isset($item['target'])) { $rr['target'] = $item['target']; if (isset($item['priority'])) $rr['priority'] = $item['priority']; } | |
103 else return FALSE; | |
104 if (isset($rr['target'])) { | |
105 if (!strlen($rr['target'])) $rr['target'] = $zonename; | |
106 elseif ($rr['target'][strlen($rr['target']) - 1] != '.') $rr['target'] .= '.'.$zonename; | |
107 } | |
108 $rr['ttl'] = isset($item['ttl']) ? $item['ttl'] : 3600; | |
109 return $rr; | |
110 } | |
111 function dns_find_records_real(&$response, $dbzone, $findname, $realname, $zonename, $qtype) { | |
112 if (!isset($dbzone[$findname])) return FALSE; | |
113 foreach ($dbzone[$findname] as $item) { | |
114 if (!is_array($item) || !isset($item['type'])) continue; | |
115 if (isset($item['class']) && strcasecmp($item['class'], 'IN')) continue; | |
116 if (strcasecmp($item['type'], $qtype) && strcasecmp($qtype, 'ANY') && strcasecmp($item['type'], 'CNAME')) continue; | |
117 $record = dns_convert_record($item, $realname, $zonename); | |
118 if ($record === FALSE) continue; | |
119 $response[] = $record; | |
120 } | |
121 return TRUE; | |
122 } | |
123 function dns_find_ns_records(&$res, $dbzone, $findname, $realname, $zonename) { | |
124 if (!isset($dbzone[$findname])) return FALSE; | |
125 $foundns = FALSE; | |
126 foreach ($dbzone[$findname] as $item) { | |
127 if (!is_array($item) || !isset($item['type'])) continue; | |
128 if (isset($item['class']) && strcasecmp($item['class'], 'IN')) continue; | |
129 if (strcasecmp($item['type'], 'NS')) continue; | |
130 $record = dns_convert_record($item, $realname, $zonename); | |
131 if ($record === FALSE) continue; | |
132 $res['authority'][] = $record; | |
133 if (strcasecmp(substr($record['target'], -strlen($zonename)), $zonename) == 0) dns_find_records_realwild($res['additional'], $dbzone, rtrim(substr($record['target'], 0, -strlen($zonename)), '.'), $zonename, 'ANY'); | |
134 $foundns = TRUE; | |
135 } | |
136 return $foundns; | |
137 } | |
138 function dns_find_records_realwild(&$res, $dbzone, $name, $zonename, $qtype) { | |
139 if (dns_find_records_real($res, $dbzone, $name, $name, $zonename, $qtype)) return TRUE; | |
140 $nameparts = strlen($name) ? explode('.', strtolower($name)) : array(); | |
141 for ($i = 1; $i < count($nameparts); $i++) { | |
142 if (dns_find_records_real($res, $dbzone, '*.'.implode('.', array_slice($nameparts, $i)), $name, $zonename, $qtype)) return TRUE; | |
143 } | |
144 if (dns_find_records_real($res, $dbzone, '*', $name, $zonename, $qtype)) return TRUE; | |
145 return FALSE; | |
146 } | |
147 function dns_find_records(&$res, $dbzone, $name, $zone, $qtype) { | |
148 if (!isset($dbzone->value['dnsrecords']) || !is_array($dbzone->value['dnsrecords'])) return FALSE; | |
149 $dbzone = $dbzone->value['dnsrecords']; | |
150 $nameparts = strlen($name) ? explode('.', strtolower($name)) : array(); | |
151 for ($i = count($nameparts) - 1; $i >= 0; $i--) { | |
152 $realname = implode('.', array_slice($nameparts, $i)); | |
153 if (dns_find_ns_records($res, $dbzone, $realname, $realname, $zone)) return TRUE; | |
154 if (dns_find_ns_records($res, $dbzone, '*.'.implode('.', array_slice($nameparts, $i + 1)), $realname, $zone)) return TRUE; | |
155 } | |
156 return dns_find_records_realwild($res['answer'], $dbzone, $name, $zone, $qtype); | |
157 } | |
158 function dns_find_authority(&$res, $dbzone, $zone, $nsquery = FALSE) { | |
159 if (!isset($dbzone->value['ns']) || !is_array($dbzone->value['ns'])) return; | |
160 foreach ($dbzone->value['ns'] as $nsname => $nsglues) { | |
161 $rr = array('name' => $zone, 'type' => 'NS', 'ttl' => 3600, 'target' => (strlen($nsname) && $nsname[strlen($nsname) - 1] != '.') ? $nsname.'.'.$zone : $nsname); | |
162 if ($nsquery) $res['answer'][] = $rr; | |
163 else $res['authority'][] = $rr; | |
164 if (is_array($nsglues)) { | |
165 foreach ($nsglues as $glue) { | |
166 $glue = inet_pton($glue); | |
167 if (!strlen($glue)) continue; | |
168 $res['additional'][] = array('name' => $rr['target'], 'ttl' => 3600, 'type' => strlen($glue) == 4 ? 'A' : 'AAAA', 'data' => $glue); | |
169 } | |
170 } | |
171 } | |
172 } | |
173 | |
174 function dns_get_response(&$res, $q) { | |
175 $name = $q['name']; | |
176 $zone = ''; | |
177 $dbzone = dns_find_zone($name, $zone); | |
178 if ($dbzone == NULL) return; | |
179 dns_find_records($res, $dbzone, $name, $zone, $q['type']); | |
180 //dns_find_authority($res, $dbzone, $zone, $q['type'] == 'NS'); | |
181 } | |
182 | |
183 function dns_decode_uint16be($data, $i = 0) { return (ord($data[$i+0]) << 8) | ord($data[$i+1]); } | |
184 function dns_decode_uint32be($data, $i = 0) { return (ord($data[$i]) << 24) | (ord($data[$i+1]) << 16) | (ord($data[$i+2]) << 8) | ord($data[$i+3]); } | |
185 function dns_encode_uint32be($v) { return pack("N", $v); } | |
186 function dns_encode_uint16be($v) { return pack("n", $v); } | |
187 | |
188 function dns_decode($packet) { | |
189 global $dns_opcode, $dns_responsecode; | |
190 $dp = array( | |
191 'id' => dns_decode_uint16be($packet, 0), | |
192 'isresponse' => ($packet[2] & 128) != 0, | |
193 'opcode' => ($packet[2] >> 3) & 0x0F, | |
194 'authoritative' => ($packet[2] & 4) != 0, | |
195 'truncation' => ($packet[2] & 2) != 0, | |
196 'recursiondesired' => ($packet[2] & 1) != 0, | |
197 'recursionavailable' => ($packet[3] & 128) != 0, | |
198 'responsecode' => ($packet[3] & 0x0F), | |
199 'qdcount' => dns_decode_uint16be($packet, 4), | |
200 'ancount' => dns_decode_uint16be($packet, 6), | |
201 'nscount' => dns_decode_uint16be($packet, 8), | |
202 'arcount' => dns_decode_uint16be($packet, 10), | |
203 ); | |
204 if (isset($dns_opcode[$dp['opcode']])) $dp['opcode'] = $dns_opcode[$dp['opcode']]; | |
205 if (isset($dns_responsecode[$dp['responsecode']])) $dp['responsecode'] = $dns_responsecode[$dp['responsecode']]; | |
206 $ptr = 12; | |
207 $dp['question'] = dns_decode_rrs($packet, $ptr, false, $dp['qdcount']); | |
208 $dp['answer'] = dns_decode_rrs($packet, $ptr, true, $dp['ancount']); | |
209 $dp['authority'] = dns_decode_rrs($packet, $ptr, true, $dp['nscount']); | |
210 $dp['additional'] = dns_decode_rrs($packet, $ptr, true, $dp['arcount']); | |
211 return $dp; | |
212 } | |
213 function dns_decode_rrs($packet, &$ptr, $isanswer, $count) { | |
214 $ret = array(); | |
215 for ($i = 0; $i < $count; $i++) $ret[] = dns_decode_rr($packet, $ptr, $isanswer); | |
216 return $ret; | |
217 } | |
218 function dns_decode_rr($packet, &$ptr, $isanswer) { | |
219 global $dns_rr_class, $dns_rr_type; | |
220 $rr = array(); | |
221 $rr['name'] = dns_decode_name($packet, $ptr); | |
222 $rr['type'] = dns_decode_uint16be($packet, $ptr); $ptr += 2; | |
223 $rr['class'] = dns_decode_uint16be($packet, $ptr); $ptr += 2; | |
224 if (isset($dns_rr_class[$rr['class']])) $rr['class'] = $dns_rr_class[$rr['class']]; | |
225 if (isset($dns_rr_type[$rr['type']])) $rr['type'] = $dns_rr_type[$rr['type']]; | |
226 if ($isanswer) { | |
227 $rr['ttl'] = dns_decode_uint32be($packet, $ptr); $ptr += 4; | |
228 $rrdatalength = dns_decode_uint16be($packet, $ptr); $ptr += 2; | |
229 $rr['data'] = substr($packet, $ptr, $rrdatalength); | |
230 $dataptr = $ptr; | |
231 $ptr += $rrdatalength; | |
232 if ($rr['class'] == 'IN') { | |
233 switch ($rr['type']) { | |
234 case 'A': | |
235 case 'AAAA': | |
236 $rr['address'] = inet_ntop($rr['data']); | |
237 break; | |
238 case 'CNAME': | |
239 case 'NS': | |
240 case 'PTR': | |
241 $rr['target'] = dns_decode_name($packet, $dataptr); | |
242 break; | |
243 case 'MX': | |
244 $rr['priority'] = dns_decode_uint16be($packet, $dataptr); $dataptr += 2; | |
245 $rr['target'] = dns_decode_name($packet, $dataptr); | |
246 break; | |
247 case 'TXT': | |
248 $rr['value'] = dns_decode_name($packet, $dataptr); | |
249 break; | |
250 } | |
251 } | |
252 } | |
253 return $rr; | |
254 } | |
255 function dns_decode_name($packet, &$ptr) { | |
256 $name = ''; | |
257 $canrecurse = 64; | |
258 while ($ptr < strlen($packet)) { | |
259 $l = ord($packet[$ptr++]); | |
260 if ($l == 0) break; | |
261 if (($l & 0xC0) == 0) { | |
262 $name .= substr($packet, $ptr, $l).'.'; | |
263 $ptr += $l; | |
264 } else if (($l & 0xC0) == 0xC0 && $canrecurse > 0) { | |
265 $newptr = (($l & 0x3F) << 8) | ord($packet[$ptr++]); | |
266 $ptr = &$newptr; | |
267 $canrecurse--; | |
268 } else { | |
269 return FALSE; | |
270 } | |
271 } | |
272 return substr($name, 0, -1); | |
273 } | |
274 | |
275 function dns_encode($dp) { | |
276 global $dns_opcode_rev, $dns_responsecode_rev; | |
277 $data = ''; | |
278 $qdcount = isset($dp['question']) ? count($dp['question']) : 0; | |
279 $ancount = isset($dp['answer']) ? count($dp['answer']) : 0; | |
280 $nscount = isset($dp['authority']) ? count($dp['authority']) : 0; | |
281 $arcount = isset($dp['additional']) ? count($dp['additional']) : 0; | |
282 $opcode = isset($dp['opcode']) ? $dp['opcode'] : 0; | |
283 $responsecode = isset($dp['responsecode']) ? $dp['responsecode'] : 0; | |
284 if (isset($dns_opcode_rev[$opcode])) $opcode = $dns_opcode_rev[$opcode]; | |
285 if (isset($dns_responsecode_rev[$responsecode])) $responsecode = $dns_responsecode_rev[$responsecode]; | |
286 $isresponse = $ancount || $nscount || $arcount || $responsecode != 0 || (isset($dp['isresponse']) && $dp['isresponse']); | |
287 $data .= dns_encode_uint16be($dp['id']); | |
288 $data .= chr(($isresponse ? 128 : 0) | (($opcode & 0x0F) << 3) | ((isset($dp['authoritative']) && $dp['authoritative']) ? 4 : 0) | ((isset($dp['truncation']) && $dp['truncation']) ? 2 : 0) | ((isset($dp['recursiondesired']) && $dp['recursiondesired']) ? 1 : 0)); | |
289 $data .= chr(((isset($dp['recursionavailable']) && $dp['recursionavailable']) ? 128 : 0) | ($responsecode & 0xF)); | |
290 $data .= dns_encode_uint16be($qdcount); | |
291 $data .= dns_encode_uint16be($ancount); | |
292 $data .= dns_encode_uint16be($nscount); | |
293 $data .= dns_encode_uint16be($arcount); | |
294 $labels = array(); | |
295 if ($qdcount) dns_encode_rrs($data, $dp['question'], false, $labels); | |
296 if ($ancount) dns_encode_rrs($data, $dp['answer'], true, $labels); | |
297 if ($nscount) dns_encode_rrs($data, $dp['authority'], true, $labels); | |
298 if ($arcount) dns_encode_rrs($data, $dp['additional'], true, $labels); | |
299 return $data; | |
300 } | |
301 function dns_encode_rrs(&$data, $rrs, $isanswer, &$labels) { | |
302 global $dns_rr_type_rev, $dns_rr_class_rev; | |
303 foreach ($rrs as $rr) { | |
304 $rrtype = $rr['type']; | |
305 $rrclass = isset($rr['class']) ? $rr['class'] : 1; | |
306 if (isset($dns_rr_type_rev[$rrtype])) $rrtype = $dns_rr_type_rev[$rrtype]; | |
307 if (isset($dns_rr_class_rev[$rrclass])) $rrclass = $dns_rr_class_rev[$rrclass]; | |
308 $data .= dns_encode_name($rr['name'], $labels, strlen($data)); | |
309 $data .= dns_encode_uint16be($rrtype); | |
310 $data .= dns_encode_uint16be($rrclass); | |
311 if ($isanswer) { | |
312 $rrdata = isset($rr['data']) ? $rr['data'] : ''; | |
313 if (($rrtype == 5 || $rrtype == 2 || $rrtype == 12) && isset($rr['target'])) $rrdata = dns_encode_name($rr['target'], $labels, strlen($data) + 6); | |
314 elseif (($rrtype == 16) && isset($rr['value'])) $rrdata = dns_encode_txt($rr['value']); | |
315 elseif ($rrtype == 15 && isset($rr['target'])) $rrdata = dns_encode_uint16be(isset($rr['priority']) ? $rr['priority'] : 0).dns_encode_name($rr['target'], $labels, strlen($data) + 8); | |
316 elseif ($rrclass == 1) { | |
317 if (($rrtype == 1 || $rrtype == 28) && isset($rr['address'])) $rrdata = inet_pton($rr['address']); | |
318 } | |
319 $rrdata = (string)$rrdata; | |
320 $data .= dns_encode_uint32be(isset($rr['ttl']) ? $rr['ttl'] : 0); | |
321 $data .= dns_encode_uint16be(strlen($rrdata)); | |
322 $data .= $rrdata; | |
323 } | |
324 } | |
325 } | |
326 function dns_encode_name($name, &$labels, $offset = FALSE) { | |
327 $name = rtrim($name, '.'); | |
328 $data = ''; | |
329 while (strlen($name)) { | |
330 $lname = strtolower($name); | |
331 if (isset($labels[$lname])) { | |
332 $ptr = $labels[$lname]; | |
333 return $data.chr(0xC0 | (($ptr >> 8) & 0x3F)).chr($ptr & 0xFF); | |
334 } else { | |
335 $dot = strpos($name, '.'); | |
336 if ($dot === FALSE) { | |
337 $part = $name; | |
338 $name = ''; | |
339 } else { | |
340 $part = substr($name, 0, $dot); | |
341 $name = substr($name, $dot + 1); | |
342 } | |
343 if (strlen($part) > 63) $part = substr($part, 0, 63); | |
344 if ($offset !== FALSE && $offset + strlen($data) <= 0x3FFF) $labels[$lname] = strlen($data) + $offset; | |
345 $data .= chr(strlen($part)).$part; | |
346 } | |
347 } | |
348 return $data.chr(0); | |
349 } | |
350 function dns_encode_txt($parts) { | |
351 $data = ''; | |
352 if (!is_array($parts)) $parts = array($parts); | |
353 foreach ($parts as $part) { | |
354 $part = (string)$part; | |
355 if (strlen($part) > 63) $part = substr($part, 0, 63); | |
356 $data .= chr(strlen($part)).$part; | |
357 } | |
358 return $data; | |
359 } | |
360 |