update copyright year
[GitHub/Stricted/Domain-Control-Panel.git] / lib / util / ParseZone.class.php
CommitLineData
2aa91ff2 1<?php
e7216dc0 2namespace 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>
d4779364 7 * @copyright 2015-2016 Jan Altensen (Stricted)
2aa91ff2
S
8 */
9class 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}