diff --git a/firewall/firewall_darwin.go b/firewall/firewall_darwin.go new file mode 100644 index 00000000..9c3d3671 --- /dev/null +++ b/firewall/firewall_darwin.go @@ -0,0 +1,180 @@ +package firewall + +import ( + "bufio" + "fmt" + "io/ioutil" + "log" + "os" + "regexp" + "strings" + + "github.com/evilsocket/bettercap-ng/core" +) + +var ( + sysCtlParser = regexp.MustCompile("([^:]+):\\s*(.+)") + pfFilePath = fmt.Sprintf("/tmp/bcap_pf_%d.conf", os.Getpid()) +) + +type PfFirewall struct { + filename string + forwarding bool +} + +func Make() FirewallManager { + firewall := &PfFirewall{ + filename: pfFilePath, + forwarding: false, + } + + firewall.forwarding = firewall.IsForwardingEnabled() + + return firewall +} + +func (f PfFirewall) sysCtlRead(param string) (string, error) { + if out, err := core.Exec("sysctl", []string{param}); err != nil { + return "", err + } else if m := sysCtlParser.FindStringSubmatch(out); len(m) == 3 && m[1] == param { + return m[2], nil + } else { + return "", fmt.Errorf("Unexpected sysctl output: %s", out) + } +} + +func (f PfFirewall) sysCtlWrite(param string, value string) (string, error) { + args := []string{"-w", fmt.Sprintf("%s=%s", param, value)} + out, err := core.Exec("sysctl", args) + if err != nil { + return "", err + } + + // make sure we actually wrote the value + if out, err = f.sysCtlRead(param); err != nil { + return "", err + } else if out != value { + return "", fmt.Errorf("Expected value for '%s' is %s, found %s", param, value, out) + } else { + return out, nil + } +} + +func (f PfFirewall) IsForwardingEnabled() bool { + out, err := f.sysCtlRead("net.inet.ip.forwarding") + if err != nil { + log.Printf("ERROR: %s", err) + return false + } + + return strings.HasSuffix(out, ": 1") +} + +func (f PfFirewall) enableParam(param string, enabled bool) error { + var value string + if enabled { + value = "1" + } else { + value = "0" + } + + if _, err := f.sysCtlWrite(param, value); err != nil { + return err + } else { + return nil + } +} + +func (f PfFirewall) EnableForwarding(enabled bool) error { + return f.enableParam("net.inet.ip.forwarding", enabled) +} + +func (f PfFirewall) EnableIcmpBcast(enabled bool) error { + return f.enableParam("net.inet.icmp.bmcastecho", enabled) +} + +func (f PfFirewall) EnableSendRedirects(enabled bool) error { + return nil +} + +func (f PfFirewall) generateRule(r *Redirection) string { + src_a := "any" + dst_a := "any" + + if r.SrcAddress != "" { + src_a = r.SrcAddress + } + + if r.DstAddress != "" { + dst_a = r.DstAddress + } + + return fmt.Sprintf("rdr pass on %s proto %s from any to %s port %d -> %s port %d", + r.Interface, r.Protocol, src_a, r.SrcPort, dst_a, r.DstPort) +} + +func (f PfFirewall) enable(enabled bool) { + if enabled { + core.Exec("pfctl", []string{"-e"}) + } else { + core.Exec("pfctl", []string{"-d"}) + } +} + +func (f PfFirewall) EnableRedirection(r *Redirection, enabled bool) error { + rule := f.generateRule(r) + + if enabled == true { + fd, err := os.OpenFile(f.filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + return err + } + defer fd.Close() + + if _, err = fd.WriteString(rule + "\n"); err != nil { + return err + } + + // load the rule + if _, err := core.Exec("pfctl", []string{"-f", f.filename}); err != nil { + return err + } + + // enable pf + f.enable(true) + + } else { + fd, err := os.Open(f.filename) + if err == nil { + defer fd.Close() + + lines := "" + scanner := bufio.NewScanner(fd) + for scanner.Scan() { + line := strings.Trim(scanner.Text(), "\r\n\t ") + if line != rule { + lines += line + "\n" + } + } + + if strings.Trim(lines, "\r\n\t ") == "" { + os.Remove(f.filename) + f.enable(false) + } else { + ioutil.WriteFile(f.filename, []byte(lines), 0600) + } + } + } + + return nil +} + +func (f PfFirewall) Restore() { + err := f.EnableForwarding(f.forwarding) + if err != nil { + fmt.Fprintf(os.Stderr, "%s", err) + } + + f.enable(false) + os.Remove(f.filename) +}