safing-portmaster/profile/endpoints/endpoints.go

149 lines
3.5 KiB
Go

package endpoints
import (
"context"
"errors"
"fmt"
"strings"
"github.com/safing/portmaster/intel"
)
// Endpoints is a list of permitted or denied endpoints.
type Endpoints []Endpoint
// EPResult represents the result of a check against an EndpointPermission.
type EPResult uint8
// Endpoint matching return values.
const (
NoMatch EPResult = iota
MatchError
Denied
Permitted
)
// IsDecision returns true if result represents a decision
// and false if result is NoMatch or Undeterminable.
func IsDecision(result EPResult) bool {
return result == Denied || result == Permitted || result == MatchError
}
// ParseEndpoints parses a list of endpoints and returns a list of Endpoints for matching.
func ParseEndpoints(entries []string) (Endpoints, error) {
var firstErr error
var errCnt int
endpoints := make(Endpoints, 0, len(entries))
entriesLoop:
for _, entry := range entries {
ep, err := parseEndpoint(entry)
if err != nil {
errCnt++
if firstErr == nil {
firstErr = err
}
continue entriesLoop
}
endpoints = append(endpoints, ep)
}
if firstErr != nil {
if errCnt > 0 {
return endpoints, fmt.Errorf("encountered %d errors, first was: %w", errCnt, firstErr)
}
return endpoints, firstErr
}
return endpoints, nil
}
// ListEntryValidationRegex is a regex to bullshit check endpoint list entries.
var ListEntryValidationRegex = strings.Join([]string{
`^(\+|\-) `, // Rule verdict.
`(! +)?`, // Invert matching.
`[A-z0-9\.:\-*/]+`, // Entity matching.
`( `, // Start of optional matching.
`[A-z0-9*]+`, // Protocol matching.
`(/[A-z0-9]+(\-[A-z0-9]+)?)?`, // Port and port range matching.
`)?`, // End of optional matching.
`( +#.*)?`, // Optional comment.
}, "")
// ValidateEndpointListConfigOption validates the given value.
func ValidateEndpointListConfigOption(value interface{}) error {
list, ok := value.([]string)
if !ok {
return errors.New("invalid type")
}
_, err := ParseEndpoints(list)
return err
}
// IsSet returns whether the Endpoints object is "set".
func (e Endpoints) IsSet() bool {
return len(e) > 0
}
// Match checks whether the given entity matches any of the endpoint definitions in the list.
func (e Endpoints) Match(ctx context.Context, entity *intel.Entity) (result EPResult, reason Reason) {
for _, entry := range e {
if entry == nil {
continue
}
if result, reason = entry.Matches(ctx, entity); result != NoMatch {
return
}
}
return NoMatch, nil
}
// MatchMulti checks whether the given entities match any of the endpoint
// definitions in the list. Every rule is evaluated against all given entities
// and only if not match was registered, the next rule is evaluated.
func (e Endpoints) MatchMulti(ctx context.Context, entities ...*intel.Entity) (result EPResult, reason Reason) {
for _, entry := range e {
if entry == nil {
continue
}
for _, entity := range entities {
if entity == nil {
continue
}
if result, reason = entry.Matches(ctx, entity); result != NoMatch {
return
}
}
}
return NoMatch, nil
}
func (e Endpoints) String() string {
s := make([]string, 0, len(e))
for _, entry := range e {
s = append(s, entry.String())
}
return fmt.Sprintf("[%s]", strings.Join(s, ", "))
}
func (epr EPResult) String() string {
switch epr {
case NoMatch:
return "No Match"
case MatchError:
return "Match Error"
case Denied:
return "Denied"
case Permitted:
return "Permitted"
default:
return "Unknown"
}
}