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 sockets extension
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 this class is used, if
57 * it's not, then the Net_DNS2_Socket_Streams class is 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_Sockets extends Net_DNS2_Socket
68{
69    /**
70     * opens a socket connection to the DNS server
71     *
72     * @return boolean
73     * @access public
74     *
75     */
76    public function open()
77    {
78        //
79        // create the socket
80        //
81        if (Net_DNS2::isIPv4($this->host) == true) {
82
83            $this->sock = @socket_create(
84                AF_INET, $this->type,
85                ($this->type == Net_DNS2_Socket::SOCK_STREAM) ? SOL_TCP : SOL_UDP
86            );
87
88        } else if (Net_DNS2::isIPv6($this->host) == true) {
89
90            $this->sock = @socket_create(
91                AF_INET6, $this->type,
92                ($this->type == Net_DNS2_Socket::SOCK_STREAM) ? SOL_TCP : SOL_UDP
93            );
94
95        } else {
96
97            $this->last_error = 'invalid address type: ' . $this->host;
98            return false;
99        }
100
101        if ($this->sock === false) {
102
103            $this->last_error = socket_strerror(socket_last_error());
104            return false;
105        }
106
107        @socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1);
108
109        //
110        // bind to a local IP/port if it's set
111        //
112        if (strlen($this->local_host) > 0) {
113
114            $result = @socket_bind(
115                $this->sock, $this->local_host,
116                ($this->local_port > 0) ? $this->local_port : null
117            );
118            if ($result === false) {
119
120                $this->last_error = socket_strerror(socket_last_error());
121                return false;
122            }
123        }
124
125        //
126        // mark the socket as non-blocking
127        //
128        if (@socket_set_nonblock($this->sock) === false) {
129
130            $this->last_error = socket_strerror(socket_last_error());
131            return false;
132        }
133
134        //
135        // connect to the socket; don't check for status here, we'll check it on the
136        // socket_select() call so we can handle timeouts properly
137        //
138        @socket_connect($this->sock, $this->host, $this->port);
139
140        $read   = null;
141        $write  = array($this->sock);
142        $except = null;
143
144        //
145        // select on write to check if the call to connect worked
146        //
147        $result = @socket_select($read, $write, $except, $this->timeout);
148        if ($result === false) {
149
150            $this->last_error = socket_strerror(socket_last_error());
151            return false;
152
153        } else if ($result == 0) {
154
155            $this->last_error = 'timeout on write select for connect()';
156            return false;
157        }
158
159        return true;
160    }
161
162    /**
163     * closes a socket connection to the DNS server
164     *
165     * @return boolean
166     * @access public
167     *
168     */
169    public function close()
170    {
171        if (is_resource($this->sock) === true) {
172
173            @socket_close($this->sock);
174        }
175        return true;
176    }
177
178    /**
179     * writes the given string to the DNS server socket
180     *
181     * @param string $data a binary packed DNS packet
182     *
183     * @return boolean
184     * @access public
185     *
186     */
187    public function write($data)
188    {
189        $length = strlen($data);
190        if ($length == 0) {
191
192            $this->last_error = 'empty data on write()';
193            return false;
194        }
195
196        $read   = null;
197        $write  = array($this->sock);
198        $except = null;
199
200        //
201        // select on write
202        //
203        $result = @socket_select($read, $write, $except, $this->timeout);
204        if ($result === false) {
205
206            $this->last_error = socket_strerror(socket_last_error());
207            return false;
208
209        } else if ($result == 0) {
210
211            $this->last_error = 'timeout on write select()';
212            return false;
213        }
214
215        //
216        // if it's a TCP socket, then we need to packet and send the length of the
217        // data as the first 16bit of data.
218        //
219        if ($this->type == Net_DNS2_Socket::SOCK_STREAM) {
220
221            $s = chr($length >> 8) . chr($length);
222
223            if (@socket_write($this->sock, $s) === false) {
224
225                $this->last_error = socket_strerror(socket_last_error());
226                return false;
227            }
228        }
229
230        //
231        // write the data to the socket
232        //
233        $size = @socket_write($this->sock, $data);
234        if ( ($size === false) || ($size != $length) ) {
235
236            $this->last_error = socket_strerror(socket_last_error());
237            return false;
238        }
239
240        return true;
241    }
242
243    /**
244     * reads a response from a DNS server
245     *
246     * @param integer &$size the size of the DNS packet read is passed back
247     *
248     * @return mixed         returns the data on success and false on error
249     * @access public
250     *
251     */
252    public function read(&$size, $max_size)
253    {
254        $read   = array($this->sock);
255        $write  = null;
256        $except = null;
257
258        //
259        // make sure our socket is non-blocking
260        //
261        if (@socket_set_nonblock($this->sock) === false) {
262
263            $this->last_error = socket_strerror(socket_last_error());
264            return false;
265        }
266
267        //
268        // select on read
269        //
270        $result = @socket_select($read, $write, $except, $this->timeout);
271        if ($result === false) {
272
273            $this->last_error = socket_strerror(socket_last_error());
274            return false;
275
276        } else if ($result == 0) {
277
278            $this->last_error = 'timeout on read select()';
279            return false;
280        }
281
282        $data = '';
283        $length = $max_size;
284
285        //
286        // if it's a TCP socket, then the first two bytes is the length of the DNS
287        // packet- we need to read that off first, then use that value for the
288        // packet read.
289        //
290        if ($this->type == Net_DNS2_Socket::SOCK_STREAM) {
291
292            if (($size = @socket_recv($this->sock, $data, 2, 0)) === false) {
293
294                $this->last_error = socket_strerror(socket_last_error());
295                return false;
296            }
297
298            $length = ord($data[0]) << 8 | ord($data[1]);
299            if ($length < Net_DNS2_Lookups::DNS_HEADER_SIZE) {
300
301                return false;
302            }
303        }
304
305        //
306        // at this point, we know that there is data on the socket to be read,
307        // because we've already extracted the length from the first two bytes.
308        //
309        // so the easiest thing to do, is just turn off socket blocking, and
310        // wait for the data.
311        //
312        if (@socket_set_block($this->sock) === false) {
313
314            $this->last_error = socket_strerror(socket_last_error());
315            return false;
316        }
317
318        //
319        // read the data from the socket
320        //
321        // loop while reading since some OS's (specifically Win < 2003) don't support
322        // MSG_WAITALL properly, so they may return with less data than is available.
323        //
324        // According to M$, XP and below don't support MSG_WAITALL at all; and there
325        // also seems to be some issue in 2003 and 2008 where the MSG_WAITALL is
326        // defined as 0, but if you actually pass 8 (which is the correct defined
327        // value), it works as it's supposed to- so in these cases, it's just the
328        // define that's incorrect- this is likely a PHP issue.
329        //
330        $data = '';
331        $size = 0;
332
333        while (1) {
334
335            $chunk_size = @socket_recv($this->sock, $chunk, $length, MSG_WAITALL);
336            if ($chunk_size === false) {
337
338                $size = $chunk_size;
339                $this->last_error = socket_strerror(socket_last_error());
340
341                return false;
342            }
343
344            $data .= $chunk;
345            $size += $chunk_size;
346
347            $length -= $chunk_size;
348            if ( ($length <= 0) || ($this->type == Net_DNS2_Socket::SOCK_DGRAM) ) {
349                break;
350            }
351        }
352
353        return $data;
354    }
355}
356
357/*
358 * Local variables:
359 * tab-width: 4
360 * c-basic-offset: 4
361 * c-hanging-comment-ender-p: nil
362 * End:
363 */
364?>
365