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 * Socket handling class using the PHP Streams
54 *
55 * The sockets extension is faster than the stream functions in PHP, but it's
56 * not standard. So if the extension is loaded, then the Net_DNS2_Socket_Sockets
57 * class it used, otherwise, this class it used.
58 *
59 * @category Networking
60 * @package  Net_DNS2
61 * @author   Mike Pultz <mike@mikepultz.com>
62 * @license  http://www.opensource.org/licenses/bsd-license.php  BSD License
63 * @link     http://pear.php.net/package/Net_DNS2
64 * @see      Net_DNS2_Socket
65 *
66 */
67class Net_DNS2_Socket_Streams extends Net_DNS2_Socket
68{
69    private $_context;
70
71    /**
72     * opens a socket connection to the DNS server
73     *
74     * @return boolean
75     * @access public
76     *
77     */
78    public function open()
79    {
80        //
81        // create a list of options for the context
82        //
83        $opts = array('socket' => array());
84
85        //
86        // bind to a local IP/port if it's set
87        //
88        if (strlen($this->local_host) > 0) {
89
90            $opts['socket']['bindto'] = $this->local_host;
91            if ($this->local_port > 0) {
92
93                $opts['socket']['bindto'] .= ':' . $this->local_port;
94            }
95        }
96
97        //
98        // create the context
99        //
100        $this->_context = @stream_context_create($opts);
101
102        //
103        // create socket
104        //
105        $errno;
106        $errstr;
107
108        switch($this->type) {
109        case Net_DNS2_Socket::SOCK_STREAM:
110
111            if (Net_DNS2::isIPv4($this->host) == true) {
112
113                $this->sock = @stream_socket_client(
114                    'tcp://' . $this->host . ':' . $this->port,
115                    $errno, $errstr, $this->timeout,
116                    STREAM_CLIENT_CONNECT, $this->_context
117                );
118            } else if (Net_DNS2::isIPv6($this->host) == true) {
119
120                $this->sock = @stream_socket_client(
121                    'tcp://[' . $this->host . ']:' . $this->port,
122                    $errno, $errstr, $this->timeout,
123                    STREAM_CLIENT_CONNECT, $this->_context
124                );
125            } else {
126
127                $this->last_error = 'invalid address type: ' . $this->host;
128                return false;
129            }
130
131            break;
132
133        case Net_DNS2_Socket::SOCK_DGRAM:
134
135            if (Net_DNS2::isIPv4($this->host) == true) {
136
137                $this->sock = @stream_socket_client(
138                    'udp://' . $this->host . ':' . $this->port,
139                    $errno, $errstr, $this->timeout,
140                    STREAM_CLIENT_CONNECT, $this->_context
141                );
142            } else if (Net_DNS2::isIPv6($this->host) == true) {
143
144                $this->sock = @stream_socket_client(
145                    'udp://[' . $this->host . ']:' . $this->port,
146                    $errno, $errstr, $this->timeout,
147                    STREAM_CLIENT_CONNECT, $this->_context
148                );
149            } else {
150
151                $this->last_error = 'invalid address type: ' . $this->host;
152                return false;
153            }
154
155            break;
156
157        default:
158            $this->last_error = 'Invalid socket type: ' . $this->type;
159            return false;
160        }
161
162        if ($this->sock === false) {
163
164            $this->last_error = $errstr;
165            return false;
166        }
167
168        //
169        // set it to non-blocking and set the timeout
170        //
171        @stream_set_blocking($this->sock, 0);
172        @stream_set_timeout($this->sock, $this->timeout);
173
174        return true;
175    }
176
177    /**
178     * closes a socket connection to the DNS server
179     *
180     * @return boolean
181     * @access public
182     *
183     */
184    public function close()
185    {
186        if (is_resource($this->sock) === true) {
187
188            @fclose($this->sock);
189        }
190        return true;
191    }
192
193    /**
194     * writes the given string to the DNS server socket
195     *
196     * @param string $data a binary packed DNS packet
197     *
198     * @return boolean
199     * @access public
200     *
201     */
202    public function write($data)
203    {
204        $length = strlen($data);
205        if ($length == 0) {
206
207            $this->last_error = 'empty data on write()';
208            return false;
209        }
210
211        $read   = null;
212        $write  = array($this->sock);
213        $except = null;
214
215        //
216        // select on write
217        //
218        $result = stream_select($read, $write, $except, $this->timeout);
219        if ($result === false) {
220
221            $this->last_error = 'failed on write select()';
222            return false;
223
224        } else if ($result == 0) {
225
226            $this->last_error = 'timeout on write select()';
227            return false;
228        }
229
230        //
231        // if it's a TCP socket, then we need to packet and send the length of the
232        // data as the first 16bit of data.
233        //
234        if ($this->type == Net_DNS2_Socket::SOCK_STREAM) {
235
236            $s = chr($length >> 8) . chr($length);
237
238            if (@fwrite($this->sock, $s) === false) {
239
240                $this->last_error = 'failed to fwrite() 16bit length';
241                return false;
242            }
243        }
244
245        //
246        // write the data to the socket
247        //
248        $size = @fwrite($this->sock, $data);
249        if ( ($size === false) || ($size != $length) ) {
250
251            $this->last_error = 'failed to fwrite() packet';
252            return false;
253        }
254
255        return true;
256    }
257
258    /**
259     * reads a response from a DNS server
260     *
261     * @param integer &$size the size of the DNS packet read is passed back
262     *
263     * @return mixed         returns the data on success and false on error
264     * @access public
265     *
266     */
267    public function read(&$size, $max_size)
268    {
269        $read   = array($this->sock);
270        $write  = null;
271        $except = null;
272
273        //
274        // make sure our socket is non-blocking
275        //
276        @stream_set_blocking($this->sock, 0);
277
278        //
279        // select on read
280        //
281        $result = stream_select($read, $write, $except, $this->timeout);
282        if ($result === false) {
283
284            $this->last_error = 'error on read select()';
285            return false;
286
287        } else if ($result == 0) {
288
289            $this->last_error = 'timeout on read select()';
290            return false;
291        }
292
293        $data = '';
294        $length = $max_size;
295
296        //
297        // if it's a TCP socket, then the first two bytes is the length of the DNS
298        // packet- we need to read that off first, then use that value for the
299        // packet read.
300        //
301        if ($this->type == Net_DNS2_Socket::SOCK_STREAM) {
302
303            if (($data = fread($this->sock, 2)) === false) {
304
305                $this->last_error = 'failed on fread() for data length';
306                return false;
307            }
308
309            $length = ord($data[0]) << 8 | ord($data[1]);
310            if ($length < Net_DNS2_Lookups::DNS_HEADER_SIZE) {
311
312                return false;
313            }
314        }
315
316        //
317        // at this point, we know that there is data on the socket to be read,
318        // because we've already extracted the length from the first two bytes.
319        //
320        // so the easiest thing to do, is just turn off socket blocking, and
321        // wait for the data.
322        //
323        @stream_set_blocking($this->sock, 1);
324
325        //
326        // read the data from the socket
327        //
328        $data = '';
329
330        //
331        // the streams socket is weird for TCP sockets; it doesn't seem to always
332        // return all the data properly; but the looping code I added broke UDP
333        // packets- my fault-
334        //
335        // the sockets library works much better.
336        //
337        if ($this->type == Net_DNS2_Socket::SOCK_STREAM) {
338
339            $chunk = '';
340            $chunk_size = $length;
341
342            //
343            // loop so we make sure we read all the data
344            //
345            while (1) {
346
347                $chunk = fread($this->sock, $chunk_size);
348                if ($chunk === false) {
349
350                    $this->last_error = 'failed on fread() for data';
351                    return false;
352                }
353
354                $data .= $chunk;
355                $chunk_size -= strlen($chunk);
356
357                if (strlen($data) >= $length) {
358                    break;
359                }
360            }
361
362        } else {
363
364            //
365            // if it's UDP, it's a single fixed-size frame, and the streams library
366            // doesn't seem to have a problem reading it.
367            //
368            $data = fread($this->sock, $length);
369            if ($length === false) {
370
371                $this->last_error = 'failed on fread() for data';
372                return false;
373            }
374        }
375
376        $size = strlen($data);
377
378        return $data;
379    }
380}
381
382/*
383 * Local variables:
384 * tab-width: 4
385 * c-basic-offset: 4
386 * c-hanging-comment-ender-p: nil
387 * End:
388 */
389?>
390