mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-02-04 08:13:38 +00:00
As reported in discussion forum[1], when clients connected via 'shared' network connection try to resolve the a static domain name configured in FreedomBox, they resolve to 127.0.1.1. Since this refers to client's own IP address, they fail to connect. In the previous version, this was not a problem because the entry was stored as <hostname>.<domainname>. To resolve this, store domain names in kvstore instead of /etc/hosts. Links: 1) https://discuss.freedombox.org/t/freedombox-resolves-its-own-external-name-as-127-0-1-1/3660 Tests: - Adding/removing static domains from Names app works. The order of added domains is preserved in the stored configuration. When adding a existing domain, a proper error message is shown. - Without the patch, configure multiple domains. They show up in /etc/hosts. Apply the patches and restart the service. Names app setup will run. Entries from /etc/hosts are removed and will be added to kvstore. The list of domains shows properly in Names app. After restarting the services, domains are show properly. - Without the patch on a version of FreedomBox without support for multiple static domains, configure a static domain. Switch to latest version FreedomBox with the patches. Restart the service. Names app setup will run. Entry from /etc/hosts will be removed and will be added to kvstore. The list of domains shows properly in Names app. After restarting the services, domains are show properly. Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
162 lines
4.9 KiB
Python
162 lines
4.9 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
"""Configure Names App."""
|
|
|
|
import pathlib
|
|
import subprocess
|
|
|
|
import augeas
|
|
|
|
from plinth import action_utils
|
|
from plinth.actions import privileged
|
|
|
|
fallback_conf = pathlib.Path(
|
|
'/etc/systemd/resolved.conf.d/freedombox-fallback.conf')
|
|
override_conf = pathlib.Path('/etc/systemd/resolved.conf.d/freedombox.conf')
|
|
source_fallback_conf = pathlib.Path(
|
|
'/usr/share/freedombox'
|
|
'/etc/systemd/resolved.conf.d/freedombox-fallback.conf')
|
|
|
|
HOSTS_LOCAL_IP = '127.0.1.1'
|
|
|
|
|
|
@privileged
|
|
def set_hostname(hostname: str):
|
|
"""Set system hostname using hostnamectl."""
|
|
subprocess.run(
|
|
['hostnamectl', 'set-hostname', '--transient', '--static', hostname],
|
|
check=True)
|
|
action_utils.service_restart('avahi-daemon')
|
|
|
|
|
|
def _load_augeas_hosts():
|
|
"""Initialize Augeas for editing /etc/hosts."""
|
|
hosts_file = '/etc/hosts'
|
|
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
|
augeas.Augeas.NO_MODL_AUTOLOAD)
|
|
aug.transform('hosts', hosts_file)
|
|
aug.set('/augeas/context', '/files' + hosts_file)
|
|
aug.load()
|
|
return aug
|
|
|
|
|
|
def get_old_domains(aug=None) -> list[str]:
|
|
"""Return the list of domains store in old /etc/hosts format."""
|
|
if not aug:
|
|
aug = _load_augeas_hosts()
|
|
|
|
domains = {} # Maintain order of entries
|
|
for match in aug.match('*'):
|
|
if aug.get(match + '/ipaddr') != HOSTS_LOCAL_IP:
|
|
continue
|
|
|
|
domain = aug.get(match + '/canonical')
|
|
aliases = []
|
|
for alias_match in aug.match(match + '/alias'):
|
|
aliases.append(aug.get(alias_match))
|
|
|
|
# Read old style domains too.
|
|
if aliases and '.' not in aliases[-1]:
|
|
hostname = aliases[-1]
|
|
if domain.startswith(hostname + '.'):
|
|
domain = domain.partition('.')[2]
|
|
aliases = aliases[:-1]
|
|
|
|
for value in [domain] + aliases:
|
|
if value:
|
|
domains[domain] = True
|
|
|
|
return list(domains.keys())
|
|
|
|
|
|
@privileged
|
|
def domain_delete_all():
|
|
"""Remove all static domain names from /etc/hosts."""
|
|
aug = _load_augeas_hosts()
|
|
for match in aug.match('*'):
|
|
if aug.get(match + '/ipaddr') == HOSTS_LOCAL_IP:
|
|
aug.remove(match)
|
|
|
|
aug.save()
|
|
|
|
|
|
@privileged
|
|
def install_resolved():
|
|
"""Install systemd-resolved related packages."""
|
|
packages = ['systemd-resolved', 'libnss-resolve']
|
|
subprocess.run(['dpkg', '--configure', '-a'], check=False)
|
|
with action_utils.apt_hold_freedombox():
|
|
action_utils.run_apt_command(['--fix-broken', 'install'])
|
|
returncode = action_utils.run_apt_command(['install'] + packages)
|
|
|
|
if returncode:
|
|
raise RuntimeError(
|
|
f'Apt command failed with return code: {returncode}')
|
|
|
|
|
|
@privileged
|
|
def set_resolved_configuration(dns_fallback: bool | None = None,
|
|
dns_over_tls: str | None = None,
|
|
dnssec: str | None = None):
|
|
"""Set systemd-resolved configuration options."""
|
|
if dns_fallback is not None:
|
|
_set_enable_dns_fallback(dns_fallback)
|
|
|
|
if dns_over_tls is not None or dnssec is not None:
|
|
_set_resolved_configuration(dns_over_tls, dnssec)
|
|
|
|
# Workaround buggy reload that does not apply DNS-over-TLS changes
|
|
# properly.
|
|
action_utils.service_try_restart('systemd-resolved')
|
|
|
|
|
|
def get_resolved_configuration() -> dict[str, bool]:
|
|
"""Return systemd-resolved configuration."""
|
|
configuration = _get_resolved_configuration()
|
|
configuration['dns_fallback'] = fallback_conf.exists()
|
|
return configuration
|
|
|
|
|
|
def _set_enable_dns_fallback(dns_fallback: bool):
|
|
"""Update whether to use DNS fallback servers."""
|
|
if dns_fallback:
|
|
if not fallback_conf.exists():
|
|
fallback_conf.parent.mkdir(parents=True, exist_ok=True)
|
|
fallback_conf.symlink_to(source_fallback_conf)
|
|
else:
|
|
fallback_conf.unlink(missing_ok=True)
|
|
|
|
|
|
def _load_augeas():
|
|
"""Initialize Augeas."""
|
|
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
|
augeas.Augeas.NO_MODL_AUTOLOAD)
|
|
aug.transform('Systemd', str(override_conf))
|
|
aug.set('/augeas/context', '/files' + str(override_conf))
|
|
aug.load()
|
|
return aug
|
|
|
|
|
|
def _get_resolved_configuration():
|
|
"""Return overridden configuration for systemd-resolved."""
|
|
aug = _load_augeas()
|
|
# Default value for DNSSEC upstream is 'allow-downgrade', but in Debian it
|
|
# is 'no'.
|
|
return {
|
|
'dns_over_tls': aug.get('Resolve/DNSOverTLS/value') or 'no',
|
|
'dnssec': aug.get('Resolve/DNSSEC/value') or 'no'
|
|
}
|
|
|
|
|
|
def _set_resolved_configuration(dns_over_tls: str | None = None,
|
|
dnssec: str | None = None):
|
|
"""Write configuration into a systemd-resolved override file."""
|
|
aug = _load_augeas()
|
|
|
|
if dns_over_tls is not None:
|
|
aug.set('Resolve/DNSOverTLS/value', dns_over_tls)
|
|
|
|
if dnssec is not None:
|
|
aug.set('Resolve/DNSSEC/value', dnssec)
|
|
|
|
aug.save()
|