void OnMessageReceived(byte[] RawMessage) { try { string Message = Encoding.Unicode.GetString(RawMessage); XElement xMsg = XElement.Parse(Message, LoadOptions.PreserveWhitespace); if (xMsg.Name.LocalName == "Connect-Terminal") { try { string Domain = xMsg.Attribute("Domain").Value; string UserName = xMsg.Attribute("UserName").Value; string Password = xMsg.Attribute("Password").Value; lock (ProcessLock) { // Remove any processes which have exited so that instead of reattaching we launch a new one... List <string> ToRemove = new List <string>(); foreach (var kvp in Processes) { var TheProcess = kvp.Value; if (TheProcess.CommandPrompt == null || !TheProcess.CommandPrompt.IsStillRunning) { ToRemove.Add(kvp.Key); } } foreach (string Key in ToRemove) { Processes.Remove(Key); } // Next check if we have this process already open, referenced by full username... string FullUserName; if (Domain.Length > 0) { FullUserName = Domain + "\\" + UserName; } else { FullUserName = UserName; } if (Processes.ContainsKey(FullUserName)) { CredentialValidation.ValidateUser(Domain, UserName, Password); ConnectedProcess = Processes[FullUserName]; if (!ConsoleApi.AttachConsole((uint)ConnectedProcess.CommandPrompt.ProcessId)) { throw new Win32Exception(Marshal.GetLastWin32Error()); } if (Conin != null) { Conin.Dispose(); Conin = null; } Conin = new ConsoleInput(); if (CT != null) { CT.Dispose(); CT = null; } CT = new ConsoleTracker(); string XmlMsg = "<Terminal-Connected Reconnected=\"true\" full-user-name=\"" + FullUserName + "\" />"; byte[] RawMsg = Encoding.Unicode.GetBytes(XmlMsg); SendMessage(NetworkStream, RawMsg); } else { ConnectedProcess = new SlaveProcess(); try { #if DEBUG && ShowCmdWindow ConnectedProcess.Start(Domain, UserName, Password, IsLocalProvider); #else ConnectedProcess.Start(Domain, UserName, Password, false); #endif Processes.Add(FullUserName, ConnectedProcess); Thread.Sleep(2500); // TODO: WaitForInputIdle() would be preferred and more robust. //ConnectedProcess.CommandPrompt.WaitForInputIdle(15000); if (!ConnectedProcess.CommandPrompt.IsStillRunning) { throw new Exception("Newly launched terminal appears to have exited immediately or never actually launched."); } if (!ConsoleApi.AttachConsole((uint)ConnectedProcess.CommandPrompt.ProcessId)) { this.Log.WriteLine("AttachConsole() failed.", Severity.Debug); throw new Win32Exception(Marshal.GetLastWin32Error()); } this.Log.WriteLine("Console attached.", Severity.Debug); if (Conin != null) { Conin.Dispose(); Conin = null; } Conin = new ConsoleInput(); if (CT != null) { CT.Dispose(); CT = null; } CT = new ConsoleTracker(); this.Log.WriteLine("Console established without errors.", Severity.Debug); } finally { if (ConnectedProcess.CommandPrompt.DebugLog.Length > 0) { this.Log.WriteLine(ConnectedProcess.CommandPrompt.DebugLog.ToString(), Severity.Debug); string XmlMsg = "<Debug>" + ConnectedProcess.CommandPrompt.DebugLog.ToString() + "</Debug>"; byte[] RawMsg = Encoding.Unicode.GetBytes(XmlMsg); SendMessage(NetworkStream, RawMsg); ConnectedProcess.CommandPrompt.DebugLog.Clear(); } } { string XmlMsg = "<Terminal-Connected Reconnected=\"false\" full-user-name=\"" + FullUserName + "\" />"; byte[] RawMsg = Encoding.Unicode.GetBytes(XmlMsg); SendMessage(NetworkStream, RawMsg); } } } } catch (Exception ex) { string EncodedText = System.Convert.ToBase64String(Encoding.Unicode.GetBytes(ex.ToString() + "\n")); string XmlMsg = "<Error>" + EncodedText + "</Error>"; byte[] RawMsg = Encoding.Unicode.GetBytes(XmlMsg); SendMessage(NetworkStream, RawMsg); } } if (xMsg.Name.LocalName == "Console-Input") { // TODO: This xml format isn't very efficient. Could at least string it all into one array of INPUT_RECORDs. List <ConsoleApi.KEY_EVENT_RECORD> Keys = new List <ConsoleApi.KEY_EVENT_RECORD>(); foreach (var Key in xMsg.Elements()) { if (Key.Name != "Key") { throw new FormatException("Expected <Key> objects in <Console-Input>"); } ConsoleApi.KEY_EVENT_RECORD KER = new ConsoleApi.KEY_EVENT_RECORD(); KER.bKeyDown = Boolean.Parse(Key.Attribute("down").Value); KER.dwControlKeyState = (ConsoleApi.ControlKeyState)UInt32.Parse(Key.Attribute("ctrl").Value); KER.UnicodeChar = (char)UInt32.Parse(Key.Attribute("char").Value); KER.wRepeatCount = UInt16.Parse(Key.Attribute("repeat").Value); KER.wVirtualKeyCode = UInt16.Parse(Key.Attribute("key").Value); KER.wVirtualScanCode = UInt16.Parse(Key.Attribute("scan").Value); Keys.Add(KER); } //byte[] Raw = System.Convert.FromBase64String(xMsg.Value); //string Msg = Encoding.Unicode.GetString(Raw); lock (ProcessLock) { if (ConnectedProcess != null) { StringBuilder Text = new StringBuilder(); foreach (var Key in Keys) { Text.Append(Key.UnicodeChar); } //Debug.WriteLine("Writing to console as input: '" + Text + "'"); ConsoleApi.INPUT_RECORD[] irs = new ConsoleApi.INPUT_RECORD[1]; ConsoleApi.INPUT_RECORD ir = new ConsoleApi.INPUT_RECORD(); ir.EventType = (ushort)ConsoleApi.EventTypes.KEY_EVENT; foreach (var Key in Keys) { ir.KeyEvent = Key; irs[0] = ir; Conin.Write(irs); } //Thread.Sleep(50); //CT.DebugWholeConsole(); } } } if (xMsg.Name.LocalName == "Reload-Console") { lock (ProcessLock) CT.PendingReload = true; } } catch (Exception ex) { Debug.Write("Exception detected in SlaveCore.OnMessageReceived(): " + ex.ToString()); throw ex; } }
protected void ClientThreadEntry() { try { TcpListener listenerV4 = new TcpListener(IPAddress.Any, PortNumber); TcpListener listenerV6 = new TcpListener(IPAddress.IPv6Any, PortNumber); listenerV4.Start(10); listenerV6.Start(10); // Start listening for connections. Log.WriteLine("Listening for TCP connections on port " + PortNumber + "...\n", Severity.Debug); while (!Stopping) { TcpClient ClientRequest; try { if (listenerV4.Pending()) { ClientRequest = listenerV4.AcceptTcpClient(); } else if (listenerV6.Pending()) { ClientRequest = listenerV6.AcceptTcpClient(); } else { Thread.Sleep(250); continue; } Log.WriteLine("TCP Client accepted.\n", Severity.Debug); NetworkStream = new SslStream(ClientRequest.GetStream(), false); NetworkStream.AuthenticateAsServer(GetRemoteDesktopCertificate(), false, SslProtocols.Tls12, true); if (!NetworkStream.IsAuthenticated) { NetworkStream = null; Log.WriteLine("Unable to authenticate incoming connection from " + ((IPEndPoint)ClientRequest.Client.RemoteEndPoint).Address.ToString() + ".", Severity.Warning); ClientRequest.Close(); continue; } if (!NetworkStream.IsEncrypted) { NetworkStream = null; Log.WriteLine("Unable to encrypt incoming connection from " + ((IPEndPoint)ClientRequest.Client.RemoteEndPoint).Address.ToString() + ".", Severity.Warning); ClientRequest.Close(); continue; } // Display the properties and settings for the authenticated stream. Log.WriteLine("Authentication successful [host].\n", Severity.Debug); #if DEBUG DisplaySecurityLevel(Log, NetworkStream, Severity.Debug); DisplaySecurityServices(Log, NetworkStream, Severity.Debug); DisplayCertificateInformation(Log, NetworkStream, Severity.Debug); DisplayStreamProperties(Log, NetworkStream, Severity.Debug); #endif // Transmit our version info as a hello. System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly(); FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location); string version = fvi.FileVersion; string XmlMsg = "<Portal-Version Core-Library=\"" + fvi.FileVersion + "\" />"; byte[] RawMsg = Encoding.Unicode.GetBytes(XmlMsg); if (NetworkStream != null) { SendMessage(NetworkStream, RawMsg); } } catch (Win32Exception ex) { Log.WriteLine("Exception accepting new connection: " + ex.ToString(), Severity.Debug); if ((uint)ex.ErrorCode == 0x80004005) { // The credentials supplied to the pacakage were not recognized. // This error comes up rom NetworkStream.AuthenticateAsServer() or GetRemoteDesktopCertificate() for localhost slave when there is no administrative access. Log.WriteLine("This exception usually indicates that administrative access was required but not present.", Severity.Debug); } continue; } catch (Exception ex) { // Note: "The credentials supplied to the package were not recognized" is probably a sign that the code doesn't have Administrator access and can't access the subsystem or certificate it needs. Log.WriteLine("Exception accepting new connection: " + ex.ToString(), Severity.Debug); continue; } try { ServiceConnection(NetworkStream, ClientRequest, ClientRequest.GetStream()); } catch (Exception ex) { Log.WriteLine("Exception servicing connection: " + ex.ToString(), Severity.Debug); } lock (ProcessLock) { // Setting ConnectedProcess to null before accepting a new connection is essential to security. It prevents access to the console without a new authentication and Connect-Terminal. ConnectedProcess = null; // If we aren't attached to a console, FreeConsole() would return ERROR_INVALID_PARAMETER. ConsoleApi.FreeConsole(); Log.WriteLine("Closing connection in SlaveCore (if it isn't already).", Severity.Debug); if (ClientRequest != null) { ClientRequest.Dispose(); ClientRequest = null; } if (Conin != null) { Conin.Dispose(); Conin = null; } if (CT != null) { CT.Dispose(); CT = null; } } } lock (ProcessLock) { ConnectedProcess = null; foreach (var kvp in Processes) { kvp.Value.Dispose(); } Processes.Clear(); } } catch (Exception e) { Log.WriteLine("Error: " + e.ToString(), Severity.Error); } }