1use std::os::unix::net::UnixDatagram;
2use std::str::from_utf8;
3use std::error::Error;
4use std::fmt::{self, Display};
5use std::net::Shutdown;
6use problem::prelude::*;
7use problem::result::Result as PResult;
8use std::path::PathBuf;
9use std::io::{Write, Error as IoError, ErrorKind};
10use chrono::offset::Local;
11use chrono::{DateTime, FixedOffset};
12use itertools::Itertools;
13use log::*;
14
15#[derive(Debug)]
16pub struct SyslogPriorityNameError;
17
18impl Display for SyslogPriorityNameError {
19 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20 write!(f, "invalid syslog facility or severity name")
21 }
22}
23impl Error for SyslogPriorityNameError { }
24
25const FACILITY: [&str; 24] = ["kern", "user", "mail", "daemon", "auth", "syslog", "lpr", "news", "uucp", "cron", "authpriv", "ftp", "ntp", "audit", "alert", "clockd", "local0", "local1", "local2", "local3", "local4", "local5", "local6", "local7"];
26
27pub fn facility_by_id(facility: u8) -> Option<&'static str> {
28 FACILITY.get(facility as usize).map(|s| *s)
29}
30
31pub fn facility_by_name(facility: &str) -> Result<u8, SyslogPriorityNameError> {
32 FACILITY
33 .iter()
34 .position(|name| *name == facility)
35 .map(|pos| pos as u8)
36 .ok_or(SyslogPriorityNameError)
37}
38
39const SEVERITY: [&str; 8] = ["emerg", "alert", "crit", "err", "warning", "notice", "info", "debug"];
40
41pub fn severity_by_id(severity: u8) -> Option<&'static str> {
42 SEVERITY.get(severity as usize).map(|s| *s)
43}
44
45pub fn severity_by_name(facility: &str) -> Result<u8, SyslogPriorityNameError> {
46 SEVERITY
47 .iter()
48 .position(|name| *name == facility)
49 .map(|pos| pos as u8)
50 .ok_or(SyslogPriorityNameError)
51}
52
53#[derive(Debug)]
54pub struct SyslogHeader<'s> {
55 pub facility: u8,
56 pub severity: u8,
57 pub timestamp: DateTime<FixedOffset>,
58 pub hostname: Option<&'s [u8]>,
59 pub tag: Option<&'s [u8]>,
60}
61
62pub fn write_syslog_message(out: &mut impl Write, message: &[u8], escape: &[u8]) -> Result<(), IoError> {
64 for (no, line) in message.split(|c| *c == b'\n').enumerate() {
65 if no != 0 {
66 out.write_all(escape)?;
67 }
68 out.write_all(line)?;
69 }
70 Ok(())
71}
72
73impl<'s> SyslogHeader<'s> {
74 pub fn new(timestamp: DateTime<FixedOffset>, facility: &str, severity: &str, tag: &'s str) -> Result<SyslogHeader<'s>, SyslogPriorityNameError> {
75 Ok(SyslogHeader {
76 facility: facility_by_name(facility)?,
77 severity: severity_by_name(severity)?,
78 timestamp,
79 hostname: None,
80 tag: Some(tag.as_bytes()),
81 })
82 }
83
84 pub fn new_raw(timestamp: DateTime<FixedOffset>, facility: u8, severity: u8, tag: &'s [u8]) -> SyslogHeader<'s> {
85 SyslogHeader {
86 facility,
87 severity,
88 timestamp,
89 hostname: None,
90 tag: Some(tag),
91 }
92 }
93
94 pub fn hostname(&mut self, hostname: &'s[u8]) {
95 self.hostname = Some(hostname);
96 }
97
98 pub fn write(&self, out: &mut impl Write, timestamp_rfc3339: bool) -> Result<(), IoError> {
99 write!(out, "<{}>{} ",
100 ((self.facility as i32) << 3) + (self.severity as i32),
101 if timestamp_rfc3339 {
102 self.timestamp.format("%Y-%m-%dT%H:%M:%S%.6f%:z")
103 } else {
104 self.timestamp.format("%b %e %T")
105 }
106 )?;
107 if let Some(hostname) = self.hostname {
108 write_syslog_message(out, hostname, b"")?;
109 out.write_all(b" ")?;
110 }
111 write_syslog_message(out, self.tag.unwrap_or(b"syslog"), b"")?;
112 out.write_all(b": ")?;
113
114 Ok(())
115 }
116
117 pub fn parse(message: &[u8], timestamp: Option<DateTime<FixedOffset>>) -> Option<(SyslogHeader, &[u8])> {
118 let (pri, message) = message
119 .strip_prefix(b"<")?
120 .splitn(2, |c| *c == b'>')
121 .collect_tuple()?;
122
123 let pri: u8 = from_utf8(pri).ok()?.parse().ok()?;
124 let facility = pri >> 3;
125 let severity = pri & 0x7;
126
127 let mut timestamp_rfc3339 = None;
129
130 let message = if let (Some(b' '), Some(b' '), Some(b':'), Some(b':'), Some(b' ')) =
131 (message.get(3), message.get(6), message.get(9), message.get(12), message.get(15)) {
132 message.split_at(16).1
134 } else if let (Some(b'-'), Some(b'-'), Some(b'T'), Some((date, message))) =
135 (message.get(4), message.get(7), message.get(10), message.splitn(2, |c| *c == b' ').collect_tuple()) {
136 let date = String::from_utf8_lossy(date);
138 timestamp_rfc3339 = DateTime::parse_from_rfc3339(&date).ok();
139 message
140 } else if let Some((_sec_since_boot, message)) =
141 message.strip_prefix(b"[").and_then(|m| m.splitn(2, |c| *c == b']').collect_tuple()) {
142 message.strip_prefix(b" ").unwrap_or(message)
144 } else {
145 message
146 };
147
148 let (tag, message) = if let Some(tag_end) = message.windows(2).position(|c| *c == [b':', b' ']) {
149 let (tag, message) = message.split_at(tag_end);
150 (Some(tag), &message[2..])
151 } else {
152 (None, message)
153 };
154
155 if timestamp_rfc3339.is_some() {
156 debug!("using RFC3339 timestamp from syslog header");
157 }
158
159 Some((SyslogHeader {
160 facility,
161 severity,
162 timestamp: timestamp_rfc3339.or(timestamp).unwrap_or_else(|| Local::now().into()),
163 hostname: None,
164 tag,
165 }, message))
166 }
167}
168
169#[derive(Debug)]
170pub struct SyslogUnix<'b> {
171 path: PathBuf,
172 socket: UnixDatagram,
173 buf: &'b mut [u8],
174}
175
176impl<'b> SyslogUnix<'b> {
177 pub fn new(buf: &'b mut [u8], path: impl Into<PathBuf>) -> PResult<SyslogUnix<'b>> {
178 let path = path.into();
179 let socket = UnixDatagram::unbound().problem_while("creating syslog socket")?;
180 socket.connect(&path).problem_while("connecting to syslog socket")?;
181
182 Ok(SyslogUnix {
183 path,
184 socket,
185 buf,
186 })
187 }
188
189 pub fn close(self) -> Result<(), IoError> {
190 self.socket.shutdown(Shutdown::Both)?;
191 Ok(())
192 }
193
194 pub fn datagram(&mut self, syslog: &SyslogHeader, timestamp_rfc3339: bool) -> Result<SyslogDatagram, IoError> {
195 let mut sd = SyslogDatagram {
196 buf_len: self.buf.len(),
197 datagram: self.buf,
198 };
199
200 syslog.write(&mut sd, timestamp_rfc3339)?;
201 Ok(sd)
202 }
203
204 pub fn with_datagram_send<O>(&mut self, syslog: &SyslogHeader, timestamp_rfc3339: bool, f: impl FnOnce(&mut SyslogDatagram) -> PResult<O> ) -> PResult<O> {
205 let mut dgram = self.datagram(syslog, timestamp_rfc3339).problem_while("writing syslog header")?;
206
207 let ret = f(&mut dgram)?;
208
209 let len = dgram.bytes_written();
210 self.send(len).problem_while("sending message to syslog socket")?;
211 Ok(ret)
212 }
213
214 pub fn send(&mut self, datagram_len: usize) -> Result<usize, IoError> {
215 let datagram = &self.buf[..datagram_len];
216 trace!("{}", String::from_utf8_lossy(datagram));
217 let len = loop {
218 match self.socket.send(datagram) {
219 Ok(len) => break Ok(len),
220 Err(err) if err.kind() == ErrorKind::NotConnected => {
221 self.socket.connect(&self.path)?;
222 debug!("deconnected to socket: {:?}", self.path);
223 }
224 Err(err) => break Err(err),
225 };
226 }?;
227 debug!("sent {} bytes datagram", len);
228 Ok(len)
229 }
230}
231
232#[derive(Debug)]
233pub struct SyslogDatagram<'b> {
234 buf_len: usize,
235 datagram: &'b mut [u8],
236}
237
238impl<'b> Write for SyslogDatagram<'b> {
239 fn write(&mut self, buf: &[u8]) -> Result<usize, IoError> {
240 self.datagram.write(buf)
241 }
242
243 fn flush(&mut self) -> Result<(), IoError> {
244 self.datagram.flush()
245 }
246}
247
248impl<'b> SyslogDatagram<'b> {
249 pub fn bytes_written(self) -> usize {
250 self.buf_len - self.datagram.len()
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_parse_minimal() {
260 let ts = DateTime::parse_from_rfc3339("2022-04-14T15:34:45.327514+00:00").unwrap();
261 let (hdr, msg) = SyslogHeader::parse(b"<5>hello world!", Some(ts)).unwrap();
262 assert_eq!(facility_by_id(hdr.facility).unwrap(), "kern");
263 assert_eq!(severity_by_id(hdr.severity).unwrap(), "notice");
264 assert_eq!(hdr.timestamp, ts);
265 assert_eq!(String::from_utf8(msg.to_vec()).unwrap(), "hello world!");
266 }
267
268 #[test]
269 fn test_parse_klog() {
270 let ts = DateTime::parse_from_rfc3339("2022-04-14T15:34:45.327514+00:00").unwrap();
271 let (hdr, msg) = SyslogHeader::parse(b"<3>[23540.809085] usb 1-1: 3:1: cannot get min/max values for control 2 (id 3)", Some(ts)).unwrap();
272 assert_eq!(facility_by_id(hdr.facility).unwrap(), "kern");
273 assert_eq!(severity_by_id(hdr.severity).unwrap(), "err");
274 assert_eq!(hdr.timestamp, ts);
275 assert_eq!(String::from_utf8(hdr.tag.unwrap().to_vec()).unwrap(), "usb 1-1");
276 assert_eq!(String::from_utf8(msg.to_vec()).unwrap(), "3:1: cannot get min/max values for control 2 (id 3)");
277 }
278
279 #[test]
280 fn test_parse_bsd() {
281 let ts = DateTime::parse_from_rfc3339("2022-04-14T15:34:45.327514+00:00").unwrap();
282 let (hdr, msg) = SyslogHeader::parse(b"<13>Apr 25 15:46:05 fred: hello world", Some(ts)).unwrap();
283 assert_eq!(facility_by_id(hdr.facility).unwrap(), "user");
284 assert_eq!(severity_by_id(hdr.severity).unwrap(), "notice");
285 assert_eq!(hdr.timestamp, ts);
286 assert_eq!(String::from_utf8(hdr.tag.unwrap().to_vec()).unwrap(), "fred");
287 assert_eq!(String::from_utf8(msg.to_vec()).unwrap(), "hello world");
288 }
289
290 #[test]
291 fn test_parse_rfs3339() {
292 let (hdr, msg) = SyslogHeader::parse(b"<13>2022-04-14T15:34:45.327514+00:00 fred: hello world", None).unwrap();
293 assert_eq!(facility_by_id(hdr.facility).unwrap(), "user");
294 assert_eq!(severity_by_id(hdr.severity).unwrap(), "notice");
295 assert_eq!(hdr.timestamp, DateTime::parse_from_rfc3339("2022-04-14T15:34:45.327514+00:00").unwrap());
296 assert_eq!(String::from_utf8(hdr.tag.unwrap().to_vec()).unwrap(), "fred");
297 assert_eq!(String::from_utf8(msg.to_vec()).unwrap(), "hello world");
298 }
299}