/// <summary> /// Start kmod and connect to it. /// </summary> private void StartKmod() { FileStream file = null; Socket listenSock = null; RegistryKey kwmRegKey = null; try { // Get the path to the kmod executable in the registry. kwmRegKey = Base.GetKwmLMRegKey(); String Kmod = "\"" + (String)kwmRegKey.GetValue("InstallDir", @"C:\Program Files\Teambox\Teambox Manager") + "\\kmod\\kmod.exe\""; // The directory where KMOD will save logs and its database for use with the kwm. String KmodDir = Misc.GetKmodDirPath(); Directory.CreateDirectory(KmodDir); // Start listening for kmod to connect when it'll be started. IPEndPoint endPoint = new IPEndPoint(IPAddress.Loopback, 0); listenSock = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); listenSock.Bind(endPoint); listenSock.Listen(1); int port = ((IPEndPoint)listenSock.LocalEndPoint).Port; // Start KMOD in debugging mode if our settings say so. String debug = Misc.ApplicationSettings.ktlstunnelLoggingLevel > 0 ? " -l 3" : ""; String args = " -C kmod_connect -p " + port + debug + " -m 20000 -k \"" + KmodDir + "\""; String cmdLine = Kmod + args; Logging.Log("About to start kmod.exe: " + cmdLine); m_kmodProc = new RawProcess(cmdLine); m_kmodProc.InheritHandles = false; m_kmodProc.CreationFlags = (uint)Syscalls.CREATION_FLAGS.CREATE_NO_WINDOW; m_kmodProc.Start(); // Wait for KMOD to connect for about 10 seconds. DateTime startTime = DateTime.Now; while (true) { SelectSockets select = new SelectSockets(); select.AddRead(listenSock); SetSelectTimeout(startTime, 10000, select, "no KMOD connection received"); Block(select); if (select.InRead(listenSock)) { m_kmodSock = listenSock.Accept(); m_kmodSock.Blocking = false; break; } } // Read the authentication data. byte[] authSockData = new byte[32]; byte[] authFileData = new byte[32]; startTime = DateTime.Now; int nbRead = 0; while (nbRead != 32) { SelectSockets select = new SelectSockets(); select.AddRead(m_kmodSock); SetSelectTimeout(startTime, 2000, select, "no authentication data received"); Block(select); int r = Base.SockRead(m_kmodSock, authSockData, nbRead, 32 - nbRead); if (r > 0) nbRead += r; } file = File.Open(KmodDir + "\\connect_secret", FileMode.Open); file.Read(authFileData, 0, 32); if (!Base.ByteArrayEqual(authFileData, authSockData)) throw new Exception("invalid authentication data received"); // Set the transport. m_transport = new K3pTransport(m_kmodSock); } finally { if (file != null) file.Close(); if (listenSock != null) listenSock.Close(); if (kwmRegKey != null) kwmRegKey.Close(); } }
/// <summary> /// Display an error message to the user and exit the application if /// required. /// </summary> public static void HandleError(String errorMessage, bool fatalFlag) { string msg = "An error has been detected." + Environment.NewLine + Environment.NewLine + errorMessage + Environment.NewLine + Environment.NewLine; if (fatalFlag) { msg += "Please restart your " + Base.GetKwmString(); } else { msg += "Please contact your technical support for further information."; } if (!fatalFlag) { Misc.KwmTellUser(msg, MessageBoxIcon.Error); return; } // The .Net framework is critically brain damaged when it comes to // handling fatal errors. There are basically two choices: exit // the process right away or try to get the application to display // the error and quit. // // The former choice is sane; there is no risk of further data // corruption if the process exits right away. The lack of any // error message is problematic however. We work around this by // spawning an external program, if possible, to report the error // before exiting the process. // // The second choice cannot be done sanely. If a MessageBox() // call is made immediately when the error is detected, then // the UI will be reentered and the damage may spread further. // Typically this causes multiple fatal error messages to // appear. After some investigation, I believe this is impossible // to prevent. The best available thing is ThreadAbortException, // which has weird semantics and is considered deprecated and // doesn't do the right thing in worker threads. // Exit right away. if (!FatalErrorMsgOKFlag || m_fatalErrorCaughtFlag) Environment.Exit(1); // We have caught a fatal error. Prevent the other threads from // spawning a fatal error. There is an inherent race condition // here which is best left alone; mutexes have no business here. m_fatalErrorCaughtFlag = true; // Spawn a program to display the message. try { String startupLine = '"' + Application.ExecutablePath + '"' + " "; startupLine += "\"-M\" \"" + EscapeArgForFatalError(msg) + "\""; RawProcess p = new RawProcess(startupLine); p.InheritHandles = false; p.Start(); } // Ignore all exceptions. catch (Exception) { } // Get out. Environment.Exit(1); }
/// <summary> /// Clean up the KMOD process and socket. /// </summary> private void CleanUpKmod() { if (m_kmodProc != null) { m_kmodProc.Terminate(); m_kmodProc = null; } if (m_kmodSock != null) { m_kmodSock.Close(); m_kmodSock = null; } }
/// <summary> /// Disconnect and close ktlstunnel. /// </summary> public void Terminate() { Disconnect(); if (TunnelProcess != null) { TunnelProcess.Terminate(); TunnelProcess = null; } }
/// <summary> /// Create a listening socket and spawn ktlstunnel process. /// </summary> public void BeginTls(string extraParams) { IPEndPoint endPoint = new IPEndPoint(IPAddress.Loopback, 0); Sock = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); Sock.Bind(endPoint); Sock.Listen(1); /* Create a logging dir for ktlstunnel, if it does not exist */ if (!Directory.Exists(Misc.GetKtlstunnelLogFilePath())) { Directory.CreateDirectory(Misc.GetKtlstunnelLogFilePath()); } // Start ktlstunnel as such. // ktlstunnel localhost ((IPEndPoint)Listener.LocalEndPoint).Port Host Port [-r host:port] String loggingPath = "-L " + "\"" + Misc.GetKtlstunnelLogFilePath() + "ktlstunnel-" + Base.GetLogFileName() + "\" "; String loggingLevel = ""; if(Misc.ApplicationSettings.ktlstunnelLoggingLevel == 1) { loggingLevel = "-l minimal "; loggingLevel += loggingPath; } else if (Misc.ApplicationSettings.ktlstunnelLoggingLevel == 2) { loggingLevel = "-l debug "; loggingLevel += loggingPath; } string startupLine = "\"" + Base.GetKcsInstallationPath() + @"ktlstunnel\ktlstunnel.exe" + "\" " + loggingLevel + "localhost " + ((IPEndPoint)Sock.LocalEndPoint).Port.ToString() + " " + Host + " " + Port + " " + extraParams; Logging.Log("Starting ktlstunnel.exe : " + startupLine); TunnelProcess = new RawProcess(startupLine); TunnelProcess.InheritHandles = false; TunnelProcess.CreationFlags = (uint)Syscalls.CREATION_FLAGS.CREATE_NO_WINDOW; TunnelProcess.Start(); }
/// <summary> /// Handles when a Start Ticket is received. /// </summary> /// <param name="_context"></param> private void OnStartTicketResponse(KasQuery ctx) { Debug.Assert(m_state == AppSharingState.StartTicket); AnpMsg cmd = ctx.Cmd; AnpMsg res = ctx.Res; if (m_state != AppSharingState.StartTicket) { Logging.Log(2, "Received spurious Screen Sharing Start ticket."); return; } if (res.Type == KAnpType.KANP_RES_VNC_START_TICKET) { m_ticket = res.Elements[0].Bin; /* Start MetaVNC (specify the desired window's handle)*/ int handle = Int32.Parse((String)ctx.MetaData[0]); bool supportSession = (bool)ctx.MetaData[2]; String vncServerPath = "\"" + Base.GetKwmInstallationPath() + @"vnc\kappserver.exe" + "\""; String args = (handle == 0) ? " -shareall" : " -sharehwnd " + (String)ctx.MetaData[0]; // If a window is being shared (not the desktop), set // it visible and in foreground. if (handle != 0) { IntPtr hWnd = new IntPtr(handle); if (Syscalls.IsIconic(hWnd)) Syscalls.ShowWindowAsync(hWnd, (int)Syscalls.WindowStatus.SW_RESTORE); Base.KSetForegroundWindow(hWnd); } /* Remove any indication of previous server's listening port */ RegistryKey port = Base.GetKwmCURegKey(); port.DeleteValue(m_portRegItem, false); port.DeleteValue(m_portRegItemWritten, false); port.Close(); // Set registry options in order to allow/deny // remote control. SetSupportSessionMode(supportSession); Logging.Log("Starting the first instance of MetaVNC (no args)."); m_serverProcess = new RawProcess(vncServerPath); m_serverProcess.CreationFlags = (uint)Syscalls.CREATION_FLAGS.CREATE_NO_WINDOW; m_serverProcess.InheritHandles = false; Syscalls.STARTUPINFO si = new Syscalls.STARTUPINFO(); si.dwFlags = 1; m_serverProcess.StartupInfo = si; m_serverProcess.ProcessEnd += HandleOnProcessEnd; m_serverProcess.Start(); Logging.Log("Started."); /* Wait until we can get our hands on the process's window handle */ int retries = 15; bool found = false; while (retries > 0) { Logging.Log("Trying to start WinVNC ( " + retries + ")"); IntPtr serverHandle = Syscalls.FindWindow("WinVNC Tray Icon", 0); if (serverHandle != IntPtr.Zero) { found = true; break; } System.Threading.Thread.Sleep(250); retries--; } if (!found) throw new Exception("Screen sharing error: server could not be started."); /* Start another process of metaVNC. * This is needed to pass the window handle * we want, it was easier to do this that way * than to pass it directly on the cmd line when * starting the process the first time. */ Logging.Log("Starting MetaVNC for the second time (this is normal): " + vncServerPath + args); m_dummyServerProcess = new RawProcess(vncServerPath + args); m_dummyServerProcess.CreationFlags = (uint)Syscalls.CREATION_FLAGS.CREATE_NO_WINDOW; m_dummyServerProcess.InheritHandles = false; m_dummyServerProcess.StartupInfo = si; m_dummyServerProcess.ProcessEnd += HandleOnProcessEnd; m_dummyServerProcess.Start(); // Find out on which port we need to tell ktlstunnel to // connect to the server RegistryKey kwmKey = Base.GetKwmCURegKey(); object readPort = null; object portOK = null; // Actively poll the synchronization key for 6 seconds. retries = 30; found = false; while (retries > 0) { portOK = kwmKey.GetValue(m_portRegItemWritten); if (portOK != null) { found = true; break; } System.Threading.Thread.Sleep(200); retries--; } if (!found) { if (kwmKey != null) kwmKey.Close(); ResetToIdle(); Misc.KwmTellUser("Screen sharing error: a timeout occured while waiting for the server's listening port."); return; } // At this stage we can presume the port was // correctly really written by the VNC server. readPort = kwmKey.GetValue(m_portRegItem); kwmKey.Close(); if (port == null) { ResetToIdle(); Misc.KwmTellUser("Screen sharing error: unable to read the server's listening port."); return; } Logging.Log("Screen Sharing server's port: " + (int)readPort); /* Connect a new tunnel */ m_tunnel = Helper.CreateTunnel(); m_tunnel.Connect("localhost", (int)readPort); /* Negociate role */ AnpMsg inMsg = Helper.NewKAnpMsg(KAnpType.KANP_CMD_MGT_SELECT_ROLE); inMsg.AddUInt32(KAnpType.KANP_KCD_ROLE_APP_SHARE); m_tunnel.SendMsg(inMsg); AnpMsg outMsg = m_tunnel.GetMsg(); if (outMsg.Type == KAnpType.KANP_RES_FAIL) { ResetToIdle(); Misc.KwmTellUser(outMsg.Elements[1].String); return; } Debug.Assert(outMsg.Type == KAnpType.KANP_RES_OK); AnpMsg startSession = Helper.NewKAnpMsg(KAnpType.KANP_CMD_VNC_START_SESSION); startSession.AddBin(m_ticket); startSession.AddString((String)ctx.MetaData[1]); m_tunnel.SendMsg(startSession); outMsg = m_tunnel.GetMsg(); if (outMsg.Type == KAnpType.KANP_RES_FAIL) { ResetToIdle(); Misc.KwmTellUser(outMsg.Elements[1].String); return; } Debug.Assert(outMsg.Type == KAnpType.KANP_RES_VNC_START_SESSION); m_createdSessionID = outMsg.Elements[0].UInt64; /* Disconnect tunnel so it can connect to metaVNC server */ Logging.Log("About to call disconnect on ktlstunnel."); m_tunnel.Disconnect(); CurrentState = AppSharingState.Started; m_connectedSessionID = 0; StopInactivityMonitor(); // 10 minutes inactivityMonitor = MonitorCreator.CreateInstance(MonitorType.GlobalHookMonitor); inactivityMonitor.Interval = 600000; inactivityMonitor.Elapsed += new ElapsedEventHandler(HandleSessionTimeout); inactivityMonitor.Enabled = true; } else if (res.Type == KAnpType.KANP_RES_FAIL) { ResetToIdle(); Misc.KwmTellUser(res.Elements[1].String); } else { Logging.Log(2, "unexpected response in OnStartTicketResponse"); } }
private void OnConnectTicketResponse(KasQuery ctx) { Debug.Assert(m_state == AppSharingState.ConnectTicket); AnpMsg cmd = ctx.Cmd; AnpMsg res = ctx.Res; if (m_state != AppSharingState.ConnectTicket) { Logging.Log(2, "Received spurious Screen Sharing Connect ticket."); return; } if (res.Type == KAnpType.KANP_RES_VNC_CONNECT_TICKET) { m_ticket = res.Elements[0].Bin; // Remove any indication of previous client's listening port RegistryKey port = Base.GetKwmCURegKey(); port.DeleteValue(m_portRegItem, false); port.DeleteValue(m_portRegItemWritten, false); port.Close(); /* Start client. Force one parameter so that it does not * prompt for connection parameters */ String vncClientPath = "\"" + Base.GetKwmInstallationPath() + @"vnc\kappviewer.exe" + "\""; String args = " /shared /notoolbar /disableclipboard /encoding tight /compresslevel 9 localhost"; m_viewerProcess = new RawProcess(vncClientPath + args); m_viewerProcess.InheritHandles = false; m_viewerProcess.ProcessEnd += HandleOnProcessEnd; m_viewerProcess.Start(); RegistryKey kwmKey = Base.GetKwmCURegKey(); object _port = null; object _portOK = null; // Actively poll the synchronization // key for 6 secs int retries = 30; bool found = false; while (retries > 0) { _portOK = kwmKey.GetValue(m_portRegItemWritten); if (_portOK != null) { found = true; break; } System.Threading.Thread.Sleep(200); retries--; } if (!found) { if (kwmKey != null) kwmKey.Close(); ResetToIdle(); Misc.KwmTellUser("a timeout occured while waiting for the client's listening port."); return; } // At this stage we can presume the port was // correctly really written by the VNC server. _port = kwmKey.GetValue(m_portRegItem); kwmKey.Close(); if (_port == null) { ResetToIdle(); Misc.KwmTellUser("could not read client's listening port."); return; } Logging.Log("Read VNC client's port: " + (int)_port); /* Connect a new tunnel */ IAnpTunnel tunnel = Helper.CreateTunnel(); tunnel.Connect("localhost", (int)_port); /* Negociate role */ AnpMsg inMsg = Helper.NewKAnpMsg(KAnpType.KANP_CMD_MGT_SELECT_ROLE); inMsg.AddUInt32(KAnpType.KANP_KCD_ROLE_APP_SHARE); tunnel.SendMsg(inMsg); AnpMsg outMsg = tunnel.GetMsg(); if (outMsg.Type == KAnpType.KANP_RES_FAIL) { ResetToIdle(); Misc.KwmTellUser(outMsg.Elements[1].String); return; } Debug.Assert(outMsg.Type == KAnpType.KANP_RES_OK); AnpMsg startSession = Helper.NewKAnpMsg(KAnpType.KANP_CMD_VNC_CONNECT_SESSION); startSession.AddBin(m_ticket); tunnel.SendMsg(startSession); outMsg = tunnel.GetMsg(); if (outMsg.Type == KAnpType.KANP_RES_FAIL) { ResetToIdle(); Misc.KwmTellUser(outMsg.Elements[1].String); return; } Debug.Assert(outMsg.Type == KAnpType.KANP_RES_OK); Logging.Log("About to call disconnect"); tunnel.Disconnect(); CurrentState = AppSharingState.Connected; } else if (res.Type == KAnpType.KANP_RES_FAIL) { ResetToIdle(); Misc.KwmTellUser(res.Elements[1].String); } else { Logging.Log("unexpected response in OnConnectTicketResponse"); } }
/// <summary> /// Callback when one of our child processes (VNC client or server) die. /// </summary> private void HandleOnProcessEnd(object _sender, EventArgs _args) { if (Misc.MainForm.InvokeRequired) { Misc.MainForm.BeginInvoke(new EventHandler(HandleOnProcessEnd), new object[] { _sender, _args }); return; } RawProcess.ProcEndEventArgs args = (RawProcess.ProcEndEventArgs)_args; RawProcess process = (RawProcess)_sender; if (process == m_dummyServerProcess) { Logging.Log("m_dummyServerProcess exited: " + args.ExitCode); m_dummyServerProcess = null; } else if (process == m_serverProcess) { Logging.Log("m_serverProcess exited"); // Do not change our CurrentState here: we will catch the // SessionEnd event sometime. m_serverProcess = null; } else if (process == m_viewerProcess) { Logging.Log("m_viewerProcess exited"); CurrentState = AppSharingState.Idle; m_viewerProcess = null; } else { Logging.Log("Unknown process exited! (" + process.CommandLine + ")"); } }