diff --git a/apps/sam/java/src/net/i2p/sam/SAMDatagramReceiver.java b/apps/sam/java/src/net/i2p/sam/SAMDatagramReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..b47568416d666c583bcee372a5f6dad2753fc57d --- /dev/null +++ b/apps/sam/java/src/net/i2p/sam/SAMDatagramReceiver.java @@ -0,0 +1,32 @@ +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 net.i2p.data.Destination; + +/** + * Interface for sending raw data to a SAM client + */ +public interface SAMDatagramReceiver { + + /** + * Send a byte array to a SAM client. + * + * @param data Byte array to be received + */ + public void receiveDatagramBytes(Destination sender, byte data[]) throws IOException; + + /** + * Stop receiving data. + * + */ + public void stopDatagramReceiving(); +} diff --git a/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java new file mode 100644 index 0000000000000000000000000000000000000000..3eb9492cc904d6d934b987d46cff3be9db009799 --- /dev/null +++ b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java @@ -0,0 +1,111 @@ +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.datagram.I2PDatagramDissector; +import net.i2p.client.datagram.I2PDatagramMaker; +import net.i2p.client.datagram.I2PInvalidDatagramException; +import net.i2p.client.I2PSessionException; +import net.i2p.data.DataFormatException; +import net.i2p.data.Destination; +import net.i2p.util.Log; + +/** + * SAM DATAGRAM session class. + * + * @author human + */ +public class SAMDatagramSession extends SAMMessageSession { + + private final static Log _log = new Log(SAMDatagramSession.class); + + private SAMDatagramReceiver recv = null; + + private I2PDatagramMaker dgramMaker; + private I2PDatagramDissector dgramDissector = new I2PDatagramDissector(); + /** + * Create a new SAM DATAGRAM 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 SAMDatagramSession(String dest, Properties props, + SAMDatagramReceiver recv) throws IOException, DataFormatException, I2PSessionException { + super(dest, props); + + this.recv = recv; + dgramMaker = new I2PDatagramMaker(getI2PSession()); + } + + /** + * Create a new SAM DATAGRAM 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 SAMDatagramSession(InputStream destStream, Properties props, + SAMDatagramReceiver recv) throws IOException, DataFormatException, I2PSessionException { + super(destStream, props); + + this.recv = recv; + dgramMaker = new I2PDatagramMaker(getI2PSession()); + } + + /** + * Send bytes through a SAM DATAGRAM 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 { + byte[] dgram = dgramMaker.makeI2PDatagram(data); + + return sendBytesThroughMessageSession(dest, dgram); + } + + protected void messageReceived(byte[] msg) { + byte[] payload; + Destination sender; + try { + dgramDissector.loadI2PDatagram(msg); + sender = dgramDissector.getSender(); + payload = dgramDissector.extractPayload(); + } catch (DataFormatException e) { + if (_log.shouldLog(Log.DEBUG)) { + _log.debug("Dropping ill-formatted I2P repliable datagram"); + } + return; + } catch (I2PInvalidDatagramException e) { + if (_log.shouldLog(Log.DEBUG)) { + _log.debug("Dropping ill-signed I2P repliable datagram"); + } + return; + } + + try { + recv.receiveDatagramBytes(sender, payload); + } catch (IOException e) { + _log.error("Error forwarding message to receiver", e); + close(); + } + } + + protected void shutDown() { + recv.stopDatagramReceiving(); + } +} diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandler.java b/apps/sam/java/src/net/i2p/sam/SAMHandler.java index bfd146ffbba7fefc06da0ad51ac9055ed9391a5b..be68e09e1169480c25294184c394b95e3305f212 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMHandler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMHandler.java @@ -9,6 +9,7 @@ package net.i2p.sam; */ import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; @@ -29,20 +30,36 @@ public abstract class SAMHandler implements Runnable { protected I2PThread thread = null; private Object socketWLock = new Object(); // Guards writings on socket + private Socket socket = null; 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(); + private boolean stopHandler = false; + + /** + * SAMHandler constructor (to be called by subclasses) + * + * @param s Socket attached to a SAM client + * @param verMajor SAM major version to manage + * @param verMinor SAM minor version to manage + */ + protected SAMHandler(Socket s, + int verMajor, int verMinor) throws IOException { + socket = s; + socketOS = socket.getOutputStream(); + + this.verMajor = verMajor; + this.verMinor = verMinor; + } /** * Start handling the SAM connection, detaching an handling thread. * */ - public void startHandling() { + public final void startHandling() { thread = new I2PThread(this, "SAMHandler"); thread.start(); } @@ -53,6 +70,14 @@ public abstract class SAMHandler implements Runnable { */ protected abstract void handle(); + /** + * Get the input stream of the socket connected to the SAM client + * + */ + protected final InputStream getClientSocketInputStream() throws IOException { + return socket.getInputStream(); + } + /** * Write a byte array on the handler's socket. This method must * always be used when writing data, unless you really know what @@ -60,11 +85,8 @@ public abstract class SAMHandler implements Runnable { * * @param data A byte array to be written */ - protected void writeBytes(byte[] data) throws IOException { + protected final void writeBytes(byte[] data) throws IOException { synchronized (socketWLock) { - if (socketOS == null) { - socketOS = socket.getOutputStream(); - } socketOS.write(data); socketOS.flush(); } @@ -79,7 +101,7 @@ public abstract class SAMHandler implements Runnable { * * @return True is the string was successfully written, false otherwise */ - protected boolean writeString(String str) { + protected final boolean writeString(String str) { try { writeBytes(str.getBytes("ISO-8859-1")); } catch (IOException e) { @@ -90,11 +112,19 @@ public abstract class SAMHandler implements Runnable { return true; } + /** + * Close the socket connected to the SAM client. + * + */ + protected final void closeClientSocket() throws IOException { + socket.close(); + } + /** * Stop the SAM handler * */ - public void stopHandling() { + public final void stopHandling() { synchronized (stopLock) { stopHandler = true; } @@ -105,7 +135,7 @@ public abstract class SAMHandler implements Runnable { * * @return True if the handler should be stopped, false otherwise */ - protected boolean shouldStop() { + protected final boolean shouldStop() { synchronized (stopLock) { return stopHandler; } @@ -116,7 +146,13 @@ public abstract class SAMHandler implements Runnable { * * @return A String describing the handler; */ - public abstract String toString(); + public final String toString() { + return ("SAM handler (class: " + this.getClass().getName() + + "; SAM version: " + verMajor + "." + verMinor + + "; client: " + + this.socket.getInetAddress().toString() + ":" + + this.socket.getPort() + ")"); + } public final void run() { handle(); diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java index f7fdb8249070bc16b3be9e47969cfee66e539fd2..26dbfd288fc3d2e8c2b8c87fa4cce416c0d8181a 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java +++ b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java @@ -36,18 +36,23 @@ public class SAMHandlerFactory { */ public static SAMHandler createSAMHandler(Socket s) throws SAMException { BufferedReader br; + String line; StringTokenizer tok; try { br = new BufferedReader(new InputStreamReader(s.getInputStream(), "ISO-8859-1")); - tok = new StringTokenizer(br.readLine(), " "); + line = br.readLine(); + if (line == null) { + _log.debug("Connection closed by client"); + return null; + } + tok = new StringTokenizer(line, " "); } catch (IOException e) { throw new SAMException("Error reading from socket: " + e.getMessage()); } catch (Exception e) { - throw new SAMException("Unexpected error: " - + e.getMessage()); + throw new SAMException("Unexpected error: " + e.getMessage()); } // Message format: HELLO VERSION MIN=v1 MAX=v2 @@ -118,15 +123,20 @@ public class SAMHandlerFactory { 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)"); - } + try { + 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! (in handler instantiation)"); + } + } catch (IOException e) { + _log.error("IOException caught during SAM handler instantiation"); + return null; + } return handler; } diff --git a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java new file mode 100644 index 0000000000000000000000000000000000000000..15bd926c19e8098bba13e3f82becf6b575f98cf8 --- /dev/null +++ b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java @@ -0,0 +1,246 @@ +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; + +/** + * Base abstract class for SAM message-based sessions. + * + * @author human + */ +public abstract class SAMMessageSession { + + private final static Log _log = new Log(SAMMessageSession.class); + + private I2PSession session = null; + + private SAMMessageSessionHandler handler = null; + + /** + * Initialize a new SAM message-based session. + * + * @param dest Base64-encoded destination (private key) + * @param props Properties to setup the I2P session + */ + protected SAMMessageSession(String dest, Properties props) throws IOException, DataFormatException, I2PSessionException { + ByteArrayInputStream bais; + + bais = new ByteArrayInputStream(Base64.decode(dest)); + + initSAMMessageSession(bais, props); + } + + /** + * Initialize a new SAM message-based session. + * + * @param destStream Input stream containing the destination keys + * @param props Properties to setup the I2P session + */ + protected SAMMessageSession(InputStream destStream, Properties props) throws IOException, DataFormatException, I2PSessionException { + initSAMMessageSession(destStream, props); + } + + private void initSAMMessageSession (InputStream destStream, Properties props) throws IOException, DataFormatException, I2PSessionException { + + _log.debug("Initializing SAM message-based session"); + + handler = new SAMMessageSessionHandler(destStream, props); + + Thread t = new I2PThread(handler, "SAMMessageSessionHandler"); + t.start(); + } + + /** + * Get the SAM message-based session Destination. + * + * @return The SAM message-based session Destination. + */ + public Destination getDestination() { + return session.getMyDestination(); + } + + /** + * Send bytes through a SAM message-based session. + * + * @param data Bytes to be sent + * + * @return True if the data was sent, false otherwise + */ + public abstract boolean sendBytes(String dest, byte[] data) throws DataFormatException; + + /** + * Actually send bytes through the SAM message-based session I2PSession + * (er...). + * + * @param data Bytes to be sent + * + * @return True if the data was sent, false otherwise + */ + protected boolean sendBytesThroughMessageSession(String dest, byte[] data) throws DataFormatException { + Destination d = new Destination(); + d.fromBase64(dest); + + if (_log.shouldLog(Log.DEBUG)) { + _log.debug("Sending " + data.length + " bytes to " + dest); + } + + try { + return session.sendMessage(d, data); + } catch (I2PSessionException e) { + _log.error("I2PSessionException while sending data", e); + return false; + } + } + + /** + * Close a SAM message-based session. + * + */ + public void close() { + handler.stopRunning(); + } + + /** + * Handle a new received message + */ + protected abstract void messageReceived(byte[] msg); + + /** + * Do whatever is needed to shutdown the SAM session + */ + protected abstract void shutDown(); + + + /** + * Get the I2PSession object used by the SAM message-based session. + * + * @return The I2PSession of the SAM message-based session + */ + protected I2PSession getI2PSession() { + return session; + } + + /** + * SAM message-based session handler, running in its own thread + * + * @author human + */ + public class SAMMessageSessionHandler implements Runnable, I2PSessionListener { + + private Object runningLock = new Object(); + private boolean stillRunning = true; + + /** + * Create a new SAM message-based session handler + * + * @param destStream Input stream containing the destination keys + * @param props Properties to setup the I2P session + */ + public SAMMessageSessionHandler(InputStream destStream, Properties props) throws I2PSessionException { + _log.debug("Instantiating new SAM message-based 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 message-based session handling thread + * + */ + public final void stopRunning() { + synchronized (runningLock) { + stillRunning = false; + runningLock.notify(); + } + } + + public void run() { + + _log.debug("SAM message-based session handler running"); + + synchronized (runningLock) { + while (stillRunning) { + try { + runningLock.wait(); + } catch (InterruptedException ie) {} + } + } + + _log.debug("Shutting down SAM message-based session handler"); + + shutDown(); + + 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){ + if (_log.shouldLog(Log.DEBUG)) { + _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)); + } + + messageReceived(msg); + } 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(); + } + } +} diff --git a/apps/sam/java/src/net/i2p/sam/SAMRawSession.java b/apps/sam/java/src/net/i2p/sam/SAMRawSession.java index 7ced494cc3c5814d66e18578ebd6660775ee14ec..13b4785c00cca2d028ecee577f6a78e5cbc53db0 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMRawSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMRawSession.java @@ -14,16 +14,8 @@ 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; /** @@ -31,16 +23,11 @@ import net.i2p.util.Log; * * @author human */ -public class SAMRawSession { +public class SAMRawSession extends SAMMessageSession { 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. * @@ -49,12 +36,10 @@ public class SAMRawSession { * @param recv Object that will receive incoming data */ public SAMRawSession(String dest, Properties props, - SAMRawReceiver recv) throws IOException, DataFormatException, I2PSessionException { - ByteArrayInputStream bais; - - bais = new ByteArrayInputStream(Base64.decode(dest)); + SAMRawReceiver recv) throws IOException, DataFormatException, I2PSessionException { + super(dest, props); - initSAMRawSession(bais, props, recv); + this.recv = recv; } /** @@ -65,29 +50,10 @@ public class SAMRawSession { * @param recv Object that will receive incoming data */ public SAMRawSession(InputStream destStream, Properties props, - SAMRawReceiver recv) throws IOException, DataFormatException, I2PSessionException { - initSAMRawSession(destStream, props, recv); - } - - private void initSAMRawSession(InputStream destStream, Properties props, - SAMRawReceiver recv) throws IOException, DataFormatException, I2PSessionException { - this.recv = recv; - - _log.debug("SAM RAW session instantiated"); - - handler = new SAMRawSessionHandler(destStream, props); + SAMRawReceiver recv) throws IOException, DataFormatException, I2PSessionException { + super(destStream, props); - Thread t = new I2PThread(handler, "SAMRawSessionHandler"); - t.start(); - } - - /** - * Get the SAM RAW session Destination. - * - * @return The SAM RAW session Destination. - */ - public Destination getDestination() { - return session.getMyDestination(); + this.recv = recv; } /** @@ -98,130 +64,19 @@ public class SAMRawSession { * @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); - - if (_log.shouldLog(Log.DEBUG)) { - _log.debug("Sending " + data.length + " bytes to " + dest); - } - - try { - return session.sendMessage(d, data); - } catch (I2PSessionException e) { - _log.error("I2PSessionException while sending data", e); - return false; - } + return sendBytesThroughMessageSession(dest, data); } - /** - * Close a SAM RAW session. - * - */ - public void close() { - handler.stopRunning(); + protected void messageReceived(byte[] msg) { + try { + recv.receiveRawBytes(msg); + } catch (IOException e) { + _log.error("Error forwarding message to receiver", e); + close(); + } } - /** - * 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.stopRawReceiving(); - - 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(); - } + protected void shutDown() { + recv.stopRawReceiving(); } } diff --git a/apps/sam/java/src/net/i2p/sam/SAMUtils.java b/apps/sam/java/src/net/i2p/sam/SAMUtils.java index 778c0d06f08430e04a4df8fcb0e115d8747312aa..f4ef4ff84f6f099da1c0b64a84ddc835744735a5 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMUtils.java +++ b/apps/sam/java/src/net/i2p/sam/SAMUtils.java @@ -40,16 +40,16 @@ public class SAMUtils { * @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(); - } + _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) { @@ -57,28 +57,6 @@ public class SAMUtils { } } - /** - * Get the Base64 representation of a Destination public key - * - * @param d A Destination - * - * @return A String representing the Destination public key - */ - public static String getBase64DestinationPubKey(Destination d) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - try { - d.writeBytes(baos); - return Base64.encode(baos.toByteArray()); - } catch (IOException e) { - _log.error("getDestinationPubKey(): caught IOException", e); - return null; - } catch (DataFormatException e) { - _log.error("getDestinationPubKey(): caught DataFormatException",e); - return null; - } - } - /** * Check whether a base64-encoded dest is valid * @@ -87,13 +65,13 @@ public class SAMUtils { * @return True if the destination is valid, false otherwise */ public static boolean checkDestination(String dest) { - try { - Destination d = new Destination(); - d.fromBase64(dest); + try { + Destination d = new Destination(); + d.fromBase64(dest); - return true; + return true; } catch (DataFormatException e) { - return false; + return false; } } @@ -106,22 +84,22 @@ public class SAMUtils { * @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; + 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; } /** @@ -132,55 +110,55 @@ public class SAMUtils { * @return Properties with the parsed SAM params, or null if none is found */ public static Properties parseParams(StringTokenizer tok) { - int pos, nprops = 0, 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); - nprops += 1; - } - - if (_log.shouldLog(Log.DEBUG)) { - _log.debug("Parsed properties: " + dumpProperties(props)); - } - - if (nprops != 0) { - return props; - } else { - return null; - } + int pos, nprops = 0, 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); + nprops += 1; + } + + if (_log.shouldLog(Log.DEBUG)) { + _log.debug("Parsed properties: " + dumpProperties(props)); + } + + if (nprops != 0) { + return props; + } else { + return null; + } } /* 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; + 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; } } diff --git a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java index 1d930d1499f7298694e0d009ae11c8d8c17fb271..cc45e7b667f58153ba850e7d199fd5e2b2b54736 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java @@ -33,14 +33,14 @@ import net.i2p.util.Log; * * @author human */ -public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStreamReceiver { +public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramReceiver, SAMStreamReceiver { 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 SAMDatagramSession datagramSession = null; private SAMStreamSession streamSession = null; /** @@ -49,20 +49,16 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea * stripped) from the socket input stream. * * @param s Socket attached to a SAM client + * @param verMajor SAM major version to manage (should be 1) + * @param verMinor SAM minor version to manage */ - public SAMv1Handler(Socket s, int verMajor, int verMinor) throws SAMException{ + public SAMv1Handler(Socket s, int verMajor, int verMinor) throws SAMException, IOException { + super(s, verMajor, verMinor); _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() { @@ -76,7 +72,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea _log.debug("SAM handling started"); try { - InputStream in = socket.getInputStream(); + InputStream in = getClientSocketInputStream(); int b = -1; while (true) { @@ -118,6 +114,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea if (domain.equals("STREAM")) { canContinue = execStreamMessage(opcode, props); + } else if (domain.equals("DATAGRAM")) { + canContinue = execDatagramMessage(opcode, props); } else if (domain.equals("RAW")) { canContinue = execRawMessage(opcode, props); } else if (domain.equals("SESSION")) { @@ -147,7 +145,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea } finally { _log.debug("Stopping handler"); try { - this.socket.close(); + closeClientSocket(); } catch (IOException e) { _log.error("Error closing socket: " + e.getMessage()); } @@ -204,6 +202,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea if (style.equals("RAW")) { rawSession = new SAMRawSession(dest, props, this); + } else if (style.equals("DATAGRAM")) { + datagramSession = new SAMDatagramSession(dest, props,this); } else if (style.equals("STREAM")) { String dir = props.getProperty("DIRECTION"); if (dir == null) { @@ -305,7 +305,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea return writeString("NAMING REPLY RESULT=OK NAME=" + name + " VALUE=" - + SAMUtils.getBase64DestinationPubKey(dest) + + dest.toBase64() + "\n"); } else { _log.debug("Unrecognized NAMING message opcode: \"" @@ -314,10 +314,82 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea } } + + /* Parse and execute a DATAGRAM message */ + private boolean execDatagramMessage(String opcode, Properties props) { + if (datagramSession == null) { + _log.error("DATAGRAM message received, but no DATAGRAM session exists"); + return false; + } + + if (opcode.equals("SEND")) { + if (props == null) { + _log.debug("No parameters specified in DATAGRAM SEND message"); + return false; + } + + String dest = props.getProperty("DESTINATION"); + if (dest == null) { + _log.debug("Destination not specified in DATAGRAM SEND message"); + return false; + } + + int size; + { + String strsize = props.getProperty("SIZE"); + if (strsize == null) { + _log.debug("Size not specified in DATAGRAM SEND message"); + return false; + } + try { + size = Integer.parseInt(strsize); + } catch (NumberFormatException e) { + _log.debug("Invalid DATAGRAM SEND size specified: " + strsize); + return false; + } + if (!checkDatagramSize(size)) { + _log.debug("Specified size (" + size + + ") is out of protocol limits"); + return false; + } + } + + try { + DataInputStream in = new DataInputStream(getClientSocketInputStream()); + byte[] data = new byte[size]; + + in.readFully(data); + + if (!datagramSession.sendBytes(dest, data)) { + _log.error("DATAGRAM SEND failed"); + return false; + } + + return true; + } catch (EOFException e) { + _log.debug("Too few bytes with DATAGRAM SEND message (expected: " + + size); + return false; + } catch (IOException e) { + _log.debug("Caught IOException while parsing DATAGRAM SEND message", + e); + return false; + } catch (DataFormatException e) { + _log.debug("Invalid key specified with DATAGRAM SEND message", + e); + return false; + } + } else { + _log.debug("Unrecognized DATAGRAM message opcode: \"" + + opcode + "\""); + return false; + } + } + /* Parse and execute a RAW message */ private boolean execRawMessage(String opcode, Properties props) { if (rawSession == null) { - _log.debug("RAW message received, but no RAW session exists"); + _log.error("RAW message received, but no RAW session exists"); return false; } @@ -354,7 +426,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea } try { - DataInputStream in = new DataInputStream(socket.getInputStream()); + DataInputStream in = new DataInputStream(getClientSocketInputStream()); byte[] data = new byte[size]; in.readFully(data); @@ -388,7 +460,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea /* Parse and execute a STREAM message */ private boolean execStreamMessage(String opcode, Properties props) { if (streamSession == null) { - _log.debug("STREAM message received, but no STREAM session exists"); + _log.error("STREAM message received, but no STREAM session exists"); return false; } @@ -434,7 +506,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea } try { - DataInputStream in = new DataInputStream(socket.getInputStream()); + DataInputStream in = new DataInputStream(getClientSocketInputStream()); byte[] data = new byte[size]; in.readFully(data); @@ -535,16 +607,15 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea } } - public String toString() { - return "SAM v1 handler (client: " - + this.socket.getInetAddress().toString() + ":" - + this.socket.getPort() + ")"; - } - /* Check whether a size is inside the limits allowed by this protocol */ private boolean checkSize(int size) { return ((size >= 1) && (size <= 32768)); } + + /* Check whether a size is inside the limits allowed by this protocol */ + private boolean checkDatagramSize(int size) { + return ((size >= 1) && (size <= 31744)); + } // SAMRawReceiver implementation public void receiveRawBytes(byte data[]) throws IOException { @@ -571,7 +642,39 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea } try { - this.socket.close(); + closeClientSocket(); + } catch (IOException e) { + _log.error("Error closing socket: " + e.getMessage()); + } + } + + // SAMDatagramReceiver implementation + public void receiveDatagramBytes(Destination sender, byte data[]) throws IOException { + if (datagramSession == null) { + _log.error("BUG! Received datagram bytes, but session is null!"); + throw new NullPointerException("BUG! DATAGRAM session is null!"); + } + + ByteArrayOutputStream msg = new ByteArrayOutputStream(); + + msg.write(("DATAGRAM RECEIVED DESTINATION=" + sender.toBase64() + + " SIZE=" + data.length + + "\n").getBytes("ISO-8859-1")); + msg.write(data); + + writeBytes(msg.toByteArray()); + } + + public void stopDatagramReceiving() { + _log.debug("stopDatagramReceiving() invoked"); + + if (datagramSession == null) { + _log.error("BUG! Got datagram receiving stop, but session is null!"); + throw new NullPointerException("BUG! DATAGRAM session is null!"); + } + + try { + closeClientSocket(); } catch (IOException e) { _log.error("Error closing socket: " + e.getMessage()); } @@ -585,7 +688,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea } if (!writeString("STREAM CONNECTED DESTINATION=" - + SAMUtils.getBase64DestinationPubKey(d) + + d.toBase64() + " ID=" + id + "\n")) { throw new IOException("Error notifying connection to SAM client"); } @@ -629,7 +732,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea } try { - this.socket.close(); + closeClientSocket(); } catch (IOException e) { _log.error("Error closing socket: " + e.getMessage()); }