Completed
Push — master ( af12eb...40e48e )
by Sam
02:10
created

Validator::countResourceRecords()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.016

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 9
cts 10
cp 0.9
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 2
crap 4.016
1
<?php
2
3
/*
4
 * This file is part of Badcow DNS Library.
5
 *
6
 * (c) Samuel Williams <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Badcow\DNS;
13
14
use Badcow\DNS\Rdata\NsRdata;
15
use Badcow\DNS\Rdata\SoaRdata;
16
17
class Validator
18
{
19
    const ZONE_OKAY = 0;
20
21
    const ZONE_NO_SOA = 1;
22
23
    const ZONE_TOO_MANY_SOA = 2;
24
25
    const ZONE_NO_NS = 4;
26
27
    const ZONE_NO_CLASS = 8;
28
29
    const ZONE_TOO_MANY_CLASSES = 16;
30
31
    /**
32
     * Validates if $string is suitable as an RR name.
33
     *
34
     * @param string $string
35
     * @param bool   $mustHaveTrailingDot
36
     *
37
     * @return bool
38
     */
39 27
    public static function rrName($string, $mustHaveTrailingDot = false)
40
    {
41 27
        if ($string === '@' ||
42 27
            self::reverseIpv4($string) ||
43 27
            self::reverseIpv6($string)
44 18
        ) {
45
            return true;
46
        }
47
48 27
        if ($string === '*.') {
49 3
            return false;
50
        }
51
52 27
        $parts = explode('.', strtolower($string));
53
54 27
        if ('' !== end($parts) && $mustHaveTrailingDot) {
55 3
            return false;
56
        }
57
58 27
        if ('' === end($parts)) {
59 24
            array_pop($parts);
60 16
        }
61
62 27
        foreach ($parts as $i => $part) {
63
            //Does the string begin with a non alphanumeric char?
64 27
            if (1 === preg_match('/^[^a-z0-9]/', $part)) {
65 3
                if ('*' === $part && 0 === $i) {
66 3
                    continue;
67
                }
68
69 3
                return false;
70
            }
71
72 27
            if (1 !== preg_match('/^[a-z0-9_\-]+$/i', $part)) {
73 9
                return false;
74
            }
75 18
        }
76
77 27
        return true;
78
    }
79
80
    /**
81
     * Validate the string as a Fully Qualified Domain Name.
82
     *
83
     * @param string $string
84
     *
85
     * @return bool
86
     */
87 3
    public static function fqdn($string)
88
    {
89 3
        $parts = explode('.', strtolower($string));
90
91
        //Is there are trailing dot?
92 3
        if ('' !== end($parts)) {
93 3
            return false;
94
        }
95
96 3
        array_pop($parts);
97
98 3
        foreach ($parts as $part) {
99
            //Does the string begin with a non alpha char?
100 3
            if (1 === preg_match('/^[^a-z]/i', $part)) {
101 3
                return false;
102
            }
103
104 3
            if (1 !== preg_match('/^[a-z0-9_\-]+$/i', $part)) {
105 3
                return false;
106
            }
107 2
        }
108
109 3
        return true;
110
    }
111
112
    /**
113
     * @param string $string
114
     * @param bool   $trailingDot Require trailing dot
115
     *
116
     * @return bool
117
     */
118 69
    public static function validateFqdn($string, $trailingDot = true)
119
    {
120 69
        if ($string === '@') {
121 3
            return true;
122
        }
123
124 69
        if ($string === '*.') {
125 3
            return false;
126
        }
127
128 69
        $parts = explode('.', strtolower($string));
129 69
        $hasTrailingDot = (end($parts) === '');
130
131 69
        if ($trailingDot && !$hasTrailingDot) {
132 12
            return false;
133
        }
134
135 60
        if ($hasTrailingDot) {
136 60
            array_pop($parts);
137 40
        }
138
139 60
        foreach ($parts as $i => $part) {
140
            //Does the string begin with a non alpha char?
141 60
            if (1 === preg_match('/^[^a-z]/', $part)) {
142 9
                if ('*' === $part && 0 === $i) {
143 3
                    continue;
144
                }
145
146 9
                return false;
147
            }
148
149 60
            if (1 !== preg_match('/^[a-z0-9_\-]+$/', $part)) {
150 22
                return false;
151
            }
152 40
        }
153
154 60
        return true;
155
    }
156
157
    /**
158
     * Validates an IPv4 Address.
159
     *
160
     * @static
161
     *
162
     * @param string $ipAddress
163
     *
164
     * @return bool
165
     */
166 36
    public static function validateIpv4Address($ipAddress)
167
    {
168 36
        return (bool) filter_var($ipAddress, FILTER_VALIDATE_IP, [
169 36
            'flags' => FILTER_FLAG_IPV4,
170 24
        ]);
171
    }
172
173
    /**
174
     * Validates an IPv6 Address.
175
     *
176
     * @static
177
     *
178
     * @param string $ipAddress
179
     *
180
     * @return bool
181
     */
182 39
    public static function validateIpv6Address($ipAddress)
183
    {
184 39
        return (bool) filter_var($ipAddress, FILTER_VALIDATE_IP, [
185 39
            'flags' => FILTER_FLAG_IPV6,
186 26
        ]);
187
    }
188
189
    /**
190
     * Validates an IPv4 or IPv6 address.
191
     *
192
     * @static
193
     *
194
     * @param $ipAddress
195
     *
196
     * @return bool
197
     */
198 3
    public static function validateIpAddress($ipAddress)
199
    {
200 3
        return (bool) filter_var($ipAddress, FILTER_VALIDATE_IP);
201
    }
202
203
    /**
204
     * Validates a zone file.
205
     *
206
     * @param string $zonename
207
     * @param string $directory
208
     * @param string $named_checkzonePath
209
     *
210
     * @return bool
211
     */
212
    public static function validateZoneFile($zonename, $directory, $named_checkzonePath = 'named-checkzone')
213
    {
214
        $command = sprintf('%s -q %s %s', $named_checkzonePath, $zonename, $directory);
215
        exec($command, $output, $exit_status);
216
217
        return $exit_status === 0;
218
    }
219
220
    /**
221
     * Validates that the zone meets
222
     * RFC-1035 especially that:
223
     *   1) 5.2.1 All RRs in the file should be of the same class.
224
     *   2) 5.2.2 Exactly one SOA RR should be present at the top of the zone.
225
     *   3) There is at least one NS record.
226
     *
227
     * @deprecated
228
     * @param ZoneInterface $zone
229
     *
230
     * @throws ZoneException
231
     *
232
     * @return bool
233
     */
234 12
    public static function validate(ZoneInterface $zone)
235
    {
236 12
        $n_soa = self::countResourceRecords($zone, SoaRdata::TYPE);
237 12
        $n_ns = self::countResourceRecords($zone, NsRdata::TYPE);
238 12
        $classes = [];
239
240 12
        foreach ($zone->getResourceRecords() as $rr) {
241 12
            if (null !== $rr->getClass()) {
242 12
                $classes[$rr->getClass()] = null;
243 8
            }
244 8
        }
245
246 12
        $n_class = count($classes);
247
248 12
        if (1 !== $n_soa) {
249 3
            throw new ZoneException(sprintf('There must be exactly one SOA record, %s given.', $n_soa));
250
        }
251
252 9
        if ($n_ns < 1) {
253 3
            throw new ZoneException(sprintf('There must be at least one NS record, %s given.', $n_ns));
254
        }
255
256 6
        if (1 !== $n_class) {
257 3
            throw new ZoneException(sprintf('There must be exactly one type of class, %s given.', $n_class));
258
        }
259
260 3
        return true;
261
    }
262
263
    /**
264
     * Validates that the zone meets
265
     * RFC-1035 especially that:
266
     *   1) 5.2.1 All RRs in the file should be of the same class.
267
     *   2) 5.2.2 Exactly one SOA RR should be present at the top of the zone.
268
     *   3) There is at least one NS record.
269
     *
270
     * Return values are:
271
     *   - ZONE_NO_SOA
272
     *   - ZONE_TOO_MANY_SOA
273
     *   - ZONE_NO_NS
274
     *   - ZONE_NO_CLASS
275
     *   - ZONE_TOO_MANY_CLASSES
276
     *   - ZONE_OKAY
277
     *
278
     * You SHOULD compare these return values to the defined constants of this
279
     * class rather than against integers directly.
280
     *
281
     * @param ZoneInterface $zone
282
     *
283
     * @return integer
284
     */
285 12
    public static function zone(ZoneInterface $zone)
286
    {
287 12
        $n_soa = self::countResourceRecords($zone, SoaRdata::TYPE);
288 12
        $n_ns = self::countResourceRecords($zone, NsRdata::TYPE);
289 12
        $classes = [];
290
291 12
        foreach ($zone->getResourceRecords() as $rr) {
292 12
            if (null !== $rr->getClass()) {
293 12
                $classes[$rr->getClass()] = null;
294 8
            }
295 8
        }
296
297 12
        $n_class = count($classes);
298
299 12
        if ($n_soa < 1) {
300
            return self::ZONE_NO_SOA;
301
        }
302
303 12
        if ($n_soa > 1) {
304 3
            return self::ZONE_TOO_MANY_SOA;
305
        }
306
307 9
        if ($n_ns < 1) {
308 3
            return self::ZONE_NO_NS;
309
        }
310
311 6
        if ($n_class < 1) {
312
            return self::ZONE_NO_CLASS;
313
        }
314
315 6
        if ($n_class > 1) {
316 3
            return self::ZONE_TOO_MANY_CLASSES;
317
        }
318
319 3
        return self::ZONE_OKAY;
320
    }
321
322
    /**
323
     * @param ZoneInterface $zone
324
     * @param null $type The ResourceRecord type to be counted. If NULL, then the method will return
325
     *                   the total number of resource records.
326
     *
327
     * @return int The number of records to be counted.
328
     */
329 15
    public static function countResourceRecords(ZoneInterface $zone, $type = null)
330
    {
331 15
        if (null === $type) {
332
            return count($zone->getResourceRecords());
333
        }
334
335 15
        $n = 0;
336
337 15
        foreach ($zone->getResourceRecords() as $rr) {
338
            /* @var $rr ResourceRecordInterface */
339 15
            if ($type === $rr->getRdata()->getType()) {
340 15
                $n += 1;
341 10
            }
342 10
        }
343
344 15
        return $n;
345
    }
346
347
    /**
348
     * @param string $address
349
     *
350
     * @return bool
351
     */
352 30
    public static function reverseIpv4($address)
353
    {
354 30
        $pattern = '/^((?:[0-9]+\.){1,4})in\-addr\.arpa\.$/i';
355
356 30
        if(1 !== preg_match($pattern, $address, $matches)) {
357 30
            return false;
358
        }
359
360 3
        $octets = explode('.', $matches[1]);
361 3
        array_pop($octets); //Remove the last decimal from the array
362
363 3
        foreach ($octets as $octet) {
364 3
            if ((int) $octet > 255) {
365 3
                return false;
366
            }
367 2
        }
368
369 3
        return true;
370
    }
371
372
    /**
373
     * @param string $address
374
     *
375
     * @return bool
376
     */
377 30
    public static function reverseIpv6($address)
378
    {
379 30
        $pattern = '/^(?:[0-9a-f]\.){1,32}ip6\.arpa\.$/i';
380
381 30
        return 1 === preg_match($pattern, $address);
382
    }
383
}
384