diff --git a/actions/ejabberd b/actions/ejabberd index cc492fffa..ae97b4b17 100755 --- a/actions/ejabberd +++ b/actions/ejabberd @@ -30,6 +30,8 @@ yaml = YAML() yaml.allow_duplicate_keys = True yaml.preserve_quotes = True +TURN_URI_REGEX = r'(stun|turn):(.*):([0-9]{4})\?transport=(tcp|udp)' + def parse_arguments(): """Return parsed command line arguments as dictionary""" @@ -303,7 +305,7 @@ def subcommand_mam(argument): def _generate_service(uri: str) -> dict: """Generate ejabberd mod_stun_disco service config from Coturn URI.""" - pattern = re.compile(r'(stun|turn):(.*):([0-9]{4})\?transport=(tcp|udp)') + pattern = re.compile(TURN_URI_REGEX) typ, domain, port, transport = pattern.match(uri).groups() return { "host": domain, diff --git a/plinth/modules/coturn/components.py b/plinth/modules/coturn/components.py index 22b9b7ad3..441ad0ffa 100644 --- a/plinth/modules/coturn/components.py +++ b/plinth/modules/coturn/components.py @@ -5,10 +5,13 @@ from __future__ import annotations # Can be removed in Python 3.10 import json +import re from dataclasses import dataclass, field from plinth import app +TURN_URI_REGEX = r'(stun|turn):(.*):([0-9]{4})\?transport=(tcp|udp)' + @dataclass class TurnConfiguration: @@ -45,6 +48,12 @@ class TurnConfiguration: 'shared_secret': self.shared_secret }) + @staticmethod + def validate_turn_uris(turn_uris: list[str]) -> bool: + """Return whether the given TURN URI is valid.""" + pattern = re.compile(TURN_URI_REGEX) + return all(map(pattern.match, turn_uris)) + class TurnConsumer(app.FollowerComponent): """Component to manage coturn configuration. diff --git a/plinth/modules/coturn/forms.py b/plinth/modules/coturn/forms.py index 53f89d199..c0d336d20 100644 --- a/plinth/modules/coturn/forms.py +++ b/plinth/modules/coturn/forms.py @@ -4,9 +4,11 @@ Forms for Coturn app. """ from django import forms +from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ from plinth.modules import coturn +from plinth.modules.coturn.components import TurnConfiguration def get_domain_choices(): @@ -14,6 +16,12 @@ def get_domain_choices(): return ((domain, domain) for domain in coturn.get_available_domains()) +def turn_uris_validator(turn_uris): + """Validate list of STUN/TURN Server URIs.""" + if not TurnConfiguration.validate_turn_uris(turn_uris.split("\n")): + raise ValidationError(_('Invalid list of STUN/TURN Server URIs')) + + class CoturnForm(forms.Form): """Form to select a TLS domain for Coturn.""" diff --git a/plinth/modules/ejabberd/forms.py b/plinth/modules/ejabberd/forms.py index ff0825ade..75cd46686 100644 --- a/plinth/modules/ejabberd/forms.py +++ b/plinth/modules/ejabberd/forms.py @@ -8,6 +8,7 @@ from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ from plinth import cfg +from plinth.modules.coturn.forms import turn_uris_validator from plinth.utils import format_lazy @@ -35,7 +36,7 @@ class EjabberdForm(forms.Form): label=_('STUN/TURN Server URIs'), required=False, strip=True, widget=forms.Textarea(attrs={'rows': 4}), help_text=_('List of public URIs of the STUN/TURN server, one on each ' - 'line.')) + 'line.'), validators=[turn_uris_validator]) shared_secret = forms.CharField( label=_('Shared Authentication Secret'), required=False, strip=True, diff --git a/plinth/modules/matrixsynapse/forms.py b/plinth/modules/matrixsynapse/forms.py index ea36735b6..d231dffe3 100644 --- a/plinth/modules/matrixsynapse/forms.py +++ b/plinth/modules/matrixsynapse/forms.py @@ -5,8 +5,9 @@ Forms for the Matrix Synapse module. from django import forms from django.urls import reverse_lazy -from django.utils.translation import ugettext_lazy as _ +from plinth.modules.coturn.forms import turn_uris_validator +from django.utils.translation import ugettext_lazy as _ from plinth.utils import format_lazy @@ -30,7 +31,7 @@ class MatrixSynapseForm(forms.Form): label=_('STUN/TURN Server URIs'), required=False, strip=True, widget=forms.Textarea(attrs={'rows': 4}), help_text=_('List of public URIs of the STUN/TURN server, one on each ' - 'line.')) + 'line.'), validators=[turn_uris_validator]) shared_secret = forms.CharField( label=_('Shared Authentication Secret'), required=False, strip=True,