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 1.0.0
17 *
18 */
19
20/**
21 * APL Resource Record - RFC3123
22 *
23 *     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
24 *     |                          ADDRESSFAMILY                        |
25 *     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
26 *     |             PREFIX            | N |         AFDLENGTH         |
27 *     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
28 *     /                            AFDPART                            /
29 *     |                                                               |
30 *     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
31 *
32 */
33class Net_DNS2_RR_APL extends Net_DNS2_RR
34{
35    /*
36     * a list of all the address prefix list items
37     */
38    public $apl_items = [];
39
40    /**
41     * method to return the rdata portion of the packet as a string
42     *
43     * @return  string
44     * @access  protected
45     *
46     */
47    protected function rrToString()
48    {
49        $out = '';
50
51        foreach ($this->apl_items as $item) {
52
53            if ($item['n'] == 1) {
54
55                $out .= '!';
56            }
57
58            $out .= $item['address_family'] . ':' .
59                $item['afd_part'] . '/' . $item['prefix'] . ' ';
60        }
61
62        return trim($out);
63    }
64
65    /**
66     * parses the rdata portion from a standard DNS config line
67     *
68     * @param array $rdata a string split line of values for the rdata
69     *
70     * @return boolean
71     * @access protected
72     *
73     */
74    protected function rrFromString(array $rdata)
75    {
76        foreach ($rdata as $item) {
77
78            if (preg_match('/^(!?)([1|2])\:([^\/]*)\/([0-9]{1,3})$/', $item, $m)) {
79
80                $i = [
81
82                    'address_family'    => $m[2],
83                    'prefix'            => $m[4],
84                    'n'                 => ($m[1] == '!') ? 1 : 0,
85                    'afd_part'          => strtolower($m[3])
86                ];
87
88                $address = $this->_trimZeros(
89                    $i['address_family'], $i['afd_part']
90                );
91
92                $i['afd_length'] = count(explode('.', $address));
93
94                $this->apl_items[] = $i;
95            }
96        }
97
98        return true;
99    }
100
101    /**
102     * parses the rdata of the Net_DNS2_Packet object
103     *
104     * @param Net_DNS2_Packet &$packet a Net_DNS2_Packet packet to parse the RR from
105     *
106     * @return boolean
107     * @access protected
108     *
109     */
110    protected function rrSet(Net_DNS2_Packet &$packet)
111    {
112        if ($this->rdlength > 0) {
113
114            $offset = 0;
115
116            while ($offset < $this->rdlength) {
117
118                //
119                // unpack the family, prefix, negate and length values
120                //
121                $x = unpack(
122                    'naddress_family/Cprefix/Cextra', substr($this->rdata, $offset)
123                );
124
125                $item = [
126
127                    'address_family'    => $x['address_family'],
128                    'prefix'            => $x['prefix'],
129                    'n'                 => ($x['extra'] >> 7) & 0x1,
130                    'afd_length'        => $x['extra'] & 0xf
131                ];
132
133                switch($item['address_family']) {
134
135                case 1:
136                    $r = unpack(
137                        'C*', substr($this->rdata, $offset + 4, $item['afd_length'])
138                    );
139                    if (count($r) < 4) {
140
141                        for ($c=count($r)+1; $c<4+1; $c++) {
142
143                            $r[$c] = 0;
144                        }
145                    }
146
147                    $item['afd_part'] = implode('.', $r);
148
149                    break;
150                case 2:
151                    $r = unpack(
152                        'C*', substr($this->rdata, $offset + 4, $item['afd_length'])
153                    );
154                    if (count($r) < 8) {
155
156                        for ($c=count($r)+1; $c<8+1; $c++) {
157
158                            $r[$c] = 0;
159                        }
160                    }
161
162                    $item['afd_part'] = sprintf(
163                        '%x:%x:%x:%x:%x:%x:%x:%x',
164                        $r[1], $r[2], $r[3], $r[4], $r[5], $r[6], $r[7], $r[8]
165                    );
166
167                    break;
168                default:
169                    return false;
170                }
171
172                $this->apl_items[] = $item;
173
174                $offset += 4 + $item['afd_length'];
175            }
176
177            return true;
178        }
179
180        return false;
181    }
182
183    /**
184     * returns the rdata portion of the DNS packet
185     *
186     * @param Net_DNS2_Packet &$packet a Net_DNS2_Packet packet use for
187     *                                 compressed names
188     *
189     * @return mixed                   either returns a binary packed
190     *                                 string or null on failure
191     * @access protected
192     *
193     */
194    protected function rrGet(Net_DNS2_Packet &$packet)
195    {
196        if (count($this->apl_items) > 0) {
197
198            $data = '';
199
200            foreach ($this->apl_items as $item) {
201
202                //
203                // pack the address_family and prefix values
204                //
205                $data .= pack(
206                    'nCC',
207                    $item['address_family'],
208                    $item['prefix'],
209                    ($item['n'] << 7) | $item['afd_length']
210                );
211
212                switch($item['address_family']) {
213                case 1:
214                    $address = explode(
215                        '.',
216                        $this->_trimZeros($item['address_family'], $item['afd_part'])
217                    );
218
219                    foreach ($address as $b) {
220                        $data .= chr($b);
221                    }
222                    break;
223                case 2:
224                    $address = explode(
225                        ':',
226                        $this->_trimZeros($item['address_family'], $item['afd_part'])
227                    );
228
229                    foreach ($address as $b) {
230                        $data .= pack('H', $b);
231                    }
232                    break;
233                default:
234                    return null;
235                }
236            }
237
238            $packet->offset += strlen($data);
239
240            return $data;
241        }
242
243        return null;
244    }
245
246    /**
247     * returns an IP address with the right-hand zero's trimmed
248     *
249     * @param integer $family  the IP address family from the rdata
250     * @param string  $address the IP address
251     *
252     * @return string the trimmed IP addresss.
253     *
254     * @access private
255     *
256     */
257    private function _trimZeros($family, $address)
258    {
259        $a = [];
260
261        switch($family) {
262        case 1:
263            $a = array_reverse(explode('.', $address));
264            break;
265        case 2:
266            $a = array_reverse(explode(':', $address));
267            break;
268        default:
269            return '';
270        }
271
272        foreach ($a as $value) {
273
274            if ($value === '0') {
275
276                array_shift($a);
277            }
278        }
279
280        $out = '';
281
282        switch($family) {
283        case 1:
284            $out = implode('.', array_reverse($a));
285            break;
286        case 2:
287            $out = implode(':', array_reverse($a));
288            break;
289        default:
290            return '';
291        }
292
293        return $out;
294    }
295}
296