# SPDX-License-Identifier: AGPL-3.0-or-later import io import itertools import pwd from django.core.exceptions import ValidationError from django.http import HttpResponseBadRequest from django.shortcuts import redirect from django.utils.html import escape from django.utils.translation import gettext_lazy as _ from django.views.generic.base import TemplateView, View import plinth.actions import plinth.utils from plinth.views import AppView, render_tabs from . import aliases, audit, forms class TabMixin(View): admin_tabs = [ ('', _('Home')), ('my_mail', _('My Mail')), ('my_aliases', _('My Aliases')), ('security', _('Security')), ('domains', _('Domains')) ] user_tabs = [ ('my_mail', _('Home')), ('my_aliases', _('My Aliases')) ] def get_context_data(self, *args, **kwargs): # Retrieve context data from the next method in the MRO context = super().get_context_data(*args, **kwargs) # Populate context with customized data context['tabs'] = self.render_dynamic_tabs() return context def render_dynamic_tabs(self): if plinth.utils.is_user_admin(self.request): return render_tabs(self.request.path, self.admin_tabs) else: return render_tabs(self.request.path, self.user_tabs) def render_validation_error(self, validation_error, status=400): context = self.get_context_data() context['error'] = validation_error return self.render_to_response(context, status=status) def render_exception(self, exception, status=500): context = self.get_context_data() context['error'] = [str(exception)] return self.render_to_response(context, status=status) def catch_exceptions(self, function, request): try: return function(request) except ValidationError as validation_error: return self.render_validation_error(validation_error) except Exception as error: return self.render_exception(error) def find_button(self, post): key_filter = (k for k in post.keys() if k.startswith('btn_')) lst = list(itertools.islice(key_filter, 2)) if len(lst) != 1: raise ValidationError('Bad post data') if not isinstance(lst[0], str): raise ValidationError('Bad post data') return lst[0][len('btn_'):] def find_form(self, post): form_name = post.get('form') for cls in self.form_classes: if cls.__name__ == form_name: return cls(post) raise ValidationError('Form was unspecified') class EmailServerView(TabMixin, AppView): """Server configuration page""" app_id = 'email_server' template_name = 'email_server.html' audit_modules = ('domain', 'tls', 'rcube') def get_context_data(self, *args, **kwargs): dlist = [] for module_name in self.audit_modules: self._get_audit_results(module_name, dlist) dlist.sort(key=audit.models.Diagnosis.sorting_key) context = super().get_context_data(*args, **kwargs) context['related_diagnostics'] = dlist return context def _get_audit_results(self, module_name, dlist): try: results = getattr(audit, module_name).get() except Exception as e: title = _('Internal error in {0}').format('audit.' + module_name) diagnosis = audit.models.Diagnosis(title) diagnosis.critical(str(e)) diagnosis.critical(_('Check syslog for more information')) results = [diagnosis] for diagnosis in results: if diagnosis.action: diagnosis.action = '%s.%s' % (module_name, diagnosis.action) if diagnosis.has_failed: dlist.append(diagnosis) def post(self, request): repair_field = request.POST.get('repair') module_name, sep, action_name = repair_field.partition('.') if not sep or module_name not in self.audit_modules: return HttpResponseBadRequest('Bad post data') self._repair(module_name, action_name) return redirect(request.path) def _repair(self, module_name, action_name): """Repair the configuration of the given audit module.""" module = getattr(audit, module_name) if not hasattr(module, 'repair_component'): return reload_list = [] try: reload_list = module.repair_component(action_name) except Exception: pass for service in reload_list: # plinth.action_utils.service_reload(service) plinth.actions.superuser_run('service', ['reload', service]) class MyMailView(TabMixin, TemplateView): template_name = 'my_mail.html' def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) nam = self.request.user.username context['has_homedir'] = audit.home.exists_nam(nam) return context def post(self, request): return self.catch_exceptions(self._post, request) def _post(self, request): if 'btn_mkhome' not in request.POST: raise ValidationError('Bad post data') audit.home.put_nam(request.user.username) return self.render_to_response(self.get_context_data()) class AliasView(TabMixin, TemplateView): class Checkboxes: def __init__(self, post=None, initial=None): self.models = initial self.post = post self.cleaned_data = {} # HTML rendering self.sb = io.StringIO() self.counter = 0 def render(self): if self.models is None: raise RuntimeError('Uninitialized form') if self.sb.tell() > 0: raise RuntimeError('render has been called') enabled = [a.email_name for a in self.models if a.enabled] disabled = [a.email_name for a in self.models if not a.enabled] self._render_fieldset(enabled, _('Enabled aliases')) self._render_fieldset(disabled, _('Disabled aliases')) return self.sb.getvalue() def _render_fieldset(self, email_names, legend): if len(email_names) > 0: self.sb.write('
') self.sb.write('%s' % escape(legend)) self._render_boxes(email_names) self.sb.write('
') def _render_boxes(self, email_names): for email_name in email_names: input_id = 'cb_alias_%d' % self._count() value = escape(email_name) self.sb.write('
') self.sb.write('' % (input_id, value)) self.sb.write('' % (input_id, value)) self.sb.write('
') def _count(self): self.counter += 1 return self.counter def is_valid(self): lst = list(filter(None, self.post.getlist('alias'))) if not lst: return False else: self.cleaned_data['alias'] = lst return True template_name = 'email_alias.html' form_classes = (forms.AliasCreationForm, Checkboxes) def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) context['form'] = forms.AliasCreationForm() uid = pwd.getpwnam(self.request.user.username).pw_uid models = aliases.get(uid) if len(models) > 0: form = AliasView.Checkboxes(initial=models) context['alias_boxes'] = form.render() else: context['no_alias'] = True return context def post(self, request): return self.catch_exceptions(self._post, request) def _post(self, request): form = self.find_form(request.POST) button = self.find_button(request.POST) if not form.is_valid(): raise ValidationError('Form invalid') if isinstance(form, AliasView.Checkboxes): if button not in ('delete', 'disable', 'enable'): raise ValidationError('Bad button') return self.alias_operation_form_valid(form, button) if isinstance(form, forms.AliasCreationForm): if button != 'add': raise ValidationError('Bad button') return self.alias_creation_form_valid(form, button) raise RuntimeError('Unknown form') def alias_operation_form_valid(self, form, button): uid = pwd.getpwnam(self.request.user.username).pw_uid alias_list = form.cleaned_data['alias'] if button == 'delete': aliases.delete(uid, alias_list) elif button == 'disable': aliases.set_disabled(uid, alias_list) elif button == 'enable': aliases.set_enabled(uid, alias_list) return self.render_to_response(self.get_context_data()) def alias_creation_form_valid(self, form, button): uid = pwd.getpwnam(self.request.user.username).pw_uid aliases.put(uid, form.cleaned_data['email_name']) return self.render_to_response(self.get_context_data()) class TLSView(TabMixin, TemplateView): template_name = 'email_security.html' class DomainView(TabMixin, TemplateView): template_name = 'email_domains.html' def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) fields = audit.domain.get_domain_config() # If having post data, display the posted values for field in fields: field.new_value = self.request.POST.get(field.key, '') context['fields'] = fields return context def post(self, request): return self.catch_exceptions(self._post, request) def _post(self, request): changed = {} # Skip blank fields for key, value in request.POST.items(): value = value.strip() if value: changed[key] = value audit.domain.set_keys(changed) return self.render_to_response(self.get_context_data()) class XmlView(TemplateView): template_name = 'email_autoconfig.xml' def render_to_response(self, *args, **kwargs): kwargs['content_type'] = 'text/xml; charset=utf-8' response = super().render_to_response(*args, **kwargs) response['X-Robots-Tag'] = 'noindex, nofollow, noarchive' return response def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['host'] = self.request.get_host() return context