/* * * Copyright (c) 2013 - 2017 Lijun Liao * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License version 3 * as published by the Free Software Foundation with the addition of the * following permission added to Section 15 as permitted in Section 7(a): * * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY * THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT * OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License. * * You can be released from the requirements of the license by purchasing * a commercial license. Buying such a license is mandatory as soon as you * develop commercial activities involving the XiPKI software without * disclosing the source code of your own applications. * * For more information, please contact Lijun Liao at this * address: lijun.liao@gmail.com */ package org.xipki.commons.audit.syslog.impl; import java.io.CharArrayWriter; import java.io.IOException; import java.util.Date; import java.util.List; import java.util.Objects; import org.eclipse.jdt.annotation.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xipki.commons.audit.AuditEvent; import org.xipki.commons.audit.AuditEventData; import org.xipki.commons.audit.AuditLevel; import org.xipki.commons.audit.AuditService; import org.xipki.commons.audit.AuditStatus; import org.xipki.commons.audit.PciAuditEvent; import com.cloudbees.syslog.Facility; import com.cloudbees.syslog.MessageFormat; import com.cloudbees.syslog.Severity; import com.cloudbees.syslog.SyslogMessage; import com.cloudbees.syslog.sender.AbstractSyslogMessageSender; import com.cloudbees.syslog.sender.TcpSyslogMessageSender; import com.cloudbees.syslog.sender.UdpSyslogMessageSender; /** * @author Lijun Liao * @since 2.0.0 */ public class SyslogAuditServiceImpl extends AuditService { /** * The default port is 514. */ public static final int DFLT_SYSLOG_PORT = 514; /** * The default mode is TCP. */ public static final String DFLT_SYSLOG_PROTOCOL = "tcp"; /** * The default facility is USER. */ public static final String DFLT_SYSLOG_FACILITY = "user"; /** * The default IP is localhost. */ public static final String DFLT_SYSLOG_HOST = "localhost"; /** * The default message format is rfc_5424. */ public static final String DFLT_MESSAGE_FORMAT = "rfc_5424"; private static final Logger LOG = LoggerFactory.getLogger(SyslogAuditServiceImpl.class); /** * The syslog client instance. */ protected AbstractSyslogMessageSender syslog; private String host = DFLT_SYSLOG_HOST; private int port = DFLT_SYSLOG_PORT; private String protocol = DFLT_SYSLOG_PROTOCOL; private String facility = DFLT_SYSLOG_FACILITY; private String messageFormat = DFLT_MESSAGE_FORMAT; private int maxMessageLength = 1024; private int writeRetries; private String localname; private String prefix; private boolean ssl; private boolean initialized; public SyslogAuditServiceImpl() { } @Override public void doLogEvent(@NonNull final AuditEvent event) { if (!initialized) { LOG.error("syslog audit not initialized"); return; } CharArrayWriter sb = new CharArrayWriter(150); if (notEmpty(prefix)) { sb.append(prefix); } AuditStatus status = event.getStatus(); if (status == null) { status = AuditStatus.UNDEFINED; } sb.append("\tstatus: ").append(status.name()); long duration = event.getDuration(); if (duration >= 0) { sb.append("\tduration: ").append(Long.toString(duration)); } List<AuditEventData> eventDataArray = event.getEventDatas(); for (AuditEventData m : eventDataArray) { if (duration >= 0 && "duration".equalsIgnoreCase(m.getName())) { continue; } sb.append("\t").append(m.getName()).append(": ").append(m.getValue()); } final int n = sb.size(); if (n > maxMessageLength) { LOG.warn("syslog message exceeds the maximal allowed length: {} > {}, ignore it", n, maxMessageLength); return; } SyslogMessage sm = new SyslogMessage(); sm.setFacility(syslog.getDefaultFacility()); if (notEmpty(localname)) { sm.setHostname(localname); } sm.setAppName(event.getApplicationName()); sm.setSeverity(getSeverity(event.getLevel())); Date timestamp = event.getTimestamp(); if (timestamp != null) { sm.setTimestamp(timestamp); } sm.setMsgId(event.getName()); sm.setMsg(sb); try { syslog.sendMessage(sm); } catch (IOException ex) { LOG.error("could not send syslog message: {}", ex.getMessage()); LOG.debug("could not send syslog message", ex); } } // method logEvent(AuditEvent) @Override public void doLogEvent(@NonNull final PciAuditEvent event) { if (!initialized) { LOG.error("syslog audit not initialiazed"); return; } CharArrayWriter msg = event.toCharArrayWriter(prefix); final int n = msg.size(); if (n > maxMessageLength) { LOG.warn("syslog message exceeds the maximal allowed length: {} > {}, ignore it", n, maxMessageLength); return; } SyslogMessage sm = new SyslogMessage(); sm.setFacility(syslog.getDefaultFacility()); if (notEmpty(localname)) { sm.setHostname(localname); } sm.setSeverity(getSeverity(event.getLevel())); sm.setMsg(msg); try { syslog.sendMessage(sm); } catch (IOException ex) { LOG.error("could not send syslog message: {}", ex.getMessage()); LOG.debug("could not send syslog message", ex); } } // method logEvent(PCIAuditEvent) public void init() { if (initialized) { return; } LOG.info("initializing: {}", SyslogAuditServiceImpl.class); MessageFormat msgFormat; if ("rfc3164".equalsIgnoreCase(messageFormat) || "rfc_3164".equalsIgnoreCase(messageFormat)) { msgFormat = MessageFormat.RFC_3164; } else if ("rfc5424".equalsIgnoreCase(messageFormat) || "rfc_5424".equalsIgnoreCase(messageFormat)) { msgFormat = MessageFormat.RFC_5424; } else { LOG.warn("invalid message format '{}', use the default one '{}'", messageFormat, DFLT_MESSAGE_FORMAT); msgFormat = MessageFormat.RFC_5424; } if ("udp".equalsIgnoreCase(protocol)) { UdpSyslogMessageSender lcSyslog = new UdpSyslogMessageSender(); syslog = lcSyslog; lcSyslog.setSyslogServerPort(port); } else if ("tcp".equalsIgnoreCase(protocol)) { TcpSyslogMessageSender lcSyslog = new TcpSyslogMessageSender(); syslog = lcSyslog; lcSyslog.setSyslogServerPort(port); lcSyslog.setSsl(ssl); if (writeRetries > 0) { lcSyslog.setMaxRetryCount(writeRetries); } } else { LOG.warn("unknown protocol '{}', use the default one 'udp'", this.protocol); UdpSyslogMessageSender lcSyslog = new UdpSyslogMessageSender(); syslog = lcSyslog; lcSyslog.setSyslogServerPort(port); } syslog.setDefaultMessageHostname(host); syslog.setMessageFormat(msgFormat); Facility sysFacility = null; if (notEmpty(facility)) { sysFacility = Facility.fromLabel(facility.toUpperCase()); } if (sysFacility == null) { LOG.warn("unknown facility, use the default one '{}'", DFLT_SYSLOG_FACILITY); sysFacility = Facility.fromLabel(DFLT_SYSLOG_FACILITY.toUpperCase()); } if (sysFacility == null) { throw new RuntimeException("should not reach here, sysFacility is null"); } syslog.setDefaultFacility(sysFacility); // after we're finished set initialized to true this.initialized = true; LOG.info("initialized: {}", SyslogAuditServiceImpl.class); } // method init public void destroy() { LOG.info("destroying: {}", SyslogAuditServiceImpl.class); LOG.info("destroyed: {}", SyslogAuditServiceImpl.class); } public void setFacility(final String facility) { this.facility = facility; } public void setHost(final String host) { this.host = Objects.requireNonNull(host, "host must not be null"); } public void setPort(final int port) { this.port = port; } public void setProtocol(final String protocol) { this.protocol = Objects.requireNonNull(protocol, "protocol must not be null"); } public void setLocalname(final String localname) { this.localname = localname; } public void setMessageFormat(final String messageFormat) { this.messageFormat = Objects.requireNonNull(messageFormat, "messageFormat must not be null"); } public void setWriteRetries(final int writeRetries) { this.writeRetries = writeRetries; } public void setPrefix(final String prefix) { if (notEmpty(prefix)) { if (prefix.charAt(prefix.length() - 1) != ' ') { this.prefix = prefix + " "; } } else { this.prefix = null; } } public void setMaxMessageLength(final int maxMessageLength) { this.maxMessageLength = (maxMessageLength <= 0) ? 1023 : maxMessageLength; } public void setSsl(final boolean ssl) { this.ssl = ssl; } private static boolean notEmpty(final String text) { return text != null && !text.isEmpty(); } private static Severity getSeverity(final AuditLevel auditLevel) { if (auditLevel == null) { return Severity.INFORMATIONAL; } switch (auditLevel) { case DEBUG: return Severity.DEBUG; case INFO: return Severity.INFORMATIONAL; case WARN: return Severity.WARNING; case ERROR: return Severity.ERROR; default: throw new RuntimeException(String.format("unknown auditLevel '%s'", auditLevel)); } } }