use of net.i2p.client.I2PSession in project i2p.i2p by i2p.
the class I2PSocketManagerFull method connect.
/**
* Create a new connected socket. Blocks until the socket is created,
* unless the connectDelay option (i2p.streaming.connectDelay) is
* set and greater than zero. If so this will return immediately,
* and the client may quickly write initial data to the socket and
* this data will be bundled in the SYN packet.
*
* @param peer Destination to connect to
* @param options I2P socket options to be used for connecting, may be null
*
* @return I2PSocket if successful
* @throws NoRouteToHostException if the peer is not found or not reachable
* @throws I2PException if there is some other I2P-related problem
*/
public I2PSocket connect(Destination peer, I2PSocketOptions options) throws I2PException, NoRouteToHostException {
if (peer == null)
throw new NullPointerException();
if (options == null)
options = _defaultOptions;
ConnectionOptions opts = null;
if (options instanceof ConnectionOptions)
opts = new ConnectionOptions((ConnectionOptions) options);
else
opts = new ConnectionOptions(options);
if (_log.shouldLog(Log.INFO))
_log.info("Connecting to " + peer.calculateHash().toBase64().substring(0, 6) + " with options: " + opts);
// pick the subsession here
I2PSession session = _session;
if (!_subsessions.isEmpty()) {
updateUserDsaList();
Hash h = peer.calculateHash();
SigAlgo myAlgo = session.getMyDestination().getSigType().getBaseAlgorithm();
if ((myAlgo == SigAlgo.EC && _ecUnsupported.contains(h)) || (myAlgo == SigAlgo.EdDSA && _edUnsupported.contains(h)) || (!_userDsaOnly.isEmpty() && _userDsaOnly.contains(h))) {
// FIXME just taking the first one for now
for (I2PSession sess : _subsessions) {
if (sess.getMyDestination().getSigType() == SigType.DSA_SHA1) {
session = sess;
break;
}
}
}
}
verifySession(session);
// the following blocks unless connect delay > 0
Connection con = _connectionManager.connect(peer, opts, session);
if (con == null)
throw new TooManyStreamsException("Too many streams, max " + _defaultOptions.getMaxConns());
I2PSocketFull socket = new I2PSocketFull(con, _context);
con.setSocket(socket);
if (con.getConnectionError() != null) {
con.disconnect(false);
throw new NoRouteToHostException(con.getConnectionError());
}
return socket;
}
use of net.i2p.client.I2PSession in project i2p.i2p by i2p.
the class I2PSocketManagerFull method addSubsession.
/**
* For a server, you must call connect() on the returned object.
* Connecting the primary session does NOT connect any subsessions.
* If the primary session is not connected, connecting a subsession will connect the primary session first.
*
* @return a new subsession, non-null
* @param privateKeyStream null for transient, if non-null must have same encryption keys as primary session
* and different signing keys
* @param opts subsession options if any, may be null
* @since 0.9.21
*/
public I2PSession addSubsession(InputStream privateKeyStream, Properties opts) throws I2PSessionException {
if (privateKeyStream == null) {
// We don't actually need the same pubkey in the dest, just in the LS.
// The dest one is unused. But this is how we find the LS keys
// to reuse in RequestLeaseSetMessageHandler.
ByteArrayOutputStream keyStream = new ByteArrayOutputStream(1024);
try {
SigType type = getSigType(opts);
if (type != SigType.DSA_SHA1) {
// hassle, have to set up the padding and cert, see I2PClientImpl
throw new I2PSessionException("type " + type + " unsupported");
}
PublicKey pub = _session.getMyDestination().getPublicKey();
PrivateKey priv = _session.getDecryptionKey();
SimpleDataStructure[] keys = _context.keyGenerator().generateSigningKeys(type);
pub.writeBytes(keyStream);
// signing pub
keys[0].writeBytes(keyStream);
Certificate.NULL_CERT.writeBytes(keyStream);
priv.writeBytes(keyStream);
// signing priv
keys[1].writeBytes(keyStream);
} catch (GeneralSecurityException e) {
throw new I2PSessionException("Error creating keys", e);
} catch (I2PException e) {
throw new I2PSessionException("Error creating keys", e);
} catch (IOException e) {
throw new I2PSessionException("Error creating keys", e);
} catch (RuntimeException e) {
throw new I2PSessionException("Error creating keys", e);
}
privateKeyStream = new ByteArrayInputStream(keyStream.toByteArray());
}
I2PSession rv = _session.addSubsession(privateKeyStream, opts);
boolean added = _subsessions.add(rv);
if (!added) {
// shouldn't happen
_session.removeSubsession(rv);
throw new I2PSessionException("dup");
}
ConnectionOptions defaultOptions = new ConnectionOptions(opts);
int protocol = defaultOptions.getEnforceProtocol() ? I2PSession.PROTO_STREAMING : I2PSession.PROTO_ANY;
rv.addMuxedSessionListener(_connectionManager.getMessageHandler(), protocol, defaultOptions.getLocalPort());
if (_log.shouldLog(Log.WARN))
_log.warn("Added subsession " + rv);
return rv;
}
use of net.i2p.client.I2PSession in project i2p.i2p by i2p.
the class I2PTunnelClientBase method verifySocketManager.
/**
* Create the manager if it doesn't exist, AND connect it to the router and
* build tunnels.
*
* Sets the this.sockMgr field if it is null, or if we want a new one.
* This may take a LONG time if building a new manager.
*
* We need a socket manager before getDefaultOptions() and most other things
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
* badly that we cant create a socketManager
*/
protected void verifySocketManager() {
synchronized (sockLock) {
boolean newManager = false;
// other shared client could have destroyed it
if (this.sockMgr == null || this.sockMgr.isDestroyed()) {
newManager = true;
} else {
I2PSession sess = sockMgr.getSession();
if (sess.isClosed() && Boolean.parseBoolean(getTunnel().getClientOptions().getProperty("i2cp.closeOnIdle")) && Boolean.parseBoolean(getTunnel().getClientOptions().getProperty("i2cp.newDestOnResume"))) {
// build a new socket manager and a new dest if the session is closed.
getTunnel().removeSession(sess);
if (_log.shouldLog(Log.WARN))
_log.warn(getTunnel().getClientOptions().getProperty("inbound.nickname") + ": Built a new destination on resume");
// make sure the old one is closed
// if it's shared client, it will be destroyed in getSocketManager()
// with the correct locking
boolean shouldDestroy;
synchronized (I2PTunnelClientBase.class) {
shouldDestroy = sockMgr != socketManager;
}
if (shouldDestroy)
sockMgr.destroySocketManager();
newManager = true;
}
// else the old socket manager will reconnect the old session if necessary
}
if (newManager) {
if (_ownDest)
this.sockMgr = buildSocketManager();
else
this.sockMgr = getSocketManager();
}
}
connectManager();
}
use of net.i2p.client.I2PSession in project i2p.i2p by i2p.
the class I2PTunnelClientBase method close.
/**
* Note that the tunnel can be reopened after this by calling startRunning().
* This may not release all resources. In particular, the I2PSocketManager remains
* and it may have timer threads that continue running.
*
* To release all resources permanently, call destroy().
*
* Does nothing if open is already false.
* Sets open = false but does not notifyAll().
*
* @return success
*/
public boolean close(boolean forced) {
if (_log.shouldLog(Log.INFO))
_log.info("close() called: forced = " + forced + " open = " + open + " sockMgr = " + sockMgr);
if (!open)
return true;
// to return with an error in that situation quickly.
synchronized (sockLock) {
if (sockMgr != null) {
mySockets.retainAll(sockMgr.listSockets());
if ((!forced) && (!mySockets.isEmpty())) {
l.log("Not closing, there are still active connections!");
_log.debug("can't close: there are still active connections!");
for (I2PSocket s : mySockets) {
l.log(" -> " + s.toString());
}
return false;
}
if (!chained) {
I2PSession session = sockMgr.getSession();
getTunnel().removeSession(session);
if (_ownDest) {
try {
session.destroySession();
} catch (I2PException ex) {
}
}
// TCG will try to destroy it too
}
// else the app chaining to this one closes it!
}
l.log("Stopping client " + toString());
open = false;
try {
if (ss != null)
ss.close();
} catch (IOException ex) {
if (_log.shouldDebug())
_log.debug("error closing", ex);
return false;
}
// l.log("Client closed.");
}
return true;
}
use of net.i2p.client.I2PSession 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