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