ssh-handler.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. // SSH Handler
  2. //
  3. // Douglas Thrift
  4. //
  5. // ssh-handler.cs
  6. /* Copyright 2013 Douglas Thrift
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. using System;
  21. using System.Collections.Generic;
  22. using System.Diagnostics;
  23. using System.IO;
  24. using System.Linq;
  25. using System.Text;
  26. using System.Text.RegularExpressions;
  27. using System.Windows.Forms;
  28. using Microsoft.Win32;
  29. public enum Option
  30. {
  31. None,
  32. Set,
  33. Optional,
  34. }
  35. public interface Handler
  36. {
  37. IList<string> Usages
  38. {
  39. get;
  40. }
  41. Option DoMatch(string arg);
  42. bool Find();
  43. void Execute(Uri uri, string user, string password);
  44. }
  45. public abstract class FindInPathMixin
  46. {
  47. protected string FindInPath(string program)
  48. {
  49. foreach (string location in Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator))
  50. {
  51. string path = Path.Combine(location, program);
  52. if (File.Exists(path))
  53. return path;
  54. }
  55. return null;
  56. }
  57. }
  58. public class Putty : FindInPathMixin, Handler
  59. {
  60. private Regex option = new Regex(@"^(?:/|--?)putty(?:[:=](?<putty_path>.*))?$", RegexOptions.IgnoreCase);
  61. private string path = null;
  62. public IList<string> Usages
  63. {
  64. get
  65. {
  66. return new string[] { "/putty[:<putty-path>] -- Use PuTTY to connect" };
  67. }
  68. }
  69. public Option DoMatch(string arg)
  70. {
  71. Match match;
  72. if ((match = option.Match(arg)).Success)
  73. {
  74. Group group = match.Groups["putty_path"];
  75. if (group.Success)
  76. path = group.Value;
  77. return Option.Set;
  78. }
  79. else
  80. return Option.None;
  81. }
  82. public bool Find()
  83. {
  84. if (path != null)
  85. goto Found;
  86. foreach (var hive in new RegistryHive[] { RegistryHive.CurrentUser, RegistryHive.LocalMachine })
  87. {
  88. var views = new List<RegistryView>(new RegistryView[] { RegistryView.Registry32 });
  89. if (Environment.Is64BitOperatingSystem)
  90. views.Insert(0, RegistryView.Registry64);
  91. foreach (RegistryView view in views)
  92. using (RegistryKey baseKey = RegistryKey.OpenBaseKey(hive, view), key = baseKey.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\PuTTY_is1"))
  93. if (key != null)
  94. {
  95. string location = (string)key.GetValue("InstallLocation");
  96. if (location == null)
  97. continue;
  98. path = Path.Combine(location, "putty.exe");
  99. if (File.Exists(path))
  100. {
  101. Debug.WriteLine("Found PuTTY in registry: {0}", path, null);
  102. goto Found;
  103. }
  104. else
  105. path = null;
  106. }
  107. }
  108. if ((path = FindInPath("putty.exe")) != null)
  109. {
  110. Debug.WriteLine("Found PuTTY in path: {0}", path, null);
  111. goto Found;
  112. }
  113. return false;
  114. Found:
  115. path = path.Trim();
  116. return true;
  117. }
  118. public void Execute(Uri uri, string user, string password)
  119. {
  120. if (!Find())
  121. throw new Exception("Could not find PuTTY executable.");
  122. StringBuilder args = new StringBuilder();
  123. if (password != null)
  124. args.AppendFormat("-pw {0} ", password);
  125. if (uri.Port != -1)
  126. args.AppendFormat("-P {0} ", uri.Port);
  127. if (user != null)
  128. args.AppendFormat("{0}@", user);
  129. args.Append(uri.Host);
  130. Debug.WriteLine("Running PuTTY command: {0} {1}", path, args);
  131. Process.Start(path, args.ToString());
  132. }
  133. }
  134. public class Openssh : FindInPathMixin, Handler
  135. {
  136. private Regex option = new Regex(@"^(?:/|--?)openssh(?:[:=](?<openssh_path>.*))?$", RegexOptions.IgnoreCase);
  137. private string path = null;
  138. public IList<string> Usages
  139. {
  140. get
  141. {
  142. return new string[]
  143. {
  144. "/openssh[:<openssh-path>] -- Use OpenSSH to connect",
  145. };
  146. }
  147. }
  148. public Option DoMatch(string arg)
  149. {
  150. Match match;
  151. if ((match = option.Match(arg)).Success)
  152. {
  153. Group group = match.Groups["openssh_path"];
  154. if (group.Success)
  155. path = group.Value;
  156. return Option.Set;
  157. }
  158. else
  159. return Option.None;
  160. }
  161. public bool Find()
  162. {
  163. if (path != null)
  164. goto Found;
  165. foreach (var hive in new RegistryHive[] { RegistryHive.CurrentUser, RegistryHive.LocalMachine })
  166. {
  167. var views = new List<RegistryView>(new RegistryView[] { RegistryView.Registry32 });
  168. if (Environment.Is64BitOperatingSystem)
  169. views.Insert(0, RegistryView.Registry64);
  170. foreach (RegistryView view in views)
  171. using (RegistryKey baseKey = RegistryKey.OpenBaseKey(hive, view), key = baseKey.OpenSubKey(@"SOFTWARE\Cygwin\setup"))
  172. if (key != null)
  173. {
  174. string location = (string)key.GetValue("rootdir");
  175. if (location == null)
  176. continue;
  177. path = Path.Combine(location, "bin", "ssh.exe");
  178. if (File.Exists(path))
  179. {
  180. Debug.WriteLine("Found OpenSSH in registry: {0}", path, null);
  181. goto Found;
  182. }
  183. else
  184. path = null;
  185. }
  186. }
  187. if ((path = FindInPath("ssh.exe")) != null)
  188. {
  189. Debug.WriteLine("Found OpenSSH in path: {0}", path, null);
  190. goto Found;
  191. }
  192. return false;
  193. Found:
  194. path = path.Trim();
  195. return true;
  196. }
  197. public void Execute(Uri uri, string user, string password)
  198. {
  199. if (!Find())
  200. throw new Exception("Could not find OpenSSH executable.");
  201. StringBuilder args = new StringBuilder();
  202. if (password != null)
  203. Debug.WriteLine("Warning: OpenSSH does not support passing a password!");
  204. if (uri.Port != -1)
  205. args.AppendFormat("-p {0} ", uri.Port);
  206. if (user != null)
  207. args.AppendFormat("{0}@", user);
  208. args.Append(uri.Host);
  209. Debug.WriteLine("Running OpenSSH command: {0} {1}", path, args);
  210. Process.Start(path, args.ToString());
  211. }
  212. }
  213. public class SshHandler
  214. {
  215. private static IList<Handler> handlers = new Handler[]
  216. {
  217. new Putty(),
  218. new Openssh(),
  219. };
  220. private static Handler handler = null;
  221. public static int Main(string[] args)
  222. {
  223. Application.EnableVisualStyles();
  224. try
  225. {
  226. Regex usage = new Regex(@"^(?:/|--?)(?:h|help|usage|\?)$", RegexOptions.IgnoreCase);
  227. IList<string> uriParts = null;
  228. foreach (string arg in args)
  229. if (uriParts == null)
  230. {
  231. if (usage.IsMatch(arg))
  232. return Usage(0);
  233. if (!MatchHandler(arg))
  234. uriParts = new List<string>(new string[] { arg });
  235. }
  236. else
  237. uriParts.Add(arg);
  238. if (uriParts != null)
  239. {
  240. Uri uri = new Uri(string.Join(" ", uriParts), UriKind.Absolute);
  241. string user, password;
  242. SetUserPassword(uri, out user, out password);
  243. if (handler == null)
  244. handler = FindHandler();
  245. handler.Execute(uri, user, password);
  246. }
  247. else
  248. return Usage(1);
  249. }
  250. catch (Exception exception)
  251. {
  252. MessageBox.Show(exception.Message, "SSH Handler Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
  253. return 2;
  254. }
  255. return 0;
  256. }
  257. private static int Usage(int code)
  258. {
  259. MessageBox.Show("ssh-handler [/putty[:<putty-path>]] <ssh-url>\n\n" +
  260. string.Join("\n", handlers.SelectMany(handler => handler.Usages)), "SSH Handler Usage", MessageBoxButtons.OK,
  261. MessageBoxIcon.Information);
  262. return code;
  263. }
  264. private static bool MatchHandler(string arg)
  265. {
  266. foreach (Handler handler in handlers)
  267. switch (handler.DoMatch(arg))
  268. {
  269. case Option.Set:
  270. Debug.WriteLine("Setting handler: {0}", handler, null);
  271. SshHandler.handler = handler;
  272. goto case Option.Optional;
  273. case Option.Optional:
  274. return true;
  275. }
  276. return false;
  277. }
  278. private static void SetUserPassword(Uri uri, out string user, out string password)
  279. {
  280. if (uri.UserInfo.Length != 0)
  281. {
  282. string[] userInfo = uri.UserInfo.Split(new char[] { ':' }, 2);
  283. user = userInfo[0];
  284. password = userInfo.Length == 2 ? userInfo[1] : null;
  285. }
  286. else
  287. {
  288. user = null;
  289. password = null;
  290. }
  291. }
  292. private static Handler FindHandler()
  293. {
  294. foreach (Handler handler in handlers)
  295. if (handler.Find())
  296. return handler;
  297. throw new Exception("Could not find a suitable SSH application.");
  298. }
  299. }