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