From 8fb21fd9004ef38355bc3e4d9bddb9cf259c76db Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 3 Dec 2018 20:02:03 +0100 Subject: [PATCH] Work on pm restructure --- process/database.go | 3 + process/matching.go | 67 ++++- profile/active.go | 66 +++++ profile/const.go | 7 +- profile/const_darwin.go | 2 +- profile/const_linux.go | 2 +- profile/database.go | 5 +- profile/defaults.go | 82 ++++++ profile/fingerprint.go | 16 +- profile/identifier_linux.go | 47 ++++ profile/identifier_linux_test.go | 23 ++ profile/index/index.go | 67 +++-- profile/{matcher => matching}/database.go | 2 +- profile/{matcher => matching}/fingerprints.go | 0 .../{matcher => matching}/identpath_linux.go | 0 .../identpath_linux_test.go | 0 profile/{matcher => matching}/matcher.go | 0 profile/module.go | 3 + profile/profile.go | 54 +++- profile/profileset.go | 13 +- profile/sampledata.go | 261 ------------------ profile/specialprofiles.go | 40 ++- profile/updates.go | 53 ++++ 23 files changed, 487 insertions(+), 326 deletions(-) create mode 100644 profile/active.go create mode 100644 profile/defaults.go create mode 100644 profile/identifier_linux.go create mode 100644 profile/identifier_linux_test.go rename profile/{matcher => matching}/database.go (95%) rename profile/{matcher => matching}/fingerprints.go (100%) rename profile/{matcher => matching}/identpath_linux.go (100%) rename profile/{matcher => matching}/identpath_linux_test.go (100%) rename profile/{matcher => matching}/matcher.go (100%) create mode 100644 profile/module.go delete mode 100644 profile/sampledata.go create mode 100644 profile/updates.go diff --git a/process/database.go b/process/database.go index a6cb8471..4c3a8a81 100644 --- a/process/database.go +++ b/process/database.go @@ -6,6 +6,7 @@ import ( "time" "github.com/Safing/portbase/database" + "github.com/Safing/portmaster/profile" "github.com/tevino/abool" ) @@ -76,6 +77,8 @@ func (p *Process) Delete() { if dbControllerFlag.IsSet() { dbController.PushUpdate(p) } + + profile.DeactivateProfileSet(p.profileSet) } // CleanProcessStorage cleans the storage from old processes. diff --git a/process/matching.go b/process/matching.go index ad821770..ccf31875 100644 --- a/process/matching.go +++ b/process/matching.go @@ -1,22 +1,81 @@ package process import ( + "fmt" + + "github.com/Safing/portbase/database" "github.com/Safing/portbase/log" "github.com/Safing/portmaster/profile" + "github.com/Safing/portmaster/profile/index" ) // FindProfiles finds and assigns a profile set to the process. -func (p *Process) FindProfiles() { +func (p *Process) FindProfiles() error { // Get fingerprints of process // Check if user profile already exists, else create new + pathIdentifier := profile.GetPathIdentifier(p.Path) + indexRecord, err := index.Get(pathIdentifier) + if err != nil && err != database.ErrNotFound { + log.Errorf("process: could not get profile index for %s: %s", pathIdentifier, err) + } + + var possibleProfiles []*profile.Profile + if indexRecord != nil { + for _, profileID := range indexRecord.UserProfiles { + prof, err := profile.Get(profileID) + if err != nil { + log.Errorf("process: failed to load profile %s: %s", profileID, err) + } + possibleProfiles = append(possibleProfiles, prof) + } + } + } + + prof := selectProfile(p, possibleProfiles) + if prof == nil { + // create new profile + prof := profile.New() + prof.Name = p.ExecName + prof.AddFingerprint(&profile.Fingerprint{ + Type: "full_path", + Value: p.Path, + }) + // TODO: maybe add sha256_sum? + prof.MarkUsed() + prof.Save() + } // Find/Re-evaluate Stamp profile + // 1. check linked stamp profile + // 2. if last check is was more than a week ago, fetch from stamp: + // 3. send path identifier to stamp + // 4. evaluate all returned profiles + // 5. select best + // 6. link stamp profile to user profile + // FIXME: implement! - // p.UserProfileKey - // p.profileSet + if prof.MarkUsed() { + prof.Save() + } + p.UserProfileKey = prof.Key() + p.profileSet = profile.NewSet(prof, nil) + p.Save() + + return nil +} + +func selectProfile(p *Process, profs []*profile.Profile) (selectedProfile *profile.Profile) { + var highestScore int + for _, prof := range profs { + score := matchProfile(p, prof) + if score > highestScore { + selectedProfile = prof + } + } + return } func matchProfile(p *Process, prof *profile.Profile) (score int) { @@ -37,8 +96,10 @@ func matchFingerprint(p *Process, fp *profile.Fingerprint) (score int) { } return profile.GetFingerprintWeight(fp.Type) case "partial_path": + // FIXME: if full_path matches, do not match partial paths return profile.GetFingerprintWeight(fp.Type) case "md5_sum", "sha1_sum", "sha256_sum": + // FIXME: one sum is enough, check sums in a grouped form, start with the best sum, err := p.GetExecHash(fp.Type) if err != nil { log.Errorf("process: failed to get hash of executable: %s", err) diff --git a/profile/active.go b/profile/active.go new file mode 100644 index 00000000..e03a7d52 --- /dev/null +++ b/profile/active.go @@ -0,0 +1,66 @@ +package profile + +import "sync" + +var ( + activeProfileSets = make(map[string]*Set) + activeProfileSetsLock sync.RWMutex +) + +func activateProfileSet(set *Set) { + set.Lock() + defer set.Unlock() + activeProfileSetsLock.Lock() + defer activeProfileSetsLock.Unlock() + activeProfileSets[set.profiles[0].ID] = set +} + +// DeactivateProfileSet marks a profile set as not active. +func DeactivateProfileSet(set *Set) { + set.Lock() + defer set.Unlock() + activeProfileSetsLock.Lock() + defer activeProfileSetsLock.Unlock() + delete(activeProfileSets, set.profiles[0].ID) +} + +func updateActiveUserProfile(profile *Profile) { + activeProfileSetsLock.RLock() + defer activeProfileSetsLock.RUnlock() + activeSet, ok := activeProfileSets[profile.ID] + if ok { + activeSet.Lock() + defer activeSet.Unlock() + activeSet.profiles[0] = profile + } +} + +func updateActiveGlobalProfile(profile *Profile) { + updateActiveProfile(1, profile) +} + +func updateActiveStampProfile(profile *Profile) { + updateActiveProfile(2, profile) +} + +func updateActiveFallbackProfile(profile *Profile) { + updateActiveProfile(3, profile) +} + +func updateActiveProfile(setID int, profile *Profile) { + activeProfileSetsLock.RLock() + defer activeProfileSetsLock.RUnlock() + + for _, activeSet := range activeProfileSets { + activeSet.Lock() + activeProfile := activeSet.profiles[setID] + if activeProfile != nil { + activeProfile.Lock() + if activeProfile.ID == profile.ID { + activeSet.profiles[setID] = profile + } + activeProfile.Unlock() + } + activeSet.Unlock() + } +} diff --git a/profile/const.go b/profile/const.go index a355eceb..8eac794d 100644 --- a/profile/const.go +++ b/profile/const.go @@ -2,8 +2,7 @@ package profile // Platform identifiers const ( - PlatformAny = "any" - PlatformLinux = "lin" - PlatformWindows = "win" - PlatformMac = "mac" + PlatformLinux = "linux" + PlatformWindows = "windows" + PlatformMac = "macos" ) diff --git a/profile/const_darwin.go b/profile/const_darwin.go index 4868ddf5..5654364a 100644 --- a/profile/const_darwin.go +++ b/profile/const_darwin.go @@ -2,5 +2,5 @@ package profile // OS Identifier const ( - osIdentifier = "mac" + osIdentifier = PlatformMac ) diff --git a/profile/const_linux.go b/profile/const_linux.go index 25d1c56f..0b8f8874 100644 --- a/profile/const_linux.go +++ b/profile/const_linux.go @@ -2,5 +2,5 @@ package profile // OS Identifier const ( - osIdentifier = "lin" + osIdentifier = PlatformLinux ) diff --git a/profile/database.go b/profile/database.go index 72cd0a55..1e748c32 100644 --- a/profile/database.go +++ b/profile/database.go @@ -12,8 +12,9 @@ import ( // Namespaces const ( - UserNamespace = "user" - StampNamespace = "stamp" + userNamespace = "user" + stampNamespace = "stamp" + specialNamespace = "special" ) var ( diff --git a/profile/defaults.go b/profile/defaults.go new file mode 100644 index 00000000..89afe4b0 --- /dev/null +++ b/profile/defaults.go @@ -0,0 +1,82 @@ +package profile + +import ( + "time" + + "github.com/Safing/portmaster/status" +) + +func makeDefaultGlobalProfile() *Profile { + return &Profile{ + ID: "global", + Name: "Global Profile", + } +} + +func makeDefaultFallbackProfile() *Profile { + return &Profile{ + ID: "fallback", + Name: "Fallback Profile", + Flags: map[uint8]uint8{ + // Profile Modes + Blacklist: status.SecurityLevelDynamic, + Prompt: status.SecurityLevelSecure, + Whitelist: status.SecurityLevelFortress, + + // Network Locations + Internet: status.SecurityLevelsDynamicAndSecure, + LAN: status.SecurityLevelsDynamicAndSecure, + Localhost: status.SecurityLevelsAll, + + // Specials + Related: status.SecurityLevelDynamic, + PeerToPeer: status.SecurityLevelDynamic, + }, + Ports: map[int16][]*Port{ + 6: []*Port{ + &Port{ // SSH + Permit: true, + Created: time.Now().Unix(), + Start: 22, + End: 22, + }, + &Port{ // HTTP + Permit: true, + Created: time.Now().Unix(), + Start: 80, + End: 80, + }, + &Port{ // HTTPS + Permit: true, + Created: time.Now().Unix(), + Start: 443, + End: 443, + }, + &Port{ // SMTP (TLS) + Permit: true, + Created: time.Now().Unix(), + Start: 465, + End: 465, + }, + &Port{ // SMTP (STARTTLS) + Permit: true, + Created: time.Now().Unix(), + Start: 587, + End: 587, + }, + &Port{ // IMAP (TLS) + Permit: true, + Created: time.Now().Unix(), + Start: 993, + End: 993, + }, + &Port{ // IMAP (STARTTLS) + Permit: true, + Created: time.Now().Unix(), + Start: 143, + End: 143, + }, + }, + }, + } +} diff --git a/profile/fingerprint.go b/profile/fingerprint.go index 79297bcd..3908aac6 100644 --- a/profile/fingerprint.go +++ b/profile/fingerprint.go @@ -63,14 +63,14 @@ func GetFingerprintWeight(fpType string) (weight int) { // return // } // -// func (p *Profile) AddFingerprint(fp *Fingerprint) error { -// if fp.OS == "" { -// fp.OS = osIdentifier -// } -// -// p.Fingerprints = append(p.Fingerprints, fp) -// return p.Save() -// } +func (p *Profile) AddFingerprint(fp *Fingerprint) { + if fp.OS == "" { + fp.OS = osIdentifier + } + + p.Fingerprints = append(p.Fingerprints, fp) +} + // // func (p *Profile) GetApplicableFingerprintTypes() (types []string) { // for _, fp := range p.Fingerprints { diff --git a/profile/identifier_linux.go b/profile/identifier_linux.go new file mode 100644 index 00000000..d8020e5e --- /dev/null +++ b/profile/identifier_linux.go @@ -0,0 +1,47 @@ +package profile + +import ( + "path/filepath" + "strings" + + "github.com/Safing/portbase/utils" +) + +// GetPathIdentifier returns the identifier from the given path +func GetPathIdentifier(path string) string { + // clean path + // TODO: is this necessary? + cleanedPath, err := filepath.EvalSymlinks(path) + if err == nil { + path = cleanedPath + } else { + path = filepath.Clean(path) + } + + splittedPath := strings.Split(path, "/") + + // strip sensitive data + switch { + case strings.HasPrefix(path, "/home/"): + splittedPath = splittedPath[3:] + case strings.HasPrefix(path, "/root/"): + splittedPath = splittedPath[2:] + } + + // common directories with executable + if i := utils.IndexOfString(splittedPath, "bin"); i > 0 { + splittedPath = splittedPath[i:] + return strings.Join(splittedPath, "/") + } + if i := utils.IndexOfString(splittedPath, "sbin"); i > 0 { + splittedPath = splittedPath[i:] + return strings.Join(splittedPath, "/") + } + + // shorten to max 3 + if len(splittedPath) > 3 { + splittedPath = splittedPath[len(splittedPath)-3:] + } + + return strings.Join(splittedPath, "/") +} diff --git a/profile/identifier_linux_test.go b/profile/identifier_linux_test.go new file mode 100644 index 00000000..fef1157a --- /dev/null +++ b/profile/identifier_linux_test.go @@ -0,0 +1,23 @@ +package profile + +import "testing" + +func testPathID(t *testing.T, execPath, identifierPath string) { + result := GetPathIdentifier(execPath) + if result != identifierPath { + t.Errorf("unexpected identifier path for %s: got %s, expected %s", execPath, result, identifierPath) + } +} + +func TestGetPathIdentifier(t *testing.T) { + testPathID(t, "/bin/bash", "bin/bash") + testPathID(t, "/home/user/bin/bash", "bin/bash") + testPathID(t, "/home/user/project/main", "project/main") + testPathID(t, "/root/project/main", "project/main") + testPathID(t, "/tmp/a/b/c/d/install.sh", "c/d/install.sh") + testPathID(t, "/sbin/init", "sbin/init") + testPathID(t, "/lib/systemd/systemd-udevd", "lib/systemd/systemd-udevd") + testPathID(t, "/bundle/ruby/2.4.0/bin/passenger", "bin/passenger") + testPathID(t, "/usr/sbin/cron", "sbin/cron") + testPathID(t, "/usr/local/bin/python", "bin/python") +} diff --git a/profile/index/index.go b/profile/index/index.go index 039d551f..52caac55 100644 --- a/profile/index/index.go +++ b/profile/index/index.go @@ -1,27 +1,26 @@ package index import ( - "sync" - "fmt" - "errors" "encoding/base64" + "errors" + "fmt" + "sync" - "github.com/Safing/portbase/database/record" - "github.com/Safing/portbase/utils" + "github.com/Safing/portbase/database/record" + "github.com/Safing/portbase/utils" ) // ProfileIndex links an Identifier to Profiles type ProfileIndex struct { - record.Base - sync.Mutex + record.Base + sync.Mutex - ID string - UserProfiles []string - StampProfiles []string + UserProfiles []string + StampProfiles []string } func makeIndexRecordKey(id string) string { - return fmt.Sprintf("core:profiles/index/%s", base64.RawURLEncoding.EncodeToString([]byte(id))) + return fmt.Sprintf("index:profiles/%s", base64.RawURLEncoding.EncodeToString([]byte(id))) } // NewIndex returns a new ProfileIndex. @@ -32,35 +31,35 @@ func NewIndex(id string) *ProfileIndex { } // 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 +func (pi *ProfileIndex) AddUserProfile(identifier 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 +func (pi *ProfileIndex) AddStampProfile(identifier 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) + 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) + pi.StampProfiles = utils.RemoveFromStringSlice(pi.StampProfiles, id) } -// GetIndex gets a ProfileIndex from the database. -func GetIndex(id string) (*ProfileIndex, error) { +// Get gets a ProfileIndex from the database. +func Get(id string) (*ProfileIndex, error) { key := makeIndexRecordKey(id) r, err := indexDB.Get(key) @@ -89,13 +88,13 @@ func GetIndex(id string) (*ProfileIndex, error) { // 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") - } - } + if pi.Key() == "" { + if pi.ID != "" { + pi.SetKey(makeIndexRecordKey(pi.ID)) + } else { + return errors.New("missing identification Key") + } + } return indexDB.Put(pi) } diff --git a/profile/matcher/database.go b/profile/matching/database.go similarity index 95% rename from profile/matcher/database.go rename to profile/matching/database.go index 9ae4a270..0196d0c5 100644 --- a/profile/matcher/database.go +++ b/profile/matching/database.go @@ -1,4 +1,4 @@ -package matcher +package matching import ( "github.com/Safing/portbase/database" diff --git a/profile/matcher/fingerprints.go b/profile/matching/fingerprints.go similarity index 100% rename from profile/matcher/fingerprints.go rename to profile/matching/fingerprints.go diff --git a/profile/matcher/identpath_linux.go b/profile/matching/identpath_linux.go similarity index 100% rename from profile/matcher/identpath_linux.go rename to profile/matching/identpath_linux.go diff --git a/profile/matcher/identpath_linux_test.go b/profile/matching/identpath_linux_test.go similarity index 100% rename from profile/matcher/identpath_linux_test.go rename to profile/matching/identpath_linux_test.go diff --git a/profile/matcher/matcher.go b/profile/matching/matcher.go similarity index 100% rename from profile/matcher/matcher.go rename to profile/matching/matcher.go diff --git a/profile/module.go b/profile/module.go new file mode 100644 index 00000000..ddfa7407 --- /dev/null +++ b/profile/module.go @@ -0,0 +1,3 @@ +package profile + +. diff --git a/profile/profile.go b/profile/profile.go index 3afba39e..f64e8fbb 100644 --- a/profile/profile.go +++ b/profile/profile.go @@ -3,6 +3,7 @@ package profile import ( "fmt" "sync" + "time" uuid "github.com/satori/go.uuid" @@ -10,6 +11,10 @@ import ( "github.com/Safing/portmaster/status" ) +var ( + lastUsedUpdateThreshold = 1 * time.Hour +) + // Profile is used to predefine a security profile for applications. type Profile struct { record.Base @@ -24,7 +29,7 @@ type Profile struct { Icon string // Fingerprints - Fingerprints []string + Fingerprints []*Fingerprint // The mininum security level to apply to connections made with this profile SecurityLevel uint8 @@ -47,6 +52,10 @@ func New() *Profile { return &Profile{} } +func makeProfileKey(namespace, ID string) string { + return fmt.Sprintf("core:profiles/%s/%s", namespace, ID) +} + // Save saves the profile to the database func (profile *Profile) Save(namespace string) error { if profile.ID == "" { @@ -61,12 +70,21 @@ func (profile *Profile) Save(namespace string) error { 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)) + profile.SetKey(makeProfileKey(namespace, profile.ID)) } return profileDB.Put(profile) } +// MarkUsed marks the profile as used, eventually. +func (profile *Profile) MarkUsed() (updated bool) { + if time.Now().Add(-lastUsedUpdateThreshold).Unix() > profile.ApproxLastUsed { + profile.ApproxLastUsed = time.Now().Unix() + return true + } + return false +} + // String returns a string representation of the Profile. func (profile *Profile) String() string { return profile.Name @@ -79,10 +97,38 @@ func (profile *Profile) DetailedString() string { // GetUserProfile loads a profile from the database. func GetUserProfile(ID string) (*Profile, error) { - return nil, nil + return getProfile(userNamespace, ID) } // GetStampProfile loads a profile from the database. func GetStampProfile(ID string) (*Profile, error) { - return nil, nil + return getProfile(stampNamespace, ID) +} + +func getProfile(namespace, ID string) (*Profile, error) { + r, err := profileDB.Get(makeProfileKey(namespace, ID)) + if err != nil { + return nil, err + } + return ensureProfile(r) +} + +func ensureProfile(r record.Record) (*Profile, error) { + // unwrap + if r.IsWrapped() { + // only allocate a new struct, if we need it + new := &Profile{} + err := record.Unwrap(r, new) + if err != nil { + return nil, err + } + return new, nil + } + + // or adjust type + new, ok := r.(*Profile) + if !ok { + return nil, fmt.Errorf("record not of type *Example, but %T", r) + } + return new, nil } diff --git a/profile/profileset.go b/profile/profileset.go index a29d5cce..7ce1d642 100644 --- a/profile/profileset.go +++ b/profile/profileset.go @@ -1,6 +1,10 @@ package profile -import "github.com/Safing/portmaster/status" +import ( + "sync" + + "github.com/Safing/portmaster/status" +) var ( emptyFlags = Flags{} @@ -9,6 +13,8 @@ var ( // Set handles Profile chaining. type Set struct { + sync.Mutex + profiles [4]*Profile // Application // Global @@ -29,6 +35,7 @@ func NewSet(user, stamp *Profile) *Set { nil, // Default }, } + activateProfileSet(new) new.Update(status.SecurityLevelFortress) return new } @@ -40,7 +47,7 @@ func (set *Set) Update(securityLevel uint8) { // update profiles set.profiles[1] = globalProfile - set.profiles[3] = defaultProfile + set.profiles[3] = fallbackProfile // update security level profileSecurityLevel := set.getProfileSecurityLevel() @@ -96,7 +103,7 @@ func (set *Set) CheckDomain(domain string) (permit, ok bool) { return false, false } -// Ports returns the highest prioritized Ports configuration. +// CheckPort checks if the given protocol and port are governed in any the lists of ports and returns whether it is permitted. func (set *Set) CheckPort(listen bool, protocol uint8, port uint16) (permit, ok bool) { signedProtocol := int16(protocol) diff --git a/profile/sampledata.go b/profile/sampledata.go deleted file mode 100644 index 8dbc55dd..00000000 --- a/profile/sampledata.go +++ /dev/null @@ -1,261 +0,0 @@ -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() -// } -// } -// -// } diff --git a/profile/specialprofiles.go b/profile/specialprofiles.go index 5ee190b5..9af2559d 100644 --- a/profile/specialprofiles.go +++ b/profile/specialprofiles.go @@ -1,12 +1,44 @@ package profile -import "sync" +import ( + "sync" + + "github.com/Safing/portbase/database" +) var ( - globalProfile *Profile - defaultProfile *Profile + globalProfile *Profile + fallbackProfile *Profile specialProfileLock sync.RWMutex ) -// FIXME: subscribe to changes and update profiles +func initSpecialProfiles() (err error) { + + specialProfileLock.Lock() + defer specialProfileLock.Unlock() + + globalProfile, err = getSpecialProfile("global") + if err != nil { + if err != database.ErrNotFound { + return err + } + globalProfile = makeDefaultGlobalProfile() + globalProfile.Save(specialNamespace) + } + + fallbackProfile, err = getSpecialProfile("fallback") + if err != nil { + if err != database.ErrNotFound { + return err + } + fallbackProfile = makeDefaultFallbackProfile() + fallbackProfile.Save(specialNamespace) + } + + return nil +} + +func getSpecialProfile(ID string) (*Profile, error) { + return getProfile(specialNamespace, ID) +} diff --git a/profile/updates.go b/profile/updates.go new file mode 100644 index 00000000..4bc6f9b7 --- /dev/null +++ b/profile/updates.go @@ -0,0 +1,53 @@ +package profile + +import ( + "fmt" + "strings" + + "github.com/Safing/portbase/database" + "github.com/Safing/portbase/database/query" + "github.com/Safing/portbase/log" +) + +func initUpdateListener() error { + sub, err := profileDB.Subscribe(query.New(makeProfileKey(specialNamespace, ""))) + if err != nil { + return err + } + + go updateListener(sub) + return nil +} + +var ( + slashedUserNamespace = fmt.Sprintf("/%s/", userNamespace) + slashedStampNamespace = fmt.Sprintf("/%s/", stampNamespace) +) + +func updateListener(sub *database.Subscription) { + for r := range sub.Feed { + profile, err := ensureProfile(r) + if err != nil { + log.Errorf("profile: received update for special profile, but could not read: %s", err) + continue + } + + specialProfileLock.Lock() + switch profile.ID { + case "global": + globalProfile = profile + updateActiveGlobalProfile(profile) + case "fallback": + fallbackProfile = profile + updateActiveFallbackProfile(profile) + default: + switch { + case strings.HasPrefix(profile.Key(), makeProfileKey(userNamespace, "")): + updateActiveUserProfile(profile) + case strings.HasPrefix(profile.Key(), makeProfileKey(stampNamespace, "")): + updateActiveStampProfile(profile) + } + } + specialProfileLock.Unlock() + } +}