Working on portmaster restructure

This commit is contained in:
Daniel 2018-11-29 18:44:31 +01:00
parent be8a1d1739
commit 3990790f17
26 changed files with 351 additions and 263 deletions

View file

@ -11,17 +11,13 @@ import (
"github.com/Safing/portbase/modules" "github.com/Safing/portbase/modules"
// include packages here // 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" _ "github.com/Safing/portmaster/nameserver/only"
) )
func main() { func main() {
// Set Info // Set Info
info.Set("Portmaster (DNS only)", "0.0.1") info.Set("Portmaster (DNS only)", "0.2.0")
// Start // Start
err := modules.Start() err := modules.Start()

View file

@ -8,7 +8,7 @@ var (
permanentVerdicts config.BoolOption permanentVerdicts config.BoolOption
) )
func prep() error { func registerConfig() error {
err := config.Register(&config.Option{ err := config.Register(&config.Option{
Name: "Permanent Verdicts", Name: "Permanent Verdicts",
Key: "firewall/permanentVerdicts", Key: "firewall/permanentVerdicts",
@ -20,5 +20,7 @@ func prep() error {
if err != nil { if err != nil {
return err return err
} }
configuredNameServers = config.Concurrent.GetAsBool("firewall/permanentVerdicts", true) permanentVerdicts = config.Concurrent.GetAsBool("firewall/permanentVerdicts", true)
return nil
} }

View file

@ -24,8 +24,6 @@ var (
packetsBlocked *uint64 packetsBlocked *uint64
packetsDropped *uint64 packetsDropped *uint64
config = configuration.Get()
localNet4 *net.IPNet localNet4 *net.IPNet
// Yes, this would normally be 127.0.0.0/8 // Yes, this would normally be 127.0.0.0/8
// TODO: figure out any side effects // TODO: figure out any side effects
@ -40,11 +38,16 @@ var (
) )
func init() { func init() {
modules.Register("firewall", prep, start, stop, "database", "nameserver") modules.Register("firewall", prep, start, stop, "global", "network", "nameserver")
} }
func prep() (err error) { func prep() (err error) {
err = registerConfig()
if err != nil {
return err
}
_, localNet4, err = net.ParseCIDR("127.0.0.0/24") _, localNet4, err = net.ParseCIDR("127.0.0.0/24")
// Yes, this would normally be 127.0.0.0/8 // Yes, this would normally be 127.0.0.0/8
// TODO: figure out any side effects // TODO: figure out any side effects
@ -71,15 +74,18 @@ func prep() (err error) {
return nil return nil
} }
func start() { func start() error {
// start interceptor
interception.Start()
go statLogger() go statLogger()
go run() go run()
// go run() // go run()
// go run() // go run()
// go run() // go run()
return interception.Start()
}
func stop() error {
return interception.Stop()
} }
func handlePacket(pkt packet.Packet) { 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) // defer log.Tracef("firewall: took %s to process packet %s", time.Now().Sub(timed).String(), pkt)
// check if packet is destined for tunnel // check if packet is destined for tunnel
switch pkt.IPVersion() { // switch pkt.IPVersion() {
case packet.IPv4: // case packet.IPv4:
if TunnelNet4 != nil && TunnelNet4.Contains(pkt.GetIPHeader().Dst) { // if TunnelNet4 != nil && TunnelNet4.Contains(pkt.GetIPHeader().Dst) {
tunnelHandler(pkt) // tunnelHandler(pkt)
} // }
case packet.IPv6: // case packet.IPv6:
if TunnelNet6 != nil && TunnelNet6.Contains(pkt.GetIPHeader().Dst) { // if TunnelNet6 != nil && TunnelNet6.Contains(pkt.GetIPHeader().Dst) {
tunnelHandler(pkt) // tunnelHandler(pkt)
} // }
} // }
// associate packet to link and handle // associate packet to link and handle
link, created := network.GetOrCreateLinkByPacket(pkt) link, created := network.GetOrCreateLinkByPacket(pkt)
@ -175,11 +181,8 @@ func initialHandler(pkt packet.Packet, link *network.Link) {
return return
} }
// persist connection // add new Link to Connection (and save both)
connection.CreateInProcessNamespace() connection.AddLink(link)
// add new Link to Connection
connection.AddLink(link, pkt)
// make a decision if not made already // make a decision if not made already
if connection.Verdict == network.UNDECIDED { if connection.Verdict == network.UNDECIDED {

View file

@ -41,24 +41,28 @@ func RunInspectors(pkt packet.Packet, link *network.Link) (network.Verdict, bool
// inspectorsLock.Lock() // inspectorsLock.Lock()
// defer inspectorsLock.Unlock() // defer inspectorsLock.Unlock()
if link.ActiveInspectors == nil { activeInspectors := link.GetActiveInspectors()
link.ActiveInspectors = make([]bool, len(inspectors), len(inspectors)) if activeInspectors == nil {
activeInspectors = make([]bool, len(inspectors), len(inspectors))
link.SetActiveInspectors(activeInspectors)
} }
if link.InspectorData == nil { inspectorData := link.GetInspectorData()
link.InspectorData = make(map[uint8]interface{}) if inspectorData == nil {
inspectorData = make(map[uint8]interface{})
link.SetInspectorData(inspectorData)
} }
continueInspection := false continueInspection := false
verdict := network.UNDECIDED verdict := network.UNDECIDED
for key, skip := range link.ActiveInspectors { for key, skip := range activeInspectors {
if skip { if skip {
continue continue
} }
if link.Verdict > inspectVerdicts[key] { if link.Verdict > inspectVerdicts[key] {
link.ActiveInspectors[key] = true activeInspectors[key] = true
continue continue
} }
@ -79,16 +83,16 @@ func RunInspectors(pkt packet.Packet, link *network.Link) (network.Verdict, bool
continueInspection = true continueInspection = true
case BLOCK_LINK: case BLOCK_LINK:
link.UpdateVerdict(network.BLOCK) link.UpdateVerdict(network.BLOCK)
link.ActiveInspectors[key] = true activeInspectors[key] = true
if verdict < network.BLOCK { if verdict < network.BLOCK {
verdict = network.BLOCK verdict = network.BLOCK
} }
case DROP_LINK: case DROP_LINK:
link.UpdateVerdict(network.DROP) link.UpdateVerdict(network.DROP)
link.ActiveInspectors[key] = true activeInspectors[key] = true
verdict = network.DROP verdict = network.DROP
case STOP_INSPECTING: case STOP_INSPECTING:
link.ActiveInspectors[key] = true activeInspectors[key] = true
} }
} }

View file

@ -5,5 +5,16 @@ package interception
import "github.com/Safing/portmaster/network/packet" import "github.com/Safing/portmaster/network/packet"
var ( var (
// Packets channel for feeding the firewall.
Packets = make(chan packet.Packet, 1000) Packets = make(chan packet.Packet, 1000)
) )
// Start starts the interception.
func Start() error {
return StartNfqueueInterception()
}
// Stop starts the interception.
func Stop() error {
return StopNfqueueInterception()
}

View file

@ -1,8 +1,8 @@
package interception package interception
import ( import (
"github.com/Safing/portbase/log" "fmt"
"github.com/Safing/portbase/modules"
"github.com/Safing/portmaster/firewall/interception/windivert" "github.com/Safing/portmaster/firewall/interception/windivert"
"github.com/Safing/portmaster/network/packet" "github.com/Safing/portmaster/network/packet"
) )
@ -10,20 +10,22 @@ import (
var Packets chan packet.Packet var Packets chan packet.Packet
func init() { func init() {
// Packets channel for feeding the firewall.
Packets = make(chan packet.Packet, 1000) Packets = make(chan packet.Packet, 1000)
} }
func Start() { // Start starts the interception.
func Start() error {
windivertModule := modules.Register("Firewall:Interception:WinDivert", 192)
wd, err := windivert.New("/WinDivert.dll", "") wd, err := windivert.New("/WinDivert.dll", "")
if err != nil { if err != nil {
log.Criticalf("firewall/interception: could not init windivert: %s", err) return fmt.Errorf("firewall/interception: could not init windivert: %s", err)
} else {
wd.Packets(Packets)
} }
<-windivertModule.Stop return wd.Packets(Packets)
windivertModule.StopComplete() }
// Stop starts the interception.
func Stop() error {
return nil
} }

View file

@ -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 package interception
import ( import (
@ -106,8 +102,8 @@ func init() {
} }
// Reverse because we'd like to insert in a loop // Reverse because we'd like to insert in a loop
sort.Reverse(sort.StringSlice(v4once)) _ = sort.Reverse(sort.StringSlice(v4once)) // silence vet (sort is used just like in the docs)
sort.Reverse(sort.StringSlice(v6once)) _ = 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 { for _, rule := range v4once {
splittedRule := strings.Split(rule, " ") 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 { if err != nil {
return err return err
} }
@ -189,9 +186,10 @@ func deactivateNfqueueFirewall() error {
return err return err
} }
var ok bool
for _, rule := range v4once { for _, rule := range v4once {
splittedRule := strings.Split(rule, " ") 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 { if err != nil {
return err return err
} }
@ -204,10 +202,10 @@ func deactivateNfqueueFirewall() error {
for _, chain := range v4chains { for _, chain := range v4chains {
splittedRule := strings.Split(chain, " ") 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 return err
} }
if err := ip4tables.DeleteChain(splittedRule[0], splittedRule[1]); err != nil { if err = ip4tables.DeleteChain(splittedRule[0], splittedRule[1]); err != nil {
return err return err
} }
} }
@ -244,8 +242,8 @@ func deactivateNfqueueFirewall() error {
return nil return nil
} }
// Start starts the nfqueue interception. // StartNfqueueInterception starts the nfqueue interception.
func Start() (err error) { func StartNfqueueInterception() (err error) {
err = activateNfqueueFirewall() err = activateNfqueueFirewall()
if err != nil { if err != nil {
@ -278,8 +276,8 @@ func Start() (err error) {
return nil return nil
} }
// Stop stops the nfqueue interception. // StopNfqueueInterception stops the nfqueue interception.
func Stop() error { func StopNfqueueInterception() error {
defer close(shutdownSignal) defer close(shutdownSignal)
if out4Queue != nil { if out4Queue != nil {

View file

@ -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 {
}

48
global/databases.go Normal file
View file

@ -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
}

View file

@ -25,7 +25,7 @@ type Intel struct {
} }
func makeIntelKey(domain string) string { 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. // GetIntelFromDB gets an Intel record from the database.

View file

@ -26,7 +26,7 @@ type IPInfo struct {
} }
func makeIPInfoKey(ip string) string { 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. // GetIPInfo gets an IPInfo record from the database.

View file

@ -3,26 +3,18 @@ package intel
import ( import (
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/Safing/portbase/database"
"github.com/Safing/portbase/log" "github.com/Safing/portbase/log"
"github.com/Safing/portbase/modules" "github.com/Safing/portbase/modules"
// module dependencies
_ "github.com/Safing/portmaster/global"
) )
func init() { func init() {
modules.Register("intel", prep, start, nil, "database") modules.Register("intel", prep, start, nil, "global")
} }
func start() error { 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 // load resolvers from config and environment
loadResolvers(false) loadResolvers(false)

View file

@ -30,7 +30,7 @@ type NameRecord struct {
} }
func makeNameRecordKey(domain string, question string) string { 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. // GetNameRecord gets a NameRecord from the database.

View file

@ -14,15 +14,13 @@ import (
_ "github.com/Safing/portbase/database/dbmodule" _ "github.com/Safing/portbase/database/dbmodule"
_ "github.com/Safing/portbase/database/storage/badger" _ "github.com/Safing/portbase/database/storage/badger"
_ "github.com/Safing/portmaster/intel" _ "github.com/Safing/portmaster/firewall"
_ "github.com/Safing/portmaster/nameserver/only"
_ "github.com/Safing/portmaster/nameserver/only"
) )
func main() { func main() {
// Set Info // Set Info
info.Set("Portmaster", "0.0.1") info.Set("Portmaster", "0.2.0")
// Start // Start
err := modules.Start() err := modules.Start()

View file

@ -9,20 +9,17 @@ import (
) )
var ( var (
deadLinksTimeout = 5 * time.Minute cleanerTickDuration = 1 * time.Minute
thresholdDuration = 1 * time.Minute deadLinksTimeout = 5 * time.Minute
thresholdDuration = 1 * time.Minute
) )
func init() {
go cleaner()
}
func cleaner() { func cleaner() {
time.Sleep(15 * time.Second)
for { for {
markDeadLinks() time.Sleep(cleanerTickDuration)
purgeDeadFor(5 * time.Minute) cleanLinks()
time.Sleep(15 * time.Second) cleanConnections()
cleanProcesses()
} }
} }

View file

@ -234,7 +234,7 @@ func (conn *Connection) AddLink(link *Link) {
conn.LinkCount++ conn.LinkCount++
conn.LastLinkEstablished = time.Now().Unix() conn.LastLinkEstablished = time.Now().Unix()
if conn.FirstLinkEstablished == 0 { if conn.FirstLinkEstablished == 0 {
conn.FirstLinkEstablished = conn.FirstLinkEstablished conn.FirstLinkEstablished = conn.LastLinkEstablished
} }
conn.Save() conn.Save()
} }

View file

@ -38,8 +38,11 @@ func (s *StorageInterface) Get(key string) (record.Record, error) {
switch len(splitted) { switch len(splitted) {
case 2: case 2:
pid, err := strconv.Atoi(splitted[1]) pid, err := strconv.Atoi(splitted[1])
if err != nil { if err == nil {
return process.GetProcessByPID(pid) proc, ok := process.GetProcessFromStorage(pid)
if ok {
return proc, nil
}
} }
case 3: case 3:
conn, ok := connections[splitted[2]] 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) { func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
// processes // processes
for _, proc := range process.All() { for _, proc := range process.All() {
if strings.HasPrefix(proc.Meta().DatabaseKey, q.DatabaseKeyPrefix()) { if strings.HasPrefix(proc.DatabaseKey(), q.DatabaseKeyPrefix()) {
it.Next <- proc it.Next <- proc
} }
} }
@ -79,14 +82,14 @@ func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
// connections // connections
for _, conn := range connections { for _, conn := range connections {
if strings.HasPrefix(conn.Meta().DatabaseKey, q.DatabaseKeyPrefix()) { if strings.HasPrefix(conn.DatabaseKey(), q.DatabaseKeyPrefix()) {
it.Next <- conn it.Next <- conn
} }
} }
// links // links
for _, link := range links { for _, link := range links {
if strings.HasPrefix(opt.Meta().DatabaseKey, q.DatabaseKeyPrefix()) { if strings.HasPrefix(link.DatabaseKey(), q.DatabaseKeyPrefix()) {
it.Next <- link it.Next <- link
} }
} }
@ -105,7 +108,7 @@ func registerAsDatabase() error {
return err return err
} }
controller, err := database.InjectDatabase("network", &ConfigStorageInterface{}) controller, err := database.InjectDatabase("network", &StorageInterface{})
if err != nil { if err != nil {
return err return err
} }

View file

@ -177,6 +177,34 @@ func CreateLinkFromPacket(pkt packet.Packet) *Link {
return 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. // String returns a string representation of Link.
func (link *Link) String() string { func (link *Link) String() string {
if link.connection == nil { if link.connection == nil {

View file

@ -5,9 +5,10 @@ import (
) )
func init() { func init() {
modules.Register("network", prep, start, nil, "database") modules.Register("network", nil, start, nil, "database")
} }
func start() error { func start() error {
go cleaner()
return registerAsDatabase() return registerAsDatabase()
} }

View file

@ -12,6 +12,7 @@ import (
"github.com/Safing/portbase/database/record" "github.com/Safing/portbase/database/record"
"github.com/Safing/portbase/log" "github.com/Safing/portbase/log"
"github.com/Safing/portmaster/profile"
) )
// A Process represents a process running on the operating system // A Process represents a process running on the operating system
@ -19,17 +20,18 @@ type Process struct {
record.Base record.Base
sync.Mutex sync.Mutex
UserID int UserID int
UserName string UserName string
UserHome string UserHome string
Pid int Pid int
ParentPid int ParentPid int
Path string Path string
Cwd string Cwd string
FileInfo *FileInfo FileInfo *FileInfo
CmdLine string CmdLine string
FirstArg string FirstArg string
ProfileKey string
profileSet *profile.Set
Name string Name string
Icon 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. // 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 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 { func (p *Process) String() string {
if p == nil { if p == nil {
return "?" return "?"

View file

@ -20,8 +20,8 @@ func (d Domains) IsSet() bool {
return false return false
} }
// CheckStatus checks if the given domain is governed in the list of domains and returns whether it is permitted. // Check 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) { func (d Domains) Check(domain string) (permit, ok bool) {
// check for exact domain // check for exact domain
dd, ok := d[domain] dd, ok := d[domain]
if ok { if ok {

View file

@ -7,8 +7,8 @@ import (
"github.com/Safing/portmaster/status" "github.com/Safing/portmaster/status"
) )
// ProfileFlags are used to quickly add common attributes to profiles // Flags are used to quickly add common attributes to profiles
type ProfileFlags map[uint8]uint8 type Flags map[uint8]uint8
// Profile Flags // Profile Flags
const ( const (
@ -31,8 +31,8 @@ const (
) )
var ( var (
// ErrProfileFlagsParseFailed is returned if a an invalid flag is encountered while parsing // ErrFlagsParseFailed is returned if a an invalid flag is encountered while parsing
ErrProfileFlagsParseFailed = errors.New("profiles: failed to parse flags") ErrFlagsParseFailed = errors.New("profiles: failed to parse flags")
sortedFlags = []uint8{ sortedFlags = []uint8{
Prompt, Prompt,
@ -77,34 +77,33 @@ var (
} }
) )
// FlagsFromNames creates ProfileFlags from a comma seperated list of flagnames (e.g. "System,Strict,Secure") // FlagsFromNames creates Flags from a comma seperated list of flagnames (e.g. "System,Strict,Secure")
// func FlagsFromNames(words []string) (*ProfileFlags, error) { // func FlagsFromNames(words []string) (*Flags, error) {
// var flags ProfileFlags // var flags Flags
// for _, entry := range words { // for _, entry := range words {
// flag, ok := flagIDs[entry] // flag, ok := flagIDs[entry]
// if !ok { // if !ok {
// return nil, ErrProfileFlagsParseFailed // return nil, ErrFlagsParseFailed
// } // }
// flags = append(flags, flag) // flags = append(flags, flag)
// } // }
// return &flags, nil // return &flags, nil
// } // }
// IsSet returns whether the ProfileFlags object is "set". // Check checks if a flag is set at all and if it's active in the given security level.
func (pf ProfileFlags) IsSet() bool { func (flags Flags) Check(flag, level uint8) (active bool, ok bool) {
if pf != nil { if flags == nil {
return true return false, false
} }
return false
}
// Has checks if a ProfileFlags object has a flag set in the given security level setting, ok := flags[flag]
func (pf ProfileFlags) Has(flag, level uint8) bool { if ok {
setting, ok := pf[flag] if setting&level > 0 {
if ok && setting&level > 0 { return true, true
return true }
return false, true
} }
return false return false, false
} }
func getLevelMarker(levels, level uint8) string { func getLevelMarker(levels, level uint8) string {
@ -114,11 +113,11 @@ func getLevelMarker(levels, level uint8) string {
return "-" return "-"
} }
// String return a string representation of ProfileFlags // String return a string representation of Flags
func (pf ProfileFlags) String() string { func (flags Flags) String() string {
var namedFlags []string var markedFlags []string
for _, flag := range sortedFlags { for _, flag := range sortedFlags {
levels, ok := pf[flag] levels, ok := flags[flag]
if ok { if ok {
s := flagNames[flag] s := flagNames[flag]
if levels != status.SecurityLevelsAll { if levels != status.SecurityLevelsAll {
@ -126,20 +125,18 @@ func (pf ProfileFlags) String() string {
s += getLevelMarker(levels, status.SecurityLevelSecure) s += getLevelMarker(levels, status.SecurityLevelSecure)
s += getLevelMarker(levels, status.SecurityLevelFortress) s += getLevelMarker(levels, status.SecurityLevelFortress)
} }
markedFlags = append(markedFlags, s)
} }
} }
for _, flag := range pf { return strings.Join(markedFlags, ", ")
namedFlags = append(namedFlags, flagNames[flag])
}
return strings.Join(namedFlags, ", ")
} }
// Add adds a flag to the Flags with the given level. // Add adds a flag to the Flags with the given level.
func (pf ProfileFlags) Add(flag, levels uint8) { func (flags Flags) Add(flag, levels uint8) {
pf[flag] = levels flags[flag] = levels
} }
// Remove removes a flag from the Flags. // Remove removes a flag from the Flags.
func (pf ProfileFlags) Remove(flag uint8) { func (flags Flags) Remove(flag uint8) {
delete(pf, flag) delete(flags, flag)
} }

View file

@ -2,6 +2,8 @@ package profile
import ( import (
"testing" "testing"
"github.com/Safing/portmaster/status"
) )
func TestProfileFlags(t *testing.T) { 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 // // check Has
// emptyFlags := ProfileFlags{} // emptyFlags := ProfileFlags{}
// for flag, name := range flagNames { // for flag, name := range flagNames {

View file

@ -9,19 +9,12 @@ import (
// Ports is a list of permitted or denied ports // Ports is a list of permitted or denied ports
type Ports map[string][]*Port type Ports map[string][]*Port
// IsSet returns whether the Ports object is "set". // Check returns whether listening/connecting to a certain port is allowed, if set.
func (p Ports) IsSet() bool { func (p Ports) Check(listen bool, protocol string, port uint16) (permit, ok 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 { if p == nil {
return false, false return false, false
} }
if listen { if listen {
protocol = "<" + protocol 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 { func (p Ports) String() string {

View file

@ -29,7 +29,7 @@ type Profile struct {
// The mininum security level to apply to connections made with this profile // The mininum security level to apply to connections made with this profile
SecurityLevel uint8 SecurityLevel uint8
Flags ProfileFlags Flags Flags
Domains Domains Domains Domains
Ports Ports Ports Ports
@ -42,6 +42,7 @@ type Profile struct {
ApproxLastUsed int64 ApproxLastUsed int64
} }
// New returns a new Profile.
func New() *Profile { func New() *Profile {
return &Profile{} return &Profile{}
} }
@ -49,12 +50,10 @@ func New() *Profile {
// Save saves the profile to the database // Save saves the profile to the database
func (profile *Profile) Save(namespace string) error { func (profile *Profile) Save(namespace string) error {
if profile.ID == "" { if profile.ID == "" {
// FIXME: this is weird, the docs says that it also returns an error u, err := uuid.NewV4()
u := uuid.NewV4() if err != nil {
// u, err := uuid.NewV4() return err
// if err != nil { }
// return err
// }
profile.ID = u.String() profile.ID = u.String()
} }

View file

@ -1,119 +1,132 @@
package profile package profile
import "github.com/Safing/portmaster/status"
var ( var (
emptyFlags = ProfileFlags{} emptyFlags = Flags{}
emptyPorts = Ports{} emptyPorts = Ports{}
) )
// ProfileSet handles Profile chaining. // Set handles Profile chaining.
type ProfileSet struct { type Set struct {
Profiles [4]*Profile profiles [4]*Profile
// Application // Application
// Global // Global
// Stamp // Stamp
// Default // Default
Independent bool securityLevel uint8
independent bool
} }
// NewSet returns a new profile set with given the profiles. // NewSet returns a new profile set with given the profiles.
func NewSet(user, stamp *Profile) *ProfileSet { func NewSet(user, stamp *Profile) *Set {
new := &ProfileSet{ new := &Set{
Profiles: [4]*Profile{ profiles: [4]*Profile{
user, // Application user, // Application
nil, // Global nil, // Global
stamp, // Stamp stamp, // Stamp
nil, // Default nil, // Default
}, },
} }
new.Update() new.Update(status.SecurityLevelFortress)
return new 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. // 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() { func (set *Set) Update(securityLevel uint8) {
specialProfileLock.RLock() specialProfileLock.RLock()
defer specialProfileLock.RUnlock() defer specialProfileLock.RUnlock()
// update profiles // update profiles
ps.Profiles[1] = globalProfile set.profiles[1] = globalProfile
ps.Profiles[3] = defaultProfile set.profiles[3] = defaultProfile
// update independence // update security level
if ps.Flags().Has(Independent, ps.SecurityLevel()) { profileSecurityLevel := set.getProfileSecurityLevel()
// Stamp profiles do not have the Independent flag if profileSecurityLevel > securityLevel {
ps.Independent = true set.securityLevel = profileSecurityLevel
} else { } else {
ps.Independent = false set.securityLevel = securityLevel
}
}
// 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 // 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. // CheckFlag returns whether a given flag is set.
func (ps *ProfileSet) CheckDomainStatus(domain string) (permit, ok bool) { func (set *Set) CheckFlag(flag) (active bool) {
for i, profile := range ps.Profiles { for i, profile := range set.profiles {
if i == 2 && ps.Independent { if i == 2 && set.independent {
continue continue
} }
if profile != nil { if profile != nil {
if profile.Domains.IsSet() { active, ok := profile.Flags.Check(flag, set.securityLevel)
permit, ok = profile.Domains.CheckStatus(domain) if ok {
if ok { return active
return }
} }
} }
}
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 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. // SecurityLevel returns the highest prioritized security level.
func (ps *ProfileSet) SecurityLevel() uint8 { func (set *Set) getProfileSecurityLevel() uint8 {
for i, profile := range ps.Profiles { for i, profile := range set.profiles {
if i == 2 { if i == 2 {
// Stamp profiles do not have the SecurityLevel setting // Stamp profiles do not have the SecurityLevel setting
continue continue
} }
if profile != nil { if profile != nil {
if profile.SecurityLevel > 0 { if profile.SecurityLevel > 0 {
return profile.SecurityLevel return profile.SecurityLevel
} }
} }
} }