forked from I2P_Developers/i2p.i2p
IRC Server: Better timeout handling when reading initial lines (ticket #723)
Send error responses for timeout, EOF, and bad registration. Only affects "user" mode, not webirc. detab move private fields to top
This commit is contained in:
@@ -1,17 +1,18 @@
|
|||||||
package net.i2p.i2ptunnel;
|
package net.i2p.i2ptunnel;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import net.i2p.client.streaming.I2PSocket;
|
import net.i2p.client.streaming.I2PSocket;
|
||||||
import net.i2p.crypto.SHA256Generator;
|
import net.i2p.crypto.SHA256Generator;
|
||||||
import net.i2p.data.DataHelper;
|
|
||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.data.Hash;
|
import net.i2p.data.Hash;
|
||||||
import net.i2p.data.Base32;
|
import net.i2p.data.Base32;
|
||||||
@@ -54,23 +55,44 @@ import net.i2p.util.Log;
|
|||||||
* @author zzz
|
* @author zzz
|
||||||
*/
|
*/
|
||||||
public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||||
|
private final byte[] cloakKey; // 32 bytes of stuff to scramble the dest with
|
||||||
|
private final String hostname;
|
||||||
|
private final String method;
|
||||||
|
private final String webircPassword;
|
||||||
|
private final String webircSpoofIP;
|
||||||
|
|
||||||
public static final String PROP_METHOD="ircserver.method";
|
public static final String PROP_METHOD="ircserver.method";
|
||||||
public static final String PROP_METHOD_DEFAULT="user";
|
public static final String PROP_METHOD_DEFAULT="user";
|
||||||
public static final String PROP_CLOAK="ircserver.cloakKey";
|
public static final String PROP_CLOAK="ircserver.cloakKey";
|
||||||
public static final String PROP_WEBIRC_PASSWORD="ircserver.webircPassword";
|
public static final String PROP_WEBIRC_PASSWORD="ircserver.webircPassword";
|
||||||
public static final String PROP_WEBIRC_SPOOF_IP="ircserver.webircSpoofIP";
|
public static final String PROP_WEBIRC_SPOOF_IP="ircserver.webircSpoofIP";
|
||||||
public static final String PROP_WEBIRC_SPOOF_IP_DEFAULT="127.0.0.1";
|
public static final String PROP_WEBIRC_SPOOF_IP_DEFAULT="127.0.0.1";
|
||||||
public static final String PROP_HOSTNAME="ircserver.fakeHostname";
|
public static final String PROP_HOSTNAME="ircserver.fakeHostname";
|
||||||
public static final String PROP_HOSTNAME_DEFAULT="%f.b32.i2p";
|
public static final String PROP_HOSTNAME_DEFAULT="%f.b32.i2p";
|
||||||
private static final long HEADER_TIMEOUT = 15*1000;
|
private static final long HEADER_TIMEOUT = 15*1000;
|
||||||
private static final long TOTAL_HEADER_TIMEOUT = 2 * HEADER_TIMEOUT;
|
private static final long TOTAL_HEADER_TIMEOUT = 2 * HEADER_TIMEOUT;
|
||||||
|
private static final int MAX_LINE_LENGTH = 1024;
|
||||||
|
|
||||||
private final static byte[] ERR_UNAVAILABLE =
|
private final static String ERR_UNAVAILABLE =
|
||||||
(":ircserver.i2p 499 you :" +
|
":ircserver.i2p 499 you :" +
|
||||||
"This I2P IRC server is unavailable. It may be down or undergoing maintenance. " +
|
"This I2P IRC server is unavailable. It may be down or undergoing maintenance. " +
|
||||||
"Please try again later." +
|
"Please try again later." +
|
||||||
"\r\n")
|
"\r\n";
|
||||||
.getBytes();
|
|
||||||
|
private final static String ERR_REGISTRATION =
|
||||||
|
":ircserver.i2p 499 you :" +
|
||||||
|
"Bad registration." +
|
||||||
|
"\r\n";
|
||||||
|
|
||||||
|
private final static String ERR_TIMEOUT =
|
||||||
|
":ircserver.i2p 499 you :" +
|
||||||
|
"Timeout registering." +
|
||||||
|
"\r\n";
|
||||||
|
|
||||||
|
private final static String ERR_EOF =
|
||||||
|
":ircserver.i2p 499 you :" +
|
||||||
|
"EOF while registering." +
|
||||||
|
"\r\n";
|
||||||
|
|
||||||
private static final String[] BAD_PROTOCOLS = {
|
private static final String[] BAD_PROTOCOLS = {
|
||||||
"GET ", "HEAD ", "POST ", "GNUTELLA CONNECT", "\023BitTorrent protocol"
|
"GET ", "HEAD ", "POST ", "GNUTELLA CONNECT", "\023BitTorrent protocol"
|
||||||
@@ -97,8 +119,8 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
|||||||
// get the password for the webirc method
|
// get the password for the webirc method
|
||||||
this.webircPassword = opts.getProperty(PROP_WEBIRC_PASSWORD);
|
this.webircPassword = opts.getProperty(PROP_WEBIRC_PASSWORD);
|
||||||
|
|
||||||
// get the spoof IP for the webirc method
|
// get the spoof IP for the webirc method
|
||||||
this.webircSpoofIP = opts.getProperty(PROP_WEBIRC_SPOOF_IP, PROP_WEBIRC_SPOOF_IP_DEFAULT);
|
this.webircSpoofIP = opts.getProperty(PROP_WEBIRC_SPOOF_IP, PROP_WEBIRC_SPOOF_IP_DEFAULT);
|
||||||
|
|
||||||
// get the cloaking passphrase
|
// get the cloaking passphrase
|
||||||
String passphrase = opts.getProperty(PROP_CLOAK);
|
String passphrase = opts.getProperty(PROP_CLOAK);
|
||||||
@@ -119,35 +141,66 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
|||||||
_log.info("Incoming connection to '" + toString() + "' port " + socket.getLocalPort() +
|
_log.info("Incoming connection to '" + toString() + "' port " + socket.getLocalPort() +
|
||||||
" from: " + socket.getPeerDestination().calculateHash() + " port " + socket.getPort());
|
" from: " + socket.getPeerDestination().calculateHash() + " port " + socket.getPort());
|
||||||
try {
|
try {
|
||||||
String modifiedRegistration;
|
String modifiedRegistration;
|
||||||
if(!this.method.equals("webirc")) {
|
if(!this.method.equals("webirc")) {
|
||||||
// The headers _should_ be in the first packet, but
|
// The headers _should_ be in the first packet, but
|
||||||
// may not be, depending on the client-side options
|
// may not be, depending on the client-side options
|
||||||
socket.setReadTimeout(HEADER_TIMEOUT);
|
modifiedRegistration = filterRegistration(socket, cloakDest(socket.getPeerDestination()));
|
||||||
InputStream in = socket.getInputStream();
|
socket.setReadTimeout(readTimeout);
|
||||||
modifiedRegistration = filterRegistration(in, cloakDest(socket.getPeerDestination()));
|
} else {
|
||||||
socket.setReadTimeout(readTimeout);
|
StringBuffer buf = new StringBuffer("WEBIRC ");
|
||||||
} else {
|
buf.append(this.webircPassword);
|
||||||
StringBuffer buf = new StringBuffer("WEBIRC ");
|
buf.append(" cgiirc ");
|
||||||
buf.append(this.webircPassword);
|
buf.append(cloakDest(socket.getPeerDestination()));
|
||||||
buf.append(" cgiirc ");
|
buf.append(' ');
|
||||||
buf.append(cloakDest(socket.getPeerDestination()));
|
buf.append(this.webircSpoofIP);
|
||||||
buf.append(' ');
|
buf.append("\r\n");
|
||||||
buf.append(this.webircSpoofIP);
|
modifiedRegistration = buf.toString();
|
||||||
buf.append("\r\n");
|
}
|
||||||
modifiedRegistration = buf.toString();
|
|
||||||
}
|
|
||||||
Socket s = getSocket(socket.getPeerDestination().calculateHash(), socket.getLocalPort());
|
Socket s = getSocket(socket.getPeerDestination().calculateHash(), socket.getLocalPort());
|
||||||
Thread t = new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(),
|
Thread t = new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(),
|
||||||
null, (I2PTunnelRunner.FailCallback) null);
|
null, (I2PTunnelRunner.FailCallback) null);
|
||||||
// run in the unlimited client pool
|
// run in the unlimited client pool
|
||||||
//t.start();
|
//t.start();
|
||||||
_clientExecutor.execute(t);
|
_clientExecutor.execute(t);
|
||||||
|
} catch (RegistrationException ex) {
|
||||||
|
try {
|
||||||
|
// Send a response so the user doesn't just see a disconnect
|
||||||
|
// and blame his router or the network.
|
||||||
|
socket.getOutputStream().write(ERR_REGISTRATION.getBytes("ISO-8859-1"));
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
} finally {
|
||||||
|
try { socket.close(); } catch (IOException ioe) {}
|
||||||
|
}
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Error while receiving the new IRC Connection", ex);
|
||||||
|
} catch (EOFException ex) {
|
||||||
|
try {
|
||||||
|
// Send a response so the user doesn't just see a disconnect
|
||||||
|
// and blame his router or the network.
|
||||||
|
socket.getOutputStream().write(ERR_EOF.getBytes("ISO-8859-1"));
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
} finally {
|
||||||
|
try { socket.close(); } catch (IOException ioe) {}
|
||||||
|
}
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Error while receiving the new IRC Connection", ex);
|
||||||
|
} catch (SocketTimeoutException ex) {
|
||||||
|
try {
|
||||||
|
// Send a response so the user doesn't just see a disconnect
|
||||||
|
// and blame his router or the network.
|
||||||
|
socket.getOutputStream().write(ERR_TIMEOUT.getBytes("ISO-8859-1"));
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
} finally {
|
||||||
|
try { socket.close(); } catch (IOException ioe) {}
|
||||||
|
}
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Error while receiving the new IRC Connection", ex);
|
||||||
} catch (SocketException ex) {
|
} catch (SocketException ex) {
|
||||||
try {
|
try {
|
||||||
// Send a response so the user doesn't just see a disconnect
|
// Send a response so the user doesn't just see a disconnect
|
||||||
// and blame his router or the network.
|
// and blame his router or the network.
|
||||||
socket.getOutputStream().write(ERR_UNAVAILABLE);
|
socket.getOutputStream().write(ERR_UNAVAILABLE.getBytes("ISO-8859-1"));
|
||||||
} catch (IOException ioe) {}
|
} catch (IOException ioe) {}
|
||||||
try {
|
try {
|
||||||
socket.close();
|
socket.close();
|
||||||
@@ -191,27 +244,35 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
|||||||
return this.hostname.replace("%f", hf).replace("%c", hc);
|
return this.hostname.replace("%f", hf).replace("%c", hc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** keep reading until we see USER or SERVER */
|
/**
|
||||||
private static String filterRegistration(InputStream in, String newHostname) throws IOException {
|
* Keep reading until we see USER or SERVER.
|
||||||
|
* This modifies the socket readTimeout, caller must save and restore.
|
||||||
|
*
|
||||||
|
* @throws SocketTimeoutException if timeout is reached before newline
|
||||||
|
* @throws EOFException if EOF is reached before newline
|
||||||
|
* @throws RegistrationException if line too long
|
||||||
|
* @throws IOException on other errors in the underlying stream
|
||||||
|
*/
|
||||||
|
private static String filterRegistration(I2PSocket socket, String newHostname) throws IOException {
|
||||||
StringBuilder buf = new StringBuilder(128);
|
StringBuilder buf = new StringBuilder(128);
|
||||||
int lineCount = 0;
|
int lineCount = 0;
|
||||||
|
|
||||||
// slowloris / darkloris
|
// slowloris / darkloris
|
||||||
long expire = System.currentTimeMillis() + TOTAL_HEADER_TIMEOUT;
|
long expire = System.currentTimeMillis() + TOTAL_HEADER_TIMEOUT;
|
||||||
while (true) {
|
while (true) {
|
||||||
String s = DataHelper.readLine(in);
|
String s = readLine(socket, expire - System.currentTimeMillis());
|
||||||
if (s == null)
|
if (s == null)
|
||||||
throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
|
throw new EOFException("EOF reached before the end of the headers");
|
||||||
if (lineCount == 0) {
|
if (lineCount == 0) {
|
||||||
for (int i = 0; i < BAD_PROTOCOLS.length; i++) {
|
for (int i = 0; i < BAD_PROTOCOLS.length; i++) {
|
||||||
if (s.startsWith(BAD_PROTOCOLS[i]))
|
if (s.startsWith(BAD_PROTOCOLS[i]))
|
||||||
throw new IOException("Bad protocol " + BAD_PROTOCOLS[i]);
|
throw new RegistrationException("Bad protocol " + BAD_PROTOCOLS[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (++lineCount > 10)
|
if (++lineCount > 10)
|
||||||
throw new IOException("Too many lines before USER or SERVER, giving up");
|
throw new RegistrationException("Too many lines before USER or SERVER, giving up");
|
||||||
if (System.currentTimeMillis() > expire)
|
if (System.currentTimeMillis() > expire)
|
||||||
throw new IOException("Headers took too long [" + buf.toString() + "]");
|
throw new SocketTimeoutException("Headers took too long");
|
||||||
s = s.trim();
|
s = s.trim();
|
||||||
//if (_log.shouldLog(Log.DEBUG))
|
//if (_log.shouldLog(Log.DEBUG))
|
||||||
// _log.debug("Got line: " + s);
|
// _log.debug("Got line: " + s);
|
||||||
@@ -225,12 +286,12 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
|||||||
idx++;
|
idx++;
|
||||||
command = field[idx++].toUpperCase(Locale.US);
|
command = field[idx++].toUpperCase(Locale.US);
|
||||||
} catch (IndexOutOfBoundsException ioobe) {
|
} catch (IndexOutOfBoundsException ioobe) {
|
||||||
throw new IOException("Dropping defective message: [" + s + ']');
|
throw new RegistrationException("Dropping defective message: [" + s + ']');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("USER".equals(command)) {
|
if ("USER".equals(command)) {
|
||||||
if (field.length < idx + 4)
|
if (field.length < idx + 4)
|
||||||
throw new IOException("Too few parameters in USER message: " + s);
|
throw new RegistrationException("Too few parameters in USER message: " + s);
|
||||||
// USER zzz1 hostname localhost :zzz
|
// USER zzz1 hostname localhost :zzz
|
||||||
// =>
|
// =>
|
||||||
// USER zzz1 abcd1234.i2p localhost :zzz
|
// USER zzz1 abcd1234.i2p localhost :zzz
|
||||||
@@ -249,9 +310,58 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
|||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final byte[] cloakKey; // 32 bytes of stuff to scramble the dest with
|
/**
|
||||||
private final String hostname;
|
* Read a line teriminated by newline, with a total read timeout.
|
||||||
private final String method;
|
*
|
||||||
private final String webircPassword;
|
* Warning - strips \n but not \r
|
||||||
private final String webircSpoofIP;
|
* Warning - 8KB line length limit as of 0.7.13, @throws IOException if exceeded
|
||||||
|
* Warning - not UTF-8
|
||||||
|
*
|
||||||
|
* @param timeout throws SocketTimeoutException immediately if zero or negative
|
||||||
|
* @throws SocketTimeoutException if timeout is reached before newline
|
||||||
|
* @throws EOFException if EOF is reached before newline
|
||||||
|
* @throws RegistrationException if line too long
|
||||||
|
* @throws IOException on other errors in the underlying stream
|
||||||
|
* @since 0.9.19 modified from DataHelper and I2PTunnelHTTPServer
|
||||||
|
*/
|
||||||
|
private static String readLine(I2PSocket socket, long timeout) throws IOException {
|
||||||
|
StringBuilder buf = new StringBuilder(128);
|
||||||
|
if (timeout <= 0)
|
||||||
|
throw new SocketTimeoutException();
|
||||||
|
long expires = System.currentTimeMillis() + timeout;
|
||||||
|
InputStream in = socket.getInputStream();
|
||||||
|
int c;
|
||||||
|
int i = 0;
|
||||||
|
socket.setReadTimeout(timeout);
|
||||||
|
while ( (c = in.read()) != -1) {
|
||||||
|
if (++i > MAX_LINE_LENGTH)
|
||||||
|
throw new RegistrationException("Line too long - max " + MAX_LINE_LENGTH);
|
||||||
|
if (c == '\n')
|
||||||
|
break;
|
||||||
|
long newTimeout = expires - System.currentTimeMillis();
|
||||||
|
if (newTimeout <= 0)
|
||||||
|
throw new SocketTimeoutException();
|
||||||
|
buf.append((char)c);
|
||||||
|
if (newTimeout != timeout) {
|
||||||
|
timeout = newTimeout;
|
||||||
|
socket.setReadTimeout(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (c == -1) {
|
||||||
|
if (System.currentTimeMillis() >= expires)
|
||||||
|
throw new SocketTimeoutException();
|
||||||
|
else
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.19
|
||||||
|
*/
|
||||||
|
private static class RegistrationException extends IOException {
|
||||||
|
public RegistrationException(String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
2015-04-05 zzz
|
||||||
|
* IRC Server: Better timeout handling reading initial lines (ticket #723)
|
||||||
|
|
||||||
2015-04-04 zzz
|
2015-04-04 zzz
|
||||||
* i2ptunnel:
|
* i2ptunnel:
|
||||||
- Better timeout handling when reading headers in HTTP server (ticket #723)
|
- Better timeout handling when reading headers in HTTP server (ticket #723)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class RouterVersion {
|
|||||||
/** deprecated */
|
/** deprecated */
|
||||||
public final static String ID = "Monotone";
|
public final static String ID = "Monotone";
|
||||||
public final static String VERSION = CoreVersion.VERSION;
|
public final static String VERSION = CoreVersion.VERSION;
|
||||||
public final static long BUILD = 17;
|
public final static long BUILD = 18;
|
||||||
|
|
||||||
/** for example "-test" */
|
/** for example "-test" */
|
||||||
public final static String EXTRA = "-rc";
|
public final static String EXTRA = "-rc";
|
||||||
|
|||||||
Reference in New Issue
Block a user