diff --git a/src/java/davmail/DavGateway.java b/src/java/davmail/DavGateway.java index fbd0a424..7709446a 100644 --- a/src/java/davmail/DavGateway.java +++ b/src/java/davmail/DavGateway.java @@ -1,5 +1,6 @@ package davmail; +import davmail.http.DavGatewaySSLProtocolSocketFactory; import davmail.pop.PopServer; import davmail.smtp.SmtpServer; import davmail.tray.DavGatewayTray; @@ -57,25 +58,27 @@ public class DavGateway { DavGatewayTray.error("Exception creating server socket", e); } + // register custom SSL Socket factory + DavGatewaySSLProtocolSocketFactory.register(); } - public static void stop() { - if (smtpServer != null) { - smtpServer.close(); - try { - smtpServer.join(); - } catch (InterruptedException e) { - DavGatewayTray.warn("Exception waiting for listener to die", e); - } - } - if (popServer != null) { - popServer.close(); - try { - popServer.join(); - } catch (InterruptedException e) { - DavGatewayTray.warn("Exception waiting for listener to die", e); - } - } - } + public static void stop() { + if (smtpServer != null) { + smtpServer.close(); + try { + smtpServer.join(); + } catch (InterruptedException e) { + DavGatewayTray.warn("Exception waiting for listener to die", e); + } + } + if (popServer != null) { + popServer.close(); + try { + popServer.join(); + } catch (InterruptedException e) { + DavGatewayTray.warn("Exception waiting for listener to die", e); + } + } + } } diff --git a/src/java/davmail/Settings.java b/src/java/davmail/Settings.java index 5ba8b43f..47dfd334 100644 --- a/src/java/davmail/Settings.java +++ b/src/java/davmail/Settings.java @@ -55,6 +55,7 @@ public class Settings { SETTINGS.put("davmail.proxyUser", ""); SETTINGS.put("davmail.proxyPassword", ""); SETTINGS.put("davmail.server", "false"); + SETTINGS.put("davmail.server.certificate.hash", ""); save(); } } catch (IOException e) { @@ -114,4 +115,11 @@ public class Settings { String propertyValue = SETTINGS.getProperty(property); return "true".equals(propertyValue); } + + public static synchronized void saveProperty(String property, String value) { + Settings.load(); + Settings.setProperty(property, value); + Settings.save(); + } + } diff --git a/src/java/davmail/SettingsFrame.java b/src/java/davmail/SettingsFrame.java index 388d9cef..89b21f97 100644 --- a/src/java/davmail/SettingsFrame.java +++ b/src/java/davmail/SettingsFrame.java @@ -9,6 +9,21 @@ import java.awt.*; * DavMail settings frame */ public class SettingsFrame extends JFrame { + protected JTextField urlField; + protected JTextField popPortField; + protected JTextField smtpPortField; + protected JTextField keepDelayField; + + JCheckBox enableProxyField; + JTextField httpProxyField; + JTextField httpProxyPortField; + JTextField httpProxyUserField; + JTextField httpProxyPasswordField; + + JCheckBox allowRemoteField; + JTextField bindAddressField; + JTextField certHashField; + protected void addSettingComponent(JPanel panel, String label, Component component) { JLabel fieldLabel = new JLabel(label); fieldLabel.setHorizontalAlignment(SwingConstants.RIGHT); @@ -16,22 +31,15 @@ public class SettingsFrame extends JFrame { panel.add(component); } - public SettingsFrame() { - setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - setTitle("DavMail Settings"); - - JTabbedPane tabbedPane = new JTabbedPane(); - - JPanel mainPanel = new JPanel(new GridLayout(2, 1)); - + protected JPanel getSettingsPanel() { JPanel settingsPanel = new JPanel(new GridLayout(4, 2)); settingsPanel.setBorder(BorderFactory.createTitledBorder("Gateway settings")); - final JTextField urlField = new JTextField(Settings.getProperty("davmail.url"), 15); + urlField = new JTextField(Settings.getProperty("davmail.url"), 15); urlField.setToolTipText("Base outlook web access URL"); - final JTextField popPortField = new JTextField(Settings.getProperty("davmail.popPort"), 4); - final JTextField smtpPortField = new JTextField(Settings.getProperty("davmail.smtpPort"), 4); - final JTextField keepDelayField = new JTextField(Settings.getProperty("davmail.keepDelay"), 4); + popPortField = new JTextField(Settings.getProperty("davmail.popPort"), 4); + smtpPortField = new JTextField(Settings.getProperty("davmail.smtpPort"), 4); + keepDelayField = new JTextField(Settings.getProperty("davmail.keepDelay"), 4); keepDelayField.setToolTipText("Number of days to keep messages in trash"); @@ -39,19 +47,20 @@ public class SettingsFrame extends JFrame { addSettingComponent(settingsPanel, "Local POP port: ", popPortField); addSettingComponent(settingsPanel, "Local SMTP port: ", smtpPortField); addSettingComponent(settingsPanel, "Keep Delay: ", keepDelayField); + return settingsPanel; + } - mainPanel.add(settingsPanel); - + protected JPanel getProxyPanel() { JPanel proxyPanel = new JPanel(new GridLayout(5, 2)); proxyPanel.setBorder(BorderFactory.createTitledBorder("Proxy settings")); boolean enableProxy = Settings.getBooleanProperty("davmail.enableProxy"); - final JCheckBox enableProxyField = new JCheckBox(); + enableProxyField = new JCheckBox(); enableProxyField.setSelected(enableProxy); - final JTextField httpProxyField = new JTextField(Settings.getProperty("davmail.proxyHost"), 15); - final JTextField httpProxyPortField = new JTextField(Settings.getProperty("davmail.proxyPort"), 4); - final JTextField httpProxyUserField = new JTextField(Settings.getProperty("davmail.proxyUser"), 4); - final JTextField httpProxyPasswordField = new JPasswordField(Settings.getProperty("davmail.proxyPassword"), 4); + httpProxyField = new JTextField(Settings.getProperty("davmail.proxyHost"), 15); + httpProxyPortField = new JTextField(Settings.getProperty("davmail.proxyPort"), 4); + httpProxyUserField = new JTextField(Settings.getProperty("davmail.proxyUser"), 4); + httpProxyPasswordField = new JPasswordField(Settings.getProperty("davmail.proxyPassword"), 4); httpProxyField.setEnabled(enableProxy); httpProxyPortField.setEnabled(enableProxy); @@ -73,28 +82,66 @@ public class SettingsFrame extends JFrame { addSettingComponent(proxyPanel, "Proxy port: ", httpProxyPortField); addSettingComponent(proxyPanel, "Proxy user: ", httpProxyUserField); addSettingComponent(proxyPanel, "Proxy password: ", httpProxyPasswordField); + return proxyPanel; + } - mainPanel.add(proxyPanel); + public JPanel getNetworkSettingsPanel() { + JPanel networkSettingsPanel = new JPanel(new GridLayout(3, 2)); + networkSettingsPanel.setBorder(BorderFactory.createTitledBorder("Network settings")); + + allowRemoteField = new JCheckBox(); + allowRemoteField.setSelected(Settings.getBooleanProperty("davmail.allowRemote")); + allowRemoteField.setToolTipText("Allow remote connections to the gateway (server mode)"); + + bindAddressField = new JTextField(Settings.getProperty("davmail.bindAddress"), 15); + bindAddressField.setToolTipText("Bind only to the specified network address"); + + certHashField = new JTextField(Settings.getProperty("davmail.server.certificate.hash"), 15); + certHashField.setToolTipText("Manually accepted server certificate hash"); + + addSettingComponent(networkSettingsPanel, "Bind address: ", bindAddressField); + addSettingComponent(networkSettingsPanel, "Allow Remote Connections: ", allowRemoteField); + addSettingComponent(networkSettingsPanel, "Server certificate hash: ", certHashField); + return networkSettingsPanel; + } + + public void reload() { + // reload settings in form + urlField.setText(Settings.getProperty("davmail.url")); + popPortField.setText(Settings.getProperty("davmail.popPort")); + smtpPortField.setText(Settings.getProperty("davmail.smtpPort")); + keepDelayField.setText(Settings.getProperty("davmail.keepDelay")); + allowRemoteField.setSelected(Settings.getBooleanProperty(("davmail.allowRemote"))); + bindAddressField.setText(Settings.getProperty("davmail.bindAddress")); + boolean enableProxy = Settings.getBooleanProperty("davmail.enableProxy"); + enableProxyField.setSelected(enableProxy); + httpProxyField.setEnabled(enableProxy); + httpProxyPortField.setEnabled(enableProxy); + httpProxyUserField.setEnabled(enableProxy); + httpProxyPasswordField.setEnabled(enableProxy); + httpProxyField.setText(Settings.getProperty("davmail.proxyHost")); + httpProxyPortField.setText(Settings.getProperty("davmail.proxyPort")); + httpProxyUserField.setText(Settings.getProperty("davmail.proxyUser")); + httpProxyPasswordField.setText(Settings.getProperty("davmail.proxyPassword")); + certHashField.setText(Settings.getProperty("davmail.server.certificate.hash")); + } + + public SettingsFrame() { + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setTitle("DavMail Settings"); + + JTabbedPane tabbedPane = new JTabbedPane(); + + JPanel mainPanel = new JPanel(new GridLayout(2, 1)); + mainPanel.add(getSettingsPanel()); + mainPanel.add(getProxyPanel()); tabbedPane.add("Main", mainPanel); JPanel advancedPanel = new JPanel(); advancedPanel.setLayout(new BorderLayout()); - JPanel networkSettingsPanel = new JPanel(new GridLayout(2, 2)); - networkSettingsPanel.setBorder(BorderFactory.createTitledBorder("Network settings")); - - final JCheckBox allowRemoteField = new JCheckBox(); - allowRemoteField.setSelected(Settings.getBooleanProperty("davmail.allowRemote")); - allowRemoteField.setToolTipText("Allow remote connections to the gateway (server mode)"); - - final JTextField bindAddressField = new JTextField(Settings.getProperty("davmail.bindAddress"), 15); - bindAddressField.setToolTipText("Bind only to the specified network address"); - - addSettingComponent(networkSettingsPanel, "Bind address: ", bindAddressField); - addSettingComponent(networkSettingsPanel, "Allow Remote Connections: ", allowRemoteField); - - advancedPanel.add("North", networkSettingsPanel); + advancedPanel.add("North", getNetworkSettingsPanel()); tabbedPane.add("Advanced", advancedPanel); @@ -117,8 +164,9 @@ public class SettingsFrame extends JFrame { Settings.setProperty("davmail.proxyPort", httpProxyPortField.getText()); Settings.setProperty("davmail.proxyUser", httpProxyUserField.getText()); Settings.setProperty("davmail.proxyPassword", httpProxyPasswordField.getText()); - Settings.save(); + Settings.setProperty("davmail.server.certificate.hash", certHashField.getText()); dispose(); + Settings.save(); // restart listeners with new config DavGateway.start(); } @@ -127,23 +175,7 @@ public class SettingsFrame extends JFrame { cancel.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { - // reload settings in form - urlField.setText(Settings.getProperty("davmail.url")); - popPortField.setText(Settings.getProperty("davmail.popPort")); - smtpPortField.setText(Settings.getProperty("davmail.smtpPort")); - keepDelayField.setText(Settings.getProperty("davmail.keepDelay")); - allowRemoteField.setSelected(Settings.getBooleanProperty(("davmail.allowRemote"))); - bindAddressField.setText(Settings.getProperty("davmail.bindAddress")); - boolean enableProxy = Settings.getBooleanProperty("davmail.allowRemote"); - enableProxyField.setSelected(enableProxy); - httpProxyField.setEnabled(enableProxy); - httpProxyPortField.setEnabled(enableProxy); - httpProxyUserField.setEnabled(enableProxy); - httpProxyPasswordField.setEnabled(enableProxy); - httpProxyField.setText(Settings.getProperty("davmail.proxyHost")); - httpProxyPortField.setText(Settings.getProperty("davmail.proxyPort")); - httpProxyUserField.setText(Settings.getProperty("davmail.proxyUser")); - httpProxyPasswordField.setText(Settings.getProperty("davmail.proxyPassword")); + reload(); dispose(); } }); diff --git a/src/java/davmail/exchange/ExchangeSession.java b/src/java/davmail/exchange/ExchangeSession.java index 06319df9..7db1425d 100644 --- a/src/java/davmail/exchange/ExchangeSession.java +++ b/src/java/davmail/exchange/ExchangeSession.java @@ -1,7 +1,15 @@ package davmail.exchange; import davmail.Settings; -import org.apache.commons.httpclient.*; +import davmail.http.DavGatewayHttpClientFactory; +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.HttpURL; +import org.apache.commons.httpclient.HttpsURL; +import org.apache.commons.httpclient.URIException; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.PutMethod; @@ -12,13 +20,22 @@ import org.apache.webdav.lib.ResponseEntity; import org.apache.webdav.lib.WebdavResource; import javax.mail.internet.MimeUtility; -import java.io.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.net.HttpURLConnection; import java.net.URL; -import java.net.UnknownHostException; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; +import java.util.List; +import java.util.SimpleTimeZone; +import java.util.Vector; /** * Exchange session through Outlook Web Access (DAV) @@ -65,91 +82,13 @@ public class ExchangeSession { * Create an exchange session for the given URL. * The session is not actually established until a call to login() */ - public ExchangeSession() { + ExchangeSession() { // SimpleDateFormat are not thread safe, need to create one instance for // each session dateParser = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); dateParser.setTimeZone(new SimpleTimeZone(0, "GMT")); } - /** - * Update http client configuration (proxy) - * - * @param httpClient current Http client - */ - protected static void configureClient(HttpClient httpClient) { - // do not send basic auth automatically - httpClient.getState().setAuthenticationPreemptive(false); - - boolean enableProxy = Settings.getBooleanProperty("davmail.enableProxy"); - String proxyHost = null; - int proxyPort = 0; - String proxyUser = null; - String proxyPassword = null; - - if (enableProxy) { - proxyHost = Settings.getProperty("davmail.proxyHost"); - proxyPort = Settings.getIntProperty("davmail.proxyPort"); - proxyUser = Settings.getProperty("davmail.proxyUser"); - proxyPassword = Settings.getProperty("davmail.proxyPassword"); - } - - // configure proxy - if (proxyHost != null && proxyHost.length() > 0) { - httpClient.getHostConfiguration().setProxy(proxyHost, proxyPort); - if (proxyUser != null && proxyUser.length() > 0) { - -/* // Only available in newer HttpClient releases, not compatible with slide library - List authPrefs = new ArrayList(); - authPrefs.add(AuthPolicy.BASIC); - httpClient.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY,authPrefs); -*/ - // instead detect ntlm authentication (windows domain name in user name) - int backslashindex = proxyUser.indexOf("\\"); - if (backslashindex > 0) { - httpClient.getState().setProxyCredentials(null, proxyHost, - new NTCredentials(proxyUser.substring(backslashindex + 1), - proxyPassword, null, - proxyUser.substring(0, backslashindex))); - } else { - httpClient.getState().setProxyCredentials(null, proxyHost, - new UsernamePasswordCredentials(proxyUser, proxyPassword)); - } - } - } - - } - - public static void checkConfig() throws IOException { - try { - String url = Settings.getProperty("davmail.url"); - - // create an HttpClient instance - HttpClient httpClient = new HttpClient(); - configureClient(httpClient); - - // get webmail root url (will not follow redirects) - HttpMethod testMethod = new GetMethod(url); - testMethod.setFollowRedirects(false); - int status = httpClient.executeMethod(testMethod); - testMethod.releaseConnection(); - LOGGER.debug("Test configuration status: " + status); - if (status != HttpStatus.SC_OK && status != HttpStatus.SC_UNAUTHORIZED - && status != HttpStatus.SC_MOVED_TEMPORARILY) { - throw new IOException("Unable to connect to OWA at " + url + ", status code " + - status + ", check configuration"); - } - - } catch (UnknownHostException exc) { - LOGGER.error("DavMail configuration exception: \n Unknown host " + exc.getMessage(), exc); - throw new IOException("DavMail configuration exception: \n Unknown host " + exc.getMessage(), exc); - } catch (Exception exc) { - LOGGER.error("DavMail configuration exception: \n" + exc.getMessage(), exc); - throw new IOException("DavMail configuration exception: \n" + exc.getMessage(), exc); - } - - } - /** * Test authentication mode : form based or basic. * @@ -159,15 +98,14 @@ public class ExchangeSession { */ protected boolean isBasicAuthentication(String url) throws IOException { // create an HttpClient instance - HttpClient httpClient = new HttpClient(); - configureClient(httpClient); + HttpClient httpClient = DavGatewayHttpClientFactory.getInstance(); HttpMethod testMethod = new GetMethod(url); int status = httpClient.executeMethod(testMethod); testMethod.releaseConnection(); return status == HttpStatus.SC_UNAUTHORIZED; } - public void login(String userName, String password) throws IOException { + void login(String userName, String password) throws IOException { try { String url = Settings.getProperty("davmail.url"); @@ -194,13 +132,24 @@ public class ExchangeSession { // get the internal HttpClient instance HttpClient httpClient = wdr.retrieveSessionInstance(); - configureClient(httpClient); + DavGatewayHttpClientFactory.configureClient(httpClient); - // get webmail root url (will follow redirects) + // get webmail root url // providing credentials + // manually follow redirect HttpMethod initmethod = new GetMethod(url); - wdr.executeHttpRequestMethod(httpClient, - initmethod); + initmethod.setFollowRedirects(false); + httpClient.executeMethod(initmethod); + int redirectCount = 0; + while (redirectCount++ < 10 && initmethod.getStatusCode() == 302) { + initmethod.releaseConnection(); + initmethod = new GetMethod(initmethod.getResponseHeader("Location").getValue()); + initmethod.setFollowRedirects(false); + httpClient.executeMethod(initmethod); + } + if (initmethod.getStatusCode() == 302) { + throw new IOException("Maximum redirections reached"); + } // default destination is provided url String destination = url; @@ -216,12 +165,12 @@ public class ExchangeSession { loginFormReader = new BufferedReader(new InputStreamReader(initmethod.getResponseBodyAsStream())); String line; // skip to form action - final String FORM_ACTION = "
=0) { + logonMethodPath = path.substring(0, end+1) + logonMethodPath; + } + } } catch (IOException e) { LOGGER.error("Error parsing login form at " + initmethod.getPath()); } finally { @@ -264,7 +221,18 @@ public class ExchangeSession { // User may be authenticated, get various session information HttpMethod method = new GetMethod(destination); - int status = wdr.executeHttpRequestMethod(httpClient, method); + method.setFollowRedirects(false); + int status = httpClient.executeMethod(method); + redirectCount = 0; + while (redirectCount++ < 10 && method.getStatusCode() == 302) { + method.releaseConnection(); + method = new GetMethod(method.getResponseHeader("Location").getValue()); + method.setFollowRedirects(false); + status = httpClient.executeMethod(method); + } + if (method.getStatusCode() == 302) { + throw new IOException("Maximum redirections reached"); + } if (status != HttpStatus.SC_OK) { HttpException ex = new HttpException(); ex.setReasonCode(status); diff --git a/src/java/davmail/exchange/ExchangeSessionFactory.java b/src/java/davmail/exchange/ExchangeSessionFactory.java new file mode 100644 index 00000000..91db579d --- /dev/null +++ b/src/java/davmail/exchange/ExchangeSessionFactory.java @@ -0,0 +1,98 @@ +package davmail.exchange; + +import davmail.Settings; +import davmail.http.DavGatewayHttpClientFactory; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.GetMethod; + +import java.io.IOException; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Enumeration; + +/** + * Create ExchangeSession instances. + */ +public class ExchangeSessionFactory { + /** + * Create authenticated Exchange session + * + * @param userName user login + * @param password user password + * @return authenticated session + * @throws java.io.IOException on error + */ + public static ExchangeSession getInstance(String userName, String password) throws IOException { + try { + ExchangeSession session = new ExchangeSession(); + session.login(userName, password); + return session; + } catch (IOException e) { + if (checkNetwork()) { + throw e; + } else { + throw new IOException("All network interfaces down !"); + } + } + } + + + public static void checkConfig() throws IOException { + try { + String url = Settings.getProperty("davmail.url"); + + // create an HttpClient instance + HttpClient httpClient = DavGatewayHttpClientFactory.getInstance(); + + // get webmail root url (will not follow redirects) + HttpMethod testMethod = new GetMethod(url); + testMethod.setFollowRedirects(false); + int status = httpClient.executeMethod(testMethod); + testMethod.releaseConnection(); + ExchangeSession.LOGGER.debug("Test configuration status: " + status); + if (status != HttpStatus.SC_OK && status != HttpStatus.SC_UNAUTHORIZED + && status != HttpStatus.SC_MOVED_TEMPORARILY) { + throw new IOException("Unable to connect to OWA at " + url + ", status code " + + status + ", check configuration"); + } + + } catch (UnknownHostException exc) { + String message = "DavMail configuration exception: \n Unknown host "; + if (checkNetwork()) { + message += exc.getMessage(); + } else { + message += "All network interfaces down !"; + } + + ExchangeSession.LOGGER.error(message, exc); + throw new IOException(message, exc); + } catch (Exception exc) { + ExchangeSession.LOGGER.error("DavMail configuration exception: \n" + exc.getMessage(), exc); + throw new IOException("DavMail configuration exception: \n" + exc.getMessage(), exc); + } + + } + + /** + * Check if at least one network interface is up and active (i.e. has an address) + * @return true if network available + */ + protected static boolean checkNetwork() { + boolean up = false; + Enumeration enumeration; + try { + enumeration = NetworkInterface.getNetworkInterfaces(); + while (!up && enumeration.hasMoreElements()) { + NetworkInterface networkInterface = enumeration.nextElement(); + up = networkInterface.isUp() && !networkInterface.isLoopback() + && networkInterface.getInetAddresses().hasMoreElements(); + } + } catch (SocketException exc) { + ExchangeSession.LOGGER.error("DavMail configuration exception: \n Error listing network interfaces " + exc.getMessage(), exc); + } + return up; + } +} diff --git a/src/java/davmail/http/DavGatewayHttpClientFactory.java b/src/java/davmail/http/DavGatewayHttpClientFactory.java new file mode 100644 index 00000000..b7a7ce8d --- /dev/null +++ b/src/java/davmail/http/DavGatewayHttpClientFactory.java @@ -0,0 +1,72 @@ +package davmail.http; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.NTCredentials; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import davmail.Settings; + +/** + * Create HttpClient instance according to DavGateway Settings + */ +public class DavGatewayHttpClientFactory { + /** + * Create a configured HttpClient instance. + * + * @return httpClient + */ + public static HttpClient getInstance() { + // create an HttpClient instance + HttpClient httpClient = new HttpClient(); + configureClient(httpClient); + return httpClient; + } + + /** + * Update http client configuration (proxy) + * + * @param httpClient current Http client + */ + public static void configureClient(HttpClient httpClient) { + // do not send basic auth automatically + httpClient.getState().setAuthenticationPreemptive(false); + + boolean enableProxy = Settings.getBooleanProperty("davmail.enableProxy"); + String proxyHost = null; + int proxyPort = 0; + String proxyUser = null; + String proxyPassword = null; + + if (enableProxy) { + proxyHost = Settings.getProperty("davmail.proxyHost"); + proxyPort = Settings.getIntProperty("davmail.proxyPort"); + proxyUser = Settings.getProperty("davmail.proxyUser"); + proxyPassword = Settings.getProperty("davmail.proxyPassword"); + } + + // configure proxy + if (proxyHost != null && proxyHost.length() > 0) { + httpClient.getHostConfiguration().setProxy(proxyHost, proxyPort); + if (proxyUser != null && proxyUser.length() > 0) { + +/* // Only available in newer HttpClient releases, not compatible with slide library + List authPrefs = new ArrayList(); + authPrefs.add(AuthPolicy.BASIC); + httpClient.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY,authPrefs); +*/ + // instead detect ntlm authentication (windows domain name in user name) + int backslashindex = proxyUser.indexOf("\\"); + if (backslashindex > 0) { + httpClient.getState().setProxyCredentials(null, proxyHost, + new NTCredentials(proxyUser.substring(backslashindex + 1), + proxyPassword, null, + proxyUser.substring(0, backslashindex))); + } else { + httpClient.getState().setProxyCredentials(null, proxyHost, + new UsernamePasswordCredentials(proxyUser, proxyPassword)); + } + } + } + + } + +} diff --git a/src/java/davmail/http/DavGatewaySSLProtocolSocketFactory.java b/src/java/davmail/http/DavGatewaySSLProtocolSocketFactory.java new file mode 100644 index 00000000..63b70d2f --- /dev/null +++ b/src/java/davmail/http/DavGatewaySSLProtocolSocketFactory.java @@ -0,0 +1,111 @@ +package davmail.http; + +import org.apache.commons.httpclient.protocol.SSLProtocolSocketFactory; +import org.apache.commons.httpclient.protocol.Protocol; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.URL; +import java.net.MalformedURLException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; + +import davmail.Settings; +import davmail.tray.DavGatewayTray; + +/** + * Manual Socket Factory. + * Let user choose to accept or reject certificate + */ +public class DavGatewaySSLProtocolSocketFactory extends SSLProtocolSocketFactory { + /** + * Register custom Socket Factory to let user accept or reject certificate + */ + public static void register() { + String urlString = Settings.getProperty("davmail.url"); + try { + URL url = new URL(urlString); + String protocol = url.getProtocol(); + if ("https".equals(protocol)) { + int port = url.getPort(); + if (port < 0) { + port = 443; + } + Protocol.registerProtocol(url.getProtocol(), + new Protocol(protocol, new DavGatewaySSLProtocolSocketFactory(), port)); + } + } catch (MalformedURLException e) { + DavGatewayTray.error("Exception handling url: " + urlString); + } + } + + private SSLContext sslcontext = null; + + private SSLContext createSSLContext() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException { + SSLContext context = SSLContext.getInstance("SSL"); + context.init( + null, + new TrustManager[]{new DavGatewayX509TrustManager()}, + null); + return context; + } + + private SSLContext getSSLContext() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException { + if (this.sslcontext == null) { + this.sslcontext = createSSLContext(); + } + return this.sslcontext; + } + + + public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException { + try { + return getSSLContext().getSocketFactory().createSocket( + host, + port, + clientHost, + clientPort + ); + } catch (NoSuchAlgorithmException e) { + throw new IOException(e); + } catch (KeyManagementException e) { + throw new IOException(e); + } catch (KeyStoreException e) { + throw new IOException(e); + } + } + + + public Socket createSocket(String host, int port) throws IOException { + try { + return getSSLContext().getSocketFactory().createSocket( + host, + port + ); + } catch (NoSuchAlgorithmException e) { + throw new IOException(e); + } catch (KeyManagementException e) { + throw new IOException(e); + } catch (KeyStoreException e) { + throw new IOException(e); + } + } + + + public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException { + try { + return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose + ); + } catch (NoSuchAlgorithmException e) { + throw new IOException(e); + } catch (KeyManagementException e) { + throw new IOException(e); + } catch (KeyStoreException e) { + throw new IOException(e); + } + } +} diff --git a/src/java/davmail/imap/ImapConnection.java b/src/java/davmail/imap/ImapConnection.java index 9b69b8ca..8bbac62e 100644 --- a/src/java/davmail/imap/ImapConnection.java +++ b/src/java/davmail/imap/ImapConnection.java @@ -8,6 +8,7 @@ import java.io.IOException; import davmail.AbstractConnection; import davmail.tray.DavGatewayTray; import davmail.exchange.ExchangeSession; +import davmail.exchange.ExchangeSessionFactory; /** * Dav Gateway smtp connection implementation. @@ -50,9 +51,8 @@ public class ImapConnection extends AbstractConnection { sendClient(commandId + " OK CAPABILITY completed"); } else if ("login".equalsIgnoreCase(command)) { parseCredentials(tokens); - session = new ExchangeSession(); try { - session.login(userName, password); + session = ExchangeSessionFactory.getInstance(userName, password); sendClient(commandId + " OK Authenticated"); state = AUTHENTICATED; } catch (Exception e) { @@ -127,7 +127,7 @@ public class ImapConnection extends AbstractConnection { os.flush(); } catch (IOException e) { - DavGatewayTray.error("Exception handling client",e); + DavGatewayTray.error("Exception handling client", e); } finally { close(); } diff --git a/src/java/davmail/pop/PopConnection.java b/src/java/davmail/pop/PopConnection.java index fb4df05a..0e789fac 100644 --- a/src/java/davmail/pop/PopConnection.java +++ b/src/java/davmail/pop/PopConnection.java @@ -2,6 +2,7 @@ package davmail.pop; import davmail.AbstractConnection; import davmail.exchange.ExchangeSession; +import davmail.exchange.ExchangeSessionFactory; import davmail.tray.DavGatewayTray; import java.io.FilterOutputStream; @@ -64,7 +65,7 @@ public class PopConnection extends AbstractConnection { StringTokenizer tokens; try { - ExchangeSession.checkConfig(); + ExchangeSessionFactory.checkConfig(); sendOK("DavMail POP ready at " + new Date()); for (; ;) { @@ -107,8 +108,7 @@ public class PopConnection extends AbstractConnection { } else { password = tokens.nextToken(); try { - session = new ExchangeSession(); - session.login(userName, password); + session = ExchangeSessionFactory.getInstance(userName, password); messages = session.getAllMessages(); sendOK("PASS"); state = AUTHENTICATED; diff --git a/src/java/davmail/smtp/SmtpConnection.java b/src/java/davmail/smtp/SmtpConnection.java index d9750cd5..e2a534a1 100644 --- a/src/java/davmail/smtp/SmtpConnection.java +++ b/src/java/davmail/smtp/SmtpConnection.java @@ -1,8 +1,8 @@ package davmail.smtp; import davmail.AbstractConnection; +import davmail.exchange.ExchangeSessionFactory; import davmail.tray.DavGatewayTray; -import davmail.exchange.ExchangeSession; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; @@ -33,7 +33,7 @@ public class SmtpConnection extends AbstractConnection { StringTokenizer tokens; try { - ExchangeSession.checkConfig(); + ExchangeSessionFactory.checkConfig(); sendClient("220 DavMail SMTP ready at " + new Date()); for (; ;) { line = readClient(); @@ -142,9 +142,8 @@ public class SmtpConnection extends AbstractConnection { * @throws IOException on error */ protected void authenticate() throws IOException { - session = new ExchangeSession(); try { - session.login(userName, password); + session = ExchangeSessionFactory.getInstance(userName, password); sendClient("235 OK Authenticated"); state = AUTHENTICATED; } catch (Exception e) { diff --git a/src/java/davmail/tray/AwtGatewayTray.java b/src/java/davmail/tray/AwtGatewayTray.java index 310e25d6..208aa5f5 100644 --- a/src/java/davmail/tray/AwtGatewayTray.java +++ b/src/java/davmail/tray/AwtGatewayTray.java @@ -102,6 +102,7 @@ public class AwtGatewayTray implements DavGatewayTrayInterface { // create an action settingsListener to listen for settings action executed on the tray icon ActionListener settingsListener = new ActionListener() { public void actionPerformed(ActionEvent e) { + settingsFrame.reload(); settingsFrame.setVisible(true); } }; diff --git a/src/java/davmail/tray/DavGatewayTray.java b/src/java/davmail/tray/DavGatewayTray.java index 350e87cb..c1d6b10b 100644 --- a/src/java/davmail/tray/DavGatewayTray.java +++ b/src/java/davmail/tray/DavGatewayTray.java @@ -16,7 +16,7 @@ public class DavGatewayTray { protected DavGatewayTray() { } - protected static DavGatewayTrayInterface davGatewayTray; + static DavGatewayTrayInterface davGatewayTray; public static void switchIcon() { if (davGatewayTray != null) { @@ -40,7 +40,7 @@ public class DavGatewayTray { protected static void displayMessage(String message, Exception e, Priority priority) { LOGGER.log(priority, message, e); if (davGatewayTray != null) { - davGatewayTray.displayMessage(message + " " + e +" "+ e.getMessage(), priority); + davGatewayTray.displayMessage(message + " " + e + " " + e.getMessage(), priority); } } diff --git a/src/java/davmail/tray/SwtGatewayTray.java b/src/java/davmail/tray/SwtGatewayTray.java index 3805a876..6ca900aa 100644 --- a/src/java/davmail/tray/SwtGatewayTray.java +++ b/src/java/davmail/tray/SwtGatewayTray.java @@ -177,6 +177,7 @@ public class SwtGatewayTray implements DavGatewayTrayInterface { display.asyncExec( new Runnable() { public void run() { + settingsFrame.reload(); settingsFrame.setVisible(true); } }); @@ -191,6 +192,7 @@ public class SwtGatewayTray implements DavGatewayTrayInterface { display.asyncExec( new Runnable() { public void run() { + settingsFrame.reload(); settingsFrame.setVisible(true); } }); diff --git a/src/java/davmail/ui/AcceptCertificateDialog.java b/src/java/davmail/ui/AcceptCertificateDialog.java new file mode 100644 index 00000000..aec5e4e4 --- /dev/null +++ b/src/java/davmail/ui/AcceptCertificateDialog.java @@ -0,0 +1,143 @@ +package davmail.ui; + +import davmail.http.DavGatewayX509TrustManager; +import davmail.tray.DavGatewayTray; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.lang.reflect.InvocationTargetException; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; + +/** + * Accept certificate dialog + */ +public class AcceptCertificateDialog extends JDialog { + protected boolean accepted; + + public boolean isAccepted() { + return accepted; + } + + public void addFieldValue(JPanel panel, String label, String value) { + JPanel fieldPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + StringBuilder buffer = new StringBuilder(); + buffer.append(""); + buffer.append(label); + buffer.append(":"); + fieldPanel.add(new JLabel(buffer.toString())); + fieldPanel.add(new JLabel(value)); + panel.add(fieldPanel); + } + + protected String getRDN(Principal principal) { + String dn = principal.getName(); + int start = dn.indexOf('='); + int end = dn.indexOf(','); + if (start >= 0 && end >= 0) { + return dn.substring(start + 1, end); + } else { + return dn; + } + } + + public String getFormattedSerial(X509Certificate certificate) { + StringBuilder builder = new StringBuilder(); + String serial = certificate.getSerialNumber().toString(16); + for (int i = 0;i 0 && i%2 == 0) { + builder.append(' '); + } + builder.append(serial.charAt(i)); + } + return builder.toString(); + } + + public AcceptCertificateDialog(X509Certificate certificate) { + String sha1Hash; + try { + sha1Hash = DavGatewayX509TrustManager.getFormattedHash(certificate); + } catch (NoSuchAlgorithmException nsa) { + sha1Hash = nsa.getMessage(); + } catch (CertificateEncodingException cee) { + sha1Hash = cee.getMessage(); + } + SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy"); + + setTitle("Accept certificate ?"); + + JPanel subjectPanel = new JPanel(); + subjectPanel.setLayout(new BoxLayout(subjectPanel, BoxLayout.Y_AXIS)); + subjectPanel.setBorder(BorderFactory.createTitledBorder("Server Certificate")); + addFieldValue(subjectPanel, "Issued to", getRDN(certificate.getSubjectDN())); + addFieldValue(subjectPanel, "Issued by", getRDN(certificate.getIssuerDN())); + addFieldValue(subjectPanel, "Valid from", formatter.format(certificate.getNotBefore())); + addFieldValue(subjectPanel, "Valid until", formatter.format(certificate.getNotAfter())); + addFieldValue(subjectPanel, "Serial", getFormattedSerial(certificate)); + addFieldValue(subjectPanel, "FingerPrint", sha1Hash); + + JPanel warningPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + JLabel imageLabel = new JLabel(); + imageLabel.setIcon(UIManager.getIcon("OptionPane.warningIcon")); + imageLabel.setText("Server provided an untrusted certificate,
you can choose to accept or deny access
"); + warningPanel.add(imageLabel); + add(warningPanel, BorderLayout.NORTH); + add(subjectPanel, BorderLayout.CENTER); + add(getButtonPanel(), BorderLayout.SOUTH); + setModal(true); + + pack(); + // center frame + setLocation(getToolkit().getScreenSize().width / 2 - + getSize().width / 2, + getToolkit().getScreenSize().height / 2 - + getSize().height / 2); + setVisible(true); + } + + protected JPanel getButtonPanel() { + JPanel buttonPanel = new JPanel(); + JButton accept = new JButton("Accept"); + JButton deny = new JButton("Deny"); + accept.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + accepted = true; + dispose(); + } + }); + deny.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + accepted = false; + dispose(); + } + }); + + buttonPanel.add(accept); + buttonPanel.add(deny); + return buttonPanel; + } + + + public static boolean isCertificateTrusted(final X509Certificate certificate) { + final boolean[] answer = new boolean[1]; + try { + SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + AcceptCertificateDialog certificateDialog = new AcceptCertificateDialog(certificate); + answer[0] = certificateDialog.isAccepted(); + } + }); + } catch (InterruptedException ie) { + DavGatewayTray.error("Error waiting for certificate check", ie); + } catch (InvocationTargetException ite) { + DavGatewayTray.error("Error waiting for certificate check", ite); + } + + return answer[0]; + } +} diff --git a/src/test/davmail/exchange/TestExchangeSession.java b/src/test/davmail/exchange/TestExchangeSession.java index 6593b4c1..f6e21525 100644 --- a/src/test/davmail/exchange/TestExchangeSession.java +++ b/src/test/davmail/exchange/TestExchangeSession.java @@ -19,7 +19,7 @@ public class TestExchangeSession { ExchangeSession session = new ExchangeSession(); // test auth try { - ExchangeSession.checkConfig(); + ExchangeSessionFactory.checkConfig(); session.login(argv[currentArg++], argv[currentArg++]); ExchangeSession.Folder folder = session.selectFolder(argv[currentArg++]);