diff options
Diffstat (limited to 'vendor/gopkg.in/mcuadros/go-syslog.v2')
16 files changed, 1856 insertions, 0 deletions
diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/.travis.yml b/vendor/gopkg.in/mcuadros/go-syslog.v2/.travis.yml new file mode 100644 index 000000000..4340e7079 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/.travis.yml @@ -0,0 +1,21 @@ +language: go +go: + - 1.4 + - 1.5 + - 1.6 + - 1.7 + - 1.8 + - 1.9 + - "1.10" + - "1.11" + - "1.12" + - tip + +matrix: + allow_failures: + - go: tip + +go_import_path: gopkg.in/mcuadros/go-syslog.v2 + +install: + - go get -v -t ./... diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/LICENSE b/vendor/gopkg.in/mcuadros/go-syslog.v2/LICENSE new file mode 100644 index 000000000..b31548967 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013 Máximo Cuadros + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/README.md b/vendor/gopkg.in/mcuadros/go-syslog.v2/README.md new file mode 100644 index 000000000..e17a0fae2 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/README.md @@ -0,0 +1,48 @@ +go-syslog [](https://travis-ci.org/mcuadros/go-syslog) [](hhttps://godoc.org/gopkg.in/mcuadros/go-syslog.v2) [](https://github.com/mcuadros/go-syslog/releases) +============================== + +Syslog server library for go, build easy your custom syslog server over UDP, TCP or Unix sockets using RFC3164, RFC6587 or RFC5424 + +Installation +------------ + +The recommended way to install go-syslog + +``` +go get gopkg.in/mcuadros/go-syslog.v2 +``` + +Examples +-------- + +How import the package + +```go +import "gopkg.in/mcuadros/go-syslog.v2" +``` + +Example of a basic syslog [UDP server](example/basic_udp.go): + +```go +channel := make(syslog.LogPartsChannel) +handler := syslog.NewChannelHandler(channel) + +server := syslog.NewServer() +server.SetFormat(syslog.RFC5424) +server.SetHandler(handler) +server.ListenUDP("0.0.0.0:514") +server.Boot() + +go func(channel syslog.LogPartsChannel) { + for logParts := range channel { + fmt.Println(logParts) + } +}(channel) + +server.Wait() +``` + +License +------- + +MIT, see [LICENSE](LICENSE) diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/doc.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/doc.go new file mode 100644 index 000000000..9ab6466a6 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/doc.go @@ -0,0 +1,5 @@ +/* +Syslog server library for go, build easy your custom syslog server +over UDP, TCP or Unix sockets using RFC3164, RFC5424 and RFC6587 +*/ +package syslog // import "gopkg.in/mcuadros/go-syslog.v2" diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/format/automatic.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/automatic.go new file mode 100644 index 000000000..2400db866 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/automatic.go @@ -0,0 +1,104 @@ +package format + +import ( + "bufio" + "bytes" + "strconv" + + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc3164" + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424" +) + +/* Selecting an 'Automatic' format detects incoming format (i.e. RFC3164 vs RFC5424) and Framing + * (i.e. RFC6587 s3.4.1 octet counting as described here as RFC6587, and either no framing or + * RFC6587 s3.4.2 octet stuffing / non-transparent framing, described here as either RFC3164 + * or RFC6587). + * + * In essence if you don't know which format to select, or have multiple incoming formats, this + * is the one to go for. There is a theoretical performance penalty (it has to look at a few bytes + * at the start of the frame), and a risk that you may parse things you don't want to parse + * (rogue syslog clients using other formats), so if you can be absolutely sure of your syslog + * format, it would be best to select it explicitly. + */ + +type Automatic struct{} + +const ( + detectedUnknown = iota + detectedRFC3164 = iota + detectedRFC5424 = iota + detectedRFC6587 = iota +) + +/* + * Will always fallback to rfc3164 (see section 4.3.3) + */ +func detect(data []byte) int { + // all formats have a sapce somewhere + if i := bytes.IndexByte(data, ' '); i > 0 { + pLength := data[0:i] + if _, err := strconv.Atoi(string(pLength)); err == nil { + return detectedRFC6587 + } + // are we starting with < + if data[0] != '<' { + return detectedRFC3164 + } + // is there a close angle bracket before the ' '? there should be + angle := bytes.IndexByte(data, '>') + if (angle < 0) || (angle >= i) { + return detectedRFC3164 + } + + // if a single digit immediately follows the angle bracket, then a space + // it is RFC5424, as RFC3164 must begin with a letter (month name) + if (angle+2 == i) && (data[angle+1] >= '0') && (data[angle+1] <= '9') { + return detectedRFC5424 + } else { + return detectedRFC3164 + } + } + // fallback to rfc 3164 section 4.3.3 + return detectedRFC3164 +} + +func (f *Automatic) GetParser(line []byte) LogParser { + switch format := detect(line); format { + case detectedRFC3164: + return &parserWrapper{rfc3164.NewParser(line)} + case detectedRFC5424: + return &parserWrapper{rfc5424.NewParser(line)} + default: + // If the line was an RFC6587 line, the splitter should already have removed the length, + // so one of the above two will be chosen if the line is correctly formed. However, it + // may have a second length illegally placed at the start, in which case the detector + // will return detectedRFC6587. The line may also simply be malformed after the length in + // which case we will have detectedUnknown. In this case we return the simplest parser so + // the illegally formatted line is properly handled + return &parserWrapper{rfc3164.NewParser(line)} + } +} + +func (f *Automatic) GetSplitFunc() bufio.SplitFunc { + return f.automaticScannerSplit +} + +func (f *Automatic) automaticScannerSplit(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + + switch format := detect(data); format { + case detectedRFC6587: + return rfc6587ScannerSplit(data, atEOF) + case detectedRFC3164, detectedRFC5424: + // the default + return bufio.ScanLines(data, atEOF) + default: + if err != nil { + return 0, nil, err + } + // Request more data + return 0, nil, nil + } +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/format/format.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/format.go new file mode 100644 index 000000000..f77eb107c --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/format.go @@ -0,0 +1,29 @@ +package format + +import ( + "bufio" + "time" + + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser" +) + +type LogParts map[string]interface{} + +type LogParser interface { + Parse() error + Dump() LogParts + Location(*time.Location) +} + +type Format interface { + GetParser([]byte) LogParser + GetSplitFunc() bufio.SplitFunc +} + +type parserWrapper struct { + syslogparser.LogParser +} + +func (w *parserWrapper) Dump() LogParts { + return LogParts(w.LogParser.Dump()) +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc3164.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc3164.go new file mode 100644 index 000000000..9ca80f7a7 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc3164.go @@ -0,0 +1,17 @@ +package format + +import ( + "bufio" + + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc3164" +) + +type RFC3164 struct{} + +func (f *RFC3164) GetParser(line []byte) LogParser { + return &parserWrapper{rfc3164.NewParser(line)} +} + +func (f *RFC3164) GetSplitFunc() bufio.SplitFunc { + return nil +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc5424.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc5424.go new file mode 100644 index 000000000..bb37c7ced --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc5424.go @@ -0,0 +1,17 @@ +package format + +import ( + "bufio" + + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424" +) + +type RFC5424 struct{} + +func (f *RFC5424) GetParser(line []byte) LogParser { + return &parserWrapper{rfc5424.NewParser(line)} +} + +func (f *RFC5424) GetSplitFunc() bufio.SplitFunc { + return nil +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc6587.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc6587.go new file mode 100644 index 000000000..e0eef9174 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/format/rfc6587.go @@ -0,0 +1,45 @@ +package format + +import ( + "bufio" + "bytes" + "strconv" + + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424" +) + +type RFC6587 struct{} + +func (f *RFC6587) GetParser(line []byte) LogParser { + return &parserWrapper{rfc5424.NewParser(line)} +} + +func (f *RFC6587) GetSplitFunc() bufio.SplitFunc { + return rfc6587ScannerSplit +} + +func rfc6587ScannerSplit(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + + if i := bytes.IndexByte(data, ' '); i > 0 { + pLength := data[0:i] + length, err := strconv.Atoi(string(pLength)) + if err != nil { + if string(data[0:1]) == "<" { + // Assume this frame uses non-transparent-framing + return len(data), data, nil + } + return 0, nil, err + } + end := length + i + 1 + if len(data) >= end { + // Return the frame with the length removed + return end, data[i+1 : end], nil + } + } + + // Request more data + return 0, nil, nil +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/handler.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/handler.go new file mode 100644 index 000000000..9914b3ea1 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/handler.go @@ -0,0 +1,35 @@ +package syslog + +import ( + "gopkg.in/mcuadros/go-syslog.v2/format" +) + +//The handler receive every syslog entry at Handle method +type Handler interface { + Handle(format.LogParts, int64, error) +} + +type LogPartsChannel chan format.LogParts + +//The ChannelHandler will send all the syslog entries into the given channel +type ChannelHandler struct { + channel LogPartsChannel +} + +//NewChannelHandler returns a new ChannelHandler +func NewChannelHandler(channel LogPartsChannel) *ChannelHandler { + handler := new(ChannelHandler) + handler.SetChannel(channel) + + return handler +} + +//The channel to be used +func (h *ChannelHandler) SetChannel(channel LogPartsChannel) { + h.channel = channel +} + +//Syslog entry receiver +func (h *ChannelHandler) Handle(logParts format.LogParts, messageLength int64, err error) { + h.channel <- logParts +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/LICENSE b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/LICENSE new file mode 100644 index 000000000..3e2dbb6f8 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2013, Jérôme Renard +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.
\ No newline at end of file diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/README.md b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/README.md new file mode 100644 index 000000000..ce59d376f --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/README.md @@ -0,0 +1,4 @@ +Syslogparser +============ + +This is a fork for [github.com/jeromer/syslogparser](https://github.com/jeromer/syslogparser), since this library is intensively used by `go-syslog`, now is integrated as a `internal` package. diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc3164/rfc3164.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc3164/rfc3164.go new file mode 100644 index 000000000..486a0cb54 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc3164/rfc3164.go @@ -0,0 +1,292 @@ +package rfc3164 + +import ( + "bytes" + "os" + "time" + + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser" +) + +type Parser struct { + buff []byte + cursor int + l int + priority syslogparser.Priority + version int + header header + message rfc3164message + location *time.Location + skipTag bool +} + +type header struct { + timestamp time.Time + hostname string +} + +type rfc3164message struct { + tag string + content string +} + +func NewParser(buff []byte) *Parser { + return &Parser{ + buff: buff, + cursor: 0, + l: len(buff), + location: time.UTC, + } +} + +func (p *Parser) Location(location *time.Location) { + p.location = location +} + +func (p *Parser) Parse() error { + tcursor := p.cursor + pri, err := p.parsePriority() + if err != nil { + // RFC3164 sec 4.3.3 + p.priority = syslogparser.Priority{13, syslogparser.Facility{Value: 1}, syslogparser.Severity{Value: 5}} + p.cursor = tcursor + content, err := p.parseContent() + p.header.timestamp = time.Now().Round(time.Second) + if err != syslogparser.ErrEOL { + return err + } + p.message = rfc3164message{content: content} + return nil + } + + tcursor = p.cursor + hdr, err := p.parseHeader() + if err == syslogparser.ErrTimestampUnknownFormat { + // RFC3164 sec 4.3.2. + hdr.timestamp = time.Now().Round(time.Second) + // No tag processing should be done + p.skipTag = true + // Reset cursor for content read + p.cursor = tcursor + } else if err != nil { + return err + } else { + p.cursor++ + } + + msg, err := p.parsemessage() + if err != syslogparser.ErrEOL { + return err + } + + p.priority = pri + p.version = syslogparser.NO_VERSION + p.header = hdr + p.message = msg + + return nil +} + +func (p *Parser) Dump() syslogparser.LogParts { + return syslogparser.LogParts{ + "timestamp": p.header.timestamp, + "hostname": p.header.hostname, + "tag": p.message.tag, + "content": p.message.content, + "priority": p.priority.P, + "facility": p.priority.F.Value, + "severity": p.priority.S.Value, + } +} + +func (p *Parser) parsePriority() (syslogparser.Priority, error) { + return syslogparser.ParsePriority(p.buff, &p.cursor, p.l) +} + +func (p *Parser) parseHeader() (header, error) { + hdr := header{} + var err error + + ts, err := p.parseTimestamp() + if err != nil { + return hdr, err + } + + hostname, err := p.parseHostname() + if err != nil { + return hdr, err + } + + hdr.timestamp = ts + hdr.hostname = hostname + + return hdr, nil +} + +func (p *Parser) parsemessage() (rfc3164message, error) { + msg := rfc3164message{} + var err error + + if !p.skipTag { + tag, err := p.parseTag() + if err != nil { + return msg, err + } + msg.tag = tag + } + + content, err := p.parseContent() + if err != syslogparser.ErrEOL { + return msg, err + } + + msg.content = content + + return msg, err +} + +// https://tools.ietf.org/html/rfc3164#section-4.1.2 +func (p *Parser) parseTimestamp() (time.Time, error) { + var ts time.Time + var err error + var tsFmtLen int + var sub []byte + + tsFmts := []string{ + time.Stamp, + time.RFC3339, + } + // if timestamps starts with numeric try formats with different order + // it is more likely that timestamp is in RFC3339 format then + if c := p.buff[p.cursor]; c > '0' && c < '9' { + tsFmts = []string{ + time.RFC3339, + time.Stamp, + } + } + + found := false + for _, tsFmt := range tsFmts { + tsFmtLen = len(tsFmt) + + if p.cursor+tsFmtLen > p.l { + continue + } + + sub = p.buff[p.cursor : tsFmtLen+p.cursor] + ts, err = time.ParseInLocation(tsFmt, string(sub), p.location) + if err == nil { + found = true + break + } + } + + if !found { + p.cursor = len(time.Stamp) + + // XXX : If the timestamp is invalid we try to push the cursor one byte + // XXX : further, in case it is a space + if (p.cursor < p.l) && (p.buff[p.cursor] == ' ') { + p.cursor++ + } + + return ts, syslogparser.ErrTimestampUnknownFormat + } + + fixTimestampIfNeeded(&ts) + + p.cursor += tsFmtLen + + if (p.cursor < p.l) && (p.buff[p.cursor] == ' ') { + p.cursor++ + } + + return ts, nil +} + +func (p *Parser) parseHostname() (string, error) { + oldcursor := p.cursor + hostname, err := syslogparser.ParseHostname(p.buff, &p.cursor, p.l) + if err == nil && len(hostname) > 0 && string(hostname[len(hostname)-1]) == ":" { // not an hostname! we found a GNU implementation of syslog() + p.cursor = oldcursor - 1 + myhostname, err := os.Hostname() + if err == nil { + return myhostname, nil + } + return "", nil + } + return hostname, err +} + +// http://tools.ietf.org/html/rfc3164#section-4.1.3 +func (p *Parser) parseTag() (string, error) { + var b byte + var endOfTag bool + var bracketOpen bool + var tag []byte + var err error + var found bool + + from := p.cursor + + for { + if p.cursor == p.l { + // no tag found, reset cursor for content + p.cursor = from + return "", nil + } + + b = p.buff[p.cursor] + bracketOpen = (b == '[') + endOfTag = (b == ':' || b == ' ') + + // XXX : parse PID ? + if bracketOpen { + tag = p.buff[from:p.cursor] + found = true + } + + if endOfTag { + if !found { + tag = p.buff[from:p.cursor] + found = true + } + + p.cursor++ + break + } + + p.cursor++ + } + + if (p.cursor < p.l) && (p.buff[p.cursor] == ' ') { + p.cursor++ + } + + return string(tag), err +} + +func (p *Parser) parseContent() (string, error) { + if p.cursor > p.l { + return "", syslogparser.ErrEOL + } + + content := bytes.Trim(p.buff[p.cursor:p.l], " ") + p.cursor += len(content) + + return string(content), syslogparser.ErrEOL +} + +func fixTimestampIfNeeded(ts *time.Time) { + now := time.Now() + y := ts.Year() + + if ts.Year() == 0 { + y = now.Year() + } + + newTs := time.Date(y, ts.Month(), ts.Day(), ts.Hour(), ts.Minute(), + ts.Second(), ts.Nanosecond(), ts.Location()) + + *ts = newTs +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424/rfc5424.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424/rfc5424.go new file mode 100644 index 000000000..bea8e5879 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424/rfc5424.go @@ -0,0 +1,606 @@ +// Note to self : never try to code while looking after your kids +// The result might look like this : https://pbs.twimg.com/media/BXqSuYXIEAAscVA.png + +package rfc5424 + +import ( + "fmt" + "math" + "strconv" + "time" + + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser" +) + +const ( + NILVALUE = '-' +) + +var ( + ErrYearInvalid = &syslogparser.ParserError{"Invalid year in timestamp"} + ErrMonthInvalid = &syslogparser.ParserError{"Invalid month in timestamp"} + ErrDayInvalid = &syslogparser.ParserError{"Invalid day in timestamp"} + ErrHourInvalid = &syslogparser.ParserError{"Invalid hour in timestamp"} + ErrMinuteInvalid = &syslogparser.ParserError{"Invalid minute in timestamp"} + ErrSecondInvalid = &syslogparser.ParserError{"Invalid second in timestamp"} + ErrSecFracInvalid = &syslogparser.ParserError{"Invalid fraction of second in timestamp"} + ErrTimeZoneInvalid = &syslogparser.ParserError{"Invalid time zone in timestamp"} + ErrInvalidTimeFormat = &syslogparser.ParserError{"Invalid time format"} + ErrInvalidAppName = &syslogparser.ParserError{"Invalid app name"} + ErrInvalidProcId = &syslogparser.ParserError{"Invalid proc ID"} + ErrInvalidMsgId = &syslogparser.ParserError{"Invalid msg ID"} + ErrNoStructuredData = &syslogparser.ParserError{"No structured data"} +) + +type Parser struct { + buff []byte + cursor int + l int + header header + structuredData string + message string +} + +type header struct { + priority syslogparser.Priority + version int + timestamp time.Time + hostname string + appName string + procId string + msgId string +} + +type partialTime struct { + hour int + minute int + seconds int + secFrac float64 +} + +type fullTime struct { + pt partialTime + loc *time.Location +} + +type fullDate struct { + year int + month int + day int +} + +func NewParser(buff []byte) *Parser { + return &Parser{ + buff: buff, + cursor: 0, + l: len(buff), + } +} + +func (p *Parser) Location(location *time.Location) { + // Ignore as RFC5424 syslog always has a timezone +} + +func (p *Parser) Parse() error { + hdr, err := p.parseHeader() + if err != nil { + return err + } + + p.header = hdr + + sd, err := p.parseStructuredData() + if err != nil { + return err + } + + p.structuredData = sd + p.cursor++ + + if p.cursor < p.l { + p.message = string(p.buff[p.cursor:]) + } + + return nil +} + +func (p *Parser) Dump() syslogparser.LogParts { + return syslogparser.LogParts{ + "priority": p.header.priority.P, + "facility": p.header.priority.F.Value, + "severity": p.header.priority.S.Value, + "version": p.header.version, + "timestamp": p.header.timestamp, + "hostname": p.header.hostname, + "app_name": p.header.appName, + "proc_id": p.header.procId, + "msg_id": p.header.msgId, + "structured_data": p.structuredData, + "message": p.message, + } +} + +// HEADER = PRI VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID +func (p *Parser) parseHeader() (header, error) { + hdr := header{} + + pri, err := p.parsePriority() + if err != nil { + return hdr, err + } + + hdr.priority = pri + + ver, err := p.parseVersion() + if err != nil { + return hdr, err + } + hdr.version = ver + p.cursor++ + + ts, err := p.parseTimestamp() + if err != nil { + return hdr, err + } + + hdr.timestamp = ts + p.cursor++ + + host, err := p.parseHostname() + if err != nil { + return hdr, err + } + + hdr.hostname = host + p.cursor++ + + appName, err := p.parseAppName() + if err != nil { + return hdr, err + } + + hdr.appName = appName + p.cursor++ + + procId, err := p.parseProcId() + if err != nil { + return hdr, nil + } + + hdr.procId = procId + p.cursor++ + + msgId, err := p.parseMsgId() + if err != nil { + return hdr, nil + } + + hdr.msgId = msgId + p.cursor++ + + return hdr, nil +} + +func (p *Parser) parsePriority() (syslogparser.Priority, error) { + return syslogparser.ParsePriority(p.buff, &p.cursor, p.l) +} + +func (p *Parser) parseVersion() (int, error) { + return syslogparser.ParseVersion(p.buff, &p.cursor, p.l) +} + +// https://tools.ietf.org/html/rfc5424#section-6.2.3 +func (p *Parser) parseTimestamp() (time.Time, error) { + var ts time.Time + + if p.cursor >= p.l { + return ts, ErrInvalidTimeFormat + } + + if p.buff[p.cursor] == NILVALUE { + p.cursor++ + return ts, nil + } + + fd, err := parseFullDate(p.buff, &p.cursor, p.l) + if err != nil { + return ts, err + } + + if p.cursor >= p.l || p.buff[p.cursor] != 'T' { + return ts, ErrInvalidTimeFormat + } + + p.cursor++ + + ft, err := parseFullTime(p.buff, &p.cursor, p.l) + if err != nil { + return ts, syslogparser.ErrTimestampUnknownFormat + } + + nSec, err := toNSec(ft.pt.secFrac) + if err != nil { + return ts, err + } + + ts = time.Date( + fd.year, + time.Month(fd.month), + fd.day, + ft.pt.hour, + ft.pt.minute, + ft.pt.seconds, + nSec, + ft.loc, + ) + + return ts, nil +} + +// HOSTNAME = NILVALUE / 1*255PRINTUSASCII +func (p *Parser) parseHostname() (string, error) { + return syslogparser.ParseHostname(p.buff, &p.cursor, p.l) +} + +// APP-NAME = NILVALUE / 1*48PRINTUSASCII +func (p *Parser) parseAppName() (string, error) { + return parseUpToLen(p.buff, &p.cursor, p.l, 48, ErrInvalidAppName) +} + +// PROCID = NILVALUE / 1*128PRINTUSASCII +func (p *Parser) parseProcId() (string, error) { + return parseUpToLen(p.buff, &p.cursor, p.l, 128, ErrInvalidProcId) +} + +// MSGID = NILVALUE / 1*32PRINTUSASCII +func (p *Parser) parseMsgId() (string, error) { + return parseUpToLen(p.buff, &p.cursor, p.l, 32, ErrInvalidMsgId) +} + +func (p *Parser) parseStructuredData() (string, error) { + return parseStructuredData(p.buff, &p.cursor, p.l) +} + +// ---------------------------------------------- +// https://tools.ietf.org/html/rfc5424#section-6 +// ---------------------------------------------- + +// XXX : bind them to Parser ? + +// FULL-DATE : DATE-FULLYEAR "-" DATE-MONTH "-" DATE-MDAY +func parseFullDate(buff []byte, cursor *int, l int) (fullDate, error) { + var fd fullDate + + year, err := parseYear(buff, cursor, l) + if err != nil { + return fd, err + } + + if *cursor >= l || buff[*cursor] != '-' { + return fd, syslogparser.ErrTimestampUnknownFormat + } + + *cursor++ + + month, err := parseMonth(buff, cursor, l) + if err != nil { + return fd, err + } + + if *cursor >= l || buff[*cursor] != '-' { + return fd, syslogparser.ErrTimestampUnknownFormat + } + + *cursor++ + + day, err := parseDay(buff, cursor, l) + if err != nil { + return fd, err + } + + fd = fullDate{ + year: year, + month: month, + day: day, + } + + return fd, nil +} + +// DATE-FULLYEAR = 4DIGIT +func parseYear(buff []byte, cursor *int, l int) (int, error) { + yearLen := 4 + + if *cursor+yearLen > l { + return 0, syslogparser.ErrEOL + } + + // XXX : we do not check for a valid year (ie. 1999, 2013 etc) + // XXX : we only checks the format is correct + sub := string(buff[*cursor : *cursor+yearLen]) + + *cursor += yearLen + + year, err := strconv.Atoi(sub) + if err != nil { + return 0, ErrYearInvalid + } + + return year, nil +} + +// DATE-MONTH = 2DIGIT ; 01-12 +func parseMonth(buff []byte, cursor *int, l int) (int, error) { + return syslogparser.Parse2Digits(buff, cursor, l, 1, 12, ErrMonthInvalid) +} + +// DATE-MDAY = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year +func parseDay(buff []byte, cursor *int, l int) (int, error) { + // XXX : this is a relaxed constraint + // XXX : we do not check if valid regarding February or leap years + // XXX : we only checks that day is in range [01 -> 31] + // XXX : in other words this function will not rant if you provide Feb 31th + return syslogparser.Parse2Digits(buff, cursor, l, 1, 31, ErrDayInvalid) +} + +// FULL-TIME = PARTIAL-TIME TIME-OFFSET +func parseFullTime(buff []byte, cursor *int, l int) (fullTime, error) { + var loc = new(time.Location) + var ft fullTime + + pt, err := parsePartialTime(buff, cursor, l) + if err != nil { + return ft, err + } + + loc, err = parseTimeOffset(buff, cursor, l) + if err != nil { + return ft, err + } + + ft = fullTime{ + pt: pt, + loc: loc, + } + + return ft, nil +} + +// PARTIAL-TIME = TIME-HOUR ":" TIME-MINUTE ":" TIME-SECOND[TIME-SECFRAC] +func parsePartialTime(buff []byte, cursor *int, l int) (partialTime, error) { + var pt partialTime + + hour, minute, err := getHourMinute(buff, cursor, l) + if err != nil { + return pt, err + } + + if *cursor >= l || buff[*cursor] != ':' { + return pt, ErrInvalidTimeFormat + } + + *cursor++ + + // ---- + + seconds, err := parseSecond(buff, cursor, l) + if err != nil { + return pt, err + } + + pt = partialTime{ + hour: hour, + minute: minute, + seconds: seconds, + } + + // ---- + + if *cursor >= l || buff[*cursor] != '.' { + return pt, nil + } + + *cursor++ + + secFrac, err := parseSecFrac(buff, cursor, l) + if err != nil { + return pt, nil + } + pt.secFrac = secFrac + + return pt, nil +} + +// TIME-HOUR = 2DIGIT ; 00-23 +func parseHour(buff []byte, cursor *int, l int) (int, error) { + return syslogparser.Parse2Digits(buff, cursor, l, 0, 23, ErrHourInvalid) +} + +// TIME-MINUTE = 2DIGIT ; 00-59 +func parseMinute(buff []byte, cursor *int, l int) (int, error) { + return syslogparser.Parse2Digits(buff, cursor, l, 0, 59, ErrMinuteInvalid) +} + +// TIME-SECOND = 2DIGIT ; 00-59 +func parseSecond(buff []byte, cursor *int, l int) (int, error) { + return syslogparser.Parse2Digits(buff, cursor, l, 0, 59, ErrSecondInvalid) +} + +// TIME-SECFRAC = "." 1*6DIGIT +func parseSecFrac(buff []byte, cursor *int, l int) (float64, error) { + maxDigitLen := 6 + + max := *cursor + maxDigitLen + from := *cursor + to := from + + for to = from; to < max; to++ { + if to >= l { + break + } + + c := buff[to] + if !syslogparser.IsDigit(c) { + break + } + } + + sub := string(buff[from:to]) + if len(sub) == 0 { + return 0, ErrSecFracInvalid + } + + secFrac, err := strconv.ParseFloat("0."+sub, 64) + *cursor = to + if err != nil { + return 0, ErrSecFracInvalid + } + + return secFrac, nil +} + +// TIME-OFFSET = "Z" / TIME-NUMOFFSET +func parseTimeOffset(buff []byte, cursor *int, l int) (*time.Location, error) { + + if *cursor >= l || buff[*cursor] == 'Z' { + *cursor++ + return time.UTC, nil + } + + return parseNumericalTimeOffset(buff, cursor, l) +} + +// TIME-NUMOFFSET = ("+" / "-") TIME-HOUR ":" TIME-MINUTE +func parseNumericalTimeOffset(buff []byte, cursor *int, l int) (*time.Location, error) { + var loc = new(time.Location) + + sign := buff[*cursor] + + if (sign != '+') && (sign != '-') { + return loc, ErrTimeZoneInvalid + } + + *cursor++ + + hour, minute, err := getHourMinute(buff, cursor, l) + if err != nil { + return loc, err + } + + tzStr := fmt.Sprintf("%s%02d:%02d", string(sign), hour, minute) + tmpTs, err := time.Parse("-07:00", tzStr) + if err != nil { + return loc, err + } + + return tmpTs.Location(), nil +} + +func getHourMinute(buff []byte, cursor *int, l int) (int, int, error) { + hour, err := parseHour(buff, cursor, l) + if err != nil { + return 0, 0, err + } + + if *cursor >= l || buff[*cursor] != ':' { + return 0, 0, ErrInvalidTimeFormat + } + + *cursor++ + + minute, err := parseMinute(buff, cursor, l) + if err != nil { + return 0, 0, err + } + + return hour, minute, nil +} + +func toNSec(sec float64) (int, error) { + _, frac := math.Modf(sec) + fracStr := strconv.FormatFloat(frac, 'f', 9, 64) + fracInt, err := strconv.Atoi(fracStr[2:]) + if err != nil { + return 0, err + } + + return fracInt, nil +} + +// ------------------------------------------------ +// https://tools.ietf.org/html/rfc5424#section-6.3 +// ------------------------------------------------ + +func parseStructuredData(buff []byte, cursor *int, l int) (string, error) { + var sdData string + var found bool + + if *cursor >= l { + return "-", nil + } + + if buff[*cursor] == NILVALUE { + *cursor++ + return "-", nil + } + + if buff[*cursor] != '[' { + return sdData, ErrNoStructuredData + } + + from := *cursor + to := from + + for to = from; to < l; to++ { + if found { + break + } + + b := buff[to] + + if b == ']' { + switch t := to + 1; { + case t == l: + found = true + case t <= l && buff[t] == ' ': + found = true + } + } + } + + if found { + *cursor = to + return string(buff[from:to]), nil + } + + return sdData, ErrNoStructuredData +} + +func parseUpToLen(buff []byte, cursor *int, l int, maxLen int, e error) (string, error) { + var to int + var found bool + var result string + + max := *cursor + maxLen + + for to = *cursor; (to <= max) && (to < l); to++ { + if buff[to] == ' ' { + found = true + break + } + } + + if found { + result = string(buff[*cursor:to]) + } else if to > max { + to = max // don't go past max + } + + *cursor = to + + if found { + return result, nil + } + + return "", e +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/syslogparser.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/syslogparser.go new file mode 100644 index 000000000..7a10820c7 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/syslogparser.go @@ -0,0 +1,213 @@ +package syslogparser + +import ( + "fmt" + "strconv" + "strings" + "time" +) + +const ( + PRI_PART_START = '<' + PRI_PART_END = '>' + + NO_VERSION = -1 +) + +var ( + ErrEOL = &ParserError{"End of log line"} + ErrNoSpace = &ParserError{"No space found"} + + ErrPriorityNoStart = &ParserError{"No start char found for priority"} + ErrPriorityEmpty = &ParserError{"Priority field empty"} + ErrPriorityNoEnd = &ParserError{"No end char found for priority"} + ErrPriorityTooShort = &ParserError{"Priority field too short"} + ErrPriorityTooLong = &ParserError{"Priority field too long"} + ErrPriorityNonDigit = &ParserError{"Non digit found in priority"} + + ErrVersionNotFound = &ParserError{"Can not find version"} + + ErrTimestampUnknownFormat = &ParserError{"Timestamp format unknown"} + + ErrHostnameTooShort = &ParserError{"Hostname field too short"} +) + +type LogParser interface { + Parse() error + Dump() LogParts + Location(*time.Location) +} + +type ParserError struct { + ErrorString string +} + +type Priority struct { + P int + F Facility + S Severity +} + +type Facility struct { + Value int +} + +type Severity struct { + Value int +} + +type LogParts map[string]interface{} + +// https://tools.ietf.org/html/rfc3164#section-4.1 +func ParsePriority(buff []byte, cursor *int, l int) (Priority, error) { + pri := newPriority(0) + + if l <= 0 { + return pri, ErrPriorityEmpty + } + + if buff[*cursor] != PRI_PART_START { + return pri, ErrPriorityNoStart + } + + i := 1 + priDigit := 0 + + for i < l { + if i >= 5 { + return pri, ErrPriorityTooLong + } + + c := buff[i] + + if c == PRI_PART_END { + if i == 1 { + return pri, ErrPriorityTooShort + } + + *cursor = i + 1 + return newPriority(priDigit), nil + } + + if IsDigit(c) { + v, e := strconv.Atoi(string(c)) + if e != nil { + return pri, e + } + + priDigit = (priDigit * 10) + v + } else { + return pri, ErrPriorityNonDigit + } + + i++ + } + + return pri, ErrPriorityNoEnd +} + +// https://tools.ietf.org/html/rfc5424#section-6.2.2 +func ParseVersion(buff []byte, cursor *int, l int) (int, error) { + if *cursor >= l { + return NO_VERSION, ErrVersionNotFound + } + + c := buff[*cursor] + *cursor++ + + // XXX : not a version, not an error though as RFC 3164 does not support it + if !IsDigit(c) { + return NO_VERSION, nil + } + + v, e := strconv.Atoi(string(c)) + if e != nil { + *cursor-- + return NO_VERSION, e + } + + return v, nil +} + +func IsDigit(c byte) bool { + return c >= '0' && c <= '9' +} + +func newPriority(p int) Priority { + // The Priority value is calculated by first multiplying the Facility + // number by 8 and then adding the numerical value of the Severity. + + return Priority{ + P: p, + F: Facility{Value: p / 8}, + S: Severity{Value: p % 8}, + } +} + +func FindNextSpace(buff []byte, from int, l int) (int, error) { + var to int + + for to = from; to < l; to++ { + if buff[to] == ' ' { + to++ + return to, nil + } + } + + return 0, ErrNoSpace +} + +func Parse2Digits(buff []byte, cursor *int, l int, min int, max int, e error) (int, error) { + digitLen := 2 + + if *cursor+digitLen > l { + return 0, ErrEOL + } + + sub := string(buff[*cursor : *cursor+digitLen]) + + *cursor += digitLen + + i, err := strconv.Atoi(sub) + if err != nil { + return 0, e + } + + if i >= min && i <= max { + return i, nil + } + + return 0, e +} + +func ParseHostname(buff []byte, cursor *int, l int) (string, error) { + from := *cursor + + if from >= l { + return "", ErrHostnameTooShort + } + + var to int + + for to = from; to < l; to++ { + if buff[to] == ' ' { + break + } + } + + hostname := buff[from:to] + + *cursor = to + + return string(hostname), nil +} + +func ShowCursorPos(buff []byte, cursor int) { + fmt.Println(string(buff)) + padding := strings.Repeat("-", cursor) + fmt.Println(padding + "↑\n") +} + +func (err *ParserError) Error() string { + return err.ErrorString +} diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/server.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/server.go new file mode 100644 index 000000000..352597b89 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/server.go @@ -0,0 +1,378 @@ +package syslog + +import ( + "bufio" + "crypto/tls" + "errors" + "net" + "strings" + "sync" + "time" + + "gopkg.in/mcuadros/go-syslog.v2/format" +) + +var ( + RFC3164 = &format.RFC3164{} // RFC3164: http://www.ietf.org/rfc/rfc3164.txt + RFC5424 = &format.RFC5424{} // RFC5424: http://www.ietf.org/rfc/rfc5424.txt + RFC6587 = &format.RFC6587{} // RFC6587: http://www.ietf.org/rfc/rfc6587.txt - octet counting variant + Automatic = &format.Automatic{} // Automatically identify the format +) + +const ( + datagramChannelBufferSize = 10 + datagramReadBufferSize = 64 * 1024 +) + +// A function type which gets the TLS peer name from the connection. Can return +// ok=false to terminate the connection +type TlsPeerNameFunc func(tlsConn *tls.Conn) (tlsPeer string, ok bool) + +type Server struct { + listeners []net.Listener + connections []net.PacketConn + wait sync.WaitGroup + doneTcp chan bool + datagramChannel chan DatagramMessage + format format.Format + handler Handler + lastError error + readTimeoutMilliseconds int64 + tlsPeerNameFunc TlsPeerNameFunc + datagramPool sync.Pool +} + +//NewServer returns a new Server +func NewServer() *Server { + return &Server{tlsPeerNameFunc: defaultTlsPeerName, datagramPool: sync.Pool{ + New: func() interface{} { + return make([]byte, 65536) + }, + }} +} + +//Sets the syslog format (RFC3164 or RFC5424 or RFC6587) +func (s *Server) SetFormat(f format.Format) { + s.format = f +} + +//Sets the handler, this handler with receive every syslog entry +func (s *Server) SetHandler(handler Handler) { + s.handler = handler +} + +//Sets the connection timeout for TCP connections, in milliseconds +func (s *Server) SetTimeout(millseconds int64) { + s.readTimeoutMilliseconds = millseconds +} + +// Set the function that extracts a TLS peer name from the TLS connection +func (s *Server) SetTlsPeerNameFunc(tlsPeerNameFunc TlsPeerNameFunc) { + s.tlsPeerNameFunc = tlsPeerNameFunc +} + +// Default TLS peer name function - returns the CN of the certificate +func defaultTlsPeerName(tlsConn *tls.Conn) (tlsPeer string, ok bool) { + state := tlsConn.ConnectionState() + if len(state.PeerCertificates) <= 0 { + return "", false + } + cn := state.PeerCertificates[0].Subject.CommonName + return cn, true +} + +//Configure the server for listen on an UDP addr +func (s *Server) ListenUDP(addr string) error { + udpAddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return err + } + + connection, err := net.ListenUDP("udp", udpAddr) + if err != nil { + return err + } + connection.SetReadBuffer(datagramReadBufferSize) + + s.connections = append(s.connections, connection) + return nil +} + +//Configure the server for listen on an unix socket +func (s *Server) ListenUnixgram(addr string) error { + unixAddr, err := net.ResolveUnixAddr("unixgram", addr) + if err != nil { + return err + } + + connection, err := net.ListenUnixgram("unixgram", unixAddr) + if err != nil { + return err + } + connection.SetReadBuffer(datagramReadBufferSize) + + s.connections = append(s.connections, connection) + return nil +} + +//Configure the server for listen on a TCP addr +func (s *Server) ListenTCP(addr string) error { + tcpAddr, err := net.ResolveTCPAddr("tcp", addr) + if err != nil { + return err + } + + listener, err := net.ListenTCP("tcp", tcpAddr) + if err != nil { + return err + } + + s.doneTcp = make(chan bool) + s.listeners = append(s.listeners, listener) + return nil +} + +//Configure the server for listen on a TCP addr for TLS +func (s *Server) ListenTCPTLS(addr string, config *tls.Config) error { + listener, err := tls.Listen("tcp", addr, config) + if err != nil { + return err + } + + s.doneTcp = make(chan bool) + s.listeners = append(s.listeners, listener) + return nil +} + +//Starts the server, all the go routines goes to live +func (s *Server) Boot() error { + if s.format == nil { + return errors.New("please set a valid format") + } + + if s.handler == nil { + return errors.New("please set a valid handler") + } + + for _, listener := range s.listeners { + s.goAcceptConnection(listener) + } + + if len(s.connections) > 0 { + s.goParseDatagrams() + } + + for _, connection := range s.connections { + s.goReceiveDatagrams(connection) + } + + return nil +} + +func (s *Server) goAcceptConnection(listener net.Listener) { + s.wait.Add(1) + go func(listener net.Listener) { + loop: + for { + select { + case <-s.doneTcp: + break loop + default: + } + connection, err := listener.Accept() + if err != nil { + continue + } + + s.goScanConnection(connection) + } + + s.wait.Done() + }(listener) +} + +func (s *Server) goScanConnection(connection net.Conn) { + scanner := bufio.NewScanner(connection) + if sf := s.format.GetSplitFunc(); sf != nil { + scanner.Split(sf) + } + + remoteAddr := connection.RemoteAddr() + var client string + if remoteAddr != nil { + client = remoteAddr.String() + } + + tlsPeer := "" + if tlsConn, ok := connection.(*tls.Conn); ok { + // Handshake now so we get the TLS peer information + if err := tlsConn.Handshake(); err != nil { + connection.Close() + return + } + if s.tlsPeerNameFunc != nil { + var ok bool + tlsPeer, ok = s.tlsPeerNameFunc(tlsConn) + if !ok { + connection.Close() + return + } + } + } + + var scanCloser *ScanCloser + scanCloser = &ScanCloser{scanner, connection} + + s.wait.Add(1) + go s.scan(scanCloser, client, tlsPeer) +} + +func (s *Server) scan(scanCloser *ScanCloser, client string, tlsPeer string) { +loop: + for { + select { + case <-s.doneTcp: + break loop + default: + } + if s.readTimeoutMilliseconds > 0 { + scanCloser.closer.SetReadDeadline(time.Now().Add(time.Duration(s.readTimeoutMilliseconds) * time.Millisecond)) + } + if scanCloser.Scan() { + s.parser([]byte(scanCloser.Text()), client, tlsPeer) + } else { + break loop + } + } + scanCloser.closer.Close() + + s.wait.Done() +} + +func (s *Server) parser(line []byte, client string, tlsPeer string) { + parser := s.format.GetParser(line) + err := parser.Parse() + if err != nil { + s.lastError = err + } + + logParts := parser.Dump() + logParts["client"] = client + if logParts["hostname"] == "" && (s.format == RFC3164 || s.format == Automatic) { + if i := strings.Index(client, ":"); i > 1 { + logParts["hostname"] = client[:i] + } else { + logParts["hostname"] = client + } + } + logParts["tls_peer"] = tlsPeer + + s.handler.Handle(logParts, int64(len(line)), err) +} + +//Returns the last error +func (s *Server) GetLastError() error { + return s.lastError +} + +//Kill the server +func (s *Server) Kill() error { + for _, connection := range s.connections { + err := connection.Close() + if err != nil { + return err + } + } + + for _, listener := range s.listeners { + err := listener.Close() + if err != nil { + return err + } + } + // Only need to close channel once to broadcast to all waiting + if s.doneTcp != nil { + close(s.doneTcp) + } + if s.datagramChannel != nil { + close(s.datagramChannel) + } + return nil +} + +//Waits until the server stops +func (s *Server) Wait() { + s.wait.Wait() +} + +type TimeoutCloser interface { + Close() error + SetReadDeadline(t time.Time) error +} + +type ScanCloser struct { + *bufio.Scanner + closer TimeoutCloser +} + +type DatagramMessage struct { + message []byte + client string +} + +func (s *Server) goReceiveDatagrams(packetconn net.PacketConn) { + s.wait.Add(1) + go func() { + defer s.wait.Done() + for { + buf := s.datagramPool.Get().([]byte) + n, addr, err := packetconn.ReadFrom(buf) + if err == nil { + // Ignore trailing control characters and NULs + for ; (n > 0) && (buf[n-1] < 32); n-- { + } + if n > 0 { + var address string + if addr != nil { + address = addr.String() + } + s.datagramChannel <- DatagramMessage{buf[:n], address} + } + } else { + // there has been an error. Either the server has been killed + // or may be getting a transitory error due to (e.g.) the + // interface being shutdown in which case sleep() to avoid busy wait. + opError, ok := err.(*net.OpError) + if (ok) && !opError.Temporary() && !opError.Timeout() { + return + } + time.Sleep(10 * time.Millisecond) + } + } + }() +} + +func (s *Server) goParseDatagrams() { + s.datagramChannel = make(chan DatagramMessage, datagramChannelBufferSize) + + s.wait.Add(1) + go func() { + defer s.wait.Done() + for { + select { + case msg, ok := (<-s.datagramChannel): + if !ok { + return + } + if sf := s.format.GetSplitFunc(); sf != nil { + if _, token, err := sf(msg.message, true); err == nil { + s.parser(token, msg.client, "") + } + } else { + s.parser(msg.message, msg.client, "") + } + s.datagramPool.Put(msg.message[:cap(msg.message)]) + } + } + }() +} |