diff --git a/modoboa/extensions/amavis/__init__.py b/modoboa/extensions/amavis/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modoboa/extensions/amavis/app_settings.py b/modoboa/extensions/amavis/app_settings.py deleted file mode 100644 index 572d31d99..000000000 --- a/modoboa/extensions/amavis/app_settings.py +++ /dev/null @@ -1,162 +0,0 @@ -# coding: utf-8 - -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from modoboa.lib.form_utils import SeparatorField, YesNoField, InlineRadioSelect -from modoboa.lib.parameters import AdminParametersForm, UserParametersForm - - -class ParametersForm(AdminParametersForm): - - """Extension settings.""" - - app = "amavis" - - qsettings_sep = SeparatorField(label=_("Quarantine settings")) - - max_messages_age = forms.IntegerField( - label=_("Maximum message age"), - initial=14, - help_text=_( - "Quarantine messages maximum age (in days) before deletion" - ) - ) - - sep1 = SeparatorField(label=_("Messages releasing")) - - released_msgs_cleanup = YesNoField( - label=_("Remove released messages"), - initial="no", - help_text=_("Remove messages marked as released while cleaning up " - "the database") - ) - - am_pdp_mode = forms.ChoiceField( - label=_("Amavis connection mode"), - choices=[("inet", "inet"), ("unix", "unix")], - initial="unix", - help_text=_("Mode used to access the PDP server"), - widget=InlineRadioSelect(attrs={"type": "checkbox"}) - ) - - am_pdp_host = forms.CharField( - label=_("PDP server address"), - initial="localhost", - help_text=_("PDP server address (if inet mode)"), - widget=forms.TextInput(attrs={"class": "form-control"}) - ) - - am_pdp_port = forms.IntegerField( - label=_("PDP server port"), - initial=9998, - help_text=_("PDP server port (if inet mode)") - ) - - am_pdp_socket = forms.CharField( - label=_("PDP server socket"), - initial="/var/amavis/amavisd.sock", - help_text=_("Path to the PDP server socket (if unix mode)") - ) - - user_can_release = YesNoField( - label=_("Allow direct release"), - initial="no", - help_text=_("Allow users to directly release their messages") - ) - - self_service = YesNoField( - label=_("Enable self-service mode"), - initial="no", - help_text=_("Activate the 'self-service' mode") - ) - - notifications_sender = forms.EmailField( - label=_("Notifications sender"), - initial="notification@modoboa.org", - help_text=_("The e-mail address used to send notitications") - ) - - lsep = SeparatorField(label=_("Manual learning")) - - manual_learning = YesNoField( - label=_("Enable manual learning"), - initial="yes", - help_text=_( - "Allow super administrators to manually train Spamassassin" - ) - ) - - sa_is_local = YesNoField( - label=_("Is Spamassassin local?"), - initial="yes", - help_text=_( - "Tell if Spamassassin is running on the same server than modoboa" - ) - ) - - default_user = forms.CharField( - label=_("Default user"), - initial="amavis", - help_text=_( - "Name of the user owning the default bayesian database" - ) - ) - - spamd_address = forms.CharField( - label=_("Spamd address"), - initial="127.0.0.1", - help_text=_("The IP address where spamd can be reached") - ) - - spamd_port = forms.IntegerField( - label=_("Spamd port"), - initial=783, - help_text=_("The TCP port spamd is listening on") - ) - - domain_level_learning = YesNoField( - label=_("Enable per-domain manual learning"), - initial="no", - help_text=_( - "Allow domain administrators to train Spamassassin " - "(within dedicated per-domain databases)" - ) - ) - - user_level_learning = YesNoField( - label=_("Enable per-user manual learning"), - initial="no", - help_text=_( - "Allow simple users to personally train Spamassassin " - "(within a dedicated database)" - ) - ) - - visibility_rules = { - "am_pdp_host": "am_pdp_mode=inet", - "am_pdp_port": "am_pdp_mode=inet", - "am_pdp_socket": "am_pdp_mode=unix", - - "sa_is_local": "manual_learning=yes", - "default_user": "manual_learning=yes", - "spamd_address": "sa_is_local=no", - "spamd_port": "sa_is_local=no", - "domain_level_learning": "manual_learning=yes", - "user_level_learning": "manual_learning=yes" - } - - -class UserSettings(UserParametersForm): - - """Per-user settings.""" - - app = "amavis" - - dsep = SeparatorField(label=_("Display")) - - messages_per_page = forms.IntegerField( - initial=40, - label=_("Number of displayed emails per page"), - help_text=_("Set the maximum number of messages displayed in a page") - ) diff --git a/modoboa/extensions/amavis/dbrouter.py b/modoboa/extensions/amavis/dbrouter.py deleted file mode 100644 index af582ef80..000000000 --- a/modoboa/extensions/amavis/dbrouter.py +++ /dev/null @@ -1,30 +0,0 @@ -class AmavisRouter(object): - """A router to control all database operations on models in - the amavis application""" - - def db_for_read(self, model, **hints): - "Point all operations on amavis models to 'amavis'" - if model._meta.app_label == 'amavis': - return 'amavis' - return None - - def db_for_write(self, model, **hints): - "Point all operations on amavis models to 'amavis'" - if model._meta.app_label == 'amavis': - return 'amavis' - return None - - def allow_relation(self, obj1, obj2, **hints): - "Allow any relation if a model in amavis is involved" - if obj1._meta.app_label == 'amavis' \ - or obj2._meta.app_label == 'amavis': - return True - return None - - def allow_syncdb(self, db, model): - "Make sure the amavis app only appears on the 'amavis' db" - if db == 'amavis': - return model._meta.app_label == 'amavis' - elif model._meta.app_label == 'amavis': - return False - return None diff --git a/modoboa/extensions/amavis/fixtures/demo.json b/modoboa/extensions/amavis/fixtures/demo.json deleted file mode 100644 index 5075ab854..000000000 --- a/modoboa/extensions/amavis/fixtures/demo.json +++ /dev/null @@ -1 +0,0 @@ -[{"pk": 1, "model": "amavis.users", "fields": {"priority": 7, "policy": 1, "email": "@demo.local", "fullname": "demo.local"}}] \ No newline at end of file diff --git a/modoboa/extensions/amavis/forms.py b/modoboa/extensions/amavis/forms.py deleted file mode 100644 index 7af8c2b52..000000000 --- a/modoboa/extensions/amavis/forms.py +++ /dev/null @@ -1,97 +0,0 @@ -# coding: utf-8 -""" -Amavis forms. -""" - -from django import forms -from django.utils.translation import ugettext as _ - -from modoboa.lib import parameters -from modoboa.lib.form_utils import InlineRadioSelect -from .models import Policy, Users - - -class DomainPolicyForm(forms.ModelForm): - - spam_subject_tag2_act = forms.BooleanField() - - class Meta: - model = Policy - fields = ('bypass_virus_checks', 'bypass_spam_checks', - 'spam_tag2_level', 'spam_subject_tag2', - 'spam_kill_level', 'bypass_banned_checks') - widgets = { - 'bypass_virus_checks': InlineRadioSelect(), - 'bypass_spam_checks': InlineRadioSelect(), - 'spam_tag2_level': forms.TextInput( - attrs={'class': 'form-control'}), - 'spam_kill_level': forms.TextInput( - attrs={'class': 'form-control'}), - 'spam_subject_tag2': forms.TextInput( - attrs={'class': 'form-control'}), - 'bypass_banned_checks': InlineRadioSelect(), - } - - def __init__(self, *args, **kwargs): - if "instance" in kwargs: - self.domain = kwargs["instance"] - try: - policy = Users.objects.get( - email="@%s" % self.domain.name).policy - kwargs["instance"] = policy - except (Users.DoesNotExist, Policy.DoesNotExist): - del kwargs["instance"] - super(DomainPolicyForm, self).__init__(*args, **kwargs) - for field in self.fields.keys(): - self.fields[field].required = False - - def save(self, user, commit=True): - policy = super(DomainPolicyForm, self).save(commit=False) - for field in ['bypass_spam_checks', 'bypass_virus_checks', - 'bypass_banned_checks']: - if getattr(policy, field) == '': - setattr(policy, field, None) - - if self.cleaned_data['spam_subject_tag2_act']: - policy.spam_subject_tag2 = None - - if commit: - policy.save() - try: - u = Users.objects.get(fullname=policy.policy_name) - except Users.DoesNotExist: - u = Users.objects.get(email="@%s" % self.domain.name) - u.policy = policy - policy.save() - return policy - - -class LearningRecipientForm(forms.Form): - - """A form to select the recipient of a learning request.""" - - recipient = forms.ChoiceField( - label=None, choices=[] - ) - ltype = forms.ChoiceField( - label="", choices=[("spam", "spam"), ("ham", "ham")], - widget=forms.widgets.HiddenInput - ) - selection = forms.CharField( - label="", widget=forms.widgets.HiddenInput) - - def __init__(self, user, *args, **kwargs): - """Constructor.""" - super(LearningRecipientForm, self).__init__(*args, **kwargs) - choices = [] - if user.group == "SuperAdmins": - choices.append(("global", _("Global database"))) - domain_level_learning = parameters.get_admin( - "DOMAIN_LEVEL_LEARNING") == "yes" - user_level_learning = parameters.get_admin( - "USER_LEVEL_LEARNING") == "yes" - if domain_level_learning: - choices.append(("domain", _("Domain's database"))) - if user_level_learning: - choices.append(("user", _("User's database"))) - self.fields["recipient"].choices = choices diff --git a/modoboa/extensions/amavis/general_callbacks.py b/modoboa/extensions/amavis/general_callbacks.py deleted file mode 100644 index 87bfd3115..000000000 --- a/modoboa/extensions/amavis/general_callbacks.py +++ /dev/null @@ -1,211 +0,0 @@ -"""General event callbacks.""" - -from django.core.urlresolvers import reverse -from django.utils.translation import ugettext as _ -from django.template import Template, Context - -from modoboa.lib import events, parameters -from modoboa.extensions.admin.models import DomainAlias -from modoboa.extensions.amavis.lib import ( - create_user_and_policy, update_user_and_policy, delete_user_and_policy, - create_user_and_use_policy, delete_user -) -from .lib import manual_learning_enabled -from .models import Policy, Users - - -def create_relay_domains_records(): - """Create records for existing relay domains.""" - from modoboa.extensions.postfix_relay_domains.models import RelayDomain - - for rdom in RelayDomain.objects.all(): - policy = create_user_and_policy("@{0}".format(rdom.name)) - for rdomalias in rdom.relaydomainalias_set.all(): - rdomalias_pattern = "@{0}".format(rdomalias.name) - create_user_and_use_policy(rdomalias_pattern, policy) - - -@events.observe("InitialDataLoaded") -def load_relay_domains_dependant_data(name): - """Load relay domains dependant data.""" - if name != "postfix_relay_domains": - return - create_relay_domains_records() - - -@events.observe("UserMenuDisplay") -def menu(target, user): - if target == "top_menu": - return [ - {"name": "quarantine", - "label": _("Quarantine"), - "url": reverse('amavis:index')} - ] - return [] - - -@events.observe("DomainCreated") -def on_domain_created(user, domain): - create_user_and_policy("@{0}".format(domain.name)) - - -@events.observe("DomainModified") -def on_domain_modified(domain): - update_user_and_policy( - "@{0}".format(domain.oldname), - "@{0}".format(domain.name) - ) - - -@events.observe("DomainDeleted") -def on_domain_deleted(domain): - delete_user_and_policy("@{0}".format(domain.name)) - - -@events.observe("DomainAliasCreated") -def on_domain_alias_created(user, domainalias): - create_user_and_use_policy( - "@{0}".format(domainalias.name), - "@{0}".format(domainalias.target.name) - ) - - -@events.observe("DomainAliasDeleted") -def on_domain_alias_deleted(domainaliases): - if isinstance(domainaliases, DomainAlias): - domainaliases = [domainaliases] - for domainalias in domainaliases: - delete_user("@{0}".format(domainalias.name)) - - -@events.observe("MailboxModified") -def on_mailbox_modified(mailbox): - """Update amavis records if address has changed.""" - if parameters.get_admin("MANUAL_LEARNING") == "no" or \ - not hasattr(mailbox, "old_full_address") or \ - mailbox.full_address == mailbox.old_full_address: - return - user = Users.objects.select_related.get(email=mailbox.old_full_address) - full_address = mailbox.full_address - user.email = full_address - user.policy.policy_name = full_address[:32] - user.policy.sa_username = full_address - user.policy.save() - user.save() - - -@events.observe("MailboxDeleted") -def on_mailbox_deleted(mailbox): - """Clean amavis database when a mailbox is removed.""" - if parameters.get_admin("MANUAL_LEARNING") == "no": - return - delete_user_and_policy("@{0}".format(mailbox.full_address)) - - -@events.observe("MailboxAliasCreated") -def on_mailboxalias_created(user, alias): - """Create amavis record for the new alias. - - FIXME: how to deal with distibution lists ? - """ - if not manual_learning_enabled(user) or alias.type != "alias": - return - try: - policy = Policy.objects.get( - policy_name=alias.mboxes.all()[0].full_address - ) - except Policy.DoesNotExist: - return - else: - email = alias.full_address - Users.objects.create( - email=email, policy=policy, fullname=email, priority=7 - ) - - -@events.observe("MailboxAliasDeleted") -def on_mailboxalias_deleted(alias): - """Clean amavis database when an alias is removed.""" - if parameters.get_admin("MANUAL_LEARNING") == "no": - return - if Users.objects.filter(email=alias.full_address).exists(): - Users.objects.delete(email=alias.full_address) - - -@events.observe("GetStaticContent") -def extra_static_content(caller, st_type, user): - if user.group == "SimpleUsers" or st_type != "js": - return [] - - if caller == 'domains': - tpl = Template(""" -""") - - return [tpl.render(Context({}))] - return [] - - -@events.observe("TopNotifications") -def check_for_pending_requests(request, include_all): - """ - Check if release requests are pending. - """ - from .sql_connector import get_connector - - if parameters.get_admin("USER_CAN_RELEASE") == "yes" \ - or request.user.group == "SimpleUsers": - return [] - - nbrequests = get_connector(user=request.user).get_pending_requests() - if not nbrequests: - return [{"id": "nbrequests", "counter": 0}] if include_all \ - else [] - - url = reverse("amavis:index") - url += "#listing/?viewrequests=1" - return [{ - "id": "nbrequests", "url": url, "text": _("Pending requests"), - "counter": nbrequests, "level": "danger" - }] - - -def send_amavis_form(): - """ - """ - from .forms import DomainPolicyForm - return [{ - 'id': 'amavis', 'title': _("Content filter"), 'cls': DomainPolicyForm, - 'formtpl': 'amavis/domain_content_filter.html' - }] - - -@events.observe("ExtraDomainForm") -def extra_domain_form(user, domain): - if not user.has_perm("admin.view_domains"): - return [] - return send_amavis_form() - - -@events.observe('ExtraRelayDomainForm') -def extra_relaydomain_form(user, rdomain): - if not user.has_perm("postfix_relay_domains.add_relaydomain"): - return [] - return send_amavis_form() - - -@events.observe("FillDomainInstances") -def fill_domain_instances(user, domain, instances): - if not user.has_perm("admin.view_domains"): - return - instances["amavis"] = domain - - -@events.observe("FillRelayDomainInstances") -def fill_relaydomain_instances(user, rdomain, instances): - if not user.has_perm("postfix_relay_domains.add_relaydomain"): - return - instances["amavis"] = rdomain diff --git a/modoboa/extensions/amavis/lib.py b/modoboa/extensions/amavis/lib.py deleted file mode 100644 index a1ab982e7..000000000 --- a/modoboa/extensions/amavis/lib.py +++ /dev/null @@ -1,345 +0,0 @@ -# coding: utf-8 - -from functools import wraps -import re -import socket -import string -import struct - -from django.utils.translation import ugettext as _ -from django.core.urlresolvers import reverse - -from modoboa.extensions.admin.models import Mailbox, Alias -from modoboa.lib import parameters -from modoboa.lib.email_utils import split_mailbox -from modoboa.lib.exceptions import InternalError -from modoboa.lib.sysutils import exec_cmd -from modoboa.lib.web_utils import NavigationParameters -from .models import Users, Policy - - -def selfservice(ssfunc=None): - """Decorator used to expose views to the 'self-service' feature - - The 'self-service' feature allows users to act on quarantined - messages without beeing authenticated. - - This decorator only acts as a 'router'. - - :param ssfunc: the function to call if the 'self-service' - pre-requisites are satisfied - """ - def decorator(f): - @wraps(f) - def wrapped_f(request, *args, **kwargs): - if request.user.is_authenticated(): - return f(request, *args, **kwargs) - if parameters.get_admin("SELF_SERVICE") == "no": - from django.contrib.auth.views import redirect_to_login - return redirect_to_login( - reverse("amavis:index") - ) - return ssfunc(request, *args, **kwargs) - return wrapped_f - return decorator - - -class AMrelease(object): - def __init__(self): - mode = parameters.get_admin("AM_PDP_MODE") - try: - if mode == "inet": - host = parameters.get_admin('AM_PDP_HOST') - port = parameters.get_admin('AM_PDP_PORT') - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.connect((host, int(port))) - else: - path = parameters.get_admin('AM_PDP_SOCKET') - self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - self.sock.connect(path) - except socket.error, err: - raise InternalError( - _("Connection to amavis failed: %s" % str(err)) - ) - - def decode(self, answer): - def repl(match): - return struct.pack("B", string.atoi(match.group(0)[1:], 16)) - - return re.sub(r"%([0-9a-fA-F]{2})", repl, answer) - - def __del__(self): - self.sock.close() - - def sendreq(self, mailid, secretid, recipient, *others): - self.sock.send("""request=release -mail_id=%s -secret_id=%s -quar_type=Q -recipient=%s - -""" % (mailid, secretid, recipient)) - answer = self.sock.recv(1024) - answer = self.decode(answer) - if re.search(r"250 [\d\.]+ Ok", answer): - return True - return False - - -class SpamassassinClient(object): - - """A stupid spamassassin client.""" - - def __init__(self, user, recipient_db): - """Constructor.""" - self._sa_is_local = parameters.get_admin("SA_IS_LOCAL") - self._default_username = parameters.get_admin("DEFAULT_USER") - self._recipient_db = recipient_db - self._setup_cache = {} - self._username_cache = [] - if user.group == "SimpleUsers": - user_level_learning = parameters.get_admin("USER_LEVEL_LEARNING") - if user_level_learning == "yes": - self._username = user.email - else: - self._username = None - self.error = None - if self._sa_is_local == "yes": - self._learn_cmd = "sa-learn --{0} --no-sync -u {1}" - self._learn_cmd_kwargs = {} - self._expected_exit_codes = [0] - else: - self._learn_cmd = "spamc -d {0} -p {1}".format( - parameters.get_admin("SPAMD_ADDRESS"), - parameters.get_admin("SPAMD_PORT") - ) - self._learn_cmd += " -L {0} -u {1}" - self._learn_cmd_kwargs = {} - self._expected_exit_codes = [5, 6] - - def _get_mailbox_from_rcpt(self, rcpt): - """Retrieve a mailbox from a recipient address.""" - local_part, domname = split_mailbox(rcpt) - try: - mailbox = Mailbox.objects.select_related("domain").get( - address=local_part, domain__name=domname) - except Mailbox.DoesNotExist: - try: - alias = Alias.objects.select_related("domain").get( - address=local_part, domain__name=domname) - except Alias.DoesNotExist: - raise InternalError(_("No recipient found")) - if alias.type != "alias": - return None - mailbox = alias.mboxes.all()[0] - return mailbox - - def _learn(self, rcpt, msg, mtype): - """Internal method to call the learning command.""" - if self._username is None: - if self._recipient_db == "global": - username = self._default_username - else: - mbox = self._get_mailbox_from_rcpt(rcpt) - if mbox is None: - username = self._default_username - if self._recipient_db == "domain": - username = mbox.domain.name - if username not in self._setup_cache and \ - setup_manual_learning_for_domain(mbox.domain): - self._setup_cache[username] = True - else: - username = mbox.full_address - if username not in self._setup_cache and \ - setup_manual_learning_for_mbox(mbox): - self._setup_cache[username] = True - else: - username = self._username - if username not in self._username_cache: - self._username_cache.append(username) - cmd = self._learn_cmd.format(mtype, username) - code, output = exec_cmd(cmd, pinput=msg, **self._learn_cmd_kwargs) - if code in self._expected_exit_codes: - return True - self.error = output - return False - - def learn_spam(self, rcpt, msg): - """Learn new spam.""" - return self._learn(rcpt, msg, "spam") - - def learn_ham(self, rcpt, msg): - """Learn new ham.""" - return self._learn(rcpt, msg, "ham") - - def done(self): - """Call this method at the end of the processing.""" - if self._sa_is_local: - for username in self._username_cache: - exec_cmd("sa-learn -u {0} --sync".format(username), - **self._learn_cmd_kwargs) - - -class QuarantineNavigationParameters(NavigationParameters): - """ - Specific NavigationParameters subclass for the quarantine. - """ - def __init__(self, request): - super(QuarantineNavigationParameters, self).__init__( - request, 'quarantine_navparams' - ) - self.parameters += [ - ('pattern', '', False), - ('criteria', 'from_addr', False), - ('msgtype', None, False), - ('viewrequests', None, False) - ] - - def _store_page(self): - """Specific method to store the current page.""" - if self.request.GET.get("reset_page", None) or "page" not in self: - self["page"] = 1 - else: - page = self.request.GET.get("page", None) - if page is not None: - self["page"] = int(page) - - def back_to_listing(self): - """Return the current listing URL. - - Looks into the user's session and the current request to build - the URL. - - :return: a string - """ - url = "listing" - params = [] - navparams = self.request.session[self.sessionkey] - if "page" in navparams: - params += ["page=%s" % navparams["page"]] - if "order" in navparams: - params += ["sort_order=%s" % navparams["order"]] - params += ["%s=%s" % (p[0], navparams[p[0]]) - for p in self.parameters if p[0] in navparams] - if params: - url += "?%s" % ("&".join(params)) - return url - - -def create_user_and_policy(name, priority=7): - """Create records. - - Create two records (a user and a policy) using :keyword:`name` as - an identifier. - - :param str name: name - :return: the new ``Policy`` object - """ - if Users.objects.filter(email=name).exists(): - return Policy.objects.get(policy_name=name[:32]) - policy = Policy.objects.create(policy_name=name[:32]) - Users.objects.create( - email=name, fullname=name, priority=priority, policy=policy - ) - return policy - - -def create_user_and_use_policy(name, policy, priority=7): - """Create a *users* record and use an existing policy. - - :param str name: user record name - :param str policy: string or Policy instance - """ - if isinstance(policy, basestring): - policy = Policy.objects.get(policy_name=policy[:32]) - Users.objects.get_or_create( - email=name, fullname=name, priority=priority, policy=policy - ) - - -def update_user_and_policy(oldname, newname): - """Update records. - - :param str oldname: old name - :param str newname: new name - """ - if oldname == newname: - return - u = Users.objects.get(email=oldname) - u.email = newname - u.fullname = newname - u.policy.policy_name = newname[:32] - u.policy.save() - u.save() - - -def delete_user_and_policy(name): - """Delete records. - - :param str name: identifier - """ - try: - u = Users.objects.get(email=name) - except Users.DoesNotExist: - return - u.policy.delete() - u.delete() - - -def delete_user(name): - """Delete a *users* record. - - :param str name: user record name - """ - try: - Users.objects.get(email=name).delete() - except Users.DoesNotExist: - pass - - -def manual_learning_enabled(user): - """Check if manual learning is enabled or not. - - Also check for :kw:`user` if necessary. - - :return: True if learning is enabled, False otherwise. - """ - manual_learning = parameters.get_admin("MANUAL_LEARNING") == "yes" - if manual_learning and user.group != 'SuperAdmins': - domain_level_learning = parameters.get_admin( - "DOMAIN_LEVEL_LEARNING") == "yes" - user_level_learning = parameters.get_admin( - "USER_LEVEL_LEARNING") == "yes" - if user.has_perm("admin.view_domains"): - manual_learning = domain_level_learning or user_level_learning - else: - manual_learning = user_level_learning - return manual_learning - - -def setup_manual_learning_for_domain(domain): - """Setup manual learning if necessary. - - :return: True if learning has been setup, False otherwise - """ - if Policy.objects.filter(sa_username=domain.name).exists(): - return False - policy = Policy.objects.get(policy_name=domain.name[:32]) - policy.sa_username = domain.name - policy.save() - return True - - -def setup_manual_learning_for_mbox(mbox): - """Setup manual learning if necessary. - - :return: True if learning has been setup, False otherwise - """ - result = False - pname = mbox.full_address[:32] - if not Policy.objects.filter(policy_name=pname).exists(): - policy = create_user_and_policy(pname) - for alias in mbox.alias_addresses: - create_user_and_use_policy(alias, policy) - result = True - return result diff --git a/modoboa/extensions/amavis/management/__init__.py b/modoboa/extensions/amavis/management/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modoboa/extensions/amavis/management/commands/__init__.py b/modoboa/extensions/amavis/management/commands/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modoboa/extensions/amavis/management/commands/amnotify.py b/modoboa/extensions/amavis/management/commands/amnotify.py deleted file mode 100644 index 209350da3..000000000 --- a/modoboa/extensions/amavis/management/commands/amnotify.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 -from optparse import make_option - -from django.core.management.base import BaseCommand, CommandError -from django.template.loader import render_to_string -from django.core.urlresolvers import reverse -from django.utils.translation import ugettext as _ - -from modoboa.core.management.commands import CloseConnectionMixin -from modoboa.core.models import User -from modoboa.lib import parameters -from modoboa.lib.email_utils import sendmail_simple -from modoboa.extensions.admin.models import Domain - -from modoboa.extensions.amavis.models import ( - Msgrcpt -) -from modoboa.extensions.amavis.sql_connector import get_connector - - -class Command(BaseCommand, CloseConnectionMixin): - help = 'Amavis notification tool' - - sender = None - baseurl = None - listingurl = None - - option_list = BaseCommand.option_list + ( - make_option("--baseurl", type="string", default=None, - help="The scheme and hostname used to access Modoboa"), - make_option("--smtp_host", type="string", default="localhost", - help="The address of the SMTP server used to send notifications"), - make_option("--smtp_port", type="int", default=25, - help="The listening port of the SMTP server used to send notifications"), - make_option("--verbose", action="store_true", - help="Activate verbose mode") - ) - - def handle(self, *args, **options): - if options["baseurl"] is None: - raise CommandError("You must provide the --baseurl option") - self.options = options - self.notify_admins_pending_requests() - - def send_pr_notification(self, rcpt, reqs): - if self.options["verbose"]: - print "Sending notification to %s" % rcpt - total = reqs.count() - reqs = reqs.all()[:10] - content = render_to_string( - "amavis/notifications/pending_requests.html", dict( - total=total, requests=reqs, - baseurl=self.baseurl, listingurl=self.listingurl - ) - ) - status, msg = sendmail_simple( - self.sender, rcpt, - subject=_("[modoboa] Pending release requests"), - content=content, - server=self.options["smtp_host"], - port=self.options["smtp_port"] - ) - if not status: - print msg - - def notify_admins_pending_requests(self): - self.sender = parameters.get_admin("NOTIFICATIONS_SENDER", - app="amavis") - self.baseurl = self.options["baseurl"].strip("/") - self.listingurl = self.baseurl \ - + reverse("amavis:_mail_list") \ - + "?viewrequests=1" - - for da in User.objects.filter(groups__name="DomainAdmins"): - if not da.mailbox_set.count(): - continue - rcpt = da.mailbox_set.all()[0].full_address - reqs = get_connector().get_domains_pending_requests( - Domain.objects.get_for_admin(da) - ) - if reqs.count(): - self.send_pr_notification(rcpt, reqs) - - reqs = Msgrcpt.objects.filter(rs='p') - if not reqs.count(): - if self.options["verbose"]: - print "No release request currently pending" - return - for su in User.objects.filter(is_superuser=True): - if not su.mailbox_set.count(): - continue - rcpt = su.mailbox_set.all()[0].full_address - self.send_pr_notification(rcpt, reqs) diff --git a/modoboa/extensions/amavis/management/commands/qcleanup.py b/modoboa/extensions/amavis/management/commands/qcleanup.py deleted file mode 100644 index 47e2d6dce..000000000 --- a/modoboa/extensions/amavis/management/commands/qcleanup.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -import time -from optparse import make_option - -from django.core.management.base import BaseCommand - -from modoboa.core.management.commands import CloseConnectionMixin -from modoboa.extensions.amavis.models import ( - Msgrcpt, Msgs, Maddr -) -from modoboa.lib import parameters - - -class Command(BaseCommand, CloseConnectionMixin): - args = '' - help = 'Amavis quarantine cleanup' - - option_list = BaseCommand.option_list + ( - make_option('--debug', - action='store_true', - default=False, - help='Activate debug output'), - make_option('--verbose', - action='store_true', - default=False, - help='Display informational messages') - ) - - def __vprint(self, msg): - if not self.verbose: - return - print msg - - def handle(self, *args, **options): - if options["debug"]: - import logging - l = logging.getLogger("django.db.backends") - l.setLevel(logging.DEBUG) - l.addHandler(logging.StreamHandler()) - self.verbose = options["verbose"] - - max_messages_age = int(parameters.get_admin("MAX_MESSAGES_AGE", - app="amavis")) - - flags = ['D'] - if parameters.get_admin("RELEASED_MSGS_CLEANUP", - app="amavis") == "yes": - flags += ['R'] - - self.__vprint("Deleting marked messages...") - ids = Msgrcpt.objects.filter(rs__in=flags).values("mail_id").distinct() - for msg in Msgs.objects.filter(mail_id__in=ids): - if not msg.msgrcpt_set.exclude(rs__in=flags).count(): - msg.delete() - - self.__vprint( - "Deleting messages older than %d days..." % max_messages_age) - limit = int(time.time()) - (max_messages_age * 24 * 3600) - Msgs.objects.filter(time_num__lt=limit).delete() - - self.__vprint("Deleting unreferenced e-mail addresses...") - for maddr in Maddr.objects.all(): - if not maddr.msgs_set.count() and not maddr.msgrcpt_set.count(): - maddr.delete() - - self.__vprint("Done.") diff --git a/modoboa/extensions/amavis/migrations/0001_initial.py b/modoboa/extensions/amavis/migrations/0001_initial.py deleted file mode 100644 index dea55f387..000000000 --- a/modoboa/extensions/amavis/migrations/0001_initial.py +++ /dev/null @@ -1,187 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Maddr', - fields=[ - ('partition_tag', models.IntegerField(unique=True, null=True, blank=True)), - ('id', models.BigIntegerField(serialize=False, primary_key=True)), - ('email', models.CharField(unique=True, max_length=255)), - ('domain', models.CharField(max_length=765)), - ], - options={ - 'db_table': 'maddr', - 'managed': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Mailaddr', - fields=[ - ('id', models.IntegerField(serialize=False, primary_key=True)), - ('priority', models.IntegerField()), - ('email', models.CharField(unique=True, max_length=255)), - ], - options={ - 'db_table': 'mailaddr', - 'managed': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Msgs', - fields=[ - ('partition_tag', models.IntegerField(null=True, blank=True)), - ('mail_id', models.CharField(max_length=12, serialize=False, primary_key=True)), - ('secret_id', models.CharField(max_length=12, blank=True)), - ('am_id', models.CharField(max_length=60)), - ('time_num', models.IntegerField()), - ('time_iso', models.CharField(max_length=48)), - ('policy', models.CharField(max_length=765, blank=True)), - ('client_addr', models.CharField(max_length=765, blank=True)), - ('size', models.IntegerField()), - ('originating', models.CharField(max_length=3)), - ('content', models.CharField(max_length=1, blank=True)), - ('quar_type', models.CharField(max_length=1, blank=True)), - ('quar_loc', models.CharField(max_length=255, blank=True)), - ('dsn_sent', models.CharField(max_length=3, blank=True)), - ('spam_level', models.FloatField(null=True, blank=True)), - ('message_id', models.CharField(max_length=765, blank=True)), - ('from_addr', models.CharField(max_length=765, blank=True)), - ('subject', models.CharField(max_length=765, blank=True)), - ('host', models.CharField(max_length=765)), - ], - options={ - 'db_table': 'msgs', - 'managed': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Msgrcpt', - fields=[ - ('partition_tag', models.IntegerField(null=True, blank=True)), - ('mail', models.ForeignKey(primary_key=True, serialize=False, to='amavis.Msgs')), - ('rseqnum', models.IntegerField(default=0)), - ('is_local', models.CharField(max_length=3)), - ('content', models.CharField(max_length=3)), - ('ds', models.CharField(max_length=3)), - ('rs', models.CharField(max_length=3)), - ('bl', models.CharField(max_length=3, blank=True)), - ('wl', models.CharField(max_length=3, blank=True)), - ('bspam_level', models.FloatField(null=True, blank=True)), - ('smtp_resp', models.CharField(max_length=765, blank=True)), - ], - options={ - 'db_table': 'msgrcpt', - 'managed': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Policy', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('policy_name', models.CharField(max_length=32, blank=True)), - ('virus_lover', models.CharField(max_length=3, null=True, blank=True)), - ('spam_lover', models.CharField(max_length=3, null=True, blank=True)), - ('unchecked_lover', models.CharField(max_length=3, null=True, blank=True)), - ('banned_files_lover', models.CharField(max_length=3, null=True, blank=True)), - ('bad_header_lover', models.CharField(max_length=3, null=True, blank=True)), - ('bypass_virus_checks', models.CharField(default=b'', choices=[(b'N', 'yes'), (b'Y', 'no'), (b'', 'default')], max_length=3, help_text="Bypass virus checks or not. Choose 'default' to use global settings.", null=True, verbose_name='Virus filter')), - ('bypass_spam_checks', models.CharField(default=b'', choices=[(b'N', 'yes'), (b'Y', 'no'), (b'', 'default')], max_length=3, help_text="Bypass spam checks or not. Choose 'default' to use global settings.", null=True, verbose_name='Spam filter')), - ('bypass_banned_checks', models.CharField(default=b'', choices=[(b'N', 'yes'), (b'Y', 'no'), (b'', 'default')], max_length=3, help_text="Bypass banned checks or not. Choose 'default' to use global settings.", null=True, verbose_name='Banned filter')), - ('bypass_header_checks', models.CharField(max_length=3, null=True, blank=True)), - ('virus_quarantine_to', models.CharField(max_length=192, null=True, blank=True)), - ('spam_quarantine_to', models.CharField(max_length=192, null=True, blank=True)), - ('banned_quarantine_to', models.CharField(max_length=192, null=True, blank=True)), - ('unchecked_quarantine_to', models.CharField(max_length=192, null=True, blank=True)), - ('bad_header_quarantine_to', models.CharField(max_length=192, null=True, blank=True)), - ('clean_quarantine_to', models.CharField(max_length=192, null=True, blank=True)), - ('archive_quarantine_to', models.CharField(max_length=192, null=True, blank=True)), - ('spam_tag_level', models.FloatField(null=True, blank=True)), - ('spam_tag2_level', models.FloatField(null=True, blank=True)), - ('spam_tag3_level', models.FloatField(null=True, blank=True)), - ('spam_kill_level', models.FloatField(null=True, blank=True)), - ('spam_dsn_cutoff_level', models.FloatField(null=True, blank=True)), - ('spam_quarantine_cutoff_level', models.FloatField(null=True, blank=True)), - ('addr_extension_virus', models.CharField(max_length=192, null=True, blank=True)), - ('addr_extension_spam', models.CharField(max_length=192, null=True, blank=True)), - ('addr_extension_banned', models.CharField(max_length=192, null=True, blank=True)), - ('addr_extension_bad_header', models.CharField(max_length=192, null=True, blank=True)), - ('warnvirusrecip', models.CharField(max_length=3, null=True, blank=True)), - ('warnbannedrecip', models.CharField(max_length=3, null=True, blank=True)), - ('warnbadhrecip', models.CharField(max_length=3, null=True, blank=True)), - ('newvirus_admin', models.CharField(max_length=192, null=True, blank=True)), - ('virus_admin', models.CharField(max_length=192, null=True, blank=True)), - ('banned_admin', models.CharField(max_length=192, null=True, blank=True)), - ('bad_header_admin', models.CharField(max_length=192, null=True, blank=True)), - ('spam_admin', models.CharField(max_length=192, null=True, blank=True)), - ('spam_subject_tag', models.CharField(max_length=192, null=True, blank=True)), - ('spam_subject_tag2', models.CharField(default=None, max_length=192, blank=True, help_text="Modify spam subject using the specified text. Choose 'default' to use global settings.", null=True, verbose_name='Spam marker')), - ('spam_subject_tag3', models.CharField(max_length=192, null=True, blank=True)), - ('message_size_limit', models.IntegerField(null=True, blank=True)), - ('banned_rulenames', models.CharField(max_length=192, null=True, blank=True)), - ('disclaimer_options', models.CharField(max_length=192, null=True, blank=True)), - ('forward_method', models.CharField(max_length=192, null=True, blank=True)), - ('sa_userconf', models.CharField(max_length=192, null=True, blank=True)), - ('sa_username', models.CharField(max_length=192, null=True, blank=True)), - ], - options={ - 'db_table': 'policy', - 'managed': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Quarantine', - fields=[ - ('partition_tag', models.IntegerField(null=True, blank=True)), - ('mail', models.ForeignKey(primary_key=True, serialize=False, to='amavis.Msgs')), - ('chunk_ind', models.IntegerField()), - ('mail_text', models.TextField()), - ], - options={ - 'ordering': ['-mail__time_num'], - 'db_table': 'quarantine', - 'managed': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Users', - fields=[ - ('id', models.AutoField(serialize=False, primary_key=True)), - ('priority', models.IntegerField()), - ('email', models.CharField(unique=True, max_length=255)), - ('fullname', models.CharField(max_length=765, blank=True)), - ], - options={ - 'db_table': 'users', - 'managed': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Wblist', - fields=[ - ('rid', models.IntegerField(serialize=False, primary_key=True)), - ('sid', models.IntegerField(primary_key=True)), - ('wb', models.CharField(max_length=30)), - ], - options={ - 'db_table': 'wblist', - 'managed': False, - }, - bases=(models.Model,), - ), - ] diff --git a/modoboa/extensions/amavis/migrations/__init__.py b/modoboa/extensions/amavis/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modoboa/extensions/amavis/models.py b/modoboa/extensions/amavis/models.py deleted file mode 100644 index 3a9876484..000000000 --- a/modoboa/extensions/amavis/models.py +++ /dev/null @@ -1,236 +0,0 @@ -# This is an auto-generated Django model module. -# You'll have to do the following manually to clean this up: -# * Rearrange models' order -# * Make sure each model has one field with primary_key=True -# Feel free to rename the models, but don't rename db_table values -# or field names. -# -# Also note: You'll have to insert the output of -# 'django-admin.py sqlcustom [appname]'into your database. -# -# Original Amavis version : 2.6.2 - -from django.db import models -from django.utils.translation import ugettext_lazy - - -class Maddr(models.Model): - partition_tag = models.IntegerField(unique=True, null=True, blank=True) - id = models.BigIntegerField(primary_key=True) - email = models.CharField(unique=True, max_length=255) - domain = models.CharField(max_length=765) - - class Meta: - db_table = u'maddr' - managed = False - - -class Mailaddr(models.Model): - id = models.IntegerField(primary_key=True) - priority = models.IntegerField() - email = models.CharField(unique=True, max_length=255) - - class Meta: - db_table = u'mailaddr' - managed = False - - -class Msgs(models.Model): - partition_tag = models.IntegerField(null=True, blank=True) - mail_id = models.CharField(max_length=12, primary_key=True) - secret_id = models.CharField(max_length=12, blank=True) - am_id = models.CharField(max_length=60) - time_num = models.IntegerField() - time_iso = models.CharField(max_length=48) - sid = models.ForeignKey(Maddr, db_column='sid') - policy = models.CharField(max_length=765, blank=True) - client_addr = models.CharField(max_length=765, blank=True) - size = models.IntegerField() - originating = models.CharField(max_length=3) - content = models.CharField(max_length=1, blank=True) - quar_type = models.CharField(max_length=1, blank=True) - quar_loc = models.CharField(max_length=255, blank=True) - dsn_sent = models.CharField(max_length=3, blank=True) - spam_level = models.FloatField(null=True, blank=True) - message_id = models.CharField(max_length=765, blank=True) - from_addr = models.CharField(max_length=765, blank=True) - subject = models.CharField(max_length=765, blank=True) - host = models.CharField(max_length=765) - - class Meta: - db_table = u'msgs' - managed = False - unique_together = ('partition_tag', 'mail_id') - - -class Msgrcpt(models.Model): - partition_tag = models.IntegerField(null=True, blank=True) - mail = models.ForeignKey(Msgs, primary_key=True) - rid = models.ForeignKey(Maddr, db_column='rid') - rseqnum = models.IntegerField(default=0) - is_local = models.CharField(max_length=3) - content = models.CharField(max_length=3) - ds = models.CharField(max_length=3) - rs = models.CharField(max_length=3) - bl = models.CharField(max_length=3, blank=True) - wl = models.CharField(max_length=3, blank=True) - bspam_level = models.FloatField(null=True, blank=True) - smtp_resp = models.CharField(max_length=765, blank=True) - - class Meta: - db_table = u'msgrcpt' - managed = False - unique_together = ("partition_tag", "mail", "rseqnum") - - -class Policy(models.Model): - policy_name = models.CharField(max_length=32, blank=True) - virus_lover = models.CharField(max_length=3, blank=True, null=True) - spam_lover = models.CharField(max_length=3, blank=True, null=True) - unchecked_lover = models.CharField(max_length=3, blank=True, null=True) - banned_files_lover = models.CharField(max_length=3, blank=True, null=True) - bad_header_lover = models.CharField(max_length=3, blank=True, null=True) - bypass_virus_checks = models.CharField( - ugettext_lazy("Virus filter"), default='', null=True, - choices=(('N', ugettext_lazy('yes')), - ('Y', ugettext_lazy('no')), - ('', ugettext_lazy('default'))), - max_length=3, - help_text=ugettext_lazy( - "Bypass virus checks or not. Choose 'default' to use global " - "settings." - ) - ) - bypass_spam_checks = models.CharField( - ugettext_lazy("Spam filter"), default='', null=True, - choices=(('N', ugettext_lazy('yes')), - ('Y', ugettext_lazy('no')), - ('', ugettext_lazy('default'))), - max_length=3, - help_text=ugettext_lazy( - "Bypass spam checks or not. Choose 'default' to use global " - "settings." - ) - ) - bypass_banned_checks = models.CharField( - ugettext_lazy("Banned filter"), default='', null=True, - choices=(('N', ugettext_lazy('yes')), - ('Y', ugettext_lazy('no')), - ('', ugettext_lazy('default'))), - max_length=3, - help_text=ugettext_lazy( - "Bypass banned checks or not. Choose 'default' to use global " - "settings." - ) - ) - bypass_header_checks = models.CharField(max_length=3, blank=True, null=True) - virus_quarantine_to = models.CharField( - max_length=192, - blank=True, - null=True) - spam_quarantine_to = models.CharField(max_length=192, blank=True, null=True) - banned_quarantine_to = models.CharField( - max_length=192, - blank=True, - null=True) - unchecked_quarantine_to = models.CharField( - max_length=192, - blank=True, - null=True) - bad_header_quarantine_to = models.CharField( - max_length=192, - blank=True, - null=True) - clean_quarantine_to = models.CharField( - max_length=192, - blank=True, - null=True) - archive_quarantine_to = models.CharField( - max_length=192, - blank=True, - null=True) - spam_tag_level = models.FloatField(null=True, blank=True) - spam_tag2_level = models.FloatField(null=True, blank=True) - spam_tag3_level = models.FloatField(null=True, blank=True) - spam_kill_level = models.FloatField(null=True, blank=True) - spam_dsn_cutoff_level = models.FloatField(null=True, blank=True) - spam_quarantine_cutoff_level = models.FloatField(null=True, blank=True) - addr_extension_virus = models.CharField( - max_length=192, - blank=True, - null=True) - addr_extension_spam = models.CharField( - max_length=192, - blank=True, - null=True) - addr_extension_banned = models.CharField( - max_length=192, - blank=True, - null=True) - addr_extension_bad_header = models.CharField( - max_length=192, - blank=True, - null=True) - warnvirusrecip = models.CharField(max_length=3, blank=True, null=True) - warnbannedrecip = models.CharField(max_length=3, blank=True, null=True) - warnbadhrecip = models.CharField(max_length=3, blank=True, null=True) - newvirus_admin = models.CharField(max_length=192, blank=True, null=True) - virus_admin = models.CharField(max_length=192, blank=True, null=True) - banned_admin = models.CharField(max_length=192, blank=True, null=True) - bad_header_admin = models.CharField(max_length=192, blank=True, null=True) - spam_admin = models.CharField(max_length=192, blank=True, null=True) - spam_subject_tag = models.CharField(max_length=192, blank=True, null=True) - spam_subject_tag2 = models.CharField( - ugettext_lazy("Spam marker"), default=None, - max_length=192, blank=True, null=True, - help_text=ugettext_lazy( - "Modify spam subject using the specified text. Choose 'default' to " - "use global settings." - ) - ) - spam_subject_tag3 = models.CharField(max_length=192, blank=True, null=True) - message_size_limit = models.IntegerField(null=True, blank=True) - banned_rulenames = models.CharField(max_length=192, blank=True, null=True) - disclaimer_options = models.CharField(max_length=192, blank=True, null=True) - forward_method = models.CharField(max_length=192, blank=True, null=True) - sa_userconf = models.CharField(max_length=192, blank=True, null=True) - sa_username = models.CharField(max_length=192, blank=True, null=True) - - class Meta: - db_table = u'policy' - managed = False - - -class Quarantine(models.Model): - partition_tag = models.IntegerField(null=True, blank=True) - mail = models.ForeignKey(Msgs, primary_key=True) - chunk_ind = models.IntegerField() - mail_text = models.BinaryField() - - class Meta: - db_table = u'quarantine' - managed = False - ordering = ["-mail__time_num"] - unique_together = ("partition_tag", "mail", "chunk_ind") - - -class Users(models.Model): - id = models.AutoField(primary_key=True) - priority = models.IntegerField() - policy = models.ForeignKey(Policy) - email = models.CharField(unique=True, max_length=255) - fullname = models.CharField(max_length=765, blank=True) - - class Meta: - db_table = u'users' - managed = False - - -class Wblist(models.Model): - rid = models.IntegerField(primary_key=True) - sid = models.IntegerField(primary_key=True) - wb = models.CharField(max_length=30) - - class Meta: - db_table = u'wblist' - managed = False diff --git a/modoboa/extensions/amavis/modo_extension.py b/modoboa/extensions/amavis/modo_extension.py deleted file mode 100644 index 81b287a44..000000000 --- a/modoboa/extensions/amavis/modo_extension.py +++ /dev/null @@ -1,52 +0,0 @@ -# coding: utf-8 -""" -Amavis management frontend. - -Provides: - -* SQL quarantine management -* Per-domain settings - -""" - -from django.utils.translation import ugettext_lazy - -from modoboa.core.extensions import ModoExtension, exts_pool -from modoboa.extensions.admin.models import Domain -from modoboa.lib import parameters - -from . import general_callbacks -from .lib import create_user_and_policy, create_user_and_use_policy - - -class Amavis(ModoExtension): - - """The Amavis extension.""" - - name = "amavis" - label = "Amavis frontend" - version = "1.0" - description = ugettext_lazy("Simple amavis management frontend") - url = "quarantine" - available_for_topredirection = True - - def load(self): - from .app_settings import ParametersForm, UserSettings - - parameters.register(ParametersForm, "Amavis") - parameters.register(UserSettings, ugettext_lazy("Quarantine")) - - def load_initial_data(self): - """Create records for existing domains and co.""" - for dom in Domain.objects.all(): - policy = create_user_and_policy("@{0}".format(dom.name)) - for domalias in dom.domainalias_set.all(): - domalias_pattern = "@{0}".format(domalias.name) - create_user_and_use_policy(domalias_pattern, policy) - - if not exts_pool.is_extension_installed( - "modoboa.extensions.postfix_relay_domains"): - return - general_callbacks.create_relay_domains_records() - -exts_pool.register_extension(Amavis) diff --git a/modoboa/extensions/amavis/sql_connector.py b/modoboa/extensions/amavis/sql_connector.py deleted file mode 100644 index e21038d59..000000000 --- a/modoboa/extensions/amavis/sql_connector.py +++ /dev/null @@ -1,312 +0,0 @@ -""" -SQL connector module. -""" - -import datetime - -from django.db.models import Q - -from modoboa.extensions.admin.models import Domain -from modoboa.lib.db_utils import db_type - -from .models import Quarantine, Msgrcpt, Maddr - - -class SQLconnector(object): - - """ - This class handles all database operations. - """ - - ORDER_TRANSLATION_TABLE = { - "type": "content", - "score": "bspam_level", - "date": "mail__time_num", - "subject": "mail__subject", - "from": "mail__from_addr", - "to": "rid__email" - } - - QUARANTINE_FIELDS = [ - "content", - "bspam_level", - "rs", - "rid__email", - "mail__from_addr", - "mail__subject", - "mail__mail_id", - "mail__time_num", - ] - - def __init__(self, user=None, navparams=None): - """Constructor.""" - self.user = user - self.navparams = navparams - self.messages = None - - self._messages_count = None - - def _exec(self, query, args): - """Execute a raw SQL query. - - :param string query: query to execute - :param list args: a list of arguments to replace in :kw:`query` - """ - from django.db import connections, transaction - - cursor = connections['amavis'].cursor() - cursor.execute(query, args) - transaction.commit_unless_managed(using='amavis') - - def _apply_msgrcpt_filters(self, flt): - """Apply filters based on user's role. - - """ - if self.user.group == 'SimpleUsers': - rcpts = [self.user.email] - if self.user.mailbox_set.count(): - rcpts += self.user.mailbox_set.all()[0].alias_addresses - return flt & Q(rid__email__in=rcpts) - elif not self.user.is_superuser: - doms = Domain.objects.get_for_admin(self.user) - regexp = "(%s)" % '|'.join([dom.name for dom in doms]) - return flt & Q(rid__email__regex=regexp) - return flt - - def _apply_extra_search_filter(self, crit, pattern): - """Apply search filters using additional criterias. - - """ - if crit == "to": - return Q(rid__email__contains=pattern) - return None - - def _apply_extra_select_filters(self, messages): - """Just a hook to apply additional filters to the queryset. - - """ - return messages - - def _get_quarantine_content(self): - """Fetch quarantine content. - - Filters: rs, rid, content - """ - flt = Q(rs__in=[' ', 'V', 'R', 'p', 'S', 'H']) \ - if self.navparams.get('viewrequests', '0') != '1' else Q(rs='p') - flt = self._apply_msgrcpt_filters(flt) - pattern = self.navparams.get("pattern", "") - if pattern: - criteria = self.navparams.get('criteria') - if criteria == "both": - criteria = "from_addr,subject,to" - search_flt = None - for crit in criteria.split(","): - if crit == "from_addr": - nfilter = Q(mail__from_addr__contains=pattern) - elif crit == "subject": - nfilter = Q(mail__subject__contains=pattern) - else: - nfilter = self._apply_extra_search_filter(crit, pattern) - if nfilter is None: - continue - search_flt = nfilter \ - if search_flt is None else search_flt | nfilter - if search_flt: - flt &= search_flt - msgtype = self.navparams.get('msgtype', None) - if msgtype is not None: - flt &= Q(content=msgtype) - - flt &= Q( - mail__in=Quarantine.objects.filter(chunk_ind=1).values("mail_id") - ) - - messages = Msgrcpt.objects.select_related("mail", "rid").filter(flt) - messages = self._apply_extra_select_filters(messages) - return messages - - def messages_count(self): - """Return the total number of messages living in the quarantine. - - We also store the built queryset for a later use. - """ - if self.user is None or self.navparams is None: - return None - if self._messages_count is None: - self.messages = self._get_quarantine_content() - self.messages = self.messages.values(*self.QUARANTINE_FIELDS) - - order = self.navparams.get("order") - if order is not None: - sign = "" - if order[0] == "-": - sign = "-" - order = order[1:] - order = self.ORDER_TRANSLATION_TABLE[order] - self.messages = self.messages.order_by(sign + order) - - self._messages_count = self.messages.count() - - return self._messages_count - - def fetch(self, start=None, stop=None): - """Fetch a range of messages from the internal cache. - - """ - emails = [] - for qm in self.messages[start - 1:stop]: - if qm["rs"] == 'D': - continue - m = { - "from": qm["mail__from_addr"], - "to": qm["rid__email"], - "subject": qm["mail__subject"], - "mailid": qm["mail__mail_id"], - "date": datetime.datetime.fromtimestamp(qm["mail__time_num"]), - "type": qm["content"], - "score": qm["bspam_level"], - "status": qm["rs"] - } - if qm["rs"] in ['', ' ']: - m["class"] = "unseen" - elif qm["rs"] == 'p': - m["class"] = "pending" - emails.append(m) - return emails - - def get_recipient_message(self, address, mailid): - """Retrieve a message for a given recipient. - """ - return Msgrcpt.objects.get(mail=mailid, rid__email=address) - - def set_msgrcpt_status(self, address, mailid, status): - """Change the status (rs field) of a message recipient. - - :param string status: status - """ - addr = Maddr.objects.get(email=address) - self._exec( - "UPDATE msgrcpt SET rs=%s WHERE mail_id=%s AND rid=%s", - [status, mailid, addr.id] - ) - - def get_domains_pending_requests(self, domains): - """Retrieve pending release requests for a list of domains. - """ - regexp = "(%s)" % '|'.join([dom.name for dom in domains]) - return Msgrcpt.objects.filter(rs='p', rid__email__regex=regexp) - - def get_pending_requests(self): - """Return the number of requests currently pending.""" - rq = Q(rs='p') - if not self.user.is_superuser: - doms = Domain.objects.get_for_admin(self.user) - if not doms.count(): - return 0 - regexp = "(%s)" % '|'.join([dom.name for dom in doms]) - doms_q = Q(rid__email__regex=regexp) - rq &= doms_q - return Msgrcpt.objects.filter(rq).count() - - def get_mail_content(self, mailid): - """Retrieve the content of a message.""" - content = "".join([ - str(qmail.mail_text) - for qmail in Quarantine.objects.filter(mail=mailid) - ]) - if isinstance(content, unicode): - content = content.encode("utf-8") - return content - - -class PgSQLconnector(SQLconnector): - - """ - The postgres version. - - Make use of ``QuerySet.extra`` and postgres ``convert_from`` - function to let the quarantine manager work as expected ! - - The T4 alias is not a random choice. The generated query was - dumped to found it. Be careful since it can changes with - future Django versions... - - """ - - def _apply_msgrcpt_filters(self, flt): - """Return filters based on user's role. """ - self._where = [] - if self.user.group == 'SimpleUsers': - rcpts = [self.user.email] - if self.user.mailbox_set.count(): - rcpts += self.user.mailbox_set.all()[0].alias_addresses - self._where.append( - "convert_from(maddr.email, 'UTF8') IN (%s)" - % (','.join(["'%s'" % rcpt for rcpt in rcpts]))) - elif not self.user.is_superuser: - doms = Domain.objects.get_for_admin(self.user) - regexp = "(%s)" % '|'.join([dom.name for dom in doms]) - self._where.append( - "convert_from(maddr.email, 'UTF8') ~ '%s'" % regexp) - return flt - - def _apply_extra_search_filter(self, crit, pattern): - """Apply search filters using additional criterias.""" - if crit == "to": - self._where.append( - "convert_from(maddr.email, 'UTF8') LIKE '%%{0}%%'".format( - pattern) - ) - return None - - def _apply_extra_select_filters(self, messages): - """Just a hook to apply additional filters to the queryset. - - """ - return messages.extra(where=self._where) - - def get_recipient_message(self, address, mailid): - """Retrieve a message for a given recipient. - """ - qset = Msgrcpt.objects.filter(mail=mailid).extra( - where=["msgrcpt.rid=maddr.id", - "convert_from(maddr.email, 'UTF8') = '%s'" % address], - tables=['maddr'] - ) - return qset.all()[0] - - def get_domains_pending_requests(self, domains): - """Retrieve pending release requests for a list of domains. - """ - regexp = "(%s)" % '|'.join([dom.name for dom in domains]) - return Msgrcpt.objects.filter(rs='p').extra( - where=["msgrcpt.rid=maddr.id", - "convert_from(maddr.email, 'UTF8') ~ '%s'" % regexp], - tables=['maddr'] - ) - - def get_pending_requests(self): - """Return the number of requests currently pending.""" - rq = Q(rs='p') - if not self.user.is_superuser: - doms = Domain.objects.get_for_admin(self.user) - if not doms.count(): - return 0 - regexp = "(%s)" % '|'.join([dom.name for dom in doms]) - return Msgrcpt.objects.filter(rq).extra( - where=["msgrcpt.rid=maddr.id", - "convert_from(maddr.email, 'UTF8') ~ '%s'" % (regexp,)], - tables=['maddr'] - ).count() - return Msgrcpt.objects.filter(rq).count() - - -def get_connector(**kwargs): - """Return the appropriate *connector* class. - - The result depends on the DB engine in use. - """ - if db_type("amavis") == 'postgres': - return PgSQLconnector(**kwargs) - return SQLconnector(**kwargs) diff --git a/modoboa/extensions/amavis/sql_email.py b/modoboa/extensions/amavis/sql_email.py deleted file mode 100644 index 201287462..000000000 --- a/modoboa/extensions/amavis/sql_email.py +++ /dev/null @@ -1,52 +0,0 @@ -# coding: utf-8 -""" -An email representation based on a database record. -""" - -from django.template.loader import render_to_string - -from modoboa.lib.email_utils import Email - -from .sql_connector import get_connector - - -class SQLemail(Email): - - """The SQL version of the Email class.""" - - def __init__(self, *args, **kwargs): - super(SQLemail, self).__init__(*args, **kwargs) - fields = ["From", "To", "Cc", "Date", "Subject"] - for f in fields: - label = f - self.headers += [ - {"name": label, "value": self.get_header(self.msg, f)} - ] - setattr(self, label, self.msg[f]) - qreason = self.get_header(self.msg, "X-Amavis-Alert") - self.qtype = "" - self.qreason = "" - if qreason != "": - if ',' in qreason: - self.qtype, self.qreason = qreason.split(',', 1) - elif qreason.startswith("BAD HEADER SECTION "): - # Workaround for amavis <= 2.8.0 :p - self.qtype = "BAD HEADER SECTION" - self.qreason = qreason[19:] - - @property - def msg(self): - """Get message's content.""" - import email - - if self._msg is None: - mail_text = get_connector().get_mail_content(self.mailid) - self._msg = email.message_from_string(mail_text) - self._parse(self._msg) - return self._msg - - def render_headers(self, **kwargs): - return render_to_string("amavis/mailheaders.html", { - "qtype": self.qtype, "qreason": self.qreason, - "headers": self.headers, - }) diff --git a/modoboa/extensions/amavis/static/amavis/css/quarantine.css b/modoboa/extensions/amavis/static/amavis/css/quarantine.css deleted file mode 100644 index 687d4fab2..000000000 --- a/modoboa/extensions/amavis/static/amavis/css/quarantine.css +++ /dev/null @@ -1,90 +0,0 @@ -@media (max-width: 400px){ - th#rstatus, - td[name='rstatus'], - th#type, - td[name='type']{ - display: none; - } -} -@media (max-width: 600px){ - th#subject, - td[name='subject']{ - display: none; - } -} -@media (max-width: 767px){ - th#to, - td[name='to']{ - display: none; - } -} - -@media (max-width: 990px){ - #filters_bar, - th#date, - td[name='date']{ - display: none; - } -} - -#menubar { - position: absolute; - top: 51px; - padding-left: 15px; - padding-right: 15px; - padding-top: 20px; - padding-bottom: 10px; - left: 0; - right: 0; - margin: 0; - z-index: 10; -} - -#menubar input[type="checkbox"] { - margin: 0; -} - -#listing { - position: absolute; - top: 125px; - left: 0; - right: 0; - bottom: 0; - margin: 0; - overflow: auto; - padding-left: 15px; - padding-right: 15px; -} - -#mailcontent { - border: 0; - margin: 0; - padding: 0; - width: 100%; - height: 100%; -} - -#emails tbody tr:hover { - color: #0088cc; -} - -thead tr { - position: relative; - z-index: 1; -} - -td[name=type] span { - cursor: pointer; -} - -td.unseen { - font-weight: bold; -} - -td[class*=pending] { - font-style: italic; -} - -td[class*=openable] { - cursor: pointer; -} \ No newline at end of file diff --git a/modoboa/extensions/amavis/static/amavis/css/selfservice.css b/modoboa/extensions/amavis/static/amavis/css/selfservice.css deleted file mode 100644 index 393c1c090..000000000 --- a/modoboa/extensions/amavis/static/amavis/css/selfservice.css +++ /dev/null @@ -1,21 +0,0 @@ -#menubar { - padding-left: 15px; - z-index: 10; -} - -#listing { - position: absolute; - padding: 0px; - right: 0; - left: 0; - bottom: 0; - overflow: hidden; -} - -#mailcontent { - border: 0; - margin: 0; - padding: 0; - width: 100%; - height: 100%; -} \ No newline at end of file diff --git a/modoboa/extensions/amavis/static/amavis/js/quarantine.js b/modoboa/extensions/amavis/static/amavis/js/quarantine.js deleted file mode 100644 index 2885c28cb..000000000 --- a/modoboa/extensions/amavis/static/amavis/js/quarantine.js +++ /dev/null @@ -1,489 +0,0 @@ -/** - * Return an instance of Quarantine. - * - * @constructor - * @param {Object} options - instance options - */ -var Quarantine = function(options) { - Listing.call(this, options); -}; - -Quarantine.prototype = { - constructor: Quarantine, - - defaults: { - deflocation: "listing", - main_table_id: "emails", - scroll_container: "#listing", - navigation_params: ["sort_order", "pattern", "criteria", "msgtype"], - eor_message: gettext("No more message to show") - }, - - initialize: function(options) { - this.options = $.extend({}, this.defaults, options); - this.options.defcallback = $.proxy(this.listing_cb, this); - Listing.prototype.initialize.call(this, this.options); - this.register_navcallbacks(); - this.listen(); - this.set_msgtype(); - - $(document).on('click', '#selectall', this.toggle_selection); - this.initialize_searchfield(); - }, - - /** - * Search field initialization. - */ - initialize_searchfield: function() { - $("#searchfield").searchbar({ - navobj: this.navobj, - pattern_changed: function(navobj) { - navobj.setparam("reset_page", "true"); - } - }); - }, - - /** - * Activate the button corresponding to the current message type - * filter. If no message type is found, the 'All' button is - * selected. - */ - set_msgtype: function() { - var msgtype = this.navobj.getparam('msgtype'); - - $("input[name*=msgtype]").attr("checked", false) - .parent().removeClass('active'); - - if (msgtype !== undefined) { - $("input[name=msgtype_" + msgtype + "]").attr("checked", true) - .parent().addClass('active'); - } else { - $("button[name=msgtype_all]").attr("checked", true) - .parent().addClass('active'); - } - }, - - /** - * Update the main page content. - * - * @this Quarantine - * @param {Object} data - new content - */ - update_page: function(data) { - if (data.menu !== undefined) { - $("#menubar").html(data.menu); - this.initialize_searchfield(); - } - - if (data.listing !== undefined) { - $("#listing").html(data.listing); - } - if (this.navobj.getbaseurl() == "listing") { - this.update_listing(data); - $(this.options.scroll_container).scrollTop(10); - } else { - this.update_listing(data, false); - } - }, - - listen: function() { - $(document).on("click", "td[class*=openable]", $.proxy(this.viewmail_loader, this)); - $(document).on("click", "a[name=selectmsgs]", $.proxy(this.selectmsgs, this)); - $(document).on("click", "a[name=release-multi]", - $.proxy(this.release_selection, this)); - $(document).on("click", "a[name=delete-multi]", - $.proxy(this.delete_selection, this)); - $(document).on("click", "a[name=mark-as-spam-multi]", - $.proxy(this.mark_selection_as_spam, this)); - $(document).on("click", "a[name=mark-as-ham-multi]", - $.proxy(this.mark_selection_as_ham, this)); - $(document).on("click", "a[name=viewrequests]", - $.proxy(this.view_requests, this)); - $(document).on("click", "a[name=release]", $.proxy(this.release, this)); - $(document).on("click", "a[name=delete]", $.proxy(this.delete, this)); - $(document).on("click", "a[name=mark-as-spam]", - $.proxy(this.mark_as_spam, this)); - $(document).on("click", "a[name=mark-as-ham]", - $.proxy(this.mark_as_ham, this)); - $(document).on("click", "a[name=headers]", $.proxy(this.headers, this)); - $(document).on("click", "td[name=type] span", $.proxy(this.filter_by_type, this)); - $(document).on("click", "#filters label", $.proxy(this.filter_by_type, this)); - }, - - load_page: function(e) { - Listing.prototype.load_page.apply(this, arguments); - this.navobj.delparam("rcpt").update(); - }, - - /** - * Return extra arguments used to fetch a page. - * - * @this Listing - */ - get_load_page_args: function() { - var args = Listing.prototype.get_load_page_args.call(this); - - args.scroll = true; - return args; - }, - - /** - * Calculate the bottom position of the scroll container. - * - * @param {Object} $element - scroll container object - */ - calculate_bottom: function($element) { - var emails_height = $("#emails").height(); - - if (!emails_height) { - return undefined; - } - return $("#emails").height() - $element.height(); - }, - - view_requests: function(e) { - e.preventDefault(); - this.navobj.setparam("viewrequests", "1").update(); - }, - - viewmail_loader: function(e) { - e.preventDefault(); - var $tr = $(e.target).parent(); - var $to = $tr.find("td[name=to]"); - - this.navobj.baseurl($tr.attr("id")); - if ($to.length) { - this.navobj.setparam("rcpt", $to.html().trim()); - } - this.navobj.update(); - }, - - /* - * Toggle message selection when the top checkbox's state is - * modified. - * - * If it is checked, all messages are selected. Otherwise, the - * current selection is resetted. - */ - toggle_selection: function(e) { - if (!$('#selectall').prop('checked')) { - $("#emails").htmltable("clear_selection"); - return; - } - $("td[name*=selection]").each(function() { - var $input = $(this).children('input'); - $input.prop('checked', true); - $('#emails').htmltable('select_row', $(this).parent()); - }); - }, - - /* - * Select all messages of a specific type. - */ - selectmsgs: function(e) { - e.preventDefault(); - var type = get_target(e, 'a').attr("href"); - var counter = 0; - - $("td[name=type]").each(function() { - var $this = $(this); - if ($this.find('span').html().trim() == type) { - var $input = $this.parent().children('td[name=selection]').children('input'); - $input.prop('checked', true); - $('#emails').htmltable('select_row', $this.parent()); - counter++; - } - }); - if (counter) { - $("#selectall").prop('checked', true); - } - }, - - /** - * Filter listing by message type (spam, virus, etc.) - * - * @param {Object} evt - event object - */ - filter_by_type: function(evt) { - var $target = $(evt.target); - var msgtype; - - if ($target.is("span")) { - msgtype = $target.html().trim(); - } else { - msgtype = $target.children("input").attr("name").replace("msgtype_", ""); - } - - if (msgtype != 'all') { - this.navobj.setparam('msgtype', msgtype); - } else { - this.navobj.delparam('msgtype'); - } - this.navobj.update(); - }, - - /** - * Return the list of currently selected messages. - * - * @return {Array} - list of message IDs - */ - get_current_selection: function() { - var selection = []; - - this.htmltable.current_selection().each(function() { - var $tr = $(this); - var $to = $tr.find("td[name=to]"); - - if ($to.length) { - selection.push($to.html().trim() + " " + $tr.attr("id")); - } else { - selection.push($tr.attr("id")); - } - }); - return selection; - }, - - _send_selection: function(e, name, message) { - var $link = $(e.target); - - e.preventDefault(); - if (!this.htmltable.current_selection().length || - !confirm(message)) { - return; - } - var selection = this.get_current_selection(); - - $.ajax({ - url: $link.attr("href"), - data: "action=" + name + "&selection=" + selection.join(","), - type: 'POST', - dataType: 'json' - }).done( - $.proxy(this.action_cb, this) - ).fail(function(jqxhr) { - var data = $.parseJSON(jqxhr.responseText); - $("body").notify("error", data.message); - }); - }, - - release_selection: function(e) { - this._send_selection(e, "release", gettext("Release this selection?")); - }, - - delete_selection: function(e) { - this._send_selection(e, "delete", gettext("Delete this selection?")); - }, - - /** - * Show the recipient selection form inside a modal box. - * - * @param {Object} evt - event object - * @param {string} ltype - learning type (spam or ham) - */ - show_select_rcpt_form: function(evt, ltype, selection) { - if (selection === undefined) { - selection = this.get_current_selection(); - } - var url = this.options.learning_recipient_url + - "?type=" + ltype + "&selection=" + - selection.join(","); - var $this = this; - - modalbox(evt, null, url, function() { - $(".submit").on("click", function(evt) { - simple_ajax_form_post(evt, { - formid: "learning_recipient_form", - reload_on_success: false, - success_cb: $.proxy($this.action_cb, $this) - }); - }); - }); - }, - - /** - * Mark the current selection as spam. - * - * @param {Object} e - event object - */ - mark_selection_as_spam: function(e) { - if (this.options.check_learning_rcpt && - this.htmltable.current_selection().length) { - this.show_select_rcpt_form(e, "spam"); - } else { - this._send_selection( - e, "mark_as_spam", gettext("Mark this selection as spam?")); - } - }, - - /** - * Mark the current selection as ham. - * - * @param {Object} e - event object - */ - mark_selection_as_ham: function(e) { - if (this.options.check_learning_rcpt && - this.htmltable.current_selection().length) { - this.show_select_rcpt_form(e, "ham"); - } else { - this._send_selection( - e, "mark_as_ham", gettext("Mark this selection as non-spam?")); - } - }, - - _send_action: function(e, message) { - var $link = get_target(e, "a"); - - e.preventDefault(); - if (!confirm(message)) { - return; - } - $.ajax({ - url: $link.attr("href"), - dataType: 'json', - type: 'POST', - data: {rcpt: get_parameter_by_name($link.attr("href"), 'rcpt')} - }).done( - $.proxy(this.action_cb, this) - ).fail(function(jqxhr) { - var data = $.parseJSON(jqxhr.responseText); - $("body").notify("error", data.message); - }); - }, - - release : function(e) { - this._send_action(e, gettext("Release this message?")); - }, - - delete: function(e) { - this._send_action(e, gettext("Delete this message?")); - }, - - /** - * Mark the current message as spam. - * - * @param {Object} e - event object - */ - mark_as_spam: function(e) { - if (this.options.check_learning_rcpt) { - var $link = get_target(e, "a"); - var selection = get_parameter_by_name($link.attr("href"), "rcpt") + - " " + $link.attr("data-mail-id"); - this.show_select_rcpt_form(e, "spam", [selection]); - } else { - this._send_action(e, gettext("Mark as spam?")); - } - }, - - /** - * Mark the current message as ham. - * - * @param {Object} e - event object - */ - mark_as_ham: function(e) { - if (this.options.check_learning_rcpt) { - var $link = get_target(e, "a"); - var selection = get_parameter_by_name($link.attr("href"), "rcpt") + - " " + $link.attr("data-mail-id"); - this.show_select_rcpt_form(e, "ham", [selection]); - } else { - this._send_action(e, gettext("Mark as non-spam?")); - } - }, - - show_rawheaders: function($mailcontent) { - $mailcontent.find("#rawheaders").removeClass("hidden"); - $mailcontent.find("#emailheaders").addClass("hidden"); - }, - - headers: function(e) { - e.preventDefault(); - - var $link = get_target(e, "a"); - var $mailcontent = $("#mailcontent").contents(); - var $headers = $mailcontent.find("#emailheaders"); - var $rawheaders = $mailcontent.find("#rawheaders"); - - if ($headers.hasClass("hidden")) { - $link.html(gettext("View full headers")); - $headers.removeClass("hidden"); - $rawheaders.addClass("hidden"); - return; - } - $link.html(gettext("Hide full headers")); - if (!$rawheaders.length) { - $.ajax({ - url: $link.attr("href"), - global: false - }).done($.proxy(function(data) { - $mailcontent.find("#table-container").append($(data)); - this.show_rawheaders($mailcontent); - }, this)); - return; - } - this.show_rawheaders($mailcontent); - }, - - update_params: function(e) { - var $link = $(e.target); - e.preventDefault(); - this.navobj.updateparams($link.attr("href")).update(); - }, - - register_navcallbacks: function() { - this.navobj.register_callback("_listing", - $.proxy(this.listing_cb, this)); - this.navobj.register_callback("viewmail", - $.proxy(this.viewmail_cb, this)); - }, - - activate_buttons: function($tr) { - $("a[name*=-multi]").removeClass('disabled'); - $("#selectall").prop('checked', true); - }, - - deactivate_buttons: function($tr) { - if (!this.htmltable || !this.htmltable.current_selection().length) { - $("a[name*=-multi]").addClass('disabled'); - $("#selectall").prop('checked', false); - } - }, - - /** - * Navigation callback: listing. - * - * @this Quarantine - * @param {Object} data - ajax call response (JSON) - */ - listing_cb: function(data) { - this.update_page(data); - this.navobj.delparam("rcpt").update(); - this.set_msgtype(); - $("#emails").htmltable({ - row_selected_event: this.activate_buttons, - row_unselected_event: $.proxy(this.deactivate_buttons, this) - }); - this.htmltable = $("#emails").data("htmltable"); - this.deactivate_buttons(); - if (this.navobj.hasparam("reset_page")) { - this.navobj.delparam("reset_page").update(false, true); - } - $("#listing").css("overflow", "auto"); - }, - - viewmail_cb: function(data) { - this.update_page(data); - $("#listing").css("overflow", "hidden"); - }, - - action_cb: function(data) { - if (data.reload) { - this.navobj.update(true); - } - if (data.url) { - this.navobj.parse_string(data.url, true).update(true); - } - $("body").notify("success", data.message, 2000); - } -}; - -Quarantine.prototype = $.extend({}, Listing.prototype, Quarantine.prototype); diff --git a/modoboa/extensions/amavis/static/amavis/js/selfservice.js b/modoboa/extensions/amavis/static/amavis/js/selfservice.js deleted file mode 100644 index d634f9bfa..000000000 --- a/modoboa/extensions/amavis/static/amavis/js/selfservice.js +++ /dev/null @@ -1,30 +0,0 @@ -function send_action(e, message) { - var $link = $(e.target); - - e.preventDefault(); - if (!confirm(message)) { - return; - } - $.ajax({ - url: $link.attr("href"), - dataType: 'json' - }).done(function(data) { - $("body").notify("success", data, 2000); - }); -} - -function release(e) { - send_action(e, gettext("Release this message?")); -} - -function remove(e) { - send_action(e, gettext("Delete this message?")); -} - -$(document).ready(function() { - $("#listing").css({ - top: $("#menubar").outerHeight() + 10 - }); - $(document).on("click", "a[name=release]", release); - $(document).on("click", "a[name=delete]", remove); -}); \ No newline at end of file diff --git a/modoboa/extensions/amavis/templates/amavis/domain_content_filter.html b/modoboa/extensions/amavis/templates/amavis/domain_content_filter.html deleted file mode 100644 index d669b7824..000000000 --- a/modoboa/extensions/amavis/templates/amavis/domain_content_filter.html +++ /dev/null @@ -1,18 +0,0 @@ -{% load i18n form_tags %} -{% render_field form.bypass_virus_checks %} -{% render_field form.bypass_spam_checks %} -
- -
- {{ form.spam_tag2_level }} -
- {% trans "or more is spam" %} -
-
-
- {{ form.spam_kill_level }} -
- {% trans "or more throw spam message away" %} -
-{% render_field form.spam_subject_tag2 activator=True activator_label=_("default") activator_size="col-sm-3" %} -{% render_field form.bypass_banned_checks %} diff --git a/modoboa/extensions/amavis/templates/amavis/email_list.html b/modoboa/extensions/amavis/templates/amavis/email_list.html deleted file mode 100644 index 439d214c0..000000000 --- a/modoboa/extensions/amavis/templates/amavis/email_list.html +++ /dev/null @@ -1,31 +0,0 @@ -{% load i18n %} - -
- - - - - - - - - - - - - - - {% include "amavis/emails_page.html" %} - -
- {% trans "Score" %} - - {% trans "To" %} - - {% trans "From" %} - - {% trans "Subject" %} - - {% trans "Date" %} -
-
diff --git a/modoboa/extensions/amavis/templates/amavis/emails_page.html b/modoboa/extensions/amavis/templates/amavis/emails_page.html deleted file mode 100644 index 1b1233c6e..000000000 --- a/modoboa/extensions/amavis/templates/amavis/emails_page.html +++ /dev/null @@ -1,34 +0,0 @@ -{% load i18n %} -{% load amavis_tags %} -{% load static %} - -{% for email in email_list %} - - - - - - {{ email.type|msgtype_to_html }} - - - {% if email.status == 'R' %} - - {% elif email.status == 'S' %} - - {% elif email.status == 'H' %} - - {% endif %} - - {{ email.score }} - {{ email.to }} - - {{ email.from|truncatechars:30 }} - - - {{ email.subject|truncatechars:30 }} - - - {{ email.date|date:"SHORT_DATETIME_FORMAT" }} - - -{% endfor %} diff --git a/modoboa/extensions/amavis/templates/amavis/getmailcontent.html b/modoboa/extensions/amavis/templates/amavis/getmailcontent.html deleted file mode 100644 index 579454343..000000000 --- a/modoboa/extensions/amavis/templates/amavis/getmailcontent.html +++ /dev/null @@ -1 +0,0 @@ -{% if pre %}
{% endif %}{{ body }}{% if pre %}
{% endif %} \ No newline at end of file diff --git a/modoboa/extensions/amavis/templates/amavis/index.html b/modoboa/extensions/amavis/templates/amavis/index.html deleted file mode 100644 index 70a1a2ee7..000000000 --- a/modoboa/extensions/amavis/templates/amavis/index.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends "fluid.html" %}{% load i18n amavis_tags %} -{% load static %} - -{% block pagetitle %}{% trans "Quarantine management" %}{% endblock %} - -{% block extra_css %} - - -{% endblock %} - -{% block extra_js %} - - - - - - - - -{% endblock %} - -{% block container_content %} - -
-{% endblock %} diff --git a/modoboa/extensions/amavis/templates/amavis/mailheaders.html b/modoboa/extensions/amavis/templates/amavis/mailheaders.html deleted file mode 100644 index 0ed8b3d61..000000000 --- a/modoboa/extensions/amavis/templates/amavis/mailheaders.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "common/mailheaders.html" %} -{% block emailheaders %} -{% if qtype %}
{{ qtype }} {{ qreason }}
{% endif %} -{{ block.super }} -{% endblock %} diff --git a/modoboa/extensions/amavis/templates/amavis/main_action_bar.html b/modoboa/extensions/amavis/templates/amavis/main_action_bar.html deleted file mode 100644 index 70b5222e6..000000000 --- a/modoboa/extensions/amavis/templates/amavis/main_action_bar.html +++ /dev/null @@ -1,79 +0,0 @@ -{% load lib_tags i18n %} -
- -
- - - - - {% if manual_learning %} - - {% endif %} - -
- -
- {% include "common/email_searchbar.html" with extraopts=extraopts %} -
- -
-
- -
-
- -
diff --git a/modoboa/extensions/amavis/templates/amavis/notifications/pending_requests.html b/modoboa/extensions/amavis/templates/amavis/notifications/pending_requests.html deleted file mode 100644 index a4729b339..000000000 --- a/modoboa/extensions/amavis/templates/amavis/notifications/pending_requests.html +++ /dev/null @@ -1,14 +0,0 @@ -{% load i18n lib_tags %}{% load url from future %}{% blocktrans count counter=total %} -{{ counter }} release request is pending for action. -{% plural %} -{{ counter }} release requests are pending for action. -{% endblocktrans %} -{% trans "Sketch:" %} -{% for msg in requests %} -{% trans "From:" %} {{ msg.mail.sid.email }} -{% trans "To:" %} {{ msg.rid.email }} -{% trans "Date:" %} {{ msg.mail.time_num|fromunix|date:"DATETIME_FORMAT" }} -{% trans "Subject:" %} {{ msg.mail.subject }} -{% trans "Act on this message:" %} {{ baseurl }}{% url "amavis:index" %}#{{ msg.mail.mail_id }}/?rcpt={{ msg.rid.email }} -{% endfor %} -{% blocktrans %}Please visit {{ listingurl }} for a full listing.{% endblocktrans %} diff --git a/modoboa/extensions/amavis/templates/amavis/viewheader.html b/modoboa/extensions/amavis/templates/amavis/viewheader.html deleted file mode 100644 index 6e5efab6c..000000000 --- a/modoboa/extensions/amavis/templates/amavis/viewheader.html +++ /dev/null @@ -1,4 +0,0 @@ -
-{% for k, v in headers %}{{ k }}: {{ v }}
-{% endfor %}
-
diff --git a/modoboa/extensions/amavis/templates/amavis/viewmail_selfservice.html b/modoboa/extensions/amavis/templates/amavis/viewmail_selfservice.html deleted file mode 100644 index 90ce332a0..000000000 --- a/modoboa/extensions/amavis/templates/amavis/viewmail_selfservice.html +++ /dev/null @@ -1,28 +0,0 @@ -{% load i18n lib_tags amavis_tags %}{% load url from future %} -{% load static %} - - - - Modoboa - - - - - - - - - - {% url "django.views.i18n.javascript_catalog" as catalog_url %} - - - - -
- -
{{ content }}
-
- - diff --git a/modoboa/extensions/amavis/templatetags/__init__.py b/modoboa/extensions/amavis/templatetags/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modoboa/extensions/amavis/templatetags/amavis_tags.py b/modoboa/extensions/amavis/templatetags/amavis_tags.py deleted file mode 100644 index f5d1a2c9a..000000000 --- a/modoboa/extensions/amavis/templatetags/amavis_tags.py +++ /dev/null @@ -1,132 +0,0 @@ -""" -Amavis frontend template tags. -""" - -from django import template -from django.core.urlresolvers import reverse -from django.template.loader import render_to_string -from django.utils.safestring import mark_safe -from django.utils.translation import ugettext as _ - -from ..lib import manual_learning_enabled - -register = template.Library() - - -@register.simple_tag -def viewm_menu(user, mail_id, rcpt): - """Menu displayed within the viewmail action.""" - entries = [ - {"name": "back", - "url": "javascript:history.go(-1);", - "img": "fa fa-arrow-left", - "class": "btn-primary", - "label": _("Back")}, - {"name": "release", - "img": "fa fa-check", - "class": "btn-success", - "url": reverse('amavis:mail_release', args=[mail_id]) - + ("?rcpt=%s" % rcpt if rcpt else ""), - "label": _("Release")}, - {"name": "delete", - "class": "btn-danger", - "img": "fa fa-trash", - "url": reverse('amavis:mail_delete', args=[mail_id]) - + ("?rcpt=%s" % rcpt if rcpt else ""), - "label": _("Delete")}, - {"name": "headers", - "class": "btn-default", - "url": reverse('amavis:headers_detail', args=[mail_id]), - "label": _("View full headers")}, - ] - - if manual_learning_enabled(user): - entries.insert(3, { - "name": "process", - "img": "fa fa-cog", - "menu": [ - {"name": "mark-as-spam", - "label": _("Mark as spam"), - "url": reverse( - "amavis:mail_mark_as_spam", args=[mail_id] - ) + ("?rcpt=%s" % rcpt if rcpt else ""), - "extra_attributes": { - "data-mail-id": mail_id - }}, - {"name": "mark-as-ham", - "label": _("Mark as non-spam"), - "url": reverse( - "amavis:mail_mark_as_ham", args=[mail_id] - ) + ("?rcpt=%s" % rcpt if rcpt else ""), - "extra_attributes": { - "data-mail-id": mail_id - }} - ] - }) - - menu = render_to_string('common/buttons_list.html', - {"entries": entries, "extraclasses": "pull-left"}) - - entries = [{"name": "close", - "url": "javascript:history.go(-1);", - "img": "icon-remove"}] - menu += render_to_string( - 'common/buttons_list.html', - {"entries": entries, "extraclasses": "pull-right"} - ) - - return menu - - -@register.simple_tag -def viewm_menu_simple(user, mail_id, rcpt, secret_id=""): - release_url = "{0}?rcpt={1}".format( - reverse("amavis:mail_release", args=[mail_id]), rcpt) - delete_url = "{0}?rcpt={1}".format( - reverse("amavis:mail_delete", args=[mail_id]), rcpt) - if secret_id: - release_url += "&secret_id={0}".format(secret_id) - delete_url += "&secret_id={0}".format(secret_id) - entries = [ - {"name": "release", - "img": "fa fa-check", - "class": "btn-success", - "url": release_url, - "label": _("Release")}, - {"name": "delete", - "img": "fa fa-trash", - "class": "btn-danger", - "url": delete_url, - "label": _("Delete")}, - ] - - return render_to_string('common/buttons_list.html', - {"entries": entries}) - - -@register.simple_tag -def quar_menu(user): - """Render the quarantine listing menu. - - :rtype: str - :return: resulting HTML - """ - extraopts = [{"name": "to", "label": _("To")}] - return render_to_string("amavis/main_action_bar.html", { - "extraopts": extraopts, - "manual_learning": manual_learning_enabled(user) - }) - - -@register.filter -def msgtype_to_html(msgtype): - """Transform a message type to a bootstrap label.""" - if msgtype in ["S", "V"]: - color = 'danger' - elif msgtype == "C": - color = "success" - else: - color = "warning" - return mark_safe( - '%s' % (color, msgtype) - ) diff --git a/modoboa/extensions/amavis/urls.py b/modoboa/extensions/amavis/urls.py deleted file mode 100644 index ae9ace7de..000000000 --- a/modoboa/extensions/amavis/urls.py +++ /dev/null @@ -1,24 +0,0 @@ -from django.conf.urls import patterns, url - -urlpatterns = patterns( - 'modoboa.extensions.amavis.views', - - url(r'^$', 'index', name="index"), - url(r'^listing/$', '_listing', name="_mail_list"), - url(r'^listing/page/$', 'listing_page', name="mail_page"), - url(r'^getmailcontent/(?P[\w\-\+]+)/$', 'getmailcontent', - name="mailcontent_get"), - url(r'^process/$', 'process', name="mail_process"), - url(r'^delete/(?P[\w\-\+]+)/$', 'delete', name="mail_delete"), - url(r'^release/(?P[\w\-\+]+)/$', 'release', - name="mail_release"), - url(r'^markspam/(?P[\w\-\+]+)/$', 'mark_as_spam', - name="mail_mark_as_spam"), - url(r'^markham/(?P[\w\-\+]+)/$', 'mark_as_ham', - name="mail_mark_as_ham"), - url(r'^learning_recipient/$', 'learning_recipient', - name="learning_recipient_set"), - url(r'^(?P[\w\-\+]+)/$', 'viewmail', name="mail_detail"), - url(r'^(?P[\w\-\+]+)/headers/$', 'viewheaders', - name="headers_detail"), -) diff --git a/modoboa/extensions/amavis/views.py b/modoboa/extensions/amavis/views.py deleted file mode 100644 index 40fba3722..000000000 --- a/modoboa/extensions/amavis/views.py +++ /dev/null @@ -1,436 +0,0 @@ -# coding: utf-8 -""" -Amavis quarantine views. -""" -import email - -import chardet - -from django.shortcuts import render -from django.http import HttpResponseRedirect, Http404 -from django.template import Template, Context -from django.utils.translation import ugettext as _, ungettext -from django.core.urlresolvers import reverse -from django.contrib.auth.decorators import login_required - -from modoboa.lib import parameters -from modoboa.lib.exceptions import BadRequest -from modoboa.lib.paginator import Paginator -from modoboa.lib.web_utils import ( - getctx, render_to_json_response, _render_to_string -) -from modoboa.extensions.admin.models import Mailbox, Domain -from modoboa.extensions.amavis.templatetags.amavis_tags import ( - quar_menu, viewm_menu -) -from .lib import ( - selfservice, AMrelease, QuarantineNavigationParameters, - SpamassassinClient, manual_learning_enabled -) -from .forms import LearningRecipientForm -from .models import Msgrcpt -from .sql_connector import get_connector -from .sql_email import SQLemail - - -def empty_quarantine(): - """Shortcut to use when no content can be displayed.""" - content = "
%s
" % _("Empty quarantine") - ctx = getctx("ok", level=2, listing=content) - return render_to_json_response(ctx) - - -def get_listing_pages(request, connector): - """Return listing pages.""" - paginator = Paginator( - connector.messages_count(), - int(parameters.get_user(request.user, "MESSAGES_PER_PAGE")) - ) - page_id = int(connector.navparams.get("page")) - page = paginator.getpage(page_id) - if not page: - return None - pages = [page] - if not page.has_next and page.has_previous and page.items < 40: - pages = [paginator.getpage(page_id - 1)] + pages - email_list = [] - for page in pages: - email_list += connector.fetch(page.id_start, page.id_stop) - return {"pages": [page.number for page in pages], "rows": email_list} - - -@login_required -def listing_page(request): - """Return a listing page.""" - navparams = QuarantineNavigationParameters(request) - previous_page_id = int(navparams["page"]) if "page" in navparams else None - navparams.store() - - connector = get_connector(user=request.user, navparams=navparams) - context = get_listing_pages(request, connector) - if context is None: - context = {"length": 0} - navparams["page"] = previous_page_id - else: - context["rows"] = _render_to_string( - request, "amavis/emails_page.html", {"email_list": context["rows"]} - ) - return render_to_json_response(context) - - -@login_required -def _listing(request): - """Listing initial view. - - Called the first time the listing page is displayed. - """ - if not request.user.is_superuser and request.user.group != 'SimpleUsers': - if not Domain.objects.get_for_admin(request.user).count(): - return empty_quarantine() - - navparams = QuarantineNavigationParameters(request) - navparams.store() - - connector = get_connector(user=request.user, navparams=navparams) - context = get_listing_pages(request, connector) - if context is None: - return empty_quarantine() - context["listing"] = _render_to_string( - request, "amavis/email_list.html", {"email_list": context["rows"]} - ) - del context["rows"] - if request.session.get('location', 'listing') != 'listing': - context["menu"] = quar_menu(request.user) - request.session["location"] = "listingx" - return render_to_json_response(context) - - -@login_required -def index(request): - """Default view.""" - check_learning_rcpt = "false" - if parameters.get_admin("MANUAL_LEARNING") == "yes": - if request.user.group != "SimpleUsers": - user_level_learning = parameters.get_admin( - "USER_LEVEL_LEARNING") == "yes" - domain_level_learning = parameters.get_admin( - "DOMAIN_LEVEL_LEARNING") == "yes" - if user_level_learning or domain_level_learning: - check_learning_rcpt = "true" - return render(request, "amavis/index.html", dict( - selection="quarantine", check_learning_rcpt=check_learning_rcpt - )) - - -def getmailcontent_selfservice(request, mail_id): - mail = SQLemail(mail_id, mformat="plain", links="0") - return render(request, "common/viewmail.html", { - "headers": mail.render_headers(), - "mailbody": mail.body - }) - - -@selfservice(getmailcontent_selfservice) -def getmailcontent(request, mail_id): - mail = SQLemail(mail_id, mformat="plain", links="0") - return render(request, "common/viewmail.html", { - "headers": mail.render_headers(), - "mailbody": mail.body - }) - - -def viewmail_selfservice(request, mail_id, - tplname="amavis/viewmail_selfservice.html"): - rcpt = request.GET.get("rcpt", None) - secret_id = request.GET.get("secret_id", "") - if rcpt is None: - raise Http404 - content = Template("""{% load url from future %} - -""").render(Context(dict(mail_id=mail_id))) - - return render(request, tplname, dict( - mail_id=mail_id, rcpt=rcpt, secret_id=secret_id, content=content - )) - - -@selfservice(viewmail_selfservice) -def viewmail(request, mail_id): - rcpt = request.GET.get("rcpt", None) - if rcpt is None: - raise BadRequest(_("Invalid request")) - if request.user.email == rcpt: - get_connector().set_msgrcpt_status(rcpt, mail_id, 'V') - elif request.user.mailbox_set.count(): - mb = Mailbox.objects.get(user=request.user) - if rcpt == mb.full_address or rcpt in mb.alias_addresses: - get_connector().set_msgrcpt_status(rcpt, mail_id, 'V') - - content = Template(""" - -""").render(Context({"url": reverse("amavis:mailcontent_get", args=[mail_id])})) - menu = viewm_menu(request.user, mail_id, rcpt) - ctx = getctx("ok", menu=menu, listing=content) - request.session['location'] = 'viewmail' - return render_to_json_response(ctx) - - -@login_required -def viewheaders(request, mail_id): - """Display message headers.""" - content = get_connector().get_mail_content(mail_id) - msg = email.message_from_string(content) - headers = [] - for name, value in msg.items(): - if value: - result = chardet.detect(value) - if result["encoding"] is not None: - value = value.decode(result["encoding"]) - headers += [(name, value)] - return render(request, 'amavis/viewheader.html', { - "headers": headers - }) - - -def check_mail_id(request, mail_id): - if type(mail_id) in [str, unicode]: - if "rcpt" in request.POST: - mail_id = ["%s %s" % (request.POST["rcpt"], mail_id)] - else: - mail_id = [mail_id] - return mail_id - - -def get_user_valid_addresses(user): - """Retrieve all valid addresses of a user.""" - valid_addresses = [] - if user.group == 'SimpleUsers': - valid_addresses.append(user.email) - try: - mb = Mailbox.objects.get(user=user) - except Mailbox.DoesNotExist: - pass - else: - valid_addresses += mb.alias_addresses - return valid_addresses - - -def delete_selfservice(request, mail_id): - rcpt = request.GET.get("rcpt", None) - if rcpt is None: - raise BadRequest(_("Invalid request")) - try: - get_connector().set_msgrcpt_status(rcpt, mail_id, 'D') - except Msgrcpt.DoesNotExist: - raise BadRequest(_("Invalid request")) - return render_to_json_response(_("Message deleted")) - - -@selfservice(delete_selfservice) -def delete(request, mail_id): - """Delete message selection. - - :param str mail_id: message unique identifier - """ - mail_id = check_mail_id(request, mail_id) - connector = get_connector() - valid_addresses = get_user_valid_addresses(request.user) - for mid in mail_id: - r, i = mid.split() - if valid_addresses and r not in valid_addresses: - continue - connector.set_msgrcpt_status(r, i, 'D') - message = ungettext("%(count)d message deleted successfully", - "%(count)d messages deleted successfully", - len(mail_id)) % {"count": len(mail_id)} - return render_to_json_response({ - "message": message, - "url": QuarantineNavigationParameters(request).back_to_listing() - }) - - -def release_selfservice(request, mail_id): - rcpt = request.GET.get("rcpt", None) - secret_id = request.GET.get("secret_id", None) - if rcpt is None or secret_id is None: - raise BadRequest(_("Invalid request")) - connector = get_connector() - try: - msgrcpt = connector.get_recipient_message(rcpt, mail_id) - except Msgrcpt.DoesNotExist: - raise BadRequest(_("Invalid request")) - if secret_id != msgrcpt.mail.secret_id: - raise BadRequest(_("Invalid request")) - if parameters.get_admin("USER_CAN_RELEASE") == "no": - connector.set_msgrcpt_status(rcpt, mail_id, 'p') - msg = _("Request sent") - else: - amr = AMrelease() - result = amr.sendreq(mail_id, secret_id, rcpt) - if result: - connector.set_msgrcpt_status(rcpt, mail_id, 'R') - msg = _("Message released") - else: - raise BadRequest(result) - return render_to_json_response(msg) - - -@selfservice(release_selfservice) -def release(request, mail_id): - """Release message selection. - - :param str mail_id: message unique identifier - """ - mail_id = check_mail_id(request, mail_id) - msgrcpts = [] - connector = get_connector() - valid_addresses = get_user_valid_addresses(request.user) - for mid in mail_id: - r, i = mid.split() - if valid_addresses and r not in valid_addresses: - continue - msgrcpts += [connector.get_recipient_message(r, i)] - if request.user.group == "SimpleUsers" and \ - parameters.get_admin("USER_CAN_RELEASE") == "no": - for msgrcpt in msgrcpts: - connector.set_msgrcpt_status( - msgrcpt.rid.email, msgrcpt.mail.mail_id, 'p' - ) - message = ungettext("%(count)d request sent", - "%(count)d requests sent", - len(mail_id)) % {"count": len(mail_id)} - return render_to_json_response({ - "message": message, - "url": QuarantineNavigationParameters(request).back_to_listing() - }) - - amr = AMrelease() - error = None - for rcpt in msgrcpts: - result = amr.sendreq( - rcpt.mail.mail_id, rcpt.mail.secret_id, rcpt.rid.email - ) - if result: - connector.set_msgrcpt_status(rcpt.rid.email, rcpt.mail.mail_id, 'R') - else: - error = result - break - - if not error: - message = ungettext("%(count)d message released successfully", - "%(count)d messages released successfully", - len(mail_id)) % {"count": len(mail_id)} - else: - message = error - status = 400 if error else 200 - return render_to_json_response({ - "message": message, - "url": QuarantineNavigationParameters(request).back_to_listing() - }, status=status) - - -def mark_messages(request, selection, mtype, recipient_db=None): - """Mark a selection of messages as spam. - - :param str selection: message unique identifier - :param str mtype: type of marking (spam or ham) - """ - if not manual_learning_enabled(request.user): - return render_to_json_response({"status": "ok"}) - if recipient_db is None: - recipient_db = ( - "user" if request.user.group == "SimpleUsers" else "global" - ) - selection = check_mail_id(request, selection) - connector = get_connector() - saclient = SpamassassinClient(request.user, recipient_db) - for item in selection: - rcpt, mail_id = item.split() - content = connector.get_mail_content(mail_id) - result = saclient.learn_spam(rcpt, content) if mtype == "spam" \ - else saclient.learn_ham(rcpt, content) - if not result: - break - connector.set_msgrcpt_status(rcpt, mail_id, mtype[0].upper()) - if saclient.error is None: - saclient.done() - message = ungettext("%(count)d message processed successfully", - "%(count)d messages processed successfully", - len(selection)) % {"count": len(selection)} - else: - message = saclient.error - status = 400 if saclient.error else 200 - return render_to_json_response({ - "message": message, "reload": True - }, status=status) - - -@login_required -def learning_recipient(request): - """A view to select the recipient database of a learning action.""" - if request.method == "POST": - form = LearningRecipientForm(request.user, request.POST) - if form.is_valid(): - return mark_messages( - request, - form.cleaned_data["selection"].split(","), - form.cleaned_data["ltype"], - form.cleaned_data["recipient"] - ) - return render_to_json_response( - {"form_errors": form.errors}, status=400 - ) - ltype = request.GET.get("type", None) - selection = request.GET.get("selection", None) - if ltype is None or selection is None: - raise BadRequest - form = LearningRecipientForm(request.user) - form.fields["ltype"].initial = ltype - form.fields["selection"].initial = selection - return render(request, "common/generic_modal_form.html", { - "title": _("Select a database"), - "formid": "learning_recipient_form", - "action": reverse("amavis:learning_recipient_set"), - "action_classes": "submit", - "action_label": _("Validate"), - "form": form - }) - - -@login_required -def mark_as_spam(request, mail_id): - """Mark a single message as spam.""" - return mark_messages(request, mail_id, "spam") - - -@login_required -def mark_as_ham(request, mail_id): - """Mark a single message as ham.""" - return mark_messages(request, mail_id, "ham") - - -@login_required -def process(request): - """Process a selection of messages. - - The request must specify an action to execute against the - selection. - - """ - action = request.POST.get("action", None) - ids = request.POST.get("selection", "") - ids = ids.split(",") - if not ids or action is None: - return HttpResponseRedirect(reverse("amavis:index")) - - if request.POST["action"] == "release": - return release(request, ids) - - if request.POST["action"] == "delete": - return delete(request, ids) - - if request.POST["action"] == "mark_as_spam": - return mark_messages(request, ids, "spam") - - if request.POST["action"] == "mark_as_ham": - return mark_messages(request, ids, "ham")