1<?php // -*- mode:php; tab-width:4; c-basic-offset:4; intent-tabs-mode:nil; -*- 2/* 3 * This file contains the MAC class 4 * 5 * Copyright (c) 2006 Andrew Teixeira 6 * 7 * PHP Version 5 8 * 9 * LICENSE: This source file is subject to version 3.0 of the PHP license 10 * that is available through the world-wide-web at the following URI: 11 * http://www.php.net/license/3_0.txt. If you did not receive a copy of 12 * the PHP License and are unable to obtain it through the web, please 13 * send a note to license@php.net so we can mail you a copy immediately. 14 * 15 * @license http://www.opensource.org/licenses/bsd-license.php BSD License 16 * @link http://pear.php.net/package/Net_MAC 17 * @author Andrew Teixeira <ateixeira@gmail.com> 18 * 19 * $Id: MAC.php,v 1.6 2007/05/01 18:03:57 atex Exp $ 20 */ 21 22/** 23 * Require PEAR/Exception.php since we will be using PEAR Exceptions 24 * in this class. 25 */ 26require_once 'PEAR/Exception.php'; 27 28/** 29 * Constant to represent the maximum length of a line in the 30 * manufacturers file. 31 */ 32define('NET_MAC_LINE_MAXLENGTH', 256); 33 34/** 35 * Error constant: signifies no problem (OK) 36 */ 37define('NET_MAC_ERROR_OK', 0); 38 39/** 40 * Error constant: signifies a bad option being passed to a function 41 */ 42define('NET_MAC_ERROR_BADOPT', 1); 43 44/** 45 * Error constant: signifies bad data being passed to a function 46 */ 47define('NET_MAC_ERROR_BADDATA', 2); 48 49/** 50 * Error constant: signifies a bad database connection 51 */ 52define('NET_MAC_ERROR_BADDB', 3); 53 54/** 55 * Error constant: signifies a bad manufacturers file 56 */ 57define('NET_MAC_ERROR_BADFILE', 4); 58 59 60/** 61 * Extension of the main PEAR_Exception Class for use with the Net_MAC 62 * class. 63 * 64 * @package Net_MAC 65 * @license http://www.opensource.org/licenses/bsd-license.php BSD License 66 * @link http://pear.php.net/package/Net_MAC 67 * @version 1.0 68 * @author Andrew Teixeira <ateixeira@gmail.com> 69 * 70 * @access public 71 */ 72class Net_MAC_Exception extends PEAR_Exception {} 73 74 75/** 76 * Class to validate and cleanly format Media Access Control (MAC) 77 * addresses 78 * 79 * @package Net_MAC 80 * @license http://www.php.net/license/3_0.txt PHP License 3.0 81 * @link http://pear.php.net/package/Net_MAC 82 * @version 1.0 83 * @author Andrew Teixeira <ateixeira@gmail.com> 84 * 85 * @access public 86 */ 87class Net_MAC 88{ 89 /** 90 * The MAC address to work on. 91 * 92 * @access protected 93 * @var string 94 */ 95 protected $_macaddr; 96 97 /** 98 * A database instance to use in looking up MAC-to-vendor 99 * relationships. 100 * 101 * @access protected 102 * @var object 103 */ 104 protected $_db; 105 106 /** 107 * The options to use while connecting to the database. 108 * 109 * @access protected 110 * @var array 111 */ 112 protected $_dbOptions; 113 114 /** 115 * The default constructor 116 * 117 * This is the default constructor that will create and populate a 118 * valid Net_MAC object. 119 * 120 * @access public 121 * 122 * @param object &$db A valid instantiated {@link 123 * http://pear.php.net/package/MDB2/ MDB2} object to use when 124 * adding/retrieving information from the database for MAC address 125 * vendors 126 * @param array $options An array of options to use with the 127 * database in retrieving MAC address vendors. The associative 128 * array should have key/value pairs as follows: 129 * 130 * <ul> 131 * <li><b>tablename</b>: The name of the table where MAC address 132 * vendor information lives</li> 133 * <li><b>macaddrcol</b>: The name of the column containing the 134 * MAC address prefixes</li> 135 * <li><b>vendorcol</b>: The name of the column containing the 136 * vendor name</li> 137 * <li><b>desccol</b>: The name of the column containing any 138 * extra descriptive information derived from the vendor list</li> 139 * </ul> 140 * 141 * @return void No return value. A {@link Net_MAC_Exception 142 * Net_MAC_Exception} Exception object will be thrown if there is 143 * an error during construction so the constructor should be 144 * called from a try/catch block 145 */ 146 function __construct(&$db, $options = NULL) { 147 require_once 'MDB2.php'; 148 149 $this->_db = NULL; 150 $this->_macaddr = NULL; 151 $this->_dbOptions = NULL; 152 153 if (!$db instanceof MDB2_Driver_Common) { 154 throw new Net_MAC_Exception('Bad database object', NET_MAC_ERROR_BADDB); 155 } 156 157 if (!is_array($options)) { 158 throw new Net_MAC_Exception('Second parameter must be an array', NET_MAC_ERROR_BADOPT); 159 } 160 161 if (!isset($options['tablename'])) { 162 throw new Net_MAC_Exception('No table name given in options', NET_MAC_ERROR_BADDATA); 163 } 164 165 if (!isset($options['macaddrcol'])) { 166 throw new Net_MAC_Exception('No MAC address column name given in options', NET_MAC_ERROR_BADDATA); 167 } 168 169 if (!isset($options['vendorcol'])) { 170 throw new Net_MAC_Exception('No vendor column name given in options', NET_MAC_ERROR_BADDATA); 171 } 172 173 if (!isset($options['desccol'])) { 174 throw new Net_MAC_Exception('No description column name given in options', NET_MAC_ERROR_BADDATA); 175 } 176 177 $this->_db = $db; 178 $this->_db->loadModule('Extended'); 179 $this->_dbOptions = $options; 180 } /* end default constructor */ 181 182 /** 183 * Checks a MAC address 184 * 185 * This function will check a MAC address to make sure it is 186 * valid. 187 * 188 * @access static 189 * 190 * @param string $input The string containing the MAC Address 191 * @param string $delimiter The string representing the delimiter 192 * to use when verifying the MAC Address 193 * 194 * @return string <b>true</b> if the MAC address is valid, 195 * <b>false</b> otherwise 196 */ 197 static function check($input, $delimiter = ':') 198 { 199 // Check for 6 octets without any punctuation 200 $retval = preg_match('/^([0-9a-fA-F][0-9a-fA-F]\Q'.$delimiter.'\E){5}([0-9a-fA-F][0-9a-fA-F]){1}$/', $input); 201 202 return $retval; 203 } /* end method check */ 204 205 /** 206 * Sets the MAC address 207 * 208 * This method will set the MAC address in the class. 209 * 210 * @access public 211 * 212 * @param string $macaddr The string representing the MAC address 213 * @param string $delimiter The string representing the delimiter 214 * to use when verifying the MAC Address 215 * 216 * @return bool Returns <b>true</b> if the MAC address is set 217 * correctly, <b>false</b> otherwise 218 */ 219 function setMAC($macaddr, $delimiter = ':') 220 { 221 $validMAC = self::check($macaddr, $delimiter); 222 if ($validMAC) { 223 $this->_macaddr = $macaddr; 224 return true; 225 } 226 227 return false; 228 } /* end method setMac */ 229 230 /** 231 * Formats a MAC address 232 * 233 * This function will format a MAC address into XX:XX:XX:XX:XX:XX 234 * format from whatever format is passed to the function. The 235 * delimiter (: in the example above) will be replaced with 236 * whatever string is passed to the $delimiter parameter 237 * (default :). 238 * 239 * @access static 240 * 241 * @param string $input The string containing the MAC Address 242 * @param string $delimiter The string representing the delimiter 243 * to use when formatting the MAC Address 244 * @param bool $uppercase If set to true (default), the 245 * hexadecimal values in the MAC Address will be returned in 246 * uppercase. If false, the hexadecimal values will be returned 247 * in lowercase. 248 * 249 * @return string The formatted MAC Address or <b>false</b> on 250 * error 251 */ 252 static function format($input, $delimiter = ':', $uppercase = true) 253 { 254 /* Replace all characters not in a valid MAC address with 255 * nothing. We are going to be testing for a MAC address as 256 * XXXXXXXXXXXX instead of XX:XX:XX:XX:XX:XX 257 */ 258 $macaddr = preg_replace('/[g-zG-Z]*\W*_*/', '', $input); 259 260 /* If $uppercase is true, set all the alpha characters to 261 * uppercase, otherwise set all the alpha characters to 262 * lowercase 263 */ 264 $macaddr = ($uppercase) ? strtoupper($macaddr) : strtolower($macaddr); 265 266 // Check for 6 octets without any punctuation 267 if (!preg_match('/^([0-9a-fA-F][0-9a-fA-F]){6}$/', $macaddr)) { 268 return false; 269 } 270 271 // Add back in the $delimiter delimiters 272 $macaddr = preg_replace('/([0-9a-fA-F][0-9a-fA-F]){1}/', '$1' . $delimiter, $macaddr); 273 274 // Remove the trailing $delimiter 275 $macaddr = preg_replace('/' . $delimiter . '$/', '', $macaddr); 276 277 return $macaddr; 278 } /* end method format */ 279 280 /** 281 * Import a manufacturers' file to the database 282 * 283 * This method will parse a manufacturers' file, such as the one 284 * from {@link 285 * http://anonsvn.wireshark.org/wireshark/trunk/manuf}, containing 286 * a list of MAC address prefix-to-vendor relationships. If the 287 * $doReturn parameter is <b>false</b>, then the data will be 288 * imported into the database defined by the factory of this 289 * class. However, if $doReturn is <b>true</b>, then the return 290 * will be an associative array with the key being the MAC address 291 * prefix and the data being an associative array with the keys 292 * 'vendor' and 'description'. 293 * 294 * @access public 295 * 296 * @param string $file The filename or URL of the manufacturers' 297 * file to parse. 298 * @param bool $doReturn If <b>true</b>, an array will be 299 * returned, if <b>false</b>, the data will be imported into the 300 * database. (default: false) 301 * 302 * @return mixed If $doReturn is true, the method will return an 303 * array. Otherwise, the method will return <b>true</b> on 304 * success. A {@link Net_MAC_Exception Net_MAC_Exception} 305 * Exception object will be thrown on failure in either case. 306 */ 307 function importVendors($file, $doReturn = false) { 308 if ($file == NULL) { 309 throw new Net_MAC_Exception('No file or URL given', NET_MAC_ERROR_BADFILE); 310 } 311 312 $fp = @fopen($file, 'r'); 313 if ($fp == false) { 314 throw new Net_MAC_Exception('Cannot open the file or URL given', NET_MAC_ERROR_BADFILE); 315 } 316 317 if ($doReturn) { 318 $retArr = array(); 319 } 320 else { 321 // Prepare parameters for MDB2->buildManipSQL() 322 $fields = array ($this->_dbOptions['macaddrcol'], 323 $this->_dbOptions['vendorcol'], 324 $this->_dbOptions['desccol']); 325 326 $sql = $this->_db->buildManipSQL($this->_dbOptions['tablename'], 327 $fields, MDB2_AUTOQUERY_INSERT); 328 329 $query = $this->_db->prepare($sql); 330 } 331 332 while ($line = fgets($fp, NET_MAC_LINE_MAXLENGTH)) { 333 // Remove comments 334 if ( preg_match('/^\#/', $line) ) { 335 continue; 336 } 337 338 if ( preg_match('/^([0-9A-Fa-f][0-9A-Fa-f]:){2}([0-9A-Fa-f][0-9A-Fa-f]){1}/', $line) ) { 339 $pieces = preg_split('/\s+/', $line); 340 341 // Since the file is space-delimited, we now need to 342 // reconstruct the description field if it existed 343 $desc = NULL; 344 for ($i = 3; $i < count($pieces); $i++) { 345 $desc .= $pieces[$i].' '; 346 } 347 $desc = rtrim($desc); 348 349 if ( (isset($pieces[0])) && (isset($pieces[1])) ) { 350 if ($doReturn) { 351 $retArr[$pieces[0]] = array('vendor' => $pieces[1], 352 'description' => $desc); 353 } 354 else { 355 $values = array($pieces[0], $pieces[1], $desc); 356 $result = $query->execute($values); 357 } 358 } 359 } 360 } 361 362 return ($doReturn) ? $retArr : true; 363 } /* end method importVendors */ 364 365 /** 366 * Finds the vendor for a MAC address 367 * 368 * This method will search through the database to find a vendor 369 * that matches the MAC address stored in the class using {@link 370 * setMAC setMAC}. If the $macList parameter is set, the method 371 * will use the array stored in $macList as the data source to 372 * find the MAC vendor instead of the database. The array would 373 * have to be an array with the same characteristics as one 374 * returned from the {@link importVendors importVendors} method 375 * when using the $doReturn parameter. 376 * 377 * @access public 378 * 379 * @param bool $getDescription If set to true, the return value 380 * will be an array with keys 'vendor' and 'description'. 381 * Normally the method will simply return the vendor name. 382 * @param array $macList An optional list of MAC-to-vendor 383 * relationships to search instead of using the 384 * database. (default: NULL) 385 * 386 * @return mixed Returns an associative array if $getDescription 387 * is <b>true</b>, returns a string with the vendor name if 388 * $getDescription is <b>false</b>. If the MAC vendor cannot be 389 * found in the vendor list, <b>false</b> is returned. 390 */ 391 function findVendor($getDescription = false, $macList = NULL) 392 { 393 if ($macList == NULL) { 394 $macaddrcol = $this->_dbOptions['macaddrcol']; 395 $vendorcol = $this->_dbOptions['vendorcol']; 396 $desccol = $this->_dbOptions['desccol']; 397 398 /* The manufacturers' list only uses the first 3 octets, 399 * so we need to get that portion of the stored MAC 400 * address. */ 401 $macaddr = substr(self::format($this->_macaddr, ':'), 0, 8); 402 403 // Prepare parameters for MDB2->buildManipSQL() 404 $fields = array ($macaddrcol, $vendorcol, $desccol); 405 $where = "$macaddrcol = '$macaddr'"; 406 407 $sql = $this->_db->buildManipSQL($this->_dbOptions['tablename'], 408 $fields, MDB2_AUTOQUERY_SELECT, 409 $where); 410 411 $query = $this->_db->prepare($sql); 412 $result = $query->execute(); 413 414 if ( (MDB2::isError($result)) || ($result->numRows() == 0) ) { 415 return false; 416 } 417 418 $macInfo = $result->fetchRow(MDB2_FETCHMODE_ASSOC); 419 420 if ($getDescription) { 421 return array($vendorcol => $macInfo[$vendorcol], 422 $desccol => $macInfo[$desccol]); 423 } 424 else { 425 return $macInfo[$vendorcol]; 426 } 427 } 428 else { 429 return false; 430 } 431 } /* end method findVendor */ 432 433} /* end class Net_MAC */ 434 435/* 436 * Local variables: 437 * tab-width: 4 438 * c-basic-offset: 4 439 * indent-tabs-mode: nil 440 * End: 441 */ 442?> 443