golang.zx2c4.com/wireguard/windows@v0.5.4-0.20230123132234-dcc0eb72a04b/ui/editdialog.go (about)

     1  /* SPDX-License-Identifier: MIT
     2   *
     3   * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
     4   */
     5  
     6  package ui
     7  
     8  import (
     9  	"net/netip"
    10  	"strings"
    11  
    12  	"github.com/lxn/walk"
    13  	"github.com/lxn/win"
    14  	"golang.org/x/sys/windows"
    15  
    16  	"golang.zx2c4.com/wireguard/windows/conf"
    17  	"golang.zx2c4.com/wireguard/windows/l18n"
    18  	"golang.zx2c4.com/wireguard/windows/manager"
    19  	"golang.zx2c4.com/wireguard/windows/ui/syntax"
    20  )
    21  
    22  type EditDialog struct {
    23  	*walk.Dialog
    24  	nameEdit                        *walk.LineEdit
    25  	pubkeyEdit                      *walk.LineEdit
    26  	syntaxEdit                      *syntax.SyntaxEdit
    27  	blockUntunneledTrafficCB        *walk.CheckBox
    28  	saveButton                      *walk.PushButton
    29  	config                          conf.Config
    30  	lastPrivateKey                  string
    31  	blockUntunneledTraficCheckGuard bool
    32  }
    33  
    34  func runEditDialog(owner walk.Form, tunnel *manager.Tunnel) *conf.Config {
    35  	dlg, err := newEditDialog(owner, tunnel)
    36  	if showError(err, owner) {
    37  		return nil
    38  	}
    39  
    40  	if dlg.Run() == walk.DlgCmdOK {
    41  		return &dlg.config
    42  	}
    43  
    44  	return nil
    45  }
    46  
    47  func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error) {
    48  	var err error
    49  	var disposables walk.Disposables
    50  	defer disposables.Treat()
    51  
    52  	dlg := new(EditDialog)
    53  
    54  	var title string
    55  	if tunnel == nil {
    56  		title = l18n.Sprintf("Create new tunnel")
    57  	} else {
    58  		title = l18n.Sprintf("Edit tunnel")
    59  	}
    60  
    61  	if tunnel == nil {
    62  		// Creating a new tunnel, create a new private key and use the default template
    63  		pk, _ := conf.NewPrivateKey()
    64  		dlg.config = conf.Config{Interface: conf.Interface{PrivateKey: *pk}}
    65  	} else {
    66  		dlg.config, _ = tunnel.StoredConfig()
    67  	}
    68  
    69  	layout := walk.NewGridLayout()
    70  	layout.SetSpacing(6)
    71  	layout.SetMargins(walk.Margins{10, 10, 10, 10})
    72  	layout.SetColumnStretchFactor(1, 3)
    73  
    74  	if dlg.Dialog, err = walk.NewDialog(owner); err != nil {
    75  		return nil, err
    76  	}
    77  	disposables.Add(dlg)
    78  	dlg.SetIcon(owner.Icon())
    79  	dlg.SetTitle(title)
    80  	dlg.SetLayout(layout)
    81  	dlg.SetMinMaxSize(walk.Size{500, 400}, walk.Size{0, 0})
    82  	if icon, err := loadSystemIcon("imageres", -114, 32); err == nil {
    83  		dlg.SetIcon(icon)
    84  	}
    85  
    86  	nameLabel, err := walk.NewTextLabel(dlg)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	layout.SetRange(nameLabel, walk.Rectangle{0, 0, 1, 1})
    91  	nameLabel.SetTextAlignment(walk.AlignHFarVCenter)
    92  	nameLabel.SetText(l18n.Sprintf("&Name:"))
    93  
    94  	if dlg.nameEdit, err = walk.NewLineEdit(dlg); err != nil {
    95  		return nil, err
    96  	}
    97  	layout.SetRange(dlg.nameEdit, walk.Rectangle{1, 0, 1, 1})
    98  	dlg.nameEdit.SetText(dlg.config.Name)
    99  
   100  	pubkeyLabel, err := walk.NewTextLabel(dlg)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	layout.SetRange(pubkeyLabel, walk.Rectangle{0, 1, 1, 1})
   105  	pubkeyLabel.SetTextAlignment(walk.AlignHFarVCenter)
   106  	pubkeyLabel.SetText(l18n.Sprintf("&Public key:"))
   107  
   108  	if dlg.pubkeyEdit, err = walk.NewLineEdit(dlg); err != nil {
   109  		return nil, err
   110  	}
   111  	layout.SetRange(dlg.pubkeyEdit, walk.Rectangle{1, 1, 1, 1})
   112  	dlg.pubkeyEdit.SetReadOnly(true)
   113  	dlg.pubkeyEdit.SetText(l18n.Sprintf("(unknown)"))
   114  	dlg.pubkeyEdit.Accessibility().SetRole(walk.AccRoleStatictext)
   115  
   116  	if dlg.syntaxEdit, err = syntax.NewSyntaxEdit(dlg); err != nil {
   117  		return nil, err
   118  	}
   119  	layout.SetRange(dlg.syntaxEdit, walk.Rectangle{0, 2, 2, 1})
   120  
   121  	buttonsContainer, err := walk.NewComposite(dlg)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	layout.SetRange(buttonsContainer, walk.Rectangle{0, 3, 2, 1})
   126  	buttonsContainer.SetLayout(walk.NewHBoxLayout())
   127  	buttonsContainer.Layout().SetMargins(walk.Margins{})
   128  
   129  	if dlg.blockUntunneledTrafficCB, err = walk.NewCheckBox(buttonsContainer); err != nil {
   130  		return nil, err
   131  	}
   132  	dlg.blockUntunneledTrafficCB.SetText(l18n.Sprintf("&Block untunneled traffic (kill-switch)"))
   133  	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."))
   134  	dlg.blockUntunneledTrafficCB.SetVisible(false)
   135  	dlg.blockUntunneledTrafficCB.CheckedChanged().Attach(dlg.onBlockUntunneledTrafficCBCheckedChanged)
   136  
   137  	walk.NewHSpacer(buttonsContainer)
   138  
   139  	if dlg.saveButton, err = walk.NewPushButton(buttonsContainer); err != nil {
   140  		return nil, err
   141  	}
   142  	dlg.saveButton.SetText(l18n.Sprintf("&Save"))
   143  	dlg.saveButton.Clicked().Attach(dlg.onSaveButtonClicked)
   144  
   145  	cancelButton, err := walk.NewPushButton(buttonsContainer)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	cancelButton.SetText(l18n.Sprintf("Cancel"))
   150  	cancelButton.Clicked().Attach(dlg.Cancel)
   151  
   152  	dlg.SetCancelButton(cancelButton)
   153  	dlg.SetDefaultButton(dlg.saveButton)
   154  
   155  	dlg.syntaxEdit.PrivateKeyChanged().Attach(dlg.onSyntaxEditPrivateKeyChanged)
   156  	dlg.syntaxEdit.BlockUntunneledTrafficStateChanged().Attach(dlg.onBlockUntunneledTrafficStateChanged)
   157  	dlg.syntaxEdit.SetText(dlg.config.ToWgQuick())
   158  
   159  	// Insert a dummy label immediately preceding syntaxEdit to have screen readers read it.
   160  	// Otherwise they fallback to "RichEdit Control".
   161  	syntaxEditWnd := dlg.syntaxEdit.Handle()
   162  	parentWnd := win.GetParent(syntaxEditWnd)
   163  	labelWnd := win.CreateWindowEx(0,
   164  		windows.StringToUTF16Ptr("STATIC"), windows.StringToUTF16Ptr(l18n.Sprintf("&Configuration:")),
   165  		win.WS_CHILD|win.WS_GROUP|win.SS_LEFT, 0, 0, 0, 0,
   166  		parentWnd, win.HMENU(^uintptr(0)), win.HINSTANCE(win.GetWindowLongPtr(parentWnd, win.GWLP_HINSTANCE)), nil)
   167  	prevWnd := win.GetWindow(syntaxEditWnd, win.GW_HWNDPREV)
   168  	nextWnd := win.GetWindow(syntaxEditWnd, win.GW_HWNDNEXT)
   169  	win.SetWindowPos(labelWnd, prevWnd, 0, 0, 0, 0, win.SWP_NOSIZE|win.SWP_NOMOVE)
   170  	win.SetWindowPos(syntaxEditWnd, labelWnd, 0, 0, 0, 0, win.SWP_NOSIZE|win.SWP_NOMOVE)
   171  	win.SetWindowPos(nextWnd, syntaxEditWnd, 0, 0, 0, 0, win.SWP_NOSIZE|win.SWP_NOMOVE)
   172  
   173  	if tunnel != nil {
   174  		dlg.Starting().Attach(func() {
   175  			dlg.syntaxEdit.SetFocus()
   176  		})
   177  	}
   178  
   179  	disposables.Spare()
   180  
   181  	return dlg, nil
   182  }
   183  
   184  func (dlg *EditDialog) onBlockUntunneledTrafficCBCheckedChanged() {
   185  	if dlg.blockUntunneledTraficCheckGuard {
   186  		return
   187  	}
   188  	var (
   189  		v400    = netip.PrefixFrom(netip.IPv4Unspecified(), 0)
   190  		v600000 = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
   191  		v401    = netip.PrefixFrom(netip.AddrFrom4([4]byte{}), 1)
   192  		v600001 = netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 1)
   193  		v41281  = netip.PrefixFrom(netip.AddrFrom4([4]byte{0x80}), 1)
   194  		v680001 = netip.PrefixFrom(netip.AddrFrom16([16]byte{0x80}), 1)
   195  	)
   196  
   197  	block := dlg.blockUntunneledTrafficCB.Checked()
   198  	cfg, err := conf.FromWgQuick(dlg.syntaxEdit.Text(), "temporary")
   199  	var newAllowedIPs []netip.Prefix
   200  
   201  	if err != nil {
   202  		goto err
   203  	}
   204  	if len(cfg.Peers) != 1 {
   205  		goto err
   206  	}
   207  
   208  	newAllowedIPs = make([]netip.Prefix, 0, len(cfg.Peers[0].AllowedIPs))
   209  	if block {
   210  		var (
   211  			foundV401    bool
   212  			foundV41281  bool
   213  			foundV600001 bool
   214  			foundV680001 bool
   215  		)
   216  		for _, allowedip := range cfg.Peers[0].AllowedIPs {
   217  			if allowedip == v600001 {
   218  				foundV600001 = true
   219  			} else if allowedip == v680001 {
   220  				foundV680001 = true
   221  			} else if allowedip == v401 {
   222  				foundV401 = true
   223  			} else if allowedip == v41281 {
   224  				foundV41281 = true
   225  			} else {
   226  				newAllowedIPs = append(newAllowedIPs, allowedip)
   227  			}
   228  		}
   229  		if !((foundV401 && foundV41281) || (foundV600001 && foundV680001)) {
   230  			goto err
   231  		}
   232  		if foundV401 && foundV41281 {
   233  			newAllowedIPs = append(newAllowedIPs, v400)
   234  		} else if foundV401 {
   235  			newAllowedIPs = append(newAllowedIPs, v401)
   236  		} else if foundV41281 {
   237  			newAllowedIPs = append(newAllowedIPs, v41281)
   238  		}
   239  		if foundV600001 && foundV680001 {
   240  			newAllowedIPs = append(newAllowedIPs, v600000)
   241  		} else if foundV600001 {
   242  			newAllowedIPs = append(newAllowedIPs, v600001)
   243  		} else if foundV680001 {
   244  			newAllowedIPs = append(newAllowedIPs, v680001)
   245  		}
   246  		cfg.Peers[0].AllowedIPs = newAllowedIPs
   247  	} else {
   248  		var (
   249  			foundV400    bool
   250  			foundV600000 bool
   251  		)
   252  		for _, allowedip := range cfg.Peers[0].AllowedIPs {
   253  			if allowedip == v600000 {
   254  				foundV600000 = true
   255  			} else if allowedip == v400 {
   256  				foundV400 = true
   257  			} else {
   258  				newAllowedIPs = append(newAllowedIPs, allowedip)
   259  			}
   260  		}
   261  		if !(foundV400 || foundV600000) {
   262  			goto err
   263  		}
   264  		if foundV400 {
   265  			newAllowedIPs = append(newAllowedIPs, v401)
   266  			newAllowedIPs = append(newAllowedIPs, v41281)
   267  		}
   268  		if foundV600000 {
   269  			newAllowedIPs = append(newAllowedIPs, v600001)
   270  			newAllowedIPs = append(newAllowedIPs, v680001)
   271  		}
   272  		cfg.Peers[0].AllowedIPs = newAllowedIPs
   273  	}
   274  	dlg.syntaxEdit.SetText(cfg.ToWgQuick())
   275  	return
   276  
   277  err:
   278  	text := dlg.syntaxEdit.Text()
   279  	dlg.syntaxEdit.SetText("")
   280  	dlg.syntaxEdit.SetText(text)
   281  }
   282  
   283  func (dlg *EditDialog) onBlockUntunneledTrafficStateChanged(state int) {
   284  	dlg.blockUntunneledTraficCheckGuard = true
   285  	switch syntax.BlockState(state) {
   286  	case syntax.InevaluableBlockingUntunneledTraffic:
   287  		dlg.blockUntunneledTrafficCB.SetVisible(false)
   288  	case syntax.BlockingUntunneledTraffic:
   289  		dlg.blockUntunneledTrafficCB.SetVisible(true)
   290  		dlg.blockUntunneledTrafficCB.SetChecked(true)
   291  	case syntax.NotBlockingUntunneledTraffic:
   292  		dlg.blockUntunneledTrafficCB.SetVisible(true)
   293  		dlg.blockUntunneledTrafficCB.SetChecked(false)
   294  	}
   295  	dlg.blockUntunneledTraficCheckGuard = false
   296  }
   297  
   298  func (dlg *EditDialog) onSyntaxEditPrivateKeyChanged(privateKey string) {
   299  	if privateKey == dlg.lastPrivateKey {
   300  		return
   301  	}
   302  	dlg.lastPrivateKey = privateKey
   303  	key, _ := conf.NewPrivateKeyFromString(privateKey)
   304  	if key != nil {
   305  		dlg.pubkeyEdit.SetText(key.Public().String())
   306  	} else {
   307  		dlg.pubkeyEdit.SetText(l18n.Sprintf("(unknown)"))
   308  	}
   309  }
   310  
   311  func (dlg *EditDialog) onSaveButtonClicked() {
   312  	newName := dlg.nameEdit.Text()
   313  	if newName == "" {
   314  		showWarningCustom(dlg, l18n.Sprintf("Invalid name"), l18n.Sprintf("A name is required."))
   315  		return
   316  	}
   317  	if !conf.TunnelNameIsValid(newName) {
   318  		showWarningCustom(dlg, l18n.Sprintf("Invalid name"), l18n.Sprintf("Tunnel name ā€˜%s’ is invalid.", newName))
   319  		return
   320  	}
   321  	newNameLower := strings.ToLower(newName)
   322  
   323  	if newNameLower != strings.ToLower(dlg.config.Name) {
   324  		existingTunnelList, err := manager.IPCClientTunnels()
   325  		if err != nil {
   326  			showWarningCustom(dlg, l18n.Sprintf("Unable to list existing tunnels"), err.Error())
   327  			return
   328  		}
   329  		for _, tunnel := range existingTunnelList {
   330  			if strings.ToLower(tunnel.Name) == newNameLower {
   331  				showWarningCustom(dlg, l18n.Sprintf("Tunnel already exists"), l18n.Sprintf("Another tunnel already exists with the name ā€˜%s’.", newName))
   332  				return
   333  			}
   334  		}
   335  	}
   336  
   337  	cfg, err := conf.FromWgQuick(dlg.syntaxEdit.Text(), newName)
   338  	if err != nil {
   339  		showErrorCustom(dlg, l18n.Sprintf("Unable to create new configuration"), err.Error())
   340  		return
   341  	}
   342  
   343  	dlg.config = *cfg
   344  	dlg.Accept()
   345  }