diff --git a/TODO b/TODO index 868771b..b3b843f 100644 --- a/TODO +++ b/TODO @@ -10,9 +10,6 @@ fw-prompt: more nesting for similar prompts (by application, pid, target host, etc) -gnome-shell: - Start using new async DBus methods - new go-procsnitch vendor package changes should be pushed into main project diff --git a/fw-prompt/fw-prompt.go b/fw-prompt/fw-prompt.go index f752582..38f73d0 100644 --- a/fw-prompt/fw-prompt.go +++ b/fw-prompt/fw-prompt.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/gotk3/gotk3/gdk" "github.com/gotk3/gotk3/glib" "github.com/gotk3/gotk3/gtk" "io/ioutil" @@ -74,6 +75,7 @@ var Notebook *gtk.Notebook var globalTS *gtk.TreeStore = nil var globalTV *gtk.TreeView var globalPromptLock = &sync.Mutex{} +var recentLock = &sync.Mutex{} var globalIcon *gtk.Image var editApp, editTarget, editPort, editUser, editGroup *gtk.Entry @@ -81,6 +83,28 @@ var comboProto *gtk.ComboBoxText var radioOnce, radioProcess, radioParent, radioSession, radioPermanent *gtk.RadioButton var btnApprove, btnDeny, btnIgnore *gtk.Button var chkTLS, chkUser, chkGroup *gtk.CheckButton +var recentlyRemoved = []string{} + +func wasRecentlyRemoved(guid string) bool { + recentLock.Lock() + defer recentLock.Unlock() + + for gind, g := range recentlyRemoved { + if g == guid { + recentlyRemoved = append(recentlyRemoved[:gind], recentlyRemoved[gind+1:]...) + return true + } + } + + return false +} + +func addRecentlyRemoved(guid string) { + recentLock.Lock() + defer recentLock.Unlock() + fmt.Println("RECENTLY REMOVED: ", guid) + recentlyRemoved = append(recentlyRemoved, guid) +} func promptInfo(msg string) { dialog := gtk.MessageDialogNew(mainWin, 0, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, "Displaying full log info:") @@ -337,6 +361,11 @@ func createTreeStore(general bool) *gtk.TreeStore { } func removeRequest(treeStore *gtk.TreeStore, guid string) { + if wasRecentlyRemoved(guid) { + fmt.Printf("Entry for %s was recently removed; deleting from cache\n", guid) + return + } + removed := false if globalTS == nil { @@ -366,7 +395,7 @@ remove_outer: if err != nil { break remove_outer } else if rule.GUID == guid { - removeSelectedRule(ridx) + removeSelectedRule(ridx, sidx) removed = true break } @@ -427,11 +456,36 @@ func storeNewEntry(ts *gtk.TreeStore, iter *gtk.TreeIter, guid, path, icon, prot log.Fatal("Could not load default icon theme:", err) } - pb, err := itheme.LoadIcon(icon, 24, gtk.ICON_LOOKUP_GENERIC_FALLBACK) - if err != nil { - log.Println("Could not load icon:", err) + make_blank := false + if icon != "" { + pb, err := itheme.LoadIcon(icon, 24, gtk.ICON_LOOKUP_GENERIC_FALLBACK) + if err != nil { + log.Println("Could not load icon:", err) + make_blank = true + } else { + colVals[COL_NO_ICON_PIXBUF] = pb + } } else { - colVals[COL_NO_ICON_PIXBUF] = pb + make_blank = true + } + + if make_blank { + pb, err := gdk.PixbufNew(gdk.COLORSPACE_RGB, true, 8, 24, 24) + if err != nil { + log.Println("Error creating blank icon:", err) + } else { + colVals[COL_NO_ICON_PIXBUF] = pb + + img, err := gtk.ImageNewFromPixbuf(pb) + if err != nil { + log.Println("Error creating image from pixbuf:", err) + } else { + img.Clear() + pb = img.GetPixbuf() + colVals[COL_NO_ICON_PIXBUF] = pb + } + } + } for n := 0; n < len(colVals); n++ { @@ -466,9 +520,7 @@ func addRequestInc(treeStore *gtk.TreeStore, guid, path, icon, proto string, pid break } - fmt.Println("YES REALLY DUPLICATE: ", rule.nrefs) duplicated = true - subiter := globalTS.Append(iter) storeNewEntry(globalTS, subiter, guid, path, icon, proto, pid, ipaddr, hostname, port, uid, gid, origin, timestamp, is_socks, optstring, sandbox, action) break @@ -515,13 +567,11 @@ func addRequest(treeStore *gtk.TreeStore, guid, path, icon, proto string, pid in } if addRequestInc(treeStore, guid, path, icon, proto, pid, ipaddr, hostname, port, uid, gid, origin, timestamp, is_socks, optstring, sandbox, action) { - fmt.Println("REQUEST WAS DUPLICATE") + fmt.Println("Request was duplicate: ", guid) globalPromptLock.Lock() toggleHover() globalPromptLock.Unlock() return true - } else { - fmt.Println("NOT DUPLICATE") } globalPromptLock.Lock() @@ -624,9 +674,9 @@ func lsGetInt(ls *gtk.TreeStore, iter *gtk.TreeIter, idx int) (int, error) { return ival.(int), nil } -func makeDecision(idx int, rule string, scope int) error { +func makeDecision(rule string, scope int, guid string) error { var dres bool - call := dbuso.Call("AddRuleAsync", 0, uint32(scope), rule, "*") + call := dbuso.Call("AddRuleAsync", 0, uint32(scope), rule, "*", guid) err := call.Store(&dres) if err != nil { @@ -764,20 +814,86 @@ func clearEditor() { chkTLS.SetActive(false) } -func removeSelectedRule(idx int) error { - fmt.Println("XXX: attempting to remove idx = ", idx) +func removeSelectedRule(idx, subidx int) error { + fmt.Printf("XXX: attempting to remove idx = %v, %v\n", idx, subidx) + ppathstr := fmt.Sprintf("%d", idx) + pathstr := ppathstr - path, err := gtk.TreePathNewFromString(fmt.Sprintf("%d", idx)) - if err != nil { - return err + if subidx > -1 { + pathstr = fmt.Sprintf("%d:%d", idx, subidx) } - iter, err := globalTS.GetIter(path) + iter, err := globalTS.GetIterFromString(pathstr) if err != nil { return err } + nchildren := globalTS.IterNChildren(iter) + + if nchildren >= 1 { + firstpath := fmt.Sprintf("%d:0", idx) + citer, err := globalTS.GetIterFromString(firstpath) + if err != nil { + return err + } + + gnrefs, err := globalTS.GetValue(iter, COL_NO_NREFS) + if err != nil { + return err + } + + vnrefs, err := gnrefs.GoValue() + if err != nil { + return err + } + + nrefs := vnrefs.(int) - 1 + + for n := 0; n < COL_NO_LAST; n++ { + val, err := globalTS.GetValue(citer, n) + if err != nil { + return err + } + + if n == COL_NO_NREFS { + err = globalTS.SetValue(iter, n, nrefs) + } else { + err = globalTS.SetValue(iter, n, val) + } + + if err != nil { + return err + } + } + + globalTS.Remove(citer) + return nil + } + globalTS.Remove(iter) + + if subidx > -1 { + ppath, err := gtk.TreePathNewFromString(ppathstr) + if err != nil { + return err + } + + piter, err := globalTS.GetIter(ppath) + if err != nil { + return err + } + + nrefs, err := lsGetInt(globalTS, piter, COL_NO_NREFS) + if err != nil { + return err + } + + err = globalTS.SetValue(piter, COL_NO_NREFS, nrefs-1) + if err != nil { + return err + } + } + toggleHover() return nil } @@ -896,18 +1012,18 @@ func getRuleByIdx(idx, subidx int) (ruleColumns, *gtk.TreeIter, error) { } // Needs to be locked by the caller -func getSelectedRule() (ruleColumns, int, error) { +func getSelectedRule() (ruleColumns, int, int, error) { rule := ruleColumns{} sel, err := globalTV.GetSelection() if err != nil { - return rule, -1, err + return rule, -1, -1, err } rows := sel.GetSelectedRows(globalTS) if rows.Length() <= 0 { - return rule, -1, errors.New("no selection was made") + return rule, -1, -1, errors.New("no selection was made") } rdata := rows.NthData(0) @@ -917,45 +1033,57 @@ func getSelectedRule() (ruleColumns, int, error) { ptoks := strings.Split(tpath, ":") if len(ptoks) > 2 { - return rule, -1, errors.New("internal error parsing selected item tree path") + return rule, -1, -1, errors.New("internal error parsing selected item tree path") } else if len(ptoks) == 2 { subidx, err = strconv.Atoi(ptoks[1]) if err != nil { - return rule, -1, err + return rule, -1, -1, err } tpath = ptoks[0] } lIndex, err := strconv.Atoi(tpath) if err != nil { - return rule, -1, err + return rule, -1, -1, err } - fmt.Printf("lindex = %d : %d\n", lIndex, subidx) + // fmt.Printf("lindex = %d : %d\n", lIndex, subidx) rule, _, err = getRuleByIdx(lIndex, subidx) if err != nil { - return rule, -1, err + return rule, -1, -1, err } - return rule, lIndex, nil + return rule, lIndex, subidx, nil } func buttonAction(action string) { globalPromptLock.Lock() - rule, idx, err := getSelectedRule() + rule, idx, subidx, err := getSelectedRule() if err != nil { globalPromptLock.Unlock() promptError("Error occurred processing request: " + err.Error()) return } - rule, err = createCurrentRule() + urule, err := createCurrentRule() if err != nil { globalPromptLock.Unlock() promptError("Error occurred constructing new rule: " + err.Error()) return } + // Overlay the rules + rule.Scope = urule.Scope + //rule.Path = urule.Path + rule.Port = urule.Port + rule.Target = urule.Target + rule.Proto = urule.Proto + // rule.UID = urule.UID + // rule.GID = urule.GID + // rule.Uname = urule.Uname + // rule.Gname = urule.Gname + rule.ForceTLS = urule.ForceTLS + fmt.Println("rule = ", rule) rulestr := action @@ -966,8 +1094,9 @@ func buttonAction(action string) { rulestr += "|" + rule.Proto + ":" + rule.Target + ":" + strconv.Itoa(rule.Port) rulestr += "|" + sgfw.RuleModeString[sgfw.RuleMode(rule.Scope)] fmt.Println("RULESTR = ", rulestr) - makeDecision(idx, rulestr, int(rule.Scope)) - err = removeSelectedRule(idx) + makeDecision(rulestr, int(rule.Scope), rule.GUID) + err = removeSelectedRule(idx, subidx) + addRecentlyRemoved(rule.GUID) globalPromptLock.Unlock() if err == nil { clearEditor() @@ -1087,6 +1216,7 @@ func main() { // globalIcon.SetFromIconName("firefox", gtk.ICON_SIZE_DND) editApp = get_entry("") + editApp.SetEditable(false) editApp.Connect("changed", toggleValidRuleState) hbox.PackStart(lbl, false, false, 10) hbox.PackStart(editApp, true, true, 10) @@ -1198,7 +1328,7 @@ func main() { // tv.SetActivateOnSingleClick(true) tv.Connect("row-activated", func() { globalPromptLock.Lock() - seldata, _, err := getSelectedRule() + seldata, _, _, err := getSelectedRule() globalPromptLock.Unlock() if err != nil { promptError("Unexpected error reading selected rule: " + err.Error()) diff --git a/sgfw/const.go b/sgfw/const.go index b556055..74808f9 100644 --- a/sgfw/const.go +++ b/sgfw/const.go @@ -147,9 +147,3 @@ type DbusRule struct { Mode uint16 Sandbox string } - -/*const ( - OZ_FWRULE_WHITELIST = iota - OZ_FWRULE_BLACKLIST - OZ_FWRULE_NONE -) */ diff --git a/sgfw/dbus.go b/sgfw/dbus.go index 3c89368..fed6100 100644 --- a/sgfw/dbus.go +++ b/sgfw/dbus.go @@ -261,12 +261,12 @@ func (ds *dbusServer) GetPendingRequests(policy string) (bool, *dbus.Error) { return succeeded, nil } -func (ds *dbusServer) AddRuleAsync(scope uint32, rule string, policy string) (bool, *dbus.Error) { - log.Warningf("AddRuleAsync %v, %v / %v\n", scope, rule, policy) +func (ds *dbusServer) AddRuleAsync(scope uint32, rule, policy, guid string) (bool, *dbus.Error) { + log.Warningf("AddRuleAsync %v, %v / %v / %v\n", scope, rule, policy, guid) ds.fw.lock.Lock() defer ds.fw.lock.Unlock() - prule := PendingRule{rule: rule, scope: int(scope), policy: policy} + prule := PendingRule{rule: rule, scope: int(scope), policy: policy, guid: guid} for pname := range ds.fw.policyMap { log.Debug("+++ Adding prule to policy") diff --git a/sgfw/icons.go b/sgfw/icons.go index 4beed49..778c0da 100644 --- a/sgfw/icons.go +++ b/sgfw/icons.go @@ -85,5 +85,14 @@ func loadDesktopFile(path string) { icon: icon, name: name, } + + lname := exec + for i := 0; i < 5; i++ { + lname, err = os.Readlink(lname) + if err == nil { + entryMap[lname] = entryMap[exec] + } + } + } } diff --git a/sgfw/ipc.go b/sgfw/ipc.go index 35d81a5..0c87701 100644 --- a/sgfw/ipc.go +++ b/sgfw/ipc.go @@ -139,19 +139,6 @@ func ReceiverLoop(fw *Firewall, c net.Conn) { c.Write([]byte(ruledesc)) } - /* for i := 0; i < len(sandboxRules); i++ { - rulestr := "" - - if sandboxRules[i].Whitelist { - rulestr += "whitelist" - } else { - rulestr += "blacklist" - } - - rulestr += " " + sandboxRules[i].SrcIf.String() + " -> " + sandboxRules[i].DstIP.String() + " : " + strconv.Itoa(int(sandboxRules[i].DstPort)) + "\n" - c.Write([]byte(rulestr)) - } */ - return } else { tokens := strings.Split(data, " ") diff --git a/sgfw/policy.go b/sgfw/policy.go index 936ae73..78765da 100644 --- a/sgfw/policy.go +++ b/sgfw/policy.go @@ -23,17 +23,6 @@ var _interpreters = []string{ "bash", } -/*type sandboxRule struct { - SrcIf net.IP - DstIP net.IP - DstPort uint16 - Whitelist bool -} - -var sandboxRules = []sandboxRule { -// { net.IP{172,16,1,42}, net.IP{140,211,166,134}, 21, false }, -} */ - type pendingConnection interface { policy() *Policy procInfo() *procsnitch.Info @@ -222,6 +211,7 @@ type PendingRule struct { rule string scope int policy string + guid string } type Policy struct { @@ -313,7 +303,6 @@ func (p *Policy) processPacket(pkt *nfqueue.NFQPacket, timestamp time.Time, pinf if !FirewallConfig.LogRedact { log.Infof("Lookup(%s): %s", dstip.String(), name) } - // fwo := matchAgainstOzRules(srcip, dstip, dstp) result := p.rules.filterPacket(pkt, pinfo, srcip, name, optstr) switch result { @@ -331,12 +320,8 @@ func (p *Policy) processPacket(pkt *nfqueue.NFQPacket, timestamp time.Time, pinf func (p *Policy) processPromptResult(pc pendingConnection) { p.pendingQueue = append(p.pendingQueue, pc) - //fmt.Println("processPromptResult(): p.promptInProgress = ", p.promptInProgress) - //if DoMultiPrompt || (!DoMultiPrompt && !p.promptInProgress) { - // if !p.promptInProgress { p.promptInProgress = true go p.fw.dbus.prompter.prompt(p) - // } } func (p *Policy) nextPending() (pendingConnection, bool) { @@ -372,6 +357,15 @@ func (p *Policy) removePending(pc pendingConnection) { } } +func (p *Policy) processNewRuleOnce(r *Rule, guid string) bool { + p.lock.Lock() + defer p.lock.Unlock() + + fmt.Println("----------------------- processNewRule() ONCE") + p.filterPendingOne(r, guid) + return true +} + func (p *Policy) processNewRule(r *Rule, scope FilterScope) bool { p.lock.Lock() defer p.lock.Unlock() @@ -419,6 +413,47 @@ func (p *Policy) removeRule(r *Rule) { p.rules = newRules } +func (p *Policy) filterPendingOne(rule *Rule, guid string) { + remaining := []pendingConnection{} + + for _, pc := range p.pendingQueue { + if guid != "" && guid != pc.getGUID() { + continue + } + + if rule.match(pc.src(), pc.dst(), pc.dstPort(), pc.hostname(), pc.proto(), pc.procInfo().UID, pc.procInfo().GID, uidToUser(pc.procInfo().UID), gidToGroup(pc.procInfo().GID), pc.procInfo().Sandbox) { + prompter := pc.getPrompter() + + if prompter == nil { + fmt.Println("-------- prompter = NULL") + } else { + call := prompter.dbusObj.Call("com.subgraph.FirewallPrompt.RemovePrompt", 0, pc.getGUID()) + fmt.Println("CAAAAAAAAAAAAAAALL = ", call) + } + + log.Infof("Adding rule for: %s", rule.getString(FirewallConfig.LogRedact)) + // log.Noticef("%s > %s", rule.getString(FirewallConfig.LogRedact), pc.print()) + if rule.rtype == RULE_ACTION_ALLOW { + pc.accept() + } else if rule.rtype == RULE_ACTION_ALLOW_TLSONLY { + pc.acceptTLSOnly() + } else { + srcs := pc.src().String() + ":" + strconv.Itoa(int(pc.srcPort())) + log.Warningf("DENIED outgoing connection attempt by %s from %s %s -> %s:%d (user prompt) %v", + pc.procInfo().ExePath, pc.proto(), srcs, pc.dst(), pc.dstPort, rule.rtype) + pc.drop() + } + + // XXX: If matching a GUID, we can break out immediately + } else { + remaining = append(remaining, pc) + } + } + if len(remaining) != len(p.pendingQueue) { + p.pendingQueue = remaining + } +} + func (p *Policy) filterPending(rule *Rule) { remaining := []pendingConnection{} for _, pc := range p.pendingQueue { @@ -532,18 +567,6 @@ func (fw *Firewall) filterPacket(pkt *nfqueue.NFQPacket, timestamp time.Time) { */ _, dstip := getPacketIPAddrs(pkt) /* _, dstp := getPacketPorts(pkt) - fwo := eatchAgainstOzRules(srcip, dstip, dstp) - log.Notice("XXX: Attempting [2] to filter packet on rules -> ", fwo) - - if fwo == OZ_FWRULE_WHITELIST { - log.Noticef("Automatically passed through whitelisted sandbox traffic from %s to %s:%d\n", srcip, dstip, dstp) - pkt.Accept() - return - } else if fwo == OZ_FWRULE_BLACKLIST { - log.Noticef("Automatically blocking blacklisted sandbox traffic from %s to %s:%d\n", srcip, dstip, dstp) - pkt.SetMark(1) - pkt.Accept() - return } */ ppath := "*" @@ -942,21 +965,3 @@ func getPacketPorts(pkt *nfqueue.NFQPacket) (uint16, uint16) { return s, d } - -/*func matchAgainstOzRules(srci, dsti net.IP, dstp uint16) int { - - for i := 0; i < len(sandboxRules); i++ { - - log.Notice("XXX: Attempting to match: ", srci, " / ", dsti, " / ", dstp, " | ", sandboxRules[i]) - - if sandboxRules[i].SrcIf.Equal(srci) && sandboxRules[i].DstIP.Equal(dsti) && sandboxRules[i].DstPort == dstp { - if sandboxRules[i].Whitelist { - return OZ_FWRULE_WHITELIST - } - return OZ_FWRULE_BLACKLIST - } - - } - - return OZ_FWRULE_NONE -} */ diff --git a/sgfw/prompt.go b/sgfw/prompt.go index 3378589..d3efd09 100644 --- a/sgfw/prompt.go +++ b/sgfw/prompt.go @@ -61,6 +61,9 @@ func (p *prompter) processNextPacket() bool { p.lock.Lock() pc, empty = p.nextConnection() p.lock.Unlock() + if pc != nil { + fmt.Println("GOT NON NIL") + } //fmt.Println("XXX: processNextPacket() loop; empty = ", empty, " / pc = ", pc) if pc == nil && empty { return false @@ -268,38 +271,6 @@ func (p *prompter) processConnection(pc pendingConnection) { return - /* p.dbusObj.Go("com.subgraph.FirewallPrompt.RequestPrompt", 0, callChan, - pc.getGUID(), - policy.application, - policy.icon, - policy.path, - addr, - int32(pc.dstPort()), - dststr, - pc.src().String(), - pc.proto(), - int32(pc.procInfo().UID), - int32(pc.procInfo().GID), - uidToUser(pc.procInfo().UID), - gidToGroup(pc.procInfo().GID), - int32(pc.procInfo().Pid), - pc.sandbox(), - pc.socks(), - pc.getOptString(), - FirewallConfig.PromptExpanded, - FirewallConfig.PromptExpert, - int32(FirewallConfig.DefaultActionID)) - - saveChannel(callChan, false, true) - - /* err := call.Store(&scope, &rule) - if err != nil { - log.Warningf("Error sending dbus RequestPrompt message: %v", err) - policy.removePending(pc) - pc.drop() - return - } */ - // the prompt sends: // ALLOW|dest or DENY|dest // @@ -370,6 +341,7 @@ func (p *prompter) nextConnection() (pendingConnection, bool) { fmt.Println("policy queue len = ", len(p.policyQueue)) for pind < len(p.policyQueue) { + fmt.Printf("policy loop %d of %d\n", pind, len(p.policyQueue)) //fmt.Printf("XXX: pind = %v of %v\n", pind, len(p.policyQueue)) policy := p.policyQueue[pind] pc, qempty := policy.nextPending() @@ -379,21 +351,54 @@ func (p *prompter) nextConnection() (pendingConnection, bool) { continue } else { pind++ - // if pc == nil && !qempty { - if len(policy.rulesPending) > 0 { - fmt.Println("policy rules pending = ", len(policy.rulesPending)) + pendingOnce := make([]PendingRule, 0) + pendingOther := make([]PendingRule, 0) + + for _, r := range policy.rulesPending { + if r.scope == int(APPLY_ONCE) { + pendingOnce = append(pendingOnce, r) + } else { + pendingOther = append(pendingOther, r) + } + } + fmt.Printf("# pending once = %d, other = %d, pc = %p / policy = %p\n", len(pendingOnce), len(pendingOther), pc, policy) + policy.rulesPending = pendingOther + + // One time filters are all applied right here, at once. + for _, pr := range pendingOnce { + toks := strings.Split(pr.rule, "|") + sandbox := "" + + if len(toks) > 2 { + sandbox = toks[2] + } + tempRule := fmt.Sprintf("%s|%s", toks[0], toks[1]) + tempRule += "||-1:-1|" + sandbox + "|" + + r, err := policy.parseRule(tempRule, false) + if err != nil { + log.Warningf("Error parsing rule string returned from dbus RequestPrompt: %v", err) + continue + } + + r.mode = RuleMode(pr.scope) + fmt.Println("+++++++ processing one time rule: ", pr.rule) + policy.processNewRuleOnce(r, pr.guid) + } + + // if pc == nil && !qempty { + if len(policy.rulesPending) > 0 { + fmt.Println("non/once policy rules pending = ", len(policy.rulesPending)) prule := policy.rulesPending[0] policy.rulesPending = append(policy.rulesPending[:0], policy.rulesPending[1:]...) - toks := strings.Split(prule.rule, "|") sandbox := "" if len(toks) > 2 { sandbox = toks[2] } - sandbox += "" tempRule := fmt.Sprintf("%s|%s", toks[0], toks[1]) tempRule += "||-1:-1|" + sandbox + "|" diff --git a/sgfw/tlsguard.go b/sgfw/tlsguard.go index 00613c5..9370a06 100644 --- a/sgfw/tlsguard.go +++ b/sgfw/tlsguard.go @@ -592,7 +592,6 @@ select_loop: if !cr.client && s == SSL3_MT_HELLO_REQUEST { fmt.Println("Server sent hello request") - continue } if s > SSL3_MT_CERTIFICATE_STATUS {