├── .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 | 
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 |
3 |
4 | <_Name>ProjectTemplate
5 |
6 |
7 |
8 |
9 |
10 | RemoteDebug
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 |
5 |
6 |
7 | <_Name>SSH Debug Host
8 | md-text-file-icon
9 | <_Category>SSH-Debug
10 | <_Description>SSH Host script
11 | remote-host.ssh.txt
12 |
13 |
14 |
15 |
16 |
17 | ' or '&>' for remote synchronous or asynchronous operation.
25 | # 's>' for synchronous shell input/output.
26 | #
27 | # [exe-file] = output .exe filename
28 | # [exe-path] = path to output .exe
29 | # [mdb-path] = path to output .exe.mdb
30 | # [pdb-path] = path to output .pdb
31 | # [build-path] = path to output
32 | # [work-dir] = remote working dir
33 |
34 | # Remember: you may want to add this file to your repository ignore file. (*.xft.ssh)
35 |
36 | Name = My Remote Host
37 | Host = user@host[:port]
38 | WorkingDir = ~
39 | PrivateKeyFile =
40 |
41 | TerminalFont Monospace 10
42 | TerminalRows 30
43 | TerminalCols 120
44 | TerminalEmulation xterm
45 |
46 | LocalTunnelPort 8000
47 | RemoteTunnelPort 12345
48 |
49 | #Copy executable & debug file to remote.
50 | scp-copy $[exe-path] $[mdb-path]
51 |
52 | # scp-sync, copys additional build directory file (e.g .dlls that may be needed)
53 | scp-sync
54 |
55 | StartTunnel
56 |
57 | #Start remote application, with debug listener.
58 | s>mono --debug --debugger-agent=transport=dt_socket,address=localhost:$[RemoteTunnelPort],server=y,suspend=y $[exe-file]
59 |
60 | #Allow time for remote debugger to initialise (in milliseconds)
61 | sleep 150
62 | ]]>
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------