1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4/**
5 * Simple wrapper interface for the Nmap utility.
6 *
7 * PHP version 5
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330,Boston,MA 02111-1307 USA
22 *
23 * @category  Net
24 * @package   Net_Nmap
25 * @author    Luca Corbo <lucor@ortro.net>
26 * @copyright 2008 Luca Corbo
27 * @license   GNU/LGPL v2.1
28 * @link      http://pear.php.net/packages/Net_Nmap
29 */
30
31require_once 'System.php';
32require_once 'Net/Nmap/Parser.php';
33require_once 'Net/Nmap/Exception.php';
34
35/**
36 * Simple wrapper interface for the Nmap utility.
37 *
38 * @category  Net
39 * @package   Net_Nmap
40 * @author    Luca Corbo <lucor@ortro.net>
41 * @copyright 2008 Luca Corbo
42 * @license   GNU/LGPL v2.1
43 * @link      http://pear.php.net/packages/Net_Nmap
44 */
45class Net_Nmap
46{
47
48    /**
49     * Location of the nmap binary
50     *
51     * @var string  $nmap_path
52     * @see Net_Nmap::__construct()
53     */
54    private $_nmap_binary;
55
56    /**
57     * Absolute path to store the Nmap XML output file.
58     *
59     * @var string
60     * @see Net_Nmap::__construct()
61     */
62    private $_output_file = null;
63
64    /**
65     * Delete Nmap output file after parsing
66     *
67     * @var    bool
68     */
69    private $_delete_output_file = true;
70
71    /**
72     * The hostname/IP failed to resolve
73     *
74     * @var    array
75     */
76    private $_failed_to_resolve = array();
77
78    /**
79     * Nmap option arguments
80     *
81     * @var array
82     * @link http://nmap.org/book/man-briefoptions.html
83     */
84    private $_nmap_options = array();
85
86    /**
87     * Statistics object from Nmap scan
88     *
89     * @var Net_Nmap_Stats
90     */
91    private $_nmap_stats = null;
92
93    /**
94     * Creates a new Nmap object
95     *
96     * Available options are:
97     *
98     * - string  nmap_binary:  The location of the Nmap binary.
99     *                         If not specified, defaults to '/usr/bin/nmap'.
100     *
101     * - string  output_file:  Path to store the Nmap XML output file.
102     *                         If not specified, a temporary file is created.
103     *
104     * @param array $options optional. An array of options used to create the
105     *                       Nmap object. All options must be optional and are
106     *                       represented as key-value pairs.
107     */
108    public function __construct(array $options = array())
109    {
110        if (array_key_exists('nmap_binary', $options)) {
111            $this->_nmap_binary = (string)$options['nmap_binary'];
112        } else {
113            $this->_nmap_binary = System::which('nmap');
114        }
115
116        if (array_key_exists('output_file', $options)) {
117            $this->_output_file = (string)$options['output_file'];
118        }
119    }
120
121    /**
122     * Prepare the command to execute
123     *
124     * @param array $targets contains hostnames, IP addresses, networks to scan
125     *
126     * @return string
127     */
128    protected function createCommandLine($targets)
129    {
130
131        if ($this->_output_file === null) {
132             $this->_output_file = tempnam(System::tmpdir(), __CLASS__);
133        } else {
134            $this->_delete_output_file = false;
135        }
136
137        $cmd  = escapeshellarg($this->_nmap_binary);
138        $cmd .= ' ' . implode(' ', $this->_nmap_options);
139        $cmd .= ' -oX ' . escapeshellarg($this->_output_file) . ' ';
140        foreach ($targets as $target) {
141            $cmd .= escapeshellarg($target) . ' ';
142        }
143        $cmd .= '2>&1';
144        if (OS_WINDOWS) {
145            $cmd = '"' . $cmd . '"';
146        }
147        return $cmd;
148    }
149
150    /**
151     * Scan the specified target
152     *
153     * @param array $targets   Array contains hostnames, IP addresses,
154     *                         networks to scan
155     * @param bool  $with_sudo Boolean to enable or not sudo scan
156     *
157     * @return true | PEAR_Error
158     * @throws Net_Nmap_Exception If Nmap binary does not exist or
159     *                            the command failed to execute.
160     */
161    public function scan($targets, $with_sudo = false)
162    {
163        $sudo = $with_sudo
164               ? 'sudo ' : '';
165
166        exec($sudo . $this->createCommandLine($targets), $out, $ret_var);
167
168        if ($ret_var > 0) {
169            throw new Net_Nmap_Exception(implode(' ', $out));
170        } else {
171            foreach ($out as $row) {
172                preg_match(
173                    '@^Failed to resolve given hostname/IP:\s+(.+)\.\s+Note@',
174                    $row,
175                    $matches
176                );
177
178                if (count($matches) > 0) {
179                    $this->_failed_to_resolve[] = $matches[1];
180                }
181            }
182            return true;
183        }
184    }
185
186    /**
187     * Get all the discovered hosts
188     *
189     * @param string $output_file Absolute path of the file to parse (optional)
190     *
191     * @return ArrayIterator      Returns Hosts Object on success.
192     * @throws Net_Nmap_Exception If a parsing error occurred.
193     */
194    public function parseXMLOutput($output_file = null)
195    {
196        if ($output_file === null) {
197            $output_file = $this->_output_file;
198        } else {
199            $this->_delete_output_file = false;
200        }
201        $parse = new Net_Nmap_Parser();
202        $parse->setInputFile($output_file);
203        $parse->folding = false;
204
205        $res = $parse->parse();
206        if (PEAR::isError($res)) {
207            throw new Net_Nmap_Exception($res);
208        }
209        if ($this->_delete_output_file) {
210            unlink($this->_output_file);
211        }
212        $this->_nmap_stats = $parse->getStats();
213        return $parse->getHosts();
214    }
215
216    /**
217     * Get all the hostnames/IPs failed to resolve during scanning operation
218     *
219     * @return Array    Returns array
220     */
221    public function getFailedToResolveHosts()
222    {
223        return $this->_failed_to_resolve;
224    }
225
226    /**
227     * Get Nmap Statistics
228     *
229     * @return Net_Nmap_Stats   Return an Nmap Stats Object
230     */
231    public function getNmapStats()
232    {
233        return $this->_nmap_stats;
234    }
235
236    /**
237     * Enable Nmap options
238     * Available nmap options are:
239     *
240     * - boolean os_detection: Enable the OS detection (-O).
241     * - boolean service_info: Probe open ports to determine
242     *                         service/version info (-sV)
243     * - string  port_ranges : Port ranges, only scan
244     *                         specified ports (-p <port ranges>)
245     *                         Ex: 22; 1-65535; U:53,111,137,T:21-25,80,139,8080
246     * - boolean all_options : Enables OS detection and Version detection,
247     *                         Script scanning and Traceroute (-A)
248     *
249     * @param array $nmap_options Nmap options to enable
250     *
251     * @return void
252     * @link http://nmap.org/book/man-briefoptions.html
253     * @throws Net_Nmap_Exception If the option argument is not valid.
254     */
255    public function enableOptions($nmap_options)
256    {
257        $enable_os_detection = array_key_exists('os_detection', $nmap_options);
258        $enable_service_info = array_key_exists('service_info', $nmap_options);
259        $enable_port_ranges  = array_key_exists('port_ranges', $nmap_options);
260        $enable_all_options  = array_key_exists('all_options', $nmap_options);
261
262        if ($enable_os_detection && $nmap_options['os_detection']) {
263            $this->_nmap_options[] = '-O';
264        }
265        if ($enable_service_info && $nmap_options['service_info']) {
266            $this->_nmap_options[] = '-sV';
267        }
268        if ($enable_port_ranges) {
269            $port_ranges    = $nmap_options['port_ranges'];
270            $allowed_format = '([U,T]\:)*[0-9]+(-[0-9]+)*';
271
272            $regexp = '/^' . $allowed_format . '(,' . $allowed_format . ')*$/';
273            if (preg_match($regexp, $port_ranges)) {
274                $this->_nmap_options[] = '-p ' . $port_ranges;
275            } else {
276                throw new Net_Nmap_Exception('Port ranges: not valid format.');
277            }
278        }
279        if ($enable_all_options && $nmap_options['all_options']) {
280            $this->_nmap_options[] = '-A';
281        }
282    }
283}
284