Completed
Push — master ( 1d13ba...f9bf31 )
by Sam
49:13
created

Validator::validate()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 28
ccs 17
cts 17
cp 1
rs 8.439
cc 6
eloc 15
nc 12
nop 1
crap 6
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\NS;
15
use Badcow\DNS\Rdata\SOA;
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 a trailing dot?
92 3
        if ('' !== end($parts)) {
93 3
            return false;
94
        }
95
96 3
        //Remove the empty string at the end of the array.
97
        array_pop($parts);
98 3
99
        foreach ($parts as $part) {
100 3
            //Does the string begin with a non alpha char?
101 3
            if (1 === preg_match('/^[^a-z]/i', $part)) {
102
                return false;
103
            }
104 3
105 3
            if (1 !== preg_match('/^[a-z0-9_\-]+$/i', $part)) {
106
                return false;
107 2
            }
108
        }
109 3
110
        return true;
111
    }
112
113
    /**
114
     * @param string $string
115
     * @param bool   $trailingDot Require trailing dot
116
     *
117
     * @return bool
118 72
     */
119
    public static function validateFqdn($string, $trailingDot = true)
120 72
    {
121 3
        if ($string === '@') {
122
            return true;
123
        }
124 72
125 3
        if ($string === '*.') {
126
            return false;
127
        }
128 72
129 72
        $parts = explode('.', strtolower($string));
130
        $hasTrailingDot = (end($parts) === '');
131 72
132 12
        if ($trailingDot && !$hasTrailingDot) {
133
            return false;
134
        }
135 63
136 63
        if ($hasTrailingDot) {
137 42
            array_pop($parts);
138
        }
139 63
140
        foreach ($parts as $i => $part) {
141 63
            //Does the string begin with a non alpha char?
142 9
            if (1 === preg_match('/^[^a-z]/', $part)) {
143 3
                if ('*' === $part && 0 === $i) {
144
                    continue;
145
                }
146 9
147
                return false;
148
            }
149 63
150 23
            if (1 !== preg_match('/^[a-z0-9_\-]+$/', $part)) {
151
                return false;
152 42
            }
153
        }
154 63
155
        return true;
156
    }
157
158
    /**
159
     * Validates an IPv4 Address.
160
     *
161
     * @static
162
     *
163
     * @param string $ipAddress
164
     *
165
     * @return bool
166 36
     */
167
    public static function validateIpv4Address($ipAddress)
168 36
    {
169 36
        return (bool) filter_var($ipAddress, FILTER_VALIDATE_IP, [
170 24
            'flags' => FILTER_FLAG_IPV4,
171
        ]);
172
    }
173
174
    /**
175
     * Validates an IPv6 Address.
176
     *
177
     * @static
178
     *
179
     * @param string $ipAddress
180
     *
181
     * @return bool
182 39
     */
183
    public static function validateIpv6Address($ipAddress)
184 39
    {
185 39
        return (bool) filter_var($ipAddress, FILTER_VALIDATE_IP, [
186 26
            'flags' => FILTER_FLAG_IPV6,
187
        ]);
188
    }
189
190
    /**
191
     * Validates an IPv4 or IPv6 address.
192
     *
193
     * @static
194
     *
195
     * @param $ipAddress
196
     *
197
     * @return bool
198 3
     */
199
    public static function validateIpAddress($ipAddress)
200 3
    {
201
        return (bool) filter_var($ipAddress, FILTER_VALIDATE_IP);
202
    }
203
204
    /**
205
     * Validates a zone file.
206
     *
207
     * @deprecated
208
     * @param string $zonename
209
     * @param string $directory
210
     * @param string $named_checkzonePath
211
     *
212
     * @return bool
213
     */
214
    public static function validateZoneFile($zonename, $directory, $named_checkzonePath = 'named-checkzone')
215
    {
216
        $command = sprintf('%s -q %s %s', $named_checkzonePath, $zonename, $directory);
217
        exec($command, $output, $exit_status);
218
219
        return $exit_status === 0;
220
    }
221
222
    /**
223
     * Validates that the zone meets
224
     * RFC-1035 especially that:
225
     *   1) 5.2.1 All RRs in the file should be of the same class.
226
     *   2) 5.2.2 Exactly one SOA RR should be present at the top of the zone.
227
     *   3) There is at least one NS record.
228
     *
229
     * Return values are:
230
     *   - ZONE_NO_SOA
231
     *   - ZONE_TOO_MANY_SOA
232
     *   - ZONE_NO_NS
233
     *   - ZONE_NO_CLASS
234
     *   - ZONE_TOO_MANY_CLASSES
235 12
     *   - ZONE_OKAY
236
     *
237 12
     * You SHOULD compare these return values to the defined constants of this
238 12
     * class rather than against integers directly.
239 12
     *
240
     * @param ZoneInterface $zone
241 12
     *
242 12
     * @return integer
243 12
     */
244 8
    public static function zone(ZoneInterface $zone)
245 8
    {
246
        $n_soa = self::countResourceRecords($zone, SOA::TYPE);
247 12
        $n_ns = self::countResourceRecords($zone, NS::TYPE);
248
        $classes = [];
249 12
250 3
        foreach ($zone->getResourceRecords() as $rr) {
251
            if (null !== $rr->getClass()) {
252
                $classes[$rr->getClass()] = null;
253 9
            }
254 3
        }
255
256
        $n_class = count($classes);
257 6
258 3
        if ($n_soa < 1) {
259
            return self::ZONE_NO_SOA;
260
        }
261 3
262
        if ($n_soa > 1) {
263
            return self::ZONE_TOO_MANY_SOA;
264
        }
265
266
        if ($n_ns < 1) {
267
            return self::ZONE_NO_NS;
268
        }
269
270
        if ($n_class < 1) {
271
            return self::ZONE_NO_CLASS;
272
        }
273
274
        if ($n_class > 1) {
275
            return self::ZONE_TOO_MANY_CLASSES;
276
        }
277
278
        return self::ZONE_OKAY;
279
    }
280
281
    /**
282
     * Counts the number of Resource Records of a particular type ($type) in a Zone.
283
     *
284
     * @param ZoneInterface $zone
285
     * @param null $type The ResourceRecord type to be counted. If NULL, then the method will return
286 12
     *                   the total number of resource records.
287
     *
288 12
     * @return int The number of records to be counted.
289 12
     */
290 12
    public static function countResourceRecords(ZoneInterface $zone, $type = null)
291
    {
292 12
        if (null === $type) {
293 12
            return count($zone->getResourceRecords());
294 12
        }
295 8
296 8
        $n = 0;
297
298 12
        foreach ($zone->getResourceRecords() as $rr) {
299
            /* @var $rr ResourceRecordInterface */
300 12
            if ($type === $rr->getRdata()->getType()) {
301
                $n += 1;
302
            }
303
        }
304 12
305 3
        return $n;
306
    }
307
308 9
    /**
309 3
     * Validates a reverse IPv4 address. Ensures that all octets are in the range [0-255].
310
     *
311
     * @param string $address
312 6
     *
313
     * @return bool
314
     */
315
    public static function reverseIpv4($address)
316 6
    {
317 3
        $pattern = '/^((?:[0-9]+\.){1,4})in\-addr\.arpa\.$/i';
318
319
        if(1 !== preg_match($pattern, $address, $matches)) {
320 3
            return false;
321
        }
322
323
        $octets = explode('.', $matches[1]);
324
        array_pop($octets); //Remove the last decimal from the array
325
326
        foreach ($octets as $octet) {
327
            if ((int) $octet > 255) {
328
                return false;
329
            }
330
        }
331
332 15
        return true;
333
    }
334 15
335
    /**
336
     * Validates a reverse IPv6 address.
337
     *
338 15
     * @param string $address
339
     *
340 15
     * @return bool
341
     */
342 15
    public static function reverseIpv6($address)
343 15
    {
344 10
        $pattern = '/^(?:[0-9a-f]\.){1,32}ip6\.arpa\.$/i';
345 10
346
        return 1 === preg_match($pattern, $address);
347 15
    }
348
}
349