You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fw-daemon/sgfw/prompt.go

419 lines
8.9 KiB

package sgfw
9 years ago
import (
"fmt"
"net"
9 years ago
"os/user"
"strconv"
"strings"
9 years ago
"sync"
"time"
9 years ago
8 years ago
"github.com/godbus/dbus"
"github.com/subgraph/fw-daemon/proc-coroner"
9 years ago
)
var DoMultiPrompt = true
const MAX_PROMPTS = 5
var outstandingPrompts = 0
var outstandingPromptChans [](chan *dbus.Call)
var promptLock = &sync.Mutex{}
var promptChanLock = &sync.Mutex{}
9 years ago
func newPrompter(conn *dbus.Conn) *prompter {
p := new(prompter)
p.cond = sync.NewCond(&p.lock)
p.dbusObj = conn.Object("com.subgraph.FirewallPrompt", "/com/subgraph/FirewallPrompt")
p.policyMap = make(map[string]*Policy)
go p.promptLoop()
return p
}
type prompter struct {
dbusObj dbus.BusObject
lock sync.Mutex
cond *sync.Cond
policyMap map[string]*Policy
policyQueue []*Policy
}
func saveChannel(ch chan *dbus.Call, add bool, do_close bool) {
promptChanLock.Lock()
if add {
outstandingPromptChans = append(outstandingPromptChans, ch)
} else {
for idx, och := range outstandingPromptChans {
if och == ch {
outstandingPromptChans = append(outstandingPromptChans[:idx], outstandingPromptChans[idx+1:]...)
break
}
}
}
if !add && do_close {
close(ch)
}
promptChanLock.Unlock()
return
}
9 years ago
func (p *prompter) prompt(policy *Policy) {
p.lock.Lock()
defer p.lock.Unlock()
_, ok := p.policyMap[policy.sandbox+"|"+policy.path]
9 years ago
if ok {
return
}
p.policyMap[policy.sandbox+"|"+policy.path] = policy
fmt.Println("Saving policy key:" + policy.sandbox + "|" + policy.path)
9 years ago
p.policyQueue = append(p.policyQueue, policy)
p.cond.Signal()
}
func (p *prompter) promptLoop() {
p.lock.Lock()
for {
// fmt.Println("XXX: promptLoop() outer")
9 years ago
for p.processNextPacket() {
// fmt.Println("XXX: promptLoop() inner")
9 years ago
}
// fmt.Println("promptLoop() wait")
9 years ago
p.cond.Wait()
}
}
func (p *prompter) processNextPacket() bool {
var pc pendingConnection = nil
if !DoMultiPrompt {
pc, _ = p.nextConnection()
if pc == nil {
return false
}
p.lock.Unlock()
defer p.lock.Lock()
p.processConnection(pc)
return true
}
empty := true
for {
pc, empty = p.nextConnection()
// fmt.Println("XXX: processNextPacket() loop; empty = ", empty, " / pc = ", pc)
if pc == nil && empty {
return false
} else if pc == nil {
continue
} else if pc != nil {
break
}
9 years ago
}
p.lock.Unlock()
defer p.lock.Lock()
// fmt.Println("XXX: Waiting for prompt lock go...")
for {
promptLock.Lock()
if outstandingPrompts >= MAX_PROMPTS {
promptLock.Unlock()
continue
}
if pc.getPrompting() {
fmt.Println("Skipping over already prompted connection")
promptLock.Unlock()
continue
}
break
}
// fmt.Println("XXX: Passed prompt lock!")
outstandingPrompts++
// fmt.Println("XXX: Incremented outstanding to ", outstandingPrompts)
promptLock.Unlock()
// if !pc.getPrompting() {
pc.setPrompting(true)
go p.processConnection(pc)
// }
9 years ago
return true
}
func processReturn(pc pendingConnection) {
promptLock.Lock()
outstandingPrompts--
// fmt.Println("XXX: Return decremented outstanding to ", outstandingPrompts)
promptLock.Unlock()
pc.setPrompting(false)
}
func alertChannel(chidx int, scope int32, rule string) {
defer func() {
if r := recover(); r != nil {
log.Warning("SGFW recovered from panic while delivering out of band rule:", r)
}
}()
promptData := make([]interface{}, 3)
promptData[0] = scope
promptData[1] = rule
promptData[2] = 666
outstandingPromptChans[chidx] <- &dbus.Call{Body: promptData}
}
func (p *prompter) processConnection(pc pendingConnection) {
9 years ago
var scope int32
var rule string
if pc.getPrompter() == nil {
pc.setPrompter(&dbusObjectP{p.dbusObj})
}
if DoMultiPrompt {
defer processReturn(pc)
}
addr := pc.hostname()
9 years ago
if addr == "" {
addr = pc.dst().String()
9 years ago
}
policy := pc.policy()
9 years ago
dststr := ""
if pc.dst() != nil {
dststr = pc.dst().String()
} else {
dststr = addr + " (via proxy resolver)"
}
callChan := make(chan *dbus.Call, 10)
saveChannel(callChan, true, false)
fmt.Println("# outstanding prompt chans = ", len(outstandingPromptChans))
p.dbusObj.Go("com.subgraph.FirewallPrompt.RequestPrompt", 0, callChan,
pc.getGUID(),
policy.application,
policy.icon,
policy.path,
9 years ago
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,
8 years ago
int32(FirewallConfig.DefaultActionID))
select {
case call := <-callChan:
if call.Err != nil {
fmt.Println("Error reading DBus channel (accepting packet): ", call.Err)
policy.removePending(pc)
pc.accept()
saveChannel(callChan, false, true)
time.Sleep(1 * time.Second)
return
}
if len(call.Body) != 2 {
log.Warning("SGFW got back response in unrecognized format, len = ", len(call.Body))
saveChannel(callChan, false, true)
if (len(call.Body) == 3) && (call.Body[2] == 666) {
fmt.Printf("+++++++++ AWESOME: %v | %v | %v\n", call.Body[0], call.Body[1], call.Body[2])
scope = call.Body[0].(int32)
rule = call.Body[1].(string)
}
return
}
fmt.Printf("DBUS GOT BACK: %v, %v\n", call.Body[0], call.Body[1])
scope = call.Body[0].(int32)
rule = call.Body[1].(string)
}
saveChannel(callChan, false, true)
// Try alerting every other channel
promptData := make([]interface{}, 3)
promptData[0] = scope
promptData[1] = rule
promptData[2] = 666
promptChanLock.Lock()
fmt.Println("# channels to alert: ", len(outstandingPromptChans))
for chidx, _ := range outstandingPromptChans {
alertChannel(chidx, scope, rule)
// ch <- &dbus.Call{Body: promptData}
9 years ago
}
promptChanLock.Unlock()
/* 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
//
// rule string needs to be:
// VERB|dst|class|uid:gid|sandbox|[src]
// sometimes there's a src
// this needs to be re-visited
toks := strings.Split(rule, "|")
//verb := toks[0]
//target := toks[1]
sandbox := ""
if len(toks) > 2 {
sandbox = toks[2]
}
tempRule := fmt.Sprintf("%s|%s", toks[0], toks[1])
if pc.src() != nil && !pc.src().Equal(net.ParseIP("127.0.0.1")) && sandbox != "" {
//if !strings.HasSuffix(rule, "SYSTEM") && !strings.HasSuffix(rule, "||") {
//rule += "||"
//}
//ule += "|||" + pc.src().String()
tempRule += "||-1:-1|" + sandbox + "|" + pc.src().String()
} else {
tempRule += "||-1:-1|" + sandbox + "|"
}
r, err := policy.parseRule(tempRule, false)
9 years ago
if err != nil {
log.Warningf("Error parsing rule string returned from dbus RequestPrompt: %v", err)
policy.removePending(pc)
pc.drop()
9 years ago
return
}
8 years ago
fscope := FilterScope(scope)
if fscope == APPLY_SESSION {
r.mode = RULE_MODE_SESSION
} else if fscope == APPLY_PROCESS {
r.mode = RULE_MODE_PROCESS
r.pid = pc.procInfo().Pid
pcoroner.MonitorProcess(r.pid)
9 years ago
}
8 years ago
if !policy.processNewRule(r, fscope) {
9 years ago
p.lock.Lock()
defer p.lock.Unlock()
p.removePolicy(pc.policy())
9 years ago
}
8 years ago
if fscope == APPLY_FOREVER {
r.mode = RULE_MODE_PERMANENT
policy.fw.saveRules()
9 years ago
}
log.Warningf("Prompt returning rule: %v", tempRule)
dbusp.alertRule("sgfw prompt added new rule")
9 years ago
}
func (p *prompter) nextConnection() (pendingConnection, bool) {
9 years ago
for {
if len(p.policyQueue) == 0 {
return nil, true
9 years ago
}
policy := p.policyQueue[0]
pc, qempty := policy.nextPending()
if pc == nil && qempty {
9 years ago
p.removePolicy(policy)
} else {
if pc == nil && !qempty {
fmt.Println("FIX ME: I NEED TO SLEEP ON A WAKEABLE CONDITION PROPERLY!!")
time.Sleep(time.Millisecond * 300)
}
return pc, qempty
9 years ago
}
}
}
func (p *prompter) removePolicy(policy *Policy) {
var newQueue []*Policy = nil
if DoMultiPrompt {
if len(p.policyQueue) == 0 {
fmt.Println("Skipping over zero length policy queue")
newQueue = make([]*Policy, 0, 0)
}
}
if !DoMultiPrompt || newQueue == nil {
newQueue = make([]*Policy, 0, len(p.policyQueue)-1)
}
9 years ago
for _, pol := range p.policyQueue {
if pol != policy {
newQueue = append(newQueue, pol)
}
}
p.policyQueue = newQueue
delete(p.policyMap, policy.sandbox+"|"+policy.path)
9 years ago
}
var userMap = make(map[int]string)
var groupMap = make(map[int]string)
9 years ago
func lookupUser(uid int) string {
if uid == -1 {
return "[unknown]"
}
9 years ago
u, err := user.LookupId(strconv.Itoa(uid))
if err != nil {
return fmt.Sprintf("%d", uid)
}
return u.Username
}
func lookupGroup(gid int) string {
if gid == -1 {
return "[unknown]"
}
g, err := user.LookupGroupId(strconv.Itoa(gid))
if err != nil {
return fmt.Sprintf("%d", gid)
}
return g.Name
9 years ago
}
func uidToUser(uid int) string {
uname, ok := userMap[uid]
if ok {
return uname
}
uname = lookupUser(uid)
userMap[uid] = uname
return uname
}
func gidToGroup(gid int) string {
gname, ok := groupMap[gid]
if ok {
return gname
}
gname = lookupGroup(gid)
groupMap[gid] = gname
return gname
}