Finish initial adaption of profiles
This commit is contained in:
9
profile/const.go
Normal file
9
profile/const.go
Normal 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
6
profile/const_linux.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package profile
|
||||
|
||||
// OS Identifier Prefix
|
||||
const (
|
||||
IdentifierPrefix = "lin:"
|
||||
)
|
||||
21
profile/database.go
Normal file
21
profile/database.go
Normal 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
42
profile/domains.go
Normal 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
145
profile/flags.go
Normal 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
54
profile/flags_test.go
Normal 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
76
profile/framework.go
Normal 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
30
profile/framework_test.go
Normal 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
101
profile/index/index.go
Normal 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
101
profile/index/indexer.go
Normal 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
|
||||
}
|
||||
17
profile/matcher/database.go
Normal file
17
profile/matcher/database.go
Normal 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)
|
||||
})
|
||||
)
|
||||
23
profile/matcher/fingerprints.go
Normal file
23
profile/matcher/fingerprints.go
Normal 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
|
||||
}
|
||||
18
profile/matcher/identpath_linux.go
Normal file
18
profile/matcher/identpath_linux.go
Normal 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)
|
||||
}
|
||||
25
profile/matcher/identpath_linux_test.go
Normal file
25
profile/matcher/identpath_linux_test.go
Normal 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!")
|
||||
}
|
||||
}
|
||||
61
profile/matcher/matcher.go
Normal file
61
profile/matcher/matcher.go
Normal 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
89
profile/ports.go
Normal 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
87
profile/profile.go
Normal 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
121
profile/profileset.go
Normal 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
261
profile/sampledata.go
Normal 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()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
12
profile/specialprofiles.go
Normal file
12
profile/specialprofiles.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package profile
|
||||
|
||||
import "sync"
|
||||
|
||||
var (
|
||||
globalProfile *Profile
|
||||
defaultProfile *Profile
|
||||
|
||||
specialProfileLock sync.RWMutex
|
||||
)
|
||||
|
||||
// FIXME: subscribe to changes and update profiles
|
||||
Reference in New Issue
Block a user