* I2PTunnel HTTP and Connect clients:
- Shim in a new abstract superclass I2PTunnelHTTPClientBase for common code - Add local proxy username/password authorization - Add outproxy username/password authorization - Filter hop-by-hop Proxy headers appropriately
This commit is contained in:
@ -179,6 +179,8 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
proxyConnectionSent = true;
|
||||
} else if ( ("Content-encoding".equalsIgnoreCase(key)) && ("x-i2p-gzip".equalsIgnoreCase(val)) ) {
|
||||
_gzip = true;
|
||||
} else if ("Proxy-Authenticate".equalsIgnoreCase(key)) {
|
||||
// filter this hop-by-hop header; outproxy authentication must be configured in I2PTunnelHTTPClient
|
||||
} else {
|
||||
out.write((key.trim() + ": " + val.trim() + "\r\n").getBytes());
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
@ -56,11 +57,9 @@ import net.i2p.util.Log;
|
||||
*
|
||||
* @author zzz a stripped-down I2PTunnelHTTPClient
|
||||
*/
|
||||
public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runnable {
|
||||
public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements Runnable {
|
||||
private static final Log _log = new Log(I2PTunnelConnectClient.class);
|
||||
|
||||
private final List<String> _proxyList;
|
||||
|
||||
private final static byte[] ERR_DESTINATION_UNKNOWN =
|
||||
("HTTP/1.1 503 Service Unavailable\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
@ -73,16 +72,6 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
"Could not find the following Destination:<BR><BR><div>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_NO_OUTPROXY =
|
||||
("HTTP/1.1 503 Service Unavailable\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: No outproxy found</H1>"+
|
||||
"Your request was for a site outside of I2P, but you have no "+
|
||||
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_BAD_PROTOCOL =
|
||||
("HTTP/1.1 405 Bad Method\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
@ -102,17 +91,23 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_AUTH =
|
||||
("HTTP/1.1 407 Proxy Authentication Required\r\n"+
|
||||
"Content-Type: text/html; charset=UTF-8\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.5\r\n" + // try to get a UTF-8-encoded response back for the password
|
||||
"Proxy-Authenticate: Basic realm=\"I2P SSL Proxy\"\r\n" +
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: PROXY AUTHENTICATION REQUIRED</H1>"+
|
||||
"This proxy is configured to require authentication.<BR>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] SUCCESS_RESPONSE =
|
||||
("HTTP/1.1 200 Connection Established\r\n"+
|
||||
"Proxy-agent: I2P\r\n"+
|
||||
"\r\n")
|
||||
.getBytes();
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
private static final File _errorDir = new File(I2PAppContext.getGlobalContext().getBaseDir(), "docs");
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||
* valid config to contact the router
|
||||
@ -122,7 +117,6 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
I2PTunnel tunnel) throws IllegalArgumentException {
|
||||
super(localPort, ownDest, l, notifyThis, "HTTPHandler " + (++__clientId), tunnel);
|
||||
|
||||
_proxyList = new ArrayList();
|
||||
if (waitEventValue("openBaseClientResult").equals("error")) {
|
||||
notifyEvent("openConnectClientResult", "error");
|
||||
return;
|
||||
@ -139,20 +133,6 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
startRunning();
|
||||
}
|
||||
|
||||
private String getPrefix(long requestId) { return "Client[" + _clientId + "/" + requestId + "]: "; }
|
||||
|
||||
private String selectProxy() {
|
||||
synchronized (_proxyList) {
|
||||
int size = _proxyList.size();
|
||||
if (size <= 0)
|
||||
return null;
|
||||
int index = _context.random().nextInt(size);
|
||||
return _proxyList.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int DEFAULT_READ_TIMEOUT = 60*1000;
|
||||
|
||||
/**
|
||||
* create the default options (using the default timeout, etc)
|
||||
*
|
||||
@ -172,7 +152,6 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
return opts;
|
||||
}
|
||||
|
||||
private static long __requestId = 0;
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
@ -186,6 +165,7 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
String line, method = null, host = null, destination = null, restofline = null;
|
||||
StringBuilder newRequest = new StringBuilder();
|
||||
int ahelper = 0;
|
||||
String authorization = null;
|
||||
while (true) {
|
||||
// Use this rather than BufferedReader because we can't have readahead,
|
||||
// since we are passing the stream on to I2PTunnelRunner
|
||||
@ -226,7 +206,7 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
}
|
||||
destination = currentProxy;
|
||||
usingWWWProxy = true;
|
||||
newRequest.append("CONNECT ").append(host).append(restofline).append("\r\n\r\n"); // HTTP spec
|
||||
newRequest.append("CONNECT ").append(host).append(restofline).append("\r\n"); // HTTP spec
|
||||
} else if (host.toLowerCase().equals("localhost")) {
|
||||
writeErrorMessage(ERR_LOCALHOST, out);
|
||||
s.close();
|
||||
@ -242,7 +222,11 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
_log.debug(getPrefix(requestId) + "REST :" + restofline + ":");
|
||||
_log.debug(getPrefix(requestId) + "DEST :" + destination + ":");
|
||||
}
|
||||
|
||||
} else if (line.toLowerCase().startsWith("proxy-authorization: basic ")) {
|
||||
// strip Proxy-Authenticate from the response in HTTPResponseOutputStream
|
||||
// save for auth check below
|
||||
authorization = line.substring(27); // "proxy-authorization: basic ".length()
|
||||
line = null;
|
||||
} else if (line.length() > 0) {
|
||||
// Additional lines - shouldn't be too many. Firefox sends:
|
||||
// User-Agent: blabla
|
||||
@ -253,6 +237,23 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
// but for now just chomp them all.
|
||||
line = null;
|
||||
} else {
|
||||
// Add Proxy-Authentication header for next hop (outproxy)
|
||||
if (usingWWWProxy && Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_AUTH)).booleanValue()) {
|
||||
// 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(Base64.encode((user + ':' + pw).getBytes(), true)) // true = use standard alphabet
|
||||
.append("\r\n");
|
||||
}
|
||||
}
|
||||
newRequest.append("\r\n"); // HTTP spec
|
||||
// do it
|
||||
break;
|
||||
}
|
||||
@ -264,6 +265,19 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
return;
|
||||
}
|
||||
|
||||
// Authorization
|
||||
if (!authorize(s, requestId, authorization)) {
|
||||
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");
|
||||
}
|
||||
writeErrorMessage(ERR_AUTH, out);
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
|
||||
Destination clientDest = I2PTunnel.destFromName(destination);
|
||||
if (clientDest == null) {
|
||||
String str;
|
||||
|
@ -27,6 +27,7 @@ import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
@ -60,11 +61,9 @@ import net.i2p.util.Translate;
|
||||
* and POST have been tested, though other $methods should work.
|
||||
*
|
||||
*/
|
||||
public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable {
|
||||
public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runnable {
|
||||
private static final Log _log = new Log(I2PTunnelHTTPClient.class);
|
||||
|
||||
protected final List proxyList = new ArrayList();
|
||||
|
||||
private HashMap addressHelpers = new HashMap();
|
||||
|
||||
/**
|
||||
@ -152,10 +151,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>")
|
||||
.getBytes();
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
private static final File _errorDir = new File(I2PAppContext.getGlobalContext().getBaseDir(), "docs");
|
||||
private final static byte[] ERR_AUTH =
|
||||
("HTTP/1.1 407 Proxy Authentication Required\r\n"+
|
||||
"Content-Type: text/html; charset=UTF-8\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.5\r\n" + // try to get a UTF-8-encoded response back for the password
|
||||
"Proxy-Authenticate: Basic realm=\"I2P HTTP Proxy\"\r\n" +
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: PROXY AUTHENTICATION REQUIRED</H1>"+
|
||||
"This proxy is configured to require authentication.<BR>")
|
||||
.getBytes();
|
||||
|
||||
public I2PTunnelHTTPClient(int localPort, Logging l, I2PSocketManager sockMgr, I2PTunnel tunnel, EventDispatcher notifyThis, long clientId) {
|
||||
super(localPort, l, sockMgr, tunnel, notifyThis, clientId);
|
||||
@ -184,7 +189,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if (wwwProxy != null) {
|
||||
StringTokenizer tok = new StringTokenizer(wwwProxy, ", ");
|
||||
while (tok.hasMoreTokens())
|
||||
proxyList.add(tok.nextToken().trim());
|
||||
_proxyList.add(tok.nextToken().trim());
|
||||
}
|
||||
|
||||
setName(getLocalPort() + " -> HTTPClient [WWW outproxy list: " + wwwProxy + "]");
|
||||
@ -194,25 +199,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
notifyEvent("openHTTPClientResult", "ok");
|
||||
}
|
||||
|
||||
private String getPrefix(long requestId) { return "Client[" + _clientId + "/" + requestId + "]: "; }
|
||||
|
||||
private String selectProxy() {
|
||||
synchronized (proxyList) {
|
||||
int size = proxyList.size();
|
||||
if (size <= 0) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Proxy list is empty - no outproxy available");
|
||||
l.log("Proxy list is empty - no outproxy available");
|
||||
return null;
|
||||
}
|
||||
int index = _context.random().nextInt(size);
|
||||
String proxy = (String)proxyList.get(index);
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int DEFAULT_READ_TIMEOUT = 60*1000;
|
||||
|
||||
/**
|
||||
* create the default options (using the default timeout, etc)
|
||||
* unused?
|
||||
@ -281,7 +267,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
public static final String PROP_VIA = "i2ptunnel.httpclient.sendVia";
|
||||
public static final String PROP_JUMP_SERVERS = "i2ptunnel.httpclient.jumpServers";
|
||||
|
||||
private static long __requestId = 0;
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
@ -296,6 +281,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
String line, method = null, protocol = null, host = null, destination = null;
|
||||
StringBuilder newRequest = new StringBuilder();
|
||||
int ahelper = 0;
|
||||
String authorization = null;
|
||||
while ((line = reader.readLine(method)) != null) {
|
||||
line = line.trim();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -630,15 +616,21 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
// Block Windows NTLM after 401
|
||||
line = null;
|
||||
continue;
|
||||
} else if (lowercaseLine.startsWith("proxy-authorization: ntlm ")) {
|
||||
// Block Windows NTLM after 407
|
||||
} 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
|
||||
if (lowercaseLine.startsWith("proxy-authorization: basic "))
|
||||
// save for auth check below
|
||||
authorization = line.substring(27); // "proxy-authorization: basic ".length()
|
||||
line = null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@ -657,6 +649,22 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
else
|
||||
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
|
||||
}
|
||||
// Add Proxy-Authentication header for next hop (outproxy)
|
||||
if (usingWWWProxy && Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_AUTH)).booleanValue()) {
|
||||
// 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(Base64.encode((user + ':' + pw).getBytes(), true)) // true = use standard alphabet
|
||||
.append("\r\n");
|
||||
}
|
||||
}
|
||||
newRequest.append("Connection: close\r\n\r\n");
|
||||
break;
|
||||
} else {
|
||||
@ -685,12 +693,26 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
// Serve local proxy files (images, css linked from error pages)
|
||||
// Ignore all the headers
|
||||
// Allow without authorization
|
||||
if (usingInternalServer) {
|
||||
serveLocalFile(out, method, targetRequest);
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Authorization
|
||||
if (!authorize(s, requestId, authorization)) {
|
||||
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(getErrorPage("auth", ERR_AUTH));
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -0,0 +1,151 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.InternalSocket;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Common things for HTTPClient and ConnectClient
|
||||
* Retrofit over them in 0.8.2
|
||||
*
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implements Runnable {
|
||||
private static final Log _log = new Log(I2PTunnelHTTPClientBase.class);
|
||||
protected final List<String> _proxyList;
|
||||
|
||||
protected final static byte[] ERR_NO_OUTPROXY =
|
||||
("HTTP/1.1 503 Service Unavailable\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: No outproxy found</H1>"+
|
||||
"Your request was for a site outside of I2P, but you have no "+
|
||||
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel")
|
||||
.getBytes();
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
protected static volatile long __clientId = 0;
|
||||
|
||||
protected static final File _errorDir = new File(I2PAppContext.getGlobalContext().getBaseDir(), "docs");
|
||||
|
||||
protected String getPrefix(long requestId) { return "Client[" + _clientId + "/" + requestId + "]: "; }
|
||||
|
||||
protected String selectProxy() {
|
||||
synchronized (_proxyList) {
|
||||
int size = _proxyList.size();
|
||||
if (size <= 0)
|
||||
return null;
|
||||
int index = _context.random().nextInt(size);
|
||||
return _proxyList.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
protected static final int DEFAULT_READ_TIMEOUT = 60*1000;
|
||||
|
||||
protected static long __requestId = 0;
|
||||
|
||||
public I2PTunnelHTTPClientBase(int localPort, boolean ownDest, Logging l,
|
||||
EventDispatcher notifyThis, String handlerName,
|
||||
I2PTunnel tunnel) throws IllegalArgumentException {
|
||||
super(localPort, ownDest, l, notifyThis, handlerName, tunnel);
|
||||
_proxyList = new ArrayList(4);
|
||||
}
|
||||
|
||||
public I2PTunnelHTTPClientBase(int localPort, Logging l, I2PSocketManager sktMgr,
|
||||
I2PTunnel tunnel, EventDispatcher notifyThis, long clientId )
|
||||
throws IllegalArgumentException {
|
||||
super(localPort, l, sktMgr, tunnel, notifyThis, clientId);
|
||||
_proxyList = new ArrayList(4);
|
||||
}
|
||||
|
||||
/** all auth @ince 0.8.2 */
|
||||
public static final String PROP_AUTH = "proxyAuth";
|
||||
public static final String PROP_USER = "proxyUsername";
|
||||
public static final String PROP_PW = "proxyPassword";
|
||||
/** additional users may be added with proxyPassword.user=pw */
|
||||
public static final String PROP_PW_PREFIX = PROP_PW + '.';
|
||||
public static final String PROP_OUTPROXY_AUTH = "outproxyAuth";
|
||||
public static final String PROP_OUTPROXY_USER = "outproxyUsername";
|
||||
public static final String PROP_OUTPROXY_PW = "outproxyPassword";
|
||||
/** passwords for specific outproxies may be added with outproxyUsername.fooproxy.i2p=user and outproxyPassword.fooproxy.i2p=pw */
|
||||
public static final String PROP_OUTPROXY_USER_PREFIX = PROP_OUTPROXY_USER + '.';
|
||||
public static final String PROP_OUTPROXY_PW_PREFIX = PROP_OUTPROXY_PW + '.';
|
||||
|
||||
/**
|
||||
* @param authorization may be null
|
||||
* @return success
|
||||
*/
|
||||
protected boolean authorize(Socket s, long requestId, String authorization) {
|
||||
// Authorization
|
||||
// Ref: RFC 2617
|
||||
// If the socket is an InternalSocket, no auth required.
|
||||
String authRequired = getTunnel().getClientOptions().getProperty(PROP_AUTH);
|
||||
if (authRequired != null && (authRequired.equalsIgnoreCase("true") || authRequired.equalsIgnoreCase("basic"))) {
|
||||
if (s instanceof InternalSocket) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Internal access, no auth required");
|
||||
return true;
|
||||
} else if (authorization != null) {
|
||||
// hmm safeDecode(foo, true) to use standard alphabet is private in Base64
|
||||
byte[] decoded = Base64.decode(authorization.replace("/", "~").replace("+", "="));
|
||||
if (decoded != null) {
|
||||
// We send Accept-Charset: UTF-8 in the 407 so hopefully it comes back that way inside the B64 ?
|
||||
try {
|
||||
String dec = new String(decoded, "UTF-8");
|
||||
String[] parts = dec.split(":");
|
||||
String user = parts[0];
|
||||
String pw = parts[1];
|
||||
// first try pw for that user
|
||||
String configPW = getTunnel().getClientOptions().getProperty(PROP_PW_PREFIX + user);
|
||||
if (configPW == null) {
|
||||
// if not, look at default user and pw
|
||||
String configUser = getTunnel().getClientOptions().getProperty(PROP_USER);
|
||||
if (user.equals(configUser))
|
||||
configPW = getTunnel().getClientOptions().getProperty(PROP_PW);
|
||||
}
|
||||
if (configPW != null) {
|
||||
if (pw.equals(configPW)) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Good auth - user: " + user + " pw: " + pw);
|
||||
return true;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Bad auth, pw mismatch - user: " + user + " pw: " + pw + " expected: " + configPW);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Bad auth, no stored pw for user: " + user + " pw: " + pw);
|
||||
}
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
_log.error(getPrefix(requestId) + "No UTF-8 support? B64: " + authorization, uee);
|
||||
} catch (ArrayIndexOutOfBoundsException aioobe) {
|
||||
// no ':' in response
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Bad auth B64: " + authorization, aioobe);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Bad auth B64: " + authorization);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ import net.i2p.data.Destination;
|
||||
import net.i2p.data.PrivateKeyFile;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
|
||||
@ -200,6 +201,39 @@ public class EditBean extends IndexBean {
|
||||
return getBooleanProperty(tunnel, "i2cp.delayOpen");
|
||||
}
|
||||
|
||||
/** all proxy auth @since 0.8.2 */
|
||||
public boolean getProxyAuth(int tunnel) {
|
||||
return getBooleanProperty(tunnel, I2PTunnelHTTPClientBase.PROP_AUTH) &&
|
||||
getProxyUsername(tunnel).length() > 0 &&
|
||||
getProxyPassword(tunnel).length() > 0;
|
||||
}
|
||||
|
||||
public String getProxyUsername(int tunnel) {
|
||||
return getProperty(tunnel, I2PTunnelHTTPClientBase.PROP_USER, "");
|
||||
}
|
||||
|
||||
public String getProxyPassword(int tunnel) {
|
||||
if (getProxyUsername(tunnel).length() <= 0)
|
||||
return "";
|
||||
return getProperty(tunnel, I2PTunnelHTTPClientBase.PROP_PW, "");
|
||||
}
|
||||
|
||||
public boolean getOutproxyAuth(int tunnel) {
|
||||
return getBooleanProperty(tunnel, I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH) &&
|
||||
getOutproxyUsername(tunnel).length() > 0 &&
|
||||
getOutproxyPassword(tunnel).length() > 0;
|
||||
}
|
||||
|
||||
public String getOutproxyUsername(int tunnel) {
|
||||
return getProperty(tunnel, I2PTunnelHTTPClientBase.PROP_OUTPROXY_USER, "");
|
||||
}
|
||||
|
||||
public String getOutproxyPassword(int tunnel) {
|
||||
if (getOutproxyUsername(tunnel).length() <= 0)
|
||||
return "";
|
||||
return getProperty(tunnel, I2PTunnelHTTPClientBase.PROP_OUTPROXY_PW, "");
|
||||
}
|
||||
|
||||
private int getProperty(int tunnel, String prop, int def) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
|
@ -24,6 +24,7 @@ import net.i2p.data.Certificate;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.PrivateKeyFile;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
@ -676,6 +677,35 @@ public class IndexBean {
|
||||
}
|
||||
}
|
||||
|
||||
/** all proxy auth @since 0.8.2 */
|
||||
public void setProxyAuth(String s) {
|
||||
_booleanOptions.add(I2PTunnelHTTPClientBase.PROP_AUTH);
|
||||
}
|
||||
|
||||
public void setProxyUsername(String s) {
|
||||
if (s != null)
|
||||
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_USER, s.trim());
|
||||
}
|
||||
|
||||
public void setProxyPassword(String s) {
|
||||
if (s != null)
|
||||
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_PW, s.trim());
|
||||
}
|
||||
|
||||
public void setOutproxyAuth(String s) {
|
||||
_booleanOptions.add(I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH);
|
||||
}
|
||||
|
||||
public void setOutproxyUsername(String s) {
|
||||
if (s != null)
|
||||
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_USER, s.trim());
|
||||
}
|
||||
|
||||
public void setOutproxyPassword(String s) {
|
||||
if (s != null)
|
||||
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_PW, s.trim());
|
||||
}
|
||||
|
||||
/** params needed for hashcash and dest modification */
|
||||
public void setEffort(String val) {
|
||||
if (val != null) {
|
||||
@ -825,6 +855,13 @@ public class IndexBean {
|
||||
config.setProperty("option." + p, _otherOptions.get(p));
|
||||
}
|
||||
|
||||
// generic proxy stuff
|
||||
if ("httpclient".equals(_type) || "connectclient".equals(_type) || "httpbidirserver".equals(_type) ||
|
||||
"sockstunnel".equals(_type) ||"socksirctunnel".equals(_type)) {
|
||||
for (String p : _booleanProxyOpts)
|
||||
config.setProperty("option." + p, "" + _booleanOptions.contains(p));
|
||||
}
|
||||
|
||||
if ("httpclient".equals(_type) || "connectclient".equals(_type)) {
|
||||
if (_proxyList != null)
|
||||
config.setProperty("proxyList", _proxyList);
|
||||
@ -858,11 +895,15 @@ public class IndexBean {
|
||||
private static final String _booleanClientOpts[] = {
|
||||
"i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.newDestOnResume", "persistentClientKey", "i2cp.delayOpen"
|
||||
};
|
||||
private static final String _booleanProxyOpts[] = {
|
||||
I2PTunnelHTTPClientBase.PROP_AUTH, I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH
|
||||
};
|
||||
private static final String _booleanServerOpts[] = {
|
||||
"i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", "i2cp.enableAccessList"
|
||||
};
|
||||
private static final String _otherClientOpts[] = {
|
||||
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime"
|
||||
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime",
|
||||
"proxyUsername", "proxyPassword", "outproxyUsername", "outproxyPassword"
|
||||
};
|
||||
private static final String _otherServerOpts[] = {
|
||||
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.leaseSetKey", "i2cp.accessList"
|
||||
@ -871,6 +912,7 @@ public class IndexBean {
|
||||
static {
|
||||
_noShowSet.addAll(Arrays.asList(_noShowOpts));
|
||||
_noShowSet.addAll(Arrays.asList(_booleanClientOpts));
|
||||
_noShowSet.addAll(Arrays.asList(_booleanProxyOpts));
|
||||
_noShowSet.addAll(Arrays.asList(_booleanServerOpts));
|
||||
_noShowSet.addAll(Arrays.asList(_otherClientOpts));
|
||||
_noShowSet.addAll(Arrays.asList(_otherServerOpts));
|
||||
@ -960,12 +1002,13 @@ public class IndexBean {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getMessages(List msgs) {
|
||||
private static String getMessages(List msgs) {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
getMessages(msgs, buf);
|
||||
return buf.toString();
|
||||
}
|
||||
private void getMessages(List msgs, StringBuilder buf) {
|
||||
|
||||
private static void getMessages(List msgs, StringBuilder buf) {
|
||||
if (msgs == null) return;
|
||||
for (int i = 0; i < msgs.size(); i++) {
|
||||
buf.append((String)msgs.get(i)).append("\n");
|
||||
|
@ -298,7 +298,7 @@
|
||||
<label for="clientPort" accesskey="r">
|
||||
<%=intl._("Port")%>(<span class="accessKey">r</span>):
|
||||
</label>
|
||||
<input type="text" id="clientPort" name="clientport" size="20" title="I2CP Port Number" value="<%=editBean.getI2CPPort(curTunnel)%>" class="freetext" />
|
||||
<input type="text" id="port" name="clientport" size="20" title="I2CP Port Number" value="<%=editBean.getI2CPPort(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
|
||||
<% if (!"streamrclient".equals(tunnelType)) { // streamr client sends pings so it will never be idle %>
|
||||
@ -414,6 +414,57 @@
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if ("httpclient".equals(tunnelType) || "connectclient".equals(tunnelType) || "sockstunnel".equals(tunnelType) || "socksirctunnel".equals(tunnelType)) { %>
|
||||
<div id="accessField" class="rowItem">
|
||||
<label><%=intl._("Local Authorization")%>:</label>
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label>
|
||||
<%=intl._("Enable")%>:
|
||||
</label>
|
||||
<input value="1" type="checkbox" id="proxyAuth" name="proxyAuth" title="Check to require authorization for this service"<%=(editBean.getProxyAuth(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label>
|
||||
<%=intl._("Username")%>:
|
||||
</label>
|
||||
<input type="text" id="clientPort" name="proxyUsername" title="Set username for this service" value="<%=editBean.getProxyUsername(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label>
|
||||
<%=intl._("Password")%>:
|
||||
</label>
|
||||
<input type="password" id="clientPort" name="proxyPassword" title="Set password for this service" value="<%=editBean.getProxyPassword(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
<div id="accessField" class="rowItem">
|
||||
<label><%=intl._("Outproxy Authorization")%>:</label>
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label>
|
||||
<%=intl._("Enable")%>:
|
||||
</label>
|
||||
<input value="1" type="checkbox" id="outproxyAuth" name="outproxyAuth" title="Check if the outproxy requires authorization"<%=(editBean.getOutproxyAuth(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label>
|
||||
<%=intl._("Username")%>:
|
||||
</label>
|
||||
<input type="text" id="clientPort" name="outproxyUsername" title="Enter username required by outproxy" value="<%=editBean.getOutproxyUsername(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
<label>
|
||||
<%=intl._("Password")%>:
|
||||
</label>
|
||||
<input type="password" id="clientPort" name="outproxyPassword" title="Enter password required by outproxy" value="<%=editBean.getOutproxyPassword(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
<div class="subdivider">
|
||||
<hr />
|
||||
</div>
|
||||
<% } // httpclient || connect || socks || socksirc %>
|
||||
|
||||
<div id="customOptionsField" class="rowItem">
|
||||
<label for="customOptions" accesskey="u">
|
||||
<%=intl._("Custom options")%>(<span class="accessKey">u</span>):
|
||||
|
23
installer/resources/proxy/auth-header.ht
Normal file
23
installer/resources/proxy/auth-header.ht
Normal file
@ -0,0 +1,23 @@
|
||||
HTTP/1.1 407 Proxy Authorization Required
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.5
|
||||
Proxy-Authenticate: Basic realm="I2P HTTP Proxy"
|
||||
Cache-control: no-cache
|
||||
Connection: close
|
||||
Proxy-Connection: close
|
||||
|
||||
<html><head>
|
||||
<title>Proxy Authorization Required</title>
|
||||
<!-- we cannot have links to CSS or images here, but we could put in some simple inline style -->
|
||||
</head>
|
||||
<body>
|
||||
<div class=warning id=warning>
|
||||
<h3>I2P HTTP Proxy Authorization Required</h3>
|
||||
This proxy is configured to require a username and password for access.
|
||||
Please enter your username and password, or check your
|
||||
<a href="http://127.0.0.1:7657/advancedconfig.jsp">router configuration</a>
|
||||
or
|
||||
<a href="http://127.0.0.1:7657/i2ptunnel/index.jsp">I2PTunnel configuration</a>.
|
||||
To disable authorization, remove the configuration
|
||||
<code>i2ptunnel.proxy.auth=basic</code>, then stop and restart the HTTP Proxy tunnel.
|
||||
</div>
|
Reference in New Issue
Block a user