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;
040
041import java.io.IOException;
042import java.net.ServerSocket;
043import java.net.Socket;
044import java.net.SocketAddress;
045import java.security.GeneralSecurityException;
046
047import javax.net.ssl.SSLContext;
048import javax.net.ssl.SSLServerSocket;
049import javax.net.ssl.SSLServerSocketFactory;
050import javax.net.ssl.SSLSocket;
051
052/**
053 * @author Gunter Zeilinger <gunterze@gmail.com>
054 *
055 */
056class TCPListener implements Listener {
057
058    private final Connection conn;
059    private final TCPProtocolHandler handler;
060    private final ServerSocket ss;
061
062    public TCPListener(Connection conn, TCPProtocolHandler handler)
063            throws IOException, GeneralSecurityException {
064        try {
065        
066            this.conn = conn;
067            this.handler = handler;
068            ss = conn.isTls() ? createTLSServerSocket(conn) : new ServerSocket();
069            conn.setReceiveBufferSize(ss);
070            ss.bind(conn.getBindPoint(), conn.getBacklog());
071            conn.getDevice().execute(new Runnable(){
072    
073                @Override
074                public void run() { listen(); }
075            });
076        
077        } catch (IOException e) {
078            throw new IOException("Unable to start TCPListener on "+conn.getHostname()+":"+conn.getPort(), e);
079        }
080    }
081
082    private ServerSocket createTLSServerSocket(Connection conn)
083            throws IOException, GeneralSecurityException {
084        SSLContext sslContext = conn.getDevice().sslContext();
085        SSLServerSocketFactory ssf = sslContext.getServerSocketFactory();
086        SSLServerSocket ss = (SSLServerSocket) ssf.createServerSocket();
087        ss.setEnabledProtocols(conn.tlsProtocols());
088        ss.setEnabledCipherSuites(conn.getTlsCipherSuites());
089        ss.setNeedClientAuth(conn.isTlsNeedClientAuth());
090        return ss;
091    }
092
093    private void listen() {
094        SocketAddress sockAddr = ss.getLocalSocketAddress();
095        Connection.LOG.info("Start TCP Listener on {}", sockAddr);
096        try {
097            while (!ss.isClosed()) {
098                Connection.LOG.debug("Wait for connection on {}", sockAddr);
099                Socket s = ss.accept();
100                ConnectionMonitor monitor = conn.getDevice() != null
101                        ? conn.getDevice().getConnectionMonitor()
102                        : null;
103                if (conn.isBlackListed(s.getInetAddress())) {
104                    if (monitor != null)
105                        monitor.onConnectionRejectedBlacklisted(conn, s);
106                    Connection.LOG.info("Reject blacklisted connection {}", s);
107                    conn.close(s);
108                } else {
109                    try {
110                        conn.setSocketSendOptions(s);
111                        if (s instanceof SSLSocket) {
112                            ((SSLSocket) s).startHandshake();
113                        }
114                    } catch (Throwable e) {
115                        if (monitor != null)
116                            monitor.onConnectionRejected(conn, s, e);
117                        Connection.LOG.warn("Reject connection {}:",s, e);
118                        conn.close(s);
119                        continue;
120                    }
121
122                    if (monitor != null)
123                        monitor.onConnectionAccepted(conn, s);
124                    Connection.LOG.info("Accept connection {}", s);
125                    try {
126                        handler.onAccept(conn, s);
127                    } catch (Throwable e) {
128                        Connection.LOG.warn("Exception on accepted connection {}:",s, e);
129                        conn.close(s);
130                    }
131                }
132            }
133        } catch (Throwable e) {
134            if (!ss.isClosed()) // ignore exception caused by close()
135                Connection.LOG.error("Exception on listing on {}:", sockAddr, e);
136        }
137        Connection.LOG.info("Stop TCP Listener on {}", sockAddr);
138    }
139
140
141    @Override
142    public SocketAddress getEndPoint() {
143        return ss.getLocalSocketAddress();
144    }
145
146    @Override
147    public void close() throws IOException {
148         try {
149            ss.close();
150        } catch (Throwable e) {
151            // Ignore errors when closing the server socket.
152        }
153    }
154}