/** * */ package org.limewire.lws.server; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * Dispatches commands after going through an authentication phase explained * here. <br/> This represents the state in the FSA. Basically, the operation of * the local server is the following: * * <pre> * Code -> Local : Authenticate * +------+ * + -- | Wait | <---------------------------+ * | +------+ | Local -> Remote : StoreKey * | | Code -> Local : Detach | Local -> Code : PrivateKey * | | | * | V Code -> Local : Detach | * | +------+ <-------------------------- +-------+ * | | Idle | | Store | * | +------+ --------------------------> +-------+ * | ˆ Code -> Local : StartCom * | | * | | Code -> Local : Detach * | | * | +---------------+ * +--> | Communicating | * +---------------+ * </pre> * */ public final class LWSDispatcherImpl extends LWSDispatcherSupport { private final LWSSenderOfMessagesToServer sender; private String publicKey; private String privateKey; private String sharedKey; public LWSDispatcherImpl(LWSSenderOfMessagesToServer sender) { this.sender = sender; } public void deauthenticate() { publicKey = null; privateKey = null; sharedKey = null; } @Override protected final Handler[] createHandlers() { return new Handler[] { new StartCom(), new Authenticate(), new Detatch(), new Msg() }; } @Override protected final boolean isAuthenticated() { return publicKey != null && privateKey != null && sharedKey != null; } /** * Returns the arguments to the right of the <code>?</code>. <br> * <code>static</code> for testing * * @param request may be <code>null</code> * @return the arguments to the right of the <code>?</code> */ @Override protected Map<String, String> getArgs(String request) { if (request == null || request.length() == 0) { return Collections.emptyMap(); } int ihuh = request.indexOf('?'); if (ihuh == -1) { return Collections.emptyMap(); } final String rest = request.substring(ihuh + 1); return LWSServerUtil.parseArgs(rest); } @Override protected String getCommand(String request) { int iprefix = request.indexOf(PREFIX); String res = iprefix == -1 ? request : request.substring(iprefix + PREFIX.length()); final char[] cs = { '#', '?' }; for (char c : cs) { final int id = res.indexOf(c); if (id != -1) res = res.substring(0, id); } return res; } final String getPublicKey() { return publicKey; } final String getPrivateKey() { return privateKey; } final String getSharedKey() { return sharedKey; } private void regenerateKeys() { publicKey = LWSServerUtil.generateKey(); privateKey = LWSServerUtil.generateKey(); sharedKey = LWSServerUtil.generateKey(); note("public key : {0}", publicKey); note("private key : {0}", privateKey); note("shared key : {0}", sharedKey); } // ------------------------------------------------------------ // Handlers // ------------------------------------------------------------ /** * A {@link Handler} that needs both a callback and private * key. */ protected abstract class HandlerWithCallbackWithPrivateKey extends HandlerWithCallback { @Override protected final void handleRest(final Map<String, String> args, StringCallback cb) { // // Check the private key // String privateKey = getPrivateKey(); if (privateKey == null) { cb.process(report(LWSDispatcherSupport.ErrorCodes.UNITIALIZED_PRIVATE_KEY)); return; } String herPrivateKey = args.get(LWSDispatcherSupport.Parameters.PRIVATE); if (herPrivateKey == null) { cb.process(report(LWSDispatcherSupport.ErrorCodes.MISSING_PRIVATE_KEY_PARAMETER)); return; } if (!herPrivateKey.equals(privateKey)) { cb.process(report(LWSDispatcherSupport.ErrorCodes.INVALID_PRIVATE_KEY)); return; } // // Check the shared key // String sharedKey = getSharedKey(); if (sharedKey == null) { cb.process(report(LWSDispatcherSupport.ErrorCodes.UNITIALIZED_SHARED_KEY)); return; } String herSharedKey = args.get(LWSDispatcherSupport.Parameters.SHARED); if (herSharedKey == null) { cb.process(report(LWSDispatcherSupport.ErrorCodes.MISSING_SHARED_KEY_PARAMETER)); return; } if (!herSharedKey.equals(sharedKey)) { cb.process(report(LWSDispatcherSupport.ErrorCodes.INVALID_SHARED_KEY)); return; } handleRest(herPrivateKey, herSharedKey, args, cb); } /** * The result <b>IN PLAIN TEXT</b> using the private key, * <tt>privateKey</tt>. Override this to do something meaningful * with the passed along private key, too. * * @param privateKey private key pulled from the args * @param herSharedKey the shared key sent from a Browser * @param args original, untouched arguments * @return result <b>IN PLAIN TEXT</b> using the private key, * <tt>privateKey</tt> */ abstract void handleRest(String privateKey,String herSharedKey, Map<String, String> args, StringCallback cb); } /** * Issues a command to start authentication. */ class StartCom extends HandlerWithCallback { @Override protected void handleRest(final Map<String, String> args, final StringCallback cb) { regenerateKeys(); // // send the keys to the Server and wait for a response // final Map<String, String> sendArgs = new HashMap<String, String>(); sendArgs.put(LWSDispatcherSupport.Parameters.PRIVATE, privateKey); sendArgs.put(LWSDispatcherSupport.Parameters.PUBLIC, publicKey); try { sender.sendMessageToServer(LWSDispatcherSupport.Commands.STORE_KEY, sendArgs, new StringCallback() { public void process(String response) { // // This could have a trailing space, so be nice // cb.process(response.indexOf(Responses.OK) != -1 ? publicKey + " " + sharedKey : "0"); } }); } catch (IOException e) { // ignored } } } /** * Sent from code with private key to authenticate. */ class Authenticate extends HandlerWithCallbackWithPrivateKey { @Override protected void handleRest(String privateKey, String sharedKey, Map<String, String> args, StringCallback cb) { notifyConnectionListeners(true); cb.process(LWSDispatcherSupport.Responses.OK); } } /** * Send from code to end session. */ class Detatch extends HandlerWithCallback { @Override protected void handleRest(Map<String, String> args, StringCallback cb) { privateKey = null; publicKey = null; sharedKey = null; notifyConnectionListeners(false); cb.process(LWSDispatcherSupport.Responses.OK); } } /** * Sent from code with parameter {@link Parameters#COMMAND}. */ class Msg extends HandlerWithCallbackWithPrivateKey { @Override protected void handleRest(String privateKey, String herSharedKey, Map<String, String> args, StringCallback cb) { String cmd = args.get(LWSDispatcherSupport.Parameters.COMMAND); if (cmd == null) { cb.process(report(LWSDispatcherSupport.ErrorCodes.MISSING_COMMAND_PARAMETER)); return; } if (getCommandReceiver() != null) { Map<String, String> newArgs = new HashMap<String, String>(args); String newCmd = LWSServerUtil.addURLEncodedArguments(cmd, newArgs); String res = getCommandReceiver().receiveCommand(newCmd, newArgs); cb.process(res); return; } cb.process(LWSDispatcherSupport.Responses.NO_DISPATCHEE); } } }