pax_global_header00006660000000000000000000000064136642054740014525gustar00rootroot0000000000000052 comment=e1f43517bf3e343a50a4c1d9605cd8d4b6f7be1f rfc5424-0.1.0/000077500000000000000000000000001366420547400126145ustar00rootroot00000000000000rfc5424-0.1.0/.travis.yml000066400000000000000000000000421366420547400147210ustar00rootroot00000000000000language: go go: - 1.5 - 1.6 rfc5424-0.1.0/GoFuzz/000077500000000000000000000000001366420547400140405ustar00rootroot00000000000000rfc5424-0.1.0/GoFuzz/README.md000066400000000000000000000005711366420547400153220ustar00rootroot00000000000000Fuzz.go contains to methods that can be fuzzed using go-fuzz, https://github.com/dvyukov/go-fuzz. (1) Build and Fuzz ReadFrom method ``` go-fuzz-build -func=FuzzReadFrom go-fuzz -bin=GoFuzz-fuzz.zip -workdir=workdir -procs=1 ``` (2) Build and Fuzz UnmarshalBinary method ``` go-fuzz-build -func=FuzzUnmarshalBinary go-fuzz -bin=GoFuzz-fuzz.zip -workdir=workdir -procs=1 ```rfc5424-0.1.0/GoFuzz/fuzz.go000066400000000000000000000005531366420547400153700ustar00rootroot00000000000000package GoFuzz import ( "bytes" "github.com/crewjam/rfc5424" ) func FuzzReadFrom(data []byte) int { m := rfc5424.Message{} _, err := m.ReadFrom(bytes.NewReader(data)) if err != nil { return -1 } return 1 } func FuzzUnmarshalBinary(data []byte) int { m := rfc5424.Message{} err := m.UnmarshalBinary(data) if err != nil { return -1 } return 1 } rfc5424-0.1.0/GoFuzz/workdir/000077500000000000000000000000001366420547400155215ustar00rootroot00000000000000rfc5424-0.1.0/GoFuzz/workdir/corpus/000077500000000000000000000000001366420547400170345ustar00rootroot00000000000000rfc5424-0.1.0/GoFuzz/workdir/corpus/0af38e0e6c946ce5acf41c7bf5e602576d19f1a5-1000066400000000000000000000001231366420547400247150ustar00rootroot00000000000000<30>1 2020-03-11T17:00:19.935482Z myhostname someapp [foo@1234Revision="1.2.3.4"]rfc5424-0.1.0/GoFuzz/workdir/corpus/2f74283e9f408e369ee973fb8ca12da37fd54314-1000066400000000000000000000000371366420547400245210ustar00rootroot00000000000000<0>1 2020-03-11T7:00:.2Z []rfc5424-0.1.0/GoFuzz/workdir/corpus/data.txt000066400000000000000000000001441366420547400205050ustar00rootroot00000000000000<30>1 2020-03-11T17:00:19.935482Z myhostname someapp - - [foo@1234 Revision="1.2.3.4"] Hello, World!rfc5424-0.1.0/LICENSE000066400000000000000000000024441366420547400136250ustar00rootroot00000000000000BSD 2-Clause License Copyright (c) 2016, Ross Kinder All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. rfc5424-0.1.0/README.md000066400000000000000000000015671366420547400141040ustar00rootroot00000000000000 [![Build Status](https://travis-ci.org/crewjam/rfc5424.png)](https://travis-ci.org/crewjam/rfc5424) [![](https://godoc.org/github.com/crewjam/rfc5424?status.png)](http://godoc.org/github.com/crewjam/rfc5424) This is a Go library that can read and write RFC-5424 syslog messages: Example usage: m := rfc5424.Message{ Priority: rfc5424.Daemon | rfc5424.Info, Timestamp: time.Now(), Hostname: "myhostname", AppName: "someapp", Message: []byte("Hello, World!"), } m.AddDatum("foo@1234", "Revision", "1.2.3.4") m.WriteTo(os.Stdout) Produces output like: 107 <7>1 2016-02-28T09:57:10.804642398-05:00 myhostname someapp - - [foo@1234 Revision="1.2.3.4"] Hello, World! You can also use the library to parse syslog messages: m := rfc5424.Message{} _, err := m.ReadFrom(os.Stdin) fmt.Printf("%s\n", m.Message) rfc5424-0.1.0/example/000077500000000000000000000000001366420547400142475ustar00rootroot00000000000000rfc5424-0.1.0/example/main.go000066400000000000000000000010201366420547400155130ustar00rootroot00000000000000package main import ( "fmt" "os" "time" "github.com/crewjam/rfc5424" ) func writeMain() { m := rfc5424.Message{ Priority: rfc5424.Daemon | rfc5424.Info, Timestamp: time.Now(), Hostname: "myhostname", AppName: "someapp", Message: []byte("Hello, World!"), } m.AddDatum("foo@1234", "Revision", "1.2.3.4") m.WriteTo(os.Stdout) } func readMain() { m := rfc5424.Message{} _, err := m.ReadFrom(os.Stdin) if err != nil { fmt.Printf("%s\n", err) } fmt.Printf("%#v\n", m) } func main() { readMain() } rfc5424-0.1.0/go.mod000066400000000000000000000002711366420547400137220ustar00rootroot00000000000000module github.com/crewjam/rfc5424 go 1.13 require ( github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f ) rfc5424-0.1.0/go.sum000066400000000000000000000012371366420547400137520ustar00rootroot00000000000000github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= rfc5424-0.1.0/marshal.go000066400000000000000000000073241366420547400146000ustar00rootroot00000000000000package rfc5424 import ( "bytes" "fmt" "unicode/utf8" ) const rfc3339Micro = "2006-01-02T15:04:05.999999Z07:00" // allowLongSdNames is true to allow names longer than the RFC-specified limit // of 32-characters. (When true, this violates RFC-5424). const allowLongSdNames = true // ErrInvalidValue is returned when a log message cannot be emitted because one // of the values is invalid. type ErrInvalidValue struct { Property string Value interface{} } func (e ErrInvalidValue) Error() string { return fmt.Sprintf("Message cannot be serialized because %s is invalid: %v", e.Property, e.Value) } // invalidValue returns an invalid value error with the given property func invalidValue(property string, value interface{}) error { return ErrInvalidValue{Property: property, Value: value} } func nilify(x string) string { if x == "" { return "-" } return x } func escapeSDParam(s string) string { escapeCount := 0 for i := 0; i < len(s); i++ { switch s[i] { case '\\', '"', ']': escapeCount++ } } if escapeCount == 0 { return s } t := make([]byte, len(s)+escapeCount) j := 0 for i := 0; i < len(s); i++ { switch c := s[i]; c { case '\\', '"', ']': t[j] = '\\' t[j+1] = c j += 2 default: t[j] = s[i] j++ } } return string(t) } func isPrintableUsASCII(s string) bool { for _, ch := range s { if ch < 33 || ch > 126 { return false } } return true } func isValidSdName(s string) bool { if !allowLongSdNames && len(s) > 32 { return false } for _, ch := range s { if ch < 33 || ch > 126 { return false } if ch == '=' || ch == ']' || ch == '"' { return false } } return true } func (m Message) assertValid() error { // HOSTNAME = NILVALUE / 1*255PRINTUSASCII if !isPrintableUsASCII(m.Hostname) { return invalidValue("Hostname", m.Hostname) } if len(m.Hostname) > 255 { return invalidValue("Hostname", m.Hostname) } // APP-NAME = NILVALUE / 1*48PRINTUSASCII if !isPrintableUsASCII(m.AppName) { return invalidValue("AppName", m.AppName) } if len(m.AppName) > 48 { return invalidValue("AppName", m.AppName) } // PROCID = NILVALUE / 1*128PRINTUSASCII if !isPrintableUsASCII(m.ProcessID) { return invalidValue("ProcessID", m.ProcessID) } if len(m.ProcessID) > 128 { return invalidValue("ProcessID", m.ProcessID) } // MSGID = NILVALUE / 1*32PRINTUSASCII if !isPrintableUsASCII(m.MessageID) { return invalidValue("MessageID", m.MessageID) } if len(m.MessageID) > 32 { return invalidValue("MessageID", m.MessageID) } for _, sdElement := range m.StructuredData { if !isValidSdName(sdElement.ID) { return invalidValue("StructuredData/ID", sdElement.ID) } for _, sdParam := range sdElement.Parameters { if !isValidSdName(sdParam.Name) { return invalidValue("StructuredData/Name", sdParam.Name) } if !utf8.ValidString(sdParam.Value) { return invalidValue("StructuredData/Value", sdParam.Value) } } } return nil } // MarshalBinary marshals the message to a byte slice, or returns an error func (m Message) MarshalBinary() ([]byte, error) { if err := m.assertValid(); err != nil { return nil, err } b := bytes.NewBuffer(nil) fmt.Fprintf(b, "<%d>1 %s %s %s %s %s ", m.Priority, m.Timestamp.Format(rfc3339Micro), nilify(m.Hostname), nilify(m.AppName), nilify(m.ProcessID), nilify(m.MessageID)) if len(m.StructuredData) == 0 { fmt.Fprint(b, "-") } for _, sdElement := range m.StructuredData { fmt.Fprintf(b, "[%s", sdElement.ID) for _, sdParam := range sdElement.Parameters { fmt.Fprintf(b, " %s=\"%s\"", sdParam.Name, escapeSDParam(sdParam.Value)) } fmt.Fprintf(b, "]") } if len(m.Message) > 0 { fmt.Fprint(b, " ") b.Write(m.Message) } return b.Bytes(), nil } rfc5424-0.1.0/marshal_test.go000066400000000000000000000225151366420547400156360ustar00rootroot00000000000000package rfc5424 import ( "fmt" "time" . "gopkg.in/check.v1" ) var _ = Suite(&MarshalTest{}) type MarshalTest struct { } func T(s string) time.Time { rv, err := time.Parse(rfc3339Micro, s) if err != nil { panic(err) } return rv } var testCases = []struct { in Message expected string }{ // RFC-5424 Example 1 {Message{ Priority: 34, Timestamp: T("2003-10-11T22:14:15.003Z"), Hostname: "mymachine.example.com", AppName: "su", MessageID: "ID47", StructuredData: []StructuredData{}, Message: []byte("'su root' failed for lonvick on /dev/pts/8"), }, `<34>1 2003-10-11T22:14:15.003Z mymachine.example.com su - ID47 - 'su root' failed for lonvick on /dev/pts/8`}, // RFC-5424 Example 2 {Message{ Priority: 165, Timestamp: T("2003-08-24T05:14:15.000003-07:00"), Hostname: "192.0.2.1", AppName: "myproc", ProcessID: "8710", StructuredData: []StructuredData{}, Message: []byte("%% It's time to make the do-nuts."), }, `<165>1 2003-08-24T05:14:15.000003-07:00 192.0.2.1 myproc 8710 - - %% It's time to make the do-nuts.`}, // RFC-5424 Example 3 {Message{ Priority: 165, Timestamp: T("2003-10-11T22:14:15.003Z"), Hostname: "mymachine.example.com", AppName: "evntslog", MessageID: "ID47", StructuredData: []StructuredData{ StructuredData{ ID: "exampleSDID@32473", Parameters: []SDParam{ SDParam{ Name: "iut", Value: "3", }, SDParam{ Name: "eventSource", Value: "Application", }, SDParam{ Name: "eventID", Value: "1011", }, }, }, }, Message: []byte("An application event log entry..."), }, `<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"] An application event log entry...`}, // RFC-5424 Example 4 {Message{ Priority: 165, Timestamp: T("2003-10-11T22:14:15.003Z"), Hostname: "mymachine.example.com", AppName: "evntslog", MessageID: "ID47", StructuredData: []StructuredData{ StructuredData{ ID: "exampleSDID@32473", Parameters: []SDParam{ SDParam{ Name: "iut", Value: "3", }, SDParam{ Name: "eventSource", Value: "Application", }, SDParam{ Name: "eventID", Value: "1011", }, }, }, StructuredData{ ID: "examplePriority@32473", Parameters: []SDParam{ SDParam{ Name: "class", Value: "high", }, }, }, }, }, `<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"][examplePriority@32473 class="high"]`}, {Message{ Timestamp: T("0000-12-31T00:00:00Z"), StructuredData: []StructuredData{ StructuredData{ ID: "x@1", Parameters: []SDParam{ SDParam{ Name: "class", Value: `backslash=\ quote=" right bracket=] left bracket=[`, }, }, }, }, }, `<0>1 0000-12-31T00:00:00Z - - - - [x@1 class="backslash=\\ quote=\" right bracket=\] left bracket=["]`}, {Message{ Timestamp: T("0000-12-31T00:00:00Z"), StructuredData: []StructuredData{}, }, `<0>1 0000-12-31T00:00:00Z - - - - -`}, {Message{ Timestamp: T("0000-12-31T00:00:00Z"), StructuredData: []StructuredData{ StructuredData{ ID: "x@1", Parameters: []SDParam{ SDParam{ Name: "", Value: "value", }, }, }, }, }, `<0>1 0000-12-31T00:00:00Z - - - - [x@1 ="value"]`}, } func (s *MarshalTest) TestCanMarshalAndUnmarshal(c *C) { for _, tt := range testCases { actual, err := tt.in.MarshalBinary() c.Assert(err, IsNil) c.Assert(string(actual), Equals, tt.expected) m := Message{} err = m.UnmarshalBinary(actual) if err != nil { c.Logf(": %s", actual) c.Logf(": %#v", m) } c.Assert(err, IsNil) c.Assert(m, DeepEquals, tt.in) } } // These two strings form the basis of the invalidStrings below. (We change to // make sure they are valid to we know our tests are sensitive the way we want // them to be. var validStrings = [][]byte{ []byte(`<34>1 2003-10-11T22:14:15.003Z mymachine.example.com su X ID47 - msg`), []byte(`<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [id name="value"]`), } var invalidStrings = [][]byte{ []byte(``), []byte(`<`), []byte(`<3`), []byte(`<34>`), []byte(`<34>1`), []byte(`<34>1 `), []byte(`<34>1 2003-10-11T22:14:15.003Z`), []byte(`<34>1 2003-10-11T22:14:15.003Z `), []byte(`<34>1 2003-10-11T22:14:15.003Z mymachine.example.com`), []byte(`<34>1 2003-10-11T22:14:15.003Z mymachine.example.com su`), []byte(`<34>1 2003-10-11T22:14:15.003Z mymachine.example.com su X`), []byte(`<34>1 2003-10-11T22:14:15.003Z mymachine.example.com su X ID47`), []byte(`1 2003-10-11T22:14:15.003Z mymachine.example.com su - ID47 - msg`), []byte(`<34>X 2003-10-11T22:14:15.003Z mymachine.example.com su - ID47 - msg`), []byte(`<34>1 notATimestamp mymachine.example.com su - ID47 - 'su root' failed for lonvick on /dev/pts/8`), []byte(`>34<1 2003-10-11T22:14:15.003Z mymachine.example.com su X ID47 - msg`), []byte(`<3499999999999999999999999999999999>1 2003-10-11T22:14:15.003Z mymachine.example.com su X ID47 - msg`), []byte(`<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47`), []byte(`<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 `), []byte(`<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 ]`), []byte(`<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [`), []byte(`<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [id`), []byte(`<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [id name=`), []byte(`<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [id name="]`), []byte(`<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [id name="value`), []byte(`<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [id name="value"`), []byte(`<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [id name="value"x]`), []byte(`<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [id name="value\`), } func (s *MarshalTest) TestCannotUnmarshalBrokenStrings(c *C) { for _, actual := range validStrings { m := Message{} err := m.UnmarshalBinary(actual) c.Assert(err, IsNil) } for _, actual := range invalidStrings { m := Message{} err := m.UnmarshalBinary(actual) if err == nil { c.Logf(": %s", actual) c.Logf(": %#v", m) } c.Assert(err, Not(IsNil)) c.Assert(fmt.Sprintf("%s", err), Not(Equals), "") } } var invalidMessages = []Message{ Message{Hostname: "\x7f"}, Message{Hostname: "\x20"}, Message{Hostname: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAA", }, Message{AppName: "\x7f"}, Message{AppName: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}, Message{ProcessID: "\x7f"}, Message{ProcessID: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAA", }, Message{MessageID: "\x7f"}, Message{MessageID: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}, Message{ StructuredData: []StructuredData{ StructuredData{ ID: "\x20", Parameters: []SDParam{SDParam{Name: "", Value: "value"}}, }, }, }, Message{ StructuredData: []StructuredData{ StructuredData{ ID: "\x7f", Parameters: []SDParam{SDParam{Name: "", Value: "value"}}, }, }, }, Message{ StructuredData: []StructuredData{ StructuredData{ ID: "foo=bar", Parameters: []SDParam{SDParam{Name: "", Value: "value"}}, }, }, }, Message{ StructuredData: []StructuredData{ StructuredData{ ID: "foo[bar]", Parameters: []SDParam{SDParam{Name: "", Value: "value"}}, }, }, }, Message{ StructuredData: []StructuredData{ StructuredData{ ID: `foo"bar`, Parameters: []SDParam{SDParam{Name: "", Value: "value"}}, }, }, }, Message{ StructuredData: []StructuredData{ StructuredData{ ID: `x@1`, Parameters: []SDParam{SDParam{Name: "\x7f", Value: "value"}}, }, }, }, Message{ StructuredData: []StructuredData{ StructuredData{ ID: `x@1`, Parameters: []SDParam{SDParam{Name: "x", Value: "\xc3\x28"}}, }, }, }, } func (s *MarshalTest) TestCannotMarshalInvalidMessages(c *C) { for i, m := range invalidMessages { bin, err := m.MarshalBinary() if err == nil { c.Logf(": %d", i) c.Logf(": %s", string(bin)) c.Logf(": %#v", m) } c.Assert(err, Not(IsNil)) c.Assert(fmt.Sprintf("%s", err), Not(Equals), "") } } func (s *MarshalTest) TestLongAttributes(c *C) { m := Message{ StructuredData: []StructuredData{ StructuredData{ ID: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Parameters: []SDParam{SDParam{Name: "", Value: "value"}}, }, }, } bin, err := m.MarshalBinary() if allowLongSdNames { c.Assert(err, IsNil) c.Assert(string(bin), Equals, "<0>1 0001-01-01T00:00:00Z - - - - [AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA =\"value\"]") } else { c.Assert(err, Not(IsNil)) c.Assert(fmt.Sprintf("%s", err), Not(Equals), "") } } rfc5424-0.1.0/message.go000066400000000000000000000035501366420547400145720ustar00rootroot00000000000000// Pacakge rfc5424 is a library for parsing and serializing RFC-5424 structured // syslog messages. // // Example usage: // // m := rfc5424.Message{ // Priority: rfc5424.Daemon | rfc5424.Info, // Timestamp: time.Now(), // Hostname: "myhostname", // AppName: "someapp", // Message: []byte("Hello, World!"), // } // m.AddDatum("foo@1234", "Revision", "1.2.3.4") // m.WriteTo(os.Stdout) // // Produces output like: // // 107 <7>1 2016-02-28T09:57:10.804642398-05:00 myhostname someapp - - [foo@1234 Revision="1.2.3.4"] Hello, World! // // You can also use the library to parse syslog messages: // // m := rfc5424.Message{} // _, err := m.ReadFrom(os.Stdin) // fmt.Printf("%s\n", m.Message) package rfc5424 import "time" // Message represents a log message as defined by RFC-5424 // (https://tools.ietf.org/html/rfc5424) type Message struct { Priority Priority Timestamp time.Time Hostname string AppName string ProcessID string MessageID string StructuredData []StructuredData Message []byte } // SDParam represents parameters for structured data type SDParam struct { Name string Value string } // StructuredData represents structured data within a log message type StructuredData struct { ID string Parameters []SDParam } // AddDatum adds structured data to a log message func (m *Message) AddDatum(ID string, Name string, Value string) { if m.StructuredData == nil { m.StructuredData = []StructuredData{} } for i, sd := range m.StructuredData { if sd.ID == ID { sd.Parameters = append(sd.Parameters, SDParam{Name: Name, Value: Value}) m.StructuredData[i] = sd return } } m.StructuredData = append(m.StructuredData, StructuredData{ ID: ID, Parameters: []SDParam{ SDParam{ Name: Name, Value: Value, }, }, }) } rfc5424-0.1.0/message_test.go000066400000000000000000000026641366420547400156360ustar00rootroot00000000000000package rfc5424 import . "gopkg.in/check.v1" var _ = Suite(&MessageTest{}) type MessageTest struct { } func (s *MessageTest) TestAddDatum(c *C) { m := Message{} m.AddDatum("id", "name", "value") c.Assert(m, DeepEquals, Message{ StructuredData: []StructuredData{ StructuredData{ ID: "id", Parameters: []SDParam{ SDParam{"name", "value"}, }, }, }, }) m.AddDatum("id2", "name", "value") c.Assert(m, DeepEquals, Message{ StructuredData: []StructuredData{ StructuredData{ ID: "id", Parameters: []SDParam{ SDParam{"name", "value"}, }, }, StructuredData{ ID: "id2", Parameters: []SDParam{ SDParam{"name", "value"}, }, }, }, }) m.AddDatum("id", "name2", "value2") c.Assert(m, DeepEquals, Message{ StructuredData: []StructuredData{ StructuredData{ ID: "id", Parameters: []SDParam{ SDParam{"name", "value"}, SDParam{"name2", "value2"}, }, }, StructuredData{ ID: "id2", Parameters: []SDParam{ SDParam{"name", "value"}, }, }, }, }) m.AddDatum("id", "name", "value3") c.Assert(m, DeepEquals, Message{ StructuredData: []StructuredData{ StructuredData{ ID: "id", Parameters: []SDParam{ SDParam{"name", "value"}, SDParam{"name2", "value2"}, SDParam{"name", "value3"}, }, }, StructuredData{ ID: "id2", Parameters: []SDParam{ SDParam{"name", "value"}, }, }, }, }) } rfc5424-0.1.0/rfc5424.go000066400000000000000000000005361366420547400142400ustar00rootroot00000000000000package rfc5424 const severityMask = 0x07 const facilityMask = 0xf8 type Priority int const ( Emergency Priority = iota Alert Crit Error Warning Notice Info Debug ) const ( Kern Priority = iota << 3 User Mail Daemon Auth Syslog Lpr News Uucp Cron Authpriv Ftp Local0 Local1 Local2 Local3 Local4 Local5 Local6 Local7 ) rfc5424-0.1.0/setup_test.go000066400000000000000000000002261366420547400153420ustar00rootroot00000000000000package rfc5424 import ( "testing" . "gopkg.in/check.v1" ) // Hook up gocheck into the "go test" runner. func Test(t *testing.T) { TestingT(t) } rfc5424-0.1.0/stream.go000066400000000000000000000025301366420547400144360ustar00rootroot00000000000000package rfc5424 import ( "fmt" "io" "io/ioutil" "strconv" ) // WriteTo writes the message to a stream of messages in the style defined // by RFC-5425. (It does not implement the TLS stuff described in the RFC, just // the length delimiting. func (m Message) WriteTo(w io.Writer) (int64, error) { b, err := m.MarshalBinary() if err != nil { return 0, err } n, err := fmt.Fprintf(w, "%d %s", len(b), b) return int64(n), err } func readUntilSpace(r io.Reader) ([]byte, int, error) { buf := []byte{} nbytes := 0 for { b := []byte{0} n, err := r.Read(b) nbytes += n if err != nil { return nil, nbytes, err } if b[0] == ' ' { return buf, nbytes, nil } buf = append(buf, b...) } } // ReadFrom reads a single record from an RFC-5425 style stream of messages func (m *Message) ReadFrom(r io.Reader) (int64, error) { lengthBuf, n1, err := readUntilSpace(r) if err != nil { return 0, err } length, err := strconv.Atoi(string(lengthBuf)) if err != nil { return 0, err } r2 := io.LimitReader(r, int64(length)) buf, err := ioutil.ReadAll(r2) if err != nil { return int64(n1 + len(buf)), err } if len(buf) != int(length) { return int64(n1 + len(buf)), fmt.Errorf("Expected to read %d bytes, got %d", length, len(buf)) } err = m.UnmarshalBinary(buf) if err != nil { return 0, err } return int64(n1 + len(buf)), err } rfc5424-0.1.0/stream_test.go000066400000000000000000000026651366420547400155060ustar00rootroot00000000000000package rfc5424 import ( "bytes" . "gopkg.in/check.v1" ) var _ = Suite(&StreamTest{}) type StreamTest struct { } func (s *StreamTest) TestCanReadAndWrite(c *C) { stream := bytes.Buffer{} for i := 0; i < 4; i++ { m := Message{Priority: Priority(i), Timestamp: T("0000-12-31T00:00:00Z")} nbytes, err := m.WriteTo(&stream) c.Assert(err, IsNil) c.Assert(nbytes, Equals, int64(38)) } c.Assert(string(stream.Bytes()), Equals, `35 <0>1 0000-12-31T00:00:00Z - - - - -`+ `35 <1>1 0000-12-31T00:00:00Z - - - - -`+ `35 <2>1 0000-12-31T00:00:00Z - - - - -`+ `35 <3>1 0000-12-31T00:00:00Z - - - - -`) for i := 0; i < 4; i++ { m := Message{Priority: Priority(i << 3)} nbytes, err := m.ReadFrom(&stream) c.Assert(err, IsNil) c.Assert(nbytes, Equals, int64(38)) c.Assert(m, DeepEquals, Message{Priority: Priority(i), Timestamp: T("0000-12-31T00:00:00Z"), StructuredData: []StructuredData{}}) } } func (s *StreamTest) TestRejectsInvalidStream(c *C) { stream := bytes.NewBufferString(`99 <0>1 0000-12-31T00:00:00Z - - - - -`) for i := 0; i < 4; i++ { m := Message{Priority: Priority(i << 3)} _, err := m.ReadFrom(stream) c.Assert(err, Not(IsNil)) } } func (s *StreamTest) TestRejectsInvalidStream2(c *C) { stream := bytes.NewBufferString(`0 <0>1 0000-12-31T00:00:00Z - - - - -`) for i := 0; i < 4; i++ { m := Message{Priority: Priority(i << 3)} _, err := m.ReadFrom(stream) c.Assert(err, Not(IsNil)) } } rfc5424-0.1.0/unmarshal.go000066400000000000000000000253311366420547400151410ustar00rootroot00000000000000package rfc5424 import ( "bytes" "fmt" "io" "strconv" "time" "unicode" ) // ErrBadFormat is the error that is returned when a log message cannot be parsed type ErrBadFormat struct { Property string } func (e ErrBadFormat) Error() string { return fmt.Sprintf("Message cannot be unmarshaled because it is not well formed (%s)", e.Property) } // badFormat returns a bad format error with the given property func badFormat(property string) error { return ErrBadFormat{Property: property} } // UnmarshalBinary unmarshals a byte slice into a message func (m *Message) UnmarshalBinary(inputBuffer []byte) error { r := bytes.NewBuffer(inputBuffer) // RFC-5424 // SYSLOG-MSG = HEADER SP STRUCTURED-DATA [SP MSG] if err := m.readHeader(r); err != nil { return err } if err := readSpace(r); err != nil { return err // unreachable } if err := m.readStructuredData(r); err != nil { return err } // MSG is optional ch, _, err := r.ReadRune() if err == io.EOF { return nil } else if ch != ' ' { return badFormat("MSG") // unreachable } // TODO(ross): detect and handle UTF-8 BOM (\xef\xbb\xbf) // // MSG = MSG-ANY / MSG-UTF8 // MSG-ANY = *OCTET ; not starting with BOM // MSG-UTF8 = BOM UTF-8-STRING // BOM = %xEF.BB.BF // To be on the safe side, remaining stuff is copied over m.Message = copyFrom(r.Bytes()) return nil } // readHeader reads a HEADER as defined in RFC-5424 // // HEADER = PRI VERSION SP TIMESTAMP SP HOSTNAME // SP APP-NAME SP PROCID SP MSGID // PRI = "<" PRIVAL ">" // PRIVAL = 1*3DIGIT ; range 0 .. 191 // VERSION = NONZERO-DIGIT 0*2DIGIT // HOSTNAME = NILVALUE / 1*255PRINTUSASCII // // APP-NAME = NILVALUE / 1*48PRINTUSASCII // PROCID = NILVALUE / 1*128PRINTUSASCII // MSGID = NILVALUE / 1*32PRINTUSASCII // // TIMESTAMP = NILVALUE / FULL-DATE "T" FULL-TIME // FULL-DATE = DATE-FULLYEAR "-" DATE-MONTH "-" DATE-MDAY // DATE-FULLYEAR = 4DIGIT // DATE-MONTH = 2DIGIT ; 01-12 // DATE-MDAY = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on // ; month/year // FULL-TIME = PARTIAL-TIME TIME-OFFSET // PARTIAL-TIME = TIME-HOUR ":" TIME-MINUTE ":" TIME-SECOND // [TIME-SECFRAC] // TIME-HOUR = 2DIGIT ; 00-23 // TIME-MINUTE = 2DIGIT ; 00-59 // TIME-SECOND = 2DIGIT ; 00-59 // TIME-SECFRAC = "." 1*6DIGIT // TIME-OFFSET = "Z" / TIME-NUMOFFSET // TIME-NUMOFFSET = ("+" / "-") TIME-HOUR ":" TIME-MINUTE // func (m *Message) readHeader(r io.RuneScanner) error { if err := m.readPriority(r); err != nil { return err } if err := m.readVersion(r); err != nil { return err } if err := readSpace(r); err != nil { return err // unreachable } if err := m.readTimestamp(r); err != nil { return err } if err := readSpace(r); err != nil { return err // unreachable } if err := m.readHostname(r); err != nil { return err } if err := readSpace(r); err != nil { return err // unreachable } if err := m.readAppName(r); err != nil { return err } if err := readSpace(r); err != nil { return err // unreachable } if err := m.readProcID(r); err != nil { return err } if err := readSpace(r); err != nil { return err // unreachable } if err := m.readMsgID(r); err != nil { return err } return nil } // readPriority reads the PRI as defined in RFC-5424 and assigns Severity and // Facility accordingly. func (m *Message) readPriority(r io.RuneScanner) error { ch, _, err := r.ReadRune() if err != nil { return err } if ch != '<' { return badFormat("Priority") } rv := &bytes.Buffer{} for { ch, _, err := r.ReadRune() if err != nil { return err } if unicode.IsDigit(ch) { rv.WriteRune(ch) continue } if ch != '>' { return badFormat("Priority") } // We have a complete integer expression priority, err := strconv.ParseInt(string(rv.Bytes()), 10, 32) if err != nil { return badFormat("Priority") } m.Priority = Priority(priority) return nil } } // readVersion reads the version string fails if it isn't `1` func (m *Message) readVersion(r io.RuneScanner) error { ch, _, err := r.ReadRune() if err != nil { return err } if ch != '1' { return badFormat("Version") } return nil } // readTimestamp reads a TIMESTAMP as defined in RFC-5424 and assigns // m.Timestamp // // TIMESTAMP = NILVALUE / FULL-DATE "T" FULL-TIME // FULL-DATE = DATE-FULLYEAR "-" DATE-MONTH "-" DATE-MDAY // DATE-FULLYEAR = 4DIGIT // DATE-MONTH = 2DIGIT ; 01-12 // DATE-MDAY = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on // ; month/year // FULL-TIME = PARTIAL-TIME TIME-OFFSET // PARTIAL-TIME = TIME-HOUR ":" TIME-MINUTE ":" TIME-SECOND // [TIME-SECFRAC] // TIME-HOUR = 2DIGIT ; 00-23 // TIME-MINUTE = 2DIGIT ; 00-59 // TIME-SECOND = 2DIGIT ; 00-59 // TIME-SECFRAC = "." 1*6DIGIT // TIME-OFFSET = "Z" / TIME-NUMOFFSET // TIME-NUMOFFSET = ("+" / "-") TIME-HOUR ":" TIME-MINUTE func (m *Message) readTimestamp(r io.RuneScanner) error { timestampString, err := readWord(r) if err != nil { return err } m.Timestamp, err = time.Parse(time.RFC3339, timestampString) if err != nil { return err } return nil } func (m *Message) readHostname(r io.RuneScanner) (err error) { m.Hostname, err = readWord(r) return err } func (m *Message) readAppName(r io.RuneScanner) (err error) { m.AppName, err = readWord(r) return err } func (m *Message) readProcID(r io.RuneScanner) (err error) { m.ProcessID, err = readWord(r) return err } func (m *Message) readMsgID(r io.RuneScanner) (err error) { m.MessageID, err = readWord(r) return err } // readStructuredData reads a STRUCTURED-DATA (as defined in RFC-5424) // from `r` and assigns the StructuredData member. // // STRUCTURED-DATA = NILVALUE / 1*SD-ELEMENT // SD-ELEMENT = "[" SD-ID *(SP SD-PARAM) "]" // SD-PARAM = PARAM-NAME "=" %d34 PARAM-VALUE %d34 // SD-ID = SD-NAME // PARAM-NAME = SD-NAME // PARAM-VALUE = UTF-8-STRING ; characters '"', '\' and ']' MUST be escaped. // SD-NAME = 1*32PRINTUSASCII except '=', SP, ']', %d34 (") func (m *Message) readStructuredData(r io.RuneScanner) (err error) { m.StructuredData = []StructuredData{} ch, _, err := r.ReadRune() if err != nil { return err } if ch == '-' { return nil } r.UnreadRune() for { ch, _, err := r.ReadRune() if err == io.EOF { return nil } else if err != nil { return err // hard to reach without underlying IO error } else if ch == ' ' { r.UnreadRune() return nil } else if ch == '[' { r.UnreadRune() sde, err := readSDElement(r) if err != nil { return err } m.StructuredData = append(m.StructuredData, sde) } else { return badFormat("StructuredData") } } } // readSDElement reads an SD-ELEMENT as defined by RFC-5424 // // SD-ELEMENT = "[" SD-ID *(SP SD-PARAM) "]" // SD-PARAM = PARAM-NAME "=" %d34 PARAM-VALUE %d34 // SD-ID = SD-NAME // PARAM-NAME = SD-NAME // PARAM-VALUE = UTF-8-STRING ; characters '"', '\' and ']' MUST be escaped. // SD-NAME = 1*32PRINTUSASCII except '=', SP, ']', %d34 (") func readSDElement(r io.RuneScanner) (element StructuredData, err error) { ch, _, err := r.ReadRune() if err != nil { return element, err // hard to reach without underlying IO error } if ch != '[' { return element, badFormat("StructuredData[]") // unreachable } element.ID, err = readSdID(r) if err != nil { return element, err } for { ch, _, err := r.ReadRune() if err != nil { return element, err } else if ch == ']' { return element, nil } else if ch == ' ' { param, err := readSdParam(r) if err != nil { return element, err } element.Parameters = append(element.Parameters, *param) } else { return element, badFormat("StructuredData[]") } } } // readSDID reads an SD-ID as defined by RFC-5424 // SD-ID = SD-NAME // SD-NAME = 1*32PRINTUSASCII except '=', SP, ']', %d34 (") func readSdID(r io.RuneScanner) (string, error) { rv := &bytes.Buffer{} for { ch, _, err := r.ReadRune() if err != nil { return "", err } if ch == ' ' || ch == ']' { r.UnreadRune() return string(rv.Bytes()), nil } rv.WriteRune(ch) } } // readSdParam reads an SD-PARAM as defined by RFC-5424 // SD-PARAM = PARAM-NAME "=" %d34 PARAM-VALUE %d34 // SD-ID = SD-NAME // PARAM-NAME = SD-NAME // PARAM-VALUE = UTF-8-STRING ; characters '"', '\' and ']' MUST be escaped. // SD-NAME = 1*32PRINTUSASCII except '=', SP, ']', %d34 (") func readSdParam(r io.RuneScanner) (sdp *SDParam, err error) { sdp = &SDParam{} sdp.Name, err = readSdParamName(r) if err != nil { return nil, err } ch, _, err := r.ReadRune() if err != nil { return nil, err // hard to reach } if ch != '=' { return nil, badFormat("StructuredData[].Parameters") // not reachable } sdp.Value, err = readSdParamValue(r) if err != nil { return nil, err } return sdp, nil } // readSdParam reads a PARAM-NAME as defined by RFC-5424 // SD-PARAM = PARAM-NAME "=" %d34 PARAM-VALUE %d34 // PARAM-NAME = SD-NAME // SD-NAME = 1*32PRINTUSASCII except '=', SP, ']', %d34 (") func readSdParamName(r io.RuneScanner) (string, error) { rv := &bytes.Buffer{} for { ch, _, err := r.ReadRune() if err != nil { return "", err } if ch == '=' { r.UnreadRune() return string(rv.Bytes()), nil } rv.WriteRune(ch) } } // readSdParamValue reads an PARAM-VALUE as defined by RFC-5424 // SD-PARAM = PARAM-NAME "=" %d34 PARAM-VALUE %d34 // PARAM-VALUE = UTF-8-STRING ; characters '"', '\' and ']' MUST be escaped. func readSdParamValue(r io.RuneScanner) (string, error) { ch, _, err := r.ReadRune() if err != nil { return "", err } if ch != '"' { return "", badFormat("StructuredData[].Parameters[]") // hard to reach } rv := &bytes.Buffer{} for { ch, _, err := r.ReadRune() if err != nil { return "", err } if ch == '\\' { ch, _, err := r.ReadRune() if err != nil { return "", err } rv.WriteRune(ch) continue } if ch == '"' { return string(rv.Bytes()), nil } rv.WriteRune(ch) } } // readSpace reads a single space func readSpace(r io.RuneScanner) error { ch, _, err := r.ReadRune() if err != nil { return err } if ch != ' ' { return badFormat("expected space") } return nil } // readWord reads `r` until it encounters a space (0x20) func readWord(r io.RuneScanner) (string, error) { rv := &bytes.Buffer{} for { ch, _, err := r.ReadRune() if err != nil { return "", err } else if ch != ' ' { rv.WriteRune(ch) } else { r.UnreadRune() rvString := string(rv.Bytes()) if rvString == "-" { rvString = "" } return rvString, nil } } } func copyFrom(in []byte ) []byte { out := make([]byte,len(in)) copy(out, in) return out }