1<?php
2
3/**
4 * DNS Library for handling lookups and updates.
5 *
6 * Copyright (c) 2020, Mike Pultz <mike@mikepultz.com>. All rights reserved.
7 *
8 * See LICENSE for more details.
9 *
10 * @category  Networking
11 * @package   Net_DNS2
12 * @author    Mike Pultz <mike@mikepultz.com>
13 * @copyright 2020 Mike Pultz <mike@mikepultz.com>
14 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
15 * @link      https://netdns2.com/
16 * @since     File available since Release 0.6.0
17 *
18 */
19
20/**
21 * This is the main resolver class, providing DNS query functions.
22 *
23 */
24class Net_DNS2_Resolver extends Net_DNS2
25{
26    /**
27     * Constructor - creates a new Net_DNS2_Resolver object
28     *
29     * @param mixed $options either an array with options or null
30     *
31     * @access public
32     *
33     */
34    public function __construct(array $options = null)
35    {
36        parent::__construct($options);
37    }
38
39    /**
40     * does a basic DNS lookup query
41     *
42     * @param string $name  the DNS name to loookup
43     * @param string $type  the name of the RR type to lookup
44     * @param string $class the name of the RR class to lookup
45     *
46     * @return Net_DNS2_Packet_Response object
47     * @throws Net_DNS2_Exception
48     * @access public
49     *
50     */
51    public function query($name, $type = 'A', $class = 'IN')
52    {
53        //
54        // make sure we have some name servers set
55        //
56        $this->checkServers(Net_DNS2::RESOLV_CONF);
57
58        //
59        // we dont' support incremental zone tranfers; so if it's requested, a full
60        // zone transfer can be returned
61        //
62        if ($type == 'IXFR') {
63
64            $type = 'AXFR';
65        }
66
67        //
68        // if the name *looks* too short, then append the domain from the config
69        //
70        if ( (strpos($name, '.') === false) && ($type != 'PTR') ) {
71
72            $name .= '.' . strtolower($this->domain);
73        }
74
75        //
76        // create a new packet based on the input
77        //
78        $packet = new Net_DNS2_Packet_Request($name, $type, $class);
79
80        //
81        // check for an authentication method; either TSIG or SIG
82        //
83        if (   ($this->auth_signature instanceof Net_DNS2_RR_TSIG)
84            || ($this->auth_signature instanceof Net_DNS2_RR_SIG)
85        ) {
86            $packet->additional[]       = $this->auth_signature;
87            $packet->header->arcount    = count($packet->additional);
88        }
89
90        //
91        // check for the DNSSEC flag, and if it's true, then add an OPT
92        // RR to the additional section, and set the DO flag to 1.
93        //
94        if ($this->dnssec == true) {
95
96            //
97            // create a new OPT RR
98            //
99            $opt = new Net_DNS2_RR_OPT();
100
101            //
102            // set the DO flag, and the other values
103            //
104            $opt->do = 1;
105            $opt->class = $this->dnssec_payload_size;
106
107            //
108            // add the RR to the additional section.
109            //
110            $packet->additional[] = $opt;
111            $packet->header->arcount = count($packet->additional);
112        }
113
114        //
115        // set the DNSSEC AD or CD bits
116        //
117        if ($this->dnssec_ad_flag == true) {
118
119            $packet->header->ad = 1;
120        }
121        if ($this->dnssec_cd_flag == true) {
122
123            $packet->header->cd = 1;
124        }
125
126        //
127        // if caching is turned on, then check then hash the question, and
128        // do a cache lookup.
129        //
130        // don't use the cache for zone transfers
131        //
132        $packet_hash = '';
133
134        if ( ($this->use_cache == true) && ($this->cacheable($type) == true) ) {
135
136            //
137            // open the cache
138            //
139            $this->cache->open(
140                $this->cache_file, $this->cache_size, $this->cache_serializer
141            );
142
143            //
144            // build the key and check for it in the cache.
145            //
146            $packet_hash = md5(
147                $packet->question[0]->qname . '|' . $packet->question[0]->qtype
148            );
149
150            if ($this->cache->has($packet_hash)) {
151
152                return $this->cache->get($packet_hash);
153            }
154        }
155
156        //
157        // set the RD (recursion desired) bit to 1 / 0 depending on the config
158        // setting.
159        //
160        if ($this->recurse == false) {
161            $packet->header->rd = 0;
162        } else {
163            $packet->header->rd = 1;
164        }
165
166        //
167        // send the packet and get back the response
168        //
169        // *always* use TCP for zone transfers- does this cause any problems?
170        //
171        $response = $this->sendPacket(
172            $packet, ($type == 'AXFR') ? true : $this->use_tcp
173        );
174
175        //
176        // if strict mode is enabled, then make sure that the name that was
177        // looked up is *actually* in the response object.
178        //
179        // only do this is strict_query_mode is turned on, AND we've received
180        // some answers; no point doing any else if there were no answers.
181        //
182        if ( ($this->strict_query_mode == true)
183            && ($response->header->ancount > 0)
184        ) {
185
186            $found = false;
187
188            //
189            // look for the requested name/type/class
190            //
191            foreach ($response->answer as $index => $object) {
192
193                if ( (strcasecmp(trim($object->name, '.'), trim($packet->question[0]->qname, '.')) == 0)
194                    && ($object->type == $packet->question[0]->qtype)
195                    && ($object->class == $packet->question[0]->qclass)
196                ) {
197                    $found = true;
198                    break;
199                }
200            }
201
202            //
203            // if it's not found, then unset the answer section; it's not correct to
204            // throw an exception here; if the hostname didn't exist, then
205            // sendPacket() would have already thrown an NXDOMAIN error- so the host
206            // *exists*, but just not the request type/class.
207            //
208            // the correct response in this case, is an empty answer section; the
209            // authority section may still have usual information, like a SOA record.
210            //
211            if ($found == false) {
212
213                $response->answer = [];
214                $response->header->ancount = 0;
215            }
216        }
217
218        //
219        // cache the response object
220        //
221        if ( ($this->use_cache == true) && ($this->cacheable($type) == true) ) {
222
223            $this->cache->put($packet_hash, $response);
224        }
225
226        return $response;
227    }
228
229    /**
230     * does an inverse query for the given RR; most DNS servers do not implement
231     * inverse queries, but they should be able to return "not implemented"
232     *
233     * @param Net_DNS2_RR $rr the RR object to lookup
234     *
235     * @return Net_DNS2_RR object
236     * @throws Net_DNS2_Exception
237     * @access public
238     *
239     */
240    public function iquery(Net_DNS2_RR $rr)
241    {
242        //
243        // make sure we have some name servers set
244        //
245        $this->checkServers(Net_DNS2::RESOLV_CONF);
246
247        //
248        // create an empty packet
249        //
250        $packet = new Net_DNS2_Packet_Request($rr->name, 'A', 'IN');
251
252        //
253        // unset the question
254        //
255        $packet->question = [];
256        $packet->header->qdcount = 0;
257
258        //
259        // set the opcode to IQUERY
260        //
261        $packet->header->opcode = Net_DNS2_Lookups::OPCODE_IQUERY;
262
263        //
264        // add the given RR as the answer
265        //
266        $packet->answer[] = $rr;
267        $packet->header->ancount = 1;
268
269        //
270        // check for an authentication method; either TSIG or SIG
271        //
272        if (   ($this->auth_signature instanceof Net_DNS2_RR_TSIG)
273            || ($this->auth_signature instanceof Net_DNS2_RR_SIG)
274        ) {
275            $packet->additional[]       = $this->auth_signature;
276            $packet->header->arcount    = count($packet->additional);
277        }
278
279        //
280        // send the packet and get back the response
281        //
282        return $this->sendPacket($packet, $this->use_tcp);
283    }
284}
285