/*
* Syncany, www.syncany.org
* Copyright (C) 2011-2014 Philipp C. Heckel <philipp.heckel@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.syncany.config;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.KeyStore;
import java.util.Map;
import org.syncany.config.to.UserConfigTO;
import org.syncany.crypto.CipherUtil;
import org.syncany.crypto.SaltedSecretKey;
import org.syncany.util.EnvironmentUtil;
/**
* Represents the configuration parameters and application user directory
* of the currently logged in user, including system properties that will be
* set with every application start.
*
* @author Philipp C. Heckel <philipp.heckel@gmail.com>
*/
public class UserConfig {
/*
* Note:
* This class can't have any logging methods, because the init() method is called
* BEFORE the logging initialization. All errors must be printed to STDERR.
*/
// Daemon-specific config
public static final String DAEMON_FILE = "daemon.xml";
public static final String DAEMON_EXAMPLE_FILE = "daemon-example.xml";
public static final String DEFAULT_FOLDER = "Syncany";
public static final String USER_ADMIN = "admin";
public static final String USER_CLI = "CLI";
// These fields are not final to enable a PluginOperationTest
private static File USER_APP_DIR_WINDOWS = new File(System.getenv("APPDATA") + "\\Syncany");
private static File USER_APP_DIR_UNIX_LIKE = new File(System.getProperty("user.home") + "/.config/syncany");
private static final String USER_LOG_DIR = "logs";
private static final String USER_PLUGINS_LIB_DIR = "plugins/lib";
private static final String USER_PLUGINS_USERDATA_DIR_FORMAT = "plugins/userdata/%s";
private static final String USER_CONFIG_FILE = "userconfig.xml";
private static final String USER_TRUSTSTORE_FILE = "truststore.jks";
private static final String USER_KEYSTORE_FILE = "keystore.jks";
private static final int USER_CONFIG_ENCRYPTION_KEY_LENGTH = 32;
private static File userConfigDir;
private static File userLogDir;
private static File userPluginLibDir;
private static File userConfigFile;
private static File userTrustStoreFile;
private static KeyStore userTrustStore;
private static File userKeyStoreFile;
private static KeyStore userKeyStore;
private static boolean preventStandby;
private static SaltedSecretKey configEncryptionKey;
static {
init();
}
public static void init() {
if (userConfigDir == null) {
initUserAppDirs();
initUserConfig();
initUserTrustStore();
initUserKeyStore();
}
}
public static File getUserConfigDir() {
return userConfigDir;
}
public static File getUserLogDir() {
return userLogDir;
}
public static File getUserPluginLibDir() {
return userPluginLibDir;
}
public static File getUserPluginsUserdataDir(String pluginId) {
File pluginConfigDir = new File(userConfigDir, String.format(USER_PLUGINS_USERDATA_DIR_FORMAT, pluginId));
pluginConfigDir.mkdirs();
return pluginConfigDir;
}
public static File getUserConfigFile() {
return userConfigFile;
}
public static boolean preventStandbyEnabled() {
return preventStandby;
}
public static SaltedSecretKey getConfigEncryptionKey() {
return configEncryptionKey;
}
public static KeyStore getUserTrustStore() {
// Note: This method might not be used by the main project modules,
// but it might be used by plugins. Do not remove unless you are
// sure that it is not needed.
return userTrustStore;
}
public static KeyStore getUserKeyStore() {
return userKeyStore;
}
public static void storeTrustStore() {
storeKeyStore(userTrustStore, userTrustStoreFile);
}
public static void storeUserKeyStore() {
storeKeyStore(userKeyStore, userKeyStoreFile);
}
public static SSLContext createUserSSLContext() throws Exception {
return CipherUtil.createSSLContext(userKeyStore, userTrustStore);
}
// General initialization methods
private static void initUserAppDirs() {
userConfigDir = (EnvironmentUtil.isWindows()) ? USER_APP_DIR_WINDOWS : USER_APP_DIR_UNIX_LIKE;
userConfigDir.mkdirs();
userLogDir = new File(userConfigDir, USER_LOG_DIR);
userLogDir.mkdirs();
userPluginLibDir = new File(userConfigDir, USER_PLUGINS_LIB_DIR);
userPluginLibDir.mkdirs();
}
private static void initUserConfig() {
userConfigFile = new File(userConfigDir, USER_CONFIG_FILE);
if (userConfigFile.exists()) {
loadAndInitUserConfigFile(userConfigFile);
}
else {
writeExampleUserConfigFile(userConfigFile);
loadAndInitUserConfigFile(userConfigFile);
}
}
private static void loadAndInitUserConfigFile(File userConfigFile) {
try {
UserConfigTO userConfigTO = UserConfigTO.load(userConfigFile);
// System properties
for (Map.Entry<String, String> systemProperty : userConfigTO.getSystemProperties().entrySet()) {
System.setProperty(systemProperty.getKey(), systemProperty.getValue());
}
// Other options
preventStandby = userConfigTO.preventStandbyEnabled();
configEncryptionKey = userConfigTO.getConfigEncryptionKey();
}
catch (ConfigException e) {
System.err.println("ERROR: " + e.getMessage());
System.err.println(" Ignoring user config file!");
System.err.println();
}
}
private static void writeExampleUserConfigFile(File userConfigFile) {
UserConfigTO userConfigTO = new UserConfigTO();
userConfigTO.getSystemProperties().put("example.property", "This is a demo property. You can delete it.");
userConfigTO.getSystemProperties().put("syncany.rocks", "Yes, it does!");
try {
System.out.println("First launch, creating a secret key (could take a sec)...");
SaltedSecretKey configEncryptionKey = CipherUtil.createMasterKey(CipherUtil.createRandomAlphabeticString(USER_CONFIG_ENCRYPTION_KEY_LENGTH));
userConfigTO.setConfigEncryptionKey(configEncryptionKey);
userConfigTO.save(userConfigFile);
}
catch (Exception e) {
// Don't care!
}
}
// Key store / Trust store methods
private static void initUserTrustStore() {
userTrustStoreFile = new File(userConfigDir, USER_TRUSTSTORE_FILE);
userTrustStore = initKeyStore(userTrustStoreFile);
}
private static void initUserKeyStore() {
userKeyStoreFile = new File(userConfigDir, USER_KEYSTORE_FILE);
userKeyStore = initKeyStore(userKeyStoreFile);
}
private static KeyStore initKeyStore(File keyStoreFile) {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
if (keyStoreFile.exists()) {
FileInputStream trustStoreInputStream = new FileInputStream(keyStoreFile);
keyStore.load(trustStoreInputStream, new char[0]);
trustStoreInputStream.close();
}
else {
keyStore.load(null, new char[0]); // Initialize empty store
}
return keyStore;
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void storeKeyStore(KeyStore keyStore, File keyStoreFile) {
try {
FileOutputStream trustStoreOutputStream = new FileOutputStream(keyStoreFile);
keyStore.store(trustStoreOutputStream, new char[0]);
trustStoreOutputStream.close();
}
catch (Exception e) {
throw new RuntimeException("Cannot store key/truststore to file " + keyStoreFile, e);
}
}
}