Merge branch 'master' into feat-abstract-db
This commit is contained in:
@@ -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():
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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') }}">
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
}
|
||||
|
||||
###############
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM alpine
|
||||
FROM alpine:3.7
|
||||
|
||||
RUN apk add --no-cache postfix postfix-pcre rsyslog \
|
||||
python3 py3-pip \
|
||||
|
||||
Reference in New Issue
Block a user