Merge branch 'master' into feat-abstract-db

This commit is contained in:
kaiyou
2018-09-25 21:46:12 +02:00
47 changed files with 1890 additions and 31 deletions

View File

@@ -1,3 +1,5 @@
from flask_limiter import RateLimitExceeded
from mailu import limiter
import socket
@@ -6,6 +8,14 @@ import flask
internal = flask.Blueprint('internal', __name__)
@internal.app_errorhandler(RateLimitExceeded)
def rate_limit_handler(e):
response = flask.Response()
response.headers['Auth-Status'] = 'Authentication rate limit from one source exceeded'
response.headers['Auth-Error-Code'] = '451 4.3.2'
if int(flask.request.headers['Auth-Login-Attempt']) < 10:
response.headers['Auth-Wait'] = '3'
return response
@limiter.request_filter
def whitelist_webmail():

View File

@@ -6,6 +6,7 @@ import flask_login
import flask_wtf
import re
LOCALPART_REGEX = "^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+$"
class DestinationField(fields.SelectMultipleField):
""" Allow for multiple emails selection from current user choices and
@@ -49,7 +50,7 @@ class DomainForm(flask_wtf.FlaskForm):
max_quota_bytes = fields_.IntegerSliderField(_('Maximum user quota'), default=0)
signup_enabled = fields.BooleanField(_('Enable sign-up'), default=False)
comment = fields.StringField(_('Comment'))
submit = fields.SubmitField(_('Create'))
submit = fields.SubmitField(_('Save'))
class DomainSignupForm(flask_wtf.FlaskForm):
@@ -63,18 +64,18 @@ class DomainSignupForm(flask_wtf.FlaskForm):
class AlternativeForm(flask_wtf.FlaskForm):
name = fields.StringField(_('Alternative name'), [validators.DataRequired()])
submit = fields.SubmitField(_('Create'))
submit = fields.SubmitField(_('Save'))
class RelayForm(flask_wtf.FlaskForm):
name = fields.StringField(_('Relayed domain name'), [validators.DataRequired()])
smtp = fields.StringField(_('Remote host'))
comment = fields.StringField(_('Comment'))
submit = fields.SubmitField(_('Create'))
submit = fields.SubmitField(_('Save'))
class UserForm(flask_wtf.FlaskForm):
localpart = fields.StringField(_('E-mail'), [validators.DataRequired()])
localpart = fields.StringField(_('E-mail'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)])
pw = fields.PasswordField(_('Password'), [validators.DataRequired()])
pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')])
quota_bytes = fields_.IntegerSliderField(_('Quota'), default=1000000000)
@@ -86,7 +87,7 @@ class UserForm(flask_wtf.FlaskForm):
class UserSignupForm(flask_wtf.FlaskForm):
localpart = fields.StringField(_('Email address'), [validators.DataRequired(), validators.Regexp("^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+$")])
localpart = fields.StringField(_('Email address'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)])
pw = fields.PasswordField(_('Password'), [validators.DataRequired()])
pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')])
captcha = flask_wtf.RecaptchaField()
@@ -129,7 +130,7 @@ class TokenForm(flask_wtf.FlaskForm):
ip = fields.StringField(
_('Authorized IP'), [validators.Optional(), validators.IPAddress()]
)
submit = fields.SubmitField(_('Create'))
submit = fields.SubmitField(_('Save'))
class AliasForm(flask_wtf.FlaskForm):
@@ -138,7 +139,7 @@ class AliasForm(flask_wtf.FlaskForm):
_('Use SQL LIKE Syntax (e.g. for catch-all aliases)'))
destination = DestinationField(_('Destination'))
comment = fields.StringField(_('Comment'))
submit = fields.SubmitField(_('Create'))
submit = fields.SubmitField(_('Save'))
class AdminForm(flask_wtf.FlaskForm):

View File

@@ -32,7 +32,9 @@
</a>
</li>
{% if current_user.manager_of or current_user.global_admin %}
<li class="header">{% trans %}Administration{% endtrans %}</li>
{% endif %}
{% if current_user.global_admin %}
<li>
<a href="{{ url_for('.announcement') }}">

View File

@@ -1,7 +1,7 @@
FROM alpine:edge
FROM alpine:3.7
RUN apk add --no-cache \
dovecot dovecot-pop3d dovecot-lmtpd dovecot-pigeonhole-plugin rspamd-client \
dovecot dovecot-pop3d dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-fts-lucene rspamd-client \
python3 py3-pip \
&& pip3 install jinja2 podop

View File

@@ -7,6 +7,20 @@ postmaster_address = {{ POSTMASTER }}@{{ DOMAIN }}
hostname = {{ HOSTNAMES.split(",")[0] }}
submission_host = {{ FRONT_ADDRESS }}
###############
# Full-text search
###############
mail_plugins = $mail_plugins fts fts_lucene
plugin {
fts = lucene
fts_autoindex = yes
fts_autoindex_exclude = \Junk
fts_lucene = whitespace_chars=@.
}
###############
# Mailboxes
###############
@@ -21,7 +35,7 @@ mail_access_groups = mail
maildir_stat_dirs = yes
mailbox_list_index = yes
mail_vsize_bg_after_count = 100
mail_plugins = $mail_plugins quota quota_clone
mail_plugins = $mail_plugins quota quota_clone zlib
namespace inbox {
inbox = yes
@@ -37,6 +51,14 @@ plugin {
quota = count:User quota
quota_vsizes = yes
quota_clone_dict = proxy:/tmp/podop.socket:quota
{% if COMPRESSION in [ 'gz', 'bz2' ] %}
zlib_save = {{ COMPRESSION }}
{% endif %}
{% if COMPRESSION_LEVEL %}
zlib_save_level = {{ COMPRESSION_LEVEL }}
{% endif %}
}
###############

View File

@@ -1,3 +1,11 @@
require "vnd.dovecot.execute";
require ["vnd.dovecot.execute", "copy", "imapsieve", "environment", "variables"];
if environment :matches "imap.mailbox" "*" {
set "mailbox" "${1}";
}
if string "${mailbox}" "Trash" {
stop;
}
execute :pipe "mailtrain" "ham";

View File

@@ -1,4 +1,4 @@
FROM alpine:edge
FROM alpine:3.7
RUN apk add --no-cache nginx nginx-mod-mail python py-jinja2 certbot openssl

View File

@@ -84,15 +84,17 @@ http {
# Actual logic
{% if WEBMAIL != 'none' %}
{% if WEB_WEBMAIL != '/' %}
location / {
return 301 {{ WEB_WEBMAIL }};
}
{% endif %}
location {{ WEB_WEBMAIL }} {
rewrite ^({{ WEB_WEBMAIL }})$ $1/ permanent;
rewrite ^{{ WEB_WEBMAIL }}/(.*) /$1 break;
include /etc/nginx/proxy.conf;
client_max_body_size 30M;
client_max_body_size {{ MESSAGE_SIZE_LIMIT|int + 8388608 }};
proxy_pass http://$webmail;
}
{% endif %}

View File

@@ -1,4 +1,4 @@
FROM alpine
FROM alpine:3.7
RUN apk add --no-cache postfix postfix-pcre rsyslog \
python3 py3-pip \