2020-05-15 09:39:16 +00:00
|
|
|
package exprhelpers
|
|
|
|
|
|
|
|
import (
|
2020-05-27 14:31:08 +00:00
|
|
|
"bufio"
|
2020-05-28 08:59:43 +00:00
|
|
|
"fmt"
|
2020-07-02 09:09:40 +00:00
|
|
|
"net"
|
2021-10-08 14:50:31 +00:00
|
|
|
"net/url"
|
2020-05-27 14:31:08 +00:00
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"regexp"
|
2020-05-15 09:39:16 +00:00
|
|
|
"strconv"
|
2020-05-22 11:55:48 +00:00
|
|
|
"strings"
|
2021-04-16 17:13:48 +00:00
|
|
|
"time"
|
2020-05-15 09:39:16 +00:00
|
|
|
|
2022-02-14 15:50:52 +00:00
|
|
|
"github.com/c-robinson/iplib"
|
|
|
|
|
2022-06-22 09:29:52 +00:00
|
|
|
"github.com/crowdsecurity/crowdsec/pkg/database"
|
2020-11-30 09:37:17 +00:00
|
|
|
"github.com/davecgh/go-spew/spew"
|
2020-05-15 09:39:16 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
2020-05-27 14:31:08 +00:00
|
|
|
var dataFile map[string][]string
|
|
|
|
var dataFileRegex map[string][]*regexp.Regexp
|
2022-06-22 09:29:52 +00:00
|
|
|
var dbClient *database.Client
|
2020-05-27 14:31:08 +00:00
|
|
|
|
2020-05-15 09:39:16 +00:00
|
|
|
func Atof(x string) float64 {
|
|
|
|
log.Debugf("debug atof %s", x)
|
|
|
|
ret, err := strconv.ParseFloat(x, 64)
|
|
|
|
if err != nil {
|
|
|
|
log.Warningf("Atof : can't convert float '%s' : %v", x, err)
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2020-06-01 14:12:48 +00:00
|
|
|
func Upper(s string) string {
|
|
|
|
return strings.ToUpper(s)
|
2020-05-22 11:55:48 +00:00
|
|
|
}
|
|
|
|
|
2021-12-14 10:07:40 +00:00
|
|
|
func Lower(s string) string {
|
|
|
|
return strings.ToLower(s)
|
|
|
|
}
|
|
|
|
|
2020-05-15 09:39:16 +00:00
|
|
|
func GetExprEnv(ctx map[string]interface{}) map[string]interface{} {
|
2020-05-29 15:25:09 +00:00
|
|
|
var ExprLib = map[string]interface{}{
|
2022-06-22 09:29:52 +00:00
|
|
|
"Atof": Atof,
|
|
|
|
"JsonExtract": JsonExtract,
|
|
|
|
"JsonExtractUnescape": JsonExtractUnescape,
|
|
|
|
"JsonExtractLib": JsonExtractLib,
|
|
|
|
"JsonExtractSlice": JsonExtractSlice,
|
|
|
|
"JsonExtractObject": JsonExtractObject,
|
|
|
|
"ToJsonString": ToJson,
|
|
|
|
"File": File,
|
|
|
|
"RegexpInFile": RegexpInFile,
|
|
|
|
"Upper": Upper,
|
|
|
|
"Lower": Lower,
|
|
|
|
"IpInRange": IpInRange,
|
|
|
|
"TimeNow": TimeNow,
|
|
|
|
"ParseUri": ParseUri,
|
|
|
|
"PathUnescape": PathUnescape,
|
|
|
|
"QueryUnescape": QueryUnescape,
|
|
|
|
"PathEscape": PathEscape,
|
|
|
|
"QueryEscape": QueryEscape,
|
|
|
|
"XMLGetAttributeValue": XMLGetAttributeValue,
|
|
|
|
"XMLGetNodeValue": XMLGetNodeValue,
|
|
|
|
"IpToRange": IpToRange,
|
|
|
|
"IsIPV6": IsIPV6,
|
2022-10-26 09:17:48 +00:00
|
|
|
"LookupHost": net.LookupHost,
|
2022-06-22 09:29:52 +00:00
|
|
|
"GetDecisionsCount": GetDecisionsCount,
|
|
|
|
"GetDecisionsSinceCount": GetDecisionsSinceCount,
|
|
|
|
"Sprintf": fmt.Sprintf,
|
2020-05-29 15:25:09 +00:00
|
|
|
}
|
2020-05-15 09:39:16 +00:00
|
|
|
for k, v := range ctx {
|
|
|
|
ExprLib[k] = v
|
|
|
|
}
|
|
|
|
return ExprLib
|
|
|
|
}
|
2020-05-27 14:31:08 +00:00
|
|
|
|
2022-06-22 09:29:52 +00:00
|
|
|
func Init(databaseClient *database.Client) error {
|
2020-05-27 14:31:08 +00:00
|
|
|
dataFile = make(map[string][]string)
|
|
|
|
dataFileRegex = make(map[string][]*regexp.Regexp)
|
2022-06-22 09:29:52 +00:00
|
|
|
dbClient = databaseClient
|
2020-05-27 14:31:08 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func FileInit(fileFolder string, filename string, fileType string) error {
|
2020-07-03 16:26:23 +00:00
|
|
|
log.Debugf("init (folder:%s) (file:%s) (type:%s)", fileFolder, filename, fileType)
|
2020-05-27 14:31:08 +00:00
|
|
|
filepath := path.Join(fileFolder, filename)
|
|
|
|
file, err := os.Open(filepath)
|
|
|
|
if err != nil {
|
2020-05-27 15:56:52 +00:00
|
|
|
return err
|
2020-05-27 14:31:08 +00:00
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
2020-08-21 12:20:44 +00:00
|
|
|
if fileType == "" {
|
|
|
|
log.Debugf("ignored file %s%s because no type specified", fileFolder, filename)
|
|
|
|
return nil
|
|
|
|
}
|
2020-05-27 14:31:08 +00:00
|
|
|
if _, ok := dataFile[filename]; !ok {
|
|
|
|
dataFile[filename] = []string{}
|
|
|
|
}
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
|
|
for scanner.Scan() {
|
2020-07-07 14:26:00 +00:00
|
|
|
if strings.HasPrefix(scanner.Text(), "#") { // allow comments
|
|
|
|
continue
|
|
|
|
}
|
2021-02-25 08:57:24 +00:00
|
|
|
if len(scanner.Text()) == 0 { //skip empty lines
|
|
|
|
continue
|
|
|
|
}
|
2020-05-27 14:31:08 +00:00
|
|
|
switch fileType {
|
2020-05-27 15:56:52 +00:00
|
|
|
case "regex", "regexp":
|
2020-05-27 14:31:08 +00:00
|
|
|
dataFileRegex[filename] = append(dataFileRegex[filename], regexp.MustCompile(scanner.Text()))
|
|
|
|
case "string":
|
|
|
|
dataFile[filename] = append(dataFile[filename], scanner.Text())
|
|
|
|
default:
|
2020-05-28 08:59:43 +00:00
|
|
|
return fmt.Errorf("unknown data type '%s' for : '%s'", fileType, filename)
|
2020-05-27 14:31:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := scanner.Err(); err != nil {
|
2020-05-27 15:56:52 +00:00
|
|
|
return err
|
2020-05-27 14:31:08 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-12-14 10:07:40 +00:00
|
|
|
func QueryEscape(s string) string {
|
|
|
|
return url.QueryEscape(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
func PathEscape(s string) string {
|
|
|
|
return url.PathEscape(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
func PathUnescape(s string) string {
|
|
|
|
ret, err := url.PathUnescape(s)
|
|
|
|
if err != nil {
|
2022-04-19 10:31:29 +00:00
|
|
|
log.Debugf("unable to PathUnescape '%s': %+v", s, err)
|
2021-12-14 10:07:40 +00:00
|
|
|
return s
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func QueryUnescape(s string) string {
|
|
|
|
ret, err := url.QueryUnescape(s)
|
|
|
|
if err != nil {
|
2022-04-19 10:31:29 +00:00
|
|
|
log.Debugf("unable to QueryUnescape '%s': %+v", s, err)
|
2021-12-14 10:07:40 +00:00
|
|
|
return s
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2020-05-27 14:31:08 +00:00
|
|
|
func File(filename string) []string {
|
|
|
|
if _, ok := dataFile[filename]; ok {
|
|
|
|
return dataFile[filename]
|
|
|
|
}
|
2020-07-02 15:56:39 +00:00
|
|
|
log.Errorf("file '%s' (type:string) not found in expr library", filename)
|
2020-11-30 09:37:17 +00:00
|
|
|
log.Errorf("expr library : %s", spew.Sdump(dataFile))
|
2020-05-27 14:31:08 +00:00
|
|
|
return []string{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func RegexpInFile(data string, filename string) bool {
|
|
|
|
if _, ok := dataFileRegex[filename]; ok {
|
|
|
|
for _, re := range dataFileRegex[filename] {
|
|
|
|
if re.Match([]byte(data)) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2020-07-02 15:56:39 +00:00
|
|
|
log.Errorf("file '%s' (type:regexp) not found in expr library", filename)
|
2020-11-30 09:37:17 +00:00
|
|
|
log.Errorf("expr library : %s", spew.Sdump(dataFileRegex))
|
2020-05-27 14:31:08 +00:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2020-07-02 09:09:40 +00:00
|
|
|
|
|
|
|
func IpInRange(ip string, ipRange string) bool {
|
|
|
|
var err error
|
|
|
|
var ipParsed net.IP
|
|
|
|
var ipRangeParsed *net.IPNet
|
|
|
|
|
|
|
|
ipParsed = net.ParseIP(ip)
|
|
|
|
if ipParsed == nil {
|
2020-07-30 15:12:47 +00:00
|
|
|
log.Debugf("'%s' is not a valid IP", ip)
|
2020-07-02 09:09:40 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
if _, ipRangeParsed, err = net.ParseCIDR(ipRange); err != nil {
|
2020-07-30 15:12:47 +00:00
|
|
|
log.Debugf("'%s' is not a valid IP Range", ipRange)
|
2020-07-02 09:09:40 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
if ipRangeParsed.Contains(ipParsed) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2021-04-16 17:13:48 +00:00
|
|
|
|
2022-05-19 14:28:25 +00:00
|
|
|
func IsIPV6(ip string) bool {
|
|
|
|
ipParsed := net.ParseIP(ip)
|
|
|
|
if ipParsed == nil {
|
|
|
|
log.Debugf("'%s' is not a valid IP", ip)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// If it's a valid IP and can't be converted to IPv4 then it is an IPv6
|
|
|
|
return ipParsed.To4() == nil
|
|
|
|
}
|
|
|
|
|
2022-02-14 15:50:52 +00:00
|
|
|
func IpToRange(ip string, cidr string) string {
|
|
|
|
cidr = strings.TrimPrefix(cidr, "/")
|
|
|
|
mask, err := strconv.Atoi(cidr)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("bad cidr '%s': %s", cidr, err)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
ipAddr := net.ParseIP(ip)
|
|
|
|
if ipAddr == nil {
|
|
|
|
log.Errorf("can't parse IP address '%s'", ip)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
ipRange := iplib.NewNet(ipAddr, mask)
|
|
|
|
if ipRange.IP() == nil {
|
|
|
|
log.Errorf("can't get cidr '%s' of '%s'", cidr, ip)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return ipRange.String()
|
|
|
|
}
|
|
|
|
|
2021-04-16 17:13:48 +00:00
|
|
|
func TimeNow() string {
|
2022-01-19 13:56:05 +00:00
|
|
|
return time.Now().UTC().Format(time.RFC3339)
|
2021-04-16 17:13:48 +00:00
|
|
|
}
|
2021-10-04 15:14:52 +00:00
|
|
|
|
2021-10-08 14:50:31 +00:00
|
|
|
func ParseUri(uri string) map[string][]string {
|
|
|
|
ret := make(map[string][]string)
|
|
|
|
u, err := url.Parse(uri)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Could not parse URI: %s", err)
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
parsed, err := url.ParseQuery(u.RawQuery)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Could not parse query uri : %s", err)
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
for k, v := range parsed {
|
|
|
|
ret[k] = v
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2021-10-04 15:14:52 +00:00
|
|
|
func KeyExists(key string, dict map[string]interface{}) bool {
|
|
|
|
_, ok := dict[key]
|
|
|
|
return ok
|
|
|
|
}
|
2022-06-22 09:29:52 +00:00
|
|
|
|
|
|
|
func GetDecisionsCount(value string) int {
|
|
|
|
if dbClient == nil {
|
|
|
|
log.Error("No database config to call GetDecisionsCount()")
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
count, err := dbClient.CountDecisionsByValue(value)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Failed to get decisions count from value '%s'", value)
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return count
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetDecisionsSinceCount(value string, since string) int {
|
|
|
|
if dbClient == nil {
|
|
|
|
log.Error("No database config to call GetDecisionsCount()")
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
sinceDuration, err := time.ParseDuration(since)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Failed to parse since parameter '%s' : %s", since, err)
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
sinceTime := time.Now().UTC().Add(-sinceDuration)
|
|
|
|
count, err := dbClient.CountDecisionsSinceByValue(value, sinceTime)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Failed to get decisions count from value '%s'", value)
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return count
|
|
|
|
}
|