Skip to content
Snippets Groups Projects
Unverified Commit 75b26ec7 authored by Kali Kaneko's avatar Kali Kaneko
Browse files

[feat] initial implementation of windows service

parent af04003d
No related branches found
No related tags found
No related merge requests found
......@@ -28,7 +28,7 @@ require (
github.com/stretchr/testify v1.4.0 // indirect
golang.org/x/crypto v0.0.0-20191105034135-c7e5f84aec59 // indirect
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
golang.org/x/sys v0.0.0-20191105142833-ac3223d80179 // indirect
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20200427153019-a90b7300be7c // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
......
......@@ -131,7 +131,10 @@ golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191104094858-e8c54fb511f6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191105142833-ac3223d80179 h1:IqVhUQp5B9ARnZUcfqXy6zP+A+YuPpP7IFo8gFeCOzU=
golang.org/x/sys v0.0.0-20191105142833-ac3223d80179/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
......
......@@ -60,6 +60,10 @@ var (
}
)
func parseCliArgs() {
// OSX helper does not respond to arguments
}
func daemonize() {
cntxt := &daemon.Context{
PidFileName: "pid",
......@@ -82,6 +86,10 @@ func daemonize() {
log.Print("bitmask-helper daemon started")
}
func doHandleCommands(bindAddr string) {
runCommandServer(bindAddr)
}
func getOpenvpnPath() string {
return openvpnPath
}
......
......@@ -26,8 +26,7 @@ type openvpnT struct {
cmd *exec.Cmd
}
func ServeHTTP(bindAddr string) {
daemonize()
func runCommandServer(bindAddr string) {
openvpn := openvpnT{nil}
http.HandleFunc("/openvpn/start", openvpn.start)
http.HandleFunc("/openvpn/stop", openvpn.stop)
......@@ -38,6 +37,12 @@ func ServeHTTP(bindAddr string) {
log.Fatal(http.ListenAndServe(bindAddr, nil))
}
func ServeHTTP(bindAddr string) {
parseCliArgs()
daemonize()
doHandleCommands(bindAddr)
}
func (openvpn *openvpnT) start(w http.ResponseWriter, r *http.Request) {
args, err := getArgs(r)
if err != nil {
......
......@@ -40,8 +40,16 @@ var (
}
)
func parseCliArgs() {
// linux helper does not reply to args
}
func daemonize() {}
func doHandleCommands(bindAddr string) {
runCommandServer(bindAddr)
}
func getOpenvpnPath() string {
if os.Getenv("SNAP") != "" {
return snapOpenvpnPath
......
......@@ -17,14 +17,19 @@
package helper
import (
"fmt"
"log"
"os"
"os/exec"
"strings"
"0xacab.org/leap/bitmask-vpn/pkg/config"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
)
const (
svcName = config.BinaryName + `-helper`
appPath = `C:\Program Files\` + config.ApplicationName + `\`
LogFolder = appPath
openvpnPath = appPath + `openvpn.exe`
......@@ -36,10 +41,66 @@ var (
"--script-security", "1",
"--block-outside-dns",
}
httpBindAddr string
)
func parseCliArgs() {
isIntSess, err := svc.IsAnInteractiveSession()
if err != nil {
log.Fatalf("Failed to determine if we are running in an interactive session: %v", err)
}
if !isIntSess {
runService(svcName, false)
return
}
admin := isAdmin()
fmt.Printf("Running as admin: %v\n", admin)
if !admin {
log.Fatal("Needs to be run as administrator")
}
if len(os.Args) < 2 {
usage("ERROR: no command specified")
}
cmd := strings.ToLower(os.Args[1])
switch cmd {
case "debug":
runService(svcName, true)
return
case "install":
// TODO get binary name
err = installService(svcName, "bitmask-helper service")
case "remove":
err = removeService(svcName)
case "start":
err = startService(svcName)
case "stop":
err = controlService(svcName, svc.Stop, svc.Stopped)
default:
usage(fmt.Sprintf("ERROR: Invalid command %s", cmd))
}
if err != nil {
log.Fatalf("Failed to %s %s: %v", cmd, svcName, err)
}
return
}
func usage(errmsg string) {
fmt.Fprintf(os.Stderr,
"%s\n\n"+
"usage: %s <command>\n"+
" where <command> is one of\n"+
" install, remove, debug, start, stop\n",
errmsg, os.Args[0])
os.Exit(2)
}
func daemonize() {}
// http server is called from within Execute in windows
func doHandleCommands(bindAddr string) {
httpBindAddr = bindAddr
}
func getOpenvpnPath() string {
if _, err := os.Stat(openvpnPath); !os.IsNotExist(err) {
return openvpnPath
......@@ -67,3 +128,41 @@ func firewallIsUp() bool {
log.Println("IsUp firewall: do nothing, not implemented")
return false
}
func isAdmin() bool {
var sid *windows.SID
// Although this looks scary, it is directly copied from the
// official windows documentation. The Go API for this is a
// direct wrap around the official C++ API.
// See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-checktokenmembership
err := windows.AllocateAndInitializeSid(
&windows.SECURITY_NT_AUTHORITY,
2,
windows.SECURITY_BUILTIN_DOMAIN_RID,
windows.DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&sid)
if err != nil {
log.Fatalf("SID Error: %s", err)
return false
}
// This appears to cast a null pointer so I'm not sure why this
// works, but this guy says it does and it Works for Me™:
// https://github.com/golang/go/issues/28804#issuecomment-438838144
token := windows.Token(0)
member, err := token.IsMember(sid)
//fmt.Println("Admin?", member)
if err != nil {
log.Fatalf("Token Membership Error: %s", err)
return false
}
return member
// Also note that an admin is _not_ necessarily considered
// elevated.
// For elevation see https://github.com/mozey/run-as-admin
//fmt.Println("Elevated?", token.IsElevated())
}
// +build windows
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package helper
import (
"fmt"
"os"
"path/filepath"
"golang.org/x/sys/windows/svc/eventlog"
"golang.org/x/sys/windows/svc/mgr"
)
func exePath() (string, error) {
prog := os.Args[0]
p, err := filepath.Abs(prog)
if err != nil {
return "", err
}
fi, err := os.Stat(p)
if err == nil {
if !fi.Mode().IsDir() {
return p, nil
}
err = fmt.Errorf("%s is directory", p)
}
if filepath.Ext(p) == "" {
p += ".exe"
fi, err := os.Stat(p)
if err == nil {
if !fi.Mode().IsDir() {
return p, nil
}
err = fmt.Errorf("%s is directory", p)
}
}
return "", err
}
func installService(name, desc string) error {
exepath, err := exePath()
if err != nil {
return err
}
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(name)
if err == nil {
s.Close()
return fmt.Errorf("service %s already exists", name)
}
s, err = m.CreateService(name, exepath, mgr.Config{DisplayName: desc}, "is", "auto-started")
if err != nil {
return err
}
defer s.Close()
err = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info)
if err != nil {
s.Delete()
return fmt.Errorf("SetupEventLogSource() failed: %s", err)
}
return nil
}
func removeService(name string) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(name)
if err != nil {
return fmt.Errorf("service %s is not installed", name)
}
defer s.Close()
err = s.Delete()
if err != nil {
return err
}
err = eventlog.Remove(name)
if err != nil {
return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
}
return nil
}
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package helper
import (
"fmt"
"time"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
)
func startService(name string) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(name)
if err != nil {
return fmt.Errorf("could not access service: %v", err)
}
defer s.Close()
err = s.Start("is", "manual-started")
if err != nil {
return fmt.Errorf("could not start service: %v", err)
}
return nil
}
func controlService(name string, c svc.Cmd, to svc.State) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(name)
if err != nil {
return fmt.Errorf("could not access service: %v", err)
}
defer s.Close()
status, err := s.Control(c)
if err != nil {
return fmt.Errorf("could not send control=%d: %v", c, err)
}
timeout := time.Now().Add(10 * time.Second)
for status.State != to {
if timeout.Before(time.Now()) {
return fmt.Errorf("timeout waiting for service to go to state=%d", to)
}
time.Sleep(300 * time.Millisecond)
status, err = s.Query()
if err != nil {
return fmt.Errorf("could not retrieve service status: %v", err)
}
}
return nil
}
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package helper
import (
"fmt"
//"strings"
//"time"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/debug"
"golang.org/x/sys/windows/svc/eventlog"
)
var elog debug.Log
type myservice struct{}
func (m *myservice) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
changes <- svc.Status{State: svc.StartPending}
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
// TODO use httpBindAddr
go runCommandServer("localhost:7171")
loop:
for {
select {
case c := <-r:
switch c.Cmd {
// TODO start??
case svc.Interrogate:
changes <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
elog.Info(1, "shutting down service")
break loop
default:
elog.Error(1, fmt.Sprintf("unexpected control request #%d", c))
}
}
}
changes <- svc.Status{State: svc.StopPending}
return
}
func runService(name string, isDebug bool) {
var err error
if isDebug {
elog = debug.New(name)
} else {
elog, err = eventlog.Open(name)
if err != nil {
return
}
}
defer elog.Close()
elog.Info(1, fmt.Sprintf("starting %s service", name))
run := svc.Run
if isDebug {
run = debug.Run
}
err = run(name, &myservice{})
if err != nil {
elog.Error(1, fmt.Sprintf("%s service failed: %v", name, err))
return
}
elog.Info(1, fmt.Sprintf("%s service stopped", name))
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment