diff --git a/plinth/modules/email/dns.py b/plinth/modules/email/dns.py new file mode 100644 index 000000000..0b0a19fd1 --- /dev/null +++ b/plinth/modules/email/dns.py @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +""" +Manage DNS entries needed for an email server. +""" + +from dataclasses import dataclass +from typing import Union + +from plinth.errors import ActionError + + +@dataclass +class Entry: # pylint: disable=too-many-instance-attributes + """A DNS entry.""" + type_: str + value: str + domain: Union[str, None] = None + class_: str = 'IN' + ttl: int = 60 + priority: int = 10 + weight: Union[int, None] = None + port: Union[int, None] = None + + def get_split_value(self): + """If the record is TXT and value > 255, split it.""" + if len(self.value) <= 255: + return self.value + + pieces = [] + value = self.value + while value: + pieces.append(f'"{value[:255]}"') + value = value[255:] + + return ' '.join(pieces) + + +def get_entries(): + """Return the list of DNS entries to make.""" + from . import audit + + domain = audit.domain.get_domains()['primary_domain'] + mx_spam_entries = [ + Entry(type_='MX', value=f'{domain}.'), + Entry(type_='TXT', value='v=spf1 mx a ~all'), + Entry( + domain='_dmarc', type_='TXT', + value='v=DMARC1; p=none; sp=quarantine; ' + f'rua=mailto:postmaster@{domain}; ') + ] + try: + dkim_public_key = audit.dkim.get_public_key(domain) + dkim_entries = [ + Entry(domain='dkim._domainkey', type_='TXT', + value=f'v=DKIM1; k=rsa; p={dkim_public_key}') + ] + except ActionError: + dkim_entries = [] + + autoconfig_entries = [ + Entry(domain='_submission._tcp', type_='SRV', weight=10, port=587, + value=f'{domain}.'), + Entry(domain='_imaps._tcp', type_='SRV', weight=10, port=993, + value=f'{domain}.'), + Entry(domain='_pop3s._tcp', type_='SRV', priority=20, weight=10, + port=995, value=f'{domain}.'), + ] + return mx_spam_entries + dkim_entries + autoconfig_entries diff --git a/plinth/modules/email/templates/email.html b/plinth/modules/email/templates/email.html index 03bf737d8..9af501d41 100644 --- a/plinth/modules/email/templates/email.html +++ b/plinth/modules/email/templates/email.html @@ -15,3 +15,47 @@ {% trans "Manage Aliases" %} {% endblock %} + +{% block extra_content %} + {{ block.super }} + +

{% trans "DNS Records" %}

+ +

+ {% blocktrans trimmed %} + The following DNS records must be added manually on your primary domain + for the mail server to work properly. + {% endblocktrans %} +

+ +
+ + + + + + + + + + + + + + + {% for dns_entry in dns_entries %} + + + + + + + + + + + {% endfor %} + +
{% trans "Domain" %}{% trans "TTL" %}{% trans "Class" %}{% trans "Type" %}{% trans "Priority" %}{% trans "Weight" %}{% trans "Port" %}{% trans "Host/Target/Value" %}
{{ dns_entry.domain|default_if_none:"" }}{{ dns_entry.ttl }}{{ dns_entry.class_ }}{{ dns_entry.type_ }}{{ dns_entry.priority }}{{ dns_entry.weight|default_if_none:"" }}{{ dns_entry.port|default_if_none:"" }}{{ dns_entry.get_split_value }}
+
+{% endblock %} diff --git a/plinth/modules/email/views.py b/plinth/modules/email/views.py index 8c09a2cee..9332dfbf2 100644 --- a/plinth/modules/email/views.py +++ b/plinth/modules/email/views.py @@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _ from django.views.generic.base import TemplateView from django.views.generic.edit import FormView +from plinth.modules.email import dns from plinth.views import AppView from . import aliases as aliases_module @@ -21,6 +22,12 @@ class EmailAppView(AppView): form_class = forms.DomainForm template_name = 'email.html' + def get_context_data(self, **kwargs): + """Add additional context data for rendering the template.""" + context = super().get_context_data(**kwargs) + context['dns_entries'] = dns.get_entries() + return context + def get_initial(self): """Return the initial values to populate in the form.""" initial = super().get_initial()