1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package davmail.exchange;
20
21 import davmail.BundleMessage;
22 import davmail.Settings;
23 import davmail.exception.DavMailAuthenticationException;
24 import davmail.exception.DavMailException;
25 import davmail.exception.WebdavNotAvailableException;
26 import davmail.exchange.auth.ExchangeAuthenticator;
27 import davmail.exchange.auth.ExchangeFormAuthenticator;
28 import davmail.exchange.dav.DavExchangeSession;
29 import davmail.exchange.ews.EwsExchangeSession;
30 import davmail.http.HttpClientAdapter;
31 import davmail.http.request.GetRequest;
32 import org.apache.http.HttpStatus;
33 import org.apache.http.client.methods.CloseableHttpResponse;
34
35 import java.awt.*;
36 import java.io.IOException;
37 import java.net.NetworkInterface;
38 import java.net.SocketException;
39 import java.net.UnknownHostException;
40 import java.util.Enumeration;
41 import java.util.HashMap;
42 import java.util.Map;
43
44
45
46
47 public final class ExchangeSessionFactory {
48 private static final Object LOCK = new Object();
49 private static final Map<PoolKey, ExchangeSession> POOL_MAP = new HashMap<>();
50 private static boolean configChecked;
51 private static boolean errorSent;
52
53 static class PoolKey {
54 final String url;
55 final String userName;
56 final String password;
57
58 PoolKey(String url, String userName, String password) {
59 this.url = url;
60 this.userName = convertUserName(userName);
61 this.password = password;
62 }
63
64 @Override
65 public boolean equals(Object object) {
66 return object == this ||
67 object instanceof PoolKey &&
68 ((PoolKey) object).url.equals(this.url) &&
69 ((PoolKey) object).userName.equals(this.userName) &&
70 ((PoolKey) object).password.equals(this.password);
71 }
72
73 @Override
74 public int hashCode() {
75 return url.hashCode() + userName.hashCode() + password.hashCode();
76 }
77 }
78
79 private ExchangeSessionFactory() {
80 }
81
82
83
84
85
86
87
88
89
90 public static ExchangeSession getInstance(String userName, String password) throws IOException {
91 String baseUrl = Settings.getProperty("davmail.url");
92 if (Settings.getBooleanProperty("davmail.server")) {
93 return getInstance(baseUrl, userName, password);
94 } else {
95
96 synchronized (LOCK) {
97 return getInstance(baseUrl, userName, password);
98 }
99 }
100 }
101
102 private static String convertUserName(String userName) {
103 String result = userName;
104
105 String defaultDomain = Settings.getProperty("davmail.defaultDomain");
106 if (defaultDomain != null && userName.indexOf('\\') < 0 && userName.indexOf('@') < 0) {
107 result = defaultDomain + '\\' + userName;
108 }
109 return result;
110 }
111
112
113
114
115
116
117
118
119
120
121 public static ExchangeSession getInstance(String baseUrl, String userName, String password) throws IOException {
122 ExchangeSession session = null;
123 try {
124 String mode = Settings.getProperty("davmail.mode");
125 if (Settings.O365.equals(mode)) {
126
127 baseUrl = Settings.O365_URL;
128 }
129
130 PoolKey poolKey = new PoolKey(baseUrl, userName, password);
131
132 synchronized (LOCK) {
133 session = POOL_MAP.get(poolKey);
134 }
135 if (session != null) {
136 ExchangeSession.LOGGER.debug("Got session " + session + " from cache");
137 }
138
139 if (session != null && session.isExpired()) {
140 synchronized (LOCK) {
141 session.close();
142 ExchangeSession.LOGGER.debug("Session " + session + " for user " + session.userName + " expired");
143 session = null;
144
145 POOL_MAP.remove(poolKey);
146 }
147 }
148
149 if (session == null) {
150
151 if (mode == null) {
152 if ("false".equals(Settings.getProperty("davmail.enableEws"))) {
153 mode = Settings.WEBDAV;
154 } else {
155 mode = Settings.EWS;
156 }
157 }
158
159 String authenticatorClass = Settings.getProperty("davmail.authenticator");
160 if (authenticatorClass == null) {
161 switch (mode) {
162 case Settings.O365_MODERN:
163 authenticatorClass = "davmail.exchange.auth.O365Authenticator";
164 break;
165 case Settings.O365_INTERACTIVE:
166 authenticatorClass = "davmail.exchange.auth.O365InteractiveAuthenticator";
167 if (GraphicsEnvironment.isHeadless()) {
168 throw new DavMailException("EXCEPTION_DAVMAIL_CONFIGURATION", "O365Interactive not supported in headless mode");
169 }
170 break;
171 case Settings.O365_MANUAL:
172 authenticatorClass = "davmail.exchange.auth.O365ManualAuthenticator";
173 break;
174 }
175 }
176
177 if (authenticatorClass != null) {
178 ExchangeAuthenticator authenticator = (ExchangeAuthenticator) Class.forName(authenticatorClass)
179 .getDeclaredConstructor().newInstance();
180 authenticator.setUsername(poolKey.userName);
181 authenticator.setPassword(poolKey.password);
182 authenticator.authenticate();
183 session = new EwsExchangeSession(authenticator.getExchangeUri(), authenticator.getToken(), poolKey.userName);
184
185 } else if (Settings.EWS.equals(mode) || Settings.O365.equals(mode)
186
187 || poolKey.url.toLowerCase().endsWith("/ews/exchange.asmx")
188 || poolKey.url.toLowerCase().endsWith("/ews/services.wsdl")) {
189 if (poolKey.url.toLowerCase().endsWith("/ews/exchange.asmx")
190 || poolKey.url.toLowerCase().endsWith("/ews/services.wsdl")) {
191 ExchangeSession.LOGGER.debug("Direct EWS authentication");
192 session = new EwsExchangeSession(poolKey.url, poolKey.userName, poolKey.password);
193 } else {
194 ExchangeSession.LOGGER.debug("OWA authentication in EWS mode");
195 ExchangeFormAuthenticator exchangeFormAuthenticator = new ExchangeFormAuthenticator();
196 exchangeFormAuthenticator.setUrl(poolKey.url);
197 exchangeFormAuthenticator.setUsername(poolKey.userName);
198 exchangeFormAuthenticator.setPassword(poolKey.password);
199 exchangeFormAuthenticator.authenticate();
200 session = new EwsExchangeSession(exchangeFormAuthenticator.getHttpClientAdapter(),
201 exchangeFormAuthenticator.getExchangeUri(), exchangeFormAuthenticator.getUsername());
202 }
203 } else {
204 ExchangeFormAuthenticator exchangeFormAuthenticator = new ExchangeFormAuthenticator();
205 exchangeFormAuthenticator.setUrl(poolKey.url);
206 exchangeFormAuthenticator.setUsername(poolKey.userName);
207 exchangeFormAuthenticator.setPassword(poolKey.password);
208 exchangeFormAuthenticator.authenticate();
209 try {
210 session = new DavExchangeSession(exchangeFormAuthenticator.getHttpClientAdapter(),
211 exchangeFormAuthenticator.getExchangeUri(),
212 exchangeFormAuthenticator.getUsername());
213 } catch (WebdavNotAvailableException e) {
214 if (Settings.AUTO.equals(mode)) {
215 ExchangeSession.LOGGER.debug(e.getMessage() + ", retry with EWS");
216 session = new EwsExchangeSession(poolKey.url, poolKey.userName, poolKey.password);
217 } else {
218 throw e;
219 }
220 }
221 }
222 checkWhiteList(session.getEmail());
223 ExchangeSession.LOGGER.debug("Created new session " + session + " for user " + poolKey.userName);
224 }
225
226 synchronized (LOCK) {
227 POOL_MAP.put(poolKey, session);
228 }
229
230 configChecked = true;
231
232 errorSent = false;
233 } catch (DavMailException | IllegalStateException | NullPointerException exc) {
234 throw exc;
235 } catch (Exception exc) {
236 handleNetworkDown(exc);
237 }
238 return session;
239 }
240
241
242
243
244
245
246
247
248 private static void checkWhiteList(String email) throws DavMailAuthenticationException {
249 String whiteListString = Settings.getProperty("davmail.userWhiteList");
250 if (whiteListString != null && !whiteListString.isEmpty()) {
251 for (String whiteListvalue : whiteListString.split(",")) {
252 if (whiteListvalue.startsWith("@") && email.endsWith(whiteListvalue)) {
253 return;
254 } else if (email.equalsIgnoreCase(whiteListvalue)) {
255 return;
256 }
257 }
258 ExchangeSession.LOGGER.warn(email + " not allowed by whitelist");
259 throw new DavMailAuthenticationException("EXCEPTION_AUTHENTICATION_FAILED");
260 }
261 }
262
263
264
265
266
267
268
269
270
271
272
273 public static ExchangeSession getInstance(ExchangeSession currentSession, String userName, String password)
274 throws IOException {
275 ExchangeSession session = currentSession;
276 try {
277 if (session.isExpired()) {
278 ExchangeSession.LOGGER.debug("Session " + session + " expired, trying to open a new one");
279 session = null;
280 String baseUrl = Settings.getProperty("davmail.url");
281 PoolKey poolKey = new PoolKey(baseUrl, userName, password);
282
283 synchronized (LOCK) {
284 POOL_MAP.remove(poolKey);
285 }
286 session = getInstance(userName, password);
287 }
288 } catch (DavMailAuthenticationException exc) {
289 ExchangeSession.LOGGER.debug("Unable to reopen session", exc);
290 throw exc;
291 } catch (Exception exc) {
292 ExchangeSession.LOGGER.debug("Unable to reopen session", exc);
293 handleNetworkDown(exc);
294 }
295 return session;
296 }
297
298
299
300
301
302
303 public static void checkConfig() throws IOException {
304 String url = Settings.getProperty("davmail.url");
305 if (url == null || (!url.startsWith("http://") && !url.startsWith("https://"))) {
306 throw new DavMailException("LOG_INVALID_URL", url);
307 }
308 try (
309 HttpClientAdapter httpClientAdapter = new HttpClientAdapter(url);
310 CloseableHttpResponse response = httpClientAdapter.execute(new GetRequest(url))
311 ) {
312
313 int status = response.getStatusLine().getStatusCode();
314 ExchangeSession.LOGGER.debug("Test configuration status: " + status);
315 if (status != HttpStatus.SC_OK && status != HttpStatus.SC_UNAUTHORIZED
316 && !HttpClientAdapter.isRedirect(status)) {
317 throw new DavMailException("EXCEPTION_CONNECTION_FAILED", url, status);
318 }
319
320 configChecked = true;
321
322 errorSent = false;
323 } catch (Exception exc) {
324 handleNetworkDown(exc);
325 }
326
327 }
328
329 private static void handleNetworkDown(Exception exc) throws DavMailException {
330 if (!checkNetwork() || configChecked) {
331 ExchangeSession.LOGGER.warn(BundleMessage.formatLog("EXCEPTION_NETWORK_DOWN"));
332
333 if (!((exc instanceof UnknownHostException) || (exc instanceof NetworkDownException))) {
334 ExchangeSession.LOGGER.debug(exc, exc);
335 }
336 throw new NetworkDownException("EXCEPTION_NETWORK_DOWN");
337 } else {
338 BundleMessage message = new BundleMessage("EXCEPTION_CONNECT", exc.getClass().getName(), exc.getMessage());
339 if (errorSent) {
340 ExchangeSession.LOGGER.warn(message);
341 throw new NetworkDownException("EXCEPTION_DAVMAIL_CONFIGURATION", message);
342 } else {
343
344
345 errorSent = true;
346 ExchangeSession.LOGGER.error(message);
347 throw new DavMailException("EXCEPTION_DAVMAIL_CONFIGURATION", message);
348 }
349 }
350 }
351
352
353
354
355
356
357
358 public static String getUserPassword(String userName) {
359 String fullUserName = convertUserName(userName);
360 for (PoolKey poolKey : POOL_MAP.keySet()) {
361 if (poolKey.userName.equals(fullUserName)) {
362 return poolKey.password;
363 }
364 }
365 return null;
366 }
367
368
369
370
371
372
373 static boolean checkNetwork() {
374 boolean up = false;
375 Enumeration<NetworkInterface> enumeration;
376 try {
377 enumeration = NetworkInterface.getNetworkInterfaces();
378 if (enumeration != null) {
379 while (!up && enumeration.hasMoreElements()) {
380 NetworkInterface networkInterface = enumeration.nextElement();
381 up = networkInterface.isUp() && !networkInterface.isLoopback()
382 && networkInterface.getInetAddresses().hasMoreElements();
383 }
384 }
385 } catch (NoSuchMethodError error) {
386 ExchangeSession.LOGGER.debug("Unable to test network interfaces (not available under Java 1.5)");
387 up = true;
388 } catch (SocketException exc) {
389 ExchangeSession.LOGGER.error("DavMail configuration exception: \n Error listing network interfaces " + exc.getMessage(), exc);
390 }
391 return up;
392 }
393
394
395
396
397 public static void shutdown() {
398 configChecked = false;
399 errorSent = false;
400 synchronized (LOCK) {
401 for (ExchangeSession session:POOL_MAP.values()) {
402 session.close();
403 }
404 POOL_MAP.clear();
405 }
406 }
407 }