1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4/**
5 * DNS Library for handling lookups and updates.
6 *
7 * PHP Version 5
8 *
9 * Copyright (c) 2010, Mike Pultz <mike@mikepultz.com>.
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 *
16 *   * Redistributions of source code must retain the above copyright
17 *     notice, this list of conditions and the following disclaimer.
18 *
19 *   * Redistributions in binary form must reproduce the above copyright
20 *     notice, this list of conditions and the following disclaimer in
21 *     the documentation and/or other materials provided with the
22 *     distribution.
23 *
24 *   * Neither the name of Mike Pultz nor the names of his contributors
25 *     may be used to endorse or promote products derived from this
26 *     software without specific prior written permission.
27 *
28 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
31 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
32 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
33 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
34 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
35 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
36 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC
37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
38 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
39 * POSSIBILITY OF SUCH DAMAGE.
40 *
41 * @category  Networking
42 * @package   Net_DNS2
43 * @author    Mike Pultz <mike@mikepultz.com>
44 * @copyright 2010 Mike Pultz <mike@mikepultz.com>
45 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
46 * @version   SVN: $Id$
47 * @link      http://pear.php.net/package/Net_DNS2
48 * @since     File available since Release 0.6.0
49 *
50 */
51
52
53/**
54 * This is the base class for DNS Resource Records
55 *
56 * Each resource record type (defined in RR/*.php) extends this class for
57 * base functionality.
58 *
59 * This class handles parsing and constructing the common parts of the DNS
60 * resource records, while the RR specific functionality is handled in each
61 * child class.
62 *
63 * DNS resource record format - RFC1035 section 4.1.3
64 *
65 *      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
66 *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
67 *    |                                               |
68 *    /                                               /
69 *    /                      NAME                     /
70 *    |                                               |
71 *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
72 *    |                      TYPE                     |
73 *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
74 *    |                     CLASS                     |
75 *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
76 *    |                      TTL                      |
77 *    |                                               |
78 *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
79 *    |                   RDLENGTH                    |
80 *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
81 *    /                     RDATA                     /
82 *    /                                               /
83 *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
84 *
85 * @category Networking
86 * @package  Net_DNS2
87 * @author   Mike Pultz <mike@mikepultz.com>
88 * @license  http://www.opensource.org/licenses/bsd-license.php  BSD License
89 * @link     http://pear.php.net/package/Net_DNS2
90 *
91 */
92abstract class Net_DNS2_RR
93{
94    /*
95     * The name of the resource record
96     */
97    public $name;
98
99    /*
100     * The resource record type
101     */
102    public $type;
103
104    /*
105     * The resouce record class
106     */
107    public $class;
108
109    /*
110     * The time to live for this resource record
111     */
112    public $ttl;
113
114    /*
115     * The length of the rdata field
116     */
117    public $rdlength;
118
119    /*
120     * The resource record specific data as a packed binary string
121     */
122    public $rdata;
123
124    /**
125     * abstract definition - method to return a RR as a string; not to
126     * be confused with the __toString() magic method.
127     *
128     * @return string
129     * @access protected
130     *
131     */
132    abstract protected function rrToString();
133
134    /**
135     * abstract definition - parses a RR from a standard DNS config line
136     *
137     * @param array $rdata a string split line of values for the rdata
138     *
139     * @return boolean
140     * @access protected
141     *
142     */
143    abstract protected function rrFromString(array $rdata);
144
145    /**
146     * abstract definition - sets a Net_DNS2_RR from a Net_DNS2_Packet object
147     *
148     * @param Net_DNS2_Packet &$packet a Net_DNS2_Packet packet to parse the RR from
149     *
150     * @return boolean
151     * @access protected
152     *
153     */
154    abstract protected function rrSet(Net_DNS2_Packet &$packet);
155
156    /**
157     * abstract definition - returns a binary packet DNS RR object
158     *
159     * @param Net_DNS2_Packet &$packet a Net_DNS2_Packet packet use for
160     *                                 compressed names
161     *
162     * @return mixed                   either returns a binary packed string or
163     *                                 null on failure
164     * @access protected
165     *
166     */
167    abstract protected function rrGet(Net_DNS2_Packet &$packet);
168
169    /**
170     * Constructor - builds a new Net_DNS2_RR object
171     *
172     * @param Net_DNS2_Packet &$packet a Net_DNS2_Packet packet or null to create
173     *                                 an empty object
174     * @param array           $rr      an array with RR parse values or null to
175     *                                 create an empty object
176     *
177     * @throws Net_DNS2_Exception
178     * @access public
179     *
180     */
181    public function __construct(Net_DNS2_Packet &$packet = null, array $rr = null)
182    {
183        if ( (!is_null($packet)) && (!is_null($rr)) ) {
184
185            if ($this->set($packet, $rr) == false) {
186
187                throw new Net_DNS2_Exception(
188                    'failed to generate resource record',
189                    Net_DNS2_Lookups::E_RR_INVALID
190                );
191            }
192        } else {
193
194            $class = Net_DNS2_Lookups::$rr_types_class_to_id[get_class($this)];
195            if (isset($class)) {
196
197                $this->type = Net_DNS2_Lookups::$rr_types_by_id[$class];
198            }
199
200            $this->class    = 'IN';
201            $this->ttl      = 86400;
202        }
203    }
204
205    /**
206     * magic __toString() method to return the Net_DNS2_RR object object as a string
207     *
208     * @return string
209     * @access public
210     *
211     */
212    public function __toString()
213    {
214        return $this->name . '. ' . $this->ttl . ' ' . $this->class .
215            ' ' . $this->type . ' ' . $this->rrToString();
216    }
217
218    /**
219     * return a formatted string; if a string has spaces in it, then return
220     * it with double quotes around it, otherwise, return it as it was passed in.
221     *
222     * @param string $string the string to format
223     *
224     * @return string
225     * @access protected
226     *
227     */
228    protected function formatString($string)
229    {
230        return '"' . str_replace('"', '\"', trim($string, '"')) . '"';
231    }
232
233    /**
234     * builds an array of strings from an array of chunks of text split by spaces
235     *
236     * @param array $chunks an array of chunks of text split by spaces
237     *
238     * @return array
239     * @access protected
240     *
241     */
242    protected function buildString(array $chunks)
243    {
244        $data = array();
245        $c = 0;
246        $in = false;
247
248        foreach ($chunks as $r) {
249
250            $r = trim($r);
251            if (strlen($r) == 0) {
252                continue;
253            }
254
255            if ( ($r[0] == '"')
256                && ($r[strlen($r) - 1] == '"')
257                && ($r[strlen($r) - 2] != '\\')
258            ) {
259
260                $data[$c] = $r;
261                ++$c;
262                $in = false;
263
264            } else if ($r[0] == '"') {
265
266                $data[$c] = $r;
267                $in = true;
268
269            } else if ( ($r[strlen($r) - 1] == '"')
270                && ($r[strlen($r) - 2] != '\\')
271            ) {
272
273                $data[$c] .= ' ' . $r;
274                ++$c;
275                $in = false;
276
277            } else {
278
279                if ($in == true) {
280                    $data[$c] .= ' ' . $r;
281                } else {
282                    $data[$c++] = $r;
283                }
284            }
285        }
286
287        foreach ($data as $index => $string) {
288
289            $data[$index] = str_replace('\"', '"', trim($string, '"'));
290        }
291
292        return $data;
293    }
294
295    /**
296     * builds a new Net_DNS2_RR object
297     *
298     * @param Net_DNS2_Packet &$packet a Net_DNS2_Packet packet or null to create
299     *                                 an empty object
300     * @param array           $rr      an array with RR parse values or null to
301     *                                 create an empty object
302     *
303     * @return boolean
304     * @throws Net_DNS2_Exception
305     * @access public
306     *
307     */
308    public function set(Net_DNS2_Packet &$packet, array $rr)
309    {
310        $this->name     = $rr['name'];
311        $this->type     = Net_DNS2_Lookups::$rr_types_by_id[$rr['type']];
312
313        //
314        // for RR OPT (41), the class value includes the requestors UDP payload size,
315        // and not a class value
316        //
317        if ($this->type == 'OPT') {
318            $this->class = $rr['class'];
319        } else {
320            $this->class = Net_DNS2_Lookups::$classes_by_id[$rr['class']];
321        }
322
323        $this->ttl      = $rr['ttl'];
324        $this->rdlength = $rr['rdlength'];
325        $this->rdata    = substr($packet->rdata, $packet->offset, $rr['rdlength']);
326
327        return $this->rrSet($packet);
328    }
329
330    /**
331     * returns a binary packed DNS RR object
332     *
333     * @param Net_DNS2_Packet &$packet a Net_DNS2_Packet packet used for
334     *                                 compressing names
335     *
336     * @return string
337     * @throws Net_DNS2_Exception
338     * @access public
339     *
340     */
341    public function get(Net_DNS2_Packet &$packet)
342    {
343        $data  = '';
344        $rdata = '';
345
346        //
347        // pack the name
348        //
349        $data = $packet->compress($this->name, $packet->offset);
350
351        //
352        // pack the main values
353        //
354        if ($this->type == 'OPT') {
355
356            //
357            // pre-build the TTL value
358            //
359            $this->preBuild();
360
361            //
362            // the class value is different for OPT types
363            //
364            $data .= pack(
365                'nnN',
366                Net_DNS2_Lookups::$rr_types_by_name[$this->type],
367                $this->class,
368                $this->ttl
369            );
370        } else {
371
372            $data .= pack(
373                'nnN',
374                Net_DNS2_Lookups::$rr_types_by_name[$this->type],
375                Net_DNS2_Lookups::$classes_by_name[$this->class],
376                $this->ttl
377            );
378        }
379
380        //
381        // increase the offset, and allow for the rdlength
382        //
383        $packet->offset += 10;
384
385        //
386        // get the RR specific details
387        //
388        if ($this->rdlength != -1) {
389
390            $rdata = $this->rrGet($packet);
391        }
392
393        //
394        // add the RR
395        //
396        $data .= pack('n', strlen($rdata)) . $rdata;
397
398        return $data;
399    }
400
401    /**
402     * parses a binary packet, and returns the appropriate Net_DNS2_RR object,
403     * based on the RR type of the binary content.
404     *
405     * @param Net_DNS2_Packet &$packet a Net_DNS2_Packet packet used for
406     *                                 decompressing names
407     *
408     * @return mixed                   returns a new Net_DNS2_RR_* object for
409     *                                 the given RR
410     * @throws Net_DNS2_Exception
411     * @access public
412     *
413     */
414    public static function parse(Net_DNS2_Packet &$packet)
415    {
416        $object = array();
417
418        //
419        // expand the name
420        //
421        $object['name'] = $packet->expand($packet, $packet->offset);
422        if (is_null($object['name'])) {
423
424            throw new Net_DNS2_Exception(
425                'failed to parse resource record: failed to expand name.',
426                Net_DNS2_Lookups::E_PARSE_ERROR
427            );
428        }
429        if ($packet->rdlength < ($packet->offset + 10)) {
430
431            throw new Net_DNS2_Exception(
432                'failed to parse resource record: packet too small.',
433                Net_DNS2_Lookups::E_PARSE_ERROR
434            );
435        }
436
437        //
438        // unpack the RR details
439        //
440        $object['type']     = ord($packet->rdata[$packet->offset++]) << 8 |
441                                ord($packet->rdata[$packet->offset++]);
442        $object['class']    = ord($packet->rdata[$packet->offset++]) << 8 |
443                                ord($packet->rdata[$packet->offset++]);
444
445        $object['ttl']      = ord($packet->rdata[$packet->offset++]) << 24 |
446                                ord($packet->rdata[$packet->offset++]) << 16 |
447                                ord($packet->rdata[$packet->offset++]) << 8 |
448                                ord($packet->rdata[$packet->offset++]);
449
450        $object['rdlength'] = ord($packet->rdata[$packet->offset++]) << 8 |
451                                ord($packet->rdata[$packet->offset++]);
452
453        if ($packet->rdlength < ($packet->offset + $object['rdlength'])) {
454            return null;
455        }
456
457        //
458        // lookup the class to use
459        //
460        $o      = null;
461        $class  = Net_DNS2_Lookups::$rr_types_id_to_class[$object['type']];
462
463        if (isset($class)) {
464
465            $o = new $class($packet, $object);
466            if ($o) {
467
468                $packet->offset += $object['rdlength'];
469            }
470        } else {
471
472            throw new Net_DNS2_Exception(
473                'un-implemented resource record type: ' . $object['type'],
474                Net_DNS2_Lookups::E_RR_INVALID
475            );
476        }
477
478        return $o;
479    }
480
481    /**
482     * cleans up some RR data
483     *
484     * @param string $data the text string to clean
485     *
486     * @return string returns the cleaned string
487     *
488     * @access public
489     *
490     */
491    public function cleanString($data)
492    {
493        return strtolower(rtrim($data, '.'));
494    }
495
496    /**
497     * parses a standard RR format lines, as defined by rfc1035 (kinda)
498     *
499     * In our implementation, the domain *must* be specified- format must be
500     *
501     *        <name> [<ttl>] [<class>] <type> <rdata>
502     * or
503     *        <name> [<class>] [<ttl>] <type> <rdata>
504     *
505     * name, title, class and type are parsed by this function, rdata is passed
506     * to the RR specific classes for parsing.
507     *
508     * @param string $line a standard DNS config line
509     *
510     * @return mixed       returns a new Net_DNS2_RR_* object for the given RR
511     * @throws Net_DNS2_Exception
512     * @access public
513     *
514     */
515    public static function fromString($line)
516    {
517        if (strlen($line) == 0) {
518            throw new Net_DNS2_Exception(
519                'empty config line provided.',
520                Net_DNS2_Lookups::E_PARSE_ERROR
521            );
522        }
523
524        $name   = '';
525        $type   = '';
526        $class  = 'IN';
527        $ttl    = 86400;
528
529        //
530        // split the line by spaces
531        //
532        $values = preg_split('/[\s]+/', $line);
533        if (count($values) < 3) {
534
535            throw new Net_DNS2_Exception(
536                'failed to parse config: minimum of name, type and rdata required.',
537                Net_DNS2_Lookups::E_PARSE_ERROR
538            );
539        }
540
541        //
542        // assume the first value is the name
543        //
544        $name = trim(strtolower(array_shift($values)), '.');
545
546        //
547        // The next value is either a TTL, Class or Type
548        //
549        foreach ($values as $value) {
550
551            switch(true) {
552            case is_numeric($value):
553
554                $ttl = array_shift($values);
555                break;
556
557            //
558            // this is here because of a bug in is_numeric() in certain versions of
559            // PHP on windows.
560            //
561            case ($value === 0):
562
563                $ttl = array_shift($values);
564                break;
565
566            case isset(Net_DNS2_Lookups::$classes_by_name[strtoupper($value)]):
567
568                $class = strtoupper(array_shift($values));
569                break;
570
571            case isset(Net_DNS2_Lookups::$rr_types_by_name[strtoupper($value)]):
572
573                $type = strtoupper(array_shift($values));
574                break 2;
575                break;
576
577            default:
578
579                throw new Net_DNS2_Exception(
580                    'invalid config line provided: unknown file: ' . $value,
581                    Net_DNS2_Lookups::E_PARSE_ERROR
582                );
583            }
584        }
585
586        //
587        // lookup the class to use
588        //
589        $o = null;
590        $class_name = Net_DNS2_Lookups::$rr_types_id_to_class[
591            Net_DNS2_Lookups::$rr_types_by_name[$type]
592        ];
593
594        if (isset($class_name)) {
595
596            $o = new $class_name;
597            if (!is_null($o)) {
598
599                //
600                // set the parsed values
601                //
602                $o->name    = $name;
603                $o->class   = $class;
604                $o->ttl     = $ttl;
605
606                //
607                // parse the rdata
608                //
609                if ($o->rrFromString($values) === false) {
610
611                    throw new Net_DNS2_Exception(
612                        'failed to parse rdata for config: ' . $line,
613                        Net_DNS2_Lookups::E_PARSE_ERROR
614                    );
615                }
616
617            } else {
618
619                throw new Net_DNS2_Exception(
620                    'failed to create new RR record for type: ' . $type,
621                    Net_DNS2_Lookups::E_RR_INVALID
622                );
623            }
624
625        } else {
626
627            throw new Net_DNS2_Exception(
628                'un-implemented resource record type: '. $type,
629                Net_DNS2_Lookups::E_RR_INVALID
630            );
631        }
632
633        return $o;
634    }
635}
636
637/*
638 * Local variables:
639 * tab-width: 4
640 * c-basic-offset: 4
641 * c-hanging-comment-ender-p: nil
642 * End:
643 */
644?>
645