diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java index 326ebb644..890a4f141 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java @@ -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()); } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java index d9afdc5f2..16f8447ec 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java @@ -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 _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:

") .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"+ - "

I2P ERROR: No outproxy found

"+ - "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.
") .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"+ + "

I2P ERROR: PROXY AUTHENTICATION REQUIRED

"+ + "This proxy is configured to require authentication.
") + .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; diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index f34ceb1a5..14289cf2f 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -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.
") .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"+ + "

I2P ERROR: PROXY AUTHENTICATION REQUIRED

"+ + "This proxy is configured to require authentication.
") + .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. diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java new file mode 100644 index 000000000..131a02dbc --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java @@ -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 _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"+ + "

I2P ERROR: No outproxy found

"+ + "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; + } + } +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index b93b67490..b60dc289e 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -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) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index 8559c2814..e81b88a69 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -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"); diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp index 485c6758c..d755e4a74 100644 --- a/apps/i2ptunnel/jsp/editClient.jsp +++ b/apps/i2ptunnel/jsp/editClient.jsp @@ -298,7 +298,7 @@ - +
<% if (!"streamrclient".equals(tunnelType)) { // streamr client sends pings so it will never be idle %> @@ -414,6 +414,57 @@ <% } %> + <% if ("httpclient".equals(tunnelType) || "connectclient".equals(tunnelType) || "sockstunnel".equals(tunnelType) || "socksirctunnel".equals(tunnelType)) { %> +
+ +
+
+ + class="tickbox" /> +
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+ + class="tickbox" /> +
+
+ + +
+
+ + +
+
+
+
+ <% } // httpclient || connect || socks || socksirc %> +