/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is part of dcm4che, an implementation of DICOM(TM) in * Java(TM), hosted at https://github.com/gunterze/dcm4che. * * The Initial Developer of the Original Code is * Agfa Healthcare. * Portions created by the Initial Developer are Copyright (C) 2013 * the Initial Developer. All Rights Reserved. * * Contributor(s): * See @authors listed below * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package org.dcm4che3.net.audit; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.Socket; import java.net.SocketTimeoutException; import org.dcm4che3.net.Connection; import org.dcm4che3.net.TCPProtocolHandler; import org.dcm4che3.net.UDPProtocolHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Gunter Zeilinger <gunterze@gmail.com> * */ enum SyslogProtocolHandler implements TCPProtocolHandler, UDPProtocolHandler { INSTANCE; private static final int INIT_MSG_LEN = 8192; private static final int MAX_MSG_LEN = 65536; private static final int MAX_MSG_PREFIX = 200; private static final int MSG_PROMPT_LEN = 8192; private static Logger LOG = LoggerFactory.getLogger(SyslogProtocolHandler.class); @Override public void onAccept(Connection conn, Socket s) throws IOException { InputStream in = s.getInputStream(); byte[] data = new byte[INIT_MSG_LEN]; int length; s.setSoTimeout(conn.getIdleTimeout()); while ((length = readMessageLength(in, s)) > 0) { if (length > MAX_MSG_LEN) { LOG.warn("Message length: {} received from {} exceeds limit {}", length, s, MAX_MSG_LEN); break; } if (length > data.length) data = new byte[length]; if (readMessage(in, data, length) < length) { LOG.warn("Connection closed by remote host {} during receive of message", s); break; } LOG.info("Received Syslog message of {} bytes from {}", length, s); onMessage(data, 0, length, conn, s.getInetAddress()); } conn.close(s); } private int readMessageLength(InputStream in, Socket s) throws IOException { int ch; try { ch = in.read(); } catch (SocketTimeoutException e) { LOG.info("Timeout expired for connection to {}", s); return -1; } if (ch < 0) return -1; int d, len = 0; do { d = ch - '0'; if (d < 0 || d > 9) { LOG.warn("Illegal character code: {} in message length received from {}", ch, s); return -1; } len = (len << 3) + (len << 1) + d; // 10 * len + d ch = in.read(); } while (ch > 0 && ch != ' '); return len; } private int readMessage(InputStream in, byte[] data, int len) throws IOException { int count, n = 0; while (n < len && (count = in.read(data, n, len - n)) > 0) { n += count; } return n; } @Override public void onReceive(Connection conn, DatagramPacket packet) { LOG.info("Received UDP Syslog message of {} bytes from {}", packet.getLength(), packet.getAddress()); onMessage(packet.getData(), packet.getOffset(), packet.getLength(), conn, packet.getAddress()); } private void onMessage(byte[] data, int offset, int length, Connection conn, InetAddress from) { AuditRecordRepository arr = conn.getDevice() .getDeviceExtension(AuditRecordRepository.class); if (LOG.isDebugEnabled()) { LOG.debug(prompt(data, MSG_PROMPT_LEN)); } int xmlOffset = indexOfXML(data, offset, Math.min(MAX_MSG_PREFIX, length)); if (xmlOffset != -1) { int xmlLength = length - xmlOffset + offset; arr.onMessage(data, xmlOffset, xmlLength, conn, from); } else { LOG.warn("Ignore unexpected message from {}: {}", from, prompt(data, MAX_MSG_PREFIX)); } } private static int indexOfXML(byte[] buf, int offset, int maxIndex) { for(int index = offset, xmlDeclIndex = -1; index <= maxIndex; index++) { if (buf[index] != '<') continue; if (isAuditMessage(buf, index) || isIHEYr4(buf, index)) return xmlDeclIndex == -1 ? index : xmlDeclIndex; else if (xmlDeclIndex == -1 && isXMLDecl(buf, index)) xmlDeclIndex = index; } return -1; } private static boolean isXMLDecl(byte[] buf, int index) { return index + 4 < buf.length && buf[index+1] == '?' && buf[index+2] == 'x' && buf[index+3] == 'm' && buf[index+4] == 'l'; } private static boolean isAuditMessage(byte[] buf, int index) { return index + 12 < buf.length && buf[index+1] == 'A' && buf[index+2] == 'u' && buf[index+3] == 'd' && buf[index+4] == 'i' && buf[index+5] == 't' && buf[index+6] == 'M' && buf[index+7] == 'e' && buf[index+8] == 's' && buf[index+9] == 's' && buf[index+10] == 'a' && buf[index+11] == 'g' && buf[index+12] == 'e'; } private static boolean isIHEYr4(byte[] buf, int index) { return index + 6 < buf.length && buf[index+1] == 'I' && buf[index+2] == 'H' && buf[index+3] == 'E' && buf[index+4] == 'Y' && buf[index+5] == 'r' && buf[index+6] == '4'; } private static String prompt(byte[] data, int maxLen) { try { return data.length > maxLen ? (new String(data, 0, maxLen, "UTF-8") + "...") : new String(data, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } }