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