1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package davmail;
20
21 import davmail.ui.tray.DavGatewayTray;
22 import org.apache.log4j.*;
23
24 import java.io.*;
25 import java.nio.charset.StandardCharsets;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.nio.file.attribute.FileAttribute;
30 import java.nio.file.attribute.PosixFilePermissions;
31 import java.util.*;
32
33 import static org.apache.http.util.TextUtils.isEmpty;
34
35
36
37
38
39
40 public final class Settings {
41
42 private static final Logger LOGGER = Logger.getLogger(Settings.class);
43
44 public static final String OUTLOOK_URL = "https://outlook.office365.com";
45 public static final String O365_URL = OUTLOOK_URL+"/EWS/Exchange.asmx";
46
47 public static final String GRAPH_URL = "https://graph.microsoft.com";
48
49 public static final String O365_LOGIN_URL = "https://login.microsoftonline.com";
50
51 public static final String O365 = "O365";
52 public static final String O365_MODERN = "O365Modern";
53 public static final String O365_INTERACTIVE = "O365Interactive";
54 public static final String O365_MANUAL = "O365Manual";
55 public static final String WEBDAV = "WebDav";
56 public static final String EWS = "EWS";
57 public static final String AUTO = "Auto";
58
59 public static final String EDGE_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 Edg/90.0.818.49";
60
61 private Settings() {
62 }
63
64 private static final Properties SETTINGS_PROPERTIES = new Properties() {
65 @Override
66 public synchronized Enumeration<Object> keys() {
67 Enumeration<Object> keysEnumeration = super.keys();
68 TreeSet<String> sortedKeySet = new TreeSet<>();
69 while (keysEnumeration.hasMoreElements()) {
70 sortedKeySet.add((String) keysEnumeration.nextElement());
71 }
72 final Iterator<String> sortedKeysIterator = sortedKeySet.iterator();
73 return new Enumeration<Object>() {
74
75 public boolean hasMoreElements() {
76 return sortedKeysIterator.hasNext();
77 }
78
79 public Object nextElement() {
80 return sortedKeysIterator.next();
81 }
82 };
83 }
84
85 };
86 private static String configFilePath;
87 private static boolean isFirstStart;
88
89
90
91
92
93
94 public static synchronized void setConfigFilePath(String path) {
95 configFilePath = path;
96 }
97
98
99
100
101
102
103 public static synchronized boolean isFirstStart() {
104 return isFirstStart;
105 }
106
107
108
109
110
111
112
113 public static synchronized void load(InputStream inputStream) throws IOException {
114 SETTINGS_PROPERTIES.load(inputStream);
115 updateLoggingConfig();
116 }
117
118
119
120
121 public static synchronized void load() {
122 try {
123 if (configFilePath == null) {
124
125 configFilePath = System.getProperty("user.home") + "/.davmail.properties";
126 }
127 File configFile = new File(configFilePath);
128 if (configFile.exists()) {
129 try (FileInputStream fileInputStream = new FileInputStream(configFile)) {
130 load(fileInputStream);
131 }
132 } else {
133 isFirstStart = true;
134
135
136 setDefaultSettings();
137 save();
138 }
139 } catch (IOException e) {
140 DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_LOAD_SETTINGS"), e);
141 }
142 updateLoggingConfig();
143 }
144
145
146
147
148
149 public static void setDefaultSettings() {
150 SETTINGS_PROPERTIES.put("davmail.mode", "EWS");
151 SETTINGS_PROPERTIES.put("davmail.url", getO365Url());
152 SETTINGS_PROPERTIES.put("davmail.popPort", "1110");
153 SETTINGS_PROPERTIES.put("davmail.imapPort", "1143");
154 SETTINGS_PROPERTIES.put("davmail.smtpPort", "1025");
155 SETTINGS_PROPERTIES.put("davmail.caldavPort", "1080");
156 SETTINGS_PROPERTIES.put("davmail.ldapPort", "1389");
157 SETTINGS_PROPERTIES.put("davmail.clientSoTimeout", "");
158 SETTINGS_PROPERTIES.put("davmail.keepDelay", "30");
159 SETTINGS_PROPERTIES.put("davmail.sentKeepDelay", "0");
160 SETTINGS_PROPERTIES.put("davmail.caldavPastDelay", "0");
161 SETTINGS_PROPERTIES.put("davmail.caldavAutoSchedule", Boolean.TRUE.toString());
162 SETTINGS_PROPERTIES.put("davmail.imapIdleDelay", "");
163 SETTINGS_PROPERTIES.put("davmail.folderSizeLimit", "");
164 SETTINGS_PROPERTIES.put("davmail.enableKeepAlive", Boolean.FALSE.toString());
165 SETTINGS_PROPERTIES.put("davmail.allowRemote", Boolean.FALSE.toString());
166 SETTINGS_PROPERTIES.put("davmail.bindAddress", "");
167 SETTINGS_PROPERTIES.put("davmail.useSystemProxies", Boolean.FALSE.toString());
168 SETTINGS_PROPERTIES.put("davmail.enableProxy", Boolean.FALSE.toString());
169 SETTINGS_PROPERTIES.put("davmail.enableKerberos", "false");
170 SETTINGS_PROPERTIES.put("davmail.disableUpdateCheck", "false");
171 SETTINGS_PROPERTIES.put("davmail.proxyHost", "");
172 SETTINGS_PROPERTIES.put("davmail.proxyPort", "");
173 SETTINGS_PROPERTIES.put("davmail.proxyUser", "");
174 SETTINGS_PROPERTIES.put("davmail.proxyPassword", "");
175 SETTINGS_PROPERTIES.put("davmail.noProxyFor", "");
176 SETTINGS_PROPERTIES.put("davmail.server", Boolean.FALSE.toString());
177 SETTINGS_PROPERTIES.put("davmail.server.certificate.hash", "");
178 SETTINGS_PROPERTIES.put("davmail.caldavAlarmSound", "");
179 SETTINGS_PROPERTIES.put("davmail.carddavReadPhoto", Boolean.TRUE.toString());
180 SETTINGS_PROPERTIES.put("davmail.forceActiveSyncUpdate", Boolean.FALSE.toString());
181 SETTINGS_PROPERTIES.put("davmail.showStartupBanner", Boolean.TRUE.toString());
182 SETTINGS_PROPERTIES.put("davmail.disableGuiNotifications", Boolean.FALSE.toString());
183 SETTINGS_PROPERTIES.put("davmail.disableTrayActivitySwitch", Boolean.FALSE.toString());
184 SETTINGS_PROPERTIES.put("davmail.imapAutoExpunge", Boolean.TRUE.toString());
185 SETTINGS_PROPERTIES.put("davmail.imapAlwaysApproxMsgSize", Boolean.FALSE.toString());
186 SETTINGS_PROPERTIES.put("davmail.popMarkReadOnRetr", Boolean.FALSE.toString());
187 SETTINGS_PROPERTIES.put("davmail.smtpSaveInSent", Boolean.TRUE.toString());
188 SETTINGS_PROPERTIES.put("davmail.ssl.keystoreType", "");
189 SETTINGS_PROPERTIES.put("davmail.ssl.keystoreFile", "");
190 SETTINGS_PROPERTIES.put("davmail.ssl.keystorePass", "");
191 SETTINGS_PROPERTIES.put("davmail.ssl.keyPass", "");
192 if (isWindows()) {
193
194 SETTINGS_PROPERTIES.put("davmail.ssl.clientKeystoreType", "MSCAPI");
195 } else {
196 SETTINGS_PROPERTIES.put("davmail.ssl.clientKeystoreType", "");
197 }
198 SETTINGS_PROPERTIES.put("davmail.ssl.clientKeystoreFile", "");
199 SETTINGS_PROPERTIES.put("davmail.ssl.clientKeystorePass", "");
200 SETTINGS_PROPERTIES.put("davmail.ssl.pkcs11Library", "");
201 SETTINGS_PROPERTIES.put("davmail.ssl.pkcs11Config", "");
202 SETTINGS_PROPERTIES.put("davmail.ssl.nosecurepop", Boolean.FALSE.toString());
203 SETTINGS_PROPERTIES.put("davmail.ssl.nosecureimap", Boolean.FALSE.toString());
204 SETTINGS_PROPERTIES.put("davmail.ssl.nosecuresmtp", Boolean.FALSE.toString());
205 SETTINGS_PROPERTIES.put("davmail.ssl.nosecurecaldav", Boolean.FALSE.toString());
206 SETTINGS_PROPERTIES.put("davmail.ssl.nosecureldap", Boolean.FALSE.toString());
207
208
209 SETTINGS_PROPERTIES.put("log4j.rootLogger", Level.WARN.toString());
210 SETTINGS_PROPERTIES.put("log4j.logger.davmail", Level.DEBUG.toString());
211 SETTINGS_PROPERTIES.put("log4j.logger.httpclient.wire", Level.WARN.toString());
212 SETTINGS_PROPERTIES.put("log4j.logger.httpclient", Level.WARN.toString());
213 SETTINGS_PROPERTIES.put("davmail.logFilePath", "");
214 }
215
216
217
218
219
220
221 public static String getLogFilePath() {
222 String logFilePath = Settings.getProperty("davmail.logFilePath");
223
224 if ((logFilePath == null || logFilePath.isEmpty())) {
225 if (Settings.getBooleanProperty("davmail.server")) {
226 logFilePath = "davmail.log";
227 } else if (System.getProperty("os.name").toLowerCase().startsWith("mac os x")) {
228
229 logFilePath = System.getProperty("user.home") + "/Library/Logs/DavMail/davmail.log";
230 } else {
231
232 logFilePath = System.getProperty("user.home") + "/davmail.log";
233 }
234 } else {
235 File logFile = new File(logFilePath);
236 if (logFile.isDirectory()) {
237 logFilePath += "/davmail.log";
238 }
239 }
240 return logFilePath;
241 }
242
243
244
245
246
247
248 public static String getLogFileDirectory() {
249 String logFilePath = getLogFilePath();
250 if (logFilePath == null || logFilePath.isEmpty()) {
251 return ".";
252 }
253 int lastSlashIndex = logFilePath.lastIndexOf('/');
254 if (lastSlashIndex == -1) {
255 lastSlashIndex = logFilePath.lastIndexOf('\\');
256 }
257 if (lastSlashIndex >= 0) {
258 return logFilePath.substring(0, lastSlashIndex);
259 } else {
260 return ".";
261 }
262 }
263
264
265
266
267 public static void updateLoggingConfig() {
268 String logFilePath = getLogFilePath();
269
270 try {
271 if (logFilePath != null && !logFilePath.isEmpty()) {
272 File logFile = new File(logFilePath);
273
274 File logFileDir = logFile.getParentFile();
275 if (logFileDir != null && !logFileDir.exists() && (!logFileDir.mkdirs())) {
276 DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_CREATE_LOG_FILE_DIR"));
277 throw new IOException();
278
279 }
280 } else {
281 logFilePath = "davmail.log";
282 }
283 synchronized (Logger.getRootLogger()) {
284
285 FileAppender fileAppender = (FileAppender) Logger.getRootLogger().getAppender("FileAppender");
286 if (fileAppender == null) {
287 String logFileSize = Settings.getProperty("davmail.logFileSize");
288 if (logFileSize == null || logFileSize.isEmpty()) {
289 logFileSize = "1MB";
290 }
291
292 if ("0".equals(logFileSize)) {
293 fileAppender = new FileAppender();
294 } else {
295 fileAppender = new RollingFileAppender();
296 ((RollingFileAppender) fileAppender).setMaxBackupIndex(2);
297 ((RollingFileAppender) fileAppender).setMaxFileSize(logFileSize);
298 }
299 fileAppender.setName("FileAppender");
300 fileAppender.setEncoding("UTF-8");
301 fileAppender.setLayout(new PatternLayout("%d{ISO8601} %-5p [%t] %c %x - %m%n"));
302 }
303 fileAppender.setFile(logFilePath, true, false, 8192);
304 Logger.getRootLogger().addAppender(fileAppender);
305 }
306
307
308 ConsoleAppender consoleAppender = (ConsoleAppender) Logger.getRootLogger().getAppender("ConsoleAppender");
309 if (consoleAppender != null) {
310 if (Settings.getBooleanProperty("davmail.server")) {
311 consoleAppender.setThreshold(Level.ALL);
312 } else {
313 consoleAppender.setThreshold(Level.OFF);
314 }
315 }
316
317 } catch (IOException e) {
318 DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_SET_LOG_FILE_PATH"));
319 }
320
321
322 Settings.setLoggingLevel("rootLogger", Settings.getLoggingLevel("rootLogger"));
323 Settings.setLoggingLevel("davmail", Settings.getLoggingLevel("davmail"));
324
325 Settings.setLoggingLevel("org.apache.http.wire", Settings.getLoggingLevel("httpclient.wire"));
326 Settings.setLoggingLevel("org.apache.http.conn.ssl", Settings.getLoggingLevel("httpclient.wire"));
327 Settings.setLoggingLevel("org.apache.http", Settings.getLoggingLevel("httpclient"));
328 }
329
330
331
332
333 public static synchronized void save() {
334
335 if (configFilePath != null) {
336
337 Properties properties = new Properties();
338 properties.putAll(SETTINGS_PROPERTIES);
339
340 ArrayList<String> lines = new ArrayList<>();
341
342
343 Path path = Paths.get(configFilePath);
344 if (!Files.exists(path) && isUnix()) {
345 FileAttribute<?> permissions = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-------"));
346 try {
347 Files.createFile(path, permissions);
348 } catch (IOException e) {
349 LOGGER.error(e.getMessage());
350 }
351 }
352
353 readLines(lines, properties);
354
355 try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.ISO_8859_1))) {
356 for (String value : lines) {
357 writer.write(value);
358 writer.newLine();
359 }
360
361
362 Enumeration<?> propertyEnumeration = properties.propertyNames();
363 while (propertyEnumeration.hasMoreElements()) {
364 String propertyName = (String) propertyEnumeration.nextElement();
365 writer.write(propertyName + "=" + escapeValue(properties.getProperty(propertyName)));
366 writer.newLine();
367 }
368 } catch (IOException e) {
369 DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_STORE_SETTINGS"), e);
370 }
371 }
372 updateLoggingConfig();
373 }
374
375 private static void readLines(ArrayList<String> lines, Properties properties) {
376 try {
377 File configFile = new File(configFilePath);
378 if (configFile.exists()) {
379 try (BufferedReader reader = new BufferedReader(new InputStreamReader(Files.newInputStream(configFile.toPath()), StandardCharsets.ISO_8859_1))) {
380 String line;
381 while ((line = reader.readLine()) != null) {
382 lines.add(convertLine(line, properties));
383 }
384 }
385 }
386 } catch (IOException e) {
387 DavGatewayTray.error(new BundleMessage("LOG_UNABLE_TO_LOAD_SETTINGS"), e);
388 }
389 }
390
391
392
393
394
395
396
397
398
399 private static String convertLine(String line, Properties properties) {
400 int hashIndex = line.indexOf('#');
401 int equalsIndex = line.indexOf('=');
402
403
404 if (equalsIndex >= 0 && (hashIndex < 0 || hashIndex >= equalsIndex)) {
405 String key = line.substring(0, equalsIndex);
406 String value = properties.getProperty(key);
407 if (value != null) {
408
409 line = key + "=" + escapeValue(value);
410
411 properties.remove(key);
412 }
413 }
414 return line;
415 }
416
417
418
419
420
421
422
423 private static String escapeValue(String value) {
424 StringBuilder buffer = new StringBuilder();
425 for (char c : value.toCharArray()) {
426 if (c == '\\') {
427 buffer.append('\\');
428 }
429 buffer.append(c);
430 }
431 return buffer.toString();
432 }
433
434
435
436
437
438
439
440
441 public static synchronized String getProperty(String property) {
442 String value = SETTINGS_PROPERTIES.getProperty(property);
443
444 if (value != null && value.isEmpty()) {
445 value = null;
446 }
447 return value;
448 }
449
450
451
452
453
454
455
456
457 public static synchronized String getProperty(String property, String defaultValue) {
458 String value = getProperty(property);
459 if (value == null) {
460 value = defaultValue;
461 }
462 return value;
463 }
464
465
466
467
468
469
470
471
472 public static synchronized char[] getCharArrayProperty(String property) {
473 String propertyValue = Settings.getProperty(property);
474 char[] value = null;
475 if (propertyValue != null) {
476 value = propertyValue.toCharArray();
477 }
478 return value;
479 }
480
481
482
483
484
485
486
487 public static synchronized void setProperty(String property, String value) {
488 if (value != null) {
489 SETTINGS_PROPERTIES.setProperty(property, value);
490 } else {
491 SETTINGS_PROPERTIES.setProperty(property, "");
492 }
493 }
494
495
496
497
498
499
500
501 public static synchronized int getIntProperty(String property) {
502 return getIntProperty(property, 0);
503 }
504
505
506
507
508
509
510
511
512 public static synchronized int getIntProperty(String property, int defaultValue) {
513 int value = defaultValue;
514 try {
515 String propertyValue = SETTINGS_PROPERTIES.getProperty(property);
516 if (propertyValue != null && !propertyValue.isEmpty()) {
517 value = Integer.parseInt(propertyValue);
518 }
519 } catch (NumberFormatException e) {
520 DavGatewayTray.error(new BundleMessage("LOG_INVALID_SETTING_VALUE", property), e);
521 }
522 return value;
523 }
524
525
526
527
528
529
530
531 public static synchronized boolean getBooleanProperty(String property) {
532 String propertyValue = SETTINGS_PROPERTIES.getProperty(property);
533 return Boolean.parseBoolean(propertyValue);
534 }
535
536
537
538
539
540
541
542
543 public static synchronized boolean getBooleanProperty(String property, boolean defaultValue) {
544 boolean value = defaultValue;
545 String propertyValue = SETTINGS_PROPERTIES.getProperty(property);
546 if (propertyValue != null && !propertyValue.isEmpty()) {
547 value = Boolean.parseBoolean(propertyValue);
548 }
549 return value;
550 }
551
552 public static synchronized String loadRefreshToken(String username) {
553 String tokenFilePath = Settings.getProperty("davmail.oauth.tokenFilePath");
554 if (isEmpty(tokenFilePath)) {
555 return Settings.getProperty("davmail.oauth." + username.toLowerCase() + ".refreshToken");
556 } else {
557 return loadtokenFromFile(tokenFilePath, username.toLowerCase());
558 }
559 }
560
561
562 public static synchronized void storeRefreshToken(String username, String refreshToken) {
563 String tokenFilePath = Settings.getProperty("davmail.oauth.tokenFilePath");
564 if (isEmpty(tokenFilePath)) {
565 Settings.setProperty("davmail.oauth." + username.toLowerCase() + ".refreshToken", refreshToken);
566 Settings.save();
567 } else {
568 savetokentoFile(tokenFilePath, username.toLowerCase(), refreshToken);
569 }
570 }
571
572
573
574
575
576
577
578
579 private static void savetokentoFile(String tokenFilePath, String username, String refreshToken) {
580 try {
581 checkCreateTokenFilePath(tokenFilePath);
582 Properties properties = new Properties();
583 try (FileInputStream fis = new FileInputStream(tokenFilePath)) {
584 properties.load(fis);
585 }
586 properties.setProperty(username, refreshToken);
587 try (FileOutputStream fos = new FileOutputStream(tokenFilePath)) {
588 properties.store(fos, "Oauth tokens");
589 }
590 } catch (IOException e) {
591 Logger.getLogger(Settings.class).warn(e + " " + e.getMessage());
592 }
593 }
594
595
596
597
598
599
600
601
602 private static String loadtokenFromFile(String tokenFilePath, String username) {
603 try {
604 checkCreateTokenFilePath(tokenFilePath);
605 Properties properties = new Properties();
606 try (FileInputStream fis = new FileInputStream(tokenFilePath)) {
607 properties.load(fis);
608 }
609 return properties.getProperty(username);
610 } catch (IOException e) {
611 Logger.getLogger(Settings.class).warn(e + " " + e.getMessage());
612 }
613 return null;
614 }
615
616 private static void checkCreateTokenFilePath(String tokenFilePath) throws IOException {
617 File file = new File(tokenFilePath);
618 File parentFile = file.getParentFile();
619 if (parentFile != null && (parentFile.mkdirs())) {
620 LOGGER.info("Created token file directory "+parentFile.getAbsolutePath());
621
622 }
623 if (file.createNewFile()) {
624 LOGGER.info("Created token file "+tokenFilePath);
625 }
626 }
627
628
629
630
631
632
633
634 private static String getLoggingPrefix(String category) {
635 String prefix;
636 if ("rootLogger".equals(category)) {
637 prefix = "log4j.";
638 } else {
639 prefix = "log4j.logger.";
640 }
641 return prefix;
642 }
643
644
645
646
647
648
649
650 public static synchronized Level getLoggingLevel(String category) {
651 String prefix = getLoggingPrefix(category);
652 String currentValue = SETTINGS_PROPERTIES.getProperty(prefix + category);
653
654 if (currentValue != null && !currentValue.isEmpty()) {
655 return Level.toLevel(currentValue);
656 } else if ("rootLogger".equals(category)) {
657 return Logger.getRootLogger().getLevel();
658 } else {
659 return Logger.getLogger(category).getLevel();
660 }
661 }
662
663
664
665
666
667
668
669 public static synchronized Properties getSubProperties(String scope) {
670 final String keyStart;
671 if (scope == null || scope.isEmpty()) {
672 keyStart = "";
673 } else if (scope.endsWith(".")) {
674 keyStart = scope;
675 } else {
676 keyStart = scope + '.';
677 }
678 Properties result = new Properties();
679 for (Map.Entry<Object, Object> entry : SETTINGS_PROPERTIES.entrySet()) {
680 String key = (String) entry.getKey();
681 if (key.startsWith(keyStart)) {
682 String value = (String) entry.getValue();
683 result.setProperty(key.substring(keyStart.length()), value);
684 }
685 }
686 return result;
687 }
688
689
690
691
692
693
694
695 public static synchronized void setLoggingLevel(String category, Level level) {
696 if (level != null) {
697 String prefix = getLoggingPrefix(category);
698 SETTINGS_PROPERTIES.setProperty(prefix + category, level.toString());
699 if ("rootLogger".equals(category)) {
700 Logger.getRootLogger().setLevel(level);
701 } else {
702 Logger.getLogger(category).setLevel(level);
703 }
704 }
705 }
706
707
708
709
710
711
712
713 public static synchronized void saveProperty(String property, String value) {
714 Settings.load();
715 Settings.setProperty(property, value);
716 Settings.save();
717 }
718
719
720
721
722
723
724 public static boolean isWindows() {
725 return System.getProperty("os.name").toLowerCase().startsWith("windows");
726 }
727
728
729
730
731
732
733 public static boolean isLinux() {
734 return System.getProperty("os.name").toLowerCase().startsWith("linux");
735 }
736
737 public static boolean isUnix() {
738 return isLinux() ||
739 System.getProperty("os.name").toLowerCase().startsWith("freebsd");
740 }
741
742 public static String getUserAgent() {
743 return getProperty("davmail.userAgent", Settings.EDGE_USER_AGENT);
744 }
745
746 public static String getOutlookUrl() {
747 String tld = getProperty("davmail.tld");
748 String outlookUrl = getProperty("davmail.outlookUrl");
749 if (outlookUrl != null) {
750 return outlookUrl;
751 } else if (tld == null) {
752 return OUTLOOK_URL;
753 } else {
754 return "https://outlook.office365."+tld;
755 }
756 }
757
758 public static String getO365Url() {
759 String tld = getProperty("davmail.tld");
760 String outlookUrl = getProperty("davmail.outlookUrl");
761 if (outlookUrl != null) {
762 return outlookUrl+"/EWS/Exchange.asmx";
763 } else if (tld == null) {
764 return O365_URL;
765 } else {
766 return "https://outlook.office365."+tld+"/EWS/Exchange.asmx";
767 }
768 }
769
770 public static String getO365LoginUrl() {
771 String tld = getProperty("davmail.tld");
772 String loginUrl = getProperty("davmail.loginUrl");
773 if (loginUrl != null) {
774 return loginUrl;
775 } else if (tld == null) {
776 return O365_LOGIN_URL;
777 } else {
778 return "https://login.microsoftonline."+tld;
779 }
780 }
781 }