diff options
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | cmd/bitmask-helper/main.go | 41 | ||||
| -rw-r--r-- | pkg/helper/args.go | 103 | ||||
| -rw-r--r-- | pkg/helper/darwin.go | 184 | ||||
| -rw-r--r-- | pkg/helper/helper.go | 139 | ||||
| -rw-r--r-- | pkg/helper/linux.go | 67 | ||||
| -rw-r--r-- | pkg/helper/windows.go | 66 | 
7 files changed, 602 insertions, 0 deletions
@@ -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 +}  | 
