$t('PHPIDS'),
'value' => $t('Found'),
);
if (!file_exists(realpath(dirname(__FILE__) . '/IDS/default_filter.xml'))) {
$requirements['phpids']['value'] = $t('Not found');
$requirements['phpids']['description'] = $t('You must dowload the latest PHPIDS package and place in the phpids module folder. Warning: the PHP4 version is not supported.');
$requirements['phpids']['severity'] = REQUIREMENT_ERROR;
}
return $requirements;
}
/**
* Small function to display warnings on logs/configure page
*/
function _phpids_packagecheck() {
if (!file_exists(realpath(dirname(__FILE__) . '/IDS/default_filter.xml'))) {
return drupal_set_message(t('You must dowload the latest PHPIDS package and place in the phpids module folder. Warning: the PHP4 version is not supported.'),'error');
}
}
/**
* Display help and module information
*/
function phpids_help($section='') {
$output = '';
switch ($section) {
case "admin/help#phpids":
$output = '
'. t("Add PHPIDS as a security layer for Drupal"). '
';
break;
}
return $output;
}
/**
* Permissions function
*/
function phpids_perm() {
return array('access phpids logs', 'administer phpids');
}
/**
* Implementation of hook_cron().
* Deletes logs after a certain amount of time (default: one week)
*/
function phpids_cron() {
db_query('DELETE FROM {phpids} WHERE timestamp < %d', time() - variable_get('phpids_clear', 604800));
}
/**
* Implementation of hook_menu()
*/
function phpids_menu($may_cache) {
$items = array();
$access_logs = user_access('access phpids logs');
$access_conf = user_access('administer phpids');
if ($may_cache) {
$items[] = array(
'path' => 'admin/logs/phpids',
'title' => t('PHPIDS log'),
'description' => t('View PHPIDS logs'),
'callback' => 'phpids_logs',
'access' => $access_logs,
'weight' => 15);
$items[] = array(
'path' => 'admin/logs/phpids/event',
'title' => t('Details'),
'callback' => 'phpids_event',
'access' => $access_logs,
'type' => MENU_CALLBACK);
$items[] = array(
'path' => 'admin/logs/phpids/log',
'title' => t('Logs'),
'type' => MENU_DEFAULT_LOCAL_TASK,
'access' => $access_logs,
'weight' => -10);
$items[] = array(
'path' => 'admin/logs/phpids/settings',
'title' => t('Configure'),
'callback' => 'drupal_get_form',
'callback arguments' => array('phpids_admin_settings'),
'type' => MENU_LOCAL_TASK,
'access' => $access_conf);
$items[] = array(
'path' => 'phpidswarning',
'title' => t('PHPIDS warning'),
'callback' => 'phpids_warning',
'access' => TRUE,
'type' => MENU_CALLBACK
);
}
return $items;
}
/**
* Implementation of hook_init()
* @ignore : value depends which action will happen
* 0 = do nothing
* 1 = only log
* 2 = log & actions
*/
function phpids_init() {
if (file_exists(realpath(dirname(__FILE__) . '/IDS/default_filter.xml'))) {
global $user, $base_root;
// default is logging
$ignore = 1;
// anonymous user
if ($user->uid == 0) {
$anon = variable_get('phpids_anonymous',2);
if ($anon == 2) $ignore = 2;
}
// authenticated user - always ignore user 1
if ($user->uid != 0) {
if ($user->uid == 1) $ignore = 0;
else {
$auth = variable_get('phpids_authenticated',2);
if ($auth == 1) $ignore = 0;
if ($auth == 3) $ignore = 2;
}
}
// start PHPIDS if ignore is not 0
if ($ignore != 0) {
$request_uri = $base_root . request_uri();
// set include path and required the needed files
$phpids_path = realpath(dirname(__FILE__));
set_include_path(get_include_path(). PATH_SEPARATOR. $phpids_path);
require_once 'IDS/Monitor.php';
require_once 'IDS/Filter/Storage.php';
$storage = new IDS_Filter_Storage();
$storage->getFilterFromXML($phpids_path.'/IDS/default_filter.xml');
// instanciate the needed stuff
// do we need to monitor other global variables ?
$request = array_merge($_GET,$_POST);
$request = new IDS_Monitor($request, $storage);
$report = $request->run();
// if report is not empty, always log
// depending on variables, take other actions if impact level matches settings criteria.
if (!$report->isEmpty()) {
// default action is log
$action = 0;
// level of severity
$severity = $report->getImpact();
// get variables to see if we need to take more action than only logging
$mail_level = variable_get('phpids_maillevel',9);
$mail_sent = variable_get('phpids_mail','');
$warn_level = variable_get('phpids_warnlevel',27);
if ($severity >= $mail_level && !empty($mail_sent) && $ignore == 2) $action = 1;
if ($severity >= $warn_level && $ignore == 2) $action = 2;
// create detailed report
$message = 'All tags: ' . join(", ", $report->getTags()) . '
';
// iterate through the result an get every event (IDS_Event)
foreach ($report as $event) {
$message .= '
Variable: '.$event->getName().' | Value: ' . htmlspecialchars($event->getValue()) . '
';
$message .= 'Impact: '.$event->getImpact().' | Tags: ' . join(", ", $event->getTags()) . '
';
// iterator throught every filter
$message .= '';
foreach ($event as $filter) {
$message .= '- Rule: '. $filter->getRule() .'
';
$message .= 'Description: '. $filter->getDescription() .'
';
$message .= 'Tags: ' . join(", ", $filter->getTags()) . ' ';
}
$message .= '
';
}
// log the impact
phpids_addevent($user,$message,$severity,$action,$request_uri);
// send out mail if needed
// TODO: more info in mail
if ($action == 1) {
$body = 'Check your logs to see a full detail of the report.';
drupal_mail('',$mail_sent,'PHPIDS detected an attack with impact '.$severity,$body);
}
// Warning : redirect the user to a warning page so nothing can happen to the system
if ($action == 2) {
drupal_goto('phpidswarning');
exit();
}
}
}
}
}
/**
* Insert an event into the database
*/
function phpids_addevent($user,$message,$severity,$action,$request_uri) {
db_query("INSERT into {phpids} (uid,message,severity,paction,location,referer,hostname,timestamp)
VALUES (%d,'%s','%d',%d,'%s','%s','%s','%d')",
$user->uid,$message,$severity,$action,$request_uri,referer_uri(),$_SERVER['REMOTE_ADDR'],time());
}
/**
* Callback function to return PHPIDS logs
* Taken from the watchdog overview
*/
function phpids_logs() {
_phpids_packagecheck();
$header = array(
array('data' => t('Date'), 'field' => 'p.pid', 'sort' => 'desc'),
array('data' => t('Level'), 'field' => 'p.severity'),
array('data' => t('Attack'), 'field' => 'p.message'),
array('data' => t('User'), 'field' => 'u.name'),
array('data' => t('IP'), 'field' => 'p.hostname'),
array('data' => t('Action'), 'field' => 'p.paction')
);
$phpids_action = array('Log','Mail','Warning','Ban');
$sql = "SELECT p.pid, p.uid, p.severity, p.paction, p.timestamp, p.message, p.hostname, u.name FROM {phpids} p INNER JOIN {users} u ON p.uid = u.uid";
$tablesort = tablesort_sql($header);
$result = pager_query($sql . $tablesort, 50);
while ($phpids = db_fetch_object($result)) {
$rows[] = array('data' =>
array(
// Cells
format_date($phpids->timestamp, 'small'),
$phpids->severity,
l(truncate_utf8($phpids->message, 36, TRUE, TRUE), 'admin/logs/phpids/event/'. $phpids->pid, array(), NULL, NULL, FALSE, TRUE),
theme('username', $phpids),
$phpids->hostname,
$phpids_action[$phpids->paction]
),
);
}
if (!$rows) {
$rows[] = array(array('data' => t('No log messages available.'), 'colspan' => 6));
}
$output .= theme('table', $header, $rows);
$output .= theme('pager', NULL, 50, 0);
return $output;
}
/**
* Callback function to return detail of one event
*/
function phpids_event($id) {
$phpids_action = array('Log','Mail','Warning','Ban');
$row = db_fetch_object(db_query("select p.uid, p.severity, p.paction, p.timestamp, p.message, p.hostname, u.name from {phpids} p INNER JOIN {users} u ON p.uid = u.uid where pid = %d",$id));
$output = ''.t('Total impact').': '.$row->severity.'
';
$output .= t('Date').': '.format_date($row->timestamp,'small').'
';
$output .= t('User').': '.theme('username',$row);
// create deny user link if user has the appropriate rights, and if userid is not 0 or 1.
$output .= _phpids_create_rule_link('user',$row);
$output .= '
';
$output .= t('Host').': '.$row->hostname;
// create deny ip link if user has the appropriate rights.
$output .= _phpids_create_rule_link('host',$row);
$output .= '
';
$output .= t('Action').': '.$phpids_action[$row->paction].'
';
$output .= '
'.t('Detailed information about attack').'
'.wordwrap($row->message,'100',' ',TRUE).'
';
return $output;
}
/*
* Callback function to configure PHPIDS
*/
function phpids_admin_settings() {
_phpids_packagecheck();
// general settings
$period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
$period['1000000000'] = t('Never');
$form['general'] = array(
'#type' => 'fieldset',
'#title' => t('General'),
'#description' => t('A user with access to user access control will see a link to create/edit access rules when viewing a detail of an attack for a host/user.'),
);
$form['general']['phpids_clear'] = array(
'#type' => 'select',
'#title' => t('Discard PHPIDS log entries older than'),
'#default_value' => variable_get('phpids_clear', 604800),
'#options' => $period,
'#description' => t('The time PHPIDS log entries should be kept. Older entries will be automatically discarded. Requires crontab.'),
);
$form['general']['phpids_maillevel'] = array(
'#type' => 'textfield',
'#title' => t('Mail impact'),
'#default_value' => variable_get('phpids_maillevel',9),
'#description' => t('Sends out mail when this level of impact is reached.'),
);
$form['general']['phpids_mail'] = array(
'#type' => 'textfield',
'#title' => t('Email'),
'#default_value' => variable_get('phpids_mail',''),
'#description' => t('Leave empty if you don\'t want to send out email. Action field on logs overview will display "log" then.'),
);
$form['general']['phpids_warnlevel'] = array(
'#type' => 'textfield',
'#title' => t('Warning impact'),
'#default_value' => variable_get('phpids_warnlevel',27),
'#description' => t('Redirects to a warning page after this level of impact is reached.'),
);
// finetine filter settings
$form['filters'] = array(
'#type' => 'fieldset',
'#title' => t('Ignore filters'),
'#description' => t('Finetune settings when PHPIDS shouldn\'t take action. Keep in mind that user 1 is always ignored and anonymous users are always monitored!'),
);
$options_anon = array(1 => t('Log anonymous users without actions'), 2 => t('Log anonymous users and take actions'));
$form['filters']['phpids_anonymous'] = array(
'#type' => 'select',
'#title' => t('Anonymous users'),
'#description' => t('Choose a setting for anonymous users.'),
'#default_value' => variable_get('phpids_anonymous',1),
'#options' => $options_anon,
);
$options_auth = array(1 => t('Do not log authenticated users'), 2 => t('Log authenticated users without actions'), 3 => t('Log authenticated users and take actions'));
$form['filters']['phpids_authenticated'] = array(
'#type' => 'select',
'#title' => t('Authenticated users'),
'#description' => t('Choose a setting for authenticated users.'),
'#default_value' => variable_get('phpids_authenticated',2),
'#options' => $options_auth,
);
return system_settings_form($form);
}
/**
* Warning page: display this page if the attack has reached warning level thus
* making the action of the (anonymous) user completely worthless.
*/
function phpids_warning() {
$output = t('We have detected malicious input and blocked your attempt.
If you keep experiencing problems but feel like you are doing nothing wrong, please contact the site administrator.');
return $output;
}
/**
* Create access rule add/edit link
* Checks if type is in access table or not
* Only display if user has proper rights
*/
function _phpids_create_rule_link($type,$row) {
if (user_access('administer access control')) {
$return = TRUE;
$url_type = 'add';
switch ($type){
case'user':
if ($row->uid != 1 && $row->uid != 0) {
$value = $row->name;
$aid = _phpids_check_access_rule($type,$row->name);
} else $return = FALSE;
break;
case'host':
$value = $row->hostname;
$aid = _phpids_check_access_rule($type,$row->hostname);
break;
}
if ($aid != FALSE) $url_type = 'edit';
if ($return == TRUE) {
switch ($url_type){
case'add':
return ' | '.l(t("Add access rule for").' '.$type,'admin/user/rules/add/'.$value.'/'.$type.'',NULL,drupal_get_destination());
break;
case'edit':
// http://swenteldoos/phpids/?q=admin/user/rules/edit/5
return ' | '.l(t("Change access rule for").' '.$type,'admin/user/rules/edit/'.$aid.'',NULL,drupal_get_destination());
break;
}
}
}
}
/**
* Return aid from access table if match is found.
* We look for an exact match!
*/
function _phpids_check_access_rule($type,$value) {
$access = db_fetch_object(db_query("SELECT aid FROM {access} where type = '%s' AND mask = '%s'",$type,$value));
if (!empty($access)) return $access->aid; else return FALSE;
}
?>