This repository has been archived on 2023-10-10. You can view files and clone it, but cannot push or open issues/pull-requests.
backgroundcheck/main.go

173 lines
3.8 KiB
Go

package main
import (
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"regexp"
"strings"
"time"
"github.com/dustin/go-humanize"
"github.com/golang/glog"
)
var (
logPath string
bindAddress string
maxBackString string
maxBack time.Duration
reLine = regexp.MustCompile(`([^ ]+) - - \[([^\]]+)\] "([^"]+)" ([0-9]+) ([0-9]+) "([^"]+)" "([^"]+)"`)
)
type logEntry struct {
address net.IP
timestamp time.Time
request string
referrer string
agent string
}
func parseNginxLine(line []byte) (logEntry, error) {
entry := logEntry{}
matches := reLine.FindSubmatch(line)
if matches == nil {
return entry, errors.New("Could not parse line.")
}
entry.address = net.ParseIP(string(matches[1]))
t, err := time.Parse("2/Jan/2006:15:04:05 -0700", string(matches[2]))
if err != nil {
return entry, err
}
entry.timestamp = t
entry.request = string(matches[3])
entry.referrer = string(matches[6])
entry.agent = string(matches[7])
return entry, nil
}
func getBackground(address net.IP) (string, error) {
f, err := os.Open(logPath)
if err != nil {
return "", err
}
// Seek to end of file.
if _, err = f.Seek(0, os.SEEK_END); err != nil {
return "", err
}
if _, err = f.Seek(-1, os.SEEK_CUR); err != nil {
return "", err
}
now := time.Now()
for {
// Seek backwards until next newline.
line := []byte{}
for {
d := make([]byte, 1)
if _, err = io.ReadFull(f, d); err != nil {
return "", err
}
if _, err = f.Seek(-2, os.SEEK_CUR); err != nil {
return "", err
}
if string(d) == "\n" {
break
}
line = append(d, line...)
}
if string(line) == "" {
continue
}
entry, err := parseNginxLine(line)
if err != nil {
glog.Errorf("Could not parse line %s: %v", line, err)
continue
}
if now.Sub(entry.timestamp) > maxBack {
break
}
if !entry.address.Equal(address) {
continue
}
if entry.referrer == "-" {
continue
}
u, err := url.Parse(entry.referrer)
if err != nil {
continue
}
if !strings.Contains(u.Host, "google") || u.Path != "/search" {
continue
}
glog.Infof("Entry: %s, %s", entry.address.String(), entry.referrer)
query := u.Query().Get("q")
if query != "" {
t := humanize.Time(entry.timestamp)
return fmt.Sprintf("Address %s searched %s for '%s' %s", address.String(), u.Host, query, t), nil
}
}
return "", errors.New("No background.")
}
type apiResponse struct {
Error string `json:error`
Data string `json:data`
}
func handleCheck(w http.ResponseWriter, r *http.Request) {
response := apiResponse{}
defer func() {
data, err := json.Marshal(response)
if err != nil {
glog.Error("While marshalling response: %v", err)
w.WriteHeader(500)
w.Write([]byte("Internal server error."))
}
w.WriteHeader(200)
w.Write(data)
}()
addressString := r.URL.Query().Get("address")
if addressString == "" {
response.Error = "No address specified."
return
}
address := net.ParseIP(addressString)
if address == nil {
response.Error = "Invalid address."
return
}
background, err := getBackground(address)
if err != nil {
response.Error = "No background."
glog.Errorf("While getting background for %s: %v", addressString, err)
return
}
response.Data = background
}
func main() {
flag.StringVar(&logPath, "nginx_log_path", "/var/log/nginx/access_log", "Path to NGINX access log.")
flag.StringVar(&bindAddress, "bind_address", ":8080", "Port to bind HTTP/JSON interface to.")
flag.StringVar(&maxBackString, "max_back", "5m", "How far back in time to look for records.")
flag.Parse()
d, err := time.ParseDuration(maxBackString)
if err != nil {
glog.Exit(err)
}
maxBack = d
glog.Infof("Starting on %s, watching %s.", bindAddress, logPath)
http.HandleFunc("/api/1/background", handleCheck)
glog.Exit(http.ListenAndServe(bindAddress, nil))
}