/*
* 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.Locale;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.data.Destination;
import net.i2p.util.EepGet;
/**
* 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.
* Supports caching, b32, and b64.
*
* 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 DummyNamingService {
private final static String PROP_EEPGET_LIST = "i2p.naming.eepget.list";
private final static String DEFAULT_EEPGET_LIST = "http://i2host.i2p/cgi-bin/i2hostquery?";
/**
* 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);
}
private List<String> getURLs() {
String list = _context.getProperty(PROP_EEPGET_LIST, DEFAULT_EEPGET_LIST);
StringTokenizer tok = new StringTokenizer(list, ",");
List<String> rv = new ArrayList<String>(tok.countTokens());
while (tok.hasMoreTokens())
rv.add(tok.nextToken());
return rv;
}
@Override
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
Destination d = super.lookup(hostname, null, null);
if (d != null)
return d;
hostname = hostname.toLowerCase(Locale.US);
// Base32 failed?
if (hostname.length() == BASE32_HASH_LENGTH + 8 && hostname.endsWith(".b32.i2p"))
return null;
List<String> URLs = getURLs();
if (URLs.isEmpty())
return null;
// prevent lookup loops - this cannot be the only lookup service
for (int i = 0; i < URLs.size(); i++) {
String url = 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 = URLs.get(i);
String key = fetchAddr(url, hostname);
if (key != null) {
_log.error("Success: " + url + hostname);
d = lookupBase64(key);
putCache(hostname, d);
return d;
}
}
return null;
}
// FIXME allow larger Dests for non-null Certs
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("AA")) {
_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;
}
}