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