wireguard-mfa_windows/main.go

142 lines
3.2 KiB
Go
Raw Permalink Normal View History

2024-02-22 12:18:02 +01:00
package main
import (
// External
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
// Standard
"errors"
"flag"
"fmt"
"io/fs"
"net/url"
"os"
"os/exec"
"regexp"
"time"
)
2024-06-12 08:37:48 +02:00
const VERSION = "v3"
2024-02-22 12:18:02 +01:00
const SPOOLDIR = "C:\\Program Files\\Gibon\\wg-spool"
const DOMAIN = "https://vpn.gibonuddevalla.se"
var (
publicKeyPath string
runInteractiveAuth bool
connected bool
printVersion bool
)
func init() {
flag.BoolVar(&printVersion, "version", false, "print version and exit")
flag.BoolVar(&runInteractiveAuth, "interactive", false, "run interactive authentication")
flag.BoolVar(&connected, "connected", false, "postop, is connected")
flag.Parse()
if printVersion {
fmt.Println(VERSION)
os.Exit(0)
}
}
func logError(where, e string) {
fmt.Printf("ERROR: %s\n", e)
fname := fmt.Sprintf("%s\\log.txt", SPOOLDIR)
file, err := os.OpenFile(fname, os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer file.Close()
now := time.Now().String()
file.Write([]byte(now))
file.Write([]byte("\t"))
file.Write([]byte(where))
file.Write([]byte("\t"))
file.Write([]byte(e))
file.Write([]byte("\n"))
}
func main() {
2024-06-04 12:07:32 +02:00
if _, disabledDoesntExist := os.Stat(SPOOLDIR+`\disabled`); disabledDoesntExist == nil {
return
}
2024-02-22 12:18:02 +01:00
publicKeyPath = SPOOLDIR + "\\wireguard-mfa.url"
iface := os.Getenv("WIREGUARD_TUNNEL_NAME")
// Run from PreUp, step 1.
// Starts an interactive process, dropping privileges,
// in order to have permission to open a browser in step 2.
// Doesn't have permission to get public key.
if !runInteractiveAuth && !connected {
ex, _ := os.Executable()
if err := StartProcessAsCurrentUser("", ex+" -interactive", ""); err != nil {
logError("start_interactive", err.Error())
}
os.Exit(0)
}
// Started from PreUp, step 2.
// Doesn't have permission to get public key.
if runInteractiveAuth {
var err error
start := time.Now()
for {
// Abort after 60 seconds to not have an eternal loop.
if time.Since(start).Seconds() > 60 {
os.Exit(1)
}
_, err = os.ReadFile(publicKeyPath)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
time.Sleep(time.Millisecond * 200)
continue
}
if !errors.Is(err, fs.ErrNotExist) {
logError("read_publickey_file", err.Error())
os.Exit(1)
}
}
break
}
cmd := exec.Command("rundll32.exe", "url.dll,OpenURL", publicKeyPath)
cmd.Output()
os.Remove(publicKeyPath)
}
// Run from PostUp, step 3.
// Has only permission to get public key, can't run browser.
if connected {
getPublicKey(iface)
}
}
func getPublicKey(iface string) {
rxpPrivateKey := regexp.MustCompile("PrivateKey\\s*=\\s*(.*)")
cmd := exec.Command("wg", "showconf", iface)
out, _ := cmd.Output()
privateKey := rxpPrivateKey.FindStringSubmatch(string(out))
if len(privateKey) == 2 {
pubkey, err := wgtypes.ParseKey(privateKey[1])
if err != nil {
logError("parse_privatekey", fmt.Sprintf("%s [%s]", err.Error(), privateKey[1]))
return
}
urlData := fmt.Sprintf(
"[InternetShortcut]\r\nURL=%s/?key=%s\r\n",
DOMAIN,
url.QueryEscape(pubkey.PublicKey().String()),
)
os.WriteFile(publicKeyPath, []byte(urlData), 0644)
logError("publickey", pubkey.PublicKey().String())
} else {
logError("get_publickey", string(out))
}
}