use of net.i2p.client.streaming.I2PSocketOptions in project i2p.i2p by i2p.
the class SAMv3StreamSession method connect.
/**
* Connect the SAM STREAM session to the specified Destination
* for a single connection, using the socket stolen from the handler.
*
* @param handler The handler that communicates with the requesting client
* @param dest Base64-encoded Destination to connect to
* @param props Options to be used for connection
*
* @throws DataFormatException if the destination is not valid
* @throws ConnectException if the destination refuses connections
* @throws NoRouteToHostException if the destination can't be reached
* @throws InterruptedIOException if the connection timeouts
* @throws I2PException if there's another I2P-related error
* @throws IOException
*/
public void connect(SAMv3Handler handler, String dest, Properties props) throws I2PException, ConnectException, NoRouteToHostException, DataFormatException, InterruptedIOException, IOException {
boolean verbose = !Boolean.parseBoolean(props.getProperty("SILENT"));
Destination d = SAMUtils.getDest(dest);
I2PSocketOptions opts = socketMgr.buildOptions(props);
if (props.getProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT) == null)
opts.setConnectTimeout(60 * 1000);
String fromPort = props.getProperty("FROM_PORT");
if (fromPort != null) {
try {
opts.setLocalPort(Integer.parseInt(fromPort));
} catch (NumberFormatException nfe) {
throw new I2PException("Bad port " + fromPort);
}
}
String toPort = props.getProperty("TO_PORT");
if (toPort != null) {
try {
opts.setPort(Integer.parseInt(toPort));
} catch (NumberFormatException nfe) {
throw new I2PException("Bad port " + toPort);
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Connecting new I2PSocket...");
// blocking connection (SAMv3)
I2PSocket i2ps = socketMgr.connect(d, opts);
SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
if (rec == null)
throw new InterruptedIOException();
handler.notifyStreamResult(verbose, "OK", null);
handler.stealSocket();
ReadableByteChannel fromClient = handler.getClientSocket();
ReadableByteChannel fromI2P = Channels.newChannel(i2ps.getInputStream());
WritableByteChannel toClient = handler.getClientSocket();
WritableByteChannel toI2P = Channels.newChannel(i2ps.getOutputStream());
SAMBridge bridge = handler.getBridge();
(new I2PAppThread(rec.getThreadGroup(), new Pipe(fromClient, toI2P, bridge), "ConnectV3 SAMPipeClientToI2P")).start();
(new I2PAppThread(rec.getThreadGroup(), new Pipe(fromI2P, toClient, bridge), "ConnectV3 SAMPipeI2PToClient")).start();
}
use of net.i2p.client.streaming.I2PSocketOptions in project i2p.i2p by i2p.
the class SAMStreamSession method connect.
/**
* Connect the SAM STREAM session to the specified Destination
*
* @param id Unique id for the connection
* @param dest Base64-encoded Destination to connect to
* @param props Options to be used for connection
*
* @return true if successful
* @throws DataFormatException if the destination is not valid
* @throws SAMInvalidDirectionException if trying to connect through a
* receive-only session
* @throws ConnectException if the destination refuses connections
* @throws NoRouteToHostException if the destination can't be reached
* @throws InterruptedIOException if the connection timeouts
* @throws I2PException if there's another I2P-related error
* @throws IOException
*/
public boolean connect(int id, String dest, Properties props) throws I2PException, ConnectException, NoRouteToHostException, DataFormatException, InterruptedIOException, SAMInvalidDirectionException, IOException {
if (!canCreate) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Trying to create an outgoing connection using a receive-only session");
throw new SAMInvalidDirectionException("Trying to create connections through a receive-only session");
}
if (checkSocketHandlerId(id)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("The specified id (" + id + ") is already in use");
return false;
}
Destination d = SAMUtils.getDest(dest);
I2PSocketOptions opts = socketMgr.buildOptions(props);
if (props.getProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT) == null)
opts.setConnectTimeout(60 * 1000);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Connecting new I2PSocket...");
// blocking connection (SAMv1)
I2PSocket i2ps = socketMgr.connect(d, opts);
createSocketHandler(i2ps, id);
recv.notifyStreamOutgoingConnection(id, "OK", null);
return true;
}
use of net.i2p.client.streaming.I2PSocketOptions in project i2p.i2p by i2p.
the class I2PTunnelConnectClient method getDefaultOptions.
/**
* Create the default options (using the default timeout, etc).
* Warning, this does not make a copy of I2PTunnel's client options,
* it modifies them directly.
*/
@Override
protected I2PSocketOptions getDefaultOptions() {
Properties defaultOpts = getTunnel().getClientOptions();
if (!defaultOpts.contains(I2PSocketOptions.PROP_READ_TIMEOUT))
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT, "" + DEFAULT_READ_TIMEOUT);
if (!defaultOpts.contains("i2p.streaming.inactivityTimeout"))
defaultOpts.setProperty("i2p.streaming.inactivityTimeout", "" + DEFAULT_READ_TIMEOUT);
// delayed start
verifySocketManager();
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
return opts;
}
use of net.i2p.client.streaming.I2PSocketOptions in project i2p.i2p by i2p.
the class I2PTunnelConnectClient method clientConnectionRun.
protected void clientConnectionRun(Socket s) {
InputStream in = null;
OutputStream out = null;
String targetRequest = null;
boolean usingWWWProxy = false;
String currentProxy = null;
// local outproxy plugin
boolean usingInternalOutproxy = false;
Outproxy outproxy = null;
long requestId = __requestId.incrementAndGet();
I2PSocket i2ps = null;
try {
s.setSoTimeout(INITIAL_SO_TIMEOUT);
out = s.getOutputStream();
in = s.getInputStream();
String line, method = null, host = null, destination = null, restofline = null;
StringBuilder newRequest = new StringBuilder();
String authorization = null;
int remotePort = 443;
while (true) {
// Use this rather than BufferedReader because we can't have readahead,
// since we are passing the stream on to I2PTunnelRunner
line = DataHelper.readLine(in);
if (line == null) {
break;
}
line = line.trim();
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + "Line=[" + line + "]");
if (method == null) {
// first line CONNECT blah.i2p:80 HTTP/1.1
int pos = line.indexOf(' ');
// empty first line
if (pos == -1)
break;
method = line.substring(0, pos);
String request = line.substring(pos + 1);
pos = request.indexOf(':');
if (pos == -1) {
pos = request.indexOf(' ');
} else {
int spos = request.indexOf(' ');
if (spos > 0) {
try {
remotePort = Integer.parseInt(request.substring(pos + 1, spos));
} catch (NumberFormatException nfe) {
break;
} catch (IndexOutOfBoundsException ioobe) {
break;
}
}
}
if (pos == -1) {
host = request;
restofline = "";
} else {
host = request.substring(0, pos);
// ":80 HTTP/1.1" or " HTTP/1.1"
restofline = request.substring(pos);
}
if (host.toLowerCase(Locale.US).endsWith(".i2p")) {
// Destination gets the host name
destination = host;
} else if (host.contains(".") || host.startsWith("[")) {
if (Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_USE_OUTPROXY_PLUGIN, "true"))) {
ClientAppManager mgr = _context.clientAppManager();
if (mgr != null) {
ClientApp op = mgr.getRegisteredApp(Outproxy.NAME);
if (op != null) {
outproxy = (Outproxy) op;
usingInternalOutproxy = true;
if (host.startsWith("[")) {
host = host.substring(1);
if (host.endsWith("]"))
host = host.substring(0, host.length() - 1);
}
}
}
}
if (!usingInternalOutproxy) {
// The request must be forwarded to a outproxy
currentProxy = selectProxy();
if (currentProxy == null) {
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix(requestId) + "Host wants to be outproxied, but we dont have any!");
writeErrorMessage(ERR_NO_OUTPROXY, out);
return;
}
destination = currentProxy;
usingWWWProxy = true;
// HTTP spec
newRequest.append("CONNECT ").append(host).append(restofline).append("\r\n");
}
} else if (host.toLowerCase(Locale.US).equals("localhost")) {
writeErrorMessage(ERR_LOCALHOST, out);
return;
} else {
// full b64 address (hopefully)
destination = host;
}
targetRequest = host;
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getPrefix(requestId) + "METHOD:" + method + ":\n" + "HOST :" + host + ":\n" + "PORT :" + remotePort + ":\n" + "REST :" + restofline + ":\n" + "DEST :" + destination + ":\n" + "www proxy? " + usingWWWProxy + " internal proxy? " + usingInternalOutproxy);
}
} else if (line.toLowerCase(Locale.US).startsWith("proxy-authorization: ")) {
// strip Proxy-Authenticate from the response in HTTPResponseOutputStream
// save for auth check below
// "proxy-authorization: ".length()
authorization = line.substring(21);
line = null;
} else if (line.length() > 0) {
// Additional lines - shouldn't be too many. Firefox sends:
// User-Agent: blabla
// Proxy-Connection: keep-alive
// Host: blabla.i2p
//
// We could send these (filtered like in HTTPClient) on to the outproxy,
// but for now just chomp them all.
line = null;
} else {
// Add Proxy-Authentication header for next hop (outproxy)
if (usingWWWProxy && Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_AUTH))) {
// specific for this proxy
String user = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_USER_PREFIX + currentProxy);
String pw = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_PW_PREFIX + currentProxy);
if (user == null || pw == null) {
// if not, look at default user and pw
user = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_USER);
pw = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_PW);
}
if (user != null && pw != null) {
newRequest.append("Proxy-Authorization: Basic ").append(// true = use standard alphabet
Base64.encode(DataHelper.getUTF8(user + ':' + pw), true)).append("\r\n");
}
}
// HTTP spec
newRequest.append("\r\n");
s.setSoTimeout(0);
// do it
break;
}
}
if (method == null || !"CONNECT".equals(method.toUpperCase(Locale.US))) {
writeErrorMessage(ERR_BAD_PROTOCOL, out);
return;
}
// no destination, going to outproxy plugin
if (usingInternalOutproxy) {
Socket outSocket = outproxy.connect(host, remotePort);
OnTimeout onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
byte[] response = SUCCESS_RESPONSE.getBytes("UTF-8");
Thread t = new I2PTunnelOutproxyRunner(s, outSocket, sockLock, null, response, onTimeout);
// we are called from an unlimited thread pool, so run inline
t.run();
return;
}
if (destination == null) {
writeErrorMessage(ERR_BAD_PROTOCOL, out);
return;
}
// Authorization
AuthResult result = authorize(s, requestId, method, authorization);
if (result != AuthResult.AUTH_GOOD) {
if (_log.shouldLog(Log.WARN)) {
if (authorization != null)
_log.warn(getPrefix(requestId) + "Auth failed, sending 407 again");
else
_log.warn(getPrefix(requestId) + "Auth required, sending 407");
}
out.write(DataHelper.getASCII(getAuthError(result == AuthResult.AUTH_STALE)));
return;
}
Destination clientDest = _context.namingService().lookup(destination);
if (clientDest == null) {
String header;
if (usingWWWProxy)
header = getErrorPage("dnfp", ERR_DESTINATION_UNKNOWN);
else
header = getErrorPage("dnfh", ERR_DESTINATION_UNKNOWN);
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination);
return;
}
I2PSocketOptions sktOpts = getDefaultOptions();
if (!usingWWWProxy && remotePort > 0)
sktOpts.setPort(remotePort);
i2ps = createI2PSocket(clientDest, sktOpts);
byte[] data = null;
byte[] response = null;
if (usingWWWProxy)
data = newRequest.toString().getBytes("ISO-8859-1");
else
response = SUCCESS_RESPONSE.getBytes("UTF-8");
OnTimeout onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
Thread t = new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout);
// we are called from an unlimited thread pool, so run inline
// t.start();
t.run();
} catch (IOException ex) {
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
} catch (I2PException ex) {
_log.info("getPrefix(requestId) + Error trying to connect", ex);
handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
} catch (OutOfMemoryError oom) {
IOException ex = new IOException("OOM");
_log.info("getPrefix(requestId) + Error trying to connect", ex);
handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
} finally {
// only because we are running it inline
closeSocket(s);
if (i2ps != null)
try {
i2ps.close();
} catch (IOException ioe) {
}
}
}
use of net.i2p.client.streaming.I2PSocketOptions in project i2p.i2p by i2p.
the class I2PTunnelHTTPClient method clientConnectionRun.
/**
* Note: This does not handle RFC 2616 header line splitting,
* which is obsoleted in RFC 7230.
*/
protected void clientConnectionRun(Socket s) {
OutputStream out = null;
/**
* The URL after fixup, always starting with http:// or https://
*/
String targetRequest = null;
// in-net outproxy
boolean usingWWWProxy = false;
// local outproxy plugin
boolean usingInternalOutproxy = false;
Outproxy outproxy = null;
boolean usingInternalServer = false;
String internalPath = null;
String internalRawQuery = null;
String currentProxy = null;
long requestId = __requestId.incrementAndGet();
boolean shout = false;
I2PSocket i2ps = null;
try {
s.setSoTimeout(INITIAL_SO_TIMEOUT);
out = s.getOutputStream();
InputReader reader = new InputReader(s.getInputStream());
String line, method = null, protocol = null, host = null, destination = null;
StringBuilder newRequest = new StringBuilder();
boolean ahelperPresent = false;
boolean ahelperNew = false;
String ahelperKey = null;
String userAgent = null;
String authorization = null;
int remotePort = 0;
String referer = null;
URI origRequestURI = null;
while ((line = reader.readLine(method)) != null) {
line = line.trim();
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getPrefix(requestId) + "Line=[" + line + "]");
}
String lowercaseLine = line.toLowerCase(Locale.US);
if (lowercaseLine.startsWith("connection: ") || lowercaseLine.startsWith("keep-alive: ") || lowercaseLine.startsWith("proxy-connection: ")) {
continue;
}
if (method == null) {
// first line (GET /base64/realaddr)
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getPrefix(requestId) + "First line [" + line + "]");
}
String[] params = DataHelper.split(line, " ", 3);
if (params.length != 3) {
break;
}
String request = params[1];
// various obscure fixups
if (request.startsWith("/") && getTunnel().getClientOptions().getProperty("i2ptunnel.noproxy") != null) {
// what is this for ???
request = "http://i2p" + request;
} else if (request.startsWith("/eepproxy/")) {
// Deprecated
// /eepproxy/foo.i2p/bar/baz.html
String subRequest = request.substring("/eepproxy/".length());
if (subRequest.indexOf('/') == -1) {
subRequest += '/';
}
request = "http://" + subRequest;
/**
**
* } else if (request.toLowerCase(Locale.US).startsWith("http://i2p/")) {
* // http://i2p/b64key/bar/baz.html
* // we can't do this now by setting the URI host to the b64key, as
* // it probably contains '=' and '~' which are illegal,
* // and a host may not include escaped octets
* // This will get undone below.
* String subRequest = request.substring("http://i2p/".length());
* if (subRequest.indexOf("/") == -1)
* subRequest += "/";
* "http://" + "b64key/bar/baz.html"
* request = "http://" + subRequest;
* } else if (request.toLowerCase(Locale.US).startsWith("http://")) {
* // Unsupported
* // http://$b64key/...
* // This probably used to work, rewrite it so that
* // we can create a URI without illegal characters
* // This will get undone below.
* String oldPath = request.substring(7);
* int slash = oldPath.indexOf("/");
* if (slash < 0)
* slash = oldPath.length();
* if (slash >= 516 && !oldPath.substring(0, slash).contains("."))
* request = "http://i2p/" + oldPath;
***
*/
}
method = params[0];
if (method.toUpperCase(Locale.US).equals("CONNECT")) {
// this makes things easier later, by spoofing a
// protocol so the URI parser find the host and port
// For in-net outproxy, will be fixed up below
request = "https://" + request + '/';
}
// Now use the Java URI parser
// This will be the incoming URI but will then get modified
// to be the outgoing URI (with http:// if going to outproxy, otherwise without)
URI requestURI = null;
try {
try {
requestURI = new URI(request);
} catch (URISyntaxException use) {
// fixup []| in path/query not escaped by browsers, see ticket #2130
boolean error = true;
// find 3rd /
int idx = 0;
for (int i = 0; i < 2; i++) {
idx = request.indexOf('/', idx);
if (idx < 0)
break;
idx++;
}
if (idx > 0) {
String schemeHostPort = request.substring(0, idx);
String rest = request.substring(idx);
rest = rest.replace("[", "%5B");
rest = rest.replace("]", "%5D");
rest = rest.replace("|", "%7C");
String testRequest = schemeHostPort + rest;
if (!testRequest.equals(request)) {
try {
requestURI = new URI(testRequest);
request = testRequest;
error = false;
} catch (URISyntaxException use2) {
// didn't work, give up
}
}
}
// guess it wasn't []|
if (error)
throw use;
}
origRequestURI = requestURI;
if (requestURI.getRawUserInfo() != null || requestURI.getRawFragment() != null) {
// these should never be sent to the proxy in the request line
if (_log.shouldLog(Log.WARN)) {
_log.warn(getPrefix(requestId) + "Removing userinfo or fragment [" + request + "]");
}
requestURI = changeURI(requestURI, null, 0, null);
}
if (requestURI.getPath() == null || requestURI.getPath().length() <= 0) {
// Add a path
if (_log.shouldLog(Log.WARN)) {
_log.warn(getPrefix(requestId) + "Adding / path to [" + request + "]");
}
requestURI = changeURI(requestURI, null, 0, "/");
}
} catch (URISyntaxException use) {
if (_log.shouldLog(Log.WARN)) {
_log.warn(getPrefix(requestId) + "Bad request [" + request + "]", use);
}
try {
out.write(getErrorPage("baduri", ERR_BAD_URI).getBytes("UTF-8"));
String msg = use.getLocalizedMessage();
if (msg != null) {
out.write(DataHelper.getASCII("<p>\n"));
out.write(DataHelper.getUTF8(DataHelper.escapeHTML(msg)));
out.write(DataHelper.getASCII("</p>\n"));
}
out.write(DataHelper.getASCII("</div>\n"));
writeFooter(out);
reader.drain();
} catch (IOException ioe) {
// ignore
}
return;
}
String protocolVersion = params[2];
protocol = requestURI.getScheme();
host = requestURI.getHost();
if (protocol == null || host == null) {
_log.warn("Null protocol or host: " + request + ' ' + protocol + ' ' + host);
method = null;
break;
}
int port = requestURI.getPort();
// Go through the various types of host names, set
// the host and destination variables accordingly,
// and transform the first line.
// For all i2p network hosts, ensure that the host is a
// Base 32 hostname so that we do not reveal our name for it
// in our addressbook (all naming is local),
// and it is removed from the request line.
String hostLowerCase = host.toLowerCase(Locale.US);
if (hostLowerCase.equals(LOCAL_SERVER)) {
// so we don't do any naming service lookups
destination = host;
usingInternalServer = true;
internalPath = requestURI.getPath();
internalRawQuery = requestURI.getRawQuery();
} else if (hostLowerCase.equals("i2p")) {
// pull the b64 _dest out of the first path element
String oldPath = requestURI.getPath().substring(1);
int slash = oldPath.indexOf('/');
if (slash < 0) {
slash = oldPath.length();
oldPath += '/';
}
String _dest = oldPath.substring(0, slash);
if (slash >= 516 && !_dest.contains(".")) {
// possible alternative:
// redirect to b32
destination = _dest;
host = getHostName(destination);
targetRequest = requestURI.toASCIIString();
String newURI = oldPath.substring(slash);
String query = requestURI.getRawQuery();
if (query != null) {
newURI += '?' + query;
}
try {
requestURI = new URI(newURI);
} catch (URISyntaxException use) {
// shouldnt happen
_log.warn(request, use);
method = null;
break;
}
} else {
_log.warn("Bad http://i2p/b64dest " + request);
host = null;
break;
}
} else if (hostLowerCase.endsWith(".i2p")) {
// Destination gets the host name
destination = host;
// Host becomes the destination's "{b32}.b32.i2p" string, or "i2p" on lookup failure
host = getHostName(destination);
int rPort = requestURI.getPort();
if (rPort > 0) {
// Save it to put in the I2PSocketOptions,
remotePort = rPort;
/**
******
* // but strip it from the URL
* if(_log.shouldLog(Log.WARN)) {
* _log.warn(getPrefix(requestId) + "Removing port from [" + request + "]");
* }
* try {
* requestURI = changeURI(requestURI, null, -1, null);
* } catch(URISyntaxException use) {
* _log.warn(request, use);
* method = null;
* break;
* }
*****
*/
} else if ("https".equals(protocol) || method.toUpperCase(Locale.US).equals("CONNECT")) {
remotePort = 443;
} else {
remotePort = 80;
}
String query = requestURI.getRawQuery();
if (query != null) {
boolean ahelperConflict = false;
// Try to find an address helper in the query
String[] helperStrings = removeHelper(query);
if (helperStrings != null && !Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER))) {
query = helperStrings[0];
if (query.equals("")) {
query = null;
}
try {
requestURI = replaceQuery(requestURI, query);
} catch (URISyntaxException use) {
// shouldn't happen
_log.warn(request, use);
method = null;
break;
}
ahelperKey = helperStrings[1];
// Key contains data, lets not ignore it
if (ahelperKey.length() > 0) {
if (ahelperKey.endsWith(".i2p")) {
// allow i2paddresshelper=<b32>.b32.i2p syntax.
/*
also i2paddresshelper=name.i2p for aliases
i.e. on your eepsite put
<a href="?i2paddresshelper=name.i2p">This is the name I want to be called.</a>
*/
Destination _dest = _context.namingService().lookup(ahelperKey);
if (_dest == null) {
if (_log.shouldLog(Log.WARN)) {
_log.warn(getPrefix(requestId) + "Could not find destination for " + ahelperKey);
}
String header = getErrorPage("ahelper-notfound", ERR_AHELPER_NOTFOUND);
try {
out.write(header.getBytes("UTF-8"));
out.write(("<p>" + _t("This seems to be a bad destination:") + " " + ahelperKey + " " + _t("i2paddresshelper cannot help you with a destination like that!") + "</p>").getBytes("UTF-8"));
writeFooter(out);
reader.drain();
} catch (IOException ioe) {
// ignore
}
return;
}
ahelperKey = _dest.toBase64();
}
ahelperPresent = true;
// ahelperKey will be validated later
if (host == null || "i2p".equals(host)) {
// Host lookup failed - resolvable only with addresshelper
// Store in local HashMap unless there is conflict
String old = addressHelpers.putIfAbsent(destination.toLowerCase(Locale.US), ahelperKey);
ahelperNew = old == null;
// inr address helper links without trailing '=', so omit from comparison
if ((!ahelperNew) && !old.replace("=", "").equals(ahelperKey.replace("=", ""))) {
// Conflict: handle when URL reconstruction done
ahelperConflict = true;
if (_log.shouldLog(Log.WARN)) {
_log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination + "], trusted key [" + old + "], specified key [" + ahelperKey + "].");
}
}
} else {
// If the host is resolvable from database, verify addresshelper key
// Silently bypass correct keys, otherwise alert
Destination hostDest = _context.namingService().lookup(destination);
if (hostDest != null) {
String destB64 = hostDest.toBase64();
if (destB64 != null && !destB64.equals(ahelperKey)) {
// Conflict: handle when URL reconstruction done
ahelperConflict = true;
if (_log.shouldLog(Log.WARN)) {
_log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination + "], trusted key [" + destB64 + "], specified key [" + ahelperKey + "].");
}
}
}
}
}
// ahelperKey
}
// Did addresshelper key conflict?
if (ahelperConflict) {
try {
// convert ahelperKey to b32
String alias = getHostName(ahelperKey);
if (alias.equals("i2p")) {
// bad ahelperKey
String header = getErrorPage("dnfb", ERR_DESTINATION_UNKNOWN);
writeErrorMessage(header, out, targetRequest, false, destination);
} else {
String trustedURL = requestURI.toASCIIString();
URI conflictURI;
try {
conflictURI = changeURI(requestURI, alias, 0, null);
} catch (URISyntaxException use) {
// shouldn't happen
_log.warn(request, use);
method = null;
break;
}
String conflictURL = conflictURI.toASCIIString();
String header = getErrorPage("ahelper-conflict", ERR_AHELPER_CONFLICT);
out.write(header.getBytes("UTF-8"));
out.write("<p>".getBytes("UTF-8"));
out.write(_t("To visit the destination in your address book, click <a href=\"{0}\">here</a>. To visit the conflicting addresshelper destination, click <a href=\"{1}\">here</a>.", trustedURL, conflictURL).getBytes("UTF-8"));
out.write("</p>".getBytes("UTF-8"));
Hash h1 = ConvertToHash.getHash(requestURI.getHost());
Hash h2 = ConvertToHash.getHash(ahelperKey);
if (h1 != null && h2 != null) {
String conURL = _context.portMapper().getConsoleURL();
out.write(("\n<table class=\"conflict\"><tr><th align=\"center\">" + "<a href=\"" + trustedURL + "\">").getBytes("UTF-8"));
out.write(_t("Destination for {0} in address book", requestURI.getHost()).getBytes("UTF-8"));
out.write(("</a></th>\n<th align=\"center\">" + "<a href=\"" + conflictURL + "\">").getBytes("UTF-8"));
out.write(_t("Conflicting address helper destination").getBytes("UTF-8"));
out.write(("</a></th></tr>\n").getBytes("UTF-8"));
if (_context.portMapper().getPort(PortMapper.SVC_IMAGEGEN) > 0) {
out.write(("<tr><td align=\"center\">" + "<a href=\"" + trustedURL + "\">" + "<img src=\"" + conURL + "imagegen/id?s=160&c=" + h1.toBase64().replace("=", "%3d") + "\" width=\"160\" height=\"160\"></a>\n" + "</td>\n<td align=\"center\">" + "<a href=\"" + conflictURL + "\">" + "<img src=\"" + conURL + "imagegen/id?s=160&c=" + h2.toBase64().replace("=", "%3d") + "\" width=\"160\" height=\"160\"></a>\n" + "</td></tr>").getBytes("UTF-8"));
}
out.write("</table>".getBytes("UTF-8"));
}
out.write("</div>".getBytes("UTF-8"));
writeFooter(out);
}
reader.drain();
} catch (IOException ioe) {
// ignore
}
return;
}
}
// end query processing
String addressHelper = addressHelpers.get(destination);
if (addressHelper != null) {
host = getHostName(addressHelper);
}
// now strip everything but path and query from URI
targetRequest = requestURI.toASCIIString();
String newURI = requestURI.getRawPath();
if (query != null) {
newURI += '?' + query;
}
try {
requestURI = new URI(newURI);
} catch (URISyntaxException use) {
// shouldnt happen
_log.warn(request, use);
method = null;
break;
}
// end of (host endsWith(".i2p"))
} else if (hostLowerCase.equals("localhost") || host.equals("127.0.0.1") || host.startsWith("192.168.") || host.equals("[::1]")) {
// if somebody is trying to get to 192.168.example.com, oh well
try {
out.write(getErrorPage("localhost", ERR_LOCALHOST).getBytes("UTF-8"));
writeFooter(out);
reader.drain();
} catch (IOException ioe) {
// ignore
}
return;
} else if (host.contains(".") || host.startsWith("[")) {
if (Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_USE_OUTPROXY_PLUGIN, "true"))) {
ClientAppManager mgr = _context.clientAppManager();
if (mgr != null) {
ClientApp op = mgr.getRegisteredApp(Outproxy.NAME);
if (op != null) {
outproxy = (Outproxy) op;
int rPort = requestURI.getPort();
if (rPort > 0)
remotePort = rPort;
else if ("https".equals(protocol) || method.toUpperCase(Locale.US).equals("CONNECT"))
remotePort = 443;
else
remotePort = 80;
usingInternalOutproxy = true;
targetRequest = requestURI.toASCIIString();
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + " [" + host + "]: outproxy!");
}
}
}
if (!usingInternalOutproxy) {
if (port >= 0) {
host = host + ':' + port;
}
// The request must be forwarded to a WWW proxy
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Before selecting outproxy for " + host);
}
if ("https".equals(protocol) || method.toUpperCase(Locale.US).equals("CONNECT"))
currentProxy = selectSSLProxy();
else
currentProxy = selectProxy();
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("After selecting outproxy for " + host + ": " + currentProxy);
}
if (currentProxy == null) {
if (_log.shouldLog(Log.WARN)) {
_log.warn(getPrefix(requestId) + "Host wants to be outproxied, but we dont have any!");
}
l.log("No outproxy found for the request.");
try {
out.write(getErrorPage("noproxy", ERR_NO_OUTPROXY).getBytes("UTF-8"));
writeFooter(out);
reader.drain();
} catch (IOException ioe) {
// ignore
}
return;
}
destination = currentProxy;
usingWWWProxy = true;
targetRequest = requestURI.toASCIIString();
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getPrefix(requestId) + " [" + host + "]: wwwProxy!");
}
}
} else {
// Rather than look it up, just bail out.
if (_log.shouldLog(Log.WARN)) {
_log.warn("NODOTS, NOI2P: " + request);
}
try {
out.write(getErrorPage("denied", ERR_REQUEST_DENIED).getBytes("UTF-8"));
writeFooter(out);
reader.drain();
} catch (IOException ioe) {
// ignore
}
return;
}
// end host name processing
boolean isValid = usingInternalOutproxy || usingWWWProxy || usingInternalServer || isSupportedAddress(host, protocol);
if (!isValid) {
if (_log.shouldLog(Log.INFO)) {
_log.info(getPrefix(requestId) + "notValid(" + host + ")");
}
method = null;
destination = null;
break;
}
if (method.toUpperCase(Locale.US).equals("CONNECT")) {
// fix up the change to requestURI above to get back to the original host:port
line = method + ' ' + requestURI.getHost() + ':' + requestURI.getPort() + ' ' + protocolVersion;
} else {
line = method + ' ' + requestURI.toASCIIString() + ' ' + protocolVersion;
}
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getPrefix(requestId) + "NEWREQ: \"" + line + "\"");
_log.debug(getPrefix(requestId) + "HOST : \"" + host + "\"");
_log.debug(getPrefix(requestId) + "DEST : \"" + destination + "\"");
}
// end first line processing
} else {
if (lowercaseLine.startsWith("host: ") && !usingWWWProxy && !usingInternalOutproxy) {
// Note that we only pass the original Host: line through to the outproxy
// But we don't create a Host: line if it wasn't sent to us
line = "Host: " + host;
if (_log.shouldLog(Log.INFO)) {
_log.info(getPrefix(requestId) + "Setting host = " + host);
}
} else if (lowercaseLine.startsWith("user-agent: ")) {
// save for deciding whether to offer address book form
userAgent = lowercaseLine.substring(12);
if (!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT))) {
line = null;
continue;
}
} else if (lowercaseLine.startsWith("accept: ")) {
if (!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_ACCEPT))) {
// Replace with a standard one if possible
boolean html = lowercaseLine.indexOf("text/html") > 0;
boolean css = lowercaseLine.indexOf("text/css") > 0;
boolean img = lowercaseLine.indexOf("image") > 0;
if (html && !img && !css) {
// firefox, tor browser
line = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
} else if (img && !html && !css) {
// chrome
line = "Accept: image/webp,image/apng,image/*,*/*;q=0.8";
} else if (css && !html && !img) {
// chrome, firefox
line = "Accept: text/css,*/*;q=0.1";
}
// else allow as-is
}
} else if (lowercaseLine.startsWith("accept")) {
// But allow Accept-Encoding: gzip, deflate
if (!lowercaseLine.startsWith("accept-encoding: ") && !Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_ACCEPT))) {
line = null;
continue;
}
} else if (lowercaseLine.startsWith("referer: ")) {
// save for address helper form below
referer = line.substring(9);
if (!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_REFERER))) {
try {
// Either strip or rewrite the referer line
URI refererURI = new URI(referer);
String refererHost = refererURI.getHost();
if (refererHost != null) {
String origHost = origRequestURI.getHost();
if (!refererHost.equals(origHost) || refererURI.getPort() != origRequestURI.getPort() || !DataHelper.eq(refererURI.getScheme(), origRequestURI.getScheme())) {
line = null;
// completely strip the line if everything doesn't match
continue;
}
// Strip to a relative URI, to hide the original host name
StringBuilder buf = new StringBuilder();
buf.append("Referer: ");
String refererPath = refererURI.getRawPath();
buf.append(refererPath != null ? refererPath : "/");
String refererQuery = refererURI.getRawQuery();
if (refererQuery != null)
buf.append('?').append(refererQuery);
line = buf.toString();
}
// else relative URI, leave in
} catch (URISyntaxException use) {
line = null;
// completely strip the line
continue;
}
}
// else allow
} else if (lowercaseLine.startsWith("via: ") && !Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_VIA))) {
// line = "Via: i2p";
line = null;
// completely strip the line
continue;
} else if (lowercaseLine.startsWith("from: ")) {
// line = "From: i2p";
line = null;
// completely strip the line
continue;
} else if (lowercaseLine.startsWith("authorization: ntlm ")) {
// Block Windows NTLM after 401
line = null;
continue;
} else if (lowercaseLine.startsWith("proxy-authorization: ")) {
// This should be for us. It is a
// hop-by-hop header, and we definitely want to block Windows NTLM after a far-end 407.
// Response to far-end shouldn't happen, as we
// strip Proxy-Authenticate from the response in HTTPResponseOutputStream
// "proxy-authorization: ".length()
authorization = line.substring(21);
line = null;
continue;
} else if (lowercaseLine.startsWith("icy")) {
// icecast/shoutcast, We need to leave the user-agent alone.
shout = true;
}
}
if (line.length() == 0) {
// No more headers, add our own and break out of the loop
String ok = getTunnel().getClientOptions().getProperty("i2ptunnel.gzip");
boolean gzip = DEFAULT_GZIP;
if (ok != null) {
gzip = Boolean.parseBoolean(ok);
}
if (gzip && !usingInternalServer && !method.toUpperCase(Locale.US).equals("CONNECT")) {
// newRequest.append("Accept-Encoding: \r\n");
if (!usingInternalOutproxy)
newRequest.append("X-Accept-Encoding: x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n");
}
if (!shout && !method.toUpperCase(Locale.US).equals("CONNECT")) {
if (!Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT))) {
// let's not advertise to external sites that we are from I2P
if (usingWWWProxy || usingInternalOutproxy) {
newRequest.append(UA_CLEARNET);
} else {
newRequest.append(UA_I2P);
}
}
}
// Add Proxy-Authentication header for next hop (outproxy)
if (usingWWWProxy && Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_AUTH))) {
// specific for this proxy
String user = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_USER_PREFIX + currentProxy);
String pw = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_PW_PREFIX + currentProxy);
if (user == null || pw == null) {
// if not, look at default user and pw
user = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_USER);
pw = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_PW);
}
if (user != null && pw != null) {
newRequest.append("Proxy-Authorization: Basic ").append(// true = use standard alphabet
Base64.encode((user + ':' + pw).getBytes("UTF-8"), true)).append("\r\n");
}
}
newRequest.append("Connection: close\r\n\r\n");
s.setSoTimeout(0);
break;
} else {
// HTTP spec
newRequest.append(line).append("\r\n");
}
}
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getPrefix(requestId) + "NewRequest header: [" + newRequest.toString() + "]");
}
if (method == null || (destination == null && !usingInternalOutproxy)) {
// l.log("No HTTP method found in the request.");
try {
if (protocol != null && "http".equals(protocol.toLowerCase(Locale.US))) {
out.write(getErrorPage("denied", ERR_REQUEST_DENIED).getBytes("UTF-8"));
} else {
out.write(getErrorPage("protocol", ERR_BAD_PROTOCOL).getBytes("UTF-8"));
}
writeFooter(out);
} catch (IOException ioe) {
// ignore
}
return;
}
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getPrefix(requestId) + "Destination: " + destination);
}
// Authorization
AuthResult result = authorize(s, requestId, method, authorization);
if (result != AuthResult.AUTH_GOOD) {
if (_log.shouldLog(Log.WARN)) {
if (authorization != null) {
_log.warn(getPrefix(requestId) + "Auth failed, sending 407 again");
} else {
_log.warn(getPrefix(requestId) + "Auth required, sending 407");
}
}
try {
out.write(getAuthError(result == AuthResult.AUTH_STALE).getBytes("UTF-8"));
writeFooter(out);
} catch (IOException ioe) {
// ignore
}
return;
}
// Ignore all the headers
if (usingInternalServer) {
try {
// disable the add form if address helper is disabled
if (internalPath.equals("/add") && Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER))) {
out.write(ERR_HELPER_DISABLED.getBytes("UTF-8"));
} else {
LocalHTTPServer.serveLocalFile(out, method, internalPath, internalRawQuery, _proxyNonce);
}
} catch (IOException ioe) {
// ignore
}
return;
}
// no destination, going to outproxy plugin
if (usingInternalOutproxy) {
Socket outSocket = outproxy.connect(host, remotePort);
OnTimeout onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
byte[] data;
byte[] response;
if (method.toUpperCase(Locale.US).equals("CONNECT")) {
data = null;
response = SUCCESS_RESPONSE.getBytes("UTF-8");
} else {
data = newRequest.toString().getBytes("ISO-8859-1");
response = null;
}
Thread t = new I2PTunnelOutproxyRunner(s, outSocket, sockLock, data, response, onTimeout);
// we are called from an unlimited thread pool, so run inline
// t.start();
t.run();
return;
}
// LOOKUP
// If the host is "i2p", the getHostName() lookup failed, don't try to
// look it up again as the naming service does not do negative caching
// so it will be slow.
Destination clientDest = null;
String addressHelper = addressHelpers.get(destination.toLowerCase(Locale.US));
if (addressHelper != null) {
clientDest = _context.namingService().lookup(addressHelper);
if (clientDest == null) {
// remove bad entries
addressHelpers.remove(destination.toLowerCase(Locale.US));
if (_log.shouldLog(Log.WARN)) {
_log.warn(getPrefix(requestId) + "Could not find destination for " + addressHelper);
}
String header = getErrorPage("ahelper-notfound", ERR_AHELPER_NOTFOUND);
try {
writeErrorMessage(header, out, targetRequest, false, destination);
} catch (IOException ioe) {
// ignore
}
return;
}
} else if ("i2p".equals(host)) {
clientDest = null;
} else if (destination.length() == 60 && destination.toLowerCase(Locale.US).endsWith(".b32.i2p")) {
// use existing session to look up for efficiency
verifySocketManager();
I2PSession sess = sockMgr.getSession();
if (!sess.isClosed()) {
byte[] hData = Base32.decode(destination.substring(0, 52));
if (hData != null) {
if (_log.shouldLog(Log.INFO)) {
_log.info("lookup in-session " + destination);
}
Hash hash = Hash.create(hData);
clientDest = sess.lookupDest(hash, 20 * 1000);
}
} else {
clientDest = _context.namingService().lookup(destination);
}
} else {
clientDest = _context.namingService().lookup(destination);
}
if (clientDest == null) {
// l.log("Could not resolve " + destination + ".");
if (_log.shouldLog(Log.WARN)) {
_log.warn("Unable to resolve " + destination + " (proxy? " + usingWWWProxy + ", request: " + targetRequest);
}
String header;
String jumpServers = null;
String extraMessage = null;
if (usingWWWProxy) {
header = getErrorPage("dnfp", ERR_DESTINATION_UNKNOWN);
} else if (ahelperPresent) {
header = getErrorPage("dnfb", ERR_DESTINATION_UNKNOWN);
} else if (destination.length() == 60 && destination.toLowerCase(Locale.US).endsWith(".b32.i2p")) {
header = getErrorPage("nols", ERR_DESTINATION_UNKNOWN);
extraMessage = _t("Destination lease set not found");
} else {
header = getErrorPage("dnfh", ERR_DESTINATION_UNKNOWN);
jumpServers = getTunnel().getClientOptions().getProperty(PROP_JUMP_SERVERS);
if (jumpServers == null) {
jumpServers = DEFAULT_JUMP_SERVERS;
}
int jumpDelay = 400 + _context.random().nextInt(256);
try {
Thread.sleep(jumpDelay);
} catch (InterruptedException ie) {
}
}
try {
writeErrorMessage(header, extraMessage, out, targetRequest, usingWWWProxy, destination, jumpServers);
} catch (IOException ioe) {
// ignore
}
return;
}
if (method.toUpperCase(Locale.US).equals("CONNECT") && !usingWWWProxy && !Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_INTERNAL_SSL))) {
try {
writeErrorMessage(ERR_INTERNAL_SSL, out, targetRequest, false, destination);
} catch (IOException ioe) {
// ignore
}
if (_log.shouldLog(Log.WARN))
_log.warn("SSL to i2p destinations denied by configuration: " + targetRequest);
return;
}
// Don't do this for eepget, which uses a user-agent of "Wget"
if (ahelperNew && "GET".equals(method) && (userAgent == null || !userAgent.startsWith("Wget")) && !Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER))) {
try {
writeHelperSaveForm(out, destination, ahelperKey, targetRequest, referer);
} catch (IOException ioe) {
// ignore
}
return;
}
// Syndie can't handle a redirect of a POST
if (ahelperPresent && !"POST".equals(method)) {
String uri = targetRequest;
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Auto redirecting to " + uri);
}
try {
out.write(("HTTP/1.1 301 Address Helper Accepted\r\n" + "Location: " + uri + "\r\n" + "Connection: close\r\n" + "Proxy-Connection: close\r\n" + "\r\n").getBytes("UTF-8"));
} catch (IOException ioe) {
// ignore
}
return;
}
Properties opts = new Properties();
// opts.setProperty("i2p.streaming.inactivityTimeout", ""+120*1000);
// 1 == disconnect. see ConnectionOptions in the new streaming lib, which i
// dont want to hard link to here
// opts.setProperty("i2p.streaming.inactivityTimeoutAction", ""+1);
I2PSocketOptions sktOpts = getDefaultOptions(opts);
if (remotePort > 0)
sktOpts.setPort(remotePort);
i2ps = createI2PSocket(clientDest, sktOpts);
OnTimeout onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
Thread t;
if (method.toUpperCase(Locale.US).equals("CONNECT")) {
byte[] data;
byte[] response;
if (usingWWWProxy) {
data = newRequest.toString().getBytes("ISO-8859-1");
response = null;
} else {
data = null;
response = SUCCESS_RESPONSE.getBytes("UTF-8");
}
t = new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout);
} else {
byte[] data = newRequest.toString().getBytes("ISO-8859-1");
t = new I2PTunnelHTTPClientRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
}
// we are called from an unlimited thread pool, so run inline
// t.start();
t.run();
} catch (IOException ex) {
if (_log.shouldLog(Log.INFO)) {
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
}
handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
} catch (I2PException ex) {
if (_log.shouldLog(Log.INFO)) {
_log.info("getPrefix(requestId) + Error trying to connect", ex);
}
handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
} catch (OutOfMemoryError oom) {
IOException ex = new IOException("OOM");
_log.error("getPrefix(requestId) + Error trying to connect", oom);
handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
} finally {
// only because we are running it inline
closeSocket(s);
if (i2ps != null)
try {
i2ps.close();
} catch (IOException ioe) {
}
}
}
Aggregations