<?php

/**
  * SquirrelMail Lockout Plugin
  * Copyright (c) 2003-2010 Paul Lesniewski <paul@squirrelmail.org>
  * Licensed under the GNU GPL. For full terms see the file COPYING.
  *
  * @package plugins
  * @subpackage lockout
  *
  */



/**
  * Initialize this plugin (load config values)
  *
  * @return boolean FALSE if no configuration file could be loaded, TRUE otherwise
  *
  */
function lockout_init()
{

   if (!@include_once (SM_PATH . 'plugins/lockout/data/config.php'))
      return FALSE;

   return TRUE;

}



/**
  * Validate that this plugin is configured correctly
  *
  * @return boolean Whether or not there was a
  *                 configuration error for this plugin.
  *
  */
function lockout_configtest_do()
{

   // make sure base config is available
   //
   if (!lockout_init())
   {
      do_err('Lockout plugin is missing its main configuration file', FALSE);
      return TRUE;
   }


   // make sure the lockout rules configuration is in place if necessary
   //
   global $use_lockout_rules;
   if ($use_lockout_rules)
   {

      if (!file_exists(SM_PATH . 'plugins/lockout/data/lockout_table.php')
       || !is_readable(SM_PATH . 'plugins/lockout/data/lockout_table.php'))
      {
         do_err('Lockout plugin lockout rules file is missing (data/lockout_table.php)', FALSE);
         return TRUE;
      }

   }


   // make sure config settings are in correct format
   //
   global $max_login_attempts;
   if (!empty($max_login_attempts) 
    && !preg_match('/^\d+:\d+:\d+$/', $max_login_attempts))
   {
      do_err('Lockout plugin is misconfigured: $max_login_attempts must be in the form "n:n:n", where each "n" must be a number', FALSE);
      return TRUE;
   }
   global $max_login_attempts_per_IP;
   if (!empty($max_login_attempts_per_IP) 
    && !preg_match('/^\d+:\d+:\d+$/', $max_login_attempts_per_IP))
   {
      do_err('Lockout plugin is misconfigured: $max_login_attempts_per_IP must be in the form "n:n:n", where each "n" must be a number', FALSE);
      return TRUE;
   }
   global $activate_CAPTCHA_after_failed_attempts;
   if (!empty($activate_CAPTCHA_after_failed_attempts) 
    && !preg_match('/^\d+:\d+:\d+(:\d+|)$/', $activate_CAPTCHA_after_failed_attempts))
   {
      do_err('Lockout plugin is misconfigured: $activate_CAPTCHA_after_failed_attempts must be in the form "n:n:n:n", where each "n" must be a number', FALSE);
      return TRUE;
   }


   // make sure the CAPTCHA plugin is available when needed
   //
   if ($activate_CAPTCHA_after_failed_attempts 
    && !check_plugin_version('captcha', 1, 0, 0, TRUE))
   {
      do_err('Lockout plugin is configured to use the CAPTCHA plugin, but the CAPTCHA plugin was not found', FALSE);
      return TRUE;
   }


   // only need to do this pre-1.5.2, as 1.5.2 will make this
   // check for us automatically
   //
   if (!check_sm_version(1, 5, 2))
   {

      // try to find Compatibility, and then that it is v2.0.7+
      //
      if (function_exists('check_plugin_version')
       && check_plugin_version('compatibility', 2, 0, 7, TRUE))
         return FALSE;


      // something went wrong
      //
      do_err('Lockou plugin requires the Compatibility plugin version 2.0.7+', FALSE);
      return TRUE;

   }

   return FALSE;

}



/**   
  * Make sure this plugin fires the LAST on the login_before
  * hook, but before any plugins that might show different
  * logout error (captcha, <others?>)
  *
  */
function move_lockout_to_end_of_login_before_hook_do()
{

   global $PHP_SELF;
   if (stristr($PHP_SELF, '/redirect.php'))
   {
      reposition_plugin_on_hook('lockout', 'login_before', FALSE);
      reposition_plugin_on_hook('captcha', 'login_before', FALSE);
   }

}



/**
  * Activate the CAPTCHA plugin if need be
  *
  */
function activate_captcha_plugin_do($args)
{

   global $data_dir, $activate_CAPTCHA_after_failed_attempts,
          $prefs_dsn, $prefs_are_cached, $prefs_cache, $null;

   lockout_init();

   if (!$activate_CAPTCHA_after_failed_attempts
    || !sqGetGlobalVar('REMOTE_ADDR', $remote_addr, SQ_SERVER))
      return;


   // eiw - a bit messy, but we need to have prefs
   // functions here on the login page where they
   // are not usually needed - borrowed from init.php
   // and NOT tested with custom pref backends
   //
   if (check_sm_version(1, 5, 2))
   {
/* more recently, 1.5.2 fixed this issue......
      include_once(SM_PATH . 'functions/strings.php');
      include_once(SM_PATH . 'functions/prefs.php');
      $prefs_backend = do_hook('prefs_backend', $null);
      if (isset($prefs_backend) && !empty($prefs_backend) && file_exists(SM_PATH . $prefs_backend)) {
          include_once(SM_PATH . $prefs_backend);
      } elseif (isset($prefs_dsn) && !empty($prefs_dsn)) {
          include_once(SM_PATH . 'functions/db_prefs.php');
      } else {
          include_once(SM_PATH . 'functions/file_prefs.php');
      }
*/
   }
   else
      include_once(SM_PATH . 'functions/prefs.php');


   // check if IP addr has been locked out
   // because of too many login failures
   //
   // note how we unset the prefs cache flag before/after this
   // code so as not to corrupt any prefs for the user who is
   // currently logging in
   //
   $prefs_are_cached = FALSE;
   $prefs_cache = FALSE;
   sqsession_unregister('prefs_are_cached');
   sqsession_unregister('prefs_cache');

   $enable_captcha = getPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_CAPTCHA_TOO_MANY_FAILED_LOGIN_ATTEMPTS', '');

   $prefs_are_cached = FALSE;
   $prefs_cache = FALSE;
   sqsession_unregister('prefs_are_cached');
   sqsession_unregister('prefs_cache');

   if ($enable_captcha)
   {

      // is the lockout window over?  if so, clear all
      // lockout preference settings, otherwise turn on CAPTCHA
      //
      if (is_numeric($enable_captcha) && time() > $enable_captcha)
      {
         setPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_CAPTCHA_TOO_MANY_FAILED_LOGIN_ATTEMPTS', '');
         setPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_CAPTCHA_login_failure_times', '');

         // clear prefs cache again
         //
         $prefs_are_cached = FALSE;
         $prefs_cache = FALSE;
         sqsession_unregister('prefs_are_cached');
         sqsession_unregister('prefs_cache');

         $enable_captcha = '';
      }
      else
      {
         add_plugin('captcha', $args);
      }
   }

}



/**
  * Activate the CAPTCHA plugin (receiving end) if need be
  *
  */
function activate_captcha_plugin_check_code_do($args)
{

   global $data_dir, $activate_CAPTCHA_after_failed_attempts,
          $prefs_are_cached, $prefs_cache;

   lockout_init();

   if (!$activate_CAPTCHA_after_failed_attempts
    || !sqGetGlobalVar('REMOTE_ADDR', $remote_addr, SQ_SERVER))
      return;


   // check if IP addr has been locked out
   // because of too many login failures
   //
   // note how we unset the prefs cache flag before/after this
   // code so as not to corrupt any prefs for the user who is
   // currently logging in
   //
   $prefs_are_cached = FALSE;
   $prefs_cache = FALSE;
   sqsession_unregister('prefs_are_cached');
   sqsession_unregister('prefs_cache');

   $enable_captcha = getPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_CAPTCHA_TOO_MANY_FAILED_LOGIN_ATTEMPTS', '');

   $prefs_are_cached = FALSE;
   $prefs_cache = FALSE;
   sqsession_unregister('prefs_are_cached');
   sqsession_unregister('prefs_cache');

   if ($enable_captcha)
   {
      add_plugin('captcha', $args);
   }

}



/**
  * Checks for prior violations of maximum failed login attempts
  * as well as the rules for what users can and can't log in.
  *
  */
function check_lockout_do($args)
{

   global $data_dir, $login_username, $at, $reverseLockout, 
          $use_lockout_rules, $prefs_are_cached, $prefs_cache,
          $max_login_attempts, $max_login_attempts_per_IP,
          $obey_x_forwarded_headers;


   lockout_init();


   if ($max_login_attempts)
   {

      // first, check if user has already been locked out
      // because they had too many login failures
      //
      // note how we unset the prefs cache flag before/after this 
      // code so as not to corrupt any prefs for the user who is 
      // currently logging in
      //
      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');

      $locked_out = getPref($data_dir, 'lockout_plugin_login_failure_information', $login_username . '_TOO_MANY_FAILED_LOGIN_ATTEMPTS', '');

      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');
   
      if ($locked_out)
      {
   
         // is the lockout window over?  if so, clear all
         // lockout preference settings, otherwise error out
         //
         if (is_numeric($locked_out) && time() > $locked_out)
         {
            setPref($data_dir, 'lockout_plugin_login_failure_information', $login_username . '_TOO_MANY_FAILED_LOGIN_ATTEMPTS', '');
            setPref($data_dir, 'lockout_plugin_login_failure_information', $login_username . '_login_failure_times', '');

            // clear prefs cache again
            //
            $prefs_are_cached = FALSE;
            $prefs_cache = FALSE;
            sqsession_unregister('prefs_are_cached');
            sqsession_unregister('prefs_cache');
   
            $locked_out = '';
         }
         else
         {
            sq_change_text_domain('lockout');
            logout_error(_("Access denied.  Please contact your system administrator."));
            exit;
         }
      }
   }


   if ($max_login_attempts_per_IP
    && sqGetGlobalVar('REMOTE_ADDR', $remote_addr, SQ_SERVER))
   {

      // first, check if IP addr has already been locked out
      // because of too many login failures
      //
      // note how we unset the prefs cache flag before/after this
      // code so as not to corrupt any prefs for the user who is
      // currently logging in
      //
      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');

      $locked_out = getPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_TOO_MANY_FAILED_LOGIN_ATTEMPTS', '');

      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');

      if ($locked_out)
      {

         // is the lockout window over?  if so, clear all
         // lockout preference settings, otherwise error out
         //
         if (is_numeric($locked_out) && time() > $locked_out)
         {
            setPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_TOO_MANY_FAILED_LOGIN_ATTEMPTS', '');
            setPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_login_failure_times', '');

            // clear prefs cache again
            //
            $prefs_are_cached = FALSE;
            $prefs_cache = FALSE;
            sqsession_unregister('prefs_are_cached');
            sqsession_unregister('prefs_cache');

            $locked_out = '';
         }
         else
         {
            sq_change_text_domain('lockout');
            logout_error(_("Access denied.  Please contact your system administrator."));
            exit;
         }
      }
   }


   // now, proceed with checking lockout rules if necessary
   //
   if (!$use_lockout_rules) 
   {
      activate_captcha_plugin_check_code_do($args);
      return;
   }


   $user = $login_username;


   // grab hostname into local var
   //
   $host = '';
   if (!$obey_x_forwarded_headers 
    || !sqGetGlobalVar('HTTP_X_FORWARDED_HOST', $host, SQ_SERVER))
      sqGetGlobalVar('HTTP_HOST', $host, SQ_SERVER);


   // get the domain from the username, since it might
   // be different than the domain being used to log in
   //
   if (strpos($user, $at) !== FALSE)
      $usersDomain = substr($user, strpos($user, $at) + 1);
   else
      $usersDomain = '';


   if ($LOCKOUTTABLE = @fopen (SM_PATH . 'plugins/lockout/data/lockout_table.php', 'r'))
   {

      while (!feof($LOCKOUTTABLE))
      {

         $line = fgets($LOCKOUTTABLE, 4096);
         $line = trim($line);


         // skip blank lines and comment lines and php
         // open/close tag lines
         //
         if (strpos($line, '#') === 0 || strlen($line) < 3
          || strpos($line, '<?php') === 0 || strpos($line, '*/ ?>') === 0)
            continue;


         // if we have a hostname from the username, see if this is 
         // a domain lockout line
         //
         if (!empty($usersDomain) && preg_match('/^\s*domain:\s*(\S+)\s+(.+)\s*$/', $line, $matches))
         {

            // check for match with hostname, redirect if found
            //
            if (preg_match('/^' . str_replace(array('?', '*'), array('\w{1}', '.*?'), 
                          strtoupper($matches[1])) . '$/', strtoupper($usersDomain)))
            {
               fclose($LOCKOUTTABLE);
               if ($reverseLockout)
               {
                  activate_captcha_plugin_check_code_do($args);
                  return;
               }
               lockout_redirect($matches[2]);
            }

         }


         // if we were given a hostname, see if this is 
         // a domain lockout line
         //
         if (!empty($host) && preg_match('/^\s*domain:\s*(\S+)\s+(.+)\s*$/', $line, $matches))
         {

            // check for match with hostname, redirect if found
            //
            if (preg_match('/^' . str_replace(array('?', '*'), array('\w{1}', '.*?'), 
                          strtoupper($matches[1])) . '$/', strtoupper($host)))
            {
               fclose($LOCKOUTTABLE);
               if ($reverseLockout)
               {
                  activate_captcha_plugin_check_code_do($args);
                  return;
               }
               lockout_redirect($matches[2]);
            }

         }


         // if we were given a username, see if this is 
         // a user lockout line
         //
         if (!empty($user) && preg_match('/^\s*user:\s*(\S+)\s+(.+)\s*$/', $line, $matches))
         {

            // check for match with username, redirect if found
            //
            if (preg_match('/^' . str_replace(array('?', '*'), array('\w{1}', '.*?'), 
                          strtoupper($matches[1])) . '$/', strtoupper($user)))
            {
               fclose($LOCKOUTTABLE);
               if ($reverseLockout)
               {
                  activate_captcha_plugin_check_code_do($args);
                  return;
               }
               lockout_redirect($matches[2]);
            }

         }

      }

      fclose($LOCKOUTTABLE);


      // if we are reversing functionality, lockout anyone 
      // not found in the lockout file
      //
      if ($reverseLockout)
         lockout_redirect($reverseLockout);

   }

   activate_captcha_plugin_check_code_do($args);

}



/**
  * Redirect current page request to another page,
  * or the built in (fake) login error page.
  *
  * We also log lockout rules violation events here.
  *
  */
function lockout_redirect($uri)
{

   // log this event - note that to use this,
   // you'll need to have the Squirrel Logger plugin
   // installed and activated and you'll have to add
   // a "LOCKOUT" event type to its configuration
   //
   global $log_violated_lockout_rules, $login_username;
   if ($log_violated_lockout_rules && is_plugin_enabled('squirrel_logger'))
   {
      include_once(SM_PATH . 'plugins/squirrel_logger/functions.php');
      sl_logit('LOCKOUT', sprintf('User "%s" has tripped lockout rules; login disallowed', $login_username));
   }

   sqsession_destroy();

   if (strpos($uri, 'http') === 0)
   {
      header('Location: ' . $uri);
   }
   else if ($uri == '##BAD_LOGIN_PAGE##')
   {
      include_once (SM_PATH . 'functions/display_messages.php');

      // simulate delay
      sleep(5);

      global $locked_out_per_lockout_rules;
      $locked_out_per_lockout_rules = TRUE;
      logout_error( _("Unknown user or password incorrect.") );
      // Why did I choose this string before?  The above is better, right?
      //logout_error( _("You must be logged in to access this page.") );
   }
   else
   {
      $location = get_location();
      
      // need to trim off the last directory off the location path
      //
      $location = substr($location, 0, strrpos($location, '/'));
 
      header('Location: ' . $location . '/plugins/lockout/' . $uri);
   }

   exit;

}



/**
  * Valid login - reset failure count
  *
  */
function reset_failed_login_count_do()
{

   global $prefs_are_cached, $prefs_cache, $username, $data_dir,
          $max_login_attempts, $max_login_attempts_per_IP,
          $activate_CAPTCHA_after_failed_attempts; 


   lockout_init();


   if ($max_login_attempts) 
   {
      // note how we unset the prefs cache flag before/after this code so as not
      // to corrupt any prefs for the user who is currently logging in
      //
      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');

      setPref($data_dir, 'lockout_plugin_login_failure_information', $username . '_login_failure_times', '');

      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');
   }


   if (!sqGetGlobalVar('REMOTE_ADDR', $remote_addr, SQ_SERVER))
      return;


   if ($max_login_attempts_per_IP)
   {
      // note how we unset the prefs cache flag before/after this code so as not
      // to corrupt any prefs for the user who is currently logging in
      //
      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');

      setPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_login_failure_times', '');

      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');
   }


   if ($activate_CAPTCHA_after_failed_attempts) 
   {
      // note how we unset the prefs cache flag before/after this code so as not
      // to corrupt any prefs for the user who is currently logging in
      //
      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');

      setPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_CAPTCHA_login_failure_times', '');

      // immediately deactivate CAPTCHA validation if configured
      // 
      $activate_CAPTCHA_config = explode(':', $activate_CAPTCHA_after_failed_attempts);
      if (!empty($activate_CAPTCHA_config[3]))
         setPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_CAPTCHA_TOO_MANY_FAILED_LOGIN_ATTEMPTS', '');

      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');
   }

}



/**
  * Check number of successive failed login attempts
  * and block user or IP permanently if necessary
  * (or turn on CAPTCHA plugin)
  *
  */
function check_failed_login_count_do($args)
{

   global $data_dir, $login_username, $max_login_attempts, 
          $locked_out_per_lockout_rules, $prefs_are_cached,
          $prefs_cache, $max_login_attempts_per_IP,
          $activate_CAPTCHA_after_failed_attempts;


   lockout_init();


   // skip the below if our own lockout rules
   // were the cause of this failed login
   //
   if ($locked_out_per_lockout_rules) return;


   $now = time();


   if (check_sm_version(1, 5, 2))
      $logout_message = $args[0];
   else
      $logout_message = $args[1];



   // this is a bit precarious - we determine if the user failed
   // login based on the error string... 
   //
   $valid_messages = array(_("Unknown user or password incorrect."));
   if (is_plugin_enabled('captcha') || $activate_CAPTCHA_after_failed_attempts)
   {
      sq_change_text_domain('captcha');
      $valid_messages[] = _("Sorry, you did not provide the correct challenge response.");
      sq_change_text_domain('squirrelmail');
   }
   if (!in_array($logout_message, $valid_messages))
      return;



   if ($max_login_attempts)
   {

      list($max_failures, $number_minutes, $lockout_window) 
         = explode(':', $max_login_attempts);


      // get and parse previous login failure stats and add current time to list
      //
      // note how we unset the prefs cache flag before/after this code so as not
      // to corrupt any prefs for the user who is currently logging in
      //
      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');

      $failure_times = getPref($data_dir, 'lockout_plugin_login_failure_information', $login_username . '_login_failure_times', '');
      if ($failure_times)
         $failure_times = explode(':', $failure_times);
      else
         $failure_times = array();
      $new_times = array();
      foreach ($failure_times as $time)
         if ($number_minutes == 0 || $time >= $now - ($number_minutes * 60))
            $new_times[] = $time;
      $new_times[] = $now;
      setPref($data_dir, 'lockout_plugin_login_failure_information', $login_username . '_login_failure_times', implode(':', $new_times));

      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');


      // now check for and ban abusive users
      //
      if (sizeof($new_times) >= $max_failures)
      {

         if (!$lockout_window) $lockout_end_time = 'PERMANENT';
         else $lockout_end_time = $now + ($lockout_window * 60);

         setPref($data_dir, 'lockout_plugin_login_failure_information', $login_username . '_TOO_MANY_FAILED_LOGIN_ATTEMPTS', $lockout_end_time);

         // clear prefs cache again
         //
         $prefs_are_cached = FALSE;
         $prefs_cache = FALSE;
         sqsession_unregister('prefs_are_cached');
         sqsession_unregister('prefs_cache');

         $failed_times = '';
         foreach ($new_times as $time)
            $failed_times .= date('H:i:s Y-m-d', $time) . "\n";


         // send alert to admin if necessary
         //
         global $domain, $log_violated_max_user_logins;
         sq_change_text_domain('lockout');
         if ($number_minutes > 0)
         {
            if (!$lockout_window) 
            {
               // log this event - note that to use this, you'll need to have
               // the Squirrel Logger plugin installed and activated and you'll
               // have to add a "LOCKOUT" event type to its configuration
               //
               if ($log_violated_max_user_logins && is_plugin_enabled('squirrel_logger'))
               {
                  include_once(SM_PATH . 'plugins/squirrel_logger/functions.php');
                  sl_logit('LOCKOUT', sprintf('User "%s" (domain "%s") has attempted (and failed) to log in %d times in the last %d minutes; %s has been LOCKED OUT PERMANENTLY', $login_username, $domain, sizeof($new_times), $number_minutes, $login_username));
               }

               l_report_abuse(sprintf(_("NOTICE: User \"%s\" (domain \"%s\") has attempted (and failed) to log in %d times in the last %d minutes.\n\nTimes:\n%s\n\n%s has been LOCKED OUT PERMANENTLY.\n\nTo unlock this user, remove the \"%s_TOO_MANY_FAILED_LOGIN_ATTEMPTS\" setting from the \"lockout_plugin_login_failure_information\" preference set."), $login_username, $domain, sizeof($new_times), $number_minutes, $failed_times, $login_username, $login_username), sprintf(_(" --- LOCKED OUT - %s"), $login_username));
            }
            else
            {
               // log this event - note that to use this, you'll need to have
               // the Squirrel Logger plugin installed and activated and you'll
               // have to add a "LOCKOUT" event type to its configuration
               //
               if ($log_violated_max_user_logins && is_plugin_enabled('squirrel_logger'))
               {
                  include_once(SM_PATH . 'plugins/squirrel_logger/functions.php');
                  sl_logit('LOCKOUT', sprintf('User "%s" (domain "%s") has attempted (and failed) to log in %d times in the last %d minutes; %s has been LOCKED OUT for %d minutes', $login_username, $domain, sizeof($new_times), $number_minutes, $login_username, $lockout_window));
               }

               l_report_abuse(sprintf(_("NOTICE: User \"%s\" (domain \"%s\") has attempted (and failed) to log in %d times in the last %d minutes.\n\nTimes:\n%s\n\n%s has been LOCKED OUT for %d minutes.\n\nTo unlock this user before then, remove the \"%s_TOO_MANY_FAILED_LOGIN_ATTEMPTS\" setting from the \"lockout_plugin_login_failure_information\" preference set."), $login_username, $domain, sizeof($new_times), $number_minutes, $failed_times, $login_username, $lockout_window, $login_username), sprintf(_(" --- LOCKED OUT - %s"), $login_username));
            }
         }
         else
         {
            if (!$lockout_window) 
            {
               // log this event - note that to use this, you'll need to have
               // the Squirrel Logger plugin installed and activated and you'll
               // have to add a "LOCKOUT" event type to its configuration
               //
               if ($log_violated_max_user_logins && is_plugin_enabled('squirrel_logger'))
               {
                  include_once(SM_PATH . 'plugins/squirrel_logger/functions.php');
                  sl_logit('LOCKOUT', sprintf('User "%s" (domain "%s") has attempted (and failed) to log in %d times; %s has been LOCKED OUT PERMANENTLY', $login_username, $domain, sizeof($new_times), $login_username));
               }

               l_report_abuse(sprintf(_("NOTICE: User \"%s\" (domain \"%s\") has attempted (and failed) to log in %d times.\n\nTimes:\n%s\n\n%s has been LOCKED OUT PERMANENTLY.\n\nTo unlock this user, remove the \"%s_TOO_MANY_FAILED_LOGIN_ATTEMPTS\" setting from the \"lockout_plugin_login_failure_information\" preference set."), $login_username, $domain, sizeof($new_times), $failed_times, $login_username, $login_username), sprintf(_(" --- LOCKED OUT - %s"), $login_username));
            }
            else
            {
               // log this event - note that to use this, you'll need to have
               // the Squirrel Logger plugin installed and activated and you'll
               // have to add a "LOCKOUT" event type to its configuration
               //
               if ($log_violated_max_user_logins && is_plugin_enabled('squirrel_logger'))
               {
                  include_once(SM_PATH . 'plugins/squirrel_logger/functions.php');
                  sl_logit('LOCKOUT', sprintf('User "%s" (domain "%s") has attempted (and failed) to log in %d times; %s has been LOCKED OUT for %d minutes', $login_username, $domain, sizeof($new_times), $login_username, $lockout_window));
               }

               l_report_abuse(sprintf(_("NOTICE: User \"%s\" (domain \"%s\") has attempted (and failed) to log in %d times.\n\nTimes:\n%s\n\n%s has been LOCKED OUT for %d minutes.\n\nTo unlock this user before then, remove the \"%s_TOO_MANY_FAILED_LOGIN_ATTEMPTS\" setting from the \"lockout_plugin_login_failure_information\" preference set."), $login_username, $domain, sizeof($new_times), $failed_times, $login_username, $lockout_window, $login_username), sprintf(_(" --- LOCKED OUT - %s"), $login_username));
            }
         }
         sq_change_text_domain('squirrelmail');
      }

   }


   if (!sqGetGlobalVar('REMOTE_ADDR', $remote_addr, SQ_SERVER))
      return;


   if ($max_login_attempts_per_IP)
   {

      list($max_failures, $number_minutes, $lockout_window) 
         = explode(':', $max_login_attempts_per_IP);


      // get and parse previous login failure stats and add current time to list
      //
      // note how we unset the prefs cache flag before/after this code so as not
      // to corrupt any prefs for the user who is currently logging in
      //
      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');

      $failure_times = getPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_login_failure_times', '');
      if ($failure_times)
         $failure_times = explode(':', $failure_times);
      else
         $failure_times = array();
      $new_times = array();
      foreach ($failure_times as $time)
         if ($number_minutes == 0 || $time >= $now - ($number_minutes * 60))
            $new_times[] = $time;
      $new_times[] = $now;
      setPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_login_failure_times', implode(':', $new_times));

      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');


      // now check for and ban abusive users
      //
      if (sizeof($new_times) >= $max_failures)
      {

         if (!$lockout_window) $lockout_end_time = 'PERMANENT';
         else $lockout_end_time = $now + ($lockout_window * 60);

         setPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_TOO_MANY_FAILED_LOGIN_ATTEMPTS', $lockout_end_time);

         // clear prefs cache again
         //
         $prefs_are_cached = FALSE;
         $prefs_cache = FALSE;
         sqsession_unregister('prefs_are_cached');
         sqsession_unregister('prefs_cache');

         $failed_times = '';
         foreach ($new_times as $time)
            $failed_times .= date('H:i:s Y-m-d', $time) . "\n";


         // send alert to admin if necessary
         //
         global $domain, $log_violated_max_IP_logins;
         sq_change_text_domain('lockout');
         if ($number_minutes > 0)
         {
            if (!$lockout_window) 
            {
               // log this event - note that to use this, you'll need to have
               // the Squirrel Logger plugin installed and activated and you'll
               // have to add a "LOCKOUT" event type to its configuration
               //
               if ($log_violated_max_IP_logins && is_plugin_enabled('squirrel_logger'))
               {
                  include_once(SM_PATH . 'plugins/squirrel_logger/functions.php');
                  sl_logit('LOCKOUT', sprintf('Someone at %s has attempted (and failed) to log in %d times in the last %d minutes; %s has been LOCKED OUT PERMANENTLY', $remote_addr, sizeof($new_times), $number_minutes, $remote_addr));
               }

               l_report_abuse(sprintf(_("NOTICE: Someone at %s has attempted (and failed) to log in %d times in the last %d minutes.\n\nTimes:\n%s\n\n%s has been LOCKED OUT PERMANENTLY.\n\nTo unlock this client address, remove the \"%s_TOO_MANY_FAILED_LOGIN_ATTEMPTS\" setting from the \"lockout_plugin_login_failure_information\" preference set."), $remote_addr, sizeof($new_times), $number_minutes, $failed_times, $remote_addr, $remote_addr), sprintf(_(" --- LOCKED OUT - %s"), $remote_addr));
            }
            else
            {
               // log this event - note that to use this, you'll need to have
               // the Squirrel Logger plugin installed and activated and you'll
               // have to add a "LOCKOUT" event type to its configuration
               //
               if ($log_violated_max_IP_logins && is_plugin_enabled('squirrel_logger'))
               {
                  include_once(SM_PATH . 'plugins/squirrel_logger/functions.php');
                  sl_logit('LOCKOUT', sprintf('Someone at %s has attempted (and failed) to log in %d times in the last %d minutes; %s has been LOCKED OUT for %d minutes', $remote_addr, sizeof($new_times), $number_minutes, $remote_addr, $lockout_window));
               }

               l_report_abuse(sprintf(_("NOTICE: Someone at %s has attempted (and failed) to log in %d times in the last %d minutes.\n\nTimes:\n%s\n\n%s has been LOCKED OUT for %d minutes.\n\nTo unlock this client address, remove the \"%s_TOO_MANY_FAILED_LOGIN_ATTEMPTS\" setting from the \"lockout_plugin_login_failure_information\" preference set."), $remote_addr, sizeof($new_times), $number_minutes, $failed_times, $remote_addr, $lockout_window, $remote_addr), sprintf(_(" --- LOCKED OUT - %s"), $remote_addr));
            }
         }
         else
         {
            if (!$lockout_window) 
            {
               // log this event - note that to use this, you'll need to have
               // the Squirrel Logger plugin installed and activated and you'll
               // have to add a "LOCKOUT" event type to its configuration
               //
               if ($log_violated_max_IP_logins && is_plugin_enabled('squirrel_logger'))
               {
                  include_once(SM_PATH . 'plugins/squirrel_logger/functions.php');
                  sl_logit('LOCKOUT', sprintf('Someone at %s has attempted (and failed) to log in %d times; %s has been LOCKED OUT PERMANENTLY', $remote_addr, sizeof($new_times), $remote_addr));
               }

               l_report_abuse(sprintf(_("NOTICE: Someone at %s has attempted (and failed) to log in %d times.\n\nTimes:\n%s\n\n%s has been LOCKED OUT PERMANENTLY.\n\nTo unlock this client address, remove the \"%s_TOO_MANY_FAILED_LOGIN_ATTEMPTS\" setting from the \"lockout_plugin_login_failure_information\" preference set."), $remote_addr, sizeof($new_times), $failed_times, $remote_addr, $remote_addr), sprintf(_(" --- LOCKED OUT - %s"), $remote_addr));
            }
            else
            {
               // log this event - note that to use this, you'll need to have
               // the Squirrel Logger plugin installed and activated and you'll
               // have to add a "LOCKOUT" event type to its configuration
               //
               if ($log_violated_max_IP_logins && is_plugin_enabled('squirrel_logger'))
               {
                  include_once(SM_PATH . 'plugins/squirrel_logger/functions.php');
                  sl_logit('LOCKOUT', sprintf('Someone at %s has attempted (and failed) to log in %d times; %s has been LOCKED OUT for %d minutes', $remote_addr, sizeof($new_times), $remote_addr, $lockout_window));
               }

               l_report_abuse(sprintf(_("NOTICE: Someone at %s has attempted (and failed) to log in %d times.\n\nTimes:\n%s\n\n%s has been LOCKED OUT for %d minutes.\n\nTo unlock this client address before then, remove the \"%s_TOO_MANY_FAILED_LOGIN_ATTEMPTS\" setting from the \"lockout_plugin_login_failure_information\" preference set."), $remote_addr, sizeof($new_times), $failed_times, $remote_addr, $lockout_window, $remote_addr), sprintf(_(" --- LOCKED OUT - %s"), $remote_addr));
            }
         }
         sq_change_text_domain('squirrelmail');
      }

   }


   if ($activate_CAPTCHA_after_failed_attempts)
   {

      list($max_failures, $number_minutes, $lockout_window) 
         = explode(':', $activate_CAPTCHA_after_failed_attempts);


      // get and parse previous login failure stats and add current time to list
      //
      // note how we unset the prefs cache flag before/after this code so as not
      // to corrupt any prefs for the user who is currently logging in
      //
      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');

      $failure_times = getPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_CAPTCHA_login_failure_times', '');
      if ($failure_times)
         $failure_times = explode(':', $failure_times);
      else
         $failure_times = array();
      $new_times = array();
      foreach ($failure_times as $time)
         if ($number_minutes == 0 || $time >= $now - ($number_minutes * 60))
            $new_times[] = $time;
      $new_times[] = $now;
      setPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_CAPTCHA_login_failure_times', implode(':', $new_times));

      $prefs_are_cached = FALSE;
      $prefs_cache = FALSE;
      sqsession_unregister('prefs_are_cached');
      sqsession_unregister('prefs_cache');


      // now check for and ban abusive users
      //
      if (sizeof($new_times) >= $max_failures)
      {

         if (!$lockout_window) $lockout_end_time = 'PERMANENT';
         else $lockout_end_time = $now + ($lockout_window * 60);

         setPref($data_dir, 'lockout_plugin_login_failure_information', $remote_addr . '_CAPTCHA_TOO_MANY_FAILED_LOGIN_ATTEMPTS', $lockout_end_time);

         // clear prefs cache again
         //
         $prefs_are_cached = FALSE;
         $prefs_cache = FALSE;
         sqsession_unregister('prefs_are_cached');
         sqsession_unregister('prefs_cache');

         $failed_times = '';
         foreach ($new_times as $time)
            $failed_times .= date('H:i:s Y-m-d', $time) . "\n";


         // log event and send alert to admin if necessary
         //
         global $domain, $log_CAPTCHA_enabled;
         sq_change_text_domain('lockout');
         if ($number_minutes > 0)
         {
            if (!$lockout_window) 
            {
               // log this event - note that to use this, you'll need to have
               // the Squirrel Logger plugin installed and activated and you'll
               // have to add a "LOCKOUT" event type to its configuration
               //
               if ($log_CAPTCHA_enabled && is_plugin_enabled('squirrel_logger'))
               {
                  include_once(SM_PATH . 'plugins/squirrel_logger/functions.php');
                  sl_logit('LOCKOUT', sprintf('Someone at %s has attempted (and failed) to log in %d times in the last %d minutes; users attempting to log in from %s are PERMANENTLY required to enter a CAPTCHA code when logging in', $remote_addr, sizeof($new_times), $number_minutes, $remote_addr));
               }

               l_report_abuse(sprintf(_("NOTICE: Someone at %s has attempted (and failed) to log in %d times in the last %d minutes.\n\nTimes:\n%s\n\nUsers attempting to log in from %s are PERMANENTLY required to enter a CAPTCHA code when logging in.\n\nTo remove the CAPTCHA requirement, remove the \"%s_CAPTCHA_TOO_MANY_FAILED_LOGIN_ATTEMPTS\" setting from the \"lockout_plugin_login_failure_information\" preference set."), $remote_addr, sizeof($new_times), $number_minutes, $failed_times, $remote_addr, $remote_addr), sprintf(_(" --- LOCKED OUT - %s"), $remote_addr));
            }
            else
            {
               // log this event - note that to use this, you'll need to have
               // the Squirrel Logger plugin installed and activated and you'll
               // have to add a "LOCKOUT" event type to its configuration
               //
               if ($log_CAPTCHA_enabled && is_plugin_enabled('squirrel_logger'))
               {
                  include_once(SM_PATH . 'plugins/squirrel_logger/functions.php');
                  sl_logit('LOCKOUT', sprintf('Someone at %s has attempted (and failed) to log in %d times in the last %d minutes; users attempting to log in from %s will be required to enter a CAPTCHA code when logging in for the next %d minutes', $remote_addr, sizeof($new_times), $number_minutes, $remote_addr, $lockout_window));
               }

               l_report_abuse(sprintf(_("NOTICE: Someone at %s has attempted (and failed) to log in %d times in the last %d minutes.\n\nTimes:\n%s\n\nUsers attempting to log in from %s will be required to enter a CAPTCHA code when logging in for the next %d minutes.\n\nTo remove the CAPTCHA requirement, remove the \"%s_CAPTCHA_TOO_MANY_FAILED_LOGIN_ATTEMPTS\" setting from the \"lockout_plugin_login_failure_information\" preference set."), $remote_addr, sizeof($new_times), $number_minutes, $failed_times, $remote_addr, $lockout_window, $remote_addr), sprintf(_(" --- LOCKED OUT - %s"), $remote_addr));
            }
         }
         else
         {
            if (!$lockout_window) 
            {
               // log this event - note that to use this, you'll need to have
               // the Squirrel Logger plugin installed and activated and you'll
               // have to add a "LOCKOUT" event type to its configuration
               //
               if ($log_CAPTCHA_enabled && is_plugin_enabled('squirrel_logger'))
               {
                  include_once(SM_PATH . 'plugins/squirrel_logger/functions.php');
                  sl_logit('LOCKOUT', sprintf('Someone at %s has attempted (and failed) to log in %d times; users attempting to log in from %s are PERMANENTLY required to enter a CAPTCHA code when logging in', $remote_addr, sizeof($new_times), $remote_addr));
               }

               l_report_abuse(sprintf(_("NOTICE: Someone at %s has attempted (and failed) to log in %d times.\n\nTimes:\n%s\n\nUsers attempting to log in from %s are PERMANENTLY required to enter a CAPTCHA code when logging in.\n\nTo remove the CAPTCHA requirement, remove the \"%s_CAPTCHA_TOO_MANY_FAILED_LOGIN_ATTEMPTS\" setting from the \"lockout_plugin_login_failure_information\" preference set."), $remote_addr, sizeof($new_times), $failed_times, $remote_addr, $remote_addr), sprintf(_(" --- LOCKED OUT - %s"), $remote_addr));
            }
            else
            {
               // log this event - note that to use this, you'll need to have
               // the Squirrel Logger plugin installed and activated and you'll
               // have to add a "LOCKOUT" event type to its configuration
               //
               if ($log_CAPTCHA_enabled && is_plugin_enabled('squirrel_logger'))
               {
                  include_once(SM_PATH . 'plugins/squirrel_logger/functions.php');
                  sl_logit('LOCKOUT', sprintf('Someone at %s has attempted (and failed) to log in %d times; users attempting to log in from %s will be required to enter a CAPTCHA code when logging in for the next %d minutes', $remote_addr, sizeof($new_times), $remote_addr, $lockout_window));
               }

               l_report_abuse(sprintf(_("NOTICE: Someone at %s has attempted (and failed) to log in %d times.\n\nTimes:\n%s\n\nUsers attempting to log in from %s will be required to enter a CAPTCHA code when logging in for the next %d minutes.\n\nTo remove the CAPTCHA requirement before then, remove the \"%s_CAPTCHA_TOO_MANY_FAILED_LOGIN_ATTEMPTS\" setting from the \"lockout_plugin_login_failure_information\" preference set."), $remote_addr, sizeof($new_times), $failed_times, $remote_addr, $lockout_window, $remote_addr), sprintf(_(" --- LOCKED OUT - %s"), $remote_addr));
            }
         }
         sq_change_text_domain('squirrelmail');
      }

   }

}



/**
  * Reports abuse to administrator
  *
  * @param $error string Error text indicating what the abuse problem is
  * @param $title string Short text for inclusion in email subject line
  *
  */
function l_report_abuse($error, $title='')
{

   global $lockout_notification_addresses, $at, $domain, 
          $login_username, $lockout_useSendmail, $lockout_smtpServerAddress,
          $lockout_smtpPort, $lockout_sendmail_path, $lockout_sendmail_args, 
          $lockout_pop_before_smtp, $lockout_encode_header_key,
          $lockout_smtp_auth_mech, $lockout_smtp_sitewide_user,
          $lockout_smtp_sitewide_pass, $useSendmail, $smtpServerAddress,
          $smtpPort, $sendmail_path, $sendmail_args, $pop_before_smtp,
          $encode_header_key, $smtp_auth_mech, $smtp_sitewide_user,
          $smtp_sitewide_pass;

   lockout_init();

   if (empty($lockout_notification_addresses))
      return;


   sq_change_text_domain('lockout');


   $user = substr($login_username, 0, strpos($login_username, $at));


   $body = $error . "\n\n" . _("User account:") . ' ' . $login_username
         . "\n" . _("Domain:") . ' ' . $domain
         . "\n" . _("Timestamp: ") . date('Y-m-d H:i:s');


   $subject = _("[POSSIBLE BRUTEFORCE ABUSE]")
            . (empty($title) ? '' : ': ' . $title)
            . ' (' . $login_username . ')';


   sq_change_text_domain('squirrelmail');


   // override any SMTP/Sendmail settings...
   //
   $orig_useSendmail = $useSendmail;
   if (!is_null($lockout_useSendmail))
      $useSendmail = $lockout_useSendmail;
   $orig_smtpServerAddress = $smtpServerAddress;
   if (!is_null($lockout_smtpServerAddress))
      $smtpServerAddress = $lockout_smtpServerAddress;
   $orig_smtpPort = $smtpPort;
   if (!is_null($lockout_smtpPort))
      $smtpPort = $lockout_smtpPort;
   $orig_sendmail_path = $sendmail_path;
   if (!is_null($lockout_sendmail_path))
      $sendmail_path = $lockout_sendmail_path;
   $orig_sendmail_args = $sendmail_args;
   if (!is_null($lockout_sendmail_args))
      $sendmail_args = $lockout_sendmail_args;
   $orig_pop_before_smtp = $pop_before_smtp;
   if (!is_null($lockout_pop_before_smtp))
      $pop_before_smtp = $lockout_pop_before_smtp;
   $orig_encode_header_key = $encode_header_key;
   if (!is_null($lockout_encode_header_key))
      $encode_header_key = $lockout_encode_header_key;
   $orig_smtp_auth_mech = $smtp_auth_mech;
   if (!is_null($lockout_smtp_auth_mech))
      $smtp_auth_mech = $lockout_smtp_auth_mech;
   $orig_smtp_sitewide_user = $smtp_sitewide_user;
   if (!is_null($lockout_smtp_sitewide_user))
      $smtp_sitewide_user = $lockout_smtp_sitewide_user;
   $orig_smtp_sitewide_pass = $smtp_sitewide_pass;
   if (!is_null($lockout_smtp_sitewide_pass))
      $smtp_sitewide_pass = $lockout_smtp_sitewide_pass;


   // function found in SM core 1.5.2+ and Compatibility plugin 2.0.11+
   // (suppress include error, since file is only needed for 1.5.2+,
   // otherwise Compatibility has us covered)
   //
   @include_once(SM_PATH . 'functions/compose.php');
   $success = sq_send_mail($lockout_notification_addresses, $subject, $body, 'noreply@' . $domain);


   // return SMTP/Sendmail settings to what they were
   //
   $useSendmail = $orig_useSendmail;
   $smtpServerAddress = $orig_smtpServerAddress;
   $smtpPort = $orig_smtpPort;
   $sendmail_path = $orig_sendmail_path;
   $sendmail_args = $orig_sendmail_args;
   $pop_before_smtp = $orig_pop_before_smtp;
   $encode_header_key = $orig_encode_header_key;
   $smtp_auth_mech = $orig_smtp_auth_mech;
   $smtp_sitewide_user = $orig_smtp_sitewide_user;
   $smtp_sitewide_pass = $orig_smtp_sitewide_pass;

}



