View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2009  Mickael Guessant
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18   */
19  package davmail;
20  
21  import davmail.exception.DavMailException;
22  import davmail.ui.tray.DavGatewayTray;
23  
24  import javax.net.ServerSocketFactory;
25  import javax.net.ssl.*;
26  import java.io.FileInputStream;
27  import java.io.IOException;
28  import java.net.InetAddress;
29  import java.net.ServerSocket;
30  import java.net.Socket;
31  import java.security.*;
32  import java.security.cert.CertificateException;
33  import java.util.HashSet;
34  
35  /**
36   * Generic abstract server common to SMTP and POP3 implementations
37   */
38  public abstract class AbstractServer extends Thread {
39      protected boolean nosslFlag; // will cause same behavior as before with unchanged config files
40      private final int port;
41      private ServerSocket serverSocket;
42  
43      /**
44       * Get server protocol name (SMTP, POP, IMAP, ...).
45       *
46       * @return server protocol name
47       */
48      public abstract String getProtocolName();
49  
50      /**
51       * Server socket TCP port
52       *
53       * @return port
54       */
55      public int getPort() {
56          return port;
57      }
58  
59      /**
60       * Create a ServerSocket to listen for connections.
61       * Start the thread.
62       *
63       * @param name        thread name
64       * @param port        tcp socket chosen port
65       * @param defaultPort tcp socket default port
66       */
67      protected AbstractServer(String name, int port, int defaultPort) {
68          super(name);
69          setDaemon(true);
70          if (port == 0) {
71              this.port = defaultPort;
72          } else {
73              this.port = port;
74          }
75      }
76  
77      /**
78       * Bind server socket on defined port.
79       *
80       * @throws DavMailException unable to create server socket
81       */
82      public void bind() throws DavMailException {
83          String bindAddress = Settings.getProperty("davmail.bindAddress");
84          String keystoreFile = Settings.getProperty("davmail.ssl.keystoreFile");
85  
86          ServerSocketFactory serverSocketFactory;
87          if (keystoreFile == null || keystoreFile.isEmpty() || nosslFlag) {
88              serverSocketFactory = ServerSocketFactory.getDefault();
89          } else {
90              try {
91  
92                  // SSLContext is environment for implementing JSSE...
93                  // create ServerSocketFactory
94                  SSLContext sslContext = SSLContext.getInstance("TLS");
95  
96                  // initialize sslContext to work with key managers
97                  sslContext.init(getKeyManagers(), getTrustManagers(), null);
98  
99                  // create ServerSocketFactory from sslContext
100                 serverSocketFactory = sslContext.getServerSocketFactory();
101             } catch (IOException | GeneralSecurityException ex) {
102                 throw new DavMailException("LOG_EXCEPTION_CREATING_SSL_SERVER_SOCKET", getProtocolName(), port, ex.getMessage() == null ? ex.toString() : ex.getMessage());
103             }
104         }
105         try {
106             // create the server socket
107             if (bindAddress == null || bindAddress.isEmpty()) {
108                 serverSocket = serverSocketFactory.createServerSocket(port);
109             } else {
110                 serverSocket = serverSocketFactory.createServerSocket(port, 0, InetAddress.getByName(bindAddress));
111             }
112             if (serverSocket instanceof SSLServerSocket) {
113                 // CVE-2014-3566 disable SSLv3
114                 HashSet<String> protocols = new HashSet<>();
115                 for (String protocol : ((SSLServerSocket) serverSocket).getEnabledProtocols()) {
116                     if (!protocol.startsWith("SSL")) {
117                         protocols.add(protocol);
118                     }
119                 }
120                 ((SSLServerSocket) serverSocket).setEnabledProtocols(protocols.toArray(new String[0]));
121                 ((SSLServerSocket) serverSocket).setNeedClientAuth(Settings.getBooleanProperty("davmail.ssl.needClientAuth", false));
122             }
123 
124         } catch (IOException e) {
125             throw new DavMailException("LOG_SOCKET_BIND_FAILED", getProtocolName(), port);
126         }
127     }
128 
129     /**
130      * Build trust managers from truststore file.
131      *
132      * @return trust managers
133      * @throws CertificateException     on error
134      * @throws NoSuchAlgorithmException on error
135      * @throws IOException              on error
136      * @throws KeyStoreException        on error
137      */
138     protected TrustManager[] getTrustManagers() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException {
139         String truststoreFile = Settings.getProperty("davmail.ssl.truststoreFile");
140         if (truststoreFile == null || truststoreFile.isEmpty()) {
141             return null;
142         }
143         try (FileInputStream trustStoreInputStream = new FileInputStream(truststoreFile)) {
144             KeyStore trustStore = KeyStore.getInstance(Settings.getProperty("davmail.ssl.truststoreType"));
145             trustStore.load(trustStoreInputStream, Settings.getCharArrayProperty("davmail.ssl.truststorePass"));
146 
147             TrustManagerFactory tmf = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
148             tmf.init(trustStore);
149             return tmf.getTrustManagers();
150         }
151     }
152 
153     /**
154      * Build key managers from keystore file.
155      *
156      * @return key managers
157      * @throws CertificateException     on error
158      * @throws NoSuchAlgorithmException on error
159      * @throws IOException              on error
160      * @throws KeyStoreException        on error
161      */
162     protected KeyManager[] getKeyManagers() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, UnrecoverableKeyException {
163         String keystoreFile = Settings.getProperty("davmail.ssl.keystoreFile");
164         if (keystoreFile == null || keystoreFile.isEmpty()) {
165             return null;
166         }
167         try (FileInputStream keyStoreInputStream = new FileInputStream(keystoreFile)) {
168             KeyStore keystore = KeyStore.getInstance(Settings.getProperty("davmail.ssl.keystoreType"));
169             keystore.load(keyStoreInputStream, Settings.getCharArrayProperty("davmail.ssl.keystorePass"));
170 
171             KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
172             kmf.init(keystore, Settings.getCharArrayProperty("davmail.ssl.keyPass"));
173             return kmf.getKeyManagers();
174         }
175     }
176 
177     /**
178      * The body of the server thread.  Loop forever, listening for and
179      * accepting connections from clients.  For each connection,
180      * create a Connection object to handle communication through the
181      * new Socket.
182      */
183     @Override
184     public void run() {
185         AbstractConnection connection = null;
186         Socket clientSocket = null;
187         try {
188             while (!serverSocket.isClosed()) {
189                 clientSocket = serverSocket.accept();
190                 // set default timeout to 5 minutes
191                 clientSocket.setSoTimeout(Settings.getIntProperty("davmail.clientSoTimeout", 300) * 1000);
192                 DavGatewayTray.debug(new BundleMessage("LOG_CONNECTION_FROM", clientSocket.getInetAddress(), port));
193                 // only accept localhost connections for security reasons
194                 if (Settings.getBooleanProperty("davmail.allowRemote") ||
195                         clientSocket.getInetAddress().isLoopbackAddress() ||
196                         // OSX link local address on loopback interface
197                         clientSocket.getInetAddress().equals(InetAddress.getByAddress(new byte[]{(byte) 0xfe, (byte) 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
198                         )) {
199                     connection = createConnectionHandler(clientSocket);
200                     connection.start();
201 
202                 } else {
203                     DavGatewayTray.warn(new BundleMessage("LOG_EXTERNAL_CONNECTION_REFUSED"));
204                 }
205             }
206 
207         } catch (IOException e) {
208             // do not warn if exception on socket close (gateway restart)
209             if (!serverSocket.isClosed()) {
210                 DavGatewayTray.warn(new BundleMessage("LOG_EXCEPTION_LISTENING_FOR_CONNECTIONS"), e);
211             }
212         } finally {
213             try {
214                 if (clientSocket != null) {
215                     clientSocket.close();
216                 }
217             } catch (IOException e) {
218                 DavGatewayTray.warn(new BundleMessage("LOG_EXCEPTION_CLOSING_CLIENT_SOCKET"), e);
219             }
220             if (connection != null) {
221                 connection.close();
222             }
223         }
224 
225     }
226 
227     /**
228      * Create a connection handler for the current listener.
229      *
230      * @param clientSocket client socket
231      * @return connection handler
232      */
233     public abstract AbstractConnection createConnectionHandler(Socket clientSocket);
234 
235     /**
236      * Close server socket
237      */
238     public void close() {
239         try {
240             if (serverSocket != null) {
241                 serverSocket.close();
242             }
243         } catch (IOException e) {
244             DavGatewayTray.warn(new BundleMessage("LOG_EXCEPTION_CLOSING_SERVER_SOCKET"), e);
245         }
246     }
247 }