From 0ab243151a8b821763bd57cb074a377bf22df38a Mon Sep 17 00:00:00 2001
From: BOUTELIER Sebastien <sebastien.boutelier@lis-lab.fr>
Date: Thu, 19 Oct 2023 19:36:38 +0200
Subject: [PATCH] =?UTF-8?q?Refactorisation=20du=20code=20pour=20prendre=20?=
 =?UTF-8?q?en=20compte=20toutes=20les=20commandes=20LDAP=20Mise=20en=20sta?=
 =?UTF-8?q?tic=20des=20regexp=20pour=20ne=20pas=20les=20reconstruire=20?=
 =?UTF-8?q?=C3=A0=20chaque=20fois=20Lecture=20et=20v=C3=A9rification=20des?=
 =?UTF-8?q?=20arguments=20pass=C3=A9s=20=C3=A0=20la=20ligne=20de=20command?=
 =?UTF-8?q?e.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 parselog.rs | 317 +++++++++++++++++++++-------------------------------
 1 file changed, 126 insertions(+), 191 deletions(-)

diff --git a/parselog.rs b/parselog.rs
index 4546cc4..c1d5c7a 100644
--- a/parselog.rs
+++ b/parselog.rs
@@ -1,5 +1,5 @@
-//extern crate log;
 use std::net::TcpListener;
+use std::process;
 use std::io::{self, BufRead, Write};
 use std::fs;
 use std::env;
@@ -7,32 +7,33 @@ use std::fs::File;
 use rusqlite::{Connection, Result,params};
 use regex::Regex;
 use syslog::{Facility, Formatter3164};
+use std::sync::OnceLock;
 use gethostname::gethostname;
 
-thread_local!(static LOGHOST: String = getloghost().unwrap(););
-
-#[derive(Debug)]
-struct Log {
-    ip: String,
-    con: u32,
-    op: u32,
-    bind: Option<String>,
-    command: String,
-    base: Option<String>,
-    filter: Option<String>,
-    attr: Option<String>,
-    result: Option<String>,
+static LOGHOST: OnceLock<String> = OnceLock::new();
+static REGEXP_CLOSE: OnceLock<Regex> = OnceLock::new();
+static REGEXP_CMD: OnceLock<Regex> = OnceLock::new();
+static REGEXP_CONNECT: OnceLock<Regex> = OnceLock::new();
+static REGEXP_LAUNCH: OnceLock<Regex> = OnceLock::new();
+static REGEXP_RESULT: OnceLock<Regex> = OnceLock::new();
+
+fn help() {
+    println!("Usage: parselog-ldap --listen=NUMBER --loghost=IP:PORT");
+    println!("       NUMBER: Le port sur lequel on reçoit les logs.");
+    println!("       IP:PORT: La socket sur laquelle on envoie les logs.");
 }
 
 fn getport()->Result<String> {
-    let regexp_arg = Regex::new(r"--listen=(?<port>[0-9]+)").unwrap();
+    let regexp_arg = Regex::new(r"--listen=(?<port>[0-9]{1,5})").unwrap();
     for argument in env::args() {
         let Some(caps) = regexp_arg.captures(&argument) else {
             continue;
         };
         return Ok(caps["port"].to_string());
     }
-    Ok("0".to_string())
+    println!("Port invalide");
+    help();
+    process::exit(1);
 }
 
 fn getloghost()->Result<String> {
@@ -44,7 +45,9 @@ fn getloghost()->Result<String> {
         };
         return Ok(caps["ipport"].to_string());
     }
-    Ok("0".to_string())
+    println!("Loghost invalide");
+    help();
+    process::exit(1);
 }
 
 
@@ -69,7 +72,7 @@ fn getbind(con: &str) -> Result<String> {
     })?;
     for row in rows {
         let bind: String = row?;
-        if bind == "dn=\"\"" {
+        if bind == "\"\"" {
           return Ok("Anonymous".to_string());
         }
         return Ok(bind);
@@ -77,34 +80,8 @@ fn getbind(con: &str) -> Result<String> {
     Ok("Anonymous".to_string())
 }
 
-fn getlog(con: &str,op: &str) -> Result<Log> {
-    let empty = Log {ip: "0".to_string(),con: 0,op: 0,bind: None,command: "0".to_string(),base: None,filter: None,attr: None,result: None};
-    let c = Connection::open("/dev/shm/parselog.db")?;
-    let mut stmt = c.prepare("SELECT * FROM log WHERE con = :con AND op = :op")?;
-    let log_iter = stmt.query_map(&[(":con", &con),(":op", &op)], |row| {
-        Ok(Log {
-            ip: row.get(0)?,
-            con: row.get(1)?,
-            op: row.get(2)?,
-            bind: row.get(3)?,
-            command: row.get(4)?,
-            base: row.get(5)?,
-            filter: row.get(6)?,
-            attr: row.get(7)?,
-            result: row.get(8)?,
-        })
-    })?;
-    for log in log_iter {
-        return Ok(log?)
-    }
-
-    Ok(empty)
-
-}
-
 fn com_accept(line: String){
-    let regexp_command = Regex::new(r"^\S+ \S+ conn=(?<con>[0-9]+) \S+ ACCEPT from IP=(?<ip>[0-9.:]+):[0-9]+").unwrap();
-    let Some(caps) = regexp_command.captures(&line) else {
+    let Some(caps) = REGEXP_CONNECT.get().expect("unitialized Regexp").captures(&line) else {
       return;
     };
     let c = Connection::open("/dev/shm/parselog.db").unwrap();
@@ -112,119 +89,103 @@ fn com_accept(line: String){
     c.execute(query,(&caps["con"],&caps["ip"]),).unwrap();
 }
 
-fn com_bind(line: String){
-    let regexp_command = Regex::new(r"^\S+ \S+ conn=(?<con>[0-9]+) op=(?<op>[0-9]+) (?<command>[A-Z]+) (?<dn>\S+)").unwrap();
-    let Some(caps) = regexp_command.captures(&line) else {
+fn com_result(line: String){
+    let Some(caps) = REGEXP_RESULT.get().expect("unitialized Regexp").captures(&line) else {
+      println!("Parse foireux sur {}",line);
       return;
     };
-    let ip = getip(&caps["con"]).unwrap();
-    if ip != "0" {
-      let c = Connection::open("/dev/shm/parselog.db").unwrap();
-      let query = "INSERT INTO log ('ip','con','op','command','bind') VALUES(?1,?2,?3,?4,?5)";
-      c.execute(query,(ip,&caps["con"],&caps["op"],&caps["command"],&caps["dn"]),).unwrap();
-    }
+    let nentries = caps.name("nentries").map_or("0", |m| m.as_str());
+    let c = Connection::open("/dev/shm/parselog.db").unwrap();
+    let mut stmt = c.prepare("UPDATE log SET result = ?1, nentries = ?2 WHERE con = ?3 AND op = ?4").unwrap();
+    stmt.execute(params![&caps["res"],nentries,&caps["con"],&caps["op"]]).unwrap();
+    construct_log(&caps["con"],&caps["op"]);
 }
 
-fn com_result(line: String){
-    let regexp_command = Regex::new(r"^\S+ (?<date>\S+) conn=(?<con>[0-9]+) op=(?<op>[0-9]+) RESULT \S+ (?<res>\S+)").unwrap();
-    let Some(caps) = regexp_command.captures(&line) else {
-      return;
-    };
+fn construct_log(con: &str,op: &str){
     let c = Connection::open("/dev/shm/parselog.db").unwrap();
-    let mut stmt = c.prepare("UPDATE log SET result = ?1 WHERE con = ?2 AND op = ?3").unwrap();
-    stmt.execute(params![&caps["res"],&caps["con"],&caps["op"]]).unwrap();
-    let log = getlog(&caps["con"],&caps["op"]).unwrap();
-    let linelog : String;
-    if log.ip != "0" {
-      let err : String;
-      let bind = log.bind.as_deref().unwrap_or("Anonymous");
-      match &caps["res"]{
-        "err=0" => err = "OK".to_string(),
-        _       => err = "refused".to_string(),
-      }
-      match log.command.as_str() {
-        "BIND" => { let b: String;
-                    if bind == "dn=\"\"" {
-                      b = "Anonymous".to_string();
-                    } else {
-                      b=bind.to_string();
-                    }
-                    if &caps["res"] == "err=0" {
-                      let mut stmt = c.prepare("UPDATE conip SET bind = ?1 WHERE con = ?2").unwrap();
-                      stmt.execute(params![b,&caps["con"]]).unwrap();
-                    }
-                    linelog = format!("{} BIND {} {}",log.ip,b,err);
-                  },
-        _      => { let b = getbind(&caps["con"]).unwrap();
-                    linelog = format!("{} BIND {} {} {} {}",log.ip,b,log.command,bind,err);
-                  }
-      }
+    let mut stmt = c.prepare("SELECT * FROM log WHERE con = :con AND op = :op LIMIT 1").unwrap();
+    let mut rows = stmt.query(&[(":con", &con),(":op", &op)]).unwrap();
+    while let Some(row) = rows.next().unwrap() {
+       let command: String = row.get(2).unwrap();
+       let val1: String    = row.get(3).unwrap();
+       let val2: String    = row.get(4).unwrap();
+       let attr: String    = row.get(5).unwrap();
+       let result: String  = row.get(6).unwrap();
+       let nentries: String = row.get(7).unwrap();
+       let ip = getip(&con).unwrap();
+       let bind = getbind(con).unwrap();
+
+       if ip == "0" {
+          return;
+       }
+
+       let linelog : String;
+       let err : String;
+       let user : String;
+       match result.as_str(){
+         "err=0" => err = "OK".to_string(),
+         _       => err = "refused".to_string(),
+       }
+       match bind.as_str(){
+          "\"\""      => user = "Anonymous".to_string(),
+          _           => user = bind,
+       }  
+       match command.as_str() {
+         "BIND" => { 
+              if result == "err=0" {
+                 let mut stmt = c.prepare("UPDATE conip SET bind = ?1 WHERE con = ?2").unwrap();
+                 stmt.execute(params![val1,&con]).unwrap();
+              }
+              if val1 == "\"\"" { return; }
+              linelog = format!("ID={} SRC={} BIND={} {}",con,ip,val1,err);
+         },
+         "SRCH"  => {
+              linelog = format!("ID={} SRC={} BIND={} {} attr {} in {} with filter {} {} -> {} answer",con,ip,user,command,attr,val1,val2,err,nentries);
+         },
+         "MOD"   => {
+              linelog = format!("ID={} SRC={} BIND={} {} {} attr={} -> {}",con,ip,user,command,val1,attr,err);
+         },
+         "CMP"   => {
+              linelog = format!("ID={} SRC={} BIND={} {} {} attr={} -> {}",con,ip,user,command,val1,val2,err);
+         },
+         _      => { // ADD DEL 
+              linelog = format!("ID={} SRC={} BIND={} {} {} -> {}",con,ip,user,command,val1,err);
+         }
+       }
       sendlog(linelog); 
     }
-  //dumpcon();
-  //let _ = dumplog();
-
 }
-fn com_srch_filter(line: String){
-    let regexp_command = Regex::new(r"^\S+ \S+ conn=(?<con>[0-9]+) op=(?<op>[0-9]+) SRCH base=(?<base>\S+) \S+ \S+ filter=(?<filter>\S+)").unwrap();
-    let Some(caps) = regexp_command.captures(&line) else {
-      return;
-    };
-    let ip = getip(&caps["con"]).unwrap();
-    let bind = getbind(&caps["con"]).unwrap();
-    if ip != "0" {
-      let c = Connection::open("/dev/shm/parselog.db").unwrap();
-      let query = "INSERT INTO log ('ip','con','op','command','bind','base','filter') VALUES(?1,?2,?3,?4,?5,?6,?7)";
-      c.execute(query,(ip,&caps["con"],&caps["op"],"SRCH",bind,&caps["base"],&caps["filter"]),).unwrap();
-    }
 
-}
-
-fn com_srch_attr(line: String){
-    let regexp_command = Regex::new(r"^\S+ \S+ conn=(?<con>[0-9]+) op=(?<op>[0-9]+) SRCH attr=(?<attr>\S+)").unwrap();
-    let Some(caps) = regexp_command.captures(&line) else {
+fn create_log(line: String){
+    let Some(caps) = REGEXP_CMD.get().expect("unitialized Regexp").captures(&line) else {
       return;
     };
+    let val2 = caps.name("val2").map_or("-", |m| m.as_str());
     let c = Connection::open("/dev/shm/parselog.db").unwrap();
-    let mut stmt = c.prepare("UPDATE log SET attr = ?1 WHERE con = ?2 AND op = ?3").unwrap();
-    stmt.execute(params![&caps["attr"],&caps["con"],&caps["op"]]).unwrap();
-}
-
-fn com_srch(line: String){
-    let regexp_command = Regex::new(r"^\S+ \S+ \S+ \S+ SRCH (?<cmd>[a-z]+)=.*").unwrap();
-    let Some(caps) = regexp_command.captures(&line) else {
-      return;
-    };
-    match &caps["cmd"]{
-     "base"=>com_srch_filter(line.to_string()),
-     "attr"=>com_srch_attr(line.to_string()),
+    match &caps["v"]{
+     "base"=>{
+         let query = "INSERT INTO log ('con','op','command','val1','val2','attr','result','nentries') VALUES(?1,?2,?3,?4,?5,'-','-','-')";
+         c.execute(query,(&caps["con"],&caps["op"],&caps["cmd"],&caps["val1"],val2),).unwrap();
+     },
+     "dn"=>{
+         let query = "INSERT INTO log ('con','op','command','val1','val2','attr','result','nentries') VALUES(?1,?2,?3,?4,?5,'-','-','-')";
+         c.execute(query,(&caps["con"],&caps["op"],&caps["cmd"],&caps["val1"],val2),).unwrap();
+     },
+     "attr"=>{
+         let mut stmt = c.prepare("UPDATE log SET attr = ?1 WHERE con = ?2 AND op = ?3").unwrap();
+         stmt.execute(params![&caps["val1"],&caps["con"],&caps["op"]]).unwrap();
+     },
+     "id"=>{
+         let query = "INSERT INTO log ('con','op','command','val1','val2','attr','result','nentries') VALUES(?1,?2,?3,?4,'-','-','-','-')";
+         c.execute(query,(&caps["con"],&caps["op"],&caps["cmd"],&caps["val1"]),).unwrap();
+     },
      _=>com_default(line.to_string()),
     }
 }
 
-fn com_search(line: String){
-    let regexp_command = Regex::new(r"^\S+ \S+ conn=(?<con>[0-9]+) op=(?<op>[0-9]+) SEARCH RESULT \S+ err=(?<err>[0-9]+) nentries=(?<nentries>[0-9]+)").unwrap();
-    let Some(caps) = regexp_command.captures(&line) else {
-      return;
-    };
-    let log = getlog(&caps["con"],&caps["op"]).unwrap();
-    if log.ip != "0" {
-      let bind = getbind(&caps["con"]).unwrap();
-      let attr = log.attr.as_deref().unwrap_or("\"\"");
-      let linelog : String;
-      let err : String;
-      match &caps["err"]{
-        "0" => err = format!("{}{}{}", "OK -> ".to_string(),&caps["nentries"]," answer".to_string()),
-        _       => err = "error".to_string(),
-      }
-      linelog = format!("{} BIND {} SRCH attr {} in {} with filter {} {}",log.ip,bind,attr,log.base.expect(""),log.filter.expect(""),err);
-      sendlog(linelog);
-    }
-}
 
 fn com_closed(line: String){
-    let regexp_command = Regex::new(r"^\S+ \S+ conn=(?<con>[0-9]+) \S+ closed").unwrap();
-    let Some(caps) = regexp_command.captures(&line) else {
+    let Some(caps) = REGEXP_CLOSE.get().expect("unitialized Regexp").captures(&line) else {
       return;
     };
     let c = Connection::open("/dev/shm/parselog.db").unwrap();
@@ -241,6 +202,7 @@ fn com_default(line: String){
 }
 
 fn sendlog(line: String){
+    println!("OUT: {}",line);
     let formatter = Formatter3164 {
         facility: Facility::LOG_USER,
         hostname: Some(gethostname().into_string().unwrap()),
@@ -248,68 +210,34 @@ fn sendlog(line: String){
         pid: 6666,
     };
 
-    let s = LOGHOST.with(|text| text.clone());
-
-    match syslog::udp(formatter,"0.0.0.0:0",&s) {
+    match syslog::tcp(formatter,&LOGHOST.get().unwrap_or(&"0".to_string()).to_string()) {
         Err(e) => println!("impossible to connect to syslog: {:?}", e),
         Ok(mut writer) => {
             writer.info(line).expect("could not write error message");
         }
     }
-    //println!("OUT: {}",line);
-}
-
-fn dumpcon() {
-    let c = Connection::open("/dev/shm/parselog.db").unwrap();
-    let mut stmt = c.prepare("SELECT * FROM conip").unwrap();
-    let rows = stmt.query_map([], |row| {
-      Ok((row.get(0)?, row.get(1)?, row.get(2)?))
-    }).unwrap();
-    for row in rows {
-        let (con,ip,bind): (u32, String, String) = row.unwrap();
-        println!("Dump con - con: {} - IP: {} - bind: {}",con,ip,bind);
-    }
-}
-
-fn dumplog() -> Result<()> {
-    let c = Connection::open("/dev/shm/parselog.db")?;
-    let mut stmt = c.prepare("SELECT * FROM log")?;
-    let log_iter = stmt.query_map([], |row| {
-        Ok(Log {
-            ip: row.get(0)?,
-            con: row.get(1)?,
-            op: row.get(2)?,
-            bind: row.get(3)?,
-            command: row.get(4)?,
-            base: row.get(5)?,
-            filter: row.get(6)?,
-            attr: row.get(7)?,
-            result: row.get(8)?,
-        })
-    })?;
-
-    for log in log_iter {
-        println!("Dump Log {:?}", log);
-    }
-    Ok(())
 }
 
 fn launch<R: BufRead>(reader: R) {
-    let regexp_command = Regex::new(r"^\S+ \S+ (?<con>\S+) (?<op>\S+) (?<command>\S+)").unwrap();
     for l in reader.lines() {
       let line = l.unwrap();
-      let Some(caps) = regexp_command.captures(&line) else {
+      //println!("LINE: {}",line);
+      let Some(caps) = REGEXP_LAUNCH.get().expect("unitialized Regexp").captures(&line) else {
         return;
       };
     match &caps["command"]{
-     "ACCEPT"=>com_accept(line.to_string()),
-     "BIND"=>com_bind(line.to_string()),
-     "DEL"=>com_bind(line.to_string()),
-     "RESULT"=>com_result(line.to_string()),
-     "SRCH"=>com_srch(line.to_string()),
-     "SEARCH"=>com_search(line.to_string()),
-     "closed"=>com_closed(line.to_string()),
-     _=>com_default(line.to_string()),
+     "ACCEPT"  =>com_accept(line.to_string()),
+     "closed"  =>com_closed(line.to_string()),
+     "RESULT"  =>com_result(line.to_string()),
+     "SEARCH"  =>com_result(line.to_string()),
+     "ADD"     =>create_log(line.to_string()),
+     "BIND"    =>create_log(line.to_string()),
+     "CMP"     =>create_log(line.to_string()),
+     "DEL"     =>create_log(line.to_string()),
+     "MOD"     =>create_log(line.to_string()),
+     "PASSMOD" =>create_log(line.to_string()),
+     "SRCH"    =>create_log(line.to_string()),
+     _         =>com_default(line.to_string()),
     }
     }
 }
@@ -322,14 +250,21 @@ fn initdb(){
     let c = Connection::open("/dev/shm/parselog.db").unwrap();
     let query = "CREATE TABLE conip (con INT, ip TEXT, bind TEXT)";
     c.execute(query,(),).unwrap();
-    let query = "CREATE TABLE log (ip TEXT, con INT, op INT, bind TEXT, command TEXT, base TEXT, filter TEXT, attr TEXT, result TEXT)";
+    let query = "CREATE TABLE log (con INT, op INT, command TEXT, val1 TEXT, val2 TEXT, attr TEXT, result TEXT, nentries TEXT)";
     c.execute(query,(),).unwrap();
 }
 
 
 fn main() {
-    initdb();
+    let loghost = getloghost().unwrap();
+    let _ = LOGHOST.set(loghost);
     let port = getport().unwrap();
+    initdb();
+    let _ = REGEXP_LAUNCH.set(Regex::new(r"^\S+ \S+ \S+ \S+ \S+ (?<con>\S+) (?<op>\S+) (?<command>\S+)").unwrap());
+    let _ = REGEXP_RESULT.set(Regex::new(r"^\S+ \S+ \S+ \S+ \S+ conn=(?<con>[0-9]+) op=(?<op>[0-9]+)( SEARCH)? RESULT \S+ (?<res>\S+)( nentries=(?<nentries>[0-9]+))?").unwrap());
+    let _ = REGEXP_CMD.set(Regex::new(r"^\S+ \S+ \S+ \S+ \S+ conn=(?<con>[0-9]+) op=(?<op>[0-9]+) (?<cmd>(ADD|DEL|BIND|CMP|MOD|PASSMOD|SRCH)) (?<v>[a-z]+)=(?<val1>\S+)( \S+ \S+)?( (attr|filter)=(?<val2>\S+))?").unwrap());
+    let _ = REGEXP_CONNECT.set(Regex::new(r"^\S+ \S+ \S+ \S+ \S+ conn=(?<con>[0-9]+) \S+ ACCEPT from IP=(?<ip>[0-9.:]+):[0-9]+").unwrap());
+    let _ = REGEXP_CLOSE.set(Regex::new(r"^\S+ \S+ \S+ \S+ \S+ conn=(?<con>[0-9]+) \S+ closed").unwrap());
     let s = format!("127.0.0.1:{}",port);
     let listener = TcpListener::bind(s).unwrap();
     for stream in listener.incoming() {
-- 
GitLab