Commit | Line | Data |
---|---|---|
2aa91ff2 | 1 | <?php |
e7216dc0 | 2 | namespace dns\util; |
2aa91ff2 S |
3 | |
4 | /** | |
5 | * @author Jan Altensen (Stricted) | |
6 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> | |
7 | * @copyright 2015 Jan Altensen (Stricted) | |
8 | */ | |
9 | class ParseZone { | |
10 | /** | |
11 | * domain name | |
12 | * | |
13 | * var string | |
14 | */ | |
15 | private $origin = ''; | |
16 | ||
17 | /** | |
18 | * lines of zone file | |
19 | * | |
20 | * var array | |
21 | */ | |
22 | private $lines = array(); | |
23 | ||
24 | /** | |
25 | * global ttl | |
26 | * | |
27 | * var integer | |
28 | */ | |
29 | private $ttl = 0; | |
30 | ||
31 | /** | |
32 | * parsed soa record | |
33 | * | |
34 | * var array | |
35 | */ | |
36 | private $soa = array(); | |
37 | ||
38 | /** | |
39 | * parsed resource records | |
40 | * | |
41 | * var array | |
42 | */ | |
43 | private $records = array(); | |
44 | ||
45 | /** | |
46 | * init this class | |
47 | * | |
48 | * param string $file | |
49 | * param string $origin | |
50 | */ | |
51 | public function __construct ($file, $origin = "") { | |
52 | if (!empty($origin)) $this->origin = $origin; | |
8e9fc9e0 S |
53 | |
54 | // unify newlines | |
55 | $file = preg_replace("/(\r\n)|(\r)/", "\n", $file); | |
56 | ||
2aa91ff2 S |
57 | // unify all lines |
58 | $file = preg_replace_callback('/(\([^()]*\))/', function ($matches) { | |
59 | $a = explode("\n", $matches[0]); | |
60 | $b = array(); | |
61 | foreach ($a as $line) { | |
62 | ||
63 | // unify whitespaces | |
64 | $line = preg_replace('/\s+/', ' ', $line); | |
65 | ||
66 | // strip comments | |
67 | $line = preg_replace('/(\s+)?(;|#)([\s\S]+)?/i', '', $line); | |
68 | $b[] = $line; | |
69 | } | |
70 | $line = implode("", $b); | |
71 | ||
72 | return str_replace(array("( ", "(", " )", ")"), "", $line); | |
73 | }, $file); | |
74 | ||
75 | $this->lines = explode("\n", $file); | |
76 | ||
77 | /* | |
78 | * limit lines to 200, if more is needed we can change it | |
79 | */ | |
80 | if (count($this->lines) > 200) { | |
81 | throw new \Exception('zone file to big for parsing'); | |
82 | } | |
83 | } | |
84 | ||
85 | /** | |
86 | * parse zone file | |
87 | */ | |
88 | public function parse () { | |
89 | foreach ($this->lines as $line) { | |
90 | // unify whitespaces | |
91 | $line = preg_replace('/\s+/', ' ', $line); | |
92 | ||
93 | // strip comments | |
94 | $line = preg_replace('/(\s+)?(;|#)([\s\S]+)?/i', '', $line); | |
95 | ||
96 | /* ignore these lines */ | |
97 | if (empty($line)) continue; | |
98 | if (strpos($line, "RRSIG") !== false) continue; | |
99 | if (strpos($line, "NSEC") !== false) continue; | |
100 | if (strpos($line, "DNSKEY") !== false) continue; | |
101 | if (strpos($line, "SPF") !== false) continue; | |
102 | ||
103 | $this->parseORIGIN($line); | |
104 | $this->parseTTL($line); | |
105 | ||
106 | if (strpos($line, "SOA") !== false) { | |
107 | $this->parseSOA($line); | |
108 | continue; | |
109 | } | |
110 | ||
111 | // parse all other records | |
112 | $this->parseRR($line); | |
113 | } | |
114 | } | |
115 | ||
116 | /** | |
117 | * parse ORIGIN | |
118 | * | |
119 | * param string $line | |
120 | */ | |
121 | public function parseORIGIN ($line) { | |
122 | if (preg_match('/\$ORIGIN ([*-a-z0-9.]+)/i', $line, $match)) { | |
123 | $origin = $match[1]; | |
124 | if (empty($this->origin)) { | |
125 | $this->origin = $origin; | |
126 | } | |
127 | else { | |
128 | if ($this->origin != $origin) { | |
129 | throw new \Exception('parse error'); | |
130 | } | |
131 | } | |
132 | } | |
133 | } | |
134 | ||
135 | /** | |
136 | * parse TTL | |
137 | * | |
138 | * param string $line | |
139 | */ | |
140 | public function parseTTL ($line) { | |
141 | if (preg_match('/\$TTL ([0-9]+)([a-z]+)?/i', $line, $match)) { | |
142 | if (isset($match[2])) { | |
143 | $this->ttl = $this->formatTime($match[1], $match[2]); | |
144 | } | |
145 | else { | |
146 | $this->ttl = $match[1]; | |
147 | } | |
148 | } | |
149 | } | |
150 | ||
151 | /** | |
152 | * parse RR | |
153 | * | |
154 | * param string $line | |
155 | */ | |
156 | public function parseRR ($line) { | |
b8e7e273 | 157 | if (preg_match("/([*-a-z0-9.]+)? ([0-9]+)?(?: )?(IN)?(?: )?([a-z]+) ([\s\S]+)/i", $line, $matches)) { |
2aa91ff2 S |
158 | $record=array(); |
159 | // parse domain name | |
160 | if (!empty($this->origin) && $matches[1] == "@") { | |
161 | $record['name'] = $this->origin; | |
162 | } | |
163 | else { | |
164 | if (empty($matches[1])) { | |
165 | $record['name'] = $this->origin; | |
166 | } | |
167 | else { | |
168 | $record['name'] = $matches[1]; | |
169 | } | |
170 | } | |
171 | ||
172 | // parse ttl | |
173 | if (empty($matches[2])) { | |
174 | $record['ttl'] = $this->ttl; | |
175 | } | |
176 | else { | |
177 | $record['ttl'] = $matches[2]; | |
178 | } | |
179 | ||
180 | // parse type | |
181 | $record['type'] = $matches[4]; | |
182 | if ($matches[4] == 'MX' || $matches[4] == 'SRV' || $matches[4] == 'DS') { | |
183 | $exp = explode(' ', $matches[5], 2); | |
184 | $record['aux'] = $exp[0]; | |
185 | $record['data'] = $exp[1]; | |
186 | } | |
187 | else { | |
188 | $record['aux'] = 0; | |
189 | $record['data'] = $matches[5]; | |
190 | } | |
191 | ||
192 | // parse data | |
193 | if (strpos($record['data'], "@") !== false && !empty($this->origin)) { | |
194 | $record['data'] = str_replace("@", $this->origin, $record['data']); | |
195 | } | |
196 | ||
197 | $this->records[] = $record; | |
198 | } | |
199 | } | |
200 | ||
201 | /** | |
202 | * parse SOA | |
203 | * | |
204 | * param string $line | |
205 | */ | |
206 | public function parseSOA ($line) { | |
207 | if (preg_match("/([@a-z0-9.-]+) ([0-9]+)?([a-z]+)?(?: )?(IN)?(?: )?(?:[a-z]+) ([-a-z0-9.]+) ([@-a-z0-9.]+) ([0-9a-]+) ([0-9]+)([a-z]+)? ([0-9]+)([a-z]+)? ([0-9]+)([a-z]+)? ([0-9]+)([a-z]+)?/i", $line, $matches)) { | |
208 | // set domain name | |
209 | if ($matches[1] == "@") { | |
210 | if (empty($this->origin)) { | |
211 | throw new \Exception('parse error'); | |
212 | } | |
213 | } | |
214 | else { | |
215 | if (empty($this->origin)) { | |
216 | if (empty($matches[1])) { | |
217 | throw new \Exception('parse error'); | |
218 | } | |
219 | else { | |
220 | $this->origin = $matches[1]; | |
221 | } | |
222 | } | |
223 | else { | |
224 | if ($this->origin != $matches[1]) { | |
225 | throw new \Exception('parse error'); | |
226 | } | |
227 | } | |
228 | } | |
229 | ||
230 | $this->soa['origin'] = $this->origin; | |
231 | $this->soa['ns'] = $matches[5]; | |
232 | ||
233 | // replace @ with . | |
234 | if (strpos($matches[6], "@") !== false) { | |
235 | $this->soa['mbox'] = str_replace("@", ".", $matches[6]); | |
236 | } | |
237 | else { | |
238 | $this->soa['mbox'] = $matches[6]; | |
239 | } | |
240 | ||
241 | $this->soa['serial'] = $matches[7]; | |
242 | ||
243 | // parse refresh | |
244 | if (isset($matches[9]) && !empty($matches[9])) { | |
245 | $this->soa['refresh'] = $this->formatTime($matches[8], $matches[9]); | |
246 | } | |
247 | else { | |
248 | $this->soa['refresh'] = $matches[8]; | |
249 | } | |
250 | ||
251 | // parse retry | |
252 | if (isset($matches[11]) && !empty($matches[11])) { | |
253 | $this->soa['retry'] = $this->formatTime($matches[10], $matches[11]); | |
254 | } | |
255 | else { | |
256 | $this->soa['retry'] = $matches[10]; | |
257 | } | |
258 | ||
259 | // parse expire | |
260 | if (isset($matches[13]) && !empty($matches[13])) { | |
261 | $this->soa['expire'] = $this->formatTime($matches[12], $matches[13]); | |
262 | } | |
263 | else { | |
264 | $this->soa['expire'] = $matches[12]; | |
265 | } | |
266 | ||
267 | // parse minimum and ttl | |
268 | if (isset($matches[3]) && !empty($matches[3]) && $matches[3] != "IN" && $matches[3] != "SO") { | |
269 | $this->soa['minimum'] = $this->formatTime($matches[2], $matches[3]); | |
270 | $this->soa['ttl'] = $this->formatTime($matches[2], $matches[3]); | |
271 | } | |
272 | else { | |
273 | if (!empty($matches[2])) { | |
274 | $this->soa['minimum'] = $matches[2]; | |
275 | $this->soa['ttl'] = $matches[2]; | |
daff88c0 | 276 | $this->ttl = $this->soa['ttl']; |
2aa91ff2 S |
277 | } |
278 | else { | |
279 | $this->soa['minimum'] = $this->ttl; | |
280 | $this->soa['ttl'] = $this->ttl; | |
281 | } | |
282 | } | |
283 | } | |
284 | } | |
285 | ||
286 | /** | |
287 | * returns the parsed zone file | |
288 | * | |
289 | * @return array | |
290 | */ | |
291 | public function getParsedData () { | |
292 | return array('soa' => $this->soa, 'rr' => $this->records); | |
293 | } | |
294 | ||
295 | /** | |
296 | * format ttl to seconds | |
297 | * | |
298 | * @param integer $time | |
299 | * @param string $modifier | |
300 | * @return integer | |
301 | */ | |
302 | public function formatTime ($time, $modifier = '') { | |
303 | if (!empty($modifier)) { | |
304 | switch($modifier) { | |
305 | case "y": | |
306 | case "Y": | |
307 | $multiplier=86400*365; | |
308 | break; | |
309 | case 'w': | |
310 | case 'W': | |
311 | $multiplier=86400*7; | |
312 | break; | |
313 | case "d": | |
314 | case "D": | |
315 | $multiplier=86400; | |
316 | break; | |
317 | case "h": | |
318 | case "H": | |
319 | $multiplier=3600; | |
320 | break; | |
321 | case "m": | |
322 | case "M": | |
323 | $multiplier=60; | |
324 | break; | |
325 | case "s": | |
326 | case "S": | |
327 | default: | |
328 | $multiplier=1; | |
329 | break; | |
330 | } | |
331 | ||
332 | return $time * $multiplier; | |
333 | } | |
334 | else { | |
335 | return $time; | |
336 | } | |
337 | } | |
338 | } |