diff --git a/src/go.mod b/src/go.mod index 2df78ea..1e47f44 100644 --- a/src/go.mod +++ b/src/go.mod @@ -3,6 +3,8 @@ module fail2ban-prometheus-exporter go 1.15 require ( + github.com/kisielk/og-rek v1.1.0 github.com/mattn/go-sqlite3 v1.14.6 + github.com/nlpodyssey/gopickle v0.1.0 github.com/prometheus/client_golang v1.9.0 ) diff --git a/src/go.sum b/src/go.sum index 7de5c32..8f79c52 100644 --- a/src/go.sum +++ b/src/go.sum @@ -137,6 +137,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kisielk/og-rek v1.1.0 h1:u10TvQbPtrlY/6H4+BiFsBywwSVTGFsx0YOVtpx3IbI= +github.com/kisielk/og-rek v1.1.0/go.mod h1:6ihsOSzSAxR/65S3Bn9zNihoEqRquhDQZ2c6I2+MG3c= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -175,6 +177,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nlpodyssey/gopickle v0.1.0 h1:9wjwRqXsOSYWZl4c4ko472b6RW+VB1I441ZcfFg1r5g= +github.com/nlpodyssey/gopickle v0.1.0/go.mod h1:YIUwjJ2O7+vnBsxUN+MHAAI3N+adqEGiw+nDpwW95bY= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= diff --git a/src/socket/decoder.go b/src/socket/decoder.go new file mode 100644 index 0000000..41f1ac4 --- /dev/null +++ b/src/socket/decoder.go @@ -0,0 +1,8 @@ +package socket + +// Py_builtins_str is used by the pickle decoder to parse the server response into a format Go can understand +type Py_builtins_str struct{} + +func (c Py_builtins_str) Call(args ...interface{}) (interface{}, error) { + return args[0], nil +} diff --git a/src/socket/fail2banSocket.go b/src/socket/fail2banSocket.go new file mode 100644 index 0000000..3df6d3c --- /dev/null +++ b/src/socket/fail2banSocket.go @@ -0,0 +1,38 @@ +package socket + +import ( + "github.com/kisielk/og-rek" + "log" + "net" +) + +type Fail2BanSocket struct { + socket net.Conn + encoder *ogórek.Encoder +} + +func MustConnectToSocket(path string) *Fail2BanSocket { + c, err := net.Dial("unix", path) + if err != nil { + log.Fatalf("failed to open fail2ban socket: %v", err) + } + return &Fail2BanSocket{ + socket: c, + encoder: ogórek.NewEncoderWithConfig(c, &ogórek.EncoderConfig{ + Protocol: 5, + }), + } +} + +func (s *Fail2BanSocket) Ping() bool { + response, err := s.sendCommand([]string{pingCommand, "100"}) + if err != nil { + log.Printf("server ping failed: %v", err) + return false + } + if v, ok := response.([]string); ok && v[1] == "pong" { + return true + } + log.Printf("unexpected response from server: %v", response) + return false +} diff --git a/src/socket/protocol.go b/src/socket/protocol.go new file mode 100644 index 0000000..905cae1 --- /dev/null +++ b/src/socket/protocol.go @@ -0,0 +1,55 @@ +package socket + +import ( + "bytes" + "fmt" + "github.com/kisielk/og-rek" + "github.com/nlpodyssey/gopickle/pickle" + "net" +) + +const ( + commandTerminator = "" + pingCommand = "ping" + socketReadBufferSize = 10000 +) + +func (s *Fail2BanSocket) sendCommand(command []string) (interface{}, error) { + err := write(s.encoder, command) + if err != nil { + return nil, err + } + return read(&s.socket) +} + +func write(encoder *ogórek.Encoder, command []string) error { + err := encoder.Encode(command) + if err != nil { + return err + } + err = encoder.Encode(commandTerminator) + if err != nil { + return err + } + return nil +} + +func read(s *net.Conn) (interface{}, error) { + buf := make([]byte, socketReadBufferSize) + _, err := (*s).Read(buf) + if err != nil { + return nil, err + } + + bufReader := bytes.NewReader(buf) + unpickler := pickle.NewUnpickler(bufReader) + + unpickler.FindClass = func(module, name string) (interface{}, error) { + if module == "builtins" && name == "str" { + return &Py_builtins_str{}, nil + } + return nil, fmt.Errorf("class not found: " + module + " : " + name) + } + + return unpickler.Load() +}