diff --git a/res/values/strings.xml b/res/values/strings.xml index 81ed8b75d85256dea3f108f7bff2ae6bfa476b84..27bb49d469faed1ab836e3bb30b6d542e5e4239f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -89,6 +89,8 @@ <string name="i2ptunnel_not_initialized">Tunnels are not initialized yet, please reload in two minutes.</string> <string name="i2ptunnel_new_tunnel">New Tunnel</string> + <string name="i2ptunnel_msg_config_saved">Configuration changes saved</string> + <string name="i2ptunnel_msg_config_save_failed">Failed to save configuration</string> <string name="i2ptunnel_wizard_k_client_server">Client or Server</string> <string name="i2ptunnel_wizard_v_client">Client tunnel</string> diff --git a/src/net/i2p/android/i2ptunnel/fragment/TunnelListFragment.java b/src/net/i2p/android/i2ptunnel/fragment/TunnelListFragment.java index 4bb2d0035a6deef124a3a64fe8ae28935ac43a63..a69d558e6341b3808bdd80a1b6909255ac2d2b07 100644 --- a/src/net/i2p/android/i2ptunnel/fragment/TunnelListFragment.java +++ b/src/net/i2p/android/i2ptunnel/fragment/TunnelListFragment.java @@ -6,6 +6,7 @@ import net.i2p.android.i2ptunnel.activity.TunnelWizardActivity; import net.i2p.android.i2ptunnel.adapter.TunnelEntryAdapter; import net.i2p.android.i2ptunnel.loader.TunnelEntry; import net.i2p.android.i2ptunnel.loader.TunnelEntryLoader; +import net.i2p.android.i2ptunnel.util.TunnelConfig; import net.i2p.android.router.R; import net.i2p.i2ptunnel.TunnelControllerGroup; import android.app.Activity; @@ -157,6 +158,9 @@ public class TunnelListFragment extends ListFragment if (requestCode == TUNNEL_WIZARD_REQUEST) { if (resultCode == Activity.RESULT_OK) { Bundle tunnelData = data.getExtras().getBundle(TUNNEL_WIZARD_DATA); + TunnelConfig cfg = TunnelConfig.createFromWizard(getActivity(), mGroup, tunnelData); + TunnelEntry tunnel = TunnelEntry.createNewTunnel(getActivity(), mGroup, cfg); + mAdapter.add(tunnel); } } } diff --git a/src/net/i2p/android/i2ptunnel/loader/TunnelEntry.java b/src/net/i2p/android/i2ptunnel/loader/TunnelEntry.java index 9f9aa757aa845bc7066b42ce2501a40035df3dec..a0f2913fb380e690d0adf74017679bd9cb54ead8 100644 --- a/src/net/i2p/android/i2ptunnel/loader/TunnelEntry.java +++ b/src/net/i2p/android/i2ptunnel/loader/TunnelEntry.java @@ -1,11 +1,17 @@ package net.i2p.android.i2ptunnel.loader; +import java.util.List; + import android.content.Context; import android.graphics.drawable.Drawable; +import android.widget.Toast; +import net.i2p.android.i2ptunnel.util.TunnelConfig; +import net.i2p.android.i2ptunnel.util.TunnelUtil; import net.i2p.android.router.R; import net.i2p.data.Destination; import net.i2p.data.PrivateKeyFile; import net.i2p.i2ptunnel.TunnelController; +import net.i2p.i2ptunnel.TunnelControllerGroup; public class TunnelEntry { public static final int RUNNING = 1; @@ -17,6 +23,20 @@ public class TunnelEntry { private final TunnelController mController; private final int mId; + public static TunnelEntry createNewTunnel( + Context ctx, + TunnelControllerGroup tcg, + TunnelConfig cfg) { + int tunnelId = tcg.getControllers().size(); + List<String> msgs = TunnelUtil.saveTunnel( + ctx, tcg, -1, cfg.getConfig()); + // TODO: Do something else with the other messages. + Toast.makeText(ctx.getApplicationContext(), + msgs.get(0), Toast.LENGTH_LONG).show(); + TunnelController cur = TunnelUtil.getController(tcg, tunnelId); + return new TunnelEntry(ctx, cur, tunnelId); + } + public TunnelEntry(Context context, TunnelController controller, int id) { mContext = context; mController = controller; @@ -46,44 +66,7 @@ public class TunnelEntry { } public String getType() { - if ("client".equals(mController.getType())) - return mContext.getResources() - .getString(R.string.i2ptunnel_type_client); - else if ("httpclient".equals(mController.getType())) - return mContext.getResources() - .getString(R.string.i2ptunnel_type_httpclient); - else if ("ircclient".equals(mController.getType())) - return mContext.getResources() - .getString(R.string.i2ptunnel_type_ircclient); - else if ("server".equals(mController.getType())) - return mContext.getResources() - .getString(R.string.i2ptunnel_type_server); - else if ("httpserver".equals(mController.getType())) - return mContext.getResources() - .getString(R.string.i2ptunnel_type_httpserver); - else if ("sockstunnel".equals(mController.getType())) - return mContext.getResources() - .getString(R.string.i2ptunnel_type_sockstunnel); - else if ("socksirctunnel".equals(mController.getType())) - return mContext.getResources() - .getString(R.string.i2ptunnel_type_socksirctunnel); - else if ("connectclient".equals(mController.getType())) - return mContext.getResources() - .getString(R.string.i2ptunnel_type_connectclient); - else if ("ircserver".equals(mController.getType())) - return mContext.getResources() - .getString(R.string.i2ptunnel_type_ircserver); - else if ("streamrclient".equals(mController.getType())) - return mContext.getResources() - .getString(R.string.i2ptunnel_type_streamrclient); - else if ("streamrserver".equals(mController.getType())) - return mContext.getResources() - .getString(R.string.i2ptunnel_type_streamrserver); - else if ("httpbidirserver".equals(mController.getType())) - return mContext.getResources() - .getString(R.string.i2ptunnel_type_httpbidirserver); - else - return mController.getType(); + return TunnelUtil.getTypeName(mController.getType(), mContext); } public String getDescription() { @@ -108,17 +91,7 @@ public class TunnelEntry { } public boolean isClient() { - return isClient(mController.getType()); - } - - public static boolean isClient(String type) { - return ( ("client".equals(type)) || - ("httpclient".equals(type)) || - ("sockstunnel".equals(type)) || - ("socksirctunnel".equals(type)) || - ("connectclient".equals(type)) || - ("streamrclient".equals(type)) || - ("ircclient".equals(type))); + return TunnelUtil.isClient(mController.getType()); } /* Client tunnel data */ diff --git a/src/net/i2p/android/i2ptunnel/util/TunnelConfig.java b/src/net/i2p/android/i2ptunnel/util/TunnelConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..5bbb41b367502ca16c5b8926fa4dbb121b2f231d --- /dev/null +++ b/src/net/i2p/android/i2ptunnel/util/TunnelConfig.java @@ -0,0 +1,668 @@ +package net.i2p.android.i2ptunnel.util; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.concurrent.ConcurrentHashMap; + +import android.content.Context; +import android.content.res.Resources; +import android.os.Bundle; + +import net.i2p.I2PAppContext; +import net.i2p.android.router.R; +import net.i2p.android.wizard.model.Page; +import net.i2p.i2ptunnel.I2PTunnelConnectClient; +import net.i2p.i2ptunnel.I2PTunnelHTTPClient; +import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase; +import net.i2p.i2ptunnel.I2PTunnelIRCClient; +import net.i2p.i2ptunnel.TunnelControllerGroup; +import net.i2p.util.ConcurrentHashSet; +import net.i2p.util.PasswordManager; + +public class TunnelConfig { + protected final I2PAppContext _context; + + private String _type; + private String _name; + private String _description; + private String _i2cpHost; + private String _i2cpPort; + private String _tunnelDepth; + private String _tunnelQuantity; + private String _tunnelVariance; + private String _tunnelBackupQuantity; + private boolean _connectDelay; + private String _customOptions; + private String _proxyList; + private String _port; + private String _reachableBy; + private String _targetDestination; + private String _targetHost; + private String _targetPort; + private String _spoofedHost; + private String _privKeyFile; + private String _profile; + private boolean _startOnLoad; + private boolean _sharedClient; + private final Set<String> _booleanOptions; + private final Map<String, String> _otherOptions; + private String _newProxyUser; + private String _newProxyPW; + + static final String CLIENT_NICKNAME = "shared clients"; + + public static TunnelConfig createFromWizard( + Context ctx, TunnelControllerGroup tcg, Bundle data) { + // Get the Bundle keys + Resources res = ctx.getResources(); + + String kClientServer = res.getString(R.string.i2ptunnel_wizard_k_client_server); + String kType = res.getString(R.string.i2ptunnel_wizard_k_type); + + String kName = res.getString(R.string.i2ptunnel_wizard_k_name); + String kDesc = res.getString(R.string.i2ptunnel_wizard_k_desc); + String kDest = res.getString(R.string.i2ptunnel_wizard_k_dest); + String kOutproxies = res.getString(R.string.i2ptunnel_wizard_k_outproxies); + String kTargetHost = res.getString(R.string.i2ptunnel_wizard_k_target_host); + String kTargetPort = res.getString(R.string.i2ptunnel_wizard_k_target_port); + String kReachableOn = res.getString(R.string.i2ptunnel_wizard_k_reachable_on); + String kBindingPort = res.getString(R.string.i2ptunnel_wizard_k_binding_port); + String kAutoStart = res.getString(R.string.i2ptunnel_wizard_k_auto_start); + + // Create the TunnelConfig + TunnelConfig cfg = new TunnelConfig(); + + // Get/set the tunnel wizard settings + String clientServer = data.getBundle(kClientServer).getString(Page.SIMPLE_DATA_KEY); + String typeName = data.getBundle(clientServer + ":" + kType).getString(Page.SIMPLE_DATA_KEY); + String type = TunnelUtil.getTypeFromName(typeName, ctx); + cfg.setType(type); + + String name = data.getBundle(kName).getString(Page.SIMPLE_DATA_KEY); + cfg.setName(name); + + String desc = data.getBundle(kDesc).getString(Page.SIMPLE_DATA_KEY); + cfg.setDescription(desc); + + String dest = null; + Bundle pageData = data.getBundle(kDest); + if (pageData != null) dest = pageData.getString(Page.SIMPLE_DATA_KEY); + cfg.setTargetDestination(dest); + + String outproxies = null; + pageData = data.getBundle(kOutproxies); + if (pageData != null) outproxies = pageData.getString(Page.SIMPLE_DATA_KEY); + cfg.setProxyList(outproxies); + + String targetHost = null; + pageData = data.getBundle(kTargetHost); + if (pageData != null) targetHost = pageData.getString(Page.SIMPLE_DATA_KEY); + cfg.setTargetHost(targetHost); + + String targetPort = null; + pageData = data.getBundle(kTargetPort); + if (pageData != null) targetPort = pageData.getString(Page.SIMPLE_DATA_KEY); + cfg.setTargetPort(targetPort); + + String reachableOn = null; + pageData = data.getBundle(kReachableOn); + if (pageData != null) reachableOn = pageData.getString(Page.SIMPLE_DATA_KEY); + cfg.setReachableBy(reachableOn); + + String bindingPort = null; + pageData = data.getBundle(kBindingPort); + if (pageData != null) bindingPort = pageData.getString(Page.SIMPLE_DATA_KEY); + cfg.setPort(bindingPort); + + boolean autoStart = data.getBundle(kAutoStart).getBoolean(Page.SIMPLE_DATA_KEY); + cfg.setStartOnLoad(autoStart); + + // Set sensible defaults for a new tunnel + cfg.setTunnelDepth("3"); + cfg.setTunnelVariance("0"); + cfg.setTunnelQuantity("2"); + cfg.setTunnelBackupQuantity("0"); + cfg.setClientHost("internal"); + cfg.setClientport("internal"); + cfg.setCustomOptions(""); + if (!"streamrclient".equals(type)) { + cfg.setProfile("bulk"); + cfg.setReduceCount("1"); + cfg.setReduceTime("20"); + } + if (TunnelUtil.isClient(type)) { /* Client-only defaults */ + if (!"streamrclient".equals(type)) { + cfg.setNewDest("0"); + cfg.setCloseTime("30"); + } + if ("httpclient".equals(type) || + "connectclient".equals(type) || + "sockstunnel".equals(type) | + "socksirctunnel".equals(type)) { + cfg.setProxyUsername(""); + cfg.setProxyPassword(""); + cfg.setOutproxyUsername(""); + cfg.setOutproxyPassword(""); + } + if ("httpclient".equals(type)) + cfg.setJumpList("http://i2host.i2p/cgi-bin/i2hostjump?\nhttp://stats.i2p/cgi-bin/jump.cgi?a="); + } else { /* Server-only defaults */ + cfg.setPrivKeyFile(TunnelUtil.getPrivateKeyFile(tcg, -1)); + cfg.setEncrypt(""); + cfg.setEncryptKey(""); + cfg.setAccessMode("0"); + cfg.setAccessList(""); + cfg.setLimitMinute("0"); + cfg.setLimitHour("0"); + cfg.setLimitDay("0"); + cfg.setTotalMinute("0"); + cfg.setTotalHour("0"); + cfg.setTotalDay("0"); + cfg.setMaxStreams("0"); + } + + return cfg; + } + + public TunnelConfig() { + _context = I2PAppContext.getGlobalContext(); + _booleanOptions = new ConcurrentHashSet<String>(4); + _otherOptions = new ConcurrentHashMap<String,String>(4); + } + + /** + * What type of tunnel (httpclient, ircclient, client, or server). This is + * required when adding a new tunnel. + * + */ + public void setType(String type) { + _type = (type != null ? type.trim() : null); + } + String getType() { return _type; } + + /** Short name of the tunnel */ + public void setName(String name) { + _name = (name != null ? name.trim() : null); + } + /** one line description */ + public void setDescription(String description) { + _description = (description != null ? description.trim() : null); + } + /** I2CP host the router is on, ignored when in router context */ + public void setClientHost(String host) { + _i2cpHost = (host != null ? host.trim() : null); + } + /** I2CP port the router is on, ignored when in router context */ + public void setClientport(String port) { + _i2cpPort = (port != null ? port.trim() : null); + } + /** how many hops to use for inbound tunnels */ + public void setTunnelDepth(String tunnelDepth) { + _tunnelDepth = (tunnelDepth != null ? tunnelDepth.trim() : null); + } + /** how many parallel inbound tunnels to use */ + public void setTunnelQuantity(String tunnelQuantity) { + _tunnelQuantity = (tunnelQuantity != null ? tunnelQuantity.trim() : null); + } + /** how much randomisation to apply to the depth of tunnels */ + public void setTunnelVariance(String tunnelVariance) { + _tunnelVariance = (tunnelVariance != null ? tunnelVariance.trim() : null); + } + /** how many tunnels to hold in reserve to guard against failures */ + public void setTunnelBackupQuantity(String tunnelBackupQuantity) { + _tunnelBackupQuantity = (tunnelBackupQuantity != null ? tunnelBackupQuantity.trim() : null); + } + /** what I2P session overrides should be used */ + public void setCustomOptions(String customOptions) { + _customOptions = (customOptions != null ? customOptions.trim() : null); + } + /** what HTTP outproxies should be used (httpclient specific) */ + public void setProxyList(String proxyList) { + _proxyList = (proxyList != null ? proxyList.trim() : null); + } + /** what port should this client/httpclient/ircclient listen on */ + public void setPort(String port) { + _port = (port != null ? port.trim() : null); + } + /** + * what interface should this client/httpclient/ircclient listen on + */ + public void setReachableBy(String reachableBy) { + _reachableBy = (reachableBy != null ? reachableBy.trim() : null); + } + /** What peer does this client tunnel point at */ + public void setTargetDestination(String dest) { + _targetDestination = (dest != null ? dest.trim() : null); + } + /** What host does this server tunnel point at */ + public void setTargetHost(String host) { + _targetHost = (host != null ? host.trim() : null); + } + /** What port does this server tunnel point at */ + public void setTargetPort(String port) { + _targetPort = (port != null ? port.trim() : null); + } + /** What host does this http server tunnel spoof */ + public void setSpoofedHost(String host) { + _spoofedHost = (host != null ? host.trim() : null); + } + /** What filename is this server tunnel's private keys stored in */ + public void setPrivKeyFile(String file) { + _privKeyFile = (file != null ? file.trim() : null); + } + /** + * If called with true, we want this tunnel to start whenever it is + * loaded (aka right now and whenever the router is started up) + */ + public void setStartOnLoad(boolean val) { + _startOnLoad = val; + } + public void setShared(boolean val) { + _sharedClient=val; + } + public void setConnectDelay(String moo) { + _connectDelay = true; + } + public void setProfile(String profile) { + _profile = profile; + } + + public void setReduce(String moo) { + _booleanOptions.add("i2cp.reduceOnIdle"); + } + public void setClose(String moo) { + _booleanOptions.add("i2cp.closeOnIdle"); + } + public void setEncrypt(String moo) { + _booleanOptions.add("i2cp.encryptLeaseSet"); + } + + /** @since 0.8.9 */ + public void setDCC(String moo) { + _booleanOptions.add(I2PTunnelIRCClient.PROP_DCC); + } + + protected static final String PROP_ENABLE_ACCESS_LIST = "i2cp.enableAccessList"; + protected static final String PROP_ENABLE_BLACKLIST = "i2cp.enableBlackList"; + + public void setAccessMode(String val) { + if ("1".equals(val)) + _booleanOptions.add(PROP_ENABLE_ACCESS_LIST); + else if ("2".equals(val)) + _booleanOptions.add(PROP_ENABLE_BLACKLIST); + } + + public void setDelayOpen(String moo) { + _booleanOptions.add("i2cp.delayOpen"); + } + public void setNewDest(String val) { + if ("1".equals(val)) + _booleanOptions.add("i2cp.newDestOnResume"); + else if ("2".equals(val)) + _booleanOptions.add("persistentClientKey"); + } + + public void setReduceTime(String val) { + if (val != null) { + try { + _otherOptions.put("i2cp.reduceIdleTime", "" + (Integer.parseInt(val.trim()) * 60*1000)); + } catch (NumberFormatException nfe) {} + } + } + public void setReduceCount(String val) { + if (val != null) + _otherOptions.put("i2cp.reduceQuantity", val.trim()); + } + public void setEncryptKey(String val) { + if (val != null) + _otherOptions.put("i2cp.leaseSetKey", val.trim()); + } + + public void setAccessList(String val) { + if (val != null) + _otherOptions.put("i2cp.accessList", val.trim().replace("\r\n", ",").replace("\n", ",").replace(" ", ",")); + } + + public void setJumpList(String val) { + if (val != null) + _otherOptions.put(I2PTunnelHTTPClient.PROP_JUMP_SERVERS, val.trim().replace("\r\n", ",").replace("\n", ",").replace(" ", ",")); + } + + public void setCloseTime(String val) { + if (val != null) { + try { + _otherOptions.put("i2cp.closeIdleTime", "" + (Integer.parseInt(val.trim()) * 60*1000)); + } catch (NumberFormatException nfe) {} + } + } + + /** all proxy auth @since 0.8.2 */ + public void setProxyAuth(String s) { + if (s != null) + _otherOptions.put(I2PTunnelHTTPClientBase.PROP_AUTH, I2PTunnelHTTPClientBase.DIGEST_AUTH); + } + + public void setProxyUsername(String s) { + if (s != null) + _newProxyUser = s.trim(); + } + + public void setProxyPassword(String s) { + if (s != null) + _newProxyPW = s.trim(); + } + + public void setOutproxyAuth(String s) { + _otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH, I2PTunnelHTTPClientBase.DIGEST_AUTH); + } + + public void setOutproxyUsername(String s) { + if (s != null) + _otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_USER, s.trim()); + } + + public void setOutproxyPassword(String s) { + if (s != null) + _otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_PW, s.trim()); + } + + /** all of these are @since 0.8.3 */ + protected static final String PROP_MAX_CONNS_MIN = "i2p.streaming.maxConnsPerMinute"; + protected static final String PROP_MAX_CONNS_HOUR = "i2p.streaming.maxConnsPerHour"; + protected static final String PROP_MAX_CONNS_DAY = "i2p.streaming.maxConnsPerDay"; + protected static final String PROP_MAX_TOTAL_CONNS_MIN = "i2p.streaming.maxTotalConnsPerMinute"; + protected static final String PROP_MAX_TOTAL_CONNS_HOUR = "i2p.streaming.maxTotalConnsPerHour"; + protected static final String PROP_MAX_TOTAL_CONNS_DAY = "i2p.streaming.maxTotalConnsPerDay"; + protected static final String PROP_MAX_STREAMS = "i2p.streaming.maxConcurrentStreams"; + + public void setLimitMinute(String s) { + if (s != null) + _otherOptions.put(PROP_MAX_CONNS_MIN, s.trim()); + } + + public void setLimitHour(String s) { + if (s != null) + _otherOptions.put(PROP_MAX_CONNS_HOUR, s.trim()); + } + + public void setLimitDay(String s) { + if (s != null) + _otherOptions.put(PROP_MAX_CONNS_DAY, s.trim()); + } + + public void setTotalMinute(String s) { + if (s != null) + _otherOptions.put(PROP_MAX_TOTAL_CONNS_MIN, s.trim()); + } + + public void setTotalHour(String s) { + if (s != null) + _otherOptions.put(PROP_MAX_TOTAL_CONNS_HOUR, s.trim()); + } + + public void setTotalDay(String s) { + if (s != null) + _otherOptions.put(PROP_MAX_TOTAL_CONNS_DAY, s.trim()); + } + + public void setMaxStreams(String s) { + if (s != null) + _otherOptions.put(PROP_MAX_STREAMS, s.trim()); + } + + /** + * Based on all provided data, create a set of configuration parameters + * suitable for use in a TunnelController. This will replace (not add to) + * any existing parameters, so this should return a comprehensive mapping. + * + */ + public Properties getConfig() { + Properties config = new Properties(); + updateConfigGeneric(config); + + if ((TunnelUtil.isClient(_type) && !"streamrclient".equals(_type)) || "streamrserver".equals(_type)) { + // streamrserver uses interface + if (_reachableBy != null) + config.setProperty("interface", _reachableBy); + else + config.setProperty("interface", ""); + } else { + // streamrclient uses targetHost + if (_targetHost != null) + config.setProperty("targetHost", _targetHost); + } + + if (TunnelUtil.isClient(_type)) { + // generic client stuff + if (_port != null) + config.setProperty("listenPort", _port); + config.setProperty("sharedClient", _sharedClient + ""); + for (String p : _booleanClientOpts) + config.setProperty("option." + p, "" + _booleanOptions.contains(p)); + for (String p : _otherClientOpts) + if (_otherOptions.containsKey(p)) + config.setProperty("option." + p, _otherOptions.get(p)); + } else { + // generic server stuff + if (_targetPort != null) + config.setProperty("targetPort", _targetPort); + for (String p : _booleanServerOpts) + config.setProperty("option." + p, "" + _booleanOptions.contains(p)); + for (String p : _otherServerOpts) + if (_otherOptions.containsKey(p)) + config.setProperty("option." + p, _otherOptions.get(p)); + } + + // generic proxy stuff + if ("httpclient".equals(_type) || "connectclient".equals(_type) || + "sockstunnel".equals(_type) ||"socksirctunnel".equals(_type)) { + for (String p : _booleanProxyOpts) + config.setProperty("option." + p, "" + _booleanOptions.contains(p)); + if (_proxyList != null) + config.setProperty("proxyList", _proxyList); + } + + // Proxy auth including migration to MD5 + if ("httpclient".equals(_type) || "connectclient".equals(_type)) { + // Migrate even if auth is disabled + // go get the old from custom options that updateConfigGeneric() put in there + String puser = "option." + I2PTunnelHTTPClientBase.PROP_USER; + String user = config.getProperty(puser); + String ppw = "option." + I2PTunnelHTTPClientBase.PROP_PW; + String pw = config.getProperty(ppw); + if (user != null && pw != null && user.length() > 0 && pw.length() > 0) { + String pmd5 = "option." + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_PREFIX + + user + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_SUFFIX; + if (config.getProperty(pmd5) == null) { + // not in there, migrate + String realm = _type.equals("httpclient") ? I2PTunnelHTTPClient.AUTH_REALM + : I2PTunnelConnectClient.AUTH_REALM; + String hex = PasswordManager.md5Hex(realm, user, pw); + if (hex != null) { + config.setProperty(pmd5, hex); + config.remove(puser); + config.remove(ppw); + } + } + } + // New user/password + String auth = _otherOptions.get(I2PTunnelHTTPClientBase.PROP_AUTH); + if (auth != null && !auth.equals("false")) { + if (_newProxyUser != null && _newProxyPW != null && + _newProxyUser.length() > 0 && _newProxyPW.length() > 0) { + String pmd5 = "option." + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_PREFIX + + _newProxyUser + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_SUFFIX; + String realm = _type.equals("httpclient") ? I2PTunnelHTTPClient.AUTH_REALM + : I2PTunnelConnectClient.AUTH_REALM; + String hex = PasswordManager.md5Hex(realm, _newProxyUser, _newProxyPW); + if (hex != null) + config.setProperty(pmd5, hex); + } + } + } + + if ("ircclient".equals(_type) || "client".equals(_type) || "streamrclient".equals(_type)) { + if (_targetDestination != null) + config.setProperty("targetDestination", _targetDestination); + } else if ("httpserver".equals(_type) || "httpbidirserver".equals(_type)) { + if (_spoofedHost != null) + config.setProperty("spoofedHost", _spoofedHost); + } + if ("httpbidirserver".equals(_type)) { + if (_port != null) + config.setProperty("listenPort", _port); + if (_reachableBy != null) + config.setProperty("interface", _reachableBy); + else if (_targetHost != null) + config.setProperty("interface", _targetHost); + else + config.setProperty("interface", ""); + } + + if ("ircclient".equals(_type)) { + boolean dcc = _booleanOptions.contains(I2PTunnelIRCClient.PROP_DCC); + config.setProperty("option." + I2PTunnelIRCClient.PROP_DCC, + "" + dcc); + // add some sane server options since they aren't in the GUI (yet) + if (dcc) { + config.setProperty("option." + PROP_MAX_CONNS_MIN, "3"); + config.setProperty("option." + PROP_MAX_CONNS_HOUR, "10"); + config.setProperty("option." + PROP_MAX_TOTAL_CONNS_MIN, "5"); + config.setProperty("option." + PROP_MAX_TOTAL_CONNS_HOUR, "25"); + } + } + + return config; + } + + private static final String _noShowOpts[] = { + "inbound.length", "outbound.length", "inbound.lengthVariance", "outbound.lengthVariance", + "inbound.backupQuantity", "outbound.backupQuantity", "inbound.quantity", "outbound.quantity", + "inbound.nickname", "outbound.nickname", "i2p.streaming.connectDelay", "i2p.streaming.maxWindowSize", + I2PTunnelIRCClient.PROP_DCC + }; + private static final String _booleanClientOpts[] = { + "i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.newDestOnResume", "persistentClientKey", "i2cp.delayOpen" + }; + private static final String _booleanProxyOpts[] = { + I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH + }; + private static final String _booleanServerOpts[] = { + "i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", PROP_ENABLE_ACCESS_LIST, PROP_ENABLE_BLACKLIST + }; + private static final String _otherClientOpts[] = { + "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime", + "outproxyUsername", "outproxyPassword", + I2PTunnelHTTPClient.PROP_JUMP_SERVERS, + I2PTunnelHTTPClientBase.PROP_AUTH + }; + private static final String _otherServerOpts[] = { + "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.leaseSetKey", "i2cp.accessList", + PROP_MAX_CONNS_MIN, PROP_MAX_CONNS_HOUR, PROP_MAX_CONNS_DAY, + PROP_MAX_TOTAL_CONNS_MIN, PROP_MAX_TOTAL_CONNS_HOUR, PROP_MAX_TOTAL_CONNS_DAY, + PROP_MAX_STREAMS + }; + + /** + * do NOT add these to noShoOpts, we must leave them in for HTTPClient and ConnectCLient + * so they will get migrated to MD5 + * TODO migrate socks to MD5 + */ + private static final String _otherProxyOpts[] = { + "proxyUsername", "proxyPassword" + }; + + protected static final Set<String> _noShowSet = new HashSet<String>(64); + protected static final Set<String> _nonProxyNoShowSet = new HashSet<String>(4); + static { + _noShowSet.addAll(Arrays.asList(_noShowOpts)); + _noShowSet.addAll(Arrays.asList(_booleanClientOpts)); + _noShowSet.addAll(Arrays.asList(_booleanProxyOpts)); + _noShowSet.addAll(Arrays.asList(_booleanServerOpts)); + _noShowSet.addAll(Arrays.asList(_otherClientOpts)); + _noShowSet.addAll(Arrays.asList(_otherServerOpts)); + _nonProxyNoShowSet.addAll(Arrays.asList(_otherProxyOpts)); + } + + private void updateConfigGeneric(Properties config) { + config.setProperty("type", _type); + if (_name != null) + config.setProperty("name", _name); + if (_description != null) + config.setProperty("description", _description); + if (!_context.isRouterContext()) { + if (_i2cpHost != null) + config.setProperty("i2cpHost", _i2cpHost); + if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) ) { + config.setProperty("i2cpPort", _i2cpPort); + } else { + config.setProperty("i2cpPort", "7654"); + } + } + if (_privKeyFile != null) + config.setProperty("privKeyFile", _privKeyFile); + + if (_customOptions != null) { + StringTokenizer tok = new StringTokenizer(_customOptions); + while (tok.hasMoreTokens()) { + String pair = tok.nextToken(); + int eq = pair.indexOf('='); + if ( (eq <= 0) || (eq >= pair.length()) ) + continue; + String key = pair.substring(0, eq); + if (_noShowSet.contains(key)) + continue; + // leave in for HTTP and Connect so it can get migrated to MD5 + // hide for SOCKS until migrated to MD5 + if ((!"httpclient".equals(_type)) && + (! "connectclient".equals(_type)) && + _nonProxyNoShowSet.contains(key)) + continue; + String val = pair.substring(eq+1); + config.setProperty("option." + key, val); + } + } + + config.setProperty("startOnLoad", _startOnLoad + ""); + + if (_tunnelQuantity != null) { + config.setProperty("option.inbound.quantity", _tunnelQuantity); + config.setProperty("option.outbound.quantity", _tunnelQuantity); + } + if (_tunnelDepth != null) { + config.setProperty("option.inbound.length", _tunnelDepth); + config.setProperty("option.outbound.length", _tunnelDepth); + } + if (_tunnelVariance != null) { + config.setProperty("option.inbound.lengthVariance", _tunnelVariance); + config.setProperty("option.outbound.lengthVariance", _tunnelVariance); + } + if (_tunnelBackupQuantity != null) { + config.setProperty("option.inbound.backupQuantity", _tunnelBackupQuantity); + config.setProperty("option.outbound.backupQuantity", _tunnelBackupQuantity); + } + if (_connectDelay) + config.setProperty("option.i2p.streaming.connectDelay", "1000"); + else + config.setProperty("option.i2p.streaming.connectDelay", "0"); + if (TunnelUtil.isClient(_type) && _sharedClient) { + config.setProperty("option.inbound.nickname", CLIENT_NICKNAME); + config.setProperty("option.outbound.nickname", CLIENT_NICKNAME); + } else if (_name != null) { + config.setProperty("option.inbound.nickname", _name); + config.setProperty("option.outbound.nickname", _name); + } + if ("interactive".equals(_profile)) + // This was 1 which doesn't make much sense + // The real way to make it interactive is to make the streaming lib + // MessageInputStream flush faster but there's no option for that yet, + // Setting it to 16 instead of the default but not sure what good that is either. + config.setProperty("option.i2p.streaming.maxWindowSize", "16"); + else + config.remove("option.i2p.streaming.maxWindowSize"); + } +} diff --git a/src/net/i2p/android/i2ptunnel/util/TunnelUtil.java b/src/net/i2p/android/i2ptunnel/util/TunnelUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..cd73db5767c54fcba8b325d5bf89653d8ddcb9dc --- /dev/null +++ b/src/net/i2p/android/i2ptunnel/util/TunnelUtil.java @@ -0,0 +1,241 @@ +package net.i2p.android.i2ptunnel.util; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import android.content.Context; +import android.content.res.Resources; + +import net.i2p.I2PAppContext; +import net.i2p.android.router.R; +import net.i2p.android.router.util.Util; +import net.i2p.i2ptunnel.TunnelController; +import net.i2p.i2ptunnel.TunnelControllerGroup; +import net.i2p.util.FileUtil; +import net.i2p.util.SecureFile; + +public abstract class TunnelUtil { + public static TunnelController getController(TunnelControllerGroup tcg, int tunnel) { + if (tunnel < 0) return null; + if (tcg == null) return null; + List<TunnelController> controllers = tcg.getControllers(); + if (controllers.size() > tunnel) + return controllers.get(tunnel); + else + return null; + } + + public static List<String> saveTunnel(Context ctx, + TunnelControllerGroup tcg, + int tunnelId, + Properties config) { + // Get current tunnel controller + TunnelController cur = getController(tcg, tunnelId); + + if (config == null) { + List<String> ret = new ArrayList<String>(); + ret.add("Invalid params"); + return ret; + } + + if (cur == null) { + // creating new + cur = new TunnelController(config, "", true); + tcg.addController(cur); + if (cur.getStartOnLoad()) + cur.startTunnelBackground(); + } else { + cur.setConfig(config, ""); + } + // Only modify other shared tunnels + // if the current tunnel is shared, and of supported type + if (Boolean.parseBoolean(cur.getSharedClient()) && isClient(cur.getType())) { + // all clients use the same I2CP session, and as such, use the same I2CP options + List<TunnelController> controllers = tcg.getControllers(); + + for (int i = 0; i < controllers.size(); i++) { + TunnelController c = controllers.get(i); + + // Current tunnel modified by user, skip + if (c == cur) continue; + + // Only modify this non-current tunnel + // if it belongs to a shared destination, and is of supported type + if (Boolean.parseBoolean(c.getSharedClient()) && isClient(c.getType())) { + Properties cOpt = c.getConfig(""); + if (config.getProperty("option.inbound.quantity") != null) + cOpt.setProperty("option.inbound.quantity", config.getProperty("option.inbound.quantity")); + if (config.getProperty("option.outbound.quantity") != null) + cOpt.setProperty("option.outbound.quantity", config.getProperty("option.outbound.quantity")); + if (config.getProperty("option.inbound.length") != null) + cOpt.setProperty("option.inbound.length", config.getProperty("option.inbound.length")); + if (config.getProperty("option.outbound.length") != null) + cOpt.setProperty("option.outbound.length", config.getProperty("option.outbound.length")); + if (config.getProperty("option.inbound.lengthVariance") != null) + cOpt.setProperty("option.inbound.lengthVariance", config.getProperty("option.inbound.lengthVariance")); + if (config.getProperty("option.outbound.lengthVariance") != null) + cOpt.setProperty("option.outbound.lengthVariance", config.getProperty("option.outbound.lengthVariance")); + if (config.getProperty("option.inbound.backupQuantity") != null) + cOpt.setProperty("option.inbound.backupQuantity", config.getProperty("option.inbound.backupQuantity")); + if (config.getProperty("option.outbound.backupQuantity") != null) + cOpt.setProperty("option.outbound.backupQuantity", config.getProperty("option.outbound.backupQuantity")); + cOpt.setProperty("option.inbound.nickname", TunnelConfig.CLIENT_NICKNAME); + cOpt.setProperty("option.outbound.nickname", TunnelConfig.CLIENT_NICKNAME); + + c.setConfig(cOpt, ""); + } + } + } + + return doSave(ctx, tcg); + } + + /** + * Stop the tunnel, delete from config, + * rename the private key file if in the default directory + */ + public static List<String> deleteTunnel(Context ctx, TunnelControllerGroup tcg, int tunnelId) { + List<String> msgs; + TunnelController cur = getController(tcg, tunnelId); + if (cur == null) { + msgs = new ArrayList<String>(); + msgs.add("Invalid tunnel number"); + return msgs; + } + + msgs = tcg.removeController(cur); + msgs.addAll(doSave(ctx, tcg)); + + // Rename private key file if it was a default name in + // the default directory, so it doesn't get reused when a new + // tunnel is created. + // Use configured file name if available, not the one from the form. + String pk = cur.getPrivKeyFile(); + //if (pk == null) + // pk = _privKeyFile; + if (pk != null && pk.startsWith("i2ptunnel") && pk.endsWith("-privKeys.dat") && + ((!isClient(cur.getType())) || cur.getPersistentClientKey())) { + I2PAppContext context = I2PAppContext.getGlobalContext(); + File pkf = new File(context.getConfigDir(), pk); + if (pkf.exists()) { + String name = cur.getName(); + if (name == null) { + name = cur.getDescription(); + if (name == null) { + name = cur.getType(); + if (name == null) + name = Long.toString(context.clock().now()); + } + } + name = "i2ptunnel-deleted-" + name.replace(' ', '_') + '-' + context.clock().now() + "-privkeys.dat"; + File backupDir = new SecureFile(context.getConfigDir(), TunnelController.KEY_BACKUP_DIR); + File to; + if (backupDir.isDirectory() || backupDir.mkdir()) + to = new File(backupDir, name); + else + to = new File(context.getConfigDir(), name); + boolean success = FileUtil.rename(pkf, to); + if (success) + msgs.add("Private key file " + pkf.getAbsolutePath() + + " renamed to " + to.getAbsolutePath()); + } + } + return msgs; + } + + private static List<String> doSave(Context ctx, TunnelControllerGroup tcg) { + List<String> rv = tcg.clearAllMessages(); + try { + tcg.saveConfig(); + rv.add(0, ctx.getResources().getString(R.string.i2ptunnel_msg_config_saved)); + } catch (IOException ioe) { + Util.e("Failed to save config file", ioe); + rv.add(0, ctx.getResources().getString(R.string.i2ptunnel_msg_config_save_failed) + ": " + ioe.toString()); + } + return rv; + } + + /* General tunnel data for any type */ + + public static String getTypeFromName(String typeName, Context ctx) { + Resources res = ctx.getResources(); + if (res.getString(R.string.i2ptunnel_type_client).equals(typeName)) + return "client"; + else if (res.getString(R.string.i2ptunnel_type_httpclient).equals(typeName)) + return "httpclient"; + else if (res.getString(R.string.i2ptunnel_type_ircclient).equals(typeName)) + return "ircclient"; + else if (res.getString(R.string.i2ptunnel_type_server).equals(typeName)) + return "server"; + else if (res.getString(R.string.i2ptunnel_type_httpserver).equals(typeName)) + return "httpserver"; + else if (res.getString(R.string.i2ptunnel_type_sockstunnel).equals(typeName)) + return "sockstunnel"; + else if (res.getString(R.string.i2ptunnel_type_socksirctunnel).equals(typeName)) + return "socksirctunnel"; + else if (res.getString(R.string.i2ptunnel_type_connectclient).equals(typeName)) + return "connectclient"; + else if (res.getString(R.string.i2ptunnel_type_ircserver).equals(typeName)) + return "ircserver"; + else if (res.getString(R.string.i2ptunnel_type_streamrclient).equals(typeName)) + return "streamrclient"; + else if (res.getString(R.string.i2ptunnel_type_streamrserver).equals(typeName)) + return "streamrserver"; + else if (res.getString(R.string.i2ptunnel_type_httpbidirserver).equals(typeName)) + return "httpbidirserver"; + else + return typeName; + } + + public static String getTypeName(String type, Context context) { + Resources res = context.getResources(); + if ("client".equals(type)) + return res.getString(R.string.i2ptunnel_type_client); + else if ("httpclient".equals(type)) + return res.getString(R.string.i2ptunnel_type_httpclient); + else if ("ircclient".equals(type)) + return res.getString(R.string.i2ptunnel_type_ircclient); + else if ("server".equals(type)) + return res.getString(R.string.i2ptunnel_type_server); + else if ("httpserver".equals(type)) + return res.getString(R.string.i2ptunnel_type_httpserver); + else if ("sockstunnel".equals(type)) + return res.getString(R.string.i2ptunnel_type_sockstunnel); + else if ("socksirctunnel".equals(type)) + return res.getString(R.string.i2ptunnel_type_socksirctunnel); + else if ("connectclient".equals(type)) + return res.getString(R.string.i2ptunnel_type_connectclient); + else if ("ircserver".equals(type)) + return res.getString(R.string.i2ptunnel_type_ircserver); + else if ("streamrclient".equals(type)) + return res.getString(R.string.i2ptunnel_type_streamrclient); + else if ("streamrserver".equals(type)) + return res.getString(R.string.i2ptunnel_type_streamrserver); + else if ("httpbidirserver".equals(type)) + return res.getString(R.string.i2ptunnel_type_httpbidirserver); + else + return type; + } + + public static boolean isClient(String type) { + return ( ("client".equals(type)) || + ("httpclient".equals(type)) || + ("sockstunnel".equals(type)) || + ("socksirctunnel".equals(type)) || + ("connectclient".equals(type)) || + ("streamrclient".equals(type)) || + ("ircclient".equals(type))); + } + + public static String getPrivateKeyFile(TunnelControllerGroup tcg, int tunnel) { + TunnelController tun = getController(tcg, tunnel); + if (tun != null && tun.getPrivKeyFile() != null) + return tun.getPrivKeyFile(); + if (tunnel < 0) + tunnel = tcg == null ? 999 : tcg.getControllers().size(); + return "i2ptunnel" + tunnel + "-privKeys.dat"; + } +}