mirror of
https://github.com/crowdsecurity/crowdsec.git
synced 2025-05-10 20:05:55 +02:00
254 lines
4.6 KiB
Go
254 lines
4.6 KiB
Go
package rfc3164
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/syslog/internal/parser/utils"
|
|
)
|
|
|
|
type RFC3164Option func(*RFC3164)
|
|
|
|
type RFC3164 struct {
|
|
PRI int
|
|
Timestamp time.Time
|
|
Hostname string
|
|
Tag string
|
|
Message string
|
|
PID string
|
|
//
|
|
len int
|
|
position int
|
|
buf []byte
|
|
useCurrentYear bool //If no year is specified in the timestamp, use the current year
|
|
strictHostname bool //If the hostname contains invalid characters or is not an IP, return an error
|
|
}
|
|
|
|
const PRI_MAX_LEN = 3
|
|
|
|
//Order is important: format with the most information must be first because we will stop on the first match
|
|
var VALID_TIMESTAMPS = []string{
|
|
time.RFC3339,
|
|
"Jan 02 15:04:05 2006",
|
|
"Jan _2 15:04:05 2006",
|
|
"Jan 02 15:04:05",
|
|
"Jan _2 15:04:05",
|
|
}
|
|
|
|
func WithCurrentYear() RFC3164Option {
|
|
return func(r *RFC3164) {
|
|
r.useCurrentYear = true
|
|
}
|
|
}
|
|
|
|
func WithStrictHostname() RFC3164Option {
|
|
return func(r *RFC3164) {
|
|
r.strictHostname = true
|
|
}
|
|
}
|
|
|
|
func (r *RFC3164) parsePRI() error {
|
|
pri := 0
|
|
|
|
if r.buf[r.position] != '<' {
|
|
return errors.New("PRI must start with '<'")
|
|
}
|
|
|
|
r.position++
|
|
|
|
for r.position < r.len {
|
|
c := r.buf[r.position]
|
|
if c == '>' {
|
|
r.position++
|
|
break
|
|
}
|
|
if c < '0' || c > '9' {
|
|
return errors.New("PRI must be a number")
|
|
}
|
|
pri = pri*10 + int(c-'0')
|
|
r.position++
|
|
}
|
|
|
|
if pri > 999 {
|
|
return errors.New("PRI must be up to 3 characters long")
|
|
}
|
|
|
|
if r.position == r.len && r.buf[r.position-1] != '>' {
|
|
return errors.New("PRI must end with '>'")
|
|
}
|
|
|
|
r.PRI = pri
|
|
return nil
|
|
}
|
|
|
|
func (r *RFC3164) parseTimestamp() error {
|
|
validTs := false
|
|
for _, layout := range VALID_TIMESTAMPS {
|
|
tsLen := len(layout)
|
|
if r.position+tsLen > r.len {
|
|
continue
|
|
}
|
|
t, err := time.Parse(layout, string(r.buf[r.position:r.position+tsLen]))
|
|
if err == nil {
|
|
validTs = true
|
|
r.Timestamp = t
|
|
r.position += tsLen
|
|
break
|
|
}
|
|
}
|
|
if !validTs {
|
|
return errors.New("timestamp is not valid")
|
|
}
|
|
if r.useCurrentYear {
|
|
if r.Timestamp.Year() == 0 {
|
|
r.Timestamp = time.Date(time.Now().Year(), r.Timestamp.Month(), r.Timestamp.Day(), r.Timestamp.Hour(), r.Timestamp.Minute(), r.Timestamp.Second(), r.Timestamp.Nanosecond(), r.Timestamp.Location())
|
|
}
|
|
}
|
|
r.position++
|
|
return nil
|
|
}
|
|
|
|
func (r *RFC3164) parseHostname() error {
|
|
hostname := []byte{}
|
|
for r.position < r.len {
|
|
c := r.buf[r.position]
|
|
if c == ' ' {
|
|
r.position++
|
|
break
|
|
}
|
|
hostname = append(hostname, c)
|
|
r.position++
|
|
}
|
|
if r.strictHostname {
|
|
if !utils.IsValidHostnameOrIP(string(hostname)) {
|
|
return errors.New("hostname is not valid")
|
|
}
|
|
}
|
|
if len(hostname) == 0 {
|
|
return errors.New("hostname is empty")
|
|
}
|
|
r.Hostname = string(hostname)
|
|
return nil
|
|
}
|
|
|
|
//We do not enforce tag len as quite a lot of syslog client send tags with more than 32 chars
|
|
func (r *RFC3164) parseTag() error {
|
|
tag := []byte{}
|
|
tmpPid := []byte{}
|
|
pidEnd := false
|
|
hasPid := false
|
|
for r.position < r.len {
|
|
c := r.buf[r.position]
|
|
if !utils.IsAlphaNumeric(c) {
|
|
break
|
|
}
|
|
tag = append(tag, c)
|
|
r.position++
|
|
}
|
|
if len(tag) == 0 {
|
|
return errors.New("tag is empty")
|
|
}
|
|
r.Tag = string(tag)
|
|
|
|
if r.position == r.len {
|
|
return nil
|
|
}
|
|
|
|
c := r.buf[r.position]
|
|
if c == '[' {
|
|
hasPid = true
|
|
r.position++
|
|
for r.position < r.len {
|
|
c = r.buf[r.position]
|
|
if c == ']' {
|
|
pidEnd = true
|
|
r.position++
|
|
break
|
|
}
|
|
if c < '0' || c > '9' {
|
|
return errors.New("pid inside tag must be a number")
|
|
}
|
|
tmpPid = append(tmpPid, c)
|
|
r.position++
|
|
}
|
|
}
|
|
|
|
if hasPid && !pidEnd {
|
|
return errors.New("pid inside tag must be closed with ']'")
|
|
}
|
|
|
|
if hasPid {
|
|
r.PID = string(tmpPid)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *RFC3164) parseMessage() error {
|
|
err := r.parseTag()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if r.position == r.len {
|
|
return errors.New("message is empty")
|
|
}
|
|
|
|
c := r.buf[r.position]
|
|
|
|
if c == ':' {
|
|
r.position++
|
|
}
|
|
|
|
for {
|
|
if r.position >= r.len {
|
|
return errors.New("message is empty")
|
|
}
|
|
c := r.buf[r.position]
|
|
if c != ' ' {
|
|
break
|
|
}
|
|
r.position++
|
|
}
|
|
|
|
message := r.buf[r.position:r.len]
|
|
r.Message = string(message)
|
|
return nil
|
|
}
|
|
|
|
func (r *RFC3164) Parse(message []byte) error {
|
|
r.len = len(message)
|
|
if r.len == 0 {
|
|
return errors.New("message is empty")
|
|
}
|
|
r.buf = message
|
|
|
|
err := r.parsePRI()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = r.parseTimestamp()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = r.parseHostname()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = r.parseMessage()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func NewRFC3164Parser(opts ...RFC3164Option) *RFC3164 {
|
|
r := &RFC3164{}
|
|
for _, opt := range opts {
|
|
opt(r)
|
|
}
|
|
return r
|
|
}
|