include changes from v0.53. Remove some POWER modifications to closer follow original mialinabox
This commit is contained in:
parent
40adef2261
commit
c24ca5abd4
|
@ -12,9 +12,6 @@ charset = utf-8
|
|||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.html]
|
||||
indent_style = tab
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
|
|
@ -6,4 +6,3 @@ externals/
|
|||
.env
|
||||
.vagrant
|
||||
api/docs/api-docs.html
|
||||
mailinabox-ca.crt
|
||||
|
|
24
CHANGELOG.md
24
CHANGELOG.md
|
@ -1,13 +1,35 @@
|
|||
CHANGELOG
|
||||
=========
|
||||
|
||||
v0.53 (April 12, 2021)
|
||||
----------------------
|
||||
|
||||
Software updates:
|
||||
|
||||
* Upgraded Roundcube to version 1.4.11 addressing a security issue, and its desktop notifications plugin.
|
||||
* Upgraded Z-Push (for Exchange/ActiveSync) to version 2.6.2.
|
||||
|
||||
Control panel:
|
||||
|
||||
* Backblaze B2 is now a supported backup protocol.
|
||||
* Fixed an issue in the daily mail reports.
|
||||
* Sort the Custom DNS by zone and qname, and add an option to go back to the old sort order (creation order).
|
||||
|
||||
Mail:
|
||||
|
||||
* Enable sending DMARC failure reports to senders that request them.
|
||||
|
||||
Setup:
|
||||
|
||||
* Fixed error when upgrading from Nextcloud 13.
|
||||
|
||||
v0.52 (January 31, 2021)
|
||||
------------------------
|
||||
|
||||
Software updates:
|
||||
|
||||
* Upgraded Roundcube to version 1.4.10.
|
||||
* Upgraded zpush to 2.6.1.
|
||||
* Upgraded Z-Push to 2.6.1.
|
||||
|
||||
Mail:
|
||||
|
||||
|
|
96
README.md
96
README.md
|
@ -1,86 +1,14 @@
|
|||
(Power) Mail-in-a-Box
|
||||
=====================
|
||||
This is not the original Mail-in-a-Box. See https://github.com/mail-in-a-box/mailinabox for the real deal! I made a number of modifications to to:
|
||||
- add geoipblocking on the admin web console
|
||||
- add geoipblocking for ssh access
|
||||
- make fail2ban a more stricter
|
||||
- add fail2ban filter for web scanners
|
||||
- other small stuff
|
||||
|
||||
## Installation
|
||||
Original mailinabox content starts here:
|
||||
|
||||
- **PRE-REQUISITES:** Debian 10 (Buster) or Ubuntu 20.04 LTS fresh installation
|
||||
|
||||
Update packages:
|
||||
```sh
|
||||
sudo apt update
|
||||
sudo apt full-upgrade
|
||||
```
|
||||
|
||||
Make sure that the `en_US.UTF-8` locale exists and is set as primary (this depends on the image you use)
|
||||
```sh
|
||||
sudo apt install locales
|
||||
sudo dpkg-reconfigure locales
|
||||
```
|
||||
|
||||
Install Power-Mail-in-a-Box (short link)
|
||||
```sh
|
||||
curl -L https://dvn.pt/powermiab | sudo bash
|
||||
```
|
||||
|
||||
If that doesn't work:
|
||||
```sh
|
||||
curl https://raw.githubusercontent.com/ddavness/power-mailinabox/master/setup/bootstrap.sh | sudo bash
|
||||
```
|
||||
|
||||
## Current Version: v0.52.POWER.0 (Tracking v0.52)
|
||||
|
||||
This is a fork of MiaB (duh), hacked and tuned to my needs:
|
||||
|
||||
✅ - **Done**
|
||||
|
||||
👨💻 - **Not there yet, but soon!**
|
||||
|
||||
💤 - **I did not begin this part yet!**
|
||||
|
||||
- ✅ Support for Debian AND Ubuntu 20.04 LTS;
|
||||
|
||||
- ✅ Native support for SMTP relays (For example: SendGrid);
|
||||
|
||||
- ✅ Bumped the bootstrap and jQuery dependencies' versions - and we've got a brand new admin panel now!
|
||||
|
||||
- ✅ Per-domain `nginx` configuration: Custom pages will no longer have their pages defaulting to the MiaB services (`/admin`, `/mail`, etc.);
|
||||
|
||||
- ✅ Updated NextCloud to the latest version available;
|
||||
|
||||
- ✅ Performing backups immediately from the admin panel (independently from the daily schedule);
|
||||
|
||||
- 👨💻 Encrypting backups using user-provided PGP keys;
|
||||
|
||||
- 👨💻 Integrate a WKD server (Web Key Directory) for PGP keys;
|
||||
|
||||
- 💤 Restricting access to the admin panel to certain IP's?
|
||||
|
||||
- 💤 Customizing MTA names? (because privacy)
|
||||
|
||||
### Ideas section:
|
||||
|
||||
- 💤 Ability to download the backups from the admin panel;
|
||||
|
||||
- 💤 Possibility of making some services optional (if they require more software to be installed) on setup?
|
||||
|
||||
- - For example, one might simply not use NextCloud/Munin at all, and they're there... just wasting resources.
|
||||
|
||||
- 💤 AXFR Transfers (for secondary DNS) using TSIG?
|
||||
|
||||
- 💤 Expand DNS record options?
|
||||
|
||||
- 💤 More complete webmail configuration via the admin panel/plugin management?
|
||||
|
||||
- 💤 Expand the TOTP Two-Factor-Authentication for the webmail?
|
||||
|
||||
- - Maybe U2F one day, too, but I don't have a capable device for this just yet...
|
||||
|
||||
- 💤 Anything else I might need to use;
|
||||
|
||||
All in all, I think I should rename this to something like "Central [Clown Computing](https://www.urbandictionary.com/define.php?term=clown%20computing)", since I'm trying to cram as many services as possible into that poor machine (Spending 5$ is better than spending 10$)
|
||||
|
||||
Original Documentation
|
||||
======================
|
||||
Mail-in-a-Box
|
||||
=============
|
||||
|
||||
By [@JoshData](https://github.com/JoshData) and [contributors](https://github.com/mail-in-a-box/mailinabox/graphs/contributors).
|
||||
|
||||
|
@ -96,7 +24,7 @@ Our goals are to:
|
|||
* Promote [decentralization](http://redecentralize.org/), innovation, and privacy on the web.
|
||||
* Have automated, auditable, and [idempotent](https://web.archive.org/web/20190518072631/https://sharknet.us/2014/02/01/automated-configuration-management-challenges-with-idempotency/) configuration.
|
||||
* **Not** make a totally unhackable, NSA-proof server.
|
||||
* ~~**Not** make something customizable by power users.~~
|
||||
* **Not** make something customizable by power users.
|
||||
|
||||
Additionally, this project has a [Code of Conduct](CODE_OF_CONDUCT.md), which supersedes the goals above. Please review it when joining our community.
|
||||
|
||||
|
@ -104,7 +32,7 @@ Additionally, this project has a [Code of Conduct](CODE_OF_CONDUCT.md), which su
|
|||
In The Box
|
||||
----------
|
||||
|
||||
Mail-in-a-Box turns a fresh ~~Ubuntu 18.04 LTS~~ Debian 10 (Buster) 64-bit machine into a working mail server by installing and configuring various components.
|
||||
Mail-in-a-Box turns a fresh Ubuntu 20.04 or 18.04 LTS 64-bit machine into a working mail server by installing and configuring various components.
|
||||
|
||||
It is a one-click email appliance. There are no user-configurable setup options. It "just works."
|
||||
|
||||
|
@ -139,7 +67,7 @@ Clone this repository and checkout the tag corresponding to the most recent rele
|
|||
|
||||
$ git clone https://github.com/mail-in-a-box/mailinabox
|
||||
$ cd mailinabox
|
||||
$ git checkout v0.52
|
||||
$ git checkout v0.53
|
||||
|
||||
Begin the installation.
|
||||
|
||||
|
|
|
@ -1,20 +1,8 @@
|
|||
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "debian/buster64"
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
vb.customize ["modifyvm", :id, "--cpus", 4, "--memory", 4096]
|
||||
end
|
||||
config.vm.provider :libvirt do |v|
|
||||
v.memory = 4096
|
||||
v.cpus = 4
|
||||
v.nested = true
|
||||
end
|
||||
config.vm.provider :kvm do |kvm|
|
||||
kvm.memory_size = '4096m'
|
||||
end
|
||||
config.vm.box = "ubuntu/bionic64"
|
||||
|
||||
# Network config: Since it's a mail server, the machine must be connected
|
||||
# to the public web. However, we currently don't want to expose SSH since
|
||||
|
@ -22,18 +10,17 @@ Vagrant.configure("2") do |config|
|
|||
# machine on a private network.
|
||||
config.vm.hostname = "mailinabox.lan"
|
||||
config.vm.network "private_network", ip: "192.168.50.4"
|
||||
config.vm.synced_folder ".", "/vagrant", nfs_version: "3"
|
||||
#, :mount_options => ["ro"]
|
||||
|
||||
config.vm.provision :shell, :inline => <<-SH
|
||||
# Set environment variables so that the setup script does
|
||||
# not ask any questions during provisioning. We'll let the
|
||||
# machine figure out its own public IP.
|
||||
export NONINTERACTIVE=1
|
||||
export PUBLIC_IP=192.168.50.4
|
||||
export PUBLIC_IP=auto
|
||||
export PUBLIC_IPV6=auto
|
||||
export PRIMARY_HOSTNAME=auto
|
||||
export SKIP_NETWORK_CHECKS=1
|
||||
#export SKIP_NETWORK_CHECKS=1
|
||||
|
||||
# Start the setup script.
|
||||
cd /vagrant
|
||||
setup/start.sh
|
||||
|
|
|
@ -499,123 +499,6 @@ paths:
|
|||
text/html:
|
||||
schema:
|
||||
type: string
|
||||
/system/backup/new:
|
||||
post:
|
||||
tags:
|
||||
- System
|
||||
summary: Perform system backup
|
||||
description: Performs a system backup.
|
||||
operationId: performSystemBackup
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PerformBackupRequest'
|
||||
examples:
|
||||
incremental:
|
||||
summary: Perform incremental backup.
|
||||
value:
|
||||
full: false
|
||||
full:
|
||||
summary: Force a full backup.
|
||||
value:
|
||||
full: true
|
||||
x-codeSamples:
|
||||
- lang: curl
|
||||
source: |
|
||||
curl -X POST "https://{host}/admin/system/backup/new" \
|
||||
-d "full=<boolean>" \
|
||||
-u "<email>:<password>"
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PerformBackupResponse'
|
||||
403:
|
||||
description: Forbidden
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
/system/smtp/relay:
|
||||
get:
|
||||
tags:
|
||||
- System
|
||||
summary: Get SMTP relay configuration
|
||||
description: Gets basic configuration on how the box should use third-party relay services to deliver mail.
|
||||
operationId: getRelayConfig
|
||||
x-codeSamples:
|
||||
- lang: curl
|
||||
source: |
|
||||
curl -X GET "https://{host}/admin/system/smtp/relay" \
|
||||
-u "<email>:<password>"
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SmtpRelayConfig'
|
||||
403:
|
||||
description: Forbidden
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
type: string
|
||||
post:
|
||||
tags:
|
||||
- System
|
||||
summary: Set SMTP relay configuration
|
||||
description: Sets the configuration on how the box should use third-party relays to deliver mail.
|
||||
operationId: setRelayConfig
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SetSmtpRelayConfigRequest'
|
||||
examples:
|
||||
disable:
|
||||
summary: Do not use relays.
|
||||
value:
|
||||
enabled: false
|
||||
host: ""
|
||||
auth_enabled: false
|
||||
user: ""
|
||||
key: ""
|
||||
no_auth:
|
||||
summary: Use a relay that does not require authentication.
|
||||
value:
|
||||
enabled: true
|
||||
host: smtp.relay.net
|
||||
auth_enabled: false
|
||||
user: ""
|
||||
key: ""
|
||||
auth:
|
||||
summary: Use a relay that requires authentication.
|
||||
value:
|
||||
enabled: true
|
||||
host: smtp.relay.net
|
||||
auth_enabled: true
|
||||
user: someuser
|
||||
key: key-or-password-here
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
403:
|
||||
description: Forbidden
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/ssl/status:
|
||||
get:
|
||||
tags:
|
||||
|
@ -2576,19 +2459,6 @@ components:
|
|||
minimum: 1
|
||||
example: 3
|
||||
description: Backup config update request.
|
||||
PerformBackupRequest:
|
||||
type: object
|
||||
required:
|
||||
- full
|
||||
properties:
|
||||
full:
|
||||
type: boolean
|
||||
example: false
|
||||
description: New backup type.
|
||||
PerformBackupResponse:
|
||||
type: string
|
||||
example: OK
|
||||
description: Backup creation response.
|
||||
SystemBackupConfigUpdateResponse:
|
||||
type: string
|
||||
example: OK
|
||||
|
@ -2791,52 +2661,6 @@ components:
|
|||
type: string
|
||||
example: web updated
|
||||
description: Web update response.
|
||||
SmtpRelayConfig:
|
||||
type: object
|
||||
required:
|
||||
- enabled
|
||||
- host
|
||||
- auth_enabled
|
||||
- user
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
example: true
|
||||
host:
|
||||
type: string
|
||||
example: sendgrid.net
|
||||
auth_enabled:
|
||||
type: boolean
|
||||
example: true
|
||||
user:
|
||||
type: string
|
||||
example: someuser
|
||||
description: SMTP configuration details.
|
||||
SetSmtpRelayConfigRequest:
|
||||
type: object
|
||||
required:
|
||||
- enabled
|
||||
- host
|
||||
- auth_enabled
|
||||
- user
|
||||
- key
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
example: true
|
||||
host:
|
||||
type: string
|
||||
example: sendgrid.net
|
||||
auth_enabled:
|
||||
type: boolean
|
||||
example: true
|
||||
user:
|
||||
type: string
|
||||
example: apikey
|
||||
key:
|
||||
type: string
|
||||
example: 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
|
||||
description: SMTP Configuration form
|
||||
MfaStatusResponse:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Expose this directory as static files.
|
||||
root $ROOT;
|
||||
|
||||
# ADDITIONAL DIRECTIVES HERE
|
||||
index index.html index.htm;
|
||||
|
||||
location = /robots.txt {
|
||||
log_not_found off;
|
||||
|
@ -25,24 +25,31 @@
|
|||
alias /var/lib/mailinabox/mta-sts.txt;
|
||||
}
|
||||
|
||||
# Z-Push (Microsoft Exchange ActiveSync)
|
||||
location /Microsoft-Server-ActiveSync {
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME /usr/local/lib/z-push/index.php;
|
||||
fastcgi_param PHP_VALUE "include_path=.:/usr/share/php:/usr/share/pear:/usr/share/awl/inc";
|
||||
fastcgi_read_timeout 630;
|
||||
# Roundcube Webmail configuration.
|
||||
rewrite ^/mail$ /mail/ redirect;
|
||||
rewrite ^/mail/$ /mail/index.php;
|
||||
location /mail/ {
|
||||
index index.php;
|
||||
alias /usr/local/lib/roundcubemail/;
|
||||
}
|
||||
location ~ /mail/config/.* {
|
||||
# A ~-style location is needed to give this precedence over the next block.
|
||||
return 403;
|
||||
}
|
||||
location ~ /mail/.*\.php {
|
||||
# note: ~ has precendence over a regular location block
|
||||
include fastcgi_params;
|
||||
fastcgi_split_path_info ^/mail(/.*)()$;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME /usr/local/lib/roundcubemail/$fastcgi_script_name;
|
||||
fastcgi_pass php-fpm;
|
||||
|
||||
# Outgoing mail also goes through this endpoint, so increase the maximum
|
||||
# file upload limit to match the corresponding Postfix limit.
|
||||
client_max_body_size 128M;
|
||||
}
|
||||
location ~* ^/autodiscover/autodiscover.xml$ {
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME /usr/local/lib/z-push/autodiscover/autodiscover.php;
|
||||
fastcgi_param PHP_VALUE "include_path=.:/usr/share/php:/usr/share/pear:/usr/share/awl/inc";
|
||||
fastcgi_pass php-fpm;
|
||||
}
|
||||
|
||||
# ADDITIONAL DIRECTIVES HERE
|
||||
|
||||
# Disable viewing dotfiles (.htaccess, .svn, .git, etc.)
|
||||
# This block is placed at the end. Nginx's precedence rules means this block
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
# ADDITIONAL DIRECTIVES HERE
|
||||
|
||||
# Control Panel
|
||||
# Proxy /admin to our Python based control panel daemon. It is
|
||||
# listening on IPv4 only so use an IP address and not 'localhost'.
|
||||
|
@ -40,30 +38,6 @@
|
|||
add_header Content-Security-Policy "frame-ancestors 'none';";
|
||||
}
|
||||
|
||||
# Roundcube Webmail configuration.
|
||||
rewrite ^/mail$ /mail/ redirect;
|
||||
rewrite ^/mail/$ /mail/index.php;
|
||||
location /mail/ {
|
||||
index index.php;
|
||||
alias /usr/local/lib/roundcubemail/;
|
||||
}
|
||||
location ~ /mail/config/.* {
|
||||
# A ~-style location is needed to give this precedence over the next block.
|
||||
return 403;
|
||||
}
|
||||
location ~ /mail/.*\.php {
|
||||
# note: ~ has precendence over a regular location block
|
||||
include fastcgi_params;
|
||||
fastcgi_split_path_info ^/mail(/.*)()$;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME /usr/local/lib/roundcubemail/$fastcgi_script_name;
|
||||
fastcgi_pass php-fpm;
|
||||
|
||||
# Outgoing mail also goes through this endpoint, so increase the maximum
|
||||
# file upload limit to match the corresponding Postfix limit.
|
||||
client_max_body_size 128M;
|
||||
}
|
||||
|
||||
# Nextcloud configuration.
|
||||
rewrite ^/cloud$ /cloud/ redirect;
|
||||
rewrite ^/cloud/$ /cloud/index.php;
|
||||
|
@ -122,3 +96,5 @@
|
|||
rewrite ^/.well-known/host-meta.json /cloud/public.php?service=host-meta-json last;
|
||||
rewrite ^/.well-known/carddav /cloud/remote.php/carddav/ redirect;
|
||||
rewrite ^/.well-known/caldav /cloud/remote.php/caldav/ redirect;
|
||||
|
||||
# ADDITIONAL DIRECTIVES HERE
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<title>Redirecting...</title>
|
||||
<title>this is a mail-in-a-box</title>
|
||||
<meta name="robots" content="noindex">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>this is a mail-in-a-box</h1>
|
||||
<p>take control of your email at <a href="https://mailinabox.email/">https://mailinabox.email/</a></p>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
location.href = "/mail"
|
||||
</script>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
import os, os.path, shutil, glob, re, datetime, sys
|
||||
import dateutil.parser, dateutil.relativedelta, dateutil.tz
|
||||
import rtyaml
|
||||
from exclusiveprocess import Lock, CannotAcquireLock
|
||||
from exclusiveprocess import Lock
|
||||
|
||||
from utils import load_environment, shell, wait_for_service, fix_boto, get_php_version
|
||||
|
||||
|
@ -210,21 +210,13 @@ def get_target_type(config):
|
|||
protocol = config["target"].split(":")[0]
|
||||
return protocol
|
||||
|
||||
def perform_backup(full_backup, user_initiated=False):
|
||||
def perform_backup(full_backup):
|
||||
env = load_environment()
|
||||
php_fpm = f"php{get_php_version()}-fpm"
|
||||
|
||||
# Create an global exclusive lock so that the backup script
|
||||
# cannot be run more than one.
|
||||
lock = Lock(name="mailinabox_backup_daemon", die=(not user_initiated))
|
||||
if user_initiated:
|
||||
# God forgive me for what I'm about to do
|
||||
try:
|
||||
lock._acquire()
|
||||
except CannotAcquireLock:
|
||||
return "Another backup is already being done!"
|
||||
else:
|
||||
lock.forever()
|
||||
# cannot be run more than once.
|
||||
Lock(die=True).forever()
|
||||
|
||||
config = get_backup_config(env)
|
||||
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
|
||||
|
@ -329,11 +321,6 @@ def perform_backup(full_backup, user_initiated=False):
|
|||
# backup. Since it checks that dovecot and postfix are running, block for a
|
||||
# bit (maximum of 10 seconds each) to give each a chance to finish restarting
|
||||
# before the status checks might catch them down. See #381.
|
||||
if user_initiated:
|
||||
# God forgive me for what I'm about to do
|
||||
lock._release()
|
||||
# We don't need to wait for the services to be up in this case
|
||||
else:
|
||||
wait_for_service(25, True, env, 10)
|
||||
wait_for_service(993, True, env, 10)
|
||||
|
||||
|
@ -346,7 +333,6 @@ def perform_backup(full_backup, user_initiated=False):
|
|||
['su', env['STORAGE_USER'], '-c', post_script, config["target"]],
|
||||
env=env)
|
||||
|
||||
|
||||
def run_duplicity_verification():
|
||||
env = load_environment()
|
||||
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
|
||||
|
|
|
@ -280,17 +280,50 @@ def dns_set_secondary_nameserver():
|
|||
@app.route('/dns/custom')
|
||||
@authorized_personnel_only
|
||||
def dns_get_records(qname=None, rtype=None):
|
||||
from dns_update import get_custom_dns_config
|
||||
return json_response([
|
||||
# Get the current set of custom DNS records.
|
||||
from dns_update import get_custom_dns_config, get_dns_zones
|
||||
records = get_custom_dns_config(env, only_real_records=True)
|
||||
|
||||
# Filter per the arguments for the more complex GET routes below.
|
||||
records = [r for r in records
|
||||
if (not qname or r[0] == qname)
|
||||
and (not rtype or r[1] == rtype) ]
|
||||
|
||||
# Make a better data structure.
|
||||
records = [
|
||||
{
|
||||
"qname": r[0],
|
||||
"rtype": r[1],
|
||||
"value": r[2],
|
||||
}
|
||||
for r in get_custom_dns_config(env)
|
||||
if r[0] != "_secondary_nameserver"
|
||||
and (not qname or r[0] == qname)
|
||||
and (not rtype or r[1] == rtype) ])
|
||||
"sort-order": { },
|
||||
} for r in records ]
|
||||
|
||||
# To help with grouping by zone in qname sorting, label each record with which zone it is in.
|
||||
# There's an inconsistency in how we handle zones in get_dns_zones and in sort_domains, so
|
||||
# do this first before sorting the domains within the zones.
|
||||
zones = utils.sort_domains([z[0] for z in get_dns_zones(env)], env)
|
||||
for r in records:
|
||||
for z in zones:
|
||||
if r["qname"] == z or r["qname"].endswith("." + z):
|
||||
r["zone"] = z
|
||||
break
|
||||
|
||||
# Add sorting information. The 'created' order follows the order in the YAML file on disk,
|
||||
# which tracs the order entries were added in the control panel since we append to the end.
|
||||
# The 'qname' sort order sorts by our standard domain name sort (by zone then by qname),
|
||||
# then by rtype, and last by the original order in the YAML file (since sorting by value
|
||||
# may not make sense, unless we parse IP addresses, for example).
|
||||
for i, r in enumerate(records):
|
||||
r["sort-order"]["created"] = i
|
||||
domain_sort_order = utils.sort_domains([r["qname"] for r in records], env)
|
||||
for i, r in enumerate(sorted(records, key = lambda r : (
|
||||
zones.index(r["zone"]),
|
||||
domain_sort_order.index(r["qname"]),
|
||||
r["rtype"]))):
|
||||
r["sort-order"]["qname"] = i
|
||||
|
||||
# Return.
|
||||
return json_response(records)
|
||||
|
||||
@app.route('/dns/custom/<qname>', methods=['GET', 'POST', 'PUT', 'DELETE'])
|
||||
@app.route('/dns/custom/<qname>/<rtype>', methods=['GET', 'POST', 'PUT', 'DELETE'])
|
||||
|
@ -594,19 +627,6 @@ def backup_set_custom():
|
|||
request.form.get('min_age', '')
|
||||
))
|
||||
|
||||
@app.route('/system/backup/new', methods=["POST"])
|
||||
@authorized_personnel_only
|
||||
def backup_new():
|
||||
from backup import perform_backup, get_backup_config
|
||||
|
||||
# If backups are disabled, don't perform the backup
|
||||
config = get_backup_config(env)
|
||||
if config["target"] == "off":
|
||||
return "Backups are disabled in this machine. Nothing was done."
|
||||
|
||||
msg = perform_backup(request.form.get('full', False) == 'true', True)
|
||||
return "OK" if msg is None else msg
|
||||
|
||||
@app.route('/system/privacy', methods=["GET"])
|
||||
@authorized_personnel_only
|
||||
def privacy_status_get():
|
||||
|
@ -621,49 +641,6 @@ def privacy_status_set():
|
|||
utils.write_settings(config, env)
|
||||
return "OK"
|
||||
|
||||
@app.route('/system/smtp/relay', methods=["GET"])
|
||||
@authorized_personnel_only
|
||||
def smtp_relay_get():
|
||||
config = utils.load_settings(env)
|
||||
return {
|
||||
"enabled": config.get("SMTP_RELAY_ENABLED", True),
|
||||
"host": config.get("SMTP_RELAY_HOST", ""),
|
||||
"auth_enabled": config.get("SMTP_RELAY_AUTH", False),
|
||||
"user": config.get("SMTP_RELAY_USER", "")
|
||||
}
|
||||
|
||||
@app.route('/system/smtp/relay', methods=["POST"])
|
||||
@authorized_personnel_only
|
||||
def smtp_relay_set():
|
||||
from editconf import edit_conf
|
||||
config = utils.load_settings(env)
|
||||
newconf = request.form
|
||||
try:
|
||||
# Write on daemon settings
|
||||
config["SMTP_RELAY_ENABLED"] = (newconf.get("enabled") == "true")
|
||||
config["SMTP_RELAY_HOST"] = newconf.get("host")
|
||||
config["SMTP_RELAY_AUTH"] = (newconf.get("auth_enabled") == "true")
|
||||
config["SMTP_RELAY_USER"] = newconf.get("user")
|
||||
utils.write_settings(config, env)
|
||||
# Write on Postfix configs
|
||||
edit_conf("/etc/postfix/main.cf", [
|
||||
"relayhost=" + (f"[{config['SMTP_RELAY_HOST']}]:587" if config["SMTP_RELAY_ENABLED"] else ""),
|
||||
"smtp_sasl_auth_enable=" + ("yes" if config["SMTP_RELAY_AUTH"] else "no"),
|
||||
"smtp_sasl_security_options=" + ("noanonymous" if config["SMTP_RELAY_AUTH"] else "anonymous"),
|
||||
"smtp_sasl_tls_security_options=" + ("noanonymous" if config["SMTP_RELAY_AUTH"] else "anonymous")
|
||||
], delimiter_re=r"\s*=\s*", delimiter="=", comment_char="#")
|
||||
if config["SMTP_RELAY_AUTH"]:
|
||||
# Edit the sasl password
|
||||
with open("/etc/postfix/sasl_passwd", "w") as f:
|
||||
f.write(f"[{config['SMTP_RELAY_HOST']}]:587 {config['SMTP_RELAY_USER']}:{newconf.get('key')}\n")
|
||||
utils.shell("check_output", ["/usr/bin/chmod", "600", "/etc/postfix/sasl_passwd"], capture_stderr=True)
|
||||
utils.shell("check_output", ["/usr/sbin/postmap", "/etc/postfix/sasl_passwd"], capture_stderr=True)
|
||||
# Restart Postfix
|
||||
return utils.shell("check_output", ["/usr/bin/systemctl", "restart", "postfix"], capture_stderr=True)
|
||||
except Exception as e:
|
||||
return (str(e), 500)
|
||||
|
||||
|
||||
# MUNIN
|
||||
|
||||
@app.route('/munin/')
|
||||
|
|
|
@ -753,7 +753,7 @@ def write_opendkim_tables(domains, env):
|
|||
|
||||
########################################################################
|
||||
|
||||
def get_custom_dns_config(env):
|
||||
def get_custom_dns_config(env, only_real_records=False):
|
||||
try:
|
||||
custom_dns = rtyaml.load(open(os.path.join(env['STORAGE_ROOT'], 'dns/custom.yaml')))
|
||||
if not isinstance(custom_dns, dict): raise ValueError() # caught below
|
||||
|
@ -761,6 +761,8 @@ def get_custom_dns_config(env):
|
|||
return [ ]
|
||||
|
||||
for qname, value in custom_dns.items():
|
||||
if qname == "_secondary_nameserver" and only_real_records: continue # skip fake record
|
||||
|
||||
# Short form. Mapping a domain name to a string is short-hand
|
||||
# for creating A records.
|
||||
if isinstance(value, str):
|
||||
|
|
|
@ -44,9 +44,8 @@ TIME_DELTAS = OrderedDict([
|
|||
('today', datetime.datetime.now() - datetime.datetime.now().replace(hour=0, minute=0, second=0))
|
||||
])
|
||||
|
||||
# Start date > end date!
|
||||
START_DATE = datetime.datetime.now()
|
||||
END_DATE = None
|
||||
END_DATE = NOW = datetime.datetime.now()
|
||||
START_DATE = None
|
||||
|
||||
VERBOSE = False
|
||||
|
||||
|
@ -121,7 +120,7 @@ def scan_mail_log(env):
|
|||
pass
|
||||
|
||||
print("Scanning logs from {:%Y-%m-%d %H:%M:%S} to {:%Y-%m-%d %H:%M:%S}".format(
|
||||
END_DATE, START_DATE)
|
||||
START_DATE, END_DATE)
|
||||
)
|
||||
|
||||
# Scan the lines in the log files until the date goes out of range
|
||||
|
@ -253,7 +252,7 @@ def scan_mail_log(env):
|
|||
|
||||
if collector["postgrey"]:
|
||||
msg = "Greylisted Email {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
|
||||
print_header(msg.format(END_DATE, START_DATE))
|
||||
print_header(msg.format(START_DATE, END_DATE))
|
||||
|
||||
print(textwrap.fill(
|
||||
"The following mail was greylisted, meaning the emails were temporarily rejected. "
|
||||
|
@ -291,7 +290,7 @@ def scan_mail_log(env):
|
|||
|
||||
if collector["rejected"]:
|
||||
msg = "Blocked Email {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
|
||||
print_header(msg.format(END_DATE, START_DATE))
|
||||
print_header(msg.format(START_DATE, END_DATE))
|
||||
|
||||
data = OrderedDict(sorted(collector["rejected"].items(), key=email_sort))
|
||||
|
||||
|
@ -345,19 +344,19 @@ def scan_mail_log_line(line, collector):
|
|||
# Replaced the dateutil parser for a less clever way of parser that is roughly 4 times faster.
|
||||
# date = dateutil.parser.parse(date)
|
||||
|
||||
# date = datetime.datetime.strptime(date, '%b %d %H:%M:%S')
|
||||
# date = date.replace(START_DATE.year)
|
||||
|
||||
# strptime fails on Feb 29 if correct year is not provided. See https://bugs.python.org/issue26460
|
||||
date = datetime.datetime.strptime(str(START_DATE.year) + ' ' + date, '%Y %b %d %H:%M:%S')
|
||||
# strptime fails on Feb 29 with ValueError: day is out of range for month if correct year is not provided.
|
||||
# See https://bugs.python.org/issue26460
|
||||
date = datetime.datetime.strptime(str(NOW.year) + ' ' + date, '%Y %b %d %H:%M:%S')
|
||||
# if log date in future, step back a year
|
||||
if date > NOW:
|
||||
date = date.replace(year = NOW.year - 1)
|
||||
#print("date:", date)
|
||||
|
||||
# Check if the found date is within the time span we are scanning
|
||||
# END_DATE < START_DATE
|
||||
if date > START_DATE:
|
||||
if date > END_DATE:
|
||||
# Don't process, and halt
|
||||
return False
|
||||
elif date < END_DATE:
|
||||
elif date < START_DATE:
|
||||
# Don't process, but continue
|
||||
return True
|
||||
|
||||
|
@ -606,7 +605,7 @@ def email_sort(email):
|
|||
|
||||
|
||||
def valid_date(string):
|
||||
""" Validate the given date string fetched from the --startdate argument """
|
||||
""" Validate the given date string fetched from the --enddate argument """
|
||||
try:
|
||||
date = dateutil.parser.parse(string)
|
||||
except ValueError:
|
||||
|
@ -820,12 +819,14 @@ if __name__ == "__main__":
|
|||
|
||||
parser.add_argument("-t", "--timespan", choices=TIME_DELTAS.keys(), default='today',
|
||||
metavar='<time span>',
|
||||
help="Time span to scan, going back from the start date. Possible values: "
|
||||
help="Time span to scan, going back from the end date. Possible values: "
|
||||
"{}. Defaults to 'today'.".format(", ".join(list(TIME_DELTAS.keys()))))
|
||||
parser.add_argument("-d", "--startdate", action="store", dest="startdate",
|
||||
type=valid_date, metavar='<start date>',
|
||||
help="Date and time to start scanning the log file from. If no date is "
|
||||
"provided, scanning will start from the current date and time.")
|
||||
# keep the --startdate arg for backward compatibility
|
||||
parser.add_argument("-d", "--enddate", "--startdate", action="store", dest="enddate",
|
||||
type=valid_date, metavar='<end date>',
|
||||
help="Date and time to end scanning the log file. If no date is "
|
||||
"provided, scanning will end at the current date and time. "
|
||||
"Alias --startdate is for compatibility.")
|
||||
parser.add_argument("-u", "--users", action="store", dest="users",
|
||||
metavar='<email1,email2,email...>',
|
||||
help="Comma separated list of (partial) email addresses to filter the "
|
||||
|
@ -837,13 +838,13 @@ if __name__ == "__main__":
|
|||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.startdate is not None:
|
||||
START_DATE = args.startdate
|
||||
if args.enddate is not None:
|
||||
END_DATE = args.enddate
|
||||
if args.timespan == 'today':
|
||||
args.timespan = 'day'
|
||||
print("Setting start date to {}".format(START_DATE))
|
||||
print("Setting end date to {}".format(END_DATE))
|
||||
|
||||
END_DATE = START_DATE - TIME_DELTAS[args.timespan]
|
||||
START_DATE = END_DATE - TIME_DELTAS[args.timespan]
|
||||
|
||||
VERBOSE = args.verbose
|
||||
|
||||
|
|
|
@ -280,9 +280,9 @@ def run_network_checks(env, output):
|
|||
if ret == 0:
|
||||
output.print_ok("Outbound mail (SMTP port 25) is not blocked.")
|
||||
else:
|
||||
output.print_warning("""Outbound mail (SMTP port 25) seems to be blocked by your network. You
|
||||
will not be able to send any mail without a SMTP relay. Many residential networks block port 25 to prevent
|
||||
hijacked machines from being able to send spam. A quick connection test to Google's mail server on port 25
|
||||
output.print_error("""Outbound mail (SMTP port 25) seems to be blocked by your network. You
|
||||
will not be able to send any mail. Many residential networks block port 25 to prevent hijacked
|
||||
machines from being able to send spam. A quick connection test to Google's mail server on port 25
|
||||
failed.""")
|
||||
|
||||
# Stop if the IPv4 address is listed in the ZEN Spamhaus Block List.
|
||||
|
@ -300,19 +300,6 @@ def run_network_checks(env, output):
|
|||
which may prevent recipients from receiving your email. See http://www.spamhaus.org/query/ip/%s."""
|
||||
% (env['PUBLIC_IP'], zen, env['PUBLIC_IP']))
|
||||
|
||||
# Check if a SMTP relay is set up. It's not strictly required, but on some providers
|
||||
# it might be needed.
|
||||
config = load_settings(env)
|
||||
if config.get("SMTP_RELAY_ENABLED"):
|
||||
if config.get("SMTP_RELAY_AUTH"):
|
||||
output.print_ok("An authenticated SMTP relay has been set up via port 587.")
|
||||
else:
|
||||
output.print_warning("A SMTP relay has been set up, but it is not authenticated.")
|
||||
elif ret == 0:
|
||||
output.print_ok("No SMTP relay has been set up (but that's ok since port 25 is not blocked).")
|
||||
else:
|
||||
output.print_error("No SMTP relay has been set up. Since port 25 is blocked, you will probably not be able to send any mail.")
|
||||
|
||||
def run_domain_checks(rounded_time, env, output, pool):
|
||||
# Get the list of domains we handle mail for.
|
||||
mail_domains = get_mail_domains(env)
|
||||
|
|
|
@ -94,10 +94,10 @@
|
|||
<tr id="alias-template">
|
||||
<td class='actions'>
|
||||
<a href="#" onclick="aliases_edit(this); scroll_top(); return false;" class='edit' title="Edit Alias">
|
||||
<span class="fas fa-pen"></span>
|
||||
<span class="glyphicon glyphicon-pencil"></span>
|
||||
</a>
|
||||
<a href="#" onclick="aliases_remove(this); return false;" class='remove' title="Remove Alias">
|
||||
<span class="fas fa-trash"></span>
|
||||
<span class="glyphicon glyphicon-trash"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td class='address'> </td>
|
||||
|
@ -153,8 +153,8 @@ function show_aliases() {
|
|||
function(r) {
|
||||
$('#alias_table tbody').html("");
|
||||
for (var i = 0; i < r.length; i++) {
|
||||
var hdr = $("<tr><td colspan='3'><h4/></td></tr>");
|
||||
hdr.find('h4').text(r[i].domain);
|
||||
var hdr = $("<tr><th colspan='4' style='background-color: #EEE'></th></tr>");
|
||||
hdr.find('th').text(r[i].domain);
|
||||
$('#alias_table tbody').append(hdr);
|
||||
|
||||
for (var k = 0; k < r[i].aliases.length; k++) {
|
||||
|
|
|
@ -57,7 +57,13 @@
|
|||
</div>
|
||||
</form>
|
||||
|
||||
<table id="custom-dns-current" class="table" style="width: auto; display: none">
|
||||
<div style="text-align: right; font-size; 90%; margin-top: 1em;">
|
||||
sort by:
|
||||
<a href="#" onclick="window.miab_custom_dns_data_sort_order='qname'; show_current_custom_dns_update_after_sort(); return false;">domain name</a>
|
||||
|
|
||||
<a href="#" onclick="window.miab_custom_dns_data_sort_order='created'; show_current_custom_dns_update_after_sort(); return false;">created</a>
|
||||
</div>
|
||||
<table id="custom-dns-current" class="table" style="width: auto; display: none; margin-top: 0;">
|
||||
<thead>
|
||||
<th>Domain Name</th>
|
||||
<th>Record Type</th>
|
||||
|
@ -192,36 +198,38 @@ function show_current_custom_dns() {
|
|||
$('#custom-dns-current').fadeIn();
|
||||
else
|
||||
$('#custom-dns-current').fadeOut();
|
||||
|
||||
var reverse_fqdn = function(el) {
|
||||
el.qname = el.qname.split('.').reverse().join('.');
|
||||
return el;
|
||||
}
|
||||
var sort = function(a, b) {
|
||||
if(a.qname === b.qname) {
|
||||
if(a.rtype === b.rtype) {
|
||||
return a.value > b.value ? 1 : -1;
|
||||
}
|
||||
return a.rtype > b.rtype ? 1 : -1;
|
||||
}
|
||||
return a.qname > b.qname ? 1 : -1;
|
||||
window.miab_custom_dns_data = data;
|
||||
show_current_custom_dns_update_after_sort();
|
||||
});
|
||||
}
|
||||
|
||||
data = data.map(reverse_fqdn).sort(sort).map(reverse_fqdn);
|
||||
function show_current_custom_dns_update_after_sort() {
|
||||
var data = window.miab_custom_dns_data;
|
||||
var sort_key = window.miab_custom_dns_data_sort_order || "qname";
|
||||
|
||||
$('#custom-dns-current').find("tbody").text('');
|
||||
data.sort(function(a, b) { return a["sort-order"][sort_key] - b["sort-order"][sort_key] });
|
||||
|
||||
var tbody = $('#custom-dns-current').find("tbody");
|
||||
tbody.text('');
|
||||
var last_zone = null;
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if (sort_key == "qname" && data[i].zone != last_zone) {
|
||||
var r = $("<tr><th colspan=4 style='background-color: #EEE'></th></tr>");
|
||||
r.find("th").text(data[i].zone);
|
||||
tbody.append(r);
|
||||
last_zone = data[i].zone;
|
||||
}
|
||||
|
||||
var tr = $("<tr/>");
|
||||
$('#custom-dns-current').find("tbody").append(tr);
|
||||
tbody.append(tr);
|
||||
tr.attr('data-qname', data[i].qname);
|
||||
tr.attr('data-rtype', data[i].rtype);
|
||||
tr.attr('data-value', data[i].value);
|
||||
tr.append($('<td class="long"/>').text(data[i].qname));
|
||||
tr.append($('<td/>').text(data[i].rtype));
|
||||
tr.append($('<td class="long"/>').text(data[i].value));
|
||||
tr.append($('<td class="long" style="max-width: 40em"/>').text(data[i].value));
|
||||
tr.append($('<td>[<a href="#" onclick="return delete_custom_dns_record(this)">delete</a>]</td>'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function delete_custom_dns_record(elem) {
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
|
||||
<p class="alert" role="alert">
|
||||
<span class="fas fa-info-circle"></span>
|
||||
<span class="glyphicon glyphicon-info-sign"></span>
|
||||
You may encounter zone file errors when attempting to create a TXT record with a long string.
|
||||
<a href="http://tools.ietf.org/html/rfc4408#section-3.1.3">RFC 4408</a> states a TXT record is allowed to contain multiple strings, and this technique can be used to construct records that would exceed the 255-byte maximum length.
|
||||
You may need to adopt this technique when adding DomainKeys. Use a tool like <code>named-checkzone</code> to validate your zone file.
|
||||
|
@ -50,7 +50,7 @@
|
|||
<label for="downloadZonefile" class="control-label sr-only">Zone</label>
|
||||
<select id="downloadZonefile" class="form-control" style="width: auto"> </select>
|
||||
</div>
|
||||
<button type="submit" style="margin-left: 20px" class="btn btn-primary">Download</button>
|
||||
<button type="submit" class="btn btn-primary">Download</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
|
@ -10,9 +10,6 @@
|
|||
<meta name="robots" content="noindex, nofollow">
|
||||
|
||||
<link rel="stylesheet" href="/admin/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/admin/assets/fontawesome/css/fontawesome.min.css">
|
||||
<link rel="stylesheet" href="/admin/assets/fontawesome/css/solid.min.css">
|
||||
|
||||
<style>
|
||||
body {
|
||||
overflow-y: scroll;
|
||||
|
@ -66,6 +63,7 @@
|
|||
margin-bottom: 1em;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/admin/assets/bootstrap/css/bootstrap-theme.min.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
@ -73,52 +71,53 @@
|
|||
<!--[if gt IE 7]><!-->
|
||||
|
||||
<div class="navbar navbar-inverse navbar-static-top" role="navigation">
|
||||
<div class="container bg-light">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="#">{{hostname}}</a>
|
||||
</div>
|
||||
<div class="navbar navbar-expand-lg">
|
||||
<div class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="btn dropdown">
|
||||
<a style="color: black;" href="#" class="dropdown-toggle" data-toggle="dropdown">System <b class="caret"></b></a>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">System <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="dropdown-item"><a href="#system_status" onclick="return show_panel(this);">Status Checks</a></li>
|
||||
<li class="dropdown-item"><a href="#tls" onclick="return show_panel(this);">TLS (SSL) Certificates</a></li>
|
||||
<li class="dropdown-item"><a href="#system_backup" onclick="return show_panel(this);">Backup Status</a></li>
|
||||
<li class="dropdown-item"><a href="#smtp_relays" onclick="return show_panel(this);">SMTP Relays</a></li>
|
||||
<li><a href="#system_status" onclick="return show_panel(this);">Status Checks</a></li>
|
||||
<li><a href="#tls" onclick="return show_panel(this);">TLS (SSL) Certificates</a></li>
|
||||
<li><a href="#system_backup" onclick="return show_panel(this);">Backup Status</a></li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">Advanced Pages</li>
|
||||
<li class="dropdown-item"><a href="#custom_dns" onclick="return show_panel(this);">Custom DNS</a></li>
|
||||
<li class="dropdown-item"><a href="#external_dns" onclick="return show_panel(this);">External DNS</a></li>
|
||||
<li class="dropdown-item"><a href="/admin/munin" target="_blank">Munin Monitoring</a></li>
|
||||
<li><a href="#custom_dns" onclick="return show_panel(this);">Custom DNS</a></li>
|
||||
<li><a href="#external_dns" onclick="return show_panel(this);">External DNS</a></li>
|
||||
<li><a href="/admin/munin" target="_blank">Munin Monitoring</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="btn dropdown">
|
||||
<a style="color: black;" href="#" class="dropdown-toggle" data-toggle="dropdown">Mail & Users <b class="caret"></b></a>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Mail & Users <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="dropdown-item"><a href="#mail-guide" onclick="return show_panel(this);">Instructions</a></li>
|
||||
<li class="dropdown-item"><a href="#users" onclick="return show_panel(this);">Users</a></li>
|
||||
<li class="dropdown-item"><a href="#aliases" onclick="return show_panel(this);">Aliases</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="#aliases" onclick="return show_panel(this);">Aliases</a></li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">Your Account</li>
|
||||
<li class="dropdown-item"><a href="#mfa" onclick="return show_panel(this);">Two-Factor Authentication</a></li>
|
||||
<li><a href="#mfa" onclick="return show_panel(this);">Two-Factor Authentication</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="btn"><a style="color: black;" href="#sync_guide" onclick="return show_panel(this);">Contacts/Calendar</a></li>
|
||||
<li class="btn"><a style="color: black;" href="#web" onclick="return show_panel(this);">Web</a></li>
|
||||
<li><a href="#sync_guide" onclick="return show_panel(this);">Contacts/Calendar</a></li>
|
||||
<li><a href="#web" onclick="return show_panel(this);">Web</a></li>
|
||||
</ul>
|
||||
<ul class="btn nav navbar-nav navbar-right">
|
||||
<li><a href="#" onclick="do_logout(); return false;" style="color: black; font-weight: bold;">Log out</a></li>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li><a href="#" onclick="do_logout(); return false;" style="color: white">Log out</a></li>
|
||||
</ul>
|
||||
</div><!--/.navbar-collapse -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div id="panel_smtp_relays" class="admin_panel">
|
||||
{% include "smtp-relays.html" %}
|
||||
</div>
|
||||
|
||||
<div id="panel_system_status" class="admin_panel">
|
||||
{% include "system-status.html" %}
|
||||
</div>
|
||||
|
@ -170,7 +169,7 @@
|
|||
<hr>
|
||||
|
||||
<footer>
|
||||
<p>This is a <a href="https://github.com/ddavness/power-mailinabox">Power Mail-in-a-Box</a> - {{distname}}</p>
|
||||
<p>This is a <a href="https://mailinabox.email">Mail-in-a-Box</a>.</p>
|
||||
</footer>
|
||||
</div> <!-- /container -->
|
||||
|
||||
|
@ -185,8 +184,8 @@
|
|||
<div class="modal-dialog modal-sm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="errorModalTitle"> </h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title" id="errorModalTitle"> </h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p> </p>
|
||||
|
|
|
@ -51,19 +51,19 @@ sudo management/cli.py user make-admin me@{{hostname}}</pre>
|
|||
<form id="loginForm" class="form-horizontal" role="form" onsubmit="do_login(); return false;" method="get">
|
||||
<div class="form-group">
|
||||
<label for="inputEmail3" class="col-sm-3 control-label">Email</label>
|
||||
<div class="col-sm-12">
|
||||
<div class="col-sm-9">
|
||||
<input name="email" type="email" class="form-control" id="loginEmail" placeholder="Email">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputPassword3" class="col-sm-3 control-label">Password</label>
|
||||
<div class="col-sm-12">
|
||||
<div class="col-sm-9">
|
||||
<input name="password" type="password" class="form-control" id="loginPassword" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" id="loginOtp">
|
||||
<label for="loginOtpInput" class="col-sm-3 control-label">Code</label>
|
||||
<div class="col-sm-12">
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" id="loginOtpInput" placeholder="6-digit code">
|
||||
<div class="help-block" style="margin-top: 5px; font-size: 90%">Enter the six-digit code generated by your two factor authentication app.</div>
|
||||
</div>
|
||||
|
|
|
@ -36,11 +36,11 @@
|
|||
<p>When two-factor authentication is enabled, you will be prompted to enter a six digit code from an
|
||||
authenticator app (usually on your phone) when you log into this control panel.</p>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header text-white bg-danger">
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-heading">
|
||||
Enabling two-factor authentication does not protect access to your email
|
||||
</div>
|
||||
<div class="card-body bg-light">
|
||||
<div class="panel-body">
|
||||
Enabling two-factor authentication on this page only limits access to this control panel. Remember that most websites allow you to
|
||||
reset your password by checking your email, so anyone with access to your email can typically take over
|
||||
your other accounts. Additionally, if your email address or any alias that forwards to your email
|
||||
|
@ -81,7 +81,7 @@ and ensure every administrator account for this control panel does the same.</st
|
|||
<div class="form-group">
|
||||
<p>When you click Enable Two-Factor Authentication, you will be logged out of the control panel and will have to log in
|
||||
again, now using your two-factor authentication app.</p>
|
||||
<button id="totp-setup-submit" disabled type="submit" class="btn btn-primary">Enable Two-Factor Authentication</button>
|
||||
<button id="totp-setup-submit" disabled type="submit" class="btn">Enable Two-Factor Authentication</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
@ -95,8 +95,8 @@ and ensure every administrator account for this control panel does the same.</st
|
|||
</div>
|
||||
</form>
|
||||
|
||||
<div id="output-2fa" class="card bg-light">
|
||||
<div class="card-body"></div>
|
||||
<div id="output-2fa" class="panel panel-danger">
|
||||
<div class="panel-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -155,12 +155,12 @@ and ensure every administrator account for this control panel does the same.</st
|
|||
}
|
||||
|
||||
function hide_error() {
|
||||
el.output.querySelector('.card-body').innerHTML = '';
|
||||
el.output.querySelector('.panel-body').innerHTML = '';
|
||||
el.output.classList.remove('visible');
|
||||
}
|
||||
|
||||
function render_error(msg) {
|
||||
el.output.querySelector('.card-body').innerHTML = msg;
|
||||
el.output.querySelector('.panel-body').innerHTML = msg;
|
||||
el.output.classList.add('visible');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,135 +0,0 @@
|
|||
<style>
|
||||
</style>
|
||||
|
||||
<h2>SMTP Relays</h2>
|
||||
|
||||
<p>SMTP Relays are third-party services you can hand off the responsability of getting the mail delivered. They
|
||||
can be useful when, for example, port 25 is blocked.</p>
|
||||
|
||||
<p>Here, you can configure an authenticated SMTP relay (for example, <a href="https://sendgrid.com/"
|
||||
target="_blank">SendGrid</a>) over port 587.</p>
|
||||
|
||||
<div id="smtp_relay_config">
|
||||
<h3>SMTP Relay Configuration</h3>
|
||||
<form class="form-horizontal" role="form" onsubmit="set_smtp_relay_config(); return false;">
|
||||
<div class="form-group">
|
||||
<table id="smtp-relays" class="table" style="width: 600px">
|
||||
<tr>
|
||||
<td>
|
||||
<label for="use_relay" class="col-sm-1 control-label">Use Relay?</label>
|
||||
</td>
|
||||
<td>
|
||||
<div class="col-sm-10">
|
||||
<input type="checkbox" id="use_relay" name="use_relay" value="true"
|
||||
onclick="checkfields();">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<label for="relay_host" class="col-sm-1 control-label">Hostname</label>
|
||||
</td>
|
||||
<td>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="relay_host" placeholder="host.domain.tld">
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding: 0; font-weight: bold;">:587</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<label for="relay_use_auth" class="col-sm-1 control-label">Authenticate</label>
|
||||
</td>
|
||||
<td>
|
||||
<div class="col-sm-10">
|
||||
<input checked type="checkbox" id="relay_use_auth" name="relay_use_auth" value="true"
|
||||
onclick="checkfields();">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<label for="relay_auth_user" class="col-sm-1 control-label">Username</label>
|
||||
</td>
|
||||
<td>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="relay_auth_user" placeholder="user">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<label for="relay_auth_pass" class="col-sm-1 control-label">Password/Key</label>
|
||||
</td>
|
||||
<td>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" id="relay_auth_pass" placeholder="password">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit" class="btn btn-primary">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const use_relay = document.getElementById("use_relay")
|
||||
const relay_host = document.getElementById("relay_host")
|
||||
const relay_use_auth = document.getElementById("relay_use_auth")
|
||||
const relay_auth_user = document.getElementById("relay_auth_user")
|
||||
const relay_auth_pass = document.getElementById("relay_auth_pass")
|
||||
|
||||
function checkfields() {
|
||||
let relay_enabled = use_relay.checked
|
||||
let auth_enabled = relay_use_auth.checked
|
||||
|
||||
relay_host.disabled = !relay_enabled
|
||||
relay_use_auth.disabled = !relay_enabled
|
||||
relay_auth_user.disabled = !(relay_enabled && auth_enabled)
|
||||
relay_auth_pass.disabled = !(relay_enabled && auth_enabled)
|
||||
}
|
||||
|
||||
function show_smtp_relays() {
|
||||
api(
|
||||
"/system/smtp/relay",
|
||||
"GET",
|
||||
{},
|
||||
data => {
|
||||
use_relay.checked = data.enabled
|
||||
relay_host.value = data.host
|
||||
relay_use_auth.checked = data.auth_enabled
|
||||
relay_auth_user.value = data.user
|
||||
relay_auth_pass.value = ""
|
||||
|
||||
checkfields()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function set_smtp_relay_config() {
|
||||
api(
|
||||
"/system/smtp/relay",
|
||||
"POST",
|
||||
{
|
||||
enabled: use_relay.checked,
|
||||
host: relay_host.value,
|
||||
auth_enabled: relay_use_auth.checked,
|
||||
user: relay_auth_user.value,
|
||||
key: relay_auth_pass.value
|
||||
},
|
||||
() => {
|
||||
show_modal_error("Done!", "The configuration has been updated and Postfix was restarted successfully. Please make sure everything is functioning as intended.", () => {
|
||||
return false
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
</script>
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
<div id="ssl_provision_p" style="display: none; margin-top: 1.5em">
|
||||
<button onclick='return provision_tls_cert();' class='btn btn-primary' style="float: left; margin: 0 1.5em 1em 0;">Provision</button>
|
||||
<p><b>By provisioning the certificates, you’re agreeing to the <a href="https://acme-v01.api.letsencrypt.org/terms">Let’s Encrypt Subscriber Agreement</a>.</b></p>
|
||||
<p>A TLS certificate can be automatically provisioned from <a href="https://letsencrypt.org/" target="_blank">Let’s Encrypt</a>, a free TLS certificate provider, for:<br>
|
||||
<span class="text-primary"></span></p>
|
||||
</div>
|
||||
|
|
|
@ -12,12 +12,13 @@
|
|||
<form class="form-horizontal" role="form" onsubmit="set_custom_backup(); return false;">
|
||||
<div class="form-group">
|
||||
<label for="backup-target-type" class="col-sm-2 control-label">Backup to:</label>
|
||||
<div class="col-sm-3">
|
||||
<div class="col-sm-2">
|
||||
<select class="form-control" rows="1" id="backup-target-type" onchange="toggle_form()">
|
||||
<option value="off">Nowhere (Disable Backups)</option>
|
||||
<option value="local">{{hostname}}</option>
|
||||
<option value="rsync">rsync</option>
|
||||
<option value="s3">Amazon S3</option>
|
||||
<option value="b2">Backblaze B2</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -165,11 +166,6 @@
|
|||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Hide these buttons until we're sure we can use them :) -->
|
||||
<button id="create-full-backup-button" class="btn btn-primary" onclick="do_backup(true)" style="display: none;">Create Full Backup Now</button>
|
||||
<button id="create-incremental-backup-button" class="btn btn-primary" onclick="do_backup(false)" style="display: none;">Create Incremental Backup Now</button>
|
||||
|
||||
<script>
|
||||
|
||||
function toggle_form() {
|
||||
|
@ -216,17 +212,12 @@ function show_system_backup() {
|
|||
if (typeof r.backups == "undefined") {
|
||||
var tr = $('<tr><td colspan="3">Backups are turned off.</td></tr>');
|
||||
$('#backup-status tbody').append(tr);
|
||||
$('#create-full-backup-button').css("display","none")
|
||||
$('#create-incremental-backup-button').css("display","none")
|
||||
return;
|
||||
} else if (r.backups.length == 0) {
|
||||
var tr = $('<tr><td colspan="3">No backups have been made yet.</td></tr>');
|
||||
$('#backup-status tbody').append(tr);
|
||||
}
|
||||
|
||||
// Backups ARE enabled.
|
||||
$('#create-full-backup-button').css("display","unset")
|
||||
$('#create-incremental-backup-button').css("display","unset")
|
||||
for (var i = 0; i < r.backups.length; i++) {
|
||||
var b = r.backups[i];
|
||||
var tr = $('<tr/>');
|
||||
|
@ -352,29 +343,4 @@ function init_inputs(target_type) {
|
|||
set_host($('#backup-target-s3-host-select').val());
|
||||
}
|
||||
}
|
||||
|
||||
function do_backup(is_full) {
|
||||
let disclaimer = "The backup process will pause some services (such as PHP, Postfix and Dovecot). Depending on the size of the data this can take a while."
|
||||
if (!is_full) {
|
||||
disclaimer += "\nDepending on the amount of incremental backups done after the last full backup, the box may decide to do a full backup instead."
|
||||
}
|
||||
show_modal_confirm("Warning!", disclaimer, "Start Backup", () => {
|
||||
api(
|
||||
"/system/backup/new",
|
||||
"POST",
|
||||
{
|
||||
full: is_full
|
||||
},
|
||||
function(r) {
|
||||
// use .text() --- it's a text response, not html
|
||||
show_modal_error("Backup configuration", $("<p/>").text(r), function() { if (r == "OK") show_system_backup(); }); // refresh after modal on success
|
||||
},
|
||||
function(r) {
|
||||
// use .text() --- it's a text response, not html
|
||||
show_modal_error("Backup configuration", $("<p/>").text(r));
|
||||
});
|
||||
return false;
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
|
@ -6,36 +6,29 @@
|
|||
font-size: 120%;
|
||||
padding-top: 1.5em;
|
||||
}
|
||||
|
||||
#system-checks .heading.first td {
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#system-checks .status-error td {
|
||||
color: rgb(140, 0, 0);
|
||||
color: #733;
|
||||
}
|
||||
|
||||
#system-checks .status-warning td {
|
||||
color: rgb(170, 120, 0);
|
||||
color: #770;
|
||||
}
|
||||
|
||||
#system-checks .status-ok td {
|
||||
color: rgb(0, 140, 0);
|
||||
color: #040;
|
||||
}
|
||||
|
||||
#system-checks div.extra {
|
||||
display: none;
|
||||
margin-top: 1em;
|
||||
max-width: 50em;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#system-checks .showhide {
|
||||
#system-checks a.showhide {
|
||||
display: none;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
#system-checks .pre {
|
||||
margin: 1em;
|
||||
font-family: monospace;
|
||||
|
@ -43,8 +36,8 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col-md-push-9 col-md-3">
|
||||
|
||||
<div id="system-reboot-required" style="display: none; margin-bottom: 1em;">
|
||||
<button type="button" class="btn btn-danger" onclick="confirm_reboot(); return false;">Reboot Box</button>
|
||||
|
@ -52,19 +45,18 @@
|
|||
</div>
|
||||
|
||||
<div id="system-privacy-setting" style="display: none">
|
||||
<div><a onclick="return enable_privacy(!current_privacy_setting)" href="#"><span>Enable/Disable</span>
|
||||
New-Version Check</a></div>
|
||||
<p style="line-height: 125%"><small>(When enabled, status checks phone-home to check for a new release of
|
||||
Mail-in-a-Box.)</small></p>
|
||||
<div><a onclick="return enable_privacy(!current_privacy_setting)" href="#"><span>Enable/Disable</span> New-Version Check</a></div>
|
||||
<p style="line-height: 125%"><small>(When enabled, status checks phone-home to check for a new release of Mail-in-a-Box.)</small></p>
|
||||
</div>
|
||||
|
||||
</div> <!-- /col -->
|
||||
<br>
|
||||
<div>
|
||||
<div class="col-md-pull-3 col-md-8">
|
||||
|
||||
<table id="system-checks" class="table">
|
||||
<thead></thead>
|
||||
<tbody></tbody>
|
||||
<table id="system-checks" class="table" style="max-width: 60em">
|
||||
<thead>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div> <!-- /col -->
|
||||
|
@ -72,7 +64,7 @@
|
|||
|
||||
<script>
|
||||
function show_system_status() {
|
||||
$('#system-checks tbody').html("<tr><td class='text-muted'>Loading...</td></tr>")
|
||||
$('#system-checks tbody').html("<tr><td colspan='2' class='text-muted'>Loading...</td></tr>")
|
||||
|
||||
api(
|
||||
"/system/privacy",
|
||||
|
@ -102,20 +94,20 @@
|
|||
function(r) {
|
||||
$('#system-checks tbody').html("");
|
||||
for (var i = 0; i < r.length; i++) {
|
||||
var n = $("<tr><td class='status'/><td class='message'><p style='margin: 0'/><p class='showhide btn btn-light' href='#'/><div class='extra'></div></tr>");
|
||||
var n = $("<tr><td class='status'/><td class='message'><p style='margin: 0'/><div class='extra'/><a class='showhide' href='#'/></tr>");
|
||||
if (i == 0) n.addClass('first')
|
||||
if (r[i].type == "heading")
|
||||
n.addClass(r[i].type)
|
||||
else
|
||||
n.addClass("status-" + r[i].type)
|
||||
if (r[i].type == "ok") n.find('td.status').text("✔️")
|
||||
if (r[i].type == "error") n.find('td.status').text("❌")
|
||||
if (r[i].type == "warning") n.find('td.status').text("⚠️")
|
||||
if (r[i].type == "ok") n.find('td.status').text("✓")
|
||||
if (r[i].type == "error") n.find('td.status').text("✖")
|
||||
if (r[i].type == "warning") n.find('td.status').text("?")
|
||||
n.find('td.message p').text(r[i].text)
|
||||
$('#system-checks tbody').append(n);
|
||||
|
||||
if (r[i].extra.length > 0) {
|
||||
n.find('.showhide').show().text("Show More").click(function () {
|
||||
n.find('a.showhide').show().text("show more").click(function() {
|
||||
$(this).hide();
|
||||
$(this).parent().find('.extra').fadeIn();
|
||||
return false;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<h2>Users</h2>
|
||||
|
||||
<style>
|
||||
#user_table h4 { margin: 1em 0 0 0; }
|
||||
#user_table tr.account_inactive td.address { color: #888; text-decoration: line-through; }
|
||||
#user_table .actions { margin-top: .33em; font-size: 95%; }
|
||||
#user_table .account_inactive .if_active { display: none; }
|
||||
|
@ -134,8 +133,8 @@ function show_users() {
|
|||
function(r) {
|
||||
$('#user_table tbody').html("");
|
||||
for (var i = 0; i < r.length; i++) {
|
||||
var hdr = $("<tr><td colspan='3'><h4/></td></tr>");
|
||||
hdr.find('h4').text(r[i].domain);
|
||||
var hdr = $("<tr><th colspan='2' style='background-color: #EEE'></th></tr>");
|
||||
hdr.find('th').text(r[i].domain);
|
||||
$('#user_table tbody').append(hdr);
|
||||
|
||||
for (var k = 0; k < r[i].users.length; k++) {
|
||||
|
|
|
@ -206,16 +206,8 @@ def make_domain_config(domain, templates, ssl_certificates, env):
|
|||
|
||||
# Add in any user customizations in the includes/ folder.
|
||||
nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf")
|
||||
if not os.path.exists(nginx_conf_custom_include):
|
||||
with open(nginx_conf_custom_include, "a+") as f:
|
||||
f.writelines([
|
||||
f"# Custom configurations for {domain} go here\n",
|
||||
"# To use php: use the \"php-fpm\" alias\n\n",
|
||||
"index index.html index.htm;\n"
|
||||
])
|
||||
|
||||
if os.path.exists(nginx_conf_custom_include):
|
||||
nginx_conf_extra += "\tinclude %s;\n" % (nginx_conf_custom_include)
|
||||
|
||||
# PUT IT ALL TOGETHER
|
||||
|
||||
# Combine the pieces. Iteratively place each template into the "# ADDITIONAL DIRECTIVES HERE" placeholder
|
||||
|
|
|
@ -2,17 +2,43 @@
|
|||
#########################################################
|
||||
# This script is intended to be run like this:
|
||||
#
|
||||
# curl https://dvn.pt/power-miab | sudo bash
|
||||
# curl https://mailinabox.email/setup.sh | sudo bash
|
||||
#
|
||||
#########################################################
|
||||
|
||||
if [ -z "$TAG" ]; then
|
||||
# Make s
|
||||
OS=`lsb_release -d | sed 's/.*:\s*//'`
|
||||
if [ "$OS" == "Debian GNU/Linux 10 (buster)" -o "$(echo $OS | grep -o 'Ubuntu 20.04')" == "Ubuntu 20.04" ]; then
|
||||
TAG=v0.52.POWER.0
|
||||
# If a version to install isn't explicitly given as an environment
|
||||
# variable, then install the latest version. But the latest version
|
||||
# depends on the operating system. Existing Ubuntu 14.04 users need
|
||||
# to be able to upgrade to the latest version supporting Ubuntu 14.04,
|
||||
# in part because an upgrade is required before jumping to Ubuntu 18.04.
|
||||
# New users on Ubuntu 18.04 need to get the latest version number too.
|
||||
#
|
||||
# Also, the system status checks read this script for TAG = (without the
|
||||
# space, but if we put it in a comment it would confuse the status checks!)
|
||||
# to get the latest version, so the first such line must be the one that we
|
||||
# want to display in status checks.
|
||||
if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/20\.04\.[0-9]/20.04/' `" == "Ubuntu 20.04 LTS" ]; then
|
||||
# This machine is running Ubuntu 20.04.
|
||||
TAG=v0.53
|
||||
|
||||
elif [ "`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.
|
||||
TAG=v0.53
|
||||
|
||||
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.
|
||||
echo "You are installing the last version of Mail-in-a-Box that will"
|
||||
echo "support Ubuntu 14.04. If this is a new installation of Mail-in-a-Box,"
|
||||
echo "stop now and switch to a machine running Ubuntu 18.04. If you are"
|
||||
echo "upgrading an existing Mail-in-a-Box --- great. After upgrading this"
|
||||
echo "box, please visit https://mailinabox.email for notes on how to upgrade"
|
||||
echo "to Ubuntu 18.04."
|
||||
echo ""
|
||||
TAG=v0.30
|
||||
|
||||
else
|
||||
echo "This script must be run on a system running Debian 10 OR Ubuntu 20.04 LTS."
|
||||
echo "This script must be run on a system running Ubuntu 20.04, 18.04 or 14.04."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
|
|
@ -62,7 +62,8 @@ chmod go-rwx $STORAGE_ROOT/mail/dkim
|
|||
|
||||
tools/editconf.py /etc/opendmarc.conf -s \
|
||||
"Syslog=true" \
|
||||
"Socket=inet:8893@[127.0.0.1]"
|
||||
"Socket=inet:8893@[127.0.0.1]" \
|
||||
"FailureReports=true"
|
||||
|
||||
# SPFIgnoreResults causes the filter to ignore any SPF results in the header
|
||||
# of the message. This is useful if you want the filter to perfrom SPF checks
|
||||
|
@ -81,6 +82,12 @@ tools/editconf.py /etc/opendmarc.conf -s \
|
|||
tools/editconf.py /etc/opendmarc.conf -s \
|
||||
"SPFSelfValidate=true"
|
||||
|
||||
# Enables generation of failure reports for sending domains that publish a
|
||||
# "none" policy.
|
||||
|
||||
tools/editconf.py /etc/opendmarc.conf -s \
|
||||
"FailureReportsOnNone=true"
|
||||
|
||||
# AlwaysAddARHeader Adds an "Authentication-Results:" header field even to
|
||||
# unsigned messages from domains with no "signs all" policy. The reported DKIM
|
||||
# result will be "none" in such cases. Normally unsigned mail from non-strict
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#!/bin/bash
|
||||
source setup/functions.sh
|
||||
|
||||
echo Installing geoip packages...
|
||||
|
@ -8,8 +9,8 @@ echo Installing geoip packages...
|
|||
gunzip -c tools/goiplookup.gz > /usr/local/bin/goiplookup
|
||||
chmod +x /usr/local/bin/goiplookup
|
||||
|
||||
# check that geoipdb is older then 2 months, to not hit the server too often
|
||||
if [[ ! -d /usr/share/GeoIP || ! -f /usr/share/GeoIP/GeoIP.dat || $(find "/usr/share/GeoIP/GeoIP.dat" -mtime +60 -print) ]]; then
|
||||
# check that GeoLite2-Country.mmdb is older then 2 months, to not hit the server too often
|
||||
if [[ ! -d /usr/share/GeoIP || ! -f /usr/share/GeoIP/GeoLite2-Country.mmdb || $(find "/usr/share/GeoIP/GeoLite2-Country.mmdb" -mtime +60 -print) ]]; then
|
||||
echo updating goiplookup database
|
||||
goiplookup db-update
|
||||
else
|
||||
|
@ -48,6 +49,10 @@ fi
|
|||
|
||||
## Install geo ip lookup files
|
||||
|
||||
# check that GeoIP.dat is older then 2 months, to not hit the server too often
|
||||
if [[ ! -d /usr/share/GeoIP || ! -f /usr/share/GeoIP/GeoIP.dat || $(find "/usr/share/GeoIP/GeoIP.dat" -mtime +60 -print) ]]; then
|
||||
echo updating GeoIP database
|
||||
|
||||
# Move old file away if it exists
|
||||
if [ -f "/usr/share/GeoIP/GeoIP.dat" ]; then
|
||||
mv -f /usr/share/GeoIP/GeoIP.dat /usr/share/GeoIP/GeoIP.dat.bak
|
||||
|
@ -57,6 +62,7 @@ hide_output wget -P /usr/share/GeoIP/ https://dl.miyuru.lk/geoip/maxmind/country
|
|||
|
||||
if [ -f "/usr/share/GeoIP/maxmind.dat.gz" ]; then
|
||||
gunzip -c /usr/share/GeoIP/maxmind.dat.gz > /usr/share/GeoIP/GeoIP.dat
|
||||
rm -f /usr/share/GeoIP/maxmind.dat.gz
|
||||
else
|
||||
echo Did not correctly download maxmind geoip country database
|
||||
fi
|
||||
|
@ -79,6 +85,7 @@ hide_output wget -P /usr/share/GeoIP/ https://dl.miyuru.lk/geoip/maxmind/city/ma
|
|||
|
||||
if [ -f "/usr/share/GeoIP/maxmind.dat.gz" ]; then
|
||||
gunzip -c /usr/share/GeoIP/maxmind.dat.gz > /usr/share/GeoIP/GeoIPCity.dat
|
||||
rm -f /usr/share/GeoIP/maxmind.dat.gz
|
||||
else
|
||||
echo Did not correctly download maxmind geoip city database
|
||||
fi
|
||||
|
@ -91,4 +98,7 @@ if [ ! -f "/usr/share/GeoIP/GeoIPCity.dat" ]; then
|
|||
mv /usr/share/GeoIP/GeoIPCity.dat.bak /usr/share/GeoIP/GeoIPCity.dat
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo skipping GeoIP database update
|
||||
fi
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ tools/editconf.py /etc/postfix/main.cf \
|
|||
smtp_bind_address=$PRIVATE_IP \
|
||||
smtp_bind_address6=$PRIVATE_IPV6 \
|
||||
myhostname=$PRIMARY_HOSTNAME\
|
||||
smtpd_banner="\$myhostname ESMTP Hi, I'm a Power Mail-in-a-Box (Debian/Postfix)" \
|
||||
smtpd_banner="\$myhostname ESMTP Hi, I'm a Mail-in-a-Box (Ubuntu/Postfix; see https://mailinabox.email/)" \
|
||||
mydestination=localhost
|
||||
|
||||
# Tweak some queue settings:
|
||||
|
@ -260,19 +260,6 @@ chmod +x /etc/cron.daily/mailinabox-postgrey-whitelist
|
|||
tools/editconf.py /etc/postfix/main.cf \
|
||||
message_size_limit=134217728
|
||||
|
||||
# Store default configurations for SMTP relays:
|
||||
tools/editconf.py /etc/postfix/main.cf \
|
||||
smtp_sasl_auth_enable=no \
|
||||
smtp_sasl_password_maps="hash:/etc/postfix/sasl_passwd" \
|
||||
smtp_sasl_security_options=anonymous \
|
||||
smtp_sasl_tls_security_options=anonymous \
|
||||
smtp_tls_security_level=encrypt \
|
||||
header_size_limit=4096000
|
||||
|
||||
touch /etc/postfix/sasl_passwd
|
||||
chmod 600 /etc/postfix/sasl_passwd
|
||||
postmap /etc/postfix/sasl_passwd
|
||||
|
||||
# Allow the two SMTP ports in the firewall.
|
||||
|
||||
ufw_allow smtp
|
||||
|
|
|
@ -27,9 +27,10 @@ done
|
|||
# provision free TLS certificates.
|
||||
apt_install duplicity python3-pip virtualenv certbot
|
||||
|
||||
# b2sdk is used for backblaze backups.
|
||||
# boto is used for amazon aws backups.
|
||||
# Both are installed outside the pipenv, so they can be used by duplicity
|
||||
hide_output pip3 install --upgrade boto
|
||||
hide_output pip3 install --upgrade b2sdk boto
|
||||
|
||||
# Create a virtualenv for the installation of Python 3 packages
|
||||
# used by the management daemon.
|
||||
|
@ -50,7 +51,7 @@ hide_output $venv/bin/pip install --upgrade \
|
|||
rtyaml "email_validator>=1.0.0" "exclusiveprocess" \
|
||||
flask dnspython python-dateutil \
|
||||
qrcode[pil] pyotp \
|
||||
"idna>=2.0.0" "cryptography==2.2.2" boto psutil postfix-mta-sts-resolver
|
||||
"idna>=2.0.0" "cryptography==2.2.2" boto psutil postfix-mta-sts-resolver b2sdk
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
|
@ -69,32 +70,22 @@ rm -rf $assets_dir
|
|||
mkdir -p $assets_dir
|
||||
|
||||
# jQuery CDN URL
|
||||
jquery_version=3.5.1
|
||||
jquery_version=2.1.4
|
||||
jquery_url=https://code.jquery.com
|
||||
|
||||
# Get jQuery
|
||||
wget_verify $jquery_url/jquery-$jquery_version.min.js c8e1c8b386dc5b7a9184c763c88d19a346eb3342 $assets_dir/jquery.min.js
|
||||
wget_verify $jquery_url/jquery-$jquery_version.min.js 43dc554608df885a59ddeece1598c6ace434d747 $assets_dir/jquery.min.js
|
||||
|
||||
# Bootstrap CDN URL
|
||||
bootstrap_version=4.6.0
|
||||
bootstrap_version=3.3.7
|
||||
bootstrap_url=https://github.com/twbs/bootstrap/releases/download/v$bootstrap_version/bootstrap-$bootstrap_version-dist.zip
|
||||
|
||||
# Get Bootstrap
|
||||
wget_verify $bootstrap_url a1d385dc33cb415512d2f38215a554c4380dac2d /tmp/bootstrap.zip
|
||||
wget_verify $bootstrap_url e6b1000b94e835ffd37f4c6dcbdad43f4b48a02a /tmp/bootstrap.zip
|
||||
unzip -q /tmp/bootstrap.zip -d $assets_dir
|
||||
mv $assets_dir/bootstrap-$bootstrap_version-dist $assets_dir/bootstrap
|
||||
rm -f /tmp/bootstrap.zip
|
||||
|
||||
# FontAwesome CDN URL
|
||||
fontawesome_version=5.15.2
|
||||
fontawesome_url=https://github.com/FortAwesome/Font-Awesome/releases/download/$fontawesome_version/fontawesome-free-$fontawesome_version-web.zip
|
||||
|
||||
# Get FontAwesome
|
||||
wget_verify $fontawesome_url 2f0b3f88500238fa0be798d628a3e68c5784f165 /tmp/fontawesome.zip
|
||||
unzip -q /tmp/fontawesome.zip -d $assets_dir
|
||||
mv $assets_dir/fontawesome-free-$fontawesome_version-web $assets_dir/fontawesome
|
||||
rm -f /tmp/fontawesome.zip
|
||||
|
||||
# Create an init script to start the management daemon and keep it
|
||||
# running after a reboot.
|
||||
cat > $inst_dir/start <<EOF;
|
||||
|
@ -126,14 +117,3 @@ EOF
|
|||
|
||||
# Start the management server.
|
||||
restart_service mailinabox
|
||||
|
||||
# FOR DEVELOPMENT PURPOSES ONLY:
|
||||
# If there is a CA certificate in the folder, install it.
|
||||
# MIAB will only accept a manual certificate installation
|
||||
# if it is signed by a CA trusted by it.
|
||||
if [[ -f mailinabox-ca.crt ]]; then
|
||||
echo "Custom CA certificate detected. Installing..."
|
||||
rm -f /usr/local/share/ca-certificates/mailinabox-ca.crt
|
||||
cp mailinabox-ca.crt /usr/local/share/ca-certificates/
|
||||
update-ca-certificates --fresh
|
||||
fi
|
||||
|
|
|
@ -18,10 +18,10 @@ if [ -z "${NONINTERACTIVE:-}" ]; then
|
|||
hide_output pip3 install "email_validator>=1.0.0" || exit 1
|
||||
|
||||
message_box "Mail-in-a-Box Installation" \
|
||||
"Hello and thanks for deploying a (Power) Mail-in-a-Box!
|
||||
"Hello and thanks for deploying a Mail-in-a-Box!
|
||||
\n\nI'm going to ask you a few questions.
|
||||
\n\nTo change your answers later, just run 'sudo mailinabox' from the command line.
|
||||
\n\nNOTE: You should only install this on a brand new Debian/Ubuntu installation 100% dedicated to Mail-in-a-Box. Mail-in-a-Box will, for example, remove apache2."
|
||||
\n\nNOTE: You should only install this on a brand new Ubuntu installation 100% dedicated to Mail-in-a-Box. Mail-in-a-Box will, for example, remove apache2."
|
||||
fi
|
||||
|
||||
# The box needs a name.
|
||||
|
@ -124,6 +124,7 @@ if [ -z "${ADMIN_HOME_IP:-}" ]; then
|
|||
if [ -z "${DEFAULT_ADMIN_HOME_IP:-}" ]; then
|
||||
input_box "Admin Home IP Address" \
|
||||
"Enter the public IP address of the admin home, as given to you by your ISP.
|
||||
This will be used to prevent banning of the administrator IP address.
|
||||
\n\nAdmin Home IP address:" \
|
||||
"" \
|
||||
ADMIN_HOME_IP
|
||||
|
|
|
@ -112,7 +112,7 @@ apt_get_quiet autoremove
|
|||
# * openssh-client: provides ssh-keygen
|
||||
|
||||
echo Installing system packages...
|
||||
apt_install python3 python3-dev python3-pip \
|
||||
apt_install python3 python3-dev python3-pip python3-setuptools \
|
||||
netcat-openbsd wget curl git sudo coreutils bc \
|
||||
haveged pollinate openssh-client unzip \
|
||||
unattended-upgrades cron ntp fail2ban rsyslog
|
||||
|
|
|
@ -28,10 +28,11 @@ apt_install \
|
|||
# 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
|
||||
# whether we have the latest version of everything.
|
||||
VERSION=1.4.10
|
||||
HASH=36b2351030e1ebddb8e39190d7b0ba82b1bbec1b
|
||||
PERSISTENT_LOGIN_VERSION=6b3fc450cae23ccb2f393d0ef67aa319e877e435
|
||||
HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5
|
||||
|
||||
VERSION=1.4.11
|
||||
HASH=3877f0e70f29e7d0612155632e48c3db1e626be3
|
||||
PERSISTENT_LOGIN_VERSION=6b3fc450cae23ccb2f393d0ef67aa319e877e435 # version 5.2.0
|
||||
HTML5_NOTIFIER_VERSION=68d9ca194212e15b3c7225eb6085dbcf02fd13d7 # version 0.6.4+
|
||||
CARDDAV_VERSION=4.1.1
|
||||
CARDDAV_HASH=87b73661b7799b2079c28324311eddb4241242bb
|
||||
|
||||
|
|
|
@ -22,8 +22,8 @@ apt_install \
|
|||
phpenmod -v php imap
|
||||
|
||||
# Copy Z-Push into place.
|
||||
VERSION=2.6.1
|
||||
TARGETHASH=a4415f0dc0ed884acc8ad5c506944fc7e6d68eeb
|
||||
VERSION=2.6.2
|
||||
TARGETHASH=4b312d64227ef887b24d9cc8f0ae17519586f6e2
|
||||
needs_update=0 #NODOC
|
||||
if [ ! -f /usr/local/lib/z-push/version ]; then
|
||||
needs_update=1 #NODOC
|
||||
|
@ -102,7 +102,7 @@ EOF
|
|||
|
||||
# Restart service.
|
||||
|
||||
restart_service php$(php_version)-fpm
|
||||
restart_service php7.2-fpm
|
||||
|
||||
# Fix states after upgrade
|
||||
|
||||
|
|
|
@ -22,14 +22,53 @@
|
|||
# NAME VAL
|
||||
# UE
|
||||
|
||||
# create the new config file in memory
|
||||
|
||||
import sys, re
|
||||
|
||||
def edit_conf(filename, settings, delimiter_re, delimiter, comment_char, folded_lines = False, testing = False):
|
||||
# sanity check
|
||||
if len(sys.argv) < 3:
|
||||
print("usage: python3 editconf.py /etc/file.conf [-s] [-w] [-c <CHARACTER>] [-t] NAME=VAL [NAME=VAL ...]")
|
||||
sys.exit(1)
|
||||
|
||||
# parse command line arguments
|
||||
filename = sys.argv[1]
|
||||
settings = sys.argv[2:]
|
||||
|
||||
delimiter = "="
|
||||
delimiter_re = r"\s*=\s*"
|
||||
comment_char = "#"
|
||||
folded_lines = False
|
||||
testing = False
|
||||
while settings[0][0] == "-" and settings[0] != "--":
|
||||
opt = settings.pop(0)
|
||||
if opt == "-s":
|
||||
# Space is the delimiter
|
||||
delimiter = " "
|
||||
delimiter_re = r"\s+"
|
||||
elif opt == "-w":
|
||||
# Line folding is possible in this file.
|
||||
folded_lines = True
|
||||
elif opt == "-c":
|
||||
# Specifies a different comment character.
|
||||
comment_char = settings.pop(0)
|
||||
elif opt == "-t":
|
||||
testing = True
|
||||
else:
|
||||
print("Invalid option.")
|
||||
sys.exit(1)
|
||||
|
||||
# sanity check command line
|
||||
for setting in settings:
|
||||
try:
|
||||
name, value = setting.split("=", 1)
|
||||
except:
|
||||
import subprocess
|
||||
print("Invalid command line: ", subprocess.list2cmdline(sys.argv))
|
||||
|
||||
# create the new config file in memory
|
||||
|
||||
found = set()
|
||||
buf = ""
|
||||
input_lines = list(open(filename, "r+"))
|
||||
input_lines = list(open(filename))
|
||||
|
||||
while len(input_lines) > 0:
|
||||
line = input_lines.pop(0)
|
||||
|
@ -96,48 +135,3 @@ def edit_conf(filename, settings, delimiter_re, delimiter, comment_char, folded_
|
|||
else:
|
||||
# Just print the new file to stdout.
|
||||
print(buf)
|
||||
|
||||
# Run standalone
|
||||
if __name__ == "__main__":
|
||||
# sanity check
|
||||
if len(sys.argv) < 3:
|
||||
print("usage: python3 editconf.py /etc/file.conf [-s] [-w] [-c <CHARACTER>] [-t] NAME=VAL [NAME=VAL ...]")
|
||||
sys.exit(1)
|
||||
|
||||
# parse command line arguments
|
||||
filename = sys.argv[1]
|
||||
settings = sys.argv[2:]
|
||||
|
||||
delimiter = "="
|
||||
delimiter_re = r"\s*=\s*"
|
||||
comment_char = "#"
|
||||
folded_lines = False
|
||||
testing = False
|
||||
while settings[0][0] == "-" and settings[0] != "--":
|
||||
opt = settings.pop(0)
|
||||
if opt == "-s":
|
||||
# Space is the delimiter
|
||||
delimiter = " "
|
||||
delimiter_re = r"\s+"
|
||||
elif opt == "-w":
|
||||
# Line folding is possible in this file.
|
||||
folded_lines = True
|
||||
elif opt == "-c":
|
||||
# Specifies a different comment character.
|
||||
comment_char = settings.pop(0)
|
||||
elif opt == "-t":
|
||||
testing = True
|
||||
else:
|
||||
print("Invalid option.")
|
||||
sys.exit(1)
|
||||
|
||||
# sanity check command line
|
||||
for setting in settings:
|
||||
try:
|
||||
name, value = setting.split("=", 1)
|
||||
except:
|
||||
import subprocess
|
||||
print("Invalid command line: ", subprocess.list2cmdline(sys.argv))
|
||||
sys.exit(1)
|
||||
|
||||
edit_conf(filename, settings, delimiter_re, delimiter, comment_char, folded_lines, testing)
|
||||
|
|
Loading…
Reference in New Issue