summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--cmd/bitmask-helper/main.go41
-rw-r--r--pkg/helper/args.go103
-rw-r--r--pkg/helper/darwin.go184
-rw-r--r--pkg/helper/helper.go139
-rw-r--r--pkg/helper/linux.go67
-rw-r--r--pkg/helper/windows.go66
7 files changed, 602 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 42a41b7..4b631a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
/bitmask-vpn
cmd/bitmask-vpn/bitmask-vpn
+/bitmask-helper
+cmd/bitmask-helper/bitmask-helper
locales/*/out.gotext.json
.*.swp
*.exe
diff --git a/cmd/bitmask-helper/main.go b/cmd/bitmask-helper/main.go
new file mode 100644
index 0000000..9673763
--- /dev/null
+++ b/cmd/bitmask-helper/main.go
@@ -0,0 +1,41 @@
+// Copyright (C) 2018 LEAP
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package main
+
+import (
+ "log"
+ "path"
+
+ "0xacab.org/leap/bitmask-systray/pkg/config"
+ "0xacab.org/leap/bitmask-systray/pkg/helper"
+)
+
+const (
+ bindAddr = "localhost:7171"
+ logFile = "helper.log"
+)
+
+func main() {
+ logger, err := config.ConfigureLogger(path.Join(helper.LogFolder, logFile))
+ if err != nil {
+ log.Println("Can't configure logger: ", err)
+ } else {
+ defer logger.Close()
+ }
+
+ helper.ServeHTTP(bindAddr)
+
+}
diff --git a/pkg/helper/args.go b/pkg/helper/args.go
new file mode 100644
index 0000000..d6b3bb4
--- /dev/null
+++ b/pkg/helper/args.go
@@ -0,0 +1,103 @@
+package helper
+
+import (
+ "log"
+ "net"
+ "os"
+ "regexp"
+ "strconv"
+)
+
+const (
+ // TODO: this is the nameserver for tcp, but for udp is 10.42.0.1
+ // the nameserver pick up should be dependent on the proto being used
+ nameserver = "10.41.0.1"
+)
+
+var (
+ fixedArgs = []string{
+ "--nobind",
+ "--client",
+ "--dev", "tun",
+ "--tls-client",
+ "--remote-cert-tls", "server",
+ "--dhcp-option", "DNS", nameserver,
+ "--log", LogFolder + "openvpn.log",
+ }
+
+ allowendArgs = map[string][]string{
+ "--remote": []string{"IP", "NUMBER", "PROTO"},
+ "--tls-cipher": []string{"CIPHER"},
+ "--cipher": []string{"CIPHER"},
+ "--auth": []string{"CIPHER"},
+ "--management-client": []string{},
+ "--management": []string{"IP", "NUMBER"},
+ "--cert": []string{"FILE"},
+ "--key": []string{"FILE"},
+ "--ca": []string{"FILE"},
+ "--fragment": []string{"NUMBER"},
+ "--keepalive": []string{"NUMBER", "NUMBER"},
+ "--verb": []string{"NUMBER"},
+ "--tun-ipv6": []string{},
+ }
+
+ cipher = regexp.MustCompile("^[A-Z0-9-]+$")
+ formats = map[string]func(s string) bool{
+ "NUMBER": isNumber,
+ "PROTO": isProto,
+ "IP": isIP,
+ "CIPHER": cipher.MatchString,
+ "FILE": isFile,
+ }
+)
+
+func parseOpenvpnArgs(args []string) []string {
+ newArgs := fixedArgs
+ newArgs = append(newArgs, platformOpenvpnFlags...)
+ for i := 0; i < len(args); i++ {
+ params, ok := allowendArgs[args[i]]
+ if !ok {
+ log.Printf("Invalid openvpn arg: %s", args[i])
+ continue
+ }
+ for j, arg := range args[i+1 : i+len(params)+1] {
+ if !formats[params[j]](arg) {
+ ok = false
+ break
+ }
+ }
+ if ok {
+ newArgs = append(newArgs, args[i:i+len(params)+1]...)
+ i = i + len(params)
+ } else {
+ log.Printf("Invalid openvpn arg params: %v", args[i:i+len(params)+1])
+ }
+ }
+ return newArgs
+}
+
+func isNumber(s string) bool {
+ _, err := strconv.Atoi(s)
+ return err == nil
+}
+
+func isProto(s string) bool {
+ for _, proto := range []string{"tcp", "udp", "tcp4", "udp4", "tcp6", "udp6"} {
+ if s == proto {
+ return true
+ }
+ }
+ return false
+}
+
+func isIP(s string) bool {
+ return net.ParseIP(s) != nil
+}
+
+func isFile(s string) bool {
+ info, err := os.Stat(s)
+ if err != nil {
+ return false
+ }
+ return !info.IsDir()
+}
diff --git a/pkg/helper/darwin.go b/pkg/helper/darwin.go
new file mode 100644
index 0000000..7261de8
--- /dev/null
+++ b/pkg/helper/darwin.go
@@ -0,0 +1,184 @@
+// +build darwin
+// Copyright (C) 2018 LEAP
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+/*
+
+This module holds some specific constants for osx, and it also contains the implementation of the pf firewall.
+
+To inspect the rules in the firewall manually, use the bitmask anchor:
+
+ sudo pfctl -s rules -a com.apple/250.BitmaskFirewall
+
+*/
+
+package helper
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "path"
+ "strings"
+
+ "github.com/sevlyar/go-daemon"
+)
+
+const (
+ appPath = "/Applications/RiseupVPN.app/"
+ helperPath = appPath + "Contents/helper/"
+ LogFolder = helperPath
+ openvpnPath = appPath + "Contents/Resources/openvpn.leap"
+
+ rulefilePath = helperPath + "bitmask.pf.conf"
+ bitmask_anchor = "com.apple/250.BitmaskFirewall"
+ gateways_table = "bitmask_gateways"
+
+ pfctl = "/sbin/pfctl"
+)
+
+var (
+ platformOpenvpnFlags = []string{
+ "--script-security", "2",
+ "--up", helperPath + "client.up.sh",
+ "--down", helperPath + "client.down.sh",
+ }
+)
+
+func daemonize() {
+ cntxt := &daemon.Context{
+ PidFileName: "pid",
+ PidFilePerm: 0644,
+ LogFileName: "bitmask-helper.log",
+ LogFilePerm: 0640,
+ WorkDir: "./",
+ Umask: 027,
+ Args: []string{"[bitmask-helper]"},
+ }
+
+ d, err := cntxt.Reborn()
+ if err != nil {
+ log.Fatal("Unable to run: ", err)
+ }
+ if d != nil {
+ return
+ }
+ defer cntxt.Release()
+ log.Print("bitmask-helper daemon started")
+}
+
+func getOpenvpnPath() string {
+ return openvpnPath
+}
+
+func kill(cmd *exec.Cmd) error {
+ return cmd.Process.Signal(os.Interrupt)
+}
+
+func firewallStart(gateways []string) error {
+ enablePf()
+ err := resetGatewaysTable(gateways)
+ if err != nil {
+ return err
+ }
+
+ return loadBitmaskAnchor()
+}
+
+func firewallStop() error {
+ return exec.Command(pfctl, "-a", bitmask_anchor, "-F", "all").Run()
+}
+
+func firewallIsUp() bool {
+ out, err := exec.Command(pfctl, "-a", bitmask_anchor, "-sr").Output()
+ if err != nil {
+ log.Printf("An error ocurred getting the status of the firewall: %v", err)
+ return false
+ }
+ return bytes.Contains(out, []byte("block out proto udp to any port 53"))
+}
+
+func enablePf() {
+ cmd := exec.Command(pfctl, "-e")
+ cmd.Run()
+}
+
+func resetGatewaysTable(gateways []string) error {
+ log.Println("Resetting gateways")
+ cmd := exec.Command(pfctl, "-a", bitmask_anchor, "-t", gateways_table, "-T", "delete")
+ err := cmd.Run()
+ if err != nil {
+ log.Printf("Can't delete table: %v", err)
+ }
+
+ for _, gateway := range gateways {
+ log.Println("Adding Gateway:", gateway)
+ cmd = exec.Command(pfctl, "-a", bitmask_anchor, "-t", gateways_table, "-T", "add", gateway)
+ err = cmd.Run()
+ if err != nil {
+ log.Printf("Error adding gateway to table: %v", err)
+ }
+ }
+
+ cmd = exec.Command(pfctl, "-a", bitmask_anchor, "-t", gateways_table, "-T", "add", nameserver)
+ return cmd.Run()
+
+}
+
+func getDefaultDevice() string {
+ out, err := exec.Command("/bin/sh", "-c", "/sbin/route -n get -net default | /usr/bin/grep interface | /usr/bin/awk '{print $2}'").Output()
+ if err != nil {
+ log.Printf("Error getting default device")
+ }
+ return strings.TrimSpace(bytesToString(out))
+}
+
+func loadBitmaskAnchor() error {
+ dev := getDefaultDevice()
+ rulePath, err := getRulefilePath()
+ if err != nil {
+ return err
+ }
+ cmdline := fmt.Sprintf("%s -D default_device=%s -a %s -f %s", pfctl, dev, bitmask_anchor, rulePath)
+
+ log.Println("Loading Bitmask Anchor:", cmdline)
+
+ _, err = exec.Command("/bin/sh", "-c", cmdline).Output()
+ return err
+}
+
+func getRulefilePath() (string, error) {
+ if _, err := os.Stat(rulefilePath); !os.IsNotExist(err) {
+ return rulefilePath, nil
+ }
+
+ gopath := os.Getenv("GOPATH")
+ if gopath == "" {
+ gopath = path.Join(os.Getenv("HOME"), "go")
+ }
+ rulefile := path.Join(gopath, "0xacab.org", "leap", "riseup_vpn", "osx", "bitmask.pf.conf")
+
+ if _, err := os.Stat(rulefile); !os.IsNotExist(err) {
+ return rulefile, nil
+ }
+ return "", errors.New("Can't find rule file for the firewall")
+}
+
+func bytesToString(data []byte) string {
+ return string(data[:])
+}
diff --git a/pkg/helper/helper.go b/pkg/helper/helper.go
new file mode 100644
index 0000000..2e7ffd1
--- /dev/null
+++ b/pkg/helper/helper.go
@@ -0,0 +1,139 @@
+// Copyright (C) 2018 LEAP
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package helper
+
+import (
+ "encoding/json"
+ "log"
+ "net/http"
+ "os/exec"
+)
+
+type openvpnT struct {
+ cmd *exec.Cmd
+}
+
+func ServeHTTP(bindAddr string) {
+ daemonize()
+ openvpn := openvpnT{nil}
+ http.HandleFunc("/openvpn/start", openvpn.start)
+ http.HandleFunc("/openvpn/stop", openvpn.stop)
+ http.HandleFunc("/firewall/start", firewallStartHandler)
+ http.HandleFunc("/firewall/stop", firewallStopHandler)
+ http.HandleFunc("/firewall/isup", firewallIsUpHandler)
+
+ log.Fatal(http.ListenAndServe(bindAddr, nil))
+}
+
+func (openvpn *openvpnT) start(w http.ResponseWriter, r *http.Request) {
+ args, err := getArgs(r)
+ if err != nil {
+ log.Printf("An error has occurred processing flags: %v", err)
+ w.Write([]byte(err.Error()))
+ return
+ }
+
+ args = parseOpenvpnArgs(args)
+ log.Printf("start openvpn: %v", args)
+ err = openvpn.run(args)
+ if err != nil {
+ log.Printf("Error starting openvpn: %v", err)
+ w.Write([]byte(err.Error()))
+ }
+}
+
+func (openvpn *openvpnT) run(args []string) error {
+ if openvpn.cmd != nil {
+ log.Printf("openvpn was running, stop it first")
+ err := openvpn.kill()
+ if err != nil {
+ return err
+ }
+ }
+
+ // TODO: if it dies we should restart it
+ openvpn.cmd = exec.Command(getOpenvpnPath(), args...)
+ return openvpn.cmd.Start()
+}
+
+func (openvpn *openvpnT) stop(w http.ResponseWriter, r *http.Request) {
+ log.Println("stop openvpn")
+ if openvpn.cmd == nil || openvpn.cmd.ProcessState != nil {
+ openvpn.cmd = nil
+ return
+ }
+
+ err := openvpn.kill()
+ if err != nil {
+ log.Printf("Error stoping openvpn: %v", err)
+ w.Write([]byte(err.Error()))
+ }
+}
+
+func (openvpn *openvpnT) kill() error {
+ err := kill(openvpn.cmd)
+ if err == nil {
+ openvpn.cmd.Wait()
+ } else {
+ log.Printf("Error killing the process: %v", err)
+ }
+
+ openvpn.cmd = nil
+ return nil
+}
+
+func firewallStartHandler(w http.ResponseWriter, r *http.Request) {
+ gateways, err := getArgs(r)
+ if err != nil {
+ log.Printf("An error has occurred processing gateways: %v", err)
+ w.Write([]byte(err.Error()))
+ return
+ }
+
+ err = firewallStart(gateways)
+ if err != nil {
+ log.Printf("Error starting firewall: %v", err)
+ w.Write([]byte(err.Error()))
+ return
+ }
+ log.Println("Start firewall: firewall started")
+}
+
+func firewallStopHandler(w http.ResponseWriter, r *http.Request) {
+ err := firewallStop()
+ if err != nil {
+ log.Printf("Error stoping firewall: %v", err)
+ w.Write([]byte(err.Error()))
+ }
+ log.Println("Stop firewall: firewall stopped")
+}
+
+func firewallIsUpHandler(w http.ResponseWriter, r *http.Request) {
+ if firewallIsUp() {
+ w.Write([]byte("true"))
+ w.WriteHeader(http.StatusOK)
+ } else {
+ w.Write([]byte("false"))
+ w.WriteHeader(http.StatusNoContent)
+ }
+}
+
+func getArgs(r *http.Request) ([]string, error) {
+ args := []string{}
+ decoder := json.NewDecoder(r.Body)
+ err := decoder.Decode(&args)
+ return args, err
+}
diff --git a/pkg/helper/linux.go b/pkg/helper/linux.go
new file mode 100644
index 0000000..88c3e10
--- /dev/null
+++ b/pkg/helper/linux.go
@@ -0,0 +1,67 @@
+// +build linux
+// Copyright (C) 2018 LEAP
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package helper
+
+import (
+ "log"
+ "os"
+ "os/exec"
+)
+
+const (
+ openvpnUser = "nobody"
+ openvpnGroup = "nogroup"
+ LogFolder = "/var/log/"
+ systemOpenvpnPath = "/usr/sbin/openvpn"
+ snapOpenvpnPath = "/snap/bin/riseup-vpn.openvpn"
+)
+
+var (
+ platformOpenvpnFlags = []string{
+ "--script-security", "1",
+ "--user", openvpnUser,
+ "--group", openvpnGroup,
+ }
+)
+
+func daemonize() {}
+
+func getOpenvpnPath() string {
+ if os.Getenv("SNAP") != "" {
+ return snapOpenvpnPath
+ }
+ return systemOpenvpnPath
+}
+
+func kill(cmd *exec.Cmd) error {
+ return cmd.Process.Signal(os.Interrupt)
+}
+
+func firewallStart(gateways []string) error {
+ log.Println("Start firewall: do nothing, not implemented")
+ return nil
+}
+
+func firewallStop() error {
+ log.Println("Stop firewall: do nothing, not implemented")
+ return nil
+}
+
+func firewallIsUp() bool {
+ log.Println("IsUp firewall: do nothing, not implemented")
+ return false
+}
diff --git a/pkg/helper/windows.go b/pkg/helper/windows.go
new file mode 100644
index 0000000..a19c92b
--- /dev/null
+++ b/pkg/helper/windows.go
@@ -0,0 +1,66 @@
+// +build windows
+// Copyright (C) 2018 LEAP
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package helper
+
+import (
+ "log"
+ "os"
+ "os/exec"
+)
+
+const (
+ appPath = `C:\Program Files\RiseupVPN\`
+ LogFolder = appPath
+ openvpnPath = appPath + `openvpn.exe`
+ chocoOpenvpnPath = `C:\Program Files\OpenVPN\bin\openvpn.exe`
+)
+
+var (
+ platformOpenvpnFlags = []string{
+ "--script-security", "1",
+ }
+)
+
+func daemonize() {}
+
+func getOpenvpnPath() string {
+ if _, err := os.Stat(openvpnPath); !os.IsNotExist(err) {
+ return openvpnPath
+ } else if _, err := os.Stat(chocoOpenvpnPath); !os.IsNotExist(err) {
+ return chocoOpenvpnPath
+ }
+ return "openvpn.exe"
+}
+
+func kill(cmd *exec.Cmd) error {
+ return cmd.Process.Kill()
+}
+
+func firewallStart(gateways []string) error {
+ log.Println("Start firewall: do nothing, not implemented")
+ return nil
+}
+
+func firewallStop() error {
+ log.Println("Stop firewall: do nothing, not implemented")
+ return nil
+}
+
+func firewallIsUp() bool {
+ log.Println("IsUp firewall: do nothing, not implemented")
+ return false
+}