Compare commits

...

5 Commits

Author SHA1 Message Date
idk
56f76d8162 remove commented-out logging line 2023-01-15 18:00:32 +00:00
idk
62c5d1a3e4 back up original config and load the properties from the backup config 2023-01-15 17:54:07 +00:00
idk
9047ada936 Actually do then store the edits 2023-01-15 17:34:49 +00:00
idk
82c9e027da Add javadoc for ClientConfigManager 2023-01-15 17:30:53 +00:00
idk
7312e9693d make it possible to use WorkingDir.java to retrieve the default working configuration directory without a working router context.
note that this is for retrieving the default directory, and may not reflect the directory in use by a running router. It is useful in cases where an external, command-line application needs to have a sane default configuration directory. Adds a bulk configuration editing tool, which makes it possible to make changes to every line in a configuration file or configuration directory which matches a regular expression so that it contains a value. This is to ease integration with systems that make use of virtual interfaces and networks to provide isolation from tthe host system and it's network interfaces, such as Docker and QubesOS. These systems may listen virtually on hosts other than 127.0.0.1 and/or localhost, may need to change default ports, and possibly make other bulk changes to the defaults before I2P is suitable to operate on their systems.
2023-01-15 17:24:19 +00:00
3 changed files with 364 additions and 83 deletions

View File

@ -0,0 +1,265 @@
package net.i2p.router;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;
import net.i2p.router.startup.WorkingDir;
import net.i2p.util.FileSuffixFilter;
import net.i2p.util.FileUtil;
import net.i2p.util.SystemVersion;
/**
* Execute bulk edits against I2P application config files or config
* directories. This is a command-line application only, which is intended
* to work agnostic of whether the router is runnning.
*
* @author idk 2023
*/
public class ClientConfigManager extends WorkingDir {
private String clientName = null;
private String workDir = null;
/**
* editPropertiesFile opens a single properties(.config) file and edits
* every entry in it that matches the regular expression `prop` to contain
* the value `value`.
* @param clientAppConfig
* @param prop
* @param value
* @return
*/
private boolean editPropertiesFile(File clientAppConfig, String prop,
String value)
throws FileNotFoundException {
System.out.println("editing config file " + clientAppConfig.getName());
boolean go = true;
Properties clientAppConfigProps = new Properties();
File backupConfig = new File(clientAppConfig + ".bak");
FileUtil.copy(clientAppConfig, backupConfig, true,
false);
try {
FileInputStream clientAppConfigReader = new FileInputStream(backupConfig);
clientAppConfigProps.load(clientAppConfigReader);
final Iterator entries = clientAppConfigProps.entrySet().iterator();
while (entries.hasNext()) {
final Map.Entry entry = (Map.Entry)entries.next();
String key = (String)entry.getKey();
if (Pattern.matches(prop, key)) {
clientAppConfigProps.setProperty(key, value);
// System.out.println("set property " + key + "=" + value);
}
}
try {
FileWriter clientAppConfigWriter = new FileWriter(clientAppConfig);
clientAppConfigProps.store(clientAppConfigWriter,
"inserted by ClientConfigManager");
} catch (IOException e) {
go = false;
}
} catch (IOException e) {
go = false;
} catch (IllegalStateException e) {
go = false;
}
return go;
}
/**
* editClientsConfig checks clients.config and clients.config.d to enable
* bulk configuration of client applications
*
* @param configDir
* @param prop
* @param value
* @return
* @throws FileNotFoundException
*/
public void editClientsConfig(File configDir, String prop, String value)
throws FileNotFoundException {
File clientAppConfigDir = new File(configDir, "clients.config.d");
if (clientAppConfigDir.exists()) {
File[] clientAppConfigFiles =
clientAppConfigDir.listFiles(new FileSuffixFilter(".config"));
if (clientAppConfigFiles != null) {
for (int i = 0; i < clientAppConfigFiles.length; i++) {
boolean cont =
editPropertiesFile(clientAppConfigFiles[i], prop, value);
if (!cont)
break;
}
}
} else {
File clientAppConfig = new File(configDir, "clients.config");
if (!clientAppConfig.exists())
throw new FileNotFoundException();
editPropertiesFile(clientAppConfig, prop, value);
}
}
/**
* editClientAppConfig edits the configuration files for a client based
* on the client's name(the un-translated one). Common values would be
* `i2ptunnel` or `i2psnark`. It can handle both monolithic and split-file
* confiurations I2P apps.
*
* @param configDir
* @param clientName
* @param prop
* @param value
* @return
* @throws FileNotFoundException
*/
public void editClientAppConfig(File configDir, String clientName,
String prop, String value)
throws FileNotFoundException {
File i2pTunnelConfigDir = new File(configDir, clientName + ".config.d");
if (i2pTunnelConfigDir.exists()) {
File[] i2pTunnelConfigFiles =
i2pTunnelConfigDir.listFiles(new FileSuffixFilter(".config"));
if (i2pTunnelConfigFiles != null) {
for (int i = 0; i < i2pTunnelConfigFiles.length; i++) {
boolean cont =
editPropertiesFile(i2pTunnelConfigFiles[i], prop, value);
if (!cont)
break;
}
}
} else {
File i2pTunnelConfig = new File(configDir, clientName + ".config");
if (!i2pTunnelConfig.exists())
throw new FileNotFoundException();
editPropertiesFile(i2pTunnelConfig, prop, value);
}
}
private class PropSet {
String key;
String value;
PropSet(String inkey, String invalue) {
key = inkey;
value = invalue;
}
public String toString() {
if (key == null) {
return null;
}
if (value == null) {
return key + "=";
}
return key + "=" + value;
}
}
/**
* parses arguments and flags from the command line. Valid flags are:
*
* -clientapp name
* -workdir path
*
* which must be followed by 2 trailing arguments, the first of which is
* a regular expression, the second of which is a value which will be applied
* to all keys matching the regular expression.
*
* @param args
* @return PropSet containing the regex to match against the key, and the
* value to set
* @throws Error
*/
public PropSet parseArgs(String args[]) throws Error {
if (args.length < 2) {
throw new Error("insufficient bulk edit arguments");
} else if (args.length == 2) {
return new PropSet(args[0], args[1]);
}
String key = null;
String value = null;
for (int i = 0; i < args.length; i++) {
if (args[i].startsWith("-")) {
if (args[i].equals("-clientapp")) {
if (args.length >= i + 1) {
System.out.println("Setting clientName to " + args[i] +
args[i + 1]);
clientName = args[i + 1];
i++;
} else
throw new Error(
"Insufficient arguments, -clientapp requires an an application name");
} else if (args[i].equals("-clientapp")) {
if (args.length >= i + 1) {
System.out.println("Setting workDir to " + args[i] + args[i + 1]);
workDir = args[i + 1];
i++;
} else
throw new Error(
"Insufficient arguments, -workdir requires an a directory");
}
} else {
if (key == null)
key = args[i];
else {
value = args[i];
break;
}
}
}
return new PropSet(key, value);
}
/**
* editClientApp helps determine if we're editing clients.config or an
* application config.
*
* @return true if editing an app, false if editing clients.config
*/
public boolean editClientApp() {
if (clientName != null)
return true;
return false;
}
/**
* get the "default" config directory for your platform.
*
* @return the default configuration directory, or workDir if it is set.
*/
public File getConfigDir() {
if (workDir != null) {
System.out.println("workdir set to" + workDir);
return new File(workDir);
}
System.out.println("finding default config dir");
boolean isWindows = SystemVersion.isWindows();
String defaultPath = getDefaultDir(isWindows).getAbsolutePath();
System.out.println("using default config dir " + defaultPath);
return new File(defaultPath);
}
public static void main(String args[]) {
System.out.println("ClientConfigManager");
ClientConfigManager ccm = new ClientConfigManager();
try {
PropSet propSet = ccm.parseArgs(args);
System.out.println("args are " + propSet.toString());
File configDir = ccm.getConfigDir();
System.out.println("configs are " + configDir.getAbsolutePath());
if (ccm.editClientApp()) {
try {
ccm.editClientAppConfig(configDir, ccm.clientName, propSet.key,
propSet.value);
} catch (FileNotFoundException e) {
System.out.println(
"Client not found, check name and config directory");
}
} else {
try {
ccm.editClientsConfig(configDir, propSet.key, propSet.value);
} catch (FileNotFoundException e) {
System.out.println(
"Client config file not found, check config directory");
}
}
} catch (Error e) {
System.out.println(e.toString());
}
}
}

View File

@ -14,6 +14,7 @@ import java.util.List;
public class CommandLine extends net.i2p.util.CommandLine {
protected static final List<String> RCLASSES = Arrays.asList(new String[] {
"net.i2p.router.ClientConfigManager",
"com.maxmind.geoip2.DatabaseReader",
"net.i2p.data.router.RouterInfo",
"net.i2p.data.router.RouterKeyGenerator",

View File

@ -56,6 +56,103 @@ public class WorkingDir {
/** Feb 16 2006 */
private static final long EEPSITE_TIMESTAMP = 1140048000000l;
/**
* Determines the default working directory for a given platform by examining
* the host system, and applies secure configuration to that directory.
*
* @param isWindows set to true if the host system is Windows, false if it's anything else
* @return a File containing the working directory
*/
public static File getDefaultDir(boolean isWindows){
File dirf = null;
String gentooWarning = null;
String home = System.getProperty("user.home");
if (isWindows) {
String localappdata = System.getenv("LOCALAPPDATA");
if (localappdata != null) {
home = localappdata;
}
// Don't mess with existing Roaming Application Data installs,
// in case somebody is using roaming appdata for a reason
// already. In new installs, use local appdata by default. -idk
String appdata = System.getenv("APPDATA");
if (appdata != null) {
File checkOld = new File(appdata, WORKING_DIR_DEFAULT_WINDOWS);
if (checkOld.exists() && checkOld.isDirectory()){
File routerConfig = new File(checkOld.getAbsolutePath(), "router.config");
// The Firefox profile installer was mistakenly using the Roaming application data
// which is synced between devices on some Windows machines using MS cloud services,
// instead of the local application data which is used by default.
// It would create the router.config file in an empty directory, which the router would
// then attempt to use, resulting in a router with no client applications. Checking
// for clients.config.d determines if the directory is "Real" or not.
File clientAppsConfig = new File(checkOld.getAbsolutePath(), "clients.config.d");
if (routerConfig.exists() && clientAppsConfig.exists() && clientAppsConfig.isDirectory()) {
home = appdata;
} else {
clientAppsConfig = new File(checkOld.getAbsolutePath(), "clients.config");
if (routerConfig.exists() && clientAppsConfig.exists())
home = appdata;
}
System.err.println("System is Windows: " + home);
}
}
dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT_WINDOWS);
} else if (SystemVersion.isMac()) {
String appdata = "/Library/Application Support/";
File old = new File(home,WORKING_DIR_DEFAULT);
if (old.exists() && old.isDirectory())
dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT);
else {
home = home+appdata;
dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT_MAC);
}
} else {
if (SystemVersion.isLinuxService()) {
if (SystemVersion.isGentoo() &&
SystemVersion.GENTOO_USER.equals(System.getProperty("user.name"))) {
// whoops, we didn't recognize Gentoo as a service until 0.9.29,
// so the config dir was /var/lib/i2p/.i2p through 0.9.28
// and changed to /var/lib/i2p/i2p-config in 0.9.29.
// Look for both to decide which to use.
// We prefer .i2p if neither exists.
// We prefer the newer if both exist.
File d1 = new SecureDirectory(home, WORKING_DIR_DEFAULT);
File d2 = new SecureDirectory(home, WORKING_DIR_DEFAULT_DAEMON);
boolean e1 = isSetup(d1);
boolean e2 = isSetup(d2);
if (e1 && e2) {
// d1 is probably older. Switch if it isn't.
if (d2.lastModified() < d1.lastModified()) {
File tmp = d2;
d2 = d1;
d1 = tmp;
// d1 now is the older one
}
dirf = d2;
gentooWarning = "Warning - Found both an old configuration directory " + d1.getAbsolutePath() +
" and new configuration directory " + d2.getAbsolutePath() +
" created due to a bug in release 0.9.29\n. Using the new configuration" +
" directory. To use the old directory instead, stop i2p," +
" delete the new directory, and restart.";
} else if (e1 && !e2) {
dirf = d1;
} else if (!e1 && e2) {
dirf = d2;
} else {
dirf = d1;
}
} else {
dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT_DAEMON);
}
} else {
dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT);
}
}
return dirf;
}
/**
* Only call this once on router invocation.
* Caller should store the return value for future reference.
@ -78,89 +175,7 @@ public class WorkingDir {
if (dir != null) {
dirf = new SecureDirectory(dir);
} else {
String home = System.getProperty("user.home");
if (isWindows) {
String localappdata = System.getenv("LOCALAPPDATA");
if (localappdata != null) {
home = localappdata;
}
// Don't mess with existing Roaming Application Data installs,
// in case somebody is using roaming appdata for a reason
// already. In new installs, use local appdata by default. -idk
String appdata = System.getenv("APPDATA");
if (appdata != null) {
File checkOld = new File(appdata, WORKING_DIR_DEFAULT_WINDOWS);
if (checkOld.exists() && checkOld.isDirectory()){
File routerConfig = new File(checkOld.getAbsolutePath(), "router.config");
// The Firefox profile installer was mistakenly using the Roaming application data
// which is synced between devices on some Windows machines using MS cloud services,
// instead of the local application data which is used by default.
// It would create the router.config file in an empty directory, which the router would
// then attempt to use, resulting in a router with no client applications. Checking
// for clients.config.d determines if the directory is "Real" or not.
File clientAppsConfig = new File(checkOld.getAbsolutePath(), "clients.config.d");
if (routerConfig.exists() && clientAppsConfig.exists() && clientAppsConfig.isDirectory()) {
home = appdata;
} else {
clientAppsConfig = new File(checkOld.getAbsolutePath(), "clients.config");
if (routerConfig.exists() && clientAppsConfig.exists())
home = appdata;
}
System.err.println("System is Windows: " + home);
}
}
dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT_WINDOWS);
} else if (SystemVersion.isMac()) {
String appdata = "/Library/Application Support/";
File old = new File(home,WORKING_DIR_DEFAULT);
if (old.exists() && old.isDirectory())
dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT);
else {
home = home+appdata;
dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT_MAC);
}
} else {
if (SystemVersion.isLinuxService()) {
if (SystemVersion.isGentoo() &&
SystemVersion.GENTOO_USER.equals(System.getProperty("user.name"))) {
// whoops, we didn't recognize Gentoo as a service until 0.9.29,
// so the config dir was /var/lib/i2p/.i2p through 0.9.28
// and changed to /var/lib/i2p/i2p-config in 0.9.29.
// Look for both to decide which to use.
// We prefer .i2p if neither exists.
// We prefer the newer if both exist.
File d1 = new SecureDirectory(home, WORKING_DIR_DEFAULT);
File d2 = new SecureDirectory(home, WORKING_DIR_DEFAULT_DAEMON);
boolean e1 = isSetup(d1);
boolean e2 = isSetup(d2);
if (e1 && e2) {
// d1 is probably older. Switch if it isn't.
if (d2.lastModified() < d1.lastModified()) {
File tmp = d2;
d2 = d1;
d1 = tmp;
// d1 now is the older one
}
dirf = d2;
gentooWarning = "Warning - Found both an old configuration directory " + d1.getAbsolutePath() +
" and new configuration directory " + d2.getAbsolutePath() +
" created due to a bug in release 0.9.29\n. Using the new configuration" +
" directory. To use the old directory instead, stop i2p," +
" delete the new directory, and restart.";
} else if (e1 && !e2) {
dirf = d1;
} else if (!e1 && e2) {
dirf = d2;
} else {
dirf = d1;
}
} else {
dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT_DAEMON);
}
} else {
dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT);
}
}
dirf = getDefaultDir(isWindows);
}
// where we are now