-#[macro_use]
-extern crate clap;
-extern crate failure;
-extern crate flate2;
-extern crate libc;
-
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::CString;
use std::io::BufWriter;
use std::io::Write;
-use failure::Error;
+use anyhow::{bail, Context, Error};
use flate2::read;
+use libc::time_t;
+
+mod time;
+use time::{Tm, CAL_MTOD};
-use clap::{App, Arg};
+fn print_usage() {
+ let pkg_version = env!("CARGO_PKG_VERSION");
+ println!(
+ "\
+pmg-log-tracker {pkg_version}
+Proxmox Mailgateway Log Tracker. Tool to scan mail logs.
+
+USAGE:
+ pmg-log-tracker [OPTIONS]
+
+OPTIONS:
+ -e, --endtime <TIME> End time (YYYY-MM-DD HH:MM:SS) or seconds since epoch
+ -f, --from <SENDER> Mails from SENDER
+ -g, --exclude-greylist Exclude greylist entries
+ -h, --host <HOST> Hostname or Server IP
+ --help Print help information
+ -i, --inputfile <INPUTFILE> Input file to use instead of /var/log/syslog, or '-' for stdin
+ -l, --limit <MAX> Print MAX entries [default: 0]
+ -m, --message-id <MSGID> Message ID (exact match)
+ -n, --exclude-ndr Exclude NDR entries
+ -q, --queue-id <QID> Queue ID (exact match), can be specified multiple times
+ -s, --starttime <TIME> Start time (YYYY-MM-DD HH:MM:SS) or seconds since epoch
+ -t, --to <RECIPIENT> Mails to RECIPIENT
+ -v, --verbose Verbose output, can be specified multiple times
+ -V, --version Print version information
+ -x, --search-string <STRING> Search for string",
+ );
+}
fn main() -> Result<(), Error> {
- let matches = App::new(crate_name!())
- .version(crate_version!())
- .about(crate_description!())
- .arg(
- Arg::with_name("verbose")
- .short("v")
- .long("verbose")
- .help("Verbose output, can be specified multiple times")
- .multiple(true)
- .takes_value(false),
- )
- .arg(
- Arg::with_name("inputfile")
- .short("i")
- .long("inputfile")
- .help("Input file to use instead of /var/log/syslog, or '-' for stdin")
- .value_name("INPUTFILE"),
- )
- .arg(
- Arg::with_name("host")
- .short("h")
- .long("host")
- .help("Hostname or Server IP")
- .value_name("HOST"),
- )
- .arg(
- Arg::with_name("from")
- .short("f")
- .long("from")
- .help("Mails from SENDER")
- .value_name("SENDER"),
- )
- .arg(
- Arg::with_name("to")
- .short("t")
- .long("to")
- .help("Mails to RECIPIENT")
- .value_name("RECIPIENT"),
- )
- .arg(
- Arg::with_name("start")
- .short("s")
- .long("starttime")
- .help("Start time (YYYY-MM-DD HH:MM:SS) or seconds since epoch")
- .value_name("TIME"),
- )
- .arg(
- Arg::with_name("end")
- .short("e")
- .long("endtime")
- .help("End time (YYYY-MM-DD HH:MM:SS) or seconds since epoch")
- .value_name("TIME"),
- )
- .arg(
- Arg::with_name("msgid")
- .short("m")
- .long("message-id")
- .help("Message ID (exact match)")
- .value_name("MSGID"),
- )
- .arg(
- Arg::with_name("qids")
- .short("q")
- .long("queue-id")
- .help("Queue ID (exact match), can be specified multiple times")
- .value_name("QID")
- .multiple(true)
- .number_of_values(1),
- )
- .arg(
- Arg::with_name("search")
- .short("x")
- .long("search-string")
- .help("Search for string")
- .value_name("STRING"),
- )
- .arg(
- Arg::with_name("limit")
- .short("l")
- .long("limit")
- .help("Print MAX entries")
- .value_name("MAX")
- .default_value("0"),
- )
- .arg(
- Arg::with_name("exclude_greylist")
- .short("g")
- .long("exclude-greylist")
- .help("Exclude greylist entries"),
- )
- .arg(
- Arg::with_name("exclude_ndr")
- .short("n")
- .long("exclude-ndr")
- .help("Exclude NDR entries"),
- )
- .get_matches();
+ let mut args = pico_args::Arguments::from_env();
+ if args.contains("--help") {
+ print_usage();
+ return Ok(());
+ }
- let mut parser = Parser::new();
- parser.handle_args(matches)?;
+ let mut parser = Parser::new()?;
+ parser.handle_args(&mut args)?;
+
+ let remaining_options = args.finish();
+ if !remaining_options.is_empty() {
+ bail!(
+ "Found invalid arguments: {:?}",
+ remaining_options.join(", ".as_ref())
+ )
+ }
println!("# LogReader: {}", std::process::id());
println!("# Query options");
for m in parser.options.match_list.iter() {
match m {
Match::Qid(b) => println!("# QID: {}", std::str::from_utf8(b)?),
- Match::RelLineNr(t, l) => println!("# QID: T{:8X}L{:08X}", *t as u32, *l as u32),
+ Match::RelLineNr(t, l) => println!("# QID: T{:8X}L{:08X}", *t, *l as u32),
}
}
println!(
"# Start: {} ({})",
- time::strftime("%F %T", &parser.start_tm)?,
+ proxmox_time::strftime_local("%F %T", parser.options.start)?,
parser.options.start
);
println!(
"# End: {} ({})",
- time::strftime("%F %T", &parser.end_tm)?,
+ proxmox_time::strftime_local("%F %T", parser.options.end)?,
parser.options.end
);
let time = &data[..time_count];
fe.borrow_mut().set_processing_time(time);
- return;
}
}
from: Box<[u8]>,
to: Box<[u8]>,
dstatus: DStatus,
- timestamp: u64,
+ timestamp: time_t,
}
#[derive(Debug)]
to: Box<[u8]>,
relay: Box<[u8]>,
dstatus: DStatus,
- timestamp: u64,
+ timestamp: time_t,
}
impl Default for ToEntry {
}
}
-#[derive(Debug, PartialEq, Copy, Clone)]
+#[derive(Debug, PartialEq, Copy, Clone, Default)]
enum DStatus {
+ #[default]
Invalid,
Accept,
Quarantine,
Dsn(u32),
}
-impl Default for DStatus {
- fn default() -> Self {
- DStatus::Invalid
- }
-}
-
impl std::fmt::Display for DStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let c = match self {
struct SEntry {
log: Vec<(Box<[u8]>, u64)>,
connect: Box<[u8]>,
- cursor: Box<[u8]>,
pid: u64,
// references to QEntries, Weak so they are not kept alive longer than
// necessary, RefCell for mutability (Rc<> is immutable)
// used as a fallback in case no QEntry is referenced
filter: Option<Weak<RefCell<FEntry>>>,
string_match: bool,
- timestamp: u64,
+ timestamp: time_t,
rel_line_nr: u64,
// before queue filtering with the mail accepted for at least one receiver
is_bq_accepted: bool,
}
impl SEntry {
- fn add_noqueue_entry(&mut self, from: &[u8], to: &[u8], dstatus: DStatus, timestamp: u64) {
+ fn add_noqueue_entry(&mut self, from: &[u8], to: &[u8], dstatus: DStatus, timestamp: time_t) {
let ne = NoqueueEntry {
to: to.into(),
from: from.into(),
match m {
Match::Qid(_) => return,
Match::RelLineNr(t, l) => {
- if (*t as u64) == self.timestamp && *l == self.rel_line_nr {
+ if *t == self.timestamp && *l == self.rel_line_nr {
found = true;
break;
}
return;
}
- // don't print if there's a string match specified, but none of the
- // log entries matches. in the before-queue case we also have to check
- // the attached filter for a match
+ // don't print if there's a string match specified, but none of the log entries matches.
+ // in the before-queue case we also have to check the attached filter for a match
if !parser.options.string_match.is_empty() {
if let Some(fe) = &self.filter() {
if !self.string_match && !fe.borrow().string_match {
return;
}
- } else {
- if !self.string_match {
- return;
- }
+ } else if !self.string_match {
+ return;
}
}
if parser.options.verbose > 0 {
parser.write_all_ok(format!(
"SMTPD: T{:8X}L{:08X}\n",
- self.timestamp as u32, self.rel_line_nr as u32
+ self.timestamp, self.rel_line_nr as u32
));
parser.write_all_ok(format!("CTIME: {:8X}\n", parser.ctime).as_bytes());
if nq.dstatus != DStatus::Invalid {
parser.write_all_ok(format!(
"TO:{:X}:T{:08X}L{:08X}:{}: from <",
- nq.timestamp as i32, self.timestamp as i32, self.rel_line_nr, nq.dstatus,
+ nq.timestamp, self.timestamp, self.rel_line_nr, nq.dstatus,
));
parser.write_all_ok(&nq.from);
parser.write_all_ok(b"> to <");
for to in fe.borrow().to_entries.iter().rev() {
parser.write_all_ok(format!(
"TO:{:X}:T{:08X}L{:08X}:{}: from <",
- to.timestamp as i32, se.timestamp as i32, se.rel_line_nr, to.dstatus,
+ to.timestamp, se.timestamp, se.rel_line_nr, to.dstatus,
));
parser.write_all_ok(&se.bq_from);
parser.write_all_ok(b"> to <");
}
impl QEntry {
- fn add_to_entry(&mut self, to: &[u8], relay: &[u8], dstatus: DStatus, timestamp: u64) {
+ fn add_to_entry(&mut self, to: &[u8], relay: &[u8], dstatus: DStatus, timestamp: time_t) {
let te = ToEntry {
to: to.into(),
relay: relay.into(),
}
Match::RelLineNr(t, l) => {
if let Some(s) = se {
- if s.timestamp == (*t as u64) && s.rel_line_nr == *l {
+ if s.timestamp == *t && s.rel_line_nr == *l {
found = true;
break;
}
true
}
+ #[allow(clippy::wrong_self_convention)]
fn from_to_matches(&mut self, parser: &Parser) -> bool {
if !parser.options.from.is_empty() {
if self.from.is_empty() {
}
}
- parser.write_all_ok(format!("TO:{:X}:", to.timestamp as i32,));
+ parser.write_all_ok(format!("TO:{:X}:", to.timestamp));
parser.write_all_ok(&self.qid);
parser.write_all_ok(format!(":{}: from <", final_to.dstatus));
parser.write_all_ok(&self.from);
});
for to in fe.borrow().to_entries.iter().rev() {
- parser.write_all_ok(format!("TO:{:X}:", to.timestamp as i32,));
+ parser.write_all_ok(format!("TO:{:X}:", to.timestamp));
parser.write_all_ok(&self.qid);
parser.write_all_ok(format!(":{}: from <", to.dstatus));
parser.write_all_ok(&self.from);
}
impl FEntry {
- fn add_accept(&mut self, to: &[u8], qid: &[u8], timestamp: u64) {
+ fn add_accept(&mut self, to: &[u8], qid: &[u8], timestamp: time_t) {
let te = ToEntry {
to: to.into(),
relay: qid.into(),
self.is_accepted = true;
}
- fn add_quarantine(&mut self, to: &[u8], qid: &[u8], timestamp: u64) {
+ fn add_quarantine(&mut self, to: &[u8], qid: &[u8], timestamp: time_t) {
let te = ToEntry {
to: to.into(),
relay: qid.into(),
self.to_entries.push(te);
}
- fn add_block(&mut self, to: &[u8], timestamp: u64) {
+ fn add_block(&mut self, to: &[u8], timestamp: time_t) {
let te = ToEntry {
to: to.into(),
relay: (&b"none"[..]).into(),
current_record_state: RecordState,
rel_line_nr: u64,
- current_year: [i64; 32],
+ current_year: i64,
current_month: i64,
current_file_index: usize,
start_tm: time::Tm,
end_tm: time::Tm,
- ctime: libc::time_t,
+ ctime: time_t,
string_match: bool,
lines: u64,
}
impl Parser {
- fn new() -> Self {
- let mut years: [i64; 32] = [0; 32];
+ fn new() -> Result<Self, Error> {
+ let ltime = Tm::now_local()?;
- for (i, year) in years.iter_mut().enumerate() {
- let mut ts = time::get_time();
- ts.sec -= (3600 * 24 * i) as i64;
- let ltime = time::at(ts);
- *year = (ltime.tm_year + 1900) as i64;
- }
-
- Self {
+ Ok(Self {
sentries: HashMap::new(),
fentries: HashMap::new(),
qentries: HashMap::new(),
smtp_tls_log_by_pid: HashMap::new(),
current_record_state: Default::default(),
rel_line_nr: 0,
- current_year: years,
- current_month: 0,
+ current_year: (ltime.tm_year + 1900) as i64,
+ current_month: ltime.tm_mon as i64,
current_file_index: 0,
count: 0,
buffered_stdout: BufWriter::with_capacity(4 * 1024 * 1024, std::io::stdout()),
options: Options::default(),
- start_tm: time::empty_tm(),
- end_tm: time::empty_tm(),
+ start_tm: Tm::zero(),
+ end_tm: Tm::zero(),
ctime: 0,
string_match: false,
lines: 0,
- }
+ })
}
fn free_sentry(&mut self, sentry_pid: u64) {
} else {
let filecount = self.count_files_in_time_range();
for i in (0..filecount).rev() {
- self.current_month = 0;
if let Ok(file) = File::open(LOGFILES[i]) {
self.current_file_index = i;
if i > 1 {
let (time, line) = match parse_time(
line,
- self.current_year[self.current_file_index],
- &mut self.current_month,
+ self.current_year,
+ self.current_month,
+ // use start time for timezone offset in parse_time_no_year rather than the
+ // timezone offset of the current time
+ // this is required for cases where current time is in standard time, while start
+ // time is in summer time or the other way around
+ self.start_tm.tm_gmtoff,
) {
Some(t) => t,
None => continue,
self.current_record_state.host = host.into();
self.current_record_state.service = service.into();
self.current_record_state.pid = pid;
- self.current_record_state.timestamp = time as u64;
+ self.current_record_state.timestamp = time;
self.string_match = false;
if !self.options.string_match.is_empty()
let mut buffer = Vec::new();
for (i, item) in LOGFILES.iter().enumerate() {
- self.current_month = 0;
-
count = i;
if let Ok(file) = File::open(item) {
self.current_file_index = i;
}
if let Some((time, _)) = parse_time(
&buffer[0..size],
- self.current_year[i],
- &mut self.current_month,
+ self.current_year,
+ self.current_month,
+ self.start_tm.tm_gmtoff,
) {
// found the earliest file in the time frame
if time < self.options.start {
}
if let Some((time, _)) = parse_time(
&buffer[0..size],
- self.current_year[i],
- &mut self.current_month,
+ self.current_year,
+ self.current_month,
+ self.start_tm.tm_gmtoff,
) {
if time < self.options.start {
break;
count + 1
}
- fn handle_args(&mut self, args: clap::ArgMatches) -> Result<(), Error> {
- if let Some(inputfile) = args.value_of("inputfile") {
- self.options.inputfile = inputfile.to_string();
+ fn handle_args(&mut self, args: &mut pico_args::Arguments) -> Result<(), Error> {
+ if let Some(inputfile) = args.opt_value_from_str(["-i", "--inputfile"])? {
+ self.options.inputfile = inputfile;
}
- if let Some(start) = args.value_of("start") {
- if let Ok(res) = time::strptime(start, "%F %T") {
- self.options.start = mkgmtime(&res);
- self.start_tm = res;
- } else if let Ok(res) = time::strptime(start, "%s") {
- let res = res.to_local();
- self.options.start = mkgmtime(&res);
- self.start_tm = res;
+ if let Some(start) = args.opt_value_from_str::<_, String>(["-s", "--starttime"])? {
+ if let Ok(epoch) = proxmox_time::parse_rfc3339(&start).or_else(|_| {
+ time::date_to_rfc3339(&start).and_then(|s| proxmox_time::parse_rfc3339(&s))
+ }) {
+ self.options.start = epoch;
+ self.start_tm = time::Tm::at_local(epoch).context("failed to parse start time")?;
+ } else if let Ok(epoch) = start.parse::<time_t>() {
+ self.options.start = epoch;
+ self.start_tm = time::Tm::at_local(epoch).context("failed to parse start time")?;
} else {
- failure::bail!(failure::err_msg("failed to parse start time"));
+ bail!("failed to parse start time");
}
} else {
- let mut ltime = time::now();
+ let mut ltime = Tm::now_local()?;
ltime.tm_sec = 0;
ltime.tm_min = 0;
ltime.tm_hour = 0;
- self.options.start = mkgmtime(<ime);
+ self.options.start = ltime.as_utc_to_epoch();
self.start_tm = ltime;
}
- if let Some(end) = args.value_of("end") {
- if let Ok(res) = time::strptime(end, "%F %T") {
- self.options.end = mkgmtime(&res);
- self.end_tm = res;
- } else if let Ok(res) = time::strptime(end, "%s") {
- let res = res.to_local();
- self.options.end = mkgmtime(&res);
- self.end_tm = res;
+ if let Some(end) = args.opt_value_from_str::<_, String>(["-e", "--endtime"])? {
+ if let Ok(epoch) = proxmox_time::parse_rfc3339(&end).or_else(|_| {
+ time::date_to_rfc3339(&end).and_then(|s| proxmox_time::parse_rfc3339(&s))
+ }) {
+ self.options.end = epoch;
+ self.end_tm = time::Tm::at_local(epoch).context("failed to parse end time")?;
+ } else if let Ok(epoch) = end.parse::<time_t>() {
+ self.options.end = epoch;
+ self.end_tm = time::Tm::at_local(epoch).context("failed to parse end time")?;
} else {
- failure::bail!(failure::err_msg("failed to parse end time"));
+ bail!("failed to parse end time");
}
} else {
- let ltime = time::now();
- self.options.end = mkgmtime(<ime);
- self.end_tm = ltime;
+ self.options.end = unsafe { libc::time(std::ptr::null_mut()) };
+ self.end_tm = Tm::at_local(self.options.end)?;
}
if self.options.end < self.options.start {
- failure::bail!(failure::err_msg("end time before start time"));
+ bail!("end time before start time");
}
- self.options.limit = match args.value_of("limit") {
+ self.options.limit = match args.opt_value_from_str::<_, String>(["-l", "--limit"])? {
Some(l) => l.parse().unwrap(),
None => 0,
};
- if let Some(qids) = args.values_of("qids") {
- for q in qids {
- let ltime: libc::time_t = 0;
- let rel_line_nr: libc::c_ulong = 0;
- let input = CString::new(q)?;
- let bytes = concat!("T%08lXL%08lX", "\0");
- let format =
- unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(bytes.as_bytes()) };
- if unsafe {
- libc::sscanf(input.as_ptr(), format.as_ptr(), <ime, &rel_line_nr) == 2
- } {
- self.options
- .match_list
- .push(Match::RelLineNr(ltime, rel_line_nr));
- } else {
- self.options
- .match_list
- .push(Match::Qid(q.as_bytes().into()));
- }
+ while let Some(q) = args.opt_value_from_str::<_, String>(["-q", "--queue-id"])? {
+ let ltime: time_t = 0;
+ let rel_line_nr: libc::c_ulong = 0;
+ let input = CString::new(q.as_str())?;
+ let bytes = concat!("T%08lXL%08lX", "\0");
+ let format = unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(bytes.as_bytes()) };
+ if unsafe { libc::sscanf(input.as_ptr(), format.as_ptr(), <ime, &rel_line_nr) == 2 } {
+ self.options
+ .match_list
+ .push(Match::RelLineNr(ltime, rel_line_nr));
+ } else {
+ self.options
+ .match_list
+ .push(Match::Qid(q.as_bytes().into()));
}
}
- if let Some(from) = args.value_of("from") {
- self.options.from = from.to_string();
+ if let Some(from) = args.opt_value_from_str(["-f", "--from"])? {
+ self.options.from = from;
}
- if let Some(to) = args.value_of("to") {
- self.options.to = to.to_string();
+ if let Some(to) = args.opt_value_from_str(["-t", "--to"])? {
+ self.options.to = to;
}
- if let Some(host) = args.value_of("host") {
- self.options.host = host.to_string();
+ if let Some(host) = args.opt_value_from_str(["-h", "--host"])? {
+ self.options.host = host;
}
- if let Some(msgid) = args.value_of("msgid") {
- self.options.msgid = msgid.to_string();
+ if let Some(msgid) = args.opt_value_from_str(["-m", "--msgid"])? {
+ self.options.msgid = msgid;
}
- self.options.exclude_greylist = args.is_present("exclude_greylist");
- self.options.exclude_ndr = args.is_present("exclude_ndr");
+ self.options.exclude_greylist = args.contains(["-g", "--exclude-greylist"]);
+ self.options.exclude_ndr = args.contains(["-n", "--exclude-ndr"]);
- self.options.verbose = args.occurrences_of("verbose") as _;
+ while args.contains(["-v", "--verbose"]) {
+ self.options.verbose += 1;
+ }
- if let Some(string_match) = args.value_of("search") {
- self.options.string_match = string_match.to_string();
+ if let Some(string_match) = args.opt_value_from_str(["-x", "--search-string"])? {
+ self.options.string_match = string_match;
}
Ok(())
msgid: String,
from: String,
to: String,
- start: libc::time_t,
- end: libc::time_t,
+ start: time_t,
+ end: time_t,
limit: u64,
verbose: u32,
exclude_greylist: bool,
#[derive(Debug)]
enum Match {
Qid(Box<[u8]>),
- RelLineNr(libc::time_t, u64),
+ RelLineNr(time_t, u64),
}
#[derive(Debug, Default)]
host: Box<[u8]>,
service: Box<[u8]>,
pid: u64,
- timestamp: u64,
+ timestamp: time_t,
}
fn get_or_create_qentry(
sentries: &mut HashMap<u64, Rc<RefCell<SEntry>>>,
pid: u64,
rel_line_nr: u64,
- timestamp: u64,
+ timestamp: time_t,
) -> Rc<RefCell<SEntry>> {
if let Some(se) = sentries.get(&pid) {
Rc::clone(se)
}
}
-fn mkgmtime(tm: &time::Tm) -> libc::time_t {
- let mut res: libc::time_t;
-
- let mut year = (tm.tm_year + 1900) as i64;
- let mon = tm.tm_mon;
-
- res = (year - 1970) * 365 + CAL_MTOD[mon as usize];
-
- if mon <= 1 {
- year -= 1;
- }
-
- res += (year - 1968) / 4;
- res -= (year - 1900) / 100;
- res += (year - 1600) / 400;
-
- res += (tm.tm_mday - 1) as i64;
- res = res * 24 + tm.tm_hour as i64;
- res = res * 60 + tm.tm_min as i64;
- res = res * 60 + tm.tm_sec as i64;
-
- res
-}
-
const LOGFILES: [&str; 32] = [
"/var/log/syslog",
"/var/log/syslog.1",
}
/// Parse time. Returns a tuple of (parsed_time, remaining_text) or None.
-fn parse_time<'a>(
- data: &'a [u8],
+fn parse_time(
+ data: &'_ [u8],
+ cur_year: i64,
+ cur_month: i64,
+ timezone_offset: time_t,
+) -> Option<(time_t, &'_ [u8])> {
+ parse_time_with_year(data)
+ .or_else(|| parse_time_no_year(data, cur_year, cur_month, timezone_offset))
+}
+
+fn parse_time_with_year(data: &'_ [u8]) -> Option<(time_t, &'_ [u8])> {
+ let mut timestamp_buffer = [0u8; 25];
+
+ let count = data.iter().take_while(|b| **b != b' ').count();
+ if count != 27 && count != 32 {
+ return None;
+ }
+ let (timestamp, data) = data.split_at(count);
+ // remove whitespace
+ let data = &data[1..];
+
+ // microseconds: .123456 -> 7 bytes
+ let microseconds_idx = timestamp.iter().take_while(|b| **b != b'.').count();
+
+ // YYYY-MM-DDTHH:MM:SS
+ let year_time = ×tamp[0..microseconds_idx];
+ let year_time_len = year_time.len();
+ // Z | +HH:MM | -HH:MM
+ let timezone = ×tamp[microseconds_idx + 7..];
+ let timezone_len = timezone.len();
+ let timestamp_len = year_time_len + timezone_len;
+ timestamp_buffer[0..year_time_len].copy_from_slice(year_time);
+ timestamp_buffer[year_time_len..timestamp_len].copy_from_slice(timezone);
+
+ match proxmox_time::parse_rfc3339(unsafe {
+ std::str::from_utf8_unchecked(×tamp_buffer[0..timestamp_len])
+ }) {
+ Ok(ltime) => Some((ltime, data)),
+ Err(_err) => None,
+ }
+}
+
+fn parse_time_no_year(
+ data: &'_ [u8],
cur_year: i64,
- cur_month: &mut i64,
-) -> Option<(libc::time_t, &'a [u8])> {
+ cur_month: i64,
+ timezone_offset: time_t,
+) -> Option<(time_t, &'_ [u8])> {
if data.len() < 15 {
return None;
}
};
let data = &data[3..];
- let mut ltime: libc::time_t;
- let mut year = cur_year;
-
- if *cur_month == 11 && mon == 0 {
- year += 1;
- }
- if mon > *cur_month {
- *cur_month = mon;
- }
+ // assume smaller month now than in log line means yearwrap
+ let mut year = if cur_month < mon {
+ cur_year - 1
+ } else {
+ cur_year
+ };
- ltime = (year - 1970) * 365 + CAL_MTOD[mon as usize];
+ let mut ltime: time_t = (year - 1970) * 365 + CAL_MTOD[mon as usize];
+ // leap year considerations
if mon <= 1 {
year -= 1;
}
-
ltime += (year - 1968) / 4;
ltime -= (year - 1900) / 100;
ltime += (year - 1600) / 400;
let (mday, data) = match parse_number(data, 2) {
Some(t) => t,
- None => {
- return None;
- }
+ None => return None,
};
if mday == 0 {
return None;
let (hour, data) = match parse_number(data, 2) {
Some(t) => t,
- None => {
- return None;
- }
+ None => return None,
};
ltime *= 24;
let (min, data) = match parse_number(data, 2) {
Some(t) => t,
- None => {
- return None;
- }
+ None => return None,
};
ltime *= 60;
let (sec, data) = match parse_number(data, 2) {
Some(t) => t,
- None => {
- return None;
- }
+ None => return None,
};
ltime *= 60;
_ => &data[1..],
};
- Some((ltime, data))
+ Some((ltime - timezone_offset, data))
}
-const CAL_MTOD: [i64; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
-
type ByteSlice<'a> = &'a [u8];
/// Parse Host, Service and PID at the beginning of data. Returns a tuple of (host, service, pid, remaining_text).
fn parse_host_service_pid(data: &[u8]) -> Option<(ByteSlice, ByteSlice, u64, ByteSlice)> {
.count();
let service = &data[0..service_count];
let data = &data[service_count..];
- if data.get(0) != Some(&b'[') {
+ if data.first() != Some(&b'[') {
return None;
}
let data = &data[1..];