* more verbose errors (include MESSAGE data on the I2P_ERROR reply, not just in the log)
* don't create excess I2PAppContexts (if any old context will do, use the global) keep track of keys per spec (when DESTINATION=blah, create (or reuse) the destination private keys). we still need to persist this data though. * the DESTINATION in the SESSION STATUS is now the same as the one sent in the SESSION CREATE, /not/ the base64 of the private key, per spec * enum is a reserved word in 1.5, so s/enum/names/ for future compatability * logging
This commit is contained in:
@ -13,6 +13,14 @@ import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.Properties;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
@ -27,6 +35,11 @@ public class SAMBridge implements Runnable {
|
||||
private final static Log _log = new Log(SAMBridge.class);
|
||||
private ServerSocket serverSocket;
|
||||
private Properties i2cpProps;
|
||||
/**
|
||||
* app designated destination name to the base64 of the I2P formatted
|
||||
* destination keys (Destination+PrivateKey+SigningPrivateKey)
|
||||
*/
|
||||
private Map nameToPrivKeys = Collections.synchronizedMap(new HashMap(8));
|
||||
|
||||
private boolean acceptConnections = true;
|
||||
|
||||
@ -63,6 +76,46 @@ public class SAMBridge implements Runnable {
|
||||
this.i2cpProps = i2cpProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the destination associated with the given name
|
||||
*
|
||||
* @return null if the name does not exist, or if it is improperly formatted
|
||||
*/
|
||||
public Destination getDestination(String name) {
|
||||
String val = (String)nameToPrivKeys.get(name);
|
||||
if (val == null) return null;
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(val);
|
||||
return d;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error retrieving the destination from " + name, dfe);
|
||||
nameToPrivKeys.remove(name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the I2P private keystream for the given name, formatted
|
||||
* as a base64 string (Destination+PrivateKey+SessionPrivateKey, as I2CP
|
||||
* stores it).
|
||||
*
|
||||
* @return null if the name does not exist, else the stream
|
||||
*/
|
||||
public String getKeystream(String name) {
|
||||
String val = (String)nameToPrivKeys.get(name);
|
||||
if (val == null) return null;
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that the given keystream should be used for the given name
|
||||
*
|
||||
*/
|
||||
public void addKeystream(String name, String stream) {
|
||||
nameToPrivKeys.put(name, stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <pre>SAMBridge [[listenHost ]listenPort[ name=val]*]</pre>
|
||||
@ -140,10 +193,18 @@ public class SAMBridge implements Runnable {
|
||||
} catch (IOException e) {}
|
||||
continue;
|
||||
}
|
||||
handler.setBridge(this);
|
||||
handler.startHandling();
|
||||
} catch (SAMException e) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("SAM error: " + e.getMessage(), e);
|
||||
try {
|
||||
String reply = "HELLO REPLY RESULT=I2P_ERROR MESSAGE=\"" + e.getMessage() + "\"\n";
|
||||
s.getOutputStream().write(reply.getBytes("ISO-8859-1"));
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("SAM Error sending error reply", ioe);
|
||||
}
|
||||
s.close();
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import net.i2p.util.Log;
|
||||
public class SAMDatagramSession extends SAMMessageSession {
|
||||
|
||||
private final static Log _log = new Log(SAMDatagramSession.class);
|
||||
public static int DGRAM_SIZE_MAX = 31*1024;
|
||||
|
||||
private SAMDatagramReceiver recv = null;
|
||||
|
||||
@ -43,7 +44,8 @@ public class SAMDatagramSession extends SAMMessageSession {
|
||||
* @param recv Object that will receive incoming data
|
||||
*/
|
||||
public SAMDatagramSession(String dest, Properties props,
|
||||
SAMDatagramReceiver recv) throws IOException, DataFormatException, I2PSessionException {
|
||||
SAMDatagramReceiver recv) throws IOException,
|
||||
DataFormatException, I2PSessionException {
|
||||
super(dest, props);
|
||||
|
||||
this.recv = recv;
|
||||
@ -58,7 +60,8 @@ public class SAMDatagramSession extends SAMMessageSession {
|
||||
* @param recv Object that will receive incoming data
|
||||
*/
|
||||
public SAMDatagramSession(InputStream destStream, Properties props,
|
||||
SAMDatagramReceiver recv) throws IOException, DataFormatException, I2PSessionException {
|
||||
SAMDatagramReceiver recv) throws IOException,
|
||||
DataFormatException, I2PSessionException {
|
||||
super(destStream, props);
|
||||
|
||||
this.recv = recv;
|
||||
@ -73,6 +76,9 @@ public class SAMDatagramSession extends SAMMessageSession {
|
||||
* @return True if the data was sent, false otherwise
|
||||
*/
|
||||
public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
|
||||
if (data.length > DGRAM_SIZE_MAX)
|
||||
throw new DataFormatException("Datagram size exceeded (" + data.length + ")");
|
||||
|
||||
byte[] dgram = dgramMaker.makeI2PDatagram(data);
|
||||
|
||||
return sendBytesThroughMessageSession(dest, dgram);
|
||||
|
@ -30,6 +30,7 @@ public abstract class SAMHandler implements Runnable {
|
||||
private final static Log _log = new Log(SAMHandler.class);
|
||||
|
||||
protected I2PThread thread = null;
|
||||
protected SAMBridge bridge = null;
|
||||
|
||||
private Object socketWLock = new Object(); // Guards writings on socket
|
||||
private Socket socket = null;
|
||||
@ -71,6 +72,8 @@ public abstract class SAMHandler implements Runnable {
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void setBridge(SAMBridge bridge) { this.bridge = bridge; }
|
||||
|
||||
/**
|
||||
* Actually handle the SAM protocol.
|
||||
*
|
||||
@ -124,7 +127,9 @@ public abstract class SAMHandler implements Runnable {
|
||||
*
|
||||
*/
|
||||
protected final void closeClientSocket() throws IOException {
|
||||
if (socket != null)
|
||||
socket.close();
|
||||
socket = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,8 +32,8 @@ public class SAMHandlerFactory {
|
||||
*
|
||||
* @param s Socket attached to SAM client
|
||||
* @param i2cpProps config options for our i2cp connection
|
||||
*
|
||||
* @return A SAM protocol handler
|
||||
* @throws SAMException if the connection handshake (HELLO message) was malformed
|
||||
* @return A SAM protocol handler, or null if the client closed before the handshake
|
||||
*/
|
||||
public static SAMHandler createSAMHandler(Socket s, Properties i2cpProps) throws SAMException {
|
||||
BufferedReader br;
|
||||
@ -66,8 +66,8 @@ public class SAMHandlerFactory {
|
||||
{
|
||||
String opcode;
|
||||
if (!(opcode = tok.nextToken()).equals("VERSION")) {
|
||||
throw new SAMException("Unrecognized HELLO message opcode: \""
|
||||
+ opcode + "\"");
|
||||
throw new SAMException("Unrecognized HELLO message opcode: '"
|
||||
+ opcode + "'");
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,22 +88,8 @@ public class SAMHandlerFactory {
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
if (ver == null)
|
||||
throw new SAMException("No version specified");
|
||||
|
||||
// Let's answer positively
|
||||
try {
|
||||
@ -135,8 +121,8 @@ public class SAMHandlerFactory {
|
||||
throw new SAMException("BUG! (in handler instantiation)");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
_log.error("IOException caught during SAM handler instantiation");
|
||||
return null;
|
||||
_log.error("Error creating the v1 handler", e);
|
||||
throw new SAMException("IOException caught during SAM handler instantiation");
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import net.i2p.util.Log;
|
||||
public class SAMRawSession extends SAMMessageSession {
|
||||
|
||||
private final static Log _log = new Log(SAMRawSession.class);
|
||||
public static final int RAW_SIZE_MAX = 32*1024;
|
||||
|
||||
private SAMRawReceiver recv = null;
|
||||
/**
|
||||
@ -64,6 +65,8 @@ public class SAMRawSession extends SAMMessageSession {
|
||||
* @return True if the data was sent, false otherwise
|
||||
*/
|
||||
public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
|
||||
if (data.length > RAW_SIZE_MAX)
|
||||
throw new DataFormatException("Data size limit exceeded (" + data.length + ")");
|
||||
return sendBytesThroughMessageSession(dest, data);
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,6 @@ public class SAMStreamSession {
|
||||
allprops.putAll(System.getProperties());
|
||||
allprops.putAll(props);
|
||||
|
||||
// FIXME: we should setup I2CP host and port, too
|
||||
String i2cpHost = allprops.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
|
||||
int i2cpPort = 7654;
|
||||
String port = allprops.getProperty(I2PClient.PROP_TCP_PORT, "7654");
|
||||
@ -113,6 +112,7 @@ public class SAMStreamSession {
|
||||
// streams MUST be mode=guaranteed (though i think the socket manager
|
||||
// enforces this anyway...
|
||||
allprops.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
|
||||
_log.debug("Creating I2PSocketManager...");
|
||||
socketMgr = I2PSocketManagerFactory.createManager(destStream,
|
||||
i2cpHost,
|
||||
|
@ -33,7 +33,6 @@ import net.i2p.I2PAppContext;
|
||||
public class SAMUtils {
|
||||
|
||||
private final static Log _log = new Log(SAMUtils.class);
|
||||
private static I2PAppContext _context = new I2PAppContext();
|
||||
|
||||
/**
|
||||
* Generate a random destination key
|
||||
@ -86,7 +85,7 @@ 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 = _context.namingService();
|
||||
NamingService ns = I2PAppContext.getGlobalContext().namingService();
|
||||
Destination dest = ns.lookup(name);
|
||||
|
||||
if ((pubKey != null) && (dest != null)) {
|
||||
@ -144,13 +143,13 @@ public class SAMUtils {
|
||||
|
||||
/* Dump a Properties object in an human-readable form */
|
||||
private static String dumpProperties(Properties props) {
|
||||
Enumeration enum = props.propertyNames();
|
||||
Enumeration names = props.propertyNames();
|
||||
String msg = "";
|
||||
String key, val;
|
||||
boolean firstIter = true;
|
||||
|
||||
while (enum.hasMoreElements()) {
|
||||
key = (String)enum.nextElement();
|
||||
while (names.hasMoreElements()) {
|
||||
key = (String)names.nextElement();
|
||||
val = props.getProperty(key);
|
||||
|
||||
if (!firstIter) {
|
||||
|
@ -154,10 +154,10 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
_log.error("Caught UnsupportedEncodingException ("
|
||||
+ e.getMessage() + ")");
|
||||
+ e.getMessage() + ")", e);
|
||||
} catch (IOException e) {
|
||||
_log.debug("Caught IOException ("
|
||||
+ e.getMessage() + ")");
|
||||
+ e.getMessage() + ")", e);
|
||||
} catch (Exception e) {
|
||||
_log.error("Unexpected exception", e);
|
||||
} finally {
|
||||
@ -189,32 +189,47 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
if ((rawSession != null) || (datagramSession != null)
|
||||
|| (streamSession != null)) {
|
||||
_log.debug("Trying to create a session, but one still exists");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Session already exists\"\n");
|
||||
}
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in SESSION CREATE message");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No parameters for SESSION CREATE\"\n");
|
||||
}
|
||||
|
||||
dest = props.getProperty("DESTINATION");
|
||||
if (dest == null) {
|
||||
_log.debug("SESSION DESTINATION parameter not specified");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"DESTINATION not specified\"\n");
|
||||
}
|
||||
props.remove("DESTINATION");
|
||||
|
||||
String destKeystream = null;
|
||||
|
||||
if (dest.equals("TRANSIENT")) {
|
||||
_log.debug("TRANSIENT destination requested");
|
||||
ByteArrayOutputStream priv = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream priv = new ByteArrayOutputStream(640);
|
||||
SAMUtils.genRandomKey(priv, null);
|
||||
|
||||
dest = Base64.encode(priv.toByteArray());
|
||||
destKeystream = Base64.encode(priv.toByteArray());
|
||||
} else {
|
||||
destKeystream = bridge.getKeystream(dest);
|
||||
if (destKeystream == null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Custom destination specified [" + dest + "] but it isnt know, creating a new one");
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(640);
|
||||
SAMUtils.genRandomKey(baos, null);
|
||||
destKeystream = Base64.encode(baos.toByteArray());
|
||||
bridge.addKeystream(dest, destKeystream);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Custom destination specified [" + dest + "] and it is already known");
|
||||
}
|
||||
}
|
||||
|
||||
String style = props.getProperty("STYLE");
|
||||
if (style == null) {
|
||||
_log.debug("SESSION STYLE parameter not specified");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No SESSION STYLE specified\"\n");
|
||||
}
|
||||
props.remove("STYLE");
|
||||
|
||||
@ -231,34 +246,34 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
if (!dir.equals("CREATE") && !dir.equals("RECEIVE")
|
||||
&& !dir.equals("BOTH")) {
|
||||
_log.debug("Unknow DIRECTION parameter value: [" + dir + "]");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unknown DIRECTION parameter\"\n");
|
||||
}
|
||||
props.remove("DIRECTION");
|
||||
|
||||
streamSession = new SAMStreamSession(dest, dir,props,this);
|
||||
streamSession = new SAMStreamSession(destKeystream, dir,props,this);
|
||||
} else {
|
||||
_log.debug("Unrecognized SESSION STYLE: \"" + style +"\"");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized SESSION STYLE\"\n");
|
||||
}
|
||||
return writeString("SESSION STATUS RESULT=OK DESTINATION="
|
||||
+ dest + "\n");
|
||||
} else {
|
||||
_log.debug("Unrecognized SESSION message opcode: \""
|
||||
+ opcode + "\"");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized opcode\"\n");
|
||||
}
|
||||
} catch (DataFormatException e) {
|
||||
_log.debug("Invalid destination specified");
|
||||
return writeString("SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + "\n");
|
||||
return writeString("SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
|
||||
} catch (I2PSessionException e) {
|
||||
_log.debug("I2P error when instantiating session", e);
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n");
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
|
||||
} catch (SAMException e) {
|
||||
_log.error("Unexpected SAM error", e);
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n");
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
|
||||
} catch (IOException e) {
|
||||
_log.error("Unexpected IOException", e);
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n");
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
|
||||
}
|
||||
}
|
||||
|
||||
@ -483,6 +498,19 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
|
||||
if (opcode.equals("SEND")) {
|
||||
return execStreamSend(props);
|
||||
} else if (opcode.equals("CONNECT")) {
|
||||
return execStreamConnect(props);
|
||||
} else if (opcode.equals("CLOSE")) {
|
||||
return execStreamClose(props);
|
||||
} else {
|
||||
_log.debug("Unrecognized RAW message opcode: \""
|
||||
+ opcode + "\"");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean execStreamSend(Properties props) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM SEND message");
|
||||
return false;
|
||||
@ -544,7 +572,9 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
e);
|
||||
return false;
|
||||
}
|
||||
} else if (opcode.equals("CONNECT")) {
|
||||
}
|
||||
|
||||
private boolean execStreamConnect(Properties props) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM CONNECT message");
|
||||
return false;
|
||||
@ -608,7 +638,9 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
return writeString("STREAM STATUS RESULT=I2P_ERROR ID="
|
||||
+ id + "\n");
|
||||
}
|
||||
} else if (opcode.equals("CLOSE")) {
|
||||
}
|
||||
|
||||
private boolean execStreamClose(Properties props) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM CLOSE message");
|
||||
return false;
|
||||
@ -630,11 +662,6 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
|
||||
return streamSession.closeConnection(id);
|
||||
} else {
|
||||
_log.debug("Unrecognized RAW message opcode: \""
|
||||
+ opcode + "\"");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check whether a size is inside the limits allowed by this protocol */
|
||||
|
Reference in New Issue
Block a user