diff --git a/modules/oracle/connection.go b/modules/oracle/connection.go index 8cc3395..66524ff 100644 --- a/modules/oracle/connection.go +++ b/modules/oracle/connection.go @@ -1,7 +1,6 @@ package oracle import ( - "fmt" "net" "strconv" @@ -18,9 +17,13 @@ type HandshakeLog struct { // server returns in the Accept packet. GlobalServiceOptions map[string]bool `json:"global_service_options,omitempty"` - // ConnectFlags is the ste of ConnectFlags values that the server returns - // in the Accept packet. Both are included in the array. - ConnectFlags [2]map[string]bool `json:"connect_flags,omitempty"` + // ConnectFlags0 is the first set of ConnectFlags values that the server + // returns in the Accept packet for the first. + ConnectFlags0 map[string]bool `json:"connect_flags0,omitempty"` + + // ConnectFlags1 is the second set of ConnectFlags values that the server + // returns in the Accept packet for the first. + ConnectFlags1 map[string]bool `json:"connect_flags1,omitempty"` // DidResend is true if the server sent a Resend packet in response to the // client's first Connect packet. @@ -28,15 +31,28 @@ type HandshakeLog struct { // RedirectTarget is the connect descriptor returned by the server in the // Redirect packet, if one is sent. Otherwise it is empty/omitted. - RedirectTarget string `json:"redirect_target,omitempty"` + RedirectTargetRaw string `json:"redirect_target_raw,omitempty"` - // RefuseError is the Data from the Refuse packet returned by the server; + RedirectTarget Descriptor `json:"redirect_target,omitempty"` + + // RefuseErrorRaW is the Data from the Refuse packet returned by the server; // it is empty if the server does not return a Refuse packet. - RefuseError string `json:"refuse_error,omitempty"` + RefuseErrorRaw string `json:"refuse_error_raw,omitempty"` - // RefuseReason describes the reason the request was refused as given in the - // Refuse packet from the server. TODO: finalize format. - RefuseReason string `json:"refuse_reason,omitempty"` + RefuseError Descriptor `json:"refuse_error,omitempty"` + + // RefuseReasonApp is the "AppReason" returned by the server in a Refused + // response packet. + RefuseReasonApp string `json:"refuse_reason_app,omitempty"` + + // RefuseReasonSys is the "SysReason" returned by the server in a Refused + // response packet. + RefuseReasonSys string `json:"refuse_reason_sys,omitempty"` + + // RefuseVersion is the parsed DESCRIPTION.VSNNUM field from the RefuseError + // string returned by the server in the Refuse packet, in dotted-decimal + // format. + RefuseVersion string `json:"refuse_version,omitempty"` // DidResend is set to true if the server sent a Resend packet after the // first Connect packet. @@ -84,7 +100,10 @@ func (conn *Connection) readPacket() (*TNSPacket, error) { // Automatically handles Resend responses; the caller is responsible for // handling other exceptional cases. func (conn *Connection) SendPacket(packet TNSPacketBody) (TNSPacketBody, error) { - toSend := conn.tnsDriver.EncodePacket(&TNSPacket{Body: packet}) + toSend, err := conn.tnsDriver.EncodePacket(&TNSPacket{Body: packet}) + if err != nil { + return nil, err + } if err := conn.send(toSend); err != nil { return nil, err @@ -162,13 +181,26 @@ func (conn *Connection) Connect(connectDescriptor string) (*HandshakeLog, error) case *TNSAccept: accept = resp case *TNSRedirect: - result.RedirectTarget = string(resp.Data) + result.RedirectTargetRaw = string(resp.Data) + if parsed, err := DecodeDescriptor(result.RedirectTargetRaw); err != nil { + result.RedirectTarget = parsed + } // TODO: Follow redirects? return &result, nil case *TNSRefuse: - result.RefuseError = string(resp.Data) - // TODO: do better - result.RefuseReason = fmt.Sprintf("AppReason: 0x%02x; SysReason: 0x%02x", resp.AppReason, resp.SysReason) + result.RefuseErrorRaw = string(resp.Data) + result.RefuseReasonApp = resp.AppReason.String() + result.RefuseReasonSys = resp.SysReason.String() + if desc, err := DecodeDescriptor(result.RefuseErrorRaw); err == nil { + result.RefuseError = desc + if versions := desc.GetValues("DESCRIPTION.VSNNUM"); len(versions) > 0 { + // If there are multiple VSNNUMs, we only care about the first. + decVersion := versions[0] + if intVersion, err := strconv.ParseUint(decVersion, 10, 32); err == nil { + result.RefuseVersion = ReleaseVersion(intVersion).String() + } + } + } return &result, nil default: return &result, ErrUnexpectedResponse @@ -177,12 +209,13 @@ func (conn *Connection) Connect(connectDescriptor string) (*HandshakeLog, error) // TODO: Unclear what these do. Taken from my client. result.AcceptVersion = accept.Version result.GlobalServiceOptions = accept.GlobalServiceOptions.Set() - result.ConnectFlags = [2]map[string]bool{ - accept.ConnectFlags0.Set(), - accept.ConnectFlags1.Set(), - } + result.ConnectFlags0 = accept.ConnectFlags0.Set() + result.ConnectFlags1 = accept.ConnectFlags1.Set() + // uint32 PID + uint32 ?? - supervisorBytes0 := []byte{0x00, 0x00, 0x04, 0xec, 0x19, 0x2c, 0x7b, 0x4c} + // In real clients, seems to be a small u32 followed by some kind of u32 + // counter/timestamp. + supervisorBytes0 := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} supervisorBytes1 := []byte{ 0xde, 0xad, 0xbe, 0xef, diff --git a/modules/oracle/types.go b/modules/oracle/types.go index ef5f7d6..83be4a5 100644 --- a/modules/oracle/types.go +++ b/modules/oracle/types.go @@ -153,7 +153,7 @@ type TNSDriver struct { // EncodePacket encodes the packet (header + body). If header is nil, create one // with no flags and the type set to the body's type. If header.Length == 0, set // it to the appropriate value (length of encoded body + 8). -func (driver *TNSDriver) EncodePacket(packet *TNSPacket) []byte { +func (driver *TNSDriver) EncodePacket(packet *TNSPacket) ([]byte, error) { body := packet.Body.Encode() if packet.Header == nil { packet.Header = &TNSHeader{ @@ -170,7 +170,7 @@ func (driver *TNSDriver) EncodePacket(packet *TNSPacket) []byte { // It is up to the user to check the body length for overflows before calling Encode if driver.Mode == TNSModeOld { if (len(body) + 8) > 0xffff { - panic(fmt.Errorf("Body too large to fit into 16-bit length (%d bytes)", len(body))) + return nil, ErrInvalidInput } packet.Header.Length = uint32(len(body) + 8) } else { @@ -178,7 +178,7 @@ func (driver *TNSDriver) EncodePacket(packet *TNSPacket) []byte { } } header := packet.Header.Encode() - return append(header, body...) + return append(header, body...), nil } // TNSFlags is the type for the TNS header's flags. @@ -831,6 +831,11 @@ func ReadTNSAccept(reader io.Reader, header *TNSHeader) (*TNSAccept, error) { // TODO: details. type RefuseReason uint8 +func (reason RefuseReason) String() string { + // TODO: Get better const error mappings. AppReason = 0x22 = syntax error? + return fmt.Sprintf("0x%02x", uint8(reason)) +} + // TNSRefuse is returned by the server when (...TODO: details -- not returned on // failed auth from TNSConnect). type TNSRefuse struct { diff --git a/modules/oracle/types_test.go b/modules/oracle/types_test.go index 4d1b96a..2f42e07 100644 --- a/modules/oracle/types_test.go +++ b/modules/oracle/types_test.go @@ -2,6 +2,7 @@ package oracle import ( "bytes" + "encoding/binary" "encoding/hex" "encoding/json" "strconv" @@ -482,7 +483,10 @@ func TestTNSConnect(t *testing.T) { driver := getTNSDriver() for tag, info := range validTNSConnect { bin := fromHex(info.Encoding) - encoded := driver.EncodePacket(info.Value) + encoded, err := driver.EncodePacket(info.Value) + if err != nil { + t.Fatalf("%s: TNSConnect Error encoding packet: %v", tag, err) + } if !bytes.Equal(bin, encoded) { t.Errorf("%s: TNSConnect.Encode mismatch:[\n%s\n]", tag, interleave(bin, encoded)) } @@ -511,7 +515,10 @@ func TestTNSAccept(t *testing.T) { driver := getTNSDriver() for tag, info := range validTNSAccept { bin := fromHex(info.Encoding) - encoded := driver.EncodePacket(info.Value) + encoded, err := driver.EncodePacket(info.Value) + if err != nil { + t.Fatalf("%s: TNSAccept Error encoding packet: %v", tag, err) + } if !bytes.Equal(bin, encoded) { t.Errorf("%s: TNSAccept.Encode mismatch:[\n%s\n]", tag, interleave(bin, encoded)) } @@ -540,7 +547,10 @@ func TestTNSData(t *testing.T) { driver := getTNSDriver() for tag, info := range validTNSData { bin := fromHex(info.Encoding) - encoded := driver.EncodePacket(info.Value) + encoded, err := driver.EncodePacket(info.Value) + if err != nil { + t.Fatalf("%s: TNSData Error encoding packet: %v", tag, err) + } if !bytes.Equal(bin, encoded) { t.Errorf("%s: TNSData.Encode mismatch:[\n%s\n]", tag, interleave(bin, encoded)) } @@ -753,3 +763,52 @@ func TestDecodeDescriptor(t *testing.T) { } } } + +var releaseVersions = map[string]ReleaseVersion{ + "1.2.3.4.5": ReleaseVersion(0x01230405), + "0.0.0.0.0": ReleaseVersion(0), + "255.15.15.255.255": ReleaseVersion(0xFFFFFFFF), +} + +var badReleaseVersions = []string{ + "", + "1", + "1.2", + "1.2.3", + "1.2.3.4", + "256.0.0.0.0", + "0.16.0.0.0", + "0.0.16.0.0", + "0.0.0.256.0", + "0.0.0.0.256", + "a.b.c.d.e", + "A.B.C.D.E", + "p.q.r.s.t", +} + +func TestReleaseVersion(t *testing.T) { + expectedBytes := make([]byte, 4) + for stringValue, version := range releaseVersions { + actualString := version.String() + if stringValue != actualString { + t.Errorf("ReleaseVersion.String() failed: 0x%08x gave %s, expected %s", uint32(version), actualString, stringValue) + } + binary.BigEndian.PutUint32(expectedBytes, uint32(version)) + actualBytes := version.Bytes() + if !bytes.Equal(expectedBytes, actualBytes) { + t.Errorf("ReleaseVersion.Bytes() failed: 0x%08x gave %v, expected %v", uint32(version), actualBytes, expectedBytes) + } + encoded, err := EncodeReleaseVersion(stringValue) + if err != nil { + t.Fatalf("EncodeReleaseVersion(%s) failed: %v", stringValue, err) + } + if encoded != version { + t.Errorf("EncodeReleaseVersion(%s) failed: got 0x%08x, expected 0x%08x", stringValue, uint32(encoded), uint32(version)) + } + } + for _, bad := range badReleaseVersions { + if ret, err := EncodeReleaseVersion(bad); err == nil { + t.Errorf("Successfully encoded bad ReleaseVersion %s: 0x%08x", bad, uint32(ret)) + } + } +} diff --git a/schemas/oracle.py b/schemas/oracle.py index ec414cb..d3a6e1c 100644 --- a/schemas/oracle.py +++ b/schemas/oracle.py @@ -40,23 +40,32 @@ connect_flags = [ ] nsn_services = [ - "Authentication", - "Encryption", - "DataIntegrity", - "Supervisor", + "Authentication", + "Encryption", + "DataIntegrity", + "Supervisor", ] +parsed_descriptor = ListOf(SubRecord({ + "key": String(), + "value": String(), +})) + oracle_scan_response = SubRecord({ "result": SubRecord({ "handshake": SubRecord({ "accept_version": Unsigned16BitInteger(), "global_service_options": flagsSet(global_service_options), - "connect_flags": List(flagsSet(connect_flags)), + "connect_flags0": flagsSet(connect_flags), + "connect_flags1": flagsSet(connect_flags), "did_resend": Boolean(), - "redirect_target": String(), - "refuse_error": String(), - # TODO: Finalize format of refuse_reason - "refuse_reason": String(), + "redirect_target_raw": String(), + "redirect_target": parsed_descriptor, + "refuse_error_raw": String(), + "refuse_error": parsed_descriptor, + "refuse_version": String(), + "refuse_reason_app": String(), + "refuse_reason_sys": String(), "nsn_version": String(), "nsn_service_versions": SubRecord({ "Authentication": String(),