View Javadoc
1   /*
2    * Copyright 2018 the original author or authors.
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.openehealth.ipf.commons.audit.protocol;
17  
18  
19  import org.openehealth.ipf.commons.audit.AuditContext;
20  import org.openehealth.ipf.commons.audit.AuditException;
21  import org.openehealth.ipf.commons.audit.utils.AuditUtils;
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  
25  import javax.net.ssl.SSLSocketFactory;
26  import java.io.IOException;
27  import java.io.OutputStream;
28  import java.net.Socket;
29  import java.net.SocketException;
30  import java.nio.charset.StandardCharsets;
31  import java.util.concurrent.atomic.AtomicReference;
32  
33  /**
34   * Simple client implementation of RFC 5425 TLS syslog transport
35   * for sending audit messages to an Audit Record Repository
36   * that implements TLS syslog.
37   * Multiple messages may be sent over the same socket.
38   * <p>
39   * Designed to run in a standalone mode
40   * and is not dependent on any context or configuration.
41   * <p>
42   * <p>
43   * Note that this implementation disobeys the ATNA specification saying,
44   * that the Secure Application, Secure Node, or Audit Record Forwarder is unable to send the
45   * message to the Audit Record Repository, then the actor shall store the audit record
46   * locally and send it when it is able.
47   * </p>
48   *
49   * @author Lawrence Tarbox, Derived from code written by Matthew Davis of IBM.
50   * @author Christian Ohr
51   * @since 3.5
52   */
53  public class TLSSyslogSenderImpl extends RFC5424Protocol implements AuditTransmissionProtocol {
54  
55      private static final Logger LOG = LoggerFactory.getLogger(TLSSyslogSenderImpl.class);
56      private AtomicReference<Socket> socket = new AtomicReference<>();
57  
58      public TLSSyslogSenderImpl() {
59          this(AuditUtils.getLocalHostName(), AuditUtils.getProcessId());
60      }
61  
62      public TLSSyslogSenderImpl(String sendingHost, String sendingProcess) {
63          super(sendingHost, sendingProcess);
64      }
65  
66      @Override
67      public String getTransportName() {
68          return "TLS";
69      }
70  
71      private Socket getSocket(AuditContext auditContext) {
72          if (socket.get() == null) socket.compareAndSet(null, getTLSSocket(auditContext));
73          return socket.get();
74      }
75  
76      @Override
77      public void send(AuditContext auditContext, String... auditMessages) throws Exception {
78          if (auditMessages != null) {
79              for (String auditMessage : auditMessages) {
80                  byte[] msgBytes = getTransportPayload(auditContext.getSendingApplication(), auditMessage);
81                  byte[] syslogFrame = String.format("%d ", msgBytes.length).getBytes();
82                  LOG.debug("Auditing to {}:{}",
83                          auditContext.getAuditRepositoryAddress().getHostAddress(),
84                          auditContext.getAuditRepositoryPort());
85                  LOG.trace("{}", new String(msgBytes, StandardCharsets.UTF_8));
86                  try {
87                      doSend(auditContext, syslogFrame, msgBytes);
88                  } catch (SocketException e) {
89                      try {
90                          LOG.info("Failed to use existing TLS socket. Will create a new connection and retry.");
91                          socket.set(null);
92                          doSend(auditContext, syslogFrame, msgBytes);
93                      } catch (Exception exception) {
94                          LOG.error("Failed to audit using new TLS socket, giving up - this audit message will be lost.");
95                          socket.set(null);
96                          // rethrow the exception so caller knows what happened
97                          throw exception;
98                      }
99                  }
100             }
101         }
102     }
103 
104     @Override
105     public void shutdown() {
106         if (socket.get() != null) {
107             try {
108                 // TODO could wait until everything is sent
109                 socket.get().close();
110             } catch (IOException ignored) {
111             }
112         }
113     }
114 
115     private synchronized void doSend(AuditContext auditContext, byte[] syslogFrame, byte[] msgBytes) throws IOException {
116         Socket socket = getSocket(auditContext);
117         OutputStream out = socket.getOutputStream();
118         out.write(syslogFrame);
119         out.write(msgBytes);
120         out.flush();
121     }
122 
123     private Socket getTLSSocket(AuditContext auditContext) {
124         try {
125             return SSLSocketFactory.getDefault()
126                     .createSocket(auditContext.getAuditRepositoryAddress(), auditContext.getAuditRepositoryPort());
127         } catch (IOException e) {
128             throw new AuditException(String.format("Could not establish TLS connection to %s:%d",
129                     auditContext.getAuditRepositoryAddress().getHostAddress(),
130                     auditContext.getAuditRepositoryPort()), e);
131         }
132     }
133 
134 
135 }