SshTests.cs 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972
  1. using System.ComponentModel;
  2. using System.Net;
  3. using System.Net.Sockets;
  4. using Renci.SshNet.Common;
  5. using Renci.SshNet.IntegrationTests.Common;
  6. namespace Renci.SshNet.IntegrationTests
  7. {
  8. [TestClass]
  9. public class SshTests : TestBase
  10. {
  11. private IConnectionInfoFactory _connectionInfoFactory;
  12. private IConnectionInfoFactory _adminConnectionInfoFactory;
  13. private RemoteSshdConfig _remoteSshdConfig;
  14. [TestInitialize]
  15. public void SetUp()
  16. {
  17. _connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort);
  18. _adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort);
  19. _remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig();
  20. _remoteSshdConfig.AllowTcpForwarding()
  21. .PrintMotd(false)
  22. .Update()
  23. .Restart();
  24. }
  25. [TestCleanup]
  26. public void TearDown()
  27. {
  28. _remoteSshdConfig?.Reset();
  29. }
  30. /// <summary>
  31. /// Test for a channel that is being closed by the server.
  32. /// </summary>
  33. [TestMethod]
  34. public void Ssh_ShellStream_Exit()
  35. {
  36. using (var client = new SshClient(_connectionInfoFactory.Create()))
  37. {
  38. client.Connect();
  39. var terminalModes = new Dictionary<TerminalModes, uint>
  40. {
  41. { TerminalModes.ECHO, 0 }
  42. };
  43. using (var shellStream = client.CreateShellStream("xterm", 80, 24, 800, 600, 1024, terminalModes))
  44. {
  45. shellStream.WriteLine("echo Hello!");
  46. shellStream.WriteLine("exit");
  47. Thread.Sleep(1000);
  48. try
  49. {
  50. shellStream.Write("ABC");
  51. Assert.Fail();
  52. }
  53. catch (ObjectDisposedException ex)
  54. {
  55. Assert.IsNull(ex.InnerException);
  56. Assert.AreEqual("ShellStream", ex.ObjectName);
  57. Assert.AreEqual($"Cannot access a disposed object.{Environment.NewLine}Object name: '{ex.ObjectName}'.", ex.Message);
  58. }
  59. var line = shellStream.ReadLine();
  60. Assert.IsNotNull(line);
  61. Assert.IsTrue(line.EndsWith("Hello!"), line);
  62. // TODO: ReadLine should return null when the buffer is empty and the channel has been closed (issue #672)
  63. try
  64. {
  65. line = shellStream.ReadLine();
  66. Assert.Fail(line);
  67. }
  68. catch (NullReferenceException)
  69. {
  70. }
  71. }
  72. }
  73. }
  74. /// <summary>
  75. /// https://github.com/sshnet/SSH.NET/issues/63
  76. /// </summary>
  77. [TestMethod]
  78. [Category("Reproduction Tests")]
  79. [Ignore]
  80. public void Ssh_ShellStream_IntermittendOutput()
  81. {
  82. const string remoteFile = "/home/sshnet/test.sh";
  83. var expectedResult = string.Join("\n",
  84. "Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  85. "Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  86. "Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  87. "Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  88. "Line 5 ",
  89. "Line 6");
  90. var scriptBuilder = new StringBuilder();
  91. scriptBuilder.Append("#!/bin/sh\n");
  92. scriptBuilder.Append("echo Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
  93. scriptBuilder.Append("sleep .5\n");
  94. scriptBuilder.Append("echo Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
  95. scriptBuilder.Append("echo Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
  96. scriptBuilder.Append("echo Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
  97. scriptBuilder.Append("sleep 2\n");
  98. scriptBuilder.Append("echo \"Line 5 \"\n");
  99. scriptBuilder.Append("echo Line 6 \n");
  100. scriptBuilder.Append("exit 13\n");
  101. using (var sshClient = new SshClient(_connectionInfoFactory.Create()))
  102. {
  103. sshClient.Connect();
  104. CreateShellScript(_connectionInfoFactory, remoteFile, scriptBuilder.ToString());
  105. try
  106. {
  107. var terminalModes = new Dictionary<TerminalModes, uint>
  108. {
  109. { TerminalModes.ECHO, 0 }
  110. };
  111. using (var shellStream = sshClient.CreateShellStream("xterm", 80, 24, 800, 600, 1024, terminalModes))
  112. {
  113. shellStream.WriteLine(remoteFile);
  114. Thread.Sleep(1200);
  115. using (var reader = new StreamReader(shellStream, new UTF8Encoding(false), false, 10))
  116. {
  117. var lines = new List<string>();
  118. string line = null;
  119. while ((line = reader.ReadLine()) != null)
  120. {
  121. lines.Add(line);
  122. }
  123. Assert.AreEqual(6, lines.Count, string.Join("\n", lines));
  124. Assert.AreEqual(expectedResult, string.Join("\n", lines));
  125. }
  126. }
  127. }
  128. finally
  129. {
  130. RemoveFileOrDirectory(sshClient, remoteFile);
  131. }
  132. }
  133. }
  134. /// <summary>
  135. /// Issue 1555
  136. /// </summary>
  137. [TestMethod]
  138. public void Ssh_CreateShell()
  139. {
  140. using (var client = new SshClient(_connectionInfoFactory.Create()))
  141. {
  142. client.Connect();
  143. using (var input = new MemoryStream())
  144. using (var output = new MemoryStream())
  145. using (var extOutput = new MemoryStream())
  146. {
  147. var shell = client.CreateShell(input, output, extOutput);
  148. shell.Start();
  149. var inputWriter = new StreamWriter(input, Encoding.ASCII, 1024);
  150. inputWriter.WriteLine("echo $PATH");
  151. var outputReader = new StreamReader(output, Encoding.ASCII, false, 1024);
  152. Console.WriteLine(outputReader.ReadToEnd());
  153. shell.Stop();
  154. }
  155. }
  156. }
  157. [TestMethod]
  158. public void Ssh_Command_IntermittendOutput_EndExecute()
  159. {
  160. const string remoteFile = "/home/sshnet/test.sh";
  161. var expectedResult = string.Join("\n",
  162. "Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  163. "Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  164. "Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  165. "Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  166. "Line 5 ",
  167. "Line 6",
  168. "");
  169. var scriptBuilder = new StringBuilder();
  170. scriptBuilder.Append("#!/bin/sh\n");
  171. scriptBuilder.Append("echo Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
  172. scriptBuilder.Append("sleep .5\n");
  173. scriptBuilder.Append("echo Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
  174. scriptBuilder.Append("echo Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
  175. scriptBuilder.Append("echo Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
  176. scriptBuilder.Append("sleep 2\n");
  177. scriptBuilder.Append("echo \"Line 5 \"\n");
  178. scriptBuilder.Append("echo Line 6 \n");
  179. scriptBuilder.Append("exit 13\n");
  180. using (var sshClient = new SshClient(_connectionInfoFactory.Create()))
  181. {
  182. sshClient.Connect();
  183. CreateShellScript(_connectionInfoFactory, remoteFile, scriptBuilder.ToString());
  184. try
  185. {
  186. using (var cmd = sshClient.CreateCommand("chmod 777 " + remoteFile))
  187. {
  188. cmd.Execute();
  189. Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
  190. }
  191. using (var command = sshClient.CreateCommand(remoteFile))
  192. {
  193. var asyncResult = command.BeginExecute();
  194. var actualResult = command.EndExecute(asyncResult);
  195. Assert.AreEqual(expectedResult, actualResult);
  196. Assert.AreEqual(expectedResult, command.Result);
  197. Assert.AreEqual(13, command.ExitStatus);
  198. }
  199. }
  200. finally
  201. {
  202. RemoveFileOrDirectory(sshClient, remoteFile);
  203. }
  204. }
  205. }
  206. /// <summary>
  207. /// Ignored for now, because:
  208. /// * OutputStream.Read(...) does not block when no data is available
  209. /// * SshCommand.(Begin)Execute consumes *OutputStream*, advancing its position.
  210. ///
  211. /// https://github.com/sshnet/SSH.NET/issues/650
  212. /// </summary>
  213. [TestMethod]
  214. [Ignore]
  215. public void Ssh_Command_IntermittendOutput_OutputStream()
  216. {
  217. const string remoteFile = "/home/sshnet/test.sh";
  218. var expectedResult = string.Join("\n",
  219. "Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  220. "Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  221. "Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  222. "Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  223. "Line 5 ",
  224. "Line 6");
  225. var scriptBuilder = new StringBuilder();
  226. scriptBuilder.Append("#!/bin/sh\n");
  227. scriptBuilder.Append("echo Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
  228. scriptBuilder.Append("sleep .5\n");
  229. scriptBuilder.Append("echo Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
  230. scriptBuilder.Append("echo Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
  231. scriptBuilder.Append("echo Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
  232. scriptBuilder.Append("sleep 2\n");
  233. scriptBuilder.Append("echo \"Line 5 \"\n");
  234. scriptBuilder.Append("echo Line 6 \n");
  235. scriptBuilder.Append("exit 13\n");
  236. using (var sshClient = new SshClient(_connectionInfoFactory.Create()))
  237. {
  238. sshClient.Connect();
  239. CreateShellScript(_connectionInfoFactory, remoteFile, scriptBuilder.ToString());
  240. try
  241. {
  242. using (var cmd = sshClient.CreateCommand("chmod 777 " + remoteFile))
  243. {
  244. cmd.Execute();
  245. Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
  246. }
  247. using (var command = sshClient.CreateCommand(remoteFile))
  248. {
  249. var asyncResult = command.BeginExecute();
  250. using (var reader = new StreamReader(command.OutputStream, new UTF8Encoding(false), false, 10))
  251. {
  252. var lines = new List<string>();
  253. string line = null;
  254. while ((line = reader.ReadLine()) != null)
  255. {
  256. lines.Add(line);
  257. }
  258. Assert.AreEqual(6, lines.Count, string.Join("\n", lines));
  259. Assert.AreEqual(expectedResult, string.Join("\n", lines));
  260. Assert.AreEqual(13, command.ExitStatus);
  261. }
  262. var actualResult = command.EndExecute(asyncResult);
  263. Assert.AreEqual(expectedResult, actualResult);
  264. Assert.AreEqual(expectedResult, command.Result);
  265. }
  266. }
  267. finally
  268. {
  269. RemoveFileOrDirectory(sshClient, remoteFile);
  270. }
  271. }
  272. }
  273. [TestMethod]
  274. public void Ssh_DynamicPortForwarding_DisposeSshClientWithoutStoppingPort()
  275. {
  276. const string searchText = "HTTP/1.1 301 Moved Permanently";
  277. const string hostName = "github.com";
  278. var httpGetRequest = Encoding.ASCII.GetBytes($"GET / HTTP/1.1\r\nHost: {hostName}\r\n\r\n");
  279. Socket socksSocket;
  280. using (var client = new SshClient(_connectionInfoFactory.Create()))
  281. {
  282. client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(200);
  283. client.Connect();
  284. var forwardedPort = new ForwardedPortDynamic(1080);
  285. forwardedPort.Exception += (sender, args) => Console.WriteLine(args.Exception.ToString());
  286. client.AddForwardedPort(forwardedPort);
  287. forwardedPort.Start();
  288. var socksClient = new Socks5Handler(new IPEndPoint(IPAddress.Loopback, 1080),
  289. string.Empty,
  290. string.Empty);
  291. socksSocket = socksClient.Connect(hostName, 80);
  292. socksSocket.Send(httpGetRequest);
  293. var httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII);
  294. Assert.IsTrue(httpResponse.Contains(searchText), httpResponse);
  295. }
  296. Assert.IsTrue(socksSocket.Connected);
  297. // check if client socket was properly closed
  298. Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None));
  299. }
  300. [TestMethod]
  301. public void Ssh_DynamicPortForwarding_DomainName()
  302. {
  303. const string searchText = "HTTP/1.1 301 Moved Permanently";
  304. const string hostName = "github.com";
  305. // Set-up a host alias for google.be on the remote server that is not known locally; this allows us to
  306. // verify whether the host name is resolved remotely.
  307. const string hostNameAlias = "dynamicportforwarding-test.for.sshnet";
  308. // Construct a HTTP request for which we expected the response to contain the search text.
  309. var httpGetRequest = Encoding.ASCII.GetBytes($"GET / HTTP/1.1\r\nHost: {hostName}\r\n\r\n");
  310. var ipAddresses = Dns.GetHostAddresses(hostName);
  311. var hostsFileUpdated = AddOrUpdateHostsEntry(_adminConnectionInfoFactory, ipAddresses[0], hostNameAlias);
  312. try
  313. {
  314. using (var client = new SshClient(_connectionInfoFactory.Create()))
  315. {
  316. client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(200);
  317. client.Connect();
  318. var forwardedPort = new ForwardedPortDynamic(1080);
  319. forwardedPort.Exception += (sender, args) => Console.WriteLine(args.Exception.ToString());
  320. client.AddForwardedPort(forwardedPort);
  321. forwardedPort.Start();
  322. var socksClient = new Socks5Handler(new IPEndPoint(IPAddress.Loopback, 1080),
  323. string.Empty,
  324. string.Empty);
  325. var socksSocket = socksClient.Connect(hostNameAlias, 80);
  326. socksSocket.Send(httpGetRequest);
  327. var httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII);
  328. Assert.IsTrue(httpResponse.Contains(searchText), httpResponse);
  329. // Verify if port is still open
  330. socksSocket.Send(httpGetRequest);
  331. GetHttpResponse(socksSocket, Encoding.ASCII);
  332. forwardedPort.Stop();
  333. Assert.IsTrue(socksSocket.Connected);
  334. // check if client socket was properly closed
  335. Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None));
  336. forwardedPort.Start();
  337. // create new SOCKS connection and very whether the forwarded port is functional again
  338. socksSocket = socksClient.Connect(hostNameAlias, 80);
  339. socksSocket.Send(httpGetRequest);
  340. httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII);
  341. Assert.IsTrue(httpResponse.Contains(searchText), httpResponse);
  342. forwardedPort.Dispose();
  343. Assert.IsTrue(socksSocket.Connected);
  344. // check if client socket was properly closed
  345. Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None));
  346. forwardedPort.Dispose();
  347. }
  348. }
  349. finally
  350. {
  351. if (hostsFileUpdated)
  352. {
  353. RemoveHostsEntry(_adminConnectionInfoFactory, ipAddresses[0], hostNameAlias);
  354. }
  355. }
  356. }
  357. [TestMethod]
  358. public void Ssh_DynamicPortForwarding_IPv4()
  359. {
  360. const string searchText = "HTTP/1.1 301 Moved Permanently";
  361. const string hostName = "github.com";
  362. var httpGetRequest = Encoding.ASCII.GetBytes($"GET /null HTTP/1.1\r\nHost: {hostName}\r\n\r\n");
  363. var ipv4 = Dns.GetHostAddresses(hostName).FirstOrDefault(p => p.AddressFamily == AddressFamily.InterNetwork);
  364. Assert.IsNotNull(ipv4, $@"No IPv4 address found for '{hostName}'.");
  365. using (var client = new SshClient(_connectionInfoFactory.Create()))
  366. {
  367. client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(200);
  368. client.Connect();
  369. var forwardedPort = new ForwardedPortDynamic(1080);
  370. forwardedPort.Exception += (sender, args) => Console.WriteLine(args.Exception.ToString());
  371. client.AddForwardedPort(forwardedPort);
  372. forwardedPort.Start();
  373. var socksClient = new Socks5Handler(new IPEndPoint(IPAddress.Loopback, 1080),
  374. string.Empty,
  375. string.Empty);
  376. var socksSocket = socksClient.Connect(new IPEndPoint(ipv4, 80));
  377. socksSocket.Send(httpGetRequest);
  378. var httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII);
  379. Assert.IsTrue(httpResponse.Contains(searchText), httpResponse);
  380. forwardedPort.Dispose();
  381. // check if client socket was properly closed
  382. Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None));
  383. }
  384. }
  385. /// <summary>
  386. /// Verifies whether channels are effectively closed.
  387. /// </summary>
  388. [TestMethod]
  389. public void Ssh_LocalPortForwardingCloseChannels()
  390. {
  391. const string hostNameAlias = "localportforwarding-test.for.sshnet";
  392. const string hostName = "github.com";
  393. var ipAddress = Dns.GetHostAddresses(hostName)[0];
  394. var hostsFileUpdated = AddOrUpdateHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias);
  395. try
  396. {
  397. var connectionInfo = _connectionInfoFactory.Create();
  398. connectionInfo.MaxSessions = 1;
  399. using (var client = new SshClient(connectionInfo))
  400. {
  401. client.Connect();
  402. var localEndPoint = new IPEndPoint(IPAddress.Loopback, 1225);
  403. for (var i = 0; i < (connectionInfo.MaxSessions + 1); i++)
  404. {
  405. var forwardedPort = new ForwardedPortLocal(localEndPoint.Address.ToString(),
  406. (uint)localEndPoint.Port,
  407. hostNameAlias,
  408. 80);
  409. client.AddForwardedPort(forwardedPort);
  410. forwardedPort.Start();
  411. try
  412. {
  413. var httpRequest = (HttpWebRequest) WebRequest.Create("http://" + localEndPoint);
  414. httpRequest.Host = hostName;
  415. httpRequest.Method = "GET";
  416. httpRequest.AllowAutoRedirect = false;
  417. try
  418. {
  419. using (var httpResponse = (HttpWebResponse)httpRequest.GetResponse())
  420. {
  421. Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode);
  422. }
  423. }
  424. catch (WebException ex)
  425. {
  426. Assert.AreEqual(WebExceptionStatus.ProtocolError, ex.Status);
  427. Assert.IsNotNull(ex.Response);
  428. using (var httpResponse = ex.Response as HttpWebResponse)
  429. {
  430. Assert.IsNotNull(httpResponse);
  431. Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode);
  432. }
  433. }
  434. }
  435. finally
  436. {
  437. client.RemoveForwardedPort(forwardedPort);
  438. }
  439. }
  440. }
  441. }
  442. finally
  443. {
  444. if (hostsFileUpdated)
  445. {
  446. RemoveHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias);
  447. }
  448. }
  449. }
  450. [TestMethod]
  451. public void Ssh_LocalPortForwarding()
  452. {
  453. const string hostNameAlias = "localportforwarding-test.for.sshnet";
  454. const string hostName = "github.com";
  455. var ipAddress = Dns.GetHostAddresses(hostName)[0];
  456. var hostsFileUpdated = AddOrUpdateHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias);
  457. try
  458. {
  459. using (var client = new SshClient(_connectionInfoFactory.Create()))
  460. {
  461. client.Connect();
  462. var localEndPoint = new IPEndPoint(IPAddress.Loopback, 1225);
  463. var forwardedPort = new ForwardedPortLocal(localEndPoint.Address.ToString(),
  464. (uint)localEndPoint.Port,
  465. hostNameAlias,
  466. 80);
  467. forwardedPort.Exception +=
  468. (sender, args) => Console.WriteLine(@"ForwardedPort exception: " + args.Exception);
  469. client.AddForwardedPort(forwardedPort);
  470. forwardedPort.Start();
  471. try
  472. {
  473. var httpRequest = (HttpWebRequest) WebRequest.Create("http://" + localEndPoint);
  474. httpRequest.Host = hostName;
  475. httpRequest.Method = "GET";
  476. httpRequest.Accept = "text/html";
  477. httpRequest.AllowAutoRedirect = false;
  478. try
  479. {
  480. using (var httpResponse = (HttpWebResponse)httpRequest.GetResponse())
  481. {
  482. Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode);
  483. }
  484. }
  485. catch (WebException ex)
  486. {
  487. Assert.AreEqual(WebExceptionStatus.ProtocolError, ex.Status);
  488. Assert.IsNotNull(ex.Response);
  489. using (var httpResponse = ex.Response as HttpWebResponse)
  490. {
  491. Assert.IsNotNull(httpResponse);
  492. Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode);
  493. }
  494. }
  495. }
  496. finally
  497. {
  498. client.RemoveForwardedPort(forwardedPort);
  499. }
  500. }
  501. }
  502. finally
  503. {
  504. if (hostsFileUpdated)
  505. {
  506. RemoveHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias);
  507. }
  508. }
  509. }
  510. [TestMethod]
  511. public void Ssh_RemotePortForwarding()
  512. {
  513. var hostAddresses = Dns.GetHostAddresses(Dns.GetHostName());
  514. var ipv4HostAddress = hostAddresses.First(p => p.AddressFamily == AddressFamily.InterNetwork);
  515. var endpoint1 = new IPEndPoint(ipv4HostAddress, 666);
  516. var endpoint2 = new IPEndPoint(ipv4HostAddress, 667);
  517. var bytesReceivedOnListener1 = new List<byte>();
  518. var bytesReceivedOnListener2 = new List<byte>();
  519. using (var socketListener1 = new AsyncSocketListener(endpoint1))
  520. using (var socketListener2 = new AsyncSocketListener(endpoint2))
  521. using (var client = new SshClient(_connectionInfoFactory.Create()))
  522. {
  523. socketListener1.BytesReceived += (received, socket) => bytesReceivedOnListener1.AddRange(received);
  524. socketListener1.Start();
  525. socketListener2.BytesReceived += (received, socket) => bytesReceivedOnListener2.AddRange(received);
  526. socketListener2.Start();
  527. client.Connect();
  528. var forwardedPort1 = new ForwardedPortRemote(IPAddress.Loopback,
  529. 10000,
  530. endpoint1.Address,
  531. (uint)endpoint1.Port);
  532. forwardedPort1.Exception += (sender, args) => Console.WriteLine(@"forwardedPort1 exception: " + args.Exception);
  533. client.AddForwardedPort(forwardedPort1);
  534. forwardedPort1.Start();
  535. var forwardedPort2 = new ForwardedPortRemote(IPAddress.Loopback,
  536. 10001,
  537. endpoint2.Address,
  538. (uint)endpoint2.Port);
  539. forwardedPort2.Exception += (sender, args) => Console.WriteLine(@"forwardedPort2 exception: " + args.Exception);
  540. client.AddForwardedPort(forwardedPort2);
  541. forwardedPort2.Start();
  542. using (var s = client.CreateShellStream("a", 80, 25, 800, 600, 200))
  543. {
  544. s.WriteLine($"telnet {forwardedPort1.BoundHost} {forwardedPort1.BoundPort}");
  545. s.Expect($"Connected to {forwardedPort1.BoundHost}\r\n");
  546. s.WriteLine("ABC");
  547. s.Flush();
  548. s.Expect("ABC");
  549. s.Close();
  550. }
  551. using (var s = client.CreateShellStream("b", 80, 25, 800, 600, 200))
  552. {
  553. s.WriteLine($"telnet {forwardedPort2.BoundHost} {forwardedPort2.BoundPort}");
  554. s.Expect($"Connected to {forwardedPort2.BoundHost}\r\n");
  555. s.WriteLine("DEF");
  556. s.Flush();
  557. s.Expect("DEF");
  558. s.Close();
  559. }
  560. forwardedPort1.Stop();
  561. forwardedPort2.Stop();
  562. }
  563. var textReceivedOnListener1 = Encoding.ASCII.GetString(bytesReceivedOnListener1.ToArray());
  564. Assert.AreEqual("ABC\r\n", textReceivedOnListener1);
  565. var textReceivedOnListener2 = Encoding.ASCII.GetString(bytesReceivedOnListener2.ToArray());
  566. Assert.AreEqual("DEF\r\n", textReceivedOnListener2);
  567. }
  568. /// <summary>
  569. /// Issue 1591
  570. /// </summary>
  571. [TestMethod]
  572. public void Ssh_ExecuteShellScript()
  573. {
  574. const string remoteFile = "/home/sshnet/run.sh";
  575. const string content = "#\bin\bash\necho Hello World!";
  576. using (var client = new SftpClient(_connectionInfoFactory.Create()))
  577. {
  578. client.Connect();
  579. if (client.Exists(remoteFile))
  580. {
  581. client.DeleteFile(remoteFile);
  582. }
  583. using (var memoryStream = new MemoryStream())
  584. using (var sw = new StreamWriter(memoryStream, Encoding.ASCII))
  585. {
  586. sw.Write(content);
  587. sw.Flush();
  588. memoryStream.Position = 0;
  589. client.UploadFile(memoryStream, remoteFile);
  590. }
  591. }
  592. using (var client = new SshClient(_connectionInfoFactory.Create()))
  593. {
  594. client.Connect();
  595. try
  596. {
  597. var runChmod = client.RunCommand("chmod u+x " + remoteFile);
  598. runChmod.Execute();
  599. Assert.AreEqual(0, runChmod.ExitStatus, runChmod.Error);
  600. var runLs = client.RunCommand("ls " + remoteFile);
  601. var asyncResultLs = runLs.BeginExecute();
  602. var runScript = client.RunCommand(remoteFile);
  603. var asyncResultScript = runScript.BeginExecute();
  604. Assert.IsTrue(asyncResultScript.AsyncWaitHandle.WaitOne(10000));
  605. var resultScript = runScript.EndExecute(asyncResultScript);
  606. Assert.AreEqual("Hello World!\n", resultScript);
  607. Assert.IsTrue(asyncResultLs.AsyncWaitHandle.WaitOne(10000));
  608. var resultLs = runLs.EndExecute(asyncResultLs);
  609. Assert.AreEqual(remoteFile + "\n", resultLs);
  610. }
  611. finally
  612. {
  613. RemoveFileOrDirectory(client, remoteFile);
  614. }
  615. }
  616. }
  617. /// <summary>
  618. /// Verifies if a hosts file contains an entry for a given combination of IP address and hostname,
  619. /// and if necessary add either the host entry or an alias to an exist entry for the specified IP
  620. /// address.
  621. /// </summary>
  622. /// <param name="linuxAdminConnectionFactory"></param>
  623. /// <param name="ipAddress"></param>
  624. /// <param name="hostName"></param>
  625. /// <returns>
  626. /// <see langword="true"/> if an entry was added or updated in the specified hosts file; otherwise,
  627. /// <see langword="false"/>.
  628. /// </returns>
  629. private static bool AddOrUpdateHostsEntry(IConnectionInfoFactory linuxAdminConnectionFactory,
  630. IPAddress ipAddress,
  631. string hostName)
  632. {
  633. const string hostsFile = "/etc/hosts";
  634. using (var client = new ScpClient(linuxAdminConnectionFactory.Create()))
  635. {
  636. client.Connect();
  637. var hostConfig = HostConfig.Read(client, hostsFile);
  638. var hostEntry = hostConfig.Entries.SingleOrDefault(h => h.IPAddress.Equals(ipAddress));
  639. if (hostEntry != null)
  640. {
  641. if (hostEntry.HostName == hostName)
  642. {
  643. return false;
  644. }
  645. foreach (var alias in hostEntry.Aliases)
  646. {
  647. if (alias == hostName)
  648. {
  649. return false;
  650. }
  651. }
  652. hostEntry.Aliases.Add(hostName);
  653. }
  654. else
  655. {
  656. bool mappingFound = false;
  657. for (var i = (hostConfig.Entries.Count - 1); i >= 0; i--)
  658. {
  659. hostEntry = hostConfig.Entries[i];
  660. if (hostEntry.HostName == hostName)
  661. {
  662. if (hostEntry.IPAddress.Equals(ipAddress))
  663. {
  664. mappingFound = true;
  665. continue;
  666. }
  667. // If hostname is currently mapped to another IP address, then remove the
  668. // current mapping
  669. hostConfig.Entries.RemoveAt(i);
  670. }
  671. else
  672. {
  673. for (var j = (hostEntry.Aliases.Count - 1); j >= 0; j--)
  674. {
  675. var alias = hostEntry.Aliases[j];
  676. if (alias == hostName)
  677. {
  678. hostEntry.Aliases.RemoveAt(j);
  679. }
  680. }
  681. }
  682. }
  683. if (!mappingFound)
  684. {
  685. hostEntry = new HostEntry(ipAddress, hostName);
  686. hostConfig.Entries.Add(hostEntry);
  687. }
  688. }
  689. hostConfig.Write(client, hostsFile);
  690. return true;
  691. }
  692. }
  693. /// <summary>
  694. /// Remove the mapping between a given IP address and host name from the remote hosts file either by
  695. /// removing a host entry entirely (if no other aliases are defined for the IP address) or removing
  696. /// the aliases that match the host name for the IP address.
  697. /// </summary>
  698. /// <param name="linuxAdminConnectionFactory"></param>
  699. /// <param name="ipAddress"></param>
  700. /// <param name="hostName"></param>
  701. /// <returns>
  702. /// <see langword="true"/> if the hosts file was updated; otherwise, <see langword="false"/>.
  703. /// </returns>
  704. private static bool RemoveHostsEntry(IConnectionInfoFactory linuxAdminConnectionFactory,
  705. IPAddress ipAddress,
  706. string hostName)
  707. {
  708. const string hostsFile = "/etc/hosts";
  709. using (var client = new ScpClient(linuxAdminConnectionFactory.Create()))
  710. {
  711. client.Connect();
  712. var hostConfig = HostConfig.Read(client, hostsFile);
  713. var hostEntry = hostConfig.Entries.SingleOrDefault(h => h.IPAddress.Equals(ipAddress));
  714. if (hostEntry == null)
  715. {
  716. return false;
  717. }
  718. if (hostEntry.HostName == hostName)
  719. {
  720. if (hostEntry.Aliases.Count == 0)
  721. {
  722. hostConfig.Entries.Remove(hostEntry);
  723. }
  724. else
  725. {
  726. // Use one of the aliases (that are different from the specified host name) as host name
  727. // of the host entry.
  728. for (var i = hostEntry.Aliases.Count - 1; i >= 0; i--)
  729. {
  730. var alias = hostEntry.Aliases[i];
  731. if (alias == hostName)
  732. {
  733. hostEntry.Aliases.RemoveAt(i);
  734. }
  735. else if (hostEntry.HostName == hostName)
  736. {
  737. // If we haven't already used one of the aliases as host name of the host entry
  738. // then do this now and remove the alias.
  739. hostEntry.HostName = alias;
  740. hostEntry.Aliases.RemoveAt(i);
  741. }
  742. }
  743. // If for some reason the host name of the host entry matched the specified host name
  744. // and it only had aliases that match the host name, then remove the host entry altogether.
  745. if (hostEntry.Aliases.Count == 0 && hostEntry.HostName == hostName)
  746. {
  747. hostConfig.Entries.Remove(hostEntry);
  748. }
  749. }
  750. }
  751. else
  752. {
  753. var aliasRemoved = false;
  754. for (var i = hostEntry.Aliases.Count - 1; i >= 0; i--)
  755. {
  756. if (hostEntry.Aliases[i] == hostName)
  757. {
  758. hostEntry.Aliases.RemoveAt(i);
  759. aliasRemoved = true;
  760. }
  761. }
  762. if (!aliasRemoved)
  763. {
  764. return false;
  765. }
  766. }
  767. hostConfig.Write(client, hostsFile);
  768. return true;
  769. }
  770. }
  771. private static string GetHttpResponse(Socket socket, Encoding encoding)
  772. {
  773. var httpResponseBuffer = new byte[2048];
  774. // We expect:
  775. // * The response to contain the searchText in the first receive.
  776. // * The full response to be returned in the first receive.
  777. var bytesReceived = socket.Receive(httpResponseBuffer,
  778. 0,
  779. httpResponseBuffer.Length,
  780. SocketFlags.None);
  781. if (bytesReceived == 0)
  782. {
  783. return null;
  784. }
  785. if (bytesReceived == httpResponseBuffer.Length)
  786. {
  787. throw new Exception("We expect the HTTP response to be less than the buffer size. If not, we won't consume the full response.");
  788. }
  789. using (var sr = new StringReader(encoding.GetString(httpResponseBuffer, 0, bytesReceived)))
  790. {
  791. return sr.ReadToEnd();
  792. }
  793. }
  794. private static void CreateShellScript(IConnectionInfoFactory connectionInfoFactory, string remoteFile, string script)
  795. {
  796. using (var sftpClient = new SftpClient(connectionInfoFactory.Create()))
  797. {
  798. sftpClient.Connect();
  799. using (var sw = sftpClient.CreateText(remoteFile, new UTF8Encoding(false)))
  800. {
  801. sw.Write(script);
  802. }
  803. sftpClient.ChangePermissions(remoteFile, 0x1FF);
  804. }
  805. }
  806. private static void RemoveFileOrDirectory(SshClient client, string remoteFile)
  807. {
  808. using (var cmd = client.CreateCommand("rm -Rf " + remoteFile))
  809. {
  810. cmd.Execute();
  811. Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
  812. }
  813. }
  814. }
  815. }