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