I2PTunnel:

- Add options to block by referer and user-agent
- Increase size of access list field in form
- Log blocked destinations in b32, not b64
- Strip X-Runtime header
Streaming;
- Log blocked destinations in b32, not b64
This commit is contained in:
zzz
2016-02-13 15:31:38 +00:00
parent 071769679d
commit e65bd26ad5
8 changed files with 170 additions and 9 deletions

View File

@@ -25,6 +25,7 @@ import javax.net.ssl.SSLException;
import net.i2p.client.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocket;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.data.Base32;
import net.i2p.data.ByteArray; import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.Hash; import net.i2p.data.Hash;
@@ -50,6 +51,9 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
public static final String OPT_POST_MAX = "maxPosts"; public static final String OPT_POST_MAX = "maxPosts";
public static final String OPT_POST_TOTAL_MAX = "maxTotalPosts"; public static final String OPT_POST_TOTAL_MAX = "maxTotalPosts";
public static final String OPT_REJECT_INPROXY = "rejectInproxy"; public static final String OPT_REJECT_INPROXY = "rejectInproxy";
public static final String OPT_REJECT_REFERER = "rejectReferer";
public static final String OPT_REJECT_USER_AGENTS = "rejectUserAgents";
public static final String OPT_USER_AGENTS = "userAgentRejectList";
public static final int DEFAULT_POST_WINDOW = 5*60; public static final int DEFAULT_POST_WINDOW = 5*60;
public static final int DEFAULT_POST_BAN_TIME = 30*60; public static final int DEFAULT_POST_BAN_TIME = 30*60;
public static final int DEFAULT_POST_TOTAL_BAN_TIME = 10*60; public static final int DEFAULT_POST_TOTAL_BAN_TIME = 10*60;
@@ -64,7 +68,8 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
private static final String[] CLIENT_SKIPHEADERS = {HASH_HEADER, DEST64_HEADER, DEST32_HEADER}; private static final String[] CLIENT_SKIPHEADERS = {HASH_HEADER, DEST64_HEADER, DEST32_HEADER};
private static final String SERVER_HEADER = "Server"; private static final String SERVER_HEADER = "Server";
private static final String X_POWERED_BY_HEADER = "X-Powered-By"; private static final String X_POWERED_BY_HEADER = "X-Powered-By";
private static final String[] SERVER_SKIPHEADERS = {SERVER_HEADER, X_POWERED_BY_HEADER}; private static final String X_RUNTIME_HEADER = "X-Runtime"; // Rails
private static final String[] SERVER_SKIPHEADERS = {SERVER_HEADER, X_POWERED_BY_HEADER, X_RUNTIME_HEADER };
/** timeout for first request line */ /** timeout for first request line */
private static final long HEADER_TIMEOUT = 15*1000; private static final long HEADER_TIMEOUT = 15*1000;
/** total timeout for the request and all the headers */ /** total timeout for the request and all the headers */
@@ -357,7 +362,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
headers.containsKey("X-Forwarded-Host"))) { headers.containsKey("X-Forwarded-Host"))) {
if (_log.shouldLog(Log.WARN)) { if (_log.shouldLog(Log.WARN)) {
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
buf.append("Refusing inproxy access: ").append(peerHash.toBase64()); buf.append("Refusing inproxy access: ").append(Base32.encode(peerHash.getData())).append(".b32.i2p");
List<String> h = headers.get("X-Forwarded-For"); List<String> h = headers.get("X-Forwarded-For");
if (h != null) if (h != null)
buf.append(" from: ").append(h.get(0)); buf.append(" from: ").append(h.get(0));
@@ -380,12 +385,55 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
return; return;
} }
if (Boolean.parseBoolean(opts.getProperty(OPT_REJECT_REFERER)) &&
headers.containsKey("Referer")) {
if (_log.shouldLog(Log.WARN))
_log.warn("Refusing access from: " +
Base32.encode(peerHash.getData()) + ".b32.i2p" +
" with Referer: " + headers.get("Referer").get(0));
try {
socket.getOutputStream().write(ERR_INPROXY.getBytes("UTF-8"));
} catch (IOException ioe) {}
try {
socket.close();
} catch (IOException ioe) {}
return;
}
if (Boolean.parseBoolean(opts.getProperty(OPT_REJECT_USER_AGENTS)) &&
headers.containsKey("User-Agent")) {
String ua = headers.get("User-Agent").get(0);
if (!ua.startsWith("MYOB")) {
String blockAgents = opts.getProperty(OPT_USER_AGENTS);
if (blockAgents != null) {
String[] agents = DataHelper.split(blockAgents, ",");
for (int i = 0; i < agents.length; i++) {
String ag = agents[i].trim();
if (ag.length() > 0 && ua.contains(ag)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Refusing access from: " +
Base32.encode(peerHash.getData()) + ".b32.i2p" +
" with User-Agent: " + ua);
try {
socket.getOutputStream().write(ERR_INPROXY.getBytes("UTF-8"));
} catch (IOException ioe) {}
try {
socket.close();
} catch (IOException ioe) {}
return;
}
}
}
}
}
if (_postThrottler != null && if (_postThrottler != null &&
command.length() >= 5 && command.length() >= 5 &&
command.substring(0, 5).toUpperCase(Locale.US).equals("POST ")) { command.substring(0, 5).toUpperCase(Locale.US).equals("POST ")) {
if (_postThrottler.shouldThrottle(peerHash)) { if (_postThrottler.shouldThrottle(peerHash)) {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("Refusing POST since peer is throttled: " + peerHash.toBase64()); _log.warn("Refusing POST since peer is throttled: " +
Base32.encode(peerHash.getData()) + ".b32.i2p");
try { try {
// Send a 403, so the user doesn't get an HTTP Proxy error message // Send a 403, so the user doesn't get an HTTP Proxy error message
// and blame his router or the network. // and blame his router or the network.
@@ -490,7 +538,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
socket.close(); socket.close();
} catch (IOException ioe) {} } catch (IOException ioe) {}
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("Error while receiving the new HTTP request", ex); _log.warn("Error while receiving the new HTTP request from: " + Base32.encode(peerHash.getData()) + ".b32.i2p", ex);
} catch (OutOfMemoryError oom) { } catch (OutOfMemoryError oom) {
// Often actually a file handle limit problem so we can safely send a response // Often actually a file handle limit problem so we can safely send a response
// java.lang.OutOfMemoryError: unable to create new native thread // java.lang.OutOfMemoryError: unable to create new native thread
@@ -814,6 +862,9 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
* From I2P to server: socket non-null, in null. * From I2P to server: socket non-null, in null.
* From server to I2P: socket null, in non-null. * From server to I2P: socket null, in non-null.
* *
* Note: This does not handle RFC 2616 header line splitting,
* which is obsoleted in RFC 7230.
*
* @param socket if null, use in as InputStream * @param socket if null, use in as InputStream
* @param in if null, use socket.getInputStream() as InputStream * @param in if null, use socket.getInputStream() as InputStream
* @param command out parameter, first line * @param command out parameter, first line
@@ -871,7 +922,8 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
} }
int split = buf.indexOf(":"); int split = buf.indexOf(":");
if (split <= 0) if (split <= 0)
throw new BadRequestException("Invalid HTTP header, missing colon"); throw new BadRequestException("Invalid HTTP header, missing colon: \"" + buf +
"\" request: \"" + command + '"');
totalSize += buf.length(); totalSize += buf.length();
if (totalSize > MAX_TOTAL_HEADER_SIZE) if (totalSize > MAX_TOTAL_HEADER_SIZE)
throw new LineTooLongException("Req+headers too big"); throw new LineTooLongException("Req+headers too big");
@@ -893,6 +945,10 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
name = "X-Forwarded-Server"; name = "X-Forwarded-Server";
else if ("x-forwarded-host".equals(lcName)) else if ("x-forwarded-host".equals(lcName))
name = "X-Forwarded-Host"; name = "X-Forwarded-Host";
else if ("user-agent".equals(lcName))
name = "User-Agent";
else if ("referer".equals(lcName))
name = "Referer";
// For incoming, we remove certain headers to prevent spoofing. // For incoming, we remove certain headers to prevent spoofing.
// For outgoing, we remove certain headers to improve anonymity. // For outgoing, we remove certain headers to improve anonymity.

View File

@@ -630,6 +630,21 @@ public class GeneralHelper {
return getBooleanProperty(tunnel, I2PTunnelHTTPServer.OPT_REJECT_INPROXY); return getBooleanProperty(tunnel, I2PTunnelHTTPServer.OPT_REJECT_INPROXY);
} }
/** @since 0.9.25 */
public boolean getRejectReferer(int tunnel) {
return getBooleanProperty(tunnel, I2PTunnelHTTPServer.OPT_REJECT_REFERER);
}
/** @since 0.9.25 */
public boolean getRejectUserAgents(int tunnel) {
return getBooleanProperty(tunnel, I2PTunnelHTTPServer.OPT_REJECT_USER_AGENTS);
}
/** @since 0.9.25 */
public String getUserAgents(int tunnel) {
return getProperty(tunnel, I2PTunnelHTTPServer.OPT_USER_AGENTS, "");
}
public boolean getUniqueLocal(int tunnel) { public boolean getUniqueLocal(int tunnel) {
return getBooleanProperty(tunnel, I2PTunnelServer.PROP_UNIQUE_LOCAL); return getBooleanProperty(tunnel, I2PTunnelServer.PROP_UNIQUE_LOCAL);
} }

View File

@@ -214,6 +214,29 @@ public class TunnelConfig {
else else
_booleanOptions.remove(I2PTunnelHTTPServer.OPT_REJECT_INPROXY); _booleanOptions.remove(I2PTunnelHTTPServer.OPT_REJECT_INPROXY);
} }
/** @since 0.9.25 */
public void setRejectReferer(boolean val) {
if (val)
_booleanOptions.add(I2PTunnelHTTPServer.OPT_REJECT_REFERER);
else
_booleanOptions.remove(I2PTunnelHTTPServer.OPT_REJECT_REFERER);
}
/** @since 0.9.25 */
public void setRejectUserAgents(boolean val) {
if (val)
_booleanOptions.add(I2PTunnelHTTPServer.OPT_REJECT_USER_AGENTS);
else
_booleanOptions.remove(I2PTunnelHTTPServer.OPT_REJECT_USER_AGENTS);
}
/** @since 0.9.25 */
public void setUserAgents(String val) {
if (val != null)
_otherOptions.put(I2PTunnelHTTPServer.OPT_USER_AGENTS, val.trim());
}
public void setUniqueLocal(boolean val) { public void setUniqueLocal(boolean val) {
if (val) if (val)
_booleanOptions.add(I2PTunnelServer.PROP_UNIQUE_LOCAL); _booleanOptions.add(I2PTunnelServer.PROP_UNIQUE_LOCAL);
@@ -675,6 +698,8 @@ public class TunnelConfig {
"i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", PROP_ENABLE_ACCESS_LIST, PROP_ENABLE_BLACKLIST, "i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", PROP_ENABLE_ACCESS_LIST, PROP_ENABLE_BLACKLIST,
I2PTunnelServer.PROP_USE_SSL, I2PTunnelServer.PROP_USE_SSL,
I2PTunnelHTTPServer.OPT_REJECT_INPROXY, I2PTunnelHTTPServer.OPT_REJECT_INPROXY,
I2PTunnelHTTPServer.OPT_REJECT_REFERER,
I2PTunnelHTTPServer.OPT_REJECT_USER_AGENTS,
I2PTunnelServer.PROP_UNIQUE_LOCAL, I2PTunnelServer.PROP_UNIQUE_LOCAL,
"shouldBundleReplyInfo" "shouldBundleReplyInfo"
}; };
@@ -700,7 +725,8 @@ public class TunnelConfig {
I2PTunnelHTTPServer.OPT_POST_BAN_TIME, I2PTunnelHTTPServer.OPT_POST_BAN_TIME,
I2PTunnelHTTPServer.OPT_POST_TOTAL_BAN_TIME, I2PTunnelHTTPServer.OPT_POST_TOTAL_BAN_TIME,
I2PTunnelHTTPServer.OPT_POST_MAX, I2PTunnelHTTPServer.OPT_POST_MAX,
I2PTunnelHTTPServer.OPT_POST_TOTAL_MAX I2PTunnelHTTPServer.OPT_POST_TOTAL_MAX,
I2PTunnelHTTPServer.OPT_USER_AGENTS
}; };
/** /**

View File

@@ -255,6 +255,11 @@ public class EditBean extends IndexBean {
return _helper.getMultihome(tunnel); return _helper.getMultihome(tunnel);
} }
/** @since 0.9.25 */
public String getUserAgents(int tunnel) {
return _helper.getUserAgents(tunnel);
}
/** all proxy auth @since 0.8.2 */ /** all proxy auth @since 0.8.2 */
public boolean getProxyAuth(int tunnel) { public boolean getProxyAuth(int tunnel) {
return _helper.getProxyAuth(tunnel) != "false"; return _helper.getProxyAuth(tunnel) != "false";

View File

@@ -679,6 +679,31 @@ public class IndexBean {
return _helper.getRejectInproxy(tunnel); return _helper.getRejectInproxy(tunnel);
} }
/** @since 0.9.25 */
public void setRejectReferer(String moo) {
_config.setRejectReferer(true);
}
/** @since 0.9.25 */
public boolean isRejectReferer(int tunnel) {
return _helper.getRejectReferer(tunnel);
}
/** @since 0.9.25 */
public void setRejectUserAgents(String moo) {
_config.setRejectUserAgents(true);
}
/** @since 0.9.25 */
public boolean isRejectUserAgents(int tunnel) {
return _helper.getRejectUserAgents(tunnel);
}
/** @since 0.9.25 */
public void setUserAgents(String agents) {
_config.setUserAgents(agents);
}
/** @since 0.9.13 */ /** @since 0.9.13 */
public void setUniqueLocal(String moo) { public void setUniqueLocal(String moo) {
_config.setUniqueLocal(true); _config.setUniqueLocal(true);

View File

@@ -405,11 +405,11 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
<label><%=intl._t("Blacklist")%></label> <label><%=intl._t("Blacklist")%></label>
<input value="2" type="radio" id="startOnLoad" name="accessMode" title="Reject listed clients"<%=(editBean.getAccessMode(curTunnel).equals("2") ? " checked=\"checked\"" : "")%> class="tickbox" /> <input value="2" type="radio" id="startOnLoad" name="accessMode" title="Reject listed clients"<%=(editBean.getAccessMode(curTunnel).equals("2") ? " checked=\"checked\"" : "")%> class="tickbox" />
</div> </div>
<div id="hostField" class="rowItem"> <div id="accessListField" class="rowItem">
<label for="accessList" accesskey="s"> <label for="accessList" accesskey="s">
<%=intl._t("Access List")%>: <%=intl._t("Access List")%>:
</label> </label>
<textarea rows="2" style="height: 8em;" cols="60" id="hostField" name="accessList" title="Access List" wrap="off" spellcheck="false"><%=editBean.getAccessList(curTunnel)%></textarea> <textarea rows="2" style="height: 8em;" cols="60" name="accessList" title="Access List" wrap="off" spellcheck="false"><%=editBean.getAccessList(curTunnel)%></textarea>
</div> </div>
<% if (("httpserver".equals(tunnelType)) || ("httpbidirserver".equals(tunnelType))) { <% if (("httpserver".equals(tunnelType)) || ("httpbidirserver".equals(tunnelType))) {
@@ -426,6 +426,35 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
<input value="1" type="checkbox" id="startOnLoad" name="rejectInproxy" title="Deny inproxy access when enabled"<%=(editBean.isRejectInproxy(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" /> <input value="1" type="checkbox" id="startOnLoad" name="rejectInproxy" title="Deny inproxy access when enabled"<%=(editBean.isRejectInproxy(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
</div> </div>
</div> </div>
<div class="rowItem">
<div id="optionsField" class="rowItem">
<label>
<%=intl._t("Block Accesses containing Referers")%>:
</label>
</div>
<div id="portField" class="rowItem">
<label for="access" accesskey="d">
<%=intl._t("Enable")%>:
</label>
<input value="1" type="checkbox" id="startOnLoad" name="rejectReferer" title="Deny accesseses with referers (probably from inproxies)"<%=(editBean.isRejectReferer(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
</div>
</div>
<div class="rowItem">
<div id="optionsField" class="rowItem">
<label>
<%=intl._t("Block these User-Agents")%>:
</label>
</div>
<div id="portField" class="rowItem">
<label for="access" accesskey="d">
<%=intl._t("Enable")%>:
</label>
<input value="1" type="checkbox" id="startOnLoad" name="rejectUserAgents" title="Deny User-Agents matching these strings (probably from inproxies)"<%=(editBean.isRejectUserAgents(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
</div>
<div id="optionsHostField" class="rowItem">
<input type="text" id="userAgents" name="userAgents" size="20" title="comma separated, e.g. Mozilla,Opera (case-sensitive)" value="<%=editBean.getUserAgents(curTunnel)%>" class="freetext" />
</div>
</div>
<% } // httpserver <% } // httpserver
%><div class="rowItem"> %><div class="rowItem">
<div id="optionsField" class="rowItem"> <div id="optionsField" class="rowItem">

View File

@@ -241,7 +241,7 @@ class ConnectionManager {
if (why != null) { if (why != null) {
if ((!_defaultOptions.getDisableRejectLogging()) || _log.shouldLog(Log.WARN)) if ((!_defaultOptions.getDisableRejectLogging()) || _log.shouldLog(Log.WARN))
_log.logAlways(Log.WARN, "Refusing connection since peer is " + why + _log.logAlways(Log.WARN, "Refusing connection since peer is " + why +
(synPacket.getOptionalFrom() == null ? "" : ": " + synPacket.getOptionalFrom().calculateHash().toBase64())); (synPacket.getOptionalFrom() == null ? "" : ": " + synPacket.getOptionalFrom().toBase32()));
reject = true; reject = true;
} else { } else {
assignReceiveStreamId(con); assignReceiveStreamId(con);

View File

@@ -33,6 +33,11 @@
margin-right: 4px; margin-right: 4px;
} }
#tunnelEditPage #accessListField {
width: 434px;
margin-right: 4px;
}
#tunnelEditPage #portField, #tunnelEditPage #optionsPortField, #tunnelEditPage #backupField, #tunnelEditPage #varianceField { #tunnelEditPage #portField, #tunnelEditPage #optionsPortField, #tunnelEditPage #backupField, #tunnelEditPage #varianceField {
width: 140px; width: 140px;
} }