diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f825da6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# .gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ diff --git a/SshHandler.sln b/SshHandler.sln new file mode 100644 index 0000000..deffb31 --- /dev/null +++ b/SshHandler.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{50924D75-2903-46D4-86DD-02269EFDBCA9}") = "SshHandler", "SshHandler\SshHandler.csproj", "{960BAB18-309B-42D0-B67E-B4EF8E7F7490}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {960BAB18-309B-42D0-B67E-B4EF8E7F7490}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {960BAB18-309B-42D0-B67E-B4EF8E7F7490}.Debug|Any CPU.Build.0 = Debug|Any CPU + {960BAB18-309B-42D0-B67E-B4EF8E7F7490}.Release|Any CPU.ActiveCfg = Release|Any CPU + {960BAB18-309B-42D0-B67E-B4EF8E7F7490}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/SshHandler/Form1.Designer.cs b/SshHandler/Form1.Designer.cs new file mode 100644 index 0000000..b1e5ad7 --- /dev/null +++ b/SshHandler/Form1.Designer.cs @@ -0,0 +1,113 @@ +partial class Form1 +{ + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.targetTextBox = new System.Windows.Forms.TextBox(); + this.legacyCheckBox = new System.Windows.Forms.CheckBox(); + this.connectButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 15); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(73, 15); + this.label1.TabIndex = 0; + this.label1.Text = "Connect to:"; + // + // targetTextBox + // + this.targetTextBox.Location = new System.Drawing.Point(15, 33); + this.targetTextBox.Name = "targetTextBox"; + this.targetTextBox.Size = new System.Drawing.Size(357, 23); + this.targetTextBox.TabIndex = 1; + // + // legacyCheckBox + // + this.legacyCheckBox.AutoSize = true; + this.legacyCheckBox.Location = new System.Drawing.Point(15, 71); + this.legacyCheckBox.Name = "legacyCheckBox"; + this.legacyCheckBox.Size = new System.Drawing.Size(206, 19); + this.legacyCheckBox.TabIndex = 2; + this.legacyCheckBox.Text = "Enable Legacy Mode (for old devices)"; + this.legacyCheckBox.UseVisualStyleBackColor = true; + // + // connectButton + // + this.connectButton.Location = new System.Drawing.Point(297, 106); + this.connectButton.Name = "connectButton"; + this.connectButton.Size = new System.Drawing.Size(75, 23); + this.connectButton.TabIndex = 3; + this.connectButton.Text = "Connect"; + this.connectButton.UseVisualStyleBackColor = true; + this.connectButton.Click += new System.EventHandler(this.connectButton_Click); + // + // cancelButton + // + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(216, 106); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 4; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click); + // + // Form1 + // + this.AcceptButton = this.connectButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(384, 141); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.connectButton); + this.Controls.Add(this.legacyCheckBox); + this.Controls.Add(this.targetTextBox); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "Form1"; + this.ShowIcon = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "SSH Connection"; + this.ResumeLayout(false); + this.PerformLayout(); + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox targetTextBox; + private System.Windows.Forms.CheckBox legacyCheckBox; + private System.Windows.Forms.Button connectButton; + private System.Windows.Forms.Button cancelButton; +} diff --git a/SshHandler/Form1.cs b/SshHandler/Form1.cs new file mode 100644 index 0000000..90cc8c5 --- /dev/null +++ b/SshHandler/Form1.cs @@ -0,0 +1,40 @@ +using System; +using System.Windows.Forms; + +public partial class Form1 : Form +{ + // This property will store the result for the main program + public string FinalSshCommand { get; private set; } + + public Form1(string initialTarget) + { + InitializeComponent(); + targetTextBox.Text = initialTarget; + // Select the text for easy editing + targetTextBox.SelectAll(); + targetTextBox.Focus(); + } + + private void connectButton_Click(object sender, EventArgs e) + { + string target = targetTextBox.Text; + bool useLegacy = legacyCheckBox.Checked; + + string legacyArgs = "-o KexAlgorithms=+diffie-hellman-group1-sha1,diffie-hellman-group14-sha1 " + + "-o HostKeyAlgorithms=+ssh-rsa " + + "-o MACs=+hmac-sha1,hmac-sha1-96"; + + // Build the arguments for ssh.exe + string arguments = $"-A -C {(useLegacy ? legacyArgs : "")} {target}"; + + FinalSshCommand = arguments; + this.DialogResult = DialogResult.OK; // Set dialog result to OK + this.Close(); // Close the form + } + + private void cancelButton_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.Cancel; // Set dialog result to Cancel + this.Close(); + } +} diff --git a/SshHandler/Form1.resx b/SshHandler/Form1.resx new file mode 100644 index 0000000..5aada26 --- /dev/null +++ b/SshHandler/Form1.resx @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 384, 141 + + + SSH Connection + + diff --git a/SshHandler/Program.cs b/SshHandler/Program.cs new file mode 100644 index 0000000..ff98f3c --- /dev/null +++ b/SshHandler/Program.cs @@ -0,0 +1,106 @@ +using Microsoft.Win32; +using System; +using System.Diagnostics; +using System.Security.Principal; +using System.Windows.Forms; + +static class Program +{ + private const string ProtocolName = "ssh"; + + [STAThread] + static void Main(string[] args) + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + + // If no arguments, run setup mode. + if (args.Length == 0) + { + SetupHandler(); + } + // Otherwise, run handler mode. + else + { + HandleConnection(args[0]); + } + } + + static void HandleConnection(string url) + { + string target = url.Replace("ssh://", "").TrimEnd('/'); + if (!target.Contains("@")) + { + target = $"{Environment.UserName}@{target}"; + } + + // Show the form as a dialog + using (var form = new Form1(target)) + { + if (form.ShowDialog() == DialogResult.OK) + { + // If user clicked "Connect", run the command + ProcessStartInfo psi = new ProcessStartInfo + { + FileName = "ssh.exe", + Arguments = form.FinalSshCommand, + UseShellExecute = true, + CreateNoWindow = false + }; + Process.Start(psi); + } + } + } + + static void SetupHandler() + { + if (!IsAdmin()) + { + MessageBox.Show("Please run this application as an administrator to register the URL handler.", "Admin Privileges Required", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + // The path is simple and correct because it's a native app + string exePath = Application.ExecutablePath; + string commandStr = $"\"{exePath}\" \"%1\""; + + var confirmResult = MessageBox.Show( + "This will register this application to handle all ssh:// links.\n\n" + + $"Command:\n{commandStr}\n\n" + + "Do you want to proceed?", + "Registry Setup", + MessageBoxButtons.YesNo, + MessageBoxIcon.Information); + + if (confirmResult == DialogResult.Yes) + { + try + { + // Write the keys to HKEY_CLASSES_ROOT + using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(ProtocolName)) + { + key.SetValue("", $"URL:{ProtocolName} Protocol"); + key.SetValue("URL Protocol", ""); + } + using (RegistryKey key = Registry.ClassesRoot.CreateSubKey($"{ProtocolName}\\shell\\open\\command")) + { + key.SetValue("", commandStr); + } + MessageBox.Show("Successfully registered ssh:// protocol handler!", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + catch (Exception ex) + { + MessageBox.Show($"Failed to write to registry:\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + + private static bool IsAdmin() + { + using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) + { + WindowsPrincipal principal = new WindowsPrincipal(identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + } +} diff --git a/SshHandler/Properties/AssemblyInfo.cs b/SshHandler/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e69de29 diff --git a/SshHandler/SshHandler.csproj b/SshHandler/SshHandler.csproj new file mode 100644 index 0000000..b695334 --- /dev/null +++ b/SshHandler/SshHandler.csproj @@ -0,0 +1,12 @@ + + + + WinExe + net8.0-windows + enable + true + enable + ssh_icon.ico + + + diff --git a/SshHandler/ssh_icon.ico b/SshHandler/ssh_icon.ico new file mode 100644 index 0000000..3334a3d Binary files /dev/null and b/SshHandler/ssh_icon.ico differ