-
Notifications
You must be signed in to change notification settings - Fork 19
020: Disconnect Message
Welcome back for more punishment! We are going to wire up the Disconnect packet to tell the client why we are hanging up on them. This will be covering the RFC 11.1. Disconnection Message. First we need to add a new enum type for the DisconnectReason:
public enum DisconnectReason : uint { None = 0, SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1, SSH_DISCONNECT_PROTOCOL_ERROR = 2, SSH_DISCONNECT_KEY_EXCHANGE_FAILED = 3, SSH_DISCONNECT_RESERVED = 4, SSH_DISCONNECT_MAC_ERROR = 5, SSH_DISCONNECT_COMPRESSION_ERROR = 6, SSH_DISCONNECT_SERVICE_NOT_AVAILABLE = 7, SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8, SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9, SSH_DISCONNECT_CONNECTION_LOST = 10, SSH_DISCONNECT_BY_APPLICATION = 11, SSH_DISCONNECT_TOO_MANY_CONNECTIONS = 12, SSH_DISCONNECT_AUTH_CANCELLED_BY_USER = 13, SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14, SSH_DISCONNECT_ILLEGAL_USER_NAME = 15, }
Now we can create a Disconnect packet in the Packets folder that inherits from Packet:
public class Disconnect : Packet { public override PacketType PacketType { get { return PacketType.SSH_MSG_DISCONNECT; } } public DisconnectReason Reason { get; set; } public string Description { get; set; } public string Language { get; set; } = "en"; public override void Load(ByteReader reader) { Reason = (DisconnectReason)reader.GetUInt32(); Description = reader.GetString(Encoding.UTF8); if (!reader.IsEOF) Language = reader.GetString(); } protected override void InternalGetBytes(ByteWriter writer) { writer.WriteUInt32((uint)Reason); writer.WriteString(Description, Encoding.UTF8); writer.WriteString(Language); } }
Okay, and now we need to update our Client's Disconnect() method to take in a reason and message:
public class Client { ... public void Disconnect(DisconnectReason reason, string message) { m_Logger.LogDebug($"Disconnected - {reason} - {message}"); if (m_Socket != null) { if (reason != DisconnectReason.None) { try { Disconnect disconnect = new Disconnect() { Reason = reason, Description = message }; Send(disconnect); } catch (Exception) { } } try { m_Socket.Shutdown(SocketShutdown.Both); } catch (Exception) { } m_Socket = null; } } ... }
Now, we need a way to communicate why we failed. Most of our failures are due to exceptions being thrown. So, let's make a new class SSHServerException at the root of the project and inherit from Exception:
public class SSHServerException : Exception { public DisconnectReason Reason { get; set; } public SSHServerException(DisconnectReason reason, string message) : base(message) { Reason = reason; } }
Now, we need to go and refactor all of our failures. I'm not going to cover each of them, but if you need to see, I recommend you review commit 801df89.
If you run the server now and connect with OpenSSH, then press CTRL-C in the Server window to shut it down, you will see OpenSSH report:
... debug3: receive packet: type 1 Received disconnect from 127.0.0.1 port 22:11: The server is shutting down. ...
Perfect! The code at this point is tagged with Disconnect_Message. There are still more things to do, but we have connected, validated, exchanged keys, and sent and received encrypted messages! The rest of the RFCs cover authentication and other services that are offered by SSH. If I get time, I'll come back and add an appendix to cover these services.
If you'd like to give me a tip, donate at:
- Bitcoin (BTC): 1NdnffxFC7G7qMrvUYc1x4R5sqXuJhVFR7
- Etherium (ETH): 0xcF0a3f130ba0f8c4CC3A02F782805A448D45388f
- Litecoin (LTC): LV7JL8yA4fAZ3Lib9VoX1tuFPmPVrfFueT