mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2024-12-24 07:37:04 +00:00
Merge branch 'main' into dnstimeouthandling
This commit is contained in:
commit
8c6d822a49
27
CHANGELOG.md
27
CHANGELOG.md
@ -1,6 +1,33 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
Version 61.1 (January 28, 2022)
|
||||
-------------------------------
|
||||
|
||||
* Fixed rsync backups not working with the default port.
|
||||
* Reverted "Improve error messages in the management tools when external command-line tools are run." because of the possibility of user secrets being included in error messages.
|
||||
* Fix for TLS certificate SHA fingerprint not being displayed during setup.
|
||||
|
||||
Version 61 (January 21, 2023)
|
||||
-----------------------------
|
||||
|
||||
System:
|
||||
|
||||
* fail2ban didn't start after setup.
|
||||
|
||||
Mail:
|
||||
|
||||
* Disable Roundcube password plugin since it was corrupting the user database.
|
||||
|
||||
Control panel:
|
||||
|
||||
* Fix changing existing backup settings when the rsync type is used.
|
||||
* Allow setting a custom port for rsync backups.
|
||||
* Fixes to DNS lookups during status checks when there are timeouts, enforce timeouts better.
|
||||
* A new check is added to ensure fail2ban is running.
|
||||
* Fixed a color.
|
||||
* Improve error messages in the management tools when external command-line tools are run.
|
||||
|
||||
Version 60.1 (October 30, 2022)
|
||||
-------------------------------
|
||||
|
||||
|
@ -60,7 +60,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 v60.1
|
||||
$ git checkout v61.1
|
||||
|
||||
Begin the installation.
|
||||
|
||||
|
@ -213,9 +213,20 @@ def get_duplicity_additional_args(env):
|
||||
config = get_backup_config(env)
|
||||
|
||||
if get_target_type(config) == 'rsync':
|
||||
# Extract a port number for the ssh transport. Duplicity accepts the
|
||||
# optional port number syntax in the target, but it doesn't appear to act
|
||||
# on it, so we set the ssh port explicitly via the duplicity options.
|
||||
from urllib.parse import urlsplit
|
||||
try:
|
||||
port = urlsplit(config["target"]).port
|
||||
except ValueError:
|
||||
port = 22
|
||||
if port is None:
|
||||
port = 22
|
||||
|
||||
return [
|
||||
"--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\"",
|
||||
f"--ssh-options= -i /root/.ssh/id_rsa_miab -p {port}",
|
||||
f"--rsync-options= -e \"/usr/bin/ssh -oStrictHostKeyChecking=no -oBatchMode=yes -p {port} -i /root/.ssh/id_rsa_miab\"",
|
||||
]
|
||||
elif get_target_type(config) == 's3':
|
||||
# See note about hostname in get_duplicity_target_url.
|
||||
@ -408,6 +419,16 @@ def list_target_files(config):
|
||||
rsync_fn_size_re = re.compile(r'.* ([^ ]*) [^ ]* [^ ]* (.*)')
|
||||
rsync_target = '{host}:{path}'
|
||||
|
||||
# Strip off any trailing port specifier because it's not valid in rsync's
|
||||
# DEST syntax. Explicitly set the port number for the ssh transport.
|
||||
user_host, *_ = target.netloc.rsplit(':', 1)
|
||||
try:
|
||||
port = target.port
|
||||
except ValueError:
|
||||
port = 22
|
||||
if port is None:
|
||||
port = 22
|
||||
|
||||
target_path = target.path
|
||||
if not target_path.endswith('/'):
|
||||
target_path = target_path + '/'
|
||||
@ -416,11 +437,11 @@ def list_target_files(config):
|
||||
|
||||
rsync_command = [ 'rsync',
|
||||
'-e',
|
||||
'/usr/bin/ssh -i /root/.ssh/id_rsa_miab -oStrictHostKeyChecking=no -oBatchMode=yes',
|
||||
f'/usr/bin/ssh -i /root/.ssh/id_rsa_miab -oStrictHostKeyChecking=no -oBatchMode=yes -p {port}',
|
||||
'--list-only',
|
||||
'-r',
|
||||
rsync_target.format(
|
||||
host=target.netloc,
|
||||
host=user_host,
|
||||
path=target_path)
|
||||
]
|
||||
|
||||
@ -531,7 +552,8 @@ def get_backup_config(env, for_save=False, for_ui=False):
|
||||
|
||||
# Merge in anything written to custom.yaml.
|
||||
try:
|
||||
custom_config = rtyaml.load(open(os.path.join(backup_root, 'custom.yaml')))
|
||||
with open(os.path.join(backup_root, 'custom.yaml'), 'r') as f:
|
||||
custom_config = rtyaml.load(f)
|
||||
if not isinstance(custom_config, dict): raise ValueError() # caught below
|
||||
config.update(custom_config)
|
||||
except:
|
||||
@ -556,7 +578,8 @@ def get_backup_config(env, for_save=False, for_ui=False):
|
||||
config["target"] = "file://" + config["file_target_directory"]
|
||||
ssh_pub_key = os.path.join('/root', '.ssh', 'id_rsa_miab.pub')
|
||||
if os.path.exists(ssh_pub_key):
|
||||
config["ssh_pub_key"] = open(ssh_pub_key, 'r').read()
|
||||
with open(ssh_pub_key, 'r') as f:
|
||||
config["ssh_pub_key"] = f.read()
|
||||
|
||||
return config
|
||||
|
||||
|
@ -47,7 +47,8 @@ def read_password():
|
||||
return first
|
||||
|
||||
def setup_key_auth(mgmt_uri):
|
||||
key = open('/var/lib/mailinabox/api.key').read().strip()
|
||||
with open('/var/lib/mailinabox/api.key', 'r') as f:
|
||||
key = f.read().strip()
|
||||
|
||||
auth_handler = urllib.request.HTTPBasicAuthHandler()
|
||||
auth_handler.add_password(
|
||||
|
3
management/dns_update.py
Executable file → Normal file
3
management/dns_update.py
Executable file → Normal file
@ -815,7 +815,8 @@ def write_opendkim_tables(domains, 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')))
|
||||
with open(os.path.join(env['STORAGE_ROOT'], 'dns/custom.yaml'), 'r') as f:
|
||||
custom_dns = rtyaml.load(f)
|
||||
if not isinstance(custom_dns, dict): raise ValueError() # caught below
|
||||
except:
|
||||
return [ ]
|
||||
|
@ -73,7 +73,8 @@ def scan_files(collector):
|
||||
continue
|
||||
elif fn[-3:] == '.gz':
|
||||
tmp_file = tempfile.NamedTemporaryFile()
|
||||
shutil.copyfileobj(gzip.open(fn), tmp_file)
|
||||
with gzip.open(fn, 'rb') as f:
|
||||
shutil.copyfileobj(f, tmp_file)
|
||||
|
||||
if VERBOSE:
|
||||
print("Processing file", fn, "...")
|
||||
|
@ -535,7 +535,8 @@ def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring
|
||||
# Second, check that the certificate matches the private key.
|
||||
if ssl_private_key is not None:
|
||||
try:
|
||||
priv_key = load_pem(open(ssl_private_key, 'rb').read())
|
||||
with open(ssl_private_key, 'rb') as f:
|
||||
priv_key = load_pem(f.read())
|
||||
except ValueError as e:
|
||||
return ("The private key file %s is not a private key file: %s" % (ssl_private_key, str(e)), None)
|
||||
|
||||
|
@ -95,6 +95,12 @@ def run_services_checks(env, output, pool):
|
||||
fatal = fatal or fatal2
|
||||
output2.playback(output)
|
||||
|
||||
# Check fail2ban.
|
||||
code, ret = shell('check_output', ["fail2ban-client", "status"], capture_stderr=True, trap=True)
|
||||
if code != 0:
|
||||
output.print_error("fail2ban is not running.")
|
||||
all_running = False
|
||||
|
||||
if all_running:
|
||||
output.print_ok("All system services are running.")
|
||||
|
||||
@ -207,7 +213,8 @@ def check_ssh_password(env, output):
|
||||
# the configuration file.
|
||||
if not os.path.exists("/etc/ssh/sshd_config"):
|
||||
return
|
||||
sshd = open("/etc/ssh/sshd_config").read()
|
||||
with open("/etc/ssh/sshd_config", "r") as f:
|
||||
sshd = f.read()
|
||||
if re.search("\nPasswordAuthentication\s+yes", sshd) \
|
||||
or not re.search("\nPasswordAuthentication\s+no", sshd):
|
||||
output.print_error("""The SSH server on this machine permits password-based login. A more secure
|
||||
@ -594,7 +601,8 @@ def check_dnssec(domain, env, output, dns_zonefiles, is_checking_primary=False):
|
||||
# record that we suggest using is for the KSK (and that's how the DS records were generated).
|
||||
# We'll also give the nice name for the key algorithm.
|
||||
dnssec_keys = load_env_vars_from_file(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/%s.conf' % alg_name_map[ds_alg]))
|
||||
dnsssec_pubkey = open(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/' + dnssec_keys['KSK'] + '.key')).read().split("\t")[3].split(" ")[3]
|
||||
with open(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/' + dnssec_keys['KSK'] + '.key'), 'r') as f:
|
||||
dnsssec_pubkey = f.read().split("\t")[3].split(" ")[3]
|
||||
|
||||
expected_ds_records[ (ds_keytag, ds_alg, ds_digalg, ds_digest) ] = {
|
||||
"record": rr_ds,
|
||||
@ -966,7 +974,8 @@ def run_and_output_changes(env, pool):
|
||||
# Load previously saved status checks.
|
||||
cache_fn = "/var/cache/mailinabox/status_checks.json"
|
||||
if os.path.exists(cache_fn):
|
||||
prev = json.load(open(cache_fn))
|
||||
with open(cache_fn, 'r') as f:
|
||||
prev = json.load(f)
|
||||
|
||||
# Group the serial output into categories by the headings.
|
||||
def group_by_heading(lines):
|
||||
|
@ -72,11 +72,6 @@
|
||||
html {
|
||||
filter: invert(100%) hue-rotate(180deg);
|
||||
}
|
||||
|
||||
/* Set explicit background color (necessary for Firefox) */
|
||||
html {
|
||||
background-color: #111;
|
||||
}
|
||||
|
||||
/* Override Boostrap theme here to give more contrast. The black turns to white by the filter. */
|
||||
.form-control {
|
||||
|
@ -45,6 +45,10 @@
|
||||
<label for="backup-target-rsync-host" class="col-sm-2 control-label">Hostname</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" placeholder="hostname.local" class="form-control" rows="1" id="backup-target-rsync-host">
|
||||
<div class="small" style="margin-top: 2px">
|
||||
The hostname at your rsync provider, e.g. <tt>da2327.rsync.net</tt>. Optionally includes a colon
|
||||
and the provider's non-standard ssh port number, e.g. <tt>u215843.your-storagebox.de:23</tt>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group backup-target-rsync">
|
||||
@ -259,12 +263,11 @@ function show_custom_backup() {
|
||||
} else if (r.target == "off") {
|
||||
$("#backup-target-type").val("off");
|
||||
} else if (r.target.substring(0, 8) == "rsync://") {
|
||||
$("#backup-target-type").val("rsync");
|
||||
var path = r.target.substring(8).split('//');
|
||||
var host_parts = path.shift().split('@');
|
||||
$("#backup-target-rsync-user").val(host_parts[0]);
|
||||
$("#backup-target-rsync-host").val(host_parts[1]);
|
||||
$("#backup-target-rsync-path").val('/'+path[0]);
|
||||
const spec = url_split(r.target);
|
||||
$("#backup-target-type").val(spec.scheme);
|
||||
$("#backup-target-rsync-user").val(spec.user);
|
||||
$("#backup-target-rsync-host").val(spec.host);
|
||||
$("#backup-target-rsync-path").val(spec.path);
|
||||
} else if (r.target.substring(0, 5) == "s3://") {
|
||||
$("#backup-target-type").val("s3");
|
||||
var hostpath = r.target.substring(5).split('/');
|
||||
@ -344,4 +347,31 @@ function init_inputs(target_type) {
|
||||
set_host($('#backup-target-s3-host-select').val());
|
||||
}
|
||||
}
|
||||
|
||||
// Return a two-element array of the substring preceding and the substring following
|
||||
// the first occurence of separator in string. Return [undefined, string] if the
|
||||
// separator does not appear in string.
|
||||
const split1_rest = (string, separator) => {
|
||||
const index = string.indexOf(separator);
|
||||
return (index >= 0) ? [string.substring(0, index), string.substring(index + separator.length)] : [undefined, string];
|
||||
};
|
||||
|
||||
// Note: The manifest JS URL class does not work in some security-conscious
|
||||
// settings, e.g. Brave browser, so we roll our own that handles only what we need.
|
||||
//
|
||||
// Use greedy separator parsing to get parts of a MIAB backup target url.
|
||||
// Note: path will not include a leading forward slash '/'
|
||||
const url_split = url => {
|
||||
const [ scheme, scheme_rest ] = split1_rest(url, '://');
|
||||
const [ user, user_rest ] = split1_rest(scheme_rest, '@');
|
||||
const [ host, path ] = split1_rest(user_rest, '/');
|
||||
|
||||
return {
|
||||
scheme,
|
||||
user,
|
||||
host,
|
||||
path,
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
@ -10,13 +10,13 @@
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
}
|
||||
#system-checks .status-error td {
|
||||
#system-checks .status-error td, .summary-error {
|
||||
color: #733;
|
||||
}
|
||||
#system-checks .status-warning td {
|
||||
#system-checks .status-warning td, .summary-warning {
|
||||
color: #770;
|
||||
}
|
||||
#system-checks .status-ok td {
|
||||
#system-checks .status-ok td, .summary-ok {
|
||||
color: #040;
|
||||
}
|
||||
#system-checks div.extra {
|
||||
@ -52,6 +52,9 @@
|
||||
</div> <!-- /col -->
|
||||
<div class="col-md-pull-3 col-md-8">
|
||||
|
||||
<div id="system-checks-summary">
|
||||
</div>
|
||||
|
||||
<table id="system-checks" class="table" style="max-width: 60em">
|
||||
<thead>
|
||||
</thead>
|
||||
@ -64,6 +67,9 @@
|
||||
|
||||
<script>
|
||||
function show_system_status() {
|
||||
const summary = $('#system-checks-summary');
|
||||
summary.html("");
|
||||
|
||||
$('#system-checks tbody').html("<tr><td colspan='2' class='text-muted'>Loading...</td></tr>")
|
||||
|
||||
api(
|
||||
@ -93,6 +99,12 @@ function show_system_status() {
|
||||
{ },
|
||||
function(r) {
|
||||
$('#system-checks tbody').html("");
|
||||
const ok_symbol = "✓";
|
||||
const error_symbol = "✖";
|
||||
const warning_symbol = "?";
|
||||
|
||||
let count_by_status = { ok: 0, error: 0, warning: 0 };
|
||||
|
||||
for (var i = 0; i < r.length; i++) {
|
||||
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')
|
||||
@ -100,9 +112,12 @@ function show_system_status() {
|
||||
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(ok_symbol);
|
||||
if (r[i].type == "error") n.find('td.status').text(error_symbol);
|
||||
if (r[i].type == "warning") n.find('td.status').text(warning_symbol);
|
||||
count_by_status[r[i].type]++;
|
||||
|
||||
n.find('td.message p').text(r[i].text)
|
||||
$('#system-checks tbody').append(n);
|
||||
|
||||
@ -122,8 +137,17 @@ function show_system_status() {
|
||||
n.find('> td.message > div').append(m);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Summary counts
|
||||
summary.html("Summary: ");
|
||||
if (count_by_status['error'] + count_by_status['warning'] == 0) {
|
||||
summary.append($('<span class="summary-ok"/>').text(`All ${count_by_status['ok']} ${ok_symbol} OK`));
|
||||
} else {
|
||||
summary.append($('<span class="summary-ok"/>').text(`${count_by_status['ok']} ${ok_symbol} OK, `));
|
||||
summary.append($('<span class="summary-error"/>').text(`${count_by_status['error']} ${error_symbol} Error, `));
|
||||
summary.append($('<span class="summary-warning"/>').text(`${count_by_status['warning']} ${warning_symbol} Warning`));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var current_privacy_setting = null;
|
||||
|
@ -14,7 +14,9 @@ def load_env_vars_from_file(fn):
|
||||
# Load settings from a KEY=VALUE file.
|
||||
import collections
|
||||
env = collections.OrderedDict()
|
||||
for line in open(fn): env.setdefault(*line.strip().split("=", 1))
|
||||
with open(fn, 'r') as f:
|
||||
for line in f:
|
||||
env.setdefault(*line.strip().split("=", 1))
|
||||
return env
|
||||
|
||||
def save_environment(env):
|
||||
@ -34,7 +36,8 @@ def load_settings(env):
|
||||
import rtyaml
|
||||
fn = os.path.join(env['STORAGE_ROOT'], 'settings.yaml')
|
||||
try:
|
||||
config = rtyaml.load(open(fn, "r"))
|
||||
with open(fn, "r") as f:
|
||||
config = rtyaml.load(f)
|
||||
if not isinstance(config, dict): raise ValueError() # caught below
|
||||
return config
|
||||
except:
|
||||
|
@ -63,7 +63,8 @@ def get_web_domains_with_root_overrides(env):
|
||||
root_overrides = { }
|
||||
nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
|
||||
if os.path.exists(nginx_conf_custom_fn):
|
||||
custom_settings = rtyaml.load(open(nginx_conf_custom_fn))
|
||||
with open(nginx_conf_custom_fn, 'r') as f:
|
||||
custom_settings = rtyaml.load(f)
|
||||
for domain, settings in custom_settings.items():
|
||||
for type, value in [('redirect', settings.get('redirects', {}).get('/')),
|
||||
('proxy', settings.get('proxies', {}).get('/'))]:
|
||||
@ -75,13 +76,18 @@ def do_web_update(env):
|
||||
# Pre-load what SSL certificates we will use for each domain.
|
||||
ssl_certificates = get_ssl_certificates(env)
|
||||
|
||||
# Helper for reading config files and templates
|
||||
def read_conf(conf_fn):
|
||||
with open(os.path.join(os.path.dirname(__file__), "../conf", conf_fn), "r") as f:
|
||||
return f.read()
|
||||
|
||||
# Build an nginx configuration file.
|
||||
nginx_conf = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-top.conf")).read()
|
||||
nginx_conf = read_conf("nginx-top.conf")
|
||||
|
||||
# Load the templates.
|
||||
template0 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx.conf")).read()
|
||||
template1 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-alldomains.conf")).read()
|
||||
template2 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-primaryonly.conf")).read()
|
||||
template0 = read_conf("nginx.conf")
|
||||
template1 = read_conf("nginx-alldomains.conf")
|
||||
template2 = read_conf("nginx-primaryonly.conf")
|
||||
template3 = "\trewrite ^(.*) https://$REDIRECT_DOMAIN$1 permanent;\n"
|
||||
|
||||
# Add the PRIMARY_HOST configuration first so it becomes nginx's default server.
|
||||
@ -141,11 +147,8 @@ def make_domain_config(domain, templates, ssl_certificates, env):
|
||||
def hashfile(filepath):
|
||||
import hashlib
|
||||
sha1 = hashlib.sha1()
|
||||
f = open(filepath, 'rb')
|
||||
try:
|
||||
with open(filepath, 'rb') as f:
|
||||
sha1.update(f.read())
|
||||
finally:
|
||||
f.close()
|
||||
return sha1.hexdigest()
|
||||
nginx_conf_extra += "\t# ssl files sha1: %s / %s\n" % (hashfile(tls_cert["private-key"]), hashfile(tls_cert["certificate"]))
|
||||
|
||||
@ -153,7 +156,8 @@ def make_domain_config(domain, templates, ssl_certificates, env):
|
||||
hsts = "yes"
|
||||
nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
|
||||
if os.path.exists(nginx_conf_custom_fn):
|
||||
yaml = rtyaml.load(open(nginx_conf_custom_fn))
|
||||
with open(nginx_conf_custom_fn, 'r') as f:
|
||||
yaml = rtyaml.load(f)
|
||||
if domain in yaml:
|
||||
yaml = yaml[domain]
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
Mail-in-a-Box Security Guide
|
||||
============================
|
||||
|
||||
Mail-in-a-Box turns a fresh Ubuntu 18.04 LTS 64-bit machine into a mail server appliance by installing and configuring various components.
|
||||
Mail-in-a-Box turns a fresh Ubuntu 22.04 LTS 64-bit machine into a mail server appliance by installing and configuring various components.
|
||||
|
||||
This page documents the security posture of Mail-in-a-Box. The term “box” is used below to mean a configured Mail-in-a-Box.
|
||||
|
||||
|
@ -23,7 +23,7 @@ if [ -z "$TAG" ]; then
|
||||
if [ "$UBUNTU_VERSION" == "Ubuntu 22.04 LTS" ]; then
|
||||
# This machine is running Ubuntu 22.04, which is supported by
|
||||
# Mail-in-a-Box versions 60 and later.
|
||||
TAG=v60.1
|
||||
TAG=v61.1
|
||||
elif [ "$UBUNTU_VERSION" == "Ubuntu 18.04 LTS" ]; then
|
||||
# This machine is running Ubuntu 18.04, which is supported by
|
||||
# Mail-in-a-Box versions 0.40 through 5x.
|
||||
|
@ -202,13 +202,13 @@ chmod -R o-rwx /etc/dovecot
|
||||
|
||||
# Ensure mailbox files have a directory that exists and are owned by the mail user.
|
||||
mkdir -p $STORAGE_ROOT/mail/mailboxes
|
||||
chown -R mail.mail $STORAGE_ROOT/mail/mailboxes
|
||||
chown -R mail:mail $STORAGE_ROOT/mail/mailboxes
|
||||
|
||||
# Same for the sieve scripts.
|
||||
mkdir -p $STORAGE_ROOT/mail/sieve
|
||||
mkdir -p $STORAGE_ROOT/mail/sieve/global_before
|
||||
mkdir -p $STORAGE_ROOT/mail/sieve/global_after
|
||||
chown -R mail.mail $STORAGE_ROOT/mail/sieve
|
||||
chown -R mail:mail $STORAGE_ROOT/mail/sieve
|
||||
|
||||
# Allow the IMAP/POP ports in the firewall.
|
||||
ufw_allow imaps
|
||||
|
@ -34,8 +34,8 @@ contact.admin.always_send warning critical
|
||||
EOF
|
||||
|
||||
# The Debian installer touches these files and chowns them to www-data:adm for use with spawn-fcgi
|
||||
chown munin. /var/log/munin/munin-cgi-html.log
|
||||
chown munin. /var/log/munin/munin-cgi-graph.log
|
||||
chown munin /var/log/munin/munin-cgi-html.log
|
||||
chown munin /var/log/munin/munin-cgi-graph.log
|
||||
|
||||
# ensure munin-node knows the name of this machine
|
||||
# and reduce logging level to warning
|
||||
|
@ -110,7 +110,7 @@ InstallNextcloud() {
|
||||
# Make sure permissions are correct or the upgrade step won't run.
|
||||
# $STORAGE_ROOT/owncloud may not yet exist, so use -f to suppress
|
||||
# that error.
|
||||
chown -f -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud || /bin/true
|
||||
chown -f -R www-data:www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud || /bin/true
|
||||
|
||||
# If this isn't a new installation, immediately run the upgrade script.
|
||||
# Then check for success (0=ok and 3=no upgrade needed, both are success).
|
||||
@ -259,7 +259,7 @@ EOF
|
||||
EOF
|
||||
|
||||
# Set permissions
|
||||
chown -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
|
||||
chown -R www-data:www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
|
||||
|
||||
# Execute Nextcloud's setup step, which creates the Nextcloud sqlite database.
|
||||
# It also wipes it if it exists. And it updates config.php with database
|
||||
@ -311,7 +311,7 @@ var_export(\$CONFIG);
|
||||
echo ";";
|
||||
?>
|
||||
EOF
|
||||
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
|
||||
chown www-data:www-data $STORAGE_ROOT/owncloud/config.php
|
||||
|
||||
# Enable/disable apps. Note that this must be done after the Nextcloud setup.
|
||||
# The firstrunwizard gave Josh all sorts of problems, so disabling that.
|
||||
|
@ -85,7 +85,7 @@ f=$STORAGE_ROOT
|
||||
while [[ $f != / ]]; do chmod a+rx "$f"; f=$(dirname "$f"); done;
|
||||
if [ ! -f $STORAGE_ROOT/mailinabox.version ]; then
|
||||
setup/migrate.py --current > $STORAGE_ROOT/mailinabox.version
|
||||
chown $STORAGE_USER.$STORAGE_USER $STORAGE_ROOT/mailinabox.version
|
||||
chown $STORAGE_USER:$STORAGE_USER $STORAGE_ROOT/mailinabox.version
|
||||
fi
|
||||
|
||||
# Save the global options in /etc/mailinabox.conf so that standalone
|
||||
@ -167,7 +167,7 @@ if management/status_checks.py --check-primary-hostname; then
|
||||
echo "If you have a DNS problem put the box's IP address in the URL"
|
||||
echo "(https://$PUBLIC_IP/admin) but then check the TLS fingerprint:"
|
||||
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint -sha256\
|
||||
| sed "s/SHA256 Fingerprint=//"
|
||||
| sed "s/SHA256 Fingerprint=//i"
|
||||
else
|
||||
echo https://$PUBLIC_IP/admin
|
||||
echo
|
||||
@ -175,7 +175,7 @@ else
|
||||
echo the certificate fingerprint matches:
|
||||
echo
|
||||
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint -sha256\
|
||||
| sed "s/SHA256 Fingerprint=//"
|
||||
| sed "s/SHA256 Fingerprint=//i"
|
||||
echo
|
||||
echo Then you can confirm the security exception and continue.
|
||||
echo
|
||||
|
@ -373,3 +373,5 @@ cp -f conf/fail2ban/filter.d/* /etc/fail2ban/filter.d/
|
||||
# scripts will ensure the files exist and then fail2ban is given another
|
||||
# restart at the very end of setup.
|
||||
restart_service fail2ban
|
||||
|
||||
systemctl enable fail2ban
|
||||
|
@ -134,7 +134,7 @@ cat > $RCM_CONFIG <<EOF;
|
||||
\$config['product_name'] = '$PRIMARY_HOSTNAME Webmail';
|
||||
\$config['cipher_method'] = 'AES-256-CBC'; # persistent login cookie and potentially other things
|
||||
\$config['des_key'] = '$SECRET_KEY'; # 37 characters -> ~256 bits for AES-256, see above
|
||||
\$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'persistent_login', 'carddav');
|
||||
\$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'managesieve', 'jqueryui', 'persistent_login', 'carddav');
|
||||
\$config['skin'] = 'elastic';
|
||||
\$config['login_autocomplete'] = 2;
|
||||
\$config['login_username_filter'] = 'email';
|
||||
@ -170,7 +170,7 @@ EOF
|
||||
|
||||
# Create writable directories.
|
||||
mkdir -p /var/log/roundcubemail /var/tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
|
||||
chown -R www-data.www-data /var/log/roundcubemail /var/tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
|
||||
chown -R www-data:www-data /var/log/roundcubemail /var/tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
|
||||
|
||||
# Ensure the log file monitored by fail2ban exists, or else fail2ban can't start.
|
||||
sudo -u www-data touch /var/log/roundcubemail/errors.log
|
||||
@ -194,14 +194,14 @@ usermod -a -G dovecot www-data
|
||||
|
||||
# set permissions so that PHP can use users.sqlite
|
||||
# could use dovecot instead of www-data, but not sure it matters
|
||||
chown root.www-data $STORAGE_ROOT/mail
|
||||
chown root:www-data $STORAGE_ROOT/mail
|
||||
chmod 775 $STORAGE_ROOT/mail
|
||||
chown root.www-data $STORAGE_ROOT/mail/users.sqlite
|
||||
chown root:www-data $STORAGE_ROOT/mail/users.sqlite
|
||||
chmod 664 $STORAGE_ROOT/mail/users.sqlite
|
||||
|
||||
# Fix Carddav permissions:
|
||||
chown -f -R root.www-data ${RCM_PLUGIN_DIR}/carddav
|
||||
# root.www-data need all permissions, others only read
|
||||
chown -f -R root:www-data ${RCM_PLUGIN_DIR}/carddav
|
||||
# root:www-data need all permissions, others only read
|
||||
chmod -R 774 ${RCM_PLUGIN_DIR}/carddav
|
||||
|
||||
# Run Roundcube database migration script (database is created if it does not exist)
|
||||
|
@ -76,7 +76,8 @@ for setting in settings:
|
||||
|
||||
found = set()
|
||||
buf = ""
|
||||
input_lines = list(open(filename))
|
||||
with open(filename, "r") as f:
|
||||
input_lines = list(f)
|
||||
|
||||
while len(input_lines) > 0:
|
||||
line = input_lines.pop(0)
|
||||
|
@ -40,8 +40,8 @@ cp "$1/owncloud.db" $STORAGE_ROOT/owncloud/
|
||||
cp "$1/config.php" $STORAGE_ROOT/owncloud/
|
||||
|
||||
ln -sf $STORAGE_ROOT/owncloud/config.php /usr/local/lib/owncloud/config/config.php
|
||||
chown -f -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
|
||||
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
|
||||
chown -f -R www-data:www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
|
||||
chown www-data:www-data $STORAGE_ROOT/owncloud/config.php
|
||||
|
||||
sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/occ maintenance:mode --off
|
||||
|
||||
|
@ -17,13 +17,8 @@ accesses = set()
|
||||
# Scan the current and rotated access logs.
|
||||
for fn in glob.glob("/var/log/nginx/access.log*"):
|
||||
# Gunzip if necessary.
|
||||
if fn.endswith(".gz"):
|
||||
f = gzip.open(fn)
|
||||
else:
|
||||
f = open(fn, "rb")
|
||||
|
||||
# Loop through the lines in the access log.
|
||||
with f:
|
||||
with (gzip.open if fn.endswith(".gz") else open)(fn, "rb") as f:
|
||||
for line in f:
|
||||
# Find lines that are GETs on the bootstrap script by either curl or wget.
|
||||
# (Note that we purposely skip ...?ping=1 requests which is the admin panel querying us for updates.)
|
||||
@ -43,7 +38,8 @@ for date, ip in accesses:
|
||||
# Since logs are rotated, store the statistics permanently in a JSON file.
|
||||
# Load in the stats from an existing file.
|
||||
if os.path.exists(outfn):
|
||||
existing_data = json.load(open(outfn))
|
||||
with open(outfn, "r") as f:
|
||||
existing_data = json.load(f)
|
||||
for date, count in existing_data:
|
||||
if date not in by_date:
|
||||
by_date[date] = count
|
||||
|
@ -124,13 +124,14 @@ def generate_documentation():
|
||||
""")
|
||||
|
||||
parser = Source.parser()
|
||||
for line in open("setup/start.sh"):
|
||||
try:
|
||||
fn = parser.parse_string(line).filename()
|
||||
except:
|
||||
continue
|
||||
if fn in ("setup/start.sh", "setup/preflight.sh", "setup/questions.sh", "setup/firstuser.sh", "setup/management.sh"):
|
||||
continue
|
||||
with open("setup/start.sh", "r") as start_file:
|
||||
for line in start_file:
|
||||
try:
|
||||
fn = parser.parse_string(line).filename()
|
||||
except:
|
||||
continue
|
||||
if fn in ("setup/start.sh", "setup/preflight.sh", "setup/questions.sh", "setup/firstuser.sh", "setup/management.sh"):
|
||||
continue
|
||||
|
||||
import sys
|
||||
print(fn, file=sys.stderr)
|
||||
@ -401,7 +402,8 @@ class BashScript(Grammar):
|
||||
@staticmethod
|
||||
def parse(fn):
|
||||
if fn in ("setup/functions.sh", "/etc/mailinabox.conf"): return ""
|
||||
string = open(fn).read()
|
||||
with open(fn, "r") as f:
|
||||
string = f.read()
|
||||
|
||||
# tokenize
|
||||
string = re.sub(".* #NODOC\n", "", string)
|
||||
|
Loading…
Reference in New Issue
Block a user