aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/editdialog.go
diff options
context:
space:
mode:
Diffstat (limited to 'ui/editdialog.go')
-rw-r--r--ui/editdialog.go323
1 files changed, 323 insertions, 0 deletions
diff --git a/ui/editdialog.go b/ui/editdialog.go
new file mode 100644
index 00000000..3cd5b606
--- /dev/null
+++ b/ui/editdialog.go
@@ -0,0 +1,323 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+package ui
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/lxn/walk"
+ "golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/service"
+ "golang.zx2c4.com/wireguard/windows/ui/syntax"
+)
+
+const (
+ configKeyDNS = "DNS"
+ configKeyAllowedIPs = "AllowedIPs"
+)
+
+var (
+ ipv4Wildcard = orderedStringSetFromSlice([]string{"0.0.0.0/0"})
+ ipv4PublicNetworks = orderedStringSetFromSlice([]string{
+ "0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3",
+ "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12",
+ "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7",
+ "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16",
+ "192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10",
+ "193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4",
+ })
+)
+
+type allowedIPsState int
+
+const (
+ allowedIPsStateInvalid allowedIPsState = iota
+ allowedIPsStateContainsIPV4Wildcard
+ allowedIPsStateContainsIPV4PublicNetworks
+ allowedIPsStateOther
+)
+
+type EditDialog struct {
+ *walk.Dialog
+ nameEdit *walk.LineEdit
+ pubkeyEdit *walk.LineEdit
+ syntaxEdit *syntax.SyntaxEdit
+ excludePrivateIPsCB *walk.CheckBox
+ saveButton *walk.PushButton
+ tunnel *service.Tunnel
+ config conf.Config
+ allowedIPsState allowedIPsState
+ lastPrivateKey string
+ inCheckedChanged bool
+}
+
+func runTunnelEditDialog(owner walk.Form, tunnel *service.Tunnel) *conf.Config {
+ var (
+ title string
+ name string
+ )
+
+ dlg := &EditDialog{tunnel: tunnel}
+
+ if tunnel == nil {
+ // Creating a new tunnel, create a new private key and use the default template
+ title = "Create new tunnel"
+ pk, _ := conf.NewPrivateKey()
+ dlg.config = conf.Config{Interface: conf.Interface{PrivateKey: *pk}}
+ } else {
+ title = "Edit tunnel"
+ name = tunnel.Name
+ dlg.config, _ = tunnel.StoredConfig()
+ }
+
+ layout := walk.NewGridLayout()
+ layout.SetSpacing(6)
+ layout.SetMargins(walk.Margins{10, 10, 10, 10})
+ layout.SetColumnStretchFactor(1, 3)
+
+ dlg.Dialog, _ = walk.NewDialog(owner)
+ dlg.SetIcon(owner.Icon())
+ dlg.SetTitle(title)
+ dlg.SetLayout(layout)
+ dlg.SetMinMaxSize(walk.Size{500, 400}, walk.Size{0, 0})
+
+ nameLabel, _ := walk.NewTextLabel(dlg)
+ layout.SetRange(nameLabel, walk.Rectangle{0, 0, 1, 1})
+ nameLabel.SetTextAlignment(walk.AlignHFarVCenter)
+ nameLabel.SetText("Name:")
+
+ dlg.nameEdit, _ = walk.NewLineEdit(dlg)
+ layout.SetRange(dlg.nameEdit, walk.Rectangle{1, 0, 1, 1})
+ dlg.nameEdit.SetText(name)
+
+ pubkeyLabel, _ := walk.NewTextLabel(dlg)
+ layout.SetRange(pubkeyLabel, walk.Rectangle{0, 1, 1, 1})
+ pubkeyLabel.SetTextAlignment(walk.AlignHFarVCenter)
+ pubkeyLabel.SetText("Public key:")
+
+ dlg.pubkeyEdit, _ = walk.NewLineEdit(dlg)
+ layout.SetRange(dlg.pubkeyEdit, walk.Rectangle{1, 1, 1, 1})
+ dlg.pubkeyEdit.SetReadOnly(true)
+ dlg.pubkeyEdit.SetText("(unknown)")
+
+ dlg.syntaxEdit, _ = syntax.NewSyntaxEdit(dlg)
+ layout.SetRange(dlg.syntaxEdit, walk.Rectangle{0, 2, 2, 1})
+ dlg.syntaxEdit.PrivateKeyChanged().Attach(dlg.onSyntaxEditPrivateKeyChanged)
+ dlg.syntaxEdit.SetText(dlg.config.ToWgQuick())
+ dlg.syntaxEdit.TextChanged().Attach(dlg.updateExcludePrivateIPsCBVisible)
+
+ buttonsContainer, _ := walk.NewComposite(dlg)
+ layout.SetRange(buttonsContainer, walk.Rectangle{0, 3, 2, 1})
+ buttonsContainer.SetLayout(walk.NewHBoxLayout())
+ buttonsContainer.Layout().SetMargins(walk.Margins{})
+
+ dlg.excludePrivateIPsCB, _ = walk.NewCheckBox(buttonsContainer)
+ dlg.excludePrivateIPsCB.SetText("Exclude private IPs")
+ dlg.excludePrivateIPsCB.CheckedChanged().Attach(dlg.onExcludePrivateIPsCBCheckedChanged)
+ dlg.updateExcludePrivateIPsCBVisible()
+
+ walk.NewHSpacer(buttonsContainer)
+
+ dlg.saveButton, _ = walk.NewPushButton(buttonsContainer)
+ dlg.saveButton.SetText("Save")
+ dlg.saveButton.Clicked().Attach(dlg.onSaveButtonClicked)
+
+ cancelButton, _ := walk.NewPushButton(buttonsContainer)
+ cancelButton.SetText("Cancel")
+ cancelButton.Clicked().Attach(dlg.Cancel)
+
+ dlg.SetCancelButton(cancelButton)
+ dlg.SetDefaultButton(dlg.saveButton)
+
+ dlg.updateAllowedIPsState()
+
+ if dlg.Run() == walk.DlgCmdOK {
+ // Save
+ return &dlg.config
+ }
+
+ return nil
+}
+
+func (dlg *EditDialog) updateAllowedIPsState() {
+ var newState allowedIPsState
+ if len(dlg.config.Peers) == 1 {
+ if allowedIPs := dlg.allowedIPsSet(); allowedIPs.IsSupersetOf(ipv4Wildcard) {
+ newState = allowedIPsStateContainsIPV4Wildcard
+ } else if allowedIPs.IsSupersetOf(ipv4PublicNetworks) {
+ newState = allowedIPsStateContainsIPV4PublicNetworks
+ } else {
+ newState = allowedIPsStateOther
+ }
+ } else {
+ newState = allowedIPsStateInvalid
+ }
+
+ if newState != dlg.allowedIPsState {
+ dlg.allowedIPsState = newState
+
+ dlg.excludePrivateIPsCB.SetVisible(dlg.canExcludePrivateIPs())
+ dlg.excludePrivateIPsCB.SetChecked(dlg.privateIPsExcluded())
+ }
+}
+
+func (dlg *EditDialog) canExcludePrivateIPs() bool {
+ return dlg.allowedIPsState == allowedIPsStateContainsIPV4PublicNetworks ||
+ dlg.allowedIPsState == allowedIPsStateContainsIPV4Wildcard
+}
+
+func (dlg *EditDialog) privateIPsExcluded() bool {
+ return dlg.allowedIPsState == allowedIPsStateContainsIPV4PublicNetworks
+}
+
+func (dlg *EditDialog) setPrivateIPsExcluded(excluded bool) {
+ if !dlg.canExcludePrivateIPs() || dlg.privateIPsExcluded() == excluded {
+ return
+ }
+
+ var oldNetworks, newNetworks *orderedStringSet
+ if excluded {
+ oldNetworks, newNetworks = ipv4Wildcard, ipv4PublicNetworks
+ } else {
+ oldNetworks, newNetworks = ipv4PublicNetworks, ipv4Wildcard
+ }
+ input := dlg.allowedIPs()
+ output := newOrderedStringSet()
+ var replaced bool
+
+ // Replace the first instance of the wildcard with the public network list, or vice versa.
+ for _, network := range input {
+ if oldNetworks.Contains(network) {
+ if !replaced {
+ output.UniteWith(newNetworks)
+ replaced = true
+ }
+ } else {
+ output.Add(network)
+ }
+ }
+
+ // DNS servers only need to be handled specially when we're excluding private IPs.
+ for _, route := range dlg.dnsRoutes() {
+ if excluded {
+ output.Add(route)
+ } else {
+ output.Remove(route)
+ output.Remove(route + "/32")
+ }
+ }
+
+ if excluded {
+ dlg.allowedIPsState = allowedIPsStateContainsIPV4PublicNetworks
+ } else {
+ dlg.allowedIPsState = allowedIPsStateContainsIPV4Wildcard
+ }
+
+ dlg.replaceLine(configKeyAllowedIPs, strings.Join(output.ToSlice(), ", "))
+}
+
+func (dlg *EditDialog) replaceLine(key, value string) {
+ text := dlg.syntaxEdit.Text()
+
+ start := strings.Index(text, key)
+ end := start + strings.Index(text[start:], "\n")
+ oldLine := text[start:end]
+ newLine := fmt.Sprintf("%s = %s", key, value)
+
+ dlg.syntaxEdit.SetText(strings.ReplaceAll(text, oldLine, newLine))
+}
+
+func (dlg *EditDialog) updateExcludePrivateIPsCBVisible() {
+ dlg.updateAllowedIPsState()
+
+ dlg.excludePrivateIPsCB.SetVisible(dlg.canExcludePrivateIPs())
+}
+
+func (dlg *EditDialog) dnsRoutes() []string {
+ return dlg.routes(configKeyDNS)
+}
+
+func (dlg *EditDialog) allowedIPs() []string {
+ return dlg.routes(configKeyAllowedIPs)
+}
+
+func (dlg *EditDialog) allowedIPsSet() *orderedStringSet {
+ return orderedStringSetFromSlice(dlg.allowedIPs())
+}
+
+func (dlg *EditDialog) routes(key string) []string {
+ var routes []string
+
+ lines := strings.Split(dlg.syntaxEdit.Text(), "\n")
+ for _, line := range lines {
+ if strings.HasPrefix(strings.TrimSpace(line), key) {
+ routesMaybeWithSpace := strings.Split(strings.TrimSpace(line[strings.IndexByte(line, '=')+1:]), ",")
+ routes = make([]string, len(routesMaybeWithSpace))
+ for i, route := range routesMaybeWithSpace {
+ routes[i] = strings.TrimSpace(route)
+ }
+ break
+ }
+ }
+
+ return routes
+}
+
+func (dlg *EditDialog) onExcludePrivateIPsCBCheckedChanged() {
+ dlg.setPrivateIPsExcluded(dlg.excludePrivateIPsCB.Checked())
+}
+
+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("(unknown)")
+ }
+}
+
+func (dlg *EditDialog) onSaveButtonClicked() {
+ newName := dlg.nameEdit.Text()
+ if newName == "" {
+ walk.MsgBox(dlg, "Invalid configuration", "Name is required", walk.MsgBoxIconWarning)
+ return
+ }
+
+ if dlg.tunnel != nil && dlg.tunnel.Name != newName {
+ names, err := conf.ListConfigNames()
+ if err != nil {
+ walk.MsgBox(dlg, "Error", err.Error(), walk.MsgBoxIconError)
+ return
+ }
+
+ for _, name := range names {
+ if strings.ToLower(name) == strings.ToLower(newName) {
+ walk.MsgBox(dlg, "Invalid configuration", fmt.Sprintf("Another tunnel already exists with the name ā€˜%sā€™.", newName), walk.MsgBoxIconWarning)
+ return
+ }
+ }
+ }
+
+ if !conf.TunnelNameIsValid(newName) {
+ walk.MsgBox(dlg, "Invalid configuration", fmt.Sprintf("Tunnel name ā€˜%sā€™ is invalid.", newName), walk.MsgBoxIconWarning)
+ return
+ }
+
+ cfg, err := conf.FromWgQuick(dlg.syntaxEdit.Text(), newName)
+ if err != nil {
+ walk.MsgBox(dlg, "Error", err.Error(), walk.MsgBoxIconError)
+ return
+ }
+
+ dlg.config = *cfg
+
+ dlg.Accept()
+}