/// <summary> /// Does some basic authentication checks and then /// dispatches to the JayRock RPC system. /// </summary> /// <param name="message">The JSON-RPC formatted message.</param> /// <param name="keePassRPCClient">The client we're communicating with.</param> private void DispatchToRPCService(string message, KeePassRPCClientConnection keePassRPCClientConnection) { if (KeePassRPCPlugin.logger != null) { KeePassRPCPlugin.logger.WriteLine("Preparing to dispatch: " + message); } StringBuilder sb = new StringBuilder(); string requiredResultRegex = ""; long authorisationAttemptId = -1; if (!keePassRPCClientConnection.Authorised && _authorisationRequired) { Match match = Regex.Match(message, "^\\{.*?\\\"method\\\"\\:\\\"Idle\\\".*?,.*?\\\"id\\\"\\:(\\d+).*?\\}$"); if (match.Success) { if (KeePassRPCPlugin.logger != null) { KeePassRPCPlugin.logger.WriteLine("Got Idle method- ignoring."); } // Do nothing return; } else // Check for alternative Idle message structure { match = Regex.Match(message, "^\\{.*?\\\"id\\\"\\:(\\d+).*?,.*?\\\"method\\\"\\:\\\"Idle\\\".*?\\}$"); if (match.Success) { if (KeePassRPCPlugin.logger != null) { KeePassRPCPlugin.logger.WriteLine("Got Idle method- ignoring."); } // Do nothing return; } } } if (!keePassRPCClientConnection.Authorised && _authorisationRequired) { // We only accept one type of request if the client has not // already authenticated. Maybe it's not nice having to do this // outside of the main JayRockJsonRpc library but it'll be good enough Match match = Regex.Match(message, "^\\{.*?\\\"method\\\"\\:\\\"Authenticate\\\".*?,.*?\\\"id\\\"\\:(\\d+).*?\\}$"); if (!match.Success) { match = Regex.Match(message, "^\\{.*?\\\"id\\\"\\:(\\d+).*?,.*?\\\"method\\\"\\:\\\"Authenticate\\\".*?\\}$"); if (!match.Success) { throw new AuthorisationException("Authentication required. You must send a properly formed JSON Authenticate request before using this connection.", -1, 1); } } authorisationAttemptId = int.Parse(match.Groups[1].Value); requiredResultRegex = "^\\{\\\"id\\\"\\:" + authorisationAttemptId + ",\\\"result\\\"\\:\\{\\\"result\\\"\\:0,\\\"name\\\"\\:\\\"(.*)\\\"\\}\\}$"; } //Stream clientStream = keePassRPCClientConnection.ConnectionStream; //TODO2: is this Jayrock stuff thread-safe or do I need new instances of the Service each time? JsonRpcDispatcher dispatcher = JsonRpcDispatcherFactory.CreateDispatcher(Service); dispatcher.Process(new StringReader(message), new StringWriter(sb), keePassRPCClientConnection.Authorised); string output = sb.ToString(); //MessageBox.Show("result: " + output); if (_authorisationRequired && !keePassRPCClientConnection.Authorised) { string authenticatedClientName; // Process the output from the JsonRpcDispatcher which // should tell us if the authorisation was successful Match match = Regex.Match(output, requiredResultRegex); if (match.Success) { authenticatedClientName = match.Groups[1].Value; keePassRPCClientConnection.Authorised = true; KeePassRPCPlugin.PromoteNullRPCClient(keePassRPCClientConnection, authenticatedClientName); } else { // If the result follows an accepted syntax we will send // it back to the client so they know why it failed but otherwise... if (!Regex.IsMatch(output, "^\\{\\\"id\\\"\\:(\\d+),\\\"result\\\"\\:\\{\\\"result\\\"\\:(\\d+),\\\"name\\\"\\:\\\".*\\\"\\}\\}$")) { MessageBox.Show("ERROR! Please click on this box, press CTRL-C on your keyboard and paste into a new post on the KeeFox forum (http://keefox.org/help/forum). Doing this will help other people to use KeeFox without any unexpected error messages like this. Please briefly describe what you were doing when the problem occurred, which version of KeeFox, KeePass and Firefox you use and what other security software you run on your machine. Thanks! Technical detail follows: " + output); return; // maybe could return a proper result indicating failure //but user might get annoyed with this popup appearing every 10 seconds! } } } byte[] bytes = System.Text.Encoding.UTF8.GetBytes(output); keePassRPCClientConnection.ConnectionStreamWrite(bytes); }
/// <summary> /// Does some basic authentication checks and then /// dispatches to the JayRock RPC system. /// </summary> /// <param name="message">The JSON-RPC formatted message.</param> /// <param name="keePassRPCClient">The client we're communicating with.</param> private void DispatchToRPCService(string message, KeePassRPCClientConnection keePassRPCClientConnection) { if (KeePassRPCPlugin.logger != null) KeePassRPCPlugin.logger.WriteLine("Preparing to dispatch: " + message); StringBuilder sb = new StringBuilder(); string requiredResultRegex = ""; long authorisationAttemptId = -1; if (!keePassRPCClientConnection.Authorised && _authorisationRequired) { // We only accept one type of request if the client has not // already authenticated. Maybe it's not nice having to do this // outside of the main JayRockJsonRpc library but it'll be good enough Match match = Regex.Match(message, "^\\{.*?\\\"method\\\"\\:\\\"Authenticate\\\".*?,.*?\\\"id\\\"\\:(\\d+).*?\\}$"); if (!match.Success) { match = Regex.Match(message, "^\\{.*?\\\"id\\\"\\:(\\d+).*?,.*?\\\"method\\\"\\:\\\"Authenticate\\\".*?\\}$"); if (!match.Success) throw new AuthorisationException("Authentication required. You must send a properly formed JSON Authenticate request before using this connection.", -1, 1); } authorisationAttemptId = int.Parse(match.Groups[1].Value); requiredResultRegex = "^\\{\\\"id\\\"\\:" + authorisationAttemptId + ",\\\"result\\\"\\:\\{\\\"result\\\"\\:0,\\\"name\\\"\\:\\\"(.*)\\\"\\}\\}$"; } //Stream clientStream = keePassRPCClientConnection.ConnectionStream; //TODO2: is this Jayrock stuff thread-safe or do I need new instances of the Service each time? JsonRpcDispatcher dispatcher = JsonRpcDispatcherFactory.CreateDispatcher(Service); dispatcher.Process(new StringReader(message), new StringWriter(sb), keePassRPCClientConnection.Authorised); string output = sb.ToString(); //MessageBox.Show("result: " + output); if (_authorisationRequired && !keePassRPCClientConnection.Authorised) { string authenticatedClientName; // Process the output from the JsonRpcDispatcher which // should tell us if the authorisation was successful Match match = Regex.Match(output, requiredResultRegex); if (match.Success) { authenticatedClientName = match.Groups[1].Value; keePassRPCClientConnection.Authorised = true; KeePassRPCPlugin.PromoteNullRPCClient(keePassRPCClientConnection, authenticatedClientName); } else { // If the result follows an accepted syntax we will send // it back to the client so they know why it failed but otherwise... if (!Regex.IsMatch(output, "^\\{\\\"id\\\"\\:(\\d+),\\\"result\\\"\\:\\{\\\"result\\\"\\:(\\d+),\\\"name\\\"\\:\\\".*\\\"\\}\\}$")) { MessageBox.Show("ERROR! Please click on this box, press CTRL-C on your keyboard and paste into a new post on the KeeFox forum (http://keefox.org/help/forum). Doing this will help other people to use KeeFox without any unexpected error messages like this. Please briefly describe what you were doing when the problem occurred, which version of KeeFox, KeePass and Firefox you use and what other security software you run on your machine. Thanks! Technical detail follows: " + output); return; // maybe could return a proper result indicating failure //but user might get annoyed with this popup appearing every 10 seconds! } } } byte[] bytes = System.Text.Encoding.UTF8.GetBytes(output); keePassRPCClientConnection.ConnectionStreamWrite(bytes); }
/// <summary> /// Handles the client communication /// </summary> /// <param name="client">The client.</param> private void HandleClientComm(object client) { KeePassRPCClientConnection keePassRPCClient = null; TcpClient tcpClient = null; NetworkStream clientStream = null; SslStream sslStream = null; if (KeePassRPCPlugin.logger != null) { KeePassRPCPlugin.logger.WriteLine("HandleClientComm: "); } if (_useSSL) { // A client has connected. Create the // SslStream using the client's network stream. ServicePointManager.ServerCertificateValidationCallback = delegate { return(true); }; sslStream = new SslStream(((TcpClient)client).GetStream(), false);//, new RemoteCertificateValidationCallback (ValidateServerCertificate), // new LocalCertificateSelectionCallback(SelectLocalCertificate) // ); } else { tcpClient = (TcpClient)client; clientStream = tcpClient.GetStream(); } if (KeePassRPCPlugin.logger != null) { KeePassRPCPlugin.logger.WriteLine("stream ready to be authenticated"); } try { if (_useSSL) { // Authenticate the server but don't require the client to // authenticate - we've got our own authentication requirements sslStream.AuthenticateAsServer( _serverCertificate, false, SslProtocols.Ssl3 | SslProtocols.Tls, false); if (KeePassRPCPlugin.logger != null) { KeePassRPCPlugin.logger.WriteLine("stream authenticated"); } sslStream.ReadTimeout = -1; sslStream.WriteTimeout = -1; } else { clientStream.ReadTimeout = -1; clientStream.WriteTimeout = -1; } byte[] message = new byte[4096]; int bytesRead; //bool authorised = false; //TODO2: creation of this client probably should happen later // but we need to know that this connection needs to be closed // during shutdown, even if the client never // successfully authenticates. keePassRPCClient = _useSSL ? new KeePassRPCClientConnection(sslStream, false) : new KeePassRPCClientConnection(tcpClient, false); KeePassRPCPlugin.AddRPCClientConnection(keePassRPCClient); // send an "invitation to authenticate" to the new RPC client keePassRPCClient.Signal(KeePassRPC.DataExchangeModel.Signal.PLEASE_AUTHENTICATE, "KPRPCListener"); int tokenCurlyCount = 0; int tokenSquareCount = 0; int adjacentBackslashCount = 0; bool parsingStringContents = false; StringBuilder currentJSONPacket = new StringBuilder(50); // Keep reading data from the network stream whenever it's available while (true) { bytesRead = 0; try { //blocks until a client sends a message bytesRead = _useSSL ? sslStream.Read(message, 0, 4096) : clientStream.Read(message, 0, 4096); } catch (Exception ex) { if (KeePassRPCPlugin.logger != null) { KeePassRPCPlugin.logger.WriteLine("a socket error has occured:" + ex.ToString()); } break; } if (bytesRead == 0) { //the client has disconnected from the server break; } // Can we ever receive a partial UTF8 character? if so, this could go wrong, albeit rarely string receivedData = System.Text.Encoding.UTF8.GetString(message, 0, bytesRead); int jsonPacketStartIndex = 0; for (int i = 0; i < receivedData.Length; i++) { bool incrementAdjacentBackslashCount = false; // Use the simple structure of JSON-RPC to extract // complete messages from the network stream switch (receivedData[i]) { case TOKEN_QUOT: if (adjacentBackslashCount % 2 == 0) { parsingStringContents = parsingStringContents ? false : true; } break; case TOKEN_BACKSLASH: incrementAdjacentBackslashCount = true; break; case TOKEN_CURLY_START: if (!parsingStringContents) { tokenCurlyCount++; } break; case TOKEN_CURLY_END: if (!parsingStringContents) { tokenCurlyCount--; } break; case TOKEN_SQUARE_START: if (!parsingStringContents) { tokenSquareCount++; } break; case TOKEN_SQUARE_END: if (!parsingStringContents) { tokenSquareCount--; } break; } if (incrementAdjacentBackslashCount) { adjacentBackslashCount++; } else { adjacentBackslashCount = 0; } // When both counts are zero, we know we have // reached the end of a JSON-RPC request if (tokenCurlyCount == 0 && tokenSquareCount == 0) { currentJSONPacket.Append(receivedData.Substring( jsonPacketStartIndex, i - jsonPacketStartIndex + 1)); DispatchToRPCService(currentJSONPacket.ToString(), keePassRPCClient); currentJSONPacket = new StringBuilder(50); jsonPacketStartIndex = i + 1; } } // If the JSON request is not complete we store what we have already found if (tokenCurlyCount != 0 || tokenSquareCount != 0) { currentJSONPacket.Append(receivedData); } // http://groups.google.com/group/jayrock/browse_thread/thread/59cf6a58bc63f0df/a7775c3097cf6957?lnk=gst&q=thread+JsonRpcDispatcher+#a7775c3097cf6957 } } catch (AuthorisationException authEx) { // Send a JSON message down the pipe byte[] bytes = System.Text.Encoding.UTF8.GetBytes(authEx.AsJSONResult()); keePassRPCClient.ConnectionStreamWrite(bytes); } catch (AuthenticationException ex) { if (KeePassRPCPlugin.logger != null) { KeePassRPCPlugin.logger.WriteLine("Authentication exception: " + ex.ToString()); } // Nothing we can do about this since client can't // receive messages over an invalid network stream } catch (Exception e) { if (KeePassRPCPlugin.logger != null) { KeePassRPCPlugin.logger.WriteLine("Unknown exception: " + e.ToString()); } //TODO2: send a JSON message down the pipe // ex.AsJSONResult(); } finally { if (KeePassRPCPlugin.logger != null) { KeePassRPCPlugin.logger.WriteLine("!!!Hit finally"); } if (keePassRPCClient != null) { KeePassRPCPlugin.RemoveRPCClientConnection(keePassRPCClient); } if (_useSSL) { try { sslStream.Close(); } catch (IOException ioex) { // This is okay } } else { clientStream.Close(); } } }