/// <summary> /// Handles a request from a Web Client who is requesting a proxied web socket connection to a specific Host Service. /// </summary> /// <param name="p">The HttpProcessor handling the request.</param> private static void HandleWebSocketProxyRequest(HttpProcessor p) { string[] parts = p.request_url.Segments; #region Validate Input if (parts.Length != 3) { p.writeFailure("400 Bad Request"); return; } if (!int.TryParse(parts[1], out int computerId)) { p.writeFailure("400 Bad Request"); return; } #endregion #region Verify Permission string sid = parts[2]; ServerSession session = SessionManager.GetSession(sid); if (session == null || session.Expired) { p.writeFailure("403 Forbidden"); return; } Computer computer = ServiceWrapper.db.GetComputer(computerId); if (computer == null) { p.writeFailure("404 Not Found"); return; } User user = session.GetUser(); if (user == null) { p.writeFailure("403 Forbidden"); return; } // If we get here, we have an active authenticated session. if (!user.IsAdmin) { // Admin users can access all computers. // This user is not an adminn, so we must check group membership. ComputerGroupMembership[] cgm = computer.GetGroupMemberships(); if (cgm.Length == 0) { Logger.Info("Non-admin user " + user.ID + " (" + user.Name + ") attempted to access computer " + computer.ID + " (" + computer.Name + ") but computer has no group memberships."); p.writeFailure("403 Forbidden"); return; } UserGroupMembership[] ugm = user.GetGroupMemberships(); if (ugm.Length == 0) { Logger.Info("Non-admin user " + user.ID + " (" + user.Name + ") attempted to access computer " + computer.ID + " (" + computer.Name + ") but user has no group memberships."); p.writeFailure("403 Forbidden"); return; } // The computer is accessible to this user if the computer and the user share at least one group membership. bool accessible = 0 < cgm.Select(m => m.GroupID).Intersect(ugm.Select(m => m.GroupID)).Count(); if (!accessible) { Logger.Info("Non-admin user " + user.ID + " (" + user.Name + ") attempted to access computer " + computer.ID + " (" + computer.Name + ") without permission."); p.writeFailure("403 Forbidden"); return; } } #endregion // Now that permission has been verified, find out of the specified computer is online. HostConnectHandle host = HostConnect.GetOnlineComputer(computer.ID); if (host == null) { p.writeFailure("504 Gateway Timeout", "Computer " + computer.ID + " is not online."); return; } // The computer is online. Send a request to have the Host Service connect to this Master Server's web socket proxy service. string proxyKey = Util.GetRandomAlphaNumericString(64); WaitingClient waitingClient = null; try { waitingClient = new WaitingClient(p); pendingProxyConnections[proxyKey] = waitingClient; host.RequestWebSocketProxy(p.RemoteIPAddress, proxyKey); // Wait for the connection from the Host Service. if (!waitingClient.clientWaitHandle.WaitOne(10000)) { p.writeFailure("504 Gateway Timeout", "Computer " + computer.ID + " did not respond in a timely manner."); return; } Stream hostStream = waitingClient.hostProcessor?.tcpStream; if (hostStream == null) { p.writeFailure("500 Internal Server Error"); Logger.Debug("hostStream was null in WebSocketProxy handler"); return; } // The Host Service has connected. Remove the pending connection and clean up before starting to proxy data between the sockets. pendingProxyConnections.TryRemove(proxyKey, out WaitingClient ignored); proxyKey = null; waitingClient.Dispose(); // Copy data from Host Service to Web Client p.responseWritten = true; CopyStreamUntilClosed(hostStream, p.tcpStream); } finally { if (proxyKey != null) { pendingProxyConnections.TryRemove(proxyKey, out WaitingClient ignored); } waitingClient?.Dispose(); } }
/// <summary> /// Handles a request from a Web Client who is requesting a proxied web socket connection to a specific Host Service. /// </summary> /// <param name="p">The HttpProcessor handling the request.</param> public static void HandleWebSocketClientProxyRequest(HttpProcessor p) { #region Perform Validation // Path format: "/WebSocketClientProxy/1/SESSION". More path segments, if given, are ignored (the last path segment is typically used as a display name in browser developer tools). string[] parts = p.request_url.LocalPath.Split('/').Skip(1).ToArray(); int computerId; // The computer ID the client wishes to connect to. if (parts.Length < 3 || parts[0] != "WebSocketClientProxy" || !int.TryParse(parts[1], out computerId)) { p.writeFailure("400 Bad Request"); return; } string sid = parts[2]; // The session ID of the client's session. #endregion #region Verify Permission ServerSession session = SessionManager.GetSession(sid); if (session == null || session.Expired) { p.writeFailure("403 Forbidden"); return; } Computer computer = ServiceWrapper.db.GetComputer(computerId); if (computer == null) { p.writeFailure("404 Not Found"); return; } User user = session.GetUser(); if (user == null) { p.writeFailure("403 Forbidden"); return; } // If we get here, we have an active authenticated session. if (!user.IsAdmin) { // Admin users can access all computers. // This user is not an adminn, so we must check group membership. ComputerGroupMembership[] cgm = computer.GetGroupMemberships(); if (cgm.Length == 0) { Logger.Info("Non-admin user " + user.ID + " (" + user.Name + ") attempted to access computer " + computer.ID + " (" + computer.Name + ") but computer has no group memberships."); p.writeFailure("403 Forbidden"); return; } UserGroupMembership[] ugm = user.GetGroupMemberships(); if (ugm.Length == 0) { Logger.Info("Non-admin user " + user.ID + " (" + user.Name + ") attempted to access computer " + computer.ID + " (" + computer.Name + ") but user has no group memberships."); p.writeFailure("403 Forbidden"); return; } // The computer is accessible to this user if the computer and the user share at least one group membership. bool accessible = 0 < cgm.Select(m => m.GroupID).Intersect(ugm.Select(m => m.GroupID)).Count(); if (!accessible) { Logger.Info("Non-admin user " + user.ID + " (" + user.Name + ") attempted to access computer " + computer.ID + " (" + computer.Name + ") without permission."); p.writeFailure("403 Forbidden"); return; } } #endregion // Now that permission has been verified, find out of the specified computer is online. HostConnectHandle host = HostConnect.GetOnlineComputer(computer.ID); if (host == null) { p.writeFailure("504 Gateway Timeout", "Computer " + computer.ID + " is not online."); return; } // The computer is online. Send a request to have the Host Service connect to this Master Server's web socket proxy service. string proxyKey = StringUtil.GetRandomAlphaNumericString(64); WaitingClient waitingClient = null; try { waitingClient = new WaitingClient(p); pendingProxyConnections[proxyKey] = waitingClient; host.RequestWebSocketProxy(p.RemoteIPAddress, proxyKey); // Wait for the connection from the Host Service. if (!waitingClient.clientWaitHandle.WaitOne(10000)) { p.writeFailure("504 Gateway Timeout", "Computer " + computer.ID + " did not respond in a timely manner."); return; } Stream hostStream = waitingClient.hostProcessor?.tcpStream; if (hostStream == null) { p.writeFailure("500 Internal Server Error"); Logger.Debug("hostStream was null in WebSocketProxy handler"); return; } // The Host Service has connected. Remove the pending connection and clean up before starting to proxy data between the sockets. pendingProxyConnections.TryRemove(proxyKey, out WaitingClient ignored); proxyKey = null; waitingClient.Dispose(); // Copy data from Host Service to Web Client p.responseWritten = true; p.tcpClient.NoDelay = true; Console.WriteLine("Client Proxy Initialized"); CopyStreamUntilClosed(hostStream, p.tcpStream); } finally { // If anything went wrong initializing the proxy connection, we might not have cleaned up yet. if (proxyKey != null) { pendingProxyConnections.TryRemove(proxyKey, out WaitingClient ignored); } waitingClient?.Dispose(); } }