mirror of https://github.com/subgraph/fw-daemon
				
				
				
			
			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.
		
		
		
		
		
			
		
			
				
					
					
						
							299 lines
						
					
					
						
							5.7 KiB
						
					
					
				
			
		
		
	
	
							299 lines
						
					
					
						
							5.7 KiB
						
					
					
				| package main
 | |
| 
 | |
| import (
 | |
| 	"encoding/binary"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"net"
 | |
| 	"os"
 | |
| 	"path"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"unicode"
 | |
| 
 | |
| 	"github.com/subgraph/fw-daemon/nfqueue"
 | |
| 	"github.com/subgraph/go-procsnitch"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	RULE_DENY = iota
 | |
| 	RULE_ALLOW
 | |
| )
 | |
| 
 | |
| const matchAny = 0
 | |
| const noAddress = uint32(0xffffffff)
 | |
| 
 | |
| type Rule struct {
 | |
| 	id          uint
 | |
| 	policy      *Policy
 | |
| 	sessionOnly bool
 | |
| 	rtype       int
 | |
| 	hostname    string
 | |
| 	addr        uint32
 | |
| 	port        uint16
 | |
| }
 | |
| 
 | |
| func (r *Rule) String() string {
 | |
| 	return r.getString(false)
 | |
| }
 | |
| 
 | |
| func (r *Rule) getString(redact bool) string {
 | |
| 	rtype := "DENY"
 | |
| 	if r.rtype == RULE_ALLOW {
 | |
| 		rtype = "ALLOW"
 | |
| 	}
 | |
| 
 | |
| 	return fmt.Sprintf("%s|%s", rtype, r.AddrString(redact))
 | |
| }
 | |
| 
 | |
| func (r *Rule) AddrString(redact bool) string {
 | |
| 	addr := "*"
 | |
| 	port := "*"
 | |
| 	if r.hostname != "" {
 | |
| 		addr = r.hostname
 | |
| 	} else if r.addr != matchAny && r.addr != noAddress {
 | |
| 		bs := make([]byte, 4)
 | |
| 		binary.BigEndian.PutUint32(bs, r.addr)
 | |
| 		addr = fmt.Sprintf("%d.%d.%d.%d", bs[0], bs[1], bs[2], bs[3])
 | |
| 	}
 | |
| 
 | |
| 	if r.port != matchAny {
 | |
| 		port = fmt.Sprintf("%d", r.port)
 | |
| 	}
 | |
| 
 | |
| 	if redact && addr != "*" {
 | |
| 		addr = "[redacted]"
 | |
| 	}
 | |
| 
 | |
| 	return fmt.Sprintf("%s:%s", addr, port)
 | |
| }
 | |
| 
 | |
| type RuleList []*Rule
 | |
| 
 | |
| func (r *Rule) match(dst net.IP, dstPort uint16, hostname string) bool {
 | |
| 	if r.port != matchAny && r.port != dstPort {
 | |
| 		return false
 | |
| 	}
 | |
| 	if r.addr == matchAny {
 | |
| 		return true
 | |
| 	}
 | |
| 	if r.hostname != "" {
 | |
| 		return r.hostname == hostname
 | |
| 	}
 | |
| 	return r.addr == binary.BigEndian.Uint32(dst)
 | |
| }
 | |
| 
 | |
| type FilterResult int
 | |
| 
 | |
| const (
 | |
| 	FILTER_DENY FilterResult = iota
 | |
| 	FILTER_ALLOW
 | |
| 	FILTER_PROMPT
 | |
| )
 | |
| 
 | |
| func (rl *RuleList) filterPacket(p *nfqueue.Packet, pinfo *procsnitch.Info, hostname string) FilterResult {
 | |
| 	return rl.filter(p.Dst, p.DstPort, hostname, pinfo)
 | |
| }
 | |
| 
 | |
| func (rl *RuleList) filter(dst net.IP, dstPort uint16, hostname string, pinfo *procsnitch.Info) FilterResult {
 | |
| 	if rl == nil {
 | |
| 		return FILTER_PROMPT
 | |
| 	}
 | |
| 	result := FILTER_PROMPT
 | |
| 	for _, r := range *rl {
 | |
| 		if r.match(dst, dstPort, hostname) {
 | |
| 			dstStr := dst.String()
 | |
| 			if logRedact {
 | |
| 				dstStr = "[redacted]"
 | |
| 			}
 | |
| 			log.Info("%s (%s -> %s:%d)", r.getString(logRedact), pinfo.ExePath, dstStr, dstPort)
 | |
| 			if r.rtype == RULE_DENY {
 | |
| 				return FILTER_DENY
 | |
| 			} else if r.rtype == RULE_ALLOW {
 | |
| 				result = FILTER_ALLOW
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return result
 | |
| }
 | |
| 
 | |
| func parseError(s string) error {
 | |
| 	return fmt.Errorf("unable to parse rule string: %s", s)
 | |
| }
 | |
| 
 | |
| func (r *Rule) parse(s string) bool {
 | |
| 	r.addr = noAddress
 | |
| 	parts := strings.Split(s, "|")
 | |
| 	if len(parts) != 2 {
 | |
| 		return false
 | |
| 	}
 | |
| 	return r.parseVerb(parts[0]) && r.parseTarget(parts[1])
 | |
| }
 | |
| 
 | |
| func (r *Rule) parseVerb(v string) bool {
 | |
| 	switch v {
 | |
| 	case "ALLOW":
 | |
| 		r.rtype = RULE_ALLOW
 | |
| 		return true
 | |
| 	case "DENY":
 | |
| 		r.rtype = RULE_DENY
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (r *Rule) parseTarget(t string) bool {
 | |
| 	addrPort := strings.Split(t, ":")
 | |
| 	if len(addrPort) != 2 {
 | |
| 		return false
 | |
| 	}
 | |
| 	return r.parseAddr(addrPort[0]) && r.parsePort(addrPort[1])
 | |
| }
 | |
| 
 | |
| func (r *Rule) parseAddr(a string) bool {
 | |
| 	if a == "*" {
 | |
| 		r.hostname = ""
 | |
| 		r.addr = matchAny
 | |
| 		return true
 | |
| 	}
 | |
| 	if strings.IndexFunc(a, unicode.IsLetter) != -1 {
 | |
| 		r.hostname = a
 | |
| 		return true
 | |
| 	}
 | |
| 	ip := net.ParseIP(a)
 | |
| 	if ip == nil {
 | |
| 		return false
 | |
| 	}
 | |
| 	r.addr = binary.BigEndian.Uint32(ip.To4())
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func (r *Rule) parsePort(p string) bool {
 | |
| 	if p == "*" {
 | |
| 		r.port = matchAny
 | |
| 		return true
 | |
| 	}
 | |
| 	var err error
 | |
| 	port, err := strconv.ParseUint(p, 10, 16)
 | |
| 	if err != nil || port == 0 || port > 0xFFFF {
 | |
| 		return false
 | |
| 	}
 | |
| 	r.port = uint16(port)
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| const ruleFile = "/var/lib/sgfw/sgfw_rules"
 | |
| 
 | |
| func maybeCreateDir(dir string) error {
 | |
| 	_, err := os.Stat(dir)
 | |
| 	if os.IsNotExist(err) {
 | |
| 		return os.MkdirAll(dir, 0755)
 | |
| 	}
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func rulesPath() (string, error) {
 | |
| 	if err := maybeCreateDir(path.Dir(ruleFile)); err != nil {
 | |
| 		return ruleFile, err
 | |
| 	}
 | |
| 	return ruleFile, nil
 | |
| }
 | |
| 
 | |
| func (fw *Firewall) saveRules() {
 | |
| 	fw.lock.Lock()
 | |
| 	defer fw.lock.Unlock()
 | |
| 
 | |
| 	p, err := rulesPath()
 | |
| 	if err != nil {
 | |
| 		log.Warning("Failed to open %s for writing: %v", p, err)
 | |
| 		return
 | |
| 	}
 | |
| 	f, err := os.Create(p)
 | |
| 	if err != nil {
 | |
| 		log.Warning("Failed to open %s for writing: %v", p, err)
 | |
| 		return
 | |
| 	}
 | |
| 	defer f.Close()
 | |
| 
 | |
| 	for _, p := range fw.policies {
 | |
| 		savePolicy(f, p)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func savePolicy(f *os.File, p *Policy) {
 | |
| 	p.lock.Lock()
 | |
| 	defer p.lock.Unlock()
 | |
| 	if !p.hasPersistentRules() {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if !writeLine(f, "["+p.path+"]") {
 | |
| 		return
 | |
| 	}
 | |
| 	for _, r := range p.rules {
 | |
| 		if !r.sessionOnly {
 | |
| 			if !writeLine(f, r.String()) {
 | |
| 				return
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func writeLine(f *os.File, line string) bool {
 | |
| 	_, err := f.WriteString(line + "\n")
 | |
| 	if err != nil {
 | |
| 		log.Warning("Error writing to rule file: %v", err)
 | |
| 		return false
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func (fw *Firewall) loadRules() {
 | |
| 	fw.lock.Lock()
 | |
| 	defer fw.lock.Unlock()
 | |
| 
 | |
| 	fw.clearRules()
 | |
| 
 | |
| 	p, err := rulesPath()
 | |
| 	if err != nil {
 | |
| 		log.Warning("Failed to open %s for reading: %v", p, err)
 | |
| 		return
 | |
| 	}
 | |
| 	bs, err := ioutil.ReadFile(p)
 | |
| 	if err != nil {
 | |
| 		if !os.IsNotExist(err) {
 | |
| 			log.Warning("Failed to open %s for reading: %v", p, err)
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 	var policy *Policy
 | |
| 	for _, line := range strings.Split(string(bs), "\n") {
 | |
| 		if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
 | |
| 			policy = fw.processPathLine(line)
 | |
| 		} else if len(strings.TrimSpace(line)) > 0 {
 | |
| 			processRuleLine(policy, line)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (fw *Firewall) processPathLine(line string) *Policy {
 | |
| 	path := line[1 : len(line)-1]
 | |
| 	policy := fw.policyForPath(path)
 | |
| 	policy.lock.Lock()
 | |
| 	defer policy.lock.Unlock()
 | |
| 	policy.rules = nil
 | |
| 	return policy
 | |
| }
 | |
| 
 | |
| func processRuleLine(policy *Policy, line string) {
 | |
| 	if policy == nil {
 | |
| 		log.Warning("Cannot process rule line without first seeing path line: %s", line)
 | |
| 		return
 | |
| 	}
 | |
| 	_, err := policy.parseRule(line, true)
 | |
| 	if err != nil {
 | |
| 		log.Warning("Error parsing rule (%s): %v", line, err)
 | |
| 		return
 | |
| 	}
 | |
| }
 |