/// <summary> /// Constructor /// </summary> /// <param name="type">packet type (message number)</param> public SSH2Packet(SSH2PacketType type) { if (_lockFlag.Value) { throw new InvalidOperationException( "simultaneous editing packet detected: " + typeof(SSH2Packet).FullName); } _lockFlag.Value = true; _payload = _payloadBuffer.Value; _payload.Clear(); _payload.WriteUInt32(0); // sequence_number field for computing MAC _payload.WriteUInt32(0); // packet_length field _payload.WriteByte(0); // padding_length field _payload.WriteByte((byte)type); }
/// <summary> /// Process packet additionally. /// </summary> /// <remarks> /// This method will be called repeatedly while <see cref="State"/> is <see cref="State.Established"/> or <see cref="State.Ready"/>. /// </remarks> /// <param name="packetType">a packet type (message number)</param> /// <param name="packetFragment">a packet image except message number and recipient channel.</param> /// <returns>result</returns> protected virtual SubPacketProcessResult ProcessPacketSub(SSH2PacketType packetType, DataFragment packetFragment) { // derived class can override this. return SubPacketProcessResult.NotConsumed; }
//alternative version internal void TraceTransmissionEvent(SSH2PacketType pt, string message, params object[] args) { ISSHEventTracer t = _param.EventTracer; if (t != null) t.OnTranmission(pt.ToString(), String.Format(message, args)); }
/// <summary> /// Process packet about this channel. /// </summary> /// <param name="packetType">a packet type (message number)</param> /// <param name="packetFragment">a packet image except message number and recipient channel.</param> public void ProcessPacket(SSH2PacketType packetType, DataFragment packetFragment) { if (_state == State.Closed) { return; // ignore } DataFragment dataFragmentArg; uint dataTypeCodeArg; lock (_stateSync) { switch (_state) { case State.InitiatedByServer: break; case State.InitiatedByClient: if (packetType == SSH2PacketType.SSH_MSG_CHANNEL_OPEN_CONFIRMATION) { SSH2DataReader reader = new SSH2DataReader(packetFragment); RemoteChannel = reader.ReadUInt32(); _serverWindowSizeLeft = reader.ReadUInt32(); _serverMaxPacketSize = reader.ReadUInt32(); _state = State.Established; Monitor.PulseAll(_stateSync); // notifies state change dataFragmentArg = reader.GetRemainingDataView(); goto OnEstablished; // do it out of the lock block } if (packetType == SSH2PacketType.SSH_MSG_CHANNEL_OPEN_FAILURE) { SSH2DataReader reader = new SSH2DataReader(packetFragment); uint reasonCode = reader.ReadUInt32(); string description = reader.ReadUTF8String(); string lang = reader.ReadString(); goto RequestFailed; // do it out of the lock block } break; case State.Closing: if (packetType == SSH2PacketType.SSH_MSG_CHANNEL_CLOSE) { goto SetStateClosedByClient; // do it out of the lock block } break; case State.Established: case State.Ready: if (ProcessPacketSub(packetType, packetFragment) == SubPacketProcessResult.Consumed) { return; } switch (packetType) { case SSH2PacketType.SSH_MSG_CHANNEL_DATA: { SSH2DataReader reader = new SSH2DataReader(packetFragment); int len = reader.ReadInt32(); dataFragmentArg = reader.GetRemainingDataView(len); AdjustWindowSize(len); } goto OnData; // do it out of the lock block case SSH2PacketType.SSH_MSG_CHANNEL_EXTENDED_DATA: { SSH2DataReader reader = new SSH2DataReader(packetFragment); dataTypeCodeArg = reader.ReadUInt32(); int len = reader.ReadInt32(); dataFragmentArg = reader.GetRemainingDataView(len); AdjustWindowSize(len); } goto OnExtendedData; // do it out of the lock block case SSH2PacketType.SSH_MSG_CHANNEL_REQUEST: { SSH2DataReader reader = new SSH2DataReader(packetFragment); string request = reader.ReadString(); bool wantReply = reader.ReadBool(); if (wantReply) { //we reject unknown requests including keep-alive check Transmit( 0, new SSH2Packet(SSH2PacketType.SSH_MSG_CHANNEL_FAILURE) .WriteUInt32(RemoteChannel) ); } } break; case SSH2PacketType.SSH_MSG_CHANNEL_EOF: goto OnEOF; // do it out of the lock block case SSH2PacketType.SSH_MSG_CHANNEL_CLOSE: Transmit( 0, new SSH2Packet(SSH2PacketType.SSH_MSG_CHANNEL_CLOSE) .WriteUInt32(RemoteChannel) ); goto SetStateClosedByServer; // do it out of the lock block case SSH2PacketType.SSH_MSG_CHANNEL_WINDOW_ADJUST: { SSH2DataReader reader = new SSH2DataReader(packetFragment); uint bytesToAdd = reader.ReadUInt32(); // some servers may not send SSH_MSG_CHANNEL_WINDOW_ADJUST. // it is dangerous to wait this message in send procedure _serverWindowSizeLeft += bytesToAdd; } goto OnWindowAdjust; case SSH2PacketType.SSH_MSG_CHANNEL_SUCCESS: case SSH2PacketType.SSH_MSG_CHANNEL_FAILURE: { _channelRequestResult.TrySet(packetType == SSH2PacketType.SSH_MSG_CHANNEL_SUCCESS, 1000); } break; default: goto OnUnhandledPacket; } break; // case State.Ready } } return; OnEstablished: _protocolEventManager.Trace( "CH[{0}] remoteCH={1} remoteWindowSize={2} remoteMaxPacketSize={3}", LocalChannel, RemoteChannel, _serverWindowSizeLeft, _serverMaxPacketSize); _handler.OnEstablished(dataFragmentArg); OnChannelEstablished(); return; RequestFailed: _protocolEventManager.Trace("CH[{0}] request failed", LocalChannel); RequestFailed(); return; SetStateClosedByClient: _protocolEventManager.Trace("CH[{0}] closed completely", LocalChannel); SetStateClosed(false); return; SetStateClosedByServer: _protocolEventManager.Trace("CH[{0}] closed by server", LocalChannel); SetStateClosed(true); return; OnData: _handler.OnData(dataFragmentArg); return; OnExtendedData: _handler.OnExtendedData(dataTypeCodeArg, dataFragmentArg); return; OnEOF: _protocolEventManager.Trace("CH[{0}] caught EOF", LocalChannel); _handler.OnEOF(); return; OnWindowAdjust: _protocolEventManager.Trace( "CH[{0}] adjusted remote window size to {1}", LocalChannel, _serverWindowSizeLeft); return; OnUnhandledPacket: _handler.OnUnhandledPacket((byte)packetType, packetFragment); return; }
private void ReceivePortForwardingResponse(ISSHChannelEventReceiver receiver, SSH2PacketType pt, SSH2DataReader reader) { if (_negotiationStatus == NegotiationStatus.WaitingChannelConfirmation) { if (pt != SSH2PacketType.SSH_MSG_CHANNEL_OPEN_CONFIRMATION) { if (pt != SSH2PacketType.SSH_MSG_CHANNEL_OPEN_FAILURE) receiver.OnChannelError(new SSHException("opening channel failed; packet type=" + pt)); else { int errcode = reader.ReadInt32(); string msg = reader.ReadUTF8String(); receiver.OnChannelError(new SSHException(msg)); } Close(); } else { _remoteID = reader.ReadInt32(); _serverMaxPacketSize = reader.ReadInt32(); _negotiationStatus = NegotiationStatus.Ready; receiver.OnChannelReady(); } } else throw new SSHException("internal state error"); }
internal void TraceReceptionEvent(SSH2PacketType pt, byte[] msg) { TraceReceptionEvent(pt.ToString(), Encoding.ASCII.GetString(msg)); }
private void ProcessChannelLocalData(ISSHChannelEventReceiver receiver, SSH2PacketType pt, SSH2DataReader re) { switch (pt) { case SSH2PacketType.SSH_MSG_CHANNEL_DATA: { int len = re.ReadInt32(); DataFragment frag = re.GetRemainingDataView(len); receiver.OnData(frag.Data, frag.Offset, frag.Length); } break; case SSH2PacketType.SSH_MSG_CHANNEL_EXTENDED_DATA: { int t = re.ReadInt32(); byte[] data = re.ReadByteString(); receiver.OnExtendedData(t, data); } break; case SSH2PacketType.SSH_MSG_CHANNEL_REQUEST: { string request = re.ReadString(); bool reply = re.ReadBool(); if (request == "exit-status") { int status = re.ReadInt32(); } else if (reply) { //we reject unknown requests including keep-alive check Transmit( 0, new SSH2Packet(SSH2PacketType.SSH_MSG_CHANNEL_FAILURE) .WriteInt32(_remoteID) ); } } break; case SSH2PacketType.SSH_MSG_CHANNEL_EOF: receiver.OnChannelEOF(); break; case SSH2PacketType.SSH_MSG_CHANNEL_CLOSE: _connection.ChannelCollection.UnregisterChannelEventReceiver(_localID); receiver.OnChannelClosed(); break; case SSH2PacketType.SSH_MSG_CHANNEL_FAILURE: { DataFragment frag = re.GetRemainingDataView(); receiver.OnMiscPacket((byte)pt, frag.Data, frag.Offset, frag.Length); } break; default: { DataFragment frag = re.GetRemainingDataView(); receiver.OnMiscPacket((byte)pt, frag.Data, frag.Offset, frag.Length); } Debug.WriteLine("Unknown Packet " + pt); break; } }
//Progress the state of this channel establishment negotiation private void ProgressChannelNegotiation(ISSHChannelEventReceiver receiver, SSH2PacketType pt, SSH2DataReader re) { if (_type == ChannelType.Shell) OpenShellOrSubsystem(receiver, pt, re, "shell"); else if (_type == ChannelType.ForwardedLocalToRemote) ReceivePortForwardingResponse(receiver, pt, re); else if (_type == ChannelType.Session) EstablishSession(receiver, pt, re); else if (_type == ChannelType.ExecCommand) // for SCP ExecCommand(receiver, pt, re); else if (_type == ChannelType.Subsystem) OpenShellOrSubsystem(receiver, pt, re, "subsystem"); }
private void OpenShellOrSubsystem(ISSHChannelEventReceiver receiver, SSH2PacketType pt, SSH2DataReader reader, string scheme) { if (_negotiationStatus == NegotiationStatus.WaitingChannelConfirmation) { if (pt != SSH2PacketType.SSH_MSG_CHANNEL_OPEN_CONFIRMATION) { if (pt != SSH2PacketType.SSH_MSG_CHANNEL_OPEN_FAILURE) receiver.OnChannelError(new SSHException("opening channel failed; packet type=" + pt)); else { int errcode = reader.ReadInt32(); string msg = reader.ReadUTF8String(); receiver.OnChannelError(new SSHException(msg)); } // Close() shouldn't be called because remote channel number is not given yet. // We just remove an event receiver from the collection of channels. // FIXME: _negotiationStatus sould be set an error status ? _connection.ChannelCollection.UnregisterChannelEventReceiver(_localID); } else { _remoteID = reader.ReadInt32(); _allowedDataSize = reader.ReadUInt32(); _serverMaxPacketSize = reader.ReadInt32(); if (_type == ChannelType.Subsystem) { OpenScheme(scheme); _negotiationStatus = NegotiationStatus.WaitingSubsystemConfirmation; } else { //open pty SSHConnectionParameter param = _connection.Param; Transmit( 0, new SSH2Packet(SSH2PacketType.SSH_MSG_CHANNEL_REQUEST) .WriteInt32(_remoteID) .WriteString("pty-req") .WriteBool(true) .WriteString(param.TerminalName) .WriteInt32(param.TerminalWidth) .WriteInt32(param.TerminalHeight) .WriteInt32(param.TerminalPixelWidth) .WriteInt32(param.TerminalPixelHeight) .WriteAsString(new byte[0]) ); if (_connection.IsEventTracerAvailable) { _connection.TraceTransmissionEvent( SSH2PacketType.SSH_MSG_CHANNEL_REQUEST, "pty-req", "terminal={0} width={1} height={2}", param.TerminalName, param.TerminalWidth, param.TerminalHeight); } _negotiationStatus = NegotiationStatus.WaitingPtyReqConfirmation; } } } else if (_negotiationStatus == NegotiationStatus.WaitingPtyReqConfirmation) { if (pt != SSH2PacketType.SSH_MSG_CHANNEL_SUCCESS) { receiver.OnChannelError(new SSHException("opening pty failed")); Close(); } else { //agent request (optional) if (_connection.Param.AgentForward != null) { Transmit( 0, new SSH2Packet(SSH2PacketType.SSH_MSG_CHANNEL_REQUEST) .WriteInt32(_remoteID) .WriteString("*****@*****.**") .WriteBool(true) ); _connection.TraceTransmissionEvent(SSH2PacketType.SSH_MSG_CHANNEL_REQUEST, "auth-agent-req", ""); _negotiationStatus = NegotiationStatus.WaitingAuthAgentReqConfirmation; } else { OpenScheme(scheme); _negotiationStatus = NegotiationStatus.WaitingShellConfirmation; } } } else if (_negotiationStatus == NegotiationStatus.WaitingAuthAgentReqConfirmation) { if (pt != SSH2PacketType.SSH_MSG_CHANNEL_SUCCESS && pt != SSH2PacketType.SSH_MSG_CHANNEL_FAILURE) { receiver.OnChannelError(new SSHException("auth-agent-req error")); Close(); } else { //auth-agent-req is optional _connection.SetAgentForwardConfirmed(pt == SSH2PacketType.SSH_MSG_CHANNEL_SUCCESS); _connection.TraceReceptionEvent(pt, "auth-agent-req"); OpenScheme(scheme); _negotiationStatus = NegotiationStatus.WaitingShellConfirmation; } } else if (_negotiationStatus == NegotiationStatus.WaitingShellConfirmation) { if (pt != SSH2PacketType.SSH_MSG_CHANNEL_SUCCESS) { receiver.OnChannelError(new SSHException("Opening shell failed: packet type=" + pt.ToString())); Close(); } else { receiver.OnChannelReady(); _negotiationStatus = NegotiationStatus.Ready; //goal! } } else if (_negotiationStatus == NegotiationStatus.WaitingSubsystemConfirmation) { if (pt != SSH2PacketType.SSH_MSG_CHANNEL_SUCCESS) { receiver.OnChannelError(new SSHException("Opening subsystem failed: packet type=" + pt.ToString())); Close(); } else { receiver.OnChannelReady(); _negotiationStatus = NegotiationStatus.Ready; //goal! } } }
// sending "exec" service for SCP protocol. private void ExecCommand(ISSHChannelEventReceiver receiver, SSH2PacketType pt, SSH2DataReader reader) { if (_negotiationStatus == NegotiationStatus.WaitingChannelConfirmation) { if (pt != SSH2PacketType.SSH_MSG_CHANNEL_OPEN_CONFIRMATION) { if (pt != SSH2PacketType.SSH_MSG_CHANNEL_OPEN_FAILURE) receiver.OnChannelError(new SSHException("opening channel failed; packet type=" + pt)); else { int errcode = reader.ReadInt32(); string msg = reader.ReadUTF8String(); receiver.OnChannelError(new SSHException(msg)); } Close(); } else { _remoteID = reader.ReadInt32(); _allowedDataSize = reader.ReadUInt32(); _serverMaxPacketSize = reader.ReadInt32(); // exec command SSHConnectionParameter param = _connection.Param; Transmit( 0, new SSH2Packet(SSH2PacketType.SSH_MSG_CHANNEL_REQUEST) .WriteInt32(_remoteID) .WriteString("exec") // "exec" .WriteBool(false) // want confirm is disabled. (*) .WriteString(_command) ); if (_connection.IsEventTracerAvailable) _connection.TraceTransmissionEvent("exec command", "cmd={0}", _command); //confirmation is omitted receiver.OnChannelReady(); _negotiationStatus = NegotiationStatus.Ready; //goal! } } else if (_negotiationStatus == NegotiationStatus.WaitingExecCmdConfirmation) { if (pt != SSH2PacketType.SSH_MSG_CHANNEL_DATA) { receiver.OnChannelError(new SSHException("exec command failed")); Close(); } else { receiver.OnChannelReady(); _negotiationStatus = NegotiationStatus.Ready; //goal! } } else throw new SSHException("internal state error"); }
private void AdjustWindowSize(SSH2PacketType pt, int dataLength) { // need not send window size to server when the channel is not opened. if (_negotiationStatus != NegotiationStatus.Ready) return; _leftWindowSize = Math.Max(_leftWindowSize - dataLength, 0); if (_leftWindowSize < _windowSize / 2) { Transmit( 0, new SSH2Packet(SSH2PacketType.SSH_MSG_CHANNEL_WINDOW_ADJUST) .WriteInt32(_remoteID) .WriteInt32(_windowSize - _leftWindowSize) ); if (_connection.IsEventTracerAvailable) _connection.TraceTransmissionEvent("SSH_MSG_CHANNEL_WINDOW_ADJUST", "adjusted window size : {0} --> {1}", _leftWindowSize, _windowSize); _leftWindowSize = _windowSize; } }
internal void ProcessPacket(ISSHChannelEventReceiver receiver, SSH2PacketType pt, SSH2DataReader re) { //NOTE: the offset of 're' is next to 'receipiant channel' field if (pt == SSH2PacketType.SSH_MSG_CHANNEL_DATA || pt == SSH2PacketType.SSH_MSG_CHANNEL_EXTENDED_DATA) { AdjustWindowSize(pt, re.RemainingDataLength); } //SSH_MSG_CHANNEL_WINDOW_ADJUST comes before the complete of channel establishment if (pt == SSH2PacketType.SSH_MSG_CHANNEL_WINDOW_ADJUST) { uint w = re.ReadUInt32(); //some servers may not send SSH_MSG_CHANNEL_WINDOW_ADJUST. //it is dangerous to wait this message in send procedure _allowedDataSize += w; if (_connection.IsEventTracerAvailable) _connection.TraceReceptionEvent("SSH_MSG_CHANNEL_WINDOW_ADJUST", "adjusted to {0} by increasing {1}", _allowedDataSize, w); return; } // check closing sequence if (_waitingChannelClose && pt == SSH2PacketType.SSH_MSG_CHANNEL_CLOSE) { _waitingChannelClose = false; return; // ignore it } if (_negotiationStatus != NegotiationStatus.Ready) //when the negotiation is not completed ProgressChannelNegotiation(receiver, pt, re); else ProcessChannelLocalData(receiver, pt, re); }
private void TraceTransmissionNegotiation(SSH2PacketType pt, string msg) { _connection.TraceTransmissionEvent(pt.ToString(), msg); }