diff --git a/pkg/apf/processor.go b/pkg/apf/processor.go index 76e45422..b6cde9da 100644 --- a/pkg/apf/processor.go +++ b/pkg/apf/processor.go @@ -22,6 +22,18 @@ func Process(data []byte, session *Session) bytes.Buffer { var dataToSend interface{} switch data[0] { + case APF_KEEPALIVE_REQUEST: + log.Debug("received APF_KEEPALIVE_REQUEST") + + dataToSend = ProcessKeepAliveRequest(data, session) + case APF_KEEPALIVE_REPLY: + log.Debug("received APF_KEEPALIVE_REPLY") + + // dataToSend = ProcessKeepAliveReply(data, session) + case APF_KEEPALIVE_OPTIONS_REPLY: + log.Debug("received APF_KEEPALIVE_OPTIONS_REQUEST") + + // dataToSend = ProcessKeepAliveOptionsReply(data, session) case APF_GLOBAL_REQUEST: // 80 log.Debug("received APF_GLOBAL_REQUEST") @@ -75,6 +87,9 @@ func Process(data []byte, session *Session) bytes.Buffer { dataToSend = ProcessProtocolVersion(data) } case APF_USERAUTH_REQUEST: // 50 + log.Debug("received APF_USERAUTH_REQUEST") + + dataToSend = ProcessUserAuthRequest(data, session) default: } @@ -85,9 +100,235 @@ func Process(data []byte, session *Session) bytes.Buffer { } } + // fmt.Printf("bin_buf: %x\n", bin_buf.Bytes()) + return bin_buf } +func ProcessKeepAliveRequest(data []byte, session *Session) any { + if len(data) < 5 { + log.Warn("APF_KEEPALIVE_REQUEST message too short") + + return APF_KEEPALIVE_REPLY_MESSAGE{} + } + + cookie := binary.BigEndian.Uint32(data[1:5]) + log.Debugf("received APF_KEEPALIVE_REQUEST with cookie: %d", cookie) + + reply := APF_KEEPALIVE_REPLY_MESSAGE{ + MessageType: APF_KEEPALIVE_REPLY, + Cookie: cookie, + } + + return reply +} + +func ProcessKeepAliveReply(data []byte, session *Session) { + if len(data) < 5 { + log.Warn("APF_KEEPALIVE_REPLY message too short") + + return + } + + cookie := binary.BigEndian.Uint32(data[1:5]) + log.Debugf("received APF_KEEPALIVE_REPLY with cookie: %d", cookie) // TODO: Update session state if necessary +} + +func ProcessKeepAliveOptionsReply(data []byte, session *Session) { + if len(data) < 9 { + log.Warn("APF_KEEPALIVE_OPTIONS_REPLY message too short") + + return + } + + keepaliveInterval := binary.BigEndian.Uint32(data[1:5]) + timeout := binary.BigEndian.Uint32(data[5:9]) + log.Debugf("KEEPALIVE_OPTIONS_REPLY, Keepalive Interval=%d Timeout=%d", keepaliveInterval, timeout) // TODO: // Update session state or configurations as needed +} + +func ProcessUserAuthRequest(data []byte, session *Session) interface{} { + log.Debug("received APF_USERAUTH_REQUEST") + + dataBuffer := bytes.NewReader(data) + + var messageType byte + + err := binary.Read(dataBuffer, binary.BigEndian, &messageType) + if err != nil { + log.Error(err) + + return nil + } + + // Read username length + var usernameLen uint32 + + err = binary.Read(dataBuffer, binary.BigEndian, &usernameLen) + if err != nil { + log.Error(err) + + return nil + } + + if usernameLen > 2048 || uint32(dataBuffer.Len()) < usernameLen { + log.Error("Invalid username length") + + return nil + } + + usernameBytes := make([]byte, usernameLen) + + n, err := dataBuffer.Read(usernameBytes) + if err != nil || n != int(usernameLen) { + log.Error("Failed to read username") + + return nil + } + + username := string(usernameBytes) + + // Read serviceName length + var serviceNameLen uint32 + + err = binary.Read(dataBuffer, binary.BigEndian, &serviceNameLen) + if err != nil { + log.Error(err) + + return nil + } + + if serviceNameLen > 2048 || uint32(dataBuffer.Len()) < serviceNameLen { + log.Error("Invalid serviceName length") + + return nil + } + + serviceNameBytes := make([]byte, serviceNameLen) + + n, err = dataBuffer.Read(serviceNameBytes) + if err != nil || n != int(serviceNameLen) { + log.Error("Failed to read serviceName") + + return nil + } + + serviceName := string(serviceNameBytes) + + // Read methodName length + var methodNameLen uint32 + + err = binary.Read(dataBuffer, binary.BigEndian, &methodNameLen) + if err != nil { + log.Error(err) + + return nil + } + + if methodNameLen > 2048 || uint32(dataBuffer.Len()) < methodNameLen { + log.Error("Invalid methodName length") + + return nil + } + + methodNameBytes := make([]byte, methodNameLen) + + n, err = dataBuffer.Read(methodNameBytes) + if err != nil || n != int(methodNameLen) { + log.Error("Failed to read methodName") + + return nil + } + + methodName := string(methodNameBytes) + + if methodName == "password" { + if dataBuffer.Len() < 1 { + log.Error("Not enough data for password FALSE byte") + + return nil + } + // Read boolean FALSE + var passwordFalse byte + + err = binary.Read(dataBuffer, binary.BigEndian, &passwordFalse) + if err != nil { + log.Error(err) + + return nil + } + + if passwordFalse != 0 { + log.Error("passwordFalse is not zero") + + return nil + } + + // Read password length + var passwordLen uint32 + + err = binary.Read(dataBuffer, binary.BigEndian, &passwordLen) + if err != nil { + log.Error(err) + + return nil + } + + if passwordLen > 2048 || uint32(dataBuffer.Len()) < passwordLen { + log.Error("Invalid password length") + + return nil + } + + passwordBytes := make([]byte, passwordLen) + + n, err = dataBuffer.Read(passwordBytes) + if err != nil || n != int(passwordLen) { + log.Error("Failed to read password") + + return nil + } + + _ = string(passwordBytes) + } else { + // Unsupported method + log.Warn("Unsupported authentication method: ", methodName) + // Return failure + // failureMessage := &APF_USERAUTH_FAILURE_MESSAGE{ + // MessageType: APF_USERAUTH_FAILURE, + // AuthenticationsThatCanContinueLength: uint32(len("password")), + // AuthenticationsThatCanContinue: []byte("password"), + // PartialSuccess: 0, + // } + // return failureMessage + return nil + } + + log.Debugf("usernameLen=%d serviceNameLen=%d methodNameLen=%d", usernameLen, serviceNameLen, methodNameLen) + log.Debugf("username=%s serviceName=%s methodName=%s", username, serviceName, methodName) + + // Now authenticate the user + authenticated := true // session.AuthenticateUser(username, password) + + if authenticated { + // Return success message + message := &APF_USERAUTH_SUCCESS_MESSAGE{ + MessageType: APF_USERAUTH_SUCCESS, + } + + return message + } else { + // Return failure message + // failureMessage := &APF_USERAUTH_FAILURE_MESSAGE{ + // MessageType: APF_USERAUTH_FAILURE, + // AuthenticationsThatCanContinueLength: uint32(len("password")), + // AuthenticationsThatCanContinue: []byte("password"), + // PartialSuccess: 0, + // } + // return failureMessage + return nil + } +} + func ProcessChannelWindowAdjust(data []byte, session *Session) { adjustMessage := APF_CHANNEL_WINDOW_ADJUST_MESSAGE{} dataBuffer := bytes.NewBuffer(data) @@ -173,11 +414,14 @@ func ProcessGlobalRequest(data []byte) interface{} { switch genericHeader.String { case APF_GLOBAL_REQUEST_STR_TCP_FORWARD_REQUEST: - if tcpForwardRequest.Port == 16992 || tcpForwardRequest.Port == 16993 { - reply = TcpForwardReplySuccess(tcpForwardRequest.Port) - } else { - reply = APF_REQUEST_FAILURE + // if tcpForwardRequest.Port == 16992 || tcpForwardRequest.Port == 16993 { + reply = TcpForwardReplySuccess(tcpForwardRequest.Port) + + if tcpForwardRequest.Port == 5900 { } + // } else { + // reply = APF_REQUEST_FAILURE + // } case APF_GLOBAL_REQUEST_STR_TCP_FORWARD_CANCEL_REQUEST: reply = APF_REQUEST_SUCCESS } diff --git a/pkg/apf/types.go b/pkg/apf/types.go index de4c2a4c..e5b035e8 100644 --- a/pkg/apf/types.go +++ b/pkg/apf/types.go @@ -32,6 +32,9 @@ const ( APF_CHANNEL_DATA = 94 APF_CHANNEL_CLOSE = 97 APF_PROTOCOLVERSION = 192 + APF_KEEPALIVE_REQUEST = 208 + APF_KEEPALIVE_REPLY = 209 + APF_KEEPALIVE_OPTIONS_REPLY = 211 ) // disconnect reason codes. @@ -93,6 +96,11 @@ type APF_MESSAGE_HEADER struct { MessageType byte } +type APF_KEEPALIVE_REPLY_MESSAGE struct { + MessageType byte + Cookie uint32 +} + /** * APF_GENERIC_HEADER - generic request header (note that its not complete header per protocol (missing WantReply) * diff --git a/pkg/wsman/client/types.go b/pkg/wsman/client/types.go index 07271bce..3d159ac1 100644 --- a/pkg/wsman/client/types.go +++ b/pkg/wsman/client/types.go @@ -2,6 +2,7 @@ package client import ( "crypto/tls" + "net" "net/http" ) @@ -17,6 +18,7 @@ type Parameters struct { Transport http.RoundTripper IsRedirection bool PinnedCert string + Connection net.Conn TlsConfig *tls.Config AllowInsecureCipherSuites bool } diff --git a/pkg/wsman/client/wsman_tcp.go b/pkg/wsman/client/wsman_tcp.go index 2a1c5baa..cb12ed90 100644 --- a/pkg/wsman/client/wsman_tcp.go +++ b/pkg/wsman/client/wsman_tcp.go @@ -39,6 +39,7 @@ func NewWsmanTCP(cp Parameters) *Target { UseTLS: cp.UseTLS, InsecureSkipVerify: cp.SelfSignedAllowed, PinnedCert: cp.PinnedCert, + conn: cp.Connection, bufferPool: sync.Pool{ New: func() interface{} { // Larger buffer to reduce read syscalls and frame fragmentation for KVM streams @@ -52,6 +53,10 @@ func NewWsmanTCP(cp Parameters) *Target { func (t *Target) Connect() error { // Use a Dialer so we can enable TCP keep-alives and TCP_NODELAY for lower latency. d := &net.Dialer{KeepAlive: defaultKeepAlive} + // already connected and connection has been provided + if t.conn != nil { + return nil + } if t.UseTLS { // Build TLS config with optional pinning