mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2024-12-25 07:47:05 +00:00
Merge branch 'master' of supplee.net:mailinabox-quota
This commit is contained in:
commit
e0626a4304
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ tools/__pycache__/
|
|||||||
externals/
|
externals/
|
||||||
.env
|
.env
|
||||||
.vagrant
|
.vagrant
|
||||||
|
.idea/
|
||||||
|
43
CHANGELOG.md
43
CHANGELOG.md
@ -1,6 +1,49 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
v0.43 (September 1, 2019)
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Security fixes:
|
||||||
|
|
||||||
|
* A security issue was discovered in rsync backups. If you have enabled rsync backups, the file `id_rsa_miab` may have been copied to your backup destination. This file can be used to access your backup destination. If the file was copied to your backup destination, we recommend that you delete the file on your backup destination, delete `/root/.ssh/id_rsa_miab` on your Mail-in-a-Box, then re-run Mail-in-a-Box setup, and re-configure your SSH public key at your backup destination according to the instructions in the Mail-in-a-Box control panel.
|
||||||
|
* Brute force attack prevention was missing for the managesieve service.
|
||||||
|
|
||||||
|
Setup:
|
||||||
|
|
||||||
|
* Nextcloud was not upgraded properly after restoring Mail-in-a-Box from a backup from v0.40 or earlier.
|
||||||
|
|
||||||
|
Mail:
|
||||||
|
|
||||||
|
* Upgraded Roundcube to 1.3.10.
|
||||||
|
* Fetch an updated whitelist for greylisting on a monthly basis to reduce the number of delayed incoming emails.
|
||||||
|
|
||||||
|
Control panel:
|
||||||
|
|
||||||
|
* When using secondary DNS, it is now possible to specify a subnet range with the `xfr:` option.
|
||||||
|
* Fixed an issue when the secondary DNS option is used and the secondary DNS hostname resolves to multiple IP addresses.
|
||||||
|
* Fix a bug in how a backup configuration error is shown.
|
||||||
|
|
||||||
|
v0.42b (August 3, 2019)
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* Decreased the minimum supported RAM to 502 Mb.
|
||||||
|
* Improved mail client autoconfiguration.
|
||||||
|
* Added support for S3-compatible backup services besides Amazon S3.
|
||||||
|
* Fixed the control panel login page to let LastPass save passwords.
|
||||||
|
* Fixed an error in the user privileges API.
|
||||||
|
* Silenced some spurrious messages.
|
||||||
|
|
||||||
|
Software updates:
|
||||||
|
|
||||||
|
* Upgraded Roundcube from 1.3.8 to 1.3.9.
|
||||||
|
* Upgraded Nextcloud from 14.0.6 to 15.0.8 (with Contacts from 2.1.8 to 3.1.1 and Calendar from 1.6.4 to 1.6.5).
|
||||||
|
* Upgraded Z-Push from 2.4.4 to 2.5.0.
|
||||||
|
|
||||||
|
Note that v0.42 (July 4, 2019) was pulled shortly after it was released to fix a Nextcloud upgrade issue.
|
||||||
|
|
||||||
v0.41 (February 26, 2019)
|
v0.41 (February 26, 2019)
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
26
README.md
26
README.md
@ -20,8 +20,8 @@ Mailbox size recalculation by Dovecot can be forced using the command:
|
|||||||
Please report any bugs on github.
|
Please report any bugs on github.
|
||||||
|
|
||||||
|
|
||||||
Installing v0.41-quota
|
Installing v0.42b-quota
|
||||||
----------------------
|
-----------------------
|
||||||
|
|
||||||
To install the latest version, log into your box and execute the following commands:
|
To install the latest version, log into your box and execute the following commands:
|
||||||
|
|
||||||
@ -34,8 +34,8 @@ Follow the standard directions for setting up an MiaB installation. There are n
|
|||||||
The default quota is set to `0` which means unlimited. If you want to set a different default quota, follow the directions above.
|
The default quota is set to `0` which means unlimited. If you want to set a different default quota, follow the directions above.
|
||||||
|
|
||||||
|
|
||||||
Upgrading v0.41 to v.0.41-quota
|
Upgrading v0.4x to v.0.42b-quota
|
||||||
-------------------------------
|
--------------------------------
|
||||||
|
|
||||||
This is experimental software. You have been warned.
|
This is experimental software. You have been warned.
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ This is experimental software. You have been warned.
|
|||||||
|
|
||||||
|
|
||||||
Upgrading MiaB with quotas to a New Version
|
Upgrading MiaB with quotas to a New Version
|
||||||
---------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
* `cd` into the `mailinabox` directory.
|
* `cd` into the `mailinabox` directory.
|
||||||
|
|
||||||
@ -69,6 +69,18 @@ Issues
|
|||||||
Changes
|
Changes
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
### v0.43-quota-0.19-beta
|
||||||
|
|
||||||
|
* Add user quota API documentation to the mail users page
|
||||||
|
|
||||||
|
### v0.43-quota-0.18-beta
|
||||||
|
|
||||||
|
* Update to v0.43 of Mail-in-a-Box
|
||||||
|
|
||||||
|
### v0.42b-quota-0.18-beta
|
||||||
|
|
||||||
|
* Update to v0.42b of Mail-in-a-Box
|
||||||
|
|
||||||
### v0.41-quota-0.18-beta
|
### v0.41-quota-0.18-beta
|
||||||
|
|
||||||
* Bump version to add a new annotated tag. The last version had a plain tag which is not seen when checking for the latest version.
|
* Bump version to add a new annotated tag. The last version had a plain tag which is not seen when checking for the latest version.
|
||||||
@ -198,7 +210,7 @@ by him:
|
|||||||
$ curl -s https://keybase.io/joshdata/key.asc | gpg --import
|
$ curl -s https://keybase.io/joshdata/key.asc | gpg --import
|
||||||
gpg: key C10BDD81: public key "Joshua Tauberer <jt@occams.info>" imported
|
gpg: key C10BDD81: public key "Joshua Tauberer <jt@occams.info>" imported
|
||||||
|
|
||||||
$ git verify-tag v0.41
|
$ git verify-tag v0.43
|
||||||
gpg: Signature made ..... using RSA key ID C10BDD81
|
gpg: Signature made ..... using RSA key ID C10BDD81
|
||||||
gpg: Good signature from "Joshua Tauberer <jt@occams.info>"
|
gpg: Good signature from "Joshua Tauberer <jt@occams.info>"
|
||||||
gpg: WARNING: This key is not certified with a trusted signature!
|
gpg: WARNING: This key is not certified with a trusted signature!
|
||||||
@ -211,7 +223,7 @@ and on his [personal homepage](https://razor.occams.info/). (Of course, if this
|
|||||||
|
|
||||||
Checkout the tag corresponding to the most recent release:
|
Checkout the tag corresponding to the most recent release:
|
||||||
|
|
||||||
$ git checkout v0.41
|
$ git checkout v0.43
|
||||||
|
|
||||||
Begin the installation.
|
Begin the installation.
|
||||||
|
|
||||||
|
1
conf/cronjob/dovecot
Normal file
1
conf/cronjob/dovecot
Normal file
@ -0,0 +1 @@
|
|||||||
|
/usr/bin/doveadm fts rescan -A
|
2
conf/cronjob/solr
Normal file
2
conf/cronjob/solr
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*/1 * * * * root /usr/bin/curl http://127.0.0.1:8080/solr/update?commit=true &>/dev/null
|
||||||
|
30 3 * * * root /usr/bin/curl http://127.0.0.1:8080/solr/update?optimize=true &>/dev/null
|
@ -1,4 +1,4 @@
|
|||||||
# Fail2Ban filter Dovecot authentication and pop3/imap server
|
# Fail2Ban filter Dovecot authentication and pop3/imap/managesieve server
|
||||||
# For Mail-in-a-Box
|
# For Mail-in-a-Box
|
||||||
|
|
||||||
[INCLUDES]
|
[INCLUDES]
|
||||||
@ -9,7 +9,7 @@ before = common.conf
|
|||||||
|
|
||||||
_daemon = (auth|dovecot(-auth)?|auth-worker)
|
_daemon = (auth|dovecot(-auth)?|auth-worker)
|
||||||
|
|
||||||
failregex = ^%(__prefix_line)s(pop3|imap)-login: (Info: )?(Aborted login|Disconnected)(: Inactivity)? \(((no auth attempts|auth failed, \d+ attempts)( in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<\S*>,)?( method=\S+,)? rip=<HOST>, lip=(\d{1,3}\.){3}\d{1,3}(, TLS( handshaking)?(: Disconnected)?)?(, session=<\S+>)?\s*$
|
failregex = ^%(__prefix_line)s(pop3|imap|managesieve)-login: (Info: )?(Aborted login|Disconnected)(: Inactivity)? \(((no auth attempts|auth failed, \d+ attempts)( in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<\S*>,)?( method=\S+,)? rip=<HOST>, lip=(\d{1,3}\.){3}\d{1,3}(, TLS( handshaking)?(: Disconnected)?)?(, session=<\S+>)?\s*$
|
||||||
|
|
||||||
ignoreregex =
|
ignoreregex =
|
||||||
|
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
location = /.well-known/autoconfig/mail/config-v1.1.xml {
|
location = /.well-known/autoconfig/mail/config-v1.1.xml {
|
||||||
alias /var/lib/mailinabox/mozilla-autoconfig.xml;
|
alias /var/lib/mailinabox/mozilla-autoconfig.xml;
|
||||||
}
|
}
|
||||||
|
location = /mail/config-v1.1.xml {
|
||||||
|
alias /var/lib/mailinabox/mozilla-autoconfig.xml;
|
||||||
|
}
|
||||||
|
|
||||||
# Roundcube Webmail configuration.
|
# Roundcube Webmail configuration.
|
||||||
rewrite ^/mail$ /mail/ redirect;
|
rewrite ^/mail$ /mail/ redirect;
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
rewrite ^/cloud/$ /cloud/index.php;
|
rewrite ^/cloud/$ /cloud/index.php;
|
||||||
rewrite ^/cloud/(contacts|calendar|files)$ /cloud/index.php/apps/$1/ redirect;
|
rewrite ^/cloud/(contacts|calendar|files)$ /cloud/index.php/apps/$1/ redirect;
|
||||||
rewrite ^(/cloud/core/doc/[^\/]+/)$ $1/index.html;
|
rewrite ^(/cloud/core/doc/[^\/]+/)$ $1/index.html;
|
||||||
|
rewrite ^(/cloud/oc[sm]-provider)/$ $1/index.php redirect;
|
||||||
location /cloud/ {
|
location /cloud/ {
|
||||||
alias /usr/local/lib/owncloud/;
|
alias /usr/local/lib/owncloud/;
|
||||||
location ~ ^/cloud/(build|tests|config|lib|3rdparty|templates|data|README)/ {
|
location ~ ^/cloud/(build|tests|config|lib|3rdparty|templates|data|README)/ {
|
||||||
@ -27,6 +28,14 @@
|
|||||||
location ~ ^/cloud/(?:\.|autotest|occ|issue|indie|db_|console) {
|
location ~ ^/cloud/(?:\.|autotest|occ|issue|indie|db_|console) {
|
||||||
deny all;
|
deny all;
|
||||||
}
|
}
|
||||||
|
# Enable paths for service and cloud federation discovery
|
||||||
|
# Resolves warning in Nextcloud Settings panel
|
||||||
|
location ~ ^/cloud/(oc[sm]-provider)?/([^/]+\.php)$ {
|
||||||
|
index index.php;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME /usr/local/lib/owncloud/$1/$2;
|
||||||
|
fastcgi_pass php-fpm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
location ~ ^(/cloud)((?:/ocs)?/[^/]+\.php)(/.*)?$ {
|
location ~ ^(/cloud)((?:/ocs)?/[^/]+\.php)(/.*)?$ {
|
||||||
# note: ~ has precendence over a regular location block
|
# note: ~ has precendence over a regular location block
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
## $HOSTNAME
|
## $HOSTNAME
|
||||||
|
|
||||||
|
#BEGIN_HTTP
|
||||||
# Redirect all HTTP to HTTPS *except* the ACME challenges (Let's Encrypt TLS certificate
|
# Redirect all HTTP to HTTPS *except* the ACME challenges (Let's Encrypt TLS certificate
|
||||||
# domain validation challenges) path, which must be served over HTTP per the ACME spec
|
# domain validation challenges) path, which must be served over HTTP per the ACME spec
|
||||||
# (due to some Apache vulnerability).
|
# (due to some Apache vulnerability).
|
||||||
@ -28,11 +29,12 @@ server {
|
|||||||
alias $STORAGE_ROOT/ssl/lets_encrypt/webroot/.well-known/acme-challenge/;
|
alias $STORAGE_ROOT/ssl/lets_encrypt/webroot/.well-known/acme-challenge/;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#END_HTTP
|
||||||
|
|
||||||
# The secure HTTPS server.
|
# The secure HTTPS server.
|
||||||
server {
|
server {
|
||||||
listen 443 ssl http2;
|
listen $HTTP_SSL_PORT ssl http2;
|
||||||
listen [::]:443 ssl http2;
|
listen [::]:$HTTP_SSL_PORT ssl http2;
|
||||||
|
|
||||||
server_name $HOSTNAME;
|
server_name $HOSTNAME;
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ class KeyAuthService:
|
|||||||
|
|
||||||
credentials = decode(credentials)
|
credentials = decode(credentials)
|
||||||
if ":" not in credentials:
|
if ":" not in credentials:
|
||||||
return None, None
|
return credentials, None
|
||||||
username, password = credentials.split(':', maxsplit=1)
|
username, password = credentials.split(':', maxsplit=1)
|
||||||
return username, password
|
return username, password
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@ from exclusiveprocess import Lock
|
|||||||
from utils import load_environment, shell, wait_for_service, fix_boto
|
from utils import load_environment, shell, wait_for_service, fix_boto
|
||||||
|
|
||||||
rsync_ssh_options = [
|
rsync_ssh_options = [
|
||||||
"--ssh-options='-i /root/.ssh/id_rsa_miab'",
|
"--ssh-options= -i /root/.ssh/id_rsa_miab",
|
||||||
"--rsync-options=-e \"/usr/bin/ssh -oStrictHostKeyChecking=no -oBatchMode=yes -p 22 -i /root/.ssh/id_rsa_miab\"",
|
"--rsync-options= -e \"/usr/bin/ssh -oStrictHostKeyChecking=no -oBatchMode=yes -p 22 -i /root/.ssh/id_rsa_miab\"",
|
||||||
]
|
]
|
||||||
|
|
||||||
def backup_status(env):
|
def backup_status(env):
|
||||||
@ -406,7 +406,7 @@ def list_target_files(config):
|
|||||||
reason = "Provided path {} is invalid.".format(target_path)
|
reason = "Provided path {} is invalid.".format(target_path)
|
||||||
elif 'Network is unreachable' in listing:
|
elif 'Network is unreachable' in listing:
|
||||||
reason = "The IP address {} is unreachable.".format(target.hostname)
|
reason = "The IP address {} is unreachable.".format(target.hostname)
|
||||||
elif 'Could not resolve hostname':
|
elif 'Could not resolve hostname' in listing:
|
||||||
reason = "The hostname {} cannot be resolved.".format(target.hostname)
|
reason = "The hostname {} cannot be resolved.".format(target.hostname)
|
||||||
else:
|
else:
|
||||||
reason = "Unknown error." \
|
reason = "Unknown error." \
|
||||||
@ -419,15 +419,22 @@ def list_target_files(config):
|
|||||||
fix_boto() # must call prior to importing boto
|
fix_boto() # must call prior to importing boto
|
||||||
import boto.s3
|
import boto.s3
|
||||||
from boto.exception import BotoServerError
|
from boto.exception import BotoServerError
|
||||||
|
custom_region = False
|
||||||
for region in boto.s3.regions():
|
for region in boto.s3.regions():
|
||||||
if region.endpoint == target.hostname:
|
if region.endpoint == target.hostname:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid S3 region/host.")
|
# If region is not found this is a custom region
|
||||||
|
custom_region = True
|
||||||
|
|
||||||
bucket = target.path[1:].split('/')[0]
|
bucket = target.path[1:].split('/')[0]
|
||||||
path = '/'.join(target.path[1:].split('/')[1:]) + '/'
|
path = '/'.join(target.path[1:].split('/')[1:]) + '/'
|
||||||
|
|
||||||
|
# Create a custom region with custom endpoint
|
||||||
|
if custom_region:
|
||||||
|
from boto.s3.connection import S3Connection
|
||||||
|
region = boto.s3.S3RegionInfo(name=bucket, endpoint=target.hostname, connection_cls=S3Connection)
|
||||||
|
|
||||||
# If no prefix is specified, set the path to '', otherwise boto won't list the files
|
# If no prefix is specified, set the path to '', otherwise boto won't list the files
|
||||||
if path == '/':
|
if path == '/':
|
||||||
path = ''
|
path = ''
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import os, os.path, re, json, time
|
import os, os.path, re, json, time
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import base64
|
||||||
|
import sys
|
||||||
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
@ -347,6 +349,34 @@ def dns_get_dump():
|
|||||||
from dns_update import build_recommended_dns
|
from dns_update import build_recommended_dns
|
||||||
return json_response(build_recommended_dns(env))
|
return json_response(build_recommended_dns(env))
|
||||||
|
|
||||||
|
@app.route('/letsencrypt/dns-auth/<domain>/<token>', methods=['GET'])
|
||||||
|
@authorized_personnel_only
|
||||||
|
def letsencrypt_dns_auth(domain, token):
|
||||||
|
from dns_update import do_dns_update, set_custom_dns_record
|
||||||
|
try:
|
||||||
|
qname = '_acme-challenge.' + domain
|
||||||
|
if set_custom_dns_record(qname, 'TXT', token, 'add', env):
|
||||||
|
if not do_dns_update(env):
|
||||||
|
return ("Error updating DNS", 400)
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
return (str(e), 400)
|
||||||
|
|
||||||
|
@app.route('/letsencrypt/dns-cleanup/<domain>', methods=['GET'])
|
||||||
|
@authorized_personnel_only
|
||||||
|
def letsencrypt_dns_cleanup(domain):
|
||||||
|
from dns_update import do_dns_update, set_custom_dns_record
|
||||||
|
try:
|
||||||
|
qname = '_acme-challenge.' + domain
|
||||||
|
if set_custom_dns_record(qname, 'TXT', None, 'remove', env):
|
||||||
|
if not do_dns_update(env):
|
||||||
|
return ("Error updating DNS", 400)
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
return (str(e), 400)
|
||||||
|
|
||||||
# SSL
|
# SSL
|
||||||
|
|
||||||
@app.route('/ssl/status')
|
@app.route('/ssl/status')
|
||||||
@ -543,28 +573,62 @@ def privacy_status_set():
|
|||||||
utils.write_settings(config, env)
|
utils.write_settings(config, env)
|
||||||
return "OK"
|
return "OK"
|
||||||
|
|
||||||
|
|
||||||
|
# Quotas
|
||||||
|
|
||||||
@app.route('/system/default-quota', methods=["GET"])
|
@app.route('/system/default-quota', methods=["GET"])
|
||||||
@authorized_personnel_only
|
@authorized_personnel_only
|
||||||
def default_quota_get():
|
def default_quota_get():
|
||||||
if request.values.get('text'):
|
if request.values.get('text'):
|
||||||
return get_default_quota(env)
|
return get_default_quota(env)
|
||||||
else:
|
else:
|
||||||
return json_response({
|
return json_response({
|
||||||
"default-quota": get_default_quota(env),
|
"default-quota": get_default_quota(env),
|
||||||
})
|
})
|
||||||
|
|
||||||
@app.route('/system/default-quota', methods=["POST"])
|
@app.route('/system/default-quota', methods=["POST"])
|
||||||
@authorized_personnel_only
|
@authorized_personnel_only
|
||||||
def default_quota_set():
|
def default_quota_set():
|
||||||
config = utils.load_settings(env)
|
config = utils.load_settings(env)
|
||||||
try:
|
try:
|
||||||
config["default-quota"] = validate_quota(request.values.get('default_quota'))
|
config["default-quota"] = validate_quota(request.values.get('default_quota'))
|
||||||
utils.write_settings(config, env)
|
utils.write_settings(config, env)
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return ("ERROR: %s" % str(e), 400)
|
return ("ERROR: %s" % str(e), 400)
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
|
||||||
|
# Mailgraph
|
||||||
|
|
||||||
|
@app.route('/mailgraph/image.cgi', methods=['GET'])
|
||||||
|
@authorized_personnel_only
|
||||||
|
def mailgraph():
|
||||||
|
if request.query_string:
|
||||||
|
query = request.query_string.decode('utf-8', 'ignore')
|
||||||
|
if '&' in query:
|
||||||
|
query = query.split('&')[0]
|
||||||
|
|
||||||
|
print("QUERY_STRING=%s" % query, file=sys.stderr)
|
||||||
|
|
||||||
|
code, bin_out = utils.shell(
|
||||||
|
"check_output",
|
||||||
|
["/usr/share/mailgraph/mailgraph.cgi"],
|
||||||
|
env={"QUERY_STRING": query},
|
||||||
|
return_bytes=True,
|
||||||
|
trap=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if code != 0:
|
||||||
|
return ('Error generating mailgraph image: %s' % query, 500)
|
||||||
|
|
||||||
|
headers, image_bytes = bin_out.split(b'\n\n', 1)
|
||||||
|
|
||||||
|
return base64.b64encode(image_bytes)
|
||||||
|
|
||||||
|
return ('Mailgraph: no image requested', 500)
|
||||||
|
|
||||||
return "OK"
|
|
||||||
|
|
||||||
# MUNIN
|
# MUNIN
|
||||||
|
|
||||||
|
@ -288,6 +288,21 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
|
|||||||
if not has_rec(qname, "SRV"):
|
if not has_rec(qname, "SRV"):
|
||||||
records.append((qname, "SRV", "0 0 443 " + env["PRIMARY_HOSTNAME"] + ".", "Recommended. Specifies the hostname of the server that handles CardDAV/CalDAV services for email addresses on this domain."))
|
records.append((qname, "SRV", "0 0 443 " + env["PRIMARY_HOSTNAME"] + ".", "Recommended. Specifies the hostname of the server that handles CardDAV/CalDAV services for email addresses on this domain."))
|
||||||
|
|
||||||
|
# Adds autoconfiguration A records for all domains.
|
||||||
|
# This allows the following clients to automatically configure email addresses in the respective applications.
|
||||||
|
# autodiscover.* - Z-Push ActiveSync Autodiscover
|
||||||
|
# autoconfig.* - Thunderbird Autoconfig
|
||||||
|
autodiscover_records = [
|
||||||
|
("autodiscover", "A", env["PUBLIC_IP"], "Provides email configuration autodiscovery support for Z-Push ActiveSync Autodiscover."),
|
||||||
|
("autodiscover", "AAAA", env["PUBLIC_IPV6"], "Provides email configuration autodiscovery support for Z-Push ActiveSync Autodiscover."),
|
||||||
|
("autoconfig", "A", env["PUBLIC_IP"], "Provides email configuration autodiscovery support for Thunderbird Autoconfig."),
|
||||||
|
("autoconfig", "AAAA", env["PUBLIC_IPV6"], "Provides email configuration autodiscovery support for Thunderbird Autoconfig.")
|
||||||
|
]
|
||||||
|
for qname, rtype, value, explanation in autodiscover_records:
|
||||||
|
if value is None or value.strip() == "": continue # skip IPV6 if not set
|
||||||
|
if not has_rec(qname, rtype):
|
||||||
|
records.append((qname, rtype, value, explanation))
|
||||||
|
|
||||||
# Sort the records. The None records *must* go first in the nsd zone file. Otherwise it doesn't matter.
|
# Sort the records. The None records *must* go first in the nsd zone file. Otherwise it doesn't matter.
|
||||||
records.sort(key = lambda rec : list(reversed(rec[0].split(".")) if rec[0] is not None else ""))
|
records.sort(key = lambda rec : list(reversed(rec[0].split(".")) if rec[0] is not None else ""))
|
||||||
|
|
||||||
@ -888,10 +903,14 @@ def set_secondary_dns(hostnames, env):
|
|||||||
else:
|
else:
|
||||||
# Validate IP address.
|
# Validate IP address.
|
||||||
try:
|
try:
|
||||||
v = ipaddress.ip_address(item[4:]) # raises a ValueError if there's a problem
|
if "/" in item[4:]:
|
||||||
if not isinstance(v, ipaddress.IPv4Address): raise ValueError("That's an IPv6 address.")
|
v = ipaddress.ip_network(item[4:]) # raises a ValueError if there's a problem
|
||||||
|
if not isinstance(v, ipaddress.IPv4Network): raise ValueError("That's an IPv6 subnet.")
|
||||||
|
else:
|
||||||
|
v = ipaddress.ip_address(item[4:]) # raises a ValueError if there's a problem
|
||||||
|
if not isinstance(v, ipaddress.IPv4Address): raise ValueError("That's an IPv6 address.")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError("'%s' is not an IPv4 address." % item[4:])
|
raise ValueError("'%s' is not an IPv4 address or subnet." % item[4:])
|
||||||
|
|
||||||
# Set.
|
# Set.
|
||||||
set_custom_dns_record("_secondary_nameserver", "A", " ".join(hostnames), "set", env)
|
set_custom_dns_record("_secondary_nameserver", "A", " ".join(hostnames), "set", env)
|
||||||
|
@ -195,7 +195,7 @@ def get_mail_users_ex(env, with_archived=False):
|
|||||||
if email in active_accounts: continue
|
if email in active_accounts: continue
|
||||||
user = {
|
user = {
|
||||||
"email": email,
|
"email": email,
|
||||||
"privileges": "",
|
"privileges": [],
|
||||||
"status": "inactive",
|
"status": "inactive",
|
||||||
"mailbox": mbox,
|
"mailbox": mbox,
|
||||||
}
|
}
|
||||||
|
@ -313,6 +313,7 @@ def provision_certificates(env, limit_domains):
|
|||||||
webroot = os.path.join(account_path, 'webroot')
|
webroot = os.path.join(account_path, 'webroot')
|
||||||
os.makedirs(webroot, exist_ok=True)
|
os.makedirs(webroot, exist_ok=True)
|
||||||
with tempfile.TemporaryDirectory() as d:
|
with tempfile.TemporaryDirectory() as d:
|
||||||
|
miab_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||||
cert_file = os.path.join(d, 'cert_and_chain.pem')
|
cert_file = os.path.join(d, 'cert_and_chain.pem')
|
||||||
print("Provisioning TLS certificates for " + ", ".join(domain_list) + ".")
|
print("Provisioning TLS certificates for " + ", ".join(domain_list) + ".")
|
||||||
certbotret = subprocess.check_output([
|
certbotret = subprocess.check_output([
|
||||||
@ -328,7 +329,10 @@ def provision_certificates(env, limit_domains):
|
|||||||
"--chain-path", os.path.join(d, 'chain'), # we only use the full chain
|
"--chain-path", os.path.join(d, 'chain'), # we only use the full chain
|
||||||
"--fullchain-path", cert_file,
|
"--fullchain-path", cert_file,
|
||||||
|
|
||||||
"--webroot", "--webroot-path", webroot,
|
"--manual",
|
||||||
|
"--preferred-challenge", "dns",
|
||||||
|
"--manual-auth-hook", os.path.join(miab_dir, "tools/dns-auth.sh"),
|
||||||
|
"--manual-cleanup-hook", os.path.join(miab_dir, "tools/dns-cleanup.sh"),
|
||||||
|
|
||||||
"--config-dir", account_path,
|
"--config-dir", account_path,
|
||||||
#"--staging",
|
#"--staging",
|
||||||
|
@ -39,6 +39,7 @@ def get_services():
|
|||||||
{ "name": "Mail Filters (Sieve/dovecot)", "port": 4190, "public": True, },
|
{ "name": "Mail Filters (Sieve/dovecot)", "port": 4190, "public": True, },
|
||||||
{ "name": "HTTP Web (nginx)", "port": 80, "public": True, },
|
{ "name": "HTTP Web (nginx)", "port": 80, "public": True, },
|
||||||
{ "name": "HTTPS Web (nginx)", "port": 443, "public": True, },
|
{ "name": "HTTPS Web (nginx)", "port": 443, "public": True, },
|
||||||
|
{ "name": "Solr Full Text Search (tomcat)", "port": 8080, "public": False, },
|
||||||
]
|
]
|
||||||
|
|
||||||
def run_checks(rounded_values, env, output, pool):
|
def run_checks(rounded_values, env, output, pool):
|
||||||
@ -487,10 +488,12 @@ def check_dns_zone(domain, env, output, dns_zonefiles):
|
|||||||
if custom_secondary_ns and not probably_external_dns:
|
if custom_secondary_ns and not probably_external_dns:
|
||||||
for ns in custom_secondary_ns:
|
for ns in custom_secondary_ns:
|
||||||
# We must first resolve the nameserver to an IP address so we can query it.
|
# We must first resolve the nameserver to an IP address so we can query it.
|
||||||
ns_ip = query_dns(ns, "A")
|
ns_ips = query_dns(ns, "A")
|
||||||
if not ns_ip:
|
if not ns_ips:
|
||||||
output.print_error("Secondary nameserver %s is not valid (it doesn't resolve to an IP address)." % ns)
|
output.print_error("Secondary nameserver %s is not valid (it doesn't resolve to an IP address)." % ns)
|
||||||
continue
|
continue
|
||||||
|
# Choose the first IP if nameserver returns multiple
|
||||||
|
ns_ip = ns_ips.split('; ')[0]
|
||||||
|
|
||||||
# Now query it to see what it says about this domain.
|
# Now query it to see what it says about this domain.
|
||||||
ip = query_dns(domain, "A", at=ns_ip, nxdomain=None)
|
ip = query_dns(domain, "A", at=ns_ip, nxdomain=None)
|
||||||
|
@ -90,7 +90,7 @@
|
|||||||
<div class="col-sm-offset-1 col-sm-11">
|
<div class="col-sm-offset-1 col-sm-11">
|
||||||
<p class="small">
|
<p class="small">
|
||||||
Multiple secondary servers can be separated with commas or spaces (i.e., <code>ns2.hostingcompany.com ns3.hostingcompany.com</code>).
|
Multiple secondary servers can be separated with commas or spaces (i.e., <code>ns2.hostingcompany.com ns3.hostingcompany.com</code>).
|
||||||
To enable zone transfers to additional servers without listing them as secondary nameservers, add <code>xfr:IPADDRESS</code>.
|
To enable zone transfers to additional servers without listing them as secondary nameservers, add an IP address or subnet using <code>xfr:10.20.30.40</code> or <code>xfr:10.20.30.40/24</code>.
|
||||||
</p>
|
</p>
|
||||||
<p id="secondarydns-clear-instructions" style="display: none" class="small">
|
<p id="secondarydns-clear-instructions" style="display: none" class="small">
|
||||||
Clear the input field above and click Update to use this machine itself as secondary DNS, which is the default/normal setup.
|
Clear the input field above and click Update to use this machine itself as secondary DNS, which is the default/normal setup.
|
||||||
|
@ -102,6 +102,7 @@
|
|||||||
<li><a href="#mail-guide" onclick="return show_panel(this);">Instructions</a></li>
|
<li><a href="#mail-guide" onclick="return show_panel(this);">Instructions</a></li>
|
||||||
<li><a href="#users" onclick="return show_panel(this);">Users</a></li>
|
<li><a href="#users" onclick="return show_panel(this);">Users</a></li>
|
||||||
<li><a href="#aliases" onclick="return show_panel(this);">Aliases</a></li>
|
<li><a href="#aliases" onclick="return show_panel(this);">Aliases</a></li>
|
||||||
|
<li><a href="#mailgraph" onclick="return show_panel(this);">Mailgraph</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#sync_guide" onclick="return show_panel(this);">Contacts/Calendar</a></li>
|
<li><a href="#sync_guide" onclick="return show_panel(this);">Contacts/Calendar</a></li>
|
||||||
@ -151,6 +152,10 @@
|
|||||||
{% include "sync-guide.html" %}
|
{% include "sync-guide.html" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="panel_mailgraph" class="admin_panel">
|
||||||
|
{% include "mailgraph.html" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="panel_web" class="admin_panel">
|
<div id="panel_web" class="admin_panel">
|
||||||
{% include "web.html" %}
|
{% include "web.html" %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,7 +23,7 @@ sudo tools/mail.py user make-admin me@{{hostname}}</pre>
|
|||||||
<p style="margin: 2em; text-align: center;">Log in here for your Mail-in-a-Box control panel.</p>
|
<p style="margin: 2em; text-align: center;">Log in here for your Mail-in-a-Box control panel.</p>
|
||||||
|
|
||||||
<div style="margin: 0 auto; max-width: 32em;">
|
<div style="margin: 0 auto; max-width: 32em;">
|
||||||
<form class="form-horizontal" role="form" onsubmit="do_login(); return false;">
|
<form class="form-horizontal" role="form" onsubmit="do_login(); return false;" method="get">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputEmail3" class="col-sm-3 control-label">Email</label>
|
<label for="inputEmail3" class="col-sm-3 control-label">Email</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
|
48
management/templates/mailgraph.html
Normal file
48
management/templates/mailgraph.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<h2>Mail statistics</h2>
|
||||||
|
<ul id="jump">
|
||||||
|
<li><a href="#G0">Day</a> </li>
|
||||||
|
<li><a href="#G1">Week</a> </li>
|
||||||
|
<li><a href="#G2">Month</a> </li>
|
||||||
|
<li><a href="#G3">Year</a> </li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="G0">Last Day</h3>
|
||||||
|
<p><img src="" data-src="/mailgraph/image.cgi?0-n" alt="mailgraph"/></p>
|
||||||
|
<p><img src="" data-src="/mailgraph/image.cgi?0-e" alt="mailgraph"/></p>
|
||||||
|
<p><img src="" data-src="/mailgraph/image.cgi?0-g" alt="mailgraph"/></p>
|
||||||
|
|
||||||
|
<h3 id="G1">Last Week</h3>
|
||||||
|
<p><img src="" data-src="/mailgraph/image.cgi?1-n" alt="mailgraph"/></p>
|
||||||
|
<p><img src="" data-src="/mailgraph/image.cgi?1-e" alt="mailgraph"/></p>
|
||||||
|
<p><img src="" data-src="/mailgraph/image.cgi?1-g" alt="mailgraph"/></p>
|
||||||
|
|
||||||
|
<h3 id="G2">Last Month</h3>
|
||||||
|
<p><img src="" data-src="/mailgraph/image.cgi?2-n" alt="mailgraph"/></p>
|
||||||
|
<p><img src="" data-src="/mailgraph/image.cgi?2-e" alt="mailgraph"/></p>
|
||||||
|
<p><img src="" data-src="/mailgraph/image.cgi?2-g" alt="mailgraph"/></p>
|
||||||
|
|
||||||
|
<h3 id="G3">Last Year</h3>
|
||||||
|
<p><img src="" data-src="/mailgraph/image.cgi?3-n" alt="mailgraph"/></p>
|
||||||
|
<p><img src="" data-src="/mailgraph/image.cgi?3-e" alt="mailgraph"/></p>
|
||||||
|
<p><img src="" data-src="/mailgraph/image.cgi?3-g" alt="mailgraph"/></p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p><a href="http://mailgraph.schweikert.ch/">Mailgraph</a> 1.14 by <a href="http://david.schweikert.ch/">David Schweikert</a>
|
||||||
|
(built on Tobi Oetiker's <a href="http://oss.oetiker.ch/rrdtool/">RRDtool</a>)</p>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
function show_mailgraph() {
|
||||||
|
$('[data-src]').each(function() {
|
||||||
|
var that = this;
|
||||||
|
api(
|
||||||
|
$(that).attr('data-src'),
|
||||||
|
'GET',
|
||||||
|
'',
|
||||||
|
function(data) {
|
||||||
|
$(that).attr('src', 'data:image/gif;base64,' + data);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
@ -77,15 +77,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group backup-target-s3">
|
<div class="form-group backup-target-s3">
|
||||||
<label for="backup-target-s3-host" class="col-sm-2 control-label">S3 Region</label>
|
<label for="backup-target-s3-host-select" class="col-sm-2 control-label">S3 Region</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<select class="form-control" rows="1" id="backup-target-s3-host">
|
<select class="form-control" rows="1" id="backup-target-s3-host-select">
|
||||||
{% for name, host in backup_s3_hosts %}
|
{% for name, host in backup_s3_hosts %}
|
||||||
<option value="{{host}}">{{name}}</option>
|
<option value="{{host}}">{{name}}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
<option value="other">Other</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group backup-target-s3">
|
||||||
|
<label for="backup-target-s3-host" class="col-sm-2 control-label">S3 Host / Endpoint</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input type="text" placeholder="Endpoint" class="form-control" rows="1" id="backup-target-s3-host">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group backup-target-s3">
|
<div class="form-group backup-target-s3">
|
||||||
<label for="backup-target-s3-path" class="col-sm-2 control-label">S3 Path</label>
|
<label for="backup-target-s3-path" class="col-sm-2 control-label">S3 Path</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
@ -139,6 +146,8 @@ function toggle_form() {
|
|||||||
var target_type = $("#backup-target-type").val();
|
var target_type = $("#backup-target-type").val();
|
||||||
$(".backup-target-local, .backup-target-rsync, .backup-target-s3").hide();
|
$(".backup-target-local, .backup-target-rsync, .backup-target-s3").hide();
|
||||||
$(".backup-target-" + target_type).show();
|
$(".backup-target-" + target_type).show();
|
||||||
|
|
||||||
|
init_inputs(target_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
function nice_size(bytes) {
|
function nice_size(bytes) {
|
||||||
@ -278,4 +287,20 @@ function set_custom_backup() {
|
|||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function init_inputs(target_type) {
|
||||||
|
function set_host(host) {
|
||||||
|
if(host !== 'other') {
|
||||||
|
$("#backup-target-s3-host").val(host);
|
||||||
|
} else {
|
||||||
|
$("#backup-target-s3-host").val('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (target_type == "s3") {
|
||||||
|
$('#backup-target-s3-host-select').off('change').on('change', function() {
|
||||||
|
set_host($('#backup-target-s3-host-select').val());
|
||||||
|
});
|
||||||
|
set_host($('#backup-target-s3-host-select').val());
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -117,10 +117,24 @@
|
|||||||
<table class="table" style="margin-top: .5em">
|
<table class="table" style="margin-top: .5em">
|
||||||
<thead><th>Verb</th> <th>Action</th><th></th></thead>
|
<thead><th>Verb</th> <th>Action</th><th></th></thead>
|
||||||
<tr><td>GET</td><td><i>(none)</i></td> <td>Returns a list of existing mail users. Adding <code>?format=json</code> to the URL will give JSON-encoded results.</td></tr>
|
<tr><td>GET</td><td><i>(none)</i></td> <td>Returns a list of existing mail users. Adding <code>?format=json</code> to the URL will give JSON-encoded results.</td></tr>
|
||||||
<tr><td>POST</td><td>/add</td> <td>Adds a new mail user. Required POST-body parameters are <code>email</code> and <code>password</code>.</td></tr>
|
<tr>
|
||||||
|
<td>POST</td>
|
||||||
|
<td>/add</td>
|
||||||
|
<td>Adds a new mail user. Required POST-body parameters are <code>email</code> and <code>password</code>. Optional parameters: <code>privilege=admin</code> and <code>quota</code></td>
|
||||||
|
</tr>
|
||||||
<tr><td>POST</td><td>/remove</td> <td>Removes a mail user. Required POST-by parameter is <code>email</code>.</td></tr>
|
<tr><td>POST</td><td>/remove</td> <td>Removes a mail user. Required POST-by parameter is <code>email</code>.</td></tr>
|
||||||
<tr><td>POST</td><td>/privileges/add</td> <td>Used to make a mail user an admin. Required POST-body parameters are <code>email</code> and <code>privilege=admin</code>.</td></tr>
|
<tr><td>POST</td><td>/privileges/add</td> <td>Used to make a mail user an admin. Required POST-body parameters are <code>email</code> and <code>privilege=admin</code>.</td></tr>
|
||||||
<tr><td>POST</td><td>/privileges/remove</td> <td>Used to remove the admin privilege from a mail user. Required POST-body parameter is <code>email</code>.</td></tr>
|
<tr><td>POST</td><td>/privileges/remove</td> <td>Used to remove the admin privilege from a mail user. Required POST-body parameter is <code>email</code>.</td></tr>
|
||||||
|
<tr>
|
||||||
|
<td>GET</td>
|
||||||
|
<td>/quota</td>
|
||||||
|
<td>Get the quota for a mail user. Required POST-body parameters are <code>email</code> and will return JSON result</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>POST</td>
|
||||||
|
<td>/quota</td>
|
||||||
|
<td>Set the quota for a mail user. Required POST-body parameters are <code>email</code> and <code>quota</code>.</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<h4>Examples:</h4>
|
<h4>Examples:</h4>
|
||||||
|
@ -29,6 +29,12 @@ def get_web_domains(env, include_www_redirects=True, exclude_dns_elsewhere=True)
|
|||||||
# IP address than this box. Remove those domains from our list.
|
# IP address than this box. Remove those domains from our list.
|
||||||
domains -= get_domains_with_a_records(env)
|
domains -= get_domains_with_a_records(env)
|
||||||
|
|
||||||
|
# Add Autoconfiguration domains, allowing us to serve correct SSL certs.
|
||||||
|
# 'autoconfig.' for Mozilla Thunderbird auto setup.
|
||||||
|
# 'autodiscover.' for Activesync autodiscovery.
|
||||||
|
domains |= set('autoconfig.' + maildomain for maildomain in get_mail_domains(env))
|
||||||
|
domains |= set('autodiscover.' + maildomain for maildomain in get_mail_domains(env))
|
||||||
|
|
||||||
# Ensure the PRIMARY_HOSTNAME is in the list so we can serve webmail
|
# Ensure the PRIMARY_HOSTNAME is in the list so we can serve webmail
|
||||||
# as well as Z-Push for Exchange ActiveSync. This can't be removed
|
# as well as Z-Push for Exchange ActiveSync. This can't be removed
|
||||||
# by a custom A/AAAA record and is never a 'www.' redirect.
|
# by a custom A/AAAA record and is never a 'www.' redirect.
|
||||||
@ -94,6 +100,20 @@ def do_web_update(env):
|
|||||||
# Add default 'www.' redirect.
|
# Add default 'www.' redirect.
|
||||||
nginx_conf += make_domain_config(domain, [template0, template3], ssl_certificates, env)
|
nginx_conf += make_domain_config(domain, [template0, template3], ssl_certificates, env)
|
||||||
|
|
||||||
|
if str(env['HTTP_SSL_PORT']) != "443":
|
||||||
|
in_http = False
|
||||||
|
new_conf = ''
|
||||||
|
for line in nginx_conf.split('\n'):
|
||||||
|
if line.strip() == '#BEGIN_HTTP':
|
||||||
|
in_http = True
|
||||||
|
elif line.strip() == '#END_HTTP':
|
||||||
|
in_http = False
|
||||||
|
|
||||||
|
if not in_http:
|
||||||
|
new_conf += line + '\n'
|
||||||
|
|
||||||
|
nginx_conf = new_conf
|
||||||
|
|
||||||
# Did the file change? If not, don't bother writing & restarting nginx.
|
# Did the file change? If not, don't bother writing & restarting nginx.
|
||||||
nginx_conf_fn = "/etc/nginx/conf.d/local.conf"
|
nginx_conf_fn = "/etc/nginx/conf.d/local.conf"
|
||||||
if os.path.exists(nginx_conf_fn):
|
if os.path.exists(nginx_conf_fn):
|
||||||
@ -178,8 +198,12 @@ def make_domain_config(domain, templates, ssl_certificates, env):
|
|||||||
nginx_conf = re.sub("[ \t]*# ADDITIONAL DIRECTIVES HERE *\n", t, nginx_conf)
|
nginx_conf = re.sub("[ \t]*# ADDITIONAL DIRECTIVES HERE *\n", t, nginx_conf)
|
||||||
|
|
||||||
# Replace substitution strings in the template & return.
|
# Replace substitution strings in the template & return.
|
||||||
|
if int(env['HTTP_SSL_PORT']) != 443:
|
||||||
|
# disable the regular HTTP server
|
||||||
|
nginx_conf = re.sub(r'#BEGIN_HTTP.*?#END_HTTP', repl='', string=nginx_conf, flags=re.MULTILINE)
|
||||||
nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
|
nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
|
||||||
nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
|
nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
|
||||||
|
nginx_conf = nginx_conf.replace("$HTTP_SSL_PORT", env['HTTP_SSL_PORT'])
|
||||||
nginx_conf = nginx_conf.replace("$ROOT", root)
|
nginx_conf = nginx_conf.replace("$ROOT", root)
|
||||||
nginx_conf = nginx_conf.replace("$SSL_KEY", tls_cert["private-key"])
|
nginx_conf = nginx_conf.replace("$SSL_KEY", tls_cert["private-key"])
|
||||||
nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", tls_cert["certificate"])
|
nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", tls_cert["certificate"])
|
||||||
|
11
requirements.txt
Normal file
11
requirements.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
rtyaml
|
||||||
|
email_validator>=1.0.0
|
||||||
|
exclusiveprocess
|
||||||
|
flask
|
||||||
|
dnspython
|
||||||
|
python-dateutil
|
||||||
|
idna>=2.0.0
|
||||||
|
cryptography==2.2.2
|
||||||
|
boto
|
||||||
|
psutil
|
||||||
|
npyscreen
|
@ -20,7 +20,7 @@ if [ -z "$TAG" ]; then
|
|||||||
# want to display in status checks.
|
# want to display in status checks.
|
||||||
if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then
|
if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then
|
||||||
# This machine is running Ubuntu 18.04.
|
# This machine is running Ubuntu 18.04.
|
||||||
TAG=v0.41-quota-0.18-beta
|
TAG=v0.43-quota-0.19-beta
|
||||||
|
|
||||||
elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then
|
elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then
|
||||||
# This machine is running Ubuntu 14.04.
|
# This machine is running Ubuntu 14.04.
|
||||||
|
@ -127,7 +127,7 @@ function get_default_privateip {
|
|||||||
if [ "$1" == "6" ]; then target=2001:4860:4860::8888; fi
|
if [ "$1" == "6" ]; then target=2001:4860:4860::8888; fi
|
||||||
|
|
||||||
# Get the route information.
|
# Get the route information.
|
||||||
route=$(ip -$1 -o route get $target | grep -v unreachable)
|
route=$(ip -$1 -o route get $target 2>/dev/null | grep -v unreachable)
|
||||||
|
|
||||||
# Parse the address out of the route information.
|
# Parse the address out of the route information.
|
||||||
address=$(echo $route | sed "s/.* src \([^ ]*\).*/\1/")
|
address=$(echo $route | sed "s/.* src \([^ ]*\).*/\1/")
|
||||||
|
@ -82,11 +82,12 @@ tools/editconf.py /etc/dovecot/conf.d/10-auth.conf \
|
|||||||
# Enable SSL, specify the location of the SSL certificate and private key files.
|
# Enable SSL, specify the location of the SSL certificate and private key files.
|
||||||
# Disable obsolete SSL protocols and allow only good ciphers per http://baldric.net/2013/12/07/tls-ciphers-in-postfix-and-dovecot/.
|
# Disable obsolete SSL protocols and allow only good ciphers per http://baldric.net/2013/12/07/tls-ciphers-in-postfix-and-dovecot/.
|
||||||
# Enable strong ssl dh parameters
|
# Enable strong ssl dh parameters
|
||||||
|
|
||||||
tools/editconf.py /etc/dovecot/conf.d/10-ssl.conf \
|
tools/editconf.py /etc/dovecot/conf.d/10-ssl.conf \
|
||||||
ssl=required \
|
ssl=required \
|
||||||
"ssl_cert=<$STORAGE_ROOT/ssl/ssl_certificate.pem" \
|
"ssl_cert=<$STORAGE_ROOT/ssl/ssl_certificate.pem" \
|
||||||
"ssl_key=<$STORAGE_ROOT/ssl/ssl_private_key.pem" \
|
"ssl_key=<$STORAGE_ROOT/ssl/ssl_private_key.pem" \
|
||||||
"ssl_protocols=!SSLv3 !SSLv2" \
|
"ssl_protocols=!SSLv3" \
|
||||||
"ssl_cipher_list=ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS" \
|
"ssl_cipher_list=ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS" \
|
||||||
"ssl_prefer_server_ciphers = yes" \
|
"ssl_prefer_server_ciphers = yes" \
|
||||||
"ssl_dh_parameters_length = 2048"
|
"ssl_dh_parameters_length = 2048"
|
||||||
@ -137,6 +138,14 @@ service lmtp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Enable imap-login on localhost to allow the user_external plugin
|
||||||
|
# for Nextcloud to do imap authentication. (See #1577)
|
||||||
|
service imap-login {
|
||||||
|
inet_listener imap {
|
||||||
|
address = 127.0.0.1
|
||||||
|
port = 143
|
||||||
|
}
|
||||||
|
}
|
||||||
protocol imap {
|
protocol imap {
|
||||||
mail_max_userip_connections = 20
|
mail_max_userip_connections = 20
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,8 @@ source /etc/mailinabox.conf # load global vars
|
|||||||
# * `ca-certificates`: A trust store used to squelch postfix warnings about
|
# * `ca-certificates`: A trust store used to squelch postfix warnings about
|
||||||
# untrusted opportunistically-encrypted connections.
|
# untrusted opportunistically-encrypted connections.
|
||||||
echo "Installing Postfix (SMTP server)..."
|
echo "Installing Postfix (SMTP server)..."
|
||||||
apt_install postfix postfix-sqlite postfix-pcre postgrey ca-certificates
|
apt_install postfix postfix-sqlite postfix-pcre postgrey ca-certificates \
|
||||||
|
postfix-policyd-spf-python postsrsd
|
||||||
|
|
||||||
# ### Basic Settings
|
# ### Basic Settings
|
||||||
|
|
||||||
@ -97,7 +98,9 @@ tools/editconf.py /etc/postfix/master.cf -s -w \
|
|||||||
-o cleanup_service_name=authclean" \
|
-o cleanup_service_name=authclean" \
|
||||||
"authclean=unix n - - - 0 cleanup
|
"authclean=unix n - - - 0 cleanup
|
||||||
-o header_checks=pcre:/etc/postfix/outgoing_mail_header_filters
|
-o header_checks=pcre:/etc/postfix/outgoing_mail_header_filters
|
||||||
-o nested_header_checks="
|
-o nested_header_checks=" \
|
||||||
|
"policy-spf=unix - n n - - spawn
|
||||||
|
user=nobody argv=/usr/bin/policyd-spf"
|
||||||
|
|
||||||
# Install the `outgoing_mail_header_filters` file required by the new 'authclean' service.
|
# Install the `outgoing_mail_header_filters` file required by the new 'authclean' service.
|
||||||
cp conf/postfix_outgoing_mail_header_filters /etc/postfix/outgoing_mail_header_filters
|
cp conf/postfix_outgoing_mail_header_filters /etc/postfix/outgoing_mail_header_filters
|
||||||
@ -196,9 +199,23 @@ tools/editconf.py /etc/postfix/main.cf lmtp_destination_recipient_limit=1
|
|||||||
# so these IPs get mail delivered quickly. But when an IP is not listed in the permit_dnswl_client list (i.e. it is not #NODOC
|
# so these IPs get mail delivered quickly. But when an IP is not listed in the permit_dnswl_client list (i.e. it is not #NODOC
|
||||||
# whitelisted) then postfix does a DEFER_IF_REJECT, which results in all "unknown user" sorts of messages turning into #NODOC
|
# whitelisted) then postfix does a DEFER_IF_REJECT, which results in all "unknown user" sorts of messages turning into #NODOC
|
||||||
# "450 4.7.1 Client host rejected: Service unavailable". This is a retry code, so the mail doesn't properly bounce. #NODOC
|
# "450 4.7.1 Client host rejected: Service unavailable". This is a retry code, so the mail doesn't properly bounce. #NODOC
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
|
||||||
smtpd_sender_restrictions="reject_non_fqdn_sender,reject_unknown_sender_domain,reject_authenticated_sender_login_mismatch,reject_rhsbl_sender dbl.spamhaus.org" \
|
postconf -e smtpd_sender_restrictions="reject_non_fqdn_sender,reject_unknown_sender_domain,reject_authenticated_sender_login_mismatch,reject_rhsbl_sender dbl.spamhaus.org"
|
||||||
smtpd_recipient_restrictions=permit_sasl_authenticated,permit_mynetworks,"reject_rbl_client zen.spamhaus.org",reject_unlisted_recipient,"check_policy_service inet:127.0.0.1:10023","check_policy_service inet:127.0.0.1:12340"
|
|
||||||
|
RECIPIENT_RESTRICTIONS="permit_sasl_authenticated,permit_mynetworks,reject_rbl_client zen.spamhaus.org,reject_unlisted_recipient"
|
||||||
|
|
||||||
|
if [ $POSTGREY == 1 ]; then
|
||||||
|
RECIPIENT_RESTRICTIONS="${RECIPIENT_RESTRICTIONS},check_policy_service inet:127.0.0.1:10023"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $POLICY_SPF == 1 ]; then
|
||||||
|
RECIPIENT_RESTRICTIONS="${RECIPIENT_RESTRICTIONS},check_policy_service unix:private/policy-spf"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add quota check
|
||||||
|
RECIPIENT_RESTRICTIONS="${RECIPIENT_RESTRICTIONS},check_policy_service inet:127.0.0.1:12340"
|
||||||
|
|
||||||
|
postconf -e smtpd_recipient_restrictions="$RECIPIENT_RESTRICTIONS"
|
||||||
|
|
||||||
# Postfix connects to Postgrey on the 127.0.0.1 interface specifically. Ensure that
|
# Postfix connects to Postgrey on the 127.0.0.1 interface specifically. Ensure that
|
||||||
# Postgrey listens on the same interface (and not IPv6, for instance).
|
# Postgrey listens on the same interface (and not IPv6, for instance).
|
||||||
@ -208,13 +225,63 @@ tools/editconf.py /etc/postfix/main.cf \
|
|||||||
# e-mails really latter, delay of greylisting has been set to
|
# e-mails really latter, delay of greylisting has been set to
|
||||||
# 180 seconds (default is 300 seconds).
|
# 180 seconds (default is 300 seconds).
|
||||||
tools/editconf.py /etc/default/postgrey \
|
tools/editconf.py /etc/default/postgrey \
|
||||||
POSTGREY_OPTS=\"'--inet=127.0.0.1:10023 --delay=180'\"
|
POSTGREY_OPTS=\"'--inet=127.0.0.1:10023 --delay=180 --whitelist-recipients=/etc/postgrey/whitelist_clients'\"
|
||||||
|
|
||||||
|
|
||||||
|
# We are going to setup a newer whitelist for postgrey, the version included in the distribution is old
|
||||||
|
cat > /etc/cron.daily/mailinabox-postgrey-whitelist << EOF;
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Mail-in-a-Box
|
||||||
|
|
||||||
|
# check we have a postgrey_whitelist_clients file and that it is not older than 28 days
|
||||||
|
if [ ! -f /etc/postgrey/whitelist_clients ] || find /etc/postgrey/whitelist_clients -mtime +28 > /dev/null ; then
|
||||||
|
# ok we need to update the file, so lets try to fetch it
|
||||||
|
if curl https://postgrey.schweikert.ch/pub/postgrey_whitelist_clients --output /tmp/postgrey_whitelist_clients -sS --fail > /dev/null 2>&1 ; then
|
||||||
|
# if fetching hasn't failed yet then check it is a plain text file
|
||||||
|
# curl manual states that --fail sometimes still produces output
|
||||||
|
# this final check will at least check the output is not html
|
||||||
|
# before moving it into place
|
||||||
|
if [ "\$(file -b --mime-type /tmp/postgrey_whitelist_clients)" == "text/plain" ]; then
|
||||||
|
mv /tmp/postgrey_whitelist_clients /etc/postgrey/whitelist_clients
|
||||||
|
service postgrey restart
|
||||||
|
else
|
||||||
|
rm /tmp/postgrey_whitelist_clients
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
chmod +x /etc/cron.daily/mailinabox-postgrey-whitelist
|
||||||
|
/etc/cron.daily/mailinabox-postgrey-whitelist
|
||||||
|
|
||||||
# Increase the message size limit from 10MB to 128MB.
|
# Increase the message size limit from 10MB to 128MB.
|
||||||
# The same limit is specified in nginx.conf for mail submitted via webmail and Z-Push.
|
# The same limit is specified in nginx.conf for mail submitted via webmail and Z-Push.
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
tools/editconf.py /etc/postfix/main.cf \
|
||||||
message_size_limit=134217728
|
message_size_limit=134217728
|
||||||
|
|
||||||
|
if [ $POSTSRSD == 1 ]; then
|
||||||
|
# Setup SRS
|
||||||
|
postconf -e \
|
||||||
|
sender_canonical_maps=tcp:localhost:10001 \
|
||||||
|
sender_canonical_classes=envelope_sender \
|
||||||
|
recipient_canonical_maps=tcp:localhost:10002 \
|
||||||
|
recipient_canonical_classes=envelope_recipient,header_recipient
|
||||||
|
|
||||||
|
hide_output systemctl enable postsrsd
|
||||||
|
hide_output systemctl restart postsrsd
|
||||||
|
|
||||||
|
else
|
||||||
|
postconf -e \
|
||||||
|
sender_canonical_maps= \
|
||||||
|
sender_canonical_classes= \
|
||||||
|
recipient_canonical_maps= \
|
||||||
|
recipient_canonical_classes=
|
||||||
|
|
||||||
|
hide_output systemctl disable postsrsd
|
||||||
|
hide_output systemctl stop postsrsd
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
# Allow the two SMTP ports in the firewall.
|
# Allow the two SMTP ports in the firewall.
|
||||||
|
|
||||||
ufw_allow smtp
|
ufw_allow smtp
|
||||||
@ -223,4 +290,11 @@ ufw_allow submission
|
|||||||
# Restart services
|
# Restart services
|
||||||
|
|
||||||
restart_service postfix
|
restart_service postfix
|
||||||
restart_service postgrey
|
|
||||||
|
if [ $POSTGREY == 1 ]; then
|
||||||
|
hide_output systemctl enable postgrey
|
||||||
|
hide_output systemctl restart postgrey
|
||||||
|
else
|
||||||
|
hide_output systemctl disable postgrey
|
||||||
|
hide_output systemctl stop postgrey
|
||||||
|
fi
|
||||||
|
@ -38,7 +38,7 @@ inst_dir=/usr/local/lib/mailinabox
|
|||||||
mkdir -p $inst_dir
|
mkdir -p $inst_dir
|
||||||
venv=$inst_dir/env
|
venv=$inst_dir/env
|
||||||
if [ ! -d $venv ]; then
|
if [ ! -d $venv ]; then
|
||||||
virtualenv -ppython3 $venv
|
hide_output virtualenv -ppython3 $venv
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Upgrade pip because the Ubuntu-packaged version is out of date.
|
# Upgrade pip because the Ubuntu-packaged version is out of date.
|
||||||
|
@ -76,4 +76,8 @@ restart_service munin-node
|
|||||||
# generate initial statistics so the directory isn't empty
|
# generate initial statistics so the directory isn't empty
|
||||||
# (We get "Pango-WARNING **: error opening config file '/root/.config/pango/pangorc': Permission denied"
|
# (We get "Pango-WARNING **: error opening config file '/root/.config/pango/pangorc': Permission denied"
|
||||||
# if we don't explicitly set the HOME directory when sudo'ing.)
|
# if we don't explicitly set the HOME directory when sudo'ing.)
|
||||||
sudo -H -u munin munin-cron
|
# We check to see if munin-cron is already running, if it is, there is no need to run it simultaneously
|
||||||
|
# generating an error.
|
||||||
|
if [ ! -f /var/run/munin/munin-update.lock ]; then
|
||||||
|
sudo -H -u munin munin-cron
|
||||||
|
fi
|
||||||
|
@ -13,7 +13,8 @@ apt-get purge -qq -y owncloud* # we used to use the package manager
|
|||||||
|
|
||||||
apt_install php php-fpm \
|
apt_install php php-fpm \
|
||||||
php-cli php-sqlite3 php-gd php-imap php-curl php-pear curl \
|
php-cli php-sqlite3 php-gd php-imap php-curl php-pear curl \
|
||||||
php-dev php-gd php-xml php-mbstring php-zip php-apcu php-json php-intl
|
php-dev php-gd php-xml php-mbstring php-zip php-apcu php-json \
|
||||||
|
php-intl php-imagick
|
||||||
|
|
||||||
InstallNextcloud() {
|
InstallNextcloud() {
|
||||||
|
|
||||||
@ -24,12 +25,12 @@ InstallNextcloud() {
|
|||||||
echo "Upgrading to Nextcloud version $version"
|
echo "Upgrading to Nextcloud version $version"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
# Download and verify
|
||||||
|
wget_verify https://download.nextcloud.com/server/releases/nextcloud-$version.zip $hash /tmp/nextcloud.zip
|
||||||
|
|
||||||
# Remove the current owncloud/Nextcloud
|
# Remove the current owncloud/Nextcloud
|
||||||
rm -rf /usr/local/lib/owncloud
|
rm -rf /usr/local/lib/owncloud
|
||||||
|
|
||||||
# Download and verify
|
|
||||||
wget_verify https://download.nextcloud.com/server/releases/nextcloud-$version.zip $hash /tmp/nextcloud.zip
|
|
||||||
|
|
||||||
# Extract ownCloud/Nextcloud
|
# Extract ownCloud/Nextcloud
|
||||||
unzip -q /tmp/nextcloud.zip -d /usr/local/lib
|
unzip -q /tmp/nextcloud.zip -d /usr/local/lib
|
||||||
mv /usr/local/lib/nextcloud /usr/local/lib/owncloud
|
mv /usr/local/lib/nextcloud /usr/local/lib/owncloud
|
||||||
@ -39,14 +40,22 @@ InstallNextcloud() {
|
|||||||
# their github repositories.
|
# their github repositories.
|
||||||
mkdir -p /usr/local/lib/owncloud/apps
|
mkdir -p /usr/local/lib/owncloud/apps
|
||||||
|
|
||||||
wget_verify https://github.com/nextcloud/contacts/releases/download/v2.1.8/contacts.tar.gz b5d5bbee33f0c32b124b46cb6aaab90c695ac170 /tmp/contacts.tgz
|
wget_verify https://github.com/nextcloud/contacts/releases/download/v3.1.1/contacts.tar.gz a06bd967197dcb03c94ec1dbd698c037018669e5 /tmp/contacts.tgz
|
||||||
tar xf /tmp/contacts.tgz -C /usr/local/lib/owncloud/apps/
|
tar xf /tmp/contacts.tgz -C /usr/local/lib/owncloud/apps/
|
||||||
rm /tmp/contacts.tgz
|
rm /tmp/contacts.tgz
|
||||||
|
|
||||||
wget_verify https://github.com/nextcloud/calendar/releases/download/v1.6.4/calendar.tar.gz d8a7950dba14803472b6c19625a8ceb23d6fd4ef /tmp/calendar.tgz
|
wget_verify https://github.com/nextcloud/calendar/releases/download/v1.6.5/calendar.tar.gz 79941255521a5172f7e4ce42dc7773838b5ede2f /tmp/calendar.tgz
|
||||||
tar xf /tmp/calendar.tgz -C /usr/local/lib/owncloud/apps/
|
tar xf /tmp/calendar.tgz -C /usr/local/lib/owncloud/apps/
|
||||||
rm /tmp/calendar.tgz
|
rm /tmp/calendar.tgz
|
||||||
|
|
||||||
|
# Starting with Nextcloud 15, the app user_external is no longer included in Nextcloud core,
|
||||||
|
# we will install from their github repository.
|
||||||
|
if [[ $version =~ ^15 ]]; then
|
||||||
|
wget_verify https://github.com/nextcloud/user_external/releases/download/v0.6.3/user_external-0.6.3.tar.gz 0f756d35fef6b64a177d6a16020486b76ea5799c /tmp/user_external.tgz
|
||||||
|
tar -xf /tmp/user_external.tgz -C /usr/local/lib/owncloud/apps/
|
||||||
|
rm /tmp/user_external.tgz
|
||||||
|
fi
|
||||||
|
|
||||||
# Fix weird permissions.
|
# Fix weird permissions.
|
||||||
chmod 750 /usr/local/lib/owncloud/{apps,config}
|
chmod 750 /usr/local/lib/owncloud/{apps,config}
|
||||||
|
|
||||||
@ -75,15 +84,32 @@ InstallNextcloud() {
|
|||||||
|
|
||||||
# Add missing indices. NextCloud didn't include this in the normal upgrade because it might take some time.
|
# Add missing indices. NextCloud didn't include this in the normal upgrade because it might take some time.
|
||||||
sudo -u www-data php /usr/local/lib/owncloud/occ db:add-missing-indices
|
sudo -u www-data php /usr/local/lib/owncloud/occ db:add-missing-indices
|
||||||
|
|
||||||
|
# Run conversion to BigInt identifiers, this process may take some time on large tables.
|
||||||
|
sudo -u www-data php /usr/local/lib/owncloud/occ db:convert-filecache-bigint --no-interaction
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
nextcloud_ver=14.0.6
|
# Nextcloud Version to install. Checks are done down below to step through intermediate versions.
|
||||||
nextcloud_hash=4e43a57340f04c2da306c8eea98e30040399ae5a
|
nextcloud_ver=15.0.8
|
||||||
|
nextcloud_hash=4129d8d4021c435f2e86876225fb7f15adf764a3
|
||||||
|
|
||||||
# Check if Nextcloud dir exist, and check if version matches nextcloud_ver (if either doesn't - install/upgrade)
|
# Current Nextcloud Version, #1623
|
||||||
if [ ! -d /usr/local/lib/owncloud/ ] \
|
# Checking /usr/local/lib/owncloud/version.php shows version of the Nextcloud application, not the DB
|
||||||
|| ! grep -q $nextcloud_ver /usr/local/lib/owncloud/version.php; then
|
# $STORAGE_ROOT/owncloud is kept together even during a backup. It is better to rely on config.php than
|
||||||
|
# version.php since the restore procedure can leave the system in a state where you have a newer Nextcloud
|
||||||
|
# application version than the database.
|
||||||
|
|
||||||
|
# If config.php exists, get version number, otherwise CURRENT_NEXTCLOUD_VER is empty.
|
||||||
|
if [ -f "$STORAGE_ROOT/owncloud/config.php" ]; then
|
||||||
|
CURRENT_NEXTCLOUD_VER=$(php -r "include(\"$STORAGE_ROOT/owncloud/config.php\"); echo(\$CONFIG['version']);")
|
||||||
|
else
|
||||||
|
CURRENT_NEXTCLOUD_VER=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If the Nextcloud directory is missing (never been installed before, or the nextcloud version to be installed is different
|
||||||
|
# from the version currently installed, do the install/upgrade
|
||||||
|
if [ ! -d /usr/local/lib/owncloud/ ] || [[ ! ${CURRENT_NEXTCLOUD_VER} =~ ^$nextcloud_ver ]]; then
|
||||||
|
|
||||||
# Stop php-fpm if running. If theyre not running (which happens on a previously failed install), dont bail.
|
# Stop php-fpm if running. If theyre not running (which happens on a previously failed install), dont bail.
|
||||||
service php7.2-fpm stop &> /dev/null || /bin/true
|
service php7.2-fpm stop &> /dev/null || /bin/true
|
||||||
@ -104,16 +130,22 @@ if [ ! -d /usr/local/lib/owncloud/ ] \
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# If ownCloud or Nextcloud was previously installed....
|
# If ownCloud or Nextcloud was previously installed....
|
||||||
if [ -e /usr/local/lib/owncloud/version.php ]; then
|
if [ ! -z ${CURRENT_NEXTCLOUD_VER} ]; then
|
||||||
# Database migrations from ownCloud are no longer possible because ownCloud cannot be run under
|
# Database migrations from ownCloud are no longer possible because ownCloud cannot be run under
|
||||||
# PHP 7.
|
# PHP 7.
|
||||||
if grep -q "OC_VersionString = '[89]\." /usr/local/lib/owncloud/version.php; then
|
if [[ ${CURRENT_NEXTCLOUD_VER} =~ ^[89] ]]; then
|
||||||
echo "Upgrades from Mail-in-a-Box prior to v0.28 (dated July 30, 2018) with Nextcloud < 13.0.6 (you have ownCloud 8 or 9) are not supported. Upgrade to Mail-in-a-Box version v0.30 first. Setup aborting."
|
echo "Upgrades from Mail-in-a-Box prior to v0.28 (dated July 30, 2018) with Nextcloud < 13.0.6 (you have ownCloud 8 or 9) are not supported. Upgrade to Mail-in-a-Box version v0.30 first. Setup aborting."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
elif [[ ${CURRENT_NEXTCLOUD_VER} =~ ^1[012] ]]; then
|
||||||
if grep -q "OC_VersionString = '1[012]\." /usr/local/lib/owncloud/version.php; then
|
|
||||||
echo "Upgrades from Mail-in-a-Box prior to v0.28 (dated July 30, 2018) with Nextcloud < 13.0.6 (you have ownCloud 10, 11 or 12) are not supported. Upgrade to Mail-in-a-Box version v0.30 first. Setup aborting."
|
echo "Upgrades from Mail-in-a-Box prior to v0.28 (dated July 30, 2018) with Nextcloud < 13.0.6 (you have ownCloud 10, 11 or 12) are not supported. Upgrade to Mail-in-a-Box version v0.30 first. Setup aborting."
|
||||||
exit 1
|
exit 1
|
||||||
|
elif [[ ${CURRENT_NEXTCLOUD_VER} =~ ^13 ]]; then
|
||||||
|
# If we are running Nextcloud 13, upgrade to Nextcloud 14
|
||||||
|
InstallNextcloud 14.0.6 4e43a57340f04c2da306c8eea98e30040399ae5a
|
||||||
|
elif [[ ${CURRENT_NEXTCLOUD_VER} =~ ^14 ]]; then
|
||||||
|
# During the upgrade from Nextcloud 14 to 15, user_external may cause the upgrade to fail.
|
||||||
|
# We will disable it here before the upgrade and install it again after the upgrade.
|
||||||
|
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:disable user_external
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -142,10 +174,12 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
|||||||
'overwritewebroot' => '/cloud',
|
'overwritewebroot' => '/cloud',
|
||||||
'overwrite.cli.url' => '/cloud',
|
'overwrite.cli.url' => '/cloud',
|
||||||
'user_backends' => array(
|
'user_backends' => array(
|
||||||
array(
|
array(
|
||||||
'class'=>'OC_User_IMAP',
|
'class' => 'OC_User_IMAP',
|
||||||
'arguments'=>array('{127.0.0.1:993/imap/ssl/novalidate-cert}')
|
'arguments' => array(
|
||||||
)
|
'127.0.0.1', 143, null
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
'memcache.local' => '\OC\Memcache\APCu',
|
'memcache.local' => '\OC\Memcache\APCu',
|
||||||
'mail_smtpmode' => 'sendmail',
|
'mail_smtpmode' => 'sendmail',
|
||||||
@ -217,6 +251,8 @@ include("$STORAGE_ROOT/owncloud/config.php");
|
|||||||
|
|
||||||
\$CONFIG['mail_domain'] = '$PRIMARY_HOSTNAME';
|
\$CONFIG['mail_domain'] = '$PRIMARY_HOSTNAME';
|
||||||
|
|
||||||
|
\$CONFIG['user_backends'] = array(array('class' => 'OC_User_IMAP','arguments' => array('127.0.0.1', 143, null),),);
|
||||||
|
|
||||||
echo "<?php\n\\\$CONFIG = ";
|
echo "<?php\n\\\$CONFIG = ";
|
||||||
var_export(\$CONFIG);
|
var_export(\$CONFIG);
|
||||||
echo ";";
|
echo ";";
|
||||||
|
93
setup/options-dialog.py
Normal file
93
setup/options-dialog.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
import npyscreen
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class OptionsApp(npyscreen.NPSApp):
|
||||||
|
def main(self):
|
||||||
|
# These lines create the form and populate it with widgets.
|
||||||
|
# A fairly complex screen in only 8 or so lines of code - a line for each control.
|
||||||
|
npyscreen.setTheme(npyscreen.Themes.BlackOnWhiteTheme)
|
||||||
|
|
||||||
|
form = npyscreen.Form(name = "Mail-in-a-Box Options",)
|
||||||
|
form.add(
|
||||||
|
npyscreen.TitleFixedText,
|
||||||
|
name="POSTGREY",
|
||||||
|
value="",
|
||||||
|
editable=False
|
||||||
|
)
|
||||||
|
form.add(
|
||||||
|
npyscreen.MultiLineEdit,
|
||||||
|
value="The Postgrey service greylists incoming messages from unknown senders.\n"
|
||||||
|
"It can be useful for fighting spam but often causes message delivery\n"
|
||||||
|
"delays of several minutes.",
|
||||||
|
max_height=4,
|
||||||
|
editable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
form.add(
|
||||||
|
npyscreen.TitleFixedText,
|
||||||
|
name="POSTSRSD",
|
||||||
|
value="",
|
||||||
|
editable=False
|
||||||
|
)
|
||||||
|
form.add(
|
||||||
|
npyscreen.MultiLineEdit,
|
||||||
|
value="The PostSRSd daemon performs return path rewriting using the SRS protocol.\n"
|
||||||
|
"Not that all messages, including locally delivered mail will have their return\n"
|
||||||
|
"paths rewritten",
|
||||||
|
max_height=4,
|
||||||
|
editable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
form.add(
|
||||||
|
npyscreen.TitleFixedText,
|
||||||
|
name="POLICY_SPF",
|
||||||
|
value="",
|
||||||
|
editable=False
|
||||||
|
)
|
||||||
|
form.add(
|
||||||
|
npyscreen.MultiLineEdit,
|
||||||
|
value=""
|
||||||
|
"The policy SPF service checks the SPF of incoming mails and rejects those\n"
|
||||||
|
"that do not qualify. This helps to prevent spoofing, but if valid mail does\n"
|
||||||
|
"not have SPF configured properly it will be rejected.",
|
||||||
|
max_height=4,
|
||||||
|
editable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
init_values = []
|
||||||
|
if int(os.getenv('POSTGREY', 1)) == 1:
|
||||||
|
init_values.append(0)
|
||||||
|
|
||||||
|
if int(os.getenv('POSTSRSD', 0)) == 1:
|
||||||
|
init_values.append(1)
|
||||||
|
|
||||||
|
if int(os.getenv('POLICY_SPF', 0)) == 1:
|
||||||
|
init_values.append(2)
|
||||||
|
|
||||||
|
options = form.add(
|
||||||
|
npyscreen.TitleMultiSelect,
|
||||||
|
max_height=-2,
|
||||||
|
value=init_values,
|
||||||
|
name="Options",
|
||||||
|
values= ["POSTGREY","POSTSRSD","POLICY_SPF"],
|
||||||
|
scroll_exit=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# This lets the user interact with the Form.
|
||||||
|
form.edit()
|
||||||
|
|
||||||
|
with open('_options.sh', 'w') as output:
|
||||||
|
print('POSTGREY=%i' % (1 if 0 in options.value else 0), file=output)
|
||||||
|
print('POSTSRSD=%i' % (1 if 1 in options.value else 0), file=output)
|
||||||
|
print('POLICY_SPF=%i' % (1 if 2 in options.value else 0), file=output)
|
||||||
|
# print(npyscreen.ThemeManager.default_colors, file=output)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
App = OptionsApp()
|
||||||
|
App.run()
|
@ -26,7 +26,7 @@ fi
|
|||||||
#
|
#
|
||||||
# Skip the check if we appear to be running inside of Vagrant, because that's really just for testing.
|
# Skip the check if we appear to be running inside of Vagrant, because that's really just for testing.
|
||||||
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}')
|
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}')
|
||||||
if [ $TOTAL_PHYSICAL_MEM -lt 500000 ]; then
|
if [ $TOTAL_PHYSICAL_MEM -lt 490000 ]; then
|
||||||
if [ ! -d /vagrant ]; then
|
if [ ! -d /vagrant ]; then
|
||||||
TOTAL_PHYSICAL_MEM=$(expr \( \( $TOTAL_PHYSICAL_MEM \* 1024 \) / 1000 \) / 1000)
|
TOTAL_PHYSICAL_MEM=$(expr \( \( $TOTAL_PHYSICAL_MEM \* 1024 \) / 1000 \) / 1000)
|
||||||
echo "Your Mail-in-a-Box needs more memory (RAM) to function properly."
|
echo "Your Mail-in-a-Box needs more memory (RAM) to function properly."
|
||||||
|
@ -16,6 +16,7 @@ if [ -z "${NONINTERACTIVE:-}" ]; then
|
|||||||
# we install it inside a virtualenv. In this script, we don't have the virtualenv yet
|
# we install it inside a virtualenv. In this script, we don't have the virtualenv yet
|
||||||
# so we install the python package globally.
|
# so we install the python package globally.
|
||||||
hide_output pip3 install "email_validator>=1.0.0" || exit 1
|
hide_output pip3 install "email_validator>=1.0.0" || exit 1
|
||||||
|
hide_output pip3 install npyscreen || exit 1
|
||||||
|
|
||||||
message_box "Mail-in-a-Box Installation" \
|
message_box "Mail-in-a-Box Installation" \
|
||||||
"Hello and thanks for deploying a Mail-in-a-Box!
|
"Hello and thanks for deploying a Mail-in-a-Box!
|
||||||
@ -193,6 +194,16 @@ if [ -z "${STORAGE_ROOT:-}" ]; then
|
|||||||
STORAGE_ROOT=$([[ -z "${DEFAULT_STORAGE_ROOT:-}" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT")
|
STORAGE_ROOT=$([[ -z "${DEFAULT_STORAGE_ROOT:-}" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# export options variables so they are visible to the options program
|
||||||
|
export POSTGREY
|
||||||
|
export POSTSRSD
|
||||||
|
export POLICY_SPF
|
||||||
|
|
||||||
|
python3 setup/options-dialog.py
|
||||||
|
source ./_options.sh
|
||||||
|
rm _options.sh
|
||||||
|
|
||||||
|
|
||||||
# Show the configuration, since the user may have not entered it manually.
|
# Show the configuration, since the user may have not entered it manually.
|
||||||
echo
|
echo
|
||||||
echo "Primary Hostname: $PRIMARY_HOSTNAME"
|
echo "Primary Hostname: $PRIMARY_HOSTNAME"
|
||||||
|
85
setup/solr.sh
Normal file
85
setup/solr.sh
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Inspired by the solr.sh from jkaberg (https://github.com/jkaberg/mailinabox-sogo)
|
||||||
|
# with some modifications
|
||||||
|
#
|
||||||
|
# IMAP search with lucene via solr
|
||||||
|
# --------------------------------
|
||||||
|
#
|
||||||
|
# By default dovecot uses its own Squat search index that has awful performance
|
||||||
|
# on large mailboxes. Dovecot 2.1+ has support for using Lucene internally but
|
||||||
|
# this didn't make it into the Ubuntu packages, so we use Solr instead to run
|
||||||
|
# Lucene for us.
|
||||||
|
#
|
||||||
|
# Solr runs as a tomcat process. The dovecot solr plugin talks to solr via its
|
||||||
|
# HTTP interface, causing mail to be indexed when searches occur, and getting
|
||||||
|
# results back.
|
||||||
|
|
||||||
|
source setup/functions.sh # load our functions
|
||||||
|
source /etc/mailinabox.conf # load global vars
|
||||||
|
|
||||||
|
# Install packages and basic configuation
|
||||||
|
# ---------------------------------------
|
||||||
|
|
||||||
|
echo "Installing Solr..."
|
||||||
|
|
||||||
|
# Install packages
|
||||||
|
apt_install solr-tomcat dovecot-solr
|
||||||
|
|
||||||
|
# Solr requires a schema to tell it how to index data, this is provided by dovecot
|
||||||
|
cp /usr/share/dovecot/solr-schema.xml /etc/solr/conf/schema.xml
|
||||||
|
|
||||||
|
# Update the dovecot plugin configuration
|
||||||
|
#
|
||||||
|
# Break-imap-search makes search work the way users expect, rather than the way
|
||||||
|
# the IMAP specification expects
|
||||||
|
tools/editconf.py /etc/dovecot/conf.d/10-mail.conf \
|
||||||
|
mail_plugins="fts fts_solr"
|
||||||
|
|
||||||
|
cat > /etc/dovecot/conf.d/90-plugin-fts.conf << EOF;
|
||||||
|
plugin {
|
||||||
|
fts = solr
|
||||||
|
fts_autoindex = yes
|
||||||
|
fts_solr = break-imap-search url=http://127.0.0.1:8080/solr/
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Bump memory allocation for Solr.
|
||||||
|
# Not needed? I'll let it sit here for a while.
|
||||||
|
#echo 'export JAVA_OPTS=-Xms512M -Xmx1024M' > /usr/share/tomcat7/bin/setenv.sh
|
||||||
|
|
||||||
|
# Install cronjobs to keep FTS up to date
|
||||||
|
hide_output install -m 755 conf/cronjob/dovecot /etc/cron.daily/
|
||||||
|
hide_output install -m 644 conf/cronjob/solr /etc/cron.d/
|
||||||
|
|
||||||
|
# PERMISSIONS
|
||||||
|
|
||||||
|
# Ensure configuration files are owned by dovecot and not world readable.
|
||||||
|
chown -R mail:dovecot /etc/dovecot
|
||||||
|
chmod -R o-rwx /etc/dovecot
|
||||||
|
|
||||||
|
mkdir -p /etc/systemd/system/tomcat9.service.d
|
||||||
|
cat > /etc/systemd/system/tomcat9.service.d/solr-permissions.conf << EOF
|
||||||
|
[Service]
|
||||||
|
ReadWritePaths=/var/lib/solr/
|
||||||
|
ReadWritePaths=/var/lib/solr/data/
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Restart services to reload solr schema & dovecot plugins
|
||||||
|
restart_service tomcat9
|
||||||
|
restart_service dovecot
|
||||||
|
|
||||||
|
|
||||||
|
# Kickoff building the index
|
||||||
|
|
||||||
|
# Per doveadm-fts manpage: Scan what mails exist in the full text search index
|
||||||
|
# and compare those to what actually exist in mailboxes.
|
||||||
|
# This removes mails from the index that have already been expunged and makes
|
||||||
|
# sure that the next doveadm index will index all the missing mails (if any).
|
||||||
|
doveadm fts rescan -A
|
||||||
|
|
||||||
|
# Adds unindexed files to the fts database
|
||||||
|
# * `-q`: Queues the indexing to be run by indexer process. (will background the indexing)
|
||||||
|
# * `-A`: All users
|
||||||
|
# * `'*'`: All folders
|
||||||
|
doveadm index -q -A '*'
|
@ -42,6 +42,22 @@ else
|
|||||||
FIRST_TIME_SETUP=1
|
FIRST_TIME_SETUP=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -z "${HTTP_SSL_PORT:-}" ]; then
|
||||||
|
HTTP_SSL_PORT=$([[ -z "${DEFAULT_HTTP_SSL_PORT:-}" ]] && echo "443" || echo "$DEFAULT_HTTP_SSL_PORT")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${POSTGREY:-}" ]; then
|
||||||
|
POSTGREY=$([[ -z "${DEFAULT_POSTGREY:-}" ]] && echo "1" || echo "$DEFAULT_POSTGREY")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${POSTSRSD:-}" ]; then
|
||||||
|
POSTSRSD=$([[ -z "${DEFAULT_POSTSRSD:-}" ]] && echo "0" || echo "$DEFAULT_POSTSRSD")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${POLICY_SPF:-}" ]; then
|
||||||
|
POLICY_SPF=$([[ -z "${DEFAULT_POLICY_SPF:-}" ]] && echo "0" || echo "$DEFAULT_POLICY_SPF")
|
||||||
|
fi
|
||||||
|
|
||||||
# Put a start script in a global location. We tell the user to run 'mailinabox'
|
# Put a start script in a global location. We tell the user to run 'mailinabox'
|
||||||
# in the first dialog prompt, so we should do this before that starts.
|
# in the first dialog prompt, so we should do this before that starts.
|
||||||
cat > /usr/local/bin/mailinabox << EOF;
|
cat > /usr/local/bin/mailinabox << EOF;
|
||||||
@ -93,6 +109,10 @@ PUBLIC_IP=$PUBLIC_IP
|
|||||||
PUBLIC_IPV6=$PUBLIC_IPV6
|
PUBLIC_IPV6=$PUBLIC_IPV6
|
||||||
PRIVATE_IP=$PRIVATE_IP
|
PRIVATE_IP=$PRIVATE_IP
|
||||||
PRIVATE_IPV6=$PRIVATE_IPV6
|
PRIVATE_IPV6=$PRIVATE_IPV6
|
||||||
|
HTTP_SSL_PORT=$HTTP_SSL_PORT
|
||||||
|
POSTGREY=$POSTGREY
|
||||||
|
POSTSRSD=$POSTSRSD
|
||||||
|
POLICY_SPF=$POLICY_SPF
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Start service configuration.
|
# Start service configuration.
|
||||||
@ -102,6 +122,7 @@ source setup/dns.sh
|
|||||||
source setup/mail-postfix.sh
|
source setup/mail-postfix.sh
|
||||||
source setup/mail-dovecot.sh
|
source setup/mail-dovecot.sh
|
||||||
source setup/mail-users.sh
|
source setup/mail-users.sh
|
||||||
|
source setup/solr.sh
|
||||||
source setup/dkim.sh
|
source setup/dkim.sh
|
||||||
source setup/spamassassin.sh
|
source setup/spamassassin.sh
|
||||||
source setup/web.sh
|
source setup/web.sh
|
||||||
|
17
setup/web.sh
17
setup/web.sh
@ -19,7 +19,7 @@ fi
|
|||||||
|
|
||||||
echo "Installing Nginx (web server)..."
|
echo "Installing Nginx (web server)..."
|
||||||
|
|
||||||
apt_install nginx php-cli php-fpm
|
apt_install nginx php-cli php-fpm fcgiwrap mailgraph
|
||||||
|
|
||||||
rm -f /etc/nginx/sites-enabled/default
|
rm -f /etc/nginx/sites-enabled/default
|
||||||
|
|
||||||
@ -48,6 +48,12 @@ tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
|
|||||||
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
|
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
|
||||||
default_charset="UTF-8"
|
default_charset="UTF-8"
|
||||||
|
|
||||||
|
# Set higher timeout since searches with Roundcube and Solr may take longer
|
||||||
|
# than the default 60 seconds. We will also match Roundcube's timeout to the
|
||||||
|
# same value
|
||||||
|
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
|
||||||
|
default_socket_timeout=180
|
||||||
|
|
||||||
# Switch from the dynamic process manager to the ondemand manager see #1216
|
# Switch from the dynamic process manager to the ondemand manager see #1216
|
||||||
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
||||||
pm=ondemand
|
pm=ondemand
|
||||||
@ -96,6 +102,9 @@ restart_service nginx
|
|||||||
restart_service php7.2-fpm
|
restart_service php7.2-fpm
|
||||||
|
|
||||||
# Open ports.
|
# Open ports.
|
||||||
ufw_allow http
|
if [ $HTTP_SSL_PORT == 443 ]; then
|
||||||
ufw_allow https
|
ufw_allow http
|
||||||
|
ufw_allow https
|
||||||
|
else
|
||||||
|
ufw_allow $HTTP_SSL_PORT
|
||||||
|
fi
|
||||||
|
@ -28,8 +28,8 @@ apt_install \
|
|||||||
# Install Roundcube from source if it is not already present or if it is out of date.
|
# Install Roundcube from source if it is not already present or if it is out of date.
|
||||||
# Combine the Roundcube version number with the commit hash of plugins to track
|
# Combine the Roundcube version number with the commit hash of plugins to track
|
||||||
# whether we have the latest version of everything.
|
# whether we have the latest version of everything.
|
||||||
VERSION=1.3.8
|
VERSION=1.3.10
|
||||||
HASH=90c7900ccf7b2f46fe49c650d5adb9b85ee9cc22
|
HASH=431625fc737e301f9b7e502cccc61e50a24786b8
|
||||||
PERSISTENT_LOGIN_VERSION=dc5ca3d3f4415cc41edb2fde533c8a8628a94c76
|
PERSISTENT_LOGIN_VERSION=dc5ca3d3f4415cc41edb2fde533c8a8628a94c76
|
||||||
HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5
|
HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5
|
||||||
CARDDAV_VERSION=3.0.3
|
CARDDAV_VERSION=3.0.3
|
||||||
@ -108,7 +108,7 @@ cat > $RCM_CONFIG <<EOF;
|
|||||||
'verify_peer_name' => false,
|
'verify_peer_name' => false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
\$config['imap_timeout'] = 15;
|
\$config['imap_timeout'] = 180;
|
||||||
\$config['smtp_server'] = 'tls://127.0.0.1';
|
\$config['smtp_server'] = 'tls://127.0.0.1';
|
||||||
\$config['smtp_port'] = 587;
|
\$config['smtp_port'] = 587;
|
||||||
\$config['smtp_user'] = '%u';
|
\$config['smtp_user'] = '%u';
|
||||||
|
@ -22,8 +22,8 @@ apt_install \
|
|||||||
phpenmod -v php imap
|
phpenmod -v php imap
|
||||||
|
|
||||||
# Copy Z-Push into place.
|
# Copy Z-Push into place.
|
||||||
VERSION=2.4.4
|
VERSION=2.5.0
|
||||||
TARGETHASH=104d44426852429dac8ec2783a4e9ad7752d4682
|
TARGETHASH=30ce5c1af3f10939036361b6032d1187651b621e
|
||||||
needs_update=0 #NODOC
|
needs_update=0 #NODOC
|
||||||
if [ ! -f /usr/local/lib/z-push/version ]; then
|
if [ ! -f /usr/local/lib/z-push/version ]; then
|
||||||
needs_update=1 #NODOC
|
needs_update=1 #NODOC
|
||||||
|
@ -90,6 +90,26 @@ def pop_test():
|
|||||||
if M:
|
if M:
|
||||||
M.quit()
|
M.quit()
|
||||||
|
|
||||||
|
def managesieve_test():
|
||||||
|
# We don't have a Python sieve client, so we'll
|
||||||
|
# just run the IMAP client and see what happens.
|
||||||
|
import imaplib
|
||||||
|
|
||||||
|
try:
|
||||||
|
M = imaplib.IMAP4(hostname, 4190)
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
# looks like fail2ban worked
|
||||||
|
raise IsBlocked()
|
||||||
|
|
||||||
|
try:
|
||||||
|
M.login("fakeuser", "fakepassword")
|
||||||
|
raise Exception("authentication didn't fail")
|
||||||
|
except imaplib.IMAP4.error:
|
||||||
|
# authentication should fail
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
M.logout() # shuts down connection, has nothing to do with login()
|
||||||
|
|
||||||
def http_test(url, expected_status, postdata=None, qsargs=None, auth=None):
|
def http_test(url, expected_status, postdata=None, qsargs=None, auth=None):
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import requests
|
import requests
|
||||||
@ -208,6 +228,9 @@ if __name__ == "__main__":
|
|||||||
# POP
|
# POP
|
||||||
run_test(pop_test, [], 20, 30, 4)
|
run_test(pop_test, [], 20, 30, 4)
|
||||||
|
|
||||||
|
# Managesieve
|
||||||
|
run_test(managesieve_test, [], 20, 30, 4)
|
||||||
|
|
||||||
# Mail-in-a-Box control panel
|
# Mail-in-a-Box control panel
|
||||||
run_test(http_test, ["/admin/me", 200], 20, 30, 1)
|
run_test(http_test, ["/admin/me", 200], 20, 30, 1)
|
||||||
|
|
||||||
|
10
tools/dns-auth.sh
Executable file
10
tools/dns-auth.sh
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# TODO: Make work with port other than 443
|
||||||
|
|
||||||
|
API_KEY=`cat /var/lib/mailinabox/api.key`
|
||||||
|
HOSTNAME=`hostname`
|
||||||
|
|
||||||
|
curl -s -X PUT -d "$CERTBOT_VALIDATION" --user "$API_KEY:" https://$HOSTNAME/admin/dns/custom/_acme-challenge.$CERTBOT_DOMAIN/TXT
|
||||||
|
|
||||||
|
sleep 15
|
8
tools/dns-cleanup.sh
Executable file
8
tools/dns-cleanup.sh
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# TODO: Make work with port other than 443
|
||||||
|
|
||||||
|
API_KEY=`cat /var/lib/mailinabox/api.key`
|
||||||
|
HOSTNAME=`hostname`
|
||||||
|
|
||||||
|
curl -s -X DELETE --user "$API_KEY:" https://$HOSTNAME/admin/dns/custom/_acme-challenge.$CERTBOT_DOMAIN/TXT
|
Loading…
Reference in New Issue
Block a user