Exemplo n.º 1
0
        public ClientConfiguration(ClientConfigurationList parentList)
        {
            _parentList = parentList;

            // Client properties
            PrivateKeyProperty.TargetTypes.Add(GetType());
            DnsProperty.TargetTypes.Add(GetType());
            AddressProperty.TargetTypes.Add(GetType());

            // Server properties
            PresharedKeyProperty.TargetTypes.Add(typeof(ServerConfiguration));
            PublicKeyProperty.TargetTypes.Add(typeof(ServerConfiguration));
            ServerPersistentKeepaliveProperty.TargetTypes.Add(typeof(ServerConfiguration));

            var    serverConfiguration = new ServerConfiguration().Load <ServerConfiguration>(Configuration.LoadFromFile(ServerConfigurationPrerequisite.ServerDataPath));
            string serverIp            = serverConfiguration.AddressProperty.Value;

            // Add support for generating client IP
            AddressProperty.Action = new ConfigurationPropertyAction(this)
            {
                Name                    = nameof(Resources.GenerateFromServerAction),
                Description             = string.Format(Resources.GenerateClientAddressActionDescription, serverIp),
                DependentProperty       = serverConfiguration.AddressProperty,
                DependencySatisfiedFunc = prop => string.IsNullOrEmpty(prop.Validation?.Validate?.Invoke(prop)),
                Action                  = (conf, prop) =>
                {
                    IPNetwork serverNetwork     = IPNetwork.Parse(serverConfiguration.AddressProperty.Value);
                    var       possibleAddresses = serverNetwork.ListIPAddress().Skip(2).SkipLast(1).ToList(); // Skip reserved .0 and .1 and .255.

                    // If the current address is already in range, we're done
                    if (possibleAddresses.Select(a => a.ToString()).Contains(prop.Value))
                    {
                        return;
                    }

                    WaitCursor.SetOverrideCursor(Cursors.Wait);

                    var existingAddresses = parentList.List.Select(c => c.AddressProperty.Value);

                    // Find the first address that isn't used by another client
                    prop.Value = possibleAddresses.FirstOrDefault(a => existingAddresses.Contains(a.ToString()) == false)?.ToString();

                    WaitCursor.SetOverrideCursor(null);
                }
            };

            // Do custom validation on the Address (we want a specific IP or a CIDR with /32)
            AddressProperty.Validation = new ConfigurationPropertyValidation
            {
                Validate = obj =>
                {
                    string result = default;

                    // First, try parsing with IPNetwork to see if it's in CIDR format
                    if (IPNetwork.TryParse(obj.Value, out var network))
                    {
                        // At this point, we know it's a valid network. Let's see how many addresses are in range
                        if (network.Usable > 1)
                        {
                            // It's CIDR, but it defines more than one address.
                            // However, IPNetwork has a quirk that parses single addresses (without mask) as a range.
                            // So now let's see if it's a single address
                            if (IPAddress.TryParse(obj.Value, out _) == false)
                            {
                                // If we get here, it passed CIDR parsing, but it defined more than one address (i.e., had a mask). It's bad!
                                result = Resources.ClientAddressValidationError;
                            }
                            // Else, it's a single address as parsed by IPAddress, so we're good!
                        }
                        // Else
                        // It's in CIDR notation and only defines a single address (/32) so we're good!
                    }
                    else
                    {
                        // Not even IPNetwork could parse it, so it's really bad!
                        result = Resources.ClientAddressValidationError;
                    }

                    return(result);
                }
            };

            // The client generates the PSK
            PresharedKeyProperty.Action = new ConfigurationPropertyAction(this)
            {
                Name   = $"{nameof(PresharedKeyProperty)}{nameof(ConfigurationProperty.Action)}",
                Action = (conf, prop) =>
                {
                    WaitCursor.SetOverrideCursor(Cursors.Wait);
                    prop.Value = new WireGuardExe().ExecuteCommand(new GeneratePresharedKeyCommand());
                    WaitCursor.SetOverrideCursor(null);
                }
            };

            // Allowed IPs is special, because for the server, it's the same as the Address property for the client
            var allowedIpsProperty = new ConfigurationProperty(this)
            {
                PersistentPropertyName = "AllowedIPs",
                Value    = AddressProperty.Value,
                IsHidden = true
            };

            allowedIpsProperty.TargetTypes.Add(typeof(ServerConfiguration));
            AddressProperty.PropertyChanged += (_, args) =>
            {
                if (args.PropertyName == nameof(AddressProperty.Value))
                {
                    allowedIpsProperty.Value = AddressProperty.Value;
                }
            };
            Properties.Add(allowedIpsProperty);

            // Adjust index of properties and resort
            AddressProperty.Index = 1;
            DnsProperty.Index     = 2;
            SortProperties();

            Properties.ForEach(p => p.PropertyChanged += (_, args) =>
            {
                if (args.PropertyName == nameof(p.Value))
                {
                    GenerateQrCodeAction.RaisePropertyChanged(nameof(GenerateQrCodeAction.DependencySatisfied));
                    ExportConfigurationFileAction.RaisePropertyChanged(nameof(ExportConfigurationFileAction.DependencySatisfied));
                }
            });
        }
Exemplo n.º 2
0
        public override void Configure()
        {
            ClientConfigurationList    clientConfigurations         = new ClientConfigurationList();
            List <ClientConfiguration> clientConfigurationsFromFile = new List <ClientConfiguration>();

            // Load the clients from the conf files into a temporary list
            foreach (string clientConfigurationFile in Directory.GetFiles(ClientDataDirectory, "*.conf"))
            {
                clientConfigurationsFromFile.Add(new ClientConfiguration(clientConfigurations).Load <ClientConfiguration>(Configuration.LoadFromFile(clientConfigurationFile)));
            }

            // Now add them to the ObservableCollection, after sorting the temporary list
            foreach (ClientConfiguration clientConfiguration in clientConfigurationsFromFile.OrderBy(c => c.IndexProperty.Value))
            {
                clientConfigurations.List.Add(clientConfiguration);
            }

            ClientConfigurationEditorWindow clientConfigurationEditorWindow = new ClientConfigurationEditorWindow {
                DataContext = clientConfigurations
            };

            WaitCursor.SetOverrideCursor(Cursors.Wait);
            if (clientConfigurationEditorWindow.ShowDialog() == true)
            {
                WaitCursor.SetOverrideCursor(Cursors.Wait);

                // Delete the existing files (can't rely on updating them since the name of the client may have changed)
                foreach (string clientConfigurationFile in Directory.GetFiles(ClientDataDirectory, "*.conf"))
                {
                    File.Delete(clientConfigurationFile);
                }

                foreach (string clientConfigurationFile in Directory.GetFiles(ClientWGDirectory, "*.conf"))
                {
                    File.Delete(clientConfigurationFile);
                }

                // Check for duplicate names
                HashSet <string> discoveredDuplicateNames = new HashSet <string>();
                foreach (ClientConfiguration clientConfiguration in clientConfigurations.List)
                {
                    int    i            = 1;
                    string originalName = clientConfiguration.NameProperty.Value;
                    while (clientConfigurations.List.Any(c => c != clientConfiguration && c.NameProperty.Value == clientConfiguration.NameProperty.Value))
                    {
                        if (discoveredDuplicateNames.Contains(originalName) == false)
                        {
                            // This is a duplicate name, but we haven't discovered it yet, meaning it's the first of its kind.
                            // We want to rename the SECOND one, so we'll skip this one.
                            discoveredDuplicateNames.Add(originalName);
                            break;
                        }

                        clientConfiguration.NameProperty.Value = $"{originalName} ({i++})";
                    }
                }

                // Save to Data
                foreach (ClientConfiguration clientConfiguration in clientConfigurations.List)
                {
                    clientConfiguration.IndexProperty.Value = clientConfigurations.List.IndexOf(clientConfiguration).ToString();
                    SaveData(clientConfiguration);
                }

                // Save to WG
                foreach (ClientConfiguration clientConfiguration in clientConfigurations.List)
                {
                    SaveWG(clientConfiguration);
                }

                // Update server
                var serverConfigurationPrerequisite = new ServerConfigurationPrerequisite();
                serverConfigurationPrerequisite.Update();

                // Update the tunnel service, if everyone is happy
                if ((Fulfilled || !AnyClients) && serverConfigurationPrerequisite.Fulfilled && new TunnelServicePrerequisite().Fulfilled)
                {
                    string output = new WireGuardExe().ExecuteCommand(new SyncConfigurationCommand(ServerConfigurationPrerequisite.WireGuardServerInterfaceName, ServerConfigurationPrerequisite.ServerWGPath), out int exitCode);

                    if (exitCode != 0)
                    {
                        // Notify the user that there was an error syncing the server conf.
                        WaitCursor.SetOverrideCursor(null);

                        new UnhandledErrorWindow
                        {
                            DataContext = new UnhandledErrorWindowModel
                            {
                                Title     = Resources.Error,
                                Text      = $"{Resources.ServerSyncError}{Environment.NewLine}{Environment.NewLine}{output}",
                                Exception = new Exception(output)
                            }
                        }.ShowDialog();
                    }
                }

                WaitCursor.SetOverrideCursor(null);
            }

            Refresh();
        }