package plugins.CENO.Bridge;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.Security;
import java.util.List;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import plugins.CENO.CENOException;
import plugins.CENO.CENOL10n;
import plugins.CENO.Configuration;
import plugins.CENO.Version;
import plugins.CENO.Bridge.Signaling.Channel;
import plugins.CENO.Bridge.Signaling.ChannelMaker;
import plugins.CENO.Bridge.Signaling.ChannelManager;
import plugins.CENO.Common.Crypto;
import plugins.CENO.FreenetInterface.NodeInterface;
import freenet.keys.FreenetURI;
import freenet.pluginmanager.FredPlugin;
import freenet.pluginmanager.FredPluginRealVersioned;
import freenet.pluginmanager.FredPluginVersioned;
import freenet.pluginmanager.PluginRespirator;
import freenet.support.IllegalBase64Exception;
import freenet.support.Logger;
public class CENOBridge implements FredPlugin, FredPluginVersioned, FredPluginRealVersioned {
public static final Integer cacheLookupPort = 3091;
public static final Integer requestReceiverPort = 3093;
public static final Integer bundleServerPort = 3094;
public static final Integer bundleInserterPort = 3095;
/** The HTTP Server to handle requests from other agents */
private Server cenoHttpServer;
// Interface objects with fred
public static NodeInterface nodeInterface;
BridgeDatabase bridgeDatabase;
private static boolean isMasterBridge = false;
private static boolean isSignalBridge = false;
// Plugin-specific configuration
public static final String PLUGIN_URI = "/plugins/plugins.CENO.CENOBridge";
public static final String PLUGIN_NAME = "CENOBridge";
public static Configuration initConfig;
private static final Version VERSION = new Version(Version.PluginType.BRIDGE);
private static final String CONFIGPATH = ".CENO/bridge.properties";
private static final String DBPATH = ".CENO/bridge.db";
public static final String ANNOUNCER_PATH = "CENO-signaler";
public void runPlugin(PluginRespirator pr)
{
// Initialize interfaces with Freenet node
nodeInterface = new NodeInterface(pr.getNode(), pr);
CENOL10n.getInstance().setLanguageFromEnvVar("CENOLANG");
// Read properties of the configuration file
initConfig = new Configuration(CONFIGPATH);
initConfig.readProperties();
// If CENO has no private key for inserting freesites,
// generate a new key pair and store it in the configuration file
if (initConfig.getProperty("insertURI") == null || initConfig.getProperty("insertURI").isEmpty()) {
Logger.warning(this, "CENOBridge will generate a new public key for inserting bundles.");
FreenetURI[] keyPair = nodeInterface.generateKeyPair();
initConfig.setProperty("insertURI", keyPair[0].toString());
initConfig.setProperty("requestURI", keyPair[1].toString());
initConfig.storeProperties();
}
String confIsMasterBridge = initConfig.getProperty("isMasterBridge");
if (confIsMasterBridge != null && confIsMasterBridge.equals("true")) {
isMasterBridge = true;
}
String confIsSingalBridge = initConfig.getProperty("isSignalBridge");
if (confIsSingalBridge != null && confIsSingalBridge.equals("true")) {
isSignalBridge = true;
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
// Read RSA keypair and modulus from configuration file or, if not available, create a new one
KeyPair asymKeyPair = null;
try {
if (!Crypto.isValidKeypair(initConfig.getProperty("asymkey.pubKey"), initConfig.getProperty("asymkey.privKey"))) {
Logger.warning(this, "CENOBridge will generate a new RSA key pair for the decentralized signaling. This might take a while");
asymKeyPair = Crypto.generateAsymKey();
initConfig.setProperty("asymkey.pubKey", Crypto.savePublicKey(asymKeyPair.getPublic()));
initConfig.setProperty("asymkey.privKey", Crypto.savePrivateKey(asymKeyPair.getPrivate()));
initConfig.storeProperties();
} else {
asymKeyPair = new KeyPair(Crypto.loadPublicKey(initConfig.getProperty("asymkey.pubKey")), Crypto.loadPrivateKey(initConfig.getProperty("asymkey.privKey")));
Logger.normal(this, "Found RSA key in configuration file");
}
} catch (UnsupportedEncodingException e) {
Logger.error(this, "Unsupported Encoding Exception during RSA key validation: " + e.getMessage());
} catch (GeneralSecurityException e) {
Logger.error(this, "General Security Exception: " + e.getMessage());
} catch (IllegalBase64Exception e) {
Logger.error(this, "Failed to base64 encode/decode: " + e.getMessage());
} finally {
if (asymKeyPair == null) {
terminate();
return;
}
}
String confBridgeDB = initConfig.getProperty("bridgeDB");
if (confBridgeDB == null) {
confBridgeDB = DBPATH;
initConfig.setProperty("bridgeDB", DBPATH);
initConfig.storeProperties();
}
try {
bridgeDatabase = new BridgeDatabase(DBPATH);
} catch (CENOException e) {
Logger.error(this, "Could not open bridge database");
terminate();
return;
}
try {
ChannelMaker.getInstance().config(initConfig.getProperty("insertURI"), asymKeyPair);
ChannelMaker.getInstance().publishNewPuzzle();
} catch (IOException e) {
Logger.error(this, "Could not start channel listener for the given insertURI: " + e.getMessage());
terminate();
return;
} catch (GeneralSecurityException e) {
Logger.error(this, "The given public RSA key is invalid");
terminate();
return;
} catch (CENOException e) {
Logger.error(this, "Could not start decentralized signaling channel maker: " + e.getMessage());
terminate();
return;
}
//Retrieve and poll previously established channels from the bridge.database
try {
List<Channel> databaseChannels = bridgeDatabase.retrieveChannels();
int counter = ChannelManager.getInstance().addChannels(databaseChannels);
Logger.normal(this, "Retrieved " + counter + " previously established channels from the database, failed to subscribe to updates from " +
Integer.toString(databaseChannels.size() - counter));
} catch (CENOException e) {
Logger.error(this, "Exception while retrieving previously established channels from the database: " + e.getMessage());
}
}
// Configure CENO's jetty embedded server
cenoHttpServer = new Server();
configHttpServer(cenoHttpServer);
// Start server and wait until it gets interrupted
try {
cenoHttpServer.start();
cenoHttpServer.join();
} catch (InterruptedException interruptedEx) {
Logger.normal(this, "HTTP Server interrupted. Terminating plugin...");
terminate();
return;
} catch (Exception ex) {
Logger.error(this, "HTTP Server terminated abnormally");
Logger.error(this, ex.getMessage());
terminate();
return;
}
}
/**
* Configure CENO's embedded server
*
* @param cenoHttpServer the jetty server to be configured
*/
private void configHttpServer(Server cenoHttpServer) {
// Create a collection of ContextHandlers for the server
ContextHandlerCollection handlers = new ContextHandlerCollection();
// Add a ServerConnector for the BundlerInserter agent
ServerConnector bundleInserterConnector = new ServerConnector(cenoHttpServer);
bundleInserterConnector.setName("bundleInserter");
bundleInserterConnector.setHost("localhost");
bundleInserterConnector.setPort(bundleInserterPort);
// Add the connector to the server
cenoHttpServer.addConnector(bundleInserterConnector);
// Configure ContextHandlers to listen to a specific port
// and upon request call the appropriate CENOJettyHandler subclass
ContextHandler cacheInsertCtxHandler = new ContextHandler();
cacheInsertCtxHandler.setMaxFormContentSize(2000000);
cacheInsertCtxHandler.setHandler(new BundleInserterHandler());
//cacheInsertCtxHandler.setVirtualHosts(new String[]{"@cacheInsert"});
// Add the configured ContextHandler to the server
handlers.addHandler(cacheInsertCtxHandler);
//Uncomment the following block if you need a lookup handler in the bridge side
/*
ServerConnector httpConnector = new ServerConnector(cenoHttpServer);
httpConnector.setName("cacheLookup");
httpConnector.setPort(cacheLookupPort);
cenoHttpServer.addConnector(httpConnector);
ContextHandler cacheLookupCtxHandler = new ContextHandler();
cacheLookupCtxHandler.setHandler(new CacheLookupHandler());
cacheLookupCtxHandler.setVirtualHosts(new String[]{"@cacheLookup"});
handlers.addHandler(cacheLookupCtxHandler);
*/
cenoHttpServer.setHandler(handlers);
}
public String getVersion() {
return VERSION.getVersion();
}
public long getRealVersion() {
return VERSION.getRealVersion();
}
public static boolean isMasterBridge() {
return isMasterBridge;
}
/**
* Method called before termination of the CENO bridge plugin
* Terminates ceNoHttpServer and releases resources
*/
public void terminate()
{
// Stop the thread that is polling for new channel requests
if (isSignalBridge) {
ChannelMaker.getInstance().stopPuzzleListeners();
for (Channel channel : ChannelManager.getInstance().getAllChannels()) {
try {
bridgeDatabase.storeChannel(channel);
} catch (CENOException e) {
Logger.warning(this, "Failed to save signaling channels with SSK: " + channel.getInsertSSK());
}
}
}
// Stop cenoHttpServer and unbind ports
if (cenoHttpServer != null) {
try {
cenoHttpServer.stop();
} catch (Exception e) {
Logger.error(this, "Exception while terminating HTTP server.");
Logger.error(this, e.getMessage());
}
}
Logger.normal(this, PLUGIN_NAME + " terminated.");
}
}