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) {
162 switch (mode) {
163 case Settings.O365_MODERN:
164 authenticatorClass = "davmail.exchange.auth.O365Authenticator";
165 break;
166 case Settings.O365_INTERACTIVE:
167 authenticatorClass = "davmail.exchange.auth.O365InteractiveAuthenticator";
168 if (GraphicsEnvironment.isHeadless()) {
169 throw new DavMailException("EXCEPTION_DAVMAIL_CONFIGURATION", "O365Interactive not supported in headless mode");
170 }
171 break;
172 case Settings.O365_MANUAL:
173 authenticatorClass = "davmail.exchange.auth.O365ManualAuthenticator";
174 break;
175 }
176 }
177
178 if (authenticatorClass != null) {
179 ExchangeAuthenticator authenticator = (ExchangeAuthenticator) Class.forName(authenticatorClass)
180 .getDeclaredConstructor().newInstance();
181 authenticator.setUsername(poolKey.userName);
182 authenticator.setPassword(poolKey.password);
183 authenticator.authenticate();
184
185 if (Settings.getBooleanProperty("davmail.enableGraph", false)) {
186 session = new GraphExchangeSession(authenticator.getHttpClientAdapter(), authenticator.getToken(), poolKey.userName);
187 } else {
188 session = new EwsExchangeSession(authenticator.getExchangeUri(), authenticator.getToken(), poolKey.userName);
189 }
190
191 } else if (Settings.EWS.equals(mode) || Settings.O365.equals(mode)
192
193 || poolKey.url.toLowerCase().endsWith("/ews/exchange.asmx")
194 || poolKey.url.toLowerCase().endsWith("/ews/services.wsdl")) {
195 if (poolKey.url.toLowerCase().endsWith("/ews/exchange.asmx")
196 || poolKey.url.toLowerCase().endsWith("/ews/services.wsdl")) {
197 ExchangeSession.LOGGER.debug("Direct EWS authentication");
198 session = new EwsExchangeSession(poolKey.url, poolKey.userName, poolKey.password);
199 } else {
200 ExchangeSession.LOGGER.debug("OWA authentication in EWS mode");
201 ExchangeFormAuthenticator exchangeFormAuthenticator = new ExchangeFormAuthenticator();
202 exchangeFormAuthenticator.setUrl(poolKey.url);
203 exchangeFormAuthenticator.setUsername(poolKey.userName);
204 exchangeFormAuthenticator.setPassword(poolKey.password);
205 exchangeFormAuthenticator.authenticate();
206 session = new EwsExchangeSession(exchangeFormAuthenticator.getHttpClientAdapter(),
207 exchangeFormAuthenticator.getExchangeUri(), exchangeFormAuthenticator.getUsername());
208 }
209 } else {
210 ExchangeFormAuthenticator exchangeFormAuthenticator = new ExchangeFormAuthenticator();
211 exchangeFormAuthenticator.setUrl(poolKey.url);
212 exchangeFormAuthenticator.setUsername(poolKey.userName);
213 exchangeFormAuthenticator.setPassword(poolKey.password);
214 exchangeFormAuthenticator.authenticate();
215 try {
216 session = new DavExchangeSession(exchangeFormAuthenticator.getHttpClientAdapter(),
217 exchangeFormAuthenticator.getExchangeUri(),
218 exchangeFormAuthenticator.getUsername());
219 } catch (WebdavNotAvailableException e) {
220 if (Settings.AUTO.equals(mode)) {
221 ExchangeSession.LOGGER.debug(e.getMessage() + ", retry with EWS");
222 session = new EwsExchangeSession(poolKey.url, poolKey.userName, poolKey.password);
223 } else {
224 throw e;
225 }
226 }
227 }
228 checkWhiteList(session.getEmail());
229 ExchangeSession.LOGGER.debug("Created new session " + session + " for user " + poolKey.userName);
230 }
231
232 synchronized (LOCK) {
233 POOL_MAP.put(poolKey, session);
234 }
235
236 configChecked = true;
237
238 errorSent = false;
239 } catch (DavMailException | IllegalStateException | NullPointerException exc) {
240 throw exc;
241 } catch (Exception exc) {
242 handleNetworkDown(exc);
243 }
244 return session;
245 }
246
247
248
249
250
251
252
253
254 private static void checkWhiteList(String email) throws DavMailAuthenticationException {
255 String whiteListString = Settings.getProperty("davmail.userWhiteList");
256 if (whiteListString != null && !whiteListString.isEmpty()) {
257 for (String whiteListvalue : whiteListString.split(",")) {
258 if (whiteListvalue.startsWith("@") && email.endsWith(whiteListvalue)) {
259 return;
260 } else if (email.equalsIgnoreCase(whiteListvalue)) {
261 return;
262 }
263 }
264 ExchangeSession.LOGGER.warn(email + " not allowed by whitelist");
265 throw new DavMailAuthenticationException("EXCEPTION_AUTHENTICATION_FAILED");
266 }
267 }
268
269
270
271
272
273
274
275
276
277
278
279 public static ExchangeSession getInstance(ExchangeSession currentSession, String userName, String password)
280 throws IOException {
281 ExchangeSession session = currentSession;
282 try {
283 if (session.isExpired()) {
284 ExchangeSession.LOGGER.debug("Session " + session + " expired, trying to open a new one");
285 session = null;
286 String baseUrl = Settings.getProperty("davmail.url", Settings.getO365Url());
287 PoolKey poolKey = new PoolKey(baseUrl, userName, password);
288
289 synchronized (LOCK) {
290 POOL_MAP.remove(poolKey);
291 }
292 session = getInstance(userName, password);
293 }
294 } catch (DavMailAuthenticationException exc) {
295 ExchangeSession.LOGGER.debug("Unable to reopen session", exc);
296 throw exc;
297 } catch (Exception exc) {
298 ExchangeSession.LOGGER.debug("Unable to reopen session", exc);
299 handleNetworkDown(exc);
300 }
301 return session;
302 }
303
304
305
306
307
308
309 public static void checkConfig() throws IOException {
310 String url = Settings.getProperty("davmail.url", Settings.getO365Url());
311 if (url == null || (!url.startsWith("http://") && !url.startsWith("https://"))) {
312 throw new DavMailException("LOG_INVALID_URL", url);
313 }
314 try (
315 HttpClientAdapter httpClientAdapter = new HttpClientAdapter(url);
316 CloseableHttpResponse response = httpClientAdapter.execute(new GetRequest(url))
317 ) {
318
319 int status = response.getStatusLine().getStatusCode();
320 ExchangeSession.LOGGER.debug("Test configuration status: " + status);
321 if (status != HttpStatus.SC_OK && status != HttpStatus.SC_UNAUTHORIZED
322 && !HttpClientAdapter.isRedirect(status)) {
323 throw new DavMailException("EXCEPTION_CONNECTION_FAILED", url, status);
324 }
325
326 configChecked = true;
327
328 errorSent = false;
329 } catch (Exception exc) {
330 handleNetworkDown(exc);
331 }
332
333 }
334
335 private static void handleNetworkDown(Exception exc) throws DavMailException {
336 if (!checkNetwork() || configChecked) {
337 ExchangeSession.LOGGER.warn(BundleMessage.formatLog("EXCEPTION_NETWORK_DOWN"));
338
339 if (!((exc instanceof UnknownHostException) || (exc instanceof NetworkDownException))) {
340 ExchangeSession.LOGGER.debug(exc, exc);
341 }
342 throw new NetworkDownException("EXCEPTION_NETWORK_DOWN");
343 } else {
344 BundleMessage message = new BundleMessage("EXCEPTION_CONNECT", exc.getClass().getName(), exc.getMessage());
345 if (errorSent) {
346 ExchangeSession.LOGGER.warn(message);
347 throw new NetworkDownException("EXCEPTION_DAVMAIL_CONFIGURATION", message);
348 } else {
349
350
351 errorSent = true;
352 ExchangeSession.LOGGER.error(message);
353 throw new DavMailException("EXCEPTION_DAVMAIL_CONFIGURATION", message);
354 }
355 }
356 }
357
358
359
360
361
362
363
364 public static String getUserPassword(String userName) {
365 String fullUserName = convertUserName(userName);
366 for (PoolKey poolKey : POOL_MAP.keySet()) {
367 if (poolKey.userName.equals(fullUserName)) {
368 return poolKey.password;
369 }
370 }
371 return null;
372 }
373
374
375
376
377
378
379 static boolean checkNetwork() {
380 boolean up = false;
381 Enumeration<NetworkInterface> enumeration;
382 try {
383 enumeration = NetworkInterface.getNetworkInterfaces();
384 if (enumeration != null) {
385 while (!up && enumeration.hasMoreElements()) {
386 NetworkInterface networkInterface = enumeration.nextElement();
387 up = networkInterface.isUp() && !networkInterface.isLoopback()
388 && networkInterface.getInetAddresses().hasMoreElements();
389 }
390 }
391 } catch (NoSuchMethodError error) {
392 ExchangeSession.LOGGER.debug("Unable to test network interfaces (not available under Java 1.5)");
393 up = true;
394 } catch (SocketException exc) {
395 ExchangeSession.LOGGER.error("DavMail configuration exception: \n Error listing network interfaces " + exc.getMessage(), exc);
396 }
397 return up;
398 }
399
400
401
402
403 public static void shutdown() {
404 configChecked = false;
405 errorSent = false;
406 synchronized (LOCK) {
407 for (ExchangeSession session:POOL_MAP.values()) {
408 session.close();
409 }
410 POOL_MAP.clear();
411 }
412 }
413 }