mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
Remove amavis extension.
This commit is contained in:
@@ -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")
|
||||
)
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
[{"pk": 1, "model": "amavis.users", "fields": {"priority": 7, "policy": 1, "email": "@demo.local", "fullname": "demo.local"}}]
|
||||
@@ -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
|
||||
@@ -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("""<script type="text/javascript">
|
||||
$(document).bind('domform_init', function() {
|
||||
activate_widget.call($('#id_spam_subject_tag2_act'));
|
||||
});
|
||||
</script>
|
||||
""")
|
||||
|
||||
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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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.")
|
||||
@@ -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,),
|
||||
),
|
||||
]
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
})
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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%;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
{% load i18n form_tags %}
|
||||
{% render_field form.bypass_virus_checks %}
|
||||
{% render_field form.bypass_spam_checks %}
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-4 col-md-4 col-sm-4">{% trans "Thresholds" %}</label>
|
||||
<div class="col-lg-2 col-md-2 col-sm-2">
|
||||
{{ form.spam_tag2_level }}
|
||||
</div>
|
||||
<span class="help-block">{% trans "or more is spam" %}</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-4 col-lg-2 col-md-2 col-sm-2">
|
||||
{{ form.spam_kill_level }}
|
||||
</div>
|
||||
<span class="help-block">{% trans "or more throw spam message away" %}</span>
|
||||
</div>
|
||||
{% render_field form.spam_subject_tag2 activator=True activator_label=_("default") activator_size="col-sm-3" %}
|
||||
{% render_field form.bypass_banned_checks %}
|
||||
@@ -1,31 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
<form method="POST" id="listingform">
|
||||
<table id="emails" class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="selection" width="5px"></th>
|
||||
<th id="type" width="30px" class="sortable sort-header" align="center" data-sort_order="type"></th>
|
||||
<th id="rstatus" width="25px"></th>
|
||||
<th id="score" class="sortable sort-header" data-sort_order="score">
|
||||
{% trans "Score" %}
|
||||
</th>
|
||||
<th id="to" class="sortable sort-header" data-sort_order="to">
|
||||
{% trans "To" %}
|
||||
</th>
|
||||
<th id="from" class="sortable sort-header" data-sort_order="from">
|
||||
{% trans "From" %}
|
||||
</th>
|
||||
<th id="subject" class="sortable sort-header" data-sort_order="subject">
|
||||
{% trans "Subject" %}
|
||||
</th>
|
||||
<th id="date" class="sortable sort-header" data-sort_order="date">
|
||||
{% trans "Date" %}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% include "amavis/emails_page.html" %}
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
@@ -1,34 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load amavis_tags %}
|
||||
{% load static %}
|
||||
|
||||
{% for email in email_list %}
|
||||
<tr id="{{ email.mailid }}">
|
||||
<td name="selection" width="5px">
|
||||
<input id="selection" type="checkbox" name="select_{{ email.mailid }}" value="{{ email.mailid }}">
|
||||
</td>
|
||||
<td align="center" width="30px" name="type" class="{{ email.class }}">
|
||||
{{ email.type|msgtype_to_html }}
|
||||
</td>
|
||||
<td width="25px" name="rstatus">
|
||||
{% if email.status == 'R' %}
|
||||
<span class="fa fa-mail-reply" title="{% trans 'Released' %}"></span>
|
||||
{% elif email.status == 'S' %}
|
||||
<span class="fa fa-thumbs-down" title="{% trans 'Marked as spam' %}"></span>
|
||||
{% elif email.status == 'H' %}
|
||||
<span class="fa fa-thumbs-up" title="{% trans 'Marked as non-spam' %}"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="openable {{ email.class }}" name="score">{{ email.score }}</td>
|
||||
<td class="openable {{ email.class }}" name="to">{{ email.to }}</td>
|
||||
<td class="openable {{ email.class }}" name="from">
|
||||
{{ email.from|truncatechars:30 }}
|
||||
</td>
|
||||
<td class="openable {{ email.class }}" name="subject">
|
||||
{{ email.subject|truncatechars:30 }}
|
||||
</td>
|
||||
<td class="openable {{ email.class }}" name="date">
|
||||
{{ email.date|date:"SHORT_DATETIME_FORMAT" }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -1 +0,0 @@
|
||||
{% if pre %}<pre>{% endif %}{{ body }}{% if pre %}</pre>{% endif %}
|
||||
@@ -1,37 +0,0 @@
|
||||
{% extends "fluid.html" %}{% load i18n amavis_tags %}
|
||||
{% load static %}
|
||||
|
||||
{% block pagetitle %}{% trans "Quarantine management" %}{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'amavis/css/quarantine.css' %}" />
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/jquery.sortable.css' %}" />
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script type="text/javascript" src="{% static 'js/jquery.htmltable.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/jquery.infinitescroll.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/jquery.sortable.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/history.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/searchbar.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/listing.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'amavis/js/quarantine.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
var qm = new Quarantine({
|
||||
deflocation: "listing/?sort_order=-date&reset_page=true",
|
||||
defcallback: "listing_cb",
|
||||
load_page_url: "{% url 'amavis:mail_page' %}",
|
||||
check_learning_rcpt: {{ check_learning_rcpt }},
|
||||
learning_recipient_url: "{% url 'amavis:learning_recipient_set' %}"
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block container_content %}
|
||||
<div id="menubar">
|
||||
{% quar_menu user %}
|
||||
</div>
|
||||
<div id="listing"></div>
|
||||
{% endblock %}
|
||||
@@ -1,5 +0,0 @@
|
||||
{% extends "common/mailheaders.html" %}
|
||||
{% block emailheaders %}
|
||||
{% if qtype %}<div class="alert alert-danger"><strong>{{ qtype }}</strong> {{ qreason }}</div>{% endif %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
@@ -1,79 +0,0 @@
|
||||
{% load lib_tags i18n %}
|
||||
<div class="row">
|
||||
|
||||
<div class="col-xs-7 col-md-5 col-lg-4">
|
||||
<div class="btn-group" title="{% trans 'Select' %}">
|
||||
<a class="btn btn-default"><input type="checkbox" id="selectall" /></a>
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a name="selectmsgs" href="C">{% trans "Clean" %}</a></li>
|
||||
<li><a name="selectmsgs" href="S">{% trans "Spam" %}</a></li>
|
||||
<li><a name="selectmsgs" href="V">{% trans "Virus" %}</a></li>
|
||||
<li><a name="selectmsgs" href="H">{% trans "Bad Header" %}</a></li>
|
||||
<li><a name="selectmsgs" href="M">{% trans "Bad MIME" %}</a></li>
|
||||
<li><a name="selectmsgs" href="B">{% trans "Banned" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="btn-group" title="{% trans 'Release' %}">
|
||||
<a name="release-multi" href="{% url 'amavis:mail_process' %}" class="btn btn-success disabled">
|
||||
<span class="fa fa-check"></span> {% trans 'Release' %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="btn-group" title="{% trans 'Delete' %}">
|
||||
<a name="delete-multi" href="{% url 'amavis:mail_process' %}" class="btn btn-danger disabled">
|
||||
<span class="fa fa-trash"></span> {% trans 'Delete' %}
|
||||
</a>
|
||||
</div>
|
||||
{% if manual_learning %}
|
||||
<div class="btn-group" title="{% trans 'Manual learning' %}">
|
||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span class="fa fa-cog"></span> <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a name="mark-as-spam-multi" href="{% url 'amavis:mail_process' %}">{% trans "Mark as spam" %}</a></li>
|
||||
<li><a name="mark-as-ham-multi" href="{% url 'amavis:mail_process' %}">{% trans "Mark as non-spam" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-xs-5 col-lg-4">
|
||||
{% include "common/email_searchbar.html" with extraopts=extraopts %}
|
||||
</div>
|
||||
|
||||
<div class="hidden-md col-lg-4">
|
||||
<div class="btn-group">
|
||||
<div id="filters_bar" class="navbar-nav navbar-right">
|
||||
<span class="label label-default">{% trans "Display" %}</span>
|
||||
<div id="filters" class="btn-group" data-toggle="buttons">
|
||||
<label class="btn btn-default active">
|
||||
<input type="radio" name="msgtype_all" title="{% trans 'All messages' %}" checked> {% trans "All" %}
|
||||
</label>
|
||||
<label class="btn btn-success">
|
||||
<input type="radio" name="msgtype_C" title="{% trans 'Clean only' %}"> C
|
||||
</label>
|
||||
<label class="btn btn-danger">
|
||||
<input type="radio" name="msgtype_S" title="{% trans 'Spam only' %}"> S
|
||||
</label>
|
||||
<label class="btn btn-danger">
|
||||
<input type="radio" name="msgtype_V" title="{% trans 'Virus only' %}"> V
|
||||
</label>
|
||||
<label class="btn btn-warning">
|
||||
<input type="radio" name="msgtype_H" title="{% trans 'Bad Header only' %}"> H
|
||||
</label>
|
||||
<label class="btn btn-warning">
|
||||
<input type="radio" name="msgtype_M" title="{% trans 'Bad MIME only' %}"> M
|
||||
</label>
|
||||
<label class="btn btn-warning">
|
||||
<input type="radio" name="msgtype_B" title="{% trans 'Banned only' %}"> B
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -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 %}
|
||||
@@ -1,4 +0,0 @@
|
||||
<pre id="rawheaders" class="well">
|
||||
{% for k, v in headers %}{{ k }}: {{ v }}
|
||||
{% endfor %}
|
||||
</pre>
|
||||
@@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>{% load i18n lib_tags amavis_tags %}{% load url from future %}
|
||||
{% load static %}
|
||||
<html lang="{{ LANGUAGE_CODE }}">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Modoboa</title>
|
||||
<link rel="icon" type="image/png" href="{% static 'css/logo-icon.png' %}" />
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.css' %}" />
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap-responsive.css' %}" />
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'amavis/css/selfservice.css' %}" />
|
||||
<script src="{% static 'js/jquery.js' %}" type="text/javascript"></script>
|
||||
<script src="{% static 'js/bootstrap.js' %}" type="text/javascript"></script>
|
||||
<script src="{% static 'js/global.js' %}" type="text/javascript"></script>
|
||||
<script src="{% static 'js/notify.js' %}" type="text/javascript"></script>
|
||||
<script src="{% static 'amavis/js/selfservice.js' %}" type="text/javascript"></script>
|
||||
{% url "django.views.i18n.javascript_catalog" as catalog_url %}
|
||||
<script type="text/javascript" src="{{ catalog_url }}"></script>
|
||||
<script type="text/javascript">
|
||||
set_media_url("{{ STATIC_URL }}");
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="fixed_content">
|
||||
<div id="menubar">{% viewm_menu_simple user mail_id rcpt secret_id %}</div>
|
||||
<div id="listing">{{ content }}</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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(
|
||||
'<span class="label label-%s">%s</span>' % (color, msgtype)
|
||||
)
|
||||
@@ -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<mail_id>[\w\-\+]+)/$', 'getmailcontent',
|
||||
name="mailcontent_get"),
|
||||
url(r'^process/$', 'process', name="mail_process"),
|
||||
url(r'^delete/(?P<mail_id>[\w\-\+]+)/$', 'delete', name="mail_delete"),
|
||||
url(r'^release/(?P<mail_id>[\w\-\+]+)/$', 'release',
|
||||
name="mail_release"),
|
||||
url(r'^markspam/(?P<mail_id>[\w\-\+]+)/$', 'mark_as_spam',
|
||||
name="mail_mark_as_spam"),
|
||||
url(r'^markham/(?P<mail_id>[\w\-\+]+)/$', 'mark_as_ham',
|
||||
name="mail_mark_as_ham"),
|
||||
url(r'^learning_recipient/$', 'learning_recipient',
|
||||
name="learning_recipient_set"),
|
||||
url(r'^(?P<mail_id>[\w\-\+]+)/$', 'viewmail', name="mail_detail"),
|
||||
url(r'^(?P<mail_id>[\w\-\+]+)/headers/$', 'viewheaders',
|
||||
name="headers_detail"),
|
||||
)
|
||||
@@ -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 = "<div class='alert alert-info'>%s</div>" % _("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 %}
|
||||
<iframe src="{% url 'amavis:mailcontent_get' mail_id %}" id="mailcontent"></iframe>
|
||||
""").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("""
|
||||
<iframe src="{{ url }}" id="mailcontent"></iframe>
|
||||
""").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")
|
||||
Reference in New Issue
Block a user