Merge branch 'master' into refacto/vuejs_interface

This commit is contained in:
Antoine Nguyen
2020-10-07 10:14:27 +02:00
1086 changed files with 127968 additions and 30886 deletions
+3 -1
View File
@@ -23,5 +23,7 @@ node_modules
/frontend/webpack-stats.json
# TOX stuff
.eggs/
test_project/modoboa.db
/.project
/.pydevproject
+3
View File
@@ -0,0 +1,3 @@
python:
install:
- requirements: doc/requirements.txt
+10 -8
View File
@@ -1,9 +1,9 @@
language: python
python:
- '2.7'
- '3.4'
- '3.5'
- '3.6'
- '3.7'
- '3.8'
env:
global:
@@ -15,23 +15,25 @@ env:
sudo: false
addons:
postgresql: '9.3'
postgresql: '9.6'
mysql: '5.5'
apt:
packages:
- ldap-utils
- postfix
- slapd
- librrd-dev
- rrdtool
cache: pip
services:
- postgres
- mysql
- redis-server
before_install:
- pip install codecov
- pip install codacy-coverage
install:
- pip install -r requirements.txt
@@ -50,12 +52,12 @@ before_script:
script:
- python ./tests.py
- cd test_project
- coverage run --source ../modoboa manage.py test modoboa.core modoboa.lib modoboa.admin modoboa.limits modoboa.transport modoboa.relaydomains
- 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 xml
- coverage combine
- coverage report
- codecov
- python-codacy-coverage -r coverage.xml
before_deploy:
- cd ../modoboa
@@ -71,5 +73,5 @@ deploy:
distributions: sdist bdist_wheel
on:
tags: true
python: '3.6'
python: '3.8'
condition: "$DB = POSTGRESQL"
+10 -3
View File
@@ -1,14 +1,21 @@
FROM python:3.6-alpine
FROM python:3.6-alpine as base
MAINTAINER Antoine Nguyen <tonio@ngyn.org>
RUN apk add --update python3-dev libffi-dev gcc musl-dev libxml2-dev libxslt-dev \
RUN apk add --update openssl python3-dev libffi-dev gcc musl-dev libxml2-dev libxslt-dev \
libressl-dev jpeg-dev \
&& rm -rf /var/cache/apk/*
WORKDIR /tmp
COPY requirements.txt /tmp
RUN pip install -r requirements.txt
COPY test-requirements.txt /tmp
RUN pip install --user -r requirements.txt --user -r test-requirements.txt
FROM python:3.6-alpine
COPY --from=base /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
RUN apk add --no-cache --update libxml2-dev libxslt-dev\
&& rm -rf /var/cache/apk/*
RUN mkdir /code
WORKDIR /code
+13 -5
View File
@@ -2,7 +2,7 @@
`Modoboa <http://modoboa.org/>`_
################################
|travis| |codecov| |codacy| |latest-version|
|travis| |codecov| |latest-version|
Modoboa is a mail hosting and management platform including a modern
and simplified Web User Interface. It provides useful components such
@@ -17,7 +17,7 @@ is used as a central point of communication between all components.
Modoboa is developed with modularity in mind, expanding it is really
easy. Actually, all current features are extensions.
It is written in Python and uses the `Django
It is written in Python 3 and uses the `Django
<https://www.djangoproject.com>`_, `jQuery <http://jquery.com>`_ and
`Bootstrap <http://getbootstrap.com/>`_
frameworks.
@@ -49,6 +49,16 @@ Demo Installation
If you want to try out Modoboa, check out our `demo installation <https://demo.modoboa.org/>`_.
************
Getting help
************
If you have any question, you can use the following ways to get help:
* IRC: join the ``#modoboa`` channel on Freenode
* Mailing list: join the `modoboa-users <https://groups.google.com/forum/#!forum/modoboa-users>`_ group on Google Groups
* Github: open an issue if you found a bug
*************
External code
*************
@@ -57,7 +67,7 @@ The following external libraries are provided with Modoboa:
* `jQuery version 1.9.1 <http://www.jquery.org/>`_
* `jQuery-UI 1.10+ <http://jqueryui.com/>`_
* `Bootstrap version 3.3.5 <http://getbootstrap.com/>`_
* `Bootstrap version 3.3.7 <http://getbootstrap.com/>`_
* `Bootstrap datetimepicker <http://eonasdan.github.io/bootstrap-datetimepicker/>`_
.. |latest-version| image:: https://img.shields.io/pypi/v/modoboa.svg
@@ -67,5 +77,3 @@ The following external libraries are provided with Modoboa:
:target: https://travis-ci.org/modoboa/modoboa
.. |codecov| image:: http://codecov.io/github/modoboa/modoboa/coverage.svg?branch=master
:target: http://codecov.io/github/modoboa/modoboa?branch=master
.. |codacy| image:: https://api.codacy.com/project/badge/Grade/82fd10127ac3468899ca75f04e9862b6
:target: https://www.codacy.com/app/modoboa/modoboa?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=modoboa/modoboa&amp;utm_campaign=Badge_Grade
-3
View File
@@ -1,7 +1,4 @@
#!/usr/bin/env python
# coding: utf-8
from __future__ import unicode_literals
from modoboa.core.commands import handle_command_line
+10 -3
View File
@@ -11,11 +11,18 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
from __future__ import unicode_literals
from unittest.mock import Mock as MagicMock
import os
import sys
# import sys
class Mock(MagicMock):
@classmethod
def __getattr__(cls, name):
return MagicMock()
MOCK_MODULES = ['rrdtool']
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
+59 -3
View File
@@ -14,7 +14,7 @@ are two available levels:
* User level: per user customization. Available at *User > Settings >
Preferences*
Regardless level, parameters are displayed using tabs, each tab
corresponding to one application.
@@ -297,6 +297,7 @@ like this::
AUTHENTICATION_BACKENDS = (
'modoboa.lib.authbackends.LDAPBackend',
# 'modoboa.lib.authbackends.LDAPSecondaryBackend', # Useful for a fallback mechanism
'django.contrib.auth.backends.ModelBackend',
)
@@ -386,7 +387,7 @@ Modoboa should connect to your LDAP server. They are described just below:
+--------------------+---------------------------------+--------------------+
If you need additional parameters, you will find a detailled
If you need additional parameters, you will find a detailed
documentation `here <http://packages.python.org/django-auth-ldap/>`_.
Once the authentication is properly configured, the users defined in
@@ -405,7 +406,7 @@ group. In this case, the username is not necessarily an email address.
Users will also be able to update their LDAP password directly from
Modoboa.
.. note::
.. note::
Modoboa doesn't provide any synchronization mechanism once a user
is registered into the database. Any modification done from the
@@ -438,6 +439,61 @@ SMTP server location can be customized using the following settings:
AUTH_SMTP_SERVER_PORT = 25
AUTH_SMTP_SECURED_MODE = None # 'ssl' or 'starttls' are accepted
.. _ldap_sync:
********************
LDAP synchronization
********************
Modoboa can synchronize accounts with an LDAP directory (tested with
OpenLDAP) but this feature is not enabled by default. To activate it,
add ``modoboa.ldapsync`` to ``MODOBOA_APPS`` in the
:file:`settings.py` file::
MODOBOA_APPS = (
'modoboa',
'modoboa.core',
'modoboa.lib',
'modoboa.admin',
'modoboa.transport',
'modoboa.relaydomains',
'modoboa.limits',
'modoboa.parameters',
'modoboa.dnstools',
'modoboa.ldapsync',
)
and enable it from the admin panel.
.. warning::
Make sure to install additional :ref:`requirements <ldap_auth>`
otherwise it won't work.
The following parameters are available and must be filled:
+--------------------+---------------------------------+--------------------+
|Name |Description |Default value |
+====================+=================================+====================+
|Enable LDAP |Control LDAP synchronization |no |
|synchronization |state | |
| | | |
+--------------------+---------------------------------+--------------------+
|Bind DN |The DN of a user with write | |
| |permission to create/update | |
| |accounts | |
+--------------------+---------------------------------+--------------------+
|Bind password |The associated password | |
| | | |
| | | |
+--------------------+---------------------------------+--------------------+
|Account DN template |The template used to build | |
| |account DNs (must contain a | |
| |%(user)s placeholder | |
+--------------------+---------------------------------+--------------------+
********************
Database maintenance
********************
+20 -8
View File
@@ -5,23 +5,35 @@ Useful tips
You would like to work on Modoboa but you don't know where to start?
You're at the right place! Browse this page to learn useful tips.
Docker
======
A docker image is available for developers. To use it, you must
install `docker <https://docs.docker.com/install/>`_ and
`docker-compose <https://docs.docker.com/compose/install/>`_ first.
Then, just run the following command::
$ docker-compose up
It will start the docker environment and make a Modoboa instance
available at ``http://localhost:8000``.
If you don't want to use docker or need a more complex development
setup, go to the next section.
.. _venv_for_dev:
Prepare a virtual environment
=============================
A `virtual environment
<http://virtualenv.readthedocs.org/en/latest/>`_ is a good way to
<https://docs.python.org/fr/3/library/venv.html>`_ is a good way to
setup a development environment on your machine.
.. note::
``virtualenv`` is available on all major distributions, just
install it using your favorite packages manager.
To do so, run the following commands::
$ virtualenv <path>
$ python3 -m venv <path>
$ source <path>/bin/activate
$ git clone https://github.com/modoboa/modoboa.git
$ cd modoboa
@@ -102,7 +114,7 @@ If you need to modify the documenation and want to see the result, you
can build it as follows:
.. sourcecode:: bash
> tox -e doc
> firefox .tox/doc/tmp/html/index.html
+1
View File
@@ -5,4 +5,5 @@ Contributors
- `Antidot <http://antidot.com>`_
- `Bearstech <http://bearstech.com>`_
- `CAP-REL <https://cap-rel.fr/>`_
- `Dalnix <https://www.dalnix.se/>`_
+7
View File
@@ -9,6 +9,13 @@ You will need a Server to perform well with at least:
* CPU: 2
* RAM: 2GB
* Disk: 10GB
* Python 3
.. note:: **👀 Heads up for modoboa versions 1.15 and later**
Python 2 support has been dropped with modoboa version 1.15.
If you still have Python 2 installed on your system either uninstall it
or force modoboa user to run with Python 3.
Recommended way
===============
+43
View File
@@ -193,6 +193,49 @@ SQLite users
Replace values between ``<>`` with yours.
LDAP
====
To make the LDAP authentication work, edit the :file:`conf.d/10-auth.conf` and
uncomment the following line at the end::
!include auth-ldap.conf.ext
Then edit the :file:`conf.d/auth-ldap.conf.ext` and edit the passdb section as following.
You should comment the userdb section, which will be managed by SQL with modoboa database.::
passdb {
driver = ldap
# Path for LDAP configuration file, see example-config/dovecot-ldap.conf.ext
args = /etc/dovecot/dovecot-ldap.conf.ext
}
#userdb {
#driver = ldap
#args = /etc/dovecot/dovecot-ldap.conf.ext
# Default fields can be used to specify defaults that LDAP may override
#default_fields = home=/home/virtual/%u
#}
Your own dovecot LDAP configuration file is now :file:`/etc/dovecot/dovecot-ldap.conf.ext`.
You can add your default LDAP conf in it, following the `official documentation <https://doc.dovecot.org/configuration_manual/authentication/ldap/>`_.
Synchronize dovecot LDAP conf with modoboa LDAP conf
----------------------------------------------------
To make dovecot LDAP configuration synchronized with modoboa LDAP configuration, you should create a dedicated dovecot conf file.
At the end of your dovecot configuration file (:file:`dovecot-ldap.conf.ext`), add the following line::
!include_try dovecot-modoboa.conf.ext
Then, set modoboa parameter *Enable Dovecot LDAP sync* to *Yes*.
Then set the *Dovecot LDAP config file* following the previous step (*/etc/dovecot/dovecot-modoboa.conf.ext* in the example)
The last step is to add the command **update_dovecot_conf** to the cron job of modoboa.
Then, each time your modoboa LDAP configuration is updated, your dovecot LDAP configuration will also be.
LMTP
====
+135 -76
View File
@@ -10,56 +10,63 @@ This section describes the installation of the web interface (a
Prepare the system
------------------
First of all, we recommand the following context:
First of all, we recommend the following context:
* Use a dedicated system user
* Use a `virtualenv <http://www.virtualenv.org/en/latest/>`_ to
install the application because it will isolate it (and its
* A dedicated system user
* A `virtual environment
<https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments>`_
to install the application because it will isolate it (and its
dependencies) from the rest of your system
The following example illustrates how to realize this (Debian like system):
.. sourcecode:: bash
The following example illustrates how to realize this on Debian-based
distributions using `virtualenv <https://virtualenv.pypa.io/en/stable/>`_:
> sudo apt-get install python-virtualenv python-pip
> sudo useradd modoboa
> sudo -i modoboa
> virtualenv env
> source env/bin/activate
(env)> pip install -U pip
.. code-block:: console
# apt-get install virtualenv python3-pip
# useradd modoboa # create a dedicated user
# su -l modoboa # log in as the newly created user
$ virtualenv --python python3 ./env # create the virtual environment
$ source ./env/bin/activate # activate the virtual environment
Modoboa depends on external tools and some of them require compilation
so you need a compiler and a few C libraries. Make sure to install the
following system packages according to your distribution:
+------------------------------+
|Debian / Ubuntu |
+==============================+
|build-essential python-dev |
|libxml2-dev libxslt-dev |
|libjpeg-dev librrd-dev |
|rrdtool libffi-dev |
| |
+------------------------------+
+-------------------------------+
| Debian / Ubuntu |
+===============================+
| build-essential python3-dev |
| libxml2-dev libxslt-dev |
| libjpeg-dev librrd-dev |
| rrdtool libffi-dev libssl-dev |
+-------------------------------+
+------------------------------+
|CentOS |
+==============================+
|gcc gcc-c++ python-devel |
|libxml2-devel libxslt-devel |
|libjpeg-turbo-devel |
|rrdtool-devel rrdtool |
|libffi-devel |
+------------------------------+
+-----------------------------+
| CentOS |
+=============================+
| gcc gcc-c++ python3-devel |
| libxml2-devel libxslt-devel |
| libjpeg-turbo-devel |
| rrdtool-devel rrdtool |
| libffi-devel |
+-----------------------------+
Then, install Modoboa:
.. note::
.. sourcecode:: bash
Alternatively, you could rely on your distribution packages for the Modoboa
dependencies which require compilation - e.g. ``psycopg2`` - if the version
is compatible. In this case, you have to create your virtual environment
with the ``--system-site-packages`` option.
(env)> pip install modoboa
Then, install Modoboa by running:
.. code-block:: console
(env)$ pip install modoboa
.. _database:
Database
--------
@@ -73,54 +80,76 @@ Thanks to Django, Modoboa is compatible with the following databases:
* PostgreSQL
* MySQL / MariaDB
* SQLite
* SQLite
Since the last one does not require particular actions, only the first
two ones are described.
two ones are described. You should also read the notes for those database
backends on the `official Django documentation
<https://docs.djangoproject.com/en/stable/ref/databases/>`_.
PostgreSQL
**********
Install the corresponding Python binding:
.. sourcecode:: bash
.. code-block:: console
(env)> pip install psycopg2
(env)$ pip install psycopg2
Then, create a user and a database:
.. note::
.. sourcecode:: bash
Alternatively, you can install the ``python3-psycopg2`` package instead on
Debian-based distributions if your virtual environment was created with
``--system-site-packages`` option.
> sudo -i postgres
>
Then, create a user and a database. For example, to create the ``modoboa``
database owned by a ``modoboa`` user, run the following commands on your
PostgreSQL server:
.. code-block:: console
# sudo -l -u postgres createuser --no-createdb modoboa
# sudo -l -u postgres createdb --owner=modoboa modoboa
MySQL / MariaDB
***************
Install the corresponding Python binding:
.. sourcecode:: bash
.. code-block:: console
(env)> pip install mysqlclient
(env)$ pip install mysqlclient
.. note::
Alternatively, you can install the ``python3-mysqldb`` package instead on
Debian-based distributions if your virtual environment was created with
``--system-site-packages`` option.
.. note::
MariaDB 10.2 (and newer) require mysqlclient 1.3.11 (or newer).
Then, create a user and a database:
Then, create a user and a database. For example, to create the ``modoboa``
database owned by a ``modoboa`` user, run the following SQL commands:
.. sourcecode:: bash
.. code-block:: mysql
> mysqladmin -u root -p create modoboa
CREATE DATABASE modoboa;
CREATE USER 'modoboa'@'localhost' IDENTIFIED BY 'my-strong-password-here';
GRANT ALL PRIVILEGES ON modoboa.* TO 'modoboa'@'localhost';
Deploy an instance
------------------
``modoboa-admin.py``, a command line tool, lets you deploy a
*ready-to-use* Modoboa site using only one instruction::
``modoboa-admin.py`` is a command line tool that lets you deploy a
*ready-to-use* Modoboa site. To create a new instance into ``./instance``,
you just have to run the following command:
(env)> modoboa-admin.py deploy instance --collectstatic \
--domain <hostname of your server> --dburl default:database-url
.. code-block:: console
(env)$ modoboa-admin.py deploy instance --collectstatic \
--domain <hostname of your server> --dburl default:<database url>
.. note::
@@ -160,12 +189,14 @@ done.
If you need a **silent installation** (e.g. if you're using
Salt-Stack, Ansible or whatever), it's possible to supply the database
credentials as commandline arguments.
credentials as command line arguments.
You can consult the complete option list by running the following
command::
command:
$ modoboa-admin.py help deploy
.. code-block:: console
(env)$ modoboa-admin.py help deploy
Cron jobs
---------
@@ -174,35 +205,63 @@ A few recurring jobs must be configured to make Modoboa works as
expected.
Create a new file, for example :file:`/etc/cron.d/modoboa` and put the
following content inside::
following content inside:
#
# Modoboa specific cron jobs
#
PYTHON=<PATH TO PYTHON BINARY>
INSTANCE=<PATH TO MODOBOA INSTANCE>
.. sourcecode:: bash
# Operations on mailboxes
* * * * * vmail $PYTHON $INSTANCE/manage.py handle_mailbox_operations
#
# Modoboa specific cron jobs
#
PYTHON=<path to Python binary inside the virtual environment>
INSTANCE=<path to Modoboa instance>
# Sessions table cleanup
0 0 * * * root $PYTHON $INSTANCE/manage.py clearsessions
# Operations on mailboxes
* * * * * vmail $PYTHON $INSTANCE/manage.py handle_mailbox_operations
# Logs table cleanup
0 0 * * * root $PYTHON $INSTANCE/manage.py cleanlogs
# Generate DKIM keys (they will belong to the user running this job)
* * * * * root umask 077 && $PYTHON $INSTANCE/manage.py modo manage_dkim_keys
# Logs parsing
*/5 * * * * root $PYTHON $INSTANCE/manage.py logparser &> /dev/null
0 * * * * root $PYTHON $INSTANCE/manage.py update_statistics
# Sessions table cleanup
0 0 * * * modoboa $PYTHON $INSTANCE/manage.py clearsessions
# Logs table cleanup
0 0 * * * modoboa $PYTHON $INSTANCE/manage.py cleanlogs
# DNSBL checks
*/30 * * * * modoboa $PYTHON $INSTANCE/manage.py modo check_mx
# Public API communication
0 * * * * modoboa $PYTHON $INSTANCE/manage.py communicate_with_public_api
# DNSBL checks
*/30 * * * * root $PYTHON $INSTANCE/manage.py modo check_mx
.. _policy_daemon:
# Public API communication
0 * * * * root $PYTHON $INSTANCE/manage.py communicate_with_public_api
Policy daemon
-------------
# Generate DKIM keys (they will belong to the user running this job)
* * * * * root umask 077 && $PYTHON $INSTANCE/manage.py modo manage_dkim_keys
Modoboa comes with a built-in `Policy Daemon for Postfix <http://www.postfix.org/SMTPD_POLICY_README.html>`_. Current features are:
* Define daily sending limits for domains and/or accounts
A `redis server <https://redis.io/>`_ is required to run this new daemon.
You can launch it manually using the following command:
.. sourcecode:: bash
(env)> python manage.py policy_daemon
But we recommend an automatic start using ``systemd`` or
``supervisor``. Here is a configuration example for ``supervisor``:
.. sourcecode:: ini
[program:policyd]
autostart=true
autorestart=true
command=/srv/modoboa/env/bin/python /srv/modoboa/instance/manage.py policy_daemon
directory=/srv/modoboa
redirect_stderr=true
user=modoboa
numprocs=1
It will listen by default on ``127.0.0.1`` and port ``9999``. The
policy daemon won't do anything unless you tell :ref:`postfix <policyd_config>` to use it.
Now you can continue to the :ref:`webserver` section.
+20
View File
@@ -65,3 +65,23 @@ Use the following configuration in the :file:`/etc/postfix/main.cf` file
Replace ``<driver>`` by the name of the database you use.
Restart Postfix.
.. _policyd_config:
Policy daemon
-------------
If you want to enable the built-in policy daemon, add the following
content to the :file:`/etc/postfix/main.cf` file::
smtpd_recipient_restrictions =
# ...
check_policy_service inet:127.0.0.1:9999
# ...
And reload postfix.
.. note::
The ``check_policy_service`` line must be placed before the
``permit_mynetworks`` one, otherwise the daemon won't be called.
+1 -1
View File
@@ -50,7 +50,7 @@ This is just one possible configuration.
To use mod_wsgi daemon mode, add the two following directives just
under ``WSGIScriptAlias``::
WSGIDaemonProcess example.com python-path=<modoboa_instance>:<virtualenv path>/lib/python2.7/site-packages
WSGIDaemonProcess example.com python-path=<modoboa_instance>:<virtualenv path>/lib/python3.7/site-packages
WSGIProcessGroup example.com
Replace values between ``<>`` with yours. If you don't use a
View File
+138 -1
View File
@@ -62,7 +62,7 @@ use them:
.. sourcecode:: bash
> sudo -i <modoboa_user>
> sudo -u <modoboa_user> -i
> source <virtuenv_path>/bin/activate
Then, run the following commands:
@@ -91,6 +91,143 @@ Rebuild Virtual Environment
Specific instructions
*********************
1.16.0
======
A new :ref:`policy daemon <policy_daemon>` has been added.
Make sure to have a Redis instance running on your server.
Add ``'modoboa.policyd'`` to ``MODOBOA_APPS``:
.. sourcecode:: python
MODOBOA_APPS = (
'modoboa',
'modoboa.core',
'modoboa.lib',
'modoboa.admin',
'modoboa.transport',
'modoboa.relaydomains',
'modoboa.limits',
'modoboa.parameters',
'modoboa.dnstools',
'modoboa.policyd'
)
Add the following settings to your ``settings.py`` file:
.. sourcecode:: python
REDIS_HOST = '<IP or hostname here>'
REDIS_PORT = 6379
REDIS_QUOTA_DB = 0
REDIS_URL = 'redis://{}:{}/{}'.format(REDIS_HOST, REDIS_PORT, REDIS_QUOTA_DB)
Once done, you can start the policy daemon using the following commands:
.. sourcecode:: bash
> python manage.py policy_daemon
Don't forget to configure :ref:`_policyd_config <postfix>` if you want
to use this feature.
The ``modoboa-stats`` plugin has been merged into the core.
Add ``'modoboa.maillog'`` to ``MODOBOA_APPS``:
.. sourcecode:: python
MODOBOA_APPS = (
'modoboa',
'modoboa.core',
'modoboa.lib',
'modoboa.admin',
'modoboa.transport',
'modoboa.relaydomains',
'modoboa.limits',
'modoboa.parameters',
'modoboa.dnstools',
'modoboa.policyd',
'modoboa.maillog',
)
And remove any reference to ``modoboa_stats`` in this same variable.
1.15.0
======
This version drops Python 2 support so don't forget to update all the
extensions you use.
.. warning::
If you upgrade an existing python 2 installation, you will need to
create a new Python 3 virtual environment. You can remove the
existing virtual environment and replace it by the new one so you
won't have to modify your configuration files.
Add the following new setting:
.. sourcecode:: python
DISABLE_DASHBOARD_EXTERNAL_QUERIES = False
Reload uwsgi/gunicorn/apache depending on your setup.
Finally, Make sure to use root privileges and run the following
command:
.. sourcecode:: bash
> python manage.py generate_postfix_maps --destdir <directory>
Then, reload postfix.
1.14.0
======
This release introduces an optional LDAP synchronization process. If
you want to use it, please follow the :ref:`dedicated procedure <ldap_sync>`.
1.13.1
======
Upgrade postfix maps files as follows:
.. sourcecode:: bash
> python manage.py generate_postfix_maps --destdir <path> --force-overwrite
1.13.0
======
Add ``'modoboa.dnstools'`` to ``MODOBOA_APPS``:
.. sourcecode:: python
MODOBOA_APPS = (
'modoboa',
'modoboa.core',
'modoboa.lib',
'modoboa.admin',
'modoboa.transport',
'modoboa.relaydomains',
'modoboa.limits',
'modoboa.parameters',
'modoboa.dnstools',
)
Add the following new settings:
.. sourcecode:: python
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
modoboa-postfix-autoreply 1.5.0
===============================
+3481 -2580
View File
File diff suppressed because it is too large Load Diff
-4
View File
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
"""Modoboa - Mail hosting made simple."""
from __future__ import unicode_literals
from pkg_resources import DistributionNotFound, get_distribution
try:
-4
View File
@@ -1,5 +1 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
default_app_config = "modoboa.admin.apps.AdminConfig"
+40 -9
View File
@@ -1,20 +1,19 @@
# -*- coding: utf-8 -*-
"""Admin API."""
from __future__ import unicode_literals
from django import http
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext as _
from rest_framework import filters, status, viewsets
from rest_framework.decorators import detail_route, list_route
from rest_framework.decorators import action
from rest_framework.exceptions import ParseError
from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated
from rest_framework.response import Response
from modoboa.core import models as core_models
from . import models, serializers
from modoboa.core import sms_backends
from . import lib, models, serializers
class DomainViewSet(viewsets.ModelViewSet):
@@ -69,7 +68,8 @@ class AccountViewSet(viewsets.ModelViewSet):
action_dict = {
"list": serializers.AccountSerializer,
"retrieve": serializers.AccountSerializer,
"password": serializers.AccountPasswordSerializer
"password": serializers.AccountPasswordSerializer,
"reset_password": serializers.ResetPasswordSerializer,
}
return action_dict.get(
self.action, serializers.WritableAccountSerializer)
@@ -86,7 +86,7 @@ class AccountViewSet(viewsets.ModelViewSet):
queryset = queryset.filter(mailbox__domain__name=domain)
return queryset
@detail_route(methods=["put"])
@action(methods=["put"], detail=True)
def password(self, request, pk=None):
"""Change account password."""
try:
@@ -100,7 +100,7 @@ class AccountViewSet(viewsets.ModelViewSet):
return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@list_route()
@action(detail=False)
def exists(self, request):
"""Check if account exists.
@@ -119,6 +119,37 @@ class AccountViewSet(viewsets.ModelViewSet):
serializer = serializers.AccountExistsSerializer(data)
return Response(serializer.data)
@action(methods=["post"], detail=False)
def reset_password(self, request):
"""Reset account password and send a new one by SMS."""
sms_password_recovery = (
request.localconfig.parameters
.get_value("sms_password_recovery", app="core")
)
if not sms_password_recovery:
return Response(status=404)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = core_models.User.objects.filter(
email=serializer.validated_data["email"]).first()
if not user or not user.phone_number:
return Response(status=404)
backend = sms_backends.get_active_backend(
request.localconfig.parameters)
if not backend:
return Response(status=404)
password = lib.make_password()
content = _("Here is your new Modoboa password: {}").format(
password)
if not backend.send(content, [str(user.phone_number)]):
body = {"status": "ko"}
else:
# SMS was sent, now we can set the new password.
body = {"status": "ok"}
user.set_password(password)
user.save(update_fields=["password"])
return Response(body)
class AliasViewSet(viewsets.ModelViewSet):
"""
+71 -15
View File
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
"""Modoboa admin settings."""
from __future__ import unicode_literals
import collections
import os
@@ -11,6 +7,7 @@ from django import forms
from django.conf import settings
from django.utils.encoding import force_text
from django.utils.translation import ugettext as _, ugettext_lazy
from django.forms.fields import GenericIPAddressField
from modoboa.lib.form_utils import SeparatorField, YesNoField
from modoboa.lib.sysutils import exec_cmd
@@ -52,6 +49,39 @@ class AdminParametersForm(param_forms.AdminParametersForm):
)
)
enable_spf_checks = YesNoField(
label=ugettext_lazy("Enable SPF checks"),
initial=True,
help_text=ugettext_lazy(
"Check if every domain has a valid SPF record"
)
)
enable_dkim_checks = YesNoField(
label=ugettext_lazy("Enable DKIM checks"),
initial=True,
help_text=ugettext_lazy(
"Check if every domain with DKIM signin enabled has a valid DNS "
"record"
)
)
enable_dmarc_checks = YesNoField(
label=ugettext_lazy("Enable DMARC checks"),
initial=True,
help_text=ugettext_lazy(
"Check if every domain has a valid DMARC record"
)
)
enable_autoconfig_checks = YesNoField(
label=ugettext_lazy("Enable autoconfig checks"),
initial=True,
help_text=ugettext_lazy(
"Check if every domain has a valid records for autoconfiguration"
)
)
enable_dnsbl_checks = YesNoField(
label=ugettext_lazy("Enable DNSBL checks"),
initial=True,
@@ -60,6 +90,14 @@ class AdminParametersForm(param_forms.AdminParametersForm):
)
)
custom_dns_server = GenericIPAddressField(
label=ugettext_lazy("Custom DNS server"),
required=False,
help_text=ugettext_lazy(
"Use a custom DNS server instead of local server configuration"
)
)
dkim_keys_storage_dir = forms.CharField(
label=ugettext_lazy("DKIM keys storage directory"),
initial="",
@@ -80,6 +118,24 @@ class AdminParametersForm(param_forms.AdminParametersForm):
)
)
default_domain_quota = forms.IntegerField(
label=ugettext_lazy("Default domain quota"),
initial=0,
help_text=ugettext_lazy(
"Default quota (in MB) applied to freshly created domains with no "
"value specified. A value of 0 means no quota."
)
)
default_domain_message_limit = forms.IntegerField(
label=ugettext_lazy("Default domain sending limit"),
required=False,
help_text=ugettext_lazy(
"Number of messages freshly created domains will be "
"allowed to send per day. Leave empty for no limit."
)
)
mbsep = SeparatorField(label=ugettext_lazy("Mailboxes"))
handle_mailboxes = YesNoField(
@@ -99,21 +155,21 @@ class AdminParametersForm(param_forms.AdminParametersForm):
)
)
default_domain_quota = forms.IntegerField(
label=ugettext_lazy("Default domain quota"),
initial=0,
help_text=ugettext_lazy(
"Default quota (in MB) applied to freshly created domains with no "
"value specified. A value of 0 means no quota."
)
)
default_mailbox_quota = forms.IntegerField(
label=ugettext_lazy("Default mailbox quota"),
initial=0,
help_text=ugettext_lazy(
"Default mailbox quota (in MB) applied to freshly created domains "
"with no value specified. A value of 0 means no quota."
"Default mailbox quota (in MB) applied to freshly created "
"mailboxes with no value specified. A value of 0 means no quota."
)
)
default_mailbox_message_limit = forms.IntegerField(
label=ugettext_lazy("Default mailbox sending limit"),
required=False,
help_text=ugettext_lazy(
"Number of messages freshly created mailboxes will be "
"allowed to send per day. Leave empty for no limit."
)
)
-4
View File
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
"""AppConfig for admin."""
from __future__ import unicode_literals
from django.apps import AppConfig
from .app_settings import load_admin_settings
+8 -70
View File
@@ -1,88 +1,18 @@
# -*- coding: utf-8 -*-
"""Admin constants."""
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
DNSBL_PROVIDERS = [
"aspews.ext.sorbs.net",
"b.barracudacentral.org",
"bad.psky.me",
"bl.deadbeef.com",
"bl.emailbasura.org",
"bl.spamcop.net",
"blackholes.five-ten-sg.com",
"blacklist.woody.ch",
"bogons.cymru.com",
"cbl.abuseat.org",
"cdl.anti-spam.org.cn",
"combined.abuse.ch",
"combined.rbl.msrbl.net",
"db.wpbl.info",
"dnsbl-1.uceprotect.net",
"dnsbl-2.uceprotect.net",
"dnsbl-3.uceprotect.net",
"dnsbl.inps.de",
"dnsbl.sorbs.net",
"drone.abuse.ch",
"duinv.aupads.org",
"dul.dnsbl.sorbs.net",
"dul.ru",
"dyna.spamrats.com",
"dynip.rothen.com",
"http.dnsbl.sorbs.net"
"images.rbl.msrbl.net",
"ips.backscatterer.org",
"ix.dnsbl.manitu.net",
"korea.services.net",
"misc.dnsbl.sorbs.net",
"noptr.spamrats.com",
"ohps.dnsbl.net.au",
"omrs.dnsbl.net.au",
"orvedb.aupads.org",
"osps.dnsbl.net.au",
"osrs.dnsbl.net.au",
"owfs.dnsbl.net.au",
"owps.dnsbl.net.au"
"pbl.spamhaus.org",
"phishing.rbl.msrbl.net",
"probes.dnsbl.net.au"
"proxy.bl.gweep.ca",
"proxy.block.transip.nl",
"psbl.surriel.com",
"rbl.interserver.net",
"rdts.dnsbl.net.au",
"relays.bl.gweep.ca",
"relays.bl.kundenserver.de",
"relays.nether.net",
"residential.block.transip.nl",
"ricn.dnsbl.net.au",
"rmst.dnsbl.net.au",
"sbl.spamhaus.org",
"short.rbl.jp",
"smtp.dnsbl.sorbs.net",
"socks.dnsbl.sorbs.net",
"spam.abuse.ch",
"spam.dnsbl.sorbs.net",
"spam.rbl.msrbl.net",
"spam.spamrats.com",
"spamlist.or.kr",
"spamrbl.imp.ch",
"t3direct.dnsbl.net.au",
"tor.dnsbl.sectoor.de",
"torserver.tor.dnsbl.sectoor.de",
"ubl.lashback.com",
"ubl.unsubscore.com",
"virbl.bit.nl",
"virus.rbl.jp",
"virus.rbl.msrbl.net",
"web.dnsbl.sorbs.net",
"wormrbl.imp.ch",
"xbl.spamhaus.org",
"zen.spamhaus.org",
"zombie.dnsbl.sorbs.net",
]
# Do not run tests for these domains.
@@ -104,3 +34,11 @@ DKIM_KEY_LENGTHS = [
(2048, "2048"),
(4096, "4096"),
]
ALARM_OPENED = 1
ALARM_CLOSED = 2
ALARM_STATUSES = [
(ALARM_OPENED, _("Opened")),
(ALARM_CLOSED, _("Closed")),
]
+11 -4
View File
@@ -1,11 +1,7 @@
# -*- coding: utf-8 -*-
"""
Fixtures factories.
"""
from __future__ import unicode_literals
import factory
from modoboa.core.factories import PermissionFactory, UserFactory
@@ -20,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
@@ -74,6 +71,16 @@ class SenderAddressFactory(factory.django.DjangoModelFactory):
model = models.SenderAddress
class AlarmFactory(factory.django.DjangoModelFactory):
"""Factory for Alarm model."""
domain = factory.SubFactory(DomainFactory)
mailbox = factory.SubFactory(MailboxFactory)
class Meta:
model = models.Alarm
def populate_database():
"""Create test data.
+2 -6
View File
@@ -1,7 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from .account import (
AccountForm, AccountFormGeneral, AccountFormMail, AccountPermissionsForm,
AccountWizard
@@ -10,7 +6,7 @@ from .alias import AliasForm
from .domain import (
DomainForm, DomainFormGeneral, DomainFormOptions, DomainWizard
)
from .export import ExportDataForm, ExportDomainsForm, ExportIdentitiesForm
from .export import ExportDataForm
from .forward import ForwardForm
from .import_ import ImportDataForm, ImportIdentitiesForm
@@ -18,6 +14,6 @@ __all__ = [
"DomainFormGeneral", "DomainFormOptions", "DomainForm",
"AccountFormGeneral", "AccountFormMail", "AccountPermissionsForm",
"AccountForm", "AliasForm", "ImportDataForm", "ImportIdentitiesForm",
"ExportDataForm", "ExportDomainsForm", "ExportIdentitiesForm",
"ExportDataForm",
"ForwardForm", "DomainWizard", "AccountWizard"
]
+34 -16
View File
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
"""Forms related to accounts management."""
from __future__ import unicode_literals
from collections import OrderedDict
from functools import reduce
@@ -23,7 +19,7 @@ from modoboa.lib.form_utils import (
)
from modoboa.lib.permissions import get_account_roles
from modoboa.lib.validators import validate_utf8_email
from modoboa.lib.web_utils import render_to_json_response
from modoboa.lib.web_utils import render_to_json_response, size2integer
from modoboa.parameters import tools as param_tools
from .. import lib, models, signals
@@ -229,16 +225,26 @@ class AccountFormMail(forms.Form, DynamicForm):
required=False,
initial=False
)
quota = forms.IntegerField(
quota = forms.CharField(
label=ugettext_lazy("Quota"),
required=False,
help_text=_("Quota in MB for this mailbox. Define a custom value or "
"use domain's default one. Leave empty to define an "
"unlimited value (not allowed for domain "
"administrators)."),
help_text=_(
"Quota for this mailbox, can be expressed in KB, MB (default) or "
"GB. Define a custom value or "
"use domain's default one. Leave empty to define an "
"unlimited value (not allowed for domain "
"administrators)."
),
widget=forms.widgets.TextInput(attrs={"class": "form-control"})
)
quota_act = forms.BooleanField(required=False)
message_limit = forms.IntegerField(
label=ugettext_lazy("Message sending limit"),
required=False,
min_value=0,
help_text=ugettext_lazy(
"Number of messages this mailbox can send per day")
)
aliases = lib_fields.UTF8AndEmptyUserEmailField(
label=ugettext_lazy("Alias(es)"),
required=False,
@@ -262,10 +268,11 @@ class AccountFormMail(forms.Form, DynamicForm):
def __init__(self, user, *args, **kwargs):
self.mb = kwargs.pop("instance", None)
self.user = user
super(AccountFormMail, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.field_widths = {
"quota": 3
}
params = dict(param_tools.get_global_parameters("admin"))
if self.mb is not None:
self.fields["email"].required = True
qset = self.mb.aliasrecipient_set.filter(alias__internal=False)
@@ -283,13 +290,17 @@ class AccountFormMail(forms.Form, DynamicForm):
self.fields["quota_act"].initial = self.mb.use_domain_quota
if not self.mb.use_domain_quota and self.mb.quota:
self.fields["quota"].initial = self.mb.quota
if self.mb.message_limit:
self.fields["message_limit"].initial = self.mb.message_limit
self.fields["create_alias_with_old_address"].initial = (
param_tools.get_global_parameter(
"create_alias_on_mbox_rename")
params["create_alias_on_mbox_rename"]
)
else:
del self.fields["create_alias_with_old_address"]
self.fields["quota_act"].initial = True
if params["default_mailbox_message_limit"] is not None:
self.fields["message_limit"].initial = (
params["default_mailbox_message_limit"])
if len(args) and isinstance(args[0], QueryDict):
self._load_from_qdict(
@@ -317,6 +328,10 @@ class AccountFormMail(forms.Form, DynamicForm):
raise forms.ValidationError(inst)
return email
def clean_quota(self):
"""Convert quota to Bytes."""
return size2integer(self.cleaned_data["quota"], output_unit="MB")
def clean(self):
"""Custom fields validation.
@@ -364,7 +379,9 @@ class AccountFormMail(forms.Form, DynamicForm):
self.__class__, context=user, klass=models.Mailbox)
self.mb = models.Mailbox(
address=self.locpart, domain=self.domain, user=account,
use_domain_quota=self.cleaned_data["quota_act"])
use_domain_quota=self.cleaned_data["quota_act"],
message_limit=self.cleaned_data.get("message_limit")
)
override_rules = (
user.is_superuser or
user.has_perm("admin.add_domain") and
@@ -473,6 +490,8 @@ class AccountPermissionsForm(forms.Form, DynamicForm):
args[0], "domains", lib_fields.DomainNameField)
def save(self):
if self.account.role == "SimpleUsers":
return
current_domains = [
dom.name for dom in
models.Domain.objects.get_for_admin(self.account)
@@ -487,8 +506,7 @@ class AccountPermissionsForm(forms.Form, DynamicForm):
domain.add_admin(self.account)
for domain in models.Domain.objects.get_for_admin(self.account):
if not filter(lambda name: self.cleaned_data[name] == domain.name,
self.cleaned_data.keys()):
if domain.name not in self.cleaned_data.values():
domain.remove_admin(self.account)
-4
View File
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
"""Forms related to aliases management."""
from __future__ import unicode_literals
from collections import OrderedDict
from django import forms
+35 -9
View File
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
"""Forms related to domains management."""
from __future__ import unicode_literals
from functools import reduce
from django import forms
@@ -19,7 +15,7 @@ from modoboa.lib.fields import DomainNameField
from modoboa.lib.form_utils import (
DynamicForm, TabForms, WizardForm, WizardStep, YesNoField
)
from modoboa.lib.web_utils import render_to_json_response
from modoboa.lib.web_utils import render_to_json_response, size2integer
from modoboa.parameters import tools as param_tools
from .. import constants, lib, signals
from ..models import Alias, Domain, DomainAlias, Mailbox
@@ -40,23 +36,43 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
)
)
dkim_key_selector = forms.CharField(
label=ugettext_lazy("Key selector"), required=False)
label=ugettext_lazy("Key selector"), initial="modoboa", required=False)
quota = forms.CharField(
label=ugettext_lazy("Quota"),
initial=0,
help_text=ugettext_lazy(
"Quota shared between mailboxes. Can be expressed in KB, "
"MB (default) or GB. A value of 0 means no quota."
)
)
default_mailbox_quota = forms.CharField(
label=ugettext_lazy("Default mailbox quota"),
initial=0,
help_text=ugettext_lazy(
"Default quota applied to mailboxes. Can be expressed in KB, MB "
"(default) or GB. A value of 0 means no quota."
)
)
class Meta:
model = Domain
fields = (
"name", "type", "quota", "default_mailbox_quota", "aliases",
"enabled", "enable_dns_checks", "enable_dkim",
"name", "type", "quota", "default_mailbox_quota", "message_limit",
"aliases", "enabled", "enable_dns_checks", "enable_dkim",
"dkim_key_selector", "dkim_key_length"
)
def __init__(self, user, *args, **kwargs):
self.oldname = None
if "instance" in kwargs:
self.old_dkim_key_length = kwargs["instance"].dkim_key_length
self.oldname = kwargs["instance"].name
super(DomainFormGeneral, self).__init__(*args, **kwargs)
params = dict(param_tools.get_global_parameters("admin"))
self.fields["quota"].initial = params["default_domain_quota"]
if params["default_domain_message_limit"] is not None:
self.fields["message_limit"].initial = (
params["default_domain_message_limit"])
self.fields["default_mailbox_quota"].initial = (
params["default_mailbox_quota"])
self.fields["type"].choices = constants.DOMAIN_TYPES
@@ -104,6 +120,15 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
_("DKIM keys storage directory not configured"))
return enabled
def clean_quota(self):
"""Return proper quota value."""
return size2integer(self.cleaned_data["quota"], output_unit="MB")
def clean_default_mailbox_quota(self):
"""Return proper quota value."""
return size2integer(
self.cleaned_data["default_mailbox_quota"], output_unit="MB")
def clean(self):
"""Custom fields validation.
@@ -374,7 +399,8 @@ class DomainForm(TabForms):
"""
if "general" in self.instances:
self.instances["general"].oldname = self.instances["general"].name
instance = self.instances["general"]
instance.oldname = instance.name
return super(DomainForm, self).is_valid()
def save(self):
+1 -28
View File
@@ -1,18 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
"""Export related forms."""
from django import forms
from django.utils.translation import ugettext_lazy
class ExportDataForm(forms.Form):
filename = forms.CharField(
label=ugettext_lazy("File name"),
max_length=100,
required=False,
widget=forms.TextInput(attrs={"class": "form-control"})
)
sepchar = forms.CharField(
label=ugettext_lazy("Separator"),
max_length=1,
@@ -28,22 +20,3 @@ class ExportDataForm(forms.Form):
if self.cleaned_data["sepchar"] == "":
return ";"
return self.cleaned_data["sepchar"]
def clean_filename(self):
if self.cleaned_data["filename"] == "":
return self.fields["filename"].initial
return self.cleaned_data["filename"]
class ExportDomainsForm(ExportDataForm):
def __init__(self, *args, **kwargs):
super(ExportDomainsForm, self).__init__(*args, **kwargs)
self.fields["filename"].initial = "modoboa-domains.csv"
class ExportIdentitiesForm(ExportDataForm):
def __init__(self, *args, **kwargs):
super(ExportIdentitiesForm, self).__init__(*args, **kwargs)
self.fields["filename"].initial = "modoboa-identities.csv"
-4
View File
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
"""Forms related to forwards management."""
from __future__ import unicode_literals
from django import forms
from django.core import validators
from django.utils.translation import ugettext_lazy
-4
View File
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
"""Forms related to import operations."""
from __future__ import unicode_literals
from django import forms
from django.utils.translation import ugettext_lazy
+1 -5
View File
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
"""Django signal handlers for admin."""
from __future__ import unicode_literals
from django.contrib.contenttypes.models import ContentType
from django.db.models import signals
from django.dispatch import receiver
@@ -258,7 +254,7 @@ def admin_menu(sender, location, user, **kwargs):
if location != "top_menu":
return []
entries = []
if user.has_perm("admin.view_domains"):
if user.has_perm("admin.view_domain"):
entries += [
{"name": "domains",
"url": reverse("admin:domain_list"),
+55 -38
View File
@@ -1,13 +1,8 @@
# -*- coding: utf-8 -*-
"""Internal library for admin."""
from __future__ import unicode_literals
import ipaddress
import logging
import random
import socket
import string
from functools import wraps
from itertools import chain
@@ -24,6 +19,7 @@ from modoboa.core import signals as core_signals
from modoboa.core.models import User
from modoboa.lib.exceptions import PermDeniedException
from modoboa.parameters import tools as param_tools
from . import signals
from .models import Alias, Domain, DomainAlias
@@ -171,49 +167,70 @@ def import_dlist(user, row, formopts):
_import_alias(user, row)
def get_domain_mx_list(domain):
"""Return a list of MX IP address for domain."""
result = []
def get_dns_resolver():
"""Return a DNS resolver object."""
dns_server = param_tools.get_global_parameter("custom_dns_server")
if dns_server:
resolver = dns.resolver.Resolver()
resolver.nameservers = [dns_server]
else:
resolver = dns.resolver
return resolver
def get_dns_records(name, typ, resolver=None):
"""Retrieve DNS records for given name and type."""
logger = logging.getLogger("modoboa.admin")
if not resolver:
resolver = get_dns_resolver()
try:
dns_answers = dns.resolver.query(domain, "MX")
dns_answers = resolver.query(name, typ)
except dns.resolver.NXDOMAIN as e:
logger.error(_("No DNS records found for %s") % domain, exc_info=e)
logger.error(_("No DNS record found for %s") % name, exc_info=e)
except dns.resolver.NoAnswer as e:
logger.error(_("No MX record for %s") % domain, exc_info=e)
logger.error(
_("No %(type)s record for %(name)s") % {"type": typ, "name": name},
exc_info=e
)
except dns.resolver.NoNameservers as e:
logger.error(_("No working name servers found"), exc_info=e)
except dns.resolver.Timeout as e:
logger.warning(
_("DNS resolution timeout, unable to query %s at the moment") %
domain, exc_info=e)
name, exc_info=e)
else:
for dns_answer in dns_answers:
try:
mx_domain = dns_answer.exchange.to_unicode(
omit_final_dot=True, idna_codec=IDNA_2008_UTS_46)
ip_answers = socket.getaddrinfo(
mx_domain, 25, 0, socket.SOCK_STREAM)
except socket.gaierror as e:
logger.warning(
_("Unable to lookup ip addresses for %(domain)s; "
"%(error)s")
% {"domain": mx_domain, "error": str(e)},
exc_info=e)
else:
for ip_answer in ip_answers:
try:
mx_ip = ipaddress.ip_address(
smart_text(ip_answer[4][0]))
except ValueError as e:
logger.warning(
_("Invalid IP address format for %(domain)s; "
"%(addr)s")
% {"domain": mx_domain,
"addr": smart_text(ip_answer[4][0])},
exc_info=e)
else:
result.append((mx_domain, mx_ip))
return dns_answers
return None
def get_domain_mx_list(domain):
"""Return a list of MX IP address for domain."""
result = []
logger = logging.getLogger("modoboa.admin")
resolver = get_dns_resolver()
dns_answers = get_dns_records(domain, "MX", resolver)
if dns_answers is None:
return result
for dns_answer in dns_answers:
mx_domain = dns_answer.exchange.to_unicode(
omit_final_dot=True, idna_codec=IDNA_2008_UTS_46)
for rtype in ["A", "AAAA"]:
ip_answers = get_dns_records(mx_domain, rtype, resolver)
if not ip_answers:
continue
for ip_answer in ip_answers:
try:
address_smart = smart_text(ip_answer.address)
mx_ip = ipaddress.ip_address(address_smart)
except ValueError as e:
logger.warning(
_("Invalid IP address format for "
"{domain}; {addr}").format(
domain=mx_domain,
addr=smart_text(ip_answer.address)
), exc_info=e)
else:
result.append((mx_domain, mx_ip))
return result
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,59 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Irriep Nala Novram <allannkorh@yahoo.fr>, 2019
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2019-03-08 09:15+0000\n"
"Last-Translator: Irriep Nala Novram <allannkorh@yahoo.fr>\n"
"Language-Team: Breton (http://www.transifex.com/tonio/modoboa/language/br/)\n"
"Language: br\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=5; plural=((n%10 == 1) && (n%100 != 11) && (n%100 !"
"=71) && (n%100 !=91) ? 0 :(n%10 == 2) && (n%100 != 12) && (n%100 !=72) && (n"
"%100 !=92) ? 1 :(n%10 ==3 || n%10==4 || n%10==9) && (n%100 < 10 || n% 100 > "
"19) && (n%100 < 70 || n%100 > 79) && (n%100 < 90 || n%100 > 99) ? 2 :(n != 0 "
"&& n % 1000000 == 0) ? 3 : 4);\n"
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "Domani ebet ken da ziskouez"
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "Na zilamit ket ar c'havlec'h domanioù"
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "An oberiadenn-mañ a zilamo an HOLL roadennoù stag ouzh an domani-mañ."
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr ""
"An oberiadenn-mañ a zilamo an HOLL roadennoù stag ouzh an domani-mañ, paneve "
"ar c'hontoù."
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "Kota ebet ken da ziskouez"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "Merour domani ebet termenet"
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "Identelezh ebet ken da ziskouez"
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "Na zilamit ket kavlec'h ar voest-posteloù"
File diff suppressed because it is too large Load Diff
@@ -1,53 +1,56 @@
# 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 <miris@kunago.com>, 2013,2015
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-04 22:08+0100\n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2017-09-22 09:50+0000\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
"Language-Team: Czech (Czech Republic) (http://www.transifex.com/tonio/modoboa/language/cs_CZ/)\n"
"Language-Team: Czech (Czech Republic) (http://www.transifex.com/tonio/"
"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"
"Language: cs_CZ\n"
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n "
"<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "Žádná další doména k zobrazení"
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "Nemazat adresář domény"
#: admin/static/admin/js/admin.js:152
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "Tento krok odstraní VŠECHNA data související s touto doménou."
#: admin/static/admin/js/admin.js:153
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr "Tento krok odstraní VŠECHNA data související s touto doménou, vyjma účtů."
msgstr ""
"Tento krok odstraní VŠECHNA data související s touto doménou, vyjma účtů."
#: admin/static/admin/js/admin.js:236
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "Žádná další kvóta k zobrazení"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "Není určen správce domény"
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "Žádná další identita k zobrazení"
#: admin/static/admin/js/admin.js:333
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "Nemazat adresář schránky"
#: admin/static/admin/js/admin.js:362
msgid "No more quota to show"
msgstr "Žádná další kvóta k zobrazení"
File diff suppressed because it is too large Load Diff
+15 -16
View File
@@ -9,30 +9,29 @@ msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-04 22:08+0100\n"
"PO-Revision-Date: 2014-12-01 12:12+0000\n"
"Last-Translator: Arvedui <arvedui@posteo.de>\n"
"Language-Team: German (http://www.transifex.com/projects/p/modoboa/language/"
"de/)\n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2017-09-22 09:50+0000\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
"Language-Team: German (http://www.transifex.com/tonio/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"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "Keine weiteren Domains"
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "Verzeichnis der Domain nicht löschen"
#: admin/static/admin/js/admin.js:152
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "Diese Aktion wird ALLE mit der Domain verbundenen Daten löschen."
#: admin/static/admin/js/admin.js:153
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
@@ -40,18 +39,18 @@ msgstr ""
"Diese Aktion wird ALLE mit der Domain verbundenen Daten, außer Mailkonten, "
"löschen."
#: admin/static/admin/js/admin.js:236
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "Keine weiteren Quotas"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "Domain-Administrator fehlt"
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "Keine weiteren Identitäten"
#: admin/static/admin/js/admin.js:333
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "Verzeichnis der Mailbox nicht löschen"
#: admin/static/admin/js/admin.js:362
msgid "No more quota to show"
msgstr "Keine weiteren Quotas"
File diff suppressed because it is too large Load Diff
@@ -1,7 +1,7 @@
# 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 <bilias@edu.physics.uoc.gr>, 2016
# Kostas Moumouris <moumouriskostas@gmail.com>, 2017
@@ -9,46 +9,51 @@ msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-04 22:08+0100\n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2017-10-05 20:01+0000\n"
"Last-Translator: Kostas Moumouris <moumouriskostas@gmail.com>\n"
"Language-Team: Greek (Greece) (http://www.transifex.com/tonio/modoboa/language/el_GR/)\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"
"Language: el_GR\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "Δεν υπάρχουν άλλα domains για εμφάνιση"
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "Να μην διαγραφεί ο κατάλογος αρχείων του domain "
#: admin/static/admin/js/admin.js:152
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "Αυτή η λειτουργία θα καταργήσει ΟΛΑ τα δεδομένα που σχετίζονται με αυτό το domain."
msgstr ""
"Αυτή η λειτουργία θα καταργήσει ΟΛΑ τα δεδομένα που σχετίζονται με αυτό το "
"domain."
#: admin/static/admin/js/admin.js:153
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr "Αυτή η λειτουργία θα καταργήσει όλα τα δεδομένα που σχετίζονται με αυτό το domain, εκτός από τους λογαριασμούς χρηστών."
msgstr ""
"Αυτή η λειτουργία θα καταργήσει όλα τα δεδομένα που σχετίζονται με αυτό το "
"domain, εκτός από τους λογαριασμούς χρηστών."
#: admin/static/admin/js/admin.js:236
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "Δεν υπάρχει κανένα quota"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "Δεν έχει οριστεί διαχειριστής domain"
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "Δεν υπάρχουν άλλες ταυτότητες για εμφάνιση"
#: admin/static/admin/js/admin.js:333
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "Να μην διαγραφεί ο κατάλογος αρχείων του γραμματοκιβωτίου "
#: admin/static/admin/js/admin.js:362
msgid "No more quota to show"
msgstr "Δεν υπάρχει κανένα quota"
File diff suppressed because it is too large Load Diff
+12 -12
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-04 22:08+0100\n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,36 +17,36 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr ""
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr ""
#: admin/static/admin/js/admin.js:152
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr ""
#: admin/static/admin/js/admin.js:153
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr ""
#: admin/static/admin/js/admin.js:236
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr ""
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr ""
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr ""
#: admin/static/admin/js/admin.js:333
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr ""
#: admin/static/admin/js/admin.js:362
msgid "No more quota to show"
msgstr ""
File diff suppressed because it is too large Load Diff
+19 -16
View File
@@ -1,7 +1,7 @@
# 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 <lasarux@neuroomante.com>, 2011,2013
# Pedro Gracia <pedro.gracia@impulzia.com>, 2010-2011
@@ -10,46 +10,49 @@ msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-04 22:08+0100\n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2017-02-21 00:55+0000\n"
"Last-Translator: samuel santos <sags9201@gmail.com>\n"
"Language-Team: Spanish (http://www.transifex.com/tonio/modoboa/language/es/)\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"
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "Sin dominios que mostrar"
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "No eleminar el directorio del dominio"
#: admin/static/admin/js/admin.js:152
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "Esta operación eliminará TODOS los datos asociados a este dominio."
#: admin/static/admin/js/admin.js:153
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr "Esta operación eliminará todos los datos asociados a este dominio, excepto las cuentas."
msgstr ""
"Esta operación eliminará todos los datos asociados a este dominio, excepto "
"las cuentas."
#: admin/static/admin/js/admin.js:236
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "Sin cuota para mostrar"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "No hay un administrador de dominio definido"
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "Sin identidades que mostrar"
#: admin/static/admin/js/admin.js:333
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "No eliminar el directorio del mailbox"
#: admin/static/admin/js/admin.js:362
msgid "No more quota to show"
msgstr "Sin cuota para mostrar"
File diff suppressed because it is too large Load Diff
+17 -15
View File
@@ -1,52 +1,54 @@
# 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: 2016-11-04 22:08+0100\n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2017-09-22 09:50+0000\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\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"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "Plus de domaine à afficher"
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "Ne pas supprimer le répertoire du domaine"
#: admin/static/admin/js/admin.js:152
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "Cette opération supprimera TOUTES les données associées à ce domaine."
#: admin/static/admin/js/admin.js:153
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr "Cette opération supprimera toutes les données associées à ce domaine à l'exception des comptes."
msgstr ""
"Cette opération supprimera toutes les données associées à ce domaine à "
"l'exception des comptes."
#: admin/static/admin/js/admin.js:236
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "Plus de quota à afficher"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "Aucun administrateur de domaine défini"
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "Plus d'identité à afficher"
#: admin/static/admin/js/admin.js:333
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "Ne pas supprimer le répertoire de la boite aux lettres"
#: admin/static/admin/js/admin.js:362
msgid "No more quota to show"
msgstr "Plus de quota à afficher"
File diff suppressed because it is too large Load Diff
+13 -13
View File
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-04 22:08+0100\n"
"PO-Revision-Date: 2015-10-11 11:19+0000\n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2017-09-22 09:50+0000\n"
"Last-Translator: Rocco <mors10@hotmail.com>\n"
"Language-Team: Italian (http://www.transifex.com/tonio/modoboa/language/"
"it/)\n"
@@ -19,19 +19,19 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "Non ci sono altri domini da mostrare"
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "Non eliminare la cartella dei domini"
#: admin/static/admin/js/admin.js:152
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "Questa operazione eliminerà TUTTI i dati associati a questo dominio."
#: admin/static/admin/js/admin.js:153
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
@@ -39,18 +39,18 @@ msgstr ""
"Questa operazione eliminerà tutti i dati associati a questo dominio tranne "
"gli account"
#: admin/static/admin/js/admin.js:236
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "Non ci sono altre quote da mostrare"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "Non è stato definito un amministratore di dominio"
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "Non ci sono altre identità da mostrare"
#: admin/static/admin/js/admin.js:333
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "Non eliminare la cartella della casella posta"
#: admin/static/admin/js/admin.js:362
msgid "No more quota to show"
msgstr "Non ci sono altre quote da mostrare"
File diff suppressed because it is too large Load Diff
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-04 22:08+0100\n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2016-06-07 07:01+0000\n"
"Last-Translator: cwatanab <chihiro.watanabe@live.jp>\n"
"Language-Team: Japanese (Japan) (http://www.transifex.com/tonio/modoboa/"
@@ -19,19 +19,19 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "表示するドメインはありません。"
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "ドメインディレクトリを削除しないでください"
#: admin/static/admin/js/admin.js:152
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "この操作は、このドメインに関連するすべてのデータを削除します。"
#: admin/static/admin/js/admin.js:153
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
@@ -39,18 +39,18 @@ msgstr ""
"この操作は、アカウントを除いたこのドメインに関連するすべてのデータを削除しま"
"す。"
#: admin/static/admin/js/admin.js:236
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "表示するクォータはありません"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "ドメイン管理者が指定されていません"
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "表示する ID はありません"
#: admin/static/admin/js/admin.js:333
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "メールボックスディレクトリを削除しないでください"
#: admin/static/admin/js/admin.js:362
msgid "No more quota to show"
msgstr "表示するクォータはありません"
File diff suppressed because it is too large Load Diff
@@ -3,57 +3,56 @@
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Harrie v.d. Vegt <hvdvegt@gmail.com>, 2016
# TuxBrother <vrijwilligerswerk@wij.rs>, 2014
# TuxBrother <vrijwilligerswerk@wij.rs>, 2014
# Tuxis Internet Engineering V.O.F. <info@tuxis.nl>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-04 22:08+0100\n"
"PO-Revision-Date: 2014-12-31 11:42+0000\n"
"Last-Translator: TuxBrother <vrijwilligerswerk@wij.rs>\n"
"Language-Team: Dutch (Netherlands) (http://www.transifex.com/projects/p/"
"modoboa/language/nl_NL/)\n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2017-09-23 21:20+0000\n"
"Last-Translator: Tuxis Internet Engineering V.O.F. <info@tuxis.nl>\n"
"Language-Team: Dutch (Netherlands) (http://www.transifex.com/tonio/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"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "Geen domein meer om te weergeven"
msgstr "Geen domeinen meer om weer te geven"
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "De map van dit domein niet verwijderen (Zal de data ervan behouden)"
msgstr "De map van dit domein niet verwijderen"
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "Deze actie zal ALLE data verwijderen die bij dit domein hoort."
#: admin/static/admin/js/admin.js:152
msgid "This operation will remove ALL data associated to this domain."
msgstr ""
"Deze actie zal ALLE data verwijderen die bij dit domein hoort. Wilt u "
"doorgaan?"
#: admin/static/admin/js/admin.js:153
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr ""
"Deze actie zal alle data die bij dit domein hoort verwijderen, behalve de "
"gebruikers."
"accounts."
#: admin/static/admin/js/admin.js:236
msgid "No domain administrator defined"
msgstr "Geen administrator toegewezen"
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
msgid "No more identity to show"
msgstr "Geen identiteiten meer om te weergeven"
#: admin/static/admin/js/admin.js:333
msgid "Do not delete mailbox directory"
msgstr "Verwijder de map van deze mailbox niet (zal de data ervan behouden)"
#: admin/static/admin/js/admin.js:362
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "Geen quota meer om te weergeven"
msgstr "Geen quotum meer om weer te geven"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "Geen domeinbeheerder toegewezen"
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "Geen identiteiten meer om weer te geven"
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "Verwijder de map van deze mailbox niet"
File diff suppressed because it is too large Load Diff
@@ -1,53 +1,57 @@
# 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 <sin@sin.waw.pl>, 2016
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-04 22:08+0100\n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2017-09-22 09:50+0000\n"
"Last-Translator: sin88 <sin@sin.waw.pl>\n"
"Language-Team: Polish (Poland) (http://www.transifex.com/tonio/modoboa/language/pl_PL/)\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"
"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"
"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"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "Brak domen do pokazania"
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "Nie usuwaj katalogu domeny"
#: admin/static/admin/js/admin.js:152
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "Ta operacja usunie WSZYSTKIE dane powiązane z tą domeną."
#: admin/static/admin/js/admin.js:153
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr "Ta operacja usunie wszystkie dane powiązana z tą domeną za wyjątkiem kont."
msgstr ""
"Ta operacja usunie wszystkie dane powiązana z tą domeną za wyjątkiem kont."
#: admin/static/admin/js/admin.js:236
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "Brak limitów do wyświetlenia"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "Nie zdefiniowano administratora domeny"
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "Brak tożsamości do wyświetlenia"
#: admin/static/admin/js/admin.js:333
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "Nie usuwaj katalogu skrzynki pocztowej"
#: admin/static/admin/js/admin.js:362
msgid "No more quota to show"
msgstr "Brak limitów do wyświetlenia"
File diff suppressed because it is too large Load Diff
@@ -1,53 +1,56 @@
# 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 <paulino@michelazzo.com.br>, 2014
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-04 22:08+0100\n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2017-09-23 21:20+0000\n"
"Last-Translator: Rafael Barretto <ch3yd@hotmail.com>\n"
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/tonio/modoboa/language/pt_BR/)\n"
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/tonio/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"
"Language: pt_BR\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "Nenhum domínio a mais para mostrar"
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "Não remova o diretório de domínio"
#: admin/static/admin/js/admin.js:152
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "Esta operação removerá TODOS os dados associados com este domínio."
#: admin/static/admin/js/admin.js:153
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr "Esta operação removerá todos os dados associados com este domínio, exceto contas."
msgstr ""
"Esta operação removerá todos os dados associados com este domínio, exceto "
"contas."
#: admin/static/admin/js/admin.js:236
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "Nenhuma quota a mais para mostrar"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "Nenhum administrador de domínio definido"
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "Nenhuma identidade a mais para mostrar"
#: admin/static/admin/js/admin.js:333
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "Não remova o diretório de caixas de mensagens"
#: admin/static/admin/js/admin.js:362
msgid "No more quota to show"
msgstr "Nenhuma quota a mais para mostrar"
File diff suppressed because it is too large Load Diff
@@ -13,7 +13,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-04 22:08+0100\n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2014-12-29 12:08+0000\n"
"Last-Translator: Mike C. <miguelmclara@gmail.com>\n"
"Language-Team: Portuguese (Portugal) (http://www.transifex.com/projects/p/"
@@ -24,19 +24,19 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "Não existem mais dominios para mostrar"
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "Não excluir o diretório do domínio"
#: admin/static/admin/js/admin.js:152
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "Esta operação irá remover TODOS os dados associados a este domínio."
#: admin/static/admin/js/admin.js:153
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
@@ -44,18 +44,18 @@ msgstr ""
"Esta operação irá remover TODOS os dados associados a este domínio, excepto "
"as contas."
#: admin/static/admin/js/admin.js:236
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "Não existem mais quotas para mostrar"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "Não está definido nenhum administrador para o domínio"
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "Não existem mais identidades para mostrar"
#: admin/static/admin/js/admin.js:333
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "Não excluir o diretório da caixa de correio"
#: admin/static/admin/js/admin.js:362
msgid "No more quota to show"
msgstr "Não existem mais quotas para mostrar"
File diff suppressed because it is too large Load Diff
@@ -1,53 +1,58 @@
# 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 <andrei@flashsoft.ro>, 2018
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-04 22:08+0100\n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2018-06-12 11:38+0000\n"
"Last-Translator: Andrei-Ilie Olteanu <andrei@flashsoft.ro>\n"
"Language-Team: Romanian (Romania) (http://www.transifex.com/tonio/modoboa/language/ro_RO/)\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"
"Language: ro_RO\n"
"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n"
"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?"
"2:1));\n"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "Nu mai sunt domenii de afișat"
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "Nu sterge directorul pentru domeniu"
#: admin/static/admin/js/admin.js:152
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "Această operațiune va șterge toată informația asociată cu acest domeniu."
msgstr ""
"Această operațiune va șterge toată informația asociată cu acest domeniu."
#: admin/static/admin/js/admin.js:153
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr "Această operațiune va șterge toată informația asociată cu acest domeniu, cu excepția conturilor."
msgstr ""
"Această operațiune va șterge toată informația asociată cu acest domeniu, cu "
"excepția conturilor."
#: admin/static/admin/js/admin.js:236
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "Nu mai există cote de afișat"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "Nu există configurat un administartor de domeniu"
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "Nu mai sunt identități de afișat"
#: admin/static/admin/js/admin.js:333
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "Nu sterge directorul mailbox"
#: admin/static/admin/js/admin.js:362
msgid "No more quota to show"
msgstr "Nu mai există cote de afișat"
File diff suppressed because it is too large Load Diff
+22 -19
View File
@@ -3,35 +3,38 @@
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Ravil Kadyrbayev, 2015
# Yuriy Matskanyuk <umackanuk@gmail.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-04 22:08+0100\n"
"PO-Revision-Date: 2013-12-03 09:35+0000\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
"Language-Team: Russian (http://www.transifex.com/projects/p/modoboa/language/"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2018-11-27 17:54+0000\n"
"Last-Translator: Yuriy Matskanyuk <umackanuk@gmail.com>\n"
"Language-Team: Russian (http://www.transifex.com/tonio/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"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
"%100>=11 && n%100<=14)? 2 : 3);\n"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr ""
msgstr "Нет больше доменов для показа"
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "Не удаляйте каталог домена"
#: admin/static/admin/js/admin.js:152
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "Эта операция удалит ВСЕ данные, связанные с этим доменом."
#: admin/static/admin/js/admin.js:153
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
@@ -39,18 +42,18 @@ msgstr ""
"Эта операция удалит ВСЕ данные, связанные с этим доменом, за исключением "
"аккаунтов."
#: admin/static/admin/js/admin.js:236
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "Нет больше квоты для показа"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "Не указан администратор домена"
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr ""
msgstr "Нет личности для показа"
#: admin/static/admin/js/admin.js:333
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "Не удаляйте каталог почтовых ящиков"
#: admin/static/admin/js/admin.js:362
msgid "No more quota to show"
msgstr ""
File diff suppressed because it is too large Load Diff
+19 -16
View File
@@ -1,53 +1,56 @@
# 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 <olle@dalnix.se>, 2013,2015
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-04 22:08+0100\n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2017-09-22 09:50+0000\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
"Language-Team: Swedish (http://www.transifex.com/tonio/modoboa/language/sv/)\n"
"Language-Team: Swedish (http://www.transifex.com/tonio/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"
"Language: sv\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "Inga fler domäner att visa"
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "Radera inte domän katalog"
#: admin/static/admin/js/admin.js:152
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "Denna åtgärd kommer radera ALL data associerad med den här domänen."
#: admin/static/admin/js/admin.js:153
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr "Den här operationen raderar all data som är associerad med denna domän, förutom konton."
msgstr ""
"Den här operationen raderar all data som är associerad med denna domän, "
"förutom konton."
#: admin/static/admin/js/admin.js:236
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "Ingen mer kvota att visa"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "Ingen domänadministratör definierad"
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "Inga fler konton att visa"
#: admin/static/admin/js/admin.js:333
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "Radera inte brevlådans katalog"
#: admin/static/admin/js/admin.js:362
msgid "No more quota to show"
msgstr "Ingen mer kvota att visa"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,54 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Ediz Duman <neps1192@gmail.com>, 2016
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2017-09-22 09:50+0000\n"
"Last-Translator: Ediz Duman <neps1192@gmail.com>\n"
"Language-Team: Turkish (Turkey) (http://www.transifex.com/tonio/modoboa/"
"language/tr_TR/)\n"
"Language: tr_TR\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"
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "Gösterilecek başka domain yok"
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "Domain dizini silmeyin"
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "Bu işlem, bu domain ilişkili TÜM verileri kaldıracaktır."
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr "Bu işlem hesap dışında bu domain ilişkili tüm verileri kaldıracaktır."
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "Gösterilecek kota yok"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "domain yöneticisi tanımlanmadı"
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "Gösterilecek daha fazla kimlik yok"
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "Posta kutusu dizini silmeyin"
File diff suppressed because it is too large Load Diff
@@ -1,7 +1,7 @@
# 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 <akong@aspa.idv.tw>, 2018
# Radium <ztl8702@126.com>, 2017
@@ -9,46 +9,47 @@ msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-04 22:08+0100\n"
"POT-Creation-Date: 2020-03-09 16:48+0100\n"
"PO-Revision-Date: 2018-06-29 00:40+0000\n"
"Last-Translator: akong <akong@aspa.idv.tw>\n"
"Language-Team: Chinese (Taiwan) (http://www.transifex.com/tonio/modoboa/language/zh_TW/)\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"
"Language: zh_TW\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: admin/static/admin/js/admin.js:131
#: admin/static/admin/js/admin.js:129 admin/static/admin/js/admin.js:174
msgid "No more domain to show"
msgstr "無更多網域可展示"
#: admin/static/admin/js/admin.js:150
#: admin/static/admin/js/admin.js:149
msgid "Do not delete domain directory"
msgstr "不可以刪除網域目錄"
#: admin/static/admin/js/admin.js:152
#: admin/static/admin/js/admin.js:151
msgid "This operation will remove ALL data associated to this domain."
msgstr "這個操作將會刪除所有這個網域有關的資料。"
#: admin/static/admin/js/admin.js:153
#: admin/static/admin/js/admin.js:152
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr "這個操作將會刪除所有這個網域有關的資料,除了帳號。"
#: admin/static/admin/js/admin.js:236
#: admin/static/admin/js/admin.js:177 admin/static/admin/js/admin.js:433
msgid "No more quota to show"
msgstr "沒有配額可能顯示"
#: admin/static/admin/js/admin.js:306
msgid "No domain administrator defined"
msgstr "網域管理員未指定"
#: admin/static/admin/js/admin.js:291 admin/static/admin/js/admin.js:359
#: admin/static/admin/js/admin.js:362 admin/static/admin/js/admin.js:430
msgid "No more identity to show"
msgstr "沒有身份可以顯示"
#: admin/static/admin/js/admin.js:333
#: admin/static/admin/js/admin.js:404
msgid "Do not delete mailbox directory"
msgstr "不可能刪除郵件目錄"
#: admin/static/admin/js/admin.js:362
msgid "No more quota to show"
msgstr "沒有配額可能顯示"
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
"""A management command to apply mailbox operations."""
from __future__ import print_function, unicode_literals
import logging
import os
import shutil
+27 -7
View File
@@ -1,10 +1,6 @@
# -*- coding: utf-8 -*-
"""Modoboa main management command."""
from __future__ import unicode_literals
from subcommand.base import SubcommandCommand
from django.core.management.base import BaseCommand
from .subcommands._export import ExportCommand
from .subcommands._import import ImportCommand
@@ -13,13 +9,13 @@ from .subcommands._mx import CheckMXRecords
from .subcommands._repair import Repair
class Command(SubcommandCommand):
class Command(BaseCommand):
"""Top management command for modoboa.
$ python manage.py modo
"""
help = "Modoboa top management command." # NOQA:A003
help = "Modoboa top management command."
subcommands = {
"export": ExportCommand,
@@ -28,3 +24,27 @@ class Command(SubcommandCommand):
"manage_dkim_keys": ManageDKIMKeys,
"repair": Repair,
}
def add_arguments(self, parser):
subparsers = parser.add_subparsers(
dest='subcommand', title='subcommands'
)
# required argument is added in Python 3.7
subparsers.required = True
for command_name, command_class in self.subcommands.items():
command = command_class(self.stdout, self.stderr)
command.style = self.style
subparser = subparsers.add_parser(
command_name, help=command_class.help
)
# This is needed to output console friendly errors
subparser.called_from_command_line = self._called_from_command_line
command.add_arguments(subparser)
subparser.set_defaults(command=command)
def execute(self, *args, **options):
return options.pop('command').execute(*args, **options)
@@ -1,24 +1,14 @@
# -*- coding: utf-8 -*-
"""Django management command to export admin objects."""
from __future__ import unicode_literals
import sys
import csv
from django.core.management.base import BaseCommand
from django.utils import six
from django.utils.encoding import smart_text
from modoboa.core.extensions import exts_pool
from modoboa.core.models import User
from .... import models
if six.PY2:
from backports import csv
else:
import csv
class ExportCommand(BaseCommand):
"""Command class."""
@@ -1,9 +1,6 @@
# -*- coding: utf-8 -*-
"""Django management command to import admin objects."""
from __future__ import unicode_literals
import csv
import io
import os
@@ -12,7 +9,6 @@ from chardet.universaldetector import UniversalDetector
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from django.utils import six
from django.utils.translation import ugettext as _
from modoboa.core import models as core_models
@@ -20,11 +16,6 @@ from modoboa.core.extensions import exts_pool
from modoboa.lib.exceptions import Conflict
from .... import signals
if six.PY2:
from backports import csv
else:
import csv
class ImportCommand(BaseCommand):
"""Command class."""
@@ -35,7 +26,7 @@ class ImportCommand(BaseCommand):
def add_arguments(self, parser):
"""Add extra arguments to command."""
parser.add_argument(
"--sepchar", type=six.text_type, default=";",
"--sepchar", type=str, default=";",
help="Separator used in file.")
parser.add_argument(
"--continue-if-exists", action="store_true",
@@ -45,7 +36,7 @@ class ImportCommand(BaseCommand):
"--crypt-password", action="store_true",
default=False, help="Encrypt provided passwords.")
parser.add_argument(
"files", type=six.text_type, nargs="+", help="CSV files to import.")
"files", type=str, nargs="+", help="CSV files to import.")
def _import(self, filename, options, encoding="utf-8"):
"""Import domains or identities."""
@@ -121,10 +112,7 @@ class ImportCommand(BaseCommand):
encoding=detector.result["encoding"]
)
except UnicodeDecodeError as exc:
six.raise_from(
CommandError(
_("Unable to decode CSV file using %(encoding)s "
"encoding") % detector.result
),
exc
)
raise CommandError(
_("Unable to decode CSV file using %(encoding)s "
"encoding") % detector.result
) from exc
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
"""Management command to create DKIM keys."""
from __future__ import print_function, unicode_literals
import os
from django.core.management.base import BaseCommand
@@ -1,24 +1,21 @@
# -*- coding: utf-8 -*-
"""Management command to check defined domains."""
from __future__ import print_function, unicode_literals
import ipaddress
import gevent
from gevent import socket
from django.conf import settings
from django.core import mail
from django.core.mail import EmailMessage
from django.core.management.base import BaseCommand
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.encoding import smart_text
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _
from modoboa.admin import constants, models
from modoboa.dnstools import models as dns_models
from modoboa.parameters import tools as param_tools
@@ -74,7 +71,7 @@ class CheckMXRecords(BaseCommand):
for ip, mxs in mx_list.items():
try:
ip = ipaddress.ip_address(smart_text(ip))
except ValueError as e:
except ValueError:
continue
else:
delim = "." if ip.version == 4 else ":"
@@ -89,8 +86,11 @@ class CheckMXRecords(BaseCommand):
return provider, results
def store_dnsbl_result(self, domain, provider, results, **options):
"""Store DNSBL provider results for domain."""
alerts = {}
"""Store DNSBL provider results for domain.
Return a list of alerts.
"""
alerts = []
to_create = []
for mx, result in list(results.items()):
if not result:
@@ -111,13 +111,29 @@ class CheckMXRecords(BaseCommand):
dnsbl_result.save()
if not dnsbl_result.status and result:
trigger = True
alarm_name = "domain_mx_in_dnsbl_{}".format(provider)
if trigger:
if domain not in alerts:
alerts[domain] = []
alerts[domain].append((provider, mx))
title = _("MX {} listed by DNSBL provider {}").format(
mx.name, provider)
domain.alarms.create(
internal_name=alarm_name,
status=constants.ALARM_OPENED,
title=title
)
alerts.append((provider, mx.name))
else:
domain.alarms.filter(
internal_name=alarm_name,
status=constants.ALARM_OPENED
).update(
status=constants.ALARM_CLOSED,
closed=timezone.now()
)
models.DNSBLResult.objects.bulk_create(to_create)
if not alerts:
return
return alerts
def send_alert_notifications(self, domain, alerts, subject, tpl, **options):
"""Send email notifications about given alerts."""
emails = list(options["email"])
if not options["skip_admin_emails"]:
emails.extend(
@@ -126,19 +142,9 @@ class CheckMXRecords(BaseCommand):
)
if not len(emails):
return
with mail.get_connection() as connection:
for domain, providers in list(alerts.items()):
content = render_to_string(
"admin/notifications/domain_in_dnsbl.html", {
"domain": domain, "alerts": providers
})
subject = _("[modoboa] DNSBL issue(s) for domain {}").format(
domain.name)
msg = EmailMessage(
subject, content.strip(), self.sender, emails,
connection=connection
)
msg.send()
content = render_to_string(tpl, {"domain": domain, "alerts": alerts})
msg = EmailMessage(subject, content.strip(), self.sender, emails)
msg.send()
def check_valid_mx(self, domain, mx_list, **options):
"""Check that domain's MX record exist.
@@ -152,38 +158,57 @@ class CheckMXRecords(BaseCommand):
for mx in mx_list]
valid_mxs = self.valid_mxs
if not mxs:
alerts.append(_("Domain {} has no MX record").format(domain))
elif valid_mxs:
for subnet in valid_mxs:
for mx, addr in mxs:
if addr in subnet:
mx.managed = check = True
mx.save()
if check is False:
mx_names = [
"{0.name} ({0.address})".format(mx) for mx in mx_list]
alarm, created = domain.alarms.get_or_create(
internal_name="domain_has_no_mx",
status=constants.ALARM_OPENED,
defaults={"title": _("Domain has no MX record")}
)
if created:
alerts.append(
_("MX record for domain {0} is invalid: {1}").format(
domain, ", ".join(mx_names))
)
_("Domain {} has no MX record").format(domain.name))
else:
# Close opened alarm
domain.alarms.filter(
internal_name="domain_has_no_mx",
status=constants.ALARM_OPENED).update(
status=constants.ALARM_CLOSED, closed=timezone.now())
if valid_mxs:
for subnet in valid_mxs:
for mx, addr in mxs:
if addr in subnet:
mx.managed = check = True
mx.save()
if check is False:
mx_names = [
"{0.name} ({0.address})".format(mx) for mx in mx_list]
alarm, created = domain.alarms.get_or_create(
internal_name="domain_invalid_mx",
status=constants.ALARM_OPENED,
defaults={
"title": _("Invalid MX record: {}").format(
", ".join(mx_names))
}
)
if created:
alerts.append(
_("MX record for domain {0} is invalid: {1}")
.format(domain, ", ".join(mx_names))
)
else:
# Close opened alarm
domain.alarms.filter(
internal_name="domain_invalid_mx",
status=constants.ALARM_OPENED
).update(
status=constants.ALARM_CLOSED,
closed=timezone.now()
)
if not alerts:
return
emails = list(options["email"])
if not options["skip_admin_emails"]:
emails.extend(
domain.admins.exclude(mailbox__isnull=True)
.values_list("email", flat=True)
)
if not len(emails):
return
content = render_to_string(
"admin/notifications/domain_invalid_mx.html", {
"domain": domain, "alerts": alerts
})
subject = _("[modoboa] MX issue(s) for domain {}").format(
domain.name)
msg = EmailMessage(subject, content.strip(), self.sender, emails)
msg.send()
tpl = "admin/notifications/domain_invalid_mx.html"
self.send_alert_notifications(domain, alerts, subject, tpl, **options)
def check_domain(self, domain, timeout=3, ttl=7200, **options):
"""Check specified domain."""
@@ -193,6 +218,25 @@ class CheckMXRecords(BaseCommand):
if param_tools.get_global_parameter("enable_mx_checks"):
self.check_valid_mx(domain, mx_list, **options)
if param_tools.get_global_parameter("enable_spf_checks"):
dns_models.DNSRecord.objects.get_or_create_for_domain(
domain, "spf", ttl)
condition = (
param_tools.get_global_parameter("enable_dkim_checks") and
domain.dkim_public_key
)
if condition:
dns_models.DNSRecord.objects.get_or_create_for_domain(
domain, "dkim", ttl)
if param_tools.get_global_parameter("enable_dmarc_checks"):
dns_models.DNSRecord.objects.get_or_create_for_domain(
domain, "dmarc", ttl)
if param_tools.get_global_parameter("enable_autoconfig_checks"):
dns_models.DNSRecord.objects.get_or_create_for_domain(
domain, "autoconfig", ttl)
dns_models.DNSRecord.objects.get_or_create_for_domain(
domain, "autodiscover", ttl)
condition = (
not param_tools.get_global_parameter("enable_dnsbl_checks") or
options["no_dnsbl"] is True)
@@ -210,11 +254,19 @@ class CheckMXRecords(BaseCommand):
gevent.spawn(self.query_dnsbl, mx_by_ip, provider)
for provider in self.providers]
gevent.joinall(jobs, timeout)
alerts = []
for job in jobs:
if not job.successful():
continue
provider, results = job.value
self.store_dnsbl_result(domain, provider, results, **options)
alerts += self.store_dnsbl_result(
domain, provider, results, **options)
if not alerts:
return
subject = _("[modoboa] DNSBL issue(s) for domain {}").format(
domain.name)
tpl = "admin/notifications/domain_in_dnsbl.html"
self.send_alert_notifications(domain, alerts, subject, tpl, **options)
def handle(self, *args, **options):
"""Command entry point."""
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
"""Management command to check and fix known problems."""
from __future__ import print_function, unicode_literals
from django.conf import settings
from django.core.management.base import BaseCommand
from django.utils.encoding import smart_str
-11
View File
@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
@@ -22,7 +19,6 @@ class Migration(migrations.Migration):
],
options={
'ordering': ['address'],
'permissions': (('view_aliases', 'View aliases'),),
},
bases=(models.Model,),
),
@@ -50,7 +46,6 @@ class Migration(migrations.Migration):
],
options={
'ordering': ['name'],
'permissions': (('view_domain', 'View domain'), ('view_domains', 'View domains')),
},
bases=(models.Model,),
),
@@ -61,9 +56,6 @@ class Migration(migrations.Migration):
('name', models.CharField(help_text='The alias name', unique=True, max_length=100, verbose_name='name')),
('enabled', models.BooleanField(default=True, help_text='Check to activate this alias', verbose_name='enabled')),
],
options={
'permissions': (('view_domaliases', 'View domain aliases'),),
},
bases=(models.Model,),
),
migrations.CreateModel(
@@ -74,9 +66,6 @@ class Migration(migrations.Migration):
('quota', models.PositiveIntegerField()),
('use_domain_quota', models.BooleanField(default=False)),
],
options={
'permissions': (('view_mailboxes', 'View mailboxes'),),
},
bases=(models.Model,),
),
migrations.CreateModel(
@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.9 on 2016-09-14 09:07
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-10-26 08:03
from __future__ import unicode_literals
from django.db import migrations, models
@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-11-04 18:00
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-11-04 18:15
from __future__ import unicode_literals
from django.db import migrations, models
@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.12 on 2017-02-06 06:13
from __future__ import unicode_literals
from django.db import migrations, models
@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-02-15 08:48
from __future__ import unicode_literals
from django.db import migrations
@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-02-15 12:28
from __future__ import unicode_literals
from django.db import migrations, models
@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-20 14:17
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.8 on 2018-01-12 15:58
from __future__ import unicode_literals
from django.db import migrations, models
@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.8 on 2018-01-24 23:11
from __future__ import unicode_literals
from django.db import migrations, models
@@ -0,0 +1,22 @@
# Generated by Django 1.11.16 on 2018-10-17 14:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('admin', '0013_auto_20180124_2311'),
]
operations = [
migrations.AlterField(
model_name='domain',
name='default_mailbox_quota',
field=models.PositiveIntegerField(default=0, verbose_name='Default mailbox quota'),
),
migrations.AlterField(
model_name='domain',
name='quota',
field=models.PositiveIntegerField(default=0),
),
]
@@ -0,0 +1,44 @@
# Generated by Django 2.1.15 on 2020-01-13 09:22
import sys
from django.db import IntegrityError, migrations, transaction
VIEW_PERMISSION_CODENAMES = {
'view_aliases': 'view_alias',
'view_domaliases': 'view_domainalias',
'view_mailboxes': 'view_mailbox',
}
def rename_view_permissions(apps, schema_editor):
Permission = apps.get_model('auth', 'Permission')
db_alias = schema_editor.connection.alias
# Iterate over permissions and try to rename the codename
for perm in Permission.objects.using(db_alias).filter(
codename__in=VIEW_PERMISSION_CODENAMES.keys()
):
codename = perm.codename
try:
with transaction.atomic():
perm.codename = VIEW_PERMISSION_CODENAMES[perm.codename]
perm.save()
except IntegrityError:
print(
(
"Unable to rename '{}' to '{}', a permission with this "
"codename already exists.".format(codename, perm.codename)
),
file=sys.stderr,
)
class Migration(migrations.Migration):
dependencies = [
('admin', '0014_auto_20181017_1628'),
]
operations = [
migrations.RunPython(rename_view_permissions),
]
@@ -0,0 +1,23 @@
# Generated by Django 2.2.12 on 2020-06-02 10:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('admin', '0015_rename_view_permissions'),
]
operations = [
migrations.AddField(
model_name='domain',
name='message_limit',
field=models.PositiveIntegerField(blank=True, help_text='Number of messages this domain can send per day', null=True, verbose_name='Message sending limit'),
),
migrations.AddField(
model_name='mailbox',
name='message_limit',
field=models.PositiveIntegerField(blank=True, help_text='Number of messages this mailbox can send per day', null=True, verbose_name='Message sending limit'),
),
]
+31
View File
@@ -0,0 +1,31 @@
# Generated by Django 2.2.12 on 2020-09-11 07:46
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('admin', '0016_auto_20200602_1201'),
]
operations = [
migrations.CreateModel(
name='Alarm',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(default=django.utils.timezone.now)),
('closed', models.DateTimeField(blank=True, null=True)),
('status', models.IntegerField(choices=[(1, 'Opened'), (2, 'Closed')], db_index=True, default=1)),
('title', models.CharField(max_length=150)),
('internal_name', models.CharField(max_length=120)),
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='alarms', to='admin.Domain')),
('mailbox', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='alarms', to='admin.Mailbox')),
],
options={
'ordering': ['created'],
},
),
]
-1
View File
@@ -1 +0,0 @@
from __future__ import unicode_literals
+2 -4
View File
@@ -1,9 +1,6 @@
# -*- coding: utf-8 -*-
"""Admin models."""
from __future__ import unicode_literals
from .alarm import Alarm
from .alias import Alias, AliasRecipient
from .base import AdminObject
from .domain import Domain
@@ -12,6 +9,7 @@ from .mailbox import Mailbox, MailboxOperation, Quota, SenderAddress
from .mxrecord import DNSBLResult, MXRecord
__all__ = [
"Alarm",
"AdminObject",
"Alias",
"AliasRecipient",
+55
View File
@@ -0,0 +1,55 @@
"""Alert related models."""
from django.db import models
from django.utils import timezone
from reversion import revisions as reversion
from .. import constants
class AlarmQuerySet(models.QuerySet):
"""Custom queryset for Alarm."""
def opened(self):
"""Return opened alarms."""
return self.filter(status=constants.ALARM_OPENED)
class Alarm(models.Model):
"""A simple alarm to attach to a domain and/or mailbox."""
domain = models.ForeignKey(
"admin.Domain", on_delete=models.CASCADE, related_name="alarms")
mailbox = models.ForeignKey(
"admin.Mailbox", on_delete=models.SET_NULL,
related_name="alarms",
null=True, blank=True,
)
created = models.DateTimeField(default=timezone.now)
closed = models.DateTimeField(null=True, blank=True)
status = models.IntegerField(
default=constants.ALARM_OPENED, choices=constants.ALARM_STATUSES,
db_index=True
)
title = models.CharField(max_length=150)
internal_name = models.CharField(max_length=120)
objects = models.Manager.from_queryset(AlarmQuerySet)()
class Meta:
ordering = ["created"]
def __str__(self):
return "[{}] {} - {}".format(
self.created, self.domain, self.get_status_display())
def close(self):
"""Close this alarm."""
self.status = constants.ALARM_CLOSED
self.closed = timezone.now()
self.save()
reversion.register(Alarm)
-7
View File
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
"""Models related to aliases management."""
from __future__ import unicode_literals
import hashlib
import random
@@ -52,9 +48,6 @@ class Alias(AdminObject):
_objectname = "MailboxAlias"
class Meta:
permissions = (
("view_aliases", "View aliases"),
)
ordering = ["address"]
unique_together = (("address", "internal"), )
app_label = "admin"
+2 -6
View File
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
"""Base admin models."""
from __future__ import unicode_literals
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from django.utils import timezone
@@ -47,13 +43,13 @@ class AdminObject(models.Model):
def __init__(self, *args, **kwargs):
"""Custom constructor."""
super(AdminObject, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self._loaded_values = {}
@classmethod
def from_db(cls, db, field_names, values):
"""Store loaded values."""
instance = super(AdminObject, cls).from_db(db, field_names, values)
instance = super().from_db(db, field_names, values)
instance._loaded_values = dict(zip(field_names, values))
return instance
+45 -21
View File
@@ -1,18 +1,12 @@
# -*- coding: utf-8 -*-
"""Models related to domains management."""
from __future__ import unicode_literals
import datetime
from reversion import revisions as reversion
from django.db import models
from django.utils import timezone
from django.utils.encoding import (
force_text, python_2_unicode_compatible, smart_text
)
from django.utils.encoding import force_text, smart_text
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _, ugettext_lazy
@@ -20,29 +14,29 @@ from modoboa.core import signals as core_signals
from modoboa.core.models import User
from modoboa.lib.exceptions import BadRequest, Conflict
from modoboa.parameters import tools as param_tools
from .. import constants
from .base import AdminObject
from . import mixins
@python_2_unicode_compatible
class Domain(AdminObject):
class Domain(mixins.MessageLimitMixin, AdminObject):
"""Mail domain."""
name = models.CharField(ugettext_lazy("name"), max_length=100, unique=True,
help_text=ugettext_lazy("The domain name"))
quota = models.PositiveIntegerField(
default=0,
help_text=ugettext_lazy(
"Quota in MB shared between mailboxes. A value of 0 means "
"no quota."
)
)
default_mailbox_quota = models.PositiveIntegerField(
verbose_name=ugettext_lazy("Default mailbox quota"),
default=0,
default=0
)
message_limit = models.PositiveIntegerField(
ugettext_lazy("Message sending limit"),
null=True, blank=True,
help_text=ugettext_lazy(
"Default quota in MB applied to mailboxes. A value of 0 means "
"no quota."
"Number of messages this domain can send per day"
)
)
enabled = models.BooleanField(
@@ -75,10 +69,6 @@ class Domain(AdminObject):
dkim_private_key_path = models.CharField(max_length=254, blank=True)
class Meta:
permissions = (
("view_domain", "View domain"),
("view_domains", "View domains"),
)
ordering = ["name"]
app_label = "admin"
@@ -87,6 +77,7 @@ class Domain(AdminObject):
super(Domain, self).__init__(*args, **kwargs)
self.old_mail_homes = None
self.oldname = self.name
self.old_dkim_key_length = self.dkim_key_length
@property
def domainalias_count(self):
@@ -189,6 +180,31 @@ class Domain(AdminObject):
else:
return "success"
@cached_property
def spf_record(self):
"""Return SPF record."""
return self.dnsrecord_set.filter(type="spf").first()
@cached_property
def dkim_record(self):
"""Return DKIM record."""
return self.dnsrecord_set.filter(type="dkim").first()
@cached_property
def dmarc_record(self):
"""Return DMARC record."""
return self.dnsrecord_set.filter(type="dmarc").first()
@cached_property
def autoconfig_record(self):
"""Return autoconfig record."""
return self.dnsrecord_set.filter(type="autoconfig").first()
@cached_property
def autodiscover_record(self):
"""Return autodiscover record."""
return self.dnsrecord_set.filter(type="autodiscover").first()
@cached_property
def allocated_quota(self):
"""Return current quota allocation."""
@@ -224,6 +240,11 @@ class Domain(AdminObject):
return 0
return int(self.used_quota / float(self.quota) * 100)
@property
def message_counter_key(self):
"""Return the key used to store messages count."""
return self.name
@property
def bind_format_dkim_public_key(self):
"""Return DKIM public key using Bind format."""
@@ -240,7 +261,7 @@ class Domain(AdminObject):
split_record.append(" \"%s\"" % record)
break
record = "\n".join(split_record)
return "{}._domainkey.{}. 10800 IN TXT (\n{})".format(
return "{}._domainkey.{}. IN TXT (\n{})".format(
self.dkim_key_selector, self.name, record)
def add_admin(self, account):
@@ -283,6 +304,9 @@ class Domain(AdminObject):
self.old_mail_homes = {
mb.id: mb.mail_home for mb in self.mailbox_set.all()
}
if self.old_dkim_key_length != self.dkim_key_length:
self.dkim_public_key = ""
self.dkim_private_key_path = ""
super(Domain, self).save(*args, **kwargs)
def delete(self, fromuser, keepdir=False):

Some files were not shown because too many files have changed in this diff Show More