diff --git a/dbus.go b/dbus.go index ea169f4..55b5829 100644 --- a/dbus.go +++ b/dbus.go @@ -7,6 +7,8 @@ import ( "github.com/subgraph/fw-daemon/Godeps/_workspace/src/github.com/godbus/dbus" "github.com/subgraph/fw-daemon/Godeps/_workspace/src/github.com/godbus/dbus/introspect" + "github.com/subgraph/fw-daemon/Godeps/_workspace/src/github.com/op/go-logging" + "path" ) const introspectXml = ` @@ -15,9 +17,31 @@ const introspectXml = ` + + + + + + + + + + + + + + + + + + + + + + ` + introspect.IntrospectDataString + `` @@ -27,10 +51,19 @@ const objectPath = "/com/subgraph/Firewall" const interfaceName = "com.subgraph.Firewall" type dbusServer struct { + fw *Firewall conn *dbus.Conn prompter *prompter } +type DbusRule struct { + Id uint32 + App string + Path string + Verb uint32 + Target string +} + func newDbusServer() (*dbusServer, error) { conn, err := dbus.SystemBus() if err != nil { @@ -88,13 +121,96 @@ func (ds *dbusServer) Introspect(msg dbus.Message) (string, *dbus.Error) { } func (ds *dbusServer) SetEnabled(flag bool) *dbus.Error { - log.Info("SetEnabled(%v) called", flag) + log.Debug("SetEnabled(%v) called", flag) + ds.fw.setEnabled(flag) return nil } func (ds *dbusServer) IsEnabled() (bool, *dbus.Error) { - log.Info("IsEnabled() called") - return true, nil + log.Debug("IsEnabled() called") + return ds.fw.isEnabled(), nil +} + +func createDbusRule(r *Rule) DbusRule { + return DbusRule{ + Id: uint32(r.id), + App: path.Base(r.policy.path), + Path: r.policy.path, + Verb: uint32(r.rtype), + Target: r.AddrString(), + } +} + +func (ds *dbusServer) ListRules() ([]DbusRule, *dbus.Error) { + ds.fw.lock.Lock() + defer ds.fw.lock.Unlock() + var result []DbusRule + for _, p := range ds.fw.policies { + for _, r := range p.rules { + result = append(result, createDbusRule(r)) + } + } + return result, nil +} + +func (ds *dbusServer) DeleteRule(id uint32) *dbus.Error { + ds.fw.lock.Lock() + r := ds.fw.rulesById[uint(id)] + ds.fw.lock.Unlock() + if r != nil { + r.policy.removeRule(r) + } + if !r.sessionOnly { + ds.fw.saveRules() + } + return nil +} + +func (ds *dbusServer) UpdateRule(rule DbusRule) *dbus.Error { + log.Debug("UpdateRule %v", rule) + ds.fw.lock.Lock() + r := ds.fw.rulesById[uint(rule.Id)] + ds.fw.lock.Unlock() + if r != nil { + tmp := new(Rule) + tmp.addr = noAddress + if !tmp.parseTarget(rule.Target) { + log.Warning("Unable to parse target: %s", rule.Target) + return nil + } + r.policy.lock.Lock() + if rule.Verb == RULE_ALLOW || rule.Verb == RULE_DENY { + r.rtype = int(rule.Verb) + } + r.hostname = tmp.hostname + r.addr = tmp.addr + r.port = tmp.port + r.policy.lock.Unlock() + if !r.sessionOnly { + ds.fw.saveRules() + } + } + return nil +} + +func (ds *dbusServer) GetConfig() (map[string]dbus.Variant, *dbus.Error) { + conf := make(map[string]dbus.Variant) + conf["loglevel"] = dbus.MakeVariant(int32(ds.fw.logBackend.GetLevel("sgfw"))) + conf["logredact"] = dbus.MakeVariant(ds.fw.logRedact) + return conf, nil +} + +func (ds *dbusServer) SetConfig(key string, val dbus.Variant) *dbus.Error { + switch key { + case "loglevel": + l := val.Value().(int32) + lvl := logging.Level(l) + ds.fw.logBackend.SetLevel(lvl, "sgfw") + case "logredact": + flag := val.Value().(bool) + ds.fw.logRedact = flag + } + return nil } func (ds *dbusServer) prompt(p *Policy) { diff --git a/iptables.go b/iptables.go index cdf943d..2a4e2d0 100644 --- a/iptables.go +++ b/iptables.go @@ -17,7 +17,7 @@ func setupIPTables() { func addIPTRules(rules ...string) { for _, r := range rules { - if(iptables('C', r)) { + if iptables('C', r) { log.Info("IPTables rule already present: %s", r) } else { log.Info("Installing IPTables rule: %s", r) diff --git a/main.go b/main.go index dd6f3d6..71fd102 100644 --- a/main.go +++ b/main.go @@ -8,10 +8,10 @@ import ( "github.com/subgraph/fw-daemon/Godeps/_workspace/src/github.com/op/go-logging" "github.com/subgraph/fw-daemon/nfqueue" + "github.com/subgraph/fw-daemon/proc" "sync" "syscall" "unsafe" - "github.com/subgraph/fw-daemon/proc" ) var log = logging.MustGetLogger("sgfw") @@ -22,6 +22,7 @@ var logFormat = logging.MustStringFormatter( var ttyFormat = logging.MustStringFormatter( "%{color}%{time:15:04:05} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}", ) + const ioctlReadTermios = 0x5401 func isTerminal(fd int) bool { @@ -30,7 +31,7 @@ func isTerminal(fd int) bool { return err == 0 } -func init() { +func setupLoggerBackend() logging.LeveledBackend { format := logFormat if isTerminal(int(os.Stderr.Fd())) { format = ttyFormat @@ -38,16 +39,66 @@ func init() { backend := logging.NewLogBackend(os.Stderr, "", 0) formatter := logging.NewBackendFormatter(backend, format) leveler := logging.AddModuleLevel(formatter) - log.SetBackend(leveler) + return leveler } type Firewall struct { dbus *dbusServer dns *dnsCache + enabled bool + + logRedact bool + logBackend logging.LeveledBackend + lock sync.Mutex policyMap map[string]*Policy policies []*Policy + + ruleLock sync.Mutex + rulesById map[uint]*Rule + nextRuleId uint +} + +func (fw *Firewall) setEnabled(flag bool) { + fw.lock.Lock() + defer fw.lock.Unlock() + fw.enabled = flag +} + +func (fw *Firewall) isEnabled() bool { + fw.lock.Lock() + defer fw.lock.Unlock() + return fw.enabled +} + +func (fw *Firewall) clearRules() { + fw.ruleLock.Lock() + defer fw.ruleLock.Unlock() + fw.rulesById = nil + fw.nextRuleId = 0 +} + +func (fw *Firewall) addRule(r *Rule) { + fw.ruleLock.Lock() + defer fw.ruleLock.Unlock() + + r.id = fw.nextRuleId + fw.nextRuleId += 1 + if fw.rulesById == nil { + fw.rulesById = make(map[uint]*Rule) + } + fw.rulesById[r.id] = r +} + +func (fw *Firewall) getRuleById(id uint) *Rule { + fw.ruleLock.Lock() + defer fw.ruleLock.Unlock() + + if fw.rulesById == nil { + return nil + } + return fw.rulesById[id] } func (fw *Firewall) runFilter() { @@ -64,7 +115,11 @@ func (fw *Firewall) runFilter() { for { select { case pkt := <-packets: - fw.filterPacket(pkt) + if fw.isEnabled() { + fw.filterPacket(pkt) + } else { + pkt.Accept() + } case <-sigs: return } @@ -72,6 +127,8 @@ func (fw *Firewall) runFilter() { } func main() { + logBackend := setupLoggerBackend() + log.SetBackend(logBackend) proc.SetLogger(log) if os.Geteuid() != 0 { @@ -88,17 +145,21 @@ func main() { } fw := &Firewall{ - dbus: ds, - dns: NewDnsCache(), - policyMap: make(map[string]*Policy), + dbus: ds, + dns: NewDnsCache(), + enabled: true, + logRedact: false, + logBackend: logBackend, + policyMap: make(map[string]*Policy), } + ds.fw = fw fw.loadRules() /* - go func() { - http.ListenAndServe("localhost:6060", nil) - }() + go func() { + http.ListenAndServe("localhost:6060", nil) + }() */ fw.runFilter() diff --git a/policy.go b/policy.go index d1a298b..424b67b 100644 --- a/policy.go +++ b/policy.go @@ -12,7 +12,7 @@ type pendingPkt struct { policy *Policy hostname string pkt *nfqueue.Packet - pinfo *proc.ProcInfo + pinfo *proc.ProcInfo } type Policy struct { @@ -110,6 +110,34 @@ func (p *Policy) processNewRule(r *Rule, scope int32) bool { return p.promptInProgress } +func (p *Policy) parseRule(s string, add bool) (*Rule, error) { + r := new(Rule) + r.policy = p + if !r.parse(s) { + return nil, parseError(s) + } + if add { + p.lock.Lock() + defer p.lock.Unlock() + p.rules = append(p.rules, r) + } + p.fw.addRule(r) + return r, nil +} + +func (p *Policy) removeRule(r *Rule) { + p.lock.Lock() + defer p.lock.Unlock() + + var newRules RuleList + for _, rr := range p.rules { + if rr.id != r.id { + newRules = append(newRules, rr) + } + } + p.rules = newRules +} + func (p *Policy) filterPending(rule *Rule) { remaining := []*pendingPkt{} for _, pp := range p.pendingQueue { diff --git a/prompt.go b/prompt.go index 04edd86..c3f6b18 100644 --- a/prompt.go +++ b/prompt.go @@ -100,18 +100,16 @@ func (p *prompter) processPacket(pp *pendingPkt) { pp.policy.removePending(pp) pp.pkt.Mark = 1 pp.pkt.Accept() - //pp.pkt.Drop() return } log.Debug("Received prompt response: %s [%s]", printScope(scope), rule) - r, err := parseRule(rule) + r, err := pp.policy.parseRule(rule, false) if err != nil { log.Warning("Error parsing rule string returned from dbus RequestPrompt: %v", err) pp.policy.removePending(pp) pp.pkt.Mark = 1 pp.pkt.Accept() - //pp.pkt.Drop() return } if scope == APPLY_SESSION { diff --git a/rules.go b/rules.go index 21dd45a..85ce1da 100644 --- a/rules.go +++ b/rules.go @@ -8,11 +8,11 @@ import ( "unicode" "github.com/subgraph/fw-daemon/nfqueue" + "github.com/subgraph/fw-daemon/proc" "io/ioutil" "os" - "strconv" "path" - "github.com/subgraph/fw-daemon/proc" + "strconv" ) const ( @@ -24,6 +24,8 @@ const matchAny = 0 const noAddress = uint32(0xffffffff) type Rule struct { + id uint + policy *Policy sessionOnly bool rtype int hostname string @@ -32,10 +34,17 @@ type Rule struct { } func (r *Rule) String() string { - addr := "*" - port := "*" rtype := "DENY" + if r.rtype == RULE_ALLOW { + rtype = "ALLOW" + } + return fmt.Sprintf("%s|%s", rtype, r.AddrString()) +} + +func (r *Rule) AddrString() string { + addr := "*" + port := "*" if r.hostname != "" { addr = r.hostname } else if r.addr != matchAny && r.addr != noAddress { @@ -48,11 +57,7 @@ func (r *Rule) String() string { port = fmt.Sprintf("%d", r.port) } - if r.rtype == RULE_ALLOW { - rtype = "ALLOW" - } - - return fmt.Sprintf("%s|%s:%s", rtype, addr, port) + return fmt.Sprintf("%s:%s", addr, port) } type RuleList []*Rule @@ -154,25 +159,17 @@ func (r *Rule) parsePort(p string) bool { } var err error port, err := strconv.ParseUint(p, 10, 16) - if err != nil { + if err != nil || port == 0 || port > 0xFFFF { return false } r.port = uint16(port) return true } -func parseRule(s string) (*Rule, error) { - r := new(Rule) - if !r.parse(s) { - return nil, parseError(s) - } - return r, nil -} - const ruleFile = "/var/lib/sgfw/sgfw_rules" func maybeCreateDir(dir string) error { - _,err := os.Stat(dir) + _, err := os.Stat(dir) if os.IsNotExist(err) { return os.MkdirAll(dir, 0755) } @@ -190,7 +187,7 @@ func (fw *Firewall) saveRules() { fw.lock.Lock() defer fw.lock.Unlock() - p,err := rulesPath() + p, err := rulesPath() if err != nil { log.Warning("Failed to open %s for writing: %v", p, err) return @@ -239,7 +236,9 @@ func (fw *Firewall) loadRules() { fw.lock.Lock() defer fw.lock.Unlock() - p,err := rulesPath() + fw.clearRules() + + p, err := rulesPath() if err != nil { log.Warning("Failed to open %s for reading: %v", p, err) return @@ -275,12 +274,9 @@ func processRuleLine(policy *Policy, line string) { log.Warning("Cannot process rule line without first seeing path line: %s", line) return } - rule, err := parseRule(line) + _, err := policy.parseRule(line, true) if err != nil { log.Warning("Error parsing rule (%s): %v", line, err) return } - policy.lock.Lock() - defer policy.lock.Unlock() - policy.rules = append(policy.rules, rule) }