Finish initial adaption of profiles

This commit is contained in:
Daniel
2018-10-30 19:13:21 +01:00
parent 247c7dca00
commit 5533203fa1
26 changed files with 1299 additions and 814 deletions

9
profile/const.go Normal file
View File

@@ -0,0 +1,9 @@
package profile
// Platform identifiers
const (
PlatformAny = "any"
PlatformLinux = "lin"
PlatformWindows = "win"
PlatformMac = "mac"
)

6
profile/const_linux.go Normal file
View File

@@ -0,0 +1,6 @@
package profile
// OS Identifier Prefix
const (
IdentifierPrefix = "lin:"
)

21
profile/database.go Normal file
View File

@@ -0,0 +1,21 @@
package profile
import (
"github.com/Safing/portbase/database"
)
// core:profiles/user/12345-1234-125-1234-1235
// core:profiles/special/default
// /global
// core:profiles/stamp/12334-1235-1234-5123-1234
// core:profiles/identifier/base64
// Namespaces
const (
UserNamespace = "user"
StampNamespace = "stamp"
)
var (
profileDB = database.NewInterface(nil)
)

42
profile/domains.go Normal file
View File

@@ -0,0 +1,42 @@
package profile
import "strings"
// Domains is a list of permitted or denied domains.
type Domains map[string]*DomainDecision
// DomainDecision holds a decision about a domain.
type DomainDecision struct {
Permit bool
Created int64
IncludeSubdomains bool
}
// IsSet returns whether the Domains object is "set".
func (d Domains) IsSet() bool {
if d != nil {
return true
}
return false
}
// CheckStatus checks if the given domain is governed in the list of domains and returns whether it is permitted.
func (d Domains) CheckStatus(domain string) (permit, ok bool) {
// check for exact domain
dd, ok := d[domain]
if ok {
return dd.Permit, true
}
// check if domain is a subdomain of any of the domains
for key, dd := range d {
if dd.IncludeSubdomains && strings.HasSuffix(domain, key) {
preDottedKey := "." + key
if strings.HasSuffix(domain, preDottedKey) {
return dd.Permit, true
}
}
}
return false, false
}

145
profile/flags.go Normal file
View File

@@ -0,0 +1,145 @@
package profile
import (
"errors"
"strings"
"github.com/Safing/portmaster/status"
)
// ProfileFlags are used to quickly add common attributes to profiles
type ProfileFlags map[uint8]uint8
// Profile Flags
const (
// Profile Modes
Prompt uint8 = 0 // Prompt first-seen connections
Blacklist uint8 = 1 // Allow everything not explicitly denied
Whitelist uint8 = 2 // Only allow everything explicitly allowed
// Network Locations
Internet uint8 = 16 // Allow connections to the Internet
LAN uint8 = 17 // Allow connections to the local area network
Localhost uint8 = 18 // Allow connections on the local host
// Specials
Related uint8 = 32 // If and before prompting, allow domains that are related to the program
PeerToPeer uint8 = 33 // Allow program to directly communicate with peers, without resolving DNS first
Service uint8 = 34 // Allow program to accept incoming connections
Independent uint8 = 35 // Ignore profile settings coming from the Community
RequireGate17 uint8 = 36 // Require all connections to go over Gate17
)
var (
// ErrProfileFlagsParseFailed is returned if a an invalid flag is encountered while parsing
ErrProfileFlagsParseFailed = errors.New("profiles: failed to parse flags")
sortedFlags = []uint8{
Prompt,
Blacklist,
Whitelist,
Internet,
LAN,
Localhost,
Related,
PeerToPeer,
Service,
Independent,
RequireGate17,
}
flagIDs = map[string]uint8{
"Prompt": Prompt,
"Blacklist": Blacklist,
"Whitelist": Whitelist,
"Internet": Internet,
"LAN": LAN,
"Localhost": Localhost,
"Related": Related,
"PeerToPeer": PeerToPeer,
"Service": Service,
"Independent": Independent,
"RequireGate17": RequireGate17,
}
flagNames = map[uint8]string{
Prompt: "Prompt",
Blacklist: "Blacklist",
Whitelist: "Whitelist",
Internet: "Internet",
LAN: "LAN",
Localhost: "Localhost",
Related: "Related",
PeerToPeer: "PeerToPeer",
Service: "Service",
Independent: "Independent",
RequireGate17: "RequireGate17",
}
)
// FlagsFromNames creates ProfileFlags from a comma seperated list of flagnames (e.g. "System,Strict,Secure")
// func FlagsFromNames(words []string) (*ProfileFlags, error) {
// var flags ProfileFlags
// for _, entry := range words {
// flag, ok := flagIDs[entry]
// if !ok {
// return nil, ErrProfileFlagsParseFailed
// }
// flags = append(flags, flag)
// }
// return &flags, nil
// }
// IsSet returns whether the ProfileFlags object is "set".
func (pf ProfileFlags) IsSet() bool {
if pf != nil {
return true
}
return false
}
// Has checks if a ProfileFlags object has a flag set in the given security level
func (pf ProfileFlags) Has(flag, level uint8) bool {
setting, ok := pf[flag]
if ok && setting&level > 0 {
return true
}
return false
}
func getLevelMarker(levels, level uint8) string {
if levels&level > 0 {
return "+"
}
return "-"
}
// String return a string representation of ProfileFlags
func (pf ProfileFlags) String() string {
var namedFlags []string
for _, flag := range sortedFlags {
levels, ok := pf[flag]
if ok {
s := flagNames[flag]
if levels != status.SecurityLevelsAll {
s += getLevelMarker(levels, status.SecurityLevelDynamic)
s += getLevelMarker(levels, status.SecurityLevelSecure)
s += getLevelMarker(levels, status.SecurityLevelFortress)
}
}
}
for _, flag := range pf {
namedFlags = append(namedFlags, flagNames[flag])
}
return strings.Join(namedFlags, ", ")
}
// Add adds a flag to the Flags with the given level.
func (pf ProfileFlags) Add(flag, levels uint8) {
pf[flag] = levels
}
// Remove removes a flag from the Flags.
func (pf ProfileFlags) Remove(flag uint8) {
delete(pf, flag)
}

54
profile/flags_test.go Normal file
View File

@@ -0,0 +1,54 @@
package profile
import (
"testing"
)
func TestProfileFlags(t *testing.T) {
// check if all IDs have a name
for key, entry := range flagIDs {
if _, ok := flagNames[entry]; !ok {
t.Errorf("could not find entry for %s in flagNames", key)
}
}
// check if all names have an ID
for key, entry := range flagNames {
if _, ok := flagIDs[entry]; !ok {
t.Errorf("could not find entry for %d in flagNames", key)
}
}
// // check Has
// emptyFlags := ProfileFlags{}
// for flag, name := range flagNames {
// if !sortedFlags.Has(flag) {
// t.Errorf("sortedFlags should have flag %s (%d)", name, flag)
// }
// if emptyFlags.Has(flag) {
// t.Errorf("emptyFlags should not have flag %s (%d)", name, flag)
// }
// }
//
// // check ProfileFlags creation from strings
// var allFlagStrings []string
// for _, flag := range *sortedFlags {
// allFlagStrings = append(allFlagStrings, flagNames[flag])
// }
// newFlags, err := FlagsFromNames(allFlagStrings)
// if err != nil {
// t.Errorf("error while parsing flags: %s", err)
// }
// if newFlags.String() != sortedFlags.String() {
// t.Errorf("parsed flags are not correct (or tests have not been updated to reflect the right number), expected %v, got %v", *sortedFlags, *newFlags)
// }
//
// // check ProfileFlags Stringer
// flagString := newFlags.String()
// check := strings.Join(allFlagStrings, ",")
// if flagString != check {
// t.Errorf("flag string is not correct, expected %s, got %s", check, flagString)
// }
}

76
profile/framework.go Normal file
View File

@@ -0,0 +1,76 @@
package profile
// DEACTIVATED
// import (
// "fmt"
// "os"
// "path/filepath"
// "regexp"
// "strings"
//
// "github.com/Safing/portbase/log"
// )
//
// type Framework struct {
// // go hirarchy up
// FindParent uint8 `json:",omitempty bson:",omitempty"`
// // get path from parent, amount of levels to go up the tree (1 means parent, 2 means parent of parents, and so on)
// MergeWithParent bool `json:",omitempty bson:",omitempty"`
// // instead of getting the path of the parent, merge with it by presenting connections as if they were from that parent
//
// // go hirarchy down
// Find string `json:",omitempty bson:",omitempty"`
// // Regular expression for finding path elements
// Build string `json:",omitempty bson:",omitempty"`
// // Path definitions for building path
// Virtual bool `json:",omitempty bson:",omitempty"`
// // Treat resulting path as virtual, do not check if valid
// }
//
// func (f *Framework) GetNewPath(command string, cwd string) (string, error) {
// // "/usr/bin/python script"
// // to
// // "/path/to/script"
// regex, err := regexp.Compile(f.Find)
// if err != nil {
// return "", fmt.Errorf("profiles(framework): failed to compile framework regex: %s", err)
// }
// matched := regex.FindAllStringSubmatch(command, -1)
// if len(matched) == 0 || len(matched[0]) < 2 {
// return "", fmt.Errorf("profiles(framework): regex \"%s\" for constructing path did not match command \"%s\"", f.Find, command)
// }
//
// var lastError error
// var buildPath string
// for _, buildPath = range strings.Split(f.Build, "|") {
//
// buildPath = strings.Replace(buildPath, "{CWD}", cwd, -1)
// for i := 1; i < len(matched[0]); i++ {
// buildPath = strings.Replace(buildPath, fmt.Sprintf("{%d}", i), matched[0][i], -1)
// }
//
// buildPath = filepath.Clean(buildPath)
//
// if !f.Virtual {
// if !strings.HasPrefix(buildPath, "~/") && !filepath.IsAbs(buildPath) {
// lastError = fmt.Errorf("constructed path \"%s\" from framework is not absolute", buildPath)
// continue
// }
// if _, err := os.Stat(buildPath); os.IsNotExist(err) {
// lastError = fmt.Errorf("constructed path \"%s\" does not exist", buildPath)
// continue
// }
// }
//
// lastError = nil
// break
//
// }
//
// if lastError != nil {
// return "", fmt.Errorf("profiles(framework): failed to construct valid path, last error: %s", lastError)
// }
// log.Tracef("profiles(framework): transformed \"%s\" (%s) to \"%s\"", command, cwd, buildPath)
// return buildPath, nil
// }

30
profile/framework_test.go Normal file
View File

@@ -0,0 +1,30 @@
package profile
// DEACTIVATED
// import (
// "testing"
// )
//
// func testGetNewPath(t *testing.T, f *Framework, command, cwd, expect string) {
// newPath, err := f.GetNewPath(command, cwd)
// if err != nil {
// t.Errorf("GetNewPath failed: %s", err)
// }
// if newPath != expect {
// t.Errorf("GetNewPath return unexpected result: got %s, expected %s", newPath, expect)
// }
// }
//
// func TestFramework(t *testing.T) {
// f1 := &Framework{
// Find: "([^ ]+)$",
// Build: "{CWD}/{1}",
// }
// testGetNewPath(t, f1, "/usr/bin/python bash", "/bin", "/bin/bash")
// f2 := &Framework{
// Find: "([^ ]+)$",
// Build: "{1}|{CWD}/{1}",
// }
// testGetNewPath(t, f2, "/usr/bin/python /bin/bash", "/tmp", "/bin/bash")
// }

101
profile/index/index.go Normal file
View File

@@ -0,0 +1,101 @@
package index
import (
"sync"
"fmt"
"errors"
"encoding/base64"
"github.com/Safing/portbase/database/record"
"github.com/Safing/portbase/utils"
)
// ProfileIndex links an Identifier to Profiles
type ProfileIndex struct {
record.Base
sync.Mutex
ID string
UserProfiles []string
StampProfiles []string
}
func makeIndexRecordKey(id string) string {
return fmt.Sprintf("core:profiles/index/%s", base64.RawURLEncoding.EncodeToString([]byte(id)))
}
// NewIndex returns a new ProfileIndex.
func NewIndex(id string) *ProfileIndex {
return &ProfileIndex{
ID: id,
}
}
// AddUserProfile adds a User Profile to the index.
func (pi *ProfileIndex) AddUserProfile(id string) (changed bool) {
if !utils.StringInSlice(pi.UserProfiles, id) {
pi.UserProfiles = append(pi.UserProfiles, id)
return true
}
return false
}
// AddStampProfile adds a Stamp Profile to the index.
func (pi *ProfileIndex) AddStampProfile(id string) (changed bool) {
if !utils.StringInSlice(pi.StampProfiles, id) {
pi.StampProfiles = append(pi.StampProfiles, id)
return true
}
return false
}
// RemoveUserProfile removes a profile from the index.
func (pi *ProfileIndex) RemoveUserProfile(id string) {
pi.UserProfiles = utils.RemoveFromStringSlice(pi.UserProfiles, id)
}
// RemoveStampProfile removes a profile from the index.
func (pi *ProfileIndex) RemoveStampProfile(id string) {
pi.StampProfiles = utils.RemoveFromStringSlice(pi.StampProfiles, id)
}
// GetIndex gets a ProfileIndex from the database.
func GetIndex(id string) (*ProfileIndex, error) {
key := makeIndexRecordKey(id)
r, err := indexDB.Get(key)
if err != nil {
return nil, err
}
// unwrap
if r.IsWrapped() {
// only allocate a new struct, if we need it
new := &ProfileIndex{}
err = record.Unwrap(r, new)
if err != nil {
return nil, err
}
return new, nil
}
// or adjust type
new, ok := r.(*ProfileIndex)
if !ok {
return nil, fmt.Errorf("record not of type *ProfileIndex, but %T", r)
}
return new, nil
}
// Save saves the Identifiers to the database
func (pi *ProfileIndex) Save() error {
if pi.Key() == "" {
if pi.ID != "" {
pi.SetKey(makeIndexRecordKey(pi.ID))
} else {
return errors.New("missing identification Key")
}
}
return indexDB.Put(pi)
}

101
profile/index/indexer.go Normal file
View File

@@ -0,0 +1,101 @@
package index
import (
"strings"
"github.com/Safing/portbase/database"
"github.com/Safing/portbase/database/query"
"github.com/Safing/portbase/database/record"
"github.com/Safing/portbase/log"
"github.com/Safing/portbase/modules"
"github.com/Safing/portmaster/profile"
)
// FIXME: listen for profile changes and update the index
var (
indexDB = database.NewInterface(&database.Options{
Local: true, // we want to access crownjewel records
AlwaysMakeCrownjewel: true, // never sync the index
})
indexSub *database.Subscription
shutdownIndexer = make(chan struct{})
)
func init() {
modules.Register("profile:index", nil, start, stop, "database")
}
func start() (err error) {
indexSub, err = indexDB.Subscribe(query.New("core:profiles/user/"))
if err != nil {
return err
}
return nil
}
func stop() error {
close(shutdownIndexer)
indexSub.Cancel()
return nil
}
func indexer() {
for {
select {
case <-shutdownIndexer:
return
case r := <-indexSub.Feed:
prof := ensureProfile(r)
if prof != nil {
for _, id := range prof.Identifiers {
if strings.HasPrefix(id, profile.IdentifierPrefix) {
// get Profile and ensure identifier is set
pi, err := GetIndex(id)
if err != nil {
if err == database.ErrNotFound {
pi = NewIndex(id)
} else {
log.Errorf("profile/index: could not save updated profile index: %s", err)
}
}
if pi.AddUserProfile(prof.ID) {
err := pi.Save()
if err != nil {
log.Errorf("profile/index: could not save updated profile index: %s", err)
}
}
}
}
}
}
}
}
func ensureProfile(r record.Record) *profile.Profile {
// unwrap
if r.IsWrapped() {
// only allocate a new struct, if we need it
new := &profile.Profile{}
err := record.Unwrap(r, new)
if err != nil {
log.Errorf("profile/index: could not unwrap Profile: %s", err)
return nil
}
return new
}
// or adjust type
new, ok := r.(*profile.Profile)
if !ok {
log.Errorf("profile/index: record not of type *Profile, but %T", r)
return nil
}
return new
}

View File

@@ -0,0 +1,17 @@
package matcher
import (
"github.com/Safing/portbase/database"
)
// core:profiles/user/12345-1234-125-1234-1235
// core:profiles/special/default
// /global
// core:profiles/stamp/12334-1235-1234-5123-1234
// core:profiles/identifier/base64
var (
profileDB = database.NewInterface(&database.Options{
Local: true, // we want to access crownjewel records (indexes are)
})
)

View File

@@ -0,0 +1,23 @@
package matcher
import (
"strings"
"github.com/Safing/portmaster/process"
"github.com/Safing/portmaster/profile"
)
// CheckFingerprints checks what fingerprints match and returns the total score.
func CheckFingerprints(proc *process.Process, prof *profile.Profile) (score int, err error) {
// FIXME: kinda a dummy for now
for _, fp := range prof.Fingerprints {
if strings.HasPrefix(fp, "fullpath:") {
if fp[9:] == proc.Path {
return 3, nil
}
}
}
return 0, nil
}

View File

@@ -0,0 +1,18 @@
package matcher
import (
"fmt"
"strings"
"github.com/Safing/portmaster/process"
"github.com/Safing/portmaster/profile"
)
// GetIdentificationPath returns the identifier for the given process (linux edition).
func GetIdentificationPath(p *process.Process) string {
splittedPath := strings.Split(p.Path, "/")
if len(splittedPath) > 3 {
return fmt.Sprintf("%s%s", profile.IdentifierPrefix, strings.Join(splittedPath[len(splittedPath)-3:len(splittedPath)], "/"))
}
return fmt.Sprintf("%s%s", profile.IdentifierPrefix, p.Path)
}

View File

@@ -0,0 +1,25 @@
package matcher
import (
"testing"
"github.com/Safing/portmaster/process"
)
func TestGetIdentifierLinux(t *testing.T) {
p := &process.Process{
Path: "/usr/lib/firefox/firefox",
}
if GetIdentificationPath(p) != "lin:lib/firefox/firefox" {
t.Fatal("mismatch!")
}
p = &process.Process{
Path: "/opt/start",
}
if GetIdentificationPath(p) != "lin:/opt/start" {
t.Fatal("mismatch!")
}
}

View File

@@ -0,0 +1,61 @@
package matcher
import (
"fmt"
"strings"
"github.com/Safing/portbase/log"
"github.com/Safing/portmaster/process"
"github.com/Safing/portmaster/profile"
"github.com/Safing/portmaster/profile/index"
)
// GetProfileSet finds a local profile.
func GetProfileSet(proc *process.Process) (set *profile.ProfileSet, err error) {
identPath := GetIdentificationPath(proc)
pi, err := index.GetIndex(identPath)
var bestScore int
var bestProfile *profile.Profile
for _, id := range pi.UserProfiles {
prof, err := profile.GetUserProfile(id)
if err != nil {
log.Errorf("profile/matcher: failed to load profile: %s", err)
continue
}
score, err := CheckFingerprints(proc, prof)
if score > bestScore {
bestScore = score
bestProfile = prof
}
}
if bestProfile == nil {
bestProfile = ProfileFromProcess(proc)
}
// FIXME: fetch stamp profile
set = profile.NewSet(bestProfile, nil)
return set, nil
}
// ProfileFromProcess creates an initial profile based on the given process.
func ProfileFromProcess(proc *process.Process) *profile.Profile {
new := profile.New()
splittedPath := strings.Split(proc.Path, "/")
new.Name = strings.ToTitle(splittedPath[len(splittedPath)-1])
new.Identifiers = append(new.Identifiers, GetIdentificationPath(proc))
new.Fingerprints = append(new.Fingerprints, fmt.Sprintf("fullpath:%s", proc.Path))
err := new.Save(profile.UserNamespace)
if err != nil {
log.Errorf("profile/matcher: could not save new profile: %s", new.Name)
}
return new
}

89
profile/ports.go Normal file
View File

@@ -0,0 +1,89 @@
package profile
import (
"fmt"
"strconv"
"strings"
)
// Ports is a list of permitted or denied ports
type Ports map[string][]*Port
// IsSet returns whether the Ports object is "set".
func (p Ports) IsSet() bool {
if p != nil {
return true
}
return false
}
// CheckStatus returns whether listening/connecting to a certain port is allowed, and if this option is even set.
func (p Ports) CheckStatus(listen bool, protocol string, port uint16) (permit, ok bool) {
if p == nil {
return false, false
}
if listen {
protocol = "<" + protocol
}
portDefinitions, ok := p[protocol]
if ok {
for _, portD := range portDefinitions {
if portD.Matches(port) {
return portD.Permit, true
}
}
}
return false, true
}
func (p Ports) String() string {
var s []string
for protocol, ports := range p {
var portStrings []string
for _, port := range ports {
portStrings = append(portStrings, port.String())
}
s = append(s, fmt.Sprintf("%s:[%s]", protocol, strings.Join(portStrings, ", ")))
}
if len(s) == 0 {
return "None"
}
return strings.Join(s, ", ")
}
// Port represents a port range and a verdict.
type Port struct {
Permit bool
Created int64
Start uint16
End uint16
}
// Matches checks whether a port object matches the given port.
func (p Port) Matches(port uint16) bool {
if port >= p.Start && port <= p.End {
return true
}
return false
}
func (p Port) String() string {
var s string
if p.Permit {
s += "permit:"
} else {
s += "deny:"
}
if p.Start == p.End {
s += strconv.Itoa(int(p.Start))
} else {
s += fmt.Sprintf("%d-%d", p.Start, p.End)
}
return s
}

87
profile/profile.go Normal file
View File

@@ -0,0 +1,87 @@
package profile
import (
"fmt"
"sync"
"github.com/satori/go.uuid"
"github.com/Safing/portbase/database/record"
"github.com/Safing/portmaster/status"
)
// Profile is used to predefine a security profile for applications.
type Profile struct {
record.Base
sync.Mutex
// Profile Metadata
ID string
Name string
Description string
Homepage string
// Icon is a path to the icon and is either prefixed "f:" for filepath, "d:" for a database path or "e:" for the encoded data.
Icon string
// Identification
Identifiers []string
Fingerprints []string
// The mininum security level to apply to connections made with this profile
SecurityLevel uint8
Flags ProfileFlags
Domains Domains
Ports Ports
StampProfileKey string
// If a Profile is declared as a Framework (i.e. an Interpreter and the likes), then the real process must be found
// Framework *Framework `json:",omitempty bson:",omitempty"`
// When this Profile was approximately last used (for performance reasons not every single usage is saved)
ApproxLastUsed int64
}
func New() *Profile {
return &Profile{}
}
// Save saves the profile to the database
func (profile *Profile) Save(namespace string) error {
if profile.ID == "" {
u, err := uuid.NewV4()
if err != nil {
return err
}
profile.ID = u.String()
}
if profile.Key() == "" {
if namespace == "" {
return fmt.Errorf("no key or namespace defined for profile %s", profile.String())
}
profile.SetKey(fmt.Sprintf("config:profiles/%s/%s", namespace, profile.ID))
}
return profileDB.Put(profile)
}
// String returns a string representation of the Profile.
func (profile *Profile) String() string {
return profile.Name
}
// DetailedString returns a more detailed string representation of theProfile.
func (profile *Profile) DetailedString() string {
return fmt.Sprintf("%s(SL=%s Flags=[%s] Ports=[%s] #Domains=%d)", profile.Name, status.FmtSecurityLevel(profile.SecurityLevel), profile.Flags.String(), profile.Ports.String(), len(profile.Domains))
}
// GetUserProfile loads a profile from the database.
func GetUserProfile(ID string) (*Profile, error) {
return nil, nil
}
// GetStampProfile loads a profile from the database.
func GetStampProfile(ID string) (*Profile, error) {
return nil, nil
}

121
profile/profileset.go Normal file
View File

@@ -0,0 +1,121 @@
package profile
var (
emptyFlags = ProfileFlags{}
emptyPorts = Ports{}
)
// ProfileSet handles Profile chaining.
type ProfileSet struct {
Profiles [4]*Profile
// Application
// Global
// Stamp
// Default
Independent bool
}
// NewSet returns a new profile set with given the profiles.
func NewSet(user, stamp *Profile) *ProfileSet {
new := &ProfileSet{
Profiles: [4]*Profile{
user, // Application
nil, // Global
stamp, // Stamp
nil, // Default
},
}
new.Update()
return new
}
// Update gets the new global and default profile and updates the independence status. It must be called when reusing a profile set for a series of calls.
func (ps *ProfileSet) Update() {
specialProfileLock.RLock()
defer specialProfileLock.RUnlock()
// update profiles
ps.Profiles[1] = globalProfile
ps.Profiles[3] = defaultProfile
// update independence
if ps.Flags().Has(Independent, ps.SecurityLevel()) {
// Stamp profiles do not have the Independent flag
ps.Independent = true
} else {
ps.Independent = false
}
}
// Flags returns the highest prioritized ProfileFlags configuration.
func (ps *ProfileSet) Flags() ProfileFlags {
for _, profile := range ps.Profiles {
if profile != nil {
if profile.Flags.IsSet() {
return profile.Flags
}
}
}
return emptyFlags
}
// CheckDomainStatus checks if the given domain is governed in any the lists of domains and returns whether it is permitted.
func (ps *ProfileSet) CheckDomainStatus(domain string) (permit, ok bool) {
for i, profile := range ps.Profiles {
if i == 2 && ps.Independent {
continue
}
if profile != nil {
if profile.Domains.IsSet() {
permit, ok = profile.Domains.CheckStatus(domain)
if ok {
return
}
}
}
}
return false, false
}
// Ports returns the highest prioritized Ports configuration.
func (ps *ProfileSet) Ports() Ports {
for i, profile := range ps.Profiles {
if i == 2 && ps.Independent {
continue
}
if profile != nil {
if profile.Ports.IsSet() {
return profile.Ports
}
}
}
return emptyPorts
}
// SecurityLevel returns the highest prioritized security level.
func (ps *ProfileSet) SecurityLevel() uint8 {
for i, profile := range ps.Profiles {
if i == 2 {
// Stamp profiles do not have the SecurityLevel setting
continue
}
if profile != nil {
if profile.SecurityLevel > 0 {
return profile.SecurityLevel
}
}
}
return 0
}

261
profile/sampledata.go Normal file
View File

@@ -0,0 +1,261 @@
package profile
// DEACTIVATED
// import (
// "runtime"
//
// "github.com/Safing/portbase/database"
// "github.com/Safing/portbase/log"
// )
//
// func init() {
//
// // Data here is for demo purposes, Profiles will be served over network soon™.
//
// log.Tracef("profiles: loading sample profiles for %s", runtime.GOOS)
//
// switch runtime.GOOS {
// case "linux":
//
// log.Trace("profiles: loading linux sample profiles")
//
// (&Profile{
// Name: "Chromium",
// Description: "Browser by Google",
// Path: "/usr/lib/chromium-browser/chromium-browser",
// Flags: []int8{User, Internet, LocalNet, Browser},
// ConnectPorts: []uint16{80, 443},
// }).CreateInDist()
//
// (&Profile{
// Name: "Evolution",
// Description: "PIM solution by GNOME",
// Path: "/usr/bin/evolution",
// Flags: []int8{User, Internet, Gateway},
// ConnectPorts: []uint16{25, 80, 143, 443, 465, 587, 993, 995},
// SecurityLevel: 2,
// }).CreateInDist()
//
// (&Profile{
// Name: "Evolution Calendar",
// Description: "PIM solution by GNOME - Calendar",
// Path: "/usr/lib/evolution/evolution-calendar-factory-subprocess",
// Flags: []int8{User, Internet, Gateway},
// ConnectPorts: []uint16{80, 443},
// SecurityLevel: 2,
// }).CreateInDist()
//
// (&Profile{
// Name: "Spotify",
// Description: "Music streaming",
// Path: "/usr/share/spotify/spotify",
// ConnectPorts: []uint16{80, 443, 4070},
// Flags: []int8{User, Internet, Strict},
// }).CreateInDist()
//
// (&Profile{
// // flatpak edition
// Name: "Spotify",
// Description: "Music streaming",
// Path: "/newroot/app/extra/share/spotify/spotify",
// ConnectPorts: []uint16{80, 443, 4070},
// Flags: []int8{User, Internet, Strict},
// }).CreateInDist()
//
// (&Profile{
// Name: "Evince",
// Description: "PDF Document Reader",
// Path: "/usr/bin/evince",
// Flags: []int8{},
// SecurityLevel: 2,
// }).CreateInDist()
//
// (&Profile{
// Name: "Ahavi",
// Description: "mDNS service",
// Path: "/usr/bin/avahi-daemon",
// Flags: []int8{System, LocalNet, Service, Directconnect},
// }).CreateInDist()
//
// (&Profile{
// Name: "Python 2.7 Framework",
// Description: "Correctly handle python scripts",
// Path: "/usr/bin/python2.7",
// Framework: &Framework{
// Find: "^[^ ]+ ([^ ]+)",
// Build: "{1}|{CWD}/{1}",
// },
// }).CreateInDist()
//
// (&Profile{
// Name: "Python 3.5 Framework",
// Description: "Correctly handle python scripts",
// Path: "/usr/bin/python3.5",
// Framework: &Framework{
// Find: "^[^ ]+ ([^ ]+)",
// Build: "{1}|{CWD}/{1}",
// },
// }).CreateInDist()
//
// (&Profile{
// Name: "DHCP Client",
// Description: "Client software for the DHCP protocol",
// Path: "/sbin/dhclient",
// Framework: &Framework{
// FindParent: 1,
// MergeWithParent: true,
// },
// }).CreateInDist()
//
// // Default Profiles
// // Until Profiles are distributed over the network, default profiles are activated when the Default Profile for "/" is missing.
//
// if ok, err := database.Has(ds.NewKey("/Data/Profiles/Profile_d-2f")); !ok || err != nil {
//
// log.Trace("profiles: loading linux default sample profiles")
//
// (&Profile{
// Name: "Default Base",
// Description: "Default Profile for /",
// Path: "/",
// Flags: []int8{Internet, LocalNet, Strict},
// Default: true,
// }).Create()
//
// (&Profile{
// Name: "Installed Applications",
// Description: "Default Profile for /usr/bin",
// Path: "/usr/bin/",
// Flags: []int8{Internet, LocalNet, Gateway},
// Default: true,
// }).Create()
//
// (&Profile{
// Name: "System Binaries (/sbin)",
// Description: "Default Profile for ~/Downloads",
// Path: "/sbin/",
// Flags: []int8{Internet, LocalNet, Directconnect, Service, System},
// Default: true,
// }).Create()
//
// (&Profile{
// Name: "System Binaries (/usr/sbin)",
// Description: "Default Profile for ~/Downloads",
// Path: "/usr/sbin/",
// Flags: []int8{Internet, LocalNet, Directconnect, Service, System},
// Default: true,
// }).Create()
//
// (&Profile{
// Name: "System Tmp folder",
// Description: "Default Profile for /tmp",
// Path: "/tmp/",
// Flags: []int8{}, // deny all
// Default: true,
// }).Create()
//
// (&Profile{
// Name: "User Home",
// Description: "Default Profile for ~/",
// Path: "~/",
// Flags: []int8{Internet, LocalNet, Gateway},
// Default: true,
// }).Create()
//
// (&Profile{
// Name: "User Downloads",
// Description: "Default Profile for ~/Downloads",
// Path: "~/Downloads/",
// Flags: []int8{}, // deny all
// Default: true,
// }).Create()
//
// (&Profile{
// Name: "User Cache",
// Description: "Default Profile for ~/.cache",
// Path: "~/.cache/",
// Flags: []int8{}, // deny all
// Default: true,
// }).Create()
//
// }
//
// case "windows":
//
// log.Trace("profiles: loading windows sample profiles")
//
// (&Profile{
// Name: "Firefox",
// Description: "Firefox Browser by Mozilla",
// Path: "C:\\Program Files\\Mozilla Firefox\\firefox.exe",
// Flags: []int8{User, Internet, LocalNet, Browser},
// ConnectPorts: []uint16{80, 443},
// }).CreateInDist()
//
// // Default Profiles
// // Until Profiles are distributed over the network, default profiles are activated when the Default Profile for "C" is missing.
//
// if ok, err := database.Has(ds.NewKey("/Data/Profiles/Profile:d-C")); !ok || err != nil {
//
// log.Trace("profiles: loading windows default sample profiles")
//
// (&Profile{
// Name: "Default Base",
// Description: "Default Profile for C",
// Path: "C",
// Flags: []int8{Internet, LocalNet, Strict},
// Default: true,
// }).Create()
//
// (&Profile{
// Name: "Installed Applications",
// Description: "Default Profile for C:\\Program Files",
// Path: "C:\\Program Files\\",
// Flags: []int8{Internet, LocalNet, Gateway},
// Default: true,
// }).Create()
//
// (&Profile{
// Name: "Installed Applications (x86)",
// Description: "Default Profile for C:\\Program Files (x86)",
// Path: "C:\\Program Files (x86)\\",
// Flags: []int8{Internet, LocalNet, Gateway},
// Default: true,
// }).Create()
//
// (&Profile{
// Name: "System Applications (C:\\Windows\\System32)",
// Description: "Default Profile for C:\\Windows\\System32",
// Path: "C:\\Windows\\System32\\",
// Flags: []int8{Internet, LocalNet, Directconnect, Service, System},
// Default: true,
// }).Create()
//
// (&Profile{
// Name: "User Home",
// Description: "Default Profile for ~/",
// Path: "~/",
// Flags: []int8{Internet, LocalNet, Gateway},
// Default: true,
// }).Create()
//
// (&Profile{
// Name: "User Downloads",
// Description: "Default Profile for ~/Downloads",
// Path: "~/Downloads/",
// Flags: []int8{}, // deny all
// Default: true,
// }).Create()
//
// (&Profile{
// Name: "User Cache",
// Description: "Default Profile for ~/.cache",
// Path: "~/.cache/",
// Flags: []int8{}, // deny all
// Default: true,
// }).Create()
// }
// }
//
// }

View File

@@ -0,0 +1,12 @@
package profile
import "sync"
var (
globalProfile *Profile
defaultProfile *Profile
specialProfileLock sync.RWMutex
)
// FIXME: subscribe to changes and update profiles