1<?php 2/** 3 * Skeleton Test class file 4 * 5 * @package PhpSecInfo 6 * @author Ed Finkler <coj@funkatron.com> 7 */ 8 9/** 10 * require the main PhpSecInfo class 11 */ 12require_once('PhpSecInfo/PhpSecInfo.php'); 13 14 15 16define ('PHPSECINFO_TEST_RESULT_OK', -1); 17 18define ('PHPSECINFO_TEST_RESULT_NOTICE', -2); 19 20define ('PHPSECINFO_TEST_RESULT_WARN', -4); 21 22define ('PHPSECINFO_TEST_RESULT_ERROR', -1024); 23 24define ('PHPSECINFO_TEST_RESULT_NOTRUN', -2048); 25 26define ('PHPSECINFO_TEST_COMMON_TMPDIR', '/tmp'); 27 28define ('PHPSECINFO_TEST_MOREINFO_BASEURL', 'http://phpsec.org/projects/phpsecinfo/tests/'); 29 30/** 31 * This is a skeleton class for PhpSecInfo tests You should extend this to make a "group" skeleton 32 * to categorize tests under, then make a subdir with your group name that contains test classes 33 * extending your group skeleton class. 34 * @package PhpSecInfo 35 */ 36class PhpSecInfo_Test 37{ 38 39 /** 40 * This value is used to group test results together. 41 * 42 * For example, all tests related to the mysql lib should be grouped under "mysql." 43 * 44 * @var string 45 */ 46 var $test_group = 'misc'; 47 48 49 /** 50 * This should be a <b>unique</b>, human-readable identifier for this test 51 * 52 * @var string 53 */ 54 var $test_name = 'misc_test'; 55 56 57 /** 58 * This is the recommended value the test will be looking for 59 * 60 * @var mixed 61 */ 62 var $recommended_value = "bar"; 63 64 65 /** 66 * The result returned from the test 67 * 68 * @var integer 69 */ 70 var $_result = PHPSECINFO_TEST_RESULT_NOTRUN; 71 72 73 /** 74 * The message corresponding to the result of the test 75 * 76 * @var string 77 */ 78 var $_message; 79 80 81 /** 82 * the language code. Should be a pointer to the setting in the PhpSecInfo object 83 * 84 * @var string 85 */ 86 var $_language = PHPSECINFO_LANG_DEFAULT; 87 88 /** 89 * Enter description here... 90 * 91 * @var mixed 92 */ 93 var $current_value; 94 95 /** 96 * This is a hash of messages that correspond to various test result levels. 97 * 98 * There are five messages, each corresponding to one of the result constants 99 * (PHPSECINFO_TEST_RESULT_OK, PHPSECINFO_TEST_RESULT_NOTICE, PHPSECINFO_TEST_RESULT_WARN, 100 * PHPSECINFO_TEST_RESULT_ERROR, PHPSECINFO_TEST_RESULT_NOTRUN) 101 * 102 * 103 * @var array array 104 */ 105 var $_messages = array(); 106 107 108 /** 109 * Constructor for Test skeleton class 110 * 111 * @return PhpSecInfo_Test 112 */ 113 function PhpSecInfo_Test() { 114 //$this->_setTestValues(); 115 116 $this->_retrieveCurrentValue(); 117 //$this->setRecommendedValue(); 118 119 $this->_setMessages(); 120 } 121 122 123 /** 124 * Determines whether or not it's appropriate to run this test (for example, if 125 * this test is for a particular library, it shouldn't be run if the lib isn't 126 * loaded). 127 * 128 * This is a terrible name, but I couldn't think of a better one atm. 129 * 130 * @return boolean 131 */ 132 function isTestable() { 133 134 return true; 135 } 136 137 138 /** 139 * The "meat" of the test. This is where the real test code goes. You should override this when extending 140 * 141 * @return integer 142 */ 143 function _execTest() { 144 145 return PHPSECINFO_TEST_RESULT_NOTRUN; 146 } 147 148 149 /** 150 * This function loads up result messages into the $this->_messages array. 151 * 152 * Using this method rather than setting $this->_messages directly allows result 153 * messages to be inherited. This is broken out into a separate function rather 154 * than the constructor for ease of extension purposes (php4 is whack, man). 155 * 156 */ 157 function _setMessages() { 158 $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'This setting should be safe'); 159 $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'This could potentially be a security issue'); 160 $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'This setting may be a serious security problem'); 161 $this->setMessageForResult(PHPSECINFO_TEST_RESULT_ERROR, 'en', 'There was an error running this test'); 162 $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'This test cannot be run'); 163 } 164 165 166 /** 167 * Placeholder - extend for tests 168 * 169 */ 170 function _retrieveCurrentValue() { 171 $this->current_value = "foo"; 172 } 173 174 175 176 /** 177 * This is the wrapper that executes the test and sets the result code and message 178 */ 179 function test() { 180 $result = $this->_execTest(); 181 $this->_setResult($result); 182 183 } 184 185 186 187 /** 188 * Retrieves the result 189 * 190 * @return integer 191 */ 192 function getResult() { 193 return $this->_result; 194 } 195 196 197 198 199 /** 200 * Retrieves the message for the current result 201 * 202 * @return string 203 */ 204 function getMessage() { 205 if (!isset($this->_message) || empty($this->_message)) { 206 $this->_setMessage($this->_result, $this->_language); 207 } 208 209 return $this->_message; 210 } 211 212 213 214 /** 215 * Sets the message for a given result code and language 216 * 217 * <code> 218 * $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'This test cannot be run'); 219 * </code> 220 * 221 * @param integer $result_code 222 * @param string $language_code 223 * @param string $message 224 * 225 */ 226 function setMessageForResult($result_code, $language_code, $message) { 227 228 if ( !isset($this->_messages[$result_code]) ) { 229 $this->_messages[$result_code] = array(); 230 } 231 232 if ( !is_array($this->_messages[$result_code]) ) { 233 $this->_messages[$result_code] = array(); 234 } 235 236 $this->_messages[$result_code][$language_code] = $message; 237 238 } 239 240 241 242 243 /** 244 * returns the current value. This function should be used to access the 245 * value for display. All values are cast as strings 246 * 247 * @return string 248 */ 249 function getCurrentTestValue() { 250 return $this->getStringValue($this->current_value); 251 } 252 253 /** 254 * returns the recommended value. This function should be used to access the 255 * value for display. All values are cast as strings 256 * 257 * @return string 258 */ 259 function getRecommendedTestValue() { 260 return $this->getStringValue($this->recommended_value); 261 } 262 263 264 /** 265 * Sets the result code 266 * 267 * @param integer $result_code 268 */ 269 function _setResult($result_code) { 270 $this->_result = $result_code; 271 } 272 273 274 /** 275 * Sets the $this->_message variable based on the passed result and language codes 276 * 277 * @param integer $result_code 278 * @param string $language_code 279 */ 280 function _setMessage($result_code, $language_code) { 281 $messages = $this->_messages[$result_code]; 282 $message = $messages[$language_code]; 283 $this->_message = $message; 284 } 285 286 287 /** 288 * Returns a link to a page with detailed information about the test 289 * 290 * URL is formatted as PHPSECINFO_TEST_MOREINFO_BASEURL + testName 291 * 292 * @see PHPSECINFO_TEST_MOREINFO_BASEURL 293 * 294 * @return string|boolean 295 */ 296 function getMoreInfoURL() { 297 if ($tn = $this->getTestName()) { 298 return PHPSECINFO_TEST_MOREINFO_BASEURL.strtolower("{$tn}.html"); 299 } else { 300 return false; 301 } 302 } 303 304 305 306 307 /** 308 * This retrieves the name of this test. 309 * 310 * If a name has not been set, this returns a formatted version of the class name. 311 * 312 * @return string 313 */ 314 function getTestName() { 315 if (isset($this->test_name) && !empty($this->test_name)) { 316 return $this->test_name; 317 } else { 318 return ucwords( 319 str_replace('_', ' ', 320 get_class($this) 321 ) 322 ); 323 } 324 325 } 326 327 328 /** 329 * sets the test name 330 * 331 * @param string $test_name 332 */ 333 function setTestName($test_name) { 334 $this->test_name = $test_name; 335 } 336 337 338 /** 339 * Returns the test group this test belongs to 340 * 341 * @return string 342 */ 343 function getTestGroup() { 344 return $this->test_group; 345 } 346 347 348 /** 349 * sets the test group 350 * 351 * @param string $test_group 352 */ 353 function setTestGroup($test_group) { 354 $this->test_group = $test_group; 355 } 356 357 358 359 /** 360 * This function takes the shorthand notation used in memory limit settings for PHP 361 * and returns the byte value. Totally stolen from http://us3.php.net/manual/en/function.ini-get.php 362 * 363 * <code> 364 * echo 'post_max_size in bytes = ' . $this->return_bytes(ini_get('post_max_size')); 365 * </code> 366 * 367 * @link http://php.net/manual/en/function.ini-get.php 368 * @param string $val 369 * @return integer 370 */ 371 function returnBytes($val) { 372 $val = trim($val); 373 374 if ( (int)$val === 0 ) { 375 return 0; 376 } 377 378 $last = strtolower($val{strlen($val)-1}); 379 switch($last) { 380 // The 'G' modifier is available since PHP 5.1.0 381 case 'g': 382 $val *= 1024; 383 case 'm': 384 $val *= 1024; 385 case 'k': 386 $val *= 1024; 387 } 388 389 return $val; 390 } 391 392 393 /** 394 * This just does the usual PHP string casting, except for 395 * the boolean FALSE value, where the string "0" is returned 396 * instead of an empty string 397 * 398 * @param mixed $val 399 * @return string 400 */ 401 function getStringValue($val) { 402 if ($val === FALSE) { 403 return "0"; 404 } else { 405 return (string)$val; 406 } 407 } 408 409 410 /** 411 * This method converts the several possible return values from 412 * allegedly "boolean" ini settings to proper booleans 413 * 414 * Properly converted input values are: 'off', 'on', 'false', 'true', '', '0', '1' 415 * (the last two might not be neccessary, but I'd rather be safe) 416 * 417 * If the ini_value doesn't match any of those, the value is returned as-is. 418 * 419 * @param string $ini_key the ini_key you need the value of 420 * @return boolean|mixed 421 */ 422 function getBooleanIniValue($ini_key) { 423 424 $ini_val = ini_get($ini_key); 425 426 switch ( strtolower($ini_val) ) { 427 428 case 'off': 429 return false; 430 break; 431 case 'on': 432 return true; 433 break; 434 case 'false': 435 return false; 436 break; 437 case 'true': 438 return true; 439 break; 440 case '0': 441 return false; 442 break; 443 case '1': 444 return true; 445 break; 446 case '': 447 return false; 448 break; 449 default: 450 return $ini_val; 451 452 } 453 454 } 455 456 /** 457 * sys_get_temp_dir provides some temp dir detection capability 458 * that is lacking in versions of PHP that do not have the 459 * sys_get_temp_dir() function 460 * 461 * @return string|NULL 462 */ 463 function sys_get_temp_dir() { 464 // Try to get from environment variable 465 if ( !empty($_ENV['TMP']) ) { 466 return realpath( $_ENV['TMP'] ); 467 } else if ( !empty($_ENV['TMPDIR']) ) { 468 return realpath( $_ENV['TMPDIR'] ); 469 } else if ( !empty($_ENV['TEMP']) ) { 470 return realpath( $_ENV['TEMP'] ); 471 } else { 472 return NULL; 473 } 474 } 475 476 477 /** 478 * A quick function to determine whether we're running on Windows. 479 * Uses the PHP_OS constant. 480 * 481 * @return boolean 482 */ 483 function osIsWindows() { 484 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 485 return true; 486 } else { 487 return false; 488 } 489 } 490 491 492 /** 493 * Returns an array of data returned from the UNIX 'id' command 494 * 495 * includes uid, username, gid, groupname, and groups (if "exec" 496 * is enabled). Groups is an array of all the groups the user 497 * belongs to. Keys are the group ids, values are the group names. 498 * 499 * returns FALSE if no suitable function is available to retrieve 500 * the data 501 * 502 * @return array|boolean 503 */ 504 function getUnixId() { 505 if ($this->osIsWindows()) { 506 return false; 507 } elseif (function_exists("exec") 508 && !PhpSecInfo_Test::getBooleanIniValue('safe_mode')) { 509 $id_raw = exec('id'); 510 // uid=1000(coj) gid=1000(coj) groups=1000(coj),1001(admin) 511 preg_match( "|uid=(\d+)\((\S+)\)\s+gid=(\d+)\((\S+)\)\s+groups=(.+)|i", 512 $id_raw, 513 $matches); 514 515 $id_data = array( 'uid'=>$matches[1], 516 'username'=>$matches[2], 517 'gid'=>$matches[3], 518 'group'=>$matches[4] ); 519 520 if ($matches[5]) { 521 $gs = $matches[5]; 522 $gs = explode(',', $gs); 523 foreach ($gs as $groupstr) { 524 preg_match("/(\d+)\(([^\)]+)\)/", $groupstr, $subs); 525 $groups[$subs[1]] = $subs[2]; 526 } 527 ksort($groups); 528 $id_data['groups'] = $groups; 529 } 530 return $id_data; 531 } elseif (function_exists("posix_getpwuid") 532 && function_exists("posix_geteuid") 533 && function_exists('posix_getgrgid') 534 && function_exists('posix_getgroups') ) { 535 $data = posix_getpwuid( posix_getuid() ); 536 $id_data['uid'] = $data['uid']; 537 $id_data['username'] = $data['name']; 538 $id_data['gid'] = $data['gid']; 539 //$group_data = posix_getgrgid( posix_getegid() ); 540 //$id_data['group'] = $group_data['name']; 541 $groups = posix_getgroups(); 542 foreach ( $groups as $gid ) { 543 //$group_data = posix_getgrgid(posix_getgid()); 544 $id_data['groups'][$gid] = '<unknown>'; 545 } 546 } 547 return false; 548 } 549 550} 551