package main import ( // External "golang.zx2c4.com/wireguard/wgctrl/wgtypes" // Standard "errors" "flag" "fmt" "io/fs" "net/url" "os" "os/exec" "regexp" "time" ) const VERSION = "v3" 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() { if _, disabledDoesntExist := os.Stat(SPOOLDIR+`\disabled`); disabledDoesntExist == nil { return } 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)) } }