OpensshHandler.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. // OpenSSH Handler
  2. //
  3. // Douglas Thrift
  4. //
  5. // OpensshHandler.cs
  6. /* Copyright 2014 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 Microsoft.Win32;
  28. public class OpensshHandler : AbstractHandler, Handler
  29. {
  30. private Regex regex = new Regex(@"^(?:/|--?)openssh(?:[:=](?<openssh_path>.*))?$", RegexOptions.IgnoreCase);
  31. private Regex cygwinRegex = new Regex(@"^(?:/|--?)cygwin(?:[:=](?<cygwin_path>.*))?$", RegexOptions.IgnoreCase);
  32. private Regex minttyRegex = new Regex(@"^(?:/|--?)mintty(?:[:=](?<mintty_path>.*))?$", RegexOptions.IgnoreCase);
  33. private Regex bashRegex = new Regex(@"^(?:/|--?)bash(?:[:=](?<bash_path>.*))?$", RegexOptions.IgnoreCase);
  34. private AutoYesNoOption cygwin = AutoYesNoOption.Auto;
  35. private AutoYesNoOption mintty = AutoYesNoOption.Auto;
  36. private bool bash = false;
  37. private string path = null;
  38. string cygwinPath = null;
  39. string minttyPath = null;
  40. string bashPath = null;
  41. public IList<string> Options
  42. {
  43. get
  44. {
  45. return new string[]
  46. {
  47. "/openssh[:<openssh-path>]",
  48. "/cygwin[:(yes|no|<cygwin-path>)]",
  49. "/mintty[:(yes|no|<mintty-path>)]",
  50. "/bash[:(yes|no|<bash-path>)]",
  51. };
  52. }
  53. }
  54. public IList<string> Usages
  55. {
  56. get
  57. {
  58. return new string[]
  59. {
  60. "Use OpenSSH to connect",
  61. "Use Cygwin for OpenSSH (by default, Cygwin will be used for OpenSSH if detected)",
  62. "Use MinTTY for OpenSSH (by default, MinTTY will be used for OpenSSH if detected)",
  63. "Use Bash login shell for use with ssh-agent",
  64. };
  65. }
  66. }
  67. public MatchOption DoMatch(string arg)
  68. {
  69. Match match;
  70. if ((match = regex.Match(arg)).Success)
  71. return SetValue(match, "openssh_path", ref path);
  72. else if ((match = cygwinRegex.Match(arg)).Success)
  73. return SetYesNoValue(match, "cygwin_path", out cygwin, ref cygwinPath);
  74. else if ((match = minttyRegex.Match(arg)).Success)
  75. return SetYesNoValue(match, "mintty_path", out mintty, ref minttyPath);
  76. else if ((match = bashRegex.Match(arg)).Success)
  77. return SetBooleanValue(match, "bash_path", out bash, ref bashPath);
  78. else
  79. return MatchOption.None;
  80. }
  81. public bool Find()
  82. {
  83. if (path != null)
  84. goto Found;
  85. switch (cygwin)
  86. {
  87. case AutoYesNoOption.Auto:
  88. case AutoYesNoOption.Yes:
  89. if (FindCygwin())
  90. goto Found;
  91. break;
  92. }
  93. if ((path = FindInPath("ssh.exe")) != null)
  94. {
  95. Debug.WriteLine("Found OpenSSH in path: {0}", path, null);
  96. goto Found;
  97. }
  98. return false;
  99. Found:
  100. path = path.Trim();
  101. switch (mintty)
  102. {
  103. case AutoYesNoOption.Auto:
  104. case AutoYesNoOption.Yes:
  105. FindMintty();
  106. break;
  107. }
  108. if (bash)
  109. FindBash();
  110. return true;
  111. }
  112. public void Execute(Uri uri, string user, string password)
  113. {
  114. if (!Find())
  115. throw new Exception("Could not find OpenSSH executable.");
  116. if (cygwinPath != null && bash)
  117. {
  118. ProcessStartInfo info = new ProcessStartInfo(Path.Combine(cygwinPath, "bin", "cygpath.exe"), CygwinQuote(path));
  119. info.CreateNoWindow = true;
  120. info.RedirectStandardOutput = true;
  121. info.RedirectStandardError = true;
  122. info.UseShellExecute = false;
  123. Process process = Process.Start(info);
  124. string error = process.StandardError.ReadToEnd().Trim();
  125. path = process.StandardOutput.ReadToEnd().Trim();
  126. process.WaitForExit();
  127. if (process.ExitCode != 0)
  128. throw new Exception(error);
  129. }
  130. var command = new List<string>(new string[] { path });
  131. if (password != null)
  132. Debug.WriteLine("Warning: OpenSSH does not support passing a password.");
  133. if (uri.Port != -1)
  134. AddArguments(command, "-p", uri.Port);
  135. AddArguments(command, user != null ? string.Format("{0}@{1}", user, uri.Host) : uri.Host);
  136. if (bash)
  137. {
  138. command = new List<string>(new string[] { bashPath, "-lc", CygwinCommand(command) });
  139. }
  140. if (minttyPath != null)
  141. {
  142. var minttyCommand = new List<string>(new string[] { minttyPath });
  143. if (cygwinPath != null)
  144. {
  145. string icon = Path.Combine(cygwinPath, "Cygwin-Terminal.ico");
  146. if (File.Exists(icon))
  147. AddArguments(minttyCommand, "-i", icon);
  148. }
  149. AddArguments(minttyCommand, "-e");
  150. command.InsertRange(0, minttyCommand);
  151. }
  152. string arguments = CygwinCommand(command.Skip(1));
  153. Debug.WriteLine("Running OpenSSH command: {0} {1}", command.First(), arguments);
  154. Process.Start(command.First(), arguments);
  155. }
  156. private bool FindCygwin()
  157. {
  158. if (cygwinPath != null)
  159. goto Found;
  160. foreach (var hive in new RegistryHive[] { RegistryHive.CurrentUser, RegistryHive.LocalMachine })
  161. {
  162. var views = new List<RegistryView>(new RegistryView[] { RegistryView.Registry32 });
  163. if (Environment.Is64BitOperatingSystem)
  164. views.Insert(0, RegistryView.Registry64);
  165. foreach (RegistryView view in views)
  166. using (RegistryKey baseKey = RegistryKey.OpenBaseKey(hive, view), key = baseKey.OpenSubKey(@"SOFTWARE\Cygwin\setup"))
  167. if (key != null)
  168. {
  169. cygwinPath = (string)key.GetValue("rootdir");
  170. if (cygwinPath == null)
  171. continue;
  172. if (Directory.Exists(cygwinPath))
  173. {
  174. Debug.WriteLine("Found Cygwin in registry: {0}", cygwinPath, null);
  175. goto Found;
  176. }
  177. else
  178. cygwinPath = null;
  179. }
  180. }
  181. if (cygwin == AutoYesNoOption.Yes)
  182. throw new Exception("Could not find Cygwin in registry.");
  183. return false;
  184. Found:
  185. cygwinPath = cygwinPath.Trim();
  186. path = Path.Combine(cygwinPath, "bin", "ssh.exe");
  187. if (File.Exists(path))
  188. {
  189. Debug.WriteLine("Found OpenSSH in Cygwin directory: {0}", path, null);
  190. return true;
  191. }
  192. else if (cygwin == AutoYesNoOption.Yes)
  193. throw new Exception("Could not find OpenSSH in Cygwin directory.");
  194. else
  195. {
  196. path = null;
  197. return false;
  198. }
  199. }
  200. private void FindMintty()
  201. {
  202. if (minttyPath != null)
  203. goto Found;
  204. if (cygwinPath != null)
  205. {
  206. minttyPath = Path.Combine(cygwinPath, "bin", "mintty.exe");
  207. if (File.Exists(minttyPath))
  208. {
  209. Debug.WriteLine("Found MinTTY in Cygwin directory: {0}", minttyPath, null);
  210. goto Found;
  211. }
  212. else
  213. minttyPath = null;
  214. }
  215. if ((minttyPath = FindInPath("mintty.exe")) != null)
  216. {
  217. Debug.WriteLine("Found MinTTY in path: {0}", minttyPath, null);
  218. goto Found;
  219. }
  220. if (mintty == AutoYesNoOption.Yes)
  221. throw new Exception("Could not find MinTTY executable.");
  222. return;
  223. Found:
  224. minttyPath = minttyPath.Trim();
  225. }
  226. private void FindBash()
  227. {
  228. if (bashPath != null)
  229. goto Found;
  230. if (cygwinPath != null)
  231. {
  232. bashPath = Path.Combine(cygwinPath, "bin", "bash.exe");
  233. if (File.Exists(bashPath))
  234. {
  235. Debug.WriteLine("Found Bash in Cygwin directory: {0}", bashPath, null);
  236. goto Found;
  237. }
  238. else
  239. bashPath = null;
  240. }
  241. if ((bashPath = FindInPath("bash.exe")) != null)
  242. {
  243. Debug.WriteLine("Found Bash in path: {0}", bashPath, null);
  244. goto Found;
  245. }
  246. throw new Exception("Could not find Bash executable.");
  247. Found:
  248. bashPath = bashPath.Trim();
  249. }
  250. private string CygwinQuote(string value)
  251. {
  252. return string.Format("'{0}'", value.Replace("\"", "\\\"").Replace('\'', '"'));
  253. }
  254. private string CygwinAutoQuote(string value)
  255. {
  256. if (Regex.IsMatch(value, "[ '\"]"))
  257. return CygwinQuote(value);
  258. else
  259. return value;
  260. }
  261. private string CygwinCommand(IEnumerable<string> command)
  262. {
  263. return string.Join(" ", command.Select(item => CygwinAutoQuote(item)));
  264. }
  265. }