001/* ***** BEGIN LICENSE BLOCK ***** 002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 003 * 004 * The contents of this file are subject to the Mozilla Public License Version 005 * 1.1 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * http://www.mozilla.org/MPL/ 008 * 009 * Software distributed under the License is distributed on an "AS IS" basis, 010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 011 * for the specific language governing rights and limitations under the 012 * License. 013 * 014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 015 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 016 * 017 * The Initial Developer of the Original Code is 018 * Agfa Healthcare. 019 * Portions created by the Initial Developer are Copyright (C) 2013 020 * the Initial Developer. All Rights Reserved. 021 * 022 * Contributor(s): 023 * See @authors listed below 024 * 025 * Alternatively, the contents of this file may be used under the terms of 026 * either the GNU General Public License Version 2 or later (the "GPL"), or 027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 028 * in which case the provisions of the GPL or the LGPL are applicable instead 029 * of those above. If you wish to allow use of your version of this file only 030 * under the terms of either the GPL or the LGPL, and not to allow others to 031 * use your version of this file under the terms of the MPL, indicate your 032 * decision by deleting the provisions above and replace them with the notice 033 * and other provisions required by the GPL or the LGPL. If you do not delete 034 * the provisions above, a recipient may use your version of this file under 035 * the terms of any one of the MPL, the GPL or the LGPL. 036 * 037 * ***** END LICENSE BLOCK ***** */ 038 039package org.dcm4che3.net.audit; 040 041import java.io.IOException; 042import java.io.InputStream; 043import java.io.UnsupportedEncodingException; 044import java.net.DatagramPacket; 045import java.net.InetAddress; 046import java.net.Socket; 047import java.net.SocketTimeoutException; 048 049import org.dcm4che3.net.Connection; 050import org.dcm4che3.net.TCPProtocolHandler; 051import org.dcm4che3.net.UDPProtocolHandler; 052import org.slf4j.Logger; 053import org.slf4j.LoggerFactory; 054 055/** 056 * @author Gunter Zeilinger <gunterze@gmail.com> 057 * 058 */ 059enum SyslogProtocolHandler implements TCPProtocolHandler, UDPProtocolHandler { 060 INSTANCE; 061 062 private static final int INIT_MSG_LEN = 8192; 063 private static final int MAX_MSG_LEN = 65536; 064 private static final int MAX_MSG_PREFIX = 200; 065 private static final int MSG_PROMPT_LEN = 8192; 066 067 private static Logger LOG = LoggerFactory.getLogger(SyslogProtocolHandler.class); 068 069 @Override 070 public void onAccept(Connection conn, Socket s) throws IOException { 071 InputStream in = s.getInputStream(); 072 byte[] data = new byte[INIT_MSG_LEN]; 073 int length; 074 s.setSoTimeout(conn.getIdleTimeout()); 075 while ((length = readMessageLength(in, s)) > 0) { 076 if (length > MAX_MSG_LEN) { 077 LOG.warn("Message length: {} received from {} exceeds limit {}", 078 length, s, MAX_MSG_LEN); 079 break; 080 } 081 if (length > data.length) 082 data = new byte[length]; 083 084 if (readMessage(in, data, length) < length) { 085 LOG.warn("Connection closed by remote host {} during receive of message", 086 s); 087 break; 088 } 089 LOG.info("Received Syslog message of {} bytes from {}", 090 length, s); 091 onMessage(data, 0, length, conn, s.getInetAddress()); 092 } 093 conn.close(s); 094 } 095 096 private int readMessageLength(InputStream in, Socket s) throws IOException { 097 int ch; 098 try { 099 ch = in.read(); 100 } catch (SocketTimeoutException e) { 101 LOG.info("Timeout expired for connection to {}", s); 102 return -1; 103 } 104 if (ch < 0) 105 return -1; 106 107 int d, len = 0; 108 do { 109 d = ch - '0'; 110 if (d < 0 || d > 9) { 111 LOG.warn("Illegal character code: {} in message length received from {}", 112 ch, s); 113 return -1; 114 } 115 len = (len << 3) + (len << 1) + d; // 10 * len + d 116 ch = in.read(); 117 } while (ch > 0 && ch != ' '); 118 return len; 119 } 120 121 private int readMessage(InputStream in, byte[] data, int len) throws IOException { 122 int count, n = 0; 123 while (n < len && (count = in.read(data, n, len - n)) > 0) { 124 n += count; 125 } 126 return n; 127 } 128 129 @Override 130 public void onReceive(Connection conn, DatagramPacket packet) { 131 LOG.info("Received UDP Syslog message of {} bytes from {}", 132 packet.getLength(), packet.getAddress()); 133 onMessage(packet.getData(), packet.getOffset(), packet.getLength(), 134 conn, packet.getAddress()); 135 } 136 137 private void onMessage(byte[] data, int offset, int length, 138 Connection conn, InetAddress from) { 139 AuditRecordRepository arr = conn.getDevice() 140 .getDeviceExtension(AuditRecordRepository.class); 141 if (LOG.isDebugEnabled()) { 142 LOG.debug(prompt(data, MSG_PROMPT_LEN)); 143 } 144 int xmlOffset = indexOfXML(data, offset, Math.min(MAX_MSG_PREFIX, length)); 145 if (xmlOffset != -1) { 146 int xmlLength = length - xmlOffset + offset; 147 arr.onMessage(data, xmlOffset, xmlLength, conn, from); 148 } else { 149 LOG.warn("Ignore unexpected message from {}: {}", from, 150 prompt(data, MAX_MSG_PREFIX)); 151 } 152 } 153 154 private static int indexOfXML(byte[] buf, int offset, int maxIndex) { 155 for(int index = offset, xmlDeclIndex = -1; index <= maxIndex; index++) { 156 if (buf[index] != '<') 157 continue; 158 if (isAuditMessage(buf, index) || isIHEYr4(buf, index)) 159 return xmlDeclIndex == -1 ? index : xmlDeclIndex; 160 else if (xmlDeclIndex == -1 && isXMLDecl(buf, index)) 161 xmlDeclIndex = index; 162 } 163 return -1; 164 } 165 166 private static boolean isXMLDecl(byte[] buf, int index) { 167 return index + 4 < buf.length 168 && buf[index+1] == '?' 169 && buf[index+2] == 'x' 170 && buf[index+3] == 'm' 171 && buf[index+4] == 'l'; 172 } 173 174 private static boolean isAuditMessage(byte[] buf, int index) { 175 return index + 12 < buf.length 176 && buf[index+1] == 'A' 177 && buf[index+2] == 'u' 178 && buf[index+3] == 'd' 179 && buf[index+4] == 'i' 180 && buf[index+5] == 't' 181 && buf[index+6] == 'M' 182 && buf[index+7] == 'e' 183 && buf[index+8] == 's' 184 && buf[index+9] == 's' 185 && buf[index+10] == 'a' 186 && buf[index+11] == 'g' 187 && buf[index+12] == 'e'; 188 } 189 190 private static boolean isIHEYr4(byte[] buf, int index) { 191 return index + 6 < buf.length 192 && buf[index+1] == 'I' 193 && buf[index+2] == 'H' 194 && buf[index+3] == 'E' 195 && buf[index+4] == 'Y' 196 && buf[index+5] == 'r' 197 && buf[index+6] == '4'; 198 } 199 200 private static String prompt(byte[] data, int maxLen) { 201 try { 202 return data.length > maxLen 203 ? (new String(data, 0, maxLen, "UTF-8") + "...") 204 : new String(data, "UTF-8"); 205 } catch (UnsupportedEncodingException e) { 206 throw new RuntimeException(e); 207 } 208 } 209}