I2P Address: [http://git.idk.i2p]

Skip to content
Snippets Groups Projects
Commit cce710e3 authored by zzz's avatar zzz
Browse files

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
parent 013c79bc
Branches
Tags
No related merge requests found
package net.i2p.i2ptunnel;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.Locale;
import java.util.Properties;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.crypto.SHA256Generator;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.Base32;
......@@ -54,23 +55,44 @@ import net.i2p.util.Log;
* @author zzz
*/
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_DEFAULT="user";
public static final String PROP_CLOAK="ircserver.cloakKey";
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_DEFAULT="127.0.0.1";
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_HOSTNAME="ircserver.fakeHostname";
public static final String PROP_HOSTNAME_DEFAULT="%f.b32.i2p";
private static final long HEADER_TIMEOUT = 15*1000;
private static final long TOTAL_HEADER_TIMEOUT = 2 * HEADER_TIMEOUT;
private static final int MAX_LINE_LENGTH = 1024;
private final static byte[] ERR_UNAVAILABLE =
(":ircserver.i2p 499 you :" +
private final static String ERR_UNAVAILABLE =
":ircserver.i2p 499 you :" +
"This I2P IRC server is unavailable. It may be down or undergoing maintenance. " +
"Please try again later." +
"\r\n")
.getBytes();
"\r\n";
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 = {
"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
this.webircPassword = opts.getProperty(PROP_WEBIRC_PASSWORD);
// get the spoof IP for the webirc method
this.webircSpoofIP = opts.getProperty(PROP_WEBIRC_SPOOF_IP, PROP_WEBIRC_SPOOF_IP_DEFAULT);
// get the spoof IP for the webirc method
this.webircSpoofIP = opts.getProperty(PROP_WEBIRC_SPOOF_IP, PROP_WEBIRC_SPOOF_IP_DEFAULT);
// get the cloaking passphrase
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() +
" from: " + socket.getPeerDestination().calculateHash() + " port " + socket.getPort());
try {
String modifiedRegistration;
if(!this.method.equals("webirc")) {
// The headers _should_ be in the first packet, but
// may not be, depending on the client-side options
socket.setReadTimeout(HEADER_TIMEOUT);
InputStream in = socket.getInputStream();
modifiedRegistration = filterRegistration(in, cloakDest(socket.getPeerDestination()));
socket.setReadTimeout(readTimeout);
} else {
StringBuffer buf = new StringBuffer("WEBIRC ");
buf.append(this.webircPassword);
buf.append(" cgiirc ");
buf.append(cloakDest(socket.getPeerDestination()));
buf.append(' ');
buf.append(this.webircSpoofIP);
buf.append("\r\n");
modifiedRegistration = buf.toString();
}
String modifiedRegistration;
if(!this.method.equals("webirc")) {
// The headers _should_ be in the first packet, but
// may not be, depending on the client-side options
modifiedRegistration = filterRegistration(socket, cloakDest(socket.getPeerDestination()));
socket.setReadTimeout(readTimeout);
} else {
StringBuffer buf = new StringBuffer("WEBIRC ");
buf.append(this.webircPassword);
buf.append(" cgiirc ");
buf.append(cloakDest(socket.getPeerDestination()));
buf.append(' ');
buf.append(this.webircSpoofIP);
buf.append("\r\n");
modifiedRegistration = buf.toString();
}
Socket s = getSocket(socket.getPeerDestination().calculateHash(), socket.getLocalPort());
Thread t = new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(),
null, (I2PTunnelRunner.FailCallback) null);
// run in the unlimited client pool
//t.start();
_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) {
try {
// Send a response so the user doesn't just see a disconnect
// and blame his router or the network.
socket.getOutputStream().write(ERR_UNAVAILABLE);
socket.getOutputStream().write(ERR_UNAVAILABLE.getBytes("ISO-8859-1"));
} catch (IOException ioe) {}
try {
socket.close();
......@@ -191,27 +244,35 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
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);
int lineCount = 0;
// slowloris / darkloris
long expire = System.currentTimeMillis() + TOTAL_HEADER_TIMEOUT;
while (true) {
String s = DataHelper.readLine(in);
String s = readLine(socket, expire - System.currentTimeMillis());
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) {
for (int i = 0; i < BAD_PROTOCOLS.length; 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)
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)
throw new IOException("Headers took too long [" + buf.toString() + "]");
throw new SocketTimeoutException("Headers took too long");
s = s.trim();
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Got line: " + s);
......@@ -225,12 +286,12 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
idx++;
command = field[idx++].toUpperCase(Locale.US);
} catch (IndexOutOfBoundsException ioobe) {
throw new IOException("Dropping defective message: [" + s + ']');
throw new RegistrationException("Dropping defective message: [" + s + ']');
}
if ("USER".equals(command)) {
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 abcd1234.i2p localhost :zzz
......@@ -249,9 +310,58 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
return buf.toString();
}
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;
/**
* Read a line teriminated by newline, with a total read timeout.
*
* Warning - strips \n but not \r
* 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);
}
}
}
2015-04-05 zzz
* IRC Server: Better timeout handling reading initial lines (ticket #723)
2015-04-04 zzz
* i2ptunnel:
- Better timeout handling when reading headers in HTTP server (ticket #723)
......
......@@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 17;
public final static long BUILD = 18;
/** for example "-test" */
public final static String EXTRA = "-rc";
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment