diff --git a/core/java/src/net/i2p/client/naming/EepGetNamingService.java b/core/java/src/net/i2p/client/naming/EepGetNamingService.java new file mode 100644 index 000000000..99aae007c --- /dev/null +++ b/core/java/src/net/i2p/client/naming/EepGetNamingService.java @@ -0,0 +1,145 @@ +/* + * By zzz 2008, released into the public domain + * with no warranty of any kind, either expressed or implied. + */ +package net.i2p.client.naming; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.StringTokenizer; + +import net.i2p.I2PAppContext; +import net.i2p.data.DataHelper; +import net.i2p.data.Destination; +import net.i2p.util.EepGet; +import net.i2p.util.Log; + +/** + * A network-based naming service using HTTP, with in-memory caching. + * Fetches from one or more remote (in-i2p) CGI services using HTTP GET. + * + * The remote HTTP service takes a CGI parameter and must return (only) the + * 516-byte Base64 destination, or hostname=dest. + * A trailing \n or \r\n is acceptable. + * + * Should be used from MetaNamingService, after HostsTxtNamingService. + * Cannot be used as the only NamingService! Be sure any naming service hosts + * are in hosts.txt. + * + * Sample config to put in configadvanced.jsp (restart required): + * + * i2p.naming.impl=net.i2p.client.naming.MetaNamingService + * i2p.nameservicelist=net.i2p.client.naming.HostsTxtNamingService,net.i2p.client.naming.EepGetNamingService + * i2p.naming.eepget.list=http://namingservice.i2p/cgi-bin/lkup.cgi?host=,http://i2host.i2p/cgi-bin/i2hostquery? + * + */ +public class EepGetNamingService extends NamingService { + + private final static String PROP_EEPGET_LIST = "i2p.naming.eepget.list"; + private final static String DEFAULT_EEPGET_LIST = "http://i2host.i2p/cgi-bin/i2hostquery?"; + private static Properties _hosts; + private final static Log _log = new Log(EepGetNamingService.class); + + /** + * The naming service should only be constructed and accessed through the + * application context. This constructor should only be used by the + * appropriate application context itself. + * + */ + public EepGetNamingService(I2PAppContext context) { + super(context); + _hosts = new Properties(); + } + + private List getURLs() { + String list = _context.getProperty(PROP_EEPGET_LIST, DEFAULT_EEPGET_LIST); + StringTokenizer tok = new StringTokenizer(list, ","); + List rv = new ArrayList(tok.countTokens()); + while (tok.hasMoreTokens()) + rv.add(tok.nextToken()); + return rv; + } + + public Destination lookup(String hostname) { + hostname = hostname.toLowerCase(); + + // check the cache + String key = _hosts.getProperty(hostname); + if (key != null) { + _log.error("Found in cache: " + hostname); + return lookupBase64(key); + } + + List URLs = getURLs(); + if (URLs.size() == 0) + return null; + + // prevent lookup loops - this cannot be the only lookup service + for (int i = 0; i < URLs.size(); i++) { + String url = (String)URLs.get(i); + if (url.startsWith("http://" + hostname + "/")) { + _log.error("Lookup loop: " + hostname); + return null; + } + } + + // lookup + for (int i = 0; i < URLs.size(); i++) { + String url = (String)URLs.get(i); + key = fetchAddr(url, hostname); + if (key != null) { + _log.error("Success: " + url + hostname); + _hosts.setProperty(hostname, key); // cache + return lookupBase64(key); + } + } + + // If we can't find name, + // assume it's a key. + return lookupBase64(hostname); + } + + private static final int DEST_SIZE = 516; // Std. Base64 length (no certificate) + private static final int MAX_RESPONSE = DEST_SIZE + 68 + 10; // allow for hostname= and some trailing stuff + private String fetchAddr(String url, String hostname) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(MAX_RESPONSE); + + try { + // Do a proxied eepget into our ByteArrayOutputStream with 0 retries + EepGet get = new EepGet(_context, true, "localhost", 4444, 0, DEST_SIZE, MAX_RESPONSE, + null, baos, url + hostname, false, null, null); + // 10s header timeout, 15s total timeout, unlimited inactivity timeout + if (get.fetch(10*1000l, 15*1000l, -1l)) { + if (baos.size() < DEST_SIZE) { + _log.error("Short response: " + url + hostname); + return null; + } + String key = baos.toString(); + if (key.startsWith(hostname + "=")) // strip hostname= + key = key.substring(hostname.length() + 1); + key = key.substring(0, DEST_SIZE); // catch IndexOutOfBounds exception below + if (!key.endsWith("AAAA")) { + _log.error("Invalid key: " + url + hostname); + return null; + } + if (key.replaceAll("[a-zA-Z0-9~-]", "").length() != 0) { + _log.error("Invalid chars: " + url + hostname); + return null; + } + return key; + } + _log.error("Fetch failed from: " + url + hostname); + return null; + } catch (Throwable t) { + _log.error("Error fetching the addr", t); + } + _log.error("Caught from: " + url + hostname); + return null; + } + + public String reverseLookup(Destination dest) { + return null; + } +} diff --git a/core/java/src/net/i2p/client/naming/ExecNamingService.java b/core/java/src/net/i2p/client/naming/ExecNamingService.java new file mode 100644 index 000000000..19e94dfca --- /dev/null +++ b/core/java/src/net/i2p/client/naming/ExecNamingService.java @@ -0,0 +1,136 @@ +/* + * By zzz 2008, released into the public domain + * with no warranty of any kind, either expressed or implied. + */ +package net.i2p.client.naming; + +import java.io.InputStream; +import java.util.Properties; + +import net.i2p.I2PAppContext; +import net.i2p.data.DataHelper; +import net.i2p.data.Destination; +import net.i2p.util.Exec; +import net.i2p.util.Log; + +/** + * An interface to an external naming service program, with in-memory caching. + * This can be used as a simple and flexible way to experiment with + * alternative naming systems. + * + * The external command takes a hostname argument and must return (only) the + * 516-byte Base64 destination, or hostname=dest, on stdout. + * A trailing \n or \r\n is acceptable. + * The command must exit 0 on success. Nonzero on failure is optional. + * + * The external command can do local and/or remote (via i2p or not) lookups. + * No timeouts are implemented here - the author of the external program + * must ensure that the program returns in a reasonable amount of time - + * (15 sec max suggested) + * + * Can be used from MetaNamingService, (e.g. after HostsTxtNamingService), + * or as the sole naming service. + * + * Sample chained config to put in configadvanced.jsp (restart required): + * + * i2p.naming.impl=net.i2p.client.naming.MetaNamingService + * i2p.nameservicelist=net.i2p.client.naming.HostsTxtNamingService,net.i2p.client.naming.ExecNamingService + * i2p.naming.exec.command=/usr/local/bin/i2presolve + * + * Sample unchained config to put in configadvanced.jsp (restart required): + * + * i2p.naming.impl=net.i2p.client.naming.ExecNamingService + * i2p.naming.exec.command=/usr/local/bin/i2presolve + * + */ +public class ExecNamingService extends NamingService { + + private final static String PROP_EXEC_CMD = "i2p.naming.exec.command"; + private final static String DEFAULT_EXEC_CMD = "/usr/local/bin/i2presolve"; + private final static String PROP_SHELL_CMD = "i2p.naming.exec.shell"; + private final static String DEFAULT_SHELL_CMD = "/bin/bash"; + private static Properties _hosts; + private final static Log _log = new Log(ExecNamingService.class); + + /** + * The naming service should only be constructed and accessed through the + * application context. This constructor should only be used by the + * appropriate application context itself. + * + */ + public ExecNamingService(I2PAppContext context) { + super(context); + _hosts = new Properties(); + } + + public Destination lookup(String hostname) { + hostname = hostname.toLowerCase(); + + // check the cache + String key = _hosts.getProperty(hostname); + if (key != null) { + _log.error("Found in cache: " + hostname); + return lookupBase64(key); + } + + // lookup + key = fetchAddr(hostname); + if (key != null) { + _log.error("Success: " + hostname); + _hosts.setProperty(hostname, key); // cache + return lookupBase64(key); + } + + // If we can't find name, + // assume it's a key. + return lookupBase64(hostname); + } + + private static final int DEST_SIZE = 516; // Std. Base64 length (no certificate) + private static final int MAX_RESPONSE = DEST_SIZE + 68 + 10; // allow for hostname= and some trailing stuff + private String fetchAddr(String hostname) { + String[] commandArr = new String[3]; + commandArr[0] = _context.getProperty(PROP_SHELL_CMD, DEFAULT_SHELL_CMD); + commandArr[1] = "-c"; + String command = _context.getProperty(PROP_EXEC_CMD, DEFAULT_EXEC_CMD) + " " + hostname; + commandArr[2] = command; + + try { + Process get = Runtime.getRuntime().exec(commandArr); + get.waitFor(); + int exitValue = get.exitValue(); + if (exitValue != 0) { + _log.error("Exit " + exitValue + " from " + commandArr[0] + " " + commandArr[1] + " \"" + command + "\""); + return null; + } + InputStream is = get.getInputStream(); + byte[] input = new byte[MAX_RESPONSE]; + int count = is.read(input); + is.close(); + if (count < DEST_SIZE) { + _log.error("Short response: " + command); + return null; + } + String key = new String(input); + if (key.startsWith(hostname + "=")) // strip hostname= + key = key.substring(hostname.length() + 1); + key = key.substring(0, DEST_SIZE); // catch IndexOutOfBounds exception below + if (!key.endsWith("AAAA")) { + _log.error("Invalid key: " + command); + return null; + } + if (key.replaceAll("[a-zA-Z0-9~-]", "").length() != 0) { + _log.error("Invalid chars: " + command); + return null; + } + return key; + } catch (Throwable t) { + _log.error("Error fetching the addr", t); + } + return null; + } + + public String reverseLookup(Destination dest) { + return null; + } +} diff --git a/core/java/src/net/i2p/client/naming/NamingService.java b/core/java/src/net/i2p/client/naming/NamingService.java index 43f003696..5f8f80bf9 100644 --- a/core/java/src/net/i2p/client/naming/NamingService.java +++ b/core/java/src/net/i2p/client/naming/NamingService.java @@ -24,7 +24,7 @@ public abstract class NamingService { /** what classname should be used as the naming service impl? */ public static final String PROP_IMPL = "i2p.naming.impl"; - private static final String DEFAULT_IMPL = "net.i2p.client.naming.MetaNamingService"; + private static final String DEFAULT_IMPL = "net.i2p.client.naming.HostsTxtNamingService"; /** @@ -89,4 +89,4 @@ public abstract class NamingService { } return instance; } -} \ No newline at end of file +}