forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p.zzz.sam' (head b328f0edb961263d7606ea964ecb3f7c319ca1cf)
to branch 'i2p.i2p' (head 7b4c0525be182722ef2cc7b564691f27d997da3b)
This commit is contained in:
25
apps/sam/doc/README-test.txt
Normal file
25
apps/sam/doc/README-test.txt
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
To run tests:
|
||||||
|
|
||||||
|
Build and run standalone Java I2CP (no router):
|
||||||
|
ant buildTest
|
||||||
|
java -cp build/i2ptest.jar:build/routertest.jar -Djava.library.path=. net.i2p.router.client.LocalClientManager
|
||||||
|
|
||||||
|
|
||||||
|
Build and run standalone SAM server:
|
||||||
|
ant buildSAM
|
||||||
|
java -cp build/i2p.jar:build/mstreaming.jar:build/streaming.jar:build/sam.jar -Djava.library.path=. net.i2p.sam.SAMBridge
|
||||||
|
|
||||||
|
|
||||||
|
Build Java test clients:
|
||||||
|
cd apps/sam/java
|
||||||
|
ant clientjar
|
||||||
|
cd ../../..
|
||||||
|
|
||||||
|
Run sink client:
|
||||||
|
mkdir samsinkdir
|
||||||
|
java -cp build/i2p.jar:apps/sam/java/build/samclient.jar net.i2p.sam.client.SAMStreamSink samdest.txt samsinkdir -v 3.2
|
||||||
|
run with no args to see usage
|
||||||
|
|
||||||
|
Run send client:
|
||||||
|
java -cp build/i2p.jar:apps/sam/java/build/samclient.jar net.i2p.sam.client.SAMStreamSend samdest.txt samtestdata -v 3.2
|
||||||
|
run with no args to see usage
|
@ -21,7 +21,9 @@
|
|||||||
</depend>
|
</depend>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<property name="javac.compilerargs" value="" />
|
<!-- ignored for now, we require java 7 here -->
|
||||||
|
<property name="javac.compilerargs7" value="" />
|
||||||
|
<!-- ignored for now, we require java 7 here -->
|
||||||
<property name="javac.version" value="1.6" />
|
<property name="javac.version" value="1.6" />
|
||||||
|
|
||||||
<!-- compile everything including client classes -->
|
<!-- compile everything including client classes -->
|
||||||
@ -30,22 +32,22 @@
|
|||||||
<mkdir dir="./build/obj" />
|
<mkdir dir="./build/obj" />
|
||||||
<javac
|
<javac
|
||||||
srcdir="./src"
|
srcdir="./src"
|
||||||
debug="true" deprecation="on" source="${javac.version}" target="${javac.version}"
|
debug="true" deprecation="on" source="1.7" target="1.7"
|
||||||
includeAntRuntime="false"
|
includeAntRuntime="false"
|
||||||
destdir="./build/obj"
|
destdir="./build/obj"
|
||||||
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" >
|
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" >
|
||||||
<compilerarg line="${javac.compilerargs}" />
|
<compilerarg line="${javac.compilerargs7}" />
|
||||||
</javac>
|
</javac>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="compileTest" depends="compile">
|
<target name="compileTest" depends="compile">
|
||||||
<javac
|
<javac
|
||||||
srcdir="./test"
|
srcdir="./test"
|
||||||
debug="true" deprecation="on" source="${javac.version}" target="${javac.version}"
|
debug="true" deprecation="on" source="1.7" target="1.7"
|
||||||
includeAntRuntime="false"
|
includeAntRuntime="false"
|
||||||
destdir="./build/obj"
|
destdir="./build/obj"
|
||||||
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" >
|
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" >
|
||||||
<compilerarg line="${javac.compilerargs}" />
|
<compilerarg line="${javac.compilerargs7}" />
|
||||||
</javac>
|
</javac>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
|
73
apps/sam/java/src/net/i2p/sam/ReadLine.java
Normal file
73
apps/sam/java/src/net/i2p/sam/ReadLine.java
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package net.i2p.sam;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modified from I2PTunnelHTTPServer
|
||||||
|
*
|
||||||
|
* @since 0.9.24
|
||||||
|
*/
|
||||||
|
class ReadLine {
|
||||||
|
|
||||||
|
private static final int MAX_LINE_LENGTH = 8*1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* @param buf output
|
||||||
|
* @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 LineTooLongException if too long
|
||||||
|
* @throws IOException on other errors in the underlying stream
|
||||||
|
*/
|
||||||
|
public static void readLine(Socket socket, StringBuilder buf, int timeout) throws IOException {
|
||||||
|
if (timeout <= 0)
|
||||||
|
throw new SocketTimeoutException();
|
||||||
|
long expires = System.currentTimeMillis() + timeout;
|
||||||
|
// this reads and buffers extra bytes, so we can't use it
|
||||||
|
// unless we're going to decode UTF-8 on-the-fly, we're stuck with ASCII
|
||||||
|
//InputStreamReader in = new InputStreamReader(socket.getInputStream(), "UTF-8");
|
||||||
|
InputStream in = socket.getInputStream();
|
||||||
|
int c;
|
||||||
|
int i = 0;
|
||||||
|
socket.setSoTimeout(timeout);
|
||||||
|
while ( (c = in.read()) != -1) {
|
||||||
|
if (++i > MAX_LINE_LENGTH)
|
||||||
|
throw new LineTooLongException("Line too long - max " + MAX_LINE_LENGTH);
|
||||||
|
if (c == '\n')
|
||||||
|
break;
|
||||||
|
int newTimeout = (int) (expires - System.currentTimeMillis());
|
||||||
|
if (newTimeout <= 0)
|
||||||
|
throw new SocketTimeoutException();
|
||||||
|
buf.append((char)c);
|
||||||
|
if (newTimeout != timeout) {
|
||||||
|
timeout = newTimeout;
|
||||||
|
socket.setSoTimeout(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (c == -1) {
|
||||||
|
if (System.currentTimeMillis() >= expires)
|
||||||
|
throw new SocketTimeoutException();
|
||||||
|
else
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LineTooLongException extends IOException {
|
||||||
|
public LineTooLongException(String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -9,15 +9,16 @@ package net.i2p.sam;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.channels.ServerSocketChannel;
|
import java.nio.channels.ServerSocketChannel;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -27,14 +28,22 @@ import java.util.Map;
|
|||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLServerSocket;
|
||||||
|
import javax.net.ssl.SSLServerSocketFactory;
|
||||||
|
|
||||||
|
import gnu.getopt.Getopt;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.app.*;
|
import net.i2p.app.*;
|
||||||
import static net.i2p.app.ClientAppState.*;
|
import static net.i2p.app.ClientAppState.*;
|
||||||
import net.i2p.data.DataFormatException;
|
import net.i2p.data.DataFormatException;
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.util.I2PAppThread;
|
import net.i2p.util.I2PAppThread;
|
||||||
|
import net.i2p.util.I2PSSLSocketFactory;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.PortMapper;
|
import net.i2p.util.PortMapper;
|
||||||
|
import net.i2p.util.SystemVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SAM bridge implementation.
|
* SAM bridge implementation.
|
||||||
@ -48,7 +57,11 @@ public class SAMBridge implements Runnable, ClientApp {
|
|||||||
private final String _listenHost;
|
private final String _listenHost;
|
||||||
private final int _listenPort;
|
private final int _listenPort;
|
||||||
private final Properties i2cpProps;
|
private final Properties i2cpProps;
|
||||||
|
private final boolean _useSSL;
|
||||||
|
private final File _configFile;
|
||||||
private volatile Thread _runner;
|
private volatile Thread _runner;
|
||||||
|
private final Object _v3DGServerLock = new Object();
|
||||||
|
private SAMv3DatagramServer _v3DGServer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* filename in which the name to private key mapping should
|
* filename in which the name to private key mapping should
|
||||||
@ -65,21 +78,27 @@ public class SAMBridge implements Runnable, ClientApp {
|
|||||||
private volatile boolean acceptConnections = true;
|
private volatile boolean acceptConnections = true;
|
||||||
|
|
||||||
private final ClientAppManager _mgr;
|
private final ClientAppManager _mgr;
|
||||||
private final String[] _args;
|
|
||||||
private volatile ClientAppState _state = UNINITIALIZED;
|
private volatile ClientAppState _state = UNINITIALIZED;
|
||||||
|
|
||||||
private static final int SAM_LISTENPORT = 7656;
|
private static final int SAM_LISTENPORT = 7656;
|
||||||
|
|
||||||
public static final String DEFAULT_SAM_KEYFILE = "sam.keys";
|
public static final String DEFAULT_SAM_KEYFILE = "sam.keys";
|
||||||
|
static final String DEFAULT_SAM_CONFIGFILE = "sam.config";
|
||||||
|
private static final String PROP_SAM_KEYFILE = "sam.keyfile";
|
||||||
|
private static final String PROP_SAM_SSL = "sam.useSSL";
|
||||||
public static final String PROP_TCP_HOST = "sam.tcp.host";
|
public static final String PROP_TCP_HOST = "sam.tcp.host";
|
||||||
public static final String PROP_TCP_PORT = "sam.tcp.port";
|
public static final String PROP_TCP_PORT = "sam.tcp.port";
|
||||||
protected static final String DEFAULT_TCP_HOST = "0.0.0.0";
|
public static final String PROP_AUTH = "sam.auth";
|
||||||
|
public static final String PROP_PW_PREFIX = "sam.auth.";
|
||||||
|
public static final String PROP_PW_SUFFIX = ".shash";
|
||||||
|
protected static final String DEFAULT_TCP_HOST = "127.0.0.1";
|
||||||
protected static final String DEFAULT_TCP_PORT = "7656";
|
protected static final String DEFAULT_TCP_PORT = "7656";
|
||||||
|
|
||||||
public static final String PROP_DATAGRAM_HOST = "sam.udp.host";
|
public static final String PROP_DATAGRAM_HOST = "sam.udp.host";
|
||||||
public static final String PROP_DATAGRAM_PORT = "sam.udp.port";
|
public static final String PROP_DATAGRAM_PORT = "sam.udp.port";
|
||||||
protected static final String DEFAULT_DATAGRAM_HOST = "0.0.0.0";
|
protected static final String DEFAULT_DATAGRAM_HOST = "127.0.0.1";
|
||||||
protected static final String DEFAULT_DATAGRAM_PORT = "7655";
|
protected static final int DEFAULT_DATAGRAM_PORT_INT = 7655;
|
||||||
|
protected static final String DEFAULT_DATAGRAM_PORT = Integer.toString(DEFAULT_DATAGRAM_PORT_INT);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,11 +114,14 @@ public class SAMBridge implements Runnable, ClientApp {
|
|||||||
public SAMBridge(I2PAppContext context, ClientAppManager mgr, String[] args) throws Exception {
|
public SAMBridge(I2PAppContext context, ClientAppManager mgr, String[] args) throws Exception {
|
||||||
_log = context.logManager().getLog(SAMBridge.class);
|
_log = context.logManager().getLog(SAMBridge.class);
|
||||||
_mgr = mgr;
|
_mgr = mgr;
|
||||||
_args = args;
|
|
||||||
Options options = getOptions(args);
|
Options options = getOptions(args);
|
||||||
_listenHost = options.host;
|
_listenHost = options.host;
|
||||||
_listenPort = options.port;
|
_listenPort = options.port;
|
||||||
|
_useSSL = options.isSSL;
|
||||||
|
if (_useSSL && !SystemVersion.isJava7())
|
||||||
|
throw new IllegalArgumentException("SSL requires Java 7 or higher");
|
||||||
persistFilename = options.keyFile;
|
persistFilename = options.keyFile;
|
||||||
|
_configFile = options.configFile;
|
||||||
nameToPrivKeys = new HashMap<String,String>(8);
|
nameToPrivKeys = new HashMap<String,String>(8);
|
||||||
_handlers = new HashSet<Handler>(8);
|
_handlers = new HashSet<Handler>(8);
|
||||||
this.i2cpProps = options.opts;
|
this.i2cpProps = options.opts;
|
||||||
@ -123,13 +145,18 @@ public class SAMBridge implements Runnable, ClientApp {
|
|||||||
* @param persistFile location to store/load named keys to/from
|
* @param persistFile location to store/load named keys to/from
|
||||||
* @throws RuntimeException if a server socket can't be opened
|
* @throws RuntimeException if a server socket can't be opened
|
||||||
*/
|
*/
|
||||||
public SAMBridge(String listenHost, int listenPort, Properties i2cpProps, String persistFile) {
|
public SAMBridge(String listenHost, int listenPort, boolean isSSL, Properties i2cpProps,
|
||||||
|
String persistFile, File configFile) {
|
||||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(SAMBridge.class);
|
_log = I2PAppContext.getGlobalContext().logManager().getLog(SAMBridge.class);
|
||||||
_mgr = null;
|
_mgr = null;
|
||||||
_args = new String[] {listenHost, Integer.toString(listenPort) }; // placeholder
|
|
||||||
_listenHost = listenHost;
|
_listenHost = listenHost;
|
||||||
_listenPort = listenPort;
|
_listenPort = listenPort;
|
||||||
|
_useSSL = isSSL;
|
||||||
|
if (_useSSL && !SystemVersion.isJava7())
|
||||||
|
throw new IllegalArgumentException("SSL requires Java 7 or higher");
|
||||||
|
this.i2cpProps = i2cpProps;
|
||||||
persistFilename = persistFile;
|
persistFilename = persistFile;
|
||||||
|
_configFile = configFile;
|
||||||
nameToPrivKeys = new HashMap<String,String>(8);
|
nameToPrivKeys = new HashMap<String,String>(8);
|
||||||
_handlers = new HashSet<Handler>(8);
|
_handlers = new HashSet<Handler>(8);
|
||||||
loadKeys();
|
loadKeys();
|
||||||
@ -142,7 +169,6 @@ public class SAMBridge implements Runnable, ClientApp {
|
|||||||
+ ":" + listenPort, e);
|
+ ":" + listenPort, e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
this.i2cpProps = i2cpProps;
|
|
||||||
_state = INITIALIZED;
|
_state = INITIALIZED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,17 +176,28 @@ public class SAMBridge implements Runnable, ClientApp {
|
|||||||
* @since 0.9.6
|
* @since 0.9.6
|
||||||
*/
|
*/
|
||||||
private void openSocket() throws IOException {
|
private void openSocket() throws IOException {
|
||||||
if ( (_listenHost != null) && !("0.0.0.0".equals(_listenHost)) ) {
|
if (_useSSL) {
|
||||||
serverSocket = ServerSocketChannel.open();
|
SSLServerSocketFactory fact = SSLUtil.initializeFactory(i2cpProps);
|
||||||
serverSocket.socket().bind(new InetSocketAddress(_listenHost, _listenPort));
|
InetAddress addr;
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_listenHost != null && !_listenHost.equals("0.0.0.0"))
|
||||||
_log.debug("SAM bridge listening on "
|
addr = InetAddress.getByName(_listenHost);
|
||||||
+ _listenHost + ":" + _listenPort);
|
else
|
||||||
|
addr = null;
|
||||||
|
SSLServerSocket sock = (SSLServerSocket) fact.createServerSocket(_listenPort, 0, addr);
|
||||||
|
I2PSSLSocketFactory.setProtocolsAndCiphers(sock);
|
||||||
|
serverSocket = new SSLServerSocketChannel(sock);
|
||||||
} else {
|
} else {
|
||||||
serverSocket = ServerSocketChannel.open();
|
serverSocket = ServerSocketChannel.open();
|
||||||
serverSocket.socket().bind(new InetSocketAddress(_listenPort));
|
if (_listenHost != null && !_listenHost.equals("0.0.0.0")) {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
serverSocket.socket().bind(new InetSocketAddress(_listenHost, _listenPort));
|
||||||
_log.debug("SAM bridge listening on 0.0.0.0:" + _listenPort);
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("SAM bridge listening on "
|
||||||
|
+ _listenHost + ":" + _listenPort);
|
||||||
|
} else {
|
||||||
|
serverSocket.socket().bind(new InetSocketAddress(_listenPort));
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("SAM bridge listening on 0.0.0.0:" + _listenPort);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,6 +357,40 @@ public class SAMBridge implements Runnable, ClientApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Was a static singleton, now a singleton for this bridge.
|
||||||
|
* Instantiate and start server if it doesn't exist.
|
||||||
|
* We only listen on one host and port, as specified in the
|
||||||
|
* sam.udp.host and sam.udp.port properties.
|
||||||
|
* TODO we could have multiple servers on different hosts/ports in the future.
|
||||||
|
*
|
||||||
|
* @param props non-null instantiate and start server if it doesn't exist
|
||||||
|
* @param return non-null
|
||||||
|
* @throws IOException if can't bind to host/port, or if different than existing
|
||||||
|
* @since 0.9.24
|
||||||
|
*/
|
||||||
|
SAMv3DatagramServer getV3DatagramServer(Properties props) throws IOException {
|
||||||
|
String host = props.getProperty(PROP_DATAGRAM_HOST, DEFAULT_DATAGRAM_HOST);
|
||||||
|
int port;
|
||||||
|
String portStr = props.getProperty(PROP_DATAGRAM_PORT, DEFAULT_DATAGRAM_PORT);
|
||||||
|
try {
|
||||||
|
port = Integer.parseInt(portStr);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
port = DEFAULT_DATAGRAM_PORT_INT;
|
||||||
|
}
|
||||||
|
synchronized (_v3DGServerLock) {
|
||||||
|
if (_v3DGServer == null) {
|
||||||
|
_v3DGServer = new SAMv3DatagramServer(this, host, port, props);
|
||||||
|
_v3DGServer.start();
|
||||||
|
} else {
|
||||||
|
if (_v3DGServer.getPort() != port || !_v3DGServer.getHost().equals(host))
|
||||||
|
throw new IOException("Already have V3 DatagramServer with host=" + host + " port=" + port);
|
||||||
|
}
|
||||||
|
return _v3DGServer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
////// begin ClientApp interface, use only if using correct construtor
|
////// begin ClientApp interface, use only if using correct construtor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -381,7 +452,7 @@ public class SAMBridge implements Runnable, ClientApp {
|
|||||||
* @since 0.9.6
|
* @since 0.9.6
|
||||||
*/
|
*/
|
||||||
public String getDisplayName() {
|
public String getDisplayName() {
|
||||||
return "SAM " + Arrays.toString(_args);
|
return "SAM " + _listenHost + ':' + _listenPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
////// end ClientApp interface
|
////// end ClientApp interface
|
||||||
@ -421,7 +492,8 @@ public class SAMBridge implements Runnable, ClientApp {
|
|||||||
public static void main(String args[]) {
|
public static void main(String args[]) {
|
||||||
try {
|
try {
|
||||||
Options options = getOptions(args);
|
Options options = getOptions(args);
|
||||||
SAMBridge bridge = new SAMBridge(options.host, options.port, options.opts, options.keyFile);
|
SAMBridge bridge = new SAMBridge(options.host, options.port, options.isSSL, options.opts,
|
||||||
|
options.keyFile, options.configFile);
|
||||||
bridge.startThread();
|
bridge.startThread();
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -459,9 +531,13 @@ public class SAMBridge implements Runnable, ClientApp {
|
|||||||
private final String host, keyFile;
|
private final String host, keyFile;
|
||||||
private final int port;
|
private final int port;
|
||||||
private final Properties opts;
|
private final Properties opts;
|
||||||
|
private final boolean isSSL;
|
||||||
|
private final File configFile;
|
||||||
|
|
||||||
public Options(String host, int port, Properties opts, String keyFile) {
|
public Options(String host, int port, boolean isSSL, Properties opts, String keyFile, File configFile) {
|
||||||
this.host = host; this.port = port; this.opts = opts; this.keyFile = keyFile;
|
this.host = host; this.port = port; this.opts = opts; this.keyFile = keyFile;
|
||||||
|
this.isSSL = isSSL;
|
||||||
|
this.configFile = configFile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -476,73 +552,162 @@ public class SAMBridge implements Runnable, ClientApp {
|
|||||||
* depth, etc.
|
* depth, etc.
|
||||||
* @param args [ keyfile [ listenHost ] listenPort [ name=val ]* ]
|
* @param args [ keyfile [ listenHost ] listenPort [ name=val ]* ]
|
||||||
* @return non-null Options or throws Exception
|
* @return non-null Options or throws Exception
|
||||||
|
* @throws HelpRequestedException on command line problems
|
||||||
|
* @throws IllegalArgumentException if specified config file does not exist
|
||||||
|
* @throws IOException if specified config file cannot be read, or on SSL keystore problems
|
||||||
* @since 0.9.6
|
* @since 0.9.6
|
||||||
*/
|
*/
|
||||||
private static Options getOptions(String args[]) throws Exception {
|
private static Options getOptions(String args[]) throws Exception {
|
||||||
String keyfile = DEFAULT_SAM_KEYFILE;
|
String keyfile = null;
|
||||||
int port = SAM_LISTENPORT;
|
int port = -1;
|
||||||
String host = DEFAULT_TCP_HOST;
|
String host = null;
|
||||||
Properties opts = null;
|
boolean isSSL = false;
|
||||||
if (args.length > 0) {
|
String cfile = null;
|
||||||
opts = parseOptions(args, 0);
|
Getopt g = new Getopt("SAM", args, "hsc:");
|
||||||
keyfile = args[0];
|
int c;
|
||||||
int portIndex = 1;
|
while ((c = g.getopt()) != -1) {
|
||||||
try {
|
switch (c) {
|
||||||
if (args.length>portIndex) port = Integer.parseInt(args[portIndex]);
|
case 's':
|
||||||
} catch (NumberFormatException nfe) {
|
isSSL = true;
|
||||||
host = args[portIndex];
|
break;
|
||||||
portIndex++;
|
|
||||||
try {
|
case 'c':
|
||||||
if (args.length>portIndex) port = Integer.parseInt(args[portIndex]);
|
cfile = g.getOptarg();
|
||||||
} catch (NumberFormatException nfe1) {
|
break;
|
||||||
port = Integer.parseInt(opts.getProperty(SAMBridge.PROP_TCP_PORT, SAMBridge.DEFAULT_TCP_PORT));
|
|
||||||
host = opts.getProperty(SAMBridge.PROP_TCP_HOST, SAMBridge.DEFAULT_TCP_HOST);
|
case 'h':
|
||||||
}
|
case '?':
|
||||||
}
|
case ':':
|
||||||
|
default:
|
||||||
|
throw new HelpRequestedException();
|
||||||
|
} // switch
|
||||||
|
} // while
|
||||||
|
|
||||||
|
int startArgs = g.getOptind();
|
||||||
|
// possible args before ones containing '=';
|
||||||
|
// (none)
|
||||||
|
// key port
|
||||||
|
// key host port
|
||||||
|
int startOpts;
|
||||||
|
for (startOpts = startArgs; startOpts < args.length; startOpts++) {
|
||||||
|
if (args[startOpts].contains("="))
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return new Options(host, port, opts, keyfile);
|
int numArgs = startOpts - startArgs;
|
||||||
|
switch (numArgs) {
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
keyfile = args[startArgs];
|
||||||
|
try {
|
||||||
|
port = Integer.parseInt(args[startArgs + 1]);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
throw new HelpRequestedException();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
keyfile = args[startArgs];
|
||||||
|
host = args[startArgs + 1];
|
||||||
|
try {
|
||||||
|
port = Integer.parseInt(args[startArgs + 2]);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
throw new HelpRequestedException();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new HelpRequestedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
String scfile = cfile != null ? cfile : DEFAULT_SAM_CONFIGFILE;
|
||||||
|
File file = new File(scfile);
|
||||||
|
if (!file.isAbsolute())
|
||||||
|
file = new File(I2PAppContext.getGlobalContext().getConfigDir(), scfile);
|
||||||
|
|
||||||
|
Properties opts = new Properties();
|
||||||
|
if (file.exists()) {
|
||||||
|
DataHelper.loadProps(opts, file);
|
||||||
|
} else if (cfile != null) {
|
||||||
|
// only throw if specified on command line
|
||||||
|
throw new IllegalArgumentException("Config file not found: " + file);
|
||||||
|
}
|
||||||
|
// command line trumps config file trumps defaults
|
||||||
|
if (host == null)
|
||||||
|
host = opts.getProperty(PROP_TCP_HOST, DEFAULT_TCP_HOST);
|
||||||
|
if (port < 0) {
|
||||||
|
try {
|
||||||
|
port = Integer.parseInt(opts.getProperty(PROP_TCP_PORT, DEFAULT_TCP_PORT));
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
throw new HelpRequestedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keyfile == null)
|
||||||
|
keyfile = opts.getProperty(PROP_SAM_KEYFILE, DEFAULT_SAM_KEYFILE);
|
||||||
|
if (!isSSL)
|
||||||
|
isSSL = Boolean.parseBoolean(opts.getProperty(PROP_SAM_SSL));
|
||||||
|
if (isSSL) {
|
||||||
|
// must do this before we add command line opts since we may be writing them back out
|
||||||
|
boolean shouldSave = SSLUtil.verifyKeyStore(opts);
|
||||||
|
if (shouldSave)
|
||||||
|
DataHelper.storeProps(opts, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
int remaining = args.length - startOpts;
|
||||||
|
if (remaining > 0) {
|
||||||
|
parseOptions(args, startOpts, opts);
|
||||||
|
}
|
||||||
|
return new Options(host, port, isSSL, opts, keyfile, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Properties parseOptions(String args[], int startArgs) throws HelpRequestedException {
|
/**
|
||||||
Properties props = new Properties();
|
* Parse key=value options starting at startArgs.
|
||||||
// skip over first few options
|
* @param props out parameter, any options found are added
|
||||||
|
* @throws HelpRequestedException on any item not of the form key=value.
|
||||||
|
*/
|
||||||
|
private static void parseOptions(String args[], int startArgs, Properties props) throws HelpRequestedException {
|
||||||
for (int i = startArgs; i < args.length; i++) {
|
for (int i = startArgs; i < args.length; i++) {
|
||||||
if (args[i].equals("-h")) throw new HelpRequestedException();
|
|
||||||
int eq = args[i].indexOf('=');
|
int eq = args[i].indexOf('=');
|
||||||
if (eq <= 0) continue;
|
if (eq <= 0)
|
||||||
if (eq >= args[i].length()-1) continue;
|
throw new HelpRequestedException();
|
||||||
|
if (eq >= args[i].length()-1)
|
||||||
|
throw new HelpRequestedException();
|
||||||
String key = args[i].substring(0, eq);
|
String key = args[i].substring(0, eq);
|
||||||
String val = args[i].substring(eq+1);
|
String val = args[i].substring(eq+1);
|
||||||
key = key.trim();
|
key = key.trim();
|
||||||
val = val.trim();
|
val = val.trim();
|
||||||
if ( (key.length() > 0) && (val.length() > 0) )
|
if ( (key.length() > 0) && (val.length() > 0) )
|
||||||
props.setProperty(key, val);
|
props.setProperty(key, val);
|
||||||
|
else
|
||||||
|
throw new HelpRequestedException();
|
||||||
}
|
}
|
||||||
return props;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void usage() {
|
private static void usage() {
|
||||||
System.err.println("Usage: SAMBridge [keyfile [listenHost] listenPortNum[ name=val]*]");
|
System.err.println("Usage: SAMBridge [-s] [-c sam.config] [keyfile [listenHost] listenPortNum[ name=val]*]\n" +
|
||||||
System.err.println("or:");
|
"or:\n" +
|
||||||
System.err.println(" SAMBridge [ name=val ]*");
|
" SAMBridge [ name=val ]*\n" +
|
||||||
System.err.println(" keyfile: location to persist private keys (default sam.keys)");
|
" -s: Use SSL\n" +
|
||||||
System.err.println(" listenHost: interface to listen on (0.0.0.0 for all interfaces)");
|
" -c sam.config: Specify config file\n" +
|
||||||
System.err.println(" listenPort: port to listen for SAM connections on (default 7656)");
|
" keyfile: location to persist private keys (default sam.keys)\n" +
|
||||||
System.err.println(" name=val: options to pass when connecting via I2CP, such as ");
|
" listenHost: interface to listen on (0.0.0.0 for all interfaces)\n" +
|
||||||
System.err.println(" i2cp.host=localhost and i2cp.port=7654");
|
" listenPort: port to listen for SAM connections on (default 7656)\n" +
|
||||||
System.err.println("");
|
" name=val: options to pass when connecting via I2CP, such as \n" +
|
||||||
System.err.println("Host and ports of the SAM bridge can be specified with the alternate");
|
" i2cp.host=localhost and i2cp.port=7654\n" +
|
||||||
System.err.println("form by specifying options "+SAMBridge.PROP_TCP_HOST+" and/or "+
|
"\n" +
|
||||||
SAMBridge.PROP_TCP_PORT);
|
"Host and ports of the SAM bridge can be specified with the alternate\n" +
|
||||||
System.err.println("");
|
"form by specifying options "+SAMBridge.PROP_TCP_HOST+" and/or "+
|
||||||
System.err.println("Options "+SAMBridge.PROP_DATAGRAM_HOST+" and "+SAMBridge.PROP_DATAGRAM_PORT+
|
SAMBridge.PROP_TCP_PORT +
|
||||||
" specify the listening ip");
|
"\n" +
|
||||||
System.err.println("range and the port of SAM datagram server. This server is");
|
"Options "+SAMBridge.PROP_DATAGRAM_HOST+" and "+SAMBridge.PROP_DATAGRAM_PORT+
|
||||||
System.err.println("only launched after a client creates the first SAM datagram");
|
" specify the listening ip\n" +
|
||||||
System.err.println("or raw session, after a handshake with SAM version >= 3.0.");
|
"range and the port of SAM datagram server. This server is\n" +
|
||||||
System.err.println("");
|
"only launched after a client creates the first SAM datagram\n" +
|
||||||
System.err.println("The option loglevel=[DEBUG|WARN|ERROR|CRIT] can be used");
|
"or raw session, after a handshake with SAM version >= 3.0.\n" +
|
||||||
System.err.println("for tuning the log verbosity.\n");
|
"\n" +
|
||||||
|
"The option loglevel=[DEBUG|WARN|ERROR|CRIT] can be used\n" +
|
||||||
|
"for tuning the log verbosity.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
@ -621,4 +786,9 @@ public class SAMBridge implements Runnable, ClientApp {
|
|||||||
changeState(STOPPED);
|
changeState(STOPPED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @since 0.9.24 */
|
||||||
|
public void saveConfig() throws IOException {
|
||||||
|
DataHelper.storeProps(i2cpProps, _configFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,9 +22,12 @@ interface SAMDatagramReceiver {
|
|||||||
*
|
*
|
||||||
* @param sender Destination
|
* @param sender Destination
|
||||||
* @param data Byte array to be received
|
* @param data Byte array to be received
|
||||||
|
* @param proto I2CP protocol
|
||||||
|
* @param fromPort I2CP from port
|
||||||
|
* @param toPort I2CP to port
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public void receiveDatagramBytes(Destination sender, byte data[]) throws IOException;
|
public void receiveDatagramBytes(Destination sender, byte data[], int proto, int fromPort, int toPort) throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop receiving data.
|
* Stop receiving data.
|
||||||
|
@ -78,24 +78,24 @@ class SAMDatagramSession extends SAMMessageSession {
|
|||||||
*
|
*
|
||||||
* @param dest Destination
|
* @param dest Destination
|
||||||
* @param data Bytes to be sent
|
* @param data Bytes to be sent
|
||||||
|
* @param proto ignored, will always use PROTO_DATAGRAM (17)
|
||||||
*
|
*
|
||||||
* @return True if the data was sent, false otherwise
|
* @return True if the data was sent, false otherwise
|
||||||
* @throws DataFormatException on unknown / bad dest
|
* @throws DataFormatException on unknown / bad dest
|
||||||
* @throws I2PSessionException on serious error, probably session closed
|
* @throws I2PSessionException on serious error, probably session closed
|
||||||
*/
|
*/
|
||||||
public boolean sendBytes(String dest, byte[] data) throws DataFormatException, I2PSessionException {
|
public boolean sendBytes(String dest, byte[] data, int proto,
|
||||||
|
int fromPort, int toPort) throws DataFormatException, I2PSessionException {
|
||||||
if (data.length > DGRAM_SIZE_MAX)
|
if (data.length > DGRAM_SIZE_MAX)
|
||||||
throw new DataFormatException("Datagram size exceeded (" + data.length + ")");
|
throw new DataFormatException("Datagram size exceeded (" + data.length + ")");
|
||||||
byte[] dgram ;
|
byte[] dgram ;
|
||||||
synchronized (dgramMaker) {
|
synchronized (dgramMaker) {
|
||||||
dgram = dgramMaker.makeI2PDatagram(data);
|
dgram = dgramMaker.makeI2PDatagram(data);
|
||||||
}
|
}
|
||||||
// TODO pass ports through
|
return sendBytesThroughMessageSession(dest, dgram, I2PSession.PROTO_DATAGRAM, fromPort, toPort);
|
||||||
return sendBytesThroughMessageSession(dest, dgram, I2PSession.PROTO_DATAGRAM,
|
|
||||||
I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void messageReceived(byte[] msg) {
|
protected void messageReceived(byte[] msg, int proto, int fromPort, int toPort) {
|
||||||
byte[] payload;
|
byte[] payload;
|
||||||
Destination sender;
|
Destination sender;
|
||||||
try {
|
try {
|
||||||
@ -106,18 +106,18 @@ class SAMDatagramSession extends SAMMessageSession {
|
|||||||
}
|
}
|
||||||
} catch (DataFormatException e) {
|
} catch (DataFormatException e) {
|
||||||
if (_log.shouldLog(Log.DEBUG)) {
|
if (_log.shouldLog(Log.DEBUG)) {
|
||||||
_log.debug("Dropping ill-formatted I2P repliable datagram");
|
_log.debug("Dropping ill-formatted I2P repliable datagram", e);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} catch (I2PInvalidDatagramException e) {
|
} catch (I2PInvalidDatagramException e) {
|
||||||
if (_log.shouldLog(Log.DEBUG)) {
|
if (_log.shouldLog(Log.DEBUG)) {
|
||||||
_log.debug("Dropping ill-signed I2P repliable datagram");
|
_log.debug("Dropping ill-signed I2P repliable datagram", e);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
recv.receiveDatagramBytes(sender, payload);
|
recv.receiveDatagramBytes(sender, payload, proto, fromPort, toPort);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
_log.error("Error forwarding message to receiver", e);
|
_log.error("Error forwarding message to receiver", e);
|
||||||
close();
|
close();
|
||||||
|
@ -35,8 +35,8 @@ abstract class SAMHandler implements Runnable, Handler {
|
|||||||
private final Object socketWLock = new Object(); // Guards writings on socket
|
private final Object socketWLock = new Object(); // Guards writings on socket
|
||||||
protected final SocketChannel socket;
|
protected final SocketChannel socket;
|
||||||
|
|
||||||
protected final int verMajor;
|
public final int verMajor;
|
||||||
protected final int verMinor;
|
public final int verMinor;
|
||||||
|
|
||||||
/** I2CP options configuring the I2CP connection (port, host, numHops, etc) */
|
/** I2CP options configuring the I2CP connection (port, host, numHops, etc) */
|
||||||
protected final Properties i2cpProps;
|
protected final Properties i2cpProps;
|
||||||
@ -102,7 +102,10 @@ abstract class SAMHandler implements Runnable, Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static public void writeBytes(ByteBuffer data, SocketChannel out) throws IOException {
|
/**
|
||||||
|
* Caller must synch
|
||||||
|
*/
|
||||||
|
private static void writeBytes(ByteBuffer data, SocketChannel out) throws IOException {
|
||||||
while (data.hasRemaining()) out.write(data);
|
while (data.hasRemaining()) out.write(data);
|
||||||
out.socket().getOutputStream().flush();
|
out.socket().getOutputStream().flush();
|
||||||
}
|
}
|
||||||
@ -132,7 +135,10 @@ abstract class SAMHandler implements Runnable, Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return success */
|
/**
|
||||||
|
* Unsynchronized, use with caution
|
||||||
|
* @return success
|
||||||
|
*/
|
||||||
public static boolean writeString(String str, SocketChannel out)
|
public static boolean writeString(String str, SocketChannel out)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@ -158,6 +164,8 @@ abstract class SAMHandler implements Runnable, Handler {
|
|||||||
* unregister with the bridge.
|
* unregister with the bridge.
|
||||||
*/
|
*/
|
||||||
public void stopHandling() {
|
public void stopHandling() {
|
||||||
|
if (_log.shouldInfo())
|
||||||
|
_log.info("Stopping: " + this, new Exception("I did it"));
|
||||||
synchronized (stopLock) {
|
synchronized (stopLock) {
|
||||||
stopHandler = true;
|
stopHandler = true;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import java.util.StringTokenizer;
|
|||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
|
import net.i2p.util.PasswordManager;
|
||||||
import net.i2p.util.VersionComparator;
|
import net.i2p.util.VersionComparator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,7 +26,7 @@ import net.i2p.util.VersionComparator;
|
|||||||
*/
|
*/
|
||||||
class SAMHandlerFactory {
|
class SAMHandlerFactory {
|
||||||
|
|
||||||
private static final String VERSION = "3.1";
|
private static final String VERSION = "3.2";
|
||||||
|
|
||||||
private static final int HELLO_TIMEOUT = 60*1000;
|
private static final int HELLO_TIMEOUT = 60*1000;
|
||||||
|
|
||||||
@ -45,20 +46,17 @@ class SAMHandlerFactory {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Socket sock = s.socket();
|
Socket sock = s.socket();
|
||||||
sock.setSoTimeout(HELLO_TIMEOUT);
|
|
||||||
sock.setKeepAlive(true);
|
sock.setKeepAlive(true);
|
||||||
String line = DataHelper.readLine(sock.getInputStream());
|
StringBuilder buf = new StringBuilder(128);
|
||||||
|
ReadLine.readLine(sock, buf, HELLO_TIMEOUT);
|
||||||
|
String line = buf.toString();
|
||||||
sock.setSoTimeout(0);
|
sock.setSoTimeout(0);
|
||||||
if (line == null) {
|
|
||||||
log.debug("Connection closed by client");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
tok = new StringTokenizer(line.trim(), " ");
|
tok = new StringTokenizer(line.trim(), " ");
|
||||||
} catch (SocketTimeoutException e) {
|
} catch (SocketTimeoutException e) {
|
||||||
throw new SAMException("Timeout waiting for HELLO VERSION", e);
|
throw new SAMException("Timeout waiting for HELLO VERSION", e);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new SAMException("Error reading from socket", e);
|
throw new SAMException("Error reading from socket", e);
|
||||||
} catch (Exception e) {
|
} catch (RuntimeException e) {
|
||||||
throw new SAMException("Unexpected error", e);
|
throw new SAMException("Unexpected error", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,6 +91,20 @@ class SAMHandlerFactory {
|
|||||||
SAMHandler.writeString("HELLO REPLY RESULT=NOVERSION\n", s);
|
SAMHandler.writeString("HELLO REPLY RESULT=NOVERSION\n", s);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Boolean.parseBoolean(i2cpProps.getProperty(SAMBridge.PROP_AUTH))) {
|
||||||
|
String user = props.getProperty("USER");
|
||||||
|
String pw = props.getProperty("PASSWORD");
|
||||||
|
if (user == null || pw == null)
|
||||||
|
throw new SAMException("USER and PASSWORD required");
|
||||||
|
String savedPW = i2cpProps.getProperty(SAMBridge.PROP_PW_PREFIX + user + SAMBridge.PROP_PW_SUFFIX);
|
||||||
|
if (savedPW == null)
|
||||||
|
throw new SAMException("Authorization failed");
|
||||||
|
PasswordManager pm = new PasswordManager(I2PAppContext.getGlobalContext());
|
||||||
|
if (!pm.checkHash(savedPW, pw))
|
||||||
|
throw new SAMException("Authorization failed");
|
||||||
|
}
|
||||||
|
|
||||||
// Let's answer positively
|
// Let's answer positively
|
||||||
if (!SAMHandler.writeString("HELLO REPLY RESULT=OK VERSION=" + ver + "\n", s))
|
if (!SAMHandler.writeString("HELLO REPLY RESULT=OK VERSION=" + ver + "\n", s))
|
||||||
throw new SAMException("Error writing to socket");
|
throw new SAMException("Error writing to socket");
|
||||||
@ -131,6 +143,9 @@ class SAMHandlerFactory {
|
|||||||
if (VersionComparator.comp(VERSION, minVer) >= 0 &&
|
if (VersionComparator.comp(VERSION, minVer) >= 0 &&
|
||||||
VersionComparator.comp(VERSION, maxVer) <= 0)
|
VersionComparator.comp(VERSION, maxVer) <= 0)
|
||||||
return VERSION;
|
return VERSION;
|
||||||
|
if (VersionComparator.comp("3.1", minVer) >= 0 &&
|
||||||
|
VersionComparator.comp("3.1", maxVer) <= 0)
|
||||||
|
return "3.1";
|
||||||
// in VersionComparator, "3" < "3.0" so
|
// in VersionComparator, "3" < "3.0" so
|
||||||
// use comparisons carefully
|
// use comparisons carefully
|
||||||
if (VersionComparator.comp("3.0", minVer) >= 0 &&
|
if (VersionComparator.comp("3.0", minVer) >= 0 &&
|
||||||
|
@ -19,11 +19,12 @@ import net.i2p.client.I2PClient;
|
|||||||
import net.i2p.client.I2PClientFactory;
|
import net.i2p.client.I2PClientFactory;
|
||||||
import net.i2p.client.I2PSession;
|
import net.i2p.client.I2PSession;
|
||||||
import net.i2p.client.I2PSessionException;
|
import net.i2p.client.I2PSessionException;
|
||||||
import net.i2p.client.I2PSessionListener;
|
import net.i2p.client.I2PSessionMuxedListener;
|
||||||
|
import net.i2p.client.SendMessageOptions;
|
||||||
import net.i2p.data.Base64;
|
import net.i2p.data.Base64;
|
||||||
import net.i2p.data.DataFormatException;
|
import net.i2p.data.DataFormatException;
|
||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.util.HexDump;
|
//import net.i2p.util.HexDump;
|
||||||
import net.i2p.util.I2PAppThread;
|
import net.i2p.util.I2PAppThread;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
@ -97,7 +98,8 @@ abstract class SAMMessageSession implements Closeable {
|
|||||||
* @throws DataFormatException on unknown / bad dest
|
* @throws DataFormatException on unknown / bad dest
|
||||||
* @throws I2PSessionException on serious error, probably session closed
|
* @throws I2PSessionException on serious error, probably session closed
|
||||||
*/
|
*/
|
||||||
public abstract boolean sendBytes(String dest, byte[] data) throws DataFormatException, I2PSessionException;
|
public abstract boolean sendBytes(String dest, byte[] data, int proto,
|
||||||
|
int fromPort, int toPort) throws DataFormatException, I2PSessionException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Actually send bytes through the SAM message-based session I2PSession
|
* Actually send bytes through the SAM message-based session I2PSession
|
||||||
@ -125,6 +127,40 @@ abstract class SAMMessageSession implements Closeable {
|
|||||||
return session.sendMessage(d, data, proto, fromPort, toPort);
|
return session.sendMessage(d, data, proto, fromPort, toPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actually send bytes through the SAM message-based session I2PSession.
|
||||||
|
* TODO unused, umimplemented in the sessions and handlers
|
||||||
|
*
|
||||||
|
* @param dest Destination
|
||||||
|
* @param data Bytes to be sent
|
||||||
|
* @param proto I2CP protocol
|
||||||
|
* @param fromPort I2CP from port
|
||||||
|
* @param toPort I2CP to port
|
||||||
|
*
|
||||||
|
* @return True if the data was sent, false otherwise
|
||||||
|
* @throws DataFormatException on unknown / bad dest
|
||||||
|
* @throws I2PSessionException on serious error, probably session closed
|
||||||
|
* @since 0.9.24
|
||||||
|
*/
|
||||||
|
protected boolean sendBytesThroughMessageSession(String dest, byte[] data,
|
||||||
|
int proto, int fromPort, int toPort,
|
||||||
|
boolean sendLeaseSet, int sendTags,
|
||||||
|
int tagThreshold, long expires)
|
||||||
|
throws DataFormatException, I2PSessionException {
|
||||||
|
Destination d = SAMUtils.getDest(dest);
|
||||||
|
|
||||||
|
if (_log.shouldLog(Log.DEBUG)) {
|
||||||
|
_log.debug("Sending " + data.length + " bytes to " + dest);
|
||||||
|
}
|
||||||
|
SendMessageOptions opts = new SendMessageOptions();
|
||||||
|
opts.setSendLeaseSet(sendLeaseSet);
|
||||||
|
opts.setTagsToSend(sendTags);
|
||||||
|
opts.setTagThreshold(tagThreshold);
|
||||||
|
opts.setDate(expires);
|
||||||
|
|
||||||
|
return session.sendMessage(d, data, 0, data.length, proto, fromPort, toPort, opts);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close a SAM message-based session.
|
* Close a SAM message-based session.
|
||||||
*/
|
*/
|
||||||
@ -136,7 +172,7 @@ abstract class SAMMessageSession implements Closeable {
|
|||||||
* Handle a new received message
|
* Handle a new received message
|
||||||
* @param msg Message payload
|
* @param msg Message payload
|
||||||
*/
|
*/
|
||||||
protected abstract void messageReceived(byte[] msg);
|
protected abstract void messageReceived(byte[] msg, int proto, int fromPort, int toPort);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do whatever is needed to shutdown the SAM session
|
* Do whatever is needed to shutdown the SAM session
|
||||||
@ -158,7 +194,7 @@ abstract class SAMMessageSession implements Closeable {
|
|||||||
*
|
*
|
||||||
* @author human
|
* @author human
|
||||||
*/
|
*/
|
||||||
class SAMMessageSessionHandler implements Runnable, I2PSessionListener {
|
class SAMMessageSessionHandler implements Runnable, I2PSessionMuxedListener {
|
||||||
|
|
||||||
private final Object runningLock = new Object();
|
private final Object runningLock = new Object();
|
||||||
private volatile boolean stillRunning = true;
|
private volatile boolean stillRunning = true;
|
||||||
@ -187,7 +223,7 @@ abstract class SAMMessageSession implements Closeable {
|
|||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("I2P session connected");
|
_log.debug("I2P session connected");
|
||||||
|
|
||||||
session.setSessionListener(this);
|
session.addMuxedSessionListener(this, I2PSession.PROTO_ANY, I2PSession.PORT_ANY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -218,6 +254,7 @@ abstract class SAMMessageSession implements Closeable {
|
|||||||
_log.debug("Shutting down SAM message-based session handler");
|
_log.debug("Shutting down SAM message-based session handler");
|
||||||
|
|
||||||
shutDown();
|
shutDown();
|
||||||
|
session.removeListener(I2PSession.PROTO_ANY, I2PSession.PORT_ANY);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
@ -243,7 +280,15 @@ abstract class SAMMessageSession implements Closeable {
|
|||||||
stopRunning();
|
stopRunning();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void messageAvailable(I2PSession session, int msgId, long size){
|
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||||
|
messageAvailable(session, msgId, size, I2PSession.PROTO_UNSPECIFIED,
|
||||||
|
I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since 0.9.24 */
|
||||||
|
public void messageAvailable(I2PSession session, int msgId, long size,
|
||||||
|
int proto, int fromPort, int toPort) {
|
||||||
|
|
||||||
if (_log.shouldLog(Log.DEBUG)) {
|
if (_log.shouldLog(Log.DEBUG)) {
|
||||||
_log.debug("I2P message available (id: " + msgId
|
_log.debug("I2P message available (id: " + msgId
|
||||||
+ "; size: " + size + ")");
|
+ "; size: " + size + ")");
|
||||||
@ -252,12 +297,12 @@ abstract class SAMMessageSession implements Closeable {
|
|||||||
byte msg[] = session.receiveMessage(msgId);
|
byte msg[] = session.receiveMessage(msgId);
|
||||||
if (msg == null)
|
if (msg == null)
|
||||||
return;
|
return;
|
||||||
if (_log.shouldLog(Log.DEBUG)) {
|
//if (_log.shouldLog(Log.DEBUG)) {
|
||||||
_log.debug("Content of message " + msgId + ":\n"
|
// _log.debug("Content of message " + msgId + ":\n"
|
||||||
+ HexDump.dump(msg));
|
// + HexDump.dump(msg));
|
||||||
}
|
//}
|
||||||
|
|
||||||
messageReceived(msg);
|
messageReceived(msg, proto, fromPort, toPort);
|
||||||
} catch (I2PSessionException e) {
|
} catch (I2PSessionException e) {
|
||||||
_log.error("Error fetching I2P message", e);
|
_log.error("Error fetching I2P message", e);
|
||||||
stopRunning();
|
stopRunning();
|
||||||
|
@ -20,9 +20,12 @@ interface SAMRawReceiver {
|
|||||||
* regarding the sender.
|
* regarding the sender.
|
||||||
*
|
*
|
||||||
* @param data Byte array to be received
|
* @param data Byte array to be received
|
||||||
|
* @param proto I2CP protocol
|
||||||
|
* @param fromPort I2CP from port
|
||||||
|
* @param toPort I2CP to port
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public void receiveRawBytes(byte data[]) throws IOException;
|
public void receiveRawBytes(byte data[], int proto, int fromPort, int toPort) throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop receiving data.
|
* Stop receiving data.
|
||||||
|
@ -67,22 +67,24 @@ class SAMRawSession extends SAMMessageSession {
|
|||||||
* Send bytes through a SAM RAW session.
|
* Send bytes through a SAM RAW session.
|
||||||
*
|
*
|
||||||
* @param data Bytes to be sent
|
* @param data Bytes to be sent
|
||||||
|
* @param proto if 0, will use PROTO_DATAGRAM_RAW (18)
|
||||||
*
|
*
|
||||||
* @return True if the data was sent, false otherwise
|
* @return True if the data was sent, false otherwise
|
||||||
* @throws DataFormatException on unknown / bad dest
|
* @throws DataFormatException on unknown / bad dest
|
||||||
* @throws I2PSessionException on serious error, probably session closed
|
* @throws I2PSessionException on serious error, probably session closed
|
||||||
*/
|
*/
|
||||||
public boolean sendBytes(String dest, byte[] data) throws DataFormatException, I2PSessionException {
|
public boolean sendBytes(String dest, byte[] data, int proto,
|
||||||
|
int fromPort, int toPort) throws DataFormatException, I2PSessionException {
|
||||||
if (data.length > RAW_SIZE_MAX)
|
if (data.length > RAW_SIZE_MAX)
|
||||||
throw new DataFormatException("Data size limit exceeded (" + data.length + ")");
|
throw new DataFormatException("Data size limit exceeded (" + data.length + ")");
|
||||||
// TODO pass ports through
|
if (proto == I2PSession.PROTO_UNSPECIFIED)
|
||||||
return sendBytesThroughMessageSession(dest, data, I2PSession.PROTO_DATAGRAM_RAW,
|
proto = I2PSession.PROTO_DATAGRAM_RAW;
|
||||||
I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
|
return sendBytesThroughMessageSession(dest, data, proto, fromPort, toPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void messageReceived(byte[] msg) {
|
protected void messageReceived(byte[] msg, int proto, int fromPort, int toPort) {
|
||||||
try {
|
try {
|
||||||
recv.receiveRawBytes(msg);
|
recv.receiveRawBytes(msg, proto, fromPort, toPort);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
_log.error("Error forwarding message to receiver", e);
|
_log.error("Error forwarding message to receiver", e);
|
||||||
close();
|
close();
|
||||||
|
@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
|
|
||||||
import net.i2p.I2PException;
|
import net.i2p.I2PException;
|
||||||
import net.i2p.client.I2PClient;
|
import net.i2p.client.I2PClient;
|
||||||
|
import net.i2p.client.I2PSession;
|
||||||
import net.i2p.client.I2PSessionException;
|
import net.i2p.client.I2PSessionException;
|
||||||
import net.i2p.crypto.SigType;
|
import net.i2p.crypto.SigType;
|
||||||
import net.i2p.data.Base64;
|
import net.i2p.data.Base64;
|
||||||
@ -186,7 +187,9 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Caught IOException for message [" + msg + "]", e);
|
_log.debug("Caught IOException for message [" + msg + "]", e);
|
||||||
} catch (Exception e) {
|
} catch (SAMException e) {
|
||||||
|
_log.error("Unexpected exception for message [" + msg + "]", e);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
_log.error("Unexpected exception for message [" + msg + "]", e);
|
_log.error("Unexpected exception for message [" + msg + "]", e);
|
||||||
} finally {
|
} finally {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
@ -438,25 +441,44 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
|
|||||||
}
|
}
|
||||||
|
|
||||||
int size;
|
int size;
|
||||||
{
|
String strsize = props.getProperty("SIZE");
|
||||||
String strsize = props.getProperty("SIZE");
|
if (strsize == null) {
|
||||||
if (strsize == null) {
|
if (_log.shouldLog(Log.WARN))
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
_log.warn("Size not specified in DATAGRAM SEND message");
|
||||||
_log.debug("Size not specified in DATAGRAM SEND message");
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
try {
|
||||||
|
size = Integer.parseInt(strsize);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Invalid DATAGRAM SEND size specified: " + strsize);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!checkDatagramSize(size)) {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Specified size (" + size
|
||||||
|
+ ") is out of protocol limits");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int proto = I2PSession.PROTO_DATAGRAM;
|
||||||
|
int fromPort = I2PSession.PORT_UNSPECIFIED;
|
||||||
|
int toPort = I2PSession.PORT_UNSPECIFIED;
|
||||||
|
String s = props.getProperty("FROM_PORT");
|
||||||
|
if (s != null) {
|
||||||
try {
|
try {
|
||||||
size = Integer.parseInt(strsize);
|
fromPort = Integer.parseInt(s);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.WARN))
|
||||||
_log.debug("Invalid DATAGRAM SEND size specified: " + strsize);
|
_log.warn("Invalid DATAGRAM SEND port specified: " + s);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
if (!checkDatagramSize(size)) {
|
}
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
s = props.getProperty("TO_PORT");
|
||||||
_log.debug("Specified size (" + size
|
if (s != null) {
|
||||||
+ ") is out of protocol limits");
|
try {
|
||||||
return false;
|
toPort = Integer.parseInt(s);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Invalid RAW SEND port specified: " + s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,7 +488,7 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
|
|||||||
|
|
||||||
in.readFully(data);
|
in.readFully(data);
|
||||||
|
|
||||||
if (!getDatagramSession().sendBytes(dest, data)) {
|
if (!getDatagramSession().sendBytes(dest, data, proto, fromPort, toPort)) {
|
||||||
_log.error("DATAGRAM SEND failed");
|
_log.error("DATAGRAM SEND failed");
|
||||||
// a message send failure is no reason to drop the SAM session
|
// a message send failure is no reason to drop the SAM session
|
||||||
// for raw and repliable datagrams, just carry on our merry way
|
// for raw and repliable datagrams, just carry on our merry way
|
||||||
@ -523,25 +545,53 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
|
|||||||
}
|
}
|
||||||
|
|
||||||
int size;
|
int size;
|
||||||
{
|
String strsize = props.getProperty("SIZE");
|
||||||
String strsize = props.getProperty("SIZE");
|
if (strsize == null) {
|
||||||
if (strsize == null) {
|
if (_log.shouldLog(Log.WARN))
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
_log.warn("Size not specified in RAW SEND message");
|
||||||
_log.debug("Size not specified in RAW SEND message");
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
try {
|
||||||
|
size = Integer.parseInt(strsize);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Invalid RAW SEND size specified: " + strsize);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!checkSize(size)) {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Specified size (" + size
|
||||||
|
+ ") is out of protocol limits");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int proto = I2PSession.PROTO_DATAGRAM_RAW;
|
||||||
|
int fromPort = I2PSession.PORT_UNSPECIFIED;
|
||||||
|
int toPort = I2PSession.PORT_UNSPECIFIED;
|
||||||
|
String s = props.getProperty("PROTOCOL");
|
||||||
|
if (s != null) {
|
||||||
try {
|
try {
|
||||||
size = Integer.parseInt(strsize);
|
proto = Integer.parseInt(s);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.WARN))
|
||||||
_log.debug("Invalid RAW SEND size specified: " + strsize);
|
_log.warn("Invalid RAW SEND protocol specified: " + s);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
if (!checkSize(size)) {
|
}
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
s = props.getProperty("FROM_PORT");
|
||||||
_log.debug("Specified size (" + size
|
if (s != null) {
|
||||||
+ ") is out of protocol limits");
|
try {
|
||||||
return false;
|
fromPort = Integer.parseInt(s);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Invalid RAW SEND port specified: " + s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s = props.getProperty("TO_PORT");
|
||||||
|
if (s != null) {
|
||||||
|
try {
|
||||||
|
toPort = Integer.parseInt(s);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Invalid RAW SEND port specified: " + s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -551,7 +601,7 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
|
|||||||
|
|
||||||
in.readFully(data);
|
in.readFully(data);
|
||||||
|
|
||||||
if (!getRawSession().sendBytes(dest, data)) {
|
if (!getRawSession().sendBytes(dest, data, proto, fromPort, toPort)) {
|
||||||
_log.error("RAW SEND failed");
|
_log.error("RAW SEND failed");
|
||||||
// a message send failure is no reason to drop the SAM session
|
// a message send failure is no reason to drop the SAM session
|
||||||
// for raw and repliable datagrams, just carry on our merry way
|
// for raw and repliable datagrams, just carry on our merry way
|
||||||
@ -796,16 +846,21 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SAMRawReceiver implementation
|
// SAMRawReceiver implementation
|
||||||
public void receiveRawBytes(byte data[]) throws IOException {
|
public void receiveRawBytes(byte data[], int proto, int fromPort, int toPort) throws IOException {
|
||||||
if (getRawSession() == null) {
|
if (getRawSession() == null) {
|
||||||
_log.error("BUG! Received raw bytes, but session is null!");
|
_log.error("BUG! Received raw bytes, but session is null!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteArrayOutputStream msg = new ByteArrayOutputStream();
|
ByteArrayOutputStream msg = new ByteArrayOutputStream(64 + data.length);
|
||||||
|
|
||||||
String msgText = "RAW RECEIVED SIZE=" + data.length + "\n";
|
String msgText = "RAW RECEIVED SIZE=" + data.length;
|
||||||
msg.write(DataHelper.getASCII(msgText));
|
msg.write(DataHelper.getASCII(msgText));
|
||||||
|
if ((verMajor == 3 && verMinor >= 2) || verMajor > 3) {
|
||||||
|
msgText = " PROTOCOL=" + proto + " FROM_PORT=" + fromPort + " TO_PORT=" + toPort;
|
||||||
|
msg.write(DataHelper.getASCII(msgText));
|
||||||
|
}
|
||||||
|
msg.write((byte) '\n');
|
||||||
msg.write(data);
|
msg.write(data);
|
||||||
|
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
@ -832,17 +887,23 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SAMDatagramReceiver implementation
|
// SAMDatagramReceiver implementation
|
||||||
public void receiveDatagramBytes(Destination sender, byte data[]) throws IOException {
|
public void receiveDatagramBytes(Destination sender, byte data[], int proto,
|
||||||
|
int fromPort, int toPort) throws IOException {
|
||||||
if (getDatagramSession() == null) {
|
if (getDatagramSession() == null) {
|
||||||
_log.error("BUG! Received datagram bytes, but session is null!");
|
_log.error("BUG! Received datagram bytes, but session is null!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteArrayOutputStream msg = new ByteArrayOutputStream();
|
ByteArrayOutputStream msg = new ByteArrayOutputStream(100 + data.length);
|
||||||
|
|
||||||
String msgText = "DATAGRAM RECEIVED DESTINATION=" + sender.toBase64()
|
String msgText = "DATAGRAM RECEIVED DESTINATION=" + sender.toBase64()
|
||||||
+ " SIZE=" + data.length + "\n";
|
+ " SIZE=" + data.length;
|
||||||
msg.write(DataHelper.getASCII(msgText));
|
msg.write(DataHelper.getASCII(msgText));
|
||||||
|
if ((verMajor == 3 && verMinor >= 2) || verMajor > 3) {
|
||||||
|
msgText = " FROM_PORT=" + fromPort + " TO_PORT=" + toPort;
|
||||||
|
msg.write(DataHelper.getASCII(msgText));
|
||||||
|
}
|
||||||
|
msg.write((byte) '\n');
|
||||||
|
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("sending to client: " + msgText);
|
_log.debug("sending to client: " + msgText);
|
||||||
|
223
apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java
Normal file
223
apps/sam/java/src/net/i2p/sam/SAMv3DatagramServer.java
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
package net.i2p.sam;
|
||||||
|
/*
|
||||||
|
* free (adj.): unencumbered; not under the control of others
|
||||||
|
* Written by human in 2004 and released into the public domain
|
||||||
|
* with no warranty of any kind, either expressed or implied.
|
||||||
|
* It probably won't make your computer catch on fire, or eat
|
||||||
|
* your children, but it might. Use at your own risk.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.DatagramChannel;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.client.I2PSession;
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
|
import net.i2p.util.I2PAppThread;
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the thread listening on 127.0.0.1:7655 or as specified by
|
||||||
|
* sam.udp.host and sam.udp.port properties.
|
||||||
|
* This is used for both repliable and raw datagrams.
|
||||||
|
*
|
||||||
|
* @since 0.9.24 moved from SAMv3Handler
|
||||||
|
*/
|
||||||
|
class SAMv3DatagramServer implements Handler {
|
||||||
|
|
||||||
|
private final DatagramChannel _server;
|
||||||
|
private final Thread _listener;
|
||||||
|
private final SAMBridge _parent;
|
||||||
|
private final String _host;
|
||||||
|
private final int _port;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does not start listener.
|
||||||
|
* Caller must call start().
|
||||||
|
*
|
||||||
|
* @param parent may be null
|
||||||
|
* @param props ignored for now
|
||||||
|
*/
|
||||||
|
public SAMv3DatagramServer(SAMBridge parent, String host, int port, Properties props) throws IOException {
|
||||||
|
_parent = parent;
|
||||||
|
_server = DatagramChannel.open();
|
||||||
|
|
||||||
|
_server.socket().bind(new InetSocketAddress(host, port));
|
||||||
|
_listener = new I2PAppThread(new Listener(_server), "SAM DatagramListener " + port);
|
||||||
|
_host = host;
|
||||||
|
_port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only call once.
|
||||||
|
* @since 0.9.22
|
||||||
|
*/
|
||||||
|
public synchronized void start() {
|
||||||
|
_listener.start();
|
||||||
|
if (_parent != null)
|
||||||
|
_parent.register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cannot be restarted.
|
||||||
|
* @since 0.9.22
|
||||||
|
*/
|
||||||
|
public synchronized void stopHandling() {
|
||||||
|
try {
|
||||||
|
_server.close();
|
||||||
|
} catch (IOException ioe) {}
|
||||||
|
_listener.interrupt();
|
||||||
|
if (_parent != null)
|
||||||
|
_parent.unregister(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(SocketAddress addr, ByteBuffer msg) throws IOException {
|
||||||
|
_server.send(msg, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since 0.9.24 */
|
||||||
|
public String getHost() { return _host; }
|
||||||
|
|
||||||
|
/** @since 0.9.24 */
|
||||||
|
public int getPort() { return _port; }
|
||||||
|
|
||||||
|
private static class Listener implements Runnable {
|
||||||
|
|
||||||
|
private final DatagramChannel server;
|
||||||
|
|
||||||
|
public Listener(DatagramChannel server)
|
||||||
|
{
|
||||||
|
this.server = server ;
|
||||||
|
}
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
ByteBuffer inBuf = ByteBuffer.allocateDirect(SAMRawSession.RAW_SIZE_MAX+1024);
|
||||||
|
|
||||||
|
while (!Thread.interrupted())
|
||||||
|
{
|
||||||
|
inBuf.clear();
|
||||||
|
try {
|
||||||
|
server.receive(inBuf);
|
||||||
|
} catch (IOException e) {
|
||||||
|
break ;
|
||||||
|
}
|
||||||
|
inBuf.flip();
|
||||||
|
ByteBuffer outBuf = ByteBuffer.wrap(new byte[inBuf.remaining()]);
|
||||||
|
outBuf.put(inBuf);
|
||||||
|
outBuf.flip();
|
||||||
|
// A new thread for every message is wildly inefficient...
|
||||||
|
//new I2PAppThread(new MessageDispatcher(outBuf.array()), "MessageDispatcher").start();
|
||||||
|
// inline
|
||||||
|
// Even though we could be sending messages through multiple sessions,
|
||||||
|
// that isn't a common use case, and blocking should be rare.
|
||||||
|
// Inside router context, I2CP drops on overflow.
|
||||||
|
(new MessageDispatcher(outBuf.array())).run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MessageDispatcher implements Runnable {
|
||||||
|
private final ByteArrayInputStream is;
|
||||||
|
|
||||||
|
public MessageDispatcher(byte[] buf) {
|
||||||
|
this.is = new ByteArrayInputStream(buf) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
String header = DataHelper.readLine(is).trim();
|
||||||
|
StringTokenizer tok = new StringTokenizer(header, " ");
|
||||||
|
if (tok.countTokens() < 3) {
|
||||||
|
// This is not a correct message, for sure
|
||||||
|
warn("Bad datagram header received");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String version = tok.nextToken();
|
||||||
|
if (!version.startsWith("3.")) {
|
||||||
|
warn("Bad datagram header received");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String nick = tok.nextToken();
|
||||||
|
String dest = tok.nextToken();
|
||||||
|
|
||||||
|
SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
|
||||||
|
if (rec!=null) {
|
||||||
|
Properties sprops = rec.getProps();
|
||||||
|
String pr = sprops.getProperty("PROTOCOL");
|
||||||
|
String fp = sprops.getProperty("FROM_PORT");
|
||||||
|
String tp = sprops.getProperty("TO_PORT");
|
||||||
|
while (tok.hasMoreTokens()) {
|
||||||
|
String t = tok.nextToken();
|
||||||
|
if (t.startsWith("PROTOCOL="))
|
||||||
|
pr = t.substring("PROTOCOL=".length());
|
||||||
|
else if (t.startsWith("FROM_PORT="))
|
||||||
|
fp = t.substring("FROM_PORT=".length());
|
||||||
|
else if (t.startsWith("TO_PORT="))
|
||||||
|
tp = t.substring("TO_PORT=".length());
|
||||||
|
}
|
||||||
|
|
||||||
|
int proto = I2PSession.PROTO_UNSPECIFIED;
|
||||||
|
int fromPort = I2PSession.PORT_UNSPECIFIED;
|
||||||
|
int toPort = I2PSession.PORT_UNSPECIFIED;
|
||||||
|
if (pr != null) {
|
||||||
|
try {
|
||||||
|
proto = Integer.parseInt(pr);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
warn("Bad datagram header received");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fp != null) {
|
||||||
|
try {
|
||||||
|
fromPort = Integer.parseInt(fp);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
warn("Bad datagram header received");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tp != null) {
|
||||||
|
try {
|
||||||
|
toPort = Integer.parseInt(tp);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
warn("Bad datagram header received");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO too many allocations and copies. One here and one in Listener above.
|
||||||
|
byte[] data = new byte[is.available()];
|
||||||
|
is.read(data);
|
||||||
|
SAMv3Handler.Session sess = rec.getHandler().getSession();
|
||||||
|
if (sess != null)
|
||||||
|
sess.sendBytes(dest, data, proto, fromPort, toPort);
|
||||||
|
else
|
||||||
|
warn("Dropping datagram, no session for " + nick);
|
||||||
|
} else {
|
||||||
|
warn("Dropping datagram, no session for " + nick);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
warn("Error handling datagram", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since 0.9.22 */
|
||||||
|
private static void warn(String s) {
|
||||||
|
warn(s, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since 0.9.22 */
|
||||||
|
private static void warn(String s, Throwable t) {
|
||||||
|
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3DatagramServer.class);
|
||||||
|
if (log.shouldLog(Log.WARN))
|
||||||
|
log.warn(s, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,7 @@ import java.nio.ByteBuffer;
|
|||||||
class SAMv3DatagramSession extends SAMDatagramSession implements SAMv3Handler.Session, SAMDatagramReceiver {
|
class SAMv3DatagramSession extends SAMDatagramSession implements SAMv3Handler.Session, SAMDatagramReceiver {
|
||||||
|
|
||||||
private final SAMv3Handler handler;
|
private final SAMv3Handler handler;
|
||||||
private final SAMv3Handler.DatagramServer server;
|
private final SAMv3DatagramServer server;
|
||||||
private final String nick;
|
private final String nick;
|
||||||
private final SocketAddress clientAddress;
|
private final SocketAddress clientAddress;
|
||||||
|
|
||||||
@ -30,52 +30,58 @@ class SAMv3DatagramSession extends SAMDatagramSession implements SAMv3Handler.Se
|
|||||||
/**
|
/**
|
||||||
* build a DatagramSession according to informations registered
|
* build a DatagramSession according to informations registered
|
||||||
* with the given nickname
|
* with the given nickname
|
||||||
|
*
|
||||||
* @param nick nickname of the session
|
* @param nick nickname of the session
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws DataFormatException
|
* @throws DataFormatException
|
||||||
* @throws I2PSessionException
|
* @throws I2PSessionException
|
||||||
*/
|
*/
|
||||||
public SAMv3DatagramSession(String nick)
|
public SAMv3DatagramSession(String nick, SAMv3DatagramServer dgServer)
|
||||||
throws IOException, DataFormatException, I2PSessionException, SAMException {
|
throws IOException, DataFormatException, I2PSessionException, SAMException {
|
||||||
|
|
||||||
super(SAMv3Handler.sSessionsHash.get(nick).getDest(),
|
super(SAMv3Handler.sSessionsHash.get(nick).getDest(),
|
||||||
SAMv3Handler.sSessionsHash.get(nick).getProps(),
|
SAMv3Handler.sSessionsHash.get(nick).getProps(),
|
||||||
null // to be replaced by this
|
null // to be replaced by this
|
||||||
);
|
);
|
||||||
this.nick = nick ;
|
this.nick = nick;
|
||||||
this.recv = this ; // replacement
|
this.recv = this; // replacement
|
||||||
this.server = SAMv3Handler.DatagramServer.getInstance() ;
|
this.server = dgServer;
|
||||||
|
|
||||||
SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
|
SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
|
||||||
if ( rec==null ) throw new SAMException("Record disappeared for nickname : \""+nick+"\"") ;
|
if (rec == null)
|
||||||
|
throw new SAMException("Record disappeared for nickname : \""+nick+"\"");
|
||||||
|
|
||||||
this.handler = rec.getHandler();
|
this.handler = rec.getHandler();
|
||||||
|
|
||||||
Properties props = rec.getProps();
|
Properties props = rec.getProps();
|
||||||
String portStr = props.getProperty("PORT") ;
|
String portStr = props.getProperty("PORT");
|
||||||
if ( portStr==null ) {
|
if (portStr == null) {
|
||||||
_log.debug("receiver port not specified. Current socket will be used.");
|
if (_log.shouldDebug())
|
||||||
this.clientAddress = null;
|
_log.debug("receiver port not specified. Current socket will be used.");
|
||||||
}
|
this.clientAddress = null;
|
||||||
else {
|
} else {
|
||||||
int port = Integer.parseInt(portStr);
|
int port = Integer.parseInt(portStr);
|
||||||
|
String host = props.getProperty("HOST");
|
||||||
String host = props.getProperty("HOST");
|
if (host == null) {
|
||||||
if ( host==null ) {
|
host = rec.getHandler().getClientIP();
|
||||||
host = rec.getHandler().getClientIP();
|
if (_log.shouldDebug())
|
||||||
_log.debug("no host specified. Taken from the client socket : " + host+':'+port);
|
_log.debug("no host specified. Taken from the client socket : " + host+':'+port);
|
||||||
}
|
}
|
||||||
|
this.clientAddress = new InetSocketAddress(host, port);
|
||||||
|
}
|
||||||
this.clientAddress = new InetSocketAddress(host,port);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void receiveDatagramBytes(Destination sender, byte[] data) throws IOException {
|
public void receiveDatagramBytes(Destination sender, byte[] data, int proto,
|
||||||
|
int fromPort, int toPort) throws IOException {
|
||||||
if (this.clientAddress==null) {
|
if (this.clientAddress==null) {
|
||||||
this.handler.receiveDatagramBytes(sender, data);
|
this.handler.receiveDatagramBytes(sender, data, proto, fromPort, toPort);
|
||||||
} else {
|
} else {
|
||||||
String msg = sender.toBase64()+"\n";
|
StringBuilder buf = new StringBuilder(600);
|
||||||
|
buf.append(sender.toBase64());
|
||||||
|
if ((handler.verMajor == 3 && handler.verMinor >= 2) || handler.verMajor > 3) {
|
||||||
|
buf.append(" FROM_PORT=").append(fromPort).append(" TO_PORT=").append(toPort);
|
||||||
|
}
|
||||||
|
buf.append('\n');
|
||||||
|
String msg = buf.toString();
|
||||||
ByteBuffer msgBuf = ByteBuffer.allocate(msg.length()+data.length);
|
ByteBuffer msgBuf = ByteBuffer.allocate(msg.length()+data.length);
|
||||||
msgBuf.put(DataHelper.getASCII(msg));
|
msgBuf.put(DataHelper.getASCII(msg));
|
||||||
msgBuf.put(data);
|
msgBuf.put(data);
|
||||||
|
@ -10,15 +10,16 @@ package net.i2p.sam;
|
|||||||
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
import java.net.NoRouteToHostException;
|
import java.net.NoRouteToHostException;
|
||||||
import java.nio.channels.DatagramChannel;
|
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
@ -28,6 +29,7 @@ import java.util.StringTokenizer;
|
|||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.I2PException;
|
import net.i2p.I2PException;
|
||||||
import net.i2p.client.I2PClient;
|
import net.i2p.client.I2PClient;
|
||||||
|
import net.i2p.client.I2PSession;
|
||||||
import net.i2p.client.I2PSessionException;
|
import net.i2p.client.I2PSessionException;
|
||||||
import net.i2p.crypto.SigType;
|
import net.i2p.crypto.SigType;
|
||||||
import net.i2p.data.Base64;
|
import net.i2p.data.Base64;
|
||||||
@ -36,6 +38,7 @@ import net.i2p.data.DataHelper;
|
|||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.I2PAppThread;
|
import net.i2p.util.I2PAppThread;
|
||||||
|
import net.i2p.util.PasswordManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class able to handle a SAM version 3 client connection.
|
* Class able to handle a SAM version 3 client connection.
|
||||||
@ -50,12 +53,15 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
public static final SessionsDB sSessionsHash = new SessionsDB();
|
public static final SessionsDB sSessionsHash = new SessionsDB();
|
||||||
private volatile boolean stolenSocket;
|
private volatile boolean stolenSocket;
|
||||||
private volatile boolean streamForwardingSocket;
|
private volatile boolean streamForwardingSocket;
|
||||||
|
private final boolean sendPorts;
|
||||||
|
private long _lastPing;
|
||||||
|
private static final int READ_TIMEOUT = 3*60*1000;
|
||||||
|
|
||||||
interface Session {
|
interface Session {
|
||||||
String getNick();
|
String getNick();
|
||||||
void close();
|
void close();
|
||||||
boolean sendBytes(String dest, byte[] data) throws DataFormatException, I2PSessionException;
|
boolean sendBytes(String dest, byte[] data, int proto,
|
||||||
|
int fromPort, int toPort) throws DataFormatException, I2PSessionException;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,6 +94,7 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
Properties i2cpProps, SAMBridge parent) throws SAMException, IOException
|
Properties i2cpProps, SAMBridge parent) throws SAMException, IOException
|
||||||
{
|
{
|
||||||
super(s, verMajor, verMinor, i2cpProps, parent);
|
super(s, verMajor, verMinor, i2cpProps, parent);
|
||||||
|
sendPorts = (verMajor == 3 && verMinor >= 2) || verMajor > 3;
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("SAM version 3 handler instantiated");
|
_log.debug("SAM version 3 handler instantiated");
|
||||||
}
|
}
|
||||||
@ -97,124 +104,6 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
{
|
{
|
||||||
return (verMajor == 3);
|
return (verMajor == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DatagramServer {
|
|
||||||
|
|
||||||
private static DatagramServer _instance;
|
|
||||||
private static DatagramChannel server;
|
|
||||||
|
|
||||||
public static DatagramServer getInstance() throws IOException {
|
|
||||||
return getInstance(new Properties());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DatagramServer getInstance(Properties props) throws IOException {
|
|
||||||
synchronized(DatagramServer.class) {
|
|
||||||
if (_instance==null)
|
|
||||||
_instance = new DatagramServer(props);
|
|
||||||
return _instance ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatagramServer(Properties props) throws IOException {
|
|
||||||
synchronized(DatagramServer.class) {
|
|
||||||
if (server==null)
|
|
||||||
server = DatagramChannel.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
String host = props.getProperty(SAMBridge.PROP_DATAGRAM_HOST, SAMBridge.DEFAULT_DATAGRAM_HOST);
|
|
||||||
String portStr = props.getProperty(SAMBridge.PROP_DATAGRAM_PORT, SAMBridge.DEFAULT_DATAGRAM_PORT);
|
|
||||||
int port ;
|
|
||||||
try {
|
|
||||||
port = Integer.parseInt(portStr);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
port = Integer.parseInt(SAMBridge.DEFAULT_DATAGRAM_PORT);
|
|
||||||
}
|
|
||||||
|
|
||||||
server.socket().bind(new InetSocketAddress(host, port));
|
|
||||||
new I2PAppThread(new Listener(server), "DatagramListener").start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void send(SocketAddress addr, ByteBuffer msg) throws IOException {
|
|
||||||
server.send(msg, addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Listener implements Runnable {
|
|
||||||
|
|
||||||
private final DatagramChannel server;
|
|
||||||
|
|
||||||
public Listener(DatagramChannel server)
|
|
||||||
{
|
|
||||||
this.server = server ;
|
|
||||||
}
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
ByteBuffer inBuf = ByteBuffer.allocateDirect(SAMRawSession.RAW_SIZE_MAX+1024);
|
|
||||||
|
|
||||||
while (!Thread.interrupted())
|
|
||||||
{
|
|
||||||
inBuf.clear();
|
|
||||||
try {
|
|
||||||
server.receive(inBuf);
|
|
||||||
} catch (IOException e) {
|
|
||||||
break ;
|
|
||||||
}
|
|
||||||
inBuf.flip();
|
|
||||||
ByteBuffer outBuf = ByteBuffer.wrap(new byte[inBuf.remaining()]);
|
|
||||||
outBuf.put(inBuf);
|
|
||||||
outBuf.flip();
|
|
||||||
// A new thread for every message is wildly inefficient...
|
|
||||||
//new I2PAppThread(new MessageDispatcher(outBuf.array()), "MessageDispatcher").start();
|
|
||||||
// inline
|
|
||||||
// Even though we could be sending messages through multiple sessions,
|
|
||||||
// that isn't a common use case, and blocking should be rare.
|
|
||||||
// Inside router context, I2CP drops on overflow.
|
|
||||||
(new MessageDispatcher(outBuf.array())).run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MessageDispatcher implements Runnable
|
|
||||||
{
|
|
||||||
private final ByteArrayInputStream is;
|
|
||||||
|
|
||||||
public MessageDispatcher(byte[] buf)
|
|
||||||
{
|
|
||||||
this.is = new java.io.ByteArrayInputStream(buf) ;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
String header = DataHelper.readLine(is).trim();
|
|
||||||
StringTokenizer tok = new StringTokenizer(header, " ");
|
|
||||||
if (tok.countTokens() != 3) {
|
|
||||||
// This is not a correct message, for sure
|
|
||||||
//_log.debug("Error in message format");
|
|
||||||
// FIXME log? throw?
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String version = tok.nextToken();
|
|
||||||
if (!"3.0".equals(version)) return ;
|
|
||||||
String nick = tok.nextToken();
|
|
||||||
String dest = tok.nextToken();
|
|
||||||
|
|
||||||
byte[] data = new byte[is.available()];
|
|
||||||
is.read(data);
|
|
||||||
SessionRecord rec = sSessionsHash.get(nick);
|
|
||||||
if (rec!=null) {
|
|
||||||
rec.getHandler().session.sendBytes(dest,data);
|
|
||||||
} else {
|
|
||||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3Handler.class);
|
|
||||||
if (log.shouldLog(Log.WARN))
|
|
||||||
log.warn("Dropping datagram, no session for " + nick);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3Handler.class);
|
|
||||||
if (log.shouldLog(Log.WARN))
|
|
||||||
log.warn("Error handling datagram", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The values in the SessionsDB
|
* The values in the SessionsDB
|
||||||
@ -342,6 +231,11 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
public void stealSocket()
|
public void stealSocket()
|
||||||
{
|
{
|
||||||
stolenSocket = true ;
|
stolenSocket = true ;
|
||||||
|
if (sendPorts) {
|
||||||
|
try {
|
||||||
|
socket.socket().setSoTimeout(0);
|
||||||
|
} catch (SocketException se) {}
|
||||||
|
}
|
||||||
this.stopHandling();
|
this.stopHandling();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,6 +247,16 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
return bridge;
|
return bridge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For SAMv3DatagramServer
|
||||||
|
* @return may be null
|
||||||
|
* @since 0.9.24
|
||||||
|
*/
|
||||||
|
Session getSession() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void handle() {
|
public void handle() {
|
||||||
String msg = null;
|
String msg = null;
|
||||||
String domain = null;
|
String domain = null;
|
||||||
@ -366,15 +270,72 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
_log.debug("SAMv3 handling started");
|
_log.debug("SAMv3 handling started");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
InputStream in = getClientSocket().socket().getInputStream();
|
Socket socket = getClientSocket().socket();
|
||||||
|
InputStream in = socket.getInputStream();
|
||||||
|
|
||||||
|
StringBuilder buf = new StringBuilder(1024);
|
||||||
while (true) {
|
while (true) {
|
||||||
if (shouldStop()) {
|
if (shouldStop()) {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Stop request found");
|
_log.debug("Stop request found");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
String line = DataHelper.readLine(in) ;
|
String line;
|
||||||
|
if (sendPorts) {
|
||||||
|
// client supports PING
|
||||||
|
try {
|
||||||
|
ReadLine.readLine(socket, buf, READ_TIMEOUT);
|
||||||
|
line = buf.toString();
|
||||||
|
buf.setLength(0);
|
||||||
|
} catch (SocketTimeoutException ste) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (buf.length() <= 0) {
|
||||||
|
if (_lastPing > 0) {
|
||||||
|
if (now - _lastPing >= READ_TIMEOUT) {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("Failed to respond to PING");
|
||||||
|
writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"PONG timeout\"\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("Sendng PING " + now);
|
||||||
|
_lastPing = now;
|
||||||
|
if (!writeString("PING " + now + '\n'))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_lastPing > 0) {
|
||||||
|
if (now - _lastPing >= 2*READ_TIMEOUT) {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("Failed to respond to PING");
|
||||||
|
writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"PONG timeout\"\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (_lastPing < 0) {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("2nd timeout");
|
||||||
|
writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"command timeout, bye\"\n");
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// don't clear buffer, don't send ping,
|
||||||
|
// go around again
|
||||||
|
_lastPing = -1;
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("timeout after partial: " + buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("loop after timeout");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf.setLength(0);
|
||||||
|
if (DataHelper.readLine(in, buf))
|
||||||
|
line = buf.toString();
|
||||||
|
else
|
||||||
|
line = null;
|
||||||
|
}
|
||||||
if (line==null) {
|
if (line==null) {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Connection closed by client (line read : null)");
|
_log.debug("Connection closed by client (line read : null)");
|
||||||
@ -394,13 +355,33 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
}
|
}
|
||||||
|
|
||||||
tok = new StringTokenizer(msg, " ");
|
tok = new StringTokenizer(msg, " ");
|
||||||
if (tok.countTokens() < 2) {
|
int count = tok.countTokens();
|
||||||
|
if (count <= 0) {
|
||||||
// This is not a correct message, for sure
|
// This is not a correct message, for sure
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Error in message format");
|
_log.debug("Ignoring whitespace");
|
||||||
break;
|
continue;
|
||||||
}
|
}
|
||||||
domain = tok.nextToken();
|
domain = tok.nextToken();
|
||||||
|
// these may not have a second token
|
||||||
|
if (domain.equals("PING")) {
|
||||||
|
execPingMessage(tok);
|
||||||
|
continue;
|
||||||
|
} else if (domain.equals("PONG")) {
|
||||||
|
execPongMessage(tok);
|
||||||
|
continue;
|
||||||
|
} else if (domain.equals("QUIT") || domain.equals("STOP") ||
|
||||||
|
domain.equals("EXIT")) {
|
||||||
|
writeString(domain + " STATUS RESULT=OK MESSAGE=bye\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (count <= 1) {
|
||||||
|
// This is not a correct message, for sure
|
||||||
|
if (writeString(domain + " STATUS RESULT=I2P_ERROR MESSAGE=\"command not specified\"\n"))
|
||||||
|
continue;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
opcode = tok.nextToken();
|
opcode = tok.nextToken();
|
||||||
if (_log.shouldLog(Log.DEBUG)) {
|
if (_log.shouldLog(Log.DEBUG)) {
|
||||||
_log.debug("Parsing (domain: \"" + domain
|
_log.debug("Parsing (domain: \"" + domain
|
||||||
@ -424,6 +405,8 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
} else if (domain.equals("RAW")) {
|
} else if (domain.equals("RAW")) {
|
||||||
// TODO not yet overridden, ID is ignored, most recent RAW session is used
|
// TODO not yet overridden, ID is ignored, most recent RAW session is used
|
||||||
canContinue = execRawMessage(opcode, props);
|
canContinue = execRawMessage(opcode, props);
|
||||||
|
} else if (domain.equals("AUTH")) {
|
||||||
|
canContinue = execAuthMessage(opcode, props);
|
||||||
} else {
|
} else {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Unrecognized message domain: \""
|
_log.debug("Unrecognized message domain: \""
|
||||||
@ -434,12 +417,14 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
if (!canContinue) {
|
if (!canContinue) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
} // while
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Caught IOException for message [" + msg + "]", e);
|
_log.debug("Caught IOException in handler", e);
|
||||||
} catch (Exception e) {
|
} catch (SAMException e) {
|
||||||
_log.error("Unexpected exception for message [" + msg + "]", e);
|
_log.error("Unexpected exception for message [" + msg + ']', e);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
_log.error("Unexpected exception for message [" + msg + ']', e);
|
||||||
} finally {
|
} finally {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Stopping handler");
|
_log.debug("Stopping handler");
|
||||||
@ -481,6 +466,8 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void stopHandling() {
|
public void stopHandling() {
|
||||||
|
if (_log.shouldInfo())
|
||||||
|
_log.info("Stopping (stolen? " + stolenSocket + "): " + this, new Exception("I did it"));
|
||||||
synchronized (stopLock) {
|
synchronized (stopLock) {
|
||||||
stopHandler = true;
|
stopHandler = true;
|
||||||
}
|
}
|
||||||
@ -609,13 +596,13 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
// Create the session
|
// Create the session
|
||||||
|
|
||||||
if (style.equals("RAW")) {
|
if (style.equals("RAW")) {
|
||||||
DatagramServer.getInstance(i2cpProps);
|
SAMv3DatagramServer dgs = bridge.getV3DatagramServer(props);
|
||||||
SAMv3RawSession v3 = newSAMRawSession(nick);
|
SAMv3RawSession v3 = new SAMv3RawSession(nick, dgs);
|
||||||
rawSession = v3;
|
rawSession = v3;
|
||||||
this.session = v3;
|
this.session = v3;
|
||||||
} else if (style.equals("DATAGRAM")) {
|
} else if (style.equals("DATAGRAM")) {
|
||||||
DatagramServer.getInstance(i2cpProps);
|
SAMv3DatagramServer dgs = bridge.getV3DatagramServer(props);
|
||||||
SAMv3DatagramSession v3 = newSAMDatagramSession(nick);
|
SAMv3DatagramSession v3 = new SAMv3DatagramSession(nick, dgs);
|
||||||
datagramSession = v3;
|
datagramSession = v3;
|
||||||
this.session = v3;
|
this.session = v3;
|
||||||
} else if (style.equals("STREAM")) {
|
} else if (style.equals("STREAM")) {
|
||||||
@ -669,18 +656,6 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
return new SAMv3StreamSession( login ) ;
|
return new SAMv3StreamSession( login ) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SAMv3RawSession newSAMRawSession(String login )
|
|
||||||
throws IOException, DataFormatException, SAMException, I2PSessionException
|
|
||||||
{
|
|
||||||
return new SAMv3RawSession( login ) ;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SAMv3DatagramSession newSAMDatagramSession(String login )
|
|
||||||
throws IOException, DataFormatException, SAMException, I2PSessionException
|
|
||||||
{
|
|
||||||
return new SAMv3DatagramSession( login ) ;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Parse and execute a STREAM message */
|
/* Parse and execute a STREAM message */
|
||||||
@Override
|
@Override
|
||||||
protected boolean execStreamMessage ( String opcode, Properties props )
|
protected boolean execStreamMessage ( String opcode, Properties props )
|
||||||
@ -757,14 +732,16 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean execStreamConnect( Properties props) {
|
protected boolean execStreamConnect( Properties props) {
|
||||||
|
// Messages are NOT sent if SILENT=true,
|
||||||
|
// The specs said that they were.
|
||||||
|
boolean verbose = !Boolean.parseBoolean(props.getProperty("SILENT"));
|
||||||
try {
|
try {
|
||||||
if (props.isEmpty()) {
|
if (props.isEmpty()) {
|
||||||
notifyStreamResult(true,"I2P_ERROR","No parameters specified in STREAM CONNECT message");
|
notifyStreamResult(verbose, "I2P_ERROR","No parameters specified in STREAM CONNECT message");
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("No parameters specified in STREAM CONNECT message");
|
_log.debug("No parameters specified in STREAM CONNECT message");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
boolean verbose = props.getProperty("SILENT","false").equals("false");
|
|
||||||
|
|
||||||
String dest = props.getProperty("DESTINATION");
|
String dest = props.getProperty("DESTINATION");
|
||||||
if (dest == null) {
|
if (dest == null) {
|
||||||
@ -804,11 +781,14 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
return false ;
|
return false ;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean execStreamForwardIncoming( Properties props ) {
|
private boolean execStreamForwardIncoming( Properties props ) {
|
||||||
|
// Messages ARE sent if SILENT=true,
|
||||||
|
// which is different from CONNECT and ACCEPT.
|
||||||
|
// But this matched the specs.
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
streamForwardingSocket = true ;
|
streamForwardingSocket = true ;
|
||||||
((SAMv3StreamSession)streamSession).startForwardingIncoming(props);
|
((SAMv3StreamSession)streamSession).startForwardingIncoming(props, sendPorts);
|
||||||
notifyStreamResult( true, "OK", null );
|
notifyStreamResult( true, "OK", null );
|
||||||
return true ;
|
return true ;
|
||||||
} catch (SAMException e) {
|
} catch (SAMException e) {
|
||||||
@ -821,9 +801,11 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
return false ;
|
return false ;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean execStreamAccept( Properties props )
|
private boolean execStreamAccept( Properties props )
|
||||||
{
|
{
|
||||||
boolean verbose = props.getProperty( "SILENT", "false").equals("false");
|
// Messages are NOT sent if SILENT=true,
|
||||||
|
// The specs said that they were.
|
||||||
|
boolean verbose = !Boolean.parseBoolean(props.getProperty("SILENT"));
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
notifyStreamResult(verbose, "OK", null);
|
notifyStreamResult(verbose, "OK", null);
|
||||||
@ -858,13 +840,18 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyStreamIncomingConnection(Destination d) throws IOException {
|
public void notifyStreamIncomingConnection(Destination d, int fromPort, int toPort) throws IOException {
|
||||||
if (getStreamSession() == null) {
|
if (getStreamSession() == null) {
|
||||||
_log.error("BUG! Received stream connection, but session is null!");
|
_log.error("BUG! Received stream connection, but session is null!");
|
||||||
throw new NullPointerException("BUG! STREAM session is null!");
|
throw new NullPointerException("BUG! STREAM session is null!");
|
||||||
}
|
}
|
||||||
|
StringBuilder buf = new StringBuilder(600);
|
||||||
if (!writeString(d.toBase64() + "\n")) {
|
buf.append(d.toBase64());
|
||||||
|
if (sendPorts) {
|
||||||
|
buf.append(" FROM_PORT=").append(fromPort).append(" TO_PORT=").append(toPort);
|
||||||
|
}
|
||||||
|
buf.append('\n');
|
||||||
|
if (!writeString(buf.toString())) {
|
||||||
throw new IOException("Error notifying connection to SAM client");
|
throw new IOException("Error notifying connection to SAM client");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -874,6 +861,91 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
throw new IOException("Error notifying connection to SAM client");
|
throw new IOException("Error notifying connection to SAM client");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @since 0.9.24 */
|
||||||
|
public static void notifyStreamIncomingConnection(SocketChannel client, Destination d,
|
||||||
|
int fromPort, int toPort) throws IOException {
|
||||||
|
if (!writeString(d.toBase64() + " FROM_PORT=" + fromPort + " TO_PORT=" + toPort + '\n', client)) {
|
||||||
|
throw new IOException("Error notifying connection to SAM client");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since 0.9.24 */
|
||||||
|
private boolean execAuthMessage(String opcode, Properties props) {
|
||||||
|
if (opcode.equals("ENABLE")) {
|
||||||
|
i2cpProps.setProperty(SAMBridge.PROP_AUTH, "true");
|
||||||
|
} else if (opcode.equals("DISABLE")) {
|
||||||
|
i2cpProps.setProperty(SAMBridge.PROP_AUTH, "false");
|
||||||
|
} else if (opcode.equals("ADD")) {
|
||||||
|
String user = props.getProperty("USER");
|
||||||
|
String pw = props.getProperty("PASSWORD");
|
||||||
|
if (user == null || pw == null)
|
||||||
|
return writeString("AUTH STATUS RESULT=I2P_ERROR MESSAGE=\"USER and PASSWORD required\"\n");
|
||||||
|
String prop = SAMBridge.PROP_PW_PREFIX + user + SAMBridge.PROP_PW_SUFFIX;
|
||||||
|
if (i2cpProps.containsKey(prop))
|
||||||
|
return writeString("AUTH STATUS RESULT=I2P_ERROR MESSAGE=\"user " + user + " already exists\"\n");
|
||||||
|
PasswordManager pm = new PasswordManager(I2PAppContext.getGlobalContext());
|
||||||
|
String shash = pm.createHash(pw);
|
||||||
|
i2cpProps.setProperty(prop, shash);
|
||||||
|
} else if (opcode.equals("REMOVE")) {
|
||||||
|
String user = props.getProperty("USER");
|
||||||
|
if (user == null)
|
||||||
|
return writeString("AUTH STATUS RESULT=I2P_ERROR MESSAGE=\"USER required\"\n");
|
||||||
|
String prop = SAMBridge.PROP_PW_PREFIX + user + SAMBridge.PROP_PW_SUFFIX;
|
||||||
|
if (!i2cpProps.containsKey(prop))
|
||||||
|
return writeString("AUTH STATUS RESULT=I2P_ERROR MESSAGE=\"user " + user + " not found\"\n");
|
||||||
|
i2cpProps.remove(prop);
|
||||||
|
} else {
|
||||||
|
return writeString("AUTH STATUS RESULT=I2P_ERROR MESSAGE=\"Unknown AUTH command\"\n");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
bridge.saveConfig();
|
||||||
|
return writeString("AUTH STATUS RESULT=OK\n");
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
return writeString("AUTH STATUS RESULT=I2P_ERROR MESSAGE=\"Config save failed: " + ioe + "\"\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a PING.
|
||||||
|
* Send a PONG.
|
||||||
|
* @since 0.9.24
|
||||||
|
*/
|
||||||
|
private void execPingMessage(StringTokenizer tok) {
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
buf.append("PONG");
|
||||||
|
while (tok.hasMoreTokens()) {
|
||||||
|
buf.append(' ').append(tok.nextToken());
|
||||||
|
}
|
||||||
|
buf.append('\n');
|
||||||
|
writeString(buf.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a PONG.
|
||||||
|
* @since 0.9.24
|
||||||
|
*/
|
||||||
|
private void execPongMessage(StringTokenizer tok) {
|
||||||
|
String s;
|
||||||
|
if (tok.hasMoreTokens()) {
|
||||||
|
s = tok.nextToken();
|
||||||
|
} else {
|
||||||
|
s = "";
|
||||||
|
}
|
||||||
|
if (_lastPing > 0) {
|
||||||
|
String expected = Long.toString(_lastPing);
|
||||||
|
if (expected.equals(s)) {
|
||||||
|
_lastPing = 0;
|
||||||
|
if (_log.shouldInfo())
|
||||||
|
_log.warn("Got expected pong: " + s);
|
||||||
|
} else {
|
||||||
|
if (_log.shouldInfo())
|
||||||
|
_log.warn("Got unexpected pong: " + s);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("Pong received without a ping: " + s);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import java.util.Properties;
|
|||||||
|
|
||||||
import net.i2p.client.I2PSessionException;
|
import net.i2p.client.I2PSessionException;
|
||||||
import net.i2p.data.DataFormatException;
|
import net.i2p.data.DataFormatException;
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,8 +23,9 @@ class SAMv3RawSession extends SAMRawSession implements SAMv3Handler.Session, SA
|
|||||||
|
|
||||||
private final String nick;
|
private final String nick;
|
||||||
private final SAMv3Handler handler;
|
private final SAMv3Handler handler;
|
||||||
private final SAMv3Handler.DatagramServer server;
|
private final SAMv3DatagramServer server;
|
||||||
private final SocketAddress clientAddress;
|
private final SocketAddress clientAddress;
|
||||||
|
private final boolean _sendHeader;
|
||||||
|
|
||||||
public String getNick() { return nick; }
|
public String getNick() { return nick; }
|
||||||
|
|
||||||
@ -36,52 +38,57 @@ class SAMv3RawSession extends SAMRawSession implements SAMv3Handler.Session, SA
|
|||||||
* @throws DataFormatException
|
* @throws DataFormatException
|
||||||
* @throws I2PSessionException
|
* @throws I2PSessionException
|
||||||
*/
|
*/
|
||||||
public SAMv3RawSession(String nick)
|
public SAMv3RawSession(String nick, SAMv3DatagramServer dgServer)
|
||||||
throws IOException, DataFormatException, I2PSessionException {
|
throws IOException, DataFormatException, I2PSessionException {
|
||||||
|
|
||||||
super(SAMv3Handler.sSessionsHash.get(nick).getDest(),
|
super(SAMv3Handler.sSessionsHash.get(nick).getDest(),
|
||||||
SAMv3Handler.sSessionsHash.get(nick).getProps(),
|
SAMv3Handler.sSessionsHash.get(nick).getProps(),
|
||||||
SAMv3Handler.sSessionsHash.get(nick).getHandler() // to be replaced by this
|
SAMv3Handler.sSessionsHash.get(nick).getHandler() // to be replaced by this
|
||||||
);
|
);
|
||||||
this.nick = nick ;
|
this.nick = nick ;
|
||||||
this.recv = this ; // replacement
|
this.recv = this ; // replacement
|
||||||
this.server = SAMv3Handler.DatagramServer.getInstance() ;
|
this.server = dgServer;
|
||||||
|
|
||||||
SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
|
SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
|
||||||
if ( rec==null ) throw new InterruptedIOException() ;
|
if (rec == null)
|
||||||
|
throw new InterruptedIOException() ;
|
||||||
this.handler = rec.getHandler();
|
this.handler = rec.getHandler();
|
||||||
|
Properties props = rec.getProps();
|
||||||
Properties props = rec.getProps();
|
String portStr = props.getProperty("PORT") ;
|
||||||
|
if (portStr == null) {
|
||||||
|
|
||||||
String portStr = props.getProperty("PORT") ;
|
|
||||||
if ( portStr==null ) {
|
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
|
||||||
_log.debug("receiver port not specified. Current socket will be used.");
|
|
||||||
this.clientAddress = null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int port = Integer.parseInt(portStr);
|
|
||||||
|
|
||||||
String host = props.getProperty("HOST");
|
|
||||||
if ( host==null ) {
|
|
||||||
host = rec.getHandler().getClientIP();
|
|
||||||
|
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("no host specified. Taken from the client socket : " + host +':'+port);
|
_log.debug("receiver port not specified. Current socket will be used.");
|
||||||
}
|
this.clientAddress = null;
|
||||||
|
} else {
|
||||||
|
int port = Integer.parseInt(portStr);
|
||||||
this.clientAddress = new InetSocketAddress(host,port);
|
String host = props.getProperty("HOST");
|
||||||
}
|
if ( host==null ) {
|
||||||
|
host = rec.getHandler().getClientIP();
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("no host specified. Taken from the client socket : " + host +':'+port);
|
||||||
|
}
|
||||||
|
this.clientAddress = new InetSocketAddress(host, port);
|
||||||
|
}
|
||||||
|
_sendHeader = ((handler.verMajor == 3 && handler.verMinor >= 2) || handler.verMajor > 3) &&
|
||||||
|
Boolean.parseBoolean(props.getProperty("HEADER"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void receiveRawBytes(byte[] data) throws IOException {
|
public void receiveRawBytes(byte[] data, int proto, int fromPort, int toPort) throws IOException {
|
||||||
if (this.clientAddress==null) {
|
if (this.clientAddress==null) {
|
||||||
this.handler.receiveRawBytes(data);
|
this.handler.receiveRawBytes(data, proto, fromPort, toPort);
|
||||||
} else {
|
} else {
|
||||||
ByteBuffer msgBuf = ByteBuffer.allocate(data.length);
|
ByteBuffer msgBuf;
|
||||||
|
if (_sendHeader) {
|
||||||
|
StringBuilder buf = new StringBuilder(64);
|
||||||
|
buf.append("PROTOCOL=").append(proto)
|
||||||
|
.append(" FROM_PORT=").append(fromPort)
|
||||||
|
.append(" TO_PORT=").append(toPort)
|
||||||
|
.append('\n');
|
||||||
|
String msg = buf.toString();
|
||||||
|
msgBuf = ByteBuffer.allocate(msg.length()+data.length);
|
||||||
|
msgBuf.put(DataHelper.getASCII(msg));
|
||||||
|
} else {
|
||||||
|
msgBuf = ByteBuffer.allocate(data.length);
|
||||||
|
}
|
||||||
msgBuf.put(data);
|
msgBuf.put(data);
|
||||||
msgBuf.flip();
|
msgBuf.flip();
|
||||||
this.server.send(this.clientAddress, msgBuf);
|
this.server.send(this.clientAddress, msgBuf);
|
||||||
|
@ -11,9 +11,22 @@ package net.i2p.sam;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.net.NoRouteToHostException;
|
import java.net.NoRouteToHostException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLException;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.I2PException;
|
import net.i2p.I2PException;
|
||||||
import net.i2p.client.streaming.I2PServerSocket;
|
import net.i2p.client.streaming.I2PServerSocket;
|
||||||
import net.i2p.client.streaming.I2PSocket;
|
import net.i2p.client.streaming.I2PSocket;
|
||||||
@ -21,12 +34,8 @@ import net.i2p.client.streaming.I2PSocketOptions;
|
|||||||
import net.i2p.data.DataFormatException;
|
import net.i2p.data.DataFormatException;
|
||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.util.I2PAppThread;
|
import net.i2p.util.I2PAppThread;
|
||||||
|
import net.i2p.util.I2PSSLSocketFactory;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import java.nio.channels.Channels;
|
|
||||||
import java.nio.channels.ReadableByteChannel;
|
|
||||||
import java.nio.channels.WritableByteChannel;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SAMv3 STREAM session class.
|
* SAMv3 STREAM session class.
|
||||||
@ -40,7 +49,12 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi
|
|||||||
private static final int BUFFER_SIZE = 1024 ;
|
private static final int BUFFER_SIZE = 1024 ;
|
||||||
|
|
||||||
private final Object socketServerLock = new Object();
|
private final Object socketServerLock = new Object();
|
||||||
|
/** this is ONLY set for FORWARD, not for ACCEPT */
|
||||||
private I2PServerSocket socketServer;
|
private I2PServerSocket socketServer;
|
||||||
|
/** this is the count of active ACCEPT sockets */
|
||||||
|
private final AtomicInteger _acceptors = new AtomicInteger();
|
||||||
|
|
||||||
|
private static I2PSSLSocketFactory _sslSocketFactory;
|
||||||
|
|
||||||
private final String nick ;
|
private final String nick ;
|
||||||
|
|
||||||
@ -91,12 +105,28 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi
|
|||||||
throws I2PException, ConnectException, NoRouteToHostException,
|
throws I2PException, ConnectException, NoRouteToHostException,
|
||||||
DataFormatException, InterruptedIOException, IOException {
|
DataFormatException, InterruptedIOException, IOException {
|
||||||
|
|
||||||
boolean verbose = (props.getProperty("SILENT", "false").equals("false"));
|
boolean verbose = !Boolean.parseBoolean(props.getProperty("SILENT"));
|
||||||
Destination d = SAMUtils.getDest(dest);
|
Destination d = SAMUtils.getDest(dest);
|
||||||
|
|
||||||
I2PSocketOptions opts = socketMgr.buildOptions(props);
|
I2PSocketOptions opts = socketMgr.buildOptions(props);
|
||||||
if (props.getProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT) == null)
|
if (props.getProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT) == null)
|
||||||
opts.setConnectTimeout(60 * 1000);
|
opts.setConnectTimeout(60 * 1000);
|
||||||
|
String fromPort = props.getProperty("FROM_PORT");
|
||||||
|
if (fromPort != null) {
|
||||||
|
try {
|
||||||
|
opts.setLocalPort(Integer.parseInt(fromPort));
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
throw new I2PException("Bad port " + fromPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String toPort = props.getProperty("TO_PORT");
|
||||||
|
if (toPort != null) {
|
||||||
|
try {
|
||||||
|
opts.setPort(Integer.parseInt(toPort));
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
throw new I2PException("Bad port " + toPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Connecting new I2PSocket...");
|
_log.debug("Connecting new I2PSocket...");
|
||||||
@ -129,6 +159,8 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Accept a single incoming STREAM on the socket stolen from the handler.
|
* Accept a single incoming STREAM on the socket stolen from the handler.
|
||||||
|
* As of version 3.2 (0.9.24), multiple simultaneous accepts are allowed.
|
||||||
|
* Accepts and forwarding may not be done at the same time.
|
||||||
*
|
*
|
||||||
* @param handler The handler that communicates with the requesting client
|
* @param handler The handler that communicates with the requesting client
|
||||||
* @param verbose If true, SAM will send the Base64-encoded peer Destination of an
|
* @param verbose If true, SAM will send the Base64-encoded peer Destination of an
|
||||||
@ -145,30 +177,30 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi
|
|||||||
public void accept(SAMv3Handler handler, boolean verbose)
|
public void accept(SAMv3Handler handler, boolean verbose)
|
||||||
throws I2PException, InterruptedIOException, IOException, SAMException {
|
throws I2PException, InterruptedIOException, IOException, SAMException {
|
||||||
|
|
||||||
synchronized( this.socketServerLock )
|
synchronized(this.socketServerLock) {
|
||||||
{
|
if (this.socketServer != null) {
|
||||||
if (this.socketServer!=null) {
|
if (_log.shouldWarn())
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
_log.warn("a forwarding server is already defined for this destination");
|
||||||
_log.debug("a socket server is already defined for this destination");
|
throw new SAMException("a forwarding server is already defined for this destination");
|
||||||
throw new SAMException("a socket server is already defined for this destination");
|
}
|
||||||
}
|
}
|
||||||
this.socketServer = this.socketMgr.getServerSocket();
|
|
||||||
}
|
I2PSocket i2ps;
|
||||||
|
_acceptors.incrementAndGet();
|
||||||
I2PSocket i2ps = this.socketServer.accept();
|
try {
|
||||||
|
i2ps = socketMgr.getServerSocket().accept();
|
||||||
|
} finally {
|
||||||
|
_acceptors.decrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
synchronized( this.socketServerLock )
|
|
||||||
{
|
|
||||||
this.socketServer = null ;
|
|
||||||
}
|
|
||||||
|
|
||||||
SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
|
SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
|
||||||
|
|
||||||
if ( rec==null || i2ps==null ) throw new InterruptedIOException() ;
|
if ( rec==null || i2ps==null ) throw new InterruptedIOException() ;
|
||||||
|
|
||||||
if (verbose)
|
if (verbose) {
|
||||||
handler.notifyStreamIncomingConnection(i2ps.getPeerDestination()) ;
|
handler.notifyStreamIncomingConnection(i2ps.getPeerDestination(),
|
||||||
|
i2ps.getPort(), i2ps.getLocalPort());
|
||||||
|
}
|
||||||
handler.stealSocket() ;
|
handler.stealSocket() ;
|
||||||
ReadableByteChannel fromClient = handler.getClientSocket();
|
ReadableByteChannel fromClient = handler.getClientSocket();
|
||||||
ReadableByteChannel fromI2P = Channels.newChannel(i2ps.getInputStream());
|
ReadableByteChannel fromI2P = Channels.newChannel(i2ps.getInputStream());
|
||||||
@ -185,10 +217,14 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void startForwardingIncoming( Properties props ) throws SAMException, InterruptedIOException
|
/**
|
||||||
|
* Forward sockets from I2P to the host/port provided.
|
||||||
|
* Accepts and forwarding may not be done at the same time.
|
||||||
|
*/
|
||||||
|
public void startForwardingIncoming(Properties props, boolean sendPorts) throws SAMException, InterruptedIOException
|
||||||
{
|
{
|
||||||
SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
|
SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
|
||||||
boolean verbose = props.getProperty("SILENT", "false").equals("false");
|
boolean verbose = !Boolean.parseBoolean(props.getProperty("SILENT"));
|
||||||
|
|
||||||
if ( rec==null ) throw new InterruptedIOException() ;
|
if ( rec==null ) throw new InterruptedIOException() ;
|
||||||
|
|
||||||
@ -206,34 +242,43 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi
|
|||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("no host specified. Taken from the client socket : " + host +':'+port);
|
_log.debug("no host specified. Taken from the client socket : " + host +':'+port);
|
||||||
}
|
}
|
||||||
|
boolean isSSL = Boolean.parseBoolean(props.getProperty("SSL"));
|
||||||
|
if (_acceptors.get() > 0) {
|
||||||
synchronized( this.socketServerLock )
|
if (_log.shouldWarn())
|
||||||
{
|
_log.warn("an accepting server is already defined for this destination");
|
||||||
if (this.socketServer!=null) {
|
throw new SAMException("an accepting server is already defined for this destination");
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
}
|
||||||
_log.debug("a socket server is already defined for this destination");
|
synchronized(this.socketServerLock) {
|
||||||
throw new SAMException("a socket server is already defined for this destination");
|
if (this.socketServer!=null) {
|
||||||
}
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("a forwarding server is already defined for this destination");
|
||||||
|
throw new SAMException("a forwarding server is already defined for this destination");
|
||||||
|
}
|
||||||
this.socketServer = this.socketMgr.getServerSocket();
|
this.socketServer = this.socketMgr.getServerSocket();
|
||||||
}
|
}
|
||||||
|
|
||||||
SocketForwarder forwarder = new SocketForwarder(host, port, this, verbose);
|
SocketForwarder forwarder = new SocketForwarder(host, port, isSSL, this, verbose, sendPorts);
|
||||||
(new I2PAppThread(rec.getThreadGroup(), forwarder, "SAMV3StreamForwarder")).start();
|
(new I2PAppThread(rec.getThreadGroup(), forwarder, "SAMV3StreamForwarder")).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forward sockets from I2P to the host/port provided
|
||||||
|
*/
|
||||||
private static class SocketForwarder implements Runnable
|
private static class SocketForwarder implements Runnable
|
||||||
{
|
{
|
||||||
private final String host;
|
private final String host;
|
||||||
private final int port;
|
private final int port;
|
||||||
private final SAMv3StreamSession session;
|
private final SAMv3StreamSession session;
|
||||||
private final boolean verbose;
|
private final boolean isSSL, verbose, sendPorts;
|
||||||
|
|
||||||
SocketForwarder(String host, int port, SAMv3StreamSession session, boolean verbose) {
|
SocketForwarder(String host, int port, boolean isSSL,
|
||||||
|
SAMv3StreamSession session, boolean verbose, boolean sendPorts) {
|
||||||
this.host = host ;
|
this.host = host ;
|
||||||
this.port = port ;
|
this.port = port ;
|
||||||
this.session = session ;
|
this.session = session ;
|
||||||
this.verbose = verbose ;
|
this.verbose = verbose ;
|
||||||
|
this.sendPorts = sendPorts;
|
||||||
|
this.isSSL = isSSL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run()
|
public void run()
|
||||||
@ -241,32 +286,77 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi
|
|||||||
while (session.getSocketServer()!=null) {
|
while (session.getSocketServer()!=null) {
|
||||||
|
|
||||||
// wait and accept a connection from I2P side
|
// wait and accept a connection from I2P side
|
||||||
I2PSocket i2ps = null ;
|
I2PSocket i2ps;
|
||||||
try {
|
try {
|
||||||
i2ps = session.getSocketServer().accept();
|
i2ps = session.getSocketServer().accept();
|
||||||
} catch (Exception e) {}
|
if (i2ps == null)
|
||||||
|
continue;
|
||||||
if (i2ps==null) {
|
} catch (SocketTimeoutException ste) {
|
||||||
continue ;
|
continue;
|
||||||
}
|
} catch (ConnectException ce) {
|
||||||
|
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3StreamSession.class);
|
||||||
|
if (log.shouldLog(Log.WARN))
|
||||||
|
log.warn("Error accepting", ce);
|
||||||
|
try { Thread.sleep(50); } catch (InterruptedException ie) {}
|
||||||
|
continue;
|
||||||
|
} catch (I2PException ipe) {
|
||||||
|
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3StreamSession.class);
|
||||||
|
if (log.shouldLog(Log.WARN))
|
||||||
|
log.warn("Error accepting", ipe);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// open a socket towards client
|
// open a socket towards client
|
||||||
java.net.InetSocketAddress addr = new java.net.InetSocketAddress(host,port);
|
|
||||||
|
|
||||||
SocketChannel clientServerSock = null ;
|
SocketChannel clientServerSock;
|
||||||
try {
|
try {
|
||||||
clientServerSock = SocketChannel.open(addr) ;
|
if (isSSL) {
|
||||||
}
|
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||||
catch ( IOException e ) {
|
synchronized(SAMv3StreamSession.class) {
|
||||||
continue ;
|
if (_sslSocketFactory == null) {
|
||||||
|
try {
|
||||||
|
_sslSocketFactory = new I2PSSLSocketFactory(
|
||||||
|
ctx, true, "certificates/sam");
|
||||||
|
} catch (GeneralSecurityException gse) {
|
||||||
|
Log log = ctx.logManager().getLog(SAMv3StreamSession.class);
|
||||||
|
log.error("SSL error", gse);
|
||||||
|
try {
|
||||||
|
i2ps.close();
|
||||||
|
} catch (IOException ee) {}
|
||||||
|
throw new RuntimeException("SSL error", gse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SSLSocket sock = (SSLSocket) _sslSocketFactory.createSocket(host, port);
|
||||||
|
I2PSSLSocketFactory.verifyHostname(ctx, sock, host);
|
||||||
|
clientServerSock = new SSLSocketChannel(sock);
|
||||||
|
} else {
|
||||||
|
InetSocketAddress addr = new InetSocketAddress(host, port);
|
||||||
|
clientServerSock = SocketChannel.open(addr) ;
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3StreamSession.class);
|
||||||
|
if (log.shouldLog(Log.WARN))
|
||||||
|
log.warn("Error forwarding", ioe);
|
||||||
|
try {
|
||||||
|
i2ps.close();
|
||||||
|
} catch (IOException ee) {}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// build pipes between both sockets
|
// build pipes between both sockets
|
||||||
try {
|
try {
|
||||||
clientServerSock.socket().setKeepAlive(true);
|
clientServerSock.socket().setKeepAlive(true);
|
||||||
if (this.verbose)
|
if (this.verbose) {
|
||||||
SAMv3Handler.notifyStreamIncomingConnection(
|
if (sendPorts) {
|
||||||
|
SAMv3Handler.notifyStreamIncomingConnection(
|
||||||
|
clientServerSock, i2ps.getPeerDestination(),
|
||||||
|
i2ps.getPort(), i2ps.getLocalPort());
|
||||||
|
} else {
|
||||||
|
SAMv3Handler.notifyStreamIncomingConnection(
|
||||||
clientServerSock, i2ps.getPeerDestination());
|
clientServerSock, i2ps.getPeerDestination());
|
||||||
|
}
|
||||||
|
}
|
||||||
ReadableByteChannel fromClient = clientServerSock ;
|
ReadableByteChannel fromClient = clientServerSock ;
|
||||||
ReadableByteChannel fromI2P = Channels.newChannel(i2ps.getInputStream());
|
ReadableByteChannel fromI2P = Channels.newChannel(i2ps.getInputStream());
|
||||||
WritableByteChannel toClient = clientServerSock ;
|
WritableByteChannel toClient = clientServerSock ;
|
||||||
@ -347,7 +437,7 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public I2PServerSocket getSocketServer()
|
private I2PServerSocket getSocketServer()
|
||||||
{
|
{
|
||||||
synchronized ( this.socketServerLock ) {
|
synchronized ( this.socketServerLock ) {
|
||||||
return this.socketServer ;
|
return this.socketServer ;
|
||||||
@ -390,7 +480,11 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi
|
|||||||
socketMgr.destroySocketManager();
|
socketMgr.destroySocketManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean sendBytes(String s, byte[] b) throws DataFormatException
|
/**
|
||||||
|
* Unsupported
|
||||||
|
* @throws DataFormatException always
|
||||||
|
*/
|
||||||
|
public boolean sendBytes(String s, byte[] b, int pr, int fp, int tp) throws DataFormatException
|
||||||
{
|
{
|
||||||
throw new DataFormatException(null);
|
throw new DataFormatException(null);
|
||||||
}
|
}
|
||||||
|
77
apps/sam/java/src/net/i2p/sam/SSLServerSocketChannel.java
Normal file
77
apps/sam/java/src/net/i2p/sam/SSLServerSocketChannel.java
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package net.i2p.sam;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
/* requires Java 7 */
|
||||||
|
import java.net.SocketOption;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.nio.channels.ServerSocketChannel;
|
||||||
|
import java.nio.channels.spi.SelectorProvider;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLServerSocket;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple wrapper for a SSLServerSocket.
|
||||||
|
* Cannot be used for asynch ops.
|
||||||
|
*
|
||||||
|
* @since 0.9.24
|
||||||
|
*/
|
||||||
|
class SSLServerSocketChannel extends ServerSocketChannel {
|
||||||
|
|
||||||
|
private final SSLServerSocket _socket;
|
||||||
|
|
||||||
|
public SSLServerSocketChannel(SSLServerSocket socket) {
|
||||||
|
super(SelectorProvider.provider());
|
||||||
|
_socket = socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
//// ServerSocketChannel abstract methods
|
||||||
|
|
||||||
|
public SocketChannel accept() throws IOException {
|
||||||
|
return new SSLSocketChannel((SSLSocket)_socket.accept());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerSocket socket() {
|
||||||
|
return _socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** requires Java 7 */
|
||||||
|
public ServerSocketChannel bind(SocketAddress local, int backlog) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** requires Java 7 */
|
||||||
|
public <T> ServerSocketChannel setOption(SocketOption<T> name, T value) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
//// AbstractSelectableChannel abstract methods
|
||||||
|
|
||||||
|
public void implCloseSelectableChannel() throws IOException {
|
||||||
|
_socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void implConfigureBlocking(boolean block) throws IOException {
|
||||||
|
if (!block)
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
//// NetworkChannel interface methods
|
||||||
|
|
||||||
|
public SocketAddress getLocalAddress() {
|
||||||
|
return _socket.getLocalSocketAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getOption(SocketOption<T> name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<SocketOption<?>> supportedOptions() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
}
|
140
apps/sam/java/src/net/i2p/sam/SSLSocketChannel.java
Normal file
140
apps/sam/java/src/net/i2p/sam/SSLSocketChannel.java
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package net.i2p.sam;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
/* requires Java 7 */
|
||||||
|
import java.net.SocketOption;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.nio.channels.spi.SelectorProvider;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple wrapper for a SSLSocket.
|
||||||
|
* Cannot be used for asynch ops.
|
||||||
|
*
|
||||||
|
* @since 0.9.24
|
||||||
|
*/
|
||||||
|
class SSLSocketChannel extends SocketChannel {
|
||||||
|
|
||||||
|
private final SSLSocket _socket;
|
||||||
|
|
||||||
|
public SSLSocketChannel(SSLSocket socket) {
|
||||||
|
super(SelectorProvider.provider());
|
||||||
|
_socket = socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
//// SocketChannel abstract methods
|
||||||
|
|
||||||
|
public Socket socket() {
|
||||||
|
return _socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean connect(SocketAddress remote) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean finishConnect() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return _socket.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnectionPending() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** new in Java 7 */
|
||||||
|
public SocketAddress getRemoteAddress() {
|
||||||
|
return _socket.getRemoteSocketAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** new in Java 7 */
|
||||||
|
public SocketChannel shutdownInput() throws IOException {
|
||||||
|
_socket.getInputStream().close();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** new in Java 7 */
|
||||||
|
public SocketChannel shutdownOutput() throws IOException {
|
||||||
|
_socket.getOutputStream().close();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** requires Java 7 */
|
||||||
|
public <T> SocketChannel setOption(SocketOption<T> name, T value) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** requires Java 7 */
|
||||||
|
public SocketChannel bind(SocketAddress local) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
//// SocketChannel abstract methods
|
||||||
|
|
||||||
|
public int read(ByteBuffer src) throws IOException {
|
||||||
|
if (!src.hasArray())
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
int pos = src.position();
|
||||||
|
int len = src.remaining();
|
||||||
|
int read = _socket.getInputStream().read(src.array(), src.arrayOffset() + pos, len);
|
||||||
|
if (read > 0)
|
||||||
|
src.position(pos + read);
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long read(ByteBuffer[] srcs, int offset, int length) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int write(ByteBuffer src) throws IOException {
|
||||||
|
if (!src.hasArray())
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
int pos = src.position();
|
||||||
|
int len = src.remaining();
|
||||||
|
_socket.getOutputStream().write(src.array(), src.arrayOffset() + pos, len);
|
||||||
|
src.position(pos + len);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long write(ByteBuffer[] srcs, int offset, int length) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
//// AbstractSelectableChannel abstract methods
|
||||||
|
|
||||||
|
public void implCloseSelectableChannel() throws IOException {
|
||||||
|
_socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void implConfigureBlocking(boolean block) throws IOException {
|
||||||
|
if (!block)
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//// NetworkChannel interface methods
|
||||||
|
|
||||||
|
public SocketAddress getLocalAddress() {
|
||||||
|
return _socket.getLocalSocketAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getOption(SocketOption<T> name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<SocketOption<?>> supportedOptions() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
188
apps/sam/java/src/net/i2p/sam/SSLUtil.java
Normal file
188
apps/sam/java/src/net/i2p/sam/SSLUtil.java
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
package net.i2p.sam;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.SSLServerSocketFactory;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.crypto.KeyStoreUtil;
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
import net.i2p.util.SecureDirectory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for SAM SSL server sockets.
|
||||||
|
*
|
||||||
|
* @since 0.9.24 adopted from net.i2p.i2ptunnel.SSLClientUtil
|
||||||
|
*/
|
||||||
|
class SSLUtil {
|
||||||
|
|
||||||
|
private static final String PROP_KEYSTORE_PASSWORD = "sam.keystorePassword";
|
||||||
|
private static final String DEFAULT_KEYSTORE_PASSWORD = "changeit";
|
||||||
|
private static final String PROP_KEY_PASSWORD = "sam.keyPassword";
|
||||||
|
private static final String PROP_KEY_ALIAS = "sam.keyAlias";
|
||||||
|
private static final String ASCII_KEYFILE_SUFFIX = ".local.crt";
|
||||||
|
private static final String PROP_KS_NAME = "sam.keystoreFile";
|
||||||
|
private static final String KS_DIR = "keystore";
|
||||||
|
private static final String PREFIX = "sam-";
|
||||||
|
private static final String KS_SUFFIX = ".ks";
|
||||||
|
private static final String CERT_DIR = "certificates/sam";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new selfsigned cert and keystore and pubkey cert if they don't exist.
|
||||||
|
* May take a while.
|
||||||
|
*
|
||||||
|
* @param opts in/out, updated if rv is true
|
||||||
|
* @return false if it already exists; if true, caller must save opts
|
||||||
|
* @throws IOException on creation fail
|
||||||
|
*/
|
||||||
|
public static boolean verifyKeyStore(Properties opts) throws IOException {
|
||||||
|
String name = opts.getProperty(PROP_KEY_ALIAS);
|
||||||
|
if (name == null) {
|
||||||
|
name = KeyStoreUtil.randomString();
|
||||||
|
opts.setProperty(PROP_KEY_ALIAS, name);
|
||||||
|
}
|
||||||
|
String ksname = opts.getProperty(PROP_KS_NAME);
|
||||||
|
if (ksname == null) {
|
||||||
|
ksname = PREFIX + name + KS_SUFFIX;
|
||||||
|
opts.setProperty(PROP_KS_NAME, ksname);
|
||||||
|
}
|
||||||
|
File ks = new File(ksname);
|
||||||
|
if (!ks.isAbsolute()) {
|
||||||
|
ks = new File(I2PAppContext.getGlobalContext().getConfigDir(), KS_DIR);
|
||||||
|
ks = new File(ks, ksname);
|
||||||
|
}
|
||||||
|
if (ks.exists())
|
||||||
|
return false;
|
||||||
|
File dir = ks.getParentFile();
|
||||||
|
if (!dir.exists()) {
|
||||||
|
File sdir = new SecureDirectory(dir.getAbsolutePath());
|
||||||
|
if (!sdir.mkdirs())
|
||||||
|
throw new IOException("Unable to create keystore " + ks);
|
||||||
|
}
|
||||||
|
boolean rv = createKeyStore(ks, name, opts);
|
||||||
|
if (!rv)
|
||||||
|
throw new IOException("Unable to create keystore " + ks);
|
||||||
|
|
||||||
|
// Now read it back out of the new keystore and save it in ascii form
|
||||||
|
// where the clients can get to it.
|
||||||
|
// Failure of this part is not fatal.
|
||||||
|
exportCert(ks, name, opts);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call out to keytool to create a new keystore with a keypair in it.
|
||||||
|
*
|
||||||
|
* @param name used in CNAME
|
||||||
|
* @param opts in/out, updated if rv is true, must contain PROP_KEY_ALIAS
|
||||||
|
* @return success, if true, opts will have password properties added to be saved
|
||||||
|
*/
|
||||||
|
private static boolean createKeyStore(File ks, String name, Properties opts) {
|
||||||
|
// make a random 48 character password (30 * 8 / 5)
|
||||||
|
String keyPassword = KeyStoreUtil.randomString();
|
||||||
|
// and one for the cname
|
||||||
|
String cname = name + ".sam.i2p.net";
|
||||||
|
|
||||||
|
String keyName = opts.getProperty(PROP_KEY_ALIAS);
|
||||||
|
boolean success = KeyStoreUtil.createKeys(ks, keyName, cname, "SAM", keyPassword);
|
||||||
|
if (success) {
|
||||||
|
success = ks.exists();
|
||||||
|
if (success) {
|
||||||
|
opts.setProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
||||||
|
opts.setProperty(PROP_KEY_PASSWORD, keyPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
logAlways("Created self-signed certificate for " + cname + " in keystore: " + ks.getAbsolutePath() + "\n" +
|
||||||
|
"The certificate name was generated randomly, and is not associated with your " +
|
||||||
|
"IP address, host name, router identity, or destination keys.");
|
||||||
|
} else {
|
||||||
|
error("Failed to create SAM SSL keystore.\n" +
|
||||||
|
"If you create the keystore manually, you must add " + PROP_KEYSTORE_PASSWORD + " and " + PROP_KEY_PASSWORD +
|
||||||
|
" to " + (new File(I2PAppContext.getGlobalContext().getConfigDir(), SAMBridge.DEFAULT_SAM_CONFIGFILE)).getAbsolutePath());
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pull the cert back OUT of the keystore and save it as ascii
|
||||||
|
* so the clients can get to it.
|
||||||
|
*
|
||||||
|
* @param name used to generate output file name
|
||||||
|
* @param opts must contain PROP_KEY_ALIAS
|
||||||
|
*/
|
||||||
|
private static void exportCert(File ks, String name, Properties opts) {
|
||||||
|
File sdir = new SecureDirectory(I2PAppContext.getGlobalContext().getConfigDir(), CERT_DIR);
|
||||||
|
if (sdir.exists() || sdir.mkdirs()) {
|
||||||
|
String keyAlias = opts.getProperty(PROP_KEY_ALIAS);
|
||||||
|
String ksPass = opts.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
||||||
|
File out = new File(sdir, PREFIX + name + ASCII_KEYFILE_SUFFIX);
|
||||||
|
boolean success = KeyStoreUtil.exportCert(ks, ksPass, keyAlias, out);
|
||||||
|
if (!success)
|
||||||
|
error("Error getting SSL cert to save as ASCII");
|
||||||
|
} else {
|
||||||
|
error("Error saving ASCII SSL keys");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the SSLContext and sets the socket factory.
|
||||||
|
* No option prefix allowed.
|
||||||
|
*
|
||||||
|
* @throws IOException; GeneralSecurityExceptions are wrapped in IOE for convenience
|
||||||
|
* @return factory, throws on all errors
|
||||||
|
*/
|
||||||
|
public static SSLServerSocketFactory initializeFactory(Properties opts) throws IOException {
|
||||||
|
String ksPass = opts.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
||||||
|
String keyPass = opts.getProperty(PROP_KEY_PASSWORD);
|
||||||
|
if (keyPass == null) {
|
||||||
|
throw new IOException("No key password, set " + PROP_KEY_PASSWORD + " in " +
|
||||||
|
(new File(I2PAppContext.getGlobalContext().getConfigDir(), SAMBridge.DEFAULT_SAM_CONFIGFILE)).getAbsolutePath());
|
||||||
|
}
|
||||||
|
String ksname = opts.getProperty(PROP_KS_NAME);
|
||||||
|
if (ksname == null) {
|
||||||
|
throw new IOException("No keystore, set " + PROP_KS_NAME + " in " +
|
||||||
|
(new File(I2PAppContext.getGlobalContext().getConfigDir(), SAMBridge.DEFAULT_SAM_CONFIGFILE)).getAbsolutePath());
|
||||||
|
}
|
||||||
|
File ks = new File(ksname);
|
||||||
|
if (!ks.isAbsolute()) {
|
||||||
|
ks = new File(I2PAppContext.getGlobalContext().getConfigDir(), KS_DIR);
|
||||||
|
ks = new File(ks, ksname);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream fis = null;
|
||||||
|
try {
|
||||||
|
SSLContext sslc = SSLContext.getInstance("TLS");
|
||||||
|
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
fis = new FileInputStream(ks);
|
||||||
|
keyStore.load(fis, ksPass.toCharArray());
|
||||||
|
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||||
|
kmf.init(keyStore, keyPass.toCharArray());
|
||||||
|
sslc.init(kmf.getKeyManagers(), null, I2PAppContext.getGlobalContext().random());
|
||||||
|
return sslc.getServerSocketFactory();
|
||||||
|
} catch (GeneralSecurityException gse) {
|
||||||
|
IOException ioe = new IOException("keystore error");
|
||||||
|
ioe.initCause(gse);
|
||||||
|
throw ioe;
|
||||||
|
} finally {
|
||||||
|
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void error(String s) {
|
||||||
|
I2PAppContext.getGlobalContext().logManager().getLog(SSLUtil.class).error(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void logAlways(String s) {
|
||||||
|
I2PAppContext.getGlobalContext().logManager().getLog(SSLUtil.class).logAlways(Log.INFO, s);
|
||||||
|
}
|
||||||
|
}
|
@ -7,12 +7,16 @@ import java.util.Properties;
|
|||||||
*/
|
*/
|
||||||
public class SAMClientEventListenerImpl implements SAMReader.SAMClientEventListener {
|
public class SAMClientEventListenerImpl implements SAMReader.SAMClientEventListener {
|
||||||
public void destReplyReceived(String publicKey, String privateKey) {}
|
public void destReplyReceived(String publicKey, String privateKey) {}
|
||||||
public void helloReplyReceived(boolean ok) {}
|
public void helloReplyReceived(boolean ok, String version) {}
|
||||||
public void namingReplyReceived(String name, String result, String value, String message) {}
|
public void namingReplyReceived(String name, String result, String value, String message) {}
|
||||||
public void sessionStatusReceived(String result, String destination, String message) {}
|
public void sessionStatusReceived(String result, String destination, String message) {}
|
||||||
public void streamClosedReceived(String result, int id, String message) {}
|
public void streamClosedReceived(String result, String id, String message) {}
|
||||||
public void streamConnectedReceived(String remoteDestination, int id) {}
|
public void streamConnectedReceived(String remoteDestination, String id) {}
|
||||||
public void streamDataReceived(int id, byte[] data, int offset, int length) {}
|
public void streamDataReceived(String id, byte[] data, int offset, int length) {}
|
||||||
public void streamStatusReceived(String result, int id, String message) {}
|
public void streamStatusReceived(String result, String id, String message) {}
|
||||||
|
public void datagramReceived(String dest, byte[] data, int offset, int length, int fromPort, int toPort) {}
|
||||||
|
public void rawReceived(byte[] data, int offset, int length, int fromPort, int toPort, int protocol) {}
|
||||||
|
public void pingReceived(String data) {}
|
||||||
|
public void pongReceived(String data) {}
|
||||||
public void unknownMessageReceived(String major, String minor, Properties params) {}
|
public void unknownMessageReceived(String major, String minor, Properties params) {}
|
||||||
}
|
}
|
||||||
|
@ -13,31 +13,35 @@ import net.i2p.util.Log;
|
|||||||
*/
|
*/
|
||||||
public class SAMEventHandler extends SAMClientEventListenerImpl {
|
public class SAMEventHandler extends SAMClientEventListenerImpl {
|
||||||
//private I2PAppContext _context;
|
//private I2PAppContext _context;
|
||||||
private Log _log;
|
private final Log _log;
|
||||||
private Boolean _helloOk;
|
private Boolean _helloOk;
|
||||||
private Object _helloLock = new Object();
|
private String _version;
|
||||||
|
private final Object _helloLock = new Object();
|
||||||
private Boolean _sessionCreateOk;
|
private Boolean _sessionCreateOk;
|
||||||
private Object _sessionCreateLock = new Object();
|
private Boolean _streamStatusOk;
|
||||||
private Object _namingReplyLock = new Object();
|
private final Object _sessionCreateLock = new Object();
|
||||||
private Map<String,String> _namingReplies = new HashMap<String,String>();
|
private final Object _namingReplyLock = new Object();
|
||||||
|
private final Object _streamStatusLock = new Object();
|
||||||
|
private final Map<String,String> _namingReplies = new HashMap<String,String>();
|
||||||
|
|
||||||
public SAMEventHandler(I2PAppContext ctx) {
|
public SAMEventHandler(I2PAppContext ctx) {
|
||||||
//_context = ctx;
|
//_context = ctx;
|
||||||
_log = ctx.logManager().getLog(getClass());
|
_log = ctx.logManager().getLog(getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void helloReplyReceived(boolean ok) {
|
public void helloReplyReceived(boolean ok, String version) {
|
||||||
synchronized (_helloLock) {
|
synchronized (_helloLock) {
|
||||||
if (ok)
|
if (ok)
|
||||||
_helloOk = Boolean.TRUE;
|
_helloOk = Boolean.TRUE;
|
||||||
else
|
else
|
||||||
_helloOk = Boolean.FALSE;
|
_helloOk = Boolean.FALSE;
|
||||||
|
_version = version;
|
||||||
_helloLock.notifyAll();
|
_helloLock.notifyAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sessionStatusReceived(String result, String destination, String msg) {
|
public void sessionStatusReceived(String result, String destination, String msg) {
|
||||||
synchronized (_sessionCreateLock) {
|
synchronized (_sessionCreateLock) {
|
||||||
if (SAMReader.SAMClientEventListener.SESSION_STATUS_OK.equals(result))
|
if (SAMReader.SAMClientEventListener.SESSION_STATUS_OK.equals(result))
|
||||||
@ -48,7 +52,7 @@ public class SAMEventHandler extends SAMClientEventListenerImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void namingReplyReceived(String name, String result, String value, String msg) {
|
public void namingReplyReceived(String name, String result, String value, String msg) {
|
||||||
synchronized (_namingReplyLock) {
|
synchronized (_namingReplyLock) {
|
||||||
if (SAMReader.SAMClientEventListener.NAMING_REPLY_OK.equals(result))
|
if (SAMReader.SAMClientEventListener.NAMING_REPLY_OK.equals(result))
|
||||||
@ -59,9 +63,20 @@ public class SAMEventHandler extends SAMClientEventListenerImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
public void streamStatusReceived(String result, String id, String message) {
|
||||||
|
synchronized (_streamStatusLock) {
|
||||||
|
if (SAMReader.SAMClientEventListener.SESSION_STATUS_OK.equals(result))
|
||||||
|
_streamStatusOk = Boolean.TRUE;
|
||||||
|
else
|
||||||
|
_streamStatusOk = Boolean.FALSE;
|
||||||
|
_streamStatusLock.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void unknownMessageReceived(String major, String minor, Properties params) {
|
public void unknownMessageReceived(String major, String minor, Properties params) {
|
||||||
_log.error("wrt, [" + major + "] [" + minor + "] [" + params + "]");
|
_log.error("Unhandled message: [" + major + "] [" + minor + "] [" + params + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -70,20 +85,20 @@ public class SAMEventHandler extends SAMClientEventListenerImpl {
|
|||||||
//
|
//
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait for the connection to be established, returning true if everything
|
* Wait for the connection to be established, returning the server version if everything
|
||||||
* went ok
|
* went ok
|
||||||
* @return true if everything ok
|
* @return SAM server version if everything ok, or null on failure
|
||||||
*/
|
*/
|
||||||
public boolean waitForHelloReply() {
|
public String waitForHelloReply() {
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
synchronized (_helloLock) {
|
synchronized (_helloLock) {
|
||||||
if (_helloOk == null)
|
if (_helloOk == null)
|
||||||
_helloLock.wait();
|
_helloLock.wait();
|
||||||
else
|
else
|
||||||
return _helloOk.booleanValue();
|
return _helloOk.booleanValue() ? _version : null;
|
||||||
}
|
}
|
||||||
} catch (InterruptedException ie) {}
|
} catch (InterruptedException ie) { return null; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +116,25 @@ public class SAMEventHandler extends SAMClientEventListenerImpl {
|
|||||||
else
|
else
|
||||||
return _sessionCreateOk.booleanValue();
|
return _sessionCreateOk.booleanValue();
|
||||||
}
|
}
|
||||||
} catch (InterruptedException ie) {}
|
} catch (InterruptedException ie) { return false; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for the stream to be created, returning true if everything went ok
|
||||||
|
*
|
||||||
|
* @return true if everything ok
|
||||||
|
*/
|
||||||
|
public boolean waitForStreamStatusReply() {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
synchronized (_streamStatusLock) {
|
||||||
|
if (_streamStatusOk == null)
|
||||||
|
_streamStatusLock.wait();
|
||||||
|
else
|
||||||
|
return _streamStatusOk.booleanValue();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ie) { return false; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +161,7 @@ public class SAMEventHandler extends SAMClientEventListenerImpl {
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (InterruptedException ie) {}
|
} catch (InterruptedException ie) { return null; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,11 @@ import net.i2p.util.Log;
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class SAMReader {
|
public class SAMReader {
|
||||||
private Log _log;
|
private final Log _log;
|
||||||
private InputStream _inRaw;
|
private final InputStream _inRaw;
|
||||||
private SAMClientEventListener _listener;
|
private final SAMClientEventListener _listener;
|
||||||
private boolean _live;
|
private volatile boolean _live;
|
||||||
|
private Thread _thread;
|
||||||
|
|
||||||
public SAMReader(I2PAppContext context, InputStream samIn, SAMClientEventListener listener) {
|
public SAMReader(I2PAppContext context, InputStream samIn, SAMClientEventListener listener) {
|
||||||
_log = context.logManager().getLog(SAMReader.class);
|
_log = context.logManager().getLog(SAMReader.class);
|
||||||
@ -27,12 +28,23 @@ public class SAMReader {
|
|||||||
_listener = listener;
|
_listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startReading() {
|
public synchronized void startReading() {
|
||||||
|
if (_live)
|
||||||
|
throw new IllegalStateException();
|
||||||
_live = true;
|
_live = true;
|
||||||
I2PAppThread t = new I2PAppThread(new Runner(), "SAM reader");
|
I2PAppThread t = new I2PAppThread(new Runner(), "SAM reader");
|
||||||
t.start();
|
t.start();
|
||||||
|
_thread = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void stopReading() {
|
||||||
|
_live = false;
|
||||||
|
if (_thread != null) {
|
||||||
|
_thread.interrupt();
|
||||||
|
_thread = null;
|
||||||
|
try { _inRaw.close(); } catch (IOException ioe) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public void stopReading() { _live = false; }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Async event notification interface for SAM clients
|
* Async event notification interface for SAM clients
|
||||||
@ -60,14 +72,18 @@ public class SAMReader {
|
|||||||
public static final String NAMING_REPLY_INVALID_KEY = "INVALID_KEY";
|
public static final String NAMING_REPLY_INVALID_KEY = "INVALID_KEY";
|
||||||
public static final String NAMING_REPLY_KEY_NOT_FOUND = "KEY_NOT_FOUND";
|
public static final String NAMING_REPLY_KEY_NOT_FOUND = "KEY_NOT_FOUND";
|
||||||
|
|
||||||
public void helloReplyReceived(boolean ok);
|
public void helloReplyReceived(boolean ok, String version);
|
||||||
public void sessionStatusReceived(String result, String destination, String message);
|
public void sessionStatusReceived(String result, String destination, String message);
|
||||||
public void streamStatusReceived(String result, int id, String message);
|
public void streamStatusReceived(String result, String id, String message);
|
||||||
public void streamConnectedReceived(String remoteDestination, int id);
|
public void streamConnectedReceived(String remoteDestination, String id);
|
||||||
public void streamClosedReceived(String result, int id, String message);
|
public void streamClosedReceived(String result, String id, String message);
|
||||||
public void streamDataReceived(int id, byte data[], int offset, int length);
|
public void streamDataReceived(String id, byte data[], int offset, int length);
|
||||||
public void namingReplyReceived(String name, String result, String value, String message);
|
public void namingReplyReceived(String name, String result, String value, String message);
|
||||||
public void destReplyReceived(String publicKey, String privateKey);
|
public void destReplyReceived(String publicKey, String privateKey);
|
||||||
|
public void datagramReceived(String dest, byte[] data, int offset, int length, int fromPort, int toPort);
|
||||||
|
public void rawReceived(byte[] data, int offset, int length, int fromPort, int toPort, int protocol);
|
||||||
|
public void pingReceived(String data);
|
||||||
|
public void pongReceived(String data);
|
||||||
|
|
||||||
public void unknownMessageReceived(String major, String minor, Properties params);
|
public void unknownMessageReceived(String major, String minor, Properties params);
|
||||||
}
|
}
|
||||||
@ -87,33 +103,29 @@ public class SAMReader {
|
|||||||
baos.write(c);
|
baos.write(c);
|
||||||
}
|
}
|
||||||
if (c == -1) {
|
if (c == -1) {
|
||||||
_log.error("Error reading from the SAM bridge");
|
_log.info("EOF reading from the SAM bridge");
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
_log.error("Error reading from SAM", ioe);
|
_log.error("Error reading from SAM", ioe);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
String line = DataHelper.getUTF8(baos.toByteArray());
|
String line = DataHelper.getUTF8(baos.toByteArray());
|
||||||
baos.reset();
|
baos.reset();
|
||||||
|
|
||||||
if (line == null) {
|
if (_log.shouldDebug())
|
||||||
_log.info("No more data from the SAM bridge");
|
_log.debug("Line read from the bridge: " + line);
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
_log.debug("Line read from the bridge: " + line);
|
|
||||||
|
|
||||||
StringTokenizer tok = new StringTokenizer(line);
|
StringTokenizer tok = new StringTokenizer(line);
|
||||||
|
|
||||||
if (tok.countTokens() < 2) {
|
if (tok.countTokens() <= 0) {
|
||||||
_log.error("Invalid SAM line: [" + line + "]");
|
_log.error("Invalid SAM line: [" + line + "]");
|
||||||
_live = false;
|
break;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String major = tok.nextToken();
|
String major = tok.nextToken();
|
||||||
String minor = tok.nextToken();
|
String minor = tok.hasMoreTokens() ? tok.nextToken() : "";
|
||||||
|
|
||||||
params.clear();
|
params.clear();
|
||||||
while (tok.hasMoreTokens()) {
|
while (tok.hasMoreTokens()) {
|
||||||
@ -132,6 +144,9 @@ public class SAMReader {
|
|||||||
|
|
||||||
processEvent(major, minor, params);
|
processEvent(major, minor, params);
|
||||||
}
|
}
|
||||||
|
_live = false;
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("SAMReader exiting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,10 +159,11 @@ public class SAMReader {
|
|||||||
if ("HELLO".equals(major)) {
|
if ("HELLO".equals(major)) {
|
||||||
if ("REPLY".equals(minor)) {
|
if ("REPLY".equals(minor)) {
|
||||||
String result = params.getProperty("RESULT");
|
String result = params.getProperty("RESULT");
|
||||||
if ("OK".equals(result))
|
String version= params.getProperty("VERSION");
|
||||||
_listener.helloReplyReceived(true);
|
if ("OK".equals(result) && version != null)
|
||||||
|
_listener.helloReplyReceived(true, version);
|
||||||
else
|
else
|
||||||
_listener.helloReplyReceived(false);
|
_listener.helloReplyReceived(false, version);
|
||||||
} else {
|
} else {
|
||||||
_listener.unknownMessageReceived(major, minor, params);
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
}
|
}
|
||||||
@ -165,24 +181,17 @@ public class SAMReader {
|
|||||||
String result = params.getProperty("RESULT");
|
String result = params.getProperty("RESULT");
|
||||||
String id = params.getProperty("ID");
|
String id = params.getProperty("ID");
|
||||||
String msg = params.getProperty("MESSAGE");
|
String msg = params.getProperty("MESSAGE");
|
||||||
if (id != null) {
|
// id is null in v3, so pass it through regardless
|
||||||
try {
|
//if (id != null) {
|
||||||
_listener.streamStatusReceived(result, Integer.parseInt(id), msg);
|
_listener.streamStatusReceived(result, id, msg);
|
||||||
} catch (NumberFormatException nfe) {
|
//} else {
|
||||||
_listener.unknownMessageReceived(major, minor, params);
|
// _listener.unknownMessageReceived(major, minor, params);
|
||||||
}
|
//}
|
||||||
} else {
|
|
||||||
_listener.unknownMessageReceived(major, minor, params);
|
|
||||||
}
|
|
||||||
} else if ("CONNECTED".equals(minor)) {
|
} else if ("CONNECTED".equals(minor)) {
|
||||||
String dest = params.getProperty("DESTINATION");
|
String dest = params.getProperty("DESTINATION");
|
||||||
String id = params.getProperty("ID");
|
String id = params.getProperty("ID");
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
try {
|
_listener.streamConnectedReceived(dest, id);
|
||||||
_listener.streamConnectedReceived(dest, Integer.parseInt(id));
|
|
||||||
} catch (NumberFormatException nfe) {
|
|
||||||
_listener.unknownMessageReceived(major, minor, params);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
_listener.unknownMessageReceived(major, minor, params);
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
}
|
}
|
||||||
@ -191,11 +200,7 @@ public class SAMReader {
|
|||||||
String id = params.getProperty("ID");
|
String id = params.getProperty("ID");
|
||||||
String msg = params.getProperty("MESSAGE");
|
String msg = params.getProperty("MESSAGE");
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
try {
|
_listener.streamClosedReceived(result, id, msg);
|
||||||
_listener.streamClosedReceived(result, Integer.parseInt(id), msg);
|
|
||||||
} catch (NumberFormatException nfe) {
|
|
||||||
_listener.unknownMessageReceived(major, minor, params);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
_listener.unknownMessageReceived(major, minor, params);
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
}
|
}
|
||||||
@ -204,7 +209,6 @@ public class SAMReader {
|
|||||||
String size = params.getProperty("SIZE");
|
String size = params.getProperty("SIZE");
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
try {
|
try {
|
||||||
int idVal = Integer.parseInt(id);
|
|
||||||
int sizeVal = Integer.parseInt(size);
|
int sizeVal = Integer.parseInt(size);
|
||||||
|
|
||||||
byte data[] = new byte[sizeVal];
|
byte data[] = new byte[sizeVal];
|
||||||
@ -212,7 +216,7 @@ public class SAMReader {
|
|||||||
if (read != sizeVal) {
|
if (read != sizeVal) {
|
||||||
_listener.unknownMessageReceived(major, minor, params);
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
} else {
|
} else {
|
||||||
_listener.streamDataReceived(idVal, data, 0, sizeVal);
|
_listener.streamDataReceived(id, data, 0, sizeVal);
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException nfe) {
|
} catch (NumberFormatException nfe) {
|
||||||
_listener.unknownMessageReceived(major, minor, params);
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
@ -226,6 +230,73 @@ public class SAMReader {
|
|||||||
} else {
|
} else {
|
||||||
_listener.unknownMessageReceived(major, minor, params);
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
}
|
}
|
||||||
|
} else if ("DATAGRAM".equals(major)) {
|
||||||
|
if ("RECEIVED".equals(minor)) {
|
||||||
|
String dest = params.getProperty("DESTINATION");
|
||||||
|
String size = params.getProperty("SIZE");
|
||||||
|
String fp = params.getProperty("FROM_PORT");
|
||||||
|
String tp = params.getProperty("TO_PORT");
|
||||||
|
int fromPort = 0;
|
||||||
|
int toPort = 0;
|
||||||
|
if (dest != null) {
|
||||||
|
try {
|
||||||
|
if (fp != null)
|
||||||
|
fromPort = Integer.parseInt(fp);
|
||||||
|
if (tp != null)
|
||||||
|
toPort = Integer.parseInt(tp);
|
||||||
|
int sizeVal = Integer.parseInt(size);
|
||||||
|
byte data[] = new byte[sizeVal];
|
||||||
|
int read = DataHelper.read(_inRaw, data);
|
||||||
|
if (read != sizeVal) {
|
||||||
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
|
} else {
|
||||||
|
_listener.datagramReceived(dest, data, 0, sizeVal, fromPort, toPort);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_live = false;
|
||||||
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
|
}
|
||||||
|
} else if ("RAW".equals(major)) {
|
||||||
|
if ("RECEIVED".equals(minor)) {
|
||||||
|
String size = params.getProperty("SIZE");
|
||||||
|
String fp = params.getProperty("FROM_PORT");
|
||||||
|
String tp = params.getProperty("TO_PORT");
|
||||||
|
String pr = params.getProperty("PROTOCOL");
|
||||||
|
int fromPort = 0;
|
||||||
|
int toPort = 0;
|
||||||
|
int protocol = 18;
|
||||||
|
try {
|
||||||
|
if (fp != null)
|
||||||
|
fromPort = Integer.parseInt(fp);
|
||||||
|
if (tp != null)
|
||||||
|
toPort = Integer.parseInt(tp);
|
||||||
|
if (pr != null)
|
||||||
|
protocol = Integer.parseInt(pr);
|
||||||
|
int sizeVal = Integer.parseInt(size);
|
||||||
|
byte data[] = new byte[sizeVal];
|
||||||
|
int read = DataHelper.read(_inRaw, data);
|
||||||
|
if (read != sizeVal) {
|
||||||
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
|
} else {
|
||||||
|
_listener.rawReceived(data, 0, sizeVal, fromPort, toPort, protocol);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_live = false;
|
||||||
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
|
}
|
||||||
} else if ("NAMING".equals(major)) {
|
} else if ("NAMING".equals(major)) {
|
||||||
if ("REPLY".equals(minor)) {
|
if ("REPLY".equals(minor)) {
|
||||||
String name = params.getProperty("NAME");
|
String name = params.getProperty("NAME");
|
||||||
@ -244,6 +315,12 @@ public class SAMReader {
|
|||||||
} else {
|
} else {
|
||||||
_listener.unknownMessageReceived(major, minor, params);
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
}
|
}
|
||||||
|
} else if ("PING".equals(major)) {
|
||||||
|
// this omits anything after a space
|
||||||
|
_listener.pingReceived(minor);
|
||||||
|
} else if ("PONG".equals(major)) {
|
||||||
|
// this omits anything after a space
|
||||||
|
_listener.pongReceived(minor);
|
||||||
} else {
|
} else {
|
||||||
_listener.unknownMessageReceived(major, minor, params);
|
_listener.unknownMessageReceived(major, minor, params);
|
||||||
}
|
}
|
||||||
|
@ -1,50 +1,141 @@
|
|||||||
package net.i2p.sam.client;
|
package net.i2p.sam.client;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
|
||||||
|
import gnu.getopt.Getopt;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.data.Base32;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.util.I2PAppThread;
|
import net.i2p.util.I2PAppThread;
|
||||||
|
import net.i2p.util.I2PSSLSocketFactory;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
|
import net.i2p.util.VersionComparator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a file to a peer
|
* Swiss army knife tester.
|
||||||
|
* Sends a file (datafile) to a peer (b64 dest in peerDestFile).
|
||||||
*
|
*
|
||||||
* Usage: SAMStreamSend samHost samPort peerDestFile dataFile
|
* Usage: SAMStreamSend [options] peerDestFile dataFile
|
||||||
|
*
|
||||||
|
* See apps/sam/doc/README-test.txt for info on test setup.
|
||||||
|
* Sends data in one of 5 modes.
|
||||||
|
* Optionally uses SSL.
|
||||||
|
* Configurable SAM client version.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class SAMStreamSend {
|
public class SAMStreamSend {
|
||||||
private I2PAppContext _context;
|
private final I2PAppContext _context;
|
||||||
private Log _log;
|
private final Log _log;
|
||||||
private String _samHost;
|
private final String _samHost;
|
||||||
private String _samPort;
|
private final String _samPort;
|
||||||
private String _destFile;
|
private final String _destFile;
|
||||||
private String _dataFile;
|
private final String _dataFile;
|
||||||
private String _conOptions;
|
private String _conOptions;
|
||||||
private Socket _samSocket;
|
private SAMReader _reader, _reader2;
|
||||||
private OutputStream _samOut;
|
private boolean _isV3;
|
||||||
private InputStream _samIn;
|
private boolean _isV32;
|
||||||
private SAMReader _reader;
|
private String _v3ID;
|
||||||
//private boolean _dead;
|
//private boolean _dead;
|
||||||
private SAMEventHandler _eventHandler;
|
|
||||||
/** Connection id (Integer) to peer (Flooder) */
|
/** Connection id (Integer) to peer (Flooder) */
|
||||||
private Map<Integer, Sender> _remotePeers;
|
private final Map<String, Sender> _remotePeers;
|
||||||
|
private static I2PSSLSocketFactory _sslSocketFactory;
|
||||||
|
|
||||||
|
private static final int STREAM=0, DG=1, V1DG=2, RAW=3, V1RAW=4;
|
||||||
|
private static final String USAGE = "Usage: SAMStreamSend [-s] [-m mode] [-v version] [-b samHost] [-p samPort] [-o opt=val] [-u user] [-w password] peerDestFile dataDir\n" +
|
||||||
|
" modes: stream: 0; datagram: 1; v1datagram: 2; raw: 3; v1raw: 4\n" +
|
||||||
|
" -s: use SSL\n" +
|
||||||
|
" multiple -o session options are allowed";
|
||||||
|
|
||||||
public static void main(String args[]) {
|
public static void main(String args[]) {
|
||||||
if (args.length < 4) {
|
Getopt g = new Getopt("SAM", args, "sb:m:o:p:u:v:w:");
|
||||||
System.err.println("Usage: SAMStreamSend samHost samPort peerDestFile dataFile");
|
boolean isSSL = false;
|
||||||
|
int mode = STREAM;
|
||||||
|
String version = "1.0";
|
||||||
|
String host = "127.0.0.1";
|
||||||
|
String port = "7656";
|
||||||
|
String user = null;
|
||||||
|
String password = null;
|
||||||
|
String opts = "";
|
||||||
|
int c;
|
||||||
|
while ((c = g.getopt()) != -1) {
|
||||||
|
switch (c) {
|
||||||
|
case 's':
|
||||||
|
isSSL = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'm':
|
||||||
|
mode = Integer.parseInt(g.getOptarg());
|
||||||
|
if (mode < 0 || mode > V1RAW) {
|
||||||
|
System.err.println(USAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'v':
|
||||||
|
version = g.getOptarg();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'b':
|
||||||
|
host = g.getOptarg();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
opts = opts + ' ' + g.getOptarg();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
port = g.getOptarg();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'u':
|
||||||
|
user = g.getOptarg();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'w':
|
||||||
|
password = g.getOptarg();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
case '?':
|
||||||
|
case ':':
|
||||||
|
default:
|
||||||
|
System.err.println(USAGE);
|
||||||
|
return;
|
||||||
|
} // switch
|
||||||
|
} // while
|
||||||
|
|
||||||
|
int startArgs = g.getOptind();
|
||||||
|
if (args.length - startArgs != 2) {
|
||||||
|
System.err.println(USAGE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
I2PAppContext ctx = new I2PAppContext();
|
if ((user == null && password != null) ||
|
||||||
//String files[] = new String[args.length - 3];
|
(user != null && password == null)) {
|
||||||
SAMStreamSend sender = new SAMStreamSend(ctx, args[0], args[1], args[2], args[3]);
|
System.err.println("both user and password or neither");
|
||||||
sender.startup();
|
return;
|
||||||
|
}
|
||||||
|
if (user != null && password != null && VersionComparator.comp(version, "3.2") < 0) {
|
||||||
|
System.err.println("user/password require 3.2");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||||
|
SAMStreamSend sender = new SAMStreamSend(ctx, host, port,
|
||||||
|
args[startArgs], args[startArgs + 1]);
|
||||||
|
sender.startup(version, isSSL, mode, user, password, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SAMStreamSend(I2PAppContext ctx, String samHost, String samPort, String destFile, String dataFile) {
|
public SAMStreamSend(I2PAppContext ctx, String samHost, String samPort, String destFile, String dataFile) {
|
||||||
@ -56,86 +147,141 @@ public class SAMStreamSend {
|
|||||||
_destFile = destFile;
|
_destFile = destFile;
|
||||||
_dataFile = dataFile;
|
_dataFile = dataFile;
|
||||||
_conOptions = "";
|
_conOptions = "";
|
||||||
_eventHandler = new SendEventHandler(_context);
|
_remotePeers = new HashMap<String, Sender>();
|
||||||
_remotePeers = new HashMap<Integer,Sender>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startup() {
|
public void startup(String version, boolean isSSL, int mode, String user, String password, String sessionOpts) {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Starting up");
|
_log.debug("Starting up");
|
||||||
boolean ok = connect();
|
try {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
Socket sock = connect(isSSL);
|
||||||
_log.debug("Connected: " + ok);
|
SAMEventHandler eventHandler = new SendEventHandler(_context);
|
||||||
if (ok) {
|
_reader = new SAMReader(_context, sock.getInputStream(), eventHandler);
|
||||||
_reader = new SAMReader(_context, _samIn, _eventHandler);
|
|
||||||
_reader.startReading();
|
_reader.startReading();
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Reader created");
|
_log.debug("Reader created");
|
||||||
String ourDest = handshake();
|
OutputStream out = sock.getOutputStream();
|
||||||
|
String ourDest = handshake(out, version, true, eventHandler, mode, user, password, sessionOpts);
|
||||||
|
if (ourDest == null)
|
||||||
|
throw new IOException("handshake failed");
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Handshake complete. we are " + ourDest);
|
_log.debug("Handshake complete. we are " + ourDest);
|
||||||
if (ourDest != null) {
|
if (_isV3 && mode == STREAM) {
|
||||||
send();
|
Socket sock2 = connect(isSSL);
|
||||||
|
eventHandler = new SendEventHandler(_context);
|
||||||
|
_reader2 = new SAMReader(_context, sock2.getInputStream(), eventHandler);
|
||||||
|
_reader2.startReading();
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("Reader2 created");
|
||||||
|
out = sock2.getOutputStream();
|
||||||
|
String ok = handshake(out, version, false, eventHandler, mode, user, password, "");
|
||||||
|
if (ok == null)
|
||||||
|
throw new IOException("2nd handshake failed");
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("Handshake2 complete.");
|
||||||
}
|
}
|
||||||
|
if (mode == DG || mode == RAW)
|
||||||
|
out = null;
|
||||||
|
send(out, eventHandler, mode);
|
||||||
|
} catch (IOException e) {
|
||||||
|
_log.error("Unable to connect to SAM at " + _samHost + ":" + _samPort, e);
|
||||||
|
if (_reader != null)
|
||||||
|
_reader.stopReading();
|
||||||
|
if (_reader2 != null)
|
||||||
|
_reader2.stopReading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SendEventHandler extends SAMEventHandler {
|
private class SendEventHandler extends SAMEventHandler {
|
||||||
public SendEventHandler(I2PAppContext ctx) { super(ctx); }
|
public SendEventHandler(I2PAppContext ctx) { super(ctx); }
|
||||||
public void streamClosedReceived(String result, int id, String message) {
|
|
||||||
|
@Override
|
||||||
|
public void streamClosedReceived(String result, String id, String message) {
|
||||||
Sender sender = null;
|
Sender sender = null;
|
||||||
synchronized (_remotePeers) {
|
synchronized (_remotePeers) {
|
||||||
sender = _remotePeers.remove(Integer.valueOf(id));
|
sender = _remotePeers.remove(id);
|
||||||
}
|
}
|
||||||
if (sender != null) {
|
if (sender != null) {
|
||||||
sender.closed();
|
sender.closed();
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Connection " + sender.getConnectionId() + " closed to " + sender.getDestination());
|
_log.debug("Connection " + sender.getConnectionId() + " closed to " + sender.getDestination());
|
||||||
} else {
|
} else {
|
||||||
_log.error("wtf, not connected to " + id + " but we were just closed?");
|
_log.error("not connected to " + id + " but we were just closed?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean connect() {
|
private Socket connect(boolean isSSL) throws IOException {
|
||||||
try {
|
int port = Integer.parseInt(_samPort);
|
||||||
_samSocket = new Socket(_samHost, Integer.parseInt(_samPort));
|
if (!isSSL)
|
||||||
_samOut = _samSocket.getOutputStream();
|
return new Socket(_samHost, port);
|
||||||
_samIn = _samSocket.getInputStream();
|
synchronized(SAMStreamSink.class) {
|
||||||
return true;
|
if (_sslSocketFactory == null) {
|
||||||
} catch (Exception e) {
|
try {
|
||||||
_log.error("Unable to connect to SAM at " + _samHost + ":" + _samPort, e);
|
_sslSocketFactory = new I2PSSLSocketFactory(
|
||||||
return false;
|
_context, true, "certificates/sam");
|
||||||
|
} catch (GeneralSecurityException gse) {
|
||||||
|
throw new IOException("SSL error", gse);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
SSLSocket sock = (SSLSocket) _sslSocketFactory.createSocket(_samHost, port);
|
||||||
|
I2PSSLSocketFactory.verifyHostname(_context, sock, _samHost);
|
||||||
|
return sock;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String handshake() {
|
/** @return our b64 dest or null */
|
||||||
synchronized (_samOut) {
|
private String handshake(OutputStream samOut, String version, boolean isMaster,
|
||||||
|
SAMEventHandler eventHandler, int mode, String user, String password,
|
||||||
|
String opts) {
|
||||||
|
synchronized (samOut) {
|
||||||
try {
|
try {
|
||||||
_samOut.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
if (user != null && password != null)
|
||||||
_samOut.flush();
|
samOut.write(("HELLO VERSION MIN=1.0 MAX=" + version + " USER=" + user + " PASSWORD=" + password + '\n').getBytes());
|
||||||
|
else
|
||||||
|
samOut.write(("HELLO VERSION MIN=1.0 MAX=" + version + '\n').getBytes());
|
||||||
|
samOut.flush();
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Hello sent");
|
_log.debug("Hello sent");
|
||||||
boolean ok = _eventHandler.waitForHelloReply();
|
String hisVersion = eventHandler.waitForHelloReply();
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Hello reply found: " + ok);
|
_log.debug("Hello reply found: " + hisVersion);
|
||||||
if (!ok)
|
if (hisVersion == null)
|
||||||
throw new IOException("wtf, hello failed?");
|
throw new IOException("Hello failed");
|
||||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=TRANSIENT " + _conOptions + "\n";
|
if (!isMaster)
|
||||||
_samOut.write(req.getBytes());
|
return "OK";
|
||||||
_samOut.flush();
|
_isV3 = VersionComparator.comp(hisVersion, "3") >= 0;
|
||||||
|
if (_isV3) {
|
||||||
|
_isV32 = VersionComparator.comp(hisVersion, "3.2") >= 0;
|
||||||
|
byte[] id = new byte[5];
|
||||||
|
_context.random().nextBytes(id);
|
||||||
|
_v3ID = Base32.encode(id);
|
||||||
|
_conOptions = "ID=" + _v3ID;
|
||||||
|
}
|
||||||
|
String style;
|
||||||
|
if (mode == STREAM)
|
||||||
|
style = "STREAM";
|
||||||
|
else if (mode == DG || mode == V1DG)
|
||||||
|
style = "DATAGRAM";
|
||||||
|
else
|
||||||
|
style = "RAW";
|
||||||
|
String req = "SESSION CREATE STYLE=" + style + " DESTINATION=TRANSIENT " + _conOptions + ' ' + opts + '\n';
|
||||||
|
samOut.write(req.getBytes());
|
||||||
|
samOut.flush();
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Session create sent");
|
_log.debug("Session create sent");
|
||||||
ok = _eventHandler.waitForSessionCreateReply();
|
boolean ok = eventHandler.waitForSessionCreateReply();
|
||||||
|
if (!ok)
|
||||||
|
throw new IOException("Session create failed");
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Session create reply found: " + ok);
|
_log.debug("Session create reply found: " + ok);
|
||||||
|
|
||||||
req = "NAMING LOOKUP NAME=ME\n";
|
req = "NAMING LOOKUP NAME=ME\n";
|
||||||
_samOut.write(req.getBytes());
|
samOut.write(req.getBytes());
|
||||||
_samOut.flush();
|
samOut.flush();
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Naming lookup sent");
|
_log.debug("Naming lookup sent");
|
||||||
String destination = _eventHandler.waitForNamingReply("ME");
|
String destination = eventHandler.waitForNamingReply("ME");
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Naming lookup reply found: " + destination);
|
_log.debug("Naming lookup reply found: " + destination);
|
||||||
if (destination == null) {
|
if (destination == null) {
|
||||||
@ -145,32 +291,56 @@ public class SAMStreamSend {
|
|||||||
_log.info("We are " + destination);
|
_log.info("We are " + destination);
|
||||||
}
|
}
|
||||||
return destination;
|
return destination;
|
||||||
} catch (Exception e) {
|
} catch (IOException e) {
|
||||||
_log.error("Error handshaking", e);
|
_log.error("Error handshaking", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void send() {
|
private void send(OutputStream samOut, SAMEventHandler eventHandler, int mode) throws IOException {
|
||||||
Sender sender = new Sender();
|
Sender sender = new Sender(samOut, eventHandler, mode);
|
||||||
boolean ok = sender.openConnection();
|
boolean ok = sender.openConnection();
|
||||||
if (ok) {
|
if (ok) {
|
||||||
I2PAppThread t = new I2PAppThread(sender, "Sender");
|
I2PAppThread t = new I2PAppThread(sender, "Sender");
|
||||||
t.start();
|
t.start();
|
||||||
|
} else {
|
||||||
|
throw new IOException("Sender failed to connect");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Sender implements Runnable {
|
private class Sender implements Runnable {
|
||||||
private int _connectionId;
|
private final String _connectionId;
|
||||||
private String _remoteDestination;
|
private String _remoteDestination;
|
||||||
private InputStream _in;
|
private InputStream _in;
|
||||||
private boolean _closed;
|
private volatile boolean _closed;
|
||||||
private long _started;
|
private long _started;
|
||||||
private long _totalSent;
|
private long _totalSent;
|
||||||
|
private final OutputStream _samOut;
|
||||||
|
private final SAMEventHandler _eventHandler;
|
||||||
|
private final int _mode;
|
||||||
|
private final DatagramSocket _dgSock;
|
||||||
|
private final InetSocketAddress _dgSAM;
|
||||||
|
|
||||||
public Sender() {
|
public Sender(OutputStream samOut, SAMEventHandler eventHandler, int mode) throws IOException {
|
||||||
_closed = false;
|
_samOut = samOut;
|
||||||
|
_eventHandler = eventHandler;
|
||||||
|
_mode = mode;
|
||||||
|
if (mode == DG || mode == RAW) {
|
||||||
|
// samOut will be null
|
||||||
|
_dgSock = new DatagramSocket();
|
||||||
|
_dgSAM = new InetSocketAddress(_samHost, 7655);
|
||||||
|
} else {
|
||||||
|
_dgSock = null;
|
||||||
|
_dgSAM = null;
|
||||||
|
}
|
||||||
|
synchronized (_remotePeers) {
|
||||||
|
if (_v3ID != null)
|
||||||
|
_connectionId = _v3ID;
|
||||||
|
else
|
||||||
|
_connectionId = Integer.toString(_remotePeers.size() + 1);
|
||||||
|
_remotePeers.put(_connectionId, Sender.this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean openConnection() {
|
public boolean openConnection() {
|
||||||
@ -181,19 +351,27 @@ public class SAMStreamSend {
|
|||||||
int read = DataHelper.read(fin, dest);
|
int read = DataHelper.read(fin, dest);
|
||||||
|
|
||||||
_remoteDestination = DataHelper.getUTF8(dest, 0, read);
|
_remoteDestination = DataHelper.getUTF8(dest, 0, read);
|
||||||
synchronized (_remotePeers) {
|
|
||||||
_connectionId = _remotePeers.size() + 1;
|
|
||||||
_remotePeers.put(Integer.valueOf(_connectionId), Sender.this);
|
|
||||||
}
|
|
||||||
|
|
||||||
_context.statManager().createRateStat("send." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
_context.statManager().createRateStat("send." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||||
_context.statManager().createRateStat("send." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
|
_context.statManager().createRateStat("send." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
|
||||||
_context.statManager().createRateStat("send." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
|
_context.statManager().createRateStat("send." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
|
||||||
|
|
||||||
byte msg[] = ("STREAM CONNECT ID=" + _connectionId + " DESTINATION=" + _remoteDestination + "\n").getBytes();
|
if (_mode == STREAM) {
|
||||||
synchronized (_samOut) {
|
StringBuilder buf = new StringBuilder(1024);
|
||||||
_samOut.write(msg);
|
buf.append("STREAM CONNECT ID=").append(_connectionId).append(" DESTINATION=").append(_remoteDestination);
|
||||||
_samOut.flush();
|
// not supported until 3.2 but 3.0-3.1 will ignore
|
||||||
|
if (_isV3)
|
||||||
|
buf.append(" FROM_PORT=1234 TO_PORT=5678");
|
||||||
|
buf.append('\n');
|
||||||
|
byte[] msg = DataHelper.getASCII(buf.toString());
|
||||||
|
synchronized (_samOut) {
|
||||||
|
_samOut.write(msg);
|
||||||
|
_samOut.flush();
|
||||||
|
}
|
||||||
|
_log.debug("STREAM CONNECT sent, waiting for STREAM STATUS...");
|
||||||
|
boolean ok = _eventHandler.waitForStreamStatusReply();
|
||||||
|
if (!ok)
|
||||||
|
throw new IOException("STREAM CONNECT failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
_in = new FileInputStream(_dataFile);
|
_in = new FileInputStream(_dataFile);
|
||||||
@ -210,7 +388,7 @@ public class SAMStreamSend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getConnectionId() { return _connectionId; }
|
public String getConnectionId() { return _connectionId; }
|
||||||
public String getDestination() { return _remoteDestination; }
|
public String getDestination() { return _remoteDestination; }
|
||||||
|
|
||||||
public void closed() {
|
public void closed() {
|
||||||
@ -224,7 +402,8 @@ public class SAMStreamSend {
|
|||||||
public void run() {
|
public void run() {
|
||||||
_started = _context.clock().now();
|
_started = _context.clock().now();
|
||||||
_context.statManager().addRateData("send." + _connectionId + ".started", 1, 0);
|
_context.statManager().addRateData("send." + _connectionId + ".started", 1, 0);
|
||||||
byte data[] = new byte[1024];
|
final long toSend = (new File(_dataFile)).length();
|
||||||
|
byte data[] = new byte[8192];
|
||||||
long lastSend = _context.clock().now();
|
long lastSend = _context.clock().now();
|
||||||
while (!_closed) {
|
while (!_closed) {
|
||||||
try {
|
try {
|
||||||
@ -239,11 +418,45 @@ public class SAMStreamSend {
|
|||||||
_log.debug("Sending " + read + " on " + _connectionId + " after " + (now-lastSend));
|
_log.debug("Sending " + read + " on " + _connectionId + " after " + (now-lastSend));
|
||||||
lastSend = now;
|
lastSend = now;
|
||||||
|
|
||||||
byte msg[] = ("STREAM SEND ID=" + _connectionId + " SIZE=" + read + "\n").getBytes();
|
if (_samOut != null) {
|
||||||
synchronized (_samOut) {
|
synchronized (_samOut) {
|
||||||
_samOut.write(msg);
|
if (!_isV3 || _mode == V1DG || _mode == V1RAW) {
|
||||||
_samOut.write(data, 0, read);
|
String m;
|
||||||
_samOut.flush();
|
if (_mode == STREAM) {
|
||||||
|
m = "STREAM SEND ID=" + _connectionId + " SIZE=" + read + "\n";
|
||||||
|
} else if (_mode == V1DG) {
|
||||||
|
m = "DATAGRAM SEND DESTINATION=" + _remoteDestination + " SIZE=" + read + "\n";
|
||||||
|
} else if (_mode == V1RAW) {
|
||||||
|
m = "RAW SEND DESTINATION=" + _remoteDestination + " SIZE=" + read + "\n";
|
||||||
|
} else {
|
||||||
|
throw new IOException("unsupported mode " + _mode);
|
||||||
|
}
|
||||||
|
byte msg[] = DataHelper.getASCII(m);
|
||||||
|
_samOut.write(msg);
|
||||||
|
}
|
||||||
|
_samOut.write(data, 0, read);
|
||||||
|
_samOut.flush();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// real datagrams
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(read + 1024);
|
||||||
|
baos.write(DataHelper.getASCII("3.0 "));
|
||||||
|
baos.write(DataHelper.getASCII(_v3ID));
|
||||||
|
baos.write((byte) ' ');
|
||||||
|
baos.write(DataHelper.getASCII(_remoteDestination));
|
||||||
|
if (_isV32) {
|
||||||
|
// only set TO_PORT to test session setting of FROM_PORT
|
||||||
|
if (_mode == RAW)
|
||||||
|
baos.write(DataHelper.getASCII(" PROTOCOL=123 TO_PORT=5678"));
|
||||||
|
else
|
||||||
|
baos.write(DataHelper.getASCII(" TO_PORT=5678"));
|
||||||
|
}
|
||||||
|
baos.write((byte) '\n');
|
||||||
|
baos.write(data, 0, read);
|
||||||
|
byte[] pkt = baos.toByteArray();
|
||||||
|
DatagramPacket p = new DatagramPacket(pkt, pkt.length, _dgSAM);
|
||||||
|
_dgSock.send(p);
|
||||||
|
try { Thread.sleep(25); } catch (InterruptedException ie) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
_totalSent += read;
|
_totalSent += read;
|
||||||
@ -251,20 +464,49 @@ public class SAMStreamSend {
|
|||||||
}
|
}
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
_log.error("Error sending", ioe);
|
_log.error("Error sending", ioe);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte msg[] = ("STREAM CLOSE ID=" + _connectionId + "\n").getBytes();
|
if (_samOut != null) {
|
||||||
try {
|
if (_isV3) {
|
||||||
synchronized (_samOut) {
|
try {
|
||||||
_samOut.write(msg);
|
_samOut.close();
|
||||||
_samOut.flush();
|
} catch (IOException ioe) {
|
||||||
|
_log.info("Error closing", ioe);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
byte msg[] = ("STREAM CLOSE ID=" + _connectionId + "\n").getBytes();
|
||||||
|
try {
|
||||||
|
synchronized (_samOut) {
|
||||||
|
_samOut.write(msg);
|
||||||
|
_samOut.flush();
|
||||||
|
_samOut.close();
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_log.info("Error closing", ioe);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException ioe) {
|
} else if (_dgSock != null) {
|
||||||
_log.error("Error closing", ioe);
|
_dgSock.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
closed();
|
closed();
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("Runner exiting");
|
||||||
|
if (toSend != _totalSent)
|
||||||
|
_log.error("Only sent " + _totalSent + " of " + toSend + " bytes");
|
||||||
|
if (_reader2 != null)
|
||||||
|
_reader2.stopReading();
|
||||||
|
// stop the reader, since we're only doing this once for testing
|
||||||
|
// you wouldn't do this in a real application
|
||||||
|
if (_isV3) {
|
||||||
|
// closing the master socket too fast will kill the data socket flushing through
|
||||||
|
try {
|
||||||
|
Thread.sleep(10000);
|
||||||
|
} catch (InterruptedException ie) {}
|
||||||
|
}
|
||||||
|
_reader.stopReading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,184 +1,636 @@
|
|||||||
package net.i2p.sam.client;
|
package net.i2p.sam.client;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
import javax.net.ssl.SSLServerSocket;
|
||||||
|
|
||||||
|
import gnu.getopt.Getopt;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.data.Base32;
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
|
import net.i2p.util.I2PAppThread;
|
||||||
|
import net.i2p.util.I2PSSLSocketFactory;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
|
import net.i2p.util.VersionComparator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sit around on a SAM destination, receiving lots of data and
|
* Swiss army knife tester.
|
||||||
* writing it to disk
|
* Saves our transient b64 destination to myKeyFile where SAMStreamSend can get it.
|
||||||
|
* Saves received data to a file (in sinkDir).
|
||||||
*
|
*
|
||||||
* Usage: SAMStreamSink samHost samPort myKeyFile sinkDir
|
* Usage: SAMStreamSink [options] myKeyFile sinkDir
|
||||||
|
*
|
||||||
|
* See apps/sam/doc/README-test.txt for info on test setup.
|
||||||
|
* Receives data in one of 7 modes.
|
||||||
|
* Optionally uses SSL.
|
||||||
|
* Configurable SAM client version.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class SAMStreamSink {
|
public class SAMStreamSink {
|
||||||
private I2PAppContext _context;
|
private final I2PAppContext _context;
|
||||||
private Log _log;
|
private final Log _log;
|
||||||
private String _samHost;
|
private final String _samHost;
|
||||||
private String _samPort;
|
private final String _samPort;
|
||||||
private String _destFile;
|
private final String _destFile;
|
||||||
private String _sinkDir;
|
private final String _sinkDir;
|
||||||
private String _conOptions;
|
private String _conOptions;
|
||||||
private Socket _samSocket;
|
private SAMReader _reader, _reader2;
|
||||||
private OutputStream _samOut;
|
private boolean _isV3;
|
||||||
private InputStream _samIn;
|
private boolean _isV32;
|
||||||
private SAMReader _reader;
|
private String _v3ID;
|
||||||
//private boolean _dead;
|
|
||||||
private SAMEventHandler _eventHandler;
|
|
||||||
/** Connection id (Integer) to peer (Flooder) */
|
/** Connection id (Integer) to peer (Flooder) */
|
||||||
private Map<Integer, Sink> _remotePeers;
|
private final Map<String, Sink> _remotePeers;
|
||||||
|
private static I2PSSLSocketFactory _sslSocketFactory;
|
||||||
|
|
||||||
|
private static final int STREAM=0, DG=1, V1DG=2, RAW=3, V1RAW=4, RAWHDR = 5, FORWARD = 6;
|
||||||
|
private static final String USAGE = "Usage: SAMStreamSink [-s] [-m mode] [-v version] [-b samHost] [-p samPort] [-o opt=val] [-u user] [-w password] myDestFile sinkDir\n" +
|
||||||
|
" modes: stream: 0; datagram: 1; v1datagram: 2; raw: 3; v1raw: 4; raw-with-headers: 5; stream-forward: 6\n" +
|
||||||
|
" -s: use SSL\n" +
|
||||||
|
" multiple -o session options are allowed";
|
||||||
|
private static final int V3FORWARDPORT=9998;
|
||||||
|
private static final int V3DGPORT=9999;
|
||||||
|
|
||||||
public static void main(String args[]) {
|
public static void main(String args[]) {
|
||||||
if (args.length < 4) {
|
Getopt g = new Getopt("SAM", args, "sb:m:p:u:v:w:");
|
||||||
System.err.println("Usage: SAMStreamSink samHost samPort myDestFile sinkDir");
|
boolean isSSL = false;
|
||||||
|
int mode = STREAM;
|
||||||
|
String version = "1.0";
|
||||||
|
String host = "127.0.0.1";
|
||||||
|
String port = "7656";
|
||||||
|
String user = null;
|
||||||
|
String password = null;
|
||||||
|
String opts = "";
|
||||||
|
int c;
|
||||||
|
while ((c = g.getopt()) != -1) {
|
||||||
|
switch (c) {
|
||||||
|
case 's':
|
||||||
|
isSSL = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'm':
|
||||||
|
mode = Integer.parseInt(g.getOptarg());
|
||||||
|
if (mode < 0 || mode > FORWARD) {
|
||||||
|
System.err.println(USAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'v':
|
||||||
|
version = g.getOptarg();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'b':
|
||||||
|
host = g.getOptarg();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
opts = opts + ' ' + g.getOptarg();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
port = g.getOptarg();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'u':
|
||||||
|
user = g.getOptarg();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'w':
|
||||||
|
password = g.getOptarg();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
case '?':
|
||||||
|
case ':':
|
||||||
|
default:
|
||||||
|
System.err.println(USAGE);
|
||||||
|
return;
|
||||||
|
} // switch
|
||||||
|
} // while
|
||||||
|
|
||||||
|
int startArgs = g.getOptind();
|
||||||
|
if (args.length - startArgs != 2) {
|
||||||
|
System.err.println(USAGE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
I2PAppContext ctx = new I2PAppContext();
|
if ((user == null && password != null) ||
|
||||||
SAMStreamSink sink = new SAMStreamSink(ctx, args[0], args[1], args[2], args[3]);
|
(user != null && password == null)) {
|
||||||
sink.startup();
|
System.err.println("both user and password or neither");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (user != null && password != null && VersionComparator.comp(version, "3.2") < 0) {
|
||||||
|
System.err.println("user/password require 3.2");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||||
|
SAMStreamSink sink = new SAMStreamSink(ctx, host, port,
|
||||||
|
args[startArgs], args[startArgs + 1]);
|
||||||
|
sink.startup(version, isSSL, mode, user, password, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SAMStreamSink(I2PAppContext ctx, String samHost, String samPort, String destFile, String sinkDir) {
|
public SAMStreamSink(I2PAppContext ctx, String samHost, String samPort, String destFile, String sinkDir) {
|
||||||
_context = ctx;
|
_context = ctx;
|
||||||
_log = ctx.logManager().getLog(SAMStreamSink.class);
|
_log = ctx.logManager().getLog(SAMStreamSink.class);
|
||||||
//_dead = false;
|
|
||||||
_samHost = samHost;
|
_samHost = samHost;
|
||||||
_samPort = samPort;
|
_samPort = samPort;
|
||||||
_destFile = destFile;
|
_destFile = destFile;
|
||||||
_sinkDir = sinkDir;
|
_sinkDir = sinkDir;
|
||||||
_conOptions = "";
|
_conOptions = "";
|
||||||
_eventHandler = new SinkEventHandler(_context);
|
_remotePeers = new HashMap<String, Sink>();
|
||||||
_remotePeers = new HashMap<Integer,Sink>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startup() {
|
public void startup(String version, boolean isSSL, int mode, String user, String password, String sessionOpts) {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Starting up");
|
_log.debug("Starting up");
|
||||||
boolean ok = connect();
|
try {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
Socket sock = connect(isSSL);
|
||||||
_log.debug("Connected: " + ok);
|
OutputStream out = sock.getOutputStream();
|
||||||
if (ok) {
|
SAMEventHandler eventHandler = new SinkEventHandler(_context, out);
|
||||||
_reader = new SAMReader(_context, _samIn, _eventHandler);
|
_reader = new SAMReader(_context, sock.getInputStream(), eventHandler);
|
||||||
_reader.startReading();
|
_reader.startReading();
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Reader created");
|
_log.debug("Reader created");
|
||||||
String ourDest = handshake();
|
String ourDest = handshake(out, version, true, eventHandler, mode, user, password, sessionOpts);
|
||||||
|
if (ourDest == null)
|
||||||
|
throw new IOException("handshake failed");
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Handshake complete. we are " + ourDest);
|
_log.debug("Handshake complete. we are " + ourDest);
|
||||||
if (ourDest != null) {
|
if (_isV32) {
|
||||||
//boolean written =
|
_log.debug("Starting pinger");
|
||||||
writeDest(ourDest);
|
Thread t = new Pinger(out);
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
t.start();
|
||||||
_log.debug("Dest written");
|
}
|
||||||
|
if (_isV3 && (mode == STREAM || mode == FORWARD)) {
|
||||||
|
// test multiple acceptors, only works in 3.2
|
||||||
|
int acceptors = (_isV32 && mode == STREAM) ? 4 : 1;
|
||||||
|
for (int i = 0; i < acceptors; i++) {
|
||||||
|
Socket sock2 = connect(isSSL);
|
||||||
|
out = sock2.getOutputStream();
|
||||||
|
eventHandler = new SinkEventHandler2(_context, sock2.getInputStream(), out);
|
||||||
|
_reader2 = new SAMReader(_context, sock2.getInputStream(), eventHandler);
|
||||||
|
_reader2.startReading();
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("Reader " + (2 + i) + " created");
|
||||||
|
String ok = handshake(out, version, false, eventHandler, mode, user, password, "");
|
||||||
|
if (ok == null)
|
||||||
|
throw new IOException("handshake " + (2 + i) + " failed");
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("Handshake " + (2 + i) + " complete.");
|
||||||
|
}
|
||||||
|
if (mode == FORWARD) {
|
||||||
|
// set up a listening ServerSocket
|
||||||
|
(new FwdRcvr(isSSL)).start();
|
||||||
|
}
|
||||||
|
} else if (_isV3 && (mode == DG || mode == RAW || mode == RAWHDR)) {
|
||||||
|
// set up a listening DatagramSocket
|
||||||
|
(new DGRcvr(mode)).start();
|
||||||
|
}
|
||||||
|
writeDest(ourDest);
|
||||||
|
} catch (IOException e) {
|
||||||
|
_log.error("Unable to connect to SAM at " + _samHost + ":" + _samPort, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DGRcvr extends I2PAppThread {
|
||||||
|
private final int _mode;
|
||||||
|
|
||||||
|
public DGRcvr(int mode) { _mode = mode; }
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
byte[] buf = new byte[32768];
|
||||||
|
try {
|
||||||
|
Sink sink = new Sink("FAKE", "FAKEFROM");
|
||||||
|
DatagramSocket dg = new DatagramSocket(V3DGPORT);
|
||||||
|
while (true) {
|
||||||
|
DatagramPacket p = new DatagramPacket(buf, 32768);
|
||||||
|
dg.receive(p);
|
||||||
|
int len = p.getLength();
|
||||||
|
int off = p.getOffset();
|
||||||
|
byte[] data = p.getData();
|
||||||
|
_log.info("Got datagram length " + len);
|
||||||
|
if (_mode == DG || _mode == RAWHDR) {
|
||||||
|
ByteArrayInputStream bais = new ByteArrayInputStream(data, off, len);
|
||||||
|
String line = DataHelper.readLine(bais);
|
||||||
|
if (line == null) {
|
||||||
|
_log.error("DGRcvr no header line");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (_mode == DG && line.length() < 516) {
|
||||||
|
_log.error("DGRcvr line too short: \"" + line + '\n');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String[] parts = line.split(" ");
|
||||||
|
int i = 0;
|
||||||
|
if (_mode == DG) {
|
||||||
|
String dest = parts[0];
|
||||||
|
_log.info("DG is from " + dest);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
for ( ; i < parts.length; i++) {
|
||||||
|
_log.info("Parameter: " + parts[i]);
|
||||||
|
}
|
||||||
|
int left = bais.available();
|
||||||
|
sink.received(data, off + len - left, left);
|
||||||
|
} else {
|
||||||
|
sink.received(data, off, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_log.error("DGRcvr", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FwdRcvr extends I2PAppThread {
|
||||||
|
private final boolean _isSSL;
|
||||||
|
|
||||||
|
public FwdRcvr(boolean isSSL) {
|
||||||
|
if (isSSL)
|
||||||
|
throw new UnsupportedOperationException("TODO");
|
||||||
|
_isSSL = isSSL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
ServerSocket ss;
|
||||||
|
if (_isSSL) {
|
||||||
|
throw new UnsupportedOperationException("TODO");
|
||||||
|
} else {
|
||||||
|
ss = new ServerSocket(V3FORWARDPORT);
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
Socket s = ss.accept();
|
||||||
|
Sink sink = new Sink("FAKE", "FAKEFROM");
|
||||||
|
try {
|
||||||
|
InputStream in = s.getInputStream();
|
||||||
|
byte[] buf = new byte[32768];
|
||||||
|
int len;
|
||||||
|
while((len = in.read(buf)) >= 0) {
|
||||||
|
sink.received(buf, 0, len);
|
||||||
|
}
|
||||||
|
sink.closed();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_log.error("Fwdcvr", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_log.error("Fwdcvr", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Pinger extends I2PAppThread {
|
||||||
|
private final OutputStream _out;
|
||||||
|
|
||||||
|
public Pinger(OutputStream out) {
|
||||||
|
super("SAM Sink Pinger");
|
||||||
|
setDaemon(true);
|
||||||
|
_out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(127*1000);
|
||||||
|
synchronized(_out) {
|
||||||
|
_out.write(DataHelper.getASCII("PING " + System.currentTimeMillis() + '\n'));
|
||||||
|
_out.flush();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
break;
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SinkEventHandler extends SAMEventHandler {
|
private class SinkEventHandler extends SAMEventHandler {
|
||||||
|
|
||||||
public SinkEventHandler(I2PAppContext ctx) { super(ctx); }
|
protected final OutputStream _out;
|
||||||
|
|
||||||
|
public SinkEventHandler(I2PAppContext ctx, OutputStream out) {
|
||||||
|
super(ctx);
|
||||||
|
_out = out;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void streamClosedReceived(String result, int id, String message) {
|
public void streamClosedReceived(String result, String id, String message) {
|
||||||
Sink sink = null;
|
Sink sink;
|
||||||
synchronized (_remotePeers) {
|
synchronized (_remotePeers) {
|
||||||
sink = _remotePeers.remove(Integer.valueOf(id));
|
sink = _remotePeers.remove(id);
|
||||||
}
|
}
|
||||||
if (sink != null) {
|
if (sink != null) {
|
||||||
sink.closed();
|
sink.closed();
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Connection " + sink.getConnectionId() + " closed to " + sink.getDestination());
|
_log.debug("Connection " + sink.getConnectionId() + " closed to " + sink.getDestination());
|
||||||
} else {
|
} else {
|
||||||
_log.error("wtf, not connected to " + id + " but we were just closed?");
|
_log.error("not connected to " + id + " but we were just closed?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void streamDataReceived(int id, byte data[], int offset, int length) {
|
public void streamDataReceived(String id, byte data[], int offset, int length) {
|
||||||
Sink sink = null;
|
Sink sink;
|
||||||
synchronized (_remotePeers) {
|
synchronized (_remotePeers) {
|
||||||
sink = _remotePeers.get(Integer.valueOf(id));
|
sink = _remotePeers.get(id);
|
||||||
}
|
}
|
||||||
if (sink != null) {
|
if (sink != null) {
|
||||||
sink.received(data, offset, length);
|
sink.received(data, offset, length);
|
||||||
} else {
|
} else {
|
||||||
_log.error("wtf, not connected to " + id + " but we received " + length + "?");
|
_log.error("not connected to " + id + " but we received " + length + "?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void streamConnectedReceived(String dest, int id) {
|
public void streamConnectedReceived(String dest, String id) {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Connection " + id + " received from " + dest);
|
_log.debug("Connection " + id + " received from " + dest);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Sink sink = new Sink(id, dest);
|
Sink sink = new Sink(id, dest);
|
||||||
synchronized (_remotePeers) {
|
synchronized (_remotePeers) {
|
||||||
_remotePeers.put(Integer.valueOf(id), sink);
|
_remotePeers.put(id, sink);
|
||||||
}
|
}
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
_log.error("Error creating a new sink", ioe);
|
_log.error("Error creating a new sink", ioe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pingReceived(String data) {
|
||||||
|
if (_log.shouldInfo())
|
||||||
|
_log.info("Got PING " + data + ", sending PONG " + data);
|
||||||
|
synchronized (_out) {
|
||||||
|
try {
|
||||||
|
_out.write(("PONG " + data + '\n').getBytes());
|
||||||
|
_out.flush();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_log.error("PONG fail", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void datagramReceived(String dest, byte[] data, int offset, int length, int fromPort, int toPort) {
|
||||||
|
// just get the first
|
||||||
|
Sink sink;
|
||||||
|
synchronized (_remotePeers) {
|
||||||
|
if (_remotePeers.isEmpty()) {
|
||||||
|
_log.error("not connected but we received datagram " + length + "?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sink = _remotePeers.values().iterator().next();
|
||||||
|
}
|
||||||
|
sink.received(data, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rawReceived(byte[] data, int offset, int length, int fromPort, int toPort, int protocol) {
|
||||||
|
// just get the first
|
||||||
|
Sink sink;
|
||||||
|
synchronized (_remotePeers) {
|
||||||
|
if (_remotePeers.isEmpty()) {
|
||||||
|
_log.error("not connected but we received raw " + length + "?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sink = _remotePeers.values().iterator().next();
|
||||||
|
}
|
||||||
|
sink.received(data, offset, length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean connect() {
|
private class SinkEventHandler2 extends SinkEventHandler {
|
||||||
try {
|
|
||||||
_samSocket = new Socket(_samHost, Integer.parseInt(_samPort));
|
private final InputStream _in;
|
||||||
_samOut = _samSocket.getOutputStream();
|
|
||||||
_samIn = _samSocket.getInputStream();
|
public SinkEventHandler2(I2PAppContext ctx, InputStream in, OutputStream out) {
|
||||||
return true;
|
super(ctx, out);
|
||||||
} catch (Exception e) {
|
_in = in;
|
||||||
_log.error("Unable to connect to SAM at " + _samHost + ":" + _samPort, e);
|
}
|
||||||
return false;
|
|
||||||
|
@Override
|
||||||
|
public void streamStatusReceived(String result, String id, String message) {
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("got STREAM STATUS, result=" + result);
|
||||||
|
super.streamStatusReceived(result, id, message);
|
||||||
|
Sink sink = null;
|
||||||
|
try {
|
||||||
|
String dest = "TODO_if_not_silent";
|
||||||
|
sink = new Sink(_v3ID, dest);
|
||||||
|
synchronized (_remotePeers) {
|
||||||
|
_remotePeers.put(_v3ID, sink);
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_log.error("Error creating a new sink", ioe);
|
||||||
|
try { _in.close(); } catch (IOException ioe2) {}
|
||||||
|
if (sink != null)
|
||||||
|
sink.closed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// inline so the reader doesn't grab the data
|
||||||
|
try {
|
||||||
|
boolean gotDest = false;
|
||||||
|
byte[] dest = new byte[1024];
|
||||||
|
int dlen = 0;
|
||||||
|
byte buf[] = new byte[4096];
|
||||||
|
int len;
|
||||||
|
while((len = _in.read(buf)) >= 0) {
|
||||||
|
if (!gotDest) {
|
||||||
|
// eat the dest line
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
byte b = buf[i];
|
||||||
|
if (b == (byte) '\n') {
|
||||||
|
gotDest = true;
|
||||||
|
if (_log.shouldInfo()) {
|
||||||
|
try {
|
||||||
|
_log.info("Got incoming accept from: \"" + new String(dest, 0, dlen, "ISO-8859-1") + '"');
|
||||||
|
} catch (IOException uee) {}
|
||||||
|
}
|
||||||
|
// feed any remaining to the sink
|
||||||
|
i++;
|
||||||
|
if (i < len)
|
||||||
|
sink.received(buf, i, len - i);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
if (dlen < dest.length) {
|
||||||
|
dest[dlen++] = b;
|
||||||
|
} else if (dlen == dest.length) {
|
||||||
|
dlen++;
|
||||||
|
_log.error("first line overflow on accept");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sink.received(buf, 0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sink.closed();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_log.error("Error reading", ioe);
|
||||||
|
} finally {
|
||||||
|
try { _in.close(); } catch (IOException ioe) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String handshake() {
|
private Socket connect(boolean isSSL) throws IOException {
|
||||||
synchronized (_samOut) {
|
int port = Integer.parseInt(_samPort);
|
||||||
|
if (!isSSL)
|
||||||
|
return new Socket(_samHost, port);
|
||||||
|
synchronized(SAMStreamSink.class) {
|
||||||
|
if (_sslSocketFactory == null) {
|
||||||
|
try {
|
||||||
|
_sslSocketFactory = new I2PSSLSocketFactory(
|
||||||
|
_context, true, "certificates/sam");
|
||||||
|
} catch (GeneralSecurityException gse) {
|
||||||
|
throw new IOException("SSL error", gse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SSLSocket sock = (SSLSocket) _sslSocketFactory.createSocket(_samHost, port);
|
||||||
|
I2PSSLSocketFactory.verifyHostname(_context, sock, _samHost);
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return our b64 dest or null */
|
||||||
|
private String handshake(OutputStream samOut, String version, boolean isMaster,
|
||||||
|
SAMEventHandler eventHandler, int mode, String user, String password,
|
||||||
|
String sopts) {
|
||||||
|
synchronized (samOut) {
|
||||||
try {
|
try {
|
||||||
_samOut.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
if (user != null && password != null)
|
||||||
_samOut.flush();
|
samOut.write(("HELLO VERSION MIN=1.0 MAX=" + version + " USER=" + user + " PASSWORD=" + password + '\n').getBytes());
|
||||||
|
else
|
||||||
|
samOut.write(("HELLO VERSION MIN=1.0 MAX=" + version + '\n').getBytes());
|
||||||
|
samOut.flush();
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Hello sent");
|
_log.debug("Hello sent");
|
||||||
boolean ok = _eventHandler.waitForHelloReply();
|
String hisVersion = eventHandler.waitForHelloReply();
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Hello reply found: " + ok);
|
_log.debug("Hello reply found: " + hisVersion);
|
||||||
if (!ok)
|
if (hisVersion == null)
|
||||||
throw new IOException("wtf, hello failed?");
|
throw new IOException("Hello failed");
|
||||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + _destFile + " " + _conOptions + "\n";
|
if (!isMaster) {
|
||||||
_samOut.write(req.getBytes());
|
// only for v3
|
||||||
_samOut.flush();
|
//String req = "STREAM ACCEPT SILENT=true ID=" + _v3ID + "\n";
|
||||||
|
// TO_PORT not supported until 3.2 but 3.0-3.1 will ignore
|
||||||
|
String req;
|
||||||
|
if (mode == STREAM)
|
||||||
|
req = "STREAM ACCEPT SILENT=false TO_PORT=5678 ID=" + _v3ID + "\n";
|
||||||
|
else if (mode == FORWARD)
|
||||||
|
req = "STREAM FORWARD ID=" + _v3ID + " PORT=" + V3FORWARDPORT + '\n';
|
||||||
|
else
|
||||||
|
throw new IllegalStateException("mode " + mode);
|
||||||
|
samOut.write(req.getBytes());
|
||||||
|
samOut.flush();
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("STREAM ACCEPT/FORWARD sent");
|
||||||
|
if (mode == FORWARD) {
|
||||||
|
// docs were wrong, we do not get a STREAM STATUS if SILENT=true for ACCEPT
|
||||||
|
boolean ok = eventHandler.waitForStreamStatusReply();
|
||||||
|
if (!ok)
|
||||||
|
throw new IOException("Stream status failed");
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("got STREAM STATUS, awaiting connection");
|
||||||
|
}
|
||||||
|
return "OK";
|
||||||
|
}
|
||||||
|
_isV3 = VersionComparator.comp(hisVersion, "3") >= 0;
|
||||||
|
String dest;
|
||||||
|
if (_isV3) {
|
||||||
|
_isV32 = VersionComparator.comp(hisVersion, "3.2") >= 0;
|
||||||
|
// we use the filename as the name in sam.keys
|
||||||
|
// and read it in ourselves
|
||||||
|
File keys = new File("sam.keys");
|
||||||
|
if (keys.exists()) {
|
||||||
|
Properties opts = new Properties();
|
||||||
|
DataHelper.loadProps(opts, keys);
|
||||||
|
String s = opts.getProperty(_destFile);
|
||||||
|
if (s != null) {
|
||||||
|
dest = s;
|
||||||
|
} else {
|
||||||
|
dest = "TRANSIENT";
|
||||||
|
(new File(_destFile)).delete();
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("Requesting new transient destination");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dest = "TRANSIENT";
|
||||||
|
(new File(_destFile)).delete();
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("Requesting new transient destination");
|
||||||
|
}
|
||||||
|
if (isMaster) {
|
||||||
|
byte[] id = new byte[5];
|
||||||
|
_context.random().nextBytes(id);
|
||||||
|
_v3ID = Base32.encode(id);
|
||||||
|
_conOptions = "ID=" + _v3ID;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we use the filename as the name in sam.keys
|
||||||
|
// and give it to the SAM server
|
||||||
|
dest = _destFile;
|
||||||
|
}
|
||||||
|
String style;
|
||||||
|
if (mode == STREAM || mode == FORWARD)
|
||||||
|
style = "STREAM";
|
||||||
|
else if (mode == V1DG)
|
||||||
|
style = "DATAGRAM";
|
||||||
|
else if (mode == DG)
|
||||||
|
style = "DATAGRAM PORT=" + V3DGPORT;
|
||||||
|
else if (mode == V1RAW)
|
||||||
|
style = "RAW";
|
||||||
|
else if (mode == RAW)
|
||||||
|
style = "RAW PORT=" + V3DGPORT;
|
||||||
|
else
|
||||||
|
style = "RAW HEADER=true PORT=" + V3DGPORT;
|
||||||
|
String req = "SESSION CREATE STYLE=" + style + " DESTINATION=" + dest + ' ' + _conOptions + ' ' + sopts + '\n';
|
||||||
|
samOut.write(req.getBytes());
|
||||||
|
samOut.flush();
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Session create sent");
|
_log.debug("Session create sent");
|
||||||
ok = _eventHandler.waitForSessionCreateReply();
|
if (mode == STREAM) {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
boolean ok = eventHandler.waitForSessionCreateReply();
|
||||||
_log.debug("Session create reply found: " + ok);
|
if (!ok)
|
||||||
|
throw new IOException("Session create failed");
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("Session create reply found: " + ok);
|
||||||
|
}
|
||||||
req = "NAMING LOOKUP NAME=ME\n";
|
req = "NAMING LOOKUP NAME=ME\n";
|
||||||
_samOut.write(req.getBytes());
|
samOut.write(req.getBytes());
|
||||||
_samOut.flush();
|
samOut.flush();
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Naming lookup sent");
|
_log.debug("Naming lookup sent");
|
||||||
String destination = _eventHandler.waitForNamingReply("ME");
|
String destination = eventHandler.waitForNamingReply("ME");
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Naming lookup reply found: " + destination);
|
_log.debug("Naming lookup reply found: " + destination);
|
||||||
if (destination == null) {
|
if (destination == null) {
|
||||||
_log.error("No naming lookup reply found!");
|
_log.error("No naming lookup reply found!");
|
||||||
return null;
|
return null;
|
||||||
} else {
|
}
|
||||||
|
if (_log.shouldInfo())
|
||||||
_log.info(_destFile + " is located at " + destination);
|
_log.info(_destFile + " is located at " + destination);
|
||||||
|
if (mode == V1DG || mode == V1RAW) {
|
||||||
|
// fake it so the sink starts
|
||||||
|
eventHandler.streamConnectedReceived(destination, "FAKE");
|
||||||
}
|
}
|
||||||
return destination;
|
return destination;
|
||||||
} catch (Exception e) {
|
} catch (IOException e) {
|
||||||
_log.error("Error handshaking", e);
|
_log.error("Error handshaking", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -186,11 +638,21 @@ public class SAMStreamSink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean writeDest(String dest) {
|
private boolean writeDest(String dest) {
|
||||||
|
File f = new File(_destFile);
|
||||||
|
/*
|
||||||
|
if (f.exists()) {
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("Destination file exists, not overwriting: " + _destFile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*/
|
||||||
FileOutputStream fos = null;
|
FileOutputStream fos = null;
|
||||||
try {
|
try {
|
||||||
fos = new FileOutputStream(_destFile);
|
fos = new FileOutputStream(f);
|
||||||
fos.write(dest.getBytes());
|
fos.write(dest.getBytes());
|
||||||
} catch (Exception e) {
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("My destination written to " + _destFile);
|
||||||
|
} catch (IOException e) {
|
||||||
_log.error("Error writing to " + _destFile, e);
|
_log.error("Error writing to " + _destFile, e);
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
@ -200,14 +662,14 @@ public class SAMStreamSink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class Sink {
|
private class Sink {
|
||||||
private int _connectionId;
|
private final String _connectionId;
|
||||||
private String _remoteDestination;
|
private final String _remoteDestination;
|
||||||
private boolean _closed;
|
private volatile boolean _closed;
|
||||||
private long _started;
|
private final long _started;
|
||||||
private long _lastReceivedOn;
|
private long _lastReceivedOn;
|
||||||
private OutputStream _out;
|
private final OutputStream _out;
|
||||||
|
|
||||||
public Sink(int conId, String remDest) throws IOException {
|
public Sink(String conId, String remDest) throws IOException {
|
||||||
_connectionId = conId;
|
_connectionId = conId;
|
||||||
_remoteDestination = remDest;
|
_remoteDestination = remDest;
|
||||||
_closed = false;
|
_closed = false;
|
||||||
@ -221,10 +683,13 @@ public class SAMStreamSink {
|
|||||||
sinkDir.mkdirs();
|
sinkDir.mkdirs();
|
||||||
|
|
||||||
File out = File.createTempFile("sink", ".dat", sinkDir);
|
File out = File.createTempFile("sink", ".dat", sinkDir);
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("outputting to " + out);
|
||||||
_out = new FileOutputStream(out);
|
_out = new FileOutputStream(out);
|
||||||
|
_started = _context.clock().now();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getConnectionId() { return _connectionId; }
|
public String getConnectionId() { return _connectionId; }
|
||||||
public String getDestination() { return _remoteDestination; }
|
public String getDestination() { return _remoteDestination; }
|
||||||
|
|
||||||
public void closed() {
|
public void closed() {
|
||||||
@ -235,7 +700,7 @@ public class SAMStreamSink {
|
|||||||
try {
|
try {
|
||||||
_out.close();
|
_out.close();
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
_log.error("Error closing", ioe);
|
_log.info("Error closing", ioe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void received(byte data[], int offset, int len) {
|
public void received(byte data[], int offset, int len) {
|
||||||
|
@ -294,9 +294,12 @@ class ConnectionHandler {
|
|||||||
private static class PoisonPacket extends Packet {
|
private static class PoisonPacket extends Packet {
|
||||||
public static final int POISON_MAX_DELAY_REQUEST = Packet.MAX_DELAY_REQUEST + 1;
|
public static final int POISON_MAX_DELAY_REQUEST = Packet.MAX_DELAY_REQUEST + 1;
|
||||||
|
|
||||||
public PoisonPacket() {
|
@Override
|
||||||
super(null);
|
public int getOptionalDelay() { return POISON_MAX_DELAY_REQUEST; }
|
||||||
setOptionalDelay(POISON_MAX_DELAY_REQUEST);
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "POISON";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,8 +46,11 @@ javac.version=1.6
|
|||||||
#javac.classpath=/PATH/TO/pack200.jar
|
#javac.classpath=/PATH/TO/pack200.jar
|
||||||
|
|
||||||
# Optional compiler args
|
# Optional compiler args
|
||||||
|
# This one is for subsystems requiring Java 6
|
||||||
# This one keeps gcj a lot quieter
|
# This one keeps gcj a lot quieter
|
||||||
#javac.compilerargs=-warn:-unchecked,raw,unused,serial
|
#javac.compilerargs=-warn:-unchecked,raw,unused,serial
|
||||||
|
# This one is for subsystems requiring Java 7
|
||||||
|
#javac.compilerargs7=
|
||||||
|
|
||||||
#
|
#
|
||||||
# Note to packagers, embedders, distributors:
|
# Note to packagers, embedders, distributors:
|
||||||
|
@ -99,6 +99,18 @@ public class PasswordManager {
|
|||||||
String shash = _context.getProperty(pfx + PROP_SHASH);
|
String shash = _context.getProperty(pfx + PROP_SHASH);
|
||||||
if (shash == null)
|
if (shash == null)
|
||||||
return false;
|
return false;
|
||||||
|
return checkHash(shash, pw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check pw against b64 salt+hash, as generated by createHash()
|
||||||
|
*
|
||||||
|
* @param shash b64 string
|
||||||
|
* @param pw plain text non-null, already trimmed
|
||||||
|
* @return if pw verified
|
||||||
|
* @since 0.9.24
|
||||||
|
*/
|
||||||
|
public boolean checkHash(String shash, String pw) {
|
||||||
byte[] shashBytes = Base64.decode(shash);
|
byte[] shashBytes = Base64.decode(shash);
|
||||||
if (shashBytes == null || shashBytes.length != SHASH_LENGTH)
|
if (shashBytes == null || shashBytes.length != SHASH_LENGTH)
|
||||||
return false;
|
return false;
|
||||||
@ -110,6 +122,23 @@ public class PasswordManager {
|
|||||||
return DataHelper.eq(hash, pwHash);
|
return DataHelper.eq(hash, pwHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a salt+hash, to be saved and verified later by verifyHash().
|
||||||
|
*
|
||||||
|
* @param pw plain text non-null, already trimmed
|
||||||
|
* @return salted+hash b64 string
|
||||||
|
* @since 0.9.24
|
||||||
|
*/
|
||||||
|
public String createHash(String pw) {
|
||||||
|
byte[] salt = new byte[SALT_LENGTH];
|
||||||
|
_context.random().nextBytes(salt);
|
||||||
|
byte[] pwHash = _context.keyGenerator().generateSessionKey(salt, DataHelper.getUTF8(pw)).getData();
|
||||||
|
byte[] shashBytes = new byte[SHASH_LENGTH];
|
||||||
|
System.arraycopy(salt, 0, shashBytes, 0, SALT_LENGTH);
|
||||||
|
System.arraycopy(pwHash, 0, shashBytes, SALT_LENGTH, SessionKey.KEYSIZE_BYTES);
|
||||||
|
return Base64.encode(shashBytes);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Either plain or b64
|
* Either plain or b64
|
||||||
*
|
*
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<author name="I2P" email="https://geti2p.net/"/>
|
<author name="I2P" email="https://geti2p.net/"/>
|
||||||
</authors>
|
</authors>
|
||||||
<url>https://geti2p.net/</url>
|
<url>https://geti2p.net/</url>
|
||||||
<javaversion>1.6</javaversion>
|
<javaversion>1.7</javaversion>
|
||||||
|
|
||||||
<!-- use pack200 compression, saves about 33%
|
<!-- use pack200 compression, saves about 33%
|
||||||
see http://java.sun.com/j2se/1.5.0/docs/guide/deployment/deployment-guide/pack200.html
|
see http://java.sun.com/j2se/1.5.0/docs/guide/deployment/deployment-guide/pack200.html
|
||||||
|
@ -158,13 +158,7 @@ public class RouterPasswordManager extends PasswordManager {
|
|||||||
String pfx = realm;
|
String pfx = realm;
|
||||||
if (user != null && user.length() > 0)
|
if (user != null && user.length() > 0)
|
||||||
pfx += '.' + user;
|
pfx += '.' + user;
|
||||||
byte[] salt = new byte[SALT_LENGTH];
|
String shash = createHash(pw);
|
||||||
_context.random().nextBytes(salt);
|
|
||||||
byte[] pwHash = _context.keyGenerator().generateSessionKey(salt, DataHelper.getUTF8(pw)).getData();
|
|
||||||
byte[] shashBytes = new byte[SHASH_LENGTH];
|
|
||||||
System.arraycopy(salt, 0, shashBytes, 0, SALT_LENGTH);
|
|
||||||
System.arraycopy(pwHash, 0, shashBytes, SALT_LENGTH, SessionKey.KEYSIZE_BYTES);
|
|
||||||
String shash = Base64.encode(shashBytes);
|
|
||||||
Map<String, String> toAdd = Collections.singletonMap(pfx + PROP_SHASH, shash);
|
Map<String, String> toAdd = Collections.singletonMap(pfx + PROP_SHASH, shash);
|
||||||
List<String> toDel = new ArrayList<String>(4);
|
List<String> toDel = new ArrayList<String>(4);
|
||||||
toDel.add(pfx + PROP_PW);
|
toDel.add(pfx + PROP_PW);
|
||||||
|
Reference in New Issue
Block a user