New IP watcher :
authorx2003bruneau <x2003bruneau@839d8a87-29fc-0310-9880-83ba4fa771e5>
Sat, 20 Jan 2007 19:22:45 +0000 (19:22 +0000)
committerx2003bruneau <x2003bruneau@839d8a87-29fc-0310-9880-83ba4fa771e5>
Sat, 20 Jan 2007 19:22:45 +0000 (19:22 +0000)
Each IP can have 5 states :
-> not in our database
-> safe and in our databe
-> registration is forbidden
-> all actions are logged
-> authentification forbidden

git-svn-id: svn+ssh://murphy/home/svn/platal/trunk@1369 839d8a87-29fc-0310-9880-83ba4fa771e5

ChangeLog
htdocs/images/icons/information.gif [new file with mode: 0644]
include/xorg.misc.inc.php
include/xorg/session.inc.php
modules/admin.php
modules/axletter.php
modules/register.php
modules/register/register.inc.php
templates/admin/index.tpl
templates/admin/ipwatcher.tpl [new file with mode: 0644]
upgrade/0.9.13/05_ip_watcher.sql [new file with mode: 0644]

index 9ff7535..c0e948b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -6,6 +6,7 @@ New:
     * Admin:
         - Show last contact date on marketing validation                -Fal/FRU
         - New page to add users in the database                            -FRU
+        - New IP based security tool                                       -FRU
 
     * AXLetter:
         - New module AXLetter (legal mailer for AX)                        -FRU
diff --git a/htdocs/images/icons/information.gif b/htdocs/images/icons/information.gif
new file mode 100644 (file)
index 0000000..9e9186c
Binary files /dev/null and b/htdocs/images/icons/information.gif differ
index 7f26748..ed8bff1 100644 (file)
@@ -110,4 +110,32 @@ function make_forlife($prenom,$nom,$promo) {
     $forlife = str_replace("'","",$forlife);
     return $forlife;
 }
+
+function check_ip($level)
+{   
+    $test = array();
+    switch ($level) {
+      case 'unsafe': $test[] = "state = 'unsafe'";
+      case 'dangerous': $test[] = "state = 'dangerous'";
+      case 'ban': $test[] = "state = 'ban'"; break;
+      default: return false;
+    }
+    $res = XDB::query("SELECT  state
+                         FROM  ip_watch
+                        WHERE  ip = {?} AND (" . implode(' OR ', $test) . ')',
+                      $_SERVER['REMOTE_ADDR']);
+    return $res->numRows();
+}
+
+function send_warning_mail($title)
+{
+    $mailer = new PlMailer();
+    $mailer->setFrom("webmaster@polytechnique.org");
+    $mailer->addTo("florent.bruneau@polytechnique.org");
+    $mailer->setSubject($title);
+    $mailer->setTxtBody("Identifiants de session :\n" . var_export($_SESSION, true) . "\n\n"
+                       ."Identifiants de connexion :\n" . var_export($_SERVER, true));
+    $mailer->send(); 
+}
+
 ?>
index 51ccfa4..71fe324 100644 (file)
  *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA                *
  ***************************************************************************/
 
+require_once 'xorg.misc.inc.php';
+
 class XorgSession
 {
     // {{{ public static function init
 
-    public static function init() {
+    public static function init() 
+    {
         S::init();
-    if (!S::has('uid')) {
-        try_cookie();
+        if (!S::has('uid')) {
+            try_cookie();
+        }
+        if (check_ip('dangerous') && S::has('uid')) {
+            $_SESSION['log']->log("view_page", $_SERVER['REQUEST_URI']);
         }
     }
 
     // }}}
     // {{{ public static function destroy()
 
-    public static function destroy() {
+    public static function destroy()
+    {
         S::destroy();
         XorgSession::init();
     }
@@ -116,7 +123,9 @@ class XorgSession
                 if ($logger) {
                     $logger->log('auth_ok');
                 }
-                start_connexion($uid, true);
+                if (!start_connexion($uid, true)) {
+                    return false;
+                }
                 if (Env::v('remember', 'false') == 'true') {
                     $cookie = hash_encrypt(S::v('password'));
                     setcookie('ORGaccess',$cookie,(time()+25920000),'/','',0);
@@ -150,16 +159,16 @@ class XorgSession
      */
     public static function doAuthCookie()
     {
-    if (S::logged()) {
-        return true;
+        if (S::logged()) {
+            return true;
         }
 
-    if (Env::has('username') and Env::has('response')) {
-        return XorgSession::doAuth();
+        if (Env::has('username') and Env::has('response')) {
+            return XorgSession::doAuth();
         }
 
-    if ($r = try_cookie()) {
-        return XorgSession::doAuth(($r > 0));
+        if ($r = try_cookie()) {
+            return XorgSession::doAuth(($r > 0));
         }
 
         return false;
@@ -177,7 +186,7 @@ class XorgSession
 function try_cookie()
 {
     if (Cookie::v('ORGaccess') == '' or !Cookie::has('ORGuid')) {
-    return -1;
+        return -1;
     }
 
     $res = @XDB::query(
@@ -186,13 +195,15 @@ function try_cookie()
             Cookie::i('ORGuid'));
 
     if ($res->numRows() != 0) {
-    list($uid, $password) = $res->fetchOneRow();
-    require_once('secure_hash.inc.php');
-    $expected_value       = hash_encrypt($password);
-    if ($expected_value == Cookie::v('ORGaccess')) {
-        start_connexion($uid, false);
-        return 0;
-    } else {
+        list($uid, $password) = $res->fetchOneRow();
+        require_once('secure_hash.inc.php');
+        $expected_value       = hash_encrypt($password);
+        if ($expected_value == Cookie::v('ORGaccess')) {
+            if (!start_connexion($uid, false)) {
+                return -3;
+            }
+            return 0;
+        } else {
             return 1;
         }
     }
@@ -217,7 +228,7 @@ function start_connexion ($uid, $identified)
           FROM  auth_user_md5   AS u
     INNER JOIN  auth_user_quick AS q  USING(user_id)
     INNER JOIN  aliases         AS a  ON (u.user_id = a.id AND a.type='a_vie')
-    INNER JOIN  aliases     AS a2 ON (u.user_id = a2.id AND FIND_IN_SET('bestalias',a2.flags))
+    INNER JOIN  aliases         AS a2 ON (u.user_id = a2.id AND FIND_IN_SET('bestalias',a2.flags))
      LEFT JOIN  logger.sessions AS s  ON (s.uid=u.user_id AND s.suid=0)
          WHERE  u.user_id = {?} AND u.perms IN('admin','user')
       ORDER BY  s.start DESC
@@ -239,7 +250,19 @@ function start_connexion ($uid, $identified)
     $_SESSION         = array_merge($_SESSION, $sess);
     $_SESSION['log']  = $logger;
     $_SESSION['auth'] = ($identified ? AUTH_MDP : AUTH_COOKIE);
+    if (check_ip('unsafe')) {
+        send_warning_mail("Une IP surveillee a tente de se connecter");
+        if (check_ip('ban')) {
+            $_SESSION = array();
+            global $page;
+            $page->trig("Une erreur est survenue lors de la procédure d'authentification. "
+                       ."Merci de contacter au plus vite "
+                       ."<a href='mailto:support@polytechnique.org'>support@polytechnique.org</a>");
+            return false;
+        }
+    }
     set_skin();
+    return true;
 }
 
 // }}}
index 0c5ff0c..54ef3b1 100644 (file)
@@ -43,6 +43,7 @@ class AdminModule extends PLModule
             'admin/validate'               => $this->make_hook('validate', AUTH_MDP, 'admin'),
             'admin/validate/answers'       => $this->make_hook('validate_answers', AUTH_MDP, 'admin'),
             'admin/wiki'                   => $this->make_hook('wiki', AUTH_MDP, 'admin'),
+            'admin/ipwatch'                => $this->make_hook('ipwatch', AUTH_MDP, 'admin'),
         );
     }
 
@@ -913,6 +914,101 @@ class AdminModule extends PLModule
         $page->assign('wiki_pages', $wiki_tree);
         $page->assign('perms_opts', $perms);
     }
+
+    function handler_ipwatch(&$page, $action = 'list', $ip = null)
+    { 
+        $page->changeTpl('admin/ipwatcher.tpl');
+            
+        $states = array('safe'      => 'Ne pas surveiller',
+                        'unsafe'    => 'Surveiller les inscription',
+                        'dangerous' => 'Surveiller tous les accès',
+                        'ban'       => 'Bannir cette adresse');
+        $page->assign('states', $states);
+
+        switch (Post::v('action')) {
+        case 'create':
+            if (trim(Post::v('ipN')) != '') {
+                Xdb::execute('INSERT IGNORE INTO ip_watch (ip, state, detection, last, uid, description)
+                                          VALUES ({?}, {?}, CURDATE(), NOW(), {?}, {?})',
+                             trim(Post::v('ipN')), Post::v('stateN'), S::i('uid'), Post::v('descriptionN'));
+            };      
+            break;  
+                                   
+        case 'edit':               
+            Xdb::execute('UPDATE ip_watch 
+                             SET state = {?}, last = NOW(), uid = {?}, description = {?}
+                           WHERE ip = {?}', Post::v('stateN'), S::i('uid'), Post::v('descriptionN'), Post::v('ipN'));
+            break;
+            
+        default:
+            if ($action == 'delete' && !is_null($ip)) {
+                Xdb::execute('DELETE FROM emails_watch WHERE ip = {?}', $ip);
+            }
+        }
+        if ($action != 'create' && $action != 'edit') {
+            $action = 'list';
+        }
+        $page->assign('action', $action);
+
+        if ($action == 'list') {
+            $sql = "SELECT  w.ip, s.host, w.detection, w.state, a.alias AS forlife
+                      FROM  ip_watch        AS w
+                 LEFT JOIN  logger.sessions AS s USING(ip)
+                 LEFT JOIN  aliases         AS a ON (a.id = s.uid AND a.type = 'a_vie')
+                  GROUP BY  w.ip, a.alias
+                  ORDER BY  w.state, w.ip, a.alias";
+            $it = Xdb::iterRow($sql);
+
+            $table = array();
+            $props = array();
+            while (list($ip, $host, $date, $state, $forlife) = $it->next()) {
+                if (count($props) == 0 || $props['ip'] != $ip) {
+                    if (count($props) > 0) {
+                        $table[] = $props;
+                    }
+                    $props = array('ip'        => $ip,
+                                   'host'      => $host,
+                                   'detection' => $date,
+                                   'state'     => $state,
+                                   'users'     => array($forlife));
+                } else {
+                    $props['users'][] = $forlife;
+                }
+            }
+            if (count($props) > 0) {
+                $table[] = $props;
+            }
+            $page->assign('table', $table);
+        } elseif ($action == 'edit') {
+            $sql = "SELECT  w.detection, w.state, w.last, w.description,
+                            a1.alias AS edit, a2.alias AS forlife, s.host
+                      FROM  ip_watch        AS w
+                 LEFT JOIN  aliases         AS a1 ON (a1.id = w.uid AND a1.type = 'a_vie')     
+                 LEFT JOIN  logger.sessions AS s  ON (w.ip = s.ip)
+                 LEFT JOIN  aliases         AS a2 ON (a2.id = s.uid AND a2.type = 'a_vie')
+                     WHERE  w.ip = {?}
+                  GROUP BY  a2.alias
+                  ORDER BY  a2.alias";
+            $it = Xdb::iterRow($sql, $ip);
+
+            $props = array();
+            while (list($detection, $state, $last, $description, $edit, $forlife, $host) = $it->next()) {
+                if (count($props) == 0) {
+                    $props = array('ip'          => $ip,
+                                   'host'        => $host,
+                                   'detection'   => $detection,
+                                   'state'       => $state,
+                                   'last'        => $last,
+                                   'description' => $description,
+                                   'edit'        => $edit,
+                                   'users'       => array($forlife));
+                } else {
+                    $props['users'][] = $forlife;
+                }
+            }
+            $page->assign('ip', $props);
+        }
+    }
 }
 
 ?>
index 4ef05de..5a93897 100644 (file)
@@ -362,7 +362,7 @@ class AXLetterModule extends PLModule
 
     function createHash($line, $key)
     {
-        $hash = implode(time(), $line);
+        $hash = implode(time(), $line) . rand();
         $hash = md5($hash);
         return $hash;
     }
index 4e57aff..a309bb7 100644 (file)
@@ -33,6 +33,7 @@ class RegisterModule extends PLModule
 
     function handler_register(&$page, $hash = null)
     {
+        $alert     = null;
         $sub_state = S::v('sub_state', Array());
         if (!isset($sub_state['step'])) {
             $sub_state['step'] = 0;
@@ -122,7 +123,6 @@ class RegisterModule extends PLModule
                 break;
 
             case 3:
-                $alert = null;
                 if (count($_POST)) {
                     require_once(dirname(__FILE__) . '/register/register.inc.php');
                     if (!isvalid_email(Post::v('email'))) {
@@ -139,7 +139,7 @@ class RegisterModule extends PLModule
                         $promo = (int)$sub_state['promo'];
                         if ($year > $promo - 15 || $year < $promo - 30) {
                             $err[] = "La 'Date de naissance' n'est pas correcte.";
-                            $alert = "Date de naissance proposée $birth\n\n";
+                            $alert = "Date de naissance incorrecte a l'inscription - ";
                         }
                     }
 
@@ -157,10 +157,11 @@ class RegisterModule extends PLModule
                         $aliases[]   = $alias;
                     }
                     if (count($aliases) != 0) {
-                        $alert .= "Email proposé : " . Post::v('email') . "\n"
-                                . "Ce mails est connu avec l'état $state :\n"
-                                . $description . "\n"
-                                . "Pour les alias :\n* " . join("\n* ", $aliases) . "\n\n";
+                        $alert .= "Email surveille propose a l'inscription - ";
+                    }
+
+                    if (check_ip('unsafe')) {
+                        unset($err);
                     }
 
                     if (isset($err)) {
@@ -171,17 +172,24 @@ class RegisterModule extends PLModule
                                                           substr($birth,2,2),
                                                           substr($birth,0,2));
                         $sub_state['email']     = Post::v('email');
-                        $sub_state['step']      = 4;
-                        finish_ins($sub_state);
-                    }
-                    if (!is_null($alert)) {
-                        send_alert_mail($sub_state, $alert);
+                        if (check_ip('unsafe')) {
+                            $err = "Une erreur s'est produite lors de l'inscription."
+                                 . " Merci de contacter <a href='register@polytechnique.org'>register@polytechnique.org</a>"
+                                 . " pour nous faire part de cette erreur";
+                            $alert .= "Tentative d'inscription depuis une IP surveillee";
+                        } else {
+                            $sub_state['step'] = 4;
+                            finish_ins($sub_state);
+                        }
                     }
                 }
                 break;
         }
 
         $_SESSION['sub_state'] = $sub_state;
+        if ($alert) {
+            send_warning_mail($alert);
+        }
         $page->changeTpl('register/step'.intval($sub_state['step']).'.tpl');
         if (isset($err)) {
             $page->trig($err);
@@ -269,7 +277,9 @@ class RegisterModule extends PLModule
         $mymail->assign('prenom', $prenom);
         $mymail->send();
 
-        start_connexion($uid,false);
+        if (!start_connexion($uid,false)) {
+            return PL_FORBIDDEN;
+        }
         $_SESSION['auth'] = AUTH_MDP;
 
         /***********************************************************/
index 4189719..59235d2 100644 (file)
@@ -217,22 +217,6 @@ function create_aliases (&$sub)
 }
 
 // }}}
-// {{{ function send_alert_mail
-
-function send_alert_mail($state, $body)
-{
-    $mailer = new PlMailer();
-    $mailer->setFrom("webmaster@polytechnique.org");
-    $mailer->addTo("hotliners@staff.polytechnique.org");
-    $mailer->setSubject("ALERTE LORS DE L'INSCRIPTION de "
-        . $state['prenom'] . ' ' . $state['nom'] . '(' . $promo . ')');
-    $mailer->setTxtBody($body
-        . "\n\nIndentifiants :\n" . var_export($state, true)
-        . "\n\nInformations de connexion :\n" . var_export($_SERVER, true));
-    $mailer->send();
-}
-
-// }}}
 // {{{ function finish_ins
 
 function finish_ins($sub_state)
index 679e18a..95e26e2 100644 (file)
@@ -44,6 +44,8 @@
       <a href="admin/logger/actions">Actions</a>
       &nbsp;&nbsp;|&nbsp;&nbsp;
       <a href="admin/emails/duplicated">Doublons</a>
+      &nbsp;&nbsp;|&nbsp;&nbsp;
+      <a href="admin/ipwatch">IPs</a>
     </td>
   </tr>
 </table>
diff --git a/templates/admin/ipwatcher.tpl b/templates/admin/ipwatcher.tpl
new file mode 100644 (file)
index 0000000..73821b3
--- /dev/null
@@ -0,0 +1,136 @@
+{**************************************************************************}
+{*                                                                        *}
+{*  Copyright (C) 2003-2007 Polytechnique.org                             *}
+{*  http://opensource.polytechnique.org/                                  *}
+{*                                                                        *}
+{*  This program is free software; you can redistribute it and/or modify  *}
+{*  it under the terms of the GNU General Public License as published by  *}
+{*  the Free Software Foundation; either version 2 of the License, or     *}
+{*  (at your option) any later version.                                   *}
+{*                                                                        *}
+{*  This program is distributed in the hope that it will be useful,       *}
+{*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *}
+{*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *}
+{*  GNU General Public License for more details.                          *}
+{*                                                                        *}
+{*  You should have received a copy of the GNU General Public License     *}
+{*  along with this program; if not, write to the Free Software           *}
+{*  Foundation, Inc.,                                                     *}
+{*  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA               *}
+{*                                                                        *}
+{**************************************************************************}
+
+<h1>Gestion des IPs surveillées</h1>
+
+{if $action eq "list"}
+<table class="bicol">
+  <tr>
+    <th>Adresse</th>
+    <th>Etat</th>
+    <th>Utilisateurs</th>
+    <th></th>
+  </tr>
+  <tr class="impair">
+    <td colspan="2">
+      <strong>Ajouter une entrée</strong>
+    </td>
+    <td colspan="2" class="right">
+      <strong><a href="admin/ipwatch/create">créer{icon name=add}</a></strong>
+    </td>
+  </tr>
+  {foreach from=$table item=ip}
+  <tr class="{cycle values="pair,impair"}">
+    <td>
+      <strong>{$ip.ip}</strong><br />
+      <small>{$ip.host}</small><br />
+      Ajoutée le {$ip.detection|date_format}
+    </td>
+    <td>
+      {$ip.state}
+    </td>
+    <td class="right">
+      {foreach from=$ip.users item=user name=all}
+      {if $user}
+      <a href="profile/{$user}" class="popup2">{$user}</a>
+      <a href="admin/user/{$user}">{icon name=wrench title=Administrer}</a>
+      <a href="admin/logger/user/{$user}">{icon name=information title="Logs"}</a>{if !$smarty.foreach.all.last}<br />{/if}
+      {/if}
+      {/foreach}
+    </td>
+    <td class="right">
+      <a href="admin/ipwatch/edit/{$ip.ip}">{icon name=page_edit title="Editer"}</a>
+      <a href="admin/ipwatch/delete/{$ip.ip}">{icon name=delete title="Supprimer"}</a>
+    </td>
+  </tr>
+  {/foreach}
+</table>
+{elseif $action eq "create" || $action eq "edit"}
+[<a href="admin/ipwatch">Retour à la liste des IPs surveillées</a>]<br /><br />
+<form method="post" action="admin/ipwatch">
+<table class="tinybicol">
+  <tr>
+    <th colspan="2">Commenter une adresse IP</th>
+  </tr>
+  <tr class="impair">
+  {if $action eq "create"}
+    <td class="titre">Adresse IP</td>
+    <td><input type="text" name="ipN" /></td>
+  {else}
+    <td colspan="2">
+      <strong>{$ip.ip}</strong> ({$ip.host})
+      <input type="hidden" name="ipN" value="{$ip.ip}" />
+    </td>
+  </tr>
+  {foreach from=$ip.users key=i name=all item=user}
+    {if $user}
+    {if $i is even}<tr class="impair">{/if}
+    <td>
+      <a href="profile/{$user}" class="popup2">{$user}</a>
+      <a href="admin/user/{$user}">{icon name=wrench title="Administrer}</a>
+      <a href="admin/logger/user/{$user}">{icon name=information title="Logs"}</a>{if !$smarty.foreach.all.last}<br />{/if}
+    </td>
+    {if $i is even && $smarty.foreach.all.last}<td></td>{/if}
+    {if $i is odd || $smarty.foreach.all.last}</tr>{/if}
+    {/if}
+  {/foreach}
+  <tr class="pair">
+    <td class="titre">Date de détection</td>
+    <td>{$ip.detection|date_format}</td>
+  {/if}
+  </tr>
+  <tr class="pair">
+    <td class="titre">Danger</td>
+    <td>
+      <select name="stateN">
+        {foreach from=$states key=state item=text}
+        <option value="{$state}"{if $ip.state eq $state} selected="selected"{/if}>{$text}</option>
+        {/foreach}
+      </select>
+    </td>
+  </tr>
+  <tr class="impair">
+    <td colspan="2" class="titre">Description</td>
+  {if $ip.edit}
+  </tr>
+  <tr class="impair">
+    <td colspan="2">
+      <small>Dernière édition par {$ip.edit} le {$ip.last|date_format}</small>
+    </td>
+  {/if}
+  </tr>
+  <tr class="impair">
+    <td colspan="2" class="center">
+      <textarea cols="50" rows="10" name="descriptionN">{$ip.description}</textarea>
+    </td>
+  </tr>
+  <tr>
+    <th colspan="2">
+      <input type="hidden" name="action" value="{$action}" />
+      <input type="submit" name="valid"  value="Valider" />
+    </th>
+  </tr>
+</table>
+</form>
+{/if}
+
+{* vim:set et sw=2 sts=2 sws=2: *}
diff --git a/upgrade/0.9.13/05_ip_watcher.sql b/upgrade/0.9.13/05_ip_watcher.sql
new file mode 100644 (file)
index 0000000..d8ad573
--- /dev/null
@@ -0,0 +1,15 @@
+CREATE TABLE `ip_watch` (
+    `ip` CHAR(16) NOT NULL,
+    `state` ENUM('safe', 'unsafe', 'dangerous', 'ban') NOT NULL DEFAULT 'unsafe',
+    `detection` DATE DEFAULT 0,
+    `last` TIMESTAMP DEFAULT 0,
+    `uid`  SMALLINT(5) DEFAULT NULL,
+    `description` TEXT NOT NULL,
+    PRIMARY KEY(`ip`)
+);
+use logger;
+alter table sessions add key(ip);
+insert into actions (text, description) values ('view_page', 'Consultation d\'une page');
+use x4dat;
+
+# vim:set syntax=mysql: