* 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:
zzz
2010-11-14 14:18:43 +00:00
parent 7967653dd1
commit 7cbf74d3f2
8 changed files with 411 additions and 71 deletions

View File

@ -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());
}

View File

@ -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;

View File

@ -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.

View File

@ -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;
}
}
}

View File

@ -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) {

View File

@ -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");

View File

@ -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>):

View 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>