ConnectionService.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. using FxSsh.Messages;
  2. using FxSsh.Messages.Connection;
  3. using System;
  4. using System.Collections.Concurrent;
  5. using System.Collections.Generic;
  6. using System.Diagnostics.Contracts;
  7. using System.Linq;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. namespace FxSsh.Services
  11. {
  12. public class ConnectionService : SshService, IDynamicInvoker
  13. {
  14. private readonly object _locker = new object();
  15. private readonly List<Channel> _channels = new List<Channel>();
  16. private readonly UserauthArgs _auth = null;
  17. private readonly BlockingCollection<ConnectionServiceMessage> _messageQueue =
  18. new BlockingCollection<ConnectionServiceMessage>(new ConcurrentQueue<ConnectionServiceMessage>());
  19. private readonly CancellationTokenSource _messageCts = new CancellationTokenSource();
  20. private int _serverChannelCounter = -1;
  21. public ConnectionService(Session session, UserauthArgs auth)
  22. : base(session)
  23. {
  24. Contract.Requires(auth != null);
  25. _auth = auth;
  26. Task.Run(MessageLoop);
  27. }
  28. public event EventHandler<CommandRequestedArgs> CommandOpened;
  29. public event EventHandler<EnvironmentArgs> EnvReceived;
  30. public event EventHandler<PtyArgs> PtyReceived;
  31. public event EventHandler<TcpRequestArgs> TcpForwardRequest;
  32. public event EventHandler<WindowChangeArgs> WindowChange;
  33. protected internal override void CloseService()
  34. {
  35. _messageCts.Cancel();
  36. lock (_locker)
  37. {
  38. foreach (var channel in _channels.ToArray())
  39. {
  40. channel.ForceClose();
  41. }
  42. }
  43. }
  44. internal void HandleMessageCore(ConnectionServiceMessage message)
  45. {
  46. Contract.Requires(message != null);
  47. if (message is ChannelWindowAdjustMessage)
  48. this.InvokeHandleMessage(message);
  49. else
  50. _messageQueue.Add(message);
  51. }
  52. private void MessageLoop()
  53. {
  54. try
  55. {
  56. while (true)
  57. {
  58. var message = _messageQueue.Take(_messageCts.Token);
  59. this.InvokeHandleMessage(message);
  60. }
  61. }
  62. catch (OperationCanceledException)
  63. {
  64. }
  65. }
  66. private void HandleMessage(ChannelOpenMessage message)
  67. {
  68. switch (message.ChannelType)
  69. {
  70. case "session":
  71. var msg = Message.LoadFrom<SessionOpenMessage>(message);
  72. HandleMessage(msg);
  73. break;
  74. case "direct-tcpip":
  75. var tcpMsg = Message.LoadFrom<DirectTcpIpMessage>(message);
  76. HandleMessage(tcpMsg);
  77. break;
  78. case "forwarded-tcpip":
  79. var forwardMsg = Message.LoadFrom<ForwardedTcpIpMessage>(message);
  80. HandleMessage(forwardMsg);
  81. break;
  82. default:
  83. _session.SendMessage(new ChannelOpenFailureMessage
  84. {
  85. RecipientChannel = message.SenderChannel,
  86. ReasonCode = ChannelOpenFailureReason.UnknownChannelType,
  87. Description = string.Format("Unknown channel type: {0}.", message.ChannelType),
  88. });
  89. throw new SshConnectionException(string.Format("Unknown channel type: {0}.", message.ChannelType));
  90. }
  91. }
  92. private void HandleMessage(ShouldIgnoreMessage message)
  93. {
  94. }
  95. private void HandleMessage(ForwardedTcpIpMessage message)
  96. {
  97. var channel = HandleChannelOpenMessage(message);
  98. var args = new TcpRequestArgs(channel,
  99. message.Address,
  100. (int)message.Port,
  101. message.OriginatorIPAddress,
  102. (int)message.OriginatorPort,
  103. _auth);
  104. TcpForwardRequest?.Invoke(this, args);
  105. }
  106. private void HandleMessage(DirectTcpIpMessage message)
  107. {
  108. var channel = HandleChannelOpenMessage(message);
  109. var args = new TcpRequestArgs(channel,
  110. message.Host,
  111. (int)message.Port,
  112. message.OriginatorIPAddress,
  113. (int)message.OriginatorPort,
  114. _auth);
  115. TcpForwardRequest?.Invoke(this, args);
  116. }
  117. private void HandleMessage(ChannelRequestMessage message)
  118. {
  119. switch (message.RequestType)
  120. {
  121. case "exec":
  122. var msg = Message.LoadFrom<CommandRequestMessage>(message);
  123. HandleMessage(msg);
  124. break;
  125. case "shell":
  126. var shell_msg = Message.LoadFrom<ShellRequestMessage>(message);
  127. HandleMessage(shell_msg);
  128. break;
  129. case "pty-req":
  130. var pty_msg = Message.LoadFrom<PtyRequestMessage>(message);
  131. HandleMessage(pty_msg);
  132. break;
  133. case "env":
  134. var env_msg = Message.LoadFrom<EnvMessage>(message);
  135. HandleMessage(env_msg);
  136. break;
  137. case "subsystem":
  138. var sub_msg = Message.LoadFrom<SubsystemRequestMessage>(message);
  139. HandleMessage(sub_msg);
  140. break;
  141. case "window-change":
  142. var window_change_msg = Message.LoadFrom<WindowChangeMessage>(message);
  143. HandleMessage(window_change_msg);
  144. break;
  145. case "simple@putty.projects.tartarus.org":
  146. //https://tartarus.org/~simon/putty-snapshots/htmldoc/AppendixF.html
  147. if (message.WantReply)
  148. {
  149. var c = FindChannelByServerId<SessionChannel>(message.RecipientChannel);
  150. _session.SendMessage(new ChannelSuccessMessage { RecipientChannel = c.ClientChannelId });
  151. }
  152. break;
  153. case "winadj@putty.projects.tartarus.org":
  154. //https://tartarus.org/~simon/putty-snapshots/htmldoc/AppendixF.html
  155. var channel = FindChannelByServerId<SessionChannel>(message.RecipientChannel);
  156. _session.SendMessage(new ChannelFailureMessage { RecipientChannel = channel.ClientChannelId });
  157. break;
  158. case "auth-agent-req@openssh.com":
  159. // https://github.com/openssh/openssh-portable/blob/V_8_0_P1/session.c#L2225
  160. break;
  161. default:
  162. if (message.WantReply)
  163. _session.SendMessage(new ChannelFailureMessage
  164. {
  165. RecipientChannel = FindChannelByServerId<Channel>(message.RecipientChannel).ClientChannelId
  166. });
  167. throw new SshConnectionException(string.Format("Unknown request type: {0}.", message.RequestType));
  168. }
  169. }
  170. private void HandleMessage(EnvMessage message)
  171. {
  172. var channel = FindChannelByServerId<SessionChannel>(message.RecipientChannel);
  173. EnvReceived?.Invoke(this, new EnvironmentArgs(channel, message.Name, message.Value, _auth));
  174. if (message.WantReply)
  175. _session.SendMessage(new ChannelSuccessMessage { RecipientChannel = channel.ClientChannelId });
  176. }
  177. private void HandleMessage(PtyRequestMessage message)
  178. {
  179. var channel = FindChannelByServerId<SessionChannel>(message.RecipientChannel);
  180. PtyReceived?.Invoke(this,
  181. new PtyArgs(channel,
  182. message.Terminal,
  183. message.heightPx,
  184. message.heightRows,
  185. message.widthPx,
  186. message.widthChars,
  187. message.modes, _auth));
  188. if (message.WantReply)
  189. _session.SendMessage(new ChannelSuccessMessage { RecipientChannel = channel.ClientChannelId });
  190. }
  191. private void HandleMessage(ChannelDataMessage message)
  192. {
  193. var channel = FindChannelByServerId<Channel>(message.RecipientChannel);
  194. channel.OnData(message.Data);
  195. }
  196. private void HandleMessage(ChannelWindowAdjustMessage message)
  197. {
  198. var channel = FindChannelByServerId<Channel>(message.RecipientChannel);
  199. channel.ClientAdjustWindow(message.BytesToAdd);
  200. }
  201. private void HandleMessage(ChannelEofMessage message)
  202. {
  203. var channel = FindChannelByServerId<Channel>(message.RecipientChannel);
  204. channel.OnEof();
  205. }
  206. private void HandleMessage(ChannelCloseMessage message)
  207. {
  208. var channel = FindChannelByServerId<Channel>(message.RecipientChannel);
  209. channel.OnClose();
  210. }
  211. private void HandleMessage(SessionOpenMessage message)
  212. {
  213. HandleChannelOpenMessage(message);
  214. }
  215. private SessionChannel HandleChannelOpenMessage(ChannelOpenMessage message)
  216. {
  217. var channel = new SessionChannel(
  218. this,
  219. message.SenderChannel,
  220. message.InitialWindowSize,
  221. message.MaximumPacketSize,
  222. (uint)Interlocked.Increment(ref _serverChannelCounter));
  223. lock (_locker)
  224. _channels.Add(channel);
  225. var msg = new ChannelOpenConfirmationMessage
  226. {
  227. RecipientChannel = channel.ClientChannelId,
  228. SenderChannel = channel.ServerChannelId,
  229. InitialWindowSize = channel.ServerInitialWindowSize,
  230. MaximumPacketSize = channel.ServerMaxPacketSize
  231. };
  232. _session.SendMessage(msg);
  233. return channel;
  234. }
  235. private void HandleMessage(ShellRequestMessage message)
  236. {
  237. var channel = FindChannelByServerId<SessionChannel>(message.RecipientChannel);
  238. if (message.WantReply)
  239. _session.SendMessage(new ChannelSuccessMessage { RecipientChannel = channel.ClientChannelId });
  240. CommandOpened?.Invoke(this, new CommandRequestedArgs(channel, "shell", null, _auth));
  241. }
  242. private void HandleMessage(CommandRequestMessage message)
  243. {
  244. var channel = FindChannelByServerId<SessionChannel>(message.RecipientChannel);
  245. if (message.WantReply)
  246. _session.SendMessage(new ChannelSuccessMessage { RecipientChannel = channel.ClientChannelId });
  247. CommandOpened?.Invoke(this, new CommandRequestedArgs(channel, "exec", message.Command, _auth));
  248. }
  249. private void HandleMessage(SubsystemRequestMessage message)
  250. {
  251. var channel = FindChannelByServerId<SessionChannel>(message.RecipientChannel);
  252. if (message.WantReply)
  253. _session.SendMessage(new ChannelSuccessMessage { RecipientChannel = channel.ClientChannelId });
  254. CommandOpened?.Invoke(this, new CommandRequestedArgs(channel, "subsystem", message.Name, _auth));
  255. }
  256. private void HandleMessage(WindowChangeMessage message)
  257. {
  258. var channel = FindChannelByServerId<SessionChannel>(message.RecipientChannel);
  259. WindowChange?.Invoke(this, new WindowChangeArgs(channel, message.WidthColumns, message.HeightRows, message.WidthPixels, message.HeightPixels));
  260. }
  261. private T FindChannelByClientId<T>(uint id) where T : Channel
  262. {
  263. lock (_locker)
  264. {
  265. var channel = _channels.FirstOrDefault(x => x.ClientChannelId == id) as T;
  266. if (channel == null)
  267. throw new SshConnectionException(string.Format("Invalid client channel id {0}.", id),
  268. DisconnectReason.ProtocolError);
  269. return channel;
  270. }
  271. }
  272. private T FindChannelByServerId<T>(uint id) where T : Channel
  273. {
  274. lock (_locker)
  275. {
  276. var channel = _channels.FirstOrDefault(x => x.ServerChannelId == id) as T;
  277. if (channel == null)
  278. throw new SshConnectionException(string.Format("Invalid server channel id {0}.", id),
  279. DisconnectReason.ProtocolError);
  280. return channel;
  281. }
  282. }
  283. internal void RemoveChannel(Channel channel)
  284. {
  285. lock (_locker)
  286. {
  287. _channels.Remove(channel);
  288. }
  289. }
  290. }
  291. }