OpensshHandler.cs 9.6 KB

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