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