From 9e257e829b76d93d2be710d8fb5bd9f50b1b8628 Mon Sep 17 00:00:00 2001
From: BOUTELIER Sebastien <sebastien.boutelier@lis-lab.fr>
Date: Fri, 13 Oct 2023 15:47:22 +0200
Subject: [PATCH] Import initial des fichiers. WIP.

---
 Cargo.toml  |  16 +++
 parselog.rs | 340 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 356 insertions(+)
 create mode 100644 Cargo.toml
 create mode 100644 parselog.rs

diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..acd5bc4
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "parselog"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+gethostname = "0.4.3"
+regex = "1.9.5"
+rusqlite = { version = "0.29.0", features = ["bundled"] }
+syslog = "6.1.0"
+
+[[bin]]
+name = "parselog"
+path = "parselog.rs"
diff --git a/parselog.rs b/parselog.rs
new file mode 100644
index 0000000..4546cc4
--- /dev/null
+++ b/parselog.rs
@@ -0,0 +1,340 @@
+//extern crate log;
+use std::net::TcpListener;
+use std::io::{self, BufRead, Write};
+use std::fs;
+use std::env;
+use std::fs::File;
+use rusqlite::{Connection, Result,params};
+use regex::Regex;
+use syslog::{Facility, Formatter3164};
+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>,
+}
+
+fn getport()->Result<String> {
+    let regexp_arg = Regex::new(r"--listen=(?<port>[0-9]+)").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())
+}
+
+fn getloghost()->Result<String> {
+    //let regexp_arg = Regex::new(r"--loghost=(?<ipport>(25[0–5]|2[0–4][0–9]|[01]?[0–9][0–9]?).(25[0–5]|2[0–4][0–9]|[01]?[0–9][0–9]?).(25[0–5]|2[0–4][0–9]|[01]?[0–9][0–9]?).(25[0–5]|2[0–4][0–9]|[01]?[0–9][0–9]?):[0-9]{1,5})").unwrap();
+    let regexp_arg = Regex::new(r"--loghost=(?<ipport>[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}:[0-9]{1,5})").unwrap();
+    for argument in env::args() {
+        let Some(caps) = regexp_arg.captures(&argument) else {
+            continue;
+        };
+        return Ok(caps["ipport"].to_string());
+    }
+    Ok("0".to_string())
+}
+
+
+fn getip(con: &str) -> Result<String> {
+    let c = Connection::open("/dev/shm/parselog.db")?;
+    let mut stmt = c.prepare("SELECT ip FROM conip WHERE con = :con")?;
+    let rows = stmt.query_map(&[(":con", &con)], |row| {
+      Ok(row.get(0)?)
+    })?;
+    for row in rows {
+        let ip: String = row?;
+        return Ok(ip);
+    }
+    Ok("0".to_string())
+}
+
+fn getbind(con: &str) -> Result<String> {
+    let c = Connection::open("/dev/shm/parselog.db")?;
+    let mut stmt = c.prepare("SELECT bind FROM conip WHERE con = :con")?;
+    let rows = stmt.query_map(&[(":con", &con)], |row| {
+      Ok(row.get(0)?)
+    })?;
+    for row in rows {
+        let bind: String = row?;
+        if bind == "dn=\"\"" {
+          return Ok("Anonymous".to_string());
+        }
+        return Ok(bind);
+    }
+    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 {
+      return;
+    };
+    let c = Connection::open("/dev/shm/parselog.db").unwrap();
+    let query = "INSERT INTO conip VALUES(?1,?2,'Anonymous')";
+    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 {
+      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();
+    }
+}
+
+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;
+    };
+    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);
+                  }
+      }
+      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 {
+      return;
+    };
+    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()),
+     _=>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 {
+      return;
+    };
+    let c = Connection::open("/dev/shm/parselog.db").unwrap();
+    let mut stmt = c.prepare("DELETE FROM conip WHERE con = ?1").unwrap();
+    stmt.execute(params![&caps["con"]]).unwrap(); 
+    let mut stmt = c.prepare("DELETE FROM log WHERE con = ?1").unwrap();
+    stmt.execute(params![&caps["con"]]).unwrap(); 
+}
+
+fn com_default(line: String){
+  let _ = line;
+  //println!("DEF {}", &line);
+
+}
+
+fn sendlog(line: String){
+    let formatter = Formatter3164 {
+        facility: Facility::LOG_USER,
+        hostname: Some(gethostname().into_string().unwrap()),
+        process: "ldap".into(),
+        pid: 6666,
+    };
+
+    let s = LOGHOST.with(|text| text.clone());
+
+    match syslog::udp(formatter,"0.0.0.0:0",&s) {
+        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 {
+        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()),
+    }
+    }
+}
+
+fn initdb(){
+    let _res = fs::remove_file("/dev/shm/parselog.db");
+    let mut file = File::create("/dev/shm/parselog.db").expect("Error encountered while creating file!");
+    file.write_all(b"Writing").expect("Error while writing to file");
+    fs::remove_file("/dev/shm/parselog.db").unwrap();
+    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)";
+    c.execute(query,(),).unwrap();
+}
+
+
+fn main() {
+    initdb();
+    let port = getport().unwrap();
+    let s = format!("127.0.0.1:{}",port);
+    let listener = TcpListener::bind(s).unwrap();
+    for stream in listener.incoming() {
+        let reader = io::BufReader::new(stream.unwrap());
+        launch(reader);
+    }
+}
+
-- 
GitLab