v0.45 (May 16, 2020)

Security fixes:
 
 * Fix missing brute force login protection for Roundcube logins.
 
 Software updates:
 
 * Upgraded Roundcube from 1.4.2 to 1.4.4.
 * Upgraded Nextcloud from 17.0.2 to 17.0.6 (with Contacts from 3.1.6 to 3.3.0 and Calendar from 1.7.1 to v2.0.3)
 * Upgraded Z-Push to 2.5.2.
 
 System:
 
 * Nightly backups now occur on a random minute in the 3am hour (in the system time zone). The minute is chosen during Mail-in-a-Box installation/upgrade and remains the same until the next upgrade.
 * Fix for mail log statistics report on leap days.
 * Fix Mozilla autoconfig useGlobalPreferredServer setting.
 
 Web:
 
 * Add a new hidden feature to set nginx alias in www/custom.yaml.
 
 Setup:
 
 * Improved error handling.
 -----BEGIN PGP SIGNATURE-----
 
 iQFDBAABCgAtFiEEX0wOcxPM10RpOyrquSBB9MEL3YEFAl7AbCoPHGp0QG9jY2Ft
 cy5pbmZvAAoJELkgQfTBC92BjbEIAIwmIpgNCT+age/SsUhDY8pjnFQWXBCl1nwa
 RFN40Ev73DoBXUP+za4RE0eyCLIw5/laCwCjaESobiBTuc6boC1QU4abFUV5NfJQ
 P3AnQ2qXkrtcmIQX42ge4AGsL3vMVRtjZWb+bvut2SmLB8BI5w/9XsQAS59lqSz0
 kK6ShlDmFaToMgTQqwl0CW8a0vdjRca5Mq011xUZrvqTAm7ACQIvS6np4UYBGSNy
 bU8O1xWMJb0HlO7f+bWCDYr1I+nRS1xXMW9pKsE08YFwcRLa+C42QkDXDuS/o/zj
 EXBLGwYcB0DEu4wLLbih8xdbED2ZiMO2t6IHtbXPcoLtHo3Tv6I=
 =RUkr
 -----END PGP SIGNATURE-----

Merge tag 'v0.45' of https://github.com/mail-in-a-box/mailinabox

v0.45 (May 16, 2020)

Security fixes:

* Fix missing brute force login protection for Roundcube logins.

Software updates:

* Upgraded Roundcube from 1.4.2 to 1.4.4.
* Upgraded Nextcloud from 17.0.2 to 17.0.6 (with Contacts from 3.1.6 to 3.3.0 and Calendar from 1.7.1 to v2.0.3)
* Upgraded Z-Push to 2.5.2.

System:

* Nightly backups now occur on a random minute in the 3am hour (in the system time zone). The minute is chosen during Mail-in-a-Box installation/upgrade and remains the same until the next upgrade.
* Fix for mail log statistics report on leap days.
* Fix Mozilla autoconfig useGlobalPreferredServer setting.

Web:

* Add a new hidden feature to set nginx alias in www/custom.yaml.

Setup:

* Improved error handling.
This commit is contained in:
John R. Supplee 2020-05-17 18:17:44 +02:00
commit c152fe6312
15 changed files with 95 additions and 89 deletions

View file

@ -1,6 +1,33 @@
CHANGELOG CHANGELOG
========= =========
v0.45 (May 16, 2020)
--------------------
Security fixes:
* Fix missing brute force login protection for Roundcube logins.
Software updates:
* Upgraded Roundcube from 1.4.2 to 1.4.4.
* Upgraded Nextcloud from 17.0.2 to 17.0.6 (with Contacts from 3.1.6 to 3.3.0 and Calendar from 1.7.1 to v2.0.3)
* Upgraded Z-Push to 2.5.2.
System:
* Nightly backups now occur on a random minute in the 3am hour (in the system time zone). The minute is chosen during Mail-in-a-Box installation/upgrade and remains the same until the next upgrade.
* Fix for mail log statistics report on leap days.
* Fix Mozilla autoconfig useGlobalPreferredServer setting.
Web:
* Add a new hidden feature to set nginx alias in www/custom.yaml.
Setup:
* Improved error handling.
v0.44 (February 15, 2020) v0.44 (February 15, 2020)
------------------------- -------------------------

View file

@ -226,7 +226,7 @@ by him:
$ curl -s https://keybase.io/joshdata/key.asc | gpg --import $ curl -s https://keybase.io/joshdata/key.asc | gpg --import
gpg: key C10BDD81: public key "Joshua Tauberer <jt@occams.info>" imported gpg: key C10BDD81: public key "Joshua Tauberer <jt@occams.info>" imported
$ git verify-tag v0.44 $ git verify-tag v0.45
gpg: Signature made ..... using RSA key ID C10BDD81 gpg: Signature made ..... using RSA key ID C10BDD81
gpg: Good signature from "Joshua Tauberer <jt@occams.info>" gpg: Good signature from "Joshua Tauberer <jt@occams.info>"
gpg: WARNING: This key is not certified with a trusted signature! gpg: WARNING: This key is not certified with a trusted signature!
@ -239,7 +239,7 @@ and on his [personal homepage](https://razor.occams.info/). (Of course, if this
Checkout the tag corresponding to the most recent release: Checkout the tag corresponding to the most recent release:
$ git checkout v0.44 $ git checkout v0.45
Begin the installation. Begin the installation.

View file

@ -50,7 +50,7 @@ findtime = 30
enabled = true enabled = true
port = http,https port = http,https
filter = miab-roundcube filter = miab-roundcube
logpath = /var/log/roundcubemail/errors logpath = /var/log/roundcubemail/errors.log
maxretry = 20 maxretry = 20
findtime = 30 findtime = 30

View file

@ -21,7 +21,7 @@
<username>%EMAILADDRESS%</username> <username>%EMAILADDRESS%</username>
<authentication>password-cleartext</authentication> <authentication>password-cleartext</authentication>
<addThisServer>true</addThisServer> <addThisServer>true</addThisServer>
<useGlobalPreferredServer>true</useGlobalPreferredServer> <useGlobalPreferredServer>false</useGlobalPreferredServer>
</outgoingServer> </outgoingServer>
<documentation url="https://PRIMARY_HOSTNAME/"> <documentation url="https://PRIMARY_HOSTNAME/">

View file

@ -18,13 +18,13 @@ import utils
LOG_FILES = ( LOG_FILES = (
'/var/log/mail.log',
'/var/log/mail.log.1',
'/var/log/mail.log.2.gz',
'/var/log/mail.log.3.gz',
'/var/log/mail.log.4.gz',
'/var/log/mail.log.5.gz',
'/var/log/mail.log.6.gz', '/var/log/mail.log.6.gz',
'/var/log/mail.log.5.gz',
'/var/log/mail.log.4.gz',
'/var/log/mail.log.3.gz',
'/var/log/mail.log.2.gz',
'/var/log/mail.log.1',
'/var/log/mail.log',
) )
TIME_DELTAS = OrderedDict([ TIME_DELTAS = OrderedDict([
@ -80,7 +80,7 @@ def scan_files(collector):
print("Processing file", fn, "...") print("Processing file", fn, "...")
fn = tmp_file.name if tmp_file else fn fn = tmp_file.name if tmp_file else fn
for line in reverse_readline(fn): for line in readline(fn):
if scan_mail_log_line(line.strip(), collector) is False: if scan_mail_log_line(line.strip(), collector) is False:
if stop_scan: if stop_scan:
return return
@ -344,16 +344,22 @@ def scan_mail_log_line(line, collector):
# Replaced the dateutil parser for a less clever way of parser that is roughly 4 times faster. # Replaced the dateutil parser for a less clever way of parser that is roughly 4 times faster.
# date = dateutil.parser.parse(date) # date = dateutil.parser.parse(date)
date = datetime.datetime.strptime(date, '%b %d %H:%M:%S')
date = date.replace(START_DATE.year) # 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')
# print("date:", date)
# Check if the found date is within the time span we are scanning # Check if the found date is within the time span we are scanning
# END_DATE < START_DATE
if date > START_DATE: if date > START_DATE:
# Don't process, but continue
return True
elif date < END_DATE:
# Don't process, and halt # Don't process, and halt
return False return False
elif date < END_DATE:
# Don't process, but continue
return True
if service == "postfix/submission/smtpd": if service == "postfix/submission/smtpd":
if SCAN_OUT: if SCAN_OUT:
@ -453,9 +459,9 @@ def scan_postfix_smtpd_line(date, log, collector):
if m: if m:
message = "domain blocked: " + m.group(2) message = "domain blocked: " + m.group(2)
if data["latest"] is None: if data["earliest"] is None:
data["latest"] = date data["earliest"] = date
data["earliest"] = date data["latest"] = date
data["blocked"].append((date, sender, message)) data["blocked"].append((date, sender, message))
collector["rejected"][user] = data collector["rejected"][user] = data
@ -487,9 +493,9 @@ def add_login(user, date, protocol_name, host, collector):
} }
) )
if data["latest"] is None: if data["earliest"] is None:
data["latest"] = date data["earliest"] = date
data["earliest"] = date data["latest"] = date
data["totals_by_protocol"][protocol_name] += 1 data["totals_by_protocol"][protocol_name] += 1
data["totals_by_protocol_and_host"][(protocol_name, host)] += 1 data["totals_by_protocol_and_host"][(protocol_name, host)] += 1
@ -528,9 +534,9 @@ def scan_postfix_lmtp_line(date, log, collector):
data["received_count"] += 1 data["received_count"] += 1
data["activity-by-hour"][date.hour] += 1 data["activity-by-hour"][date.hour] += 1
if data["latest"] is None: if data["earliest"] is None:
data["latest"] = date data["earliest"] = date
data["earliest"] = date data["latest"] = date
collector["received_mail"][user] = data collector["received_mail"][user] = data
@ -567,9 +573,9 @@ def scan_postfix_submission_line(date, log, collector):
data["hosts"].add(client) data["hosts"].add(client)
data["activity-by-hour"][date.hour] += 1 data["activity-by-hour"][date.hour] += 1
if data["latest"] is None: if data["earliest"] is None:
data["latest"] = date data["earliest"] = date
data["earliest"] = date data["latest"] = date
collector["sent_mail"][user] = data collector["sent_mail"][user] = data
@ -578,42 +584,15 @@ def scan_postfix_submission_line(date, log, collector):
# Utility functions # Utility functions
def reverse_readline(filename, buf_size=8192): def readline(filename):
""" A generator that returns the lines of a file in reverse order """ A generator that returns the lines of a file
http://stackoverflow.com/a/23646049/801870
""" """
with open(filename) as file:
with open(filename) as fh: while True:
segment = None line = file.readline()
offset = 0 if not line:
fh.seek(0, os.SEEK_END) break
file_size = remaining_size = fh.tell() yield line
while remaining_size > 0:
offset = min(file_size, offset + buf_size)
fh.seek(file_size - offset)
buff = fh.read(min(remaining_size, buf_size))
remaining_size -= buf_size
lines = buff.split('\n')
# the first line of the buffer is probably not a complete line so
# we'll save it and append it to the last line of the next buffer
# we read
if segment is not None:
# if the previous chunk starts right from the beginning of line
# do not concat the segment to the last line of new chunk
# instead, yield the segment first
if buff[-1] is not '\n':
lines[-1] += segment
else:
yield segment
segment = lines[0]
for index in range(len(lines) - 1, 0, -1):
if len(lines[index]):
yield lines[index]
# Don't yield None if the file was empty
if segment is not None:
yield segment
def user_match(user): def user_match(user):

View file

@ -125,7 +125,11 @@
<td>/add</td> <td>/add</td>
<td>Adds a new mail user. Required POST-body parameters are <code>email</code> and <code>password</code>. Optional parameters: <code>privilege=admin</code> and <code>quota</code></td> <td>Adds a new mail user. Required POST-body parameters are <code>email</code> and <code>password</code>. Optional parameters: <code>privilege=admin</code> and <code>quota</code></td>
</tr> </tr>
<tr><td>POST</td><td>/remove</td> <td>Removes a mail user. Required POST-by parameter is <code>email</code>.</td></tr> <tr>
<td>POST</td>
<td>/remove</td>
<td>Removes a mail user. Required POST-by parameter is <code>email</code>.</td>
</tr>
<tr><td>POST</td><td>/privileges/add</td> <td>Used to make a mail user an admin. Required POST-body parameters are <code>email</code> and <code>privilege=admin</code>.</td></tr> <tr><td>POST</td><td>/privileges/add</td> <td>Used to make a mail user an admin. Required POST-body parameters are <code>email</code> and <code>privilege=admin</code>.</td></tr>
<tr><td>POST</td><td>/privileges/remove</td> <td>Used to remove the admin privilege from a mail user. Required POST-body parameter is <code>email</code>.</td></tr> <tr><td>POST</td><td>/privileges/remove</td> <td>Used to remove the admin privilege from a mail user. Required POST-body parameter is <code>email</code>.</td></tr>
<tr> <tr>

View file

@ -159,6 +159,10 @@ def make_domain_config(domain, templates, ssl_certificates, env):
nginx_conf_extra += "\n\t\tproxy_pass %s;" % url nginx_conf_extra += "\n\t\tproxy_pass %s;" % url
nginx_conf_extra += "\n\t\tproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;" nginx_conf_extra += "\n\t\tproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;"
nginx_conf_extra += "\n\t}\n" nginx_conf_extra += "\n\t}\n"
for path, alias in yaml.get("aliases", {}).items():
nginx_conf_extra += "\tlocation %s {" % path
nginx_conf_extra += "\n\t\talias %s;" % alias
nginx_conf_extra += "\n\t}\n"
for path, url in yaml.get("redirects", {}).items(): for path, url in yaml.get("redirects", {}).items():
nginx_conf_extra += "\trewrite %s %s permanent;\n" % (path, url) nginx_conf_extra += "\trewrite %s %s permanent;\n" % (path, url)

View file

@ -20,7 +20,7 @@ if [ -z "$TAG" ]; then
# want to display in status checks. # want to display in status checks.
if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then
# This machine is running Ubuntu 18.04. # This machine is running Ubuntu 18.04.
TAG=v0.44-quota-0.22-beta TAG=v0.45-quota-0.22-beta
elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then
# This machine is running Ubuntu 14.04. # This machine is running Ubuntu 14.04.
@ -35,14 +35,14 @@ if [ -z "$TAG" ]; then
else else
echo "This script must be run on a system running Ubuntu 18.04 or Ubuntu 14.04." echo "This script must be run on a system running Ubuntu 18.04 or Ubuntu 14.04."
exit exit 1
fi fi
fi fi
# Are we running as root? # Are we running as root?
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root. Did you leave out sudo?" echo "This script must be run as root. Did you leave out sudo?"
exit exit 1
fi fi
# Clone the Mail-in-a-Box repository if it doesn't exist. # Clone the Mail-in-a-Box repository if it doesn't exist.
@ -73,7 +73,7 @@ if [ "$TAG" != `git describe` ]; then
git fetch --depth 1 --force --prune origin tag $TAG git fetch --depth 1 --force --prune origin tag $TAG
if ! git checkout -q $TAG; then if ! git checkout -q $TAG; then
echo "Update failed. Did you modify something in `pwd`?" echo "Update failed. Did you modify something in `pwd`?"
exit exit 1
fi fi
echo echo
fi fi

View file

@ -57,15 +57,6 @@ function apt_install {
apt_get_quiet install $PACKAGES apt_get_quiet install $PACKAGES
} }
function apt_add_repository_to_unattended_upgrades {
if [ -f /etc/apt/apt.conf.d/50unattended-upgrades ]; then
if ! grep -q "$1" /etc/apt/apt.conf.d/50unattended-upgrades; then
sed -i "/Allowed-Origins/a \
\"$1\";" /etc/apt/apt.conf.d/50unattended-upgrades
fi
fi
}
function get_default_hostname { function get_default_hostname {
# Guess the machine's hostname. It should be a fully qualified # Guess the machine's hostname. It should be a fully qualified
# domain name suitable for DNS. None of these calls may provide # domain name suitable for DNS. None of these calls may provide

View file

@ -101,10 +101,11 @@ hide_output systemctl enable mailinabox.service
# Perform nightly tasks at 3am in system time: take a backup, run # Perform nightly tasks at 3am in system time: take a backup, run
# status checks and email the administrator any changes. # status checks and email the administrator any changes.
minute=$((RANDOM % 60)) # avoid overloading mailinabox.email
cat > /etc/cron.d/mailinabox-nightly << EOF; cat > /etc/cron.d/mailinabox-nightly << EOF;
# Mail-in-a-Box --- Do not edit / will be overwritten on update. # Mail-in-a-Box --- Do not edit / will be overwritten on update.
# Run nightly tasks: backup, status checks. # Run nightly tasks: backup, status checks.
0 3 * * * root (cd `pwd` && management/daily_tasks.sh) $minute 3 * * * root (cd `pwd` && management/daily_tasks.sh)
EOF EOF
# Start the management server. # Start the management server.

View file

@ -40,11 +40,11 @@ InstallNextcloud() {
# their github repositories. # their github repositories.
mkdir -p /usr/local/lib/owncloud/apps mkdir -p /usr/local/lib/owncloud/apps
wget_verify https://github.com/nextcloud/contacts/releases/download/v3.1.6/contacts.tar.gz d331dc6db2ecf7c8e6166926a055dfa3b59722c3 /tmp/contacts.tgz wget_verify https://github.com/nextcloud/contacts/releases/download/v3.3.0/contacts.tar.gz e55d0357c6785d3b1f3b5f21780cb6d41d32443a /tmp/contacts.tgz
tar xf /tmp/contacts.tgz -C /usr/local/lib/owncloud/apps/ tar xf /tmp/contacts.tgz -C /usr/local/lib/owncloud/apps/
rm /tmp/contacts.tgz rm /tmp/contacts.tgz
wget_verify https://github.com/nextcloud/calendar/releases/download/v1.7.1/calendar.tar.gz bd7c846bad06da6d6ba04280f6fbf37ef846c2ad /tmp/calendar.tgz wget_verify https://github.com/nextcloud/calendar/releases/download/v2.0.3/calendar.tar.gz 9d9717b29337613b72c74e9914c69b74b346c466 /tmp/calendar.tgz
tar xf /tmp/calendar.tgz -C /usr/local/lib/owncloud/apps/ tar xf /tmp/calendar.tgz -C /usr/local/lib/owncloud/apps/
rm /tmp/calendar.tgz rm /tmp/calendar.tgz
@ -91,8 +91,8 @@ InstallNextcloud() {
} }
# Nextcloud Version to install. Checks are done down below to step through intermediate versions. # Nextcloud Version to install. Checks are done down below to step through intermediate versions.
nextcloud_ver=17.0.2 nextcloud_ver=17.0.6
nextcloud_hash=8095fb46e9e0c536163708aee3d17fab8b498ad6 nextcloud_hash=50b98d2c2f18510b9530e558ced9ab51eb4f11b0
# Current Nextcloud Version, #1623 # Current Nextcloud Version, #1623
# Checking /usr/local/lib/owncloud/version.php shows version of the Nextcloud application, not the DB # Checking /usr/local/lib/owncloud/version.php shows version of the Nextcloud application, not the DB

View file

@ -4,7 +4,7 @@ if [[ $EUID -ne 0 ]]; then
echo echo
echo "sudo $0" echo "sudo $0"
echo echo
exit exit 1
fi fi
# Check that we are running on Ubuntu 18.04 LTS (or 18.04.xx). # Check that we are running on Ubuntu 18.04 LTS (or 18.04.xx).
@ -14,7 +14,7 @@ if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" != "U
lsb_release -d | sed 's/.*:\s*//' lsb_release -d | sed 's/.*:\s*//'
echo echo
echo "We can't write scripts that run on every possible setup, sorry." echo "We can't write scripts that run on every possible setup, sorry."
exit exit 1
fi fi
# Check that we have enough memory. # Check that we have enough memory.

View file

@ -28,8 +28,8 @@ apt_install \
# Install Roundcube from source if it is not already present or if it is out of date. # Install Roundcube from source if it is not already present or if it is out of date.
# Combine the Roundcube version number with the commit hash of plugins to track # Combine the Roundcube version number with the commit hash of plugins to track
# whether we have the latest version of everything. # whether we have the latest version of everything.
VERSION=1.4.2 VERSION=1.4.4
HASH=d53fcd7f1109a63364d5d4a43f879c6f47d34a89 HASH=4e425263f5bec27d39c07bde524f421bda205c07
PERSISTENT_LOGIN_VERSION=6b3fc450cae23ccb2f393d0ef67aa319e877e435 PERSISTENT_LOGIN_VERSION=6b3fc450cae23ccb2f393d0ef67aa319e877e435
HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5 HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5
CARDDAV_VERSION=3.0.3 CARDDAV_VERSION=3.0.3

View file

@ -22,8 +22,8 @@ apt_install \
phpenmod -v php imap phpenmod -v php imap
# Copy Z-Push into place. # Copy Z-Push into place.
VERSION=2.5.1 VERSION=2.5.2
TARGETHASH=4fa55863a429b0033497ae477aca4c8699b8f332 TARGETHASH=2dc3dbd791b96b0ba2638df0d3d1e03c7e1cbab2
needs_update=0 #NODOC needs_update=0 #NODOC
if [ ! -f /usr/local/lib/z-push/version ]; then if [ ! -f /usr/local/lib/z-push/version ]; then
needs_update=1 #NODOC needs_update=1 #NODOC

View file

@ -22,7 +22,7 @@ fi
if [ ! -f $1/config.php ]; then if [ ! -f $1/config.php ]; then
echo "This isn't a valid backup location" echo "This isn't a valid backup location"
exit exit 1
fi fi
echo "Restoring backup from $1" echo "Restoring backup from $1"