diff --git a/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java b/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java index be74de990f2c65fd3d61ae5afbc042c0f1509598..6c95e2e19653f90dc31065232e50a297354ee1c8 100644 --- a/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java +++ b/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java @@ -25,9 +25,9 @@ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; -import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; +import java.io.OutputStreamWriter; import java.io.StringReader; import java.util.HashMap; import java.util.Iterator; @@ -35,6 +35,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import net.i2p.util.SecureFileOutputStream; + /** * Utility class providing methods to parse and write files in config file * format, and subscription file format. @@ -277,7 +279,7 @@ public class ConfigParser { */ public static void write(Map map, File file) throws IOException { ConfigParser - .write(map, new BufferedWriter(new FileWriter(file, false))); + .write(map, new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8"))); } /** @@ -316,7 +318,7 @@ public class ConfigParser { public static void writeSubscriptions(List list, File file) throws IOException { ConfigParser.writeSubscriptions(list, new BufferedWriter( - new FileWriter(file, false))); + new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8"))); } } diff --git a/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java b/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java index 077acdb7f02bbf7f1a8c13cd912cac599d994078..2588010118eb7156ba390464c942a1d3c4325174 100644 --- a/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java +++ b/apps/addressbook/java/src/net/i2p/addressbook/Daemon.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import net.i2p.I2PAppContext; +import net.i2p.util.SecureDirectory; /** * Main class of addressbook. Performs updates, and runs the main loop. @@ -131,11 +132,11 @@ public class Daemon { String settingsLocation = "config.txt"; File homeFile; if (args.length > 0) { - homeFile = new File(args[0]); + homeFile = new SecureDirectory(args[0]); if (!homeFile.isAbsolute()) - homeFile = new File(I2PAppContext.getGlobalContext().getRouterDir(), args[0]); + homeFile = new SecureDirectory(I2PAppContext.getGlobalContext().getRouterDir(), args[0]); } else { - homeFile = new File(System.getProperty("user.dir")); + homeFile = new SecureDirectory(System.getProperty("user.dir")); } Map defaultSettings = new HashMap(); diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java index 127a2bb19abfed633d7d79e440f590bff9a94e4b..b0e26bfe6acb2bb6a361e60b45224d9987031518 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java +++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java @@ -26,6 +26,7 @@ import net.i2p.util.ConcurrentHashSet; import net.i2p.util.EepGet; import net.i2p.util.FileUtil; import net.i2p.util.Log; +import net.i2p.util.SecureDirectory; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; import net.i2p.util.Translate; @@ -77,7 +78,7 @@ public class I2PSnarkUtil { // This is used for both announce replies and .torrent file downloads, // so it must be available even if not connected to I2CP. // so much for multiple instances - _tmpDir = new File(ctx.getTempDir(), "i2psnark"); + _tmpDir = new SecureDirectory(ctx.getTempDir(), "i2psnark"); FileUtil.rmdir(_tmpDir, false); _tmpDir.mkdirs(); } diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 59ab86a12920ec5d2e70ee86fa2fb9df887914e5..a1ddaf25db32235547b75090c73abd6f611ad3cb 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -21,6 +21,7 @@ import net.i2p.data.DataHelper; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; import net.i2p.util.OrderedProperties; +import net.i2p.util.SecureDirectory; /** * Manage multiple snarks @@ -130,9 +131,9 @@ public class SnarkManager implements Snark.CompleteListener { } public File getDataDir() { String dir = _config.getProperty(PROP_DIR, "i2psnark"); - File f = new File(dir); + File f = new SecureDirectory(dir); if (!f.isAbsolute()) - f = new File(_context.getAppDir(), dir); + f = new SecureDirectory(_context.getAppDir(), dir); return f; } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index 7235507ba6f28c7a25a5b85c824ebbe9af0594a1..5b7fd3529bcd581b828f58bd6aa82ade6dd19c24 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -18,6 +18,7 @@ import net.i2p.data.Base32; import net.i2p.data.Destination; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; +import net.i2p.util.SecureFileOutputStream; /** * Coordinate the runtime operation and configuration of a tunnel. @@ -84,7 +85,7 @@ public class TunnelController implements Logging { } FileOutputStream fos = null; try { - fos = new FileOutputStream(keyFile); + fos = new SecureFileOutputStream(keyFile); Destination dest = client.createDestination(fos); String destStr = dest.toBase64(); log("Private key created and saved in " + keyFile.getAbsolutePath()); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java index 112f0d62f06544973445ce97c2e37f023a4a9d5e..7b0c67f707924a469e2f6111b6708a40a3fd38ec 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java @@ -20,6 +20,7 @@ import net.i2p.client.I2PSessionException; import net.i2p.data.DataHelper; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; +import net.i2p.util.SecureFileOutputStream; /** * Coordinate a set of tunnels within the JVM, loading and storing their config @@ -254,7 +255,7 @@ public class TunnelControllerGroup { FileOutputStream fos = null; try { - fos = new FileOutputStream(cfgFile); + fos = new SecureFileOutputStream(cfgFile); fos.write(buf.toString().getBytes("UTF-8")); if (_log.shouldLog(Log.INFO)) _log.info("Config written to " + cfgFile.getPath()); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java index b17dfbdade4264f1b98a579c86586e9404b44288..6958b350e9b2f2ccaf2e4126758ab75137f659af 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java @@ -563,7 +563,7 @@ public class PluginStarter implements Runnable { /** * http://jimlife.wordpress.com/2007/12/19/java-adding-new-classpath-at-runtime/ */ - public static void addPath(URL u) throws Exception { + private static void addPath(URL u) throws Exception { URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); Class urlClass = URLClassLoader.class; Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class}); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java index 26d026a1694eb8bb62ced52827a31fb4b82c989f..38e8f28b00ef014aa859c54b13d40e39b0be6e13 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java @@ -16,6 +16,7 @@ import net.i2p.util.FileUtil; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; import net.i2p.util.OrderedProperties; +import net.i2p.util.SecureDirectory; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; import net.i2p.util.VersionComparator; @@ -150,7 +151,7 @@ public class PluginUpdateHandler extends UpdateHandler { public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) { updateStatus("<b>" + _("Plugin downloaded") + "</b>"); File f = new File(_updateFile); - File appDir = new File(_context.getConfigDir(), PLUGIN_DIR); + File appDir = new SecureDirectory(_context.getConfigDir(), PLUGIN_DIR); if ((!appDir.exists()) && (!appDir.mkdir())) { f.delete(); statusDone("<b>" + _("Cannot create plugin directory {0}", appDir.getAbsolutePath()) + "</b>"); @@ -273,7 +274,7 @@ public class PluginUpdateHandler extends UpdateHandler { return; } - File destDir = new File(appDir, appName); + File destDir = new SecureDirectory(appDir, appName); if (destDir.exists()) { if (Boolean.valueOf(props.getProperty("install-only")).booleanValue()) { to.delete(); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index 05bedee374fed8b931d06f3811739f9445e03bfa..68533f7ef63c19cfce8b1b4a7c384b4b90f1b309 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -14,6 +14,7 @@ import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; import net.i2p.util.FileUtil; import net.i2p.util.I2PAppThread; +import net.i2p.util.SecureDirectory; import org.mortbay.http.DigestAuthenticator; import org.mortbay.http.HashUserRealm; @@ -62,7 +63,7 @@ public class RouterConsoleRunner { } public void startConsole() { - File workDir = new File(I2PAppContext.getGlobalContext().getTempDir(), "jetty-work"); + File workDir = new SecureDirectory(I2PAppContext.getGlobalContext().getTempDir(), "jetty-work"); boolean workDirRemoved = FileUtil.rmdir(workDir, false); if (!workDirRemoved) System.err.println("ERROR: Unable to remove Jetty temporary work directory"); @@ -115,7 +116,7 @@ public class RouterConsoleRunner { } _server.setRootWebApp(ROUTERCONSOLE); WebApplicationContext wac = _server.addWebApplication("/", _webAppsDir + ROUTERCONSOLE + ".war"); - File tmpdir = new File(workDir, ROUTERCONSOLE + "-" + _listenPort); + File tmpdir = new SecureDirectory(workDir, ROUTERCONSOLE + "-" + _listenPort); tmpdir.mkdir(); wac.setTempDirectory(tmpdir); baseHandler = new LocaleWebAppHandler(I2PAppContext.getGlobalContext()); @@ -130,7 +131,7 @@ public class RouterConsoleRunner { String enabled = props.getProperty(PREFIX + appName + ENABLED); if (! "false".equals(enabled)) { String path = new File(dir, fileNames[i]).getCanonicalPath(); - tmpdir = new File(workDir, appName + "-" + _listenPort); + tmpdir = new SecureDirectory(workDir, appName + "-" + _listenPort); WebAppStarter.addWebApp(I2PAppContext.getGlobalContext(), _server, appName, path, tmpdir); if (enabled == null) { diff --git a/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java index 0264d6307aa782147051ee26a3d7682e4850114d..1aac8ecc9fe7e3f70be3391c108a7c34666e7809 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java @@ -10,6 +10,7 @@ import java.util.concurrent.ConcurrentHashMap; import net.i2p.I2PAppContext; import net.i2p.util.FileUtil; +import net.i2p.util.SecureDirectory; import org.mortbay.http.HttpContext; import org.mortbay.http.HttpListener; @@ -41,7 +42,7 @@ public class WebAppStarter { * @throws just about anything, caller would be wise to catch Throwable */ static void startWebApp(I2PAppContext ctx, Server server, String appName, String warPath) throws Exception { - File tmpdir = new File(ctx.getTempDir(), "jetty-work-" + appName + ctx.random().nextInt()); + File tmpdir = new SecureDirectory(ctx.getTempDir(), "jetty-work-" + appName + ctx.random().nextInt()); WebApplicationContext wac = addWebApp(ctx, server, appName, warPath, tmpdir); wac.start(); } @@ -73,7 +74,7 @@ public class WebAppStarter { warModTimes.put(warPath, new Long(newmod)); } else if (oldmod.longValue() < newmod) { // copy war to temporary directory - File warTmpDir = new File(ctx.getTempDir(), "war-copy-" + appName + ctx.random().nextInt()); + File warTmpDir = new SecureDirectory(ctx.getTempDir(), "war-copy-" + appName + ctx.random().nextInt()); warTmpDir.mkdir(); String tmpPath = (new File(warTmpDir, appName + ".war")).getAbsolutePath(); if (!FileUtil.copy(warPath, tmpPath, true)) diff --git a/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java b/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java index 63c4db85acb1f61c285ff7b7a2231ee927d552be..b6af8a12dada88fa0d30c0f359f158379d1fc681 100644 --- a/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java +++ b/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java @@ -38,6 +38,7 @@ import java.util.Properties; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; +import net.i2p.util.SecureFileOutputStream; public class AddressbookBean { @@ -330,7 +331,7 @@ public class AddressbookBean { String filename = properties.getProperty( getBook() + "_addressbook" ); - FileOutputStream fos = new FileOutputStream( ConfigBean.addressbookPrefix + filename ); + FileOutputStream fos = new SecureFileOutputStream( ConfigBean.addressbookPrefix + filename ); addressbook.store( fos, null ); try { fos.close(); diff --git a/apps/susidns/src/java/src/i2p/susi/dns/ConfigBean.java b/apps/susidns/src/java/src/i2p/susi/dns/ConfigBean.java index f6655757d8318977ee667baf4c8cbf013948fb05..ee65753564a0ff961f9432664fdfd5c8444b57bd 100644 --- a/apps/susidns/src/java/src/i2p/susi/dns/ConfigBean.java +++ b/apps/susidns/src/java/src/i2p/susi/dns/ConfigBean.java @@ -27,13 +27,13 @@ package i2p.susi.dns; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.io.Serializable; import net.i2p.I2PAppContext; +import net.i2p.util.SecureFileOutputStream; public class ConfigBean implements Serializable { @@ -111,7 +111,7 @@ public class ConfigBean implements Serializable { { File file = new File( configFileName ); try { - PrintWriter out = new PrintWriter( new FileOutputStream( file ) ); + PrintWriter out = new PrintWriter( new SecureFileOutputStream( file ) ); out.print( config ); out.flush(); out.close(); diff --git a/apps/susidns/src/java/src/i2p/susi/dns/SubscriptionsBean.java b/apps/susidns/src/java/src/i2p/susi/dns/SubscriptionsBean.java index 6b1a80576999b83406f5de862d881755b6d3f92a..176561acaf16c6934ae7c3a879b46b3f91d2cc63 100644 --- a/apps/susidns/src/java/src/i2p/susi/dns/SubscriptionsBean.java +++ b/apps/susidns/src/java/src/i2p/susi/dns/SubscriptionsBean.java @@ -28,12 +28,13 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.Properties; +import net.i2p.util.SecureFileOutputStream; + public class SubscriptionsBean { private String action, fileName, content, serial, lastSerial; @@ -113,7 +114,7 @@ public class SubscriptionsBean { File file = new File( getFileName() ); try { - PrintWriter out = new PrintWriter( new FileOutputStream( file ) ); + PrintWriter out = new PrintWriter( new SecureFileOutputStream( file ) ); out.print( content ); out.flush(); out.close(); diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java index aac61d4beb63913166384b15c995fa95d43b1cfe..751cbb37aa73c3c5d8960a2e9af21e8fc5ede02d 100644 --- a/core/java/src/net/i2p/I2PAppContext.java +++ b/core/java/src/net/i2p/I2PAppContext.java @@ -31,6 +31,7 @@ import net.i2p.util.KeyRing; import net.i2p.util.LogManager; //import net.i2p.util.PooledRandomSource; import net.i2p.util.RandomSource; +import net.i2p.util.SecureDirectory; /** * <p>Provide a base scope for accessing singletons that I2P exposes. Rather than @@ -217,7 +218,7 @@ public class I2PAppContext { // config defaults to base s = getProperty("i2p.dir.config"); if (s != null) { - _configDir = new File(s); + _configDir = new SecureDirectory(s); if (!_configDir.exists()) _configDir.mkdir(); } else { @@ -226,7 +227,7 @@ public class I2PAppContext { // router defaults to config s = getProperty("i2p.dir.router"); if (s != null) { - _routerDir = new File(s); + _routerDir = new SecureDirectory(s); if (!_routerDir.exists()) _routerDir.mkdir(); } else { @@ -240,7 +241,7 @@ public class I2PAppContext { // these all default to router s = getProperty("i2p.dir.log"); if (s != null) { - _logDir = new File(s); + _logDir = new SecureDirectory(s); if (!_logDir.exists()) _logDir.mkdir(); } else { @@ -248,7 +249,7 @@ public class I2PAppContext { } s = getProperty("i2p.dir.app"); if (s != null) { - _appDir = new File(s); + _appDir = new SecureDirectory(s); if (!_appDir.exists()) _appDir.mkdir(); } else { @@ -278,14 +279,14 @@ public class I2PAppContext { String d = getProperty("i2p.dir.temp", System.getProperty("java.io.tmpdir")); // our random() probably isn't warmed up yet String f = "i2p-" + Math.abs((new java.util.Random()).nextInt()) + ".tmp"; - _tmpDir = new File(d, f); + _tmpDir = new SecureDirectory(d, f); if (_tmpDir.exists()) { // good or bad ? } else if (_tmpDir.mkdir()) { _tmpDir.deleteOnExit(); } else { System.err.println("Could not create temp dir " + _tmpDir.getAbsolutePath()); - _tmpDir = new File(_routerDir, "tmp"); + _tmpDir = new SecureDirectory(_routerDir, "tmp"); _tmpDir.mkdir(); } } diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index 07997fd105eb5fc2a86e05aca360c0e40daa1526..6ac316ee9d1278b4ba400f803269a51f376539b6 100644 --- a/core/java/src/net/i2p/data/DataHelper.java +++ b/core/java/src/net/i2p/data/DataHelper.java @@ -17,7 +17,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -42,6 +41,7 @@ import net.i2p.util.ByteCache; import net.i2p.util.OrderedProperties; import net.i2p.util.ReusableGZIPInputStream; import net.i2p.util.ReusableGZIPOutputStream; +import net.i2p.util.SecureFileOutputStream; /** * Defines some simple IO routines for dealing with marshalling data structures @@ -304,11 +304,12 @@ public class DataHelper { /** * Writes the props to the file, unsorted (unless props is an OrderedProperties) * Note that this does not escape the \r or \n that are unescaped in loadProps() above. + * As of 0.8.1, file will be mode 600. */ public static void storeProps(Properties props, File file) throws IOException { PrintWriter out = null; try { - out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"))); + out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8"))); out.println("# NOTE: This I2P config file must use UTF-8 encoding"); for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) { String name = (String)iter.next(); diff --git a/core/java/src/net/i2p/util/LogWriter.java b/core/java/src/net/i2p/util/LogWriter.java index b15aa6cfbb723720958237b4ba5640b16cfb94dc..7dca7ba96dc75cdfe8d045491ee241baeda61aa7 100644 --- a/core/java/src/net/i2p/util/LogWriter.java +++ b/core/java/src/net/i2p/util/LogWriter.java @@ -11,7 +11,6 @@ package net.i2p.util; import java.io.BufferedWriter; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -158,7 +157,8 @@ class LogWriter implements Runnable { File parent = f.getParentFile(); if (parent != null) { if (!parent.exists()) { - boolean ok = parent.mkdirs(); + File sd = new SecureDirectory(parent.getAbsolutePath()); + boolean ok = sd.mkdirs(); if (!ok) { System.err.println("Unable to create the parent directory: " + parent.getAbsolutePath()); //System.exit(0); @@ -171,7 +171,7 @@ class LogWriter implements Runnable { } closeFile(); try { - _currentOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), "UTF-8")); + _currentOut = new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(f), "UTF-8")); } catch (IOException ioe) { System.err.println("Error rotating into [" + f.getAbsolutePath() + "]" + ioe); } diff --git a/core/java/src/net/i2p/util/RandomSource.java b/core/java/src/net/i2p/util/RandomSource.java index c6ac80a65ca63f6410b178098d2933928e2c3a7a..100ec47d736c8406b5d23a968384216fa88c000e 100644 --- a/core/java/src/net/i2p/util/RandomSource.java +++ b/core/java/src/net/i2p/util/RandomSource.java @@ -145,7 +145,7 @@ public class RandomSource extends SecureRandom implements EntropyHarvester { File f = new File(I2PAppContext.getGlobalContext().getConfigDir(), SEEDFILE); FileOutputStream fos = null; try { - fos = new FileOutputStream(f); + fos = new SecureFileOutputStream(f); fos.write(buf); } catch (IOException ioe) { // ignore diff --git a/core/java/src/net/i2p/util/SecureDirectory.java b/core/java/src/net/i2p/util/SecureDirectory.java index 393e118865cc91a7e9005b715bcae82ca5e93eec..30d609e96fb70d50c1c206e8005ff0024623d92a 100644 --- a/core/java/src/net/i2p/util/SecureDirectory.java +++ b/core/java/src/net/i2p/util/SecureDirectory.java @@ -38,6 +38,18 @@ public class SecureDirectory extends File { return rv; } + /** + * Sets directory to mode 700 if the directory is created + * Does NOT change the mode of other created directories + */ + @Override + public boolean mkdirs() { + boolean rv = super.mkdirs(); + if (rv) + setPerms(); + return rv; + } + /** * Tries to set the permissions to 700, * ignores errors diff --git a/router/java/src/net/i2p/router/KeyManager.java b/router/java/src/net/i2p/router/KeyManager.java index 4d19228b7208c16a177770fcfb490cb7acf62cf2..fd0e85a9eb6f31cefeac96dbc5cff92e4bc22564 100644 --- a/router/java/src/net/i2p/router/KeyManager.java +++ b/router/java/src/net/i2p/router/KeyManager.java @@ -27,6 +27,8 @@ import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.util.Clock; import net.i2p.util.Log; +import net.i2p.util.SecureDirectory; +import net.i2p.util.SecureFileOutputStream; /** * Maintain all of the key pairs for the router. @@ -142,7 +144,7 @@ public class KeyManager { } public void runJob() { String keyDir = getContext().getProperty(PROP_KEYDIR, DEFAULT_KEYDIR); - File dir = new File(getContext().getRouterDir(), keyDir); + File dir = new SecureDirectory(getContext().getRouterDir(), keyDir); if (!dir.exists()) dir.mkdirs(); if (dir.exists() && dir.isDirectory() && dir.canRead() && dir.canWrite()) { @@ -219,7 +221,7 @@ public class KeyManager { FileInputStream in = null; try { if (exists) { - out = new FileOutputStream(keyFile); + out = new SecureFileOutputStream(keyFile); structure.writeBytes(out); return structure; } else { diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 0651e35d261465bb41c27462b9ba291c404f34da..a091cba13451c44d9d3c62b98a198a5f69e1a7ac 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -46,6 +46,7 @@ import net.i2p.util.FileUtil; import net.i2p.util.I2PAppThread; import net.i2p.util.I2PThread; import net.i2p.util.Log; +import net.i2p.util.SecureFileOutputStream; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; @@ -305,6 +306,7 @@ public class Router { public void setHigherVersionSeen(boolean seen) { _higherVersionSeen = seen; } public long getWhenStarted() { return _started; } + /** wall clock uptime */ public long getUptime() { if ( (_context == null) || (_context.clock() == null) ) return 1; // racing on startup @@ -1053,11 +1055,12 @@ public class Router { * this does escape the \r or \n that are unescaped in DataHelper.loadProps(). * Note that the escaping of \r or \n was probably a mistake and should be taken out. * + * FIXME Synchronize!! */ public boolean saveConfig() { FileOutputStream fos = null; try { - fos = new FileOutputStream(_configFilename); + fos = new SecureFileOutputStream(_configFilename); StringBuilder buf = new StringBuilder(8*1024); buf.append("# NOTE: This I2P config file must use UTF-8 encoding\n"); synchronized (_config) { @@ -1541,7 +1544,7 @@ private static class PersistRouterInfoJob extends JobImpl { FileOutputStream fos = null; try { - fos = new FileOutputStream(infoFile); + fos = new SecureFileOutputStream(infoFile); info.writeBytes(fos); } catch (DataFormatException dfe) { _log.error("Error rebuilding the router information", dfe); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java index 77ea8c4573136b0c58b402927c110b209f846b97..0172dfcc6907a5b54923498cfcd4364877d587bc 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java @@ -29,6 +29,8 @@ import net.i2p.router.RouterContext; import net.i2p.router.networkdb.reseed.ReseedChecker; import net.i2p.util.I2PThread; import net.i2p.util.Log; +import net.i2p.util.SecureDirectory; +import net.i2p.util.SecureFileOutputStream; /** * Write out keys to disk when we get them and periodically read ones we don't know @@ -288,7 +290,7 @@ class PersistentDataStore extends TransientDataStore { long dataPublishDate = getPublishDate(data); if (dbFile.lastModified() < dataPublishDate) { // our filesystem is out of date, lets replace it - fos = new FileOutputStream(dbFile); + fos = new SecureFileOutputStream(dbFile); try { data.writeBytes(fos); fos.close(); @@ -440,7 +442,7 @@ class PersistentDataStore extends TransientDataStore { private File getDbDir() throws IOException { - File f = new File(_context.getRouterDir(), _dbDir); + File f = new SecureDirectory(_context.getRouterDir(), _dbDir); if (!f.exists()) { boolean created = f.mkdirs(); if (!created) diff --git a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java index 29fce33a6cb18e1664b2f61efff98151c36c0089..21870341e74aba73ebaf8823d03fd1e085a72097 100644 --- a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java +++ b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java @@ -17,6 +17,8 @@ import net.i2p.router.RouterContext; import net.i2p.util.EepGet; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; +import net.i2p.util.SecureDirectory; +import net.i2p.util.SecureFileOutputStream; import net.i2p.util.SSLEepGet; import net.i2p.util.Translate; @@ -261,11 +263,11 @@ public class Reseeder { private void writeSeed(String name, byte data[]) throws Exception { String dirName = "netDb"; // _context.getProperty("router.networkDatabase.dbDir", "netDb"); - File netDbDir = new File(_context.getRouterDir(), dirName); + File netDbDir = new SecureDirectory(_context.getRouterDir(), dirName); if (!netDbDir.exists()) { boolean ok = netDbDir.mkdirs(); } - FileOutputStream fos = new FileOutputStream(new File(netDbDir, "routerInfo-" + name + ".dat")); + FileOutputStream fos = new SecureFileOutputStream(new File(netDbDir, "routerInfo-" + name + ".dat")); fos.write(data); fos.close(); } diff --git a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java index 606b4883cf288f4e7a905aa67df81a3fd7bf2456..610dfec4f8a26eb01f07a781b483bf180105a43b 100644 --- a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java +++ b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java @@ -3,7 +3,6 @@ package net.i2p.router.peermanager; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.OutputStream; @@ -19,6 +18,8 @@ import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.router.RouterContext; import net.i2p.util.Log; +import net.i2p.util.SecureDirectory; +import net.i2p.util.SecureFileOutputStream; class ProfilePersistenceHelper { private Log _log; @@ -61,7 +62,7 @@ class ProfilePersistenceHelper { long before = _context.clock().now(); OutputStream fos = null; try { - fos = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(f))); + fos = new BufferedOutputStream(new GZIPOutputStream(new SecureFileOutputStream(f))); writeProfile(profile, fos); } catch (IOException ioe) { _log.error("Error writing profile to " + f); @@ -310,7 +311,7 @@ class ProfilePersistenceHelper { private File getProfileDir() { if (_profileDir == null) { String dir = _context.getProperty(PROP_PEER_PROFILE_DIR, DEFAULT_PEER_PROFILE_DIR); - _profileDir = new File(_context.getRouterDir(), dir); + _profileDir = new SecureDirectory(_context.getRouterDir(), dir); } return _profileDir; } diff --git a/router/java/src/net/i2p/router/startup/ClientAppConfig.java b/router/java/src/net/i2p/router/startup/ClientAppConfig.java index 982d4a298c4987bc5bdebe601f5f19c4fe4c0d36..33f4351fefcc06754d57bad86465cdb25b76bb89 100644 --- a/router/java/src/net/i2p/router/startup/ClientAppConfig.java +++ b/router/java/src/net/i2p/router/startup/ClientAppConfig.java @@ -11,6 +11,7 @@ import java.util.Properties; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; +import net.i2p.util.SecureFileOutputStream; /** @@ -191,7 +192,7 @@ public class ClientAppConfig { File cfgFile = configFile(ctx); FileOutputStream fos = null; try { - fos = new FileOutputStream(cfgFile); + fos = new SecureFileOutputStream(cfgFile); StringBuilder buf = new StringBuilder(2048); for(int i = 0; i < apps.size(); i++) { ClientAppConfig app = (ClientAppConfig) apps.get(i); diff --git a/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java b/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java index 459cd0fb3ae2a8d07959a34cd208efb562cd1124..36b989bc7761609ab28b9bf3d83a3b994432d27a 100644 --- a/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java +++ b/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java @@ -27,6 +27,7 @@ import net.i2p.router.JobImpl; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.util.Log; +import net.i2p.util.SecureFileOutputStream; public class CreateRouterInfoJob extends JobImpl { private static Log _log = new Log(CreateRouterInfoJob.class); @@ -80,12 +81,12 @@ public class CreateRouterInfoJob extends JobImpl { String infoFilename = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT); File ifile = new File(getContext().getRouterDir(), infoFilename); - fos1 = new FileOutputStream(ifile); + fos1 = new SecureFileOutputStream(ifile); info.writeBytes(fos1); String keyFilename = getContext().getProperty(Router.PROP_KEYS_FILENAME, Router.PROP_KEYS_FILENAME_DEFAULT); File kfile = new File(getContext().getRouterDir(), keyFilename); - fos2 = new FileOutputStream(kfile); + fos2 = new SecureFileOutputStream(kfile); privkey.writeBytes(fos2); signingPrivKey.writeBytes(fos2); pubkey.writeBytes(fos2); diff --git a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java index e3daf60ea44d7fec76f55b3b11eef195c44d1936..b417c32bbb3343ed72f34dae41f0d7399a32343c 100644 --- a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java +++ b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java @@ -26,6 +26,7 @@ import net.i2p.router.JobImpl; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.util.Log; +import net.i2p.util.SecureFileOutputStream; /** * This used be called from StartAcceptingClientsJob but is now disabled. @@ -135,7 +136,7 @@ public class RebuildRouterInfoJob extends JobImpl { FileOutputStream fos = null; try { - fos = new FileOutputStream(infoFile); + fos = new SecureFileOutputStream(infoFile); info.writeBytes(fos); } catch (DataFormatException dfe) { _log.log(Log.CRIT, "Error rebuilding the router information", dfe); diff --git a/router/java/src/net/i2p/router/startup/WorkingDir.java b/router/java/src/net/i2p/router/startup/WorkingDir.java index 545239777fedf09d15a4706dacd5683a62083308..0b32c661f0ddce616cb77f0e184006375088afdf 100644 --- a/router/java/src/net/i2p/router/startup/WorkingDir.java +++ b/router/java/src/net/i2p/router/startup/WorkingDir.java @@ -11,6 +11,8 @@ import java.io.PrintWriter; import java.util.Properties; import net.i2p.data.DataHelper; +import net.i2p.util.SecureDirectory; +import net.i2p.util.SecureFileOutputStream; /** * Get a working directory for i2p. @@ -64,19 +66,19 @@ public class WorkingDir { boolean isWindows = System.getProperty("os.name").startsWith("Win"); File dirf = null; if (dir != null) { - dirf = new File(dir); + dirf = new SecureDirectory(dir); } else { String home = System.getProperty("user.home"); if (isWindows) { String appdata = System.getenv("APPDATA"); if (appdata != null) home = appdata; - dirf = new File(home, WORKING_DIR_DEFAULT_WINDOWS); + dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT_WINDOWS); } else { if (DAEMON_USER.equals(System.getProperty("user.name"))) - dirf = new File(home, WORKING_DIR_DEFAULT_DAEMON); + dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT_DAEMON); else - dirf = new File(home, WORKING_DIR_DEFAULT); + dirf = new SecureDirectory(home, WORKING_DIR_DEFAULT); } } @@ -143,7 +145,7 @@ public class WorkingDir { // this one must be after MIGRATE_BASE success &= migrateJettyXml(oldDirf, dirf); success &= migrateClientsConfig(oldDirf, dirf); - success &= copy(new File(oldDirf, "docs/news.xml"), new File(dirf, "docs")); + success &= copy(new File(oldDirf, "docs/news.xml"), new SecureDirectory(dirf, "docs")); // Report success or failure if (success) { @@ -197,7 +199,7 @@ public class WorkingDir { PrintWriter out = null; try { in = new FileInputStream(oldFile); - out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), "UTF-8"))); + out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(newFile), "UTF-8"))); out.println("# Modified by I2P User dir migration script"); String s = null; boolean isDaemon = DAEMON_USER.equals(System.getProperty("user.name")); @@ -240,7 +242,7 @@ public class WorkingDir { PrintWriter out = null; try { in = new FileInputStream(oldFile); - out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), "UTF-8"))); + out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(newFile), "UTF-8"))); String s = null; while ((s = DataHelper.readLine(in)) != null) { if (s.indexOf("./eepsite/") >= 0) { @@ -270,7 +272,7 @@ public class WorkingDir { * @param targetDir the directory to copy to, will be created if it doesn't exist * @return true for success OR if src does not exist */ - public static final boolean copy(File src, File targetDir) { + private static boolean copy(File src, File targetDir) { if (!src.exists()) return true; if (!targetDir.exists()) { @@ -280,7 +282,8 @@ public class WorkingDir { } System.err.println("Created " + targetDir.getPath()); } - File targetFile = new File(targetDir, src.getName()); + // SecureDirectory is a File so this works for non-directories too + File targetFile = new SecureDirectory(targetDir, src.getName()); if (!src.isDirectory()) return copyFile(src, targetFile); File children[] = src.listFiles(); @@ -305,10 +308,10 @@ public class WorkingDir { /** * @param src not a directory, must exist - * @param dst not a directory, will be overwritten if existing + * @param dst not a directory, will be overwritten if existing, will be mode 600 * @return true if it was copied successfully */ - public static boolean copyFile(File src, File dst) { + private static boolean copyFile(File src, File dst) { if (!src.exists()) return false; boolean rv = true; @@ -317,7 +320,7 @@ public class WorkingDir { FileOutputStream out = null; try { in = new FileInputStream(src); - out = new FileOutputStream(dst); + out = new SecureFileOutputStream(dst); int read = 0; while ( (read = in.read(buf)) != -1)