├── .gitignore ├── .vs └── SSHDebugger │ └── xs │ └── UserPrefs.xml ├── README.md ├── SSHDebugger.png ├── SSHDebugger.sln ├── SSHDebugger ├── Debugger │ ├── clsDebuggerOptionsDialog.cs │ ├── clsSSHDebuggerEngine.cs │ └── clsSSHSoftDebuggerSession.cs ├── Extensions │ └── ExtGdkKey.cs ├── Host │ └── clsHost.cs ├── ProjectTemplate.xpt.xml ├── Properties │ ├── AssemblyInfo.cs │ ├── Manifest.addin.xml │ └── SSHDebugger.png ├── SSHDebugger.csproj ├── Terminal │ ├── FrontEnd │ │ ├── windowTerminalGTK.cs │ │ └── windowTerminalVTE.cs │ ├── ITerminal.cs │ └── clsSSHTerminal.cs ├── packages.config └── ssh-host.xft.xml ├── addin-project.xml ├── build.sh └── vte-sharp.dll /.gitignore: -------------------------------------------------------------------------------- 1 | *sslcert* 2 | *StyleCop* 3 | ################### 4 | # compiled source # 5 | ################### 6 | *.com 7 | *.class 8 | *.exe 9 | *.pdb 10 | *.dll.config 11 | *.cache 12 | *.suo 13 | # Include dlls if they’re in the NuGet packages directory 14 | !/packages/*/lib/*.dll 15 | # Include dlls if they're in the CommonReferences directory 16 | !*CommonReferences/*.dll 17 | #################### 18 | # VS Upgrade stuff # 19 | #################### 20 | UpgradeLog.XML 21 | _UpgradeReport_Files/ 22 | ############### 23 | # Directories # 24 | ############### 25 | bin/ 26 | obj/ 27 | TestResults/ 28 | [Pp]ackages 29 | ################### 30 | # Web publish log # 31 | ################### 32 | *.Publish.xml 33 | ############# 34 | # Resharper # 35 | ############# 36 | /_ReSharper.* 37 | *.ReSharper.* 38 | ############ 39 | # Packages # 40 | ############ 41 | # it’s better to unpack these files and commit the raw source 42 | # git has its own built in compression methods 43 | *.7z 44 | *.dmg 45 | *.gz 46 | *.iso 47 | *.jar 48 | *.rar 49 | *.tar 50 | *.zip 51 | ###################### 52 | # Logs and databases # 53 | ###################### 54 | *.log 55 | *.sqlite 56 | # OS generated files # 57 | ###################### 58 | .DS_Store? 59 | ehthumbs.db 60 | Icon? 61 | Thumbs.db 62 | *~ 63 | *.userprefs 64 | *.mpack 65 | run.sh -------------------------------------------------------------------------------- /.vs/SSHDebugger/xs/UserPrefs.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SSH Debugger for Monodevelop & Xamarin Studio 2 | 3 | Deploy, execute and debug your .NET apps running on a remote computer, directly from Monodevelop & Xamarin Studio. 4 | Includes a full XTerm console, on the Linux version. Standard text window on Win & Mac. 5 | 6 | ![alt tag](https://raw.githubusercontent.com/logicethos/SSHDebugger/master/SSHDebugger.png) 7 | 8 | Uses: 9 | 10 | * Develop .NET apps for embedded devices and small computers such as the Raspberry Pi. 11 | * Deploy and debug apps running in a datacentre, docker container or virtual machine. 12 | 13 | Features: 14 | 15 | * Requires no port forwarding, or special firewall rules. Just ssh access to the host. 16 | * Built-in XTerm console to support MonoCurses, Console.Output() & Console.Input(). 17 | * Simple pre-debug scripting, to copy your build files to the remote host. 18 | * Build scripts for different devices, and store them in your project. 19 | * Secure communication. Password, or key-pair security. 20 | 21 | Steps for use: 22 | 1. Make sure your remote computer has mono installed, and is accessible from ssh. 23 | 2. Add the SSH Debugger template from this add-in to your project, and change the host address. 24 | 3. Add any dependency files to the script (dll's, data etc) for copying (scp or rsync). 25 | 4. Run -> Run With -> SSH Debugger 26 | 27 | See demo https://www.youtube.com/watch?v=W2sZ56q5C8A 28 | 29 | Dependencies: 30 | 31 | Windows & Mac: none 32 | 33 | Linux requires Gnome VTE terminal libs for gtk 34 | 35 | * libgnome2.0-cil-dev 36 | * libvte-dev 37 | 38 | Suggested future improvements: 39 | 40 | * Improve Xterm Terminal, flesh out the UI, copy, paste, go fully managed to remove Gnome VTE dependency? 41 | * Automate template generation, to fill in known dependencies. 42 | * Default to rsync where available, or scp only files that have changed. 43 | * C# pre-debug scripting. 44 | * Option to Detach & Reattach debugger? Reconnect if connection lost? 45 | * Wizard to prepare a host, test for stability, setup password-less login using private keys. 46 | * Add more customisation to script (e.g SOCKS support, default Xterm settings). 47 | * Windows host support 48 | -------------------------------------------------------------------------------- /SSHDebugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicethos/SSHDebugger/7e200357f6e1ec721dd564dfc20df9e96668948a/SSHDebugger.png -------------------------------------------------------------------------------- /SSHDebugger.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SSHDebugger", "SSHDebugger\SSHDebugger.csproj", "{A84F91BF-12DD-4CA1-A9D6-2186E9F2301E}" 5 | EndProject 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D1606C53-8F02-4FD9-8891-0B587035F33A}" 7 | ProjectSection(SolutionItems) = preProject 8 | README.md = README.md 9 | addin-project.xml = addin-project.xml 10 | EndProjectSection 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | Debug-VTE|Any CPU = Debug-VTE|Any CPU 17 | Release-VTE|Any CPU = Release-VTE|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {A84F91BF-12DD-4CA1-A9D6-2186E9F2301E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {A84F91BF-12DD-4CA1-A9D6-2186E9F2301E}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {A84F91BF-12DD-4CA1-A9D6-2186E9F2301E}.Debug-VTE|Any CPU.ActiveCfg = Debug-VTE|Any CPU 23 | {A84F91BF-12DD-4CA1-A9D6-2186E9F2301E}.Debug-VTE|Any CPU.Build.0 = Debug-VTE|Any CPU 24 | {A84F91BF-12DD-4CA1-A9D6-2186E9F2301E}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {A84F91BF-12DD-4CA1-A9D6-2186E9F2301E}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {A84F91BF-12DD-4CA1-A9D6-2186E9F2301E}.Release-VTE|Any CPU.ActiveCfg = Release-VTE|Any CPU 27 | {A84F91BF-12DD-4CA1-A9D6-2186E9F2301E}.Release-VTE|Any CPU.Build.0 = Release-VTE|Any CPU 28 | EndGlobalSection 29 | GlobalSection(NestedProjects) = preSolution 30 | EndGlobalSection 31 | GlobalSection(MonoDevelopProperties) = preSolution 32 | version = 0.5 33 | Policies = $0 34 | $0.DotNetNamingPolicy = $1 35 | $1.DirectoryNamespaceAssociation = PrefixedHierarchical 36 | $0.VersionControlPolicy = $2 37 | EndGlobalSection 38 | EndGlobal 39 | -------------------------------------------------------------------------------- /SSHDebugger/Debugger/clsDebuggerOptionsDialog.cs: -------------------------------------------------------------------------------- 1 | // 2 | // clsSSHDebuggerEngine.cs 3 | // 4 | // Author: 5 | // Michael Hutchinson 6 | // Stuart Johnson 7 | // 8 | // Copyright (c) 2010 Novell, Inc. 9 | // Copyright (c) 2015 Stuart Johnson, Logic Ethos Ltd. 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | using System; 30 | using MonoDevelop.Core; 31 | using System.Linq; 32 | 33 | namespace SSHDebugger 34 | { 35 | 36 | public class clsDebuggerOptionsDialog : Gtk.Dialog 37 | { 38 | public clsHost SelectedHost; 39 | Gtk.Button newButton = new Gtk.Button ("New Host"); 40 | Gtk.Button connectButton = new Gtk.Button ("Run"); 41 | Gtk.ComboBox combo; 42 | 43 | const Gtk.ResponseType connectResponse = Gtk.ResponseType.Ok; 44 | const Gtk.ResponseType newResponse = Gtk.ResponseType.Accept; 45 | 46 | 47 | Properties properties; 48 | 49 | //TODO: dropdown menus for picking string substitutions. also substitutions for port, ip 50 | public clsDebuggerOptionsDialog () : base ( 51 | "SSH Debug", MonoDevelop.Ide.MessageService.RootWindow, 52 | Gtk.DialogFlags.DestroyWithParent | Gtk.DialogFlags.Modal) 53 | { 54 | properties = PropertyService.Get ("MonoDevelop.Debugger.Soft.SSHDebug", new Properties()); 55 | 56 | AddActionWidget (connectButton, connectResponse); 57 | AddActionWidget (newButton, newResponse); 58 | AddActionWidget (new Gtk.Button (Gtk.Stock.Cancel), Gtk.ResponseType.Cancel); 59 | 60 | var table = new Gtk.Table (1, 2, false); 61 | table.BorderWidth = 6; 62 | VBox.PackStart (table, true, true, 0); 63 | 64 | table.Attach (new Gtk.Label ("Host") { Xalign = 0 }, 0, 1, 0, 1); 65 | 66 | var values = clsSSHDebuggerEngine.HostsList.Select (x => String.Format ("{0} ({1})", x.Name, System.IO.Path.GetFileName (x.ScriptPath))).ToArray (); 67 | combo = new Gtk.ComboBox (values); 68 | 69 | int row=0; 70 | if (clsSSHDebuggerEngine.HostsList.Count == 0) { 71 | connectButton.Sensitive = false; 72 | } else { 73 | 74 | var lastSelected = clsSSHDebuggerEngine.HostsList.Find (x => x.ScriptPath == properties.Get ("host", "")); 75 | if (lastSelected != null) 76 | { 77 | row = clsSSHDebuggerEngine.HostsList.IndexOf (lastSelected); 78 | if (row == -1) 79 | row = 0; 80 | } 81 | Gtk.TreeIter iter; 82 | combo.Model.IterNthChild (out iter, row); 83 | combo.SetActiveIter (iter); 84 | SelectedHost = clsSSHDebuggerEngine.HostsList [combo.Active]; 85 | 86 | combo.Changed += (object sender, EventArgs e) => 87 | { 88 | SelectedHost = clsSSHDebuggerEngine.HostsList [combo.Active]; 89 | }; 90 | 91 | } 92 | 93 | table.Attach (combo, 1, 2, 0, 1); 94 | 95 | 96 | VBox.ShowAll (); 97 | 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /SSHDebugger/Debugger/clsSSHDebuggerEngine.cs: -------------------------------------------------------------------------------- 1 | // 2 | // clsSSHDebuggerEngine.cs 3 | // 4 | // Author: 5 | // Michael Hutchinson 6 | // Stuart Johnson 7 | // 8 | // Copyright (c) 2010 Novell, Inc. 9 | // Copyright (c) 2015 Stuart Johnson, Logic Ethos Ltd. 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | using System; 30 | using System.Diagnostics; 31 | using Mono.Debugging.Client; 32 | using MonoDevelop.Core; 33 | using MonoDevelop.Core.Execution; 34 | using System.Net; 35 | using System.Collections.Generic; 36 | using Mono.Debugging.Soft; 37 | using MonoDevelop.Ide; 38 | using System.Linq; 39 | using Gtk; 40 | using System.IO; 41 | using MonoDevelop.Projects; 42 | using System.Threading; 43 | using System.Threading.Tasks; 44 | using MonoDevelop.Debugger; 45 | 46 | 47 | namespace SSHDebugger 48 | { 49 | public class clsSSHDebuggerEngine: DebuggerEngineBackend 50 | { 51 | clsSSHSoftDebuggerSession DebuggerSession = null; 52 | public static List HostsList = new List(); 53 | 54 | clsHost selectedHost = null; 55 | AutoResetEvent termWait = new AutoResetEvent (false); 56 | 57 | public override bool CanDebugCommand (ExecutionCommand cmd) 58 | { 59 | return true; 60 | } 61 | 62 | public bool BuildList() 63 | { 64 | bool addedNew = false; 65 | 66 | foreach (var file in IdeApp.ProjectOperations.CurrentSelectedProject.Files.Where(x => x.Name.EndsWith(".ssh.txt"))) 67 | { 68 | if (!HostsList.Exists(x=>x.ScriptPath == file.FilePath)) 69 | { 70 | new clsHost(file.FilePath); 71 | addedNew=true; 72 | } 73 | } 74 | return addedNew; 75 | } 76 | 77 | public override DebuggerSession CreateSession () 78 | { 79 | DebuggerSession = new clsSSHSoftDebuggerSession (); 80 | return DebuggerSession; 81 | } 82 | 83 | public override DebuggerStartInfo CreateDebuggerStartInfo (ExecutionCommand c) 84 | { 85 | 86 | SoftDebuggerStartInfo dsi = null; 87 | try{ 88 | 89 | //If new host, no host is selected, or ther terminal window is closed 90 | if (BuildList () || selectedHost==null || selectedHost.Terminal==null) 91 | { 92 | //Load any new templates 93 | selectedHost = InvokeSynch (GetDebuggerInfo); //Query user for selected host 94 | } 95 | 96 | if (selectedHost != null) { 97 | 98 | if (selectedHost.Terminal == null) 99 | { 100 | #if VTE 101 | selectedHost.Terminal = new windowTerminalVTE(selectedHost); 102 | #else 103 | selectedHost.Terminal = new windowTerminalGTK(selectedHost); 104 | #endif 105 | } 106 | else 107 | { 108 | selectedHost.Terminal.Front(); 109 | } 110 | 111 | var done = new ManualResetEvent (false); 112 | Task.Run (() => { 113 | dsi = selectedHost.ProcessScript (true); 114 | }).ContinueWith ((t) => { 115 | done.Set (); 116 | }); 117 | 118 | while (true) { 119 | Gtk.Application.RunIteration (); 120 | if (done.WaitOne (0)) 121 | break; 122 | } 123 | 124 | } 125 | 126 | if (dsi != null) selectedHost.Terminal.SSH.WriteLine("Starting debugger"); 127 | 128 | return dsi; 129 | } 130 | catch (ThreadAbortException) //User closed terminal (probably) 131 | { 132 | return null; 133 | } 134 | catch (Exception ex) 135 | { 136 | Gtk.Application.Invoke (delegate 137 | { 138 | using (var md = new MessageDialog(null, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, "Terminal error "+ex.Message)) 139 | { 140 | md.Run (); 141 | md.Destroy(); 142 | } 143 | }); 144 | return null; 145 | } 146 | 147 | } 148 | 149 | void OpenTerminal() 150 | { 151 | 152 | } 153 | 154 | clsHost GetDebuggerInfo () 155 | { 156 | ResponseType response; 157 | String filepath = null; 158 | clsHost selectedHost = null; 159 | 160 | try { 161 | 162 | using (var dlg = new clsDebuggerOptionsDialog ()) 163 | { 164 | response = (Gtk.ResponseType) dlg.Run(); 165 | if (dlg.SelectedHost!=null) 166 | { 167 | filepath = dlg.SelectedHost.ScriptPath; 168 | selectedHost = dlg.SelectedHost; 169 | } 170 | dlg.Destroy(); 171 | } 172 | 173 | while (GLib.MainContext.Iteration ()); 174 | 175 | if (response == Gtk.ResponseType.Accept) { 176 | 177 | Gtk.Application.Invoke (delegate 178 | { 179 | using (var md = new MessageDialog(null, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, "Please add a ssh template file manually to your project")) 180 | { 181 | md.Run (); 182 | md.Destroy(); 183 | } 184 | }); 185 | return null; 186 | } else if (response != Gtk.ResponseType.Ok) 187 | return null; 188 | 189 | var properties = PropertyService.Get ("MonoDevelop.Debugger.Soft.SSHDebug", new Properties ()); 190 | properties.Set ("host", filepath); 191 | 192 | return selectedHost; 193 | } 194 | catch(Exception ex) { 195 | Gtk.Application.Invoke (delegate { 196 | using (var md = new MessageDialog (null, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, ex.Message)) { 197 | md.Title = "SoftDebuggerStartInfo"; 198 | md.Run (); 199 | md.Destroy(); 200 | } 201 | }); 202 | return null; 203 | } 204 | } 205 | 206 | static T InvokeSynch (Func func) 207 | { 208 | 209 | if (MonoDevelop.Core.Runtime.IsMainThread) 210 | return func (); 211 | 212 | var ev = new System.Threading.ManualResetEvent (false); 213 | T val = default (T); 214 | Exception caught = null; 215 | Gtk.Application.Invoke (delegate { 216 | try { 217 | val = func (); 218 | } catch (Exception ex) { 219 | caught = ex; 220 | } finally { 221 | ev.Set (); 222 | } 223 | }); 224 | ev.WaitOne (); 225 | if (caught != null) 226 | throw caught; 227 | return val; 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /SSHDebugger/Debugger/clsSSHSoftDebuggerSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MonoDevelop.Core.Execution; 3 | using Mono.Debugging.Client; 4 | using Mono.Debugging.Soft; 5 | using Gtk; 6 | using MonoDevelop.Core; 7 | using System.Diagnostics; 8 | using System.Collections.Generic; 9 | 10 | namespace SSHDebugger 11 | { 12 | class clsSSHSoftDebuggerSession : SoftDebuggerSession 13 | { 14 | protected override void OnRun (DebuggerStartInfo startInfo) 15 | { 16 | try{ 17 | if (startInfo == null) { 18 | EndSession (); 19 | return; 20 | } 21 | 22 | base.OnRun (startInfo); 23 | }catch (Exception ex) { 24 | Gtk.Application.Invoke (delegate { 25 | using (var md = new MessageDialog (null, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, ex.Message)) { 26 | md.Title = "CustomSoftDebuggerSession"; 27 | md.Run (); 28 | md.Destroy (); 29 | } 30 | }); 31 | } 32 | } 33 | 34 | 35 | protected override void EndSession () 36 | { 37 | base.EndSession (); 38 | 39 | } 40 | 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /SSHDebugger/Extensions/ExtGdkKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace SSHDebugger.Extensions 3 | { 4 | public static class ExtGdkKey 5 | { 6 | public static char GetChar(this Gdk.Key key) 7 | { 8 | return (char)Gdk.Keyval.ToUnicode((uint)key); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SSHDebugger/Host/clsHost.cs: -------------------------------------------------------------------------------- 1 | // 2 | // clsHost.cs 3 | // 4 | // Author: 5 | // Stuart Johnson 6 | // 7 | // Copyright (c) 2015 Stuart Johnson, Logic Ethos Ltd. 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | using System; 28 | using System.IO; 29 | using Mono.Debugging.Soft; 30 | using Gtk; 31 | using MonoDevelop.Ide; 32 | using MonoDevelop.Core; 33 | using System.Net; 34 | using MonoDevelop.Projects; 35 | using System.Linq; 36 | using System.Text; 37 | using System.Threading.Tasks; 38 | using System.Threading; 39 | using System.Diagnostics; 40 | 41 | namespace SSHDebugger 42 | { 43 | public class clsHost : IDisposable 44 | { 45 | 46 | 47 | 48 | public String LocalHost { get; private set;} 49 | public UInt32 LocalTunnelPort { get; private set;} 50 | public UInt32 RemoteTunnelPort { get; private set;} 51 | 52 | public String Name { get; private set;} 53 | public int RemoteSSHPort { get; private set;} 54 | public String ScriptPath { get; private set;} 55 | 56 | 57 | public String Username { get; private set;} 58 | public String Password { get; set;} 59 | public String RemoteHost { get; private set;} 60 | 61 | public String WorkingDir { get; private set;} 62 | 63 | public String build_exe_path { get; private set;} 64 | 65 | public String TerminalEmulation { get; private set;} 66 | public String TerminalFont { get; private set;} 67 | public int TerminalRows { get; private set;} 68 | public int TerminalCols { get; private set;} 69 | 70 | 71 | ITerminal _terminal = null; 72 | public ITerminal Terminal 73 | { 74 | get{ return _terminal;} 75 | set{ 76 | Password = ""; //Reset password 77 | _terminal = value; 78 | } 79 | } 80 | 81 | 82 | String _hostString; 83 | public String HostString 84 | { 85 | get { return _hostString;} 86 | 87 | private set 88 | { 89 | _hostString = value; 90 | 91 | var pt1 = value.IndexOf ('@'); 92 | var pt2 = value.IndexOf (':'); 93 | if (pt1 > -1) Username = value.Substring (0, pt1); 94 | if (pt2 > -1 && pt2 < pt1) { //password included in url 95 | var userSplit = Username.Split (new char[]{ ':' }, 2); 96 | Username = userSplit[0]; 97 | Password = userSplit[1]; 98 | pt2 = value.IndexOf (':',pt1); 99 | } 100 | 101 | if (pt2 > -1) { 102 | RemoteSSHPort = int.Parse (value.Substring (pt2 + 1, value.Length - pt2 - 1)); 103 | } else { 104 | RemoteSSHPort = 22; 105 | pt2 = value.Length; 106 | } 107 | RemoteHost = value.Substring (pt1+1, pt2 - pt1 -1); 108 | } 109 | } 110 | 111 | 112 | 113 | public clsHost (String filePath) 114 | { 115 | var buildTarget = MonoDevelop.Ide.IdeApp.ProjectOperations.CurrentSelectedBuildTarget; 116 | var buildConfigs = ((DotNetProject)buildTarget).Configurations; 117 | build_exe_path = buildConfigs.Cast ().First (x => x.DebugType == "full").CompiledOutputName; 118 | 119 | ScriptPath = filePath; 120 | LocalHost = IPAddress.Loopback.ToString (); 121 | LocalTunnelPort = 10123; 122 | 123 | TerminalFont = "Monospace 10"; 124 | TerminalCols = 120; 125 | TerminalRows = 50; 126 | TerminalEmulation = "vt100"; 127 | 128 | try 129 | { 130 | ProcessScript (false); 131 | clsSSHDebuggerEngine.HostsList.Add (this); 132 | } 133 | catch (Exception ex) 134 | { 135 | Gtk.Application.Invoke (delegate { 136 | using (var md = new MessageDialog (null, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok,ex.Message)) { 137 | md.Title = "ProcessScript"; 138 | md.Run (); 139 | md.Destroy (); 140 | } 141 | }); 142 | } 143 | 144 | } 145 | 146 | 147 | public SoftDebuggerStartInfo ProcessScript(bool Execute) 148 | { 149 | 150 | int ConsolePort = -1; 151 | int LineCount = 0; 152 | 153 | try { 154 | 155 | if (Terminal != null) 156 | { 157 | Terminal.SSH.WriteLine("Running script: {0}",Path.GetFileName(ScriptPath)); 158 | Terminal.DebuggerThread = Thread.CurrentThread; 159 | } 160 | 161 | using (var fs = File.OpenText (ScriptPath)) { 162 | String linein; 163 | while ((linein = fs.ReadLine ()) != null) { 164 | LineCount++; 165 | linein = ReplaceVarsInString(linein.Trim ()); 166 | if (linein == "" || linein.StartsWith ("#") || linein.StartsWith ("//")) 167 | continue; 168 | if (linein.StartsWith ("<")) { 169 | if (Execute) 170 | { 171 | var proc_command = linein.Substring(1).Split(new char[]{' '},2); 172 | ProcessStartInfo startInfo = new ProcessStartInfo(); 173 | startInfo.FileName = proc_command[0]; 174 | if (proc_command.Length>1) startInfo.Arguments = proc_command[1]; 175 | Process.Start(startInfo); 176 | } 177 | } else if (linein.StartsWith (">")) { 178 | if (Execute) 179 | if (!Terminal.SSH.Execute(linein.Substring(1))) return null; 180 | } else if (linein.StartsWith ("&>")) { 181 | if (Execute) 182 | if (!Terminal.SSH.ExecuteAsync(linein.Substring(2))) return null; 183 | } else if (linein.StartsWith ("s>") || linein.StartsWith ("S>")) { 184 | if (Execute) 185 | if (!Terminal.SSH.ShellExecute(linein.Substring(2), TimeSpan.FromSeconds(5))) return null; 186 | } else { 187 | var commandLine = linein.Split (new char[]{ ' ', '=' }, 2); 188 | var command = commandLine [0].Trim (); 189 | String commandArgs = ""; 190 | if (commandLine.Length > 1) { 191 | commandArgs = commandLine [1].Trim (); 192 | if (commandArgs.StartsWith ("=")) 193 | commandArgs = commandArgs.Substring (1).TrimStart (); 194 | } 195 | 196 | switch (command.ToLower ()) { 197 | case "host": 198 | HostString = commandArgs; 199 | break; 200 | case "name": 201 | Name = commandArgs; 202 | break; 203 | case "consoleport": 204 | ConsolePort = int.Parse(commandArgs); 205 | break; 206 | case "localhost": 207 | LocalHost = commandArgs; 208 | break; 209 | case "localtunnelport": 210 | LocalTunnelPort = UInt32.Parse(commandArgs); 211 | break; 212 | case "remotetunnelport": 213 | RemoteTunnelPort = UInt32.Parse(commandArgs); 214 | break; 215 | case "workingdir": 216 | case "workingdirectory": 217 | WorkingDir = commandArgs; 218 | break; 219 | case "terminalfont": 220 | TerminalFont = commandArgs; 221 | break; 222 | case "terminalrows": 223 | TerminalRows = int.Parse(commandArgs); 224 | break; 225 | case "terminalcols": 226 | TerminalCols = int.Parse(commandArgs); 227 | break; 228 | case "terminalemulation": 229 | TerminalEmulation = commandArgs; 230 | break; 231 | case "privatekeyfile": 232 | if (!String.IsNullOrEmpty(commandArgs)) Terminal.SSH.AddPrivateKeyFile(commandArgs); 233 | break; 234 | default: 235 | { 236 | if (Execute) 237 | { 238 | switch (command.ToLower ()) 239 | { 240 | case "scp-copy": // $exe-file $mdb-file 241 | foreach (var file in commandArgs.Split(new char[]{' '})) 242 | { 243 | if (!Terminal.SSH.UploadFile(file)) return null; 244 | } 245 | break; 246 | case "scp-sync": 247 | if (!Terminal.SSH.SynchronizeDir(Path.GetDirectoryName(build_exe_path))) return null; 248 | break; 249 | case "starttunnel": 250 | if (!Terminal.SSH.StartTunnel(LocalTunnelPort,RemoteTunnelPort)) return null; 251 | break; 252 | case "sleep": 253 | Thread.Sleep(int.Parse(commandArgs)); 254 | break; 255 | default: 256 | if (Terminal != null) Terminal.SSH.WriteLine ("Script Error (Line {0}): {1} Unkown command", LineCount, linein); 257 | break; 258 | } 259 | } 260 | } 261 | break; 262 | } 263 | } 264 | } 265 | } 266 | if (Execute) return DebuggerInfo(ConsolePort); 267 | } catch (Exception ex) { 268 | String errorMsg = String.Format("SSH Script ended (Line {0}:{1})", LineCount, ex.Message); 269 | if (Terminal != null) { 270 | Terminal.SSH.WriteLine (errorMsg); 271 | } else { 272 | throw new Exception(errorMsg); 273 | } 274 | } 275 | finally { 276 | 277 | } 278 | return null; 279 | } 280 | 281 | String ReplaceVarsInString (String input) 282 | { 283 | var sb = new StringBuilder (); 284 | int pt0 = 0; 285 | int pt1,pt2; 286 | 287 | while ((pt1 = input.IndexOf ("$[",pt0)) != -1) 288 | { 289 | pt2 = input.IndexOf ("]", pt1); 290 | sb.Append (input.Substring (pt0, pt1-pt0)); 291 | pt0 = pt2 + 1; 292 | sb.Append (GetVar(input.Substring(pt1 + 2, pt2 - pt1 - 2))); 293 | } 294 | 295 | if (pt0 == 0) return input; 296 | if (pt0 < input.Length-1) sb.Append (input.Substring (pt0)); 297 | return sb.ToString (); 298 | } 299 | 300 | String GetVar(String input) 301 | { 302 | switch (input) 303 | { 304 | case "exe-path": 305 | return build_exe_path; 306 | case "mdb-path": 307 | return build_exe_path + ".mdb"; 308 | case "pdb-path": 309 | //Replace Test.exe => Test.pdb 310 | return build_exe_path.Substring(0, build_exe_path.Length - ".exe".Length) + ".pdb"; 311 | case "build-path": 312 | return Path.GetDirectoryName(build_exe_path); 313 | case "work-dir": 314 | return WorkingDir; 315 | case "RemoteTunnelPort": 316 | return RemoteTunnelPort.ToString(); 317 | case "exe-file": 318 | return Path.GetFileName(build_exe_path); 319 | default: 320 | return "?"; 321 | 322 | } 323 | } 324 | 325 | 326 | public SoftDebuggerStartInfo DebuggerInfo (int consolePort = -1) 327 | { 328 | try 329 | { 330 | 331 | IPAddress[] addresslist = Dns.GetHostAddresses(LocalHost); 332 | 333 | var startArgs = new SoftDebuggerConnectArgs ("", addresslist[0], (int)LocalTunnelPort, consolePort) { 334 | //infinite connection retries (user can cancel), 800ms between them 335 | TimeBetweenConnectionAttempts = 800, 336 | MaxConnectionAttempts = -1, 337 | }; 338 | 339 | var dsi = new SoftDebuggerStartInfo (startArgs) { 340 | Command = "", 341 | Arguments = "" 342 | }; 343 | 344 | if (Terminal != null) Terminal.SSH.WriteLine ("Configuring debugger {0}:{1}",addresslist[0], (int)LocalTunnelPort); 345 | 346 | return dsi; 347 | 348 | } 349 | catch (Exception ex) 350 | { 351 | 352 | if (Terminal != null) { 353 | Terminal.SSH.WriteLine ("SoftDebuggerStartInfo Error {0}", ex.Message); 354 | } else { 355 | Gtk.Application.Invoke (delegate { 356 | using (var md = new MessageDialog (null, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, String.Format("SoftDebuggerStartInfo Error {0}", ex.Message))) { 357 | md.Title = "ProcessScript"; 358 | md.Run (); 359 | md.Destroy (); 360 | } 361 | }); 362 | } 363 | return null; 364 | } 365 | } 366 | 367 | public void Dispose() 368 | { 369 | if (Terminal!=null) Terminal.Dispose(); 370 | } 371 | 372 | } 373 | } 374 | 375 | -------------------------------------------------------------------------------- /SSHDebugger/ProjectTemplate.xpt.xml: -------------------------------------------------------------------------------- 1 |  2 | 22 | -------------------------------------------------------------------------------- /SSHDebugger/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | using Mono.Addins; 7 | 8 | [assembly: AssemblyTitle ("SSHDebugger")] 9 | [assembly: AssemblyDescription ("")] 10 | [assembly: AssemblyConfiguration ("")] 11 | [assembly: AssemblyCompany ("Logic Ethos Ltd")] 12 | [assembly: AssemblyProduct ("")] 13 | [assembly: AssemblyCopyright ("Stuart Johnson")] 14 | [assembly: AssemblyTrademark ("")] 15 | [assembly: AssemblyCulture ("")] 16 | 17 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 18 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 19 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 20 | 21 | [assembly: AssemblyVersion ("1.0.*")] 22 | 23 | // The following attributes are used to specify the signing key for the assembly, 24 | // if desired. See the Mono documentation for more information about signing. 25 | 26 | //[assembly: AssemblyDelaySign(false)] 27 | //[assembly: AssemblyKeyFile("")] 28 | 29 | #if VTE 30 | //[assembly: ImportAddinAssembly("vte-sharp.dll")] seems to be a problem loading this. 31 | #endif -------------------------------------------------------------------------------- /SSHDebugger/Properties/Manifest.addin.xml: -------------------------------------------------------------------------------- 1 |  2 | 5 |
6 | SSH Debugger (VTE Xterm version) 7 | Debugging 8 | SSH Debugger 9 | Deploy, execute and debug your .NET apps running on a remote computer, directly from Monodevelop and Xamarin Studio. Includes a full XTerm console, on the Linux version. Standard text window on Win and Mac. 10 | Stuart Johnson, Logic Ethos Ltd 11 | https://github.com/logicethos/SSHDebugger 12 | MySpecialTag:Normal 13 | SSHDebugger.png 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 31 | 32 | 33 |
-------------------------------------------------------------------------------- /SSHDebugger/Properties/SSHDebugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicethos/SSHDebugger/7e200357f6e1ec721dd564dfc20df9e96668948a/SSHDebugger/Properties/SSHDebugger.png -------------------------------------------------------------------------------- /SSHDebugger/SSHDebugger.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {86F6BF2A-E449-4B3E-813B-9ACC37E5545F};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 8 | {A84F91BF-12DD-4CA1-A9D6-2186E9F2301E} 9 | Library 10 | SSHDebugger 11 | v4.7 12 | SSHDebugger 13 | 0.5 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug 20 | DEBUG; 21 | prompt 22 | 4 23 | false 24 | 25 | 26 | true 27 | bin\Release 28 | prompt 29 | 4 30 | false 31 | 32 | 33 | true 34 | full 35 | false 36 | bin\Debug-VTE 37 | DEBUG;VTE; 38 | prompt 39 | 4 40 | false 41 | 42 | 43 | true 44 | bin\Release-VTE 45 | VTE; 46 | prompt 47 | 4 48 | false 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | ..\vte-sharp.dll 69 | 70 | 71 | ..\packages\SSH.NET.2016.1.0\lib\net40\Renci.SshNet.dll 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | SSHDebugger.png 87 | PreserveNewest 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /SSHDebugger/Terminal/FrontEnd/windowTerminalGTK.cs: -------------------------------------------------------------------------------- 1 | // 2 | // windowTerminalGTK.cs 3 | // 4 | // Author: 5 | // Stuart Johnson 6 | // 7 | // Copyright (c) 2015 Stuart Johnson, Logic Ethos Ltd. 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | using System.Text; 27 | using Pango; 28 | 29 | #if !VTE 30 | 31 | using System; 32 | using Gtk; 33 | using System.Collections; 34 | using System.Threading; 35 | 36 | namespace SSHDebugger 37 | { 38 | public class windowTerminalGTK : Window, ITerminal 39 | { 40 | public TextView textview1; 41 | 42 | public clsSSHTerminal SSH {get; private set;} 43 | 44 | clsHost Host; 45 | 46 | public Thread DebuggerThread {get; set;} 47 | 48 | 49 | public windowTerminalGTK(clsHost host) : base(String.Format("{0} - {1}:{2}",host.Name,host.RemoteHost,host.RemoteSSHPort)) 50 | { 51 | 52 | SSH = new clsSSHTerminal(host); 53 | Host = host; 54 | 55 | ScrolledWindow scrolledWindow = new ScrolledWindow(); 56 | textview1 = new TextView(); 57 | 58 | this.SetSizeRequest(800,600); 59 | 60 | scrolledWindow.Add(textview1); 61 | textview1.ModifyFont(FontDescription.FromString(host.TerminalFont)); 62 | 63 | 64 | this.Add(scrolledWindow); 65 | 66 | this.CanFocus = true; 67 | 68 | ShowAll (); 69 | 70 | SSH.TerminalData += (string text) => 71 | { 72 | Gtk.Application.Invoke (delegate { 73 | TextIter mIter = textview1.Buffer.EndIter; 74 | textview1.Buffer.Insert(ref mIter, text); 75 | textview1.ScrollToIter(textview1.Buffer.EndIter, 0, false, 0, 0); 76 | }); 77 | }; 78 | } 79 | 80 | [GLib.ConnectBefore] 81 | protected override bool OnKeyPressEvent (Gdk.EventKey evnt) { 82 | SSH.ShellSend(evnt.Key); 83 | if (SSH.LocalEcho) 84 | return base.OnKeyPressEvent (evnt); 85 | else 86 | return false; 87 | 88 | } 89 | 90 | public void Front() 91 | { 92 | Gtk.Application.Invoke (delegate { 93 | base.Present(); 94 | textview1.CanFocus = true; 95 | }); 96 | } 97 | 98 | protected override void OnDestroyed () 99 | { 100 | Host.Terminal = null; 101 | SSH.Dispose(); 102 | if (DebuggerThread!=null && DebuggerThread.IsAlive) DebuggerThread.Abort(); 103 | textview1.Dispose(); 104 | base.OnDestroyed (); 105 | } 106 | } 107 | } 108 | #endif -------------------------------------------------------------------------------- /SSHDebugger/Terminal/FrontEnd/windowTerminalVTE.cs: -------------------------------------------------------------------------------- 1 | // 2 | // windowTerminalVTE.cs 3 | // 4 | // Author: 5 | // Stuart Johnson 6 | // 7 | // Copyright (c) 2015 Stuart Johnson, Logic Ethos Ltd. 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | #if VTE 28 | 29 | using System; 30 | using Gtk; 31 | using Vte; 32 | using System.Collections; 33 | using System.Threading; 34 | 35 | namespace SSHDebugger 36 | { 37 | public class windowTerminalVTE : Window, ITerminal 38 | { 39 | public Vte.Terminal term; 40 | 41 | public clsSSHTerminal SSH {get; private set;} 42 | 43 | clsHost Host; 44 | public Thread DebuggerThread {get; set;} 45 | 46 | 47 | public windowTerminalVTE(clsHost host) : base(String.Format("{0} - {1}:{2}",host.Name,host.RemoteHost,host.RemoteSSHPort)) 48 | { 49 | 50 | SSH = new clsSSHTerminal(host); 51 | 52 | Host = host; 53 | HBox hbox = new HBox (); 54 | term = new Terminal (); 55 | term.CursorBlinks = true; 56 | term.MouseAutohide = false; 57 | term.ScrollOnKeystroke = true; 58 | term.DeleteBinding = TerminalEraseBinding.Auto; 59 | term.BackspaceBinding = TerminalEraseBinding.Auto; 60 | term.FontFromString = host.TerminalFont; 61 | term.Emulation = "xterm"; 62 | term.Encoding = "UTF-8"; 63 | 64 | term.SetSize(host.TerminalCols,host.TerminalRows); 65 | 66 | VScrollbar vscroll = new VScrollbar (term.Adjustment); 67 | hbox.PackStart (term); 68 | hbox.PackStart (vscroll); 69 | 70 | this.CanFocus = true; 71 | 72 | this.Add (hbox); 73 | ShowAll (); 74 | 75 | SSH.TerminalData += (string text) => 76 | { 77 | Gtk.Application.Invoke (delegate { 78 | term.Feed(text); 79 | }); 80 | }; 81 | 82 | } 83 | 84 | 85 | [GLib.ConnectBefore] 86 | protected override bool OnKeyPressEvent (Gdk.EventKey evnt) { 87 | SSH.ShellSend(evnt.Key); 88 | return base.OnKeyPressEvent (evnt); 89 | } 90 | 91 | 92 | protected override void OnDestroyed () 93 | { 94 | Host.Terminal = null; 95 | if (DebuggerThread!=null && DebuggerThread.IsAlive) DebuggerThread.Abort(); 96 | SSH.Dispose(); 97 | term.Dispose(); 98 | base.OnDestroyed (); 99 | } 100 | 101 | public void Front() 102 | { 103 | Gtk.Application.Invoke (delegate { 104 | base.Present(); 105 | term.CanFocus = true; 106 | }); 107 | } 108 | } 109 | } 110 | #endif -------------------------------------------------------------------------------- /SSHDebugger/Terminal/ITerminal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace SSHDebugger 5 | { 6 | public interface ITerminal : IDisposable 7 | { 8 | clsSSHTerminal SSH {get;} 9 | void Front(); 10 | Thread DebuggerThread {set;} 11 | 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /SSHDebugger/Terminal/clsSSHTerminal.cs: -------------------------------------------------------------------------------- 1 | // 2 | // clsHost.cs 3 | // 4 | // Author: 5 | // Stuart Johnson 6 | // 7 | // Copyright (c) 2015 Stuart Johnson, Logic Ethos Ltd. 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | using System; 28 | using Renci.SshNet; 29 | using Renci.SshNet.Common; 30 | using System.Threading; 31 | using System.IO; 32 | using System.Net; 33 | using System.Collections.Generic; 34 | using System.Linq; 35 | using System.Net.Sockets; 36 | using System.Text; 37 | using System.Threading.Tasks; 38 | using SSHDebugger.Extensions; 39 | 40 | namespace SSHDebugger 41 | { 42 | public class clsSSHTerminal : IDisposable 43 | { 44 | List PrivateKeyFileList = new List(); 45 | 46 | clsHost Host; 47 | 48 | SshClient sshClient; 49 | SftpClient sftpClient; 50 | ForwardedPortLocal forwardPort = null; 51 | ShellStream shellStream = null; 52 | 53 | public bool LocalEcho { get; set; } 54 | Task ShellStreamTask; 55 | 56 | ManualResetEvent keepShellAlive = new ManualResetEvent(false); 57 | 58 | public delegate void TerminalDataHandler(String text); 59 | public event TerminalDataHandler TerminalData; 60 | 61 | public Gdk.Key LastKeyPress { get; private set;} 62 | 63 | public bool UserInputMode {get; private set;} 64 | AutoResetEvent userkeypress = new AutoResetEvent (false); 65 | 66 | 67 | const int retryCount = 3; 68 | 69 | public clsSSHTerminal(clsHost host) 70 | { 71 | Host = host; 72 | AddDefaultPrivateKey (); 73 | } 74 | 75 | public void SetHost(clsHost host) 76 | { 77 | Host = host; 78 | } 79 | 80 | void AddDefaultPrivateKey () 81 | { 82 | string homePath = (Environment.OSVersion.Platform == PlatformID.Unix || 83 | Environment.OSVersion.Platform == PlatformID.MacOSX) 84 | ? Environment.GetEnvironmentVariable ("HOME") 85 | : Environment.ExpandEnvironmentVariables ("%HOMEDRIVE%%HOMEPATH%"); 86 | var keyPath = Path.Combine (homePath, ".ssh/id_rsa"); 87 | if (File.Exists (keyPath)) 88 | AddPrivateKeyFile (keyPath); 89 | 90 | } 91 | public void AddPrivateKeyFile(String path) 92 | { 93 | PrivateKeyFileList.Add(new PrivateKeyFile(path)); 94 | } 95 | 96 | public void AddPrivateKeyFile(String path, String password) 97 | { 98 | PrivateKeyFileList.Add(new PrivateKeyFile(path,password)); 99 | } 100 | 101 | public bool ConnectSSH() 102 | { 103 | int retry = 0; 104 | if (sftpClient!=null && sftpClient.IsConnected) sftpClient.Disconnect(); //Seems if sftp client is open, it blocks the shell/tunnel. 105 | 106 | while (sshClient == null || !sshClient.IsConnected) 107 | { 108 | if (retry++ > retryCount) return false; 109 | try 110 | { 111 | 112 | if (PrivateKeyFileList.Count>0 && retry == 1) 113 | { 114 | sshClient = new SshClient (Host.RemoteHost, Host.RemoteSSHPort, Host.Username, PrivateKeyFileList.ToArray()); 115 | } 116 | else 117 | { 118 | if (String.IsNullOrEmpty(Host.Password)) Host.Password = RequestUserInput("Enter Host Password: ",'*'); 119 | sshClient = new SshClient (Host.RemoteHost, Host.RemoteSSHPort, Host.Username,Host.Password); 120 | } 121 | 122 | Write("ssh connecting to {0}@{1}:{2}...",Host.Username,Host.RemoteHost,Host.RemoteSSHPort); 123 | sshClient.Connect (); 124 | if (sshClient.IsConnected) 125 | { 126 | WriteLine("OK"); 127 | 128 | WriteLine("MaxSessions:{0} {1} {2}", 129 | sshClient.ConnectionInfo.MaxSessions, 130 | sshClient.ConnectionInfo.Encoding, 131 | sshClient.ConnectionInfo.CurrentServerEncryption); 132 | 133 | return true; 134 | } 135 | } 136 | catch (Exception ex) 137 | { 138 | if (ex.Message.ToLower().Contains("password")) Host.Password=""; //Reset password 139 | WriteLine("ssh Error: "+ex.Message); 140 | return false; 141 | } 142 | } 143 | return true; 144 | } 145 | 146 | public bool StartShellStream() 147 | { 148 | if (!ConnectSSH ()) return false; 149 | 150 | ManualResetEvent started = new ManualResetEvent(false); 151 | ShellStreamTask = new Task( () => 152 | { 153 | try 154 | { 155 | WriteLine("Starting terminal: Emulation = {0}",Host.TerminalEmulation); 156 | 157 | shellStream = sshClient.CreateShellStream(Host.TerminalEmulation,(uint)Host.TerminalCols,(uint)Host.TerminalRows,0,0,4096); 158 | 159 | 160 | shellStream.DataReceived += (object sender, ShellDataEventArgs e) => 161 | { 162 | Write(Encoding.UTF8.GetString(e.Data,0,e.Data.Length)); 163 | }; 164 | 165 | shellStream.ErrorOccurred+= (object sender, ExceptionEventArgs e) => 166 | { 167 | WriteLine(e.Exception.Message); 168 | keepShellAlive.Set(); 169 | }; 170 | 171 | if (!String.IsNullOrEmpty(Host.WorkingDir)) 172 | { 173 | Write("Changing dir: {0}...",Host.WorkingDir); 174 | shellStream.WriteLine(String.Format("cd {0}\r\n",Host.WorkingDir)); 175 | } 176 | started.Set(); 177 | keepShellAlive.Reset(); 178 | keepShellAlive.WaitOne(); 179 | 180 | WriteLine("\r\n*** Console Stream End"); 181 | } 182 | catch (Exception ex) 183 | { 184 | WriteLine("\r\n*** Console Stream Error: {0}",ex.Message); 185 | } 186 | finally 187 | { 188 | shellStream.Dispose(); 189 | shellStream = null; 190 | } 191 | 192 | }); 193 | ShellStreamTask.Start(); 194 | return started.WaitOne(5000); 195 | } 196 | 197 | 198 | public bool ShellExecute(string command, TimeSpan? timespan = null) 199 | { 200 | if (!StartShellStream()) return false; 201 | shellStream.WriteLine(command); 202 | 203 | return timespan == null || shellStream.Expect(command,timespan.Value) != null; 204 | } 205 | 206 | 207 | public bool ConnectSFTP() 208 | { 209 | int retry = 0; 210 | 211 | while (sftpClient == null || !sftpClient.IsConnected) 212 | { 213 | if (retry++ > retryCount) return false; 214 | try 215 | { 216 | 217 | if (PrivateKeyFileList.Count>0 && retry == 1) 218 | { 219 | sftpClient = new SftpClient (Host.RemoteHost, Host.RemoteSSHPort, Host.Username, PrivateKeyFileList.ToArray()); 220 | } 221 | else 222 | { 223 | if (String.IsNullOrEmpty(Host.Password)) Host.Password = RequestUserInput("Enter Host Password: ",'*'); 224 | sftpClient = new SftpClient (Host.RemoteHost, Host.RemoteSSHPort, Host.Username, Host.Password); 225 | } 226 | 227 | Write("sftp connecting to {0}@{1}:{2}...",Host.Username,Host.RemoteHost,Host.RemoteSSHPort); 228 | sftpClient.Connect (); 229 | if (sftpClient.IsConnected) 230 | { 231 | WriteLine("OK"); 232 | 233 | if (!String.IsNullOrEmpty(Host.WorkingDir)) 234 | { 235 | var scpPath = Host.WorkingDir.Replace("~", sftpClient.WorkingDirectory); //this SCP library doesnt like ~ 236 | if (sftpClient.WorkingDirectory != scpPath) 237 | { 238 | try 239 | { 240 | Write("Changing dir: {0}...",scpPath); 241 | sftpClient.ChangeDirectory (scpPath); 242 | }catch(Exception ex) 243 | { 244 | WriteLine("FAILED\r\n{0}",ex.Message); 245 | return false; 246 | } 247 | } 248 | WriteLine("OK"); 249 | } 250 | } 251 | } 252 | catch (Exception ex) 253 | { 254 | if (ex.Message.ToLower().Contains("password")) Host.Password=""; //Reset password 255 | WriteLine("Error: "+ex.Message); 256 | return false; 257 | } 258 | } 259 | return true; 260 | } 261 | 262 | 263 | public bool Execute(String command) 264 | { 265 | if (!ConnectSSH ()) return false; 266 | 267 | var cmd = sshClient.CreateCommand (command); 268 | Write(cmd.Execute ()); 269 | return true; 270 | } 271 | 272 | public bool ExecuteAsync(String command) 273 | { 274 | if (!ConnectSSH ()) return false; 275 | 276 | var cmd = sshClient.CreateCommand (command); 277 | cmd.BeginExecute ( 278 | (IAsyncResult r) => { 279 | var result = (CommandAsyncResult)r; 280 | WriteLine("{0} {1}",command,result.IsCompleted); 281 | } 282 | ); 283 | ReadAsync(cmd.ExtendedOutputStream); 284 | // ReadAsync(cmd.OutputStream); 285 | return true; 286 | } 287 | 288 | 289 | public void ReadAsync(Stream stream) 290 | { 291 | new Task( async () => 292 | { 293 | int numBytesRead; 294 | byte[] data = new byte[1024]; 295 | 296 | WriteLine("stream start"); 297 | try 298 | { 299 | while ((numBytesRead = await stream.ReadAsync(data, 0, 1024)) >0) 300 | { 301 | Write(Encoding.UTF8.GetString(data,0,numBytesRead)); 302 | } 303 | } 304 | catch (Exception ex) 305 | { 306 | WriteLine("\r\nstream error: {0}",ex.Message); 307 | } 308 | 309 | }).Start(); 310 | } 311 | 312 | /// 313 | /// Starts an ssh tunnel, where conntection to a local port (which this will listen on), transparently connects it to a remote port. 314 | /// 315 | /// true, if tunnel was started, false otherwise. 316 | /// Tunnel port local. 317 | /// Tunnel port remote. 318 | /// Local network. 319 | public bool StartTunnel(UInt32 TunnelPortLocal, UInt32 TunnelPortRemote, String LocalNetwork = null) 320 | { 321 | 322 | if (!ConnectSSH ()) return false; 323 | 324 | try 325 | { 326 | 327 | if (LocalNetwork == null) LocalNetwork = IPAddress.Loopback.ToString (); 328 | 329 | Write ("ssh tunnel: {0}:{1} -> {2}:{3}...",LocalNetwork, TunnelPortLocal, "localhost", TunnelPortRemote); 330 | if (forwardPort!=null) 331 | { 332 | if (forwardPort.BoundPort == TunnelPortLocal && forwardPort.Port == TunnelPortRemote) { 333 | if (forwardPort.IsStarted) { 334 | WriteLine ("Tunnel already connencted"); 335 | return true; 336 | } else { 337 | forwardPort.Dispose (); 338 | } 339 | } else { 340 | forwardPort.Dispose (); 341 | } 342 | } 343 | 344 | forwardPort = new ForwardedPortLocal(LocalNetwork, TunnelPortLocal, "localhost", TunnelPortRemote); 345 | 346 | sshClient.AddForwardedPort(forwardPort); 347 | 348 | forwardPort.Exception += (object sender, ExceptionEventArgs e) => 349 | { 350 | WriteLine("Tunnel error: {0}",e.Exception.Message); 351 | }; 352 | 353 | forwardPort.RequestReceived += (object sender, PortForwardEventArgs e) => 354 | { 355 | WriteLine("Tunnel connection: {0}->{1}",e.OriginatorHost, e.OriginatorPort); 356 | }; 357 | forwardPort.Start(); 358 | WriteLine ("OK"); 359 | 360 | return forwardPort.IsStarted; 361 | 362 | } 363 | catch (SocketException ex) 364 | { 365 | if (ex.SocketErrorCode == SocketError.AccessDenied) 366 | { 367 | WriteLine("FAILED\r\nAccess Denied - Cannot create port redirect. Try running Monodevelop with higher privileges."); 368 | } 369 | else 370 | { 371 | WriteLine("FAILED\r\nTunnel Error: {0}",ex); 372 | } 373 | } 374 | catch (Exception ex) 375 | { 376 | WriteLine("Tunnel Error: {0}",ex); 377 | } 378 | return false; 379 | } 380 | 381 | 382 | public bool UploadFile(String LocalPath, String RemoteFileName = null) 383 | { 384 | 385 | if (!ConnectSFTP ()) return false; 386 | 387 | if (String.IsNullOrEmpty (RemoteFileName)) RemoteFileName = System.IO.Path.GetFileName(LocalPath); 388 | 389 | Write("sftp Uploading: {0}...",LocalPath); 390 | 391 | try 392 | { 393 | using (var fs = File.OpenRead(LocalPath)) 394 | { 395 | sftpClient.UploadFile (fs, RemoteFileName,true, (bytes) => {Write(".");}); 396 | } 397 | WriteLine("OK"); 398 | return true; 399 | } catch (Exception ex) 400 | { 401 | WriteLine("Error "+ex.Message); 402 | return false; 403 | } 404 | } 405 | 406 | public bool SynchronizeDir(String LocalDir, String RemoteDir = null, String SearchPattern = "*" ) 407 | { 408 | 409 | if (!ConnectSFTP ()) return false; 410 | 411 | if (RemoteDir==null || RemoteDir == "~") RemoteDir = sftpClient.WorkingDirectory; 412 | 413 | Write("sftp Synchronizing Directories: {0}->{1}...",LocalDir,RemoteDir); 414 | 415 | try 416 | { 417 | sftpClient.SynchronizeDirectories(LocalDir,RemoteDir,SearchPattern); 418 | WriteLine("OK"); 419 | return true; 420 | } catch (Exception ex) 421 | { 422 | WriteLine("Error "+ex.Message); 423 | return false; 424 | } 425 | } 426 | 427 | public void WriteLine(String output, params object[] args) 428 | { 429 | WriteLine (String.Format(output, args)); 430 | } 431 | 432 | public void WriteLine(String output) 433 | { 434 | Write (output+"\r\n"); 435 | } 436 | 437 | public void Write(String output, params object[] args) 438 | { 439 | Write (String.Format(output, args)); 440 | } 441 | 442 | public void Write(String output) 443 | { 444 | if (TerminalData!=null) TerminalData(output); 445 | 446 | } 447 | 448 | public void ShellSend(Gdk.Key key) 449 | { 450 | if (UserInputMode) 451 | { 452 | LastKeyPress = key; 453 | userkeypress.Set (); 454 | } 455 | else 456 | { 457 | if (LocalEcho) Write(key.ToString()); 458 | if (shellStream!=null && shellStream.CanWrite) 459 | { 460 | byte[] charBytes = null; 461 | 462 | switch (key) 463 | { 464 | case Gdk.Key.Shift_L: 465 | case Gdk.Key.Shift_R: 466 | case Gdk.Key.ISO_Level3_Shift: //Alt Gr 467 | //Ignore 468 | break; 469 | case Gdk.Key.Return: 470 | case Gdk.Key.KP_Enter: 471 | charBytes = Encoding.UTF8.GetBytes(Environment.NewLine); 472 | break; 473 | case Gdk.Key.BackSpace: 474 | charBytes = new byte[] { (byte)'\b' }; 475 | break; 476 | case Gdk.Key.KP_Tab: 477 | case Gdk.Key.Tab: 478 | charBytes = new byte[] { (byte)'\t' }; 479 | break; 480 | case Gdk.Key.Escape: 481 | charBytes = new byte[] { 0x1B }; //ESC 482 | break; 483 | default: 484 | charBytes = Encoding.UTF8.GetBytes(new char[] { key.GetChar() }); 485 | break; 486 | } 487 | 488 | if (charBytes != null) 489 | { 490 | shellStream.Write(charBytes, 0, charBytes.Length); 491 | shellStream.Flush(); 492 | } 493 | } 494 | } 495 | } 496 | 497 | /// 498 | /// Requests the user input, without sending it to the ssh connection. 499 | /// 500 | /// The user input. 501 | /// Prompt. 502 | /// Echo char. If null, the keypress will be displayed 503 | public String RequestUserInput(String prompt, char? echoChar=null) 504 | { 505 | UserInputMode = true; 506 | Write (prompt); 507 | String input = ""; 508 | while (UserInputMode && userkeypress.WaitOne ()) 509 | { 510 | switch(LastKeyPress) 511 | { 512 | case Gdk.Key.BackSpace: 513 | { 514 | if (input.Length > 0) 515 | { 516 | input = input.Substring(0, input.Length - 1); 517 | if (echoChar.HasValue) Write("\b \b"); 518 | } 519 | } 520 | break; 521 | case Gdk.Key.Return: 522 | case Gdk.Key.KP_Enter: 523 | { 524 | Write(Environment.NewLine); 525 | UserInputMode = false; //Stop Input 526 | break; 527 | } 528 | case Gdk.Key.Shift_L: 529 | case Gdk.Key.Shift_R: 530 | case Gdk.Key.ISO_Level3_Shift: //Alt Gr 531 | { 532 | //Do nothing 533 | } 534 | break; 535 | default: 536 | { 537 | var keyValue = LastKeyPress.GetChar(); 538 | Write(echoChar.HasValue ? echoChar.Value.ToString() : keyValue.ToString()); 539 | input += keyValue; 540 | } 541 | break; 542 | } 543 | } 544 | UserInputMode = false; 545 | return input; 546 | } 547 | 548 | public void Dispose() 549 | { 550 | userkeypress.Dispose(); 551 | keepShellAlive.Set(); 552 | if (shellStream!=null) shellStream.Dispose (); 553 | if (sshClient!=null) sshClient.Dispose (); 554 | if (sftpClient!=null) sftpClient.Dispose (); 555 | } 556 | 557 | } 558 | } 559 | -------------------------------------------------------------------------------- /SSHDebugger/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /SSHDebugger/ssh-host.xft.xml: -------------------------------------------------------------------------------- 1 |  2 | -------------------------------------------------------------------------------- /addin-project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | SSHDebugger/bin/Release-VTE/SSHDebugger.dll 4 | SSHDebugger.sln 5 | Release-VTE 6 | 7 | 8 | SSHDebugger/bin/Release/SSHDebugger.dll 9 | SSHDebugger.sln 10 | Release 11 | 12 | 13 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mdtool setup pack addin-project.xml -------------------------------------------------------------------------------- /vte-sharp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicethos/SSHDebugger/7e200357f6e1ec721dd564dfc20df9e96668948a/vte-sharp.dll --------------------------------------------------------------------------------