/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.component.syslog;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.DatatypeConverter;
import org.apache.camel.Converter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Converter
public final class SyslogConverter {
private static final Logger LOG = LoggerFactory.getLogger(SyslogConverter.class);
private enum MONTHS {
jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec
}
private static Map<String, MONTHS> monthValueMap = new HashMap<String, MONTHS>() {
private static final long serialVersionUID = 1L;
{
put("jan", MONTHS.jan);
put("feb", MONTHS.feb);
put("mar", MONTHS.mar);
put("apr", MONTHS.apr);
put("may", MONTHS.may);
put("jun", MONTHS.jun);
put("jul", MONTHS.jul);
put("aug", MONTHS.aug);
put("sep", MONTHS.sep);
put("oct", MONTHS.oct);
put("nov", MONTHS.nov);
put("dec", MONTHS.dec);
}
};
private SyslogConverter() {
// Utility class
}
@Converter
public static String toString(SyslogMessage message) {
boolean isRfc5424 = message instanceof Rfc5424SyslogMessage;
StringBuilder sbr = new StringBuilder();
sbr.append("<");
if (message.getFacility() == null) {
message.setFacility(SyslogFacility.USER);
}
if (message.getSeverity() == null) {
message.setSeverity(SyslogSeverity.INFO);
}
if (message.getHostname() == null) {
// This is massively ugly..
try {
message.setHostname(InetAddress.getLocalHost().toString());
} catch (UnknownHostException e) {
message.setHostname("UNKNOWN_HOST");
}
}
sbr.append(message.getFacility().ordinal() * 8 + message.getSeverity().ordinal());
sbr.append(">");
// version number
if (isRfc5424) {
sbr.append("1");
sbr.append(" ");
}
if (message.getTimestamp() == null) {
message.setTimestamp(Calendar.getInstance());
}
if (isRfc5424) {
sbr.append(DatatypeConverter.printDateTime(message.getTimestamp()));
} else {
addRfc3164TimeStamp(sbr, message);
}
sbr.append(" ");
sbr.append(message.getHostname());
sbr.append(" ");
if (isRfc5424) {
Rfc5424SyslogMessage rfc5424SyslogMessage = (Rfc5424SyslogMessage) message;
sbr.append(rfc5424SyslogMessage.getAppName());
sbr.append(" ");
sbr.append(rfc5424SyslogMessage.getProcId());
sbr.append(" ");
sbr.append(rfc5424SyslogMessage.getMsgId());
sbr.append(" ");
sbr.append(rfc5424SyslogMessage.getStructuredData());
sbr.append(" ");
}
sbr.append(message.getLogMessage());
return sbr.toString();
}
@Converter
public static SyslogMessage toSyslogMessage(String body) {
return parseMessage(body.getBytes());
}
public static SyslogMessage parseMessage(byte[] bytes) {
ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
byteBuffer.put(bytes);
byteBuffer.rewind();
Character charFound = (char) byteBuffer.get();
SyslogFacility foundFacility = null;
SyslogSeverity foundSeverity = null;
while (charFound != '<') {
// Ignore noise in beginning of message.
charFound = (char) byteBuffer.get();
}
char priChar = 0;
if (charFound == '<') {
int facility = 0;
while (Character.isDigit(priChar = (char) (byteBuffer.get() & 0xff))) {
facility *= 10;
facility += Character.digit(priChar, 10);
}
foundFacility = SyslogFacility.values()[facility >> 3];
foundSeverity = SyslogSeverity.values()[facility & 0x07];
}
if (priChar != '>') {
// Invalid character - this is not a well defined syslog message.
LOG.error("Invalid syslog message, missing a > in the Facility/Priority part");
}
SyslogMessage syslogMessage = new SyslogMessage();
boolean isRfc5424 = false;
// Read next character
charFound = (char) byteBuffer.get();
// If next character is a 1, we have probably found an rfc 5424 message
// message
if (charFound == '1') {
syslogMessage = new Rfc5424SyslogMessage();
isRfc5424 = true;
} else {
// go back one to parse the rfc3164 date
byteBuffer.position(byteBuffer.position() - 1);
}
syslogMessage.setFacility(foundFacility);
syslogMessage.setSeverity(foundSeverity);
if (!isRfc5424) {
// Parse rfc 3164 date
syslogMessage.setTimestamp(parseRfc3164Date(byteBuffer));
} else {
charFound = (char) byteBuffer.get();
if (charFound != ' ') {
LOG.error("Invalid syslog message, missing a mandatory space after version");
}
// This should be the timestamp
StringBuilder date = new StringBuilder();
while ((charFound = (char) (byteBuffer.get() & 0xff)) != ' ') {
date.append(charFound);
}
syslogMessage.setTimestamp(DatatypeConverter.parseDateTime(date.toString()));
}
// The host is the char sequence until the next ' '
StringBuilder host = new StringBuilder();
while ((charFound = (char) (byteBuffer.get() & 0xff)) != ' ') {
host.append(charFound);
}
syslogMessage.setHostname(host.toString());
if (isRfc5424) {
Rfc5424SyslogMessage rfc5424SyslogMessage = (Rfc5424SyslogMessage) syslogMessage;
StringBuilder appName = new StringBuilder();
while ((charFound = (char) (byteBuffer.get() & 0xff)) != ' ') {
appName.append(charFound);
}
rfc5424SyslogMessage.setAppName(appName.toString());
StringBuilder procId = new StringBuilder();
while ((charFound = (char) (byteBuffer.get() & 0xff)) != ' ') {
procId.append(charFound);
}
rfc5424SyslogMessage.setProcId(procId.toString());
StringBuilder msgId = new StringBuilder();
while ((charFound = (char) (byteBuffer.get() & 0xff)) != ' ') {
msgId.append(charFound);
}
rfc5424SyslogMessage.setMsgId(msgId.toString());
StringBuilder structuredData = new StringBuilder();
boolean inblock = false;
while (((charFound = (char) (byteBuffer.get() & 0xff)) != ' ') || inblock) {
if (charFound == '[') {
inblock = true;
}
if (charFound == ']') {
inblock = false;
}
structuredData.append(charFound);
}
rfc5424SyslogMessage.setStructuredData(structuredData.toString());
}
StringBuilder msg = new StringBuilder();
while (byteBuffer.hasRemaining()) {
charFound = (char) (byteBuffer.get() & 0xff);
msg.append(charFound);
}
syslogMessage.setLogMessage(msg.toString());
LOG.trace("Syslog message : {}", syslogMessage.toString());
return syslogMessage;
}
private static void addRfc3164TimeStamp(StringBuilder sbr, SyslogMessage message) {
// SDF isn't going to help much here.
Calendar cal = message.getTimestamp();
String firstLetter = MONTHS.values()[cal.get(Calendar.MONTH)].toString().substring(0, 1); // Get
// first
// letter
String remainder = MONTHS.values()[cal.get(Calendar.MONTH)].toString().substring(1); // Get
// remainder
// of
// word.
String capitalized = firstLetter.toUpperCase() + remainder.toLowerCase();
sbr.append(capitalized);
sbr.append(" ");
if (cal.get(Calendar.DAY_OF_MONTH) < 10) {
sbr.append(" ").append(cal.get(Calendar.DAY_OF_MONTH));
} else {
sbr.append(cal.get(Calendar.DAY_OF_MONTH));
}
sbr.append(" ");
if (cal.get(Calendar.HOUR_OF_DAY) < 10) {
sbr.append("0").append(cal.get(Calendar.HOUR_OF_DAY));
} else {
sbr.append(cal.get(Calendar.HOUR_OF_DAY));
}
sbr.append(":");
if (cal.get(Calendar.MINUTE) < 10) {
sbr.append("0").append(cal.get(Calendar.MINUTE));
} else {
sbr.append(cal.get(Calendar.MINUTE));
}
sbr.append(":");
if (cal.get(Calendar.SECOND) < 10) {
sbr.append("0").append(cal.get(Calendar.SECOND));
} else {
sbr.append(cal.get(Calendar.SECOND));
}
}
private static Calendar parseRfc3164Date(ByteBuffer byteBuffer) {
char charFound;
// Done parsing severity and facility
// <169>Oct 22 10:52:01 TZ-6 scapegoat.dmz.example.org 10.1.2.3
// sched[0]: That's All Folks!
// Need to parse the date.
/**
* The TIMESTAMP field is the local time and is in the format of
* "Mmm dd hh:mm:ss" (without the quote marks) where: Mmm is the English
* language abbreviation for the month of the year with the first
* character in uppercase and the other two characters in lowercase. The
* following are the only acceptable values: Jan, Feb, Mar, Apr, May,
* Jun, Jul, Aug, Sep, Oct, Nov, Dec dd is the day of the month. If the
* day of the month is less than 10, then it MUST be represented as a
* space and then the number. For example, the 7th day of August would
* be represented as "Aug 7", with two spaces between the "g" and the
* "7". hh:mm:ss is the local time. The hour (hh) is represented in a
* 24-hour format. Valid entries are between 00 and 23, inclusive. The
* minute (mm) and second (ss) entries are between 00 and 59 inclusive.
*/
char[] month = new char[3];
for (int i = 0; i < 3; i++) {
month[i] = (char) (byteBuffer.get() & 0xff);
}
charFound = (char) byteBuffer.get();
if (charFound != ' ') {
// Invalid Message - missing mandatory space.
LOG.error("Invalid syslog message, missing a mandatory space after month");
}
charFound = (char) (byteBuffer.get() & 0xff);
int day = 0;
if (charFound == ' ') {
// Extra space for the day - this is okay.
// Just ignored per the spec.
} else {
day *= 10;
day += Character.digit(charFound, 10);
}
while (Character.isDigit(charFound = (char) (byteBuffer.get() & 0xff))) {
day *= 10;
day += Character.digit(charFound, 10);
}
int hour = 0;
while (Character.isDigit(charFound = (char) (byteBuffer.get() & 0xff))) {
hour *= 10;
hour += Character.digit(charFound, 10);
}
int minute = 0;
while (Character.isDigit(charFound = (char) (byteBuffer.get() & 0xff))) {
minute *= 10;
minute += Character.digit(charFound, 10);
}
int second = 0;
while (Character.isDigit(charFound = (char) (byteBuffer.get() & 0xff))) {
second *= 10;
second += Character.digit(charFound, 10);
}
Calendar calendar = new GregorianCalendar();
calendar.set(Calendar.MONTH, monthValueMap.get(String.valueOf(month).toLowerCase()).ordinal());
calendar.set(Calendar.DAY_OF_MONTH, day);
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, second);
return calendar;
}
}