diff --git a/.travis.yml b/.travis.yml index 5dc6ae9..2fe0f3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,6 @@ sudo: required dist: trusty language: java jdk: -# - openjdk6 -# - openjdk7 - - oraclejdk7 - oraclejdk8 addons: # Fix OpenJDK builds diff --git a/pom.xml b/pom.xml index 3158f7a..e87cce6 100644 --- a/pom.xml +++ b/pom.xml @@ -49,6 +49,8 @@ UTF-8 + 1.8 + 1.8 @@ -105,6 +107,12 @@ 2.4.0 test + + org.assertj + assertj-core + 3.6.2 + test + @@ -120,10 +128,6 @@ org.apache.maven.plugins maven-compiler-plugin 3.5.1 - - 1.6 - 1.6 - org.apache.maven.plugins diff --git a/src/main/java/org/graylog2/syslog4j/server/impl/event/CiscoSyslogServerEvent.java b/src/main/java/org/graylog2/syslog4j/server/impl/event/CiscoSyslogServerEvent.java new file mode 100644 index 0000000..30c1045 --- /dev/null +++ b/src/main/java/org/graylog2/syslog4j/server/impl/event/CiscoSyslogServerEvent.java @@ -0,0 +1,157 @@ +package org.graylog2.syslog4j.server.impl.event; + +import java.net.InetAddress; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +/** + * CiscoSyslogServerEvent provides an implementation of the + * SyslogServerEventIF interface that supports receiving of + * Cisco syslog messages. + * + * @see An Overview of the syslog Protocol + */ +public class CiscoSyslogServerEvent extends SyslogServerEvent { + private static final DateTimeFormatter DEFAULT_FORMATTER = + DateTimeFormatter + .ofPattern("yyyy MMM ppd HH:mm:ss[.SSS][ zzz]", Locale.ROOT) + .withZone(ZoneOffset.UTC); + private int sequenceNumber = 0; + + public CiscoSyslogServerEvent(final byte[] message, int length, InetAddress inetAddress) { + super(); + + initialize(message, length, inetAddress); + parse(); + } + + public CiscoSyslogServerEvent(final String message, InetAddress inetAddress) { + super(); + + initialize(message, inetAddress); + parse(); + } + + @Override + protected void parsePriority() { + if (this.message.charAt(0) == '<') { + int i = this.message.indexOf(">"); + + if (i <= 4 && i > -1) { + String priorityStr = this.message.substring(1, i); + + int priority = 0; + try { + priority = Integer.parseInt(priorityStr); + this.facility = priority >> 3; + this.level = priority - (this.facility << 3); + + this.message = this.message.substring(i + 1); + + parseSequenceNumber(); + parseDate(); + } catch (NumberFormatException nfe) { + // + } + + parseHost(); + } + } + } + + private void parseSequenceNumber() { + int i = this.message.indexOf(':'); + if (i > -1) { + String sequenceNumberStr = this.message.substring(0, i); + if (sequenceNumberStr.isEmpty()) { + this.message = this.message.substring(i + 1); + } else { + try { + this.sequenceNumber = Integer.parseInt(sequenceNumberStr); + this.message = this.message.substring(i + 1); + } catch (NumberFormatException nfe) { + // + } + } + } + } + + @Override + protected void parseHost() { + if (message.indexOf('%') < 1) { + this.host = ""; + } else { + int i = this.message.indexOf(' '); + if (i > -1) { + this.host = this.message.substring(0, i).trim(); + + // Skip ' ' and ':' characters + char c = message.charAt(i); + while (c == ' ' || c == ':') { + c = message.charAt(++i); + } + + this.message = this.message.substring(i); + } + } + } + + @Override + protected void parseDate() { + // Skip leading spaces + while (message.charAt(0) == ' ') { + message = message.substring(1); + } + + // Skip leading asterisk + if (message.charAt(0) == '*') { + message = message.substring(1); + } + + int dateLength = message.indexOf(": "); + if (this.message.length() > dateLength) { + boolean isYearMissing = Character.isLetter(message.charAt(0)); + String originalDate = this.message.substring(0, dateLength); + DateTimeFormatter formatter = DEFAULT_FORMATTER; + + // Hacky override for: "Mar 06 2016 12:53:10 DEVICENAME :" + if (Character.isDigit(message.charAt(7)) + && Character.isDigit(message.charAt(8)) + && Character.isDigit(message.charAt(9)) + && Character.isDigit(message.charAt(10))) { + dateLength = 20; + isYearMissing = false; + originalDate = this.message.substring(0, dateLength); + formatter = DateTimeFormatter + .ofPattern("MMM ppd yyyy HH:mm:ss", Locale.ROOT) + .withZone(ZoneOffset.UTC); + } + + try { + if (isYearMissing) { + String year = Integer.toString(Calendar.getInstance().get(Calendar.YEAR)); + String modifiedDate = year + " " + originalDate; + final ZonedDateTime dateTime = ZonedDateTime.parse(modifiedDate, formatter); + this.date = Date.from(dateTime.toInstant()); + } else { + final ZonedDateTime dateTime = ZonedDateTime.parse(originalDate, formatter); + this.date = Date.from(dateTime.toInstant()); + } + + this.message = this.message.substring(dateLength + 1); + + } catch (DateTimeParseException pe) { + this.date = new Date(); + } + } + } + + public int getSequenceNumber() { + return sequenceNumber; + } +} diff --git a/src/main/java/org/graylog2/syslog4j/server/impl/event/FortiGateSyslogEvent.java b/src/main/java/org/graylog2/syslog4j/server/impl/event/FortiGateSyslogEvent.java new file mode 100644 index 0000000..c65ab18 --- /dev/null +++ b/src/main/java/org/graylog2/syslog4j/server/impl/event/FortiGateSyslogEvent.java @@ -0,0 +1,176 @@ +package org.graylog2.syslog4j.server.impl.event; + +import org.graylog2.syslog4j.server.SyslogServerEventIF; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.Objects.requireNonNull; + +/** + * FortiGateSyslogEvent provides an implementation of the + * SyslogServerEventIF interface that supports receiving of + * FortiGate/FortiOS syslog messages. + * + * @see FortiGate logging and reporting overview + */ +public class FortiGateSyslogEvent implements SyslogServerEventIF { + private static final Pattern PRI_PATTERN = Pattern.compile("^<(\\d{1,3})>(.*)$"); + private static final Pattern KV_PATTERN = Pattern.compile("(\\w+)=([^\\s\"]*)"); + private static final Pattern QUOTED_KV_PATTERN = Pattern.compile("(\\w+)=\"([^\"]*)\""); + + private final String rawEvent; + private Date date; + private int facility; + private int level; + private String host; + private String message; + private Charset charSet = StandardCharsets.UTF_8; + private Map fields = Collections.emptyMap(); + + public FortiGateSyslogEvent(final String rawEvent) { + this.rawEvent = requireNonNull(rawEvent, "rawEvent"); + parse(rawEvent); + } + + private void parse(String event) { + final Matcher matcher = PRI_PATTERN.matcher(event); + if (!matcher.find()) { + throw new IllegalArgumentException("Invalid Fortigate syslog message"); + } else { + final String priority = matcher.group(1); + final String message = matcher.group(2); + + parsePriority(priority); + setMessage(message); + parseFields(message); + parseDate(fields.get("date"), fields.get("time")); + setHost(fields.get("devname")); + } + } + + private void parsePriority(String priorityString) { + try { + final int priority = Integer.parseInt(priorityString); + setFacility(priority / 8); + setLevel(priority % 8); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Couldn't parse message priority", e); + } + } + + private void parseFields(String event) { + final Map fields = new HashMap<>(); + final Matcher matcher = KV_PATTERN.matcher(event); + while (matcher.find()) { + fields.put(matcher.group(1), matcher.group(2)); + } + final Matcher quotedMatcher = QUOTED_KV_PATTERN.matcher(event); + while (quotedMatcher.find()) { + fields.put(quotedMatcher.group(1), quotedMatcher.group(2)); + } + setFields(fields); + } + + private void parseDate(String date, String time) { + if (date != null && time != null) { + final ZonedDateTime dateTime = ZonedDateTime.of( + LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE.withZone(ZoneOffset.UTC)), + LocalTime.parse(time, DateTimeFormatter.ISO_LOCAL_TIME.withZone(ZoneOffset.UTC)), + ZoneOffset.UTC); + setDate(Date.from(dateTime.toInstant())); + + } else { + setDate(new Date()); + } + } + + @Override + public byte[] getRaw() { + return rawEvent.getBytes(charSet); + } + + @Override + public int getFacility() { + return facility; + } + + @Override + public void setFacility(int facility) { + this.facility = requireNonNull(facility, "facility"); + } + + @Override + public Date getDate() { + return date; + } + + @Override + public void setDate(Date date) { + this.date = requireNonNull(date, "date"); + } + + @Override + public int getLevel() { + return level; + } + + @Override + public void setLevel(int level) { + this.level = requireNonNull(level, "level"); + } + + @Override + public String getHost() { + return host; + } + + @Override + public void setHost(String host) { + this.host = host; + } + + @Override + public boolean isHostStrippedFromMessage() { + return false; + } + + @Override + public String getMessage() { + return message; + } + + @Override + public void setMessage(String message) { + this.message = requireNonNull(message, "message"); + } + + @Override + public String getCharSet() { + return charSet.name(); + } + + @Override + public void setCharSet(String charSet) { + this.charSet = Charset.forName(charSet); + } + + public Map getFields() { + return Collections.unmodifiableMap(fields); + } + + public void setFields(Map fields) { + this.fields = requireNonNull(fields, "fields"); + } +} diff --git a/src/test/java/org/graylog2/syslog4j/server/impl/event/CiscoSyslogServerEventTest.java b/src/test/java/org/graylog2/syslog4j/server/impl/event/CiscoSyslogServerEventTest.java new file mode 100644 index 0000000..c9a25c7 --- /dev/null +++ b/src/test/java/org/graylog2/syslog4j/server/impl/event/CiscoSyslogServerEventTest.java @@ -0,0 +1,128 @@ +package org.graylog2.syslog4j.server.impl.event; + +import org.junit.Test; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Date; + +import static java.time.ZoneOffset.UTC; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; + + +public class CiscoSyslogServerEventTest { + private static final InetAddress INET_ADDR = new InetSocketAddress(514).getAddress(); + private static final ZoneId CET = ZoneId.of("CET"); + private static final int YEAR = ZonedDateTime.now().getYear(); + + private CiscoSyslogServerEvent buildEvent(String message) { + return new CiscoSyslogServerEvent(message, INET_ADDR); + } + + @Test + public void testCisco1() throws Exception { + final String message = "<166>Mar 06 2016 12:53:10 DEVICENAME : %ASA-6-302013: Built inbound TCP connection 723494125 for FRONTEND:IP/11288 (IP/11288) to BACKEND:IP/27180 (IP/27180)"; + + final CiscoSyslogServerEvent event = buildEvent(message); + + assertThat(toZonedDateTime(event.getDate(), UTC)).isEqualTo(ZonedDateTime.of(2016, 3, 6, 12, 53, 10, 0, UTC)); + assertThat(event.getFacility()).isEqualTo(20); + assertThat(event.getLevel()).isEqualTo(6); + assertThat(event.getSequenceNumber()).isEqualTo(0); + assertThat(event.getHost()).isEqualTo("DEVICENAME"); + assertThat(event.getMessage()).isEqualTo("%ASA-6-302013: Built inbound TCP connection 723494125 for FRONTEND:IP/11288 (IP/11288) to BACKEND:IP/27180 (IP/27180)"); + } + + @Test + public void testCisco2() throws Exception { + final String message = "<186>1541800: Feb 27 06:08:59.485: %HARDWARE-2-FAN_ERROR: Fan Failure"; + + final CiscoSyslogServerEvent event = buildEvent(message); + + assertThat(toZonedDateTime(event.getDate(), UTC)).isEqualTo(ZonedDateTime.of(YEAR, 2, 27, 6, 8, 59, 485_000_000, UTC)); + assertThat(event.getFacility()).isEqualTo(23); + assertThat(event.getLevel()).isEqualTo(2); + assertThat(event.getSequenceNumber()).isEqualTo(1541800); + assertThat(event.getHost()).isEmpty(); + assertThat(event.getMessage()).isEqualTo("%HARDWARE-2-FAN_ERROR: Fan Failure"); + } + + @Test + public void testCisco3() throws Exception { + final String message = "<187>148094: Feb 27 06:07:29.716: %LINK-3-UPDOWN: Interface GigabitEthernet1/0/15, changed state to down"; + + final CiscoSyslogServerEvent event = buildEvent(message); + + assertThat(toZonedDateTime(event.getDate(), UTC)).isEqualTo(ZonedDateTime.of(YEAR, 2, 27, 6, 7, 29, 716_000_000, UTC)); + assertThat(event.getFacility()).isEqualTo(23); + assertThat(event.getLevel()).isEqualTo(3); + assertThat(event.getSequenceNumber()).isEqualTo(148094); + assertThat(event.getHost()).isEmpty(); + assertEquals("%LINK-3-UPDOWN: Interface GigabitEthernet1/0/15, changed state to down", event.getMessage()); + } + + @Test + public void testCisco4() throws Exception { + final String message = "<190>530470: *Sep 28 17:13:35.098: %SEC-6-IPACCESSLOGP: list MGMT_IN denied udp IP(49964) -> IP(161), 11 packets"; + + final CiscoSyslogServerEvent event = buildEvent(message); + + assertThat(toZonedDateTime(event.getDate(), UTC)).isEqualTo(ZonedDateTime.of(YEAR, 9, 28, 17, 13, 35, 98_000_000, UTC)); + assertThat(event.getFacility()).isEqualTo(23); + assertThat(event.getLevel()).isEqualTo(6); + assertThat(event.getSequenceNumber()).isEqualTo(530470); + assertThat(event.getHost()).isEmpty(); + assertEquals("%SEC-6-IPACCESSLOGP: list MGMT_IN denied udp IP(49964) -> IP(161), 11 packets", event.getMessage()); + } + + @Test + public void testCisco5() throws Exception { + final String message = "<190>: 2016 Mar 06 09:22:34 CET: %AUTHPRIV-6-SYSTEM_MSG: START: rsync pid=4311 from=::ffff:IP - xinetd[6219]"; + + final CiscoSyslogServerEvent event = buildEvent(message); + + assertThat(toZonedDateTime(event.getDate(), CET)).isEqualTo(ZonedDateTime.of(2016, 3, 6, 9, 22, 34, 0, CET)); + assertThat(event.getFacility()).isEqualTo(23); + assertThat(event.getLevel()).isEqualTo(6); + assertThat(event.getSequenceNumber()).isEqualTo(0); + assertThat(event.getHost()).isEmpty(); + assertThat(event.getMessage()).isEqualTo("%AUTHPRIV-6-SYSTEM_MSG: START: rsync pid=4311 from=::ffff:IP - xinetd[6219]"); + } + + @Test + public void testCisco6() throws Exception { + final String message = "<134>: 2016 Mar 6 12:53:10 UTC: %POLICY_ENGINE-6-POLICY_LOOKUP_EVENT: policy=POLICYNAME rule=RULENAME action=Permit direction=egress src.net.ip-address=IP src.net.port=38321 dst.net.ip-address=IP dst.net.port=5666 net.protocol=6 net.ethertype=800 net.service=\"protocol 6 port 5666\""; + + final CiscoSyslogServerEvent event = buildEvent(message); + + assertThat(toZonedDateTime(event.getDate(), UTC)).isEqualTo(ZonedDateTime.of(2016, 3, 6, 12, 53, 10, 0, UTC)); + assertThat(event.getFacility()).isEqualTo(16); + assertThat(event.getLevel()).isEqualTo(6); + assertThat(event.getSequenceNumber()).isEqualTo(0); + assertThat(event.getHost()).isEmpty(); + assertThat(event.getMessage()).isEqualTo("%POLICY_ENGINE-6-POLICY_LOOKUP_EVENT: policy=POLICYNAME rule=RULENAME action=Permit direction=egress src.net.ip-address=IP src.net.port=38321 dst.net.ip-address=IP dst.net.port=5666 net.protocol=6 net.ethertype=800 net.service=\"protocol 6 port 5666\""); + } + + @Test + public void testCisco7() throws Exception { + final String message = "<166>%ASA-6-302015: Built inbound UDP connection 23631055 for inside:192.168.19.91/44764 (192.168.19.91/44764) to identity:192.168.249.33/161 (192.168.249.33/161)"; + + final CiscoSyslogServerEvent event = buildEvent(message); + + assertThat(event.getDate()) + .isInThePast() + .isInSameMinuteWindowAs(new Date()); + assertThat(event.getFacility()).isEqualTo(20); + assertThat(event.getLevel()).isEqualTo(6); + assertThat(event.getSequenceNumber()).isEqualTo(0); + assertThat(event.getHost()).isEmpty(); + assertThat(event.getMessage()).isEqualTo("%ASA-6-302015: Built inbound UDP connection 23631055 for inside:192.168.19.91/44764 (192.168.19.91/44764) to identity:192.168.249.33/161 (192.168.249.33/161)"); + } + + private ZonedDateTime toZonedDateTime(Date date, ZoneId zoneId) { + return ZonedDateTime.ofInstant(date.toInstant(), zoneId); + } +} \ No newline at end of file diff --git a/src/test/java/org/graylog2/syslog4j/server/impl/event/FortiGateSyslogEventTest.java b/src/test/java/org/graylog2/syslog4j/server/impl/event/FortiGateSyslogEventTest.java new file mode 100644 index 0000000..ff430f6 --- /dev/null +++ b/src/test/java/org/graylog2/syslog4j/server/impl/event/FortiGateSyslogEventTest.java @@ -0,0 +1,34 @@ +package org.graylog2.syslog4j.server.impl.event; + +import org.junit.Test; + +import java.nio.charset.StandardCharsets; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +public class FortiGateSyslogEventTest { + @Test + public void testFortiGateMessage() { + final String rawMessage = "<45>date=2017-03-06 time=12:53:10 devname=DEVICENAME devid=DEVICEID logid=0000000013 type=traffic subtype=forward level=notice vd=ALIAS srcip=IP srcport=45748 srcintf=\"IF\" dstip=IP dstport=443 dstintf=\"IF\" sessionid=1122686199 status=close policyid=77 dstcountry=\"COUNTRY\" srccountry=\"COUNTRY\" trandisp=dnat tranip=IP tranport=443 service=HTTPS proto=6 appid=41540 app=\"SSL_TLSv1.2\" appcat=\"Network.Service\" applist=\"ACLNAME\" appact=detected duration=1 sentbyte=2313 rcvdbyte=14883 sentpkt=19 rcvdpkt=19 utmaction=passthrough utmevent=app-ctrl attack=\"SSL\" hostname=\"HOSTNAME\" custom=\"white space\""; + final FortiGateSyslogEvent event = new FortiGateSyslogEvent(rawMessage); + + assertThat(event).isNotNull(); + assertThat(event.getFacility()).isEqualTo(5); + assertThat(event.getLevel()).isEqualTo(5); + assertThat(event.getHost()).isEqualTo("DEVICENAME"); + assertThat(ZonedDateTime.ofInstant(event.getDate().toInstant(), ZoneOffset.UTC)) + .isEqualTo(ZonedDateTime.of(2017, 3, 6, 12, 53, 10, 0, ZoneOffset.UTC)); + assertThat(event.getRaw()).isEqualTo(rawMessage.getBytes(StandardCharsets.UTF_8)); + assertThat(event.getMessage()).isEqualTo("date=2017-03-06 time=12:53:10 devname=DEVICENAME devid=DEVICEID logid=0000000013 type=traffic subtype=forward level=notice vd=ALIAS srcip=IP srcport=45748 srcintf=\"IF\" dstip=IP dstport=443 dstintf=\"IF\" sessionid=1122686199 status=close policyid=77 dstcountry=\"COUNTRY\" srccountry=\"COUNTRY\" trandisp=dnat tranip=IP tranport=443 service=HTTPS proto=6 appid=41540 app=\"SSL_TLSv1.2\" appcat=\"Network.Service\" applist=\"ACLNAME\" appact=detected duration=1 sentbyte=2313 rcvdbyte=14883 sentpkt=19 rcvdpkt=19 utmaction=passthrough utmevent=app-ctrl attack=\"SSL\" hostname=\"HOSTNAME\" custom=\"white space\""); + assertThat(event.getFields()) + .containsEntry("date", "2017-03-06") + .containsEntry("time", "12:53:10") + .containsEntry("devname", "DEVICENAME") + .containsEntry("devid", "DEVICEID") + .containsEntry("hostname", "HOSTNAME") + .containsEntry("custom", "white space"); + } + +} \ No newline at end of file