1
0
mirror of https://git.zx2c4.com/wireguard-windows synced 2024-11-10 16:59:18 +00:00
wireguard-windows/ui/editdialog.go
Jason A. Donenfeld 6ed37f30f5 global: bump date
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2022-01-06 17:28:13 +01:00

346 lines
10 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
import (
"net/netip"
"strings"
"github.com/lxn/walk"
"github.com/lxn/win"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/conf"
"golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
"golang.zx2c4.com/wireguard/windows/ui/syntax"
)
type EditDialog struct {
*walk.Dialog
nameEdit *walk.LineEdit
pubkeyEdit *walk.LineEdit
syntaxEdit *syntax.SyntaxEdit
blockUntunneledTrafficCB *walk.CheckBox
saveButton *walk.PushButton
config conf.Config
lastPrivateKey string
blockUntunneledTraficCheckGuard bool
}
func runEditDialog(owner walk.Form, tunnel *manager.Tunnel) *conf.Config {
dlg, err := newEditDialog(owner, tunnel)
if showError(err, owner) {
return nil
}
if dlg.Run() == walk.DlgCmdOK {
return &dlg.config
}
return nil
}
func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error) {
var err error
var disposables walk.Disposables
defer disposables.Treat()
dlg := new(EditDialog)
var title string
if tunnel == nil {
title = l18n.Sprintf("Create new tunnel")
} else {
title = l18n.Sprintf("Edit tunnel")
}
if tunnel == nil {
// Creating a new tunnel, create a new private key and use the default template
pk, _ := conf.NewPrivateKey()
dlg.config = conf.Config{Interface: conf.Interface{PrivateKey: *pk}}
} else {
dlg.config, _ = tunnel.StoredConfig()
}
layout := walk.NewGridLayout()
layout.SetSpacing(6)
layout.SetMargins(walk.Margins{10, 10, 10, 10})
layout.SetColumnStretchFactor(1, 3)
if dlg.Dialog, err = walk.NewDialog(owner); err != nil {
return nil, err
}
disposables.Add(dlg)
dlg.SetIcon(owner.Icon())
dlg.SetTitle(title)
dlg.SetLayout(layout)
dlg.SetMinMaxSize(walk.Size{500, 400}, walk.Size{0, 0})
if icon, err := loadSystemIcon("imageres", -114, 32); err == nil {
dlg.SetIcon(icon)
}
nameLabel, err := walk.NewTextLabel(dlg)
if err != nil {
return nil, err
}
layout.SetRange(nameLabel, walk.Rectangle{0, 0, 1, 1})
nameLabel.SetTextAlignment(walk.AlignHFarVCenter)
nameLabel.SetText(l18n.Sprintf("&Name:"))
if dlg.nameEdit, err = walk.NewLineEdit(dlg); err != nil {
return nil, err
}
layout.SetRange(dlg.nameEdit, walk.Rectangle{1, 0, 1, 1})
dlg.nameEdit.SetText(dlg.config.Name)
pubkeyLabel, err := walk.NewTextLabel(dlg)
if err != nil {
return nil, err
}
layout.SetRange(pubkeyLabel, walk.Rectangle{0, 1, 1, 1})
pubkeyLabel.SetTextAlignment(walk.AlignHFarVCenter)
pubkeyLabel.SetText(l18n.Sprintf("&Public key:"))
if dlg.pubkeyEdit, err = walk.NewLineEdit(dlg); err != nil {
return nil, err
}
layout.SetRange(dlg.pubkeyEdit, walk.Rectangle{1, 1, 1, 1})
dlg.pubkeyEdit.SetReadOnly(true)
dlg.pubkeyEdit.SetText(l18n.Sprintf("(unknown)"))
dlg.pubkeyEdit.Accessibility().SetRole(walk.AccRoleStatictext)
if dlg.syntaxEdit, err = syntax.NewSyntaxEdit(dlg); err != nil {
return nil, err
}
layout.SetRange(dlg.syntaxEdit, walk.Rectangle{0, 2, 2, 1})
buttonsContainer, err := walk.NewComposite(dlg)
if err != nil {
return nil, err
}
layout.SetRange(buttonsContainer, walk.Rectangle{0, 3, 2, 1})
buttonsContainer.SetLayout(walk.NewHBoxLayout())
buttonsContainer.Layout().SetMargins(walk.Margins{})
if dlg.blockUntunneledTrafficCB, err = walk.NewCheckBox(buttonsContainer); err != nil {
return nil, err
}
dlg.blockUntunneledTrafficCB.SetText(l18n.Sprintf("&Block untunneled traffic (kill-switch)"))
dlg.blockUntunneledTrafficCB.SetToolTipText(l18n.Sprintf("When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, and the interface does not have table off, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP."))
dlg.blockUntunneledTrafficCB.SetVisible(false)
dlg.blockUntunneledTrafficCB.CheckedChanged().Attach(dlg.onBlockUntunneledTrafficCBCheckedChanged)
walk.NewHSpacer(buttonsContainer)
if dlg.saveButton, err = walk.NewPushButton(buttonsContainer); err != nil {
return nil, err
}
dlg.saveButton.SetText(l18n.Sprintf("&Save"))
dlg.saveButton.Clicked().Attach(dlg.onSaveButtonClicked)
cancelButton, err := walk.NewPushButton(buttonsContainer)
if err != nil {
return nil, err
}
cancelButton.SetText(l18n.Sprintf("Cancel"))
cancelButton.Clicked().Attach(dlg.Cancel)
dlg.SetCancelButton(cancelButton)
dlg.SetDefaultButton(dlg.saveButton)
dlg.syntaxEdit.PrivateKeyChanged().Attach(dlg.onSyntaxEditPrivateKeyChanged)
dlg.syntaxEdit.BlockUntunneledTrafficStateChanged().Attach(dlg.onBlockUntunneledTrafficStateChanged)
dlg.syntaxEdit.SetText(dlg.config.ToWgQuick())
// Insert a dummy label immediately preceding syntaxEdit to have screen readers read it.
// Otherwise they fallback to "RichEdit Control".
syntaxEditWnd := dlg.syntaxEdit.Handle()
parentWnd := win.GetParent(syntaxEditWnd)
labelWnd := win.CreateWindowEx(0,
windows.StringToUTF16Ptr("STATIC"), windows.StringToUTF16Ptr(l18n.Sprintf("&Configuration:")),
win.WS_CHILD|win.WS_GROUP|win.SS_LEFT, 0, 0, 0, 0,
parentWnd, win.HMENU(^uintptr(0)), win.HINSTANCE(win.GetWindowLongPtr(parentWnd, win.GWLP_HINSTANCE)), nil)
prevWnd := win.GetWindow(syntaxEditWnd, win.GW_HWNDPREV)
nextWnd := win.GetWindow(syntaxEditWnd, win.GW_HWNDNEXT)
win.SetWindowPos(labelWnd, prevWnd, 0, 0, 0, 0, win.SWP_NOSIZE|win.SWP_NOMOVE)
win.SetWindowPos(syntaxEditWnd, labelWnd, 0, 0, 0, 0, win.SWP_NOSIZE|win.SWP_NOMOVE)
win.SetWindowPos(nextWnd, syntaxEditWnd, 0, 0, 0, 0, win.SWP_NOSIZE|win.SWP_NOMOVE)
if tunnel != nil {
dlg.Starting().Attach(func() {
dlg.syntaxEdit.SetFocus()
})
}
disposables.Spare()
return dlg, nil
}
func (dlg *EditDialog) onBlockUntunneledTrafficCBCheckedChanged() {
if dlg.blockUntunneledTraficCheckGuard {
return
}
var (
v400 = netip.PrefixFrom(netip.IPv4Unspecified(), 0)
v600000 = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
v401 = netip.PrefixFrom(netip.AddrFrom4([4]byte{}), 1)
v600001 = netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 1)
v41281 = netip.PrefixFrom(netip.AddrFrom4([4]byte{0x80}), 1)
v680001 = netip.PrefixFrom(netip.AddrFrom16([16]byte{0x80}), 1)
)
block := dlg.blockUntunneledTrafficCB.Checked()
cfg, err := conf.FromWgQuick(dlg.syntaxEdit.Text(), "temporary")
var newAllowedIPs []netip.Prefix
if err != nil {
goto err
}
if len(cfg.Peers) != 1 {
goto err
}
newAllowedIPs = make([]netip.Prefix, 0, len(cfg.Peers[0].AllowedIPs))
if block {
var (
foundV401 bool
foundV41281 bool
foundV600001 bool
foundV680001 bool
)
for _, allowedip := range cfg.Peers[0].AllowedIPs {
if allowedip == v600001 {
foundV600001 = true
} else if allowedip == v680001 {
foundV680001 = true
} else if allowedip == v401 {
foundV401 = true
} else if allowedip == v41281 {
foundV41281 = true
} else {
newAllowedIPs = append(newAllowedIPs, allowedip)
}
}
if !((foundV401 && foundV41281) || (foundV600001 && foundV680001)) {
goto err
}
if foundV401 && foundV41281 {
newAllowedIPs = append(newAllowedIPs, v400)
} else if foundV401 {
newAllowedIPs = append(newAllowedIPs, v401)
} else if foundV41281 {
newAllowedIPs = append(newAllowedIPs, v41281)
}
if foundV600001 && foundV680001 {
newAllowedIPs = append(newAllowedIPs, v600000)
} else if foundV600001 {
newAllowedIPs = append(newAllowedIPs, v600001)
} else if foundV680001 {
newAllowedIPs = append(newAllowedIPs, v680001)
}
cfg.Peers[0].AllowedIPs = newAllowedIPs
} else {
var (
foundV400 bool
foundV600000 bool
)
for _, allowedip := range cfg.Peers[0].AllowedIPs {
if allowedip == v600000 {
foundV600000 = true
} else if allowedip == v400 {
foundV400 = true
} else {
newAllowedIPs = append(newAllowedIPs, allowedip)
}
}
if !(foundV400 || foundV600000) {
goto err
}
if foundV400 {
newAllowedIPs = append(newAllowedIPs, v401)
newAllowedIPs = append(newAllowedIPs, v41281)
}
if foundV600000 {
newAllowedIPs = append(newAllowedIPs, v600001)
newAllowedIPs = append(newAllowedIPs, v680001)
}
cfg.Peers[0].AllowedIPs = newAllowedIPs
}
dlg.syntaxEdit.SetText(cfg.ToWgQuick())
return
err:
text := dlg.syntaxEdit.Text()
dlg.syntaxEdit.SetText("")
dlg.syntaxEdit.SetText(text)
}
func (dlg *EditDialog) onBlockUntunneledTrafficStateChanged(state int) {
dlg.blockUntunneledTraficCheckGuard = true
switch syntax.BlockState(state) {
case syntax.InevaluableBlockingUntunneledTraffic:
dlg.blockUntunneledTrafficCB.SetVisible(false)
case syntax.BlockingUntunneledTraffic:
dlg.blockUntunneledTrafficCB.SetVisible(true)
dlg.blockUntunneledTrafficCB.SetChecked(true)
case syntax.NotBlockingUntunneledTraffic:
dlg.blockUntunneledTrafficCB.SetVisible(true)
dlg.blockUntunneledTrafficCB.SetChecked(false)
}
dlg.blockUntunneledTraficCheckGuard = false
}
func (dlg *EditDialog) onSyntaxEditPrivateKeyChanged(privateKey string) {
if privateKey == dlg.lastPrivateKey {
return
}
dlg.lastPrivateKey = privateKey
key, _ := conf.NewPrivateKeyFromString(privateKey)
if key != nil {
dlg.pubkeyEdit.SetText(key.Public().String())
} else {
dlg.pubkeyEdit.SetText(l18n.Sprintf("(unknown)"))
}
}
func (dlg *EditDialog) onSaveButtonClicked() {
newName := dlg.nameEdit.Text()
if newName == "" {
showWarningCustom(dlg, l18n.Sprintf("Invalid name"), l18n.Sprintf("A name is required."))
return
}
if !conf.TunnelNameIsValid(newName) {
showWarningCustom(dlg, l18n.Sprintf("Invalid name"), l18n.Sprintf("Tunnel name %s is invalid.", newName))
return
}
newNameLower := strings.ToLower(newName)
if newNameLower != strings.ToLower(dlg.config.Name) {
existingTunnelList, err := manager.IPCClientTunnels()
if err != nil {
showWarningCustom(dlg, l18n.Sprintf("Unable to list existing tunnels"), err.Error())
return
}
for _, tunnel := range existingTunnelList {
if strings.ToLower(tunnel.Name) == newNameLower {
showWarningCustom(dlg, l18n.Sprintf("Tunnel already exists"), l18n.Sprintf("Another tunnel already exists with the name %s.", newName))
return
}
}
}
cfg, err := conf.FromWgQuick(dlg.syntaxEdit.Text(), newName)
if err != nil {
showErrorCustom(dlg, l18n.Sprintf("Unable to create new configuration"), err.Error())
return
}
dlg.config = *cfg
dlg.Accept()
}