.
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | SWSH is a console application that offers SSH-like connectivity with ease to users which grants them the ability to operate remotely on SSH protocol. It is also Open source, so feel free to contribute.
30 |
31 | **If you are not using a [prebuilt SWSH binary](https://github.com/SecureWindowsShell/SWSH/releases), you will see SWSH complain about a checksum mismatch and exit, use `--IgnoreChecksumMismatch` to stop it from exiting.**
32 |
33 | 
34 | *SWSH, just doing its thing*
35 |
36 | ## Getting Started
37 |
38 | ### Generating SSH keys
39 |
40 | SSH keys serve as a means of identifying yourself to an SSH server. To Generate your private and public key, SWSH uses an add-on, swsh-keygen. You can [build swsh-keygen](https://github.com/SecureWindowsShell/swsh-keygen) yourself if you want and place the executable (.exe) in SWSH's root (installation) directory.
41 |
42 | Use command ```keygen``` to tell SWSH that you want to generate a new RSA key pair for SSH connection after that just follow the prompts.
43 | You'll be asked for locations to store your keys, leave it blank if you want it to be default.
44 | Output will be similar to this:
45 |
46 | ```swsh
47 | /users/muzzammil:swsh> keygen
48 |
49 | Generating public/private rsa key pair.
50 | exit or -e to cancel.
51 | Enter absolute path to save private key (%appdata%/SWSH/swsh.private):
52 | Enter absolute path to save public key (%appdata%/SWSH/swsh.public):
53 | Your public key:
54 |
55 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCt2MxdswuuUvmaY4JK6kP4lYIqGy0KeHCqcx1NEjB4EcqH7+MIeXGbdikACvP3wlOAEAt+7PMEhBHf7nL2S2SsOybpegJw0piiMeOIPJwQxIQFaRWyz3xn0ESItzBizsQ4yxfQiG37sFkMeQVnP5fHuc2+Z4JZ5SD56Dh1xxgnEw==
56 | ```
57 |
58 | ### Importing SSH keys
59 |
60 | If you already have SSH keys and want to use them instead of creating a new pair, you can! Use ```keygen import``` command to do so and just follow the prompts.
61 |
62 | NOTE: DO **NOT** SHARE YOUR PRIVATE KEY!
63 |
64 | ### Connecting to a host
65 |
66 | To connect run ```connect username@host```.
67 |
68 | To use a password connection, use tag `-p` like this: ```connect username@host -p```.
69 |
70 | If done properly, output would be similar to the following:
71 |
72 | ```swsh
73 | Waiting for response from username@host...
74 | Connected to username@host...
75 | ~:/ $
76 | ```
77 |
78 | ## Commands
79 |
80 | | Command | Description |
81 | |:--------------------------|:----------------------------------------------------------------------|
82 | | version | Check the version of swsh. |
83 | | connect [user@host] (-p) | Connects to Server over SSH. |
84 | | keygen (options) | Generates SSH RSA key pair. |
85 | | help [command] | Displays this help or command details. |
86 | | clear | Clears the console. |
87 | | pwd | Prints working directory. |
88 | | computehash [(>/>>) path] | Uses SHA-1 hash function to generate hashes for SWSH and swsh-keygen. |
89 | | exit | Exits. |
90 | | ls | Lists all files and directories in working directory. |
91 | | cd [arg] | Changes directory to 'arg'. arg = directory name. |
92 | | upload [arguments] | Uploads files and directories. 'upload -h' for help. |
93 |
94 | For more, see our [documentation](DOCUMENTATION.md).
95 |
96 | # License
97 |
98 | GPL v3
99 |
100 | Copyright (C) 2017 Muhammad Muzzammil
101 |
--------------------------------------------------------------------------------
/SWSH.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2024
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SWSH", "SWSH\SWSH.csproj", "{6A30C4D1-0E4F-49B9-B2B2-D210923CE44C}"
7 | EndProject
8 | Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "Installer", "Installer\Installer.vdproj", "{92400EB6-E01D-4838-A261-0C44A2E50C36}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {6A30C4D1-0E4F-49B9-B2B2-D210923CE44C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {6A30C4D1-0E4F-49B9-B2B2-D210923CE44C}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {6A30C4D1-0E4F-49B9-B2B2-D210923CE44C}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {6A30C4D1-0E4F-49B9-B2B2-D210923CE44C}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {92400EB6-E01D-4838-A261-0C44A2E50C36}.Debug|Any CPU.ActiveCfg = Debug
21 | {92400EB6-E01D-4838-A261-0C44A2E50C36}.Release|Any CPU.ActiveCfg = Release
22 | EndGlobalSection
23 | GlobalSection(SolutionProperties) = preSolution
24 | HideSolutionNode = FALSE
25 | EndGlobalSection
26 | GlobalSection(ExtensibilityGlobals) = postSolution
27 | SolutionGuid = {88AF42AC-47A4-4255-A7FC-247ECA91CCF2}
28 | EndGlobalSection
29 | EndGlobal
30 |
--------------------------------------------------------------------------------
/SWSH/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/SWSH/ExternalFunctions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * SWSH - Secure Windows Shell
3 | * Copyright (C) 2017 Muhammad Muzzammil
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | using System;
20 | using System.Runtime.InteropServices;
21 |
22 | namespace SWSH {
23 | class ExternalFunctions {
24 | private const string Kernel32 = "kernel32.dll";
25 | [DllImport(Kernel32, EntryPoint = "SetConsoleMode", SetLastError = true)]
26 | internal static extern bool SetConsoleMode(IntPtr hConsoleHandle, int mode);
27 | [DllImport(Kernel32, EntryPoint = "GetConsoleMode", SetLastError = true)]
28 | internal static extern bool GetConsoleMode(IntPtr handle, out int mode);
29 | [DllImport(Kernel32, EntryPoint = "GetStdHandle", SetLastError = true)]
30 | internal static extern IntPtr GetStdHandle(int handle);
31 | }
32 | }
--------------------------------------------------------------------------------
/SWSH/Program.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * SWSH - Secure Windows Shell
3 | * Copyright (C) 2017 Muhammad Muzzammil
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | using System;
20 | using System.Collections.Generic;
21 | using System.Diagnostics;
22 | using System.IO;
23 | using System.Linq;
24 | using System.Net;
25 | using System.Reflection;
26 | using System.Security.Cryptography;
27 | using System.Security.Principal;
28 | using System.Text;
29 | using System.Threading;
30 | using System.Xml;
31 | using Renci.SshNet;
32 |
33 | namespace SWSH {
34 | public static class Program {
35 | public static bool KeygenIsAvailable { get; set; }
36 | public static bool Unstable => Codename.StartsWith("unstable");
37 | public static string Codename => "Titan (1.0)";
38 | public static string AppDataDirectory => $"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}/SWSH";
39 | public static string History => $"{AppDataDirectory}/swsh_history";
40 | public static string Keys => $"{AppDataDirectory}/swsh_keys";
41 | public static string License => $"{AppDataDirectory}/LICENSE.txt";
42 | public static string Command { get; set; }
43 | public static string WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
44 | private static void Main(string[] args) {
45 | if (!Directory.Exists(AppDataDirectory)) Directory.CreateDirectory(AppDataDirectory);
46 | Console.Title = "SWSH - Secure Windows Shell";
47 | Notice();
48 | Console.Write("\nType `license notice` to view this notice again.\n");
49 | for (int i = 0; i < 5; i++) {
50 | Console.Write($"\rStarting in {5 - (i + 1)}s");
51 | Thread.Sleep(1000);
52 | }
53 | Console.Clear();
54 | /* Downloading License; if does not exists. START */
55 | try {
56 | if (!File.Exists(License)) {
57 | Console.WriteLine("License file not found, downloading...");
58 | new WebClient().DownloadFile(new Uri(Url.License), License);
59 | Console.Clear();
60 | }
61 | } catch (Exception exp) { Error($"Unable to download License, view online copy here: {Url.License}\nReason:{exp.Message}\n"); }
62 | /* Downloading License; if does not exists. END */
63 | Console.Title = $"SWSH - {GetVersion()}";
64 | if (!Unstable) KeygenIsAvailable = CheckHash(args.Any(x => x == "--IgnoreChecksumMismatch"));
65 | Console.Write("Use `help` command for help.\n\n");
66 | try {
67 | var handle = ExternalFunctions.GetStdHandle(-11);
68 | ExternalFunctions.GetConsoleMode(handle, out var mode);
69 | ExternalFunctions.SetConsoleMode(handle, mode | 0x4);
70 | ExternalFunctions.GetConsoleMode(handle, out mode);
71 | } catch (Exception exp) { Error($"{exp.Message}\n"); }
72 | ReadLine.History = File.ReadAllLines(History).ToList().ConvertAll(x => x.Split('=')[1].Remove(0, 2));
73 | Console.OutputEncoding = Encoding.UTF8;
74 | Console.InputEncoding = Encoding.UTF8;
75 | Start();
76 | }
77 | private static void Start() {
78 | while (Command != "exit") {
79 | try {
80 | Color($"{WorkingDirectory.Replace('\\', '/').Remove(0, 2).ToLower()}:", ConsoleColor.DarkCyan);
81 | Color("swsh> ", ConsoleColor.DarkGray);
82 | Command = GetCommand();
83 | if (Command.StartsWith("swsh")) {
84 | Color(
85 | "WARNING:\nThis type of commands is deprecated and will stop working in future.\nPlease take a look at our latest documentation or"
86 | + " use `help` command.\n", ConsoleColor.Yellow);
87 | if (Command.StartsWith("swsh --")) Command = Command.Remove(0, 7);
88 | }
89 | if (Command == "version") GetVersion();
90 | else if (Command.StartsWith("help")) Help(null);
91 | else if (Command.StartsWith("connect")) Connect();
92 | else if (Command.StartsWith("keygen")) Keygen();
93 | else if (Command.StartsWith("cd")) Cd();
94 | else if (Command.StartsWith("upload")) Upload();
95 | else if (Command.StartsWith("computehash")) PrintHash();
96 | else if (Command == "ls") Ls();
97 | else if (Command == "clear") Clear();
98 | else if (Command == "license") File.ReadAllLines(License).ToList().ForEach(Console.WriteLine);
99 | else if (Command == "license notice") Notice();
100 | else if (Command == "pwd") Console.WriteLine(WorkingDirectory.ToLower());
101 | else if (Command.Trim() != "") Error($"SWSH -> {Command} -> unknown command.\n");
102 | } catch (Exception exp) { Error($"{exp.Message}\n"); }
103 | }
104 | }
105 | private static void Help(string cmd) {
106 | Command = cmd ?? Command.Remove(0, 4).Trim();
107 | if (Command.Length > 0) {
108 | var title = $"Help for {Command}";
109 | Console.WriteLine(title);
110 | for (int i = 0; i < title.Length; i++) Console.Write("=");
111 | Console.WriteLine();
112 | switch (Command) {
113 | case "version":
114 | Console.WriteLine("Syntax: version");
115 | Console.WriteLine("Checks the version of swsh.\n\nUsage: version\n");
116 | break;
117 |
118 | case "connect":
119 | Console.WriteLine("Syntax: connect [user@host] (-p)");
120 | Console.WriteLine("Connects to Server over SSH. Use `-p` for password connection.\nUsage: connect root@server.ip");
121 | break;
122 |
123 | case "keygen":
124 | Console.WriteLine("Syntax: keygen (options)");
125 | Console.WriteLine("Generates, imports or show SSH RSA key pair. Requires swsh-keygen.exe.");
126 | Console.WriteLine("Default values are provided in parentheses.");
127 | Console.WriteLine("\nOptions:\n\timport\t\t- Imports RSA key pair.");
128 | Console.WriteLine("\tshow [private]\t- Print RSA keys. By default, prints public key. Use `private` to print private key.");
129 | break;
130 |
131 | case "help":
132 | Console.WriteLine("Syntax: help [command]");
133 | Console.WriteLine("Displays this help or command details.\nUsage: help pwd");
134 | break;
135 |
136 | case "clear":
137 | Console.WriteLine("Syntax: clear");
138 | Console.WriteLine("Clears the console.\nUsage: clear");
139 | break;
140 |
141 | case "pwd":
142 | Console.WriteLine("Syntax: pwd");
143 | Console.WriteLine("Prints working directory.\nUsage: pwd");
144 | break;
145 |
146 | case "computehash":
147 | Console.WriteLine("Syntax: computehash [(>/>>) path/to/file]");
148 | Console.WriteLine("Uses SHA-1 hash function to generate hashes for SWSH and swsh-keygen.\n");
149 | Console.WriteLine("Usage:\nTo overwrite-> computehash > path/to/file\nTo append-> computehash >> path/to/file");
150 | break;
151 |
152 | case "ls":
153 | Console.WriteLine("Syntax: ls");
154 | Console.WriteLine("Lists all files and directories in working directory.\nUsage: ls");
155 | break;
156 |
157 | case "cd":
158 | Console.WriteLine("Syntax: cd [arg]");
159 | Console.WriteLine("Changes directory to 'arg'. arg = directory name.\nUsage: cd desktop");
160 | break;
161 |
162 | case "upload":
163 | Console.WriteLine("Use `upload -h`");
164 | break;
165 |
166 | default:
167 | Error($"SWSH -> {Command} -> unknown command.\n");
168 | break;
169 | }
170 | } else {
171 | Console.Write(
172 | "Usage: [arguments] (options)\n"
173 | + "Available commands:\n"
174 | + " version -Check the version of swsh.\n"
175 | + " connect [user@host] (-p) -Connects to Server over SSH.\n"
176 | + " keygen (options) -Generates, imports or show SSH RSA key pair. `help keygen` for more.\n"
177 | + " help [command] -Displays this help or command details.\n"
178 | + " clear -Clears the console.\n"
179 | + " pwd -Prints working directory.\n"
180 | + " computehash [(>/>>) path] -Uses SHA-1 hash function to generate hashes for SWSH and swsh-keygen.\n"
181 | + " exit -Exits.\n"
182 | + " ls -Lists all files and directories in working directory.\n"
183 | + " cd [arg] -Changes directory to 'arg'. arg = directory name.\n"
184 | + " upload [arguments] -Uploads files and directories. 'upload -h' for help.\n");
185 | }
186 | }
187 | private static void Connect() {
188 | if (Command.Length <= 8) {
189 | Help("connect");
190 | return;
191 | }
192 | if (!Command.EndsWith("-p")) {
193 | if (!File.Exists(Keys)) {
194 | Console.Write("SWSH private key file not found. (I)mport or (G)enerate?: ");
195 | switch (Console.ReadKey().Key) {
196 | case ConsoleKey.I:
197 | ImportKey();
198 | break;
199 | case ConsoleKey.G:
200 | Keygen();
201 | break;
202 | default:
203 | Console.WriteLine(" <= Invalid option.");
204 | return;
205 | }
206 | return;
207 | }
208 | if (string.IsNullOrEmpty(ReadKeys()[1])) Color("WARNING: No public key detected.\n", ConsoleColor.Yellow);
209 | }
210 |
211 | var ccinfo = CreateConnection(Command.Remove(0, 8));
212 | if (ccinfo == null) return;
213 | Console.Write($"Waiting for response from {ccinfo.Username}@{ccinfo.Host}...\n");
214 | using (var ssh = new SshClient(ccinfo)) {
215 | ssh.Connect();
216 | Color($"Connected to {ccinfo.Username}@{ccinfo.Host}...\n", ConsoleColor.Green);
217 | var actual = ssh.CreateShellStream(
218 | "xterm-256color",
219 | (uint)Console.BufferWidth,
220 | (uint)Console.BufferHeight,
221 | (uint)Console.BufferWidth,
222 | (uint)Console.BufferHeight,
223 | Console.BufferHeight, null);
224 | //Read Thread
225 | var read = new Thread(() => {
226 | if (!actual.CanRead) return;
227 | while (true)
228 | Console.WriteLine(actual.ReadLine());
229 | });
230 | //Write Thread
231 | new Thread(() => {
232 | if (!actual.CanWrite) return;
233 | while (true) {
234 | try {
235 | actual.WriteLine("");
236 | var input = Console.ReadLine();
237 | Console.Write("\b\r\b\r");
238 | actual.WriteLine(input);
239 | if (input != "exit") continue;
240 | actual.Dispose();
241 | read.Abort();
242 | throw new Exception();
243 | } catch (Exception) {
244 | Color($"Connection to {ccinfo.Username}@{ccinfo.Host}, closed.\n",
245 | ConsoleColor.Yellow);
246 | Color("(E)xit SWSH - Any other key to reload SWSH: ", ConsoleColor.Blue);
247 | var key = Console.ReadKey();
248 | if (key.Key != ConsoleKey.E)
249 | Process.Start(Assembly.GetExecutingAssembly().Location);
250 | ssh.Disconnect();
251 | Environment.Exit(0);
252 | }
253 | }
254 | }).Start();
255 | read.Start();
256 | while (true) { }
257 | }
258 | }
259 | private static void ImportKey() {
260 | string[] data = new string[2];
261 | Console.WriteLine("\nImporting keys...");
262 | while (true) {
263 | Console.Write("Enter path to private key: ");
264 | data[0] = GetCommand();
265 | if (data[0].Trim() == string.Empty)
266 | Error("SWSH -> key path should not be empty!\n");
267 | else {
268 | if (File.Exists(data[0]) && File.Exists($"{WorkingDirectory}/{data[0]}"))
269 | Error($"SWSH -> {data[0]} -> file path is ambiguous.\n");
270 | else if (File.Exists($"{WorkingDirectory}/{data[0]}")) {
271 | data[0] = $"{WorkingDirectory.Replace('\\', '/')}/{data[0]}";
272 | break;
273 | } else if (!File.Exists(data[0]))
274 | Error($"SWSH -> {data[0]} -> file is non existent.\n");
275 | else break;
276 | }
277 | }
278 | Console.Write("Import public key? (y/n): ");
279 | if (GetCommand().ToUpper() == "Y") {
280 | while (true) {
281 | Console.Write("Enter path to public key: ");
282 | data[1] = GetCommand();
283 | if (data[1].Trim() == string.Empty)
284 | Error("SWSH -> key path should not be empty!\n");
285 | else {
286 | if (File.Exists(data[1]) && File.Exists($"{WorkingDirectory}/{data[1]}"))
287 | Error($"SWSH -> {data[1]} -> file path is ambiguous.\n");
288 | else if (File.Exists($"{WorkingDirectory}/{data[0]}")) {
289 | data[0] = $"{WorkingDirectory.Replace('\\', '/')}/{data[0]}";
290 | break;
291 | } else if (!File.Exists(data[1]))
292 | Error($"SWSH -> {data[1]} -> file is non existent.\n");
293 | else break;
294 | }
295 | }
296 | } else Console.Write("\r\b\rImport public key? (y/n): ...skipped\n");
297 | WriteKeys(data[0], data[1] ?? "");
298 | }
299 | private static void Keygen() {
300 | Command = Command.Length > 7 ? Command.Remove(0, 7) : null;
301 | if (Command != null && Command.StartsWith("show")) {
302 | if (Command.Remove(0, 4).Trim() == "private") {
303 | Console.WriteLine(ReadKeys()[0]);
304 | } else Console.WriteLine(!string.IsNullOrEmpty(ReadKeys()[1]) ? ReadKeys()[1] : "No public key detected.");
305 | return;
306 | }
307 | if (File.Exists(Keys)) {
308 | Color(
309 | "WARNING: This action will overwrite previously generated or imported keys in the data file but not the original keys. Continue? (y/n): ",
310 | ConsoleColor.Yellow);
311 | if (Console.ReadKey().Key != ConsoleKey.Y) {
312 | Console.WriteLine();
313 | return;
314 | }
315 | }
316 | if (Command == "import") {
317 | ImportKey();
318 | return;
319 | }
320 | if (!KeygenIsAvailable ^ Unstable) {
321 | Color("Key generation is unavailable.\n", ConsoleColor.DarkBlue);
322 | return;
323 | }
324 | if (File.Exists("swsh-keygen.exe")) {
325 | if (!CheckHash(true) ^ Unstable) return;
326 | Console.WriteLine("\nGenerating public/private rsa key pair.");
327 | string privateFile, publicFile;
328 | Color("exit", ConsoleColor.Red);
329 | Console.Write(" or ");
330 | Color("-e", ConsoleColor.Red);
331 | Console.Write(" to cancel.\n");
332 | do {
333 | Color("Enter absolute path to save private key (%appdata%/SWSH/swsh.private):\t", ConsoleColor.Yellow);
334 | privateFile = GetCommand();
335 | if (privateFile == string.Empty) privateFile = AppDataDirectory + "/swsh.private";
336 | else if (privateFile == "-e" || privateFile == "exit") return;
337 | } while (!IsWritable(privateFile));
338 | do {
339 | Color("Enter absolute path to save public key (%appdata%/SWSH/swsh.public):\t", ConsoleColor.Yellow);
340 | publicFile = GetCommand();
341 | if (publicFile == string.Empty) publicFile = AppDataDirectory + "/swsh.public";
342 | else if (publicFile == "-e" || privateFile == "exit") return;
343 | } while (!IsWritable(publicFile));
344 | bool IsWritable(string path) {
345 | if (!File.Exists(path)) return true;
346 | Color($"File exists: {new FileInfo(path).FullName}\n\n\nOverwrite? (y/n): ", ConsoleColor.Red);
347 | return GetCommand().ToUpper() == "Y";
348 | }
349 | var keygenProcess = new Process {
350 | StartInfo = new ProcessStartInfo {
351 | FileName = "swsh-keygen.exe",
352 | Arguments = $"-pub={new FileInfo(publicFile).FullName} -pri={new FileInfo(privateFile).FullName}",
353 | RedirectStandardOutput = true,
354 | UseShellExecute = false,
355 | CreateNoWindow = true
356 | }
357 | };
358 | keygenProcess.Start();
359 | keygenProcess.WaitForExit();
360 | if (keygenProcess.ExitCode != 0) {
361 | Color($"WARNING: swsh-keygen exited with exit code {keygenProcess.ExitCode}.", ConsoleColor.Yellow);
362 | return;
363 | }
364 | Color($"Your public key:\n\n{File.ReadAllLines(publicFile)[0]}\n", ConsoleColor.Green);
365 | WriteKeys(privateFile, publicFile);
366 | } else Error($"The binary 'swsh-keygen.exe' was not found. Are you sure it's installed?\nSee: {Url.Keygen}.\n");
367 | }
368 | private static void Clear() {
369 | Console.Clear();
370 | GetVersion();
371 | Console.Write("Use `help` command for help.\n\n");
372 | }
373 | private static void Ls() {
374 | if (Directory.GetDirectories(WorkingDirectory).Length > 0) {
375 | var data = new List();
376 | Directory.GetDirectories(WorkingDirectory).ToList().ForEach(dir => data.Add(dir));
377 | Directory.GetFiles(WorkingDirectory).ToList().ForEach(file => data.Add(file));
378 | data.Sort();
379 | Console.WriteLine("Size\tUser Date Modified Name\n====\t==== ============= ====");
380 | data.ForEach(x => {
381 | if (File.Exists(x)) {
382 | var info = new FileInfo(x);
383 | if (info.Attributes.ToString().Contains("Hidden")) return;
384 | var owner = File.GetAccessControl(x).GetOwner(typeof(NTAccount)).ToString().Split('\\')[1];
385 | var size = (info.Length > 1024 ? (info.Length / 1024 > 1024 ? info.Length / 1024 / 1024 : info.Length / 1024) :
386 | info.Length).ToString();
387 | var toApp = "";
388 | owner = owner.Length >= 10 ? owner.Remove(5) + "..." + owner.Remove(0, owner.Length - 2) : owner;
389 | if (owner.Length < 10) for (int i = 0; i < 10 - owner.Length; i++) toApp += " ";
390 | owner += toApp;
391 | if (size.Length < 4) for (int i = 0; i < 3 - size.Length; i++) toApp += " ";
392 | size = toApp + size;
393 | Color(size, ConsoleColor.Green);
394 | Color(info.Length > 1024 ? (info.Length / 1024 > 1024 ? "MB" : "KB") : "B", ConsoleColor.DarkGreen);
395 | Color($"\t{owner} ", ConsoleColor.Yellow);
396 | Color(
397 | $"{($"{info.LastWriteTime.Date:d}".Split('/')[0].Length > 1 ? "" : " ")}" +
398 | $"{$"{info.LastWriteTime.Date:d}".Split('/')[0]} " +
399 | $"{$"{info.LastWriteTime.Date:m}".Remove(3)} " +
400 | $"{info.LastWriteTime.ToLocalTime():HH:mm} ",
401 | ConsoleColor.Blue);
402 | Color(info.Name, Path.GetFileNameWithoutExtension(x).Length > 0 ? ConsoleColor.Magenta : ConsoleColor.Cyan);
403 | Console.WriteLine();
404 | } else if (Directory.Exists(x)) {
405 | var info = new DirectoryInfo(x);
406 | if (info.Attributes.ToString().Contains("Hidden")) return;
407 | var owner = File.GetAccessControl(x).GetOwner(typeof(NTAccount)).ToString().Split('\\')[1];
408 | owner = owner.Length >= 10 ? owner.Remove(5) + "..." + owner.Remove(0, owner.Length - 2) : owner;
409 | var toApp = "";
410 | if (owner.Length < 10) for (int i = 0; i < 10 - owner.Length; i++) toApp += " ";
411 | owner += toApp;
412 | Color(" -", ConsoleColor.DarkGray);
413 | Color($"\t{owner} ", ConsoleColor.Yellow);
414 | Color(
415 | $"{($"{info.LastWriteTime.Date:d}".Split('/')[0].Length > 1 ? "" : " ")}" +
416 | $"{$"{info.LastWriteTime.Date:d}".Split('/')[0]} " +
417 | $"{$"{info.LastWriteTime.Date:m}".Remove(3)} " +
418 | $"{info.LastWriteTime.ToLocalTime():HH:mm} ",
419 | ConsoleColor.Blue);
420 | Color(info.Name,
421 | info.Name.StartsWith(".") ? ConsoleColor.DarkCyan : info.GetFiles().Length > 0 || info.GetDirectories().Length > 0 ?
422 | ConsoleColor.White : ConsoleColor.DarkGray);
423 | Color(info.GetFiles().Length == 0 && info.GetDirectories().Length == 0 ? " " : "", ConsoleColor.DarkRed);
424 | Console.WriteLine();
425 | }
426 | });
427 | }
428 | if (Directory.GetDirectories(WorkingDirectory).Length == 0 && Directory.GetFiles(WorkingDirectory).Length == 0)
429 | Color("No files or directories here.\n", ConsoleColor.Yellow);
430 | }
431 | private static void Cd() {
432 | if (Command.Length <= 3) {
433 | Help("cd");
434 | return;
435 | }
436 | if ((Command = Command.Remove(0, 3)) == "..") ChangeWorkingDir(Path.GetDirectoryName(WorkingDirectory));
437 | else if (Command.StartsWith("./")) ChangeWorkingDir($"{WorkingDirectory}/{Command.Remove(0, 2)}");
438 | else if (Command.StartsWith("/")) ChangeWorkingDir(Path.GetPathRoot(WorkingDirectory) + Command.Remove(0, 1));
439 | else ChangeWorkingDir($"{WorkingDirectory}/{Command}");
440 | void ChangeWorkingDir(string path) {
441 | path = path.Replace('\\', '/');
442 | if (Directory.Exists(Path.GetFullPath(path))) WorkingDirectory = Path.GetFullPath(path);
443 | else Error($"SWSH -> {path} -> path does not exists.\n");
444 | }
445 | }
446 | private static void Upload() {
447 | if (Command.Length <= 7) {
448 | Command = "upload -h";
449 | }
450 | if ((Command = Command.Remove(0, 7)) == "-h") {
451 | Console.WriteLine(
452 | "upload [--dir]* [args] [user@host]:[location]\n\n'args' are seperated using spaces ( ) and last 'arg' will be treated as server data whic"
453 | + "h includes username and host location as well as the location of data to upload, part after the colon (:), where the data is to be uplo"
454 | + "aded. Use flag '--dir' to upload directiories. Do not use absolute paths for local path, change working directory to navigate.");
455 | } else {
456 | var toupload = Command.StartsWith("--dir") ? Command.Replace("--dir", "").Trim().Split(' ').ToList() : Command.Trim().Split(' ').ToList();
457 | try {
458 | var serverData = toupload.Pop().Split(':');
459 | var data = serverData[0];
460 | var location = serverData[1];
461 | try {
462 | var ccinfo = CreateConnection(data);
463 | if (ccinfo != null) {
464 | if (Command.StartsWith("--dir"))
465 | using (var sftp = new SftpClient(ccinfo)) {
466 | Command = Command.Replace("--dir", "");
467 | sftp.Connect();
468 | toupload.ForEach(x => {
469 | var path = $"{WorkingDirectory}/{x.Trim()}";
470 | location = serverData[1] + (serverData[1].EndsWith("/") ? "" : "/") +
471 | x.Trim();
472 | if (!sftp.Exists(location)) sftp.CreateDirectory(location);
473 | Color($"Uploading : {x.Trim()}\n", ConsoleColor.Yellow);
474 | UploadDir(sftp, path, location);
475 | Color("Done.\n", ConsoleColor.Green);
476 | });
477 | } else
478 | using (var scp = new ScpClient(ccinfo)) {
479 | scp.Connect();
480 | toupload.ForEach(x => {
481 | var path = $"{WorkingDirectory}/{x.Trim()}";
482 | if (File.Exists(path)) {
483 | Color($"Uploading : {x.Trim()}", ConsoleColor.Yellow);
484 | scp.Upload(new FileInfo(path), location);
485 | Color(" -> Done\n", ConsoleColor.Green);
486 | } else Error($"SWSH -> {path.Replace('/', '\\')} -> file does not exists.\n");
487 | });
488 | }
489 | }
490 | } catch (Exception exp) { Error($"{exp.Message}\n"); }
491 | } catch { Error($"SWSH -> upload {Command} -> is not the correct syntax for this command.\n"); }
492 | }
493 | void UploadDir(SftpClient client, string localPath, string remotePath) {
494 | new DirectoryInfo(localPath).EnumerateFileSystemInfos().ToList().ForEach(x => {
495 | if (x.Attributes.HasFlag(FileAttributes.Directory)) {
496 | var subPath = $"{remotePath}/{x.Name}";
497 | if (!client.Exists(subPath)) client.CreateDirectory(subPath);
498 | UploadDir(client, x.FullName, $"{remotePath}/{x.Name}");
499 | } else {
500 | using (Stream fileStream = new FileStream(x.FullName, FileMode.Open)) {
501 | Console.ForegroundColor = ConsoleColor.Yellow;
502 | Console.Write($"\tUploading : {x} ({((FileInfo)x).Length:N0} bytes)");
503 | client.UploadFile(fileStream, $"{remotePath}/{x.Name}");
504 | Color(" -> Done\n", ConsoleColor.Green);
505 | }
506 | }
507 | });
508 | }
509 | }
510 | private static string Pop(this IList list) {
511 | var retVal = list[list.Count - 1];
512 | list.RemoveAt(list.Count - 1);
513 | return retVal;
514 | }
515 | private static string GetVersion() {
516 | Console.Write(
517 | " ______ _______ __ __\n / ___/ | / / ___// / / /\n \\__ \\| | /| / /\\__ \\/ /_/ / \n ___/ /| |/ |/ /___/ / __ / \n/____/ |_"
518 | + "_/|__//____/_/ /_/ \n Secure Windows Shell \n");
519 | Console.Write($"\nRelease: {Codename}\n");
520 | return $"{Codename} {(new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)? "(Administrator)" : "")}";
521 | }
522 | private static void Color(string message, ConsoleColor cc) {
523 | Console.ForegroundColor = cc;
524 | Console.Write(message);
525 | Console.ResetColor();
526 | }
527 | private static bool CheckHash(bool ignore) {
528 | bool CompareHash(string path, string hash) => !ComputeHash(path).Equals(hash.Trim());
529 | string GetHash(string uri) => new WebClient().DownloadString($"{uri}?" + new Random().Next());
530 | string
531 | error = "ERROR: Checksum Mismatch! This executable *may* be out of date or malicious!\n",
532 | checksumfile = Url.Checksum,
533 | swshlocation = Assembly.GetExecutingAssembly().Location,
534 | keygenlocation = "swsh-keygen.exe";
535 | try {
536 | if (CompareHash(swshlocation, GetHash(checksumfile).Split(' ')[0]) || CompareHash(keygenlocation, GetHash(checksumfile).Split(' ')[1]))
537 | throw new Exception();
538 | return true;
539 | } catch (Exception) {
540 | if (!File.Exists(keygenlocation)) {
541 | Color("WARNING: Could not find swsh-keygen.exe. SSH key generation will not be available.\n", ConsoleColor.Yellow);
542 | }
543 | if (!ignore) {
544 | Color(error, ConsoleColor.Red);
545 | Console.Read();
546 | Environment.Exit(500);
547 | }
548 | return false;
549 | }
550 | }
551 | private static void PrintHash() {
552 | string action = Command.Remove(0, 11).Trim();
553 | if (action.StartsWith(">") && File.Exists("swsh-keygen.exe")) {
554 | Color("Exporting... ", ConsoleColor.Yellow);
555 | string file;
556 | if (action.StartsWith(">>")) {
557 | file = action.Remove(0, 2).Trim();
558 | File.AppendAllText(file, $"{ComputeHash(Assembly.GetExecutingAssembly().Location)} {ComputeHash("swsh-keygen.exe")}");
559 | Console.WriteLine(new FileInfo(file).FullName);
560 | return;
561 | }
562 | file = action.Remove(0, 1).Trim();
563 | File.WriteAllText(file, $"{ComputeHash(Assembly.GetExecutingAssembly().Location)} {ComputeHash("swsh-keygen.exe")}");
564 | Console.WriteLine(new FileInfo(file).FullName);
565 | return;
566 | }
567 | Console.WriteLine($"{ComputeHash(Assembly.GetExecutingAssembly().Location)} -- SHA1 -- SWSH.exe");
568 | if (File.Exists("swsh-keygen.exe")) Console.WriteLine($"{ComputeHash("swsh-keygen.exe")} -- SHA1 -- swsh-keygen.exe");
569 | }
570 | private static string ComputeHash(string path) =>
571 | new List(new SHA1CryptoServiceProvider()
572 | .ComputeHash(File.ReadAllBytes(path)))
573 | .Select(x => x.ToString("x2"))
574 | .Aggregate((x, y) => x + y);
575 | private static void Notice() =>
576 | Console.Write("SWSH - Secure Windows Shell\nCopyright (C) 2017 Muhammad Muzzammil\nThis program comes with ABSOLUTELY NO WARRANTY; for details ty"
577 | + "pe `license'.\nThis is free software, and you are welcome to redistribute it\nunder certain conditions; type `license' for detail"
578 | + "s.\n\n");
579 | private static string GetCommand() {
580 | var commands = new[] { "version", "connect", "keygen", "help", "clear", "exit", "upload", "pwd", "computehash" };
581 | var list = commands.ToList();
582 | list.AddRange(Directory.GetDirectories(WorkingDirectory).Select(i => $"cd {new DirectoryInfo(i).Name.ToLower()}"));
583 | try {
584 | ReadLine.AutoCompletionHandler = (data, length) => {
585 | var tList = new List();
586 | if (data.StartsWith("cd ") && (data.Contains("/") || data.Contains("\\")))
587 | Directory.GetDirectories($"{WorkingDirectory}/{Path.GetDirectoryName(data.Remove(0, 3))}").ToList()
588 | .Where(x => new DirectoryInfo(x)
589 | .FullName.ToLower().Contains(data.ToLower().Split(' ')[1].Replace('/', '\\'))).ToList()
590 | .ForEach(x => tList.Add(x.Remove(0, $"{WorkingDirectory}/{Path.GetDirectoryName(data.Remove(0, 3))}".Length + 1).ToLower()));
591 | if (data.Trim() == "help")
592 | commands.ToList().ForEach(x => tList.Add(x));
593 | list.Where(x => x.Contains(data)).ToList().ForEach(y => tList.Add(y.Remove(0, length)));
594 | return tList.ToArray();
595 | };
596 | } catch (IndexOutOfRangeException) { }
597 | var read = ReadLine.Read();
598 | File.AppendAllText(History, $"[{DateTime.UtcNow} UTC]\t=>\t{read}\n");
599 | if (read.Contains("%appdata%"))
600 | read = read.Replace("%appdata%", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData).Replace('\\', '/'));
601 | return read.TrimEnd().TrimStart().Trim();
602 | }
603 | private static string GetPassword(string prompt) {
604 | ReadLine.PasswordMode = true;
605 | var password = ReadLine.Read(prompt);
606 | ReadLine.GetHistory().Pop();
607 | ReadLine.PasswordMode = false;
608 | return password;
609 | }
610 | private static void Error(string err) {
611 | Color("ERROR: ", ConsoleColor.Red);
612 | Console.Write(err);
613 | }
614 | private static string[] ReadKeys() {
615 | var xml = new XmlDocument();
616 | xml.Load(Keys);
617 | return new[] { xml.GetElementsByTagName("private")[0].InnerText, xml.GetElementsByTagName("public")[0].InnerText };
618 | }
619 | private static void WriteKeys(string privateFile, string publicFile) {
620 | var publickey = publicFile == null ? "" : File.ReadAllText(new FileInfo(publicFile).FullName);
621 | File.WriteAllLines(Keys, new[] {
622 | "",
623 | "",
624 | $"{File.ReadAllText(new FileInfo(privateFile).FullName)}",
625 | $"{publickey}",
626 | ""
627 | });
628 | }
629 | private static ConnectionInfo CreateConnection(string data) {
630 | try {
631 | if (data.EndsWith("-p")) {
632 | data = data.Split(' ')[0];
633 | return new ConnectionInfo(
634 | data.Split('@')[1],
635 | data.Split('@')[0],
636 | new PasswordAuthenticationMethod(
637 | data.Split('@')[0],
638 | GetPassword($"Password for {data}: ")));
639 | }
640 | return new ConnectionInfo(
641 | data.Split('@')[1],
642 | data.Split('@')[0],
643 | new PrivateKeyAuthenticationMethod(
644 | data.Split('@')[0],
645 | new PrivateKeyFile(new StreamReader(new MemoryStream(Encoding.ASCII.GetBytes(ReadKeys()[0]))).BaseStream)));
646 | } catch (Exception exp) { Error($"{exp.Message}\n"); }
647 | return null;
648 | }
649 | }
650 | }
--------------------------------------------------------------------------------
/SWSH/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * SWSH - Secure Windows Shell
3 | * Copyright (C) 2017 Muhammad Muzzammil
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | using System.Reflection;
20 | using System.Runtime.InteropServices;
21 |
22 | // General Information about an assembly is controlled through the following
23 | // set of attributes. Change these attribute values to modify the information
24 | // associated with an assembly.
25 | [assembly: AssemblyTitle("SWSH")]
26 | [assembly: AssemblyDescription("Licensed under the GNU GPL v3")]
27 | [assembly: AssemblyConfiguration("")]
28 | [assembly: AssemblyCompany("Secure Windows Shell")]
29 | [assembly: AssemblyProduct("SWSH")]
30 | [assembly: AssemblyCopyright("Copyright © 2017 Muhammad Muzzammil")]
31 | [assembly: AssemblyTrademark("SWSH")]
32 | [assembly: AssemblyCulture("")]
33 |
34 | // Setting ComVisible to false makes the types in this assembly not visible
35 | // to COM components. If you need to access a type in this assembly from
36 | // COM, set the ComVisible attribute to true on that type.
37 | [assembly: ComVisible(false)]
38 |
39 | // The following GUID is for the ID of the typelib if this project is exposed to COM
40 | [assembly: Guid("6a30c4d1-0e4f-49b9-b2b2-d210923ce44c")]
41 |
42 | // Version information for an assembly consists of the following four values:
43 | //
44 | // Major Version
45 | // Minor Version
46 | // Build Number
47 | // Revision
48 | //
49 | // You can specify all the values or you can default the Build and Revision Numbers
50 | // by using the '*' as shown below:
51 | // [assembly: AssemblyVersion("1.0.*")]
52 | [assembly: AssemblyVersion("1.0")]
53 | [assembly: AssemblyFileVersion("1.0")]
54 |
--------------------------------------------------------------------------------
/SWSH/ReadLine.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * https://github.com/tonerdo/readline/blob/master/LICENSE
3 | * The MIT License(MIT)
4 | *
5 | * Copyright(c) 2017 Toni Solarin-Sodara
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in all
15 | * copies or substantial portions of the Software.
16 | */
17 |
18 | using System;
19 | using System.Collections.Generic;
20 | using System.Text;
21 |
22 | namespace SWSH {
23 | public static class ReadLine {
24 | private static KeyHandler _keyHandler;
25 | public static List History;
26 | public static List GetHistory() => History;
27 | public static Func AutoCompletionHandler { private get; set; }
28 | public static bool PasswordMode { private get; set; }
29 | public static string Read(string prompt = "", string defaultInput = "") {
30 | Console.Write(prompt);
31 |
32 | _keyHandler = new KeyHandler(new Console2() { PasswordMode = PasswordMode }, History, AutoCompletionHandler);
33 | ConsoleKeyInfo keyInfo = Console.ReadKey(true);
34 |
35 | while (keyInfo.Key != ConsoleKey.Enter) {
36 | _keyHandler.Handle(keyInfo);
37 | keyInfo = Console.ReadKey(true);
38 | }
39 |
40 | Console.WriteLine();
41 |
42 | string text = _keyHandler.Text;
43 | if (String.IsNullOrWhiteSpace(text) && !String.IsNullOrWhiteSpace(defaultInput))
44 | text = defaultInput;
45 | else
46 | History.Add(text);
47 |
48 | return text;
49 | }
50 | }
51 | internal class KeyHandler {
52 | private int _cursorPos;
53 | private int _cursorLimit;
54 | private readonly StringBuilder _text;
55 | private readonly List _history;
56 | private int _historyIndex;
57 | private ConsoleKeyInfo _keyInfo;
58 | private readonly Dictionary _keyActions;
59 | private string[] _completions;
60 | private int _completionStart;
61 | private int _completionsIndex;
62 | private readonly IConsole _console2;
63 |
64 | private bool IsStartOfLine() => _cursorPos == 0;
65 | private bool IsEndOfLine() => _cursorPos == _cursorLimit;
66 | private bool IsStartOfBuffer() => _console2.CursorLeft == 0;
67 | private bool IsEndOfBuffer() => _console2.CursorLeft == _console2.BufferWidth - 1;
68 | private bool IsInAutoCompleteMode() => _completions != null;
69 | private void MoveCursorLeft() {
70 | if (IsStartOfLine())
71 | return;
72 | if (IsStartOfBuffer())
73 | _console2.SetCursorPosition(_console2.BufferWidth - 1, _console2.CursorTop - 1);
74 | else
75 | _console2.SetCursorPosition(_console2.CursorLeft - 1, _console2.CursorTop);
76 | _cursorPos--;
77 | }
78 | private void MoveCursorHome() {
79 | while (!IsStartOfLine())
80 | MoveCursorLeft();
81 | }
82 | private string BuildKeyInput() {
83 | return (_keyInfo.Modifiers != ConsoleModifiers.Control && _keyInfo.Modifiers != ConsoleModifiers.Shift) ?
84 | _keyInfo.Key.ToString() : _keyInfo.Modifiers.ToString() + _keyInfo.Key.ToString();
85 | }
86 | private void MoveCursorRight() {
87 | if (IsEndOfLine())
88 | return;
89 | if (IsEndOfBuffer())
90 | _console2.SetCursorPosition(0, _console2.CursorTop + 1);
91 | else
92 | _console2.SetCursorPosition(_console2.CursorLeft + 1, _console2.CursorTop);
93 | _cursorPos++;
94 | }
95 | private void MoveCursorEnd() {
96 | while (!IsEndOfLine())
97 | MoveCursorRight();
98 | }
99 | private void ClearLine() {
100 | MoveCursorEnd();
101 | while (!IsStartOfLine())
102 | Backspace();
103 | }
104 | private void WriteNewString(string str) {
105 | ClearLine();
106 | foreach (char character in str)
107 | WriteChar(character);
108 | }
109 | private void WriteString(string str) {
110 | foreach (char character in str)
111 | WriteChar(character);
112 | }
113 | private void WriteChar() => WriteChar(_keyInfo.KeyChar);
114 | private void WriteChar(char c) {
115 | if (IsEndOfLine()) {
116 | _text.Append(c);
117 | _console2.Write(c.ToString());
118 | _cursorPos++;
119 | } else {
120 | int left = _console2.CursorLeft;
121 | int top = _console2.CursorTop;
122 | string str = _text.ToString().Substring(_cursorPos);
123 | _text.Insert(_cursorPos, c);
124 | _console2.Write(c.ToString() + str);
125 | _console2.SetCursorPosition(left, top);
126 | MoveCursorRight();
127 | }
128 | _cursorLimit++;
129 | }
130 | private void Backspace() {
131 | if (IsStartOfLine())
132 | return;
133 | MoveCursorLeft();
134 | int index = _cursorPos;
135 | _text.Remove(index, 1);
136 | string replacement = _text.ToString().Substring(index);
137 | int left = _console2.CursorLeft;
138 | int top = _console2.CursorTop;
139 | _console2.Write($"{replacement} ");
140 | _console2.SetCursorPosition(left, top);
141 | _cursorLimit--;
142 | }
143 | private void StartAutoComplete() {
144 | while (_cursorPos > _completionStart)
145 | Backspace();
146 |
147 | _completionsIndex = 0;
148 |
149 | WriteString(_completions[_completionsIndex]);
150 | }
151 | private void NextAutoComplete() {
152 | while (_cursorPos > _completionStart)
153 | Backspace();
154 |
155 | _completionsIndex++;
156 |
157 | if (_completionsIndex == _completions.Length)
158 | _completionsIndex = 0;
159 |
160 | WriteString(_completions[_completionsIndex]);
161 | }
162 | private void PreviousAutoComplete() {
163 | while (_cursorPos > _completionStart)
164 | Backspace();
165 |
166 | _completionsIndex--;
167 |
168 | if (_completionsIndex == -1)
169 | _completionsIndex = _completions.Length - 1;
170 |
171 | WriteString(_completions[_completionsIndex]);
172 | }
173 | private void PrevHistory() {
174 | if (_historyIndex > 0) {
175 | _historyIndex--;
176 | WriteNewString(_history[_historyIndex]);
177 | }
178 | }
179 | private void NextHistory() {
180 | if (_historyIndex < _history.Count) {
181 | _historyIndex++;
182 | if (_historyIndex == _history.Count)
183 | ClearLine();
184 | else
185 | WriteNewString(_history[_historyIndex]);
186 | }
187 | }
188 | private void ResetAutoComplete() {
189 | _completions = null;
190 | _completionsIndex = 0;
191 | }
192 | public string Text => _text.ToString();
193 | public KeyHandler(IConsole console, List history, Func autoCompleteHandler) {
194 | _console2 = console;
195 |
196 | _historyIndex = history.Count;
197 | _history = history;
198 | _text = new StringBuilder();
199 | _keyActions = new Dictionary {
200 | ["LeftArrow"] = MoveCursorLeft,
201 | ["Home"] = MoveCursorHome,
202 | ["End"] = MoveCursorEnd,
203 | ["ControlA"] = MoveCursorHome,
204 | ["ControlB"] = MoveCursorLeft,
205 | ["RightArrow"] = MoveCursorRight,
206 | ["ControlF"] = MoveCursorRight,
207 | ["ControlE"] = MoveCursorEnd,
208 | ["Backspace"] = Backspace,
209 | ["ControlH"] = Backspace,
210 | ["ControlL"] = ClearLine,
211 | ["UpArrow"] = PrevHistory,
212 | ["ControlP"] = PrevHistory,
213 | ["DownArrow"] = NextHistory,
214 | ["ControlN"] = NextHistory,
215 | ["ControlU"] = () => {
216 | while (!IsStartOfLine())
217 | Backspace();
218 | },
219 | ["ControlK"] = () => {
220 | int pos = _cursorPos;
221 | MoveCursorEnd();
222 | while (_cursorPos > pos)
223 | Backspace();
224 | },
225 | ["ControlW"] = () => {
226 | while (!IsStartOfLine() && _text[_cursorPos - 1] != ' ')
227 | Backspace();
228 | },
229 |
230 | ["Tab"] = () => {
231 | if (IsInAutoCompleteMode()) {
232 | NextAutoComplete();
233 | } else {
234 | if (autoCompleteHandler == null || !IsEndOfLine())
235 | return;
236 |
237 | char[] anyOf = new char[] { ' ', '.', '/', '\\', ':' };
238 | string text = _text.ToString();
239 |
240 | _completionStart = text.LastIndexOfAny(anyOf);
241 | _completionStart = _completionStart == -1 ? 0 : _completionStart + 1;
242 |
243 | _completions = autoCompleteHandler.Invoke(text, _completionStart);
244 | _completions = _completions?.Length == 0 ? null : _completions;
245 |
246 | if (_completions == null)
247 | return;
248 |
249 | StartAutoComplete();
250 | }
251 | },
252 |
253 | ["ShiftTab"] = () => {
254 | if (IsInAutoCompleteMode()) {
255 | PreviousAutoComplete();
256 | }
257 | }
258 | };
259 | }
260 | public void Handle(ConsoleKeyInfo keyInfo) {
261 | _keyInfo = keyInfo;
262 |
263 | // If in auto complete mode and Tab wasn't pressed
264 | if (IsInAutoCompleteMode() && _keyInfo.Key != ConsoleKey.Tab)
265 | ResetAutoComplete();
266 |
267 | _keyActions.TryGetValue(BuildKeyInput(), out Action action);
268 | action = action ?? WriteChar;
269 | action.Invoke();
270 | }
271 | }
272 | internal interface IConsole {
273 | int CursorLeft { get; }
274 | int CursorTop { get; }
275 | int BufferWidth { get; }
276 | int BufferHeight { get; }
277 | void SetCursorPosition(int left, int top);
278 | void SetBufferSize(int width, int height);
279 | void Write(string value);
280 | void WriteLine(string value);
281 | }
282 | internal class Console2 : IConsole {
283 | public int CursorLeft => Console.CursorLeft;
284 | public int CursorTop => Console.CursorTop;
285 | public int BufferWidth => Console.BufferWidth;
286 | public int BufferHeight => Console.BufferHeight;
287 | public bool PasswordMode { get; set; }
288 | public void SetBufferSize(int width, int height) => Console.SetBufferSize(width, height);
289 | public void SetCursorPosition(int left, int top) {
290 | if (!PasswordMode)
291 | Console.SetCursorPosition(left, top);
292 | }
293 | public void Write(string value) {
294 | if (PasswordMode)
295 | value = new String(default(char), value.Length);
296 | Console.Write(value);
297 | }
298 | public void WriteLine(string value) => Console.WriteLine(value);
299 | }
300 | }
301 |
--------------------------------------------------------------------------------
/SWSH/SWSH.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {6A30C4D1-0E4F-49B9-B2B2-D210923CE44C}
8 | Exe
9 | SWSH
10 | SWSH
11 | v4.7
12 | 512
13 | true
14 |
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 | icon.ico
37 |
38 |
39 | false
40 |
41 |
42 |
43 | ..\packages\SSH.NET.2016.1.0\lib\net40\Renci.SshNet.dll
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/SWSH/Url.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * SWSH - Secure Windows Shell
3 | * Copyright (C) 2017 Muhammad Muzzammil
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | namespace SWSH {
20 | public static class Url {
21 | public const string
22 | Keygen = "https://github.com/SecureWindowsShell/swsh-keygen",
23 | Checksum = "https://raw.githubusercontent.com/SecureWindowsShell/SWSH/master/checksum",
24 | License = "https://raw.githubusercontent.com/SecureWindowsShell/SWSH/master/LICENSE";
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/SWSH/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SecureWindowsShell/SWSH/0624a953ed867ae658abe9a298e5bf3e4d61c65d/SWSH/icon.ico
--------------------------------------------------------------------------------
/SWSH/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '{build}'
2 | image: Visual Studio 2017
3 | before_build:
4 | - cmd: nuget restore
5 | build:
6 | verbosity: minimal
7 | notifications:
8 | - provider: Email
9 | to:
10 | - swsh@muzzammil.xyz
11 | on_build_success: true
12 | on_build_failure: true
13 | on_build_status_changed: true
--------------------------------------------------------------------------------
/checksum:
--------------------------------------------------------------------------------
1 | 32c2d93ae75262588a3e24d97ae9807d83bf308e 17ac506efa4c2eea4d0efeae5b9614dc3baadfaf
--------------------------------------------------------------------------------