diff --git a/.travis.yml b/.travis.yml index 89936fae5..41ae74815 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,8 @@ addons: - ldap-utils - postfix - slapd + - librrd-dev + - rrdtool cache: pip @@ -50,7 +52,7 @@ before_script: script: - python ./tests.py - cd test_project - - coverage run manage.py test modoboa.core modoboa.lib modoboa.admin modoboa.limits modoboa.transport modoboa.relaydomains modoboa.dnstools modoboa.ldapsync modoboa.policyd + - coverage run manage.py test modoboa.core modoboa.lib modoboa.admin modoboa.limits modoboa.transport modoboa.relaydomains modoboa.dnstools modoboa.ldapsync modoboa.policyd modoboa.maillog after_success: - coverage combine diff --git a/modoboa/admin/factories.py b/modoboa/admin/factories.py index 2d839c7ad..13e5bb6d9 100644 --- a/modoboa/admin/factories.py +++ b/modoboa/admin/factories.py @@ -16,6 +16,7 @@ class DomainFactory(PermissionFactory): model = models.Domain django_get_or_create = ("name", ) + name = factory.Sequence(lambda n: "domain{}.test".format(n)) type = "domain" # NOQA:A003 quota = 0 default_mailbox_quota = 10 diff --git a/modoboa/admin/static/admin/js/admin.js b/modoboa/admin/static/admin/js/admin.js index 087434257..0ef78734c 100644 --- a/modoboa/admin/static/admin/js/admin.js +++ b/modoboa/admin/static/admin/js/admin.js @@ -172,9 +172,12 @@ Domains.prototype = { if (this.navobj.getbaseurl() === "list") { args.objtype = "domain"; this.options.eor_message = gettext("No more domain to show"); - } else { + } else if (this.navobj.getbaseurl() === "quota") { args.objtype = "quota"; this.options.eor_message = gettext("No more quota to show"); + } else { + args.objtype = "log"; + this.options.eor_message = gettext("No more log to show"); } return args; }, diff --git a/modoboa/admin/templates/admin/domains_log_headers.html b/modoboa/admin/templates/admin/domains_log_headers.html new file mode 100644 index 000000000..f278c15f3 --- /dev/null +++ b/modoboa/admin/templates/admin/domains_log_headers.html @@ -0,0 +1,6 @@ +{% load i18n %} +{% trans "Queue ID" %} +{% trans "Date" %} +{% trans "From" %} +{% trans "To" %} +{% trans "Status" %} diff --git a/modoboa/admin/templates/admin/domains_logs.html b/modoboa/admin/templates/admin/domains_logs.html new file mode 100644 index 000000000..89415883d --- /dev/null +++ b/modoboa/admin/templates/admin/domains_logs.html @@ -0,0 +1,9 @@ +{% for log in logs %} + + {{ log.queue_id }} + {{ log.date|date:"SHORT_DATETIME_FORMAT" }} + {{ log.sender|truncatechars:30 }} + {{ log.rcpt }} + {{ log.status }} + +{% endfor %} diff --git a/modoboa/admin/templatetags/admin_tags.py b/modoboa/admin/templatetags/admin_tags.py index abdc01466..c6e93bf44 100644 --- a/modoboa/admin/templatetags/admin_tags.py +++ b/modoboa/admin/templatetags/admin_tags.py @@ -36,10 +36,12 @@ def domains_menu(selection, user, ajax_mode=True): if ajax_mode: domain_list_url = "list/" quota_list_url = "quotas/" + logs_url = "logs/" nav_classes += " ajaxnav" else: domain_list_url = reverse("admin:domain_list") quota_list_url = domain_list_url + "#quotas/" + logs_url = domain_list_url + "#logs/" entries = [ {"name": "domains", "label": _("List domains"), @@ -51,6 +53,11 @@ def domains_menu(selection, user, ajax_mode=True): "img": "fa fa-hdd-o", "class": "ajaxnav navigation", "url": quota_list_url}, + {"name": "logs", + "label": _("Message logs"), + "img": "fa fa-list", + "class": "ajaxnav navigation", + "url": logs_url}, ] if user.has_perm("admin.add_domain"): extra_entries = signals.extra_domain_menu_entries.send( diff --git a/modoboa/admin/tests/test_domain.py b/modoboa/admin/tests/test_domain.py index 28bebccb4..65da58381 100644 --- a/modoboa/admin/tests/test_domain.py +++ b/modoboa/admin/tests/test_domain.py @@ -16,6 +16,8 @@ from modoboa.core import factories as core_factories from modoboa.core.models import User from modoboa.core.tests.test_views import SETTINGS_SAMPLE from modoboa.lib.tests import ModoTestCase +from modoboa.maillog import factories as ml_factories + from . import utils from .. import factories from ..models import Alias, Domain @@ -336,6 +338,30 @@ class DomainTestCase(ModoTestCase): response = self.ajax_get("{}?sort_order=allocated_quota".format(url)) self.assertNotEqual(old_rows, response["rows"]) + def test_domain_logs_list_view(self): + """Test logs list view.""" + domain = Domain.objects.get(name="test.com") + ml_factories.MaillogFactory(from_domain=domain) + ml_factories.MaillogFactory(to_domain=domain, status="received") + ml_factories.MaillogFactory() + ml_factories.MaillogFactory() + url = reverse("admin:domain_logs_list") + response = self.ajax_get(url) + self.assertIn("ID1", response["rows"]) + + old_rows = response["rows"] + response = self.ajax_get("{}?sort_order=-sender".format(url)) + self.assertNotEqual(old_rows, response["rows"]) + + response = self.ajax_get("{}?searchquery=ID1".format(url)) + self.assertNotEqual(old_rows, response["rows"]) + + admin = User.objects.get(username="admin@test.com") + self.client.force_login(admin) + url = reverse("admin:domain_logs_list") + response = self.ajax_get(url) + self.assertNotEqual(old_rows, response["rows"]) + def test_statitics_widget(self): """Test statistics display in dashboard.""" url = reverse("core:dashboard") diff --git a/modoboa/admin/urls.py b/modoboa/admin/urls.py index 681313f00..5b741ea53 100644 --- a/modoboa/admin/urls.py +++ b/modoboa/admin/urls.py @@ -24,6 +24,8 @@ urlpatterns = [ path('domains/list/', domain_views._domains, name="_domain_list"), path('domains/quotas/', domain_views.list_quotas, name="domain_quota_list"), + path('domains/logs/', domain_views.list_logs, + name="domain_logs_list"), path('domains/flatlist/', domain_views.domains_list, name="domain_flat_list"), path('domains/new/', domain_views.newdomain, name="domain_add"), diff --git a/modoboa/admin/views/domain.py b/modoboa/admin/views/domain.py index 6251b48bf..e762f2430 100644 --- a/modoboa/admin/views/domain.py +++ b/modoboa/admin/views/domain.py @@ -8,7 +8,7 @@ from django.contrib.auth import mixins as auth_mixins from django.contrib.auth.decorators import ( login_required, permission_required, user_passes_test ) -from django.db.models import Sum +from django.db.models import Q, Sum from django.http import HttpResponseRedirect from django.shortcuts import render from django.template.loader import render_to_string @@ -21,6 +21,8 @@ from modoboa.core import signals as core_signals from modoboa.lib.exceptions import PermDeniedException from modoboa.lib.listing import get_listing_page, get_sort_order from modoboa.lib.web_utils import render_to_json_response +from modoboa.maillog import models as ml_models + from .. import signals from ..forms import DomainForm, DomainWizard from ..lib import get_domains @@ -119,9 +121,12 @@ def domains(request, tplname="admin/domains.html"): ) def get_next_page(request): """Return the next page of the domain or quota list.""" - if request.GET.get("objtype", "domain") == "domain": + objtype = request.GET.get("objtype", "domain") + if objtype == "domain": return _domains(request) - return list_quotas(request) + if objtype == "quota": + return list_quotas(request) + return list_logs(request) @login_required @@ -160,6 +165,43 @@ def list_quotas(request): return render_to_json_response(context) +@login_required +@permission_required("admin.view_domain") +def list_logs(request): + """List all Maillog entries.""" + sort_order, sort_dir = get_sort_order(request.GET, "date") + search = request.GET.get("searchquery") + if not request.user.is_superuser: + domains = Domain.objects.get_for_admin(request.user) + logs = ml_models.Maillog.objects.filter( + Q(from_domain__in=domains) | Q(to_domain__in=domains) + ) + else: + logs = ml_models.Maillog.objects.all() + logs = logs.order_by("{}{}".format(sort_dir, sort_order)) + if search: + logs = logs.filter( + Q(sender__icontains=search) | + Q(rcpt__icontains=search) | + Q(queue_id__icontains=search) | + Q(status__icontains=search) + ) + page = get_listing_page(logs, request.GET.get("page", 1)) + context = { + "headers": render_to_string( + "admin/domains_log_headers.html", {}, request + ) + } + if page is None: + context["length"] = 0 + else: + context["rows"] = render_to_string( + "admin/domains_logs.html", {"logs": page}, request + ) + context["pages"] = [page.number] + return render_to_json_response(context) + + @login_required @permission_required("admin.add_domain") @reversion.create_revision() diff --git a/modoboa/core/commands/templates/settings.py.tpl b/modoboa/core/commands/templates/settings.py.tpl index e38d40927..41f47f447 100644 --- a/modoboa/core/commands/templates/settings.py.tpl +++ b/modoboa/core/commands/templates/settings.py.tpl @@ -81,6 +81,7 @@ MODOBOA_APPS = ( 'modoboa.limits', 'modoboa.parameters', 'modoboa.dnstools', + 'modoboa.maillog', # Modoboa extensions here. {% for extension in extensions %} '{{ extension }}', {% endfor %} diff --git a/modoboa/core/tests/test_views.py b/modoboa/core/tests/test_views.py index c99186858..1c14f2d8f 100644 --- a/modoboa/core/tests/test_views.py +++ b/modoboa/core/tests/test_views.py @@ -89,7 +89,10 @@ SETTINGS_SAMPLE = { "core-sms_provider": "", "core-sms_ovh_endpoint": "ovh-eu", "core-password_recovery_msg": "", - "core-ldap_dovecot_sync": False + "core-ldap_dovecot_sync": False, + "maillog-logfile": "/var/log/mail.log", + "maillog-rrd_rootdir": "/tmp", + "maillog-greylist": False, } diff --git a/modoboa/maillog/__init__.py b/modoboa/maillog/__init__.py new file mode 100644 index 000000000..43fb5ed25 --- /dev/null +++ b/modoboa/maillog/__init__.py @@ -0,0 +1 @@ +default_app_config = "modoboa.maillog.apps.MaillogConfig" diff --git a/modoboa/maillog/apps.py b/modoboa/maillog/apps.py new file mode 100644 index 000000000..c4f62c1a5 --- /dev/null +++ b/modoboa/maillog/apps.py @@ -0,0 +1,16 @@ +"""AppConfig for stats.""" + +from django.apps import AppConfig + +from .forms import load_settings + + +class MaillogConfig(AppConfig): + """App configuration.""" + + name = "modoboa.maillog" + verbose_name = "Modoboa graphical statistics" + + def ready(self): + load_settings() + from . import handlers diff --git a/modoboa/maillog/factories.py b/modoboa/maillog/factories.py new file mode 100644 index 000000000..3c26d246e --- /dev/null +++ b/modoboa/maillog/factories.py @@ -0,0 +1,25 @@ +"""App. factories.""" + +import factory + +from django.utils import timezone + +from modoboa.admin import factories as admin_factories + +from . import models + + +class MaillogFactory(factory.DjangoModelFactory): + """Factory class for Maillog.""" + + class Meta: + model = models.Maillog + + queue_id = factory.Sequence(lambda n: "ID{}".format(n)) + date = timezone.now() + sender = factory.Sequence(lambda n: "sender{}@example.test".format(n)) + rcpt = factory.Sequence(lambda n: "rcpt{}@example2.test".format(n)) + size = 100 + status = "sent" + from_domain = factory.SubFactory(admin_factories.DomainFactory) + to_domain = factory.SubFactory(admin_factories.DomainFactory) diff --git a/modoboa/maillog/forms.py b/modoboa/maillog/forms.py new file mode 100644 index 000000000..2d38ff2f1 --- /dev/null +++ b/modoboa/maillog/forms.py @@ -0,0 +1,59 @@ +"""Modoboa stats forms.""" + +import rrdtool + +from pkg_resources import parse_version + +from django.conf import settings +from django.utils.translation import ugettext_lazy +from django import forms + +from modoboa.lib import form_utils +from modoboa.parameters import forms as param_forms + + +class ParametersForm(param_forms.AdminParametersForm): + """Stats global parameters.""" + + app = "maillog" + + general_sep = form_utils.SeparatorField(label=ugettext_lazy("General")) + + logfile = forms.CharField( + label=ugettext_lazy("Path to the log file"), + initial="/var/log/mail.log", + help_text=ugettext_lazy("Path to log file used to collect statistics"), + widget=forms.TextInput(attrs={"class": "form-control"}) + ) + + rrd_rootdir = forms.CharField( + label=ugettext_lazy("Directory to store RRD files"), + initial="/tmp/modoboa", + help_text=ugettext_lazy( + "Path to directory where RRD files are stored"), + widget=forms.TextInput(attrs={"class": "form-control"}) + ) + + greylist = form_utils.YesNoField( + label=ugettext_lazy("Show greylisted messages"), + initial=False, + help_text=ugettext_lazy( + "Differentiate between hard and soft rejects (greylisting)") + ) + + def __init__(self, *args, **kwargs): + """Check RRDtool version.""" + super().__init__(*args, **kwargs) + rrd_version = parse_version(rrdtool.lib_version()) + required_version = parse_version("1.6.0") + test_mode = getattr(settings, "RRDTOOL_TEST_MODE", False) + if rrd_version < required_version and not test_mode: + del self.fields["greylist"] + + +def load_settings(): + """Load app settings.""" + from modoboa.parameters import tools as param_tools + + param_tools.registry.add( + "global", ParametersForm, ugettext_lazy("Statistics")) diff --git a/modoboa/maillog/graphics.py b/modoboa/maillog/graphics.py new file mode 100644 index 000000000..fddcf8bf3 --- /dev/null +++ b/modoboa/maillog/graphics.py @@ -0,0 +1,269 @@ +"""Classes to define graphics.""" + +import inspect +from itertools import chain +import os + +from lxml import etree +import six + +from django.conf import settings +from django.utils.encoding import smart_bytes, smart_text +from django.utils.translation import ugettext as _, ugettext_lazy + +from modoboa.admin import models as admin_models +from modoboa.lib import exceptions +from modoboa.lib.sysutils import exec_cmd +from modoboa.parameters import tools as param_tools + + +class Curve: + + """Graphic curve. + + Simple way to represent a graphic curve. + """ + + def __init__(self, dsname, color, legend, cfunc="AVERAGE"): + """Constructor. + """ + self.dsname = dsname + self.color = color + self.legend = legend + self.cfunc = cfunc + + def to_rrd_command_args(self, rrdfile): + """Convert this curve to the approriate RRDtool command. + + :param str rrdfile: RRD file name + :return: a list + """ + rrdfile = os.path.join( + param_tools.get_global_parameter("rrd_rootdir"), "%s.rrd" % rrdfile + ) + return [ + 'DEF:%s=%s:%s:%s' % + (self.dsname, rrdfile, self.dsname, self.cfunc), + 'CDEF:%(ds)spm=%(ds)s,UN,0,%(ds)s,IF,60,*' % {"ds": self.dsname}, + 'XPORT:%spm:"%s"' % (self.dsname, self.legend) + ] + + +class Graphic: + """Graphic.""" + + def __init__(self): + """Constructor.""" + self._curves = [] + try: + order = getattr(self, "order") + except AttributeError: + for member in inspect.getmembers(self): + if isinstance(member[1], Curve): + self._curves += [member[1]] + else: + for name in order: + try: + curve = getattr(self, name) + except AttributeError: + continue + if not isinstance(curve, Curve): + continue + self._curves += [curve] + + @property + def display_name(self): + return self.__class__.__name__.lower() + + @property + def rrdtool_binary(self): + """Return path to rrdtool binary.""" + dpath = None + code, output = exec_cmd("which rrdtool") + if not code: + dpath = output.strip() + else: + known_paths = getattr( + settings, "RRDTOOL_LOOKUP_PATH", + ("/usr/bin/rrdtool", "/usr/local/bin/rrdtool") + ) + for fpath in known_paths: + if os.path.isfile(fpath) and os.access(fpath, os.X_OK): + dpath = fpath + if dpath is None: + raise exceptions.InternalError( + _("Failed to locate rrdtool binary.")) + return smart_text(dpath) + + def export(self, rrdfile, start, end): + """Export data to XML using rrdtool and convert it to JSON.""" + result = [] + cmdargs = [] + for curve in self._curves: + result += [{ + "name": str(curve.legend), + "color": curve.color, "data": [] + }] + cmdargs += curve.to_rrd_command_args(rrdfile) + code = 0 + + cmd = "{} xport --start {} --end {} ".format( + self.rrdtool_binary, str(start), str(end)) + cmd += " ".join(cmdargs) + code, output = exec_cmd(smart_bytes(cmd)) + if code: + return [] + + tree = etree.fromstring(output) + timestamp = int(tree.xpath('/xport/meta/start')[0].text) + step = int(tree.xpath('/xport/meta/step')[0].text) + for row in tree.xpath('/xport/data/row'): + for vindex, value in enumerate(row.findall('v')): + if value.text == 'NaN': + result[vindex]['data'].append({'x': timestamp, 'y': 0}) + else: + result[vindex]['data'].append( + {'x': timestamp, 'y': float(value.text)} + ) + timestamp += step + return result + + +class GraphicSet(object): + """A set of graphics.""" + + domain_selector = False + title = None + _graphics = [] + + def __init__(self, instances=None): + if instances is None: + instances = [] + self.__ginstances = instances + + @property + def html_id(self): + return self.__class__.__name__.lower() + + @property + def graphics(self): + if not self.__ginstances: + self.__ginstances = [graphic() for graphic in self._graphics] + return self.__ginstances + + def get_graphic_names(self): + return [graphic.display_name for graphic in self._graphics] + + def get_file_name(self, request): + """Return database file name.""" + return self.file_name + + def export(self, rrdfile, start, end): + result = {} + for graph in self.graphics: + result[graph.display_name] = { + "title": six.text_type(graph.title), + "curves": graph.export(rrdfile, start, end) + } + return result + + +class AverageTraffic(Graphic): + """Average traffic.""" + + title = ugettext_lazy('Average traffic (msgs/min)') + + # Curve definitions + sent = Curve("sent", "lawngreen", ugettext_lazy("sent messages")) + recv = Curve("recv", "steelblue", ugettext_lazy("received messages")) + bounced = Curve("bounced", "yellow", ugettext_lazy("bounced messages")) + reject = Curve("reject", "tomato", ugettext_lazy("rejected messages")) + virus = Curve("virus", "orange", ugettext_lazy("virus messages")) + spam = Curve("spam", "silver", ugettext_lazy("spam messages")) + + order = ["reject", "bounced", "recv", "sent", "virus", "spam"] + + def __init__(self, greylist=False): + if greylist: + self.greylist = Curve( + "greylist", "dimgrey", ugettext_lazy("greylisted messages")) + self.order = [ + "reject", "greylist", "bounced", "recv", "sent", "virus", + "spam" + ] + super().__init__() + + +class AverageTrafficSize(Graphic): + """Average traffic size.""" + + title = ugettext_lazy('Average normal traffic size (bytes/min)') + + # Curve definitions + size_recv = Curve("size_recv", "orange", ugettext_lazy("received size")) + size_sent = Curve( + "size_sent", "mediumturquoise", ugettext_lazy("sent size") + ) + + +class MailTraffic(GraphicSet): + """Mail traffic graphic set.""" + + domain_selector = True + title = ugettext_lazy("Mail traffic") + _graphics = [AverageTraffic, AverageTrafficSize] + + def __init__(self, greylist=False): + instances = [AverageTraffic(greylist), AverageTrafficSize()] + super().__init__(instances) + + def _check_domain_access(self, user, pattern): + """Check if an administrator can access a domain. + + If a non super administrator asks for the global view, we give him + a view on the first domain he manage instead. + + :return: a domain name (str) or None. + """ + if pattern in [None, "global"]: + if not user.is_superuser: + domains = admin_models.Domain.objects.get_for_admin(user) + if not domains.exists(): + return None + return domains.first().name + return "global" + + results = list( + chain( + admin_models.Domain.objects.filter(name__startswith=pattern), + admin_models.DomainAlias.objects.filter( + name__startswith=pattern) + ) + ) + if len(results) != 1: + return None + if not user.can_access(results[0]): + raise exceptions.PermDeniedException + return results[0].name + + def get_file_name(self, request): + """Retrieve file name according to user and args.""" + searchq = request.GET.get("searchquery", None) + return self._check_domain_access(request.user, searchq) + + +class AccountCreationGraphic(Graphic): + """Account creation over time.""" + + title = ugettext_lazy("Average account creation (account/hour)") + + accounts = Curve( + "new_accounts", "steelblue", ugettext_lazy("New accounts")) + + +class AccountGraphicSet(GraphicSet): + """A graphic set for accounts.""" + + file_name = "new_accounts" + title = ugettext_lazy("Accounts") + _graphics = [AccountCreationGraphic] diff --git a/modoboa/maillog/handlers.py b/modoboa/maillog/handlers.py new file mode 100644 index 000000000..5d820954a --- /dev/null +++ b/modoboa/maillog/handlers.py @@ -0,0 +1,38 @@ +"""Modoboa stats signal handlers.""" + +from django.urls import reverse +from django.dispatch import receiver +from django.utils.translation import ugettext as _ + +from modoboa.admin import signals as admin_signals +from modoboa.core import signals as core_signals +from modoboa.parameters import tools as param_tools + +from . import graphics +from . import signals + + +@receiver(core_signals.extra_user_menu_entries) +def menu(sender, location, user, **kwargs): + """Return extra menu entry.""" + if location != "top_menu" or user.role == "SimpleUsers": + return [] + return [ + {"name": "stats", + "label": _("Statistics"), + "url": reverse('maillog:fullindex')} + ] + + +@receiver(signals.get_graph_sets) +def get_default_graphic_sets(sender, **kwargs): + """Return graphic set.""" + mail_traffic_gset = graphics.MailTraffic( + param_tools.get_global_parameter("greylist", raise_exception=False)) + result = { + mail_traffic_gset.html_id: mail_traffic_gset + } + if kwargs.get("user").is_superuser: + account_gset = graphics.AccountGraphicSet() + result.update({account_gset.html_id: account_gset}) + return result diff --git a/modoboa/maillog/lib.py b/modoboa/maillog/lib.py new file mode 100644 index 000000000..5417ba542 --- /dev/null +++ b/modoboa/maillog/lib.py @@ -0,0 +1,25 @@ +# coding: utf-8 +import sys +import time + + +def date_to_timestamp(timetuple): + """Date conversion. + + Returns a date and a time in seconds from the epoch. + + :param list timetuple: list containing date + :return: an integer + """ + date = " ".join( + [("%d" % elem) if isinstance(elem, int) else elem + for elem in timetuple] + ) + fmt = "%Y %m %d %H %M %S" \ + if timetuple[1].isdigit() else "%Y %b %d %H %M %S" + try: + local = time.strptime(date, fmt) + except ValueError: + print >> sys.stderr, "Error: failed to convert date and time" + return 0 + return int(time.mktime(local)) diff --git a/modoboa/maillog/locale/cs_CZ/LC_MESSAGES/django.po b/modoboa/maillog/locale/cs_CZ/LC_MESSAGES/django.po new file mode 100644 index 000000000..c16bed40e --- /dev/null +++ b/modoboa/maillog/locale/cs_CZ/LC_MESSAGES/django.po @@ -0,0 +1,178 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Miroslav Abrahám , 2013-2015 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: 2015-01-06 10:05+0000\n" +"Last-Translator: Miroslav Abrahám \n" +"Language-Team: Czech (Czech Republic) (http://www.transifex.com/projects/p/" +"modoboa/language/cs_CZ/)\n" +"Language: cs_CZ\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + +#: forms.py:20 +msgid "General" +msgstr "Obecné" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "Cesta k souboru záznamů" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "" +"Cesta k souboru záznamů, který se používá pro sběr statistických informací" + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "Adresář pro RRD soubory" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "Cesta k adresáři, ve kterém budou ukládány RRD soubory" + +#: forms.py:38 +#, fuzzy +#| msgid "rejected messages" +msgid "Show greylisted messages" +msgstr "odmítnuté zprávy" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "Statistiky" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "" + +#: graphics.py:174 +msgid "Average traffic (msgs/min)" +msgstr "Průměrný provoz (zprávy/min)" + +#: graphics.py:177 +msgid "sent messages" +msgstr "odeslané zprávy" + +#: graphics.py:178 +msgid "received messages" +msgstr "přijaté zprávy" + +#: graphics.py:179 +msgid "bounced messages" +msgstr "nedoručené zprávy" + +#: graphics.py:180 +msgid "rejected messages" +msgstr "odmítnuté zprávy" + +#: graphics.py:181 +#, fuzzy +#| msgid "sent messages" +msgid "virus messages" +msgstr "odeslané zprávy" + +#: graphics.py:182 +#, fuzzy +#| msgid "sent messages" +msgid "spam messages" +msgstr "odeslané zprávy" + +#: graphics.py:189 +#, fuzzy +#| msgid "rejected messages" +msgid "greylisted messages" +msgstr "odmítnuté zprávy" + +#: graphics.py:200 +msgid "Average normal traffic size (bytes/min)" +msgstr "Průměrná velikost provozu (bajtů/min)" + +#: graphics.py:203 +msgid "received size" +msgstr "doručená velikost" + +#: graphics.py:205 +msgid "sent size" +msgstr "odeslaná velikost" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "Provoz emailů" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "" + +#: graphics.py:261 +msgid "New accounts" +msgstr "" + +#: graphics.py:268 +msgid "Accounts" +msgstr "" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "Grafické statistiky" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "Hledat doménu" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "Uložit" + +#: views.py:33 +msgid "No statistics available" +msgstr "Žádné statistiky nejsou dostupné" + +#: views.py:40 +msgid "Day" +msgstr "Den" + +#: views.py:41 +msgid "Week" +msgstr "Týden" + +#: views.py:42 +msgid "Month" +msgstr "Měsíc" + +#: views.py:43 +msgid "Year" +msgstr "Rok" + +#: views.py:44 +msgid "Custom" +msgstr "Vlastní" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "Nastaveno neznámé zobrazení" + +#: views.py:68 +#, fuzzy +#| msgid "Unknown graphic set" +msgid "Unknown domain" +msgstr "Nastaveno neznámé zobrazení" + +#: views.py:73 +msgid "Bad custom period" +msgstr "Neplatně nastavený vlastní časový interval" + +#~ msgid "Graphical statistics about emails traffic using RRDtool" +#~ msgstr "Grafické statistiky emailového provozu s použitím RRDtool" diff --git a/modoboa/maillog/locale/cs_CZ/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/cs_CZ/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..42ede7dd8 --- /dev/null +++ b/modoboa/maillog/locale/cs_CZ/LC_MESSAGES/djangojs.po @@ -0,0 +1,24 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Miroslav Abrahám , 2013,2015 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: 2015-01-06 10:05+0000\n" +"Last-Translator: Miroslav Abrahám \n" +"Language-Team: Czech (Czech Republic) (http://www.transifex.com/projects/p/" +"modoboa/language/cs_CZ/)\n" +"Language: cs_CZ\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "Vyhledat doménu" diff --git a/modoboa/maillog/locale/de/LC_MESSAGES/django.po b/modoboa/maillog/locale/de/LC_MESSAGES/django.po new file mode 100644 index 000000000..fc892622d --- /dev/null +++ b/modoboa/maillog/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,178 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Arvedui , 2014 +# Patrick Koetter , 2010 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: 2014-12-03 11:02+0000\n" +"Last-Translator: Arvedui \n" +"Language-Team: German (http://www.transifex.com/projects/p/modoboa/language/" +"de/)\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: forms.py:20 +msgid "General" +msgstr "Allgemein" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "Pfad zur Log-Datei" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "Pfad zur Log-Datei, um Statistiken zu sammeln" + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "Ablageverzeichnis für RRD-Dateien" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "Pfad zum Verzeichnis in dem die RRD-Dateien abgelegt werden" + +#: forms.py:38 +#, fuzzy +#| msgid "rejected messages" +msgid "Show greylisted messages" +msgstr "Abgewiesene Nachrichten" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "Statistiken" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "" + +#: graphics.py:174 +msgid "Average traffic (msgs/min)" +msgstr "Durchschnittlicher Traffic (Nachrichten/min)" + +#: graphics.py:177 +msgid "sent messages" +msgstr "Gesendete Nachrichten" + +#: graphics.py:178 +msgid "received messages" +msgstr "Empfangene Nachrichten" + +#: graphics.py:179 +msgid "bounced messages" +msgstr "Zurückgeworfene Nachrichten" + +#: graphics.py:180 +msgid "rejected messages" +msgstr "Abgewiesene Nachrichten" + +#: graphics.py:181 +#, fuzzy +#| msgid "sent messages" +msgid "virus messages" +msgstr "Gesendete Nachrichten" + +#: graphics.py:182 +#, fuzzy +#| msgid "sent messages" +msgid "spam messages" +msgstr "Gesendete Nachrichten" + +#: graphics.py:189 +#, fuzzy +#| msgid "rejected messages" +msgid "greylisted messages" +msgstr "Abgewiesene Nachrichten" + +#: graphics.py:200 +msgid "Average normal traffic size (bytes/min)" +msgstr "Durchschnittliche Traffic-Menge (Bytes/min)" + +#: graphics.py:203 +msgid "received size" +msgstr "Empfangsgröße" + +#: graphics.py:205 +msgid "sent size" +msgstr "Versendegröße" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "Mailverkehr" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "" + +#: graphics.py:261 +msgid "New accounts" +msgstr "" + +#: graphics.py:268 +msgid "Accounts" +msgstr "" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "Grafische Statistiken" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "Domain suchen" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "Antworten" + +#: views.py:33 +msgid "No statistics available" +msgstr "Keine Statistik-Daten verfügbar" + +#: views.py:40 +msgid "Day" +msgstr "Tag" + +#: views.py:41 +msgid "Week" +msgstr "Woche" + +#: views.py:42 +msgid "Month" +msgstr "Monat" + +#: views.py:43 +msgid "Year" +msgstr "Jahr" + +#: views.py:44 +msgid "Custom" +msgstr "Benutzerdefiniert" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "Unbekanntes graphisches Set" + +#: views.py:68 +#, fuzzy +#| msgid "Unknown graphic set" +msgid "Unknown domain" +msgstr "Unbekanntes graphisches Set" + +#: views.py:73 +msgid "Bad custom period" +msgstr "Ungültiger individueller Zeitraum" + +#~ msgid "Graphical statistics about emails traffic using RRDtool" +#~ msgstr "Grafische E-Mail-Statistik (setzt RRDtool ein)" diff --git a/modoboa/maillog/locale/de/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/de/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..2be39c4bc --- /dev/null +++ b/modoboa/maillog/locale/de/LC_MESSAGES/djangojs.po @@ -0,0 +1,25 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Arvedui , 2014 +# Patrick Koetter , 2010 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: 2014-12-01 12:12+0000\n" +"Last-Translator: Arvedui \n" +"Language-Team: German (http://www.transifex.com/projects/p/modoboa/language/" +"de/)\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "Domain suchen" diff --git a/modoboa/maillog/locale/el_GR/LC_MESSAGES/django.po b/modoboa/maillog/locale/el_GR/LC_MESSAGES/django.po new file mode 100644 index 000000000..a493fd517 --- /dev/null +++ b/modoboa/maillog/locale/el_GR/LC_MESSAGES/django.po @@ -0,0 +1,176 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Giannis Kapetanakis , 2016 +# Kostas Moumouris , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: 2017-10-12 07:37+0000\n" +"Last-Translator: Kostas Moumouris \n" +"Language-Team: Greek (Greece) (http://www.transifex.com/tonio/modoboa/" +"language/el_GR/)\n" +"Language: el_GR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: forms.py:20 +msgid "General" +msgstr "Γενικά" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "Διαδρομή προς το αρχείο καταγραφής" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "Διαδρομή προς το αρχείο καταγραφής για την συλλογή στατιστικών" + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "Κατάλογος αρχείων για την αποθήκευση των αρχείων RRD" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "Διαδρομή προς τον κατάλογο αρχείων για την αποθήκευση αρχείων RRD" + +#: forms.py:38 +msgid "Show greylisted messages" +msgstr "Εμφάνιση μηνυμάτων greylisted" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "" +"Διαφοροποίηση μεταξύ σκληρών (hard) και μαλακών (soft) απορρίψεων " +"(greylisting)" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "Στατιστικά" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "Αδυναμία εύρεσης του εκτελέσουμε rrdtool." + +#: graphics.py:174 +msgid "Average traffic (msgs/min)" +msgstr "Μέσος όρος κίνησης (μηνύματα/λεπτό)" + +#: graphics.py:177 +msgid "sent messages" +msgstr "απεσταλμένα μηνύματα" + +#: graphics.py:178 +msgid "received messages" +msgstr "ληφθέντα μηνήματα" + +#: graphics.py:179 +msgid "bounced messages" +msgstr "Μηνύματα bounced " + +#: graphics.py:180 +msgid "rejected messages" +msgstr "απορριφθέντα μηνύματα" + +#: graphics.py:181 +msgid "virus messages" +msgstr "" +"Μηνύματα με ιούς \n" +" " + +#: graphics.py:182 +msgid "spam messages" +msgstr "Διαφημιστικά μηνύματα" + +#: graphics.py:189 +msgid "greylisted messages" +msgstr "" +"Μηνύματα που είναι greylisted\n" +" " + +#: graphics.py:200 +msgid "Average normal traffic size (bytes/min)" +msgstr "Μέσος όρος φυσιολογικού traffic (bytes/min) " + +#: graphics.py:203 +msgid "received size" +msgstr "Μέγεθος λαμβανόμενου" + +#: graphics.py:205 +msgid "sent size" +msgstr "μέγεθος αποστολής" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "Κίνηση Mail" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "Μέσος όρος δημιουργίας λογαριασμών (λογαριασμοί/ώρα)" + +#: graphics.py:261 +msgid "New accounts" +msgstr "Νέοι λογαριασμοί" + +#: graphics.py:268 +msgid "Accounts" +msgstr "Λογαριασμοί" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "Γραφικά στατιστικά" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "Αναζήτηση ενός domain" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "Εφαρμογή" + +#: views.py:33 +msgid "No statistics available" +msgstr "Δεν υπάρχουν διαθέσιμα στατιστικά" + +#: views.py:40 +msgid "Day" +msgstr "Ημέρα" + +#: views.py:41 +msgid "Week" +msgstr "Εβδομάδα" + +#: views.py:42 +msgid "Month" +msgstr "Μήνας" + +#: views.py:43 +msgid "Year" +msgstr "Έτος" + +#: views.py:44 +msgid "Custom" +msgstr "Προσαρμοσμένο" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "Άγνωστο σετ γραφικών" + +#: views.py:68 +#, fuzzy +#| msgid "Unknown graphic set" +msgid "Unknown domain" +msgstr "Άγνωστο σετ γραφικών" + +#: views.py:73 +msgid "Bad custom period" +msgstr "Λάθος προσαρμοσμένη περίοδος" + +#~ msgid "Graphical statistics about emails traffic using RRDtool" +#~ msgstr "Γραφικά στατιστικά της κίνησης email με RRDtool" diff --git a/modoboa/maillog/locale/el_GR/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/el_GR/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..cf1b82a5f --- /dev/null +++ b/modoboa/maillog/locale/el_GR/LC_MESSAGES/djangojs.po @@ -0,0 +1,23 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Giannis Kapetanakis , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: 2017-09-22 22:21+0000\n" +"Last-Translator: Giannis Kapetanakis \n" +"Language-Team: Greek (Greece) (http://www.transifex.com/tonio/modoboa/language/el_GR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: el_GR\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "Αναζήτηση ενός domain" diff --git a/modoboa/maillog/locale/en/LC_MESSAGES/django.po b/modoboa/maillog/locale/en/LC_MESSAGES/django.po new file mode 100644 index 000000000..0b5684fc4 --- /dev/null +++ b/modoboa/maillog/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,162 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: forms.py:20 +msgid "General" +msgstr "" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "" + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "" + +#: forms.py:38 +msgid "Show greylisted messages" +msgstr "" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "" + +#: graphics.py:174 +msgid "Average traffic (msgs/min)" +msgstr "" + +#: graphics.py:177 +msgid "sent messages" +msgstr "" + +#: graphics.py:178 +msgid "received messages" +msgstr "" + +#: graphics.py:179 +msgid "bounced messages" +msgstr "" + +#: graphics.py:180 +msgid "rejected messages" +msgstr "" + +#: graphics.py:181 +msgid "virus messages" +msgstr "" + +#: graphics.py:182 +msgid "spam messages" +msgstr "" + +#: graphics.py:189 +msgid "greylisted messages" +msgstr "" + +#: graphics.py:200 +msgid "Average normal traffic size (bytes/min)" +msgstr "" + +#: graphics.py:203 +msgid "received size" +msgstr "" + +#: graphics.py:205 +msgid "sent size" +msgstr "" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "" + +#: graphics.py:261 +msgid "New accounts" +msgstr "" + +#: graphics.py:268 +msgid "Accounts" +msgstr "" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "" + +#: views.py:33 +msgid "No statistics available" +msgstr "" + +#: views.py:40 +msgid "Day" +msgstr "" + +#: views.py:41 +msgid "Week" +msgstr "" + +#: views.py:42 +msgid "Month" +msgstr "" + +#: views.py:43 +msgid "Year" +msgstr "" + +#: views.py:44 +msgid "Custom" +msgstr "" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "" + +#: views.py:68 +msgid "Unknown domain" +msgstr "" + +#: views.py:73 +msgid "Bad custom period" +msgstr "" diff --git a/modoboa/maillog/locale/en/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/en/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..11897ec58 --- /dev/null +++ b/modoboa/maillog/locale/en/LC_MESSAGES/djangojs.po @@ -0,0 +1,22 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "" diff --git a/modoboa/maillog/locale/es/LC_MESSAGES/django.po b/modoboa/maillog/locale/es/LC_MESSAGES/django.po new file mode 100644 index 000000000..09e528429 --- /dev/null +++ b/modoboa/maillog/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,171 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Carlos L , 2017 +# Evilham , 2017 +# Pedro Gracia , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: 2017-11-23 16:41+0000\n" +"Last-Translator: Evilham \n" +"Language-Team: Spanish (http://www.transifex.com/tonio/modoboa/language/" +"es/)\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: forms.py:20 +msgid "General" +msgstr "General" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "Ruta del fichero de log" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "Ruta al fichero de log usado para recolectar estadísticas" + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "Directorio para guardar los ficheros RRD" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "Ruta al directorio dónde están almacenados los ficheros RRD" + +#: forms.py:38 +msgid "Show greylisted messages" +msgstr "" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "Estadísticas" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "Fallo al localizar el binario rrdtool" + +#: graphics.py:174 +msgid "Average traffic (msgs/min)" +msgstr "Media del tráfico (msgs/min)" + +#: graphics.py:177 +msgid "sent messages" +msgstr "mensajes enviados" + +#: graphics.py:178 +msgid "received messages" +msgstr "mensajes recibidos" + +#: graphics.py:179 +msgid "bounced messages" +msgstr "mensajes rebotados" + +#: graphics.py:180 +msgid "rejected messages" +msgstr "mensajes bloqueados" + +#: graphics.py:181 +msgid "virus messages" +msgstr "mensajes con virus" + +#: graphics.py:182 +msgid "spam messages" +msgstr "Mensajes Basura" + +#: graphics.py:189 +msgid "greylisted messages" +msgstr "" + +#: graphics.py:200 +msgid "Average normal traffic size (bytes/min)" +msgstr "Media normal del tamaño del tráfico (bytes/min)" + +#: graphics.py:203 +msgid "received size" +msgstr "tamaño recibido" + +#: graphics.py:205 +msgid "sent size" +msgstr "tamaño enviado" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "Tráfico de correo" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "Creación promedio de cuentas (cuentas/hora)" + +#: graphics.py:261 +msgid "New accounts" +msgstr "Cuentas nuevos" + +#: graphics.py:268 +msgid "Accounts" +msgstr "Cuentas" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "Estadísticas gráficas" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "Buscar un dominio" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "Aplicar" + +#: views.py:33 +msgid "No statistics available" +msgstr "No hay estadísticas disponibles" + +#: views.py:40 +msgid "Day" +msgstr "Día" + +#: views.py:41 +msgid "Week" +msgstr "Semana" + +#: views.py:42 +msgid "Month" +msgstr "Mes" + +#: views.py:43 +msgid "Year" +msgstr "Año" + +#: views.py:44 +msgid "Custom" +msgstr "A medida" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "Conjunto gráfico desconocido" + +#: views.py:68 +#, fuzzy +#| msgid "Unknown graphic set" +msgid "Unknown domain" +msgstr "Conjunto gráfico desconocido" + +#: views.py:73 +msgid "Bad custom period" +msgstr "Período elegido erróneo" + +#~ msgid "Graphical statistics about emails traffic using RRDtool" +#~ msgstr "Estadísticas gráficas del tráfico de emails usando RRDtool" diff --git a/modoboa/maillog/locale/es/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/es/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..be3dec69f --- /dev/null +++ b/modoboa/maillog/locale/es/LC_MESSAGES/djangojs.po @@ -0,0 +1,24 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Pedro Gracia , 2011,2013 +# Pedro Gracia , 2010-2011 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: 2017-09-22 09:57+0000\n" +"Last-Translator: Antoine Nguyen \n" +"Language-Team: Spanish (http://www.transifex.com/tonio/modoboa/language/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "Buscar un dominio" diff --git a/modoboa/maillog/locale/fr/LC_MESSAGES/django.po b/modoboa/maillog/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 000000000..f88f00c33 --- /dev/null +++ b/modoboa/maillog/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,170 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Antoine Nguyen , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: 2017-08-09 08:17+0000\n" +"Last-Translator: Antoine Nguyen \n" +"Language-Team: French (http://www.transifex.com/tonio/modoboa/language/fr/)\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: forms.py:20 +msgid "General" +msgstr "Général" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "Chemin d'accès vers le journal" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "Emplacement du log utilisé pour collecter les statistiques" + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "Répertoire où stocker les fichiers RRD" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "Emplacement du répertoire de stockage des fichiers RRD" + +#: forms.py:38 +msgid "Show greylisted messages" +msgstr "Afficher les messages greylistés" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "Différencie les rejets en douceur des rejets durs (greylisting)" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "Statistiques" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "Impossible de trouver le binaire rrdtool" + +#: graphics.py:174 +msgid "Average traffic (msgs/min)" +msgstr "Trafic moyen (msgs/min)" + +#: graphics.py:177 +msgid "sent messages" +msgstr "messages envoyés" + +#: graphics.py:178 +msgid "received messages" +msgstr "messages reçus" + +#: graphics.py:179 +msgid "bounced messages" +msgstr "messages non remis" + +#: graphics.py:180 +msgid "rejected messages" +msgstr "messages rejetés" + +#: graphics.py:181 +msgid "virus messages" +msgstr "messages de type virus" + +#: graphics.py:182 +msgid "spam messages" +msgstr "messages de type spam" + +#: graphics.py:189 +msgid "greylisted messages" +msgstr "Messages greylistés" + +#: graphics.py:200 +msgid "Average normal traffic size (bytes/min)" +msgstr "Taille moyenne du trafic normal (octets/min)" + +#: graphics.py:203 +msgid "received size" +msgstr "taille reçue" + +#: graphics.py:205 +msgid "sent size" +msgstr "taille envoyée" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "Trafic messagerie" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "Moyenne des créations de compte (compte/heure)" + +#: graphics.py:261 +msgid "New accounts" +msgstr "Nouveaux comptes" + +#: graphics.py:268 +msgid "Accounts" +msgstr "Comptes" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "Statistiques graphiques" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "Chercher un domaine" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "Appliquer" + +#: views.py:33 +msgid "No statistics available" +msgstr "Aucune statistique disponible" + +#: views.py:40 +msgid "Day" +msgstr "Jour" + +#: views.py:41 +msgid "Week" +msgstr "Semaine" + +#: views.py:42 +msgid "Month" +msgstr "Mois" + +#: views.py:43 +msgid "Year" +msgstr "Année" + +#: views.py:44 +msgid "Custom" +msgstr "Personnalisée" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "Jeu de graphiques inconnu" + +#: views.py:68 +#, fuzzy +#| msgid "Unknown graphic set" +msgid "Unknown domain" +msgstr "Jeu de graphiques inconnu" + +#: views.py:73 +msgid "Bad custom period" +msgstr "Mauvaise période personnalisée" + +#~ msgid "Graphical statistics about emails traffic using RRDtool" +#~ msgstr "" +#~ "Statistiques graphiques concernant le trafic des messages utilisant " +#~ "RRDtool" diff --git a/modoboa/maillog/locale/fr/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/fr/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..c375f231b --- /dev/null +++ b/modoboa/maillog/locale/fr/LC_MESSAGES/djangojs.po @@ -0,0 +1,22 @@ +# Modoboa french translation +# Copyright (C) 2009-2013 +# This file is distributed under the same license as the Modoboa package. +# Antoine Nguyen , 2009-2013. +# +msgid "" +msgstr "" +"Project-Id-Version: modoboa-1.0.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: 2011-01-07 10:00+0100\n" +"Last-Translator: Antoine Nguyen \n" +"Language-Team: FR \n" +"Language: Français\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "Checher un domaine" diff --git a/modoboa/maillog/locale/it/LC_MESSAGES/django.po b/modoboa/maillog/locale/it/LC_MESSAGES/django.po new file mode 100644 index 000000000..402230507 --- /dev/null +++ b/modoboa/maillog/locale/it/LC_MESSAGES/django.po @@ -0,0 +1,177 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Giancarlo Gaifas , 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: 2013-12-23 00:56+0100\n" +"Last-Translator: Alberto Berti \n" +"Language-Team: ITALIANO \n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: forms.py:20 +msgid "General" +msgstr "Generale" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "Percorso del file di log" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "Percorso del file di log utilizzato per raccogliere le statistiche" + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "Cartella dei file RRD" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "Percorso della cartella dove sono memorizzati i file RRD" + +#: forms.py:38 +#, fuzzy +#| msgid "rejected messages" +msgid "Show greylisted messages" +msgstr "messaggi respinti" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "Statistiche" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "" + +#: graphics.py:174 +#, fuzzy +msgid "Average traffic (msgs/min)" +msgstr "Dimensione del traffico normale medio" + +#: graphics.py:177 +msgid "sent messages" +msgstr "messaggi inviati" + +#: graphics.py:178 +msgid "received messages" +msgstr "messaggi ricevuti" + +#: graphics.py:179 +msgid "bounced messages" +msgstr "messaggi di rimbalzo" + +#: graphics.py:180 +msgid "rejected messages" +msgstr "messaggi respinti" + +#: graphics.py:181 +#, fuzzy +#| msgid "sent messages" +msgid "virus messages" +msgstr "messaggi inviati" + +#: graphics.py:182 +#, fuzzy +#| msgid "sent messages" +msgid "spam messages" +msgstr "messaggi inviati" + +#: graphics.py:189 +#, fuzzy +#| msgid "rejected messages" +msgid "greylisted messages" +msgstr "messaggi respinti" + +#: graphics.py:200 +#, fuzzy +msgid "Average normal traffic size (bytes/min)" +msgstr "Dimensione del traffico normale medio" + +#: graphics.py:203 +msgid "received size" +msgstr "dimensione di ricezione" + +#: graphics.py:205 +msgid "sent size" +msgstr "dimensione di spedizione" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "Traffico mail" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "" + +#: graphics.py:261 +msgid "New accounts" +msgstr "" + +#: graphics.py:268 +msgid "Accounts" +msgstr "" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "Grafico statistiche" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "Ricerca un dominio" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "Applica" + +#: views.py:33 +msgid "No statistics available" +msgstr "Non sono disponibili statistiche" + +#: views.py:40 +msgid "Day" +msgstr "Giorno" + +#: views.py:41 +msgid "Week" +msgstr "Settimana" + +#: views.py:42 +msgid "Month" +msgstr "Mese" + +#: views.py:43 +msgid "Year" +msgstr "Anno" + +#: views.py:44 +msgid "Custom" +msgstr "" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "Impostazione grafica sconosciuta" + +#: views.py:68 +#, fuzzy +#| msgid "Unknown graphic set" +msgid "Unknown domain" +msgstr "Impostazione grafica sconosciuta" + +#: views.py:73 +msgid "Bad custom period" +msgstr "Periodo personalizzato sbagliato" + +#~ msgid "Graphical statistics about emails traffic using RRDtool" +#~ msgstr "Grafico delle statistiche sul traffico e-mail utilizzando RRDtool" diff --git a/modoboa/maillog/locale/it/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/it/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..220984a84 --- /dev/null +++ b/modoboa/maillog/locale/it/LC_MESSAGES/djangojs.po @@ -0,0 +1,22 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: 2013-12-23 00:58+0100\n" +"Last-Translator: Alberto Berti \n" +"Language-Team: Italiano \n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "Cerca un dominio" diff --git a/modoboa/maillog/locale/ja_JP/LC_MESSAGES/django.po b/modoboa/maillog/locale/ja_JP/LC_MESSAGES/django.po new file mode 100644 index 000000000..1b8b19545 --- /dev/null +++ b/modoboa/maillog/locale/ja_JP/LC_MESSAGES/django.po @@ -0,0 +1,179 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# cwatanab , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: 2016-06-07 08:56+0000\n" +"Last-Translator: cwatanab \n" +"Language-Team: Japanese (Japan) (http://www.transifex.com/tonio/modoboa/" +"language/ja_JP/)\n" +"Language: ja_JP\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: forms.py:20 +msgid "General" +msgstr "全般" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "ログファイルへのパス" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "統計情報を収集するために使用されたログファイルへのパス" + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "RRD ファイルを保管するためのディレクトリ" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "RRD ファイルが保管されたディレクトリへのパス" + +#: forms.py:38 +#, fuzzy +#| msgid "rejected messages" +msgid "Show greylisted messages" +msgstr "拒否メッセージ" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "統計情報" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "" + +#: graphics.py:174 +msgid "Average traffic (msgs/min)" +msgstr "平均トラフィック (通/分)" + +#: graphics.py:177 +msgid "sent messages" +msgstr "送信メッセージ" + +#: graphics.py:178 +msgid "received messages" +msgstr "受信メッセージ" + +#: graphics.py:179 +msgid "bounced messages" +msgstr "バウンスメッセージ" + +#: graphics.py:180 +msgid "rejected messages" +msgstr "拒否メッセージ" + +#: graphics.py:181 +#, fuzzy +#| msgid "sent messages" +msgid "virus messages" +msgstr "送信メッセージ" + +#: graphics.py:182 +#, fuzzy +#| msgid "sent messages" +msgid "spam messages" +msgstr "送信メッセージ" + +#: graphics.py:189 +#, fuzzy +#| msgid "rejected messages" +msgid "greylisted messages" +msgstr "拒否メッセージ" + +#: graphics.py:200 +msgid "Average normal traffic size (bytes/min)" +msgstr "平均トラフィックサイズ (バイト/分)" + +#: graphics.py:203 +msgid "received size" +msgstr "受信サイズ" + +#: graphics.py:205 +msgid "sent size" +msgstr "送信サイズ" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "メールトラフィック" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "" + +#: graphics.py:261 +msgid "New accounts" +msgstr "" + +#: graphics.py:268 +msgid "Accounts" +msgstr "" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "グラフィカルな統計情報" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "ドメインを検索" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "適用" + +#: views.py:33 +msgid "No statistics available" +msgstr "統計情報がありません" + +#: views.py:40 +msgid "Day" +msgstr "日" + +#: views.py:41 +msgid "Week" +msgstr "週" + +#: views.py:42 +msgid "Month" +msgstr "月" + +#: views.py:43 +msgid "Year" +msgstr "年" + +#: views.py:44 +msgid "Custom" +msgstr "カスタム" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "不明なグラフィックセット" + +#: views.py:68 +#, fuzzy +#| msgid "Unknown graphic set" +msgid "Unknown domain" +msgstr "不明なグラフィックセット" + +#: views.py:73 +msgid "Bad custom period" +msgstr "カスタム期間が不正です" + +#~ msgid "Graphical statistics about emails traffic using RRDtool" +#~ msgstr "" +#~ "RRDtool を使用して、電子メールトラフィックに関するグラフィカルな統計情報を" +#~ "表示します。" diff --git a/modoboa/maillog/locale/ja_JP/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/ja_JP/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..80202fcf6 --- /dev/null +++ b/modoboa/maillog/locale/ja_JP/LC_MESSAGES/djangojs.po @@ -0,0 +1,24 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# cwatanab , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: 2016-06-07 07:16+0000\n" +"Last-Translator: cwatanab \n" +"Language-Team: Japanese (Japan) (http://www.transifex.com/tonio/modoboa/" +"language/ja_JP/)\n" +"Language: ja_JP\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "ドメインを検索" diff --git a/modoboa/maillog/locale/nl_NL/LC_MESSAGES/django.po b/modoboa/maillog/locale/nl_NL/LC_MESSAGES/django.po new file mode 100644 index 000000000..1f24ac57d --- /dev/null +++ b/modoboa/maillog/locale/nl_NL/LC_MESSAGES/django.po @@ -0,0 +1,178 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Andrew , 2014 +# TuxBrother , 2014 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: 2014-12-31 11:37+0000\n" +"Last-Translator: TuxBrother \n" +"Language-Team: Dutch (Netherlands) (http://www.transifex.com/projects/p/" +"modoboa/language/nl_NL/)\n" +"Language: nl_NL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: forms.py:20 +msgid "General" +msgstr "Algemeen" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "Locatie van logbestand" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "Locatie van logbestand gebruik om statistieken te maken" + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "Map om RRD bestanden op te slaan" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "Locatie van map waar RRD bestanden staan" + +#: forms.py:38 +#, fuzzy +#| msgid "rejected messages" +msgid "Show greylisted messages" +msgstr "geweigerde berichten" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "Statistieken" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "" + +#: graphics.py:174 +msgid "Average traffic (msgs/min)" +msgstr "Gemiddeld verkeer per minuut (berichten)" + +#: graphics.py:177 +msgid "sent messages" +msgstr "verstuurde berichten" + +#: graphics.py:178 +msgid "received messages" +msgstr "ontvangen berichten" + +#: graphics.py:179 +msgid "bounced messages" +msgstr "geweigerde berichten" + +#: graphics.py:180 +msgid "rejected messages" +msgstr "geweigerde berichten" + +#: graphics.py:181 +#, fuzzy +#| msgid "sent messages" +msgid "virus messages" +msgstr "verstuurde berichten" + +#: graphics.py:182 +#, fuzzy +#| msgid "sent messages" +msgid "spam messages" +msgstr "verstuurde berichten" + +#: graphics.py:189 +#, fuzzy +#| msgid "rejected messages" +msgid "greylisted messages" +msgstr "geweigerde berichten" + +#: graphics.py:200 +msgid "Average normal traffic size (bytes/min)" +msgstr "Gemiddelde bandbreette per minuut (bytes)" + +#: graphics.py:203 +msgid "received size" +msgstr "ontvangen grootte" + +#: graphics.py:205 +msgid "sent size" +msgstr "verstuurde grootte" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "Mailverkeer" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "" + +#: graphics.py:261 +msgid "New accounts" +msgstr "" + +#: graphics.py:268 +msgid "Accounts" +msgstr "" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "Grafische statistieken" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "Zoek een domein" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "Toepassen" + +#: views.py:33 +msgid "No statistics available" +msgstr "Geen statistieken beschikbaar" + +#: views.py:40 +msgid "Day" +msgstr "Dag" + +#: views.py:41 +msgid "Week" +msgstr "Week" + +#: views.py:42 +msgid "Month" +msgstr "Maand" + +#: views.py:43 +msgid "Year" +msgstr "Jaar" + +#: views.py:44 +msgid "Custom" +msgstr "Aangepast" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "Onbekende grafische set" + +#: views.py:68 +#, fuzzy +#| msgid "Unknown graphic set" +msgid "Unknown domain" +msgstr "Onbekende grafische set" + +#: views.py:73 +msgid "Bad custom period" +msgstr "Ongeldige aangepaste periode" + +#~ msgid "Graphical statistics about emails traffic using RRDtool" +#~ msgstr "Grafische statistieken over emailverkeer dmv. RRDtool" diff --git a/modoboa/maillog/locale/nl_NL/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/nl_NL/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..d863fc318 --- /dev/null +++ b/modoboa/maillog/locale/nl_NL/LC_MESSAGES/djangojs.po @@ -0,0 +1,25 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# TuxBrother , 2014 +# TuxBrother , 2014 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: 2014-12-31 11:42+0000\n" +"Last-Translator: TuxBrother \n" +"Language-Team: Dutch (Netherlands) (http://www.transifex.com/projects/p/" +"modoboa/language/nl_NL/)\n" +"Language: nl_NL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "Zoek een domein" diff --git a/modoboa/maillog/locale/pl_PL/LC_MESSAGES/django.po b/modoboa/maillog/locale/pl_PL/LC_MESSAGES/django.po new file mode 100644 index 000000000..ecd15f35a --- /dev/null +++ b/modoboa/maillog/locale/pl_PL/LC_MESSAGES/django.po @@ -0,0 +1,172 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# sin88 , 2016 +# Tomasz Proć , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: 2017-12-01 16:06+0000\n" +"Last-Translator: Tomasz Proć \n" +"Language-Team: Polish (Poland) (http://www.transifex.com/tonio/modoboa/" +"language/pl_PL/)\n" +"Language: pl_PL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n" +"%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n" +"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" + +#: forms.py:20 +msgid "General" +msgstr "Ogólne" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "Ścieżka do pliku z logiem" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "Sćieżka do pliku z logiem dla colect " + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "Katalog do przechowywania plików RRD" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "Katalog gdzie przechowywane są pliki RRD" + +#: forms.py:38 +msgid "Show greylisted messages" +msgstr "Pokaż greylistowane wiadomości" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "Rozróżniaj twarde i miękkie odrzuty (greylisting)" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "Statystyki" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "Nie znaleziono binarki rrdtool." + +#: graphics.py:174 +msgid "Average traffic (msgs/min)" +msgstr "Średni ruch (wiadomości/minutę)" + +#: graphics.py:177 +msgid "sent messages" +msgstr "Wysłane wiadomości" + +#: graphics.py:178 +msgid "received messages" +msgstr "Odebrane wiadomości" + +#: graphics.py:179 +msgid "bounced messages" +msgstr "Odbite wiadomości" + +#: graphics.py:180 +msgid "rejected messages" +msgstr "Odrzucone wiadomości" + +#: graphics.py:181 +msgid "virus messages" +msgstr "wiadomości zawirusowane" + +#: graphics.py:182 +msgid "spam messages" +msgstr "wiadomości spam" + +#: graphics.py:189 +msgid "greylisted messages" +msgstr "wiadomości opóźnione (greylisting)" + +#: graphics.py:200 +msgid "Average normal traffic size (bytes/min)" +msgstr "Średnia normalna wielkość ruchu (bajtów/min)" + +#: graphics.py:203 +msgid "received size" +msgstr "Ilość odebranych danych" + +#: graphics.py:205 +msgid "sent size" +msgstr "Ilość wysłanych danych" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "Ruch e-mail" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "Średnia ilość tworzonych kont (konto/godzina)" + +#: graphics.py:261 +msgid "New accounts" +msgstr "Nowe konta" + +#: graphics.py:268 +msgid "Accounts" +msgstr "Konta" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "Graficzne statytyki" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "Wyszukaj domenę" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "Zastosuj" + +#: views.py:33 +msgid "No statistics available" +msgstr "Brak dostępnych statysty" + +#: views.py:40 +msgid "Day" +msgstr "Dzień" + +#: views.py:41 +msgid "Week" +msgstr "Tydzień" + +#: views.py:42 +msgid "Month" +msgstr "Miesiąc" + +#: views.py:43 +msgid "Year" +msgstr "Rok" + +#: views.py:44 +msgid "Custom" +msgstr "Własne" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "Unknown graphic set" + +#: views.py:68 +#, fuzzy +#| msgid "Unknown graphic set" +msgid "Unknown domain" +msgstr "Unknown graphic set" + +#: views.py:73 +msgid "Bad custom period" +msgstr "Nieprawudłowy przedział czasowy" + +#~ msgid "Graphical statistics about emails traffic using RRDtool" +#~ msgstr "Graficzne statystyki wiadomości z wykorzystaniem " diff --git a/modoboa/maillog/locale/pl_PL/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/pl_PL/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..3b9b9304a --- /dev/null +++ b/modoboa/maillog/locale/pl_PL/LC_MESSAGES/djangojs.po @@ -0,0 +1,23 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# sin88 , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: 2017-09-22 09:57+0000\n" +"Last-Translator: sin88 \n" +"Language-Team: Polish (Poland) (http://www.transifex.com/tonio/modoboa/language/pl_PL/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pl_PL\n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "Wyszukaj domenę" diff --git a/modoboa/maillog/locale/pt_BR/LC_MESSAGES/django.po b/modoboa/maillog/locale/pt_BR/LC_MESSAGES/django.po new file mode 100644 index 000000000..7f34c5b1e --- /dev/null +++ b/modoboa/maillog/locale/pt_BR/LC_MESSAGES/django.po @@ -0,0 +1,179 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# csmk, 2014 +# Paulino Michelazzo , 2014 +# Rafael Barretto Alves , 2013 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: 2014-12-13 09:10+0000\n" +"Last-Translator: Paulino Michelazzo \n" +"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/" +"modoboa/language/pt_BR/)\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: forms.py:20 +msgid "General" +msgstr "Geral" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "Caminho para o arquivo de log" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "Caminho para o arquivo de log usado para coletar estatísticas" + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "Diretório para armazenar arquivos RRD" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "Caminho para o diretório onde os arquivos RRD são armazenados" + +#: forms.py:38 +#, fuzzy +#| msgid "rejected messages" +msgid "Show greylisted messages" +msgstr "mensagens rejeitadas" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "Estatísticas" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "" + +#: graphics.py:174 +msgid "Average traffic (msgs/min)" +msgstr "Tráfego médio (msgs/min)" + +#: graphics.py:177 +msgid "sent messages" +msgstr "mensagens enviadas" + +#: graphics.py:178 +msgid "received messages" +msgstr "mensagens recebidas" + +#: graphics.py:179 +msgid "bounced messages" +msgstr "mensagens devolvidas" + +#: graphics.py:180 +msgid "rejected messages" +msgstr "mensagens rejeitadas" + +#: graphics.py:181 +#, fuzzy +#| msgid "sent messages" +msgid "virus messages" +msgstr "mensagens enviadas" + +#: graphics.py:182 +#, fuzzy +#| msgid "sent messages" +msgid "spam messages" +msgstr "mensagens enviadas" + +#: graphics.py:189 +#, fuzzy +#| msgid "rejected messages" +msgid "greylisted messages" +msgstr "mensagens rejeitadas" + +#: graphics.py:200 +msgid "Average normal traffic size (bytes/min)" +msgstr "Tamanho médio de tráfego (bytes/min)" + +#: graphics.py:203 +msgid "received size" +msgstr "tamanho recebido" + +#: graphics.py:205 +msgid "sent size" +msgstr "tamanho enviado" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "Tráfego de e-mail" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "" + +#: graphics.py:261 +msgid "New accounts" +msgstr "" + +#: graphics.py:268 +msgid "Accounts" +msgstr "" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "Estatísticas gráficas" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "Buscar um domínio" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "Aplicar" + +#: views.py:33 +msgid "No statistics available" +msgstr "Não há estatísticas disponíveis" + +#: views.py:40 +msgid "Day" +msgstr "Dia" + +#: views.py:41 +msgid "Week" +msgstr "Semana" + +#: views.py:42 +msgid "Month" +msgstr "Mês" + +#: views.py:43 +msgid "Year" +msgstr "Ano" + +#: views.py:44 +msgid "Custom" +msgstr "Customizado" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "Conjunto gráfico desconhecido" + +#: views.py:68 +#, fuzzy +#| msgid "Unknown graphic set" +msgid "Unknown domain" +msgstr "Conjunto gráfico desconhecido" + +#: views.py:73 +msgid "Bad custom period" +msgstr "Período customizado inválido" + +#~ msgid "Graphical statistics about emails traffic using RRDtool" +#~ msgstr "Estatísticas gráficas sobre o tráfego de e-mails usando RRDtool" diff --git a/modoboa/maillog/locale/pt_BR/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/pt_BR/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..8ed8fade5 --- /dev/null +++ b/modoboa/maillog/locale/pt_BR/LC_MESSAGES/djangojs.po @@ -0,0 +1,24 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Paulino Michelazzo , 2014 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: 2014-12-13 09:14+0000\n" +"Last-Translator: Paulino Michelazzo \n" +"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/" +"modoboa/language/pt_BR/)\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "Buscar um domínio" diff --git a/modoboa/maillog/locale/pt_PT/LC_MESSAGES/django.po b/modoboa/maillog/locale/pt_PT/LC_MESSAGES/django.po new file mode 100644 index 000000000..5f0a06ada --- /dev/null +++ b/modoboa/maillog/locale/pt_PT/LC_MESSAGES/django.po @@ -0,0 +1,182 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Damien Duponchelle , 2013 +# lusitan , 2013 +# Mike C. , 2012 +# Mike C. , 2013-2014 +# Sandra Ribeiro , 2012-2013 +# Sandra Ribeiro , 2012 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: 2014-12-29 12:51+0000\n" +"Last-Translator: Mike C. \n" +"Language-Team: Portuguese (Portugal) (http://www.transifex.com/projects/p/" +"modoboa/language/pt_PT/)\n" +"Language: pt_PT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: forms.py:20 +msgid "General" +msgstr "Geral" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "Caminho para o ficheiro log" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "Caminho para o ficheiro de log usado para recolher as estatísticas" + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "Diretório para armazenar os ficheiros RRD" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "Caminho para o diretório onde os ficheiros RRD são guardados" + +#: forms.py:38 +#, fuzzy +#| msgid "rejected messages" +msgid "Show greylisted messages" +msgstr "mensagens rejeitadas" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "Estatísticas" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "" + +#: graphics.py:174 +msgid "Average traffic (msgs/min)" +msgstr "Trafego em média (msgs/min)" + +#: graphics.py:177 +msgid "sent messages" +msgstr "mensagens enviadas" + +#: graphics.py:178 +msgid "received messages" +msgstr "mensagens recebidas" + +#: graphics.py:179 +msgid "bounced messages" +msgstr "mensagens devolvidas" + +#: graphics.py:180 +msgid "rejected messages" +msgstr "mensagens rejeitadas" + +#: graphics.py:181 +#, fuzzy +#| msgid "sent messages" +msgid "virus messages" +msgstr "mensagens enviadas" + +#: graphics.py:182 +#, fuzzy +#| msgid "sent messages" +msgid "spam messages" +msgstr "mensagens enviadas" + +#: graphics.py:189 +#, fuzzy +#| msgid "rejected messages" +msgid "greylisted messages" +msgstr "mensagens rejeitadas" + +#: graphics.py:200 +msgid "Average normal traffic size (bytes/min)" +msgstr "Tráfego em média normal por tamanho (bytes/min)" + +#: graphics.py:203 +msgid "received size" +msgstr "tamanho recebido" + +#: graphics.py:205 +msgid "sent size" +msgstr "tamanho enviado" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "Tráfego de correio" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "" + +#: graphics.py:261 +msgid "New accounts" +msgstr "" + +#: graphics.py:268 +msgid "Accounts" +msgstr "" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "Estatísticas gráficas" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "Procurar um domínio" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "Aplicar" + +#: views.py:33 +msgid "No statistics available" +msgstr "Não há estatísticas disponíveis" + +#: views.py:40 +msgid "Day" +msgstr "Dia" + +#: views.py:41 +msgid "Week" +msgstr "Semana" + +#: views.py:42 +msgid "Month" +msgstr "Mês" + +#: views.py:43 +msgid "Year" +msgstr "Ano" + +#: views.py:44 +msgid "Custom" +msgstr "Personalizado" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "Conjunto de gráficos desconhecido" + +#: views.py:68 +#, fuzzy +#| msgid "Unknown graphic set" +msgid "Unknown domain" +msgstr "Conjunto de gráficos desconhecido" + +#: views.py:73 +msgid "Bad custom period" +msgstr "Período personalizado inválido" + +#~ msgid "Graphical statistics about emails traffic using RRDtool" +#~ msgstr "Estatísticas gráficas sobre tráfego de email usando RRDtool" diff --git a/modoboa/maillog/locale/pt_PT/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/pt_PT/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..1d7e6e4c3 --- /dev/null +++ b/modoboa/maillog/locale/pt_PT/LC_MESSAGES/djangojs.po @@ -0,0 +1,29 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# lusitan , 2013 +# lusitan , 2013 +# Mike C. , 2012 +# Mike C. , 2012 +# Mike C. , 2014 +# Sandra Ribeiro , 2013 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: 2014-12-29 12:08+0000\n" +"Last-Translator: Mike C. \n" +"Language-Team: Portuguese (Portugal) (http://www.transifex.com/projects/p/" +"modoboa/language/pt_PT/)\n" +"Language: pt_PT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "Pesquisar domínio" diff --git a/modoboa/maillog/locale/ro_RO/LC_MESSAGES/django.po b/modoboa/maillog/locale/ro_RO/LC_MESSAGES/django.po new file mode 100644 index 000000000..68a4fe92b --- /dev/null +++ b/modoboa/maillog/locale/ro_RO/LC_MESSAGES/django.po @@ -0,0 +1,170 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Andrei-Ilie Olteanu , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: 2018-06-12 15:03+0000\n" +"Last-Translator: Andrei-Ilie Olteanu \n" +"Language-Team: Romanian (Romania) (http://www.transifex.com/tonio/modoboa/" +"language/ro_RO/)\n" +"Language: ro_RO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?" +"2:1));\n" + +#: forms.py:20 +msgid "General" +msgstr "General" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "Calea către fișierul de log" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "Calea către fișierul de log în care se colectează statistici" + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "Directorul în care se stochează fisierele RRD" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "Calea către directorul în care se stochează fisierele RRD" + +#: forms.py:38 +msgid "Show greylisted messages" +msgstr "Arată mesajele suspecte" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "Diferențiază între mesjale rejectate hard și soft (suspecte)" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "Statistici" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "Nu s-a gasit binarul rrdtool" + +#: graphics.py:174 +msgid "Average traffic (msgs/min)" +msgstr "Media traficului (mesaje/min)" + +#: graphics.py:177 +msgid "sent messages" +msgstr "mesaje trimise" + +#: graphics.py:178 +msgid "received messages" +msgstr "mesaje primite " + +#: graphics.py:179 +msgid "bounced messages" +msgstr "mesaje returnate" + +#: graphics.py:180 +msgid "rejected messages" +msgstr "mesaje rejectate" + +#: graphics.py:181 +msgid "virus messages" +msgstr "mesaje cu virus" + +#: graphics.py:182 +msgid "spam messages" +msgstr "mesaje spam" + +#: graphics.py:189 +msgid "greylisted messages" +msgstr "mesaje suspecte" + +#: graphics.py:200 +msgid "Average normal traffic size (bytes/min)" +msgstr "Media de trafic (octecți/minut)" + +#: graphics.py:203 +msgid "received size" +msgstr "mărime pentru primite" + +#: graphics.py:205 +msgid "sent size" +msgstr "mărime pentru trimise" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "Trafic Email" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "Media creări de conturi (cont/oră)" + +#: graphics.py:261 +msgid "New accounts" +msgstr "Conturi noi" + +#: graphics.py:268 +msgid "Accounts" +msgstr "Conturi" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "Statistici grafice" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "Cauta un domeniu" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "Aplică" + +#: views.py:33 +msgid "No statistics available" +msgstr "Nu sunt disponibile statistici" + +#: views.py:40 +msgid "Day" +msgstr "Zi" + +#: views.py:41 +msgid "Week" +msgstr "Săptămană" + +#: views.py:42 +msgid "Month" +msgstr "Lună" + +#: views.py:43 +msgid "Year" +msgstr "An" + +#: views.py:44 +msgid "Custom" +msgstr "Personalizat" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "Set grafic necunoscut" + +#: views.py:68 +#, fuzzy +#| msgid "Unknown graphic set" +msgid "Unknown domain" +msgstr "Set grafic necunoscut" + +#: views.py:73 +msgid "Bad custom period" +msgstr "Perioada personalizată invalidă" + +#~ msgid "Graphical statistics about emails traffic using RRDtool" +#~ msgstr "Statistici grafice despre traficul pe email folosind RRDtool" diff --git a/modoboa/maillog/locale/ro_RO/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/ro_RO/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..78d5ddd1f --- /dev/null +++ b/modoboa/maillog/locale/ro_RO/LC_MESSAGES/djangojs.po @@ -0,0 +1,23 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Andrei-Ilie Olteanu , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: 2018-06-12 15:04+0000\n" +"Last-Translator: Andrei-Ilie Olteanu \n" +"Language-Team: Romanian (Romania) (http://www.transifex.com/tonio/modoboa/language/ro_RO/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ro_RO\n" +"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "Cauta un domeniu" diff --git a/modoboa/maillog/locale/ru/LC_MESSAGES/django.po b/modoboa/maillog/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 000000000..53a02cbc9 --- /dev/null +++ b/modoboa/maillog/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,179 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: 2013-12-03 09:35+0000\n" +"Last-Translator: Antoine Nguyen \n" +"Language-Team: Russian (http://www.transifex.com/projects/p/modoboa/language/" +"ru/)\n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: forms.py:20 +msgid "General" +msgstr "Основные" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "Путь к лог файлу" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "Путь к лог файлу для сбора статистики" + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "Папка для сохранения RRD файлов" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "Путь к директории для сохранения RRD файлов" + +#: forms.py:38 +#, fuzzy +#| msgid "rejected messages" +msgid "Show greylisted messages" +msgstr "Отвергнутые сообщения" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "Статистика" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "" + +#: graphics.py:174 +#, fuzzy +msgid "Average traffic (msgs/min)" +msgstr "Средний размер траффика" + +#: graphics.py:177 +msgid "sent messages" +msgstr "Отправлено сообщений" + +#: graphics.py:178 +msgid "received messages" +msgstr "Принято сообщений" + +#: graphics.py:179 +msgid "bounced messages" +msgstr "Возвращенные сообщения" + +#: graphics.py:180 +msgid "rejected messages" +msgstr "Отвергнутые сообщения" + +#: graphics.py:181 +#, fuzzy +#| msgid "sent messages" +msgid "virus messages" +msgstr "Отправлено сообщений" + +#: graphics.py:182 +#, fuzzy +#| msgid "sent messages" +msgid "spam messages" +msgstr "Отправлено сообщений" + +#: graphics.py:189 +#, fuzzy +#| msgid "rejected messages" +msgid "greylisted messages" +msgstr "Отвергнутые сообщения" + +#: graphics.py:200 +#, fuzzy +msgid "Average normal traffic size (bytes/min)" +msgstr "Средний размер траффика" + +#: graphics.py:203 +msgid "received size" +msgstr "Принято" + +#: graphics.py:205 +msgid "sent size" +msgstr "Отправлено" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "Почтовый траффик" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "" + +#: graphics.py:261 +msgid "New accounts" +msgstr "" + +#: graphics.py:268 +msgid "Accounts" +msgstr "" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "Графическая статистика" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "Искать домен" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "Применить" + +#: views.py:33 +msgid "No statistics available" +msgstr "Статистика не доступна" + +#: views.py:40 +msgid "Day" +msgstr "День" + +#: views.py:41 +msgid "Week" +msgstr "Неделя" + +#: views.py:42 +msgid "Month" +msgstr "Месяц" + +#: views.py:43 +msgid "Year" +msgstr "Год" + +#: views.py:44 +msgid "Custom" +msgstr "" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "Неизвестный графический набор " + +#: views.py:68 +#, fuzzy +#| msgid "Unknown graphic set" +msgid "Unknown domain" +msgstr "Неизвестный графический набор " + +#: views.py:73 +msgid "Bad custom period" +msgstr "Не верный выбор периода" + +#~ msgid "Graphical statistics about emails traffic using RRDtool" +#~ msgstr "Графическая статистика о почтовом траффика использует RRDtool" diff --git a/modoboa/maillog/locale/ru/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/ru/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..e35e123e6 --- /dev/null +++ b/modoboa/maillog/locale/ru/LC_MESSAGES/djangojs.po @@ -0,0 +1,24 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: 2013-12-03 09:35+0000\n" +"Last-Translator: Antoine Nguyen \n" +"Language-Team: Russian (http://www.transifex.com/projects/p/modoboa/language/" +"ru/)\n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "Найти домен" diff --git a/modoboa/maillog/locale/sv/LC_MESSAGES/django.po b/modoboa/maillog/locale/sv/LC_MESSAGES/django.po new file mode 100644 index 000000000..e7072a06e --- /dev/null +++ b/modoboa/maillog/locale/sv/LC_MESSAGES/django.po @@ -0,0 +1,179 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Martin Persson , 2014 +# Olle Gustafsson , 2013 +# Olle Gustafsson , 2013,2015 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: 2015-01-13 15:18+0000\n" +"Last-Translator: Olle Gustafsson \n" +"Language-Team: Swedish (http://www.transifex.com/projects/p/modoboa/language/" +"sv/)\n" +"Language: sv\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: forms.py:20 +msgid "General" +msgstr "Generellt" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "Sökväg till loggfil" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "Sökväg till loggfil som används för att samla in statistik" + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "Katalog för lagring av RRD filer" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "Sökväg till katalog där RRD filer lagras" + +#: forms.py:38 +#, fuzzy +#| msgid "rejected messages" +msgid "Show greylisted messages" +msgstr "avvisade meddelanden" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "Statistik" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "" + +#: graphics.py:174 +msgid "Average traffic (msgs/min)" +msgstr "Genomsnittlig trafik (meddelanden/min)" + +#: graphics.py:177 +msgid "sent messages" +msgstr "skickade meddelanden" + +#: graphics.py:178 +msgid "received messages" +msgstr "mottagna meddelande" + +#: graphics.py:179 +msgid "bounced messages" +msgstr "studsade meddelanden" + +#: graphics.py:180 +msgid "rejected messages" +msgstr "avvisade meddelanden" + +#: graphics.py:181 +#, fuzzy +#| msgid "sent messages" +msgid "virus messages" +msgstr "skickade meddelanden" + +#: graphics.py:182 +#, fuzzy +#| msgid "sent messages" +msgid "spam messages" +msgstr "skickade meddelanden" + +#: graphics.py:189 +#, fuzzy +#| msgid "rejected messages" +msgid "greylisted messages" +msgstr "avvisade meddelanden" + +#: graphics.py:200 +msgid "Average normal traffic size (bytes/min)" +msgstr "Genomsnittlig normal trafik (bytes/min)" + +#: graphics.py:203 +msgid "received size" +msgstr "mottagen storlek" + +#: graphics.py:205 +msgid "sent size" +msgstr "sänd storlek" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "Mail trafik" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "" + +#: graphics.py:261 +msgid "New accounts" +msgstr "" + +#: graphics.py:268 +msgid "Accounts" +msgstr "" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "Grafisk statistik" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "Sök en domän" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "Applicera" + +#: views.py:33 +msgid "No statistics available" +msgstr "Ingen statistik tillgänglig" + +#: views.py:40 +msgid "Day" +msgstr "Dag" + +#: views.py:41 +msgid "Week" +msgstr "Vecka" + +#: views.py:42 +msgid "Month" +msgstr "Månad" + +#: views.py:43 +msgid "Year" +msgstr "År" + +#: views.py:44 +msgid "Custom" +msgstr "Anpassad" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "Okänt grafisk uppsättning" + +#: views.py:68 +#, fuzzy +#| msgid "Unknown graphic set" +msgid "Unknown domain" +msgstr "Okänt grafisk uppsättning" + +#: views.py:73 +msgid "Bad custom period" +msgstr "Ogiltig anpassad tid" + +#~ msgid "Graphical statistics about emails traffic using RRDtool" +#~ msgstr "Grafisk statistik om e-post trafik med hjälp av RRDtool" diff --git a/modoboa/maillog/locale/sv/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/sv/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..396f6e3a2 --- /dev/null +++ b/modoboa/maillog/locale/sv/LC_MESSAGES/djangojs.po @@ -0,0 +1,25 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Olle Gustafsson , 2013 +# Olle Gustafsson , 2013,2015 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: 2015-01-13 15:21+0000\n" +"Last-Translator: Olle Gustafsson \n" +"Language-Team: Swedish (http://www.transifex.com/projects/p/modoboa/language/" +"sv/)\n" +"Language: sv\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "Sök domän" diff --git a/modoboa/maillog/locale/zh_TW/LC_MESSAGES/django.po b/modoboa/maillog/locale/zh_TW/LC_MESSAGES/django.po new file mode 100644 index 000000000..1edcb28dd --- /dev/null +++ b/modoboa/maillog/locale/zh_TW/LC_MESSAGES/django.po @@ -0,0 +1,170 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# akong , 2018 +# Radium , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-17 11:24+0200\n" +"PO-Revision-Date: 2018-06-28 09:01+0000\n" +"Last-Translator: akong \n" +"Language-Team: Chinese (Taiwan) (http://www.transifex.com/tonio/modoboa/" +"language/zh_TW/)\n" +"Language: zh_TW\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: forms.py:20 +msgid "General" +msgstr "一般" + +#: forms.py:23 +msgid "Path to the log file" +msgstr "日誌檔的路徑" + +#: forms.py:25 +msgid "Path to log file used to collect statistics" +msgstr "日誌檔的路徑用於蒐集狀態" + +#: forms.py:30 +msgid "Directory to store RRD files" +msgstr "儲存 RRD 檔案的目錄" + +#: forms.py:33 +msgid "Path to directory where RRD files are stored" +msgstr "RRD 檔案已儲存到目錄路徑" + +#: forms.py:38 +msgid "Show greylisted messages" +msgstr "顯示灰名單郵件" + +#: forms.py:41 +msgid "Differentiate between hard and soft rejects (greylisting)" +msgstr "區分在硬的退件跟軟的退件之間 (灰名單)" + +#: forms.py:59 handlers.py:22 +msgid "Statistics" +msgstr "統計" + +#: graphics.py:95 +msgid "Failed to locate rrdtool binary." +msgstr "指定 rrdtools 位置失敗。" + +#: graphics.py:174 +msgid "Average traffic (msgs/min)" +msgstr "平均流量 (郵件數/分鐘)" + +#: graphics.py:177 +msgid "sent messages" +msgstr "已發訊息" + +#: graphics.py:178 +msgid "received messages" +msgstr "收到的訊息" + +#: graphics.py:179 +msgid "bounced messages" +msgstr "已退回郵件" + +#: graphics.py:180 +msgid "rejected messages" +msgstr "已拒絕郵件" + +#: graphics.py:181 +msgid "virus messages" +msgstr "病毒郵件" + +#: graphics.py:182 +msgid "spam messages" +msgstr "垃圾郵件" + +#: graphics.py:189 +msgid "greylisted messages" +msgstr "灰名單郵件" + +#: graphics.py:200 +msgid "Average normal traffic size (bytes/min)" +msgstr "平均流量大小 (位元組/分鐘)" + +#: graphics.py:203 +msgid "received size" +msgstr "已接收大小" + +#: graphics.py:205 +msgid "sent size" +msgstr "寄送大小" + +#: graphics.py:213 +msgid "Mail traffic" +msgstr "郵件流量" + +#: graphics.py:258 +msgid "Average account creation (account/hour)" +msgstr "建立帳號平均值 (帳號/小時)" + +#: graphics.py:261 +msgid "New accounts" +msgstr "新帳號" + +#: graphics.py:268 +msgid "Accounts" +msgstr "帳號" + +#: templates/maillog/index.html:4 +msgid "Graphical statistics" +msgstr "圖形狀態" + +#: templates/maillog/index.html:44 +msgid "Search a domain" +msgstr "搜尋網域" + +#: templates/maillog/index.html:77 +msgid "Apply" +msgstr "套用" + +#: views.py:33 +msgid "No statistics available" +msgstr "沒有可用的狀態" + +#: views.py:40 +msgid "Day" +msgstr "日" + +#: views.py:41 +msgid "Week" +msgstr "周" + +#: views.py:42 +msgid "Month" +msgstr "月" + +#: views.py:43 +msgid "Year" +msgstr "年" + +#: views.py:44 +msgid "Custom" +msgstr "自訂" + +#: views.py:63 +msgid "Unknown graphic set" +msgstr "未知圖形設定" + +#: views.py:68 +#, fuzzy +#| msgid "Unknown graphic set" +msgid "Unknown domain" +msgstr "未知圖形設定" + +#: views.py:73 +msgid "Bad custom period" +msgstr "錯誤的自訂期間" + +#~ msgid "Graphical statistics about emails traffic using RRDtool" +#~ msgstr "有關郵件流量的圖形狀態表是使用 RRDtool" diff --git a/modoboa/maillog/locale/zh_TW/LC_MESSAGES/djangojs.po b/modoboa/maillog/locale/zh_TW/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..6c0595e6a --- /dev/null +++ b/modoboa/maillog/locale/zh_TW/LC_MESSAGES/djangojs.po @@ -0,0 +1,23 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Radium , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-03 09:37+0100\n" +"PO-Revision-Date: 2017-09-23 14:31+0000\n" +"Last-Translator: Radium \n" +"Language-Team: Chinese (Taiwan) (http://www.transifex.com/tonio/modoboa/language/zh_TW/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_TW\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: static/modoboa_stats/js/stats.js:43 +msgid "Search a domain" +msgstr "搜尋一個網域" diff --git a/modoboa/maillog/management/__init__.py b/modoboa/maillog/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modoboa/maillog/management/commands/__init__.py b/modoboa/maillog/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modoboa/maillog/management/commands/logparser.py b/modoboa/maillog/management/commands/logparser.py new file mode 100644 index 000000000..87817fd2a --- /dev/null +++ b/modoboa/maillog/management/commands/logparser.py @@ -0,0 +1,632 @@ +# coding: utf-8 + +""" +Postfix log parser. + +This scripts parses a log file produced by postfix (or using the same +format). It looks for predefined events and build statistics about +their occurence rate. + +At the, somes default graphics are generated using the grapher module. +(see grapher.py) + +Predefined events are: + * Per domain sent/received messages, + * Per domain received bad messages (bounced, reject for now), + * Per domain sent/received traffics size, + * Global consolidation of all previous events. + +""" + +from datetime import datetime +import io +import os +import re +import sys +import time + +import rrdtool + +from django.core.management.base import BaseCommand +from django.db.models import Q +from django.utils import timezone + +from modoboa.admin.models import Domain +from modoboa.parameters import tools as param_tools +from modoboa.lib.email_utils import split_mailbox + +from ... import lib +from ... import models + + +rrdstep = 60 +xpoints = 540 +points_per_sample = 3 +variables = ["sent", "recv", "bounced", "reject", "spam", "virus", + "size_sent", "size_recv"] + + +class LogParser: + + def __init__(self, options, workdir, year=None, greylist=False): + """Constructor.""" + self.logfile = options["logfile"] + self.debug = options["debug"] + self.verbose = options["verbose"] + try: + self.f = io.open(self.logfile, encoding="utf-8") + except IOError as errno: + self._dprint("%s" % errno) + sys.exit(1) + self.workdir = workdir + self.__year = year + self.cfs = ["AVERAGE", "MAX"] + + curtime = time.localtime() + if not self.__year: + self.__year = curtime.tm_year + self.curmonth = curtime.tm_mon + + self.data = {"global": {}} + self.domains = [] + self._load_domain_list() + + self.workdict = {} + self.lupdates = {} + + # set up regular expression + self._date_expressions = [ + r"(?P\w+)\s+(?P\d+)\s+(?P\d+):(?P\d+):(?P\d+)(?P.*)", # noqa + r"(?P\d+)-(?P\d+)-(?P\d+)T(?P\d+):(?P\d+):(?P\d+)\.\d+\+\d+:\d+(?P.*)" # noqa + ] + self._date_expressions = [ + re.compile(v) for v in self._date_expressions] + self.date_expr = None + self._regex = { + "line": r"\s+([-\w\.]+)\s+(\w+)/?(\w*)[[](\d+)[]]:\s+(.*)", + "id": r"(\w+): (.*)", + "reject": r"reject: .*from=<.*>,? to=<[^@]+@([^>]+)>", + "message-id": r"message-id=<([^>]*)>", + "from+size": r"from=<([^>]*)>, size=(\d+)", + "to+status": r"to=<([^>]*)>.*status=(\S+)", + "orig_to": r"orig_to=<([^>]*)>.*", + "amavis": r"(?PINFECTED|SPAM|SPAMMY) .* <[^>]+> -> <[^@]+@(?P[^>]+)>.*", # noqa + "rmilter_line": r"<(?P[0-9a-f]{10})>; (?P.*)", + "rmilter_msg_done": r"msg done: queue_id: <(?P[^>]+)>; message id: <(?P[^>]+)>.*; from: <(?P[^>]+)>; rcpt: <(?P[^>]+)>.*; spam scan: (?P[^;]+); virus scan:", # noqa + "rmilter_spam": r"mlfi_eom: (rejecting spam|add spam header to message according to spamd action)", # noqa + "rmilter_virus": r"mlfi_eom:.* virus found", + "rmilter_greylist": ( + r"GREYLIST\([0-9]+\.[0-9]{2}\)\[greylisted[^\]]*\]") + } + self._regex = {k: re.compile(v) for k, v in self._regex.items()} + self._srs_regex = { + "detect_srs": "^SRS[01][+-=]", + "reverse_srs0": r"^SRS0[+-=]\S+=\S{2}=(\S+)=(\S+)\@\S+$", + "reverse_srs1": r"^SRS1[+-=]\S+=\S+==\S+=\S{2}=(\S+)=(\S+)\@\S+$" + } + self._srs_regex = { + k: re.compile(v, re.IGNORECASE) for k, v in self._srs_regex.items() + } + + self.greylist = greylist + if greylist: + variables.insert(4, "greylist") + self._dprint("[settings] greylisting enabled") + + self._prev_se = -1 + self._prev_mi = -1 + self._prev_ho = -1 + self.cur_t = 0 + + def _load_domain_list(self): + """Load the list of allowed domains.""" + for dom in Domain.objects.all(): + domname = str(dom.name) + self.domains += [domname] + self.data[domname] = {} + + # Also add alias domains + for alias in dom.domainalias_set.all(): + aliasname = str(alias.name) + self.domains += [aliasname] + self.data[aliasname] = {} + + def _dprint(self, msg): + """Print a debug message if required. + + :param str msg: debug message + """ + if not self.debug: + return + print(msg) + + def _parse_date(self, line): + """Try to match a date inside :kw:`line` and to convert it to + a timestamp. + + We try different date format until we find valid one. We then + store it for future use. + + :param str line: a log entry + :return: the remaining part of the line or None + """ + match = None + if self.date_expr is None: + for expr in self._date_expressions: + match = expr.match(line) + if match is not None: + self.date_expr = expr + break + else: + match = self.date_expr.match(line) + if match is None: + return None + ho = match.group("hour") + mi = match.group("min") + se = match.group("sec") + mo = match.group("month") + da = match.group("day") + try: + ye = match.group("year") + except IndexError: + ye = self.year(mo) + # Keep original timestamp + self.orig_ts = lib.date_to_timestamp([ye, mo, da, ho, mi, se]) + + # Now get the current period (based on rrdstep) + se = int(int(se) / rrdstep) # rrd step is one-minute => se = 0 + if self._prev_se != se or self._prev_mi != mi or self._prev_ho != ho: + self.cur_t = lib.date_to_timestamp([ye, mo, da, ho, mi, se]) + self.cur_t = self.cur_t - self.cur_t % rrdstep + self._prev_mi = mi + self._prev_ho = ho + self._prev_se = se + return match.group("eol") + + def init_rrd(self, fname, m): + """init_rrd. + + Set-up Data Sources (DS) + Set-up Round Robin Archives (RRA): + - day,week,month and year archives + - 2 types : AVERAGE and MAX + + parameter : start time + return : last epoch recorded + """ + ds_type = "ABSOLUTE" + rows = xpoints / points_per_sample + realrows = int(rows * 1.1) # ensure that the full range is covered + day_steps = int(3600 * 24 / (rrdstep * rows)) + week_steps = day_steps * 7 + month_steps = week_steps * 5 + year_steps = month_steps * 12 + + # Set up data sources for our RRD + params = [] + for v in variables: + params += ["DS:%s:%s:%s:0:U" % (v, ds_type, rrdstep * 2)] + + # Set up RRD to archive data + for cf in ["AVERAGE", "MAX"]: + for step in [day_steps, week_steps, month_steps, year_steps]: + params += ["RRA:%s:0.5:%s:%s" % (cf, step, realrows)] + + # With those setup, we can now created the RRD + rrdtool.create(str(fname), + "--start", str(m), + "--step", str(rrdstep), + *params) + return m + + def add_datasource_to_rrd(self, fname, dsname): + """Add a new data source to an exisitng file. + + Add missing Data Sources (DS) to existing Round Robin Archive (RRA): + See init_rrd for details. + """ + ds_def = "DS:%s:ABSOLUTE:%s:0:U" % (dsname, rrdstep * 2) + rrdtool.tune(fname, ds_def) + self._dprint("[rrd] added DS %s to %s" % (dsname, fname)) + + def add_point_to_rrd(self, fname, tpl, values, ts=None): + """Try to add a new point to RRD file.""" + if ts: + values = "{}:{}".format(ts, values) + if self.verbose: + print("[rrd] VERBOSE update -t %s %s" % (tpl, values)) + try: + rrdtool.update(str(fname), "-t", tpl, values) + except rrdtool.OperationalError as e: + op_match = re.match(r"unknown DS name '(\w+)'", str(e)) + if op_match is None: + raise + self.add_datasource_to_rrd(str(fname), op_match.group(1)) + rrdtool.update(str(fname), "-t", tpl, values) + + def update_rrd(self, dom, t): + """update_rrd + + Update RRD with records at t time. + + True : if data are up-to-date for current minute + False : syslog may have probably been already recorded + or something wrong + """ + fname = "%s/%s.rrd" % (self.workdir, dom) + m = t - (t % rrdstep) + + self._dprint("[rrd] updating %s" % fname) + if not os.path.exists(fname): + self.lupdates[fname] = self.init_rrd(fname, m) + self._dprint("[rrd] create new RRD file %s" % fname) + else: + if fname not in self.lupdates: + self.lupdates[fname] = rrdtool.last(str(fname)) + + if m <= self.lupdates[fname]: + if self.verbose: + print("[rrd] VERBOSE events at %s already recorded in RRD" % m) + return False + + tpl = "" + for v in variables: + if tpl != "": + tpl += ":" + tpl += v + # Missing some RRD steps + # Est ce vraiment nécessaire... ? + if m > self.lupdates[fname] + rrdstep: + values = "" + for v in variables: + if values != "": + values += ":" + values += "0" + for p in range(self.lupdates[fname] + rrdstep, m, rrdstep): + self.add_point_to_rrd(fname, tpl, values, ts=p) + + values = "%s" % m + tpl = "" + for v in variables: + values += ":" + values += str(self.data[dom][m][v]) + if tpl != "": + tpl += ":" + tpl += v + self.add_point_to_rrd(fname, tpl, values) + self.lupdates[fname] = m + return True + + def initcounters(self, dom): + init = {} + for v in variables: + init[v] = 0 + self.data[dom][self.cur_t] = init + + def inc_counter(self, dom, counter, val=1): + if dom is not None and dom in self.domains: + if self.cur_t not in self.data[dom]: + self.initcounters(dom) + self.data[dom][self.cur_t][counter] += val + + if self.cur_t not in self.data["global"]: + self.initcounters("global") + self.data["global"][self.cur_t][counter] += val + + def year(self, month): + """Return the appropriate year + + Date used in log files don't always embark the year so we need + to guess it :p + + This method tries to deal with year changes in a simply + (ugly?) way: if we currently are in january and the given + month is different, return the current year -1. Otherwise, + return the current year. + + Obviously, this method only works for year to year + 1 + changes. + + :param month: the month of the current record beeing parsed + :return: an integer + """ + month = time.strptime(month, "%b").tm_mon if not month.isdigit() \ + else int(month) + if self.curmonth == 1 and month != self.curmonth: + return self.__year - 1 + return self.__year + + def is_srs_forward(self, mail_address): + """ Check if mail address has been mangled by SRS + + Sender Rewriting Scheme (SRS) modifies mail adresses so that they + always end in a local domain. + + :param str mail_address + :return a boolean + """ + return self._srs_regex["detect_srs"].match(mail_address) is not None + + def reverse_srs(self, mail_address): + """ Try to unwind a mail address rewritten by SRS + + Sender Rewriting Scheme (SRS) modifies mail adresses so that they + always end in a local domain. Common Postfix implementations of SRS + rewrite all non-local mail addresss. + + :param str mail_address + :return a str + """ + m = self._srs_regex["reverse_srs0"].match(mail_address) + m = self._srs_regex["reverse_srs1"].match(mail_address) \ + if m is None else m + + if m is not None: + return "%s@%s" % m.group(2, 1) + else: + return mail_address + + def _parse_amavis(self, log, host, pid, subprog): + """ Parse an Amavis log entry. + + :param str log: logged message + :param str host: hostname + :param str pid: process ID + :return: True on success + """ + m = self._regex["amavis"].search(log) + if m is not None: + dom = m.group("domain") + spam_result = m.group("result") + if dom is not None and dom in self.domains: + if spam_result == "INFECTED": + self.inc_counter(dom, "virus") + elif spam_result in ["SPAM", "SPAMMY"]: + self.inc_counter(dom, "spam") + return True + + return False + + def _parse_rmilter(self, log, host, pid, subprog): + """ Parse an Rmilter log entry. + + :param str log: logged message + :param str host: hostname + :param str pid: process ID + :return: True on success + """ + m = self._regex["rmilter_line"].match(log) + if m is None: + return False + rmilter_hash, msg = m.groups() + workdict_key = "rmilter_" + rmilter_hash + + # Virus check must come before spam check due to pattern similarity. + m = self._regex["rmilter_virus"].match(msg) + if m is not None: + self.workdict[workdict_key] = { + "action": "virus" + } + return True + m = self._regex["rmilter_spam"].match(msg) + if m is not None: + self.workdict[workdict_key] = { + "action": "spam" + } + return True + + # Greylisting + if self.greylist: + m = self._regex["rmilter_greylist"].search(msg) + if m is not None: + self.workdict[workdict_key] = { + "action": "greylist" + } + return True + + # Gather information about message sender and queue ID + m = self._regex["rmilter_msg_done"].match(msg) + if m is not None: + dom = split_mailbox(m.group("rcpt"))[1] + + if workdict_key in self.workdict: + action = self.workdict[workdict_key].get("action", None) + if action is not None: + self.inc_counter(dom, action) + return True + + return False + + def _parse_postfix(self, log, host, pid, subprog): + """ Parse a log entry generated by Postfix. + + :param str log: logged message + :param str host: hostname + :param str pid: process ID + :return: True on success + """ + m = self._regex["id"].match(log) + if m is None: + return False + + queue_id, msg = m.groups() + + # Handle rejected mails. + if queue_id == "NOQUEUE": + m = self._regex["reject"].match(msg) + dom = m.group(1) if m is not None else None + if dom in self.domains: + condition = ( + self.greylist and ( + "Greylisted" in msg or + (subprog == "postscreen" and " 450 " in msg)) + ) + if condition: + self.inc_counter(dom, "greylist") + else: + self.inc_counter(dom, "reject") + return True + + # Message acknowledged. + m = self._regex["message-id"].search(msg) + if m is not None: + self.workdict[queue_id] = {"from": m.group(1), "size": 0} + return True + + # Message enqueued. + m = self._regex["from+size"].search(msg) + if m is not None: + self.workdict[queue_id] = { + "from": self.reverse_srs(m.group(1)), + "size": int(m.group(2)) + } + return True + + # Message disposition. + m = self._regex["to+status"].search(msg) + if m is None: + return False + (msg_to, msg_status) = m.groups() + if queue_id not in self.workdict: + self._dprint("[parser] inconsistent mail (%s: %s), skipping" + % (queue_id, msg_to)) + return True + if msg_status not in variables: + self._dprint("[parser] unsupported status %s, skipping" + % msg_status) + return True + + # orig_to is optional. + m = self._regex["orig_to"].search(msg) + msg_orig_to = m.group(1) if m is not None else None + + # Handle local "from" domains. + from_domain = split_mailbox(self.workdict[queue_id]["from"])[1] + if from_domain is not None and from_domain in self.domains: + self.inc_counter(from_domain, "sent") + self.inc_counter(from_domain, "size_sent", + self.workdict[queue_id]["size"]) + + # Handle local "to" domains. + to_domain = None + condition = ( + msg_orig_to is not None and + not self.is_srs_forward(msg_orig_to) + ) + if condition: + to_domain = split_mailbox(msg_orig_to)[1] + if to_domain is None: + to_domain = split_mailbox(msg_to)[1] + + if msg_status == "sent": + self.inc_counter(to_domain, "recv") + self.inc_counter(to_domain, "size_recv", + self.workdict[queue_id]["size"]) + else: + self.inc_counter(to_domain, msg_status) + + last_message = models.Maillog.objects.last() + cur_dt = datetime.fromtimestamp(self.orig_ts) + tz = timezone.get_current_timezone() + cur_dt = tz.localize(cur_dt) + condition = ( + last_message and ( + cur_dt < last_message.date or + (cur_dt == last_message.date and + models.Maillog.objects.filter( + date=cur_dt, queue_id=queue_id).exists()) + ) + ) + if not condition: + if from_domain in self.domains: + from_domain = Domain.objects.filter( + Q(name=from_domain) | + Q(domainalias__name=from_domain) + ).first() + else: + from_domain = None + if to_domain in self.domains: + to_domain = Domain.objects.filter( + Q(name=to_domain) | + Q(domainalias__name=to_domain) + ).first() + else: + to_domain = None + if msg_status == "sent" and to_domain: + msg_status = "received" + models.Maillog.objects.create( + queue_id=queue_id, + date=cur_dt, + sender=self.workdict[queue_id]["from"], + rcpt=msg_to, + original_rcpt=msg_orig_to, + size=self.workdict[queue_id]["size"], + status=msg_status, + from_domain=from_domain, + to_domain=to_domain + ) + + return True + + def _parse_line(self, line): + """Parse a single log line. + + :param str line: log line + """ + line = self._parse_date(line) + if line is None: + return + m = self._regex["line"].match(line) + if not m: + return + host, prog, subprog, pid, log = m.groups() + + try: + parser = getattr(self, "_parse_{}".format(prog)) + if not parser(log, host, pid, subprog): + self._dprint("[parser] ignoring %r log: %r" % (prog, log)) + except AttributeError: + self._dprint( + "[parser] no log handler for \"{}\": {}".format(prog, log)) + + def process(self): + """Process the log file. + + We parse it and then generate standard graphics (day, week, + month). + """ + for line in self.f.readlines(): + self._parse_line(line) + + for dom, data in self.data.items(): + self._dprint("[rrd] dealing with domain %s" % dom) + for t in sorted(data.keys()): + self.update_rrd(dom, t) + + +class Command(BaseCommand): + help = "Log file parser" + + def add_arguments(self, parser): + """Add extra arguments to command line.""" + parser.add_argument( + "--logfile", default=None, + help="postfix log in syslog format", metavar="FILE") + parser.add_argument( + "--verbose", default=False, action="store_true", + dest="verbose", help="Set verbose mode") + parser.add_argument( + "--debug", default=False, action="store_true", + help="Set debug mode") + + def handle(self, *args, **options): + if options["logfile"] is None: + options["logfile"] = param_tools.get_global_parameter( + "logfile", app="maillog") + greylist = param_tools.get_global_parameter( + "greylist", raise_exception=False) + p = LogParser( + options, param_tools.get_global_parameter("rrd_rootdir"), + None, greylist + ) + p.process() diff --git a/modoboa/maillog/management/commands/update_statistics.py b/modoboa/maillog/management/commands/update_statistics.py new file mode 100644 index 000000000..d828acab9 --- /dev/null +++ b/modoboa/maillog/management/commands/update_statistics.py @@ -0,0 +1,84 @@ +"""Management command to update various statistics.""" + +import os + +from dateutil.relativedelta import relativedelta +import rrdtool + +from django.core.management.base import BaseCommand +from django.utils import timezone + +from modoboa.core import models as core_models +from modoboa.parameters import tools as param_tools + + +class Command(BaseCommand): + """Update statistics.""" + + help = "Update modoboa statistics" + + def add_arguments(self, parser): + """Add extra arguments to command line.""" + parser.add_argument( + "--rebuild", action="store_true", + help="Rebuild statistics from the begining") + + def _create_new_accounts_rrd_file(self, fname, start): + """Create RRD file.""" + step = 3600 + ds_name = "new_accounts" + params = ["DS:{}:ABSOLUTE:{}:0:U".format(ds_name, step * 2)] + params += [ + "RRA:AVERAGE:0.5:1:48", # 48 hours with a 1h granularity + "RRA:AVERAGE:0.5:24:31", # 31 days with a 1d granularity + "RRA:AVERAGE:0.5:168:52", # 52 weeks with a 1w granularity + "RRA:AVERAGE:0.5:5208:24", # 24 months with a 1m granularity + ] + rrdtool.create( + str(fname), "--start", str(start), "--step", str(step), + *params + ) + + def update_account_creation_stats(self, rebuild=False): + """Look for newly created accounts.""" + db_path = os.path.join(self.rootdir, "new_accounts.rrd") + data = [] + if not rebuild: + end = timezone.now().replace(minute=0, second=0, microsecond=0) + start = end - relativedelta(hours=1) + new_accounts = core_models.User.objects.filter( + date_joined__gte=start, date_joined__lt=end).count() + data.append( + "{}:{}".format(int(end.strftime("%s")), new_accounts * 60)) + else: + existing_stats = {} + start = None + for user in core_models.User.objects.all().order_by("date_joined"): + hour = user.date_joined.replace( + minute=0, second=0, microsecond=0) + relativedelta(hours=1) + if start is None: + start = hour + hour = int(hour.strftime("%s")) + if hour not in existing_stats: + existing_stats[hour] = 0 + existing_stats[hour] += 1 + end = timezone.now().replace( + minute=0, second=0, microsecond=0) + relativedelta(hours=1) + if os.path.exists(db_path): + os.unlink(db_path) + hour = start + while hour <= end: + new_accounts = existing_stats.get(int(hour.strftime("%s")), 0) + data.append("{}:{}".format( + int(hour.strftime("%s")), new_accounts * 60)) + hour += relativedelta(hours=1) + if not os.path.exists(db_path): + self._create_new_accounts_rrd_file( + db_path, int((start - relativedelta(hours=1)).strftime("%s")) + ) + rrdtool.update(str(db_path), *data) + + def handle(self, *args, **options): + """Entry point.""" + self.rootdir = param_tools.get_global_parameter("rrd_rootdir") + self.update_account_creation_stats(options["rebuild"]) diff --git a/modoboa/maillog/migrations/0001_initial.py b/modoboa/maillog/migrations/0001_initial.py new file mode 100644 index 000000000..92e1122eb --- /dev/null +++ b/modoboa/maillog/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# Generated by Django 2.2.12 on 2020-09-16 09:13 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('admin', '0017_alarm'), + ] + + operations = [ + migrations.CreateModel( + name='Maillog', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('queue_id', models.CharField(max_length=50)), + ('date', models.DateTimeField()), + ('sender', models.EmailField(max_length=254)), + ('rcpt', models.EmailField(max_length=254)), + ('original_rcpt', models.EmailField(max_length=254, null=True)), + ('size', models.PositiveIntegerField()), + ('status', models.CharField(max_length=15)), + ('from_domain', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sent_messages_log', to='admin.Domain')), + ('to_domain', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='recv_messages_log', to='admin.Domain')), + ], + options={ + 'ordering': ['date'], + }, + ), + ] diff --git a/modoboa/maillog/migrations/0002_auto_20200916_1346.py b/modoboa/maillog/migrations/0002_auto_20200916_1346.py new file mode 100644 index 000000000..7d6f6a821 --- /dev/null +++ b/modoboa/maillog/migrations/0002_auto_20200916_1346.py @@ -0,0 +1,36 @@ +# Generated by Django 2.2.12 on 2020-09-16 11:46 + +from django.db import migrations + + +def rename_settings(apps, schema_editor): + LocalConfig = apps.get_model("core", "LocalConfig") + lc = LocalConfig.objects.first() + if not lc: + return + stats_params = lc._parameters.pop("modoboa_stats", None) + if stats_params: + lc._parameters["maillog"] = stats_params + lc.save() + + +def backwards(apps, schema_editor): + LocalConfig = apps.get_model("core", "LocalConfig") + lc = LocalConfig.objects.first() + if not lc: + return + params = lc._parameters.pop("maillog", None) + if params: + lc._parameters["modoboa_stats"] = params + lc.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('maillog', '0001_initial'), + ] + + operations = [ + migrations.RunPython(rename_settings, backwards), + ] diff --git a/modoboa/maillog/migrations/__init__.py b/modoboa/maillog/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modoboa/maillog/models.py b/modoboa/maillog/models.py new file mode 100644 index 000000000..5f33d700e --- /dev/null +++ b/modoboa/maillog/models.py @@ -0,0 +1,27 @@ +"""App. related models.""" + +from django.db import models + + +class Maillog(models.Model): + """A model to store message logs.""" + + queue_id = models.CharField(max_length=50) + date = models.DateTimeField() + sender = models.EmailField() + rcpt = models.EmailField() + original_rcpt = models.EmailField(null=True) + size = models.PositiveIntegerField() + status = models.CharField(max_length=15) + + from_domain = models.ForeignKey( + "admin.Domain", on_delete=models.SET_NULL, null=True, + related_name="sent_messages_log" + ) + to_domain = models.ForeignKey( + "admin.Domain", on_delete=models.SET_NULL, null=True, + related_name="recv_messages_log" + ) + + class Meta: + ordering = ['date'] diff --git a/modoboa/maillog/parser.py b/modoboa/maillog/parser.py new file mode 100644 index 000000000..4541247ec --- /dev/null +++ b/modoboa/maillog/parser.py @@ -0,0 +1,301 @@ +"""Parsing tools for maillog related files.""" + +import io +import re +import time + +from modoboa.admin import models as admin_models +from modoboa.lib.email_utils import split_mailbox + +from . import utils + + +class MaillogParser: + """Main parser class for maillog file.""" + + def __init__(self, year=None, greylist=False, verbose=False, debug=False): + """Constructor.""" + self.debug = debug + self.verbose = verbose + + self.domains = [] + self._load_domain_list() + + self.workdict = {} + self.lupdates = {} + + self.__year = year + curtime = time.localtime() + if not self.__year: + self.__year = curtime.tm_year + self.curmonth = curtime.tm_mon + + # set up regular expression + self._date_expressions = [ + r"(?P\w+)\s+(?P\d+)\s+(?P\d+):(?P\d+):(?P\d+)(?P.*)", # noqa + r"(?P\d+)-(?P\d+)-(?P\d+)T(?P\d+):(?P\d+):(?P\d+)\.\d+\+\d+:\d+(?P.*)" # noqa + ] + self._date_expressions = [ + re.compile(v) for v in self._date_expressions] + self.date_expr = None + self._regex = { + "line": r"\s+([-\w\.]+)\s+(\w+)/?(\w*)[[](\d+)[]]:\s+(.*)", + "id": r"(\w+): (.*)", + "reject": r"reject: .*from=<.*>,? to=<[^@]+@([^>]+)>", + "message-id": r"message-id=<([^>]*)>", + "from+size": r"from=<([^>]*)>, size=(\d+)", + "to+status": r"to=<([^>]*)>.*status=(\S+)", + "orig_to": r"orig_to=<([^>]*)>.*", + } + self._regex = {k: re.compile(v) for k, v in self._regex.items()} + self._srs_regex = { + "detect_srs": "^SRS[01][+-=]", + "reverse_srs0": r"^SRS0[+-=]\S+=\S{2}=(\S+)=(\S+)\@\S+$", + "reverse_srs1": r"^SRS1[+-=]\S+=\S+==\S+=\S{2}=(\S+)=(\S+)\@\S+$" + } + self._srs_regex = { + k: re.compile(v, re.IGNORECASE) for k, v in self._srs_regex.items() + } + + self.greylist = greylist + if greylist: + self._dprint("[settings] greylisting enabled") + + def _load_domain_list(self): + """Load the list of allowed domains.""" + for dom in admin_models.Domain.objects.all(): + domname = str(dom.name) + self.domains += [domname] + # Also add alias domains + for alias in dom.domainalias_set.all(): + aliasname = str(alias.name) + self.domains += [aliasname] + + def _dprint(self, msg): + """Print a debug message if required. + + :param str msg: debug message + """ + if not self.debug: + return + print(msg) + + def year(self, month): + """Return the appropriate year + + Date used in log files don't always embark the year so we need + to guess it :p + + This method tries to deal with year changes in a simply + (ugly?) way: if we currently are in january and the given + month is different, return the current year -1. Otherwise, + return the current year. + + Obviously, this method only works for year to year + 1 + changes. + + :param month: the month of the current record beeing parsed + :return: an integer + """ + month = ( + time.strptime(month, "%b").tm_mon if not month.isdigit() + else int(month) + ) + if self.curmonth == 1 and month != self.curmonth: + return self.__year - 1 + return self.__year + + def _store_current_date(self, match): + """Transform and store parsed date.""" + ho = match.group("hour") + mi = match.group("min") + se = match.group("sec") + mo = match.group("month") + da = match.group("day") + try: + ye = match.group("year") + except IndexError: + ye = self.year(mo) + self.cur_t = utils.date_to_timestamp([ye, mo, da, ho, mi, se]) + + def _parse_date(self, line): + """Try to match a date inside :kw:`line` and to convert it to + a timestamp. + + We try different date format until we find valid one. We then + store it for future use. + + :param str line: a log entry + :return: the remaining part of the line or None + """ + match = None + if self.date_expr is None: + for expr in self._date_expressions: + match = expr.match(line) + if match is not None: + self.date_expr = expr + break + else: + match = self.date_expr.match(line) + if match is None: + return None + self._store_current_date(match) + return match.group("eol") + + def is_srs_forward(self, mail_address): + """ Check if mail address has been mangled by SRS + + Sender Rewriting Scheme (SRS) modifies mail adresses so that they + always end in a local domain. + + :param str mail_address + :return a boolean + """ + return self._srs_regex["detect_srs"].match(mail_address) is not None + + def reverse_srs(self, mail_address): + """ Try to unwind a mail address rewritten by SRS + + Sender Rewriting Scheme (SRS) modifies mail adresses so that they + always end in a local domain. Common Postfix implementations of SRS + rewrite all non-local mail addresss. + + :param str mail_address + :return a str + """ + m = self._srs_regex["reverse_srs0"].match(mail_address) + m = self._srs_regex["reverse_srs1"].match(mail_address) \ + if m is None else m + + if m is not None: + return "%s@%s" % m.group(2, 1) + return mail_address + + def new_domain_event(self, domain, name, size=None): + """Take action about new event for domain.""" + pass + + def new_message_processed( + self, queue_id, msg_status, from_domain, to_domain, msg_to, + msg_orig_to): + """Store new message in local database.""" + pass + + def _parse_postfix(self, log, host, pid, subprog): + """ Parse a log entry generated by Postfix. + + :param str log: logged message + :param str host: hostname + :param str pid: process ID + :return: True on success + """ + m = self._regex["id"].match(log) + if m is None: + return False + + queue_id, msg = m.groups() + + # Handle rejected mails. + if queue_id == "NOQUEUE": + m = self._regex["reject"].match(msg) + dom = m.group(1) if m is not None else None + if dom in self.domains: + condition = ( + self.greylist and ( + "Greylisted" in msg or + (subprog == "postscreen" and " 450 " in msg)) + ) + self.new_domain_event( + dom, "greylist" if condition else "reject") + return True + + # Message acknowledged. + m = self._regex["message-id"].search(msg) + if m is not None: + self.workdict[queue_id] = {"from": m.group(1), "size": 0} + return True + + # Message enqueued. + m = self._regex["from+size"].search(msg) + if m is not None: + self.workdict[queue_id] = { + "from": self.reverse_srs(m.group(1)), + "size": int(m.group(2)) + } + return True + + # Message disposition. + m = self._regex["to+status"].search(msg) + if m is None: + return False + (msg_to, msg_status) = m.groups() + if queue_id not in self.workdict: + self._dprint("[parser] inconsistent mail (%s: %s), skipping" + % (queue_id, msg_to)) + return True + + # orig_to is optional. + m = self._regex["orig_to"].search(msg) + msg_orig_to = m.group(1) if m is not None else None + + # Handle local "from" domains. + from_domain = split_mailbox(self.workdict[queue_id]["from"])[1] + if from_domain is not None and from_domain in self.domains: + self.new_domain_event( + from_domain, "sent", self.workdict[queue_id]["size"]) + + # Handle local "to" domains. + to_domain = None + condition = ( + msg_orig_to is not None and + not self.is_srs_forward(msg_orig_to) + ) + if condition: + to_domain = split_mailbox(msg_orig_to)[1] + if to_domain is None: + to_domain = split_mailbox(msg_to)[1] + + if msg_status == "sent": + msg_status = "recv" + self.new_domain_event( + to_domain, "recv", self.workdict[queue_id]["size"]) + + # Store log entry + self.new_message_processed( + queue_id, msg_status, from_domain, to_domain, msg_to, msg_orig_to) + + return True + + def _parse_line(self, line): + """Parse a single log line. + + :param str line: log line + """ + line = self._parse_date(line) + if line is None: + return + m = self._regex["line"].match(line) + if not m: + return + host, prog, subprog, pid, log = m.groups() + + try: + parser = getattr(self, "_parse_{}".format(prog)) + if not parser(log, host, pid, subprog): + self._dprint("[parser] ignoring %r log: %r" % (prog, log)) + except AttributeError: + self._dprint( + "[parser] no log handler for \"%r\": %r".format(prog, log)) + + def parse(self, logfile): + """Process the log file.""" + try: + fp = io.open(logfile, encoding="utf-8") + except IOError as errno: + self._dprint("%s" % errno) + return + + for line in fp.readlines(): + self._parse_line(line) + + fp.close() diff --git a/modoboa/maillog/signals.py b/modoboa/maillog/signals.py new file mode 100644 index 000000000..59b2c7ae4 --- /dev/null +++ b/modoboa/maillog/signals.py @@ -0,0 +1,5 @@ +"""Modoboa stats signals.""" + +import django.dispatch + +get_graph_sets = django.dispatch.Signal(providing_args=["user"]) diff --git a/modoboa/maillog/static/maillog/css/stats.css b/modoboa/maillog/static/maillog/css/stats.css new file mode 100644 index 000000000..edd761be7 --- /dev/null +++ b/modoboa/maillog/static/maillog/css/stats.css @@ -0,0 +1,71 @@ +#menubar { + padding-left: 15px; +} + +.chart { + position: relative; + margin-bottom: 20px; +} + +.grid .tick { + stroke: darkgrey; + stroke-dasharray: 1, 2; + opacity: 0.9; +} +.grid path { + stroke-width: 0; +} + +.line_over { + stroke: darkgrey; + stroke-width: 1.5px; + stroke-dasharray: 1, 2; + display: none; +} + +.axis { + font-size: 0.7em; +} + +.axis path, +.axis line { + fill: none; + stroke: #000; + shape-rendering: crispEdges; +} + +.area, .area2 { + fill-opacity: .6; +} + +.brush .extent { + stroke: #fff; + fill-opacity: .125; + shape-rendering: crispEdges; +} + +.legend rect { + fill: none; + stroke: black; + opacity: 0; +} + +.tip { + position: absolute; + z-index: 20; + top: 60px; + display: none; + font-size: 0.9em; + padding: 5px; + background: rgba(0, 0, 0, 0.6); + color: #fff; + border-radius: 5px; +} + +.tip p { + margin: 0; +} + +.tracker { + display: none; +} \ No newline at end of file diff --git a/modoboa/maillog/static/maillog/js/d3.legend.js b/modoboa/maillog/static/maillog/js/d3.legend.js new file mode 100644 index 000000000..52b267a6c --- /dev/null +++ b/modoboa/maillog/static/maillog/js/d3.legend.js @@ -0,0 +1,70 @@ +// d3.legend.js +// (C) 2012 ziggy.jonsson.nyc@gmail.com +// MIT licence + +(function() { + d3.legend = function(g, clicked) { + g.each(function() { + var g = d3.select(this), + items = {}, + svg = d3.select(g.property("nearestViewportElement")), + legendPadding = g.attr("data-style-padding") || 5, + lb = g.selectAll(".legend-box").data([true]), + li = g.selectAll(".legend-items").data([true]); + + lb.enter().append("rect").classed("legend-box", true); + li.enter().append("g").classed("legend-items", true); + + svg.selectAll("[data-legend]").each(function() { + var self = d3.select(this); + + items[self.attr("data-legend")] = { + pos: self.attr("data-legend-pos") || this.getBBox().y, + color: self.attr("data-legend-color") != undefined ? self.attr("data-legend-color") : self.style("fill") != 'none' ? self.style("fill") : self.style("stroke") + }; + }); + + items = d3.entries(items).sort(function(a, b) { + return a.value.pos - b.value.pos; + }); + + li.selectAll("text") + .data(items, function(d) { return d.key; }) + .call(function(d) { d.enter().append("text"); }) + .call(function(d) { d.exit().remove(); }) + .attr("y", function(d, i) { return i + "em"; }) + .attr("x", "1em") + .text(function(d) { return d.key; }); + + li.selectAll("circle") + .data(items, function(d) { return d.key; }) + .call(function(d) { d.enter().append("circle"); }) + .call(function(d) { d.exit().remove(); }) + .attr("cy", function(d, i) { return i - 0.25 + "em"; }) + .attr("cx", 0) + .attr("r", "4px") + .style("stroke", function(d) { return d.value.color; }) + .style("stroke-width", "1px") + .style("stroke-opacity", 0.8) + .style("fill", function(d) { return d.value.color; }) + .on("click", function(d) { + var curve = d3.selectAll("[data-legend='" + d.key + "']"); + var state = (curve.style("display") == "none") + ? "block" : "none"; + + curve.style("display", state); + d3.select(this).style( + "fill", (state == "none") ? "none" : d.value.color + ); + }); + + // Reposition and resize the box + var lbbox = li[0][0].getBBox(); + lb.attr("x", (lbbox.x - legendPadding)) + .attr("y", (lbbox.y - legendPadding)) + .attr("height", (lbbox.height + 2 * legendPadding)) + .attr("width", (lbbox.width + 2 * legendPadding)); + }); + return g; + }; +})(); diff --git a/modoboa/maillog/static/maillog/js/graphic.js b/modoboa/maillog/static/maillog/js/graphic.js new file mode 100644 index 000000000..1c422c0b8 --- /dev/null +++ b/modoboa/maillog/static/maillog/js/graphic.js @@ -0,0 +1,355 @@ +/** + * Creates an instance of ModoChart. + * + * @constructor + * @this {ModoChart} + * @param {string} selection add chart to this DOM element. + * + */ +function ModoChart(selection) { + var width = 0, + height = 300, + preview_height = 80, + brush_height = 7, + margin = { + left: 50, + top: 10, + bottom: 20 + }; + var container; + + /** + * Create and render the chart. + * + * @this {ModoChart} + * @param {Object} data chart's data. + */ + function my(data) { + container = d3.select(selection) + .append("div") + .attr("class", "chart"); + + container.append("h6").html(data.title); + width = $(selection).width(); + + var svg = container + .append("svg") + .attr("width", width) + .attr("height", my.totalHeight()); + + svg.append("defs").append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width - 2 * margin.left) + .attr("height", height + margin.top + margin.bottom); + + var x = d3.time.scale().range([0, width - 2 * margin.left]), + x2 = d3.time.scale().range([0, width - 2 * margin.left]); + var xAxis = d3.svg.axis().scale(x).orient("bottom"), + xAxis2 = d3.svg.axis().scale(x2).orient("bottom"); + var y = d3.scale.linear().range([height, 0]), + y2 = d3.scale.linear().range([preview_height, 0]); + var yAxis = d3.svg.axis().scale(y).orient("left"); + var bisectDate = d3.bisector(function(d) { return d.x * 1000; }).left; + + var brush = d3.svg.brush() + .x(x2) + .on("brush", brushed); + + x.domain( + d3.extent(data.curves[0].data.map(function(d) { return d.x * 1000; })) + ); + y.domain( + [d3.min(data.curves, function(d) { return d3.min(d.data, function(v) { return v.y; }); }), + d3.max(data.curves, function(d) { return d3.max(d.data, function(v) { return v.y; }); }) + ] + ); + x2.domain(x.domain()); + y2.domain(y.domain()); + + var area = d3.svg.area() + .interpolate("monotone") + .x(function(d) { return x(d.x * 1000); }) + .y0(height) + .y1(function(d) { return y(d.y); }); + var area2 = d3.svg.area() + .interpolate("monotone") + .x(function(d) { return x2(d.x * 1000); }) + .y0(preview_height) + .y1(function(d) { return y2(d.y); }); + + var focus = svg.append("g") + .attr("class", "focus") + .attr("width", width - 2 * margin.left) + .attr("height", height) + .attr("pointer-events", "all") + .attr("transform", + "translate(" + margin.left + "," + margin.top + ")"); + var context = svg.append("g") + .attr("class", "context") + .attr("transform", "translate(" + margin.left + "," + (height + margin.top + margin.bottom + brush_height) + ")"); + + var curvegroup = focus.append("g") + .attr('clip-path', 'url(#clip)'); + var curve = curvegroup.selectAll(".curve") + .data(data.curves) + .enter().append("g") + .attr("class", "curve"); + var curve2 = context.selectAll(".curve") + .data(data.curves) + .enter().append("g") + .attr("class", "curve"); + + /* Vertical line */ + var line = focus.append("line") + .attr("class", "line_over") + .attr("x1", 0) + .attr("y1", 0) + .attr("x2", 0) + .attr("y2", height); + + /* Custom rectangle to catch mouse events */ + var rect = focus.append("rect") + .attr("fill", "none") + .attr("width", width - 2 * margin.left) + .attr("height", height); + + var tooltip = container + .append("div") + .attr("class", "remove") + .attr("class", "tip"); + + var points = []; + + var path = curve.append("path") + .attr("class", "area") + .attr("d", function(d, i) { + var path = this; + + points[path] = {}; + d.data.forEach(function(d) { + points[path][d.x] = d.y; + }); + focus.append("circle") + .attr("class", "tracker tracker-" + i) + .attr("r", 4); + return area(d.data); + }) + .attr("data-legend", function(d) { return d.name; }) + .attr("stroke", function(d) { return d.color; }) + .attr("stroke-width", "2px") + .attr("stroke-opacity", 0.8) + .style("fill", function(d) { return d.color; }); + + var xrules = make_x_axis(); + + focus.append("g") + .attr("class", "grid") + .attr("transform", "translate(0," + height + ")") + .call(xrules); + focus.append("g") + .attr("class", "grid") + .call(make_y_axis); + focus.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + height + ")") + .call(xAxis); + focus.append("g") + .attr("class", "y axis") + .call(yAxis); + + rect.on("mouseover", function(d) { + line.style("display", "block"); + tooltip.style("display", "block"); + data.curves.forEach(function(d, cid) { + var display = d3.selectAll("[data-legend='" + d.name + "']") + .style("display"); + if (display == "none") { + return; + } + focus.select(".tracker-" + cid).style("display", "block"); + }); + }).on("mousemove", function(d) { + var mousex = d3.mouse(this)[0], + mousey = d3.mouse(this)[1]; + var date = x.invert(mousex); + var text = ""; + + line.attr("x1", mousex).attr("x2", mousex); + data.curves.forEach(function(d, cid) { + var display = d3.selectAll("[data-legend='" + d.name + "']") + .style("display"); + if (display == "none") { + return; + } + var i = bisectDate(d.data, date.getTime(), 1), + d0 = d.data[i - 1], + d1 = d.data[i], + point = date.getTime() - (d0.x * 1000) > (d1.x * 1000) - date.getTime() ? d1 : d0; + text += "

" + d.name + ": " + point.y.toFixed(3) + "

"; + + var tracker_x = x(point.x * 1000), + tracker_y = y(point.y); + + focus.selectAll(".tracker-" + cid) + .style("fill", d.color) + .attr("cx", tracker_x) + .attr("cy", tracker_y); + }); + if (mousex + tooltip[0][0].clientWidth >= width - 2 * margin.left) { + var left = mousex - (tooltip[0][0].clientWidth - (margin.left - 20)); + tooltip.style("left", left + "px"); + } else { + tooltip.style("left", (mousex + margin.left + 20) + "px"); + } + tooltip.html("

" + date + "

" + text); + }).on("mouseout", function(d) { + tooltip.style("display", "none"); + line.style("display", "none"); + focus.selectAll(".tracker").style("display", "none"); + }); + + var legend = focus.append("g") + .attr("class", "legend") + .attr("transform", "translate(50,10)") + .style("font-size", "0.8em") + .call(d3.legend); + + curve2.append("path") + .attr("class", "area2") + .attr("d", function(d) { return area2(d.data); }) + .attr("stroke", function(d) { return d.color; }) + .attr("stroke-width", "2px") + .attr("stroke-opacity", 0.8) + .attr("data-legend", function(d) { return d.name; }) + .style("fill", function(d) { return d.color; }); + + context.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + preview_height + ")") + .call(xAxis2); + + context.append("g") + .attr("class", "grid") + .attr("transform", "translate(0," + preview_height + ")") + .call(make_preview_x_axis); + + context.append("g") + .attr("class", "x brush") + .call(brush) + .selectAll("rect") + .attr("y", -6) + .attr("height", preview_height + brush_height); + + function make_x_axis() { + return d3.svg.axis() + .scale(x) + .orient("bottom") + .ticks(6) + .tickSize(-height, 0, 0) + .tickFormat(""); + } + + function make_y_axis() { + return d3.svg.axis() + .scale(y) + .orient("left") + .ticks(6) + .tickSize(-(width - 2 * margin.left), 0, 0) + .tickFormat(""); + } + + function make_preview_x_axis() { + return d3.svg.axis() + .scale(x2) + .orient("bottom") + .ticks(6) + .tickSize(-preview_height, 0, 0) + .tickFormat(""); + } + + function brushed() { + x.domain(brush.empty() ? x2.domain() : brush.extent()); + focus.selectAll(".area") + .attr("d", function(d) { return area(d.data); }); + focus.select(".x.axis").call(xAxis); + focus.select(".grid").call(xrules); + } + } + + /** + * Set or get chart's width. + * + * @this {ModoChart} + * @param {integer} value new width. + * @return {number} current width. + */ + my.width = function(value) { + if (!arguments.length) return width; + width = value; + return my; + }; + + /** + * Set or get chart's height. + * + * @this {ModoChart} + * @param {integer} value new height. + * @return {number} current height. + */ + my.height = function(value) { + if (!arguments.length) return height; + height = value; + return my; + }; + + /** + * Set or get chart's margin. + * + * @this {ModoChart} + * @param {Object} value new margin. + * @return {Object} current margin. + */ + my.margin = function(value) { + if (!arguments.length) return margin; + margin = value; + return my; + }; + + /** + * Compute and return chart's total height. + * + * @this {ModoChart} + * @return {integer} total height. + */ + my.totalHeight = function() { + return height + 2 * margin.top + + 2 * margin.bottom + preview_height + + brush_height; + }; + + /** + * Update the chart with new data. + * + * @this {ModoChart} + * @param {Object} data new data + */ + my.update = function(data) { + container.select("svg").remove(); + $(container[0][0]).empty(); + my(data); + }; + + /** + * Hide the chart. + * + * @this {ModoChart} + */ + my.hide = function () { + container.select("svg").remove(); + $(container[0][0]).empty(); + }; + + + return my; +} diff --git a/modoboa/maillog/static/maillog/js/stats.js b/modoboa/maillog/static/maillog/js/stats.js new file mode 100644 index 000000000..a62736cf6 --- /dev/null +++ b/modoboa/maillog/static/maillog/js/stats.js @@ -0,0 +1,189 @@ +/** + * Create an instance of Stats. + * + * @constructor + */ +var Stats = function(options) { + this.initialize(options); +}; + +Stats.prototype = { + constructor: Stats, + + defaults: { + deflocation: "graphs/", + language: "en", + domain_list_url: null + }, + + initialize: function(options) { + this.options = $.extend({}, this.defaults, options); + this.options.defcallback = $.proxy(this.charts_cb, this); + + var navobj = new History(this.options); + this.navobj = navobj; + + this.charts = {}; + + if (navobj.params.searchquery !== undefined) { + $("#searchquery").val(navobj.params.searchquery); + } + $("#searchquery").focus(function() { + var $this = $(this); + if ($this.val() === undefined) { + return; + } + $this.data("oldvalue", $this.val()); + $this.val(""); + }).blur(function() { + var $this = $(this); + if ($this.val() === "") { + if ($this.data("oldvalue")) { + $this.val($this.data('oldvalue')); + $this.data("oldvalue", null); + } else { + $this.val(gettext("Search a domain")); + } + } + }); + if (navobj.params.start) { + $("#id_from").val(navobj.params.start); + } + if (navobj.params.end) { + $("#id_to").val(navobj.params.end); + } + if (navobj.params.period) { + $("input[data-period={0}]".format(navobj.params.period)) + .attr("checked", true) + .parent().addClass("active"); + if (navobj.params.period == "custom") { + $("#custom_period").removeClass("hidden"); + } + } + $("#custom-period .datetime_picker").datetimepicker({ + format: 'YYYY-MM-DD HH:mm:ss', + locale: this.options.language, + ignoreReadonly: true + }); + $("#searchquery").autocompleter({ + choices: $.proxy(this.get_domain_list, this), + choice_selected: $.proxy(this.search_domain, this), + empty_choice: $.proxy(this.reset_search, this) + }); + + this.register_nav_callbacks(); + this.listen(); + }, + + listen: function() { + $(".period_selector").click($.proxy(this.change_period, this)); + $("#customsend").on("click", $.proxy(this.custom_period, this)); + $(window).resize($.proxy(this.resize_charts, this)); + }, + + register_nav_callbacks: function() { + this.navobj.register_callback("graphs", + $.proxy(this.charts_cb, this)); + }, + + /** + * Retrieve a list of domain from the server. + */ + get_domain_list: function() { + var result; + + $.ajax({ + url: this.options.domain_list_url, + dataType: "json", + async: false + }).done(function(data) { + result = data; + }); + return result; + }, + + change_period: function(e) { + e.preventDefault(); + var $link = $(e.target).children("input"); + var period = $link.attr("data-period"); + + if (period != "custom") { + $("#custom_period").addClass("hidden"); + this.navobj.delparam("start").delparam("end"); + this.navobj.setparam("period", $link.attr("data-period")).update(); + } else { + $("#custom_period").removeClass("hidden"); + } + }, + + /** + * Update all charts on resize event. + * + * @this {Stats} + */ + resize_charts: function() { + var data = this.data; + $.each(this.charts, function(id, mychart) { + mychart.update(data.graphs[id]); + }); + }, + + /** + * Create or update charts. + * + * @this {Stats} + * @param {Object} data + */ + charts_cb: function(data) { + var menuid = "menu_" + this.navobj.getparam("gset"); + var activeCharts = new Array(); + + $(".nav-sidebar > li").removeClass("active"); + $("#" + menuid).addClass("active"); + this.data = data; + if (!this.data.domain_selector) { + $("#domain-selector").hide(); + } else { + $("#domain-selector").show(); + } + $.each(data.graphs, $.proxy(function(id, graphdef) { + if (this.charts.hasOwnProperty(id)) { + this.charts[id].update(graphdef); + } else if (graphdef.curves.length) { + var mychart = ModoChart("#gset"); + this.charts[id] = mychart; + mychart(graphdef); + } + activeCharts.push(id); + }, this)); + $.each(this.charts, function (id, mychart) { + if (activeCharts.indexOf(id) === -1) { + mychart.hide(); + } + }); + }, + + search_domain: function(value) { + this.navobj.setparam("searchquery", value).update(); + }, + + reset_search: function() { + $("#searchquery").data("oldvalue", null); + this.navobj.delparam("searchquery").update(); + }, + + custom_period: function(e) { + e.preventDefault(); + var $fromdate = $("#id_from"); + var $todate = $("#id_to"); + + if ($fromdate.val() === "" || $todate.val() === "") { + return; + } + this.navobj.setparams({ + period: "custom", + start: $fromdate.val(), + end: $todate.val() + }).update(); + } +}; diff --git a/modoboa/maillog/templates/maillog/index.html b/modoboa/maillog/templates/maillog/index.html new file mode 100644 index 000000000..036ef3d32 --- /dev/null +++ b/modoboa/maillog/templates/maillog/index.html @@ -0,0 +1,91 @@ +{% extends "twocols.html" %} +{% load i18n static %} + +{% block pagetitle %}{% trans "Graphical statistics" %}{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block extra_js %} + + + + + + + +{% endblock %} + +{% block leftcol %} + +{% endblock %} + +{% block apparea %} + + + + +
+ {% for id, gset in graph_sets.items %} +
+
+
+ {% endfor %} +
+ +{% endblock %} diff --git a/modoboa/maillog/tests/__init__.py b/modoboa/maillog/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modoboa/maillog/tests/mail.log b/modoboa/maillog/tests/mail.log new file mode 100644 index 000000000..f8fec0474 --- /dev/null +++ b/modoboa/maillog/tests/mail.log @@ -0,0 +1,519 @@ +%(day)s 11:00:20 server postfix/cleanup[28945]: A0320E00C3: message-id= +%(day)s 11:00:20 server postfix/qmgr[26163]: A0320E00C3: from=, size=142646, nrcpt=1 (queue active) +%(day)s 11:00:20 server postfix/smtpd[28944]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:00:20 server amavis[14540]: (14540-09) Passed CLEAN {RelayedInbound}, [103.28.42.139]:56540 [103.28.42.139] -> , Message-ID: , Hits: -1.481, size: 142067, queued_as: A0320E00C3, dkim_sd=cm:mailing.agenda.be, 10172 ms +%(day)s 11:00:20 server postfix/smtpd[28914]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as A0320E00C3; from= to= proto=ESMTP helo= +%(day)s 11:00:20 server dovecot: lmtp(30301): Connect from local +%(day)s 11:00:20 server postfix/smtpd[28914]: disconnect from mx139.g.outbound.createsend.com[103.28.42.139] ehlo=1 mail=1 rcpt=1 data=1 quit=1 commands=5 +%(day)s 11:00:20 server dovecot: lmtp(user@test.com): z5FOLrRhDVpddgAABvoInA: sieve: msgid=: stored mail into mailbox 'notifications' +%(day)s 11:00:20 server postfix/lmtp[28946]: A0320E00C3: to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.32, delays=0.11/0.01/0.01/0.2, dsn=2.0.0, status=sent (250 2.0.0 z5FOLrRhDVpddgAABvoInA Saved) +%(day)s 11:00:20 server dovecot: lmtp(30301): Disconnect from local: Successful quit +%(day)s 11:00:20 server postfix/qmgr[26163]: A0320E00C3: removed +%(day)s 11:03:05 server postfix/postscreen[30620]: CONNECT from [207.154.235.157]:54810 to [94.23.45.181]:25 +%(day)s 11:03:11 server postfix/postscreen[30620]: PASS OLD [207.154.235.157]:54810 +%(day)s 11:03:12 server postfix/smtpd[30624]: connect from misas5.babino.pl[207.154.235.157] +%(day)s 11:03:12 server postfix/smtpd[30624]: warning: 157.235.154.207.zen.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=157.235.154.207.zen.spamhaus.org type=A: Host not found, try again +%(day)s 11:03:12 server postfix/cleanup[30637]: 2847EE008E: message-id=<20171116100312.2847EE008E@mail.koalabs.org> +%(day)s 11:03:12 server postfix/qmgr[26163]: 2847EE008E: from=, size=271, nrcpt=1 (queue active) +%(day)s 11:03:12 server policyd-spf[30640]: prepend Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=207.154.235.157; helo=misas5.babino.pl; envelope-from=zwroty@babino.pl; receiver= +%(day)s 11:03:12 server postfix/smtpd[30624]: NOQUEUE: client=misas5.babino.pl[207.154.235.157] +%(day)s 11:03:15 server postfix/smtpd[30647]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:03:15 server postfix/smtpd[30647]: 1FEB7E00C4: client=localhost.localdomain[127.0.0.1], orig_client=misas5.babino.pl[207.154.235.157] +%(day)s 11:03:15 server postfix/cleanup[30637]: 1FEB7E00C4: message-id= +%(day)s 11:03:15 server postfix/qmgr[26163]: 1FEB7E00C4: from=, size=53549, nrcpt=1 (queue active) +%(day)s 11:03:15 server postfix/smtpd[30647]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:03:15 server amavis[19749]: (19749-18) Passed SPAM {RelayedTaggedInbound}, [207.154.235.157]:54810 [207.154.235.157] -> , Message-ID: , Hits: 6.89, size: 52477, queued_as: 1FEB7E00C4, dkim_sd=1509955623.babino:babino.pl, 2365 ms +%(day)s 11:03:15 server postfix/smtpd[30624]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as 1FEB7E00C4; from= to= proto=ESMTP helo= +%(day)s 11:03:15 server postfix/smtpd[30624]: disconnect from misas5.babino.pl[207.154.235.157] ehlo=1 mail=1 rcpt=1 data=1 quit=1 commands=5 +%(day)s 11:03:18 server postfix/smtp[30639]: Anonymous TLS connection established to mail.domain.test[94.23.191.34]:25: TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits) +%(day)s 11:03:18 server postfix/smtp[30639]: 2847EE008E: to=, relay=mail.domain.test[94.23.191.34]:25, delay=6.3, delays=0.04/0.01/6/0.19, dsn=2.1.5, status=deliverable (250 2.1.5 Ok) +%(day)s 11:03:18 server postfix/qmgr[26163]: 2847EE008E: removed +%(day)s 11:03:21 server postfix/smtp[30648]: Anonymous TLS connection established to mail.domain.test[94.23.191.34]:25: TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits) +%(day)s 11:03:26 server postfix/smtp[30648]: 1FEB7E00C4: to=, relay=mail.domain.test[94.23.191.34]:25, delay=11, delays=0.05/0.01/6.1/5.2, dsn=2.0.0, status=sent (250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as 5DDDA1F30002) +%(day)s 11:03:26 server postfix/qmgr[26163]: 1FEB7E00C4: removed +%(day)s 11:04:01 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=30726, secured, session= +%(day)s 11:04:01 server dovecot: imap(anomalie@test2.com): Logged out in=86 out=892 +%(day)s 11:04:01 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=30742, secured, session=<2fBIvRZeMcd/AAAB> +%(day)s 11:04:01 server dovecot: imap(evolution@test2.com): Logged out in=86 out=891 +%(day)s 11:04:01 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=30754, secured, session= +%(day)s 11:04:01 server dovecot: imap(question@test2.com): Logged out in=86 out=891 +%(day)s 11:04:15 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=30828, secured, session= +%(day)s 11:04:18 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=30866, secured, session= +%(day)s 11:04:32 server postfix/postscreen[30620]: CONNECT from [127.0.0.1]:53903 to [127.0.0.1]:25 +%(day)s 11:04:32 server postfix/postscreen[30620]: WHITELISTED [127.0.0.1]:53903 +%(day)s 11:04:32 server postfix/smtpd[30624]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:04:32 server opendmarc[4245]: ignoring connection from localhost.localdomain +%(day)s 11:04:32 server postfix/smtpd[30624]: NOQUEUE: client=localhost.localdomain[127.0.0.1] +%(day)s 11:04:32 server postfix/smtpd[30647]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:04:32 server postfix/smtpd[30647]: 8D1B3E008E: client=localhost.localdomain[127.0.0.1], orig_client=localhost.localdomain[127.0.0.1] +%(day)s 11:04:32 server postfix/cleanup[30637]: 8D1B3E008E: message-id=<20171116100432.10564.83744@server.ngyn.org> +%(day)s 11:04:32 server postfix/smtpd[30647]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:04:32 server postfix/qmgr[26163]: 8D1B3E008E: from=, size=18506, nrcpt=1 (queue active) +%(day)s 11:04:32 server amavis[21816]: (21816-19) Passed CLEAN {RelayedInternal}, MYNETS LOCAL [127.0.0.1]:53903 -> , Message-ID: <20171116100432.10564.83744@server.ngyn.org>, Hits: -, size: 18043, queued_as: 8D1B3E008E, 102 ms +%(day)s 11:04:32 server postfix/smtpd[30624]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as 8D1B3E008E; from= to= proto=ESMTP helo= +%(day)s 11:04:32 server postfix/smtpd[30624]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 mail=1 rcpt=1 data=1 quit=1 commands=5 +%(day)s 11:04:32 server dovecot: lmtp(30933): Connect from local +%(day)s 11:04:32 server postfix/postscreen[30620]: CONNECT from [127.0.0.1]:53914 to [127.0.0.1]:25 +%(day)s 11:04:32 server postfix/postscreen[30620]: WHITELISTED [127.0.0.1]:53914 +%(day)s 11:04:32 server postfix/smtpd[30624]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:04:32 server opendmarc[4245]: ignoring connection from localhost.localdomain +%(day)s 11:04:32 server postfix/smtpd[30624]: NOQUEUE: client=localhost.localdomain[127.0.0.1] +%(day)s 11:04:32 server postfix/smtpd[30647]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:04:32 server postfix/smtpd[30647]: AA7FDE00C4: client=localhost.localdomain[127.0.0.1], orig_client=localhost.localdomain[127.0.0.1] +%(day)s 11:04:32 server postfix/cleanup[30637]: AA7FDE00C4: message-id=<20171116100432.10564.58862@server.ngyn.org> +%(day)s 11:04:32 server postfix/smtpd[30647]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:04:32 server postfix/qmgr[26163]: AA7FDE00C4: from=, size=18506, nrcpt=1 (queue active) +%(day)s 11:04:32 server amavis[5246]: (05246-16) Passed CLEAN {RelayedInternal}, MYNETS LOCAL [127.0.0.1]:53914 -> , Message-ID: <20171116100432.10564.58862@server.ngyn.org>, Hits: -, size: 18043, queued_as: AA7FDE00C4, 79 ms +%(day)s 11:04:32 server postfix/smtpd[30624]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as AA7FDE00C4; from= to= proto=ESMTP helo= +%(day)s 11:04:32 server postfix/smtpd[30624]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 mail=1 rcpt=1 data=1 quit=1 commands=5 +%(day)s 11:04:32 server dovecot: lmtp(30937): Connect from local +%(day)s 11:04:32 server dovecot: lmtp(admin@test.com): cqWOJrBiDVrVeAAABvoInA: sieve: msgid=<20171116100432.10564.83744@server.ngyn.org>: stored mail into mailbox 'Koalabs' +%(day)s 11:04:32 server dovecot: lmtp(30933): Disconnect from local: Successful quit +%(day)s 11:04:32 server postfix/lmtp[30932]: 8D1B3E008E: to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.26, delays=0.06/0.01/0.01/0.19, dsn=2.0.0, status=sent (250 2.0.0 cqWOJrBiDVrVeAAABvoInA Saved) +%(day)s 11:04:32 server postfix/qmgr[26163]: 8D1B3E008E: removed +%(day)s 11:04:32 server dovecot: lmtp(admin@test.com): ES02LbBiDVrZeAAABvoInA: sieve: msgid=<20171116100432.10564.58862@server.ngyn.org>: stored mail into mailbox 'Koalabs' +%(day)s 11:04:32 server dovecot: lmtp(30937): Disconnect from local: Successful quit +%(day)s 11:04:32 server postfix/lmtp[30936]: AA7FDE00C4: to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.22, delays=0.05/0/0.01/0.16, dsn=2.0.0, status=sent (250 2.0.0 ES02LbBiDVrZeAAABvoInA Saved) +%(day)s 11:04:32 server postfix/qmgr[26163]: AA7FDE00C4: removed +%(day)s 11:06:45 server postfix/postscreen[31171]: CONNECT from [185.5.53.87]:58092 to [94.23.45.181]:25 +%(day)s 11:06:45 server postfix/postscreen[31171]: PASS OLD [185.5.53.87]:58092 +%(day)s 11:06:45 server postfix/smtpd[31172]: connect from main.nope.org.es[185.5.53.87] +%(day)s 11:06:45 server postfix/smtpd[31172]: Anonymous TLS connection established from main.nope.org.es[185.5.53.87]: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits) +%(day)s 11:06:45 server postfix/smtpd[31172]: warning: 87.53.5.185.zen.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=87.53.5.185.zen.spamhaus.org type=A: Host not found, try again +%(day)s 11:06:46 server policyd-spf[31184]: prepend Received-SPF: None (no SPF record) identity=no SPF record; client-ip=185.5.53.87; helo=main.nope.org.es; envelope-from=<>; receiver= +%(day)s 11:06:46 server postfix/smtpd[31172]: NOQUEUE: client=main.nope.org.es[185.5.53.87] +%(day)s 11:06:51 server postfix/smtpd[31201]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:06:51 server postfix/smtpd[31201]: AA3E6E00C3: client=localhost.localdomain[127.0.0.1], orig_client=main.nope.org.es[185.5.53.87] +%(day)s 11:06:51 server postfix/cleanup[31202]: AA3E6E00C3: message-id=<201711161006.vAGA6jpH019527@main.nope.org.es> +%(day)s 11:06:51 server postfix/smtpd[31201]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:06:51 server postfix/qmgr[26163]: AA3E6E00C3: from=<>, size=3194, nrcpt=1 (queue active) +%(day)s 11:06:51 server amavis[14540]: (14540-10) Passed CLEAN {RelayedInbound}, [185.5.53.87]:58092 [185.5.53.87] <> -> , Message-ID: <201711161006.vAGA6jpH019527@main.nope.org.es>, Hits: -1.889, size: 2718, queued_as: AA3E6E00C3, 5178 ms +%(day)s 11:06:51 server postfix/smtpd[31172]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as AA3E6E00C3; from=<> to= proto=ESMTP helo= +%(day)s 11:06:51 server dovecot: lmtp(31204): Connect from local +%(day)s 11:06:51 server dovecot: lmtp(admin@test.com): wUc3LDtjDVrkeQAABvoInA: sieve: msgid=<201711161006.vAGA6jpH019527@main.nope.org.es>: not sending reject message to <> +%(day)s 11:06:51 server postfix/lmtp[31203]: AA3E6E00C3: to=, orig_to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.07, delays=0.04/0.01/0.01/0.01, dsn=2.0.0, status=sent (250 2.0.0 wUc3LDtjDVrkeQAABvoInA Saved) +%(day)s 11:06:51 server dovecot: lmtp(31204): Disconnect from local: Successful quit +%(day)s 11:06:51 server postfix/qmgr[26163]: AA3E6E00C3: removed +%(day)s 11:06:52 server policyd-spf[31184]: prepend Received-SPF: None (no SPF record) identity=no SPF record; client-ip=185.5.53.87; helo=main.nope.org.es; envelope-from=<>; receiver= +%(day)s 11:06:52 server postfix/smtpd[31172]: NOQUEUE: client=main.nope.org.es[185.5.53.87] +%(day)s 11:06:54 server postfix/smtpd[31201]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:06:54 server postfix/smtpd[31201]: 94D70E00C3: client=localhost.localdomain[127.0.0.1], orig_client=main.nope.org.es[185.5.53.87] +%(day)s 11:06:54 server postfix/cleanup[31202]: 94D70E00C3: message-id=<201711161006.vAGA6jpI019527@main.nope.org.es> +%(day)s 11:06:54 server postfix/smtpd[31201]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:06:54 server postfix/qmgr[26163]: 94D70E00C3: from=<>, size=2988, nrcpt=1 (queue active) +%(day)s 11:06:54 server dovecot: lmtp(31204): Connect from local +%(day)s 11:06:54 server dovecot: lmtp(admin@test.com): AQQ5Jz5jDVrkeQAABvoInA: sieve: msgid=<201711161006.vAGA6jpI019527@main.nope.org.es>: not sending reject message to <> +%(day)s 11:06:54 server postfix/lmtp[31203]: 94D70E00C3: to=, orig_to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.05, delays=0.05/0/0/0, dsn=2.0.0, status=sent (250 2.0.0 AQQ5Jz5jDVrkeQAABvoInA Saved) +%(day)s 11:06:54 server dovecot: lmtp(31204): Disconnect from local: Successful quit +%(day)s 11:06:54 server postfix/qmgr[26163]: 94D70E00C3: removed +%(day)s 11:06:54 server amavis[19749]: (19749-19) Passed CLEAN {RelayedInbound}, [185.5.53.87]:58092 [185.5.53.87] <> -> , Message-ID: <201711161006.vAGA6jpI019527@main.nope.org.es>, Hits: -1.889, size: 2512, queued_as: 94D70E00C3, 2262 ms +%(day)s 11:06:54 server postfix/smtpd[31172]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as 94D70E00C3; from=<> to= proto=ESMTP helo= +%(day)s 11:06:54 server postfix/smtpd[31172]: disconnect from main.nope.org.es[185.5.53.87] ehlo=2 starttls=1 mail=2 rcpt=2 data=2 rset=1 quit=1 commands=11 +%(day)s 11:09:09 server postfix/anvil[28916]: statistics: max connection rate 1/60s for (smtpd:192.254.112.99) at %(day)s 10:59:09 +%(day)s 11:09:09 server postfix/anvil[28916]: statistics: max connection count 1 for (smtpd:192.254.112.99) at %(day)s 10:59:09 +%(day)s 11:09:09 server postfix/anvil[28916]: statistics: max cache size 3 at %(day)s 11:00:08 +%(day)s 11:10:02 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=31600, secured, session= +%(day)s 11:10:02 server dovecot: imap(anomalie@test2.com): Logged out in=86 out=892 +%(day)s 11:10:02 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=31618, secured, session= +%(day)s 11:10:02 server dovecot: imap(evolution@test2.com): Logged out in=86 out=891 +%(day)s 11:10:02 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=31630, secured, session= +%(day)s 11:10:02 server dovecot: imap(question@test2.com): Logged out in=86 out=891 +%(day)s 11:14:42 server postfix/postscreen[32096]: CONNECT from [51.15.3.241]:35169 to [94.23.45.181]:25 +%(day)s 11:14:42 server postfix/postscreen[32096]: PASS OLD [51.15.3.241]:35169 +%(day)s 11:14:42 server postfix/smtpd[32097]: warning: hostname 51-15-3-241.rev.poneytelecom.eu does not resolve to address 51.15.3.241: Name or service not known +%(day)s 11:14:42 server postfix/smtpd[32097]: connect from unknown[51.15.3.241] +%(day)s 11:14:42 server postfix/smtpd[32097]: Anonymous TLS connection established from unknown[51.15.3.241]: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits) +%(day)s 11:14:42 server postfix/smtpd[32097]: warning: 241.3.15.51.zen.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=241.3.15.51.zen.spamhaus.org type=A: Host not found, try again +%(day)s 11:14:43 server policyd-spf[32112]: prepend Received-SPF: None (mailfrom) identity=mailfrom; client-ip=51.15.3.241; helo=intra.logikap.com; envelope-from=gitlab@intra.logikap.com; receiver= +%(day)s 11:14:43 server postfix/smtpd[32097]: NOQUEUE: client=unknown[51.15.3.241] +%(day)s 11:14:45 server postfix/smtpd[32119]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:14:45 server postfix/smtpd[32119]: 339B2E00C3: client=localhost.localdomain[127.0.0.1], orig_client=unknown[51.15.3.241] +%(day)s 11:14:45 server postfix/cleanup[32120]: 339B2E00C3: message-id=<31819cf6c71e45f4569b94888ea90ead@gitlab.intra.logikap.com> +%(day)s 11:14:45 server postfix/smtpd[32119]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:14:45 server postfix/qmgr[26163]: 339B2E00C3: from=, size=3772, nrcpt=1 (queue active) +%(day)s 11:14:45 server amavis[14540]: (14540-11) Passed CLEAN {RelayedInbound}, [51.15.3.241]:35169 [51.15.3.241] -> , Message-ID: <31819cf6c71e45f4569b94888ea90ead@gitlab.intra.logikap.com>, Hits: -1.105, size: 3307, queued_as: 339B2E00C3, 1891 ms +%(day)s 11:14:45 server postfix/smtpd[32097]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as 339B2E00C3; from= to= proto=ESMTP helo= +%(day)s 11:14:45 server postfix/smtpd[32097]: disconnect from unknown[51.15.3.241] ehlo=2 starttls=1 mail=1 rcpt=1 data=1 quit=1 commands=7 +%(day)s 11:14:45 server dovecot: lmtp(32122): Connect from local +%(day)s 11:14:45 server dovecot: lmtp(admin@test.com): +NuIEBVlDVp6fQAABvoInA: sieve: msgid=<31819cf6c71e45f4569b94888ea90ead@gitlab.intra.logikap.com>: stored mail into mailbox 'INBOX' +%(day)s 11:14:45 server postfix/lmtp[32121]: 339B2E00C3: to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.23, delays=0.06/0.01/0.01/0.16, dsn=2.0.0, status=sent (250 2.0.0 +NuIEBVlDVp6fQAABvoInA Saved) +%(day)s 11:14:45 server dovecot: lmtp(32122): Disconnect from local: Successful quit +%(day)s 11:14:45 server postfix/qmgr[26163]: 339B2E00C3: removed +%(day)s 11:16:01 server dovecot: imap(admin@test.com): Logged out in=3056 out=66597 +%(day)s 11:16:01 server dovecot: managesieve-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=32316, secured, session= +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: Loading modules from directory: /usr/lib/dovecot/modules +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: Module loaded: /usr/lib/dovecot/modules/lib10_quota_plugin.so +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: Added userdb setting: plugin/quota_rule=*:bytes=2048M +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: Effective uid=1010, gid=1010, home=/srv/vmail/ngyn.org/tonio +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: Quota root: name=User quota backend=dict args=:proxy::quota +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: Quota rule: root=User quota mailbox=* bytes=2147483648 messages=0 +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: Quota grace: root=User quota bytes=214748364 (10%%) +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: dict quota: user=admin@test.com, uri=proxy::quota, noenforcing=0 +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: Namespace inbox: type=private, prefix=, sep=, inbox=yes, hidden=no, list=yes, subscriptions=yes location=maildir:~/.maildir +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: maildir++: root=/srv/vmail/ngyn.org/tonio/.maildir, index=, indexpvt=, control=, inbox=/srv/vmail/ngyn.org/tonio/.maildir, alt= +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: quota: quota_over_flag check: STORAGE ret=1 value=989542 limit=2097152 +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: quota: quota_over_flag check: MESSAGE ret=0 value=47722 limit=0 +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: quota: quota_over_flag=0((null)) vs currently overquota=0 +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: sieve: Pigeonhole version 0.4.16 (fed8554) initializing +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: sieve: file storage: Using active Sieve script path: /srv/vmail/ngyn.org/tonio/.dovecot.sieve +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: sieve: file storage: Using script storage path: /srv/vmail/ngyn.org/tonio/sieve +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: sieve: file storage: Using permissions from /srv/vmail/ngyn.org/tonio/sieve: mode=0750 gid=-1 +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: sieve: file storage: Relative path to sieve storage in active link: sieve/ +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Debug: sieve: file storage: sync: Synchronization active +%(day)s 11:16:01 server dovecot: managesieve(admin@test.com): Disconnected: Logged out bytes=6/41 +%(day)s 11:16:01 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=32327, secured, session= +%(day)s 11:16:01 server dovecot: imap(anomalie@test2.com): Logged out in=86 out=892 +%(day)s 11:16:01 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=32345, secured, session=<2d046BZe1sh/AAAB> +%(day)s 11:16:01 server dovecot: imap(evolution@test2.com): Logged out in=86 out=891 +%(day)s 11:16:02 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=32359, secured, session= +%(day)s 11:16:02 server dovecot: imap(question@test2.com): Logged out in=86 out=891 +%(day)s 11:16:03 server postfix/postscreen[32096]: CONNECT from [81.92.113.114]:60750 to [94.23.45.181]:25 +%(day)s 11:16:03 server postfix/postscreen[32096]: PASS OLD [81.92.113.114]:60750 +%(day)s 11:16:03 server postfix/smtpd[32097]: connect from mta1.news.tarsus.fr[81.92.113.114] +%(day)s 11:16:03 server postfix/smtpd[32097]: warning: 114.113.92.81.zen.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=114.113.92.81.zen.spamhaus.org type=A: Host not found, try again +%(day)s 11:16:03 server policyd-spf[32112]: prepend Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=81.92.113.114; helo=mta1.news.tarsus.fr; envelope-from=marketing@info.opensourcesummit.paris; receiver= +%(day)s 11:16:03 server postfix/smtpd[32097]: NOQUEUE: client=mta1.news.tarsus.fr[81.92.113.114] +%(day)s 11:16:04 server postfix/smtpd[32119]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:16:04 server postfix/smtpd[32119]: F39BCE00C3: client=localhost.localdomain[127.0.0.1], orig_client=mta1.news.tarsus.fr[81.92.113.114] +%(day)s 11:16:04 server postfix/cleanup[32120]: F39BCE00C3: message-id=<2.1.4.1101006021.4688801.152618499@info.opensourcesummit.paris> +%(day)s 11:16:05 server postfix/smtpd[32119]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:16:05 server postfix/qmgr[26163]: F39BCE00C3: from=, size=55627, nrcpt=1 (queue active) +%(day)s 11:16:05 server amavis[19749]: (19749-20) Passed CLEAN {RelayedInbound}, [81.92.113.114]:60750 [81.92.113.114] -> , Message-ID: <2.1.4.1101006021.4688801.152618499@info.opensourcesummit.paris>, Hits: 3.698, size: 54587, queued_as: F39BCE00C3, dkim_sd=emv:info.opensourcesummit.paris, 1310 ms +%(day)s 11:16:05 server postfix/smtpd[32097]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as F39BCE00C3; from= to= proto=ESMTP helo= +%(day)s 11:16:05 server dovecot: lmtp(32392): Connect from local +%(day)s 11:16:05 server postfix/smtpd[32097]: disconnect from mta1.news.tarsus.fr[81.92.113.114] ehlo=1 mail=1 rcpt=1 data=1 quit=1 commands=5 +%(day)s 11:16:05 server dovecot: lmtp(user@test.com): d8zmAmVlDVqIfgAABvoInA: sieve: msgid=<2.1.4.1101006021.4688801.152618499@info.opensourcesummit.paris>: stored mail into mailbox 'INBOX' +%(day)s 11:16:05 server postfix/lmtp[32121]: F39BCE00C3: to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.23, delays=0.04/0.01/0.01/0.17, dsn=2.0.0, status=sent (250 2.0.0 d8zmAmVlDVqIfgAABvoInA Saved) +%(day)s 11:16:05 server dovecot: lmtp(32392): Disconnect from local: Successful quit +%(day)s 11:16:05 server postfix/qmgr[26163]: F39BCE00C3: removed +%(day)s 11:16:45 server dovecot: pop3-login: Login: user=, method=PLAIN, rip=209.85.216.158, lip=94.23.45.181, mpid=32406, TLS, session= +%(day)s 11:16:45 server dovecot: pop3(imhotep@koalabs.org): Disconnected: Logged out top=0/0, retr=0/0, del=0/141, size=8023766 +%(day)s 11:17:01 server postfix/postscreen[32096]: CONNECT from [13.111.20.45]:47742 to [94.23.45.181]:25 +%(day)s 11:17:01 server postfix/postscreen[32096]: WHITELISTED [13.111.20.45]:47742 +%(day)s 11:17:01 server postfix/smtpd[32097]: connect from mta4.email.tfl.gov.uk[13.111.20.45] +%(day)s 11:17:02 server postfix/smtpd[32097]: Anonymous TLS connection established from mta4.email.tfl.gov.uk[13.111.20.45]: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits) +%(day)s 11:17:02 server postfix/smtpd[32097]: warning: 45.20.111.13.zen.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=45.20.111.13.zen.spamhaus.org type=A: Host not found, try again +%(day)s 11:17:03 server policyd-spf[32112]: prepend Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=13.111.20.45; helo=mta4.email.tfl.gov.uk; envelope-from=bounce-150_html-21457874-18921-100001609-123@bounce.email.tfl.gov.uk; receiver= +%(day)s 11:17:03 server postfix/smtpd[32097]: NOQUEUE: client=mta4.email.tfl.gov.uk[13.111.20.45] +%(day)s 11:17:06 server postfix/smtpd[32119]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:17:06 server postfix/smtpd[32119]: 9E1C3E00C3: client=localhost.localdomain[127.0.0.1], orig_client=mta4.email.tfl.gov.uk[13.111.20.45] +%(day)s 11:17:06 server postfix/cleanup[32120]: 9E1C3E00C3: message-id=<42e8ad52-02fb-45f5-a7fe-763c52409f6c@dfw1s10mta44.xt.local> +%(day)s 11:17:08 server postfix/smtpd[32119]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:17:08 server postfix/qmgr[26163]: 9E1C3E00C3: from=, size=71561, nrcpt=1 (queue active) +%(day)s 11:17:08 server amavis[5246]: (05246-17) Passed CLEAN {RelayedInbound}, [13.111.20.45]:47742 [13.111.20.45] -> , Message-ID: <42e8ad52-02fb-45f5-a7fe-763c52409f6c@dfw1s10mta44.xt.local>, Hits: 2.492, size: 70304, queued_as: 9E1C3E00C3, dkim_sd=10dkim1:email.tfl.gov.uk, 3667 ms +%(day)s 11:17:08 server postfix/smtpd[32097]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as 9E1C3E00C3; from= to= proto=ESMTP helo= +%(day)s 11:17:08 server dovecot: lmtp(32482): Connect from local +%(day)s 11:17:08 server dovecot: lmtp(user@test.com): +IarBqRlDVrifgAABvoInA: sieve: msgid=<42e8ad52-02fb-45f5-a7fe-763c52409f6c@dfw1s10mta44.xt.local>: stored mail into mailbox 'INBOX' +%(day)s 11:17:08 server postfix/lmtp[32121]: 9E1C3E00C3: to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=1.6, delays=1.5/0.01/0.01/0.17, dsn=2.0.0, status=sent (250 2.0.0 +IarBqRlDVrifgAABvoInA Saved) +%(day)s 11:17:08 server dovecot: lmtp(32482): Disconnect from local: Successful quit +%(day)s 11:17:08 server postfix/qmgr[26163]: 9E1C3E00C3: removed +%(day)s 11:17:08 server postfix/smtpd[32097]: disconnect from mta4.email.tfl.gov.uk[13.111.20.45] ehlo=2 starttls=1 mail=1 rcpt=1 data=1 quit=1 commands=7 +%(day)s 11:17:27 server postfix/postscreen[32096]: CONNECT from [51.254.143.19]:34827 to [94.23.45.181]:25 +%(day)s 11:17:33 server postfix/postscreen[32096]: NOQUEUE: reject: RCPT from [51.254.143.19]:34827: 450 4.3.2 Service currently unavailable; from=, to=, proto=ESMTP, helo= +%(day)s 11:17:33 server postfix/postscreen[32096]: PASS NEW [51.254.143.19]:34827 +%(day)s 11:17:33 server postfix/postscreen[32096]: DISCONNECT [51.254.143.19]:34827 +%(day)s 11:17:43 server postfix/postscreen[32096]: CONNECT from [172.110.9.131]:37348 to [94.23.45.181]:25 +%(day)s 11:17:43 server postfix/postscreen[32096]: PASS OLD [172.110.9.131]:37348 +%(day)s 11:17:43 server postfix/smtpd[32097]: connect from unknown[172.110.9.131] +%(day)s 11:17:43 server postfix/smtpd[32097]: Anonymous TLS connection established from unknown[172.110.9.131]: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits) +%(day)s 11:17:43 server postfix/smtpd[32097]: warning: 131.9.110.172.zen.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=131.9.110.172.zen.spamhaus.org type=A: Host not found, try again +%(day)s 11:17:44 server policyd-spf[32112]: prepend Received-SPF: None (no SPF record) identity=no SPF record; client-ip=172.110.9.131; helo=mailstore.paxi.cf; envelope-from=<>; receiver= +%(day)s 11:17:44 server postfix/smtpd[32097]: NOQUEUE: client=unknown[172.110.9.131] +%(day)s 11:17:45 server postfix/smtpd[32119]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:17:45 server postfix/smtpd[32119]: 34B20E00C3: client=localhost.localdomain[127.0.0.1], orig_client=unknown[172.110.9.131] +%(day)s 11:17:45 server postfix/cleanup[32120]: 34B20E00C3: message-id=<20171116101742.C6A924DCECC6@mailstore.paxi.cf> +%(day)s 11:17:45 server postfix/smtpd[32119]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:17:45 server postfix/qmgr[26163]: 34B20E00C3: from=<>, size=4102, nrcpt=1 (queue active) +%(day)s 11:17:45 server amavis[21816]: (21816-20) Passed CLEAN {RelayedInbound}, [172.110.9.131]:37348 [172.110.9.131] <> -> , Message-ID: <20171116101742.C6A924DCECC6@mailstore.paxi.cf>, Hits: -1.106, size: 3626, queued_as: 34B20E00C3, 1017 ms +%(day)s 11:17:45 server dovecot: lmtp(32482): Connect from local +%(day)s 11:17:45 server postfix/smtpd[32097]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as 34B20E00C3; from=<> to= proto=ESMTP helo= +%(day)s 11:17:45 server dovecot: lmtp(admin@test.com): WQg7D8llDVrifgAABvoInA: sieve: msgid=<20171116101742.C6A924DCECC6@mailstore.paxi.cf>: not sending reject message to <> +%(day)s 11:17:45 server postfix/lmtp[32121]: 34B20E00C3: to=, orig_to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.05, delays=0.04/0.01/0/0, dsn=2.0.0, status=sent (250 2.0.0 WQg7D8llDVrifgAABvoInA Saved) +%(day)s 11:17:45 server dovecot: lmtp(32482): Disconnect from local: Successful quit +%(day)s 11:17:45 server postfix/qmgr[26163]: 34B20E00C3: removed +%(day)s 11:17:45 server postfix/postscreen[32096]: CONNECT from [108.174.3.154]:54018 to [94.23.45.181]:25 +%(day)s 11:17:45 server postfix/postscreen[32096]: WHITELISTED [108.174.3.154]:54018 +%(day)s 11:17:45 server postfix/smtpd[32097]: disconnect from unknown[172.110.9.131] ehlo=2 starttls=1 mail=1 rcpt=1 data=1 quit=1 commands=7 +%(day)s 11:17:45 server postfix/smtpd[32502]: connect from mailb-bb.linkedin.com[108.174.3.154] +%(day)s 11:17:45 server postfix/smtpd[32502]: Anonymous TLS connection established from mailb-bb.linkedin.com[108.174.3.154]: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits) +%(day)s 11:17:46 server postfix/smtpd[32502]: warning: 154.3.174.108.zen.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=154.3.174.108.zen.spamhaus.org type=A: Host not found, try again +%(day)s 11:17:46 server policyd-spf[32505]: prepend Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=108.174.3.154; helo=mailb-bb.linkedin.com; envelope-from=m-i58nvfj318qraba58ra9m2nttnwbk7s0evxwduql0g5d0pa6k36@bounce.linkedin.com; receiver= +%(day)s 11:17:46 server postfix/smtpd[32502]: NOQUEUE: client=mailb-bb.linkedin.com[108.174.3.154] +%(day)s 11:17:47 server postfix/postscreen[32096]: CONNECT from [81.92.113.115]:43100 to [94.23.45.181]:25 +%(day)s 11:17:47 server postfix/postscreen[32096]: PASS OLD [81.92.113.115]:43100 +%(day)s 11:17:47 server postfix/smtpd[32097]: connect from mta2.news.tarsus.fr[81.92.113.115] +%(day)s 11:17:47 server postfix/smtpd[32097]: warning: 115.113.92.81.zen.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=115.113.92.81.zen.spamhaus.org type=A: Host not found, try again +%(day)s 11:17:48 server policyd-spf[32112]: prepend Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=81.92.113.115; helo=mta2.news.tarsus.fr; envelope-from=marketing@info.opensourcesummit.paris; receiver= +%(day)s 11:17:48 server postfix/smtpd[32097]: NOQUEUE: client=mta2.news.tarsus.fr[81.92.113.115] +%(day)s 11:17:52 server postfix/smtpd[32119]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:17:52 server postfix/smtpd[32119]: 69E9BE00C4: client=localhost.localdomain[127.0.0.1], orig_client=mailb-bb.linkedin.com[108.174.3.154] +%(day)s 11:17:52 server postfix/cleanup[32120]: 69E9BE00C4: message-id=<205549081.609208.1510827464487.JavaMail.app@lor1-app2315.prod.linkedin.com> +%(day)s 11:17:52 server postfix/smtpd[32119]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:17:52 server postfix/qmgr[26163]: 69E9BE00C4: from=, size=35732, nrcpt=1 (queue active) +%(day)s 11:17:52 server amavis[14540]: (14540-12) Passed CLEAN {RelayedInbound}, [108.174.3.154]:54018 [108.174.3.154] -> , Message-ID: <205549081.609208.1510827464487.JavaMail.app@lor1-app2315.prod.linkedin.com>, Hits: -7.019, size: 35066, queued_as: 69E9BE00C4, dkim_sd=proddkim1024:linkedin.com,proddkim1024:mailb.linkedin.com, 5477 ms +%(day)s 11:17:52 server dovecot: lmtp(32482): Connect from local +%(day)s 11:17:52 server postfix/smtpd[32502]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as 69E9BE00C4; from= to= proto=ESMTP helo= +%(day)s 11:17:52 server dovecot: lmtp(user@test.com): UdRqG9BlDVrifgAABvoInA: sieve: msgid=<205549081.609208.1510827464487.JavaMail.app@lor1-app2315.prod.linkedin.com>: stored mail into mailbox 'INBOX' +%(day)s 11:17:52 server postfix/lmtp[32121]: 69E9BE00C4: to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.16, delays=0.03/0.01/0/0.13, dsn=2.0.0, status=sent (250 2.0.0 UdRqG9BlDVrifgAABvoInA Saved) +%(day)s 11:17:52 server dovecot: lmtp(32482): Disconnect from local: Successful quit +%(day)s 11:17:52 server postfix/qmgr[26163]: 69E9BE00C4: removed +%(day)s 11:17:54 server postfix/smtpd[32119]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:17:54 server postfix/smtpd[32119]: 0EC0CE00C4: client=localhost.localdomain[127.0.0.1], orig_client=mta2.news.tarsus.fr[81.92.113.115] +%(day)s 11:17:54 server postfix/cleanup[32120]: 0EC0CE00C4: message-id=<2.1.4.1101006021.4688801.3909372752@info.opensourcesummit.paris> +%(day)s 11:17:54 server postfix/qmgr[26163]: 0EC0CE00C4: from=, size=55600, nrcpt=1 (queue active) +%(day)s 11:17:54 server amavis[32393]: (32393-01) Passed CLEAN {RelayedInbound}, [81.92.113.115]:43100 [81.92.113.115] -> , Message-ID: <2.1.4.1101006021.4688801.3909372752@info.opensourcesummit.paris>, Hits: 3.507, size: 54590, queued_as: 0EC0CE00C4, dkim_sd=emv:info.opensourcesummit.paris, 5899 ms +%(day)s 11:17:54 server dovecot: lmtp(32482): Connect from local +%(day)s 11:17:54 server postfix/smtpd[32097]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as 0EC0CE00C4; from= to= proto=ESMTP helo= +%(day)s 11:17:54 server postfix/smtpd[32097]: disconnect from mta2.news.tarsus.fr[81.92.113.115] ehlo=1 mail=1 rcpt=1 data=1 quit=1 commands=5 +%(day)s 11:17:54 server dovecot: lmtp(admin@test.com): kRSZCNJlDVrifgAABvoInA: sieve: msgid=<2.1.4.1101006021.4688801.3909372752@info.opensourcesummit.paris>: stored mail into mailbox 'INBOX' +%(day)s 11:17:54 server postfix/lmtp[32121]: 0EC0CE00C4: to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.26, delays=0.09/0.01/0/0.17, dsn=2.0.0, status=sent (250 2.0.0 kRSZCNJlDVrifgAABvoInA Saved) +%(day)s 11:17:54 server dovecot: lmtp(32482): Disconnect from local: Successful quit +%(day)s 11:17:54 server postfix/qmgr[26163]: 0EC0CE00C4: removed +%(day)s 11:17:57 server postfix/smtpd[32502]: disconnect from mailb-bb.linkedin.com[108.174.3.154] ehlo=2 starttls=1 mail=1 rcpt=1 data=1 quit=1 commands=7 +%(day)s 11:18:26 server postfix/postscreen[32096]: CONNECT from [209.85.223.201]:46671 to [94.23.45.181]:25 +%(day)s 11:18:26 server postfix/postscreen[32096]: WHITELISTED [209.85.223.201]:46671 +%(day)s 11:18:26 server postfix/smtpd[32097]: connect from mail-io0-f201.google.com[209.85.223.201] +%(day)s 11:18:26 server postfix/smtpd[32097]: Anonymous TLS connection established from mail-io0-f201.google.com[209.85.223.201]: TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits) +%(day)s 11:18:26 server postfix/smtpd[32097]: warning: 201.223.85.209.zen.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=201.223.85.209.zen.spamhaus.org type=A: Host not found, try again +%(day)s 11:18:26 server postfix/cleanup[32120]: E8BF5E00C4: message-id=<20171116101826.E8BF5E00C4@mail.koalabs.org> +%(day)s 11:18:26 server postfix/qmgr[26163]: E8BF5E00C4: from=, size=271, nrcpt=1 (queue active) +%(day)s 11:18:27 server postfix/pipe[32592]: E8BF5E00C4: to=, relay=dmarc-rua, delay=0.04, delays=0.03/0.01/0/0.01, dsn=2.0.0, status=deliverable (delivers to command: /srv/modoboa/env/bin/python) +%(day)s 11:18:27 server postfix/qmgr[26163]: E8BF5E00C4: removed +%(day)s 11:18:27 server policyd-spf[32112]: prepend Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.223.201; helo=mail-io0-f201.google.com; envelope-from=noreply-dmarc-support@google.com; receiver= +%(day)s 11:18:27 server postfix/smtpd[32097]: NOQUEUE: client=mail-io0-f201.google.com[209.85.223.201] +%(day)s 11:18:28 server postfix/smtpd[32601]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:18:28 server postfix/smtpd[32601]: 5E852E00C4: client=localhost.localdomain[127.0.0.1], orig_client=mail-io0-f201.google.com[209.85.223.201] +%(day)s 11:18:28 server postfix/cleanup[32120]: 5E852E00C4: message-id=<5520130506303437380@google.com> +%(day)s 11:18:28 server postfix/smtpd[32601]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:18:28 server postfix/qmgr[26163]: 5E852E00C4: from=, size=4390, nrcpt=1 (queue active) +%(day)s 11:18:28 server amavis[5246]: (05246-18) Passed CLEAN {RelayedInbound}, [209.85.223.201]:46671 [209.85.223.201] -> , Message-ID: <5520130506303437380@google.com>, Hits: 0.715, size: 3807, queued_as: 5E852E00C4, dkim_sd=20161025:google.com, 907 ms +%(day)s 11:18:28 server postfix/smtpd[32097]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as 5E852E00C4; from= to= proto=ESMTP helo= +%(day)s 11:18:28 server postfix/smtpd[32097]: disconnect from mail-io0-f201.google.com[209.85.223.201] ehlo=2 starttls=1 mail=1 rcpt=1 data=1 quit=1 commands=7 +%(day)s 11:18:29 server postfix/pipe[32592]: 5E852E00C4: to=, relay=dmarc-rua, delay=0.85, delays=0.04/0/0/0.81, dsn=2.0.0, status=sent (delivered via dmarc-rua service (Importing report 5520130506303437380 received from google.com)) +%(day)s 11:18:29 server postfix/qmgr[26163]: 5E852E00C4: removed +%(day)s 11:18:30 server postfix/postscreen[32096]: CONNECT from [193.105.248.56]:62968 to [94.23.45.181]:25 +%(day)s 11:18:36 server postfix/tlsproxy[32611]: CONNECT from [193.105.248.56]:62968 +%(day)s 11:18:38 server postfix/tlsproxy[32611]: Anonymous TLS connection established from [193.105.248.56]:62968: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits) +%(day)s 11:18:38 server postfix/postscreen[32096]: NOQUEUE: reject: RCPT from [193.105.248.56]:62968: 450 4.3.2 Service currently unavailable; from=, to=, proto=ESMTP, helo= +%(day)s 11:18:38 server postfix/postscreen[32096]: PASS NEW [193.105.248.56]:62968 +%(day)s 11:18:38 server postfix/postscreen[32096]: DISCONNECT [193.105.248.56]:62968 +%(day)s 11:18:38 server postfix/tlsproxy[32611]: DISCONNECT [193.105.248.56]:62968 +%(day)s 11:19:59 server postfix/postscreen[32096]: CONNECT from [192.254.112.98]:6107 to [94.23.45.181]:25 +%(day)s 11:19:59 server postfix/postscreen[32096]: WHITELISTED [192.254.112.98]:6107 +%(day)s 11:19:59 server postfix/smtpd[32097]: connect from o3.sgmail.github.com[192.254.112.98] +%(day)s 11:19:59 server postfix/smtpd[32097]: Anonymous TLS connection established from o3.sgmail.github.com[192.254.112.98]: TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits) +%(day)s 11:20:00 server postfix/smtpd[32097]: warning: 98.112.254.192.zen.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=98.112.254.192.zen.spamhaus.org type=A: Host not found, try again +%(day)s 11:20:00 server policyd-spf[32112]: prepend Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=192.254.112.98; helo=o3.sgmail.github.com; envelope-from=bounces+848413-16c5-tonio=ngyn.org@sgmail.github.com; receiver= +%(day)s 11:20:00 server postfix/smtpd[32097]: NOQUEUE: client=o3.sgmail.github.com[192.254.112.98] +%(day)s 11:20:07 server postfix/smtpd[32601]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:20:07 server postfix/smtpd[32601]: D9EA5E00C3: client=localhost.localdomain[127.0.0.1], orig_client=o3.sgmail.github.com[192.254.112.98] +%(day)s 11:20:07 server postfix/cleanup[32120]: D9EA5E00C3: message-id= +%(day)s 11:20:07 server postfix/smtpd[32601]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:20:07 server postfix/qmgr[26163]: D9EA5E00C3: from=, size=12574, nrcpt=1 (queue active) +%(day)s 11:20:07 server amavis[14540]: (14540-13) Passed CLEAN {RelayedInbound}, [192.254.112.98]:6107 [192.30.253.17] -> , Message-ID: , Hits: -2.029, size: 11999, queued_as: D9EA5E00C3, dkim_sd=s20150108:github.com, 6918 ms +%(day)s 11:20:07 server postfix/smtpd[32097]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as D9EA5E00C3; from= to= proto=ESMTP helo= +%(day)s 11:20:07 server dovecot: lmtp(323): Connect from local +%(day)s 11:20:08 server dovecot: lmtp(admin@test.com): ZLuaN1dmDVpDAQAABvoInA: sieve: msgid=: stored mail into mailbox 'Listes.Tandoori' +%(day)s 11:20:08 server postfix/lmtp[322]: D9EA5E00C3: to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.22, delays=0.03/0.01/0.01/0.17, dsn=2.0.0, status=sent (250 2.0.0 ZLuaN1dmDVpDAQAABvoInA Saved) +%(day)s 11:20:08 server dovecot: lmtp(323): Disconnect from local: Successful quit +%(day)s 11:20:08 server postfix/qmgr[26163]: D9EA5E00C3: removed +%(day)s 11:20:09 server postfix/smtpd[32097]: disconnect from o3.sgmail.github.com[192.254.112.98] ehlo=2 starttls=1 mail=1 rcpt=1 data=1 quit=1 commands=7 +%(day)s 11:20:50 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=336, secured, session= +%(day)s 11:20:50 server dovecot: imap(admin@test.com): Logged out in=12 out=431 +%(day)s 11:20:50 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=350, secured, session= +%(day)s 11:20:50 server dovecot: imap(admin@test.com): Logged out in=12 out=431 +%(day)s 11:21:48 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=436, secured, session= +%(day)s 11:21:48 server dovecot: imap(mowee@modoboa.com): Logged out in=12 out=431 +%(day)s 11:22:01 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=514, secured, session= +%(day)s 11:22:01 server dovecot: imap(anomalie@test2.com): Logged out in=86 out=892 +%(day)s 11:22:01 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=526, secured, session= +%(day)s 11:22:02 server dovecot: imap(evolution@test2.com): Logged out in=86 out=891 +%(day)s 11:22:02 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=538, secured, session= +%(day)s 11:22:02 server dovecot: imap(question@test2.com): Logged out in=86 out=891 +%(day)s 11:22:54 server postfix/smtpd[32119]: timeout after END-OF-MESSAGE from localhost.localdomain[127.0.0.1] +%(day)s 11:22:54 server postfix/smtpd[32119]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 commands=5 +%(day)s 11:23:29 server postfix/anvil[32099]: statistics: max connection rate 1/60s for (smtpd:51.15.3.241) at %(day)s 11:14:42 +%(day)s 11:23:29 server postfix/anvil[32099]: statistics: max connection count 1 for (smtpd:51.15.3.241) at %(day)s 11:14:42 +%(day)s 11:23:29 server postfix/anvil[32099]: statistics: max cache size 4 at %(day)s 11:17:47 +%(day)s 11:26:26 server postfix/postscreen[916]: CONNECT from [167.89.55.170]:3520 to [94.23.45.181]:25 +%(day)s 11:26:26 server postfix/postscreen[916]: WHITELISTED [167.89.55.170]:3520 +%(day)s 11:26:26 server postfix/smtpd[917]: connect from o9.sendgrid.meetup.com[167.89.55.170] +%(day)s 11:26:27 server postfix/smtpd[917]: Anonymous TLS connection established from o9.sendgrid.meetup.com[167.89.55.170]: TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits) +%(day)s 11:26:28 server postfix/smtpd[917]: warning: 170.55.89.167.zen.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=170.55.89.167.zen.spamhaus.org type=A: Host not found, try again +%(day)s 11:26:28 server policyd-spf[932]: prepend Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=167.89.55.170; helo=o9.sendgrid.meetup.com; envelope-from=bounces+3922699-145a-tonio=ngyn.org@sendgrid.meetup.com; receiver= +%(day)s 11:26:28 server postfix/smtpd[917]: NOQUEUE: client=o9.sendgrid.meetup.com[167.89.55.170] +%(day)s 11:26:36 server postfix/smtpd[944]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:26:36 server postfix/smtpd[944]: 61F9DE00C3: client=localhost.localdomain[127.0.0.1], orig_client=o9.sendgrid.meetup.com[167.89.55.170] +%(day)s 11:26:36 server postfix/cleanup[945]: 61F9DE00C3: message-id=<710800146.1510827986412.JavaMail.root@1c1b55d0dbe0> +%(day)s 11:26:36 server postfix/smtpd[944]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:26:36 server postfix/qmgr[26163]: 61F9DE00C3: from=, size=72067, nrcpt=1 (queue active) +%(day)s 11:26:36 server amavis[32393]: (32393-02) Passed CLEAN {RelayedInbound}, [167.89.55.170]:3520 [34.196.236.240] -> , Message-ID: <710800146.1510827986412.JavaMail.root@1c1b55d0dbe0>, Hits: -2.221, size: 71500, queued_as: 61F9DE00C3, dkim_sd=s1:meetup.com, 7334 ms +%(day)s 11:26:36 server postfix/smtpd[917]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as 61F9DE00C3; from= to= proto=ESMTP helo= +%(day)s 11:26:36 server dovecot: lmtp(947): Connect from local +%(day)s 11:26:36 server dovecot: lmtp(admin@test.com): f7GkHNxnDVqzAwAABvoInA: sieve: msgid=<710800146.1510827986412.JavaMail.root@1c1b55d0dbe0>: stored mail into mailbox 'INBOX' +%(day)s 11:26:36 server postfix/lmtp[946]: 61F9DE00C3: to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.28, delays=0.07/0.01/0.01/0.19, dsn=2.0.0, status=sent (250 2.0.0 f7GkHNxnDVqzAwAABvoInA Saved) +%(day)s 11:26:36 server dovecot: lmtp(947): Disconnect from local: Successful quit +%(day)s 11:26:36 server postfix/qmgr[26163]: 61F9DE00C3: removed +%(day)s 11:26:38 server postfix/smtpd[917]: disconnect from o9.sendgrid.meetup.com[167.89.55.170] ehlo=2 starttls=1 mail=1 rcpt=1 data=1 quit=1 commands=7 +%(day)s 11:28:01 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=1109, secured, session= +%(day)s 11:28:01 server dovecot: imap(anomalie@test2.com): Logged out in=86 out=892 +%(day)s 11:28:01 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=1125, secured, session= +%(day)s 11:28:01 server dovecot: imap(evolution@test2.com): Logged out in=86 out=891 +%(day)s 11:28:01 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=1137, secured, session=<+4AiExdewMl/AAAB> +%(day)s 11:28:01 server dovecot: imap(question@test2.com): Logged out in=86 out=891 +%(day)s 11:29:58 server postfix/anvil[919]: statistics: max connection rate 1/60s for (smtpd:167.89.55.170) at %(day)s 11:26:26 +%(day)s 11:29:58 server postfix/anvil[919]: statistics: max connection count 1 for (smtpd:167.89.55.170) at %(day)s 11:26:26 +%(day)s 11:29:58 server postfix/anvil[919]: statistics: max cache size 1 at %(day)s 11:26:26 +%(day)s 11:30:30 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=1406, secured, session= +%(day)s 11:30:30 server dovecot: imap(admin@test.com): Logged out in=12 out=431 +%(day)s 11:30:30 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=1420, secured, session= +%(day)s 11:30:30 server dovecot: imap(admin@test.com): Logged out in=12 out=431 +%(day)s 11:30:34 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=1436, secured, session= +%(day)s 11:30:34 server dovecot: imap(admin@test.com): Logged out in=12 out=431 +%(day)s 11:30:34 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=1448, secured, session= +%(day)s 11:30:34 server dovecot: imap(admin@test.com): Logged out in=12 out=431 +%(day)s 11:32:47 server postfix/postscreen[1625]: CONNECT from [197.155.150.177]:16256 to [94.23.45.181]:25 +%(day)s 11:32:59 server postfix/postscreen[1625]: NOQUEUE: reject: RCPT from [197.155.150.177]:16256: 450 4.3.2 Service currently unavailable; from=, to=, proto=ESMTP, helo=<[197.155.150.177]> +%(day)s 11:32:59 server postfix/postscreen[1625]: HANGUP after 6.6 from [197.155.150.177]:16256 in tests after SMTP handshake +%(day)s 11:32:59 server postfix/postscreen[1625]: PASS NEW [197.155.150.177]:16256 +%(day)s 11:32:59 server postfix/postscreen[1625]: DISCONNECT [197.155.150.177]:16256 +%(day)s 11:34:02 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=1769, secured, session= +%(day)s 11:34:02 server dovecot: imap(anomalie@test2.com): Logged out in=86 out=892 +%(day)s 11:34:02 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=1785, secured, session= +%(day)s 11:34:02 server dovecot: imap(evolution@test2.com): Logged out in=86 out=891 +%(day)s 11:34:02 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=1797, secured, session=<8peeKBdeCsp/AAAB> +%(day)s 11:34:02 server dovecot: imap(question@test2.com): Logged out in=86 out=891 +%(day)s 11:35:07 server postfix/postscreen[1912]: CONNECT from [14.173.93.73]:55595 to [94.23.45.181]:25 +%(day)s 11:35:14 server postfix/postscreen[1912]: NOQUEUE: reject: RCPT from [14.173.93.73]:55595: 450 4.3.2 Service currently unavailable; from=, to=, proto=ESMTP, helo= +%(day)s 11:35:15 server postfix/postscreen[1912]: HANGUP after 1.4 from [14.173.93.73]:55595 in tests after SMTP handshake +%(day)s 11:35:15 server postfix/postscreen[1912]: PASS NEW [14.173.93.73]:55595 +%(day)s 11:35:15 server postfix/postscreen[1912]: DISCONNECT [14.173.93.73]:55595 +%(day)s 11:36:00 server postfix/postscreen[1912]: CONNECT from [185.83.51.33]:58639 to [94.23.45.181]:25 +%(day)s 11:36:06 server postfix/tlsproxy[2011]: CONNECT from [185.83.51.33]:58639 +%(day)s 11:36:06 server postfix/tlsproxy[2011]: Anonymous TLS connection established from [185.83.51.33]:58639: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits) +%(day)s 11:36:07 server postfix/postscreen[1912]: NOQUEUE: reject: RCPT from [185.83.51.33]:58639: 450 4.3.2 Service currently unavailable; from=, to=, proto=ESMTP, helo= +%(day)s 11:36:07 server postfix/postscreen[1912]: PASS NEW [185.83.51.33]:58639 +%(day)s 11:36:07 server postfix/tlsproxy[2011]: DISCONNECT [185.83.51.33]:58639 +%(day)s 11:36:07 server postfix/postscreen[1912]: DISCONNECT [185.83.51.33]:58639 +%(day)s 11:36:45 server postfix/postscreen[1912]: CONNECT from [185.5.53.87]:50522 to [94.23.45.181]:25 +%(day)s 11:36:45 server postfix/postscreen[1912]: PASS OLD [185.5.53.87]:50522 +%(day)s 11:36:45 server postfix/smtpd[2027]: connect from main.nope.org.es[185.5.53.87] +%(day)s 11:36:45 server postfix/smtpd[2027]: Anonymous TLS connection established from main.nope.org.es[185.5.53.87]: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits) +%(day)s 11:36:46 server postfix/smtpd[2027]: warning: 87.53.5.185.zen.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=87.53.5.185.zen.spamhaus.org type=A: Host not found, try again +%(day)s 11:36:46 server policyd-spf[2042]: prepend Received-SPF: None (no SPF record) identity=no SPF record; client-ip=185.5.53.87; helo=main.nope.org.es; envelope-from=<>; receiver= +%(day)s 11:36:46 server postfix/smtpd[2027]: NOQUEUE: client=main.nope.org.es[185.5.53.87] +%(day)s 11:36:47 server postfix/smtpd[2051]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:36:47 server postfix/smtpd[2051]: E0B74E00C3: client=localhost.localdomain[127.0.0.1], orig_client=main.nope.org.es[185.5.53.87] +%(day)s 11:36:47 server postfix/cleanup[2052]: E0B74E00C3: message-id=<201711161036.vAGAajZS022777@main.nope.org.es> +%(day)s 11:36:47 server postfix/smtpd[2051]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:36:47 server postfix/qmgr[26163]: E0B74E00C3: from=<>, size=3195, nrcpt=1 (queue active) +%(day)s 11:36:47 server amavis[14540]: (14540-14) Passed CLEAN {RelayedInbound}, [185.5.53.87]:50522 [185.5.53.87] <> -> , Message-ID: <201711161036.vAGAajZS022777@main.nope.org.es>, Hits: -1.889, size: 2719, queued_as: E0B74E00C3, 1212 ms +%(day)s 11:36:47 server postfix/smtpd[2027]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as E0B74E00C3; from=<> to= proto=ESMTP helo= +%(day)s 11:36:47 server dovecot: lmtp(2054): Connect from local +%(day)s 11:36:47 server dovecot: lmtp(admin@test.com): e4pnOj9qDVoGCAAABvoInA: sieve: msgid=<201711161036.vAGAajZS022777@main.nope.org.es>: not sending reject message to <> +%(day)s 11:36:47 server dovecot: lmtp(2054): Disconnect from local: Successful quit +%(day)s 11:36:47 server postfix/lmtp[2053]: E0B74E00C3: to=, orig_to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.08, delays=0.05/0.01/0.01/0.01, dsn=2.0.0, status=sent (250 2.0.0 e4pnOj9qDVoGCAAABvoInA Saved) +%(day)s 11:36:47 server postfix/qmgr[26163]: E0B74E00C3: removed +%(day)s 11:36:48 server policyd-spf[2042]: prepend Received-SPF: None (no SPF record) identity=no SPF record; client-ip=185.5.53.87; helo=main.nope.org.es; envelope-from=<>; receiver= +%(day)s 11:36:48 server postfix/smtpd[2027]: NOQUEUE: client=main.nope.org.es[185.5.53.87] +%(day)s 11:36:49 server postfix/smtpd[2051]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:36:49 server postfix/smtpd[2051]: 62D9BE00C3: client=localhost.localdomain[127.0.0.1], orig_client=main.nope.org.es[185.5.53.87] +%(day)s 11:36:49 server postfix/cleanup[2052]: 62D9BE00C3: message-id=<201711161036.vAGAajZT022777@main.nope.org.es> +%(day)s 11:36:49 server postfix/smtpd[2051]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:36:49 server postfix/qmgr[26163]: 62D9BE00C3: from=<>, size=2989, nrcpt=1 (queue active) +%(day)s 11:36:49 server dovecot: lmtp(2054): Connect from local +%(day)s 11:36:49 server dovecot: lmtp(admin@test.com): aVPzGUFqDVoGCAAABvoInA: sieve: msgid=<201711161036.vAGAajZT022777@main.nope.org.es>: not sending reject message to <> +%(day)s 11:36:49 server postfix/lmtp[2053]: 62D9BE00C3: to=, orig_to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.03, delays=0.03/0/0/0, dsn=2.0.0, status=sent (250 2.0.0 aVPzGUFqDVoGCAAABvoInA Saved) +%(day)s 11:36:49 server dovecot: lmtp(2054): Disconnect from local: Successful quit +%(day)s 11:36:49 server postfix/qmgr[26163]: 62D9BE00C3: removed +%(day)s 11:36:49 server amavis[32393]: (32393-03) Passed CLEAN {RelayedInbound}, [185.5.53.87]:50522 [185.5.53.87] <> -> , Message-ID: <201711161036.vAGAajZT022777@main.nope.org.es>, Hits: -1.889, size: 2513, queued_as: 62D9BE00C3, 955 ms +%(day)s 11:36:49 server postfix/smtpd[2027]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as 62D9BE00C3; from=<> to= proto=ESMTP helo= +%(day)s 11:36:49 server postfix/smtpd[2027]: disconnect from main.nope.org.es[185.5.53.87] ehlo=2 starttls=1 mail=2 rcpt=2 data=2 rset=1 quit=1 commands=11 +%(day)s 11:38:49 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=2280, secured, session= +%(day)s 11:38:49 server dovecot: imap(mowee@modoboa.com): Logged out in=12 out=431 +%(day)s 11:40:02 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=2474, secured, session= +%(day)s 11:40:02 server dovecot: imap(anomalie@test2.com): Logged out in=86 out=892 +%(day)s 11:40:02 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=2490, secured, session= +%(day)s 11:40:02 server dovecot: imap(evolution@test2.com): Logged out in=86 out=891 +%(day)s 11:40:02 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=2502, secured, session=<3Y0XPhdeZsp/AAAB> +%(day)s 11:40:02 server dovecot: imap(question@test2.com): Logged out in=86 out=891 +%(day)s 11:40:09 server postfix/anvil[2029]: statistics: max connection rate 1/60s for (smtpd:185.5.53.87) at %(day)s 11:36:45 +%(day)s 11:40:09 server postfix/anvil[2029]: statistics: max connection count 1 for (smtpd:185.5.53.87) at %(day)s 11:36:45 +%(day)s 11:40:09 server postfix/anvil[2029]: statistics: max cache size 1 at %(day)s 11:36:45 +%(day)s 11:43:42 server dovecot: imap(admin@test.com): Disconnected for inactivity in=1797 out=54357 +%(day)s 11:44:20 server postfix/postscreen[2852]: CONNECT from [192.254.113.101]:53977 to [94.23.45.181]:25 +%(day)s 11:44:20 server postfix/postscreen[2852]: WHITELISTED [192.254.113.101]:53977 +%(day)s 11:44:20 server postfix/smtpd[2853]: connect from o6.sgmail.github.com[192.254.113.101] +%(day)s 11:44:20 server postfix/smtpd[2853]: Anonymous TLS connection established from o6.sgmail.github.com[192.254.113.101]: TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits) +%(day)s 11:44:21 server postfix/smtpd[2853]: warning: 101.113.254.192.zen.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=101.113.254.192.zen.spamhaus.org type=A: Host not found, try again +%(day)s 11:44:21 server policyd-spf[2868]: prepend Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=192.254.113.101; helo=o6.sgmail.github.com; envelope-from=bounces+848413-16c5-tonio=ngyn.org@sgmail.github.com; receiver= +%(day)s 11:44:21 server postfix/smtpd[2853]: NOQUEUE: client=o6.sgmail.github.com[192.254.113.101] +%(day)s 11:44:31 server postfix/smtpd[2878]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:44:31 server postfix/smtpd[2878]: 70959E00C3: client=localhost.localdomain[127.0.0.1], orig_client=o6.sgmail.github.com[192.254.113.101] +%(day)s 11:44:31 server postfix/cleanup[2879]: 70959E00C3: message-id= +%(day)s 11:44:31 server postfix/smtpd[2878]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:44:31 server postfix/qmgr[26163]: 70959E00C3: from=, size=9954, nrcpt=1 (queue active) +%(day)s 11:44:31 server amavis[14540]: (14540-15) Passed CLEAN {RelayedInbound}, [192.254.113.101]:53977 [192.30.253.16] -> , Message-ID: , Hits: -2.22, size: 9378, queued_as: 70959E00C3, dkim_sd=s20150108:github.com, 9468 ms +%(day)s 11:44:31 server postfix/smtpd[2853]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as 70959E00C3; from= to= proto=ESMTP helo= +%(day)s 11:44:31 server dovecot: lmtp(2881): Connect from local +%(day)s 11:44:31 server dovecot: lmtp(admin@test.com): FmU3Hg9sDVpBCwAABvoInA: sieve: msgid=: stored mail into mailbox 'Listes.Tandoori' +%(day)s 11:44:31 server postfix/lmtp[2880]: 70959E00C3: to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.2, delays=0.04/0.01/0.01/0.14, dsn=2.0.0, status=sent (250 2.0.0 FmU3Hg9sDVpBCwAABvoInA Saved) +%(day)s 11:44:31 server dovecot: lmtp(2881): Disconnect from local: Successful quit +%(day)s 11:44:31 server postfix/qmgr[26163]: 70959E00C3: removed +%(day)s 11:44:33 server postfix/smtpd[2853]: disconnect from o6.sgmail.github.com[192.254.113.101] ehlo=2 starttls=1 mail=1 rcpt=1 data=1 quit=1 commands=7 +%(day)s 11:46:01 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=3041, secured, session= +%(day)s 11:46:01 server dovecot: imap(anomalie@test2.com): Logged out in=86 out=892 +%(day)s 11:46:01 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=3057, secured, session= +%(day)s 11:46:01 server dovecot: imap(evolution@test2.com): Logged out in=86 out=891 +%(day)s 11:46:01 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=3069, secured, session=<6mqDUxdevMp/AAAB> +%(day)s 11:46:02 server dovecot: imap(question@test2.com): Logged out in=86 out=891 +%(day)s 11:47:16 server postfix/postscreen[3161]: CONNECT from [37.187.155.42]:33450 to [94.23.45.181]:25 +%(day)s 11:47:22 server postfix/tlsproxy[3164]: CONNECT from [37.187.155.42]:33450 +%(day)s 11:47:22 server postfix/tlsproxy[3164]: Anonymous TLS connection established from [37.187.155.42]:33450: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits) +%(day)s 11:47:22 server postfix/postscreen[3161]: NOQUEUE: reject: RCPT from [37.187.155.42]:33450: 450 4.3.2 Service currently unavailable; from=, to=, proto=ESMTP, helo= +%(day)s 11:47:22 server postfix/postscreen[3161]: PASS NEW [37.187.155.42]:33450 +%(day)s 11:47:22 server postfix/postscreen[3161]: DISCONNECT [37.187.155.42]:33450 +%(day)s 11:47:22 server postfix/tlsproxy[3164]: DISCONNECT [37.187.155.42]:33450 +%(day)s 11:47:42 server postfix/postscreen[3161]: CONNECT from [172.110.9.131]:58398 to [94.23.45.181]:25 +%(day)s 11:47:42 server postfix/postscreen[3161]: PASS OLD [172.110.9.131]:58398 +%(day)s 11:47:43 server postfix/smtpd[3182]: connect from unknown[172.110.9.131] +%(day)s 11:47:43 server postfix/smtpd[3182]: Anonymous TLS connection established from unknown[172.110.9.131]: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits) +%(day)s 11:47:43 server postfix/smtpd[3182]: warning: 131.9.110.172.zen.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=131.9.110.172.zen.spamhaus.org type=A: Host not found, try again +%(day)s 11:47:44 server policyd-spf[3195]: prepend Received-SPF: None (no SPF record) identity=no SPF record; client-ip=172.110.9.131; helo=mailstore.paxi.cf; envelope-from=<>; receiver= +%(day)s 11:47:44 server postfix/smtpd[3182]: NOQUEUE: client=unknown[172.110.9.131] +%(day)s 11:47:45 server postfix/smtpd[3202]: connect from localhost.localdomain[127.0.0.1] +%(day)s 11:47:45 server postfix/smtpd[3202]: E81D5E00C3: client=localhost.localdomain[127.0.0.1], orig_client=unknown[172.110.9.131] +%(day)s 11:47:45 server postfix/cleanup[3203]: E81D5E00C3: message-id=<20171116104742.7CC0E4DCECC7@mailstore.paxi.cf> +%(day)s 11:47:45 server postfix/smtpd[3202]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 11:47:45 server postfix/qmgr[26163]: E81D5E00C3: from=<>, size=4166, nrcpt=1 (queue active) +%(day)s 11:47:45 server amavis[32393]: (32393-04) Passed CLEAN {RelayedInbound}, [172.110.9.131]:58398 [172.110.9.131] <> -> , Message-ID: <20171116104742.7CC0E4DCECC7@mailstore.paxi.cf>, Hits: -1.106, size: 3690, queued_as: E81D5E00C3, 1410 ms +%(day)s 11:47:45 server postfix/smtpd[3182]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as E81D5E00C3; from=<> to= proto=ESMTP helo= +%(day)s 11:47:46 server dovecot: lmtp(3205): Connect from local +%(day)s 11:47:46 server dovecot: lmtp(admin@test.com): RTETANJsDVqFDAAABvoInA: sieve: msgid=<20171116104742.7CC0E4DCECC7@mailstore.paxi.cf>: not sending reject message to <> +%(day)s 11:47:46 server postfix/lmtp[3204]: E81D5E00C3: to=, orig_to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.07, delays=0.04/0.01/0.01/0, dsn=2.0.0, status=sent (250 2.0.0 RTETANJsDVqFDAAABvoInA Saved) +%(day)s 11:47:46 server dovecot: lmtp(3205): Disconnect from local: Successful quit +%(day)s 11:47:46 server postfix/qmgr[26163]: E81D5E00C3: removed +%(day)s 11:47:46 server postfix/smtpd[3182]: disconnect from unknown[172.110.9.131] ehlo=2 starttls=1 mail=1 rcpt=1 data=1 quit=1 commands=7 +%(day)s 11:50:50 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=3536, secured, session= +%(day)s 11:50:50 server dovecot: imap(admin@test.com): Logged out in=12 out=431 +%(day)s 11:50:50 server dovecot: imap-login: Login: user=, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=3551, secured, session=<0rGyZBdeJst/AAAB> +%(day)s 11:50:50 server dovecot: imap(admin@test.com): Logged out in=12 out=431 +%(day)s 14:40:58 server postfix/postscreen[27044]: CONNECT from [188.213.142.238]:42388 to [94.23.45.181]:25 +%(day)s 14:40:58 server postfix/postscreen[27044]: PASS OLD [188.213.142.238]:42388 +%(day)s 14:40:58 server postfix/smtpd[27045]: connect from c264-2.lesmails.info[188.213.142.238] +%(day)s 14:40:58 server postfix/smtpd[27045]: warning: 238.142.213.188.zen.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=238.142.213.188.zen.spamhaus.org type=A: Host not found, try again +%(day)s 14:40:59 server policyd-spf[27066]: prepend Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=188.213.142.238; helo=sender.234.com; envelope-from=no-reply@mailing-lowcost.fr; receiver= +%(day)s 14:40:59 server postfix/smtpd[27045]: NOQUEUE: client=c264-2.lesmails.info[188.213.142.238] +%(day)s 14:41:00 server postfix/smtpd[27073]: connect from localhost.localdomain[127.0.0.1] +%(day)s 14:41:00 server postfix/smtpd[27073]: C2E3BE00C3: client=localhost.localdomain[127.0.0.1], orig_client=c264-2.lesmails.info[188.213.142.238] +%(day)s 14:41:00 server postfix/cleanup[27074]: C2E3BE00C3: message-id=<20171116131143.F75320333C@JM6_EVENTS.home> +%(day)s 14:41:00 server postfix/smtpd[27073]: disconnect from localhost.localdomain[127.0.0.1] ehlo=1 xforward=1 mail=1 rcpt=1 data=1 quit=1 commands=6 +%(day)s 14:41:00 server postfix/qmgr[26163]: C2E3BE00C3: from=, size=97952, nrcpt=1 (queue active) +%(day)s 14:41:00 server amavis[8518]: (08518-10) Passed SPAM {RelayedTaggedInbound}, [188.213.142.238]:42388 [188.213.142.238] -> , Message-ID: <20171116131143.F75320333C@jm6_events.home>, Hits: 13.086, size: 96743, queued_as: C2E3BE00C3, 1258 ms +%(day)s 14:41:00 server postfix/smtpd[27045]: proxy-accept: END-OF-MESSAGE: 250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as C2E3BE00C3; from= to= proto=ESMTP helo= +%(day)s 14:41:00 server postfix/smtpd[27045]: disconnect from c264-2.lesmails.info[188.213.142.238] ehlo=1 mail=1 rcpt=1 data=1 quit=1 commands=5 +%(day)s 14:41:00 server dovecot: lmtp(27260): Connect from local +%(day)s 14:41:01 server dovecot: lmtp(admin@test.com): +TSvNWyVDVp8agAABvoInA: sieve: msgid=<20171116131143.F75320333C@JM6_EVENTS.home>: stored mail into mailbox 'Junk' +%(day)s 14:41:01 server postfix/lmtp[27075]: C2E3BE00C3: to=, orig_to=, relay=mail.koalabs.org[private/dovecot-lmtp], delay=0.29, delays=0.09/0.01/0.01/0.18, dsn=2.0.0, status=sent (250 2.0.0 +TSvNWyVDVp8agAABvoInA Saved) +%(day)s 14:41:01 server dovecot: lmtp(27260): Disconnect from local: Successful quit +%(day)s 14:41:01 server postfix/qmgr[26163]: C2E3BE00C3: removed diff --git a/modoboa/maillog/tests/test_views.py b/modoboa/maillog/tests/test_views.py new file mode 100644 index 000000000..b1dcb5b17 --- /dev/null +++ b/modoboa/maillog/tests/test_views.py @@ -0,0 +1,168 @@ +"""modoboa-stats tests.""" + +import datetime +import os +import shutil +import tempfile + +from django.core.management import call_command +from django.urls import reverse +from django.test import override_settings + +from modoboa.admin import factories as admin_factories +from modoboa.core import models as core_models +from modoboa.lib.tests import ModoTestCase + + +class RunCommandsMixin(object): + """Mixin to run management commands.""" + + def setUp(self): + super(RunCommandsMixin, self).setUp() + self.workdir = tempfile.mkdtemp() + self.set_global_parameter("rrd_rootdir", self.workdir) + + def tearDown(self): + shutil.rmtree(self.workdir) + + def run_logparser(self): + """Run logparser command.""" + path = os.path.join( + os.path.dirname(__file__), "mail.log") + with open(path) as fp: + content = fp.read() % { + "day": datetime.date.today().strftime("%b %d")} + path = os.path.join(self.workdir, "mail.log") + with open(path, "w") as fp: + fp.write(content) + self.set_global_parameter("logfile", path) + call_command("logparser") + + def run_update_statistics(self, rebuild=False): + """Run update_statistics command.""" + args = [] + if rebuild: + args.append("--rebuild") + call_command("update_statistics", *args) + + +@override_settings(RRDTOOL_TEST_MODE=True) +class ViewsTestCase(RunCommandsMixin, ModoTestCase): + """Views test cases.""" + + @classmethod + def setUpTestData(cls): # noqa + super(ViewsTestCase, cls).setUpTestData() + admin_factories.populate_database() + cls.da = core_models.User.objects.get(username="admin@test.com") + + def tearDown(self): + super(ViewsTestCase, self).tearDown() + self.set_global_parameter("greylist", False) + + def test_index(self): + """Test index view.""" + url = reverse("maillog:fullindex") + response = self.client.get(url) + self.assertContains(response, 'id="graphs_accountgraphicset"') + self.assertContains(response, 'id="graphs_mailtraffic"') + + self.client.force_login(self.da) + response = self.client.get(url) + self.assertContains(response, 'id="graphs_mailtraffic"') + + def test_graphs(self): + """Test graphs views.""" + self.run_logparser() + url = reverse("maillog:graph_list") + self.ajax_get(url, status=404) + response = self.ajax_get("{}?gset=mailtraffic".format(url)) + self.assertIn("averagetraffic", response["graphs"]) + for period in ["week", "month", "year"]: + response = self.ajax_get( + "{}?gset=mailtraffic&period={}".format(url, period)) + self.assertIn("averagetraffic", response["graphs"]) + self.assertEqual(response["period_name"], period) + + # custom period + today = datetime.date.today() + start = "{} 11:00:00".format(today) + end = "{} 11:40:00".format(today) + response = self.ajax_get( + "{}?gset=mailtraffic&period=custom&start={}&end={}".format( + url, start, end) + ) + self.assertIn("averagetraffic", response["graphs"]) + + # unknown domain + response = self.ajax_get( + "{}?gset=mailtraffic&searchquery=unknown.com".format(url), + status=400) + + # check with greylist enabled + self.set_global_parameter("greylist", True) + response = self.ajax_get("{}?gset=mailtraffic".format(url)) + self.assertIn("averagetraffic", response["graphs"]) + + def test_account_created_graph(self): + """Check data.""" + self.run_update_statistics(rebuild=True) + url = reverse("maillog:graph_list") + response = self.ajax_get("{}?gset=accountgraphicset".format(url)) + data = ( + response["graphs"]["accountcreationgraphic"]["curves"][0]["data"]) + self.assertEqual(data[-1]["y"], 5.0) + + def test_graphs_as_domainadmin(self): + """Test graph views as domain admin.""" + self.run_logparser() + self.client.force_login(self.da) + url = "{}?gset=mailtraffic".format(reverse("maillog:graph_list")) + response = self.ajax_get(url) + self.assertIn("averagetraffic", response["graphs"]) + + response = self.ajax_get("{}&searchquery=test.com".format(url)) + self.assertIn("averagetraffic", response["graphs"]) + + response = self.ajax_get( + "{}&searchquery=test2.com".format(url), status=403) + + def test_get_domain_list(self): + """Test get_domain_list view.""" + url = reverse("maillog:domain_list") + response = self.ajax_get(url) + self.assertIn("test.com", response) + self.assertIn("test2.com", response) + + +@override_settings(RRDTOOL_TEST_MODE=True) +class ManagementCommandsTestCase(RunCommandsMixin, ModoTestCase): + """Management command test cases.""" + + @classmethod + def setUpTestData(cls): # noqa + super(ManagementCommandsTestCase, cls).setUpTestData() + admin_factories.populate_database() + + def test_logparser(self): + """Test logparser command.""" + self.run_logparser() + for d in ["global", "test.com"]: + path = os.path.join(self.workdir, "{}.rrd".format(d)) + self.assertTrue(os.path.exists(path)) + + def test_logparser_with_greylist(self): + """Test logparser when greylist activated.""" + self.set_global_parameter("greylist", True) + self.run_logparser() + for d in ["global", "test.com"]: + path = os.path.join(self.workdir, "{}.rrd".format(d)) + self.assertTrue(os.path.exists(path)) + + def test_update_statistics(self): + """Test update_statistics command.""" + self.run_update_statistics() + path = os.path.join(self.workdir, "new_accounts.rrd") + self.assertTrue(os.path.exists(path)) + self.run_update_statistics(rebuild=True) + self.assertTrue(os.path.exists(path)) diff --git a/modoboa/maillog/urls.py b/modoboa/maillog/urls.py new file mode 100644 index 000000000..e81e7a666 --- /dev/null +++ b/modoboa/maillog/urls.py @@ -0,0 +1,13 @@ +"""Stats urls.""" + +from django.urls import path + +from . import views + +app_name = "maillog" + +urlpatterns = [ + path("", views.index, name="fullindex"), + path("graphs/", views.graphs, name="graph_list"), + path("domains/", views.get_domain_list, name="domain_list"), +] diff --git a/modoboa/maillog/views.py b/modoboa/maillog/views.py new file mode 100644 index 000000000..018fd4e47 --- /dev/null +++ b/modoboa/maillog/views.py @@ -0,0 +1,103 @@ +# coding: utf-8 + +"""Custom views.""" + +import re +import time + +from django.shortcuts import render +from django.utils.translation import ugettext as _ +from django.contrib.auth.decorators import ( + login_required, user_passes_test, permission_required +) + +from modoboa.admin.models import Domain +from modoboa.lib.exceptions import BadRequest, NotFound +from modoboa.lib.web_utils import ( + render_to_json_response +) + +from . import signals +from .lib import date_to_timestamp + + +@login_required +@permission_required("admin.view_mailbox") +def index(request): + """ + FIXME: how to select a default graph set ? + """ + deflocation = "graphs/?gset=mailtraffic" + if not request.user.is_superuser: + if not Domain.objects.get_for_admin(request.user).count(): + raise NotFound(_("No statistics available")) + + graph_sets = {} + for result in signals.get_graph_sets.send( + sender="index", user=request.user): + graph_sets.update(result[1]) + periods = [ + {"name": "day", "label": _("Day")}, + {"name": "week", "label": _("Week")}, + {"name": "month", "label": _("Month")}, + {"name": "year", "label": _("Year")}, + {"name": "custom", "label": _("Custom")} + ] + return render(request, 'maillog/index.html', { + "periods": periods, + "selection": "stats", + "deflocation": deflocation, + "graph_sets": graph_sets + }) + + +@login_required +@user_passes_test(lambda u: u.role != "SimpleUsers") +def graphs(request): + gset = request.GET.get("gset", None) + graph_sets = {} + for result in signals.get_graph_sets.send( + sender="index", user=request.user): + graph_sets.update(result[1]) + if gset not in graph_sets: + raise NotFound(_("Unknown graphic set")) + period = request.GET.get("period", "day") + tplvars = {"graphs": {}, "period": period} + fname = graph_sets[gset].get_file_name(request) + if fname is None: + raise BadRequest(_("Unknown domain")) + tplvars["fname"] = fname + + if period == "custom": + if "start" not in request.GET or "end" not in request.GET: + raise BadRequest(_("Bad custom period")) + start = request.GET["start"] + end = request.GET["end"] + expr = re.compile(r'[:\- ]') + period_name = "%s_%s" % (expr.sub('', start), expr.sub('', end)) + start = date_to_timestamp(expr.split(start)) + end = date_to_timestamp(expr.split(end)) + else: + end = int(time.mktime(time.localtime())) + start = "-1%s" % period + period_name = period + + tplvars["domain_selector"] = graph_sets[gset].domain_selector + tplvars["graphs"] = graph_sets[gset].export(tplvars["fname"], start, end) + tplvars["period_name"] = period_name + tplvars["start"] = start + tplvars["end"] = end + + return render_to_json_response(tplvars) + + +@login_required +@user_passes_test(lambda u: u.role != "SimpleUsers") +def get_domain_list(request): + """Get the list of domains the user can see.""" + doms = [] + for dom in Domain.objects.get_for_admin(request.user): + doms += [dom.name] + doms += [alias.name for alias in dom.domainalias_set.all()] + + return render_to_json_response(doms) diff --git a/modoboa/urls.py b/modoboa/urls.py index 3b4a80c28..d5a5390de 100644 --- a/modoboa/urls.py +++ b/modoboa/urls.py @@ -27,6 +27,7 @@ urlpatterns = [ path('', include("modoboa.core.urls")), path('admin/', include("modoboa.admin.urls")), path('dnstools/', include("modoboa.dnstools.urls")), + path('stats/', include("modoboa.maillog.urls")), path('user/forward/', user_views.forward, name="user_forward"), diff --git a/requirements.txt b/requirements.txt index 7f3361119..5178fdaa1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,6 +28,7 @@ chardet ovh oath redis +rrdtool>=0.1.11 aiosmtplib aioredis diff --git a/test_project/test_project/settings.py b/test_project/test_project/settings.py index fc4593d8c..f13a7f4af 100644 --- a/test_project/test_project/settings.py +++ b/test_project/test_project/settings.py @@ -79,6 +79,7 @@ MODOBOA_APPS = ( 'modoboa.parameters', 'modoboa.dnstools', 'modoboa.policyd', + 'modoboa.maillog', # Modoboa extensions here. )