From 3990790f179b26d6c4111472c789da82d60fbba3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 29 Nov 2018 18:44:31 +0100 Subject: [PATCH] Working on portmaster restructure --- dnsonly.go | 6 +- firewall/config.go | 6 +- firewall/firewall.go | 47 ++--- firewall/inspection/inspection.go | 22 ++- firewall/interception/interception_linux.go | 11 ++ firewall/interception/interception_windows.go | 22 ++- .../{nfqueue.go => nfqueue_linux.go} | 26 ++- firewall/module.go | 19 -- global/databases.go | 48 +++++ intel/intel.go | 2 +- intel/ipinfo.go | 2 +- intel/main.go | 16 +- intel/namerecord.go | 2 +- main.go | 6 +- network/clean.go | 17 +- network/connection.go | 2 +- network/database.go | 15 +- network/link.go | 28 +++ network/module.go | 3 +- process/process.go | 31 ++-- profile/domains.go | 4 +- profile/flags.go | 61 +++--- profile/flags_test.go | 15 ++ profile/ports.go | 15 +- profile/profile.go | 13 +- profile/profileset.go | 175 ++++++++++-------- 26 files changed, 351 insertions(+), 263 deletions(-) rename firewall/interception/{nfqueue.go => nfqueue_linux.go} (91%) delete mode 100644 firewall/module.go create mode 100644 global/databases.go diff --git a/dnsonly.go b/dnsonly.go index 04ece2fa..ca3e4a56 100644 --- a/dnsonly.go +++ b/dnsonly.go @@ -11,17 +11,13 @@ import ( "github.com/Safing/portbase/modules" // include packages here - - _ "github.com/Safing/portbase/database/dbmodule" - _ "github.com/Safing/portbase/database/storage/badger" - _ "github.com/Safing/portmaster/intel" _ "github.com/Safing/portmaster/nameserver/only" ) func main() { // Set Info - info.Set("Portmaster (DNS only)", "0.0.1") + info.Set("Portmaster (DNS only)", "0.2.0") // Start err := modules.Start() diff --git a/firewall/config.go b/firewall/config.go index 7311b1f8..b900a657 100644 --- a/firewall/config.go +++ b/firewall/config.go @@ -8,7 +8,7 @@ var ( permanentVerdicts config.BoolOption ) -func prep() error { +func registerConfig() error { err := config.Register(&config.Option{ Name: "Permanent Verdicts", Key: "firewall/permanentVerdicts", @@ -20,5 +20,7 @@ func prep() error { if err != nil { return err } - configuredNameServers = config.Concurrent.GetAsBool("firewall/permanentVerdicts", true) + permanentVerdicts = config.Concurrent.GetAsBool("firewall/permanentVerdicts", true) + + return nil } diff --git a/firewall/firewall.go b/firewall/firewall.go index 5b867e35..634002b0 100644 --- a/firewall/firewall.go +++ b/firewall/firewall.go @@ -24,8 +24,6 @@ var ( packetsBlocked *uint64 packetsDropped *uint64 - config = configuration.Get() - localNet4 *net.IPNet // Yes, this would normally be 127.0.0.0/8 // TODO: figure out any side effects @@ -40,11 +38,16 @@ var ( ) func init() { - modules.Register("firewall", prep, start, stop, "database", "nameserver") + modules.Register("firewall", prep, start, stop, "global", "network", "nameserver") } func prep() (err error) { + err = registerConfig() + if err != nil { + return err + } + _, localNet4, err = net.ParseCIDR("127.0.0.0/24") // Yes, this would normally be 127.0.0.0/8 // TODO: figure out any side effects @@ -71,15 +74,18 @@ func prep() (err error) { return nil } -func start() { - // start interceptor - interception.Start() - +func start() error { go statLogger() go run() // go run() // go run() // go run() + + return interception.Start() +} + +func stop() error { + return interception.Stop() } func handlePacket(pkt packet.Packet) { @@ -119,16 +125,16 @@ func handlePacket(pkt packet.Packet) { // defer log.Tracef("firewall: took %s to process packet %s", time.Now().Sub(timed).String(), pkt) // check if packet is destined for tunnel - switch pkt.IPVersion() { - case packet.IPv4: - if TunnelNet4 != nil && TunnelNet4.Contains(pkt.GetIPHeader().Dst) { - tunnelHandler(pkt) - } - case packet.IPv6: - if TunnelNet6 != nil && TunnelNet6.Contains(pkt.GetIPHeader().Dst) { - tunnelHandler(pkt) - } - } + // switch pkt.IPVersion() { + // case packet.IPv4: + // if TunnelNet4 != nil && TunnelNet4.Contains(pkt.GetIPHeader().Dst) { + // tunnelHandler(pkt) + // } + // case packet.IPv6: + // if TunnelNet6 != nil && TunnelNet6.Contains(pkt.GetIPHeader().Dst) { + // tunnelHandler(pkt) + // } + // } // associate packet to link and handle link, created := network.GetOrCreateLinkByPacket(pkt) @@ -175,11 +181,8 @@ func initialHandler(pkt packet.Packet, link *network.Link) { return } - // persist connection - connection.CreateInProcessNamespace() - - // add new Link to Connection - connection.AddLink(link, pkt) + // add new Link to Connection (and save both) + connection.AddLink(link) // make a decision if not made already if connection.Verdict == network.UNDECIDED { diff --git a/firewall/inspection/inspection.go b/firewall/inspection/inspection.go index 0c2888b1..a0061925 100644 --- a/firewall/inspection/inspection.go +++ b/firewall/inspection/inspection.go @@ -41,24 +41,28 @@ func RunInspectors(pkt packet.Packet, link *network.Link) (network.Verdict, bool // inspectorsLock.Lock() // defer inspectorsLock.Unlock() - if link.ActiveInspectors == nil { - link.ActiveInspectors = make([]bool, len(inspectors), len(inspectors)) + activeInspectors := link.GetActiveInspectors() + if activeInspectors == nil { + activeInspectors = make([]bool, len(inspectors), len(inspectors)) + link.SetActiveInspectors(activeInspectors) } - if link.InspectorData == nil { - link.InspectorData = make(map[uint8]interface{}) + inspectorData := link.GetInspectorData() + if inspectorData == nil { + inspectorData = make(map[uint8]interface{}) + link.SetInspectorData(inspectorData) } continueInspection := false verdict := network.UNDECIDED - for key, skip := range link.ActiveInspectors { + for key, skip := range activeInspectors { if skip { continue } if link.Verdict > inspectVerdicts[key] { - link.ActiveInspectors[key] = true + activeInspectors[key] = true continue } @@ -79,16 +83,16 @@ func RunInspectors(pkt packet.Packet, link *network.Link) (network.Verdict, bool continueInspection = true case BLOCK_LINK: link.UpdateVerdict(network.BLOCK) - link.ActiveInspectors[key] = true + activeInspectors[key] = true if verdict < network.BLOCK { verdict = network.BLOCK } case DROP_LINK: link.UpdateVerdict(network.DROP) - link.ActiveInspectors[key] = true + activeInspectors[key] = true verdict = network.DROP case STOP_INSPECTING: - link.ActiveInspectors[key] = true + activeInspectors[key] = true } } diff --git a/firewall/interception/interception_linux.go b/firewall/interception/interception_linux.go index 1cfb159e..4fd6897e 100644 --- a/firewall/interception/interception_linux.go +++ b/firewall/interception/interception_linux.go @@ -5,5 +5,16 @@ package interception import "github.com/Safing/portmaster/network/packet" var ( + // Packets channel for feeding the firewall. Packets = make(chan packet.Packet, 1000) ) + +// Start starts the interception. +func Start() error { + return StartNfqueueInterception() +} + +// Stop starts the interception. +func Stop() error { + return StopNfqueueInterception() +} diff --git a/firewall/interception/interception_windows.go b/firewall/interception/interception_windows.go index 681029fa..ddebb0b7 100644 --- a/firewall/interception/interception_windows.go +++ b/firewall/interception/interception_windows.go @@ -1,8 +1,8 @@ package interception import ( - "github.com/Safing/portbase/log" - "github.com/Safing/portbase/modules" + "fmt" + "github.com/Safing/portmaster/firewall/interception/windivert" "github.com/Safing/portmaster/network/packet" ) @@ -10,20 +10,22 @@ import ( var Packets chan packet.Packet func init() { + // Packets channel for feeding the firewall. Packets = make(chan packet.Packet, 1000) } -func Start() { - - windivertModule := modules.Register("Firewall:Interception:WinDivert", 192) +// Start starts the interception. +func Start() error { wd, err := windivert.New("/WinDivert.dll", "") if err != nil { - log.Criticalf("firewall/interception: could not init windivert: %s", err) - } else { - wd.Packets(Packets) + return fmt.Errorf("firewall/interception: could not init windivert: %s", err) } - <-windivertModule.Stop - windivertModule.StopComplete() + return wd.Packets(Packets) +} + +// Stop starts the interception. +func Stop() error { + return nil } diff --git a/firewall/interception/nfqueue.go b/firewall/interception/nfqueue_linux.go similarity index 91% rename from firewall/interception/nfqueue.go rename to firewall/interception/nfqueue_linux.go index ada0a689..a2142494 100644 --- a/firewall/interception/nfqueue.go +++ b/firewall/interception/nfqueue_linux.go @@ -1,7 +1,3 @@ -// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file. - -// +build linux - package interception import ( @@ -106,8 +102,8 @@ func init() { } // Reverse because we'd like to insert in a loop - sort.Reverse(sort.StringSlice(v4once)) - sort.Reverse(sort.StringSlice(v6once)) + _ = sort.Reverse(sort.StringSlice(v4once)) // silence vet (sort is used just like in the docs) + _ = sort.Reverse(sort.StringSlice(v6once)) // silence vet (sort is used just like in the docs) } @@ -133,9 +129,10 @@ func activateNfqueueFirewall() error { } } + var ok bool for _, rule := range v4once { splittedRule := strings.Split(rule, " ") - ok, err := ip4tables.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...) + ok, err = ip4tables.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...) if err != nil { return err } @@ -189,9 +186,10 @@ func deactivateNfqueueFirewall() error { return err } + var ok bool for _, rule := range v4once { splittedRule := strings.Split(rule, " ") - ok, err := ip4tables.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...) + ok, err = ip4tables.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...) if err != nil { return err } @@ -204,10 +202,10 @@ func deactivateNfqueueFirewall() error { for _, chain := range v4chains { splittedRule := strings.Split(chain, " ") - if err := ip4tables.ClearChain(splittedRule[0], splittedRule[1]); err != nil { + if err = ip4tables.ClearChain(splittedRule[0], splittedRule[1]); err != nil { return err } - if err := ip4tables.DeleteChain(splittedRule[0], splittedRule[1]); err != nil { + if err = ip4tables.DeleteChain(splittedRule[0], splittedRule[1]); err != nil { return err } } @@ -244,8 +242,8 @@ func deactivateNfqueueFirewall() error { return nil } -// Start starts the nfqueue interception. -func Start() (err error) { +// StartNfqueueInterception starts the nfqueue interception. +func StartNfqueueInterception() (err error) { err = activateNfqueueFirewall() if err != nil { @@ -278,8 +276,8 @@ func Start() (err error) { return nil } -// Stop stops the nfqueue interception. -func Stop() error { +// StopNfqueueInterception stops the nfqueue interception. +func StopNfqueueInterception() error { defer close(shutdownSignal) if out4Queue != nil { diff --git a/firewall/module.go b/firewall/module.go deleted file mode 100644 index 6da952f5..00000000 --- a/firewall/module.go +++ /dev/null @@ -1,19 +0,0 @@ -package firewall - -import ( - "github.com/Safing/portbase/modules" - - _ "github.com/Safing/portmaster/network" -) - -func init() { - modules.Register("firewall", nil, start, stop, "network") -} - -func start() error { - return registerAsDatabase() -} - -func stop() error { - -} diff --git a/global/databases.go b/global/databases.go new file mode 100644 index 00000000..ea3b9708 --- /dev/null +++ b/global/databases.go @@ -0,0 +1,48 @@ +package global + +import ( + "github.com/Safing/portbase/database" + "github.com/Safing/portbase/modules" + + // module dependencies + _ "github.com/Safing/portbase/database/dbmodule" + _ "github.com/Safing/portbase/database/storage/badger" +) + +func init() { + modules.Register("global", nil, start, nil, "database") +} + +func start() error { + _, err := database.Register(&database.Database{ + Name: "core", + Description: "Holds core data, such as settings and profiles", + StorageType: "badger", + PrimaryAPI: "", + }) + if err != nil { + return err + } + + _, err = database.Register(&database.Database{ + Name: "cache", + Description: "Cached data, such as Intelligence and DNS Records", + StorageType: "badger", + PrimaryAPI: "", + }) + if err != nil { + return err + } + + // _, err = database.Register(&database.Database{ + // Name: "history", + // Description: "Historic event data", + // StorageType: "badger", + // PrimaryAPI: "", + // }) + // if err != nil { + // return err + // } + + return nil +} diff --git a/intel/intel.go b/intel/intel.go index 5ed4edd3..c398710e 100644 --- a/intel/intel.go +++ b/intel/intel.go @@ -25,7 +25,7 @@ type Intel struct { } func makeIntelKey(domain string) string { - return fmt.Sprintf("intel:Intel/%s", domain) + return fmt.Sprintf("cache:intel/domain/%s", domain) } // GetIntelFromDB gets an Intel record from the database. diff --git a/intel/ipinfo.go b/intel/ipinfo.go index aa13d932..eef7de29 100644 --- a/intel/ipinfo.go +++ b/intel/ipinfo.go @@ -26,7 +26,7 @@ type IPInfo struct { } func makeIPInfoKey(ip string) string { - return fmt.Sprintf("intel:IPInfo/%s", ip) + return fmt.Sprintf("cache:intel/ipInfo/%s", ip) } // GetIPInfo gets an IPInfo record from the database. diff --git a/intel/main.go b/intel/main.go index 3052a9ab..fa0bbddb 100644 --- a/intel/main.go +++ b/intel/main.go @@ -3,26 +3,18 @@ package intel import ( "github.com/miekg/dns" - "github.com/Safing/portbase/database" "github.com/Safing/portbase/log" "github.com/Safing/portbase/modules" + + // module dependencies + _ "github.com/Safing/portmaster/global" ) func init() { - modules.Register("intel", prep, start, nil, "database") + modules.Register("intel", prep, start, nil, "global") } func start() error { - _, err := database.Register(&database.Database{ - Name: "intel", - Description: "Intelligence and DNS Data", - StorageType: "badger", - PrimaryAPI: "", - }) - if err != nil { - return err - } - // load resolvers from config and environment loadResolvers(false) diff --git a/intel/namerecord.go b/intel/namerecord.go index a106fb03..8e711a75 100644 --- a/intel/namerecord.go +++ b/intel/namerecord.go @@ -30,7 +30,7 @@ type NameRecord struct { } func makeNameRecordKey(domain string, question string) string { - return fmt.Sprintf("intel:NameRecords/%s%s", domain, question) + return fmt.Sprintf("cache:intel/nameRecord/%s%s", domain, question) } // GetNameRecord gets a NameRecord from the database. diff --git a/main.go b/main.go index 1e830f80..5a5e1474 100644 --- a/main.go +++ b/main.go @@ -14,15 +14,13 @@ import ( _ "github.com/Safing/portbase/database/dbmodule" _ "github.com/Safing/portbase/database/storage/badger" - _ "github.com/Safing/portmaster/intel" - _ "github.com/Safing/portmaster/nameserver/only" - _ "github.com/Safing/portmaster/nameserver/only" + _ "github.com/Safing/portmaster/firewall" ) func main() { // Set Info - info.Set("Portmaster", "0.0.1") + info.Set("Portmaster", "0.2.0") // Start err := modules.Start() diff --git a/network/clean.go b/network/clean.go index 556605a7..9b1b88e3 100644 --- a/network/clean.go +++ b/network/clean.go @@ -9,20 +9,17 @@ import ( ) var ( - deadLinksTimeout = 5 * time.Minute - thresholdDuration = 1 * time.Minute + cleanerTickDuration = 1 * time.Minute + deadLinksTimeout = 5 * time.Minute + thresholdDuration = 1 * time.Minute ) -func init() { - go cleaner() -} - func cleaner() { - time.Sleep(15 * time.Second) for { - markDeadLinks() - purgeDeadFor(5 * time.Minute) - time.Sleep(15 * time.Second) + time.Sleep(cleanerTickDuration) + cleanLinks() + cleanConnections() + cleanProcesses() } } diff --git a/network/connection.go b/network/connection.go index 97e52d99..85fcb26a 100644 --- a/network/connection.go +++ b/network/connection.go @@ -234,7 +234,7 @@ func (conn *Connection) AddLink(link *Link) { conn.LinkCount++ conn.LastLinkEstablished = time.Now().Unix() if conn.FirstLinkEstablished == 0 { - conn.FirstLinkEstablished = conn.FirstLinkEstablished + conn.FirstLinkEstablished = conn.LastLinkEstablished } conn.Save() } diff --git a/network/database.go b/network/database.go index 3f83c159..1cd3f434 100644 --- a/network/database.go +++ b/network/database.go @@ -38,8 +38,11 @@ func (s *StorageInterface) Get(key string) (record.Record, error) { switch len(splitted) { case 2: pid, err := strconv.Atoi(splitted[1]) - if err != nil { - return process.GetProcessByPID(pid) + if err == nil { + proc, ok := process.GetProcessFromStorage(pid) + if ok { + return proc, nil + } } case 3: conn, ok := connections[splitted[2]] @@ -69,7 +72,7 @@ func (s *StorageInterface) Query(q *query.Query, local, internal bool) (*iterato func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) { // processes for _, proc := range process.All() { - if strings.HasPrefix(proc.Meta().DatabaseKey, q.DatabaseKeyPrefix()) { + if strings.HasPrefix(proc.DatabaseKey(), q.DatabaseKeyPrefix()) { it.Next <- proc } } @@ -79,14 +82,14 @@ func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) { // connections for _, conn := range connections { - if strings.HasPrefix(conn.Meta().DatabaseKey, q.DatabaseKeyPrefix()) { + if strings.HasPrefix(conn.DatabaseKey(), q.DatabaseKeyPrefix()) { it.Next <- conn } } // links for _, link := range links { - if strings.HasPrefix(opt.Meta().DatabaseKey, q.DatabaseKeyPrefix()) { + if strings.HasPrefix(link.DatabaseKey(), q.DatabaseKeyPrefix()) { it.Next <- link } } @@ -105,7 +108,7 @@ func registerAsDatabase() error { return err } - controller, err := database.InjectDatabase("network", &ConfigStorageInterface{}) + controller, err := database.InjectDatabase("network", &StorageInterface{}) if err != nil { return err } diff --git a/network/link.go b/network/link.go index 9cb6d64e..e7e69813 100644 --- a/network/link.go +++ b/network/link.go @@ -177,6 +177,34 @@ func CreateLinkFromPacket(pkt packet.Packet) *Link { return link } +// GetActiveInspectors returns the list of active inspectors. +func (link *Link) GetActiveInspectors() []bool { + link.Lock() + defer link.Unlock() + return link.activeInspectors +} + +// SetActiveInspectors sets the list of active inspectors. +func (link *Link) SetActiveInspectors(new []bool) { + link.Lock() + defer link.Unlock() + link.activeInspectors = new +} + +// GetInspectorData returns the list of inspector data. +func (link *Link) GetInspectorData() map[uint8]interface{} { + link.Lock() + defer link.Unlock() + return link.inspectorData +} + +// SetInspectorData set the list of inspector data. +func (link *Link) SetInspectorData(new map[uint8]interface{}) { + link.Lock() + defer link.Unlock() + link.inspectorData = new +} + // String returns a string representation of Link. func (link *Link) String() string { if link.connection == nil { diff --git a/network/module.go b/network/module.go index 2b598d4f..806d7ff9 100644 --- a/network/module.go +++ b/network/module.go @@ -5,9 +5,10 @@ import ( ) func init() { - modules.Register("network", prep, start, nil, "database") + modules.Register("network", nil, start, nil, "database") } func start() error { + go cleaner() return registerAsDatabase() } diff --git a/process/process.go b/process/process.go index 8481bdfe..5f1b39c7 100644 --- a/process/process.go +++ b/process/process.go @@ -12,6 +12,7 @@ import ( "github.com/Safing/portbase/database/record" "github.com/Safing/portbase/log" + "github.com/Safing/portmaster/profile" ) // A Process represents a process running on the operating system @@ -19,17 +20,18 @@ type Process struct { record.Base sync.Mutex - UserID int - UserName string - UserHome string - Pid int - ParentPid int - Path string - Cwd string - FileInfo *FileInfo - CmdLine string - FirstArg string - ProfileKey string + UserID int + UserName string + UserHome string + Pid int + ParentPid int + Path string + Cwd string + FileInfo *FileInfo + CmdLine string + FirstArg string + + profileSet *profile.Set Name string Icon string // Icon is a path to the icon and is either prefixed "f:" for filepath, "d:" for database cache path or "c:"/"a:" for a the icon key to fetch it from a company / authoritative node and cache it in its own cache. @@ -39,7 +41,12 @@ type Process struct { ConnectionCount uint } -// Strings returns a string represenation of process +// ProfileSet returns the assigned profile set. +func (p *Process) ProfileSet() *profile.Set { + return p.profileSet +} + +// Strings returns a string represenation of process. func (p *Process) String() string { if p == nil { return "?" diff --git a/profile/domains.go b/profile/domains.go index 9f6163a6..d636b053 100644 --- a/profile/domains.go +++ b/profile/domains.go @@ -20,8 +20,8 @@ func (d Domains) IsSet() bool { 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 checks if the given domain is governed in the list of domains and returns whether it is permitted. +func (d Domains) Check(domain string) (permit, ok bool) { // check for exact domain dd, ok := d[domain] if ok { diff --git a/profile/flags.go b/profile/flags.go index ac19fff9..a691008b 100644 --- a/profile/flags.go +++ b/profile/flags.go @@ -7,8 +7,8 @@ import ( "github.com/Safing/portmaster/status" ) -// ProfileFlags are used to quickly add common attributes to profiles -type ProfileFlags map[uint8]uint8 +// Flags are used to quickly add common attributes to profiles +type Flags map[uint8]uint8 // Profile Flags const ( @@ -31,8 +31,8 @@ const ( ) var ( - // ErrProfileFlagsParseFailed is returned if a an invalid flag is encountered while parsing - ErrProfileFlagsParseFailed = errors.New("profiles: failed to parse flags") + // ErrFlagsParseFailed is returned if a an invalid flag is encountered while parsing + ErrFlagsParseFailed = errors.New("profiles: failed to parse flags") sortedFlags = []uint8{ Prompt, @@ -77,34 +77,33 @@ var ( } ) -// FlagsFromNames creates ProfileFlags from a comma seperated list of flagnames (e.g. "System,Strict,Secure") -// func FlagsFromNames(words []string) (*ProfileFlags, error) { -// var flags ProfileFlags +// FlagsFromNames creates Flags from a comma seperated list of flagnames (e.g. "System,Strict,Secure") +// func FlagsFromNames(words []string) (*Flags, error) { +// var flags Flags // for _, entry := range words { // flag, ok := flagIDs[entry] // if !ok { -// return nil, ErrProfileFlagsParseFailed +// return nil, ErrFlagsParseFailed // } // 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 +// Check checks if a flag is set at all and if it's active in the given security level. +func (flags Flags) Check(flag, level uint8) (active bool, ok bool) { + if flags == nil { + return false, false } - 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 + setting, ok := flags[flag] + if ok { + if setting&level > 0 { + return true, true + } + return false, true } - return false + return false, false } func getLevelMarker(levels, level uint8) string { @@ -114,11 +113,11 @@ func getLevelMarker(levels, level uint8) string { return "-" } -// String return a string representation of ProfileFlags -func (pf ProfileFlags) String() string { - var namedFlags []string +// String return a string representation of Flags +func (flags Flags) String() string { + var markedFlags []string for _, flag := range sortedFlags { - levels, ok := pf[flag] + levels, ok := flags[flag] if ok { s := flagNames[flag] if levels != status.SecurityLevelsAll { @@ -126,20 +125,18 @@ func (pf ProfileFlags) String() string { s += getLevelMarker(levels, status.SecurityLevelSecure) s += getLevelMarker(levels, status.SecurityLevelFortress) } + markedFlags = append(markedFlags, s) } } - for _, flag := range pf { - namedFlags = append(namedFlags, flagNames[flag]) - } - return strings.Join(namedFlags, ", ") + return strings.Join(markedFlags, ", ") } // Add adds a flag to the Flags with the given level. -func (pf ProfileFlags) Add(flag, levels uint8) { - pf[flag] = levels +func (flags Flags) Add(flag, levels uint8) { + flags[flag] = levels } // Remove removes a flag from the Flags. -func (pf ProfileFlags) Remove(flag uint8) { - delete(pf, flag) +func (flags Flags) Remove(flag uint8) { + delete(flags, flag) } diff --git a/profile/flags_test.go b/profile/flags_test.go index 5fe61820..8eb87b20 100644 --- a/profile/flags_test.go +++ b/profile/flags_test.go @@ -2,6 +2,8 @@ package profile import ( "testing" + + "github.com/Safing/portmaster/status" ) func TestProfileFlags(t *testing.T) { @@ -20,6 +22,19 @@ func TestProfileFlags(t *testing.T) { } } + testFlags := Flags{ + Prompt: status.SecurityLevelsAll, + Internet: status.SecurityLevelsDynamicAndSecure, + LAN: status.SecurityLevelsDynamicAndSecure, + Localhost: status.SecurityLevelsAll, + Related: status.SecurityLevelDynamic, + RequireGate17: status.SecurityLevelsSecureAndFortress, + } + + if testFlags.String() != "Prompt, Internet++-, LAN++-, Localhost, Related+--, RequireGate17-++" { + t.Errorf("unexpected output: %s", testFlags.String()) + } + // // check Has // emptyFlags := ProfileFlags{} // for flag, name := range flagNames { diff --git a/profile/ports.go b/profile/ports.go index 22b90284..0b29f7be 100644 --- a/profile/ports.go +++ b/profile/ports.go @@ -9,19 +9,12 @@ import ( // 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) { +// Check returns whether listening/connecting to a certain port is allowed, if set. +func (p Ports) Check(listen bool, protocol string, port uint16) (permit, ok bool) { if p == nil { return false, false } + if listen { protocol = "<" + protocol } @@ -33,7 +26,7 @@ func (p Ports) CheckStatus(listen bool, protocol string, port uint16) (permit, o } } } - return false, true + return false, false } func (p Ports) String() string { diff --git a/profile/profile.go b/profile/profile.go index fa9af7b9..863f7c3b 100644 --- a/profile/profile.go +++ b/profile/profile.go @@ -29,7 +29,7 @@ type Profile struct { // The mininum security level to apply to connections made with this profile SecurityLevel uint8 - Flags ProfileFlags + Flags Flags Domains Domains Ports Ports @@ -42,6 +42,7 @@ type Profile struct { ApproxLastUsed int64 } +// New returns a new Profile. func New() *Profile { return &Profile{} } @@ -49,12 +50,10 @@ func New() *Profile { // Save saves the profile to the database func (profile *Profile) Save(namespace string) error { if profile.ID == "" { - // FIXME: this is weird, the docs says that it also returns an error - u := uuid.NewV4() - // u, err := uuid.NewV4() - // if err != nil { - // return err - // } + u, err := uuid.NewV4() + if err != nil { + return err + } profile.ID = u.String() } diff --git a/profile/profileset.go b/profile/profileset.go index 140062de..5ba72e7c 100644 --- a/profile/profileset.go +++ b/profile/profileset.go @@ -1,119 +1,132 @@ package profile +import "github.com/Safing/portmaster/status" + var ( - emptyFlags = ProfileFlags{} - emptyPorts = Ports{} + emptyFlags = Flags{} + emptyPorts = Ports{} ) -// ProfileSet handles Profile chaining. -type ProfileSet struct { - Profiles [4]*Profile +// Set handles Profile chaining. +type Set struct { + profiles [4]*Profile // Application // Global // Stamp // Default - Independent bool + securityLevel uint8 + 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 +func NewSet(user, stamp *Profile) *Set { + new := &Set{ + profiles: [4]*Profile{ + user, // Application + nil, // Global stamp, // Stamp - nil, // Default + nil, // Default }, } - new.Update() - return new + new.Update(status.SecurityLevelFortress) + 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() +func (set *Set) Update(securityLevel uint8) { + specialProfileLock.RLock() + defer specialProfileLock.RUnlock() - // update profiles - ps.Profiles[1] = globalProfile - ps.Profiles[3] = defaultProfile + // update profiles + set.profiles[1] = globalProfile + set.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 - } - } + // update security level + profileSecurityLevel := set.getProfileSecurityLevel() + if profileSecurityLevel > securityLevel { + set.securityLevel = profileSecurityLevel + } else { + set.securityLevel = securityLevel } - return emptyFlags + // update independence + if active, ok := set.CheckFlag(Independent); active && ok { + set.independent = true + } else { + set.independent = false + } } -// 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) { +// CheckFlag returns whether a given flag is set. +func (set *Set) CheckFlag(flag) (active bool) { - for i, profile := range ps.Profiles { - if i == 2 && ps.Independent { - continue - } + for i, profile := range set.profiles { + if i == 2 && set.independent { + continue + } - if profile != nil { - if profile.Domains.IsSet() { - permit, ok = profile.Domains.CheckStatus(domain) - if ok { - return - } - } - } + if profile != nil { + active, ok := profile.Flags.Check(flag, set.securityLevel) + if ok { + return active + } + } + } + + return false +} + +// CheckDomain checks if the given domain is governed in any the lists of domains and returns whether it is permitted. +func (set *Set) CheckDomain(domain string) (permit, ok bool) { + + for i, profile := range set.profiles { + if i == 2 && set.independent { + continue + } + + if profile != nil { + permit, ok = profile.Domains.Check(domain) + if ok { + return + } + } + } + + return false, false +} + +// Ports returns the highest prioritized Ports configuration. +func (set *Set) CheckPort() (permit, ok bool) { + + for i, profile := range set.profiles { + if i == 2 && set.independent { + continue + } + + if profile != nil { + if profile.Ports.Check() { + return profile.Ports + } + } } 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 { +func (set *Set) getProfileSecurityLevel() uint8 { - for i, profile := range ps.Profiles { - if i == 2 { - // Stamp profiles do not have the SecurityLevel setting - continue - } + for i, profile := range set.profiles { + if i == 2 { + // Stamp profiles do not have the SecurityLevel setting + continue + } - if profile != nil { - if profile.SecurityLevel > 0 { - return profile.SecurityLevel - } + if profile != nil { + if profile.SecurityLevel > 0 { + return profile.SecurityLevel + } } }