Passed
Push — master ( 54cc30...cc7767 )
by Maja
08:24
created

RFC7585Tests   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 298
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 298
rs 9.52
c 0
b 0
f 0
wmc 36

5 Methods

Rating   Name   Duplication   Size   Complexity  
B relevantNAPTRhostnameResolution() 0 44 9
B relevantNAPTRsrvResolution() 0 33 9
A __construct() 0 26 1
B relevantNAPTRcompliance() 0 37 10
B relevantNAPTR() 0 23 7
1
<?php
2
3
/*
4
 * ******************************************************************************
5
 * Copyright 2011-2017 DANTE Ltd. and GÉANT on behalf of the GN3, GN3+, GN4-1 
6
 * and GN4-2 consortia
7
 *
8
 * License: see the web/copyright.php file in the file structure
9
 * ******************************************************************************
10
 */
11
12
namespace core\diag;
13
14
require_once(dirname(dirname(__DIR__)) . "/config/_config.php");
15
16
/**
17
 * Test suite to verify that a given NAI realm has NAPTR records according to
18
 * consortium-agreed criteria
19
 * Can only be used if CONFIG_DIAGNOSTICS['RADIUSTESTS'] is configured.
20
 *
21
 * @author Stefan Winter <[email protected]>
22
 * @author Tomasz Wolniewicz <[email protected]>
23
 *
24
 * @license see LICENSE file in root directory
25
 *
26
 * @package Developer
27
 */
28
class RFC7585Tests extends AbstractTest {
29
30
    /**
31
     * maintains state for the question: has the NAPTR existence check already been executed? Holds the number of NAPTR records found if so.
32
     * 
33
     * @var int
34
     */
35
    private $NAPTR_executed;
36
    
37
    /**
38
     * maintains state for the question: has the NAPTR compliance check already been executed?
39
     * 
40
     * @var int
41
     */
42
    private $NAPTR_compliance_executed;
43
    
44
    /**
45
     * maintains state for the question: has the NAPTR SRV check already been executed? Holds the number of SRV records if so.
46
     * 
47
     * @var int
48
     */
49
    private $NAPTR_SRV_executed;
50
    
51
    /**
52
     * maintains state for the question: has the existrence of hostnames been checked already? Holds the number of IP:port pairs if so.
53
     * 
54
     * @var int
55
     */
56
    private $NAPTR_hostname_executed;
57
    
58
    /**
59
     * holds the list of NAPTR records found
60
     * 
61
     * @var array
62
     */
63
    private $NAPTR_records;
64
    
65
    /**
66
     * holds the list of SRV records found
67
     * 
68
     * @var array
69
     */
70
    private $NAPTR_SRV_records;
71
    
72
    /**
73
     * stores the various errors encountered during the checks
74
     * 
75
     * @var array
76
     */
77
    private $errorlist;
78
    
79
    /**
80
     * stores the IP address / port pairs (strings) which were ultimately found
81
     * as candidate RADIUS/TLS servers
82
     * 
83
     * @var array
84
     */
85
    public $NAPTR_hostname_records;
86
87
    // return codes specific to NAPTR existence checks
88
    
89
    /**
90
     * test hasn't been run yet
91
     */
92
    const RETVAL_NOTRUNYET = -1;
93
    
94
    /**
95
     * no NAPTRs for domain; this is not an error, simply means that realm is not doing dynamic discovery for any service
96
     */
97
    const RETVAL_NONAPTR = -104;
98
99
    /**
100
     * no eduroam NAPTR for domain; this is not an error, simply means that realm is not doing dynamic discovery for eduroam
101
     */
102
    const RETVAL_ONLYUNRELATEDNAPTR = -105;
103
104
    /**
105
     * This private variable contains the realm to be checked. Is filled in the
106
     * class constructor.
107
     * 
108
     * @var string
109
     */
110
    private $realm;
111
112
    /**
113
     * Initialises the dynamic discovery test instance for a specific realm that is to be tested
114
     * 
115
     * @param string $realm the realm to be tested
116
     */
117
    public function __construct(string $realm) {
118
        parent::__construct();
119
120
        // return codes specific to NAPTR existence checks
121
        /**
122
         * no NAPTRs for domain; this is not an error, simply means that realm is not doing dynamic discovery for any service
123
         */
124
        $this->returnCodes[RFC7585Tests::RETVAL_NONAPTR]["message"] = _("This realm has no NAPTR records.");
125
        $this->returnCodes[RFC7585Tests::RETVAL_NONAPTR]["severity"] = \core\common\Entity::L_OK;
126
127
        /**
128
         * no eduroam NAPTR for domain; this is not an error, simply means that realm is not doing dynamic discovery for eduroam
129
         */
130
        $this->returnCodes[RFC7585Tests::RETVAL_ONLYUNRELATEDNAPTR]["message"] = _("NAPTR records were found, but all of them refer to unrelated services.");
131
        $this->returnCodes[RFC7585Tests::RETVAL_ONLYUNRELATEDNAPTR]["severity"] = \core\common\Entity::L_OK;
132
133
134
        $this->realm = $realm;
135
        $this->NAPTR_executed = RFC7585Tests::RETVAL_NOTRUNYET;
136
        $this->NAPTR_compliance_executed = RFC7585Tests::RETVAL_NOTRUNYET;
137
        $this->NAPTR_SRV_executed = RFC7585Tests::RETVAL_NOTRUNYET;
138
        $this->NAPTR_hostname_executed = RFC7585Tests::RETVAL_NOTRUNYET;
139
        $this->NAPTR_records = [];
140
        $this->NAPTR_SRV_records = [];
141
        $this->NAPTR_hostname_records = [];
142
        $this->errorlist = [];
143
    }
144
145
    /**
146
     * Tests if this realm exists in DNS and has NAPTR records matching the
147
     * configured consortium NAPTR target.
148
     * 
149
     * possible RETVALs:
150
     * - RETVAL_NOTCONFIGURED; needs CONFIG_DIAGNOSTICS['RADIUSTESTS']['TLS-discoverytag']
151
     * - RETVAL_ONLYUNRELATEDNAPTR
152
     * - RETVAL_NONAPTR
153
     * 
154
     * @return int Either a RETVAL constant or a positive number (count of relevant NAPTR records)
155
     */
156
    public function relevantNAPTR() {
157
        if (CONFIG_DIAGNOSTICS['RADIUSTESTS']['TLS-discoverytag'] == "") {
158
            $this->NAPTR_executed = RADIUSTests::RETVAL_NOTCONFIGURED;
159
            return RADIUSTests::RETVAL_NOTCONFIGURED;
160
        }
161
        $NAPTRs = dns_get_record($this->realm . ".", DNS_NAPTR);
162
        if ($NAPTRs === FALSE || count($NAPTRs) == 0) {
163
            $this->NAPTR_executed = RFC7585Tests::RETVAL_NONAPTR;
164
            return RFC7585Tests::RETVAL_NONAPTR;
165
        }
166
        $NAPTRs_consortium = [];
167
        foreach ($NAPTRs as $naptr) {
168
            if ($naptr["services"] == CONFIG_DIAGNOSTICS['RADIUSTESTS']['TLS-discoverytag']) {
169
                $NAPTRs_consortium[] = $naptr;
170
            }
171
        }
172
        if (count($NAPTRs_consortium) == 0) {
173
            $this->NAPTR_executed = RFC7585Tests::RETVAL_ONLYUNRELATEDNAPTR;
174
            return RFC7585Tests::RETVAL_ONLYUNRELATEDNAPTR;
175
        }
176
        $this->NAPTR_records = $NAPTRs_consortium;
177
        $this->NAPTR_executed = count($NAPTRs_consortium);
178
        return count($NAPTRs_consortium);
179
    }
180
181
    /**
182
     * Tests if all the dicovered NAPTR entries conform to the consortium's requirements
183
     * 
184
     * possible RETVALs:
185
     * - RETVAL_NOTCONFIGURED; needs CONFIG_DIAGNOSTICS['RADIUSTESTS']['TLS-discoverytag']
186
     * - RETVAL_INVALID (at least one format error)
187
     * - RETVAL_OK (all fine)
188
189
     * @return int one of two RETVALs above
190
     */
191
    public function relevantNAPTRcompliance() {
192
// did we query DNS for the NAPTRs yet? If not, do so now.
193
        if ($this->NAPTR_executed == RFC7585Tests::RETVAL_NOTRUNYET) {
194
            $this->relevantNAPTR();
195
        }
196
// if the NAPTR checks aren't configured, tell the caller
197
        if ($this->NAPTR_executed === RADIUSTests::RETVAL_NOTCONFIGURED) {
198
            $this->NAPTR_compliance_executed = RADIUSTests::RETVAL_NOTCONFIGURED;
199
            return RADIUSTests::RETVAL_NOTCONFIGURED;
200
        }
201
// if there were no relevant NAPTR records, we are compliant :-)
202
        if (count($this->NAPTR_records) == 0) {
203
            $this->NAPTR_compliance_executed = RADIUSTests::RETVAL_OK;
204
            return RADIUSTests::RETVAL_OK;
205
        }
206
        $formatErrors = [];
207
// format of NAPTRs is consortium specific. eduroam below; others need
208
// their own code
209
        if (CONFIG_DIAGNOSTICS['RADIUSTESTS']['TLS-discoverytag'] == "x-eduroam:radius.tls") {
210
            foreach ($this->NAPTR_records as $edupointer) {
211
// must be "s" type for SRV
212
                if ($edupointer["flags"] != "s" && $edupointer["flags"] != "S") {
213
                    $formatErrors[] = ["TYPE" => "NAPTR-FLAG", "TARGET" => $edupointer['flag']];
214
                }
215
// no regex
216
                if ($edupointer["regex"] != "") {
217
                    $formatErrors[] = ["TYPE" => "NAPTR-REGEX", "TARGET" => $edupointer['regex']];
218
                }
219
            }
220
        }
221
        if (count($formatErrors) == 0) {
222
            $this->NAPTR_compliance_executed = RADIUSTests::RETVAL_OK;
223
            return RADIUSTests::RETVAL_OK;
224
        }
225
        $this->errorlist = array_merge($this->errorlist, $formatErrors);
226
        $this->NAPTR_compliance_executed = RADIUSTests::RETVAL_INVALID;
227
        return RADIUSTests::RETVAL_INVALID;
228
    }
229
230
    /**
231
     * Tests if NAPTR records can be resolved to SRVs. Will only run if NAPTR
232
     * checks completed without error.
233
     *
234
     * possible RETVALs:
235
     * - RETVAL_INVALID
236
     * - RETVAL_SKIPPED
237
     * 
238
     * @return int one of the RETVALs above or the number of SRV records which were resolved
239
     */
240
    public function relevantNAPTRsrvResolution() {
241
// see if preceding checks have been run, and run them if not
242
// compliance check will cascade NAPTR check on its own
243
        if ($this->NAPTR_compliance_executed == RFC7585Tests::RETVAL_NOTRUNYET) {
244
            $this->relevantNAPTRcompliance();
245
        }
246
// we only run the SRV checks if all records are compliant and more than one relevant NAPTR exists
247
        if ($this->NAPTR_executed <= 0 || $this->NAPTR_compliance_executed == RADIUSTests::RETVAL_INVALID) {
248
            $this->NAPTR_SRV_executed = RADIUSTests::RETVAL_SKIPPED;
249
            return RADIUSTests::RETVAL_SKIPPED;
250
        }
251
252
        $sRVerrors = [];
253
        $sRVtargets = [];
254
255
        foreach ($this->NAPTR_records as $edupointer) {
256
            $tempResult = dns_get_record($edupointer["replacement"], DNS_SRV);
257
            if ($tempResult === FALSE || count($tempResult) == 0) {
258
                $sRVerrors[] = ["TYPE" => "SRV_NOT_RESOLVING", "TARGET" => $edupointer['replacement']];
259
            } else {
260
                foreach ($tempResult as $res) {
261
                    $sRVtargets[] = ["hostname" => $res["target"], "port" => $res["port"]];
262
                }
263
            }
264
        }
265
        $this->NAPTR_SRV_records = $sRVtargets;
266
        if (count($sRVerrors) > 0) {
267
            $this->NAPTR_SRV_executed = RADIUSTests::RETVAL_INVALID;
268
            $this->errorlist = array_merge($this->errorlist, $sRVerrors);
269
            return RADIUSTests::RETVAL_INVALID;
270
        }
271
        $this->NAPTR_SRV_executed = count($sRVtargets);
272
        return count($sRVtargets);
273
    }
274
275
    /**
276
     * Checks whether the previously discovered hostnames have actual IP addresses in DNS.
277
     * 
278
     * The actual list is stored in the class property NAPTR_hostname_records.
279
     * 
280
     * @return int count of IP / port pairs for all the hostnames
281
     */
282
    public function relevantNAPTRhostnameResolution() {
283
// make sure the previous tests have been run before we go on
284
// preceeding tests will cascade automatically if needed
285
        if ($this->NAPTR_SRV_executed == RFC7585Tests::RETVAL_NOTRUNYET) {
286
            $this->relevantNAPTRsrvResolution();
287
        }
288
// if previous are SKIPPED, skip this one, too
289
        if ($this->NAPTR_SRV_executed == RADIUSTests::RETVAL_SKIPPED) {
290
            $this->NAPTR_hostname_executed = RADIUSTests::RETVAL_SKIPPED;
291
            return RADIUSTests::RETVAL_SKIPPED;
292
        }
293
// the SRV check may have returned INVALID, but could have found a
294
// a working subset of hosts anyway. We should continue checking all 
295
// dicovered names.
296
297
        $ipAddrs = [];
298
        $resolutionErrors = [];
299
300
        foreach ($this->NAPTR_SRV_records as $server) {
301
            $hostResolutionIPv6 = dns_get_record($server["hostname"], DNS_AAAA);
302
            $hostResolutionIPv4 = dns_get_record($server["hostname"], DNS_A);
303
            $hostResolution = array_merge($hostResolutionIPv6, $hostResolutionIPv4);
304
            if ($hostResolution === FALSE || count($hostResolution) == 0) {
305
                $resolutionErrors[] = ["TYPE" => "HOST_NO_ADDRESS", "TARGET" => $server['hostname']];
306
            } else {
307
                foreach ($hostResolution as $address) {
308
                    if (isset($address["ip"])) {
309
                        $ipAddrs[] = ["family" => "IPv4", "IP" => $address["ip"], "port" => $server["port"], "status" => ""];
310
                    } else {
311
                        $ipAddrs[] = ["family" => "IPv6", "IP" => $address["ipv6"], "port" => $server["port"], "status" => ""];
312
                    }
313
                }
314
            }
315
        }
316
317
        $this->NAPTR_hostname_records = $ipAddrs;
318
319
        if (count($resolutionErrors) > 0) {
320
            $this->errorlist = array_merge($this->errorlist, $resolutionErrors);
321
            $this->NAPTR_hostname_executed = RADIUSTests::RETVAL_INVALID;
322
            return RADIUSTests::RETVAL_INVALID;
323
        }
324
        $this->NAPTR_hostname_executed = count($this->NAPTR_hostname_records);
325
        return count($this->NAPTR_hostname_records);
326
    }
327
328
}
329