Pure-java implementation of the SAM bridge. It actually supports the

HELLO, DEST and NAMING commands, plus SESSIONs with STYLE=RAW. It works with
aum's client-side Python library (modulo a small change in the HELLO command,
required to allow protocol versioning, not yet implemented in the client lib).
(human)
This commit is contained in:
human
2004-04-13 04:38:52 +00:00
committed by zzz
parent 66ad54fbf0
commit 0d7f784773
8 changed files with 1235 additions and 0 deletions

View File

@ -0,0 +1,105 @@
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.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* SAM bridge implementation.
*
* @author human
*/
public class SAMBridge implements Runnable {
private final static Log _log = new Log(SAMBridge.class);
private ServerSocket serverSocket;
private boolean acceptConnections = true;
private final static int SAM_LISTENPORT = 7656;
/**
* Build a new SAM bridge listening on 127.0.0.1.
*
* @param listenPort The port to listen on
*/
public SAMBridge(int listenPort) {
this((String)null, listenPort);
}
/**
* Build a new SAM bridge.
*
* @param listenHost The network interface to listen on
* @param listenPort The port to listen on
*/
public SAMBridge(String listenHost, int listenPort) {
try {
if (listenHost != null) {
serverSocket = new ServerSocket(listenPort, 0,
InetAddress.getByName(listenHost));
_log.debug("SAM bridge listening on "
+ listenHost + ":" + listenPort);
} else {
serverSocket = new ServerSocket(listenPort);
_log.debug("SAM bridge listening on 0.0.0.0:" + listenPort);
}
} catch (Exception e) {
_log.error("Error starting SAM bridge on "
+ (listenHost == null ? "0.0.0.0" : listenHost)
+ ":" + listenPort, e);
}
}
public static void main(String args[]) {
SAMBridge bridge = new SAMBridge(SAM_LISTENPORT);
I2PThread t = new I2PThread(bridge, "SAMListener");
t.start();
}
public void run() {
try {
while (acceptConnections) {
Socket s = serverSocket.accept();
_log.debug("New connection from "
+ s.getInetAddress().toString() + ":"
+ s.getPort());
try {
SAMHandler handler = SAMHandlerFactory.createSAMHandler(s);
if (handler == null) {
_log.debug("SAM handler has not been instantiated");
try {
s.close();
} catch (IOException e) {}
continue;
}
handler.startHandling();
} catch (SAMException e) {
_log.error("SAM error: " + e.getMessage());
s.close();
}
}
} catch (Exception e) {
_log.error("Unexpected error while listening for connections", e);
} finally {
try {
_log.debug("Shutting down, closing server socket");
serverSocket.close();
} catch (IOException e) {}
}
}
}

View File

@ -0,0 +1,25 @@
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.
*
*/
/**
* Exception thrown by SAM methods
*
* @author human
*/
public class SAMException extends Exception {
public SAMException() {
super();
}
public SAMException(String s) {
super(s);
}
}

View File

@ -0,0 +1,104 @@
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.IOException;
import java.io.OutputStream;
import java.net.Socket;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Base class for SAM protocol handlers. It implements common
* methods, but is not able to actually parse the protocol itself:
* this task is delegated to subclasses.
*
* @author human
*/
public abstract class SAMHandler implements Runnable {
private final static Log _log = new Log(SAMHandler.class);
protected I2PThread thread = null;
private Object socketWLock = new Object(); // Guards writings on socket
private OutputStream socketOS = null; // Stream associated to socket
protected Socket socket = null;
protected int verMajor = 0;
protected int verMinor = 0;
private boolean stopHandler = false;
private Object stopLock = new Object();
/**
* Start handling the SAM connection, detaching an handling thread.
*
*/
public void startHandling() {
thread = new I2PThread(this, "SAMHandler");
thread.start();
}
/**
* Actually handle the SAM protocol.
*
*/
protected abstract void handle();
/**
* Write a byte array on the handler's socket. This method must
* always be used when writing data, unless you really know what
* you're doing.
*
* @param data A byte array to be written
*/
protected void writeBytes(byte[] data) throws IOException {
synchronized (socketWLock) {
if (socketOS == null) {
socketOS = socket.getOutputStream();
}
socketOS.write(data);
socketOS.flush();
}
}
/**
* Stop the SAM handler
*
*/
public void stopHandling() {
synchronized (stopLock) {
stopHandler = true;
}
}
/**
* Should the handler be stopped?
*
* @return True if the handler should be stopped, false otherwise
*/
protected boolean shouldStop() {
synchronized (stopLock) {
return stopHandler;
}
}
/**
* Get a string describing the handler.
*
* @return A String describing the handler;
*/
public abstract String toString();
public final void run() {
handle();
}
}

View File

@ -0,0 +1,179 @@
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.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.util.Log;
/**
* SAM handler factory class.
*/
public class SAMHandlerFactory {
private final static Log _log = new Log(SAMHandlerFactory.class);
/**
* Return the right SAM handler depending on the protocol version
* required by the client.
*
* @param s Socket attached to SAM client
*
* @return A SAM protocol handler
*/
public static SAMHandler createSAMHandler(Socket s) throws SAMException {
BufferedReader br;
StringTokenizer tok;
try {
br = new BufferedReader(new InputStreamReader(s.getInputStream(),
"ISO-8859-1"));
tok = new StringTokenizer(br.readLine(), " ");
} catch (IOException e) {
throw new SAMException("Error reading from socket: "
+ e.getMessage());
} catch (Exception e) {
throw new SAMException("Unexpected error: "
+ e.getMessage());
}
// Message format: HELLO VERSION MIN=v1 MAX=v2
if (tok.countTokens() != 4) {
throw new SAMException("Bad format in HELLO message");
}
if (!tok.nextToken().equals("HELLO")) {
throw new SAMException("Bad domain in HELLO message");
}
{
String opcode;
if (!(opcode = tok.nextToken()).equals("VERSION")) {
throw new SAMException("Unrecognized HELLO message opcode: \""
+ opcode + "\"");
}
}
Properties props;
props = SAMUtils.parseParams(tok);
if (props == null) {
throw new SAMException("No parameters in HELLO VERSION message");
}
String minVer = props.getProperty("MIN");
if (minVer == null) {
throw new SAMException("Missing MIN parameter in HELLO VERSION message");
}
String maxVer = props.getProperty("MAX");
if (maxVer == null) {
throw new SAMException("Missing MAX parameter in HELLO VERSION message");
}
String ver = chooseBestVersion(minVer, maxVer);
if (ver == null) {
// Let's answer negatively
try {
OutputStream out = s.getOutputStream();
out.write("HELLO REPLY RESULT=NOVERSION\n".getBytes("ISO-8859-1"));
return null;
} catch (UnsupportedEncodingException e) {
_log.error("Caught UnsupportedEncodingException ("
+ e.getMessage() + ")");
throw new SAMException("Character encoding error: "
+ e.getMessage());
} catch (IOException e) {
throw new SAMException("Error reading from socket: "
+ e.getMessage());
}
}
// Let's answer positively
try {
OutputStream out = s.getOutputStream();
out.write(("HELLO REPLY RESULT=OK VERSION="
+ ver + "\n").getBytes("ISO-8859-1"));
} catch (UnsupportedEncodingException e) {
_log.error("Caught UnsupportedEncodingException ("
+ e.getMessage() + ")");
throw new SAMException("Character encoding error: "
+ e.getMessage());
} catch (IOException e) {
throw new SAMException("Error writing to socket: "
+ e.getMessage());
}
// ...and instantiate the right SAM handler
int verMajor = getMajor(ver);
int verMinor = getMinor(ver);
SAMHandler handler;
switch (verMajor) {
case 1:
handler = new SAMv1Handler(s, verMajor, verMinor);
break;
default:
_log.error("BUG! Trying to initialize the wrong SAM version!");
throw new SAMException("BUG triggered! (handler instantiation)");
}
return handler;
}
/* Return the best version we can use, or null on failure */
private static String chooseBestVersion(String minVer, String maxVer) {
int minMajor = getMajor(minVer), minMinor = getMinor(minVer);
int maxMajor = getMajor(maxVer), maxMinor = getMinor(maxVer);
// Consistency checks
if ((minMajor == -1) || (minMinor == -1)
|| (maxMajor == -1) || (maxMinor == -1)) {
return null;
}
if (minMajor > maxMajor) {
return null;
} else if ((minMajor == maxMajor) && (minMinor > maxMinor)) {
return null;
}
if ((minMajor >= 1) && (minMinor >= 0)) {
return "1.0";
}
return null;
}
/* Get the major protocol version from a string */
private static int getMajor(String ver) {
try {
String major = ver.substring(0, ver.indexOf("."));
return Integer.parseInt(major);
} catch (NumberFormatException e) {
return -1;
} catch (ArrayIndexOutOfBoundsException e) {
return -1;
}
}
/* Get the minor protocol version from a string */
private static int getMinor(String ver) {
try {
String major = ver.substring(ver.indexOf(".") + 1);
return Integer.parseInt(major);
} catch (NumberFormatException e) {
return -1;
} catch (ArrayIndexOutOfBoundsException e) {
return -1;
}
}
}

View File

@ -0,0 +1,31 @@
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.IOException;
/**
* Interface for sending raw data to a SAM client
*/
public interface SAMRawReceiver {
/**
* Send a byte array to a SAM client, without informations
* regarding the sender.
*
* @param data Byte array to be written
*/
public void receiveRawBytes(byte data[]) throws IOException;
/**
* Stop receiving data.
*
*/
public void stopReceiving();
}

View File

@ -0,0 +1,213 @@
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.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.util.Properties;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.client.I2PSessionListener;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.HexDump;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* SAM RAW session class.
*
* @author human
*/
public class SAMRawSession {
private final static Log _log = new Log(SAMRawSession.class);
private I2PSession session = null;
private SAMRawReceiver recv = null;
private SAMRawSessionHandler handler = null;
/**
* Create a new SAM RAW session.
*
* @param dest Base64-encoded destination (private key)
* @param props Properties to setup the I2P session
* @param recv Object that will receive incoming data
*/
public SAMRawSession(String dest, Properties props,
SAMRawReceiver recv) throws DataFormatException, I2PSessionException {
ByteArrayInputStream bais;
bais = new ByteArrayInputStream(Base64.decode(dest));
initSAMRawSession(bais, props, recv);
}
/**
* Create a new SAM RAW session.
*
* @param destStream Input stream containing the destination keys
* @param props Properties to setup the I2P session
* @param recv Object that will receive incoming data
*/
public SAMRawSession(InputStream destStream, Properties props,
SAMRawReceiver recv) throws I2PSessionException {
initSAMRawSession(destStream, props, recv);
}
private void initSAMRawSession(InputStream destStream, Properties props,
SAMRawReceiver recv) throws I2PSessionException {
this.recv = recv;
_log.debug("SAM RAW session instantiated");
handler = new SAMRawSessionHandler(destStream, props);
Thread t = new I2PThread(handler, "SAMRawSessionHandler");
t.start();
}
/**
* Send bytes through a SAM RAW session.
*
* @param data Bytes to be sent
*
* @return True if the data was sent, false otherwise
*/
public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
Destination d = new Destination();
d.fromBase64(dest);
try {
return session.sendMessage(d, data);
} catch (I2PSessionException e) {
_log.error("I2PSessionException while sending data", e);
return false;
}
}
/**
* Close a SAM RAW session.
*
*/
public void close() {
handler.stopRunning();
}
/**
* SAM RAW session handler, running in its own thread
*
* @author human
*/
public class SAMRawSessionHandler implements Runnable, I2PSessionListener {
private Object runningLock = new Object();
private boolean stillRunning = true;
/**
* Create a new SAM RAW session handler
*
* @param destStream Input stream containing the destination keys
* @param props Properties to setup the I2P session
*/
public SAMRawSessionHandler(InputStream destStream, Properties props) throws I2PSessionException {
_log.debug("Instantiating new SAM RAW session handler");
I2PClient client = I2PClientFactory.createClient();
session = client.createSession(destStream, props);
_log.debug("Connecting I2P session...");
session.connect();
_log.debug("I2P session connected");
session.setSessionListener(this);
}
/**
* Stop a SAM RAW session handling thread
*
*/
public void stopRunning() {
synchronized (runningLock) {
stillRunning = false;
runningLock.notify();
}
}
public void run() {
_log.debug("SAM RAW session handler running");
synchronized (runningLock) {
while (stillRunning) {
try {
runningLock.wait();
} catch (InterruptedException ie) {}
}
_log.debug("Shutting down SAM RAW session handler");
recv.stopReceiving();
try {
_log.debug("Destroying I2P session...");
session.destroySession();
_log.debug("I2P session destroyed");
} catch (I2PSessionException e) {
_log.error("Error destroying I2P session", e);
}
}
}
public void disconnected(I2PSession session) {
_log.debug("I2P session disconnected");
stopRunning();
}
public void errorOccurred(I2PSession session, String message,
Throwable error) {
_log.debug("I2P error: " + message, error);
stopRunning();
}
public void messageAvailable(I2PSession session, int msgId, long size){
_log.debug("I2P message available (id: " + msgId
+ "; size: " + size + ")");
try {
byte msg[] = session.receiveMessage(msgId);
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Content of message " + msgId + ":\n"
+ HexDump.dump(msg));
}
recv.receiveRawBytes(msg);
} catch (IOException e) {
_log.error("Error forwarding message to receiver", e);
stopRunning();
} catch (I2PSessionException e) {
_log.error("Error fetching I2P message", e);
stopRunning();
}
}
public void reportAbuse(I2PSession session, int severity) {
_log.warn("Abuse reported (severity: " + severity + ")");
stopRunning();
}
}
}

View File

@ -0,0 +1,158 @@
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.IOException;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.naming.NamingService;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.Log;
/**
* Miscellaneous utility methods used by SAM protocol handlers.
*
* @author human
*/
public class SAMUtils {
private final static Log _log = new Log(SAMUtils.class);
/**
* Generate a random destination key
*
* @param priv Stream used to write the private key
* @param pub Stream used to write the public key (may be null)
*/
public static void genRandomKey(OutputStream priv, OutputStream pub) {
_log.debug("Generating random keys...");
try {
I2PClient c = I2PClientFactory.createClient();
Destination d = c.createDestination(priv);
priv.flush();
if (pub != null) {
d.writeBytes(pub);
pub.flush();
}
} catch (I2PException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Check whether a base64-encoded dest is valid
*
* @param dest The base64-encoded destination to be checked
*
* @return True if the destination is valid, false otherwise
*/
public static boolean checkDestination(String dest) {
try {
Destination d = new Destination();
d.fromBase64(dest);
return true;
} catch (DataFormatException e) {
return false;
}
}
/**
* Resolved the specified hostname.
*
* @param name Hostname to be resolved
* @param pubKey A stream to write the Destination public key (may be null)
*
* @return the Destination for the specified hostname, or null if not found
*/
public static Destination lookupHost(String name, OutputStream pubKey) {
NamingService ns = NamingService.getInstance();
Destination dest = ns.lookup(name);
if ((pubKey != null) && (dest != null)) {
try {
dest.writeBytes(pubKey);
} catch (IOException e) {
e.printStackTrace();
return null;
} catch (DataFormatException e) {
e.printStackTrace();
return null;
}
}
return dest;
}
/**
* Parse SAM parameters, and put them into a Propetries object
*
* @param tok A StringTokenizer pointing to the SAM parameters
*
* @return A Properties object with the parsed SAM parameters
*/
public static Properties parseParams(StringTokenizer tok) {
int pos, ntoks = tok.countTokens();
String token, param, value;
Properties props = new Properties();
for (int i = 0; i < ntoks; ++i) {
token = tok.nextToken();
pos = token.indexOf("=");
if (pos == -1) {
_log.debug("Error in params format");
return null;
}
param = token.substring(0, pos);
value = token.substring(pos + 1);
props.setProperty(param, value);
}
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Parsed properties: " + dumpProperties(props));
}
return props;
}
/* Dump a Properties object in an human-readable form */
private static String dumpProperties(Properties props) {
Enumeration enum = props.propertyNames();
String msg = "";
String key, val;
boolean firstIter = true;
while (enum.hasMoreElements()) {
key = (String)enum.nextElement();
val = props.getProperty(key);
if (!firstIter) {
msg += ";";
} else {
firstIter = false;
}
msg += " \"" + key + "\" -> \"" + val + "\"";
}
return msg;
}
}

View File

@ -0,0 +1,420 @@
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.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.client.I2PSessionException;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.Log;
/**
* Class able to handle a SAM version 1 client connections.
*
* @author human
*/
public class SAMv1Handler extends SAMHandler implements SAMRawReceiver {
private final static Log _log = new Log(SAMv1Handler.class);
private final static int IN_BUFSIZE = 2048;
private SAMRawSession rawSession = null;
private SAMRawSession datagramSession = null;
private SAMRawSession streamSession = null;
/**
* Create a new SAM version 1 handler. This constructor expects
* that the SAM HELLO message has been still answered (and
* stripped) from the socket input stream.
*
* @param s Socket attached to a SAM client
*/
public SAMv1Handler(Socket s, int verMajor, int verMinor) throws SAMException{
_log.debug("SAM version 1 handler instantiated");
this.verMajor = verMajor;
this.verMinor = verMinor;
if ((this.verMajor != 1) || (this.verMinor != 0)) {
throw new SAMException("BUG! Wrong protocol version!");
}
this.socket = s;
this.verMajor = verMajor;
this.verMinor = verMinor;
}
public void handle() {
String msg, domain, opcode;
boolean canContinue = false;
ByteArrayOutputStream buf = new ByteArrayOutputStream(IN_BUFSIZE);
StringTokenizer tok;
this.thread.setName("SAMv1Handler");
_log.debug("SAM handling started");
try {
InputStream in = socket.getInputStream();
int b = -1;
while (true) {
if (shouldStop()) {
_log.debug("Stop request found");
break;
}
while ((b = in.read()) != -1) {
if (b == '\n') {
break;
}
buf.write(b);
}
if (b == -1) {
_log.debug("Connection closed by client");
break;
}
msg = buf.toString("ISO-8859-1");
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("New message received: " + msg);
}
buf.reset();
tok = new StringTokenizer(msg, " ");
if (tok.countTokens() < 2) {
// This is not a correct message, for sure
_log.debug("Error in message format");
break;
}
domain = tok.nextToken();
opcode = tok.nextToken();
_log.debug("Parsing (domain: \"" + domain + "\"; opcode: \""
+ opcode + "\")");
if (domain.equals("RAW")) {
canContinue = execRawMessage(opcode, tok);
} else if (domain.equals("SESSION")) {
canContinue = execSessionMessage(opcode, tok);
} else if (domain.equals("DEST")) {
canContinue = execDestMessage(opcode, tok);
} else if (domain.equals("NAMING")) {
canContinue = execNamingMessage(opcode, tok);
} else {
_log.debug("Unrecognized message domain: \""
+ domain + "\"");
break;
}
if (!canContinue) {
break;
}
}
} catch (UnsupportedEncodingException e) {
_log.error("Caught UnsupportedEncodingException ("
+ e.getMessage() + ")");
} catch (IOException e) {
_log.debug("Caught IOException ("
+ e.getMessage() + ")");
} catch (Exception e) {
_log.error("Unexpected exception", e);
} finally {
_log.debug("Stopping handler");
try {
this.socket.close();
} catch (IOException e) {
_log.error("Error closing socket: " + e.getMessage());
}
if (rawSession != null) {
rawSession.close();
}
if (datagramSession != null) {
datagramSession.close();
}
if (streamSession != null) {
streamSession.close();
}
}
}
/* Parse and execute a SESSION message */
private boolean execSessionMessage(String opcode, StringTokenizer tok) {
Properties props = null;
if (opcode.equals("CREATE")) {
if ((rawSession != null) || (datagramSession != null)
|| (streamSession != null)) {
_log.debug("Trying to create a session, but one still exists");
return false;
}
props = SAMUtils.parseParams(tok);
if (props == null) {
return false;
}
String dest = props.getProperty("DESTINATION");
if (dest == null) {
_log.debug("SESSION DESTINATION parameter not specified");
return false;
}
props.remove("DESTINATION");
String style = props.getProperty("STYLE");
if (style == null) {
_log.debug("SESSION STYLE parameter not specified");
return false;
}
props.remove("STYLE");
try {
if (style.equals("RAW")) {
try {
if (dest.equals("TRANSIENT")) {
_log.debug("TRANSIENT destination requested");
ByteArrayOutputStream priv = new ByteArrayOutputStream();
SAMUtils.genRandomKey(priv, null);
dest = Base64.encode(priv.toByteArray());
}
rawSession = new SAMRawSession (dest, props, this);
writeBytes(("SESSION STATUS RESULT=OK DESTINATION=" + dest + "\n").getBytes("ISO-8859-1"));
} catch (DataFormatException e) {
_log.debug("Invalid destination specified");
writeBytes(("SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + "\n").getBytes("ISO-8859-1"));
return true;
} catch (I2PSessionException e) {
_log.debug("I2P error when instantiating RAW session", e);
writeBytes(("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n").getBytes("ISO-8859-1"));
return true;
}
} else {
_log.debug("Unrecognized SESSION STYLE: \"" + style + "\"");
return false;
}
} catch (UnsupportedEncodingException e) {
_log.error("Caught UnsupportedEncodingException ("
+ e.getMessage() + ")");
return false;
} catch (IOException e) {
_log.error("Caught IOException while parsing SESSION message ("
+ e.getMessage() + ")");
return false;
}
return true;
} else {
_log.debug("Unrecognized SESSION message opcode: \""
+ opcode + "\"");
return false;
}
}
/* Parse and execute a DEST message*/
private boolean execDestMessage(String opcode, StringTokenizer tok) {
if (opcode.equals("GENERATE")) {
if (tok.countTokens() > 0) {
_log.debug("Bad format in DEST GENERATE message");
return false;
}
try {
ByteArrayOutputStream priv = new ByteArrayOutputStream();
ByteArrayOutputStream pub = new ByteArrayOutputStream();
SAMUtils.genRandomKey(priv, pub);
writeBytes(("DEST REPLY"
+ " PUB="
+ Base64.encode(pub.toByteArray())
+ " PRIV="
+ Base64.encode(priv.toByteArray())
+ "\n").getBytes("ISO-8859-1"));
} catch (UnsupportedEncodingException e) {
_log.error("Caught UnsupportedEncodingException ("
+ e.getMessage() + ")");
return false;
} catch (IOException e) {
_log.debug("IOException while executing DEST message", e);
return false;
}
} else {
_log.debug("Unrecognized DEST message opcode: \"" + opcode + "\"");
return false;
}
return true;
}
/* Parse and execute a NAMING message */
private boolean execNamingMessage(String opcode, StringTokenizer tok) {
Properties props = null;
if (opcode.equals("LOOKUP")) {
props = SAMUtils.parseParams(tok);
if (props == null) {
return false;
}
String name = props.getProperty("NAME");
if (name == null) {
_log.debug("Name to resolve not specified");
return false;
}
try {
ByteArrayOutputStream pubKey = new ByteArrayOutputStream();
Destination dest = SAMUtils.lookupHost(name, pubKey);
if (dest == null) {
writeBytes("NAMING REPLY RESULT=KEY_NOT_FOUND\n".getBytes("ISP-8859-1"));
return true;
}
writeBytes(("NAMING REPLY RESULT=OK NAME=" + name
+ " VALUE=" + Base64.encode(pubKey.toByteArray())
+ "\n").getBytes("ISO-8859-1"));
return true;
} catch (UnsupportedEncodingException e) {
_log.error("Caught UnsupportedEncodingException ("
+ e.getMessage() + ")");
return false;
} catch (IOException e) {
_log.debug("Caught IOException while parsing NAMING message",
e);
return false;
}
} else {
_log.debug("Unrecognized NAMING message opcode: \""
+ opcode + "\"");
return false;
}
}
public String toString() {
return "SAM v1 handler (client: "
+ this.socket.getInetAddress().toString() + ":"
+ this.socket.getPort() + ")";
}
/* Parse and execute a RAW message */
private boolean execRawMessage(String opcode, StringTokenizer tok) {
Properties props = null;
if (rawSession == null) {
_log.debug("RAW message received, but no RAW session exists");
return false;
}
if (opcode.equals("SEND")) {
props = SAMUtils.parseParams(tok);
if (props == null) {
return false;
}
String dest = props.getProperty("DESTINATION");
if (dest == null) {
_log.debug("Destination not specified in RAW SEND message");
return false;
}
int size;
{
String strsize = props.getProperty("SIZE");
if (strsize == null) {
_log.debug("Size not specified in RAW SEND message");
return false;
}
try {
size = Integer.parseInt(strsize);
} catch (NumberFormatException e) {
_log.debug("Invalid RAW SEND size specified: " + strsize);
return false;
}
if (!checkSize(size)) {
_log.debug("Specified size (" + size
+ ") is out of protocol limits");
return false;
}
}
try {
DataInputStream in = new DataInputStream(socket.getInputStream());
byte[] data = new byte[size];
in.readFully(data);
if (!rawSession.sendBytes(dest, data)) {
_log.error("RAW SEND failed");
return false;
}
return true;
} catch (EOFException e) {
_log.debug("Too few bytes with RAW SEND message (expected: "
+ size);
return false;
} catch (IOException e) {
_log.debug("Caught IOException while parsing RAW SEND message",
e);
return false;
} catch (DataFormatException e) {
_log.debug("Invalid key specified with RAW SEND message",
e);
return false;
}
} else {
_log.debug("Unrecognized RAW message opcode: \""
+ opcode + "\"");
return false;
}
}
/* Check whether a size is inside the limits allowed by this protocol */
private boolean checkSize(int size) {
return ((size >= 1) && (size <= 32768));
}
// SAMRawReceiver implementation
public void receiveRawBytes(byte data[]) throws IOException {
if (rawSession == null) {
_log.error("BUG! Trying to write raw bytes, but session is null!");
throw new NullPointerException("BUG! RAW session is null!");
}
ByteArrayOutputStream msg = new ByteArrayOutputStream();
msg.write(("RAW RECEIVED SIZE=" + data.length + "\n").getBytes());
msg.write(data);
writeBytes(msg.toByteArray());
}
public void stopReceiving() {
_log.debug("stopReceiving() invoked");
try {
this.socket.close();
} catch (IOException e) {
_log.error("Error closing socket: " + e.getMessage());
}
}
}