001/*
002 * Syncany, www.syncany.org
003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com>
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation, either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018package org.syncany.config;
019
020import javax.net.ssl.SSLContext;
021import java.io.File;
022import java.io.FileInputStream;
023import java.io.FileOutputStream;
024import java.security.KeyStore;
025import java.util.Map;
026
027import org.syncany.config.to.UserConfigTO;
028import org.syncany.crypto.CipherException;
029import org.syncany.crypto.CipherUtil;
030import org.syncany.crypto.SaltedSecretKey;
031import org.syncany.util.EnvironmentUtil;
032
033/**
034 * Represents the configuration parameters and application user directory
035 * of the currently logged in user, including system properties that will be
036 * set with every application start.
037 *
038 * @author Philipp C. Heckel (philipp.heckel@gmail.com)
039 */
040public class UserConfig {
041        /*
042         * Note:
043         *    This class can't have any logging methods, because the init() method is called
044         *    BEFORE the logging initialization. All errors must be printed to STDERR.
045         */
046
047        // Daemon-specific config
048        public static final String DAEMON_FILE = "daemon.xml";
049        public static final String DAEMON_EXAMPLE_FILE = "daemon-example.xml";
050        public static final String DEFAULT_FOLDER = "Syncany";
051        public static final String USER_ADMIN = "admin";
052        public static final String USER_CLI = "CLI";
053
054        // These fields are not final to enable a PluginOperationTest
055        private static File USER_APP_DIR_WINDOWS = new File(System.getenv("APPDATA") + "\\Syncany");
056        private static File USER_APP_DIR_UNIX_LIKE = new File(System.getProperty("user.home") + "/.config/syncany");
057        private static final String USER_LOG_DIR = "logs";
058        private static final String USER_PLUGINS_LIB_DIR = "plugins/lib";
059        private static final String USER_PLUGINS_USERDATA_DIR_FORMAT = "plugins/userdata/%s";
060        private static final String USER_CONFIG_FILE = "userconfig.xml";
061        private static final String USER_TRUSTSTORE_FILE = "truststore.jks";
062        private static final String USER_KEYSTORE_FILE = "keystore.jks";
063        private static final int USER_CONFIG_ENCRYPTION_KEY_LENGTH = 32;
064
065        private static File userConfigDir;
066        private static File userLogDir;
067        private static File userPluginLibDir;
068        private static File userConfigFile;
069
070        private static File userTrustStoreFile;
071        private static KeyStore userTrustStore;
072
073        private static File userKeyStoreFile;
074        private static KeyStore userKeyStore;
075
076        private static boolean preventStandby;
077        private static SaltedSecretKey configEncryptionKey;
078
079        static {
080                init();
081        }
082
083        public static void init() {
084                if (userConfigDir == null) {
085                        initUserAppDirs();
086                        initUserConfig();
087                        initUserTrustStore();
088                        initUserKeyStore();
089                }
090        }
091
092        public static File getUserConfigDir() {
093                return userConfigDir;
094        }
095
096        public static File getUserLogDir() {
097                return userLogDir;
098        }
099
100        public static File getUserPluginLibDir() {
101                return userPluginLibDir;
102        }
103
104        public static File getUserPluginsUserdataDir(String pluginId) {
105                File pluginConfigDir = new File(userConfigDir, String.format(USER_PLUGINS_USERDATA_DIR_FORMAT, pluginId));
106                pluginConfigDir.mkdirs();
107
108                return pluginConfigDir;
109        }
110
111        public static File getUserConfigFile() {
112                return userConfigFile;
113        }
114
115        public static boolean isPreventStandby() {
116                return preventStandby;
117        }
118        
119        public static void setPreventStandby(boolean newPreventStandby) {
120                preventStandby = newPreventStandby;
121        }
122
123        public static SaltedSecretKey getConfigEncryptionKey() {
124                return configEncryptionKey;
125        }
126
127        public static KeyStore getUserTrustStore() {
128                // Note: This method might not be used by the main project modules,
129                // but it might be used by plugins. Do not remove unless you are
130                // sure that it is not needed.
131
132                return userTrustStore;
133        }
134
135        public static KeyStore getUserKeyStore() {
136                return userKeyStore;
137        }
138
139        public static void storeTrustStore() {
140                storeKeyStore(userTrustStore, userTrustStoreFile);
141        }
142
143        public static void storeUserKeyStore() {
144                storeKeyStore(userKeyStore, userKeyStoreFile);
145        }
146
147        public static SSLContext createUserSSLContext() throws Exception {
148                return CipherUtil.createSSLContext(userKeyStore, userTrustStore);
149        }
150
151        // General initialization methods
152
153        private static void initUserAppDirs() {
154                userConfigDir = (EnvironmentUtil.isWindows()) ? USER_APP_DIR_WINDOWS : USER_APP_DIR_UNIX_LIKE;
155                userConfigDir.mkdirs();
156
157                userLogDir = new File(userConfigDir, USER_LOG_DIR);
158                userLogDir.mkdirs();
159
160                userPluginLibDir = new File(userConfigDir, USER_PLUGINS_LIB_DIR);
161                userPluginLibDir.mkdirs();
162        }
163
164        private static void initUserConfig() {
165                userConfigFile = new File(userConfigDir, USER_CONFIG_FILE);
166
167                if (userConfigFile.exists()) {
168                        loadAndInitUserConfigFile(userConfigFile);
169                }
170                else {
171                        writeExampleUserConfigFile(userConfigFile);
172                        loadAndInitUserConfigFile(userConfigFile);
173                }
174        }
175
176        private static void loadAndInitUserConfigFile(File userConfigFile) {
177                try {
178                        UserConfigTO userConfigTO = UserConfigTO.load(userConfigFile);
179
180                        // System properties
181                        for (Map.Entry<String, String> systemProperty : userConfigTO.getSystemProperties().entrySet()) {
182                                String propertyValue = (systemProperty.getValue() != null) ? systemProperty.getValue() : ""; 
183                                System.setProperty(systemProperty.getKey(), propertyValue);
184                        }
185
186                        // Other options
187                        preventStandby = userConfigTO.isPreventStandby();
188                        configEncryptionKey = userConfigTO.getConfigEncryptionKey();
189                }
190                catch (ConfigException e) {
191                        System.err.println("ERROR: " + e.getMessage());
192                        System.err.println("       Ignoring user config file!");
193                        System.err.println();
194                }
195        }
196
197        private static void writeExampleUserConfigFile(File userConfigFile) {
198                UserConfigTO userConfigTO = new UserConfigTO();
199
200                try {
201                        System.out.println("First launch, creating a secret key (could take a sec)...");
202                        SaltedSecretKey configEncryptionKey = CipherUtil.createMasterKey(CipherUtil.createRandomAlphabeticString(USER_CONFIG_ENCRYPTION_KEY_LENGTH));
203
204                        userConfigTO.setConfigEncryptionKey(configEncryptionKey);
205                        userConfigTO.save(userConfigFile);
206                }
207                catch (CipherException e) {
208                        System.err.println("ERROR: " + e.getMessage());
209                        System.err.println("       Failed to create masterkey.");
210                        System.err.println();
211                }
212                catch (ConfigException e) {
213                        System.err.println("ERROR: " + e.getMessage());
214                        System.err.println("       Failed to save to file.");
215                        System.err.println();
216                }
217        }
218
219        // Key store / Trust store methods
220
221        private static void initUserTrustStore() {
222                userTrustStoreFile = new File(userConfigDir, USER_TRUSTSTORE_FILE);
223                userTrustStore = initKeyStore(userTrustStoreFile);
224        }
225
226        private static void initUserKeyStore() {
227                userKeyStoreFile = new File(userConfigDir, USER_KEYSTORE_FILE);
228                userKeyStore = initKeyStore(userKeyStoreFile);
229        }
230
231        private static KeyStore initKeyStore(File keyStoreFile) {
232                try {
233                        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
234
235                        if (keyStoreFile.exists()) {
236                                FileInputStream trustStoreInputStream = new FileInputStream(keyStoreFile);
237                                keyStore.load(trustStoreInputStream, new char[0]);
238
239                                trustStoreInputStream.close();
240                        }
241                        else {
242                                keyStore.load(null, new char[0]); // Initialize empty store
243                        }
244
245                        return keyStore;
246                }
247                catch (Exception e) {
248                        throw new RuntimeException(e);
249                }
250        }
251
252        private static void storeKeyStore(KeyStore keyStore, File keyStoreFile) {
253                try {
254                        FileOutputStream trustStoreOutputStream = new FileOutputStream(keyStoreFile);
255                        keyStore.store(trustStoreOutputStream, new char[0]);
256
257                        trustStoreOutputStream.close();
258                }
259                catch (Exception e) {
260                        throw new RuntimeException("Cannot store key/truststore to file " + keyStoreFile, e);
261                }
262        }
263}