1
0
mirror of https://github.com/mail-in-a-box/mailinabox.git synced 2026-03-12 17:07:23 +01:00

Compare commits

..

185 Commits
v0.26 ... v0.47

Author SHA1 Message Date
hija
56d0289ed9 v0.47 2020-07-29 10:24:56 -04:00
Marcus Bointon
f253c40012 [backport] Add rate limiting of SSH in the firewall (#1770)
See #1767. Backport of cfc8fb484c.
2020-07-29 10:24:23 -04:00
Hilko
2c34a6df2b Update roundcube to 1.4.7 2020-07-29 10:15:12 -04:00
Joshua Tauberer
049bfb6f7f v0.46 2020-06-11 12:23:18 -04:00
Joshua Tauberer
12d60d102b Update Roundcube to 1.4.6
Fixes #1776
2020-06-11 12:21:17 -04:00
Faye Duxovni
41642f2f59 [backport] Fix roundcube error log file path in setup script (#1775) 2020-06-11 12:16:53 -04:00
Joshua Tauberer
7de8fc9bc0 v0.45 2020-05-16 06:45:23 -04:00
yeuna92
c87b62b8c2 Fix path to Roundcube error log in fail2ban jails.conf (#1761) 2020-05-11 08:59:42 -04:00
clonejo
8fe33da85d Run nightly tasks on a random minute after 03:00 to avoid overload (#1754)
- The MIAB version check regularly fails at 03:00, presumably because a
  large portion of installations is checking mailinabox.email at the same
  time.
- At installation time, the time of the nightly clock is configured to
  run at a random minute after 03:00, but before 04:00.
- Users might expect the nightly tasks to be over at a certain time and
  run their own custom tasks afterwards. This could thus interfere with
  custom backup routines.
- This breaks reproducibility of the installation process.
- Users might also be surprised by the nightly task time changing after
  updating MIAB.
2020-05-10 19:54:45 -04:00
Joshua Tauberer
c202a5cbc6 Changlog entries 2020-05-10 19:46:25 -04:00
Joshua Tauberer
1353949e42 Upgrade Roundcube to 1.4.4, Nextcloud to 17.0.6, Z-Push to 2.5.2 2020-05-10 19:44:12 -04:00
Joshua Tauberer
c19f8c9ee6 Change Mozilla autoconfig useGlobalPreferredServer property to false
Fixes #1736.
2020-05-10 19:29:01 -04:00
Michael Becker
40b21c466d Fypo fix in users.html (#1748) 2020-04-13 22:10:52 -04:00
Stefan
f52749b403 Better return codes after errors in the setup scripts (#1741) 2020-04-11 14:18:44 -04:00
Sumit
d67e09f334 Allowing adding nginx aliases in www/custom.yaml (#1742)
with this nginx will keep on proxying requests and serve static content
instead of passing this responsibility to proxied server

Without this the one needs to run an additional server to server static
content on the proxied url
2020-04-11 14:17:46 -04:00
Daniel Davis
e224fc6656 Delete unused function apt_add_repository_to_unattended_upgrades (#1721)
The function apt_add_repository_to_unattended_upgrades is defined
but never called anywhere. It appears that automatic apt updates
are handled in system.sh where the file /etc/apt/apt.conf.d/02periodic
is created. The last call was removed in bbfa01f33a.

Co-authored-by: ddavis32 <dan@nthdegreesoftware.com>
2020-03-08 09:49:39 -04:00
Joshua Tauberer
5e47677f7a Merge mail log script fixes for UTF-8 issue and Feb 29 issue (#1734) 2020-03-08 09:37:43 -04:00
Jarek Jurasz
db9637ce4f Fix Feb 29 issue #1733 2020-03-03 20:59:28 +01:00
Jarek Jurasz
f908bc364e mail_log.py reading forward #1593 2020-03-03 20:56:30 +01:00
Joshua Tauberer
30c2c60f59 v0.44 2020-02-15 07:15:09 -05:00
Joshua Tauberer
ab5ce01bdd Some changelog entries 2020-01-22 03:36:02 -05:00
Joshua Tauberer
ddadb6c28a Roundcube 1.4.2 2020-01-22 03:25:53 -05:00
Joshua Tauberer
23be1031b8 Remove security.md's information about port 25 which is out of date 2020-01-22 03:25:30 -05:00
Michael Kroes
faee29ba8b Bump Nextcloud to 17.0.2 (#1702) 2020-01-22 03:06:17 -05:00
E.M. Makat
b86bf07d57 Fix spelling of 'guarantee' (#1703) 2020-01-22 02:58:40 -05:00
jvolkenant
e6294049bc Update Roundcube persistent_login plugin (#1712) 2020-01-22 02:58:04 -05:00
Joshua Tauberer
30885bcc8a Downgrade TLS settings for port 25, partially reverting f53b18ebb9
Port 25 now is aligned with Mozilla's "Old" recommendations at https://ssl-config.mozilla.org/#server=postfix&server-version=3.3.0&config=old&openssl-version=1.1.1.

See #1705
2020-01-20 14:52:23 -05:00
Bart
a67f90593d Replace dead link with archive.org link (#1698) 2019-12-19 18:33:36 -05:00
Joshua Tauberer
385340da46 install openssh-client which provides ssh-keygen and is not present on desktop Ubuntu by default 2019-12-12 11:27:39 -05:00
jvolkenant
0271e549bb Fix typo in InstallNextcloud calls (#1693) 2019-12-10 19:01:09 -05:00
Joshua Tauberer
f53b18ebb9 Upgrade TLS settings 2019-12-01 17:49:36 -05:00
Joshua Tauberer
8567a9b719 Fix upgrade issue broken by 802e7a1f4d 2019-12-01 17:44:12 -05:00
Vasek Sraier
ad9d732608 OpenDKIM canonicalization changed to relaxed for mail headers (#1620)
Because Mailman reformats headers it breaks DKIM signatures. SPF also does
not apply in mailing lists. This together causes DMARC to fail and mark the
email as invalid. This fixes DKIM signatures for Mailman-based mailing lists
and makes sure DMARC test is passed.
2019-12-01 16:24:38 -05:00
jvolkenant
aa15670dc2 Fixed multiple commented add_header entries in /etc/spamassassin/local.cf (#1641) 2019-12-01 16:23:02 -05:00
jvolkenant
81176c8e4b Fix to prevent multiple commented entries in dovecot conf (#1642) 2019-12-01 16:22:17 -05:00
Carl Reinke
960b5d5bbd Don't use ifquery to check interface state since it is no longer installed (#1689) 2019-12-01 16:21:38 -05:00
Carl Reinke
802e7a1f4d Copy systemd service files before linking to avoid issue with order of mounting filesystems (#1688) 2019-12-01 16:15:04 -05:00
Michael Kroes
52c68c6510 Implement Nextcloud php-fpm recommended performance tuning settings (#1679) 2019-12-01 16:13:33 -05:00
Michael Kroes
54b1ee9a3d Nextcloud 17 (#1676) 2019-12-01 16:11:00 -05:00
Francesco Montanari
6e3dee8b3b Upgrade RoundCube to 1.4.1 and set the default skin to elastic (#1673)
* Upgrade RoundCube to 1.4.0 and set the default skin to elastic
* Install php-ldap extension
* Remove smtp parameters that are now the default
2019-12-01 16:10:04 -05:00
Matthias Hähnel
cd62fd9826 Update usage hint in backup.py (#1662)
removed explicit call of the system python, cause the file has a shebang with the mail-in-a-box shipped python. 
for me the system python complaint, that it is missing some modules
2019-11-23 08:04:22 -05:00
Michael Kroes
91638c7fe0 Removed the postgrey option that specifies which whitelist file to use. This allows the usage of a .local verion (#1675) 2019-11-23 07:58:29 -05:00
Michael Kroes
ff8170d5ab Align nextcloud cron job with recommended settings (#1680) 2019-11-23 07:51:22 -05:00
Joshua Tauberer
f6f75f6fab Don't fail when resolving zone transfer IP addresses since a nameserver may not have an IPv6 address 2019-11-19 09:57:33 -05:00
Edwin Schaap
2f54f39f31 If xfr is subnet, do not create "notify" entry (#1672) 2019-11-10 11:58:22 -05:00
Victor
fa792f664e Use correct setting for .editorconfig indent_style (#1670) 2019-11-03 13:31:29 -05:00
Joshua Tauberer
b50dfb7f93 changelog entries 2019-11-02 15:57:14 -04:00
Dan Jensen
cde4e0caca Change SSL notification email subject (#1653)
Previously the notification email sent when a box's SSL certificate
is automatically updated said, "Error Provisioning TLS Certificate"
even when there was no error. This changes the subject line to "TLS
Certificate Provisioning Results", which is more accurate.
2019-11-02 15:29:05 -04:00
jvolkenant
df80b9fc71 Allow user_external for Nextcloud 16 (and eventually 17) (#1655) 2019-11-02 15:28:36 -04:00
notEvil
7558ffd4f3 Allow dns zone transfer from IPv6 (#1643) 2019-10-28 06:31:50 -04:00
Victor
50e9e8af30 Sort custom dns table based on fqdn, rtype, and value (#1651) 2019-10-28 06:29:40 -04:00
jvolkenant
ed02e2106b Update zpush to 2.5.1 (#1654) 2019-10-28 06:27:54 -04:00
Jeff Volkenant
24a567c3be Fix mailinabox-postgrey-whitelist cron job return code for file over 28 days
Merges #1639
2019-10-05 16:27:21 -04:00
Brendan Hide
70f05e9d52 Ensure the universe repository is enabled
A minimal Ubuntu server installation might not have universe enabled by
default. By adding it, we ensure we can install packages only available
in universe, such as python3-pip

Merges #1650.
2019-10-05 16:14:12 -04:00
Michael Kroes
889118aeb6 Upgraded Nextcloud to 16.0.5 (#1648)
* Upgraded Nextcloud to 16.0.5

* Improved Nextcloud upgrade detection
2019-10-05 16:12:00 -04:00
Joshua Tauberer
a70ba94b0c add autoconfig domains before subtracting domains with overridden A records so that a custom DNS record can be used to suppress TLS certificate generation for those domains if needed 2019-09-10 07:11:16 -04:00
Joshua Tauberer
9e29564f48 v0.43 2019-09-01 07:43:47 -04:00
Joshua Tauberer
5aeced5c2e add a test for fail2ban monitoring managesieve 2019-08-31 09:15:41 -04:00
Joshua Tauberer
46f64e0e0a fail2ban should watch for managesieve logins too, fixes #1622 2019-08-31 09:04:17 -04:00
Joshua Tauberer
4971b63501 changelog entries 2019-08-31 08:52:32 -04:00
Joshua Tauberer
3ff9817325 document the xfr: CIDR notation, fix spaces vs tabs and syntax error, broken by c7377e602d, #1616 2019-08-31 08:50:44 -04:00
jvolkenant
d6becddbe5 Change Nextcloud upgrade logic to look at STORAGE_ROOT's config.php version vs /usr/local's version.php version (#1632)
* Download and verify Nextcloud download before deleting old install directory
* Changed install logic to look at config.php and not version.php for database version number. When restoring from a backup, config.php in STORAGE_ROOT will hold the Nextcloud version that corresponds to the user's database and version.php in /usr/local won't even exist, so we were missing Nextcloud migration steps. In other cases they should be the same.
2019-08-31 08:50:36 -04:00
Michael Kroes
1d6793d124 Update the Postgrey whitelist to a newer version monthly (#1611)
Automatically update the Postgrey whitelist to a newer version once a month.
2019-08-31 08:38:41 -04:00
Kim Schulz
c7377e602d make it possible to use subnet addresses for axfr (#1616)
it is sometimes needed to be able to set axfr to more than just one ip address. This can be done with multiple xfr: in  the secondary dns input but if you need to add an entire subnet segment (xxx.xxx.xxx.0/yy) then it will not work.
With this patch it is now possible to use a subnet as input for xfr the same way as if it was an ip address.
2019-08-31 08:00:18 -04:00
Snacho
08021ea19f Fix an issue when Secondary NS has multiple A records (#1633)
If a custom secondary NS server has multiple A records status_checks.py will fail with a timeout and Web UI won't load.
2019-08-31 07:58:12 -04:00
cmharper
295d481603 Upgraded roundcube to 1.3.10 (#1634) 2019-08-31 07:55:38 -04:00
captainwasabi
c4cb828f65 Fix rsync backup options string: extraneous single quotes causing problems (#1629)
The resulting command had nested single quotes which doesn't work

I think this fixes all/most of the issues in #1627.  I am getting a full backup, then the next time it's run I get an incremental.  running from the CLI with --status looks good, --verify looks good, and --list looks good.
2019-08-13 05:57:05 -04:00
captainwasabi
0657f9e875 add proper check for DNS error in list_target_files (#1625)
The elif needed to check to see if the string was in the listing of results of the shell command.  As it was the conditional was just the string which always evaluates to true and was therefore giving a misleading error message.
2019-08-13 05:47:11 -04:00
Joshua Tauberer
e37768ca86 v0.42b 2019-08-03 11:49:32 -04:00
jvolkenant
bea5eb0dda Add interm upgrade step from Nextcloud 13 -> 14 (#1605) 2019-07-12 06:41:16 -04:00
jvolkenant
fd5b11823c Add AAAA records for autodiscover & autoconfig (#1606) 2019-07-10 06:28:37 -04:00
Joshua Tauberer
5fc1944f04 pull v0.42, go back to v0.41 2019-07-05 11:56:54 -04:00
Joshua Tauberer
39fd4ce16c v0.42 2019-07-04 21:34:55 -04:00
Joshua Tauberer
c0f4d5479f changelog updates 2019-06-16 11:40:40 -04:00
jvolkenant
193763f8f0 Update to Nextcloud 15.0.8, Contacts to 3.1.1, and Calendar to 1.6.5 (#1577)
* Update to Nextcloud 15.0.7, Contacts to 3.1.1, and Calendar to 1.6.5
* Enabled localhost-only insecure IMAP login for localhost Nextcloud auth
* Add package php-imagick and BigInt conversion
* added support for /cloud/oc[sm]-provider/ endpoint
2019-06-16 11:10:52 -04:00
jvolkenant
79759ea5a3 Upgrade Z-Push to 2.5.0 (#1581) 2019-06-16 11:07:45 -04:00
jvolkenant
6e5ceab0f8 hide virtualenv output (#1578) 2019-05-15 11:59:32 -07:00
jvolkenant
c6fa0d23df check that munin-cron is not running (via cron) when it is run in setup, fixes #660 (#1579) 2019-05-15 11:58:40 -07:00
cmharper
85e59245fd hide 'RTNETLINK answers: Network is unreachable' error message during setup if IPv6 is not available (#1576) 2019-05-15 11:57:06 -07:00
jvolkenant
4232a1205c fix dovecot message about SSLv2 not supported by OpenSSL (#1580) 2019-05-15 11:46:52 -07:00
Michael Heuberger
0d4c693792 Add missing login form method to keep LastPass happy (#1565) 2019-05-12 05:10:34 -07:00
Pascal Garber
77b2246010 Backup Amazon S3: Added support for custom endpoints (#1427) 2019-05-12 05:09:30 -07:00
jvolkenant
aff80ac58c Autodiscovery fix for additional hosted email domains, Fixes #941 (#1467) 2019-05-09 10:13:23 -07:00
just4t
25fec63a03 RAM limit to 502Mb to meet EC2 & Vultr 512Mb inst. (#1560)
AS told here: https://github.com/mail-in-a-box/mailinabox/pull/1534
2019-04-14 16:33:50 -04:00
dexbleeker
9b46637aff Update Roundcube to version 1.3.9 (#1546) 2019-04-14 14:19:21 -04:00
mbraem
fb25013334 user privileges is a set (#1551)
fixes #1540
2019-04-14 14:17:43 -04:00
Joshua Tauberer
dd7a2aa8a6 v0.41 2019-02-26 18:17:50 -05:00
Joshua Tauberer
149552f79b systemctl link should use -f to avoid an error if a system service already exists with that name but points to a different file
https://discourse.mailinabox.email/t/new-error-failed-systemctl-link-conf-mailinabox-service/4626/2
2019-02-26 18:16:26 -05:00
Joshua Tauberer
adddd95e38 add lmtp_destination_recipient_limit=1 to work around spampd bug, see #1523 2019-02-25 13:20:57 -05:00
Ryan Stubbs
bad38840d8 Fix type on alias edit page (#1520) 2019-02-11 20:14:56 -05:00
Yoann Colin
10050aa601 Upgrade to NextCloud 14 (#1504)
* Upgraded Nextcloud from 13.0.6 to 14.0.6.
* Upgraded Contacts from 2.1.5 to 2.1.8.
* Upgraded Calendar from 1.6.1 to 1.6.4.
* Cleanup unsupported version upgrades: Since an upgrade to v0.30 is mandatory before moving upward, I removed the checks for Nextcloud prior version 12.
* Fix the storage root path.
* Add missing indices. Thx @yodax for your feedback.
2019-02-08 21:24:03 -05:00
jvolkenant
c60e3dc842 fail2ban ssh/ssh-ddos and sasl are now sshd and postfix-sasl (fixes #1453, merges #1454)
* fail2ban ssh/ssh-ddos and sasl are now sshd and postfix-sasl

* specified custom datepattern for miab-owncloud.conf
2019-01-18 09:40:51 -05:00
Joshua Tauberer
c7659d9053 v0.40 2019-01-12 08:24:15 -05:00
Joshua Tauberer
cd3fb1b487 fix bootstrap.sh to not confuse the status checks about the latest version 2019-01-09 09:03:43 -05:00
Joshua Tauberer
29e77d25fc merge branch 'ubuntu_bionic' 2019-01-09 08:53:10 -05:00
Joshua Tauberer
e56c55efe8 write changelog summary for the Ubuntu 18.04 upgrade 2019-01-09 08:52:51 -05:00
Joshua Tauberer
8e0d9b9f21 update list of tls ciphers supported 2019-01-09 08:52:51 -05:00
Joshua Tauberer
6e60b47cb5 update bootstrap.sh script to detect the operating system and choose a different version tag depending on whether the box is running Ubuntu 14.04 or Ubuntu 18.04 2019-01-09 08:52:51 -05:00
Joshua Tauberer
a3add03706 Merge branch 'master' into ubuntu_bionic 2019-01-09 07:00:44 -05:00
Joshua Tauberer
7b592b1e99 v0.30 - the last Ubuntu 14.04 release 2019-01-09 06:31:56 -05:00
Joshua Tauberer
a67aa4cfd4 changelog 2019-01-09 06:17:27 -05:00
Dean Perry
31b743b164 Fix some more $DEFAULT_PUBLIC_IP issues (#1494) 2018-12-26 15:39:47 -05:00
jvolkenant
71f1c92b9e bash strict mode fixes (#1482) 2018-12-13 20:30:05 -05:00
EliterScripts
e80a1dd4b7 fix DEFAULT_PUBLIC_IP unbound variable error (#1488)
This will fix this error while installing:
setup/questions.sh: line 95: DEFAULT_PUBLIC_IP: unbound variable
2018-12-13 20:28:21 -05:00
jvolkenant
b7e9a90005 roundcube: upgrade carddav plugin to 3.0.3 & updated migrate.py (#1479)
* roundcube:  upgrade carddav plugin to 3.0.3 & updated migrate.py

* Check for db first and clear sessions to force re-login
2018-12-03 15:33:36 -05:00
Joshua Tauberer
0d4565e71d merge master branch 2018-12-02 18:19:15 -05:00
Joshua Tauberer
703a9376ef fix /etc /usr permissions for Scaleway, see #1438 2018-12-02 18:16:40 -05:00
Joshua Tauberer
b3b798adf2 changelog entries 2018-12-02 18:03:17 -05:00
Joshua Tauberer
bd54b41041 add missing rsyslog to apt install line
see #1438
2018-12-02 18:02:00 -05:00
Joshua Tauberer
a211ad422b add a note on the aliases page that aliases should not be used to forward to outside domains
fixes #1198
2018-12-02 18:02:00 -05:00
Joshua Tauberer
ef28a1defd show the Mail-in-a-Box version in the system status checks even when the new-version check is disabled
fixes #922
2018-12-02 18:02:00 -05:00
Joshua Tauberer
c5c413b447 remove user account mailbox size from the control panel because it takes way too long to compute on very large mailboxes
fixes #531
2018-12-02 18:02:00 -05:00
Joshua Tauberer
d2beb3919b document password character limitation
fixes #407
2018-12-02 18:02:00 -05:00
Achilleas Pipinellis
a7dded8182 Add a logfile entry to the NSD conf file (#1434)
Having a log file can help debugging when something goes wrong and
NSD doesn't fail or MiaB doesn't notify you.

See
https://discourse.mailinabox.email/t/dns-email-domain-becomes-inaccessible-every-few-hours/3770
2018-12-02 18:00:16 -05:00
jeff-h
000363492e Improve greylisting explanation. (#1447)
Hopefully this improves the accuracy of the greylisting description.
2018-12-02 17:58:26 -05:00
jeff-h
5be74dec6e Improve postgrey logging (#1448)
We can't presume the redelivery timeframe of the sending server. However, we do know the blacklist timeframe within which we will reject a redelivery.
2018-12-02 17:57:37 -05:00
Joshua Tauberer
9ddca42c91 add 'nameserver' to resolv.conf, fixes #1450 2018-11-30 10:46:54 -05:00
Joshua Tauberer
ff6d8fc672 remove the ppa directory since we're no longer supporting a PPA for Ubuntu 18.04 2018-11-30 10:46:54 -05:00
Joshua Tauberer
870b82637a fix some wrong variable names, fixes #1353 2018-11-30 10:46:54 -05:00
Joshua Tauberer
dc6458623d add a note on the aliases page that aliases should not be used to forward to outside domains
fixes #1198
2018-11-30 10:46:54 -05:00
Joshua Tauberer
60f9c9e3b7 show the Mail-in-a-Box version in the system status checks even when the new-version check is disabled
fixes #922
2018-11-30 10:46:54 -05:00
Joshua Tauberer
e5e0c64395 turn on bash strict mode to better catch setup errors
fixes #893
2018-11-30 10:46:54 -05:00
Joshua Tauberer
aa52f52d02 disable SMTP AUTH on port 25 to stop it accidentally being used for submission
fixes #830
2018-11-30 10:46:54 -05:00
Joshua Tauberer
b05b06c74a remove user account mailbox size from the control panel because it takes way too long to compute on very large mailboxes
fixes #531
2018-11-30 10:46:54 -05:00
Joshua Tauberer
7f8f4518e3 document password character limitation
fixes #407
2018-11-30 10:46:54 -05:00
Joshua Tauberer
86e2cfb6c8 remove old duplicity migration code from 2015, see 42322455 2018-11-30 10:46:54 -05:00
Holger Just
0335595e7e Update Roundcube to version 1.3.8 (#1475)
https://github.com/roundcube/roundcubemail/releases/tag/1.3.8
2018-11-25 10:40:21 -05:00
jvolkenant
8d5670068a fixes nginx warning about duplicate ssl configuration (#1460) 2018-10-25 15:18:21 -04:00
jvolkenant
c9b3d88108 Fixes #1437 - package python-virtualenv is now called just virtualenv (#1452) 2018-10-24 17:20:48 -04:00
Joshua Tauberer
16f38042ec v0.29 released, closes #1440 2018-10-24 16:12:25 -04:00
Joshua Tauberer
2f494e9a1c CHANGELOG fixes/updates 2018-10-24 16:09:59 -04:00
Joshua Tauberer
f739662392 duplicity started creating signature files with invalid filenames, fixes #1431 2018-10-13 16:16:30 -04:00
Michael Kroes
6eb9055275 Upgrade NextCloud to 13.06 (#1436) 2018-10-09 07:09:54 -04:00
Joshua Tauberer
3dbd6c994a update bind9 configuration 2018-10-03 14:28:43 -04:00
Joshua Tauberer
bc4bdca752 update reference to Ubuntu 14.04 to 18.04 in README.md and security.md and drop mentions of our custom packages that we no longer maintain 2018-10-03 13:00:15 -04:00
Joshua Tauberer
bbfa01f33a update to PHP 7.2
* drop the ondrej/php PPA since PHP 7.x is available directly from Ubuntu 18.04
* intall PHP 7.2 which is just the "php" package in Ubuntu 18.04
* some package names changed, some unnecessary packages are no longer provided
* update paths
2018-10-03 13:00:15 -04:00
Joshua Tauberer
f6a641ad23 remove some cleanup steps that are no longer needed since we aren't supporting upgrades of existing machines and, even if we did, we aren't supporting upgrades from really old versions of Mail-in-a-Box 2018-10-03 13:00:15 -04:00
Joshua Tauberer
51972fd129 fix some comments 2018-10-03 13:00:15 -04:00
Joshua Tauberer
bb43a2127c turn the x64/i686 architecture check into a warning since I'm not sure if we have any architecture requirements anymore, beyond what Ubuntu supports 2018-10-03 13:00:15 -04:00
Christopher A. DeFlumeri
d96613b8fe minimal changeset to get things working on 18.04
@joshdata squashed pull request #1398, removed some comments, and added these notes:

* The old init.d script for the management daemon is replaced with a systemd service.
* A systemd service configuration is added to configure permissions for munin on startup.
* nginx SSL settings are updated because nginx's options and defaults have changed, and we now enable http2.
* Automatic SSHFP record generation is updated to know that 22 is the default SSH daemon port, since it is no longer explicit in sshd_config.
* The dovecot-lucene package is dropped because the Mail-in-a-Box PPA where we built the package has not been updated for Ubuntu 18.04.
* The stock postgrey package is installed instead of the one from our PPA (which we no longer support), which loses the automatic whitelisting of DNSWL.org-whitelisted senders.
* Drop memcached and the status check for memcached, which we used to use with ownCloud long ago but are no longer installing.
* Other minor changes.
2018-10-03 13:00:06 -04:00
Joshua Tauberer
504a9b0abc certbot uses a new directory path for API v02 accounts and we should check that before creating a new account or else we'll try to create a new account on each setup run (which certbot just fails on) 2018-09-03 13:07:24 -04:00
Joshua Tauberer
842fbb3d72 auto-agree to Let's Encrypt's terms of service during setup
fixes #1409

This reverts commit 82844ca651 ("make certbot auto-agree to TOS if NONINTERACTIVE=1 env var is set (#1399)") and instead *always* auto-agree. If we don't auto-agree, certbot asks the user interactively, but our "curl | bash" setup line does not permit interactive prompts, so certbot failed to register and all certificate things were broken until the command was re-run interactively.
2018-09-03 13:06:34 -04:00
Joshua Tauberer
a5d5a073c7 update Z-Push to 2.4.4
Starting with 2.4, Z-Push no longer provides tarballs on their download server. The only options are getting the code from their git repository or using one of their distribution packages. Their Ubuntu 18.04 packaes don't seem to actually work in Ubuntu 18.04, so thinking ahead that's currently a bad choice. In 78d1c9be6e we switched from doing a git clone to using wget on their downloads server because of a problem with something related to stash.z-hub.io's SSL certificate. But wget also seems to work on their source code repository, so we can use that.
2018-09-02 11:29:44 -04:00
Joshua Tauberer
d4b122ee94 update to Nextcloud 13.0.5 2018-08-24 11:11:52 -04:00
Joshua Tauberer
052a1f3b26 update to Roundcube 1.3.7 2018-08-24 10:47:22 -04:00
Joshua Tauberer
180b054dbc small code cleanup testing if the utf8 locale is installed 2018-08-24 09:49:08 -04:00
Joshua Tauberer
cb162da5fe Merge pull request #1412 from hlxnd/pr
Use ISO 8601 on backups table dates, fixes #1397
2018-08-05 15:16:05 -04:00
hlxnd
de9c556ad7 Add missing PHP end tag 2018-08-05 15:27:35 +02:00
hlxnd
f420294819 Use ISO 8601 on backups table dates. 2018-08-05 15:26:45 +02:00
Joshua Tauberer
738e0a6e17 v0.28 released, closes #1405 2018-07-30 11:14:38 -04:00
Pascal Garber
e0d46d1eb5 Use Nextcloud’s occ command to unlock the admin (#1406) 2018-07-25 15:37:09 -04:00
Joshua Tauberer
7f37abca05 add php7.0-curl to webmail.sh
see 7ee91f6ae6
see #1268
closes #1259
2018-07-22 09:19:36 -04:00
Joshua Tauberer
2f467556bd new ssl cert provisioning broke if a domain doesnt yet have a cert, fixes #1392 2018-07-19 11:40:49 -04:00
Joshua Tauberer
15583ec10d updated CHANGELOG 2018-07-19 11:27:37 -04:00
Nils Norman Haukås
78d1c9be6e failing z-push installation: replace git clone with wget_verify
git clone (which uses curl) underneath was failing. Curiously, the same
git clone command would work on my macos host machine.

From the screenshot it looks like curl was somehow not able to negotiate
the connection. Might have been a missing CA certificate for Comodo, but
I was not able to determine if that was the issue.

fixes #1393
closes #1387
closes #1400
2018-07-19 11:25:57 -04:00
dev9
b0b5d8e792 Fix .mobileconfig so CalDAV calendar works on Mac OS X (#1402)
The previous CalDAVPrincipalURL "/cloud/remote.php/caldav/calendars/" causes an error in OS X.

See: https://discourse.mailinabox.email/t/caldav-with-macos-10-12-2-does-not-work/1649 and other similar issues.

The correct CalDAVPrincipalURL: https://discourse.mailinabox.email/t/caldav-with-macos-10-12-2-does-not-work/1649 but it turns out you can just leave the key/value out completely and OS X/iOS are able to auto discover the correct URL.
2018-07-19 11:17:38 -04:00
Nils
82844ca651 make certbot auto-agree to TOS if NONINTERACTIVE=1 env var is set (#1399) 2018-07-15 11:24:15 -04:00
Joshua Tauberer
2a72c800f6 replace free_tls_certificates with certbot 2018-06-29 16:46:21 -04:00
Joshua Tauberer
8be23d5ef6 ssl_certificates: reuse query_dns function in status_checks and simplify calls by calling normalize_ip within query_dns 2018-06-29 16:46:21 -04:00
Joshua Tauberer
f9a0e39cc9 cryptography is now distributed as a wheel and no longer needs system development packages to be installed or pip/setuptools workarounds 2018-06-29 16:46:21 -04:00
Joshua Tauberer
0c0a079354 v0.27 2018-06-14 07:49:20 -04:00
Joshua Tauberer
42e86610ba changelog entry 2018-05-12 09:43:41 -04:00
yeah
7c62f4b8e9 Update Roundcube to 1.3.6 (#1376) 2018-04-17 11:54:24 -04:00
Joshua Tauberer
1eba7b0616 send the mail_log.py report to the box admin every Monday 2018-02-25 11:55:06 -05:00
Joshua Tauberer
9c7820f422 mail_log.py: include sent mail in the logins report in a new smtp column 2018-02-24 09:24:15 -05:00
Joshua Tauberer
87ec4e9f82 mail_log.py: refactor the dovecot login collector 2018-02-24 09:24:14 -05:00
Joshua Tauberer
08becf7fa3 the hidden feature for proxying web requests now sets X-Forwarded-For 2018-02-24 09:24:14 -05:00
Joshua Tauberer
5eb4a53de1 remove old tools/update-subresource-integrity.py script which isn't used now that we download all admin page remote assets during setup 2018-02-24 09:24:14 -05:00
Joshua Tauberer
598ade3f7a changelog entry 2018-02-24 09:24:09 -05:00
xetorixik
8f399df5bb Update Roundcube to 1.3.4 and Z-push to 2.3.9 (#1354) 2018-02-21 08:22:57 -05:00
Joshua Tauberer
ae73dc5d30 v0.26c 2018-02-13 10:46:02 -05:00
Joshua Tauberer
c409b2efd0 CHANGELOG entries 2018-02-13 10:44:07 -05:00
Joshua Tauberer
6961840c0e wrap wget in hide_output so that wget errors are shown
Our wget_verify function uses wget to download a file and then check
the file's hash. If wget fails, i.e. because of a 404 or other HTTP
or network error, we exited setup without displaying any output because
normally there are no errors and -q keeps the setup output clean.

Wrapping wget with our hide_output function, and dropping -q, captures
wget's output and shows it and exits setup just if wget fails.

see #1297
2018-02-13 10:38:10 -05:00
yeah
6162a9637c Add some development instructions to CONTRIBUTING.md (#1348) 2018-02-05 08:41:19 -05:00
Jan Schulz-Hofen
47c968e71b Upgrade Nextcloud from 12.0.3 to 12.0.5 2018-02-04 10:13:30 -05:00
Jan Schulz-Hofen
ed3e2aa712 Use new .tar.bz2 source files for ownCloud and fix upgrade paths 2018-02-04 10:13:30 -05:00
NatCC
fe597da7aa Update users.html (#1345)
Passwords must be eight characters long; when passwords are changed via the users page the dialog states that passwords need to be at least four characters but only eight or more are acceptable.
2018-02-03 17:49:11 -05:00
Joshua Tauberer
61e9888a85 Cdon't try to generate a CSR in the control panel until both the domain and country are selected
Fixes #1338.

See 0e9680fda63c33ace3f34ca7126617fb0efe8ffc, a52c56e571.
2018-01-28 09:08:24 -05:00
Joshua Tauberer
35fed8606e only spawn one process for the management daemon
In 0088fb4553 I changed the management daemon's startup
script from a symlink to a Python script to a bash script that activated the new virtualenv
and then launched Python. As a result, the init.d script that starts the daemon would
write the pid of bash to the pidfile, and when trying to kill it, it would kill bash but
not the Python process.

Using exec to start Python fixes this problem by making the Python process have the pid
that the init.d script knows about.

fixes #1339
2018-01-28 09:08:19 -05:00
Joshua Tauberer
ef6f121491 when generating a CSR in the control panel, don't set empty attributes
Same as in a52c56e571.

Fixes #1338.
2018-01-28 09:07:54 -05:00
Joshua Tauberer
ec3aab0eaa v0.26b 2018-01-25 09:27:17 -05:00
Joshua Tauberer
8c69b9e261 update CHANGELOG 2018-01-25 09:23:04 -05:00
Joshua Tauberer
e7150e3bc6 pin acme to v0.20, which is the last version compatible with free_tls_certificates
free_tls_certificates uses acme.jose, which in acme v0.21 was moved to a new Python package.

See #1328
2018-01-20 11:23:45 -05:00
Joshua Tauberer
8d6d84d87f run mailconfig.py's email address validator outside of the virtualenv during questions.sh
We don't have the virtualenv this early in setup.

Broken by 0088fb4553.

Fixes #1326.

See https://discourse.mailinabox.email/t/that-is-not-a-valid-email-error-during-mailinabox-installation/2793.
2018-01-20 10:59:37 -05:00
barrybingo
a6a1cc7ae0 Reduce munin-node log level to warning (#1330) 2018-01-19 12:00:44 -05:00
70 changed files with 1651 additions and 2163 deletions

View File

@@ -13,7 +13,7 @@ trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
[Makefile] [Makefile]
indent_style = tabs indent_style = tab
indent_size = 4 indent_size = 4
[Vagrantfile] [Vagrantfile]
@@ -23,7 +23,7 @@ indent_size = 2
indent_size = 2 indent_size = 2
[*.py] [*.py]
indent_style = tabs indent_style = tab
[*.js] [*.js]
indent_size = 2 indent_size = 2

View File

@@ -1,6 +1,246 @@
CHANGELOG CHANGELOG
========= =========
v0.47 (July 29, 2020)
---------------------
Security fixes:
* Roundcube is updated to version 1.4.7 fixing a cross-site scripting (XSS) vulnerability with HTML messages with malicious svg/namespace (CVE-2020-15562) (https://roundcube.net/news/2020/07/05/security-updates-1.4.7-1.3.14-and-1.2.11).
* SSH connections are now rate-limited at the firewall level (in addition to fail2ban).
v0.46 (June 11, 2020)
---------------------
Security fixes:
* Roundcube is updated to version 1.4.6 (https://roundcube.net/news/2020/06/02/security-updates-1.4.5-and-1.3.12).
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)
-------------------------
System:
* TLS settings have been upgraded following Mozilla's recommendations for servers. TLS1.2 and 1.3 are now the only supported protocols for web, IMAP, and SMTP (submission).
* Fixed an issue starting services when Mail-in-a-Box isn't on the root filesystem.
* Changed some performance options affecting Roundcube and Nextcloud.
Software updates:
* Upgraded Nextcloud from 15.0.8 to 17.0.2 (with Contacts from 3.1.1 to 3.1.6 and Calendar from 1.6.5 to 1.7.1)
* Upgraded Z-Push to 2.5.1.
* Upgraded Roundcube from 1.3.10 to 1.4.2 and changed the default skin (theme) to Elastic.
Control panel:
* The Custom DNS list of records is now sorted.
* The emails that report TLS provisioning results now has a less scary subject line.
Mail:
* Fetching of updated whitelist for greylisting was fetching each day instead of every month.
* OpenDKIM signing has been changed to 'relaxed' mode so that some old mail lists that forward mail can do so.
DNS:
* Automatic autoconfig.* subdomains can now be suppressed with custom DNS records.
* DNS zone transfer now works with IPv6 addresses.
Setup:
* An Ubuntu package source was missing on systems where it defaults off.
v0.43 (September 1, 2019)
-------------------------
Security fixes:
* A security issue was discovered in rsync backups. If you have enabled rsync backups, the file `id_rsa_miab` may have been copied to your backup destination. This file can be used to access your backup destination. If the file was copied to your backup destination, we recommend that you delete the file on your backup destination, delete `/root/.ssh/id_rsa_miab` on your Mail-in-a-Box, then re-run Mail-in-a-Box setup, and re-configure your SSH public key at your backup destination according to the instructions in the Mail-in-a-Box control panel.
* Brute force attack prevention was missing for the managesieve service.
Setup:
* Nextcloud was not upgraded properly after restoring Mail-in-a-Box from a backup from v0.40 or earlier.
Mail:
* Upgraded Roundcube to 1.3.10.
* Fetch an updated whitelist for greylisting on a monthly basis to reduce the number of delayed incoming emails.
Control panel:
* When using secondary DNS, it is now possible to specify a subnet range with the `xfr:` option.
* Fixed an issue when the secondary DNS option is used and the secondary DNS hostname resolves to multiple IP addresses.
* Fix a bug in how a backup configuration error is shown.
v0.42b (August 3, 2019)
-----------------------
Changes:
* Decreased the minimum supported RAM to 502 Mb.
* Improved mail client autoconfiguration.
* Added support for S3-compatible backup services besides Amazon S3.
* Fixed the control panel login page to let LastPass save passwords.
* Fixed an error in the user privileges API.
* Silenced some spurrious messages.
Software updates:
* Upgraded Roundcube from 1.3.8 to 1.3.9.
* Upgraded Nextcloud from 14.0.6 to 15.0.8 (with Contacts from 2.1.8 to 3.1.1 and Calendar from 1.6.4 to 1.6.5).
* Upgraded Z-Push from 2.4.4 to 2.5.0.
Note that v0.42 (July 4, 2019) was pulled shortly after it was released to fix a Nextcloud upgrade issue.
v0.41 (February 26, 2019)
-------------------------
System:
* Missing brute force login attack prevention (fail2ban) filters which stopped working on Ubuntu 18.04 were added back.
* Upgrades would fail if Mail-in-a-Box moved to a different directory in `systemctl link`.
Mail:
* Incoming messages addressed to more than one local user were rejected because of a bug in spampd packaged by Ubuntu 18.04. A workaround was added.
Contacts/Calendar:
* Upgraded Nextcloud from 13.0.6 to 14.0.6.
* Upgraded Contacts from 2.1.5 to 2.1.8.
* Upgraded Calendar from 1.6.1 to 1.6.4.
v0.40 (January 12, 2019)
------------------------
This is the first release for Ubuntu 18.04. This version and versions going forward can **only** be installed on Ubuntu 18.04; however, upgrades of existing Ubuntu 14.04 boxes to the latest version supporting Ubuntu 14.04 (v0.30) continue to work as normal.
When **upgrading**, you **must first upgrade your existing Ubuntu 14.04 Mail-in-a-Box box** to the latest release supporting Ubuntu 14.04 --- that's v0.30 --- before you migrate to Ubuntu 18.04. If you are running an older version of Mail-in-a-Box which has an old version of ownCloud or Nextcloud, you will *not* be able to upgrade your data because older versions of ownCloud and Nextcloud that are required to perform the upgrade *cannot* be run on Ubuntu 18.04. To upgrade from Ubuntu 14.04 to Ubuntu 18.04, you **must create a fresh Ubuntu 18.04 machine** before installing this version. In-place upgrades of servers are not supported. Since Ubuntu's support for Ubuntu 14.04 has almost ended, everyone is encouraged to create a new Ubuntu 18.04 machine and migrate to it.
For complete upgrade instructions, see:
https://discourse.mailinabox.email/t/mail-in-a-box-version-v0-40-and-moving-to-ubuntu-18-04/4289
The changelog for this release follows.
Setup:
* Mail-in-a-Box now targets Ubuntu 18.04 LTS, which will have support from Ubuntu through 2022.
* Some of the system packages updated in virtue of using Ubuntu 18.04 include postfix (2.11=>3.3) nsd (4.0=>4.1), nginx (1.4=>1.14), PHP (7.0=>7.2), Python (3.4=>3.6), fail2ban (0.8=>0.10), Duplicity (0.6=>0.7).
* [Unofficial Bash Strict Mode](http://redsymbol.net/articles/unofficial-bash-strict-mode/) is turned on for setup, which might catch previously uncaught issues during setup.
Mail:
* IMAP server-side full text search is no longer supported because we were using a custom-built `dovecot-lucene` package that we are no longer maintaining.
* Sending email is now disabled on port 25 --- you must log in to port 587 to send email, per the long-standing mail instructions.
* Greylisting may delay more emails from new senders. We were using a custom-built postgrey package previously that whitelisted sending domains in dnswl.org, but we are no longer maintaining that package.
v0.30 (January 9, 2019)
-----------------------
Setup:
* Update to Roundcube 1.3.8 and the CardDAV plugin to 3.0.3.
* Add missing rsyslog package to install line since some OS images don't have it installed by default.
* A log file for nsd was added.
Control Panel:
* The users page now documents that passwords should only have ASCII characters to prevent character encoding mismaches between clients and the server.
* The users page no longer shows user mailbox sizes because this was extremely slow for very large mailboxes.
* The Mail-in-a-Box version is now shown in the system status checks even when the new-version check is disabled.
* The alises page now warns that alises should not be used to forward mail off of the box. Mail filters within Roundcube are better for that.
* The explanation of greylisting has been improved.
v0.29 (October 25, 2018)
------------------------
* Starting with v0.28, TLS certificate provisioning wouldn't work on new boxes until the mailinabox setup command was run a second time because of a problem with the non-interactive setup.
* Update to Nextcloud 13.0.6.
* Update to Roundcube 1.3.7.
* Update to Z-Push 2.4.4.
* Backup dates listed in the control panel now use an internationalized format.
v0.28 (July 30, 2018)
---------------------
System:
* We now use EFF's `certbot` to provision TLS certificates (from Let's Encrypt) instead of our home-grown ACME library.
Contacts/Calendar:
* Fix for Mac OS X autoconfig of the calendar.
Setup:
* Installing Z-Push broke because of what looks like a change or problem in their git server HTTPS certificate. That's fixed.
v0.27 (June 14, 2018)
---------------------
Mail:
* A report of box activity, including sent/received mail totals and logins by user, is now emailed to the box's administrator user each week.
* Update Roundcube to version 1.3.6 and Z-Push to version 2.3.9.
Control Panel:
* The undocumented feature for proxying web requests to another server now sets X-Forwarded-For.
v0.26c (February 13, 2018)
--------------------------
Setup:
* Upgrades from v0.21c (February 1, 2017) or earlier were broken because the intermediate versions of ownCloud used in setup were no longer available from ownCloud.
* Some download errors had no output --- there is more output on error now.
Control Panel:
* The background service for the control panel was not restarting on updates, leaving the old version running. This was broken in v0.26 and is now fixed.
* Installing your own TLS/SSL certificate had been broken since v0.24 because the new version of openssl became stricter about CSR generation parameters.
* Fixed password length help text.
Contacts/Calendar:
* Upgraded Nextcloud from 12.0.3 to 12.0.5.
v0.26b (January 25, 2018)
-------------------------
* Fix new installations which broke at the step of asking for the user's desired email address, which was broken by v0.26's changes related to the control panel.
* Fix the provisioning of TLS certificates by pinning a Python package we rely on (acme) to an earlier version because our code isn't yet compatible with its current version.
* Reduce munin's log_level from debug to warning to prevent massive log files.
v0.26 (January 18, 2018) v0.26 (January 18, 2018)
------------------------ ------------------------
@@ -24,7 +264,7 @@ Installer:
* We now run `apt-get autoremove` at the start of setup to clear out old packages, especially old kernels that take up a lot of space. On the first run, this step may take a long time. * We now run `apt-get autoremove` at the start of setup to clear out old packages, especially old kernels that take up a lot of space. On the first run, this step may take a long time.
* We now fetch Z-Push from its tagged git repository, fixing an installation problem. * We now fetch Z-Push from its tagged git repository, fixing an installation problem.
* Some old PHP5 packages are removed from setup, fixing an installation bug where Apache would get installed. * Some old PHP5 packages are removed from setup, fixing an installation bug where Apache would get installed.
* Python 3 packages for the control panel are now installed using a virtualenv to prevent installation errors. * Python 3 packages for the control panel are now installed using a virtualenv to prevent installation errors due to conflicts in the cryptography/openssl packages between OS-installed packages and pip-installed packages.
v0.25 (November 15, 2017) v0.25 (November 15, 2017)
------------------------- -------------------------

View File

@@ -1,3 +1,50 @@
# Contributing
Mail-in-a-Box is an open source project. Your contributions and pull requests are welcome.
## Development
To start developing Mail-in-a-Box, [clone the repository](https://github.com/mail-in-a-box/mailinabox) and familiarize yourself with the code.
$ git clone https://github.com/mail-in-a-box/mailinabox
### Vagrant and VirtualBox
We recommend you use [Vagrant](https://www.vagrantup.com/intro/getting-started/install.html) and [VirtualBox](https://www.virtualbox.org/wiki/Downloads) for development. Please install them first.
With Vagrant set up, the following should boot up Mail-in-a-Box inside a virtual machine:
$ vagrant up --provision
_If you're seeing an error message about your *IP address being listed in the Spamhaus Block List*, simply uncomment the `export SKIP_NETWORK_CHECKS=1` line in `Vagrantfile`. It's normal, you're probably using a dynamic IP address assigned by your Internet providerthey're almost all listed._
### Modifying your `hosts` file
After a while, Mail-in-a-Box will be available at `192.168.50.4` (unless you changed that in your `Vagrantfile`). To be able to use the web-based bits, we recommend to add a hostname to your `hosts` file:
$ echo "192.168.50.4 mailinabox.lan" | sudo tee -a /etc/hosts
You should now be able to navigate to https://mailinabox.lan/admin using your browser. There should be an initial admin user with the name `me@mailinabox.lan` and the password `12345678`.
### Making changes
Your working copy of Mail-in-a-Box will be mounted inside your VM at `/vagrant`. Any change you make locally will appear inside your VM automatically.
Running `vagrant up --provision` again will repeat the installation with your modifications.
Alternatively, you can also ssh into the VM using:
$ vagrant ssh
Once inside the VM, you can re-run individual parts of the setup like in this example:
vm$ cd /vagrant
vm$ sudo setup/owncloud.sh # replace with script you'd like to re-run
### Tests
Mail-in-a-Box needs more tests. If you're still looking for a way to help out, writing and contributing tests would be a great start!
## Public domain ## Public domain
This project is in the public domain. Copyright and related rights in the work worldwide are waived through the [CC0 1.0 Universal public domain dedication][CC0]. See the LICENSE file in this directory. This project is in the public domain. Copyright and related rights in the work worldwide are waived through the [CC0 1.0 Universal public domain dedication][CC0]. See the LICENSE file in this directory.

View File

@@ -13,7 +13,7 @@ Our goals are to:
* Make deploying a good mail server easy. * Make deploying a good mail server easy.
* Promote [decentralization](http://redecentralize.org/), innovation, and privacy on the web. * Promote [decentralization](http://redecentralize.org/), innovation, and privacy on the web.
* Have automated, auditable, and [idempotent](https://sharknet.us/2014/02/01/automated-configuration-management-challenges-with-idempotency/) configuration. * 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 a totally unhackable, NSA-proof server.
* **Not** make something customizable by power users. * **Not** make something customizable by power users.
@@ -22,7 +22,7 @@ Additionally, this project has a [Code of Conduct](CODE_OF_CONDUCT.md), which su
The Box The Box
------- -------
Mail-in-a-Box turns a fresh Ubuntu 14.04 LTS 64-bit machine into a working mail server by installing and configuring various components. Mail-in-a-Box turns a fresh Ubuntu 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". It is a one-click email appliance. There are no user-configurable setup options. It "just works".
@@ -37,7 +37,6 @@ The components installed are:
It also includes: It also includes:
* A control panel and API for adding/removing mail users, aliases, custom DNS records, etc. and detailed system monitoring. * A control panel and API for adding/removing mail users, aliases, custom DNS records, etc. and detailed system monitoring.
* Our own builds of postgrey (adding better whitelisting) and dovecot-lucene (faster search for mail) distributed via the [Mail-in-a-Box PPA](https://launchpad.net/~mail-in-a-box/+archive/ubuntu/ppa) on Launchpad.
For more information on how Mail-in-a-Box handles your privacy, see the [security details page](security.md). For more information on how Mail-in-a-Box handles your privacy, see the [security details page](security.md).
@@ -46,20 +45,20 @@ Installation
See the [setup guide](https://mailinabox.email/guide.html) for detailed, user-friendly instructions. See the [setup guide](https://mailinabox.email/guide.html) for detailed, user-friendly instructions.
For experts, start with a completely fresh (really, I mean it) Ubuntu 14.04 LTS 64-bit machine. On the machine... For experts, start with a completely fresh (really, I mean it) Ubuntu 18.04 LTS 64-bit machine. On the machine...
Clone this repository: Clone this repository:
$ git clone https://github.com/mail-in-a-box/mailinabox $ git clone https://github.com/mail-in-a-box/mailinabox
$ cd mailinabox $ cd mailinabox
_Optional:_ Download my PGP key and then verify that the sources were signed _Optional:_ Download Josh's PGP key and then verify that the sources were signed
by me: 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.26 $ git verify-tag v0.47
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!
@@ -68,19 +67,25 @@ by me:
You'll get a lot of warnings, but that's OK. Check that the primary key fingerprint matches the You'll get a lot of warnings, but that's OK. Check that the primary key fingerprint matches the
fingerprint in the key details at [https://keybase.io/joshdata](https://keybase.io/joshdata) fingerprint in the key details at [https://keybase.io/joshdata](https://keybase.io/joshdata)
and on my [personal homepage](https://razor.occams.info/). (Of course, if this repository has been compromised you can't trust these instructions.) and on his [personal homepage](https://razor.occams.info/). (Of course, if this repository has been compromised you can't trust these instructions.)
Checkout the tag corresponding to the most recent release: Checkout the tag corresponding to the most recent release:
$ git checkout v0.26 $ git checkout v0.47
Begin the installation. Begin the installation.
$ sudo setup/start.sh $ sudo setup/start.sh
For help, DO NOT contact me directly --- I don't do tech support by email or tweet (no exceptions). For help, DO NOT contact Josh directly --- I don't do tech support by email or tweet (no exceptions).
Post your question on the [discussion forum](https://discourse.mailinabox.email/) instead, where maintainers and Mail-in-a-Box users may be able to help you.
Contributing and Development
----------------------------
Mail-in-a-Box is an open source project. Your contributions and pull requests are welcome. See [CONTRIBUTING](CONTRIBUTING.md) to get started.
Post your question on the [discussion forum](https://discourse.mailinabox.email/) instead, where me and other Mail-in-a-Box users may be able to help you.
The Acknowledgements The Acknowledgements
-------------------- --------------------

9
Vagrantfile vendored
View File

@@ -2,14 +2,7 @@
# vi: set ft=ruby : # vi: set ft=ruby :
Vagrant.configure("2") do |config| Vagrant.configure("2") do |config|
config.vm.box = "ubuntu14.04" config.vm.box = "ubuntu/bionic64"
config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"
if Vagrant.has_plugin?("vagrant-cachier")
# Configure cached packages to be shared between instances of the same base box.
# More info on http://fgrehm.viewdocs.io/vagrant-cachier/usage
config.cache.scope = :box
end
# Network config: Since it's a mail server, the machine must be connected # 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 # to the public web. However, we currently don't want to expose SSH since

View File

@@ -1,4 +1,4 @@
# Fail2Ban filter Dovecot authentication and pop3/imap server # Fail2Ban filter Dovecot authentication and pop3/imap/managesieve server
# For Mail-in-a-Box # For Mail-in-a-Box
[INCLUDES] [INCLUDES]
@@ -9,7 +9,7 @@ before = common.conf
_daemon = (auth|dovecot(-auth)?|auth-worker) _daemon = (auth|dovecot(-auth)?|auth-worker)
failregex = ^%(__prefix_line)s(pop3|imap)-login: (Info: )?(Aborted login|Disconnected)(: Inactivity)? \(((no auth attempts|auth failed, \d+ attempts)( in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<\S*>,)?( method=\S+,)? rip=<HOST>, lip=(\d{1,3}\.){3}\d{1,3}(, TLS( handshaking)?(: Disconnected)?)?(, session=<\S+>)?\s*$ failregex = ^%(__prefix_line)s(pop3|imap|managesieve)-login: (Info: )?(Aborted login|Disconnected)(: Inactivity)? \(((no auth attempts|auth failed, \d+ attempts)( in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<\S*>,)?( method=\S+,)? rip=<HOST>, lip=(\d{1,3}\.){3}\d{1,3}(, TLS( handshaking)?(: Disconnected)?)?(, session=<\S+>)?\s*$
ignoreregex = ignoreregex =

View File

@@ -3,5 +3,6 @@
before = common.conf before = common.conf
[Definition] [Definition]
datepattern = %%Y-%%m-%%d %%H:%%M:%%S
failregex=Login failed: .*Remote IP: '<HOST>[\)'] failregex=Login failed: .*Remote IP: '<HOST>[\)']
ignoreregex = ignoreregex =

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
@@ -69,13 +69,10 @@ action = iptables-allports[name=recidive]
# So the notification is ommited. This will prevent message appearing in the mail.log that mail # So the notification is ommited. This will prevent message appearing in the mail.log that mail
# can't be delivered to fail2ban@$HOSTNAME. # can't be delivered to fail2ban@$HOSTNAME.
[sasl] [postfix-sasl]
enabled = true enabled = true
[ssh] [sshd]
enabled = true enabled = true
maxretry = 7 maxretry = 7
bantime = 3600 bantime = 3600
[ssh-ddos]
enabled = true

View File

@@ -18,8 +18,6 @@
<string>PRIMARY_HOSTNAME</string> <string>PRIMARY_HOSTNAME</string>
<key>CalDAVPort</key> <key>CalDAVPort</key>
<real>443</real> <real>443</real>
<key>CalDAVPrincipalURL</key>
<string>/cloud/remote.php/caldav/calendars/</string>
<key>CalDAVUseSSL</key> <key>CalDAVUseSSL</key>
<true/> <true/>
<key>PayloadDescription</key> <key>PayloadDescription</key>

10
conf/mailinabox.service Normal file
View File

@@ -0,0 +1,10 @@
[Unit]
Description=Mail-in-a-Box System Management Service
After=multi-user.target
[Service]
Type=idle
ExecStart=/usr/local/lib/mailinabox/start
[Install]
WantedBy=multi-user.target

View File

@@ -1,135 +0,0 @@
#! /bin/sh
### BEGIN INIT INFO
# Provides: mailinabox
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Start and stop the Mail-in-a-Box management daemon.
# Description: Start and stop the Mail-in-a-Box management daemon.
### END INIT INFO
# Adapted from http://blog.codefront.net/2007/06/11/nginx-php-and-a-php-fastcgi-daemon-init-script/
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Mail-in-a-Box Management Daemon"
NAME=mailinabox
DAEMON=/usr/local/lib/mailinabox/start
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
# Set defaults.
START=yes
EXEC_AS_USER=root
# Ensure Python reads/writes files in UTF-8. If the machine
# triggers some other locale in Python, like ASCII encoding,
# Python may not be able to read/write files. Set also
# setup/start.sh (where the locale is also installed if not
# already present) and management/daily_tasks.sh.
export LANGUAGE=en_US.UTF-8
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
export LC_TYPE=en_US.UTF-8
# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions
# If the daemon is not enabled, give the user a warning and then exit,
# unless we are stopping the daemon
if [ "$START" != "yes" -a "$1" != "stop" ]; then
log_warning_msg "To enable $NAME, edit /etc/default/$NAME and set START=yes"
exit 0
fi
# Process configuration
#export ...
DAEMON_ARGS=""
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|| return 1
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
--background --make-pidfile --chuid $EXEC_AS_USER --startas $DAEMON -- \
$DAEMON_ARGS \
|| return 2
}
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE > /dev/null # --name $DAEMON
RETVAL="$?"
[ "$RETVAL" = 2 ] && return 2
# Wait for children to finish too if this is a daemon that forks
# and if the daemon is only ever run from this initscript.
# If the above conditions are not satisfied then add some other code
# that waits for the process to drop all resources that could be
# needed by services started subsequently. A last resort is to
# sleep for some time.
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
[ "$?" = 2 ] && return 2
# Many daemons don't delete their pidfiles when they exit.
rm -f $PIDFILE
return "$RETVAL"
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
restart|force-reload)
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
exit 3
;;
esac

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/">

10
conf/munin.service Normal file
View File

@@ -0,0 +1,10 @@
[Unit]
Description=Munin System Monitoring Startup Script
After=multi-user.target
[Service]
Type=idle
ExecStart=/usr/local/lib/mailinabox/munin_start.sh
[Install]
WantedBy=multi-user.target

View File

@@ -18,6 +18,9 @@
location = /.well-known/autoconfig/mail/config-v1.1.xml { location = /.well-known/autoconfig/mail/config-v1.1.xml {
alias /var/lib/mailinabox/mozilla-autoconfig.xml; alias /var/lib/mailinabox/mozilla-autoconfig.xml;
} }
location = /mail/config-v1.1.xml {
alias /var/lib/mailinabox/mozilla-autoconfig.xml;
}
# Roundcube Webmail configuration. # Roundcube Webmail configuration.
rewrite ^/mail$ /mail/ redirect; rewrite ^/mail$ /mail/ redirect;

View File

@@ -19,6 +19,7 @@
rewrite ^/cloud/$ /cloud/index.php; rewrite ^/cloud/$ /cloud/index.php;
rewrite ^/cloud/(contacts|calendar|files)$ /cloud/index.php/apps/$1/ redirect; rewrite ^/cloud/(contacts|calendar|files)$ /cloud/index.php/apps/$1/ redirect;
rewrite ^(/cloud/core/doc/[^\/]+/)$ $1/index.html; rewrite ^(/cloud/core/doc/[^\/]+/)$ $1/index.html;
rewrite ^(/cloud/oc[sm]-provider)/$ $1/index.php redirect;
location /cloud/ { location /cloud/ {
alias /usr/local/lib/owncloud/; alias /usr/local/lib/owncloud/;
location ~ ^/cloud/(build|tests|config|lib|3rdparty|templates|data|README)/ { location ~ ^/cloud/(build|tests|config|lib|3rdparty|templates|data|README)/ {
@@ -27,6 +28,14 @@
location ~ ^/cloud/(?:\.|autotest|occ|issue|indie|db_|console) { location ~ ^/cloud/(?:\.|autotest|occ|issue|indie|db_|console) {
deny all; deny all;
} }
# Enable paths for service and cloud federation discovery
# Resolves warning in Nextcloud Settings panel
location ~ ^/cloud/(oc[sm]-provider)?/([^/]+\.php)$ {
index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/local/lib/owncloud/$1/$2;
fastcgi_pass php-fpm;
}
} }
location ~ ^(/cloud)((?:/ocs)?/[^/]+\.php)(/.*)?$ { location ~ ^(/cloud)((?:/ocs)?/[^/]+\.php)(/.*)?$ {
# note: ~ has precendence over a regular location block # note: ~ has precendence over a regular location block

View File

@@ -1,76 +1,20 @@
# from https://gist.github.com/konklone/6532544 and https://mozilla.github.io/server-side-tls/ssl-config-generator/ # We track the Mozilla "intermediate" compatibility TLS recommendations.
################################################################################################################### # Note that these settings are repeated in the SMTP and IMAP configuration.
# ssl_protocols has moved to nginx.conf in bionic, check there for enabled protocols.
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_dhparam STORAGE_ROOT/ssl/dh2048.pem;
# Basically the nginx configuration I use at konklone.com.
# I check it using https://www.ssllabs.com/ssltest/analyze.html?d=konklone.com
#
# To provide feedback, please tweet at @konklone or email eric@konklone.com.
# Comments on gists don't notify the author.
#
# Thanks to WubTheCaptain (https://wubthecaptain.eu) for his help and ciphersuites.
# Thanks to Ilya Grigorik (https://www.igvita.com) for constant inspiration.
# Path to certificate and private key.
# The .crt may omit the root CA cert, if it's a standard CA that ships with clients.
#ssl_certificate /path/to/unified.crt;
#ssl_certificate_key /path/to/my-private-decrypted.key;
# Tell browsers to require SSL (warning: difficult to change your mind)
# Handled by the management daemon because we can toggle this version or a
# preload version.
#add_header Strict-Transport-Security max-age=31536000;
# Prefer certain ciphersuites, to enforce Forward Secrecy and avoid known vulnerabilities.
#
# Forces forward secrecy in all browsers and clients that can use TLS,
# but with a small exception (DES-CBC3-SHA) for IE8/XP users.
#
# Reference client: https://www.ssllabs.com/ssltest/analyze.html
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
# Cut out (the old, broken) SSLv3 entirely.
# This **excludes IE6 users** and (apparently) Yandexbot.
# Just comment out if you need to support IE6, bless your soul.
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
# Turn on session resumption, using a cache shared across nginx processes,
# as recommended by http://nginx.org/en/docs/http/configuring_https_servers.html # as recommended by http://nginx.org/en/docs/http/configuring_https_servers.html
ssl_session_cache shared:SSL:50m; ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d; ssl_session_timeout 1d;
#keepalive_timeout 70; # in Ubuntu 14.04/nginx 1.4.6 the default is 65, so plenty good
# Buffer size of 1400 bytes fits in one MTU. # Buffer size of 1400 bytes fits in one MTU.
# nginx 1.5.9+ ONLY # nginx 1.5.9+ ONLY
#ssl_buffer_size 1400; ssl_buffer_size 1400;
# SPDY header compression (0 for none, 9 for slow/heavy compression). Preferred is 6.
#
# BUT: header compression is flawed and vulnerable in SPDY versions 1 - 3.
# Disable with 0, until using a version of nginx with SPDY 4.
spdy_headers_comp 0;
# Now let's really get fancy, and pre-generate a 2048 bit random parameter
# for DH elliptic curves. If not created and specified, default is only 1024 bits.
#
# Generated by OpenSSL with the following command:
# openssl dhparam -outform pem -out dhparam2048.pem 2048
#
# Note: raising the bits to 2048 excludes Java 6 clients. Comment out if a problem.
ssl_dhparam STORAGE_ROOT/ssl/dh2048.pem;
# OCSP stapling - means nginx will poll the CA for signed OCSP responses,
# and send them to clients so clients don't make their own OCSP calls.
# http://en.wikipedia.org/wiki/OCSP_stapling
#
# while the ssl_certificate above may omit the root cert if the CA is trusted,
# ssl_trusted_certificate below must point to a chain of **all** certs
# in the trust path - (your cert, intermediary certs, root cert)
#
# 8.8.8.8 and 8.8.4.4 below are Google's public IPv4 DNS servers.
# nginx will use them to talk to the CA.
ssl_stapling on; ssl_stapling on;
ssl_stapling_verify on; ssl_stapling_verify on;
resolver 127.0.0.1 valid=86400; resolver 127.0.0.1 valid=86400;
resolver_timeout 10; resolver_timeout 10;
# h/t https://gist.github.com/konklone/6532544

View File

@@ -7,6 +7,6 @@
## your own --- please do not ask for help from us. ## your own --- please do not ask for help from us.
upstream php-fpm { upstream php-fpm {
server unix:/var/run/php/php7.0-fpm.sock; server unix:/var/run/php/php7.2-fpm.sock;
} }

View File

@@ -25,14 +25,14 @@ server {
# This path must be served over HTTP for ACME domain validation. # This path must be served over HTTP for ACME domain validation.
# We map this to a special path where our TLS cert provisioning # We map this to a special path where our TLS cert provisioning
# tool knows to store challenge response files. # tool knows to store challenge response files.
alias $STORAGE_ROOT/ssl/lets_encrypt/acme_challenges/; alias $STORAGE_ROOT/ssl/lets_encrypt/webroot/.well-known/acme-challenge/;
} }
} }
# The secure HTTPS server. # The secure HTTPS server.
server { server {
listen 443 ssl; listen 443 ssl http2;
listen [::]:443 ssl; listen [::]:443 ssl http2;
server_name $HOSTNAME; server_name $HOSTNAME;

View File

@@ -15,25 +15,22 @@ from exclusiveprocess import Lock
from utils import load_environment, shell, wait_for_service, fix_boto from utils import load_environment, shell, wait_for_service, fix_boto
rsync_ssh_options = [ rsync_ssh_options = [
"--ssh-options='-i /root/.ssh/id_rsa_miab'", "--ssh-options= -i /root/.ssh/id_rsa_miab",
"--rsync-options=-e \"/usr/bin/ssh -oStrictHostKeyChecking=no -oBatchMode=yes -p 22 -i /root/.ssh/id_rsa_miab\"", "--rsync-options= -e \"/usr/bin/ssh -oStrictHostKeyChecking=no -oBatchMode=yes -p 22 -i /root/.ssh/id_rsa_miab\"",
] ]
def backup_status(env): def backup_status(env):
# Root folder # If backups are dissbled, return no status.
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
# What is the current status of backups?
# Query duplicity to get a list of all backups.
# Use the number of volumes to estimate the size.
config = get_backup_config(env) config = get_backup_config(env)
now = datetime.datetime.now(dateutil.tz.tzlocal())
# Are backups dissbled?
if config["target"] == "off": if config["target"] == "off":
return { } return { }
# Query duplicity to get a list of all full and incremental
# backups available.
backups = { } backups = { }
now = datetime.datetime.now(dateutil.tz.tzlocal())
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
backup_cache_dir = os.path.join(backup_root, 'cache') backup_cache_dir = os.path.join(backup_root, 'cache')
def reldate(date, ref, clip): def reldate(date, ref, clip):
@@ -54,11 +51,11 @@ def backup_status(env):
date = dateutil.parser.parse(keys[1]).astimezone(dateutil.tz.tzlocal()) date = dateutil.parser.parse(keys[1]).astimezone(dateutil.tz.tzlocal())
return { return {
"date": keys[1], "date": keys[1],
"date_str": date.strftime("%x %X") + " " + now.tzname(), "date_str": date.strftime("%Y-%m-%d %X") + " " + now.tzname(),
"date_delta": reldate(date, now, "the future?"), "date_delta": reldate(date, now, "the future?"),
"full": keys[0] == "full", "full": keys[0] == "full",
"size": 0, # collection-status doesn't give us the size "size": 0, # collection-status doesn't give us the size
"volumes": keys[2], # number of archive volumes for this backup (not really helpful) "volumes": int(keys[2]), # number of archive volumes for this backup (not really helpful)
} }
code, collection_status = shell('check_output', [ code, collection_status = shell('check_output', [
@@ -80,12 +77,20 @@ def backup_status(env):
backup = parse_line(line) backup = parse_line(line)
backups[backup["date"]] = backup backups[backup["date"]] = backup
# Look at the target to get the sizes of each of the backups. There is more than one file per backup. # Look at the target directly to get the sizes of each of the backups. There is more than one file per backup.
# Starting with duplicity in Ubuntu 18.04, "signatures" files have dates in their
# filenames that are a few seconds off the backup date and so don't line up
# with the list of backups we have. Track unmatched files so we know how much other
# space is used for those.
unmatched_file_size = 0
for fn, size in list_target_files(config): for fn, size in list_target_files(config):
m = re.match(r"duplicity-(full|full-signatures|(inc|new-signatures)\.(?P<incbase>\d+T\d+Z)\.to)\.(?P<date>\d+T\d+Z)\.", fn) m = re.match(r"duplicity-(full|full-signatures|(inc|new-signatures)\.(?P<incbase>\d+T\d+Z)\.to)\.(?P<date>\d+T\d+Z)\.", fn)
if not m: continue # not a part of a current backup chain if not m: continue # not a part of a current backup chain
key = m.group("date") key = m.group("date")
if key in backups:
backups[key]["size"] += size backups[key]["size"] += size
else:
unmatched_file_size += size
# Ensure the rows are sorted reverse chronologically. # Ensure the rows are sorted reverse chronologically.
# This is relied on by should_force_full() and the next step. # This is relied on by should_force_full() and the next step.
@@ -148,6 +153,7 @@ def backup_status(env):
return { return {
"backups": backups, "backups": backups,
"unmatched_file_size": unmatched_file_size,
} }
def should_force_full(config, env): def should_force_full(config, env):
@@ -220,32 +226,6 @@ def perform_backup(full_backup):
if config["target"] == "off": if config["target"] == "off":
return return
# In an older version of this script, duplicity was called
# such that it did not encrypt the backups it created (in
# backup/duplicity), and instead openssl was called separately
# after each backup run, creating AES256 encrypted copies of
# each file created by duplicity in backup/encrypted.
#
# We detect the transition by the presence of backup/duplicity
# and handle it by 'dupliception': we move all the old *un*encrypted
# duplicity files up out of the backup/duplicity directory (as
# backup/ is excluded from duplicity runs) in order that it is
# included in the next run, and we delete backup/encrypted (which
# duplicity will output files directly to, post-transition).
old_backup_dir = os.path.join(backup_root, 'duplicity')
migrated_unencrypted_backup_dir = os.path.join(env["STORAGE_ROOT"], "migrated_unencrypted_backup")
if os.path.isdir(old_backup_dir):
# Move the old unencrypted files to a new location outside of
# the backup root so they get included in the next (new) backup.
# Then we'll delete them. Also so that they do not get in the
# way of duplicity doing a full backup on the first run after
# we take care of this.
shutil.move(old_backup_dir, migrated_unencrypted_backup_dir)
# The backup_dir (backup/encrypted) now has a new purpose.
# Clear it out.
shutil.rmtree(backup_dir)
# On the first run, always do a full backup. Incremental # On the first run, always do a full backup. Incremental
# will fail. Otherwise do a full backup when the size of # will fail. Otherwise do a full backup when the size of
# the increments since the most recent full backup are # the increments since the most recent full backup are
@@ -267,7 +247,7 @@ def perform_backup(full_backup):
if quit: if quit:
sys.exit(code) sys.exit(code)
service_command("php7.0-fpm", "stop", quit=True) service_command("php7.2-fpm", "stop", quit=True)
service_command("postfix", "stop", quit=True) service_command("postfix", "stop", quit=True)
service_command("dovecot", "stop", quit=True) service_command("dovecot", "stop", quit=True)
@@ -301,11 +281,7 @@ def perform_backup(full_backup):
# Start services again. # Start services again.
service_command("dovecot", "start", quit=False) service_command("dovecot", "start", quit=False)
service_command("postfix", "start", quit=False) service_command("postfix", "start", quit=False)
service_command("php7.0-fpm", "start", quit=False) service_command("php7.2-fpm", "start", quit=False)
# Once the migrated backup is included in a new backup, it can be deleted.
if os.path.isdir(migrated_unencrypted_backup_dir):
shutil.rmtree(migrated_unencrypted_backup_dir)
# Remove old backups. This deletes all backup data no longer needed # Remove old backups. This deletes all backup data no longer needed
# from more than 3 days ago. # from more than 3 days ago.
@@ -430,11 +406,11 @@ def list_target_files(config):
reason = "Provided path {} is invalid.".format(target_path) reason = "Provided path {} is invalid.".format(target_path)
elif 'Network is unreachable' in listing: elif 'Network is unreachable' in listing:
reason = "The IP address {} is unreachable.".format(target.hostname) reason = "The IP address {} is unreachable.".format(target.hostname)
elif 'Could not resolve hostname': elif 'Could not resolve hostname' in listing:
reason = "The hostname {} cannot be resolved.".format(target.hostname) reason = "The hostname {} cannot be resolved.".format(target.hostname)
else: else:
reason = "Unknown error." \ reason = "Unknown error." \
"Please check running 'python management/backup.py --verify'" \ "Please check running 'management/backup.py --verify'" \
"from mailinabox sources to debug the issue." "from mailinabox sources to debug the issue."
raise ValueError("Connection to rsync host failed: {}".format(reason)) raise ValueError("Connection to rsync host failed: {}".format(reason))
@@ -443,15 +419,22 @@ def list_target_files(config):
fix_boto() # must call prior to importing boto fix_boto() # must call prior to importing boto
import boto.s3 import boto.s3
from boto.exception import BotoServerError from boto.exception import BotoServerError
custom_region = False
for region in boto.s3.regions(): for region in boto.s3.regions():
if region.endpoint == target.hostname: if region.endpoint == target.hostname:
break break
else: else:
raise ValueError("Invalid S3 region/host.") # If region is not found this is a custom region
custom_region = True
bucket = target.path[1:].split('/')[0] bucket = target.path[1:].split('/')[0]
path = '/'.join(target.path[1:].split('/')[1:]) + '/' path = '/'.join(target.path[1:].split('/')[1:]) + '/'
# Create a custom region with custom endpoint
if custom_region:
from boto.s3.connection import S3Connection
region = boto.s3.S3RegionInfo(name=bucket, endpoint=target.hostname, connection_cls=S3Connection)
# If no prefix is specified, set the path to '', otherwise boto won't list the files # If no prefix is specified, set the path to '', otherwise boto won't list the files
if path == '/': if path == '/':
path = '' path = ''
@@ -556,8 +539,7 @@ if __name__ == "__main__":
run_duplicity_verification() run_duplicity_verification()
elif sys.argv[-1] == "--list": elif sys.argv[-1] == "--list":
# Run duplicity's verification command to check a) the backup files # List the saved backup files.
# are readable, and b) report if they are up to date.
for fn, size in list_target_files(get_backup_config(load_environment())): for fn, size in list_target_files(get_backup_config(load_environment())):
print("{}\t{}".format(fn, size)) print("{}\t{}".format(fn, size))
@@ -565,6 +547,7 @@ if __name__ == "__main__":
# Show backup status. # Show backup status.
ret = backup_status(load_environment()) ret = backup_status(load_environment())
print(rtyaml.dump(ret["backups"])) print(rtyaml.dump(ret["backups"]))
print("Storage for unmatched files:", ret["unmatched_file_size"])
elif len(sys.argv) >= 2 and sys.argv[1] == "--restore": elif len(sys.argv) >= 2 and sys.argv[1] == "--restore":
# Run duplicity restore. Rest of command line passed as arguments # Run duplicity restore. Rest of command line passed as arguments

View File

@@ -146,7 +146,7 @@ def me():
@authorized_personnel_only @authorized_personnel_only
def mail_users(): def mail_users():
if request.args.get("format", "") == "json": if request.args.get("format", "") == "json":
return json_response(get_mail_users_ex(env, with_archived=True, with_slow_info=True)) return json_response(get_mail_users_ex(env, with_archived=True))
else: else:
return "".join(x+"\n" for x in get_mail_users(env)) return "".join(x+"\n" for x in get_mail_users(env))
@@ -333,11 +333,16 @@ def ssl_get_status():
from web_update import get_web_domains_info, get_web_domains from web_update import get_web_domains_info, get_web_domains
# What domains can we provision certificates for? What unexpected problems do we have? # What domains can we provision certificates for? What unexpected problems do we have?
provision, cant_provision = get_certificates_to_provision(env, show_extended_problems=False) provision, cant_provision = get_certificates_to_provision(env, show_valid_certs=False)
# What's the current status of TLS certificates on all of the domain? # What's the current status of TLS certificates on all of the domain?
domains_status = get_web_domains_info(env) domains_status = get_web_domains_info(env)
domains_status = [{ "domain": d["domain"], "status": d["ssl_certificate"][0], "text": d["ssl_certificate"][1] } for d in domains_status ] domains_status = [
{
"domain": d["domain"],
"status": d["ssl_certificate"][0],
"text": d["ssl_certificate"][1] + ((" " + cant_provision[d["domain"]] if d["domain"] in cant_provision else ""))
} for d in domains_status ]
# Warn the user about domain names not hosted here because of other settings. # Warn the user about domain names not hosted here because of other settings.
for domain in set(get_web_domains(env, exclude_dns_elsewhere=False)) - set(get_web_domains(env)): for domain in set(get_web_domains(env, exclude_dns_elsewhere=False)) - set(get_web_domains(env)):
@@ -349,7 +354,6 @@ def ssl_get_status():
return json_response({ return json_response({
"can_provision": utils.sort_domains(provision, env), "can_provision": utils.sort_domains(provision, env),
"cant_provision": [{ "domain": domain, "problem": cant_provision[domain] } for domain in utils.sort_domains(cant_provision, env) ],
"status": domains_status, "status": domains_status,
}) })
@@ -376,11 +380,8 @@ def ssl_install_cert():
@authorized_personnel_only @authorized_personnel_only
def ssl_provision_certs(): def ssl_provision_certs():
from ssl_certificates import provision_certificates from ssl_certificates import provision_certificates
agree_to_tos_url = request.form.get('agree_to_tos_url') requests = provision_certificates(env, limit_domains=None)
status = provision_certificates(env, return json_response({ "requests": requests })
agree_to_tos_url=agree_to_tos_url,
jsonable=True)
return json_response(status)
# WEB # WEB
@@ -571,7 +572,7 @@ def munin_cgi(filename):
if code != 0: if code != 0:
# nonzero returncode indicates error # nonzero returncode indicates error
app.logger.error("munin_cgi: munin-cgi-graph returned nonzero exit code, %s", process.returncode) app.logger.error("munin_cgi: munin-cgi-graph returned nonzero exit code, %s", code)
return ("error processing graph image", 500) return ("error processing graph image", 500)
# /usr/lib/munin/cgi/munin-cgi-graph returns both headers and binary png when successful. # /usr/lib/munin/cgi/munin-cgi-graph returns both headers and binary png when successful.

View File

@@ -9,11 +9,17 @@ export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8 export LANG=en_US.UTF-8
export LC_TYPE=en_US.UTF-8 export LC_TYPE=en_US.UTF-8
# On Mondays, i.e. once a week, send the administrator a report of total emails
# sent and received so the admin might notice server abuse.
if [ `date "+%u"` -eq 1 ]; then
management/mail_log.py -t week | management/email_administrator.py "Mail-in-a-Box Usage Report"
fi
# Take a backup. # Take a backup.
management/backup.py | management/email_administrator.py "Backup Status" management/backup.py | management/email_administrator.py "Backup Status"
# Provision any new certificates for new domains or domains with expiring certificates. # Provision any new certificates for new domains or domains with expiring certificates.
management/ssl_certificates.py -q --headless | management/email_administrator.py "Error Provisioning TLS Certificate" management/ssl_certificates.py -q | management/email_administrator.py "TLS Certificate Provisioning Result"
# Run status checks and email the administrator if anything changed. # Run status checks and email the administrator if anything changed.
management/status_checks.py --show-changes | management/email_administrator.py "Status Checks Change Notice" management/status_checks.py --show-changes | management/email_administrator.py "Status Checks Change Notice"

View File

@@ -288,6 +288,21 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
if not has_rec(qname, "SRV"): if not has_rec(qname, "SRV"):
records.append((qname, "SRV", "0 0 443 " + env["PRIMARY_HOSTNAME"] + ".", "Recommended. Specifies the hostname of the server that handles CardDAV/CalDAV services for email addresses on this domain.")) records.append((qname, "SRV", "0 0 443 " + env["PRIMARY_HOSTNAME"] + ".", "Recommended. Specifies the hostname of the server that handles CardDAV/CalDAV services for email addresses on this domain."))
# Adds autoconfiguration A records for all domains.
# This allows the following clients to automatically configure email addresses in the respective applications.
# autodiscover.* - Z-Push ActiveSync Autodiscover
# autoconfig.* - Thunderbird Autoconfig
autodiscover_records = [
("autodiscover", "A", env["PUBLIC_IP"], "Provides email configuration autodiscovery support for Z-Push ActiveSync Autodiscover."),
("autodiscover", "AAAA", env["PUBLIC_IPV6"], "Provides email configuration autodiscovery support for Z-Push ActiveSync Autodiscover."),
("autoconfig", "A", env["PUBLIC_IP"], "Provides email configuration autodiscovery support for Thunderbird Autoconfig."),
("autoconfig", "AAAA", env["PUBLIC_IPV6"], "Provides email configuration autodiscovery support for Thunderbird Autoconfig.")
]
for qname, rtype, value, explanation in autodiscover_records:
if value is None or value.strip() == "": continue # skip IPV6 if not set
if not has_rec(qname, rtype):
records.append((qname, rtype, value, explanation))
# Sort the records. The None records *must* go first in the nsd zone file. Otherwise it doesn't matter. # Sort the records. The None records *must* go first in the nsd zone file. Otherwise it doesn't matter.
records.sort(key = lambda rec : list(reversed(rec[0].split(".")) if rec[0] is not None else "")) records.sort(key = lambda rec : list(reversed(rec[0].split(".")) if rec[0] is not None else ""))
@@ -354,19 +369,20 @@ def build_sshfp_records():
# Get our local fingerprints by running ssh-keyscan. The output looks # Get our local fingerprints by running ssh-keyscan. The output looks
# like the known_hosts file: hostname, keytype, fingerprint. The order # like the known_hosts file: hostname, keytype, fingerprint. The order
# of the output is arbitrary, so sort it to prevent spurrious updates # of the output is arbitrary, so sort it to prevent spurrious updates
# to the zone file (that trigger bumping the serial number). # to the zone file (that trigger bumping the serial number). However,
# if SSH has been configured to listen on a nonstandard port, we must
# scan the sshd_config and find the ssh ports (port 22 may be closed) # specify that port to sshkeyscan.
port = 22
with open('/etc/ssh/sshd_config', 'r') as f: with open('/etc/ssh/sshd_config', 'r') as f:
ports = [] for line in f:
t = f.readlines() s = line.rstrip().split()
for line in t:
s = line.split()
if len(s) == 2 and s[0] == 'Port': if len(s) == 2 and s[0] == 'Port':
ports = ports + [s[1]] try:
# the keys are the same at each port, so we only need to get port = int(s[1])
# them at the first port found (may not be port 22) except ValueError:
keys = shell("check_output", ["ssh-keyscan", "-t", "rsa,dsa,ecdsa,ed25519", "-p", ports[0], "localhost"]) pass
break
keys = shell("check_output", ["ssh-keyscan", "-t", "rsa,dsa,ecdsa,ed25519", "-p", str(port), "localhost"])
for key in sorted(keys.split("\n")): for key in sorted(keys.split("\n")):
if key.strip() == "" or key[0] == "#": continue if key.strip() == "" or key[0] == "#": continue
try: try:
@@ -507,9 +523,11 @@ zone:
""" % (domain, zonefile) """ % (domain, zonefile)
# If custom secondary nameservers have been set, allow zone transfers # If custom secondary nameservers have been set, allow zone transfers
# and notifies to them. # and, if not a subnet, notifies to them.
for ipaddr in get_secondary_dns(additional_records, mode="xfr"): for ipaddr in get_secondary_dns(additional_records, mode="xfr"):
nsdconf += "\n\tnotify: %s NOKEY\n\tprovide-xfr: %s NOKEY\n" % (ipaddr, ipaddr) if "/" not in ipaddr:
nsdconf += "\n\tnotify: %s NOKEY" % (ipaddr)
nsdconf += "\n\tprovide-xfr: %s NOKEY\n" % (ipaddr)
# Check if the file is changing. If it isn't changing, # Check if the file is changing. If it isn't changing,
# return False to flag that no change was made. # return False to flag that no change was made.
@@ -857,10 +875,15 @@ def get_secondary_dns(custom_dns, mode=None):
# This is a hostname. Before including in zone xfr lines, # This is a hostname. Before including in zone xfr lines,
# resolve to an IP address. Otherwise just return the hostname. # resolve to an IP address. Otherwise just return the hostname.
# It may not resolve to IPv6, so don't throw an exception if it
# doesn't.
if not hostname.startswith("xfr:"): if not hostname.startswith("xfr:"):
if mode == "xfr": if mode == "xfr":
response = dns.resolver.query(hostname+'.', "A") response = dns.resolver.query(hostname+'.', "A", raise_on_no_answer=False)
hostname = str(response[0]) values.extend(map(str, response))
response = dns.resolver.query(hostname+'.', "AAAA", raise_on_no_answer=False)
values.extend(map(str, response))
continue
values.append(hostname) values.append(hostname)
# This is a zone-xfer-only IP address. Do not return if # This is a zone-xfer-only IP address. Do not return if
@@ -887,10 +910,14 @@ def set_secondary_dns(hostnames, env):
else: else:
# Validate IP address. # Validate IP address.
try: try:
if "/" in item[4:]:
v = ipaddress.ip_network(item[4:]) # raises a ValueError if there's a problem
if not isinstance(v, ipaddress.IPv4Network): raise ValueError("That's an IPv6 subnet.")
else:
v = ipaddress.ip_address(item[4:]) # raises a ValueError if there's a problem v = ipaddress.ip_address(item[4:]) # raises a ValueError if there's a problem
if not isinstance(v, ipaddress.IPv4Address): raise ValueError("That's an IPv6 address.") if not isinstance(v, ipaddress.IPv4Address): raise ValueError("That's an IPv6 address.")
except ValueError: except ValueError:
raise ValueError("'%s' is not an IPv4 address." % item[4:]) raise ValueError("'%s' is not an IPv4 address or subnet." % item[4:])
# Set. # Set.
set_custom_dns_record("_secondary_nameserver", "A", " ".join(hostnames), "set", env) set_custom_dns_record("_secondary_nameserver", "A", " ".join(hostnames), "set", env)

View File

@@ -4,8 +4,14 @@
import sys import sys
import html
import smtplib import smtplib
from email.message import Message
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# In Python 3.6:
#from email.message import Message
from utils import load_environment from utils import load_environment
@@ -26,11 +32,23 @@ if content == "":
sys.exit(0) sys.exit(0)
# create MIME message # create MIME message
msg = Message() msg = MIMEMultipart('alternative')
# In Python 3.6:
#msg = Message()
msg['From'] = "\"%s\" <%s>" % (env['PRIMARY_HOSTNAME'], admin_addr) msg['From'] = "\"%s\" <%s>" % (env['PRIMARY_HOSTNAME'], admin_addr)
msg['To'] = admin_addr msg['To'] = admin_addr
msg['Subject'] = "[%s] %s" % (env['PRIMARY_HOSTNAME'], subject) msg['Subject'] = "[%s] %s" % (env['PRIMARY_HOSTNAME'], subject)
msg.set_payload(content, "UTF-8")
content_html = "<html><body><pre>{}</pre></body></html>".format(html.escape(content))
msg.attach(MIMEText(content, 'plain'))
msg.attach(MIMEText(content_html, 'html'))
# In Python 3.6:
#msg.set_content(content)
#msg.add_alternative(content_html, "html")
# send # send
smtpclient = smtplib.SMTP('127.0.0.1', 25) smtpclient = smtplib.SMTP('127.0.0.1', 25)

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([
@@ -53,10 +53,10 @@ VERBOSE = False
# List of strings to filter users with # List of strings to filter users with
FILTERS = None FILTERS = None
# What to show by default # What to show (with defaults)
SCAN_OUT = True # Outgoing email SCAN_OUT = True # Outgoing email
SCAN_IN = True # Incoming email SCAN_IN = True # Incoming email
SCAN_CONN = False # IMAP and POP3 logins SCAN_DOVECOT_LOGIN = True # Dovecot Logins
SCAN_GREY = False # Greylisted email SCAN_GREY = False # Greylisted email
SCAN_BLOCKED = False # Rejected email SCAN_BLOCKED = False # Rejected email
@@ -76,10 +76,11 @@ def scan_files(collector):
tmp_file = tempfile.NamedTemporaryFile() tmp_file = tempfile.NamedTemporaryFile()
shutil.copyfileobj(gzip.open(fn), tmp_file) shutil.copyfileobj(gzip.open(fn), tmp_file)
if VERBOSE:
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
@@ -105,7 +106,7 @@ def scan_mail_log(env):
"scan_time": time.time(), # The time in seconds the scan took "scan_time": time.time(), # The time in seconds the scan took
"sent_mail": OrderedDict(), # Data about email sent by users "sent_mail": OrderedDict(), # Data about email sent by users
"received_mail": OrderedDict(), # Data about email received by users "received_mail": OrderedDict(), # Data about email received by users
"dovecot": OrderedDict(), # Data about Dovecot activity "logins": OrderedDict(), # Data about login activity
"postgrey": {}, # Data about greylisting of email addresses "postgrey": {}, # Data about greylisting of email addresses
"rejected": OrderedDict(), # Emails that were blocked "rejected": OrderedDict(), # Emails that were blocked
"known_addresses": None, # Addresses handled by the Miab installation "known_addresses": None, # Addresses handled by the Miab installation
@@ -119,8 +120,8 @@ def scan_mail_log(env):
except ImportError: except ImportError:
pass pass
print("Scanning from {:%Y-%m-%d %H:%M:%S} back to {:%Y-%m-%d %H:%M:%S}".format( print("Scanning logs from {:%Y-%m-%d %H:%M:%S} to {:%Y-%m-%d %H:%M:%S}".format(
START_DATE, END_DATE) END_DATE, START_DATE)
) )
# Scan the lines in the log files until the date goes out of range # Scan the lines in the log files until the date goes out of range
@@ -138,8 +139,8 @@ def scan_mail_log(env):
# Print Sent Mail report # Print Sent Mail report
if collector["sent_mail"]: if collector["sent_mail"]:
msg = "Sent email between {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}" msg = "Sent email"
print_header(msg.format(END_DATE, START_DATE)) print_header(msg)
data = OrderedDict(sorted(collector["sent_mail"].items(), key=email_sort)) data = OrderedDict(sorted(collector["sent_mail"].items(), key=email_sort))
@@ -173,8 +174,8 @@ def scan_mail_log(env):
# Print Received Mail report # Print Received Mail report
if collector["received_mail"]: if collector["received_mail"]:
msg = "Received email between {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}" msg = "Received email"
print_header(msg.format(END_DATE, START_DATE)) print_header(msg)
data = OrderedDict(sorted(collector["received_mail"].items(), key=email_sort)) data = OrderedDict(sorted(collector["received_mail"].items(), key=email_sort))
@@ -199,43 +200,55 @@ def scan_mail_log(env):
[accum] [accum]
) )
# Print Dovecot report # Print login report
if collector["dovecot"]: if collector["logins"]:
msg = "Email client logins between {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}" msg = "User logins per hour"
print_header(msg.format(END_DATE, START_DATE)) print_header(msg)
data = OrderedDict(sorted(collector["dovecot"].items(), key=email_sort)) data = OrderedDict(sorted(collector["logins"].items(), key=email_sort))
# Get a list of all of the protocols seen in the logs in reverse count order.
all_protocols = defaultdict(int)
for u in data.values():
for protocol_name, count in u["totals_by_protocol"].items():
all_protocols[protocol_name] += count
all_protocols = [k for k, v in sorted(all_protocols.items(), key=lambda kv : -kv[1])]
print_user_table( print_user_table(
data.keys(), data.keys(),
data=[ data=[
("imap", [u["imap"] for u in data.values()]), (protocol_name, [
("pop3", [u["pop3"] for u in data.values()]), round(u["totals_by_protocol"][protocol_name] / (u["latest"]-u["earliest"]).total_seconds() * 60*60, 1)
if (u["latest"]-u["earliest"]).total_seconds() > 0
else 0 # prevent division by zero
for u in data.values()])
for protocol_name in all_protocols
], ],
sub_data=[ sub_data=[
("IMAP IP addresses", [[k + " (%d)" % v for k, v in u["imap-logins"].items()] ("Protocol and Source", [[
for u in data.values()]), "{} {}: {} times".format(protocol_name, host, count)
("POP3 IP addresses", [[k + " (%d)" % v for k, v in u["pop3-logins"].items()] for (protocol_name, host), count
for u in data.values()]), in sorted(u["totals_by_protocol_and_host"].items(), key=lambda kv:-kv[1])
] for u in data.values()])
], ],
activity=[ activity=[
("imap", [u["activity-by-hour"]["imap"] for u in data.values()]), (protocol_name, [u["activity-by-hour"][protocol_name] for u in data.values()])
("pop3", [u["activity-by-hour"]["pop3"] for u in data.values()]), for protocol_name in all_protocols
], ],
earliest=[u["earliest"] for u in data.values()], earliest=[u["earliest"] for u in data.values()],
latest=[u["latest"] for u in data.values()], latest=[u["latest"] for u in data.values()],
numstr=lambda n : str(round(n, 1)),
) )
accum = {"imap": defaultdict(int), "pop3": defaultdict(int), "both": defaultdict(int)} accum = { protocol_name: defaultdict(int) for protocol_name in all_protocols }
for h in range(24): for h in range(24):
accum["imap"][h] = sum(d["activity-by-hour"]["imap"][h] for d in data.values()) for protocol_name in all_protocols:
accum["pop3"][h] = sum(d["activity-by-hour"]["pop3"][h] for d in data.values()) accum[protocol_name][h] = sum(d["activity-by-hour"][protocol_name][h] for d in data.values())
accum["both"][h] = accum["imap"][h] + accum["pop3"][h]
print_time_table( print_time_table(
["imap", "pop3", " +"], all_protocols,
[accum["imap"], accum["pop3"], accum["both"]] [accum[protocol_name] for protocol_name in all_protocols]
) )
if collector["postgrey"]: if collector["postgrey"]:
@@ -244,7 +257,7 @@ def scan_mail_log(env):
print(textwrap.fill( print(textwrap.fill(
"The following mail was greylisted, meaning the emails were temporarily rejected. " "The following mail was greylisted, meaning the emails were temporarily rejected. "
"Legitimate senders will try again within ten minutes.", "Legitimate senders must try again after three minutes.",
width=80, initial_indent=" ", subsequent_indent=" " width=80, initial_indent=" ", subsequent_indent=" "
), end='\n\n') ), end='\n\n')
@@ -331,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:
@@ -348,9 +367,9 @@ def scan_mail_log_line(line, collector):
elif service == "postfix/lmtp": elif service == "postfix/lmtp":
if SCAN_IN: if SCAN_IN:
scan_postfix_lmtp_line(date, log, collector) scan_postfix_lmtp_line(date, log, collector)
elif service in ("imap-login", "pop3-login"): elif service.endswith("-login"):
if SCAN_CONN: if SCAN_DOVECOT_LOGIN:
scan_dovecot_line(date, log, collector, service[:4]) scan_dovecot_login_line(date, log, collector, service[:4])
elif service == "postgrey": elif service == "postgrey":
if SCAN_GREY: if SCAN_GREY:
scan_postgrey_line(date, log, collector) scan_postgrey_line(date, log, collector)
@@ -440,52 +459,51 @@ 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
def scan_dovecot_line(date, log, collector, prot): def scan_dovecot_login_line(date, log, collector, protocol_name):
""" Scan a dovecot log line and extract interesting data """ """ Scan a dovecot login log line and extract interesting data """
m = re.match("Info: Login: user=<(.*?)>, method=PLAIN, rip=(.*?),", log) m = re.match("Info: Login: user=<(.*?)>, method=PLAIN, rip=(.*?),", log)
if m: if m:
# TODO: CHECK DIT # TODO: CHECK DIT
user, rip = m.groups() user, host = m.groups()
if user_match(user): if user_match(user):
add_login(user, date, protocol_name, host, collector)
def add_login(user, date, protocol_name, host, collector):
# Get the user data, or create it if the user is new # Get the user data, or create it if the user is new
data = collector["dovecot"].get( data = collector["logins"].get(
user, user,
{ {
"imap": 0,
"pop3": 0,
"earliest": None, "earliest": None,
"latest": None, "latest": None,
"imap-logins": defaultdict(int), "totals_by_protocol": defaultdict(int),
"pop3-logins": defaultdict(int), "totals_by_protocol_and_host": defaultdict(int),
"activity-by-hour": { "activity-by-hour": defaultdict(lambda : defaultdict(int)),
"imap": defaultdict(int),
"pop3": defaultdict(int),
},
} }
) )
data[prot] += 1 if data["earliest"] is None:
data["activity-by-hour"][prot][date.hour] += 1
if data["latest"] is None:
data["latest"] = date
data["earliest"] = date data["earliest"] = date
data["latest"] = date
if rip not in ("127.0.0.1", "::1") or True: data["totals_by_protocol"][protocol_name] += 1
data["%s-logins" % prot][rip] += 1 data["totals_by_protocol_and_host"][(protocol_name, host)] += 1
collector["dovecot"][user] = data if host not in ("127.0.0.1", "::1") or True:
data["activity-by-hour"][protocol_name][date.hour] += 1
collector["logins"][user] = data
def scan_postfix_lmtp_line(date, log, collector): def scan_postfix_lmtp_line(date, log, collector):
@@ -516,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
@@ -555,51 +573,26 @@ 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
# Also log this as a login.
add_login(user, date, "smtp", client, 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):
@@ -640,7 +633,7 @@ def print_time_table(labels, data, do_print=True):
for i, d in enumerate(data): for i, d in enumerate(data):
lines[i] += base.format(d[h]) lines[i] += base.format(d[h])
lines.insert(0, "") lines.insert(0, " totals by time of day:")
lines.append("" + (len(lines[-1]) - 2) * "") lines.append("" + (len(lines[-1]) - 2) * "")
if do_print: if do_print:
@@ -650,7 +643,7 @@ def print_time_table(labels, data, do_print=True):
def print_user_table(users, data=None, sub_data=None, activity=None, latest=None, earliest=None, def print_user_table(users, data=None, sub_data=None, activity=None, latest=None, earliest=None,
delimit=False): delimit=False, numstr=str):
str_temp = "{:<32} " str_temp = "{:<32} "
lines = [] lines = []
data = data or [] data = data or []
@@ -764,7 +757,7 @@ def print_user_table(users, data=None, sub_data=None, activity=None, latest=None
# Print totals # Print totals
data_accum = [str(a) for a in data_accum] data_accum = [numstr(a) for a in data_accum]
footer = str_temp.format("Totals:" if do_accum else " ") footer = str_temp.format("Totals:" if do_accum else " ")
for row, (l, _) in enumerate(data): for row, (l, _) in enumerate(data):
temp = "{:>%d}" % max(5, len(l) + 1) temp = "{:>%d}" % max(5, len(l) + 1)
@@ -818,7 +811,7 @@ if __name__ == "__main__":
action="store_true") action="store_true")
parser.add_argument("-s", "--sent", help="Scan for sent emails.", parser.add_argument("-s", "--sent", help="Scan for sent emails.",
action="store_true") action="store_true")
parser.add_argument("-l", "--logins", help="Scan for IMAP/POP logins.", parser.add_argument("-l", "--logins", help="Scan for user logins to IMAP/POP3.",
action="store_true") action="store_true")
parser.add_argument("-g", "--grey", help="Scan for greylisted emails.", parser.add_argument("-g", "--grey", help="Scan for greylisted emails.",
action="store_true") action="store_true")
@@ -863,8 +856,8 @@ if __name__ == "__main__":
if not SCAN_OUT: if not SCAN_OUT:
print("Ignoring sent emails") print("Ignoring sent emails")
SCAN_CONN = args.logins SCAN_DOVECOT_LOGIN = args.logins
if not SCAN_CONN: if not SCAN_DOVECOT_LOGIN:
print("Ignoring logins") print("Ignoring logins")
SCAN_GREY = args.grey SCAN_GREY = args.grey

View File

@@ -1,5 +1,14 @@
#!/usr/local/lib/mailinabox/env/bin/python #!/usr/local/lib/mailinabox/env/bin/python
# NOTE:
# This script is run both using the system-wide Python 3
# interpreter (/usr/bin/python3) as well as through the
# virtualenv (/usr/local/lib/mailinabox/env). So only
# import packages at the top level of this script that
# are installed in *both* contexts. We use the system-wide
# Python 3 in setup/questions.sh to validate the email
# address entered by the user.
import subprocess, shutil, os, sqlite3, re import subprocess, shutil, os, sqlite3, re
import utils import utils
from email_validator import validate_email as validate_email_, EmailNotValidError from email_validator import validate_email as validate_email_, EmailNotValidError
@@ -96,7 +105,7 @@ def get_mail_users(env):
users = [ row[0] for row in c.fetchall() ] users = [ row[0] for row in c.fetchall() ]
return utils.sort_email_addresses(users, env) return utils.sort_email_addresses(users, env)
def get_mail_users_ex(env, with_archived=False, with_slow_info=False): def get_mail_users_ex(env, with_archived=False):
# Returns a complex data structure of all user accounts, optionally # Returns a complex data structure of all user accounts, optionally
# including archived (status="inactive") accounts. # including archived (status="inactive") accounts.
# #
@@ -130,9 +139,6 @@ def get_mail_users_ex(env, with_archived=False, with_slow_info=False):
} }
users.append(user) users.append(user)
if with_slow_info:
user["mailbox_size"] = utils.du(os.path.join(env['STORAGE_ROOT'], 'mail/mailboxes', *reversed(email.split("@"))))
# Add in archived accounts. # Add in archived accounts.
if with_archived: if with_archived:
root = os.path.join(env['STORAGE_ROOT'], 'mail/mailboxes') root = os.path.join(env['STORAGE_ROOT'], 'mail/mailboxes')
@@ -144,13 +150,11 @@ def get_mail_users_ex(env, with_archived=False, with_slow_info=False):
if email in active_accounts: continue if email in active_accounts: continue
user = { user = {
"email": email, "email": email,
"privileges": "", "privileges": [],
"status": "inactive", "status": "inactive",
"mailbox": mbox, "mailbox": mbox,
} }
users.append(user) users.append(user)
if with_slow_info:
user["mailbox_size"] = utils.du(mbox)
# Group by domain. # Group by domain.
domains = { } domains = { }

2
management/munin_start.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
mkdir -p /var/run/munin && chown munin /var/run/munin

View File

@@ -1,7 +1,7 @@
#!/usr/local/lib/mailinabox/env/bin/python #!/usr/local/lib/mailinabox/env/bin/python
# Utilities for installing and selecting SSL certificates. # Utilities for installing and selecting SSL certificates.
import os, os.path, re, shutil import os, os.path, re, shutil, subprocess, tempfile
from utils import shell, safe_domain_name, sort_domains from utils import shell, safe_domain_name, sort_domains
import idna import idna
@@ -24,6 +24,16 @@ def get_ssl_certificates(env):
if not os.path.exists(ssl_root): if not os.path.exists(ssl_root):
return return
for fn in os.listdir(ssl_root): for fn in os.listdir(ssl_root):
if fn == 'ssl_certificate.pem':
# This is always a symbolic link
# to the certificate to use for
# PRIMARY_HOSTNAME. Don't let it
# be eligible for use because we
# could end up creating a symlink
# to itself --- we want to find
# the cert that it should be a
# symlink to.
continue
fn = os.path.join(ssl_root, fn) fn = os.path.join(ssl_root, fn)
if os.path.isfile(fn): if os.path.isfile(fn):
yield fn yield fn
@@ -74,6 +84,12 @@ def get_ssl_certificates(env):
# Add this cert to the list of certs usable for the domains. # Add this cert to the list of certs usable for the domains.
for domain in cert_domains: for domain in cert_domains:
# The primary hostname can only use a certificate mapped
# to the system private key.
if domain == env['PRIMARY_HOSTNAME']:
if cert._private_key._filename != os.path.join(env['STORAGE_ROOT'], 'ssl', 'ssl_private_key.pem'):
continue
domains.setdefault(domain, []).append(cert) domains.setdefault(domain, []).append(cert)
# Sort the certificates to prefer good ones. # Sort the certificates to prefer good ones.
@@ -81,6 +97,7 @@ def get_ssl_certificates(env):
now = datetime.datetime.utcnow() now = datetime.datetime.utcnow()
ret = { } ret = { }
for domain, cert_list in domains.items(): for domain, cert_list in domains.items():
#for c in cert_list: print(domain, c.not_valid_before, c.not_valid_after, "("+str(now)+")", c.issuer, c.subject, c._filename)
cert_list.sort(key = lambda cert : ( cert_list.sort(key = lambda cert : (
# must be valid NOW # must be valid NOW
cert.not_valid_before <= now <= cert.not_valid_after, cert.not_valid_before <= now <= cert.not_valid_after,
@@ -124,7 +141,8 @@ def get_ssl_certificates(env):
return ret return ret
def get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=False, raw=False): def get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=False, use_main_cert=True):
if use_main_cert or not allow_missing_cert:
# Get the system certificate info. # Get the system certificate info.
ssl_private_key = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_private_key.pem')) ssl_private_key = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_private_key.pem'))
ssl_certificate = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem')) ssl_certificate = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem'))
@@ -135,6 +153,7 @@ def get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=False
"certificate_object": load_pem(load_cert_chain(ssl_certificate)[0]), "certificate_object": load_pem(load_cert_chain(ssl_certificate)[0]),
} }
if use_main_cert:
if domain == env['PRIMARY_HOSTNAME']: if domain == env['PRIMARY_HOSTNAME']:
# The primary domain must use the server certificate because # The primary domain must use the server certificate because
# it is hard-coded in some service configuration files. # it is hard-coded in some service configuration files.
@@ -155,136 +174,97 @@ def get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=False
# PROVISIONING CERTIFICATES FROM LETSENCRYPT # PROVISIONING CERTIFICATES FROM LETSENCRYPT
def get_certificates_to_provision(env, show_extended_problems=True, force_domains=None): def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True):
# Get a set of domain names that we should now provision certificates # Get a set of domain names that we can provision certificates for
# for. Provision if a domain name has no valid certificate or if any # using certbot. We start with domains that the box is serving web
# certificate is expiring in 14 days. If provisioning anything, also # for and subtract:
# provision certificates expiring within 30 days. The period between # * domains not in limit_domains if limit_domains is not empty
# 14 and 30 days allows us to consolidate domains into multi-domain # * domains with custom "A" records, i.e. they are hosted elsewhere
# certificates for domains expiring around the same time. # * domains with actual "A" records that point elsewhere
# * domains that already have certificates that will be valid for a while
from web_update import get_web_domains from web_update import get_web_domains
from status_checks import query_dns, normalize_ip
import datetime existing_certs = get_ssl_certificates(env)
now = datetime.datetime.utcnow()
# Get domains with missing & expiring certificates. plausible_web_domains = get_web_domains(env, exclude_dns_elsewhere=False)
certs = get_ssl_certificates(env) actual_web_domains = get_web_domains(env)
domains = set()
domains_if_any = set() domains_to_provision = set()
problems = { } domains_cant_provision = { }
for domain in get_web_domains(env):
# If the user really wants a cert for certain domains, include it. for domain in plausible_web_domains:
if force_domains: # Skip domains that the user doesn't want to provision now.
if force_domains == "ALL" or (isinstance(force_domains, list) and domain in force_domains): if limit_domains and domain not in limit_domains:
domains.add(domain)
continue continue
# Include this domain if its certificate is missing, self-signed, or expiring soon. # Check that there isn't an explicit A/AAAA record.
try: if domain not in actual_web_domains:
cert = get_domain_ssl_files(domain, certs, env, allow_missing_cert=True) domains_cant_provision[domain] = "The domain has a custom DNS A/AAAA record that points the domain elsewhere, so there is no point to installing a TLS certificate here and we could not automatically provision one anyway because provisioning requires access to the website (which isn't here)."
except FileNotFoundError as e:
# system certificate is not present # Check that the DNS resolves to here.
problems[domain] = "Error: " + str(e)
continue
if cert is None:
# No valid certificate available.
domains.add(domain)
else: else:
cert = cert["certificate_object"]
if cert.issuer == cert.subject:
# This is self-signed. Get a real one.
domains.add(domain)
# Valid certificate today, but is it expiring soon?
elif cert.not_valid_after-now < datetime.timedelta(days=14):
domains.add(domain)
elif cert.not_valid_after-now < datetime.timedelta(days=30):
domains_if_any.add(domain)
# It's valid. Should we report its validness?
elif show_extended_problems:
problems[domain] = "The certificate is valid for at least another 30 days --- no need to replace."
# Warn the user about domains hosted elsewhere.
if not force_domains and show_extended_problems:
for domain in set(get_web_domains(env, exclude_dns_elsewhere=False)) - set(get_web_domains(env)):
problems[domain] = "The domain's DNS is pointed elsewhere, so there is no point to installing a TLS certificate here and we could not automatically provision one anyway because provisioning requires access to the website (which isn't here)."
# Filter out domains that we can't provision a certificate for.
def can_provision_for_domain(domain):
from status_checks import normalize_ip
# Does the domain resolve to this machine in public DNS? If not, # Does the domain resolve to this machine in public DNS? If not,
# we can't do domain control validation. For IPv6 is configured, # we can't do domain control validation. For IPv6 is configured,
# make sure both IPv4 and IPv6 are correct because we don't know # make sure both IPv4 and IPv6 are correct because we don't know
# how Let's Encrypt will connect. # how Let's Encrypt will connect.
import dns.resolver bad_dns = []
for rtype, value in [("A", env["PUBLIC_IP"]), ("AAAA", env.get("PUBLIC_IPV6"))]: for rtype, value in [("A", env["PUBLIC_IP"]), ("AAAA", env.get("PUBLIC_IPV6"))]:
if not value: continue # IPv6 is not configured if not value: continue # IPv6 is not configured
try: response = query_dns(domain, rtype)
# Must make the qname absolute to prevent a fall-back lookup with a if response != normalize_ip(value):
# search domain appended, by adding a period to the end. bad_dns.append("%s (%s)" % (response, rtype))
response = dns.resolver.query(domain + ".", rtype)
except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as e:
problems[domain] = "DNS isn't configured properly for this domain: DNS resolution failed (%s: %s)." % (rtype, str(e) or repr(e)) # NoAnswer's str is empty
return False
except Exception as e:
problems[domain] = "DNS isn't configured properly for this domain: DNS lookup had an error: %s." % str(e)
return False
# Unfortunately, the response.__str__ returns bytes if bad_dns:
# instead of string, if it resulted from an AAAA-query. domains_cant_provision[domain] = "The domain name does not resolve to this machine: " \
# We need to convert manually, until this is fixed: + (", ".join(bad_dns)) \
# https://github.com/rthalley/dnspython/issues/204 + "."
#
# BEGIN HOTFIX
def rdata__str__(r):
s = r.to_text()
if isinstance(s, bytes):
s = s.decode('utf-8')
return s
# END HOTFIX
if len(response) != 1 or normalize_ip(rdata__str__(response[0])) != normalize_ip(value): else:
problems[domain] = "Domain control validation cannot be performed for this domain because DNS points the domain to another machine (%s %s)." % (rtype, ", ".join(rdata__str__(r) for r in response)) # DNS is all good.
return False
return True # Check for a good existing cert.
existing_cert = get_domain_ssl_files(domain, existing_certs, env, use_main_cert=False, allow_missing_cert=True)
if existing_cert:
existing_cert_check = check_certificate(domain, existing_cert['certificate'], existing_cert['private-key'],
warn_if_expiring_soon=14)
if existing_cert_check[0] == "OK":
if show_valid_certs:
domains_cant_provision[domain] = "The domain has a valid certificate already. ({} Certificate: {}, private key {})".format(
existing_cert_check[1],
existing_cert['certificate'],
existing_cert['private-key'])
continue
domains = set(filter(can_provision_for_domain, domains)) domains_to_provision.add(domain)
# If there are any domains we definitely will provision for, add in return (domains_to_provision, domains_cant_provision)
# additional domains to do at this time.
if len(domains) > 0:
domains |= set(filter(can_provision_for_domain, domains_if_any))
return (domains, problems)
def provision_certificates(env, agree_to_tos_url=None, logger=None, show_extended_problems=True, force_domains=None, jsonable=False):
import requests.exceptions
import acme.messages
from free_tls_certificates import client
def provision_certificates(env, limit_domains):
# What domains should we provision certificates for? And what # What domains should we provision certificates for? And what
# errors prevent provisioning for other domains. # errors prevent provisioning for other domains.
domains, problems = get_certificates_to_provision(env, force_domains=force_domains, show_extended_problems=show_extended_problems) domains, domains_cant_provision = get_certificates_to_provision(env, limit_domains=limit_domains)
# Build a list of what happened on each domain or domain-set.
ret = []
for domain, error in domains_cant_provision.items():
ret.append({
"domains": [domain],
"log": [error],
"result": "skipped",
})
# Exit fast if there is nothing to do.
if len(domains) == 0:
return {
"requests": [],
"problems": problems,
}
# Break into groups of up to 100 certificates at a time, which is Let's Encrypt's # Break into groups of up to 100 certificates at a time, which is Let's Encrypt's
# limit for a single certificate. We'll sort to put related domains together. # limit for a single certificate. We'll sort to put related domains together.
max_domains_per_group = 100
domains = sort_domains(domains, env) domains = sort_domains(domains, env)
certs = [] certs = []
while len(domains) > 0: while len(domains) > 0:
certs.append( domains[0:100] ) certs.append( domains[:max_domains_per_group] )
domains = domains[100:] domains = domains[max_domains_per_group:]
# Prepare to provision. # Prepare to provision.
@@ -293,115 +273,82 @@ def provision_certificates(env, agree_to_tos_url=None, logger=None, show_extende
if not os.path.exists(account_path): if not os.path.exists(account_path):
os.mkdir(account_path) os.mkdir(account_path)
# Where should we put ACME challenge files. This is mapped to /.well-known/acme_challenge
# by the nginx configuration.
challenges_path = os.path.join(account_path, 'acme_challenges')
if not os.path.exists(challenges_path):
os.mkdir(challenges_path)
# Read in the private key that we use for all TLS certificates. We'll need that
# to generate a CSR (done by free_tls_certificates).
with open(os.path.join(env['STORAGE_ROOT'], 'ssl/ssl_private_key.pem'), 'rb') as f:
private_key = f.read()
# Provision certificates. # Provision certificates.
ret = []
for domain_list in certs: for domain_list in certs:
# For return. ret.append({
ret_item = {
"domains": domain_list, "domains": domain_list,
"log": [], "log": [],
} })
ret.append(ret_item)
# Logging for free_tls_certificates.
def my_logger(message):
if logger: logger(message)
ret_item["log"].append(message)
# Attempt to provision a certificate.
try: try:
try: # Create a CSR file for our master private key so that certbot
cert = client.issue_certificate( # uses our private key.
domain_list, key_file = os.path.join(env['STORAGE_ROOT'], 'ssl', 'ssl_private_key.pem')
account_path, with tempfile.NamedTemporaryFile() as csr_file:
agree_to_tos_url=agree_to_tos_url, # We could use openssl, but certbot requires
private_key=private_key, # that the CN domain and SAN domains match
logger=my_logger) # the domain list passed to certbot, and adding
# SAN domains openssl req is ridiculously complicated.
# subprocess.check_output([
# "openssl", "req", "-new",
# "-key", key_file,
# "-out", csr_file.name,
# "-subj", "/CN=" + domain_list[0],
# "-sha256" ])
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.hazmat.primitives import hashes
from cryptography.x509.oid import NameOID
builder = x509.CertificateSigningRequestBuilder()
builder = builder.subject_name(x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, domain_list[0]) ]))
builder = builder.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
builder = builder.add_extension(x509.SubjectAlternativeName(
[x509.DNSName(d) for d in domain_list]
), critical=False)
request = builder.sign(load_pem(load_cert_chain(key_file)[0]), hashes.SHA256(), default_backend())
with open(csr_file.name, "wb") as f:
f.write(request.public_bytes(Encoding.PEM))
except client.NeedToTakeAction as e: # Provision, writing to a temporary file.
# Write out the ACME challenge files. webroot = os.path.join(account_path, 'webroot')
for action in e.actions: os.makedirs(webroot, exist_ok=True)
if isinstance(action, client.NeedToInstallFile): with tempfile.TemporaryDirectory() as d:
fn = os.path.join(challenges_path, action.file_name) cert_file = os.path.join(d, 'cert_and_chain.pem')
with open(fn, 'w') as f: print("Provisioning TLS certificates for " + ", ".join(domain_list) + ".")
f.write(action.contents) certbotret = subprocess.check_output([
else: "certbot",
raise ValueError(str(action)) "certonly",
#"-v", # just enough to see ACME errors
"--non-interactive", # will fail if user hasn't registered during Mail-in-a-Box setup
# Try to provision now that the challenge files are installed. "-d", ",".join(domain_list), # first will be main domain
cert = client.issue_certificate( "--csr", csr_file.name, # use our private key; unfortunately this doesn't work with auto-renew so we need to save cert manually
domain_list, "--cert-path", os.path.join(d, 'cert'), # we only use the full chain
account_path, "--chain-path", os.path.join(d, 'chain'), # we only use the full chain
private_key=private_key, "--fullchain-path", cert_file,
logger=my_logger)
except client.NeedToAgreeToTOS as e: "--webroot", "--webroot-path", webroot,
# The user must agree to the Let's Encrypt terms of service agreement
# before any further action can be taken.
ret_item.update({
"result": "agree-to-tos",
"url": e.url,
})
except client.WaitABit as e: "--config-dir", account_path,
# We need to hold on for a bit before querying again to see if we can #"--staging",
# acquire a provisioned certificate. ], stderr=subprocess.STDOUT).decode("utf8")
import time, datetime install_cert_copy_file(cert_file, env)
ret_item.update({
"result": "wait",
"until": e.until_when if not jsonable else e.until_when.isoformat(),
"seconds": (e.until_when - datetime.datetime.now()).total_seconds()
})
except client.AccountDataIsCorrupt as e: ret[-1]["log"].append(certbotret)
# This is an extremely rare condition. ret[-1]["result"] = "installed"
ret_item.update({ except subprocess.CalledProcessError as e:
"result": "error", ret[-1]["log"].append(e.output.decode("utf8"))
"message": "Something unexpected went wrong. It looks like your local Let's Encrypt account data is corrupted. There was a problem with the file " + e.account_file_path + ".", ret[-1]["result"] = "error"
}) except Exception as e:
ret[-1]["log"].append(str(e))
ret[-1]["result"] = "error"
except (client.InvalidDomainName, client.NeedToTakeAction, client.ChallengeFailed, client.RateLimited, acme.messages.Error, requests.exceptions.RequestException) as e: # Run post-install steps.
ret_item.update({ ret.extend(post_install_func(env))
"result": "error",
"message": "Something unexpected went wrong: " + str(e),
})
else:
# A certificate was issued.
install_status = install_cert(domain_list[0], cert['cert'].decode("ascii"), b"\n".join(cert['chain']).decode("ascii"), env, raw=True)
# str indicates the certificate was not installed.
if isinstance(install_status, str):
ret_item.update({
"result": "error",
"message": "Something unexpected was wrong with the provisioned certificate: " + install_status,
})
else:
# A list indicates success and what happened next.
ret_item["log"].extend(install_status)
ret_item.update({
"result": "installed",
})
# Return what happened with each certificate request. # Return what happened with each certificate request.
return { return ret
"requests": ret,
"problems": problems,
}
def provision_certificates_cmdline(): def provision_certificates_cmdline():
import sys import sys
@@ -412,142 +359,30 @@ def provision_certificates_cmdline():
Lock(die=True).forever() Lock(die=True).forever()
env = load_environment() env = load_environment()
verbose = False quiet = False
headless = False domains = []
force_domains = None
show_extended_problems = True
args = list(sys.argv) for arg in sys.argv[1:]:
args.pop(0) # program name if arg == "-q":
if args and args[0] == "-v": quiet = True
verbose = True
args.pop(0)
if args and args[0] == "-q":
show_extended_problems = False
args.pop(0)
if args and args[0] == "--headless":
headless = True
args.pop(0)
if args and args[0] == "--force":
force_domains = "ALL"
args.pop(0)
else: else:
force_domains = args domains.append(arg)
agree_to_tos_url = None # Go.
while True: status = provision_certificates(env, limit_domains=domains)
# Run the provisioning script. This installs certificates. If there are
# a very large number of domains on this box, it issues separate
# certificates for groups of domains. We have to check the result for
# each group.
def my_logger(message):
if verbose:
print(">", message)
status = provision_certificates(env, agree_to_tos_url=agree_to_tos_url, logger=my_logger, force_domains=force_domains, show_extended_problems=show_extended_problems)
agree_to_tos_url = None # reset to prevent infinite looping
if not status["requests"]: # Show what happened.
# No domains need certificates. for request in status:
if not headless or verbose: if isinstance(request, str):
if len(status["problems"]) == 0: print(request)
print("No domains hosted on this box need a new TLS certificate at this time.") else:
elif len(status["problems"]) > 0: if quiet and request['result'] == 'skipped':
print("No TLS certificates could be provisoned at this time:")
print()
for domain in sort_domains(status["problems"], env):
print("%s: %s" % (domain, status["problems"][domain]))
sys.exit(0)
# What happened?
wait_until = None
wait_domains = []
for request in status["requests"]:
if request["result"] == "agree-to-tos":
# We may have asked already in a previous iteration.
if agree_to_tos_url is not None:
continue continue
print(request['result'] + ":", ", ".join(request['domains']) + ":")
# Can't ask the user a question in this mode. Warn the user that something for line in request["log"]:
# needs to be done. print(line)
if headless:
print(", ".join(request["domains"]) + " need a new or renewed TLS certificate.")
print() print()
print("This box can't do that automatically for you until you agree to Let's Encrypt's")
print("Terms of Service agreement. Use the Mail-in-a-Box control panel to provision")
print("certificates for these domains.")
sys.exit(1)
print("""
I'm going to provision a TLS certificate (formerly called a SSL certificate)
for you from Let's Encrypt (letsencrypt.org).
TLS certificates are cryptographic keys that ensure communication between
you and this box are secure when getting and sending mail and visiting
websites hosted on this box. Let's Encrypt is a free provider of TLS
certificates.
Please open this document in your web browser:
%s
It is Let's Encrypt's terms of service agreement. If you agree, I can
provision that TLS certificate. If you don't agree, you will have an
opportunity to install your own TLS certificate from the Mail-in-a-Box
control panel.
Do you agree to the agreement? Type Y or N and press <ENTER>: """
% request["url"], end='', flush=True)
if sys.stdin.readline().strip().upper() != "Y":
print("\nYou didn't agree. Quitting.")
sys.exit(1)
# Okay, indicate agreement on next iteration.
agree_to_tos_url = request["url"]
if request["result"] == "wait":
# Must wait. We'll record until when. The wait occurs below.
if wait_until is None:
wait_until = request["until"]
else:
wait_until = max(wait_until, request["until"])
wait_domains += request["domains"]
if request["result"] == "error":
print(", ".join(request["domains"]) + ":")
print(request["message"])
if request["result"] == "installed":
print("A TLS certificate was successfully installed for " + ", ".join(request["domains"]) + ".")
if wait_until:
# Wait, then loop.
import time, datetime
print()
print("A TLS certificate was requested for: " + ", ".join(wait_domains) + ".")
first = True
while wait_until > datetime.datetime.now():
if not headless or first:
print ("We have to wait", int(round((wait_until - datetime.datetime.now()).total_seconds())), "seconds for the certificate to be issued...")
time.sleep(10)
first = False
continue # Loop!
if agree_to_tos_url:
# The user agrees to the TOS. Loop to try again by agreeing.
continue # Loop!
# Unless we were instructed to wait, or we just agreed to the TOS,
# we're done for now.
break
# And finally show the domains with problems.
if len(status["problems"]) > 0:
print("TLS certificates could not be provisoned for:")
for domain in sort_domains(status["problems"], env):
print("%s: %s" % (domain, status["problems"][domain]))
# INSTALLING A NEW CERTIFICATE FROM THE CONTROL PANEL # INSTALLING A NEW CERTIFICATE FROM THE CONTROL PANEL
@@ -556,7 +391,7 @@ def create_csr(domain, ssl_key, country_code, env):
"openssl", "req", "-new", "openssl", "req", "-new",
"-key", ssl_key, "-key", ssl_key,
"-sha256", "-sha256",
"-subj", "/C=%s/ST=/L=/O=/CN=%s" % (country_code, domain)]) "-subj", "/C=%s/CN=%s" % (country_code, domain)])
def install_cert(domain, ssl_cert, ssl_chain, env, raw=False): def install_cert(domain, ssl_cert, ssl_chain, env, raw=False):
# Write the combined cert+chain to a temporary path and validate that it is OK. # Write the combined cert+chain to a temporary path and validate that it is OK.
@@ -577,6 +412,16 @@ def install_cert(domain, ssl_cert, ssl_chain, env, raw=False):
cert_status += " " + cert_status_details cert_status += " " + cert_status_details
return cert_status return cert_status
# Copy certifiate into ssl directory.
install_cert_copy_file(fn, env)
# Run post-install steps.
ret = post_install_func(env)
if raw: return ret
return "\n".join(ret)
def install_cert_copy_file(fn, env):
# Where to put it? # Where to put it?
# Make a unique path for the certificate. # Make a unique path for the certificate.
from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import hashes
@@ -594,14 +439,26 @@ def install_cert(domain, ssl_cert, ssl_chain, env, raw=False):
os.makedirs(os.path.dirname(ssl_certificate), exist_ok=True) os.makedirs(os.path.dirname(ssl_certificate), exist_ok=True)
shutil.move(fn, ssl_certificate) shutil.move(fn, ssl_certificate)
ret = ["OK"]
# When updating the cert for PRIMARY_HOSTNAME, symlink it from the system def post_install_func(env):
ret = []
# Get the certificate to use for PRIMARY_HOSTNAME.
ssl_certificates = get_ssl_certificates(env)
cert = get_domain_ssl_files(env['PRIMARY_HOSTNAME'], ssl_certificates, env, use_main_cert=False)
if not cert:
# Ruh-row, we don't have any certificate usable
# for the primary hostname.
ret.append("there is no valid certificate for " + env['PRIMARY_HOSTNAME'])
# Symlink the best cert for PRIMARY_HOSTNAME to the system
# certificate path, which is hard-coded for various purposes, and then # certificate path, which is hard-coded for various purposes, and then
# restart postfix and dovecot. # restart postfix and dovecot.
if domain == env['PRIMARY_HOSTNAME']:
# Update symlink.
system_ssl_certificate = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem')) system_ssl_certificate = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem'))
if cert and os.readlink(system_ssl_certificate) != cert['certificate']:
# Update symlink.
ret.append("updating primary certificate")
ssl_certificate = cert['certificate']
os.unlink(system_ssl_certificate) os.unlink(system_ssl_certificate)
os.symlink(ssl_certificate, system_ssl_certificate) os.symlink(ssl_certificate, system_ssl_certificate)
@@ -617,12 +474,12 @@ def install_cert(domain, ssl_cert, ssl_chain, env, raw=False):
# Update the web configuration so nginx picks up the new certificate file. # Update the web configuration so nginx picks up the new certificate file.
from web_update import do_web_update from web_update import do_web_update
ret.append( do_web_update(env) ) ret.append( do_web_update(env) )
if raw: return ret
return "\n".join(ret) return ret
# VALIDATION OF CERTIFICATES # VALIDATION OF CERTIFICATES
def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring_soon=True, rounded_time=False, just_check_domain=False): def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring_soon=10, rounded_time=False, just_check_domain=False):
# Check that the ssl_certificate & ssl_private_key files are good # Check that the ssl_certificate & ssl_private_key files are good
# for the provided domain. # for the provided domain.
@@ -728,7 +585,7 @@ def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring
# We'll renew it with Lets Encrypt. # We'll renew it with Lets Encrypt.
expiry_info = "The certificate expires on %s." % cert_expiration_date.strftime("%x") expiry_info = "The certificate expires on %s." % cert_expiration_date.strftime("%x")
if ndays <= 10 and warn_if_expiring_soon: if warn_if_expiring_soon and ndays <= warn_if_expiring_soon:
# Warn on day 10 to give 4 days for us to automatically renew the # Warn on day 10 to give 4 days for us to automatically renew the
# certificate, which occurs on day 14. # certificate, which occurs on day 14.
return ("The certificate is expiring soon: " + expiry_info, None) return ("The certificate is expiring soon: " + expiry_info, None)

View File

@@ -28,7 +28,6 @@ def get_services():
{ "name": "Spamassassin", "port": 10025, "public": False, }, { "name": "Spamassassin", "port": 10025, "public": False, },
{ "name": "OpenDKIM", "port": 8891, "public": False, }, { "name": "OpenDKIM", "port": 8891, "public": False, },
{ "name": "OpenDMARC", "port": 8893, "public": False, }, { "name": "OpenDMARC", "port": 8893, "public": False, },
{ "name": "Memcached", "port": 11211, "public": False, },
{ "name": "Mail-in-a-Box Management Daemon", "port": 10222, "public": False, }, { "name": "Mail-in-a-Box Management Daemon", "port": 10222, "public": False, },
{ "name": "SSH Login (ssh)", "port": get_ssh_port(), "public": True, }, { "name": "SSH Login (ssh)", "port": get_ssh_port(), "public": True, },
{ "name": "Public DNS (nsd4)", "port": 53, "public": True, }, { "name": "Public DNS (nsd4)", "port": 53, "public": True, },
@@ -393,7 +392,7 @@ def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles):
# Check that PRIMARY_HOSTNAME resolves to PUBLIC_IP[V6] in public DNS. # Check that PRIMARY_HOSTNAME resolves to PUBLIC_IP[V6] in public DNS.
ipv6 = query_dns(domain, "AAAA") if env.get("PUBLIC_IPV6") else None ipv6 = query_dns(domain, "AAAA") if env.get("PUBLIC_IPV6") else None
if ip == env['PUBLIC_IP'] and not (ipv6 and env['PUBLIC_IPV6'] and normalize_ip(ipv6) != normalize_ip(env['PUBLIC_IPV6'])): if ip == env['PUBLIC_IP'] and not (ipv6 and env['PUBLIC_IPV6'] and ipv6 != normalize_ip(env['PUBLIC_IPV6'])):
output.print_ok("Domain resolves to box's IP address. [%s%s]" % (env['PRIMARY_HOSTNAME'], my_ips)) output.print_ok("Domain resolves to box's IP address. [%s%s]" % (env['PRIMARY_HOSTNAME'], my_ips))
else: else:
output.print_error("""This domain must resolve to your box's IP address (%s) in public DNS but it currently resolves output.print_error("""This domain must resolve to your box's IP address (%s) in public DNS but it currently resolves
@@ -487,10 +486,12 @@ def check_dns_zone(domain, env, output, dns_zonefiles):
if custom_secondary_ns and not probably_external_dns: if custom_secondary_ns and not probably_external_dns:
for ns in custom_secondary_ns: for ns in custom_secondary_ns:
# We must first resolve the nameserver to an IP address so we can query it. # We must first resolve the nameserver to an IP address so we can query it.
ns_ip = query_dns(ns, "A") ns_ips = query_dns(ns, "A")
if not ns_ip: if not ns_ips:
output.print_error("Secondary nameserver %s is not valid (it doesn't resolve to an IP address)." % ns) output.print_error("Secondary nameserver %s is not valid (it doesn't resolve to an IP address)." % ns)
continue continue
# Choose the first IP if nameserver returns multiple
ns_ip = ns_ips.split('; ')[0]
# Now query it to see what it says about this domain. # Now query it to see what it says about this domain.
ip = query_dns(domain, "A", at=ns_ip, nxdomain=None) ip = query_dns(domain, "A", at=ns_ip, nxdomain=None)
@@ -640,7 +641,7 @@ def check_web_domain(domain, rounded_time, ssl_certificates, env, output):
for (rtype, expected) in (("A", env['PUBLIC_IP']), ("AAAA", env.get('PUBLIC_IPV6'))): for (rtype, expected) in (("A", env['PUBLIC_IP']), ("AAAA", env.get('PUBLIC_IPV6'))):
if not expected: continue # IPv6 is not configured if not expected: continue # IPv6 is not configured
value = query_dns(domain, rtype) value = query_dns(domain, rtype)
if normalize_ip(value) == normalize_ip(expected): if value == normalize_ip(expected):
ok_values.append(value) ok_values.append(value)
else: else:
output.print_error("""This domain should resolve to your box's IP address (%s %s) if you would like the box to serve output.print_error("""This domain should resolve to your box's IP address (%s %s) if you would like the box to serve
@@ -687,27 +688,17 @@ def query_dns(qname, rtype, nxdomain='[Not Set]', at=None):
except dns.exception.Timeout: except dns.exception.Timeout:
return "[timeout]" return "[timeout]"
# Normalize IP addresses. IP address --- especially IPv6 addresses --- can
# be expressed in equivalent string forms. Canonicalize the form before
# returning them. The caller should normalize any IP addresses the result
# of this method is compared with.
if rtype in ("A", "AAAA"):
response = [normalize_ip(str(r)) for r in response]
# There may be multiple answers; concatenate the response. Remove trailing # There may be multiple answers; concatenate the response. Remove trailing
# periods from responses since that's how qnames are encoded in DNS but is # periods from responses since that's how qnames are encoded in DNS but is
# confusing for us. The order of the answers doesn't matter, so sort so we # confusing for us. The order of the answers doesn't matter, so sort so we
# can compare to a well known order. # can compare to a well known order.
# Unfortunately, the response.__str__ returns bytes
# instead of string, if it resulted from an AAAA-query.
# We need to convert manually, until this is fixed:
# https://github.com/rthalley/dnspython/issues/204
#
# BEGIN HOTFIX
response_new = []
for r in response:
s = r.to_text()
if isinstance(s, bytes):
s = s.decode('utf-8')
response_new.append(s)
response = response_new
# END HOTFIX
return "; ".join(sorted(str(r).rstrip('.') for r in response)) return "; ".join(sorted(str(r).rstrip('.') for r in response))
def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output): def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output):
@@ -805,14 +796,14 @@ def get_latest_miab_version():
def check_miab_version(env, output): def check_miab_version(env, output):
config = load_settings(env) config = load_settings(env)
if config.get("privacy", True):
output.print_warning("Mail-in-a-Box version check disabled by privacy setting.")
else:
try: try:
this_ver = what_version_is_this(env) this_ver = what_version_is_this(env)
except: except:
this_ver = "Unknown" this_ver = "Unknown"
if config.get("privacy", True):
output.print_warning("You are running version Mail-in-a-Box %s. Mail-in-a-Box version check disabled by privacy setting." % this_ver)
else:
latest_ver = get_latest_miab_version() latest_ver = get_latest_miab_version()
if this_ver == latest_ver: if this_ver == latest_ver:
@@ -892,7 +883,9 @@ def run_and_output_changes(env, pool):
json.dump(cur.buf, f, indent=True) json.dump(cur.buf, f, indent=True)
def normalize_ip(ip): def normalize_ip(ip):
# Use ipaddress module to normalize the IPv6 notation and ensure we are matching IPv6 addresses written in different representations according to rfc5952. # Use ipaddress module to normalize the IPv6 notation and
# ensure we are matching IPv6 addresses written in different
# representations according to rfc5952.
import ipaddress import ipaddress
try: try:
return str(ipaddress.ip_address(ip)) return str(ipaddress.ip_address(ip))

View File

@@ -39,8 +39,9 @@
<label for="addaliasForwardsTo" class="col-sm-1 control-label">Forwards To</label> <label for="addaliasForwardsTo" class="col-sm-1 control-label">Forwards To</label>
<div class="col-sm-10"> <div class="col-sm-10">
<textarea class="form-control" rows="3" id="addaliasForwardsTo"></textarea> <textarea class="form-control" rows="3" id="addaliasForwardsTo"></textarea>
<div style="margin-top: 3px; padding-left: 3px; font-size: 90%" class="text-muted"> <div style="margin-top: 3px; padding-left: 3px; font-size: 90%">
<span class="domainalias">Enter just the part of an email address starting with the @-sign.</span> <span class="domainalias text-muted">Enter just the part of an email address starting with the @-sign.</span>
<span class="text-danger">Only forward mail to addresses handled by this Mail-in-a-Box, since mail forwarded by aliases to other domains may be rejected or filtered by the receiver. To forward mail to other domains, create a mail user and then log into webmail for the user and create a filter rule to forward mail.</span>
</div> </div>
</div> </div>
</div> </div>
@@ -50,7 +51,7 @@
<div class="radio"> <div class="radio">
<label> <label>
<input id="addaliasForwardsToNotAdvanced" name="addaliasForwardsToDivToggle" type="radio" checked onclick="$('#addaliasForwardsToDiv').toggle(false)"> <input id="addaliasForwardsToNotAdvanced" name="addaliasForwardsToDivToggle" type="radio" checked onclick="$('#addaliasForwardsToDiv').toggle(false)">
Any mail user listed in the Fowards To box can send mail claiming to be from <span class="regularalias">the alias address</span><span class="catchall domainalias">any address on the alias domain</span>. Any mail user listed in the Forwards To box can send mail claiming to be from <span class="regularalias">the alias address</span><span class="catchall domainalias">any address on the alias domain</span>.
</label> </label>
</div> </div>
<div class="radio"> <div class="radio">

View File

@@ -90,7 +90,7 @@
<div class="col-sm-offset-1 col-sm-11"> <div class="col-sm-offset-1 col-sm-11">
<p class="small"> <p class="small">
Multiple secondary servers can be separated with commas or spaces (i.e., <code>ns2.hostingcompany.com ns3.hostingcompany.com</code>). Multiple secondary servers can be separated with commas or spaces (i.e., <code>ns2.hostingcompany.com ns3.hostingcompany.com</code>).
To enable zone transfers to additional servers without listing them as secondary nameservers, add <code>xfr:IPADDRESS</code>. To enable zone transfers to additional servers without listing them as secondary nameservers, add an IP address or subnet using <code>xfr:10.20.30.40</code> or <code>xfr:10.20.30.40/24</code>.
</p> </p>
<p id="secondarydns-clear-instructions" style="display: none" class="small"> <p id="secondarydns-clear-instructions" style="display: none" class="small">
Clear the input field above and click Update to use this machine itself as secondary DNS, which is the default/normal setup. Clear the input field above and click Update to use this machine itself as secondary DNS, which is the default/normal setup.
@@ -193,6 +193,22 @@ function show_current_custom_dns() {
else else
$('#custom-dns-current').fadeOut(); $('#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;
}
data = data.map(reverse_fqdn).sort(sort).map(reverse_fqdn);
$('#custom-dns-current').find("tbody").text(''); $('#custom-dns-current').find("tbody").text('');
for (var i = 0; i < data.length; i++) { for (var i = 0; i < data.length; i++) {
var tr = $("<tr/>"); var tr = $("<tr/>");

View File

@@ -23,7 +23,7 @@ sudo tools/mail.py user make-admin me@{{hostname}}</pre>
<p style="margin: 2em; text-align: center;">Log in here for your Mail-in-a-Box control panel.</p> <p style="margin: 2em; text-align: center;">Log in here for your Mail-in-a-Box control panel.</p>
<div style="margin: 0 auto; max-width: 32em;"> <div style="margin: 0 auto; max-width: 32em;">
<form class="form-horizontal" role="form" onsubmit="do_login(); return false;"> <form class="form-horizontal" role="form" onsubmit="do_login(); return false;" method="get">
<div class="form-group"> <div class="form-group">
<label for="inputEmail3" class="col-sm-3 control-label">Email</label> <label for="inputEmail3" class="col-sm-3 control-label">Email</label>
<div class="col-sm-9"> <div class="col-sm-9">

View File

@@ -59,7 +59,7 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<h4>Greylisting</h4> <h4>Greylisting</h4>
<p>Your box using a technique called greylisting to cut down on spam. Greylisting works by delaying mail from people you haven&rsquo;t received mail from before for up to about 10 minutes. The vast majority of spam gets tricked by this. If you are waiting for an email from someone new, such as if you are registering on a new website and are waiting for an email confirmation, please give it up to 10-15 minutes to arrive.</p> <p>Your box uses a technique called greylisting to cut down on spam. Greylisting works by initially rejecting mail from people you haven&rsquo;t received mail from before. Legitimate mail servers will attempt redelivery shortly afterwards, but the vast majority of spam gets tricked by this. If you are waiting for an email from someone new, such as if you are registering on a new website and are waiting for an email confirmation, please be aware there will be a minimum of 3 minutes delay, depending how soon the remote server attempts redelivery.</p>
<h4>+tag addresses</h4> <h4>+tag addresses</h4>
<p>Every incoming email address also receives mail for <code>+tag</code> addresses. If your email address is <code>you@yourdomain.com</code>, you&rsquo;ll also automatically get mail sent to <code>you+anythinghere@yourdomain.com</code>. Use this as a fast way to segment incoming mail for your own filtering rules without having to create aliases in this control panel.</p> <p>Every incoming email address also receives mail for <code>+tag</code> addresses. If your email address is <code>you@yourdomain.com</code>, you&rsquo;ll also automatically get mail sent to <code>you+anythinghere@yourdomain.com</code>. Use this as a fast way to segment incoming mail for your own filtering rules without having to create aliases in this control panel.</p>

View File

@@ -8,7 +8,7 @@
<p>You need a TLS certificate for this box&rsquo;s hostname ({{hostname}}) and every other domain name and subdomain that this box is hosting a website for (see the list below).</p> <p>You need a TLS certificate for this box&rsquo;s hostname ({{hostname}}) and every other domain name and subdomain that this box is hosting a website for (see the list below).</p>
<div id="ssl_provision"> <div id="ssl_provision">
<h3>Provision a certificate</h3> <h3>Provision certificates</h3>
<div id="ssl_provision_p" style="display: none; margin-top: 1.5em"> <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> <button onclick='return provision_tls_cert();' class='btn btn-primary' style="float: left; margin: 0 1.5em 1em 0;">Provision</button>
@@ -19,21 +19,6 @@
<div class="clearfix"> </div> <div class="clearfix"> </div>
<div id="ssl_provision_result"></div> <div id="ssl_provision_result"></div>
<div id="ssl_provision_problems_div" style="display: none;">
<p style="margin-bottom: .5em;">Certificates cannot be automatically provisioned for:</p>
<table id="ssl_provision_problems" style="margin-top: 0;" class="table">
<thead>
<tr>
<th>Domain</th>
<th>Problem</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<p>Use the <em>Install Certificate</em> button below for these domains.</p>
</div>
</div> </div>
<h3>Certificate status</h3> <h3>Certificate status</h3>
@@ -103,24 +88,12 @@ function show_tls(keep_provisioning_shown) {
// provisioning status // provisioning status
if (!keep_provisioning_shown) if (!keep_provisioning_shown)
$('#ssl_provision').toggle(res.can_provision.length + res.cant_provision.length > 0) $('#ssl_provision').toggle(res.can_provision.length > 0)
$('#ssl_provision_p').toggle(res.can_provision.length > 0); $('#ssl_provision_p').toggle(res.can_provision.length > 0);
if (res.can_provision.length > 0) if (res.can_provision.length > 0)
$('#ssl_provision_p span').text(res.can_provision.join(", ")); $('#ssl_provision_p span').text(res.can_provision.join(", "));
$('#ssl_provision_problems_div').toggle(res.cant_provision.length > 0);
$('#ssl_provision_problems tbody').text("");
for (var i = 0; i < res.cant_provision.length; i++) {
var domain = res.cant_provision[i];
var row = $("<tr><th class='domain'><a href=''></a></th><td class='status'></td></tr>");
$('#ssl_provision_problems tbody').append(row);
row.attr('data-domain', domain.domain);
row.find('.domain a').text(domain.domain);
row.find('.domain a').attr('href', 'https://' + domain.domain);
row.find('.status').text(domain.problem);
}
// certificate status // certificate status
var domains = res.status; var domains = res.status;
var tb = $('#ssl_domains tbody'); var tb = $('#ssl_domains tbody');
@@ -159,7 +132,11 @@ function ssl_install(elem) {
} }
function show_csr() { function show_csr() {
// Can't show a CSR until both inputs are entered.
if ($('#ssldomain').val() == "") return; if ($('#ssldomain').val() == "") return;
if ($('#sslcc').val() == "") return;
// Scroll to it and fetch.
$('#csr_info').slideDown(); $('#csr_info').slideDown();
$('#ssl_csr').text('Loading...'); $('#ssl_csr').text('Loading...');
api( api(
@@ -192,20 +169,15 @@ function install_cert() {
}); });
} }
var agree_to_tos_url_prompt = null;
var agree_to_tos_url = null;
function provision_tls_cert() { function provision_tls_cert() {
// Automatically provision any certs. // Automatically provision any certs.
$('#ssl_provision_p .btn').attr('disabled', '1'); // prevent double-clicks $('#ssl_provision_p .btn').attr('disabled', '1'); // prevent double-clicks
api( api(
"/ssl/provision", "/ssl/provision",
"POST", "POST",
{ { },
agree_to_tos_url: agree_to_tos_url
},
function(status) { function(status) {
// Clear last attempt. // Clear last attempt.
agree_to_tos_url = null;
$('#ssl_provision_result').text(""); $('#ssl_provision_result').text("");
may_reenable_provision_button = true; may_reenable_provision_button = true;
@@ -221,52 +193,33 @@ function provision_tls_cert() {
for (var i = 0; i < status.requests.length; i++) { for (var i = 0; i < status.requests.length; i++) {
var r = status.requests[i]; var r = status.requests[i];
if (r.result == "skipped") {
// not interested --- this domain wasn't in the table
// to begin with
continue;
}
// create an HTML block to display the results of this request // create an HTML block to display the results of this request
var n = $("<div><h4/><p/></div>"); var n = $("<div><h4/><p/></div>");
$('#ssl_provision_result').append(n); $('#ssl_provision_result').append(n);
// plain log line
if (typeof r === "string") {
n.find("p").text(r);
continue;
}
// show a header only to disambiguate request blocks // show a header only to disambiguate request blocks
if (status.requests.length > 0) if (status.requests.length > 0)
n.find("h4").text(r.domains.join(", ")); n.find("h4").text(r.domains.join(", "));
if (r.result == "agree-to-tos") { if (r.result == "error") {
// user needs to agree to Let's Encrypt's TOS
agree_to_tos_url_prompt = r.url;
$('#ssl_provision_p .btn').attr('disabled', '1');
n.find("p").html("Please open and review <a href='" + r.url + "' target='_blank'>Let's Encrypt's terms of service agreement</a>. You must agree to their terms for a certificate to be automatically provisioned from them.");
n.append($('<button onclick="agree_to_tos_url = agree_to_tos_url_prompt; return provision_tls_cert();" class="btn btn-success" style="margin-left: 2em">Agree &amp; Try Again</button>'));
// don't re-enable the Provision button -- user must use the Agree button
may_reenable_provision_button = false;
} else if (r.result == "error") {
n.find("p").addClass("text-danger").text(r.message); n.find("p").addClass("text-danger").text(r.message);
} else if (r.result == "wait") {
// Show a button that counts down to zero, at which point it becomes enabled.
n.find("p").text("A certificate is now in the process of being provisioned, but it takes some time. Please wait until the Finish button is enabled, and then click it to acquire the certificate.");
var b = $('<button onclick="return provision_tls_cert();" class="btn btn-success" style="margin-left: 2em">Finish</button>');
b.attr("disabled", "1");
var now = new Date();
n.append(b);
function ready_to_finish() {
var remaining = Math.round(r.seconds - (new Date() - now)/1000);
if (remaining > 0) {
setTimeout(ready_to_finish, 1000);
b.text("Finish (" + remaining + "...)")
} else {
b.text("Finish (ready)")
b.removeAttr("disabled");
}
}
ready_to_finish();
// don't re-enable the Provision button -- user must use the Retry button when it becomes enabled
may_reenable_provision_button = false;
} else if (r.result == "installed") { } else if (r.result == "installed") {
n.find("p").addClass("text-success").text("The TLS certificate was provisioned and installed."); n.find("p").addClass("text-success").text("The TLS certificate was provisioned and installed.");
setTimeout("show_tls(true)", 1); // update main table of certificate statuses, call with arg keep_provisioning_shown true so that we don't clear what we just outputted setTimeout("show_tls(true)", 1); // update main table of certificate statuses, call with arg keep_provisioning_shown true so that we don't clear what we just outputted
} }
// display the detailed log info in case of problems // display the detailed log info in case of problems
@@ -274,7 +227,6 @@ function provision_tls_cert() {
n.append(trace); n.append(trace);
for (var j = 0; j < r.log.length; j++) for (var j = 0; j < r.log.length; j++)
trace.append($("<div/>").text(r.log[j])); trace.append($("<div/>").text(r.log[j]));
} }
if (may_reenable_provision_button) if (may_reenable_provision_button)

View File

@@ -77,15 +77,22 @@
</div> </div>
</div> </div>
<div class="form-group backup-target-s3"> <div class="form-group backup-target-s3">
<label for="backup-target-s3-host" class="col-sm-2 control-label">S3 Region</label> <label for="backup-target-s3-host-select" class="col-sm-2 control-label">S3 Region</label>
<div class="col-sm-8"> <div class="col-sm-8">
<select class="form-control" rows="1" id="backup-target-s3-host"> <select class="form-control" rows="1" id="backup-target-s3-host-select">
{% for name, host in backup_s3_hosts %} {% for name, host in backup_s3_hosts %}
<option value="{{host}}">{{name}}</option> <option value="{{host}}">{{name}}</option>
{% endfor %} {% endfor %}
<option value="other">Other</option>
</select> </select>
</div> </div>
</div> </div>
<div class="form-group backup-target-s3">
<label for="backup-target-s3-host" class="col-sm-2 control-label">S3 Host / Endpoint</label>
<div class="col-sm-8">
<input type="text" placeholder="Endpoint" class="form-control" rows="1" id="backup-target-s3-host">
</div>
</div>
<div class="form-group backup-target-s3"> <div class="form-group backup-target-s3">
<label for="backup-target-s3-path" class="col-sm-2 control-label">S3 Path</label> <label for="backup-target-s3-path" class="col-sm-2 control-label">S3 Path</label>
<div class="col-sm-8"> <div class="col-sm-8">
@@ -139,6 +146,8 @@ function toggle_form() {
var target_type = $("#backup-target-type").val(); var target_type = $("#backup-target-type").val();
$(".backup-target-local, .backup-target-rsync, .backup-target-s3").hide(); $(".backup-target-local, .backup-target-rsync, .backup-target-s3").hide();
$(".backup-target-" + target_type).show(); $(".backup-target-" + target_type).show();
init_inputs(target_type);
} }
function nice_size(bytes) { function nice_size(bytes) {
@@ -200,6 +209,7 @@ function show_system_backup() {
total_disk_size += b.size; total_disk_size += b.size;
} }
total_disk_size += r.unmatched_file_size;
$('#backup-total-size').text(nice_size(total_disk_size)); $('#backup-total-size').text(nice_size(total_disk_size));
}) })
} }
@@ -277,4 +287,20 @@ function set_custom_backup() {
}); });
return false; return false;
} }
function init_inputs(target_type) {
function set_host(host) {
if(host !== 'other') {
$("#backup-target-s3-host").val(host);
} else {
$("#backup-target-s3-host").val('');
}
}
if (target_type == "s3") {
$('#backup-target-s3-host-select').off('change').on('change', function() {
set_host($('#backup-target-s3-host-select').val());
});
set_host($('#backup-target-s3-host-select').val());
}
}
</script> </script>

View File

@@ -31,7 +31,7 @@
<button type="submit" class="btn btn-primary">Add User</button> <button type="submit" class="btn btn-primary">Add User</button>
</form> </form>
<ul style="margin-top: 1em; padding-left: 1.5em; font-size: 90%;"> <ul style="margin-top: 1em; padding-left: 1.5em; font-size: 90%;">
<li>Passwords must be at least eight characters and may not contain spaces. For best results, <a href="#" onclick="return generate_random_password()">generate a random password</a>.</li> <li>Passwords must be at least eight characters consisting of English lettters and numbers only. For best results, <a href="#" onclick="return generate_random_password()">generate a random password</a>.</li>
<li>Use <a href="#" onclick="return show_panel('aliases')">aliases</a> to create email addresses that forward to existing accounts.</li> <li>Use <a href="#" onclick="return show_panel('aliases')">aliases</a> to create email addresses that forward to existing accounts.</li>
<li>Administrators get access to this control panel.</li> <li>Administrators get access to this control panel.</li>
<li>User accounts cannot contain any international (non-ASCII) characters, but <a href="#" onclick="return show_panel('aliases');">aliases</a> can.</li> <li>User accounts cannot contain any international (non-ASCII) characters, but <a href="#" onclick="return show_panel('aliases');">aliases</a> can.</li>
@@ -43,7 +43,6 @@
<tr> <tr>
<th width="50%">Email Address</th> <th width="50%">Email Address</th>
<th>Actions</th> <th>Actions</th>
<th>Mailbox Size</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -73,8 +72,6 @@
archive account archive account
</a> </a>
</td> </td>
<td class='mailboxsize'>
</td>
</tr> </tr>
<tr id="user-extra-template" class="if_inactive"> <tr id="user-extra-template" class="if_inactive">
<td colspan="3" style="border: 0; padding-top: 0"> <td colspan="3" style="border: 0; padding-top: 0">
@@ -102,7 +99,7 @@
<thead><th>Verb</th> <th>Action</th><th></th></thead> <thead><th>Verb</th> <th>Action</th><th></th></thead>
<tr><td>GET</td><td><i>(none)</i></td> <td>Returns a list of existing mail users. Adding <code>?format=json</code> to the URL will give JSON-encoded results.</td></tr> <tr><td>GET</td><td><i>(none)</i></td> <td>Returns a list of existing mail users. Adding <code>?format=json</code> to the URL will give JSON-encoded results.</td></tr>
<tr><td>POST</td><td>/add</td> <td>Adds a new mail user. Required POST-body parameters are <code>email</code> and <code>password</code>.</td></tr> <tr><td>POST</td><td>/add</td> <td>Adds a new mail user. Required POST-body parameters are <code>email</code> and <code>password</code>.</td></tr>
<tr><td>POST</td><td>/remove</td> <td>Removes a mail user. Required POST-by parameter is <code>email</code>.</td></tr> <tr><td>POST</td><td>/remove</td> <td>Removes a mail user. Required POST-body 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>
</table> </table>
@@ -156,7 +153,6 @@ function show_users() {
n.attr('data-email', user.email); n.attr('data-email', user.email);
n.find('.address').text(user.email) n.find('.address').text(user.email)
n.find('.mailboxsize').text(nice_size(user.mailbox_size))
n2.find('.restore_info tt').text(user.mailbox); n2.find('.restore_info tt').text(user.mailbox);
if (user.status == 'inactive') continue; if (user.status == 'inactive') continue;
@@ -213,7 +209,7 @@ function users_set_password(elem) {
show_modal_confirm( show_modal_confirm(
"Set Password", "Set Password",
$("<p>Set a new password for <b>" + email + "</b>?</p> <p><label for='users_set_password_pw' style='display: block; font-weight: normal'>New Password:</label><input type='password' id='users_set_password_pw'></p><p><small>Passwords must be at least four characters and may not contain spaces.</small>" + yourpw + "</p>"), $("<p>Set a new password for <b>" + email + "</b>?</p> <p><label for='users_set_password_pw' style='display: block; font-weight: normal'>New Password:</label><input type='password' id='users_set_password_pw'></p><p><small>Passwords must be at least eight characters and may not contain spaces.</small>" + yourpw + "</p>"),
"Set Password", "Set Password",
function() { function() {
api( api(

View File

@@ -24,6 +24,12 @@ def get_web_domains(env, include_www_redirects=True, exclude_dns_elsewhere=True)
# the topmost of each domain we serve. # the topmost of each domain we serve.
domains |= set('www.' + zone for zone, zonefile in get_dns_zones(env)) domains |= set('www.' + zone for zone, zonefile in get_dns_zones(env))
# Add Autoconfiguration domains, allowing us to serve correct SSL certs.
# 'autoconfig.' for Mozilla Thunderbird auto setup.
# 'autodiscover.' for Activesync autodiscovery.
domains |= set('autoconfig.' + maildomain for maildomain in get_mail_domains(env))
domains |= set('autodiscover.' + maildomain for maildomain in get_mail_domains(env))
if exclude_dns_elsewhere: if exclude_dns_elsewhere:
# ...Unless the domain has an A/AAAA record that maps it to a different # ...Unless the domain has an A/AAAA record that maps it to a different
# IP address than this box. Remove those domains from our list. # IP address than this box. Remove those domains from our list.
@@ -149,7 +155,14 @@ def make_domain_config(domain, templates, ssl_certificates, env):
# any proxy or redirect here? # any proxy or redirect here?
for path, url in yaml.get("proxies", {}).items(): for path, url in yaml.get("proxies", {}).items():
nginx_conf_extra += "\tlocation %s {\n\t\tproxy_pass %s;\n\t}\n" % (path, url) nginx_conf_extra += "\tlocation %s {" % path
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}\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)
@@ -198,8 +211,11 @@ def get_web_domains_info(env):
# for the SSL config panel, get cert status # for the SSL config panel, get cert status
def check_cert(domain): def check_cert(domain):
try:
tls_cert = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True) tls_cert = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
if tls_cert is None: return ("danger", "No Certificate Installed") except OSError: # PRIMARY_HOSTNAME cert is missing
tls_cert = None
if tls_cert is None: return ("danger", "No certificate installed.")
cert_status, cert_status_details = check_certificate(domain, tls_cert["certificate"], tls_cert["private-key"]) cert_status, cert_status_details = check_certificate(domain, tls_cert["certificate"], tls_cert["private-key"])
if cert_status == "OK": if cert_status == "OK":
return ("success", "Signed & valid. " + cert_status_details) return ("success", "Signed & valid. " + cert_status_details)

View File

@@ -1,62 +0,0 @@
POSTGREY_VERSION=1.35-1+miab1
DOVECOT_VERSION=2.2.9-1ubuntu2.1+miab1
all: clean build_postgrey build_dovecot_lucene
clean:
# Clean.
rm -rf /tmp/build
mkdir -p /tmp/build
build_postgrey: clean
# Download the latest Debian postgrey package. It is ahead of Ubuntu,
# and we might as well jump ahead.
git clone git://git.debian.org/git/collab-maint/postgrey.git /tmp/build/postgrey
# Download the corresponding upstream package.
wget -O /tmp/build/postgrey_1.35.orig.tar.gz http://postgrey.schweikert.ch/pub/old/postgrey-1.35.tar.gz
# Add our source patch to the debian packaging listing.
cp postgrey_sources.diff /tmp/build/postgrey/debian/patches/mailinabox
# Patch the packaging to give it a new version.
patch -p1 -d /tmp/build/postgrey < postgrey.diff
# Build the source package.
(cd /tmp/build/postgrey; dpkg-buildpackage -S -us -uc -nc)
# Sign the packages.
debsign /tmp/build/postgrey_$(POSTGREY_VERSION)_source.changes
# Upload to PPA.
dput ppa:mail-in-a-box/ppa /tmp/build/postgrey_$(POSTGREY_VERSION)_source.changes
# Clear the intermediate files.
rm -rf /tmp/build/postgrey
# TESTING BINARY PACKAGE
#sudo apt-get build-dep -y postgrey
#(cd /tmp/build/postgrey; dpkg-buildpackage -us -uc -nc)
build_dovecot_lucene: clean
# Get the upstream source.
(cd /tmp/build; apt-get source dovecot)
# Patch it so that we build dovecot-lucene (and nothing else).
patch -p1 -d /tmp/build/dovecot-2.2.9 < dovecot_lucene.diff
# Build the source package.
(cd /tmp/build/dovecot-2.2.9; dpkg-buildpackage -S -us -uc -nc)
# Sign the packages.
debsign /tmp/build/dovecot_$(DOVECOT_VERSION)_source.changes
# Upload it.
dput ppa:mail-in-a-box/ppa /tmp/build/dovecot_$(DOVECOT_VERSION)_source.changes
# TESTING BINARY PACKAGE
# Install build dependencies and build dependencies we've added in our patch,
# and then build the binary package.
#sudo apt-get build-dep -y dovecot
#sudo apt-get install libclucene-dev liblzma-dev libexttextcat-dev libstemmer-dev
#(cd /tmp/build/dovecot-2.2.9; dpkg-buildpackage -us -uc -nc)

View File

@@ -1,40 +0,0 @@
ppa instructions
================
Mail-in-a-Box maintains a Launchpad.net PPA ([Mail-in-a-Box PPA](https://launchpad.net/~mail-in-a-box/+archive/ubuntu/ppa)) for additional deb's that we want to have installed on systems.
Packages
--------
* postgrey, a fork of [postgrey](http://postgrey.schweikert.ch/) based on the [latest Debian package](http://git.debian.org/?p=collab-maint/postgrey.git), with a modification to whitelist senders that are whitelisted by [dnswl.org](https://www.dnswl.org/) (i.e. don't greylist mail from known good senders).
* dovecot-lucene, [dovecot's lucene full text search plugin](http://wiki2.dovecot.org/Plugins/FTS/Lucene), which isn't built by Ubuntu's dovecot package maintainer unfortunately.
Building
--------
To rebuild the packages in the PPA, you'll need to be @JoshData.
First:
* You should have an account on Launchpad.net.
* Your account should have your GPG key set (to the fingerprint of a GPG key on your system matching the identity at the top of the debian/changelog files).
* You should have write permission to the PPA.
To build:
# Start a clean VM.
vagrant up
# Put your signing keys (on the host machine) into the VM (so it can sign the debs).
gpg --export-secret-keys | vagrant ssh -- gpg --import
# Build & upload to launchpad.
vagrant ssh -- "cd /vagrant && make"
Mail-in-a-Box adds our PPA during setup, but if you need to do that yourself for testing:
apt-add-repository ppa:mail-in-a-box/ppa
apt-get update
apt-get install postgrey dovecot-lucene

12
ppa/Vagrantfile vendored
View File

@@ -1,12 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu14.04"
config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"
config.vm.provision :shell, :inline => <<-SH
sudo apt-get update
sudo apt-get install -y git dpkg-dev devscripts dput
SH
end

View File

@@ -1,319 +0,0 @@
--- a/debian/control
+++ b/debian/control
@@ -1,210 +1,23 @@
Source: dovecot
Section: mail
Priority: optional
-Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
-XSBC-Original-Maintainer: Dovecot Maintainers <jaldhar-dovecot@debian.org>
-Uploaders: Jaldhar H. Vyas <jaldhar@debian.org>, Fabio Tranchitella <kobold@debian.org>, Joel Johnson <mrjoel@lixil.net>, Marco Nenciarini <mnencia@debian.org>
-Build-Depends: debhelper (>= 7.2.3~), dpkg-dev (>= 1.16.1), pkg-config, libssl-dev, libpam0g-dev, libldap2-dev, libpq-dev, libmysqlclient-dev, libsqlite3-dev, libsasl2-dev, zlib1g-dev, libkrb5-dev, drac-dev (>= 1.12-5), libbz2-dev, libdb-dev, libcurl4-gnutls-dev, libexpat-dev, libwrap0-dev, dh-systemd, po-debconf, lsb-release, hardening-wrapper, dh-autoreconf, autotools-dev
+Maintainer: Joshua Tauberer <jt@occams.info>
+XSBC-Original-Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
+Build-Depends: debhelper (>= 7.2.3~), dpkg-dev (>= 1.16.1), pkg-config, libssl-dev, libpam0g-dev, libldap2-dev, libpq-dev, libmysqlclient-dev, libsqlite3-dev, libsasl2-dev, zlib1g-dev, libkrb5-dev, drac-dev (>= 1.12-5), libbz2-dev, libdb-dev, libcurl4-gnutls-dev, libexpat-dev, libwrap0-dev, dh-systemd, po-debconf, lsb-release, libclucene-dev (>= 2.3), liblzma-dev, libexttextcat-dev, libstemmer-dev, hardening-wrapper, dh-autoreconf, autotools-dev
Standards-Version: 3.9.4
Homepage: http://dovecot.org/
-Vcs-Git: git://git.debian.org/git/collab-maint/dovecot.git
-Vcs-Browser: http://git.debian.org/?p=collab-maint/dovecot.git
+Vcs-Git: https://github.com/mail-in-a-box/mailinabox
+Vcs-Browser: https://github.com/mail-in-a-box/mailinabox
-Package: dovecot-core
+Package: dovecot-lucene
Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, libpam-runtime (>= 0.76-13.1), openssl, adduser, ucf (>= 2.0020), ssl-cert (>= 1.0-11ubuntu1), lsb-base (>= 3.2-12ubuntu3)
-Suggests: ntp, dovecot-gssapi, dovecot-sieve, dovecot-pgsql, dovecot-mysql, dovecot-sqlite, dovecot-ldap, dovecot-imapd, dovecot-pop3d, dovecot-lmtpd, dovecot-managesieved, dovecot-solr, ufw
-Recommends: ntpdate
-Provides: dovecot-common
-Replaces: dovecot-common (<< 1:2.0.14-2~), mailavenger (<< 0.8.1-4)
-Breaks: dovecot-common (<< 1:2.0.14-2~), mailavenger (<< 0.8.1-4)
-Description: secure POP3/IMAP server - core files
+Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (>= 1:2.2.9-1ubuntu2.1)
+Description: secure POP3/IMAP server - Lucene support
Dovecot is a mail server whose major goals are security and extreme
reliability. It tries very hard to handle all error conditions and verify
that all data is valid, making it nearly impossible to crash. It supports
mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
fast, extensible, and portable.
.
- This package contains the Dovecot main server and its command line utility.
-
-Package: dovecot-dev
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version})
-Replaces: dovecot-common (<< 1:2.0.14-2~)
-Breaks: dovecot-common (<< 1:2.0.14-2~)
-Description: secure POP3/IMAP server - header files
- Dovecot is a mail server whose major goals are security and extreme
- reliability. It tries very hard to handle all error conditions and verify
- that all data is valid, making it nearly impossible to crash. It supports
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
- fast, extensible, and portable.
- .
- This package contains header files needed to compile plugins for the Dovecot
- mail server.
-
-Package: dovecot-imapd
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version}), ucf (>= 2.0020)
-Provides: imap-server
-Description: secure POP3/IMAP server - IMAP daemon
- Dovecot is a mail server whose major goals are security and extreme
- reliability. It tries very hard to handle all error conditions and verify
- that all data is valid, making it nearly impossible to crash. It supports
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
- fast, extensible, and portable.
- .
- This package contains the Dovecot IMAP server.
-
-Package: dovecot-pop3d
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version}), ucf (>= 2.0020)
-Provides: pop3-server
-Description: secure POP3/IMAP server - POP3 daemon
- Dovecot is a mail server whose major goals are security and extreme
- reliability. It tries very hard to handle all error conditions and verify
- that all data is valid, making it nearly impossible to crash. It supports
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
- fast, extensible, and portable.
- .
- This package contains the Dovecot POP3 server.
-
-Package: dovecot-lmtpd
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version}), ucf (>= 2.0020)
-Replaces: dovecot-common (<< 1:2.0.14-2~)
-Breaks: dovecot-common (<< 1:2.0.14-2~)
-Description: secure POP3/IMAP server - LMTP server
- Dovecot is a mail server whose major goals are security and extreme
- reliability. It tries very hard to handle all error conditions and verify
- that all data is valid, making it nearly impossible to crash. It supports
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
- fast, extensible, and portable.
- .
- This package contains the Dovecot LMTP server.
-
-Package: dovecot-managesieved
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version}), dovecot-sieve (= ${binary:Version}), ucf (>= 2.0020)
-Replaces: dovecot-common (<< 1:2.0.14-2~)
-Breaks: dovecot-common (<< 1:2.0.14-2~)
-Description: secure POP3/IMAP server - ManageSieve server
- Dovecot is a mail server whose major goals are security and extreme
- reliability. It tries very hard to handle all error conditions and verify
- that all data is valid, making it nearly impossible to crash. It supports
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
- fast, extensible, and portable.
- .
- This package contains the Dovecot ManageSieve server.
-
-Package: dovecot-pgsql
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version})
-Description: secure POP3/IMAP server - PostgreSQL support
- Dovecot is a mail server whose major goals are security and extreme
- reliability. It tries very hard to handle all error conditions and verify
- that all data is valid, making it nearly impossible to crash. It supports
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
- fast, extensible, and portable.
- .
- This package provides PostgreSQL support for Dovecot.
-
-Package: dovecot-mysql
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version})
-Description: secure POP3/IMAP server - MySQL support
- Dovecot is a mail server whose major goals are security and extreme
- reliability. It tries very hard to handle all error conditions and verify
- that all data is valid, making it nearly impossible to crash. It supports
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
- fast, extensible, and portable.
- .
- This package provides MySQL support for Dovecot.
-
-Package: dovecot-sqlite
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version})
-Description: secure POP3/IMAP server - SQLite support
- Dovecot is a mail server whose major goals are security and extreme
- reliability. It tries very hard to handle all error conditions and verify
- that all data is valid, making it nearly impossible to crash. It supports
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
- fast, extensible, and portable.
- .
- This package provides SQLite support for Dovecot.
-
-Package: dovecot-ldap
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version}), ucf (>= 2.0020)
-Description: secure POP3/IMAP server - LDAP support
- Dovecot is a mail server whose major goals are security and extreme
- reliability. It tries very hard to handle all error conditions and verify
- that all data is valid, making it nearly impossible to crash. It supports
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
- fast, extensible, and portable.
- .
- This package provides LDAP support for Dovecot.
-
-Package: dovecot-gssapi
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version})
-Description: secure POP3/IMAP server - GSSAPI support
- Dovecot is a mail server whose major goals are security and extreme
- reliability. It tries very hard to handle all error conditions and verify
- that all data is valid, making it nearly impossible to crash. It supports
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
- fast, extensible, and portable.
- .
- This package provides GSSAPI authentication support for Dovecot.
-
-Package: dovecot-sieve
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version}), ucf (>= 2.0020)
-Description: secure POP3/IMAP server - Sieve filters support
- Dovecot is a mail server whose major goals are security and extreme
- reliability. It tries very hard to handle all error conditions and verify
- that all data is valid, making it nearly impossible to crash. It supports
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
- fast, extensible, and portable.
- .
- This package provides Sieve filters support for Dovecot.
-
-Package: dovecot-solr
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version})
-Description: secure POP3/IMAP server - Solr support
- Dovecot is a mail server whose major goals are security and extreme
- reliability. It tries very hard to handle all error conditions and verify
- that all data is valid, making it nearly impossible to crash. It supports
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
- fast, extensible, and portable.
- .
- This package provides Solr full text search support for Dovecot.
-
-Package: dovecot-dbg
-Section: debug
-Priority: extra
-Architecture: any
-Depends: ${misc:Depends}, dovecot-core (= ${binary:Version})
-Description: secure POP3/IMAP server - debug symbols
- Dovecot is a mail server whose major goals are security and extreme
- reliability. It tries very hard to handle all error conditions and verify
- that all data is valid, making it nearly impossible to crash. It supports
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
- fast, extensible, and portable.
- .
- This package contains debug symbols for Dovecot.
-
-Package: mail-stack-delivery
-Architecture: all
-Depends: dovecot-core, dovecot-imapd, dovecot-pop3d, dovecot-managesieved,
- postfix, ${misc:Depends}
-Replaces: dovecot-postfix (<< 1:1.2.12-0ubuntu1~)
-Description: mail server delivery agent stack provided by Ubuntu server team
- Ubuntu's mail stack provides fully operational delivery with
- safe defaults and additional options. Out of the box it supports IMAP,
- POP3 and SMTP services with SASL authentication and Maildir as default
- storage engine.
- .
- This package contains configuration files for dovecot.
- .
- This package modifies postfix's configuration to integrate with dovecot
+ This package provides Lucene full text search support for Dovecot. It has been modified by Mail-in-a-Box
+ to supply a dovecot-lucene package compatible with the official ubuntu trusty dovecot-core.
diff --git a/debian/dovecot-lucene.links b/debian/dovecot-lucene.links
new file mode 100644
index 0000000..6ffcbeb
--- /dev/null
+++ b/debian/dovecot-lucene.links
@@ -0,0 +1 @@
+/usr/share/bug/dovecot-core /usr/share/bug/dovecot-lucene
diff --git a/debian/dovecot-lucene.lintian-overrides b/debian/dovecot-lucene.lintian-overrides
new file mode 100644
index 0000000..60d90fd
--- /dev/null
+++ b/debian/dovecot-lucene.lintian-overrides
@@ -0,0 +1,2 @@
+dovecot-lucene: hardening-no-fortify-functions usr/lib/dovecot/modules/lib21_fts_lucene_plugin.so
+
diff --git a/debian/dovecot-lucene.substvars b/debian/dovecot-lucene.substvars
new file mode 100644
index 0000000..ed54f36
--- /dev/null
+++ b/debian/dovecot-lucene.substvars
@@ -0,0 +1,2 @@
+shlibs:Depends=libc6 (>= 2.4), libclucene-core1 (>= 2.3.3.4), libgcc1 (>= 1:4.1.1), libstdc++6 (>= 4.1.1), libstemmer0d (>= 0+svn527)
+misc:Depends=
diff --git a/debian/dovecot-lucene.triggers b/debian/dovecot-lucene.triggers
new file mode 100644
index 0000000..3d933a5
--- /dev/null
+++ b/debian/dovecot-lucene.triggers
@@ -0,0 +1 @@
+activate register-dovecot-plugin
--- a/debian/rules
+++ b/debian/rules
@@ -40,6 +40,7 @@
--with-solr \
--with-ioloop=best \
--with-libwrap \
+ --with-lucene \
--host=$(DEB_HOST_GNU_TYPE) \
--build=$(DEB_BUILD_GNU_TYPE) \
--prefix=/usr \
@@ -95,6 +96,10 @@
dh_testroot
dh_clean -k
dh_installdirs
+ mkdir -p $(CURDIR)/debian/dovecot-lucene/usr/lib/dovecot/modules
+ mv $(CURDIR)/src/plugins/fts-lucene/.libs/* $(CURDIR)/debian/dovecot-lucene/usr/lib/dovecot/modules/
+
+rest_disabled_by_miab:
$(MAKE) install DESTDIR=$(CURDIR)/debian/dovecot-core
$(MAKE) -C $(PIGEONHOLE_DIR) install DESTDIR=$(CURDIR)/debian/dovecot-core
rm `find $(CURDIR)/debian -name '*.la'`
@@ -209,7 +214,7 @@
dh_installdocs -a
dh_installexamples -a
dh_installpam -a
- mv $(CURDIR)/debian/dovecot-core/etc/pam.d/dovecot-core $(CURDIR)/debian/dovecot-core/etc/pam.d/dovecot
+ # mv $(CURDIR)/debian/dovecot-core/etc/pam.d/dovecot-core $(CURDIR)/debian/dovecot-core/etc/pam.d/dovecot
dh_systemd_enable
dh_installinit -pdovecot-core --name=dovecot
dh_systemd_start
@@ -220,10 +225,10 @@
dh_lintian -a
dh_installchangelogs -a ChangeLog
dh_link -a
- dh_strip -a --dbg-package=dovecot-dbg
+ #dh_strip -a --dbg-package=dovecot-dbg
dh_compress -a
dh_fixperms -a
- chmod 0700 debian/dovecot-core/etc/dovecot/private
+ #chmod 0700 debian/dovecot-core/etc/dovecot/private
dh_makeshlibs -a -n
dh_installdeb -a
dh_shlibdeps -a
--- a/debian/changelog
+++ a/debian/changelog
@@ -1,3 +1,9 @@
+dovecot (1:2.2.9-1ubuntu2.1+miab1) trusty; urgency=low
+
+ * Changed to just build dovecot-lucene for Mail-in-a-box PPA
+
+ -- Joshua Tauberer <jt@occams.info> Sat, 14 May 2015 16:13:00 -0400
+
dovecot (1:2.2.9-1ubuntu2.1) trusty-security; urgency=medium
* SECURITY UPDATE: denial of service via SSL connection exhaustion
--- a/debian/copyright 2014-03-07 07:26:37.000000000 -0500
+++ b/debian/copyright 2015-05-23 18:17:42.668005535 -0400
@@ -1,3 +1,7 @@
+This package is a fork by Mail-in-a-box (https://mailinabox.email). Original
+copyright statement follows:
+----------------------------------------------------------------------------
+
This package was debianized by Jaldhar H. Vyas <jaldhar@debian.org> on
Tue, 3 Dec 2002 01:10:07 -0500.

View File

@@ -1,80 +0,0 @@
diff --git a/debian/NEWS b/debian/NEWS
index dd09744..de7b640 100644
--- a/debian/NEWS
+++ b/debian/NEWS
@@ -1,3 +1,9 @@
+postgrey (1.35-1+miab1)
+
+ Added DNSWL.org whitelisting.
+
+ -- Joshua Tauberer <jt@occams.info> Mon May 18 18:58:40 EDT 2015
+
postgrey (1.32-1) unstable; urgency=low
Postgrey is now listening to port 10023 and not 60000. The latter was an
diff --git a/debian/changelog b/debian/changelog
index 1058e15..e5e3557 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+postgrey (1.35-1+miab1) trusty; urgency=low
+
+ * Added DNSWL.org whitelisting.
+
+ -- Joshua Tauberer <jt@occams.info> Mon, 18 May 2015 21:58:40 +0000
+
postgrey (1.35-1) unstable; urgency=low
* New upstream release (Closes: 756486)
diff --git a/debian/control b/debian/control
index ce12ba6..0a82855 100644
--- a/debian/control
+++ b/debian/control
@@ -1,14 +1,11 @@
Source: postgrey
Section: mail
Priority: optional
-Maintainer: Antonio Radici <antonio@debian.org>
-Uploaders: Jon Daley <jondaley-guest@alioth.debian.org>
+Maintainer: Joshua Tauberer <jt@occams.info>
Build-Depends: debhelper (>= 7), quilt
Build-Depends-Indep: po-debconf
Standards-Version: 3.9.6
Homepage: http://postgrey.schweikert.ch/
-Vcs-Browser: http://git.debian.org/?p=collab-maint/postgrey.git
-Vcs-Git: git://git.debian.org/git/collab-maint/postgrey.git
Package: postgrey
Architecture: all
@@ -25,3 +22,6 @@ Description: greylisting implementation for Postfix
.
While Postgrey is designed for use with Postfix, it can also be used
with Exim.
+ .
+ This version has been modified by Mail-in-a-Box to whitelist senders
+ in the DNSWL.org list. See https://mailinabox.email.
diff --git a/debian/copyright b/debian/copyright
index 3cbe377..bf09b89 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -1,6 +1,10 @@
+This package is a fork by Mail-in-a-Box (https://mailinabox.email). Original
+copyright statement follows:
+----------------------------------------------------------------------------
+
This Debian package was prepared by Adrian von Bidder <cmot@debian.org> in
July 2004, then the package was adopted by Antonio Radici <antonio@dyne.org>
-in Sept 2009
+in Sept 2009.
It was downloaded from http://postgrey.schweikert.ch/
diff --git a/debian/patches/series b/debian/patches/series
index f4c5e31..3cd62b8 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,3 +1,3 @@
imported-upstream-diff
disable-transaction-logic
-
+mailinabox

View File

@@ -1,100 +0,0 @@
Description: whitelist whatever dnswl.org whitelists
.
postgrey (1.35-1+miab1) unstable; urgency=low
.
* Added DNSWL.org whitelisting.
Author: Joshua Tauberer <jt@occams.info>
--- postgrey-1.35.orig/README
+++ postgrey-1.35/README
@@ -13,7 +13,7 @@ Requirements
- BerkeleyDB (Perl Module)
- Berkeley DB >= 4.1 (Library)
- Digest::SHA (Perl Module, only for --privacy option)
-
+- Net::DNS (Perl Module)
Documentation
-------------
--- postgrey-1.35.orig/postgrey
+++ postgrey-1.35/postgrey
@@ -18,6 +18,7 @@ use Fcntl ':flock'; # import LOCK_* cons
use Sys::Hostname;
use Sys::Syslog; # used only to find out which version we use
use POSIX qw(strftime setlocale LC_ALL);
+use Net::DNS; # for DNSWL.org whitelisting
use vars qw(@ISA);
@ISA = qw(Net::Server::Multiplex);
@@ -26,6 +27,8 @@ my $VERSION = '1.35';
my $DEFAULT_DBDIR = '/var/lib/postgrey';
my $CONFIG_DIR = '/etc/postgrey';
+my $dns_resolver = Net::DNS::Resolver->new;
+
sub cidr_parse($)
{
defined $_[0] or return undef;
@@ -48,6 +51,36 @@ sub cidr_match($$$)
return ($addr & $mask) == $net;
}
+sub reverseDottedQuad {
+ # This is the sub _chkValidPublicIP from Net::DNSBL by PJ Goodwin
+ # at http://www.the42.net/net-dnsbl.
+ my ($quad) = @_;
+ if ($quad =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
+ my ($ip1,$ip2,$ip3,$ip4) = ($1, $2, $3, $4);
+ if (
+ $ip1 == 10 || #10.0.0.0/8 (10/8)
+ ($ip1 == 172 && $ip2 >= 16 && $ip2 <= 31) || #172.16.0.0/12 (172.16/12)
+ ($ip1 == 192 && $ip2 == 168) || #192.168.0.0/16 (192.168/16)
+ $quad eq '127.0.0.1' # localhost
+ ) {
+ # toss the RFC1918 specified privates
+ return undef;
+ } elsif (
+ ($ip1 <= 1 || $ip1 > 254) ||
+ ($ip2 < 0 || $ip2 > 255) ||
+ ($ip3 < 0 || $ip3 > 255) ||
+ ($ip4 < 0 || $ip4 > 255)
+ ) {
+ #invalid oct, toss it;
+ return undef;
+ }
+ my $revquad = $ip4 . "." . $ip3 . "." . $ip2 . "." . $ip1;
+ return $revquad;
+ } else { # invalid quad
+ return undef;
+ }
+}
+
sub read_clients_whitelists($)
{
my ($self) = @_;
@@ -361,6 +394,25 @@ sub smtpd_access_policy($$)
}
}
+ # whitelist clients in dnswl.org
+ my $revip = reverseDottedQuad($attr->{client_address});
+ if ($revip) { # valid IP / plausibly in DNSWL
+ my $answer = $dns_resolver->send($revip . '.list.dnswl.org');
+ if ($answer && scalar($answer->answer) > 0) {
+ my @rrs = $answer->answer;
+ if ($rrs[0]->type eq 'A' && $rrs[0]->address ne '127.0.0.255') {
+ # Address appears in DNSWL. (127.0.0.255 means we were rate-limited.)
+ my $code = $rrs[0]->address;
+ if ($code =~ /^127.0.(\d+)\.([0-3])$/) {
+ my %dnswltrust = (0 => 'legitimate', 1 => 'occasional spam', 2 => 'rare spam', 3 => 'highly unlikely to send spam');
+ $code = $2 . '/' . $dnswltrust{$2};
+ }
+ $self->mylog_action($attr, 'pass', 'client whitelisted by dnswl.org (' . $code . ')');
+ return 'DUNNO';
+ }
+ }
+ }
+
# auto whitelist clients (see below for explanation)
my ($cawl_db, $cawl_key, $cawl_count, $cawl_last);
if($self->{postgrey}{awl_clients}) {

View File

@@ -1,7 +1,7 @@
Mail-in-a-Box Security Guide Mail-in-a-Box Security Guide
============================ ============================
Mail-in-a-Box turns a fresh Ubuntu 14.04 LTS 64-bit machine into a mail server appliance by installing and configuring various components. 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.
This page documents the security features of Mail-in-a-Box. The term “box” is used below to mean a configured Mail-in-a-Box. This page documents the security features of Mail-in-a-Box. The term “box” is used below to mean a configured Mail-in-a-Box.
@@ -39,9 +39,8 @@ These services are protected by [TLS](https://en.wikipedia.org/wiki/Transport_La
The services all follow these rules: The services all follow these rules:
* TLS certificates are generated with 2048-bit RSA keys and SHA-256 fingerprints. The box provides a self-signed certificate by default. The [setup guide](https://mailinabox.email/guide.html) explains how to verify the certificate fingerprint on first login. Users are encouraged to replace the certificate with a proper CA-signed one. ([source](setup/ssl.sh)) * TLS certificates are generated with 2048-bit RSA keys and SHA-256 fingerprints. The box provides a self-signed certificate by default. The [setup guide](https://mailinabox.email/guide.html) explains how to verify the certificate fingerprint on first login. Users are encouraged to replace the certificate with a proper CA-signed one. ([source](setup/ssl.sh))
* Only TLSv1, TLSv1.1 and TLSv1.2 are offered (the older SSL protocols are not offered). * Only TLSv1.2+ are offered (the older SSL protocols are not offered).
* HTTPS, IMAP, and POP track the [Mozilla Intermediate Ciphers Recommendation](https://wiki.mozilla.org/Security/Server_Side_TLS), balancing security with supporting a wide range of mail clients. Diffie-Hellman ciphers use a 2048-bit key for forward secrecy. For more details, see the [output of SSLyze for these ports](tests/tls_results.txt). * We track the [Mozilla Intermediate Ciphers Recommendation](https://wiki.mozilla.org/Security/Server_Side_TLS), balancing security with supporting a wide range of mail clients. Diffie-Hellman ciphers use a 2048-bit key for forward secrecy. For more details, see the [output of SSLyze for these ports](tests/tls_results.txt).
* SMTP (port 25) uses the Postfix medium grade ciphers and SMTP Submission (port 587) uses the Postfix high grade ciphers ([more info](http://www.postfix.org/postconf.5.html#smtpd_tls_mandatory_ciphers)).
Additionally: Additionally:
@@ -95,7 +94,7 @@ Domain policy records allow recipient MTAs to detect when the _domain_ part of o
### User Policy ### User Policy
While domain policy records prevent other servers from sending mail with a "From:" header that matches a domain hosted on the box (see above), those policy records do not guarnatee that the user portion of the sender email address matches the actual sender. In enterprise environments where the box may host the mail of untrusted users, it is important to guard against users impersonating other users. While domain policy records prevent other servers from sending mail with a "From:" header that matches a domain hosted on the box (see above), those policy records do not guarantee that the user portion of the sender email address matches the actual sender. In enterprise environments where the box may host the mail of untrusted users, it is important to guard against users impersonating other users.
The box restricts the envelope sender address (also called the return path or MAIL FROM address --- this is different from the "From:" header) that users may put into outbound mail. The envelope sender address must be either their own email address (their SMTP login username) or any alias that they are listed as a permitted sender of. (There is currently no restriction on the contents of the "From:" header.) The box restricts the envelope sender address (also called the return path or MAIL FROM address --- this is different from the "From:" header) that users may put into outbound mail. The envelope sender address must be either their own email address (their SMTP login username) or any alias that they are listed as a permitted sender of. (There is currently no restriction on the contents of the "From:" header.)

View File

@@ -7,13 +7,42 @@
######################################################### #########################################################
if [ -z "$TAG" ]; then if [ -z "$TAG" ]; then
TAG=v0.26 # 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/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then
# This machine is running Ubuntu 18.04.
TAG=v0.47
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 Ubuntu 18.04 or Ubuntu 14.04."
exit 1
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.
@@ -44,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

@@ -21,11 +21,17 @@ mkdir -p $STORAGE_ROOT/mail/dkim
# Not quite sure why. # Not quite sure why.
echo "127.0.0.1" > /etc/opendkim/TrustedHosts echo "127.0.0.1" > /etc/opendkim/TrustedHosts
# We need to at least create these files, since we reference them later.
# Otherwise, opendkim startup will fail
touch /etc/opendkim/KeyTable
touch /etc/opendkim/SigningTable
if grep -q "ExternalIgnoreList" /etc/opendkim.conf; then if grep -q "ExternalIgnoreList" /etc/opendkim.conf; then
true # already done #NODOC true # already done #NODOC
else else
# Add various configuration options to the end of `opendkim.conf`. # Add various configuration options to the end of `opendkim.conf`.
cat >> /etc/opendkim.conf << EOF; cat >> /etc/opendkim.conf << EOF;
Canonicalization relaxed/simple
MinimumKeyBits 1024 MinimumKeyBits 1024
ExternalIgnoreList refile:/etc/opendkim/TrustedHosts ExternalIgnoreList refile:/etc/opendkim/TrustedHosts
InternalHosts refile:/etc/opendkim/TrustedHosts InternalHosts refile:/etc/opendkim/TrustedHosts
@@ -75,6 +81,9 @@ tools/editconf.py /etc/postfix/main.cf \
non_smtpd_milters=\$smtpd_milters \ non_smtpd_milters=\$smtpd_milters \
milter_default_action=accept milter_default_action=accept
# We need to explicitly enable the opendmarc service, or it will not start
hide_output systemctl enable opendmarc
# Restart services. # Restart services.
restart_service opendkim restart_service opendkim
restart_service opendmarc restart_service opendmarc

View File

@@ -26,6 +26,7 @@ cat > /etc/nsd/nsd.conf << EOF;
# Do not edit. Overwritten by Mail-in-a-Box setup. # Do not edit. Overwritten by Mail-in-a-Box setup.
server: server:
hide-version: yes hide-version: yes
logfile: "/var/log/nsd.log"
# identify the server (CH TXT ID.SERVER entry). # identify the server (CH TXT ID.SERVER entry).
identity: "" identity: ""
@@ -41,6 +42,18 @@ server:
EOF EOF
# Add log rotation
cat > /etc/logrotate.d/nsd <<EOF;
/var/log/nsd.log {
weekly
missingok
rotate 12
compress
delaycompress
notifempty
}
EOF
# Since we have bind9 listening on localhost for locally-generated # Since we have bind9 listening on localhost for locally-generated
# DNS queries that require a recursive nameserver, and the system # DNS queries that require a recursive nameserver, and the system
# might have other network interfaces for e.g. tunnelling, we have # might have other network interfaces for e.g. tunnelling, we have

View File

@@ -4,9 +4,9 @@ if [ -z "`tools/mail.py user`" ]; then
# aren't any yet, it'll be empty. # aren't any yet, it'll be empty.
# If we didn't ask for an email address at the start, do so now. # If we didn't ask for an email address at the start, do so now.
if [ -z "$EMAIL_ADDR" ]; then if [ -z "${EMAIL_ADDR:-}" ]; then
# In an interactive shell, ask the user for an email address. # In an interactive shell, ask the user for an email address.
if [ -z "$NONINTERACTIVE" ]; then if [ -z "${NONINTERACTIVE:-}" ]; then
input_box "Mail Account" \ input_box "Mail Account" \
"Let's create your first mail account. "Let's create your first mail account.
\n\nWhat email address do you want?" \ \n\nWhat email address do you want?" \
@@ -47,7 +47,7 @@ if [ -z "`tools/mail.py user`" ]; then
fi fi
# Create the user's mail account. This will ask for a password if none was given above. # Create the user's mail account. This will ask for a password if none was given above.
tools/mail.py user add $EMAIL_ADDR $EMAIL_PW tools/mail.py user add $EMAIL_ADDR ${EMAIL_PW:-}
# Make it an admin. # Make it an admin.
hide_output tools/mail.py user make-admin $EMAIL_ADDR hide_output tools/mail.py user make-admin $EMAIL_ADDR

View File

@@ -1,3 +1,9 @@
# Turn on "strict mode." See http://redsymbol.net/articles/unofficial-bash-strict-mode/.
# -e: exit if any command unexpectedly fails.
# -u: exit if we have a variable typo.
# -o pipefail: don't ignore errors in the non-last command in a pipeline
set -euo pipefail
function hide_output { function hide_output {
# This function hides the output of a command unless the command fails # This function hides the output of a command unless the command fails
# and returns a non-zero exit code. # and returns a non-zero exit code.
@@ -5,11 +11,14 @@ function hide_output {
# Get a temporary file. # Get a temporary file.
OUTPUT=$(tempfile) OUTPUT=$(tempfile)
# Execute command, redirecting stderr/stdout to the temporary file. # Execute command, redirecting stderr/stdout to the temporary file. Since we
# check the return code ourselves, disable 'set -e' temporarily.
set +e
$@ &> $OUTPUT $@ &> $OUTPUT
E=$?
set -e
# If the command failed, show the output that was captured in the temporary file. # If the command failed, show the output that was captured in the temporary file.
E=$?
if [ $E != 0 ]; then if [ $E != 0 ]; then
# Something failed. # Something failed.
echo echo
@@ -48,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
@@ -75,7 +75,7 @@ function get_publicip_from_web_service {
# #
# Pass '4' or '6' as an argument to this function to specify # Pass '4' or '6' as an argument to this function to specify
# what type of address to get (IPv4, IPv6). # what type of address to get (IPv4, IPv6).
curl -$1 --fail --silent --max-time 15 icanhazip.com 2>/dev/null curl -$1 --fail --silent --max-time 15 icanhazip.com 2>/dev/null || /bin/true
} }
function get_default_privateip { function get_default_privateip {
@@ -118,7 +118,7 @@ function get_default_privateip {
if [ "$1" == "6" ]; then target=2001:4860:4860::8888; fi if [ "$1" == "6" ]; then target=2001:4860:4860::8888; fi
# Get the route information. # Get the route information.
route=$(ip -$1 -o route get $target | grep -v unreachable) route=$(ip -$1 -o route get $target 2>/dev/null | grep -v unreachable)
# Parse the address out of the route information. # Parse the address out of the route information.
address=$(echo $route | sed "s/.* src \([^ ]*\).*/\1/") address=$(echo $route | sed "s/.* src \([^ ]*\).*/\1/")
@@ -131,13 +131,19 @@ function get_default_privateip {
fi fi
echo $address echo $address
} }
function ufw_allow { function ufw_allow {
if [ -z "$DISABLE_FIREWALL" ]; then if [ -z "${DISABLE_FIREWALL:-}" ]; then
# ufw has completely unhelpful output # ufw has completely unhelpful output
ufw allow $1 > /dev/null; ufw allow "$1" > /dev/null;
fi
}
function ufw_limit {
if [ -z "${DISABLE_FIREWALL:-}" ]; then
# ufw has completely unhelpful output
ufw limit "$1" > /dev/null;
fi fi
} }
@@ -154,10 +160,13 @@ function input_box {
# input_box "title" "prompt" "defaultvalue" VARIABLE # input_box "title" "prompt" "defaultvalue" VARIABLE
# The user's input will be stored in the variable VARIABLE. # The user's input will be stored in the variable VARIABLE.
# The exit code from dialog will be stored in VARIABLE_EXITCODE. # The exit code from dialog will be stored in VARIABLE_EXITCODE.
# Temporarily turn off 'set -e' because we need the dialog return code.
declare -n result=$4 declare -n result=$4
declare -n result_code=$4_EXITCODE declare -n result_code=$4_EXITCODE
set +e
result=$(dialog --stdout --title "$1" --inputbox "$2" 0 0 "$3") result=$(dialog --stdout --title "$1" --inputbox "$2" 0 0 "$3")
result_code=$? result_code=$?
set -e
} }
function input_menu { function input_menu {
@@ -167,8 +176,10 @@ function input_menu {
declare -n result=$4 declare -n result=$4
declare -n result_code=$4_EXITCODE declare -n result_code=$4_EXITCODE
local IFS=^$'\n' local IFS=^$'\n'
set +e
result=$(dialog --stdout --title "$1" --menu "$2" 0 0 0 $3) result=$(dialog --stdout --title "$1" --menu "$2" 0 0 0 $3)
result_code=$? result_code=$?
set -e
} }
function wget_verify { function wget_verify {
@@ -179,7 +190,7 @@ function wget_verify {
DEST=$3 DEST=$3
CHECKSUM="$HASH $DEST" CHECKSUM="$HASH $DEST"
rm -f $DEST rm -f $DEST
wget -q -O $DEST $URL || exit 1 hide_output wget -O $DEST $URL
if ! echo "$CHECKSUM" | sha1sum --check --strict > /dev/null; then if ! echo "$CHECKSUM" | sha1sum --check --strict > /dev/null; then
echo "------------------------------------------------------------" echo "------------------------------------------------------------"
echo "Download of $URL did not match expected checksum." echo "Download of $URL did not match expected checksum."

View File

@@ -26,7 +26,7 @@ source /etc/mailinabox.conf # load global vars
echo "Installing Dovecot (IMAP server)..." echo "Installing Dovecot (IMAP server)..."
apt_install \ apt_install \
dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-sqlite sqlite3 \ dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-sqlite sqlite3 \
dovecot-sieve dovecot-managesieved dovecot-lucene dovecot-sieve dovecot-managesieved
# The `dovecot-imapd`, `dovecot-pop3d`, and `dovecot-lmtpd` packages automatically # The `dovecot-imapd`, `dovecot-pop3d`, and `dovecot-lmtpd` packages automatically
# enable IMAP, POP and LMTP protocols. # enable IMAP, POP and LMTP protocols.
@@ -78,16 +78,16 @@ tools/editconf.py /etc/dovecot/conf.d/10-auth.conf \
"auth_mechanisms=plain login" "auth_mechanisms=plain login"
# Enable SSL, specify the location of the SSL certificate and private key files. # Enable SSL, specify the location of the SSL certificate and private key files.
# Disable obsolete SSL protocols and allow only good ciphers per http://baldric.net/2013/12/07/tls-ciphers-in-postfix-and-dovecot/. # Use Mozilla's "Intermediate" recommendations at https://ssl-config.mozilla.org/#server=dovecot&server-version=2.2.33&config=intermediate&openssl-version=1.1.1,
# Enable strong ssl dh parameters # except that the current version of Dovecot does not have a TLSv1.3 setting, so we only use TLSv1.2.
tools/editconf.py /etc/dovecot/conf.d/10-ssl.conf \ tools/editconf.py /etc/dovecot/conf.d/10-ssl.conf \
ssl=required \ ssl=required \
"ssl_cert=<$STORAGE_ROOT/ssl/ssl_certificate.pem" \ "ssl_cert=<$STORAGE_ROOT/ssl/ssl_certificate.pem" \
"ssl_key=<$STORAGE_ROOT/ssl/ssl_private_key.pem" \ "ssl_key=<$STORAGE_ROOT/ssl/ssl_private_key.pem" \
"ssl_protocols=!SSLv3 !SSLv2" \ "ssl_protocols=TLSv1.2" \
"ssl_cipher_list=ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS" \ "ssl_cipher_list=ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384" \
"ssl_prefer_server_ciphers = yes" \ "ssl_prefer_server_ciphers=no" \
"ssl_dh_parameters_length = 2048" "ssl_dh_parameters_length=2048"
# Disable in-the-clear IMAP/POP because there is no reason for a user to transmit # Disable in-the-clear IMAP/POP because there is no reason for a user to transmit
# login credentials outside of an encrypted connection. Only the over-TLS versions # login credentials outside of an encrypted connection. Only the over-TLS versions
@@ -112,17 +112,6 @@ tools/editconf.py /etc/dovecot/conf.d/20-imap.conf \
tools/editconf.py /etc/dovecot/conf.d/20-pop3.conf \ tools/editconf.py /etc/dovecot/conf.d/20-pop3.conf \
pop3_uidl_format="%08Xu%08Xv" pop3_uidl_format="%08Xu%08Xv"
# Full Text Search - Enable full text search of mail using dovecot's lucene plugin,
# which *we* package and distribute (dovecot-lucene package).
tools/editconf.py /etc/dovecot/conf.d/10-mail.conf \
mail_plugins="\$mail_plugins fts fts_lucene"
cat > /etc/dovecot/conf.d/90-plugin-fts.conf << EOF;
plugin {
fts = lucene
fts_lucene = whitespace_chars=@.
}
EOF
# ### LDA (LMTP) # ### LDA (LMTP)
# Enable Dovecot's LDA service with the LMTP protocol. It will listen # Enable Dovecot's LDA service with the LMTP protocol. It will listen
@@ -146,6 +135,14 @@ service lmtp {
} }
} }
# Enable imap-login on localhost to allow the user_external plugin
# for Nextcloud to do imap authentication. (See #1577)
service imap-login {
inet_listener imap {
address = 127.0.0.1
port = 143
}
}
protocol imap { protocol imap {
mail_max_userip_connections = 20 mail_max_userip_connections = 20
} }

View File

@@ -41,16 +41,8 @@ source /etc/mailinabox.conf # load global vars
# always will. # always will.
# * `ca-certificates`: A trust store used to squelch postfix warnings about # * `ca-certificates`: A trust store used to squelch postfix warnings about
# untrusted opportunistically-encrypted connections. # untrusted opportunistically-encrypted connections.
#
# postgrey is going to come in via the Mail-in-a-Box PPA, which publishes
# a modified version of postgrey that lets senders whitelisted by dnswl.org
# pass through without being greylisted. So please note [dnswl's license terms](https://www.dnswl.org/?page_id=9):
# > Every user with more than 100000 queries per day on the public nameserver
# > infrastructure and every commercial vendor of dnswl.org data (eg through
# > anti-spam solutions) must register with dnswl.org and purchase a subscription.
echo "Installing Postfix (SMTP server)..." echo "Installing Postfix (SMTP server)..."
apt_install postfix postfix-pcre postgrey ca-certificates apt_install postfix postfix-sqlite postfix-pcre postgrey ca-certificates
# ### Basic Settings # ### Basic Settings
@@ -81,13 +73,14 @@ tools/editconf.py /etc/postfix/main.cf \
# Enable the 'submission' port 587 smtpd server and tweak its settings. # Enable the 'submission' port 587 smtpd server and tweak its settings.
# #
# * Enable authentication. It's disabled globally so that it is disabled on port 25,
# so we need to explicitly enable it here.
# * Do not add the OpenDMAC Authentication-Results header. That should only be added # * Do not add the OpenDMAC Authentication-Results header. That should only be added
# on incoming mail. Omit the OpenDMARC milter by re-setting smtpd_milters to the # on incoming mail. Omit the OpenDMARC milter by re-setting smtpd_milters to the
# OpenDKIM milter only. See dkim.sh. # OpenDKIM milter only. See dkim.sh.
# * Even though we dont allow auth over non-TLS connections (smtpd_tls_auth_only below, and without auth the client cant # * Even though we dont allow auth over non-TLS connections (smtpd_tls_auth_only below, and without auth the client cant
# send outbound mail), don't allow non-TLS mail submission on this port anyway to prevent accidental misconfiguration. # send outbound mail), don't allow non-TLS mail submission on this port anyway to prevent accidental misconfiguration.
# * Require the best ciphers for incoming connections per http://baldric.net/2013/12/07/tls-ciphers-in-postfix-and-dovecot/. # Setting smtpd_tls_security_level=encrypt also triggers the use of the 'mandatory' settings below.
# By putting this setting here we leave opportunistic TLS on incoming mail at default cipher settings (any cipher is better than none).
# * Give it a different name in syslog to distinguish it from the port 25 smtpd server. # * Give it a different name in syslog to distinguish it from the port 25 smtpd server.
# * Add a new cleanup service specific to the submission service ('authclean') # * Add a new cleanup service specific to the submission service ('authclean')
# that filters out privacy-sensitive headers on mail being sent out by # that filters out privacy-sensitive headers on mail being sent out by
@@ -95,10 +88,10 @@ tools/editconf.py /etc/postfix/main.cf \
# emails but we turn this off by setting nested_header_checks empty. # emails but we turn this off by setting nested_header_checks empty.
tools/editconf.py /etc/postfix/master.cf -s -w \ tools/editconf.py /etc/postfix/master.cf -s -w \
"submission=inet n - - - - smtpd "submission=inet n - - - - smtpd
-o smtpd_sasl_auth_enable=yes
-o syslog_name=postfix/submission -o syslog_name=postfix/submission
-o smtpd_milters=inet:127.0.0.1:8891 -o smtpd_milters=inet:127.0.0.1:8891
-o smtpd_tls_security_level=encrypt -o smtpd_tls_security_level=encrypt
-o smtpd_tls_ciphers=high -o smtpd_tls_exclude_ciphers=aNULL,DES,3DES,MD5,DES+MD5,RC4 -o smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
-o cleanup_service_name=authclean" \ -o cleanup_service_name=authclean" \
"authclean=unix n - - - 0 cleanup "authclean=unix n - - - 0 cleanup
-o header_checks=pcre:/etc/postfix/outgoing_mail_header_filters -o header_checks=pcre:/etc/postfix/outgoing_mail_header_filters
@@ -113,20 +106,35 @@ cp conf/postfix_outgoing_mail_header_filters /etc/postfix/outgoing_mail_header_f
sed -i "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" /etc/postfix/outgoing_mail_header_filters sed -i "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" /etc/postfix/outgoing_mail_header_filters
sed -i "s/PUBLIC_IP/$PUBLIC_IP/" /etc/postfix/outgoing_mail_header_filters sed -i "s/PUBLIC_IP/$PUBLIC_IP/" /etc/postfix/outgoing_mail_header_filters
# Enable TLS on these and all other connections (i.e. ports 25 *and* 587) and # Enable TLS on incoming connections. It is not required on port 25, allowing for opportunistic
# require TLS before a user is allowed to authenticate. This also makes # encryption. On port 587 it is mandatory (see above). Shared and non-shared settings are
# opportunistic TLS available on *incoming* mail. # given here. Shared settings include:
# Set stronger DH parameters, which via openssl tend to default to 1024 bits # * Require TLS before a user is allowed to authenticate.
# (see ssl.sh). # * Set the path to the server TLS certificate and 2048-bit DH parameters for old DH ciphers.
# For port 25 only:
# * Disable extremely old versions of TLS and extremely unsafe ciphers, but some mail servers out in
# the world are very far behind and if we disable too much, they may not be able to use TLS and
# won't fall back to cleartext. So we don't disable too much. smtpd_tls_exclude_ciphers applies to
# both port 25 and port 587, but because we override the cipher list for both, it probably isn't used.
# Use Mozilla's "Old" recommendations at https://ssl-config.mozilla.org/#server=postfix&server-version=3.3.0&config=old&openssl-version=1.1.1
# For port 587 (via the 'mandatory' settings):
# * Use Mozilla's "Intermediate" TLS recommendations from https://ssl-config.mozilla.org/#server=postfix&server-version=3.3.0&config=intermediate&openssl-version=1.1.1
# using and overriding the "high" cipher list so we don't conflict with the more permissive settings for port 25.
tools/editconf.py /etc/postfix/main.cf \ tools/editconf.py /etc/postfix/main.cf \
smtpd_tls_security_level=may\ smtpd_tls_security_level=may\
smtpd_tls_auth_only=yes \ smtpd_tls_auth_only=yes \
smtpd_tls_cert_file=$STORAGE_ROOT/ssl/ssl_certificate.pem \ smtpd_tls_cert_file=$STORAGE_ROOT/ssl/ssl_certificate.pem \
smtpd_tls_key_file=$STORAGE_ROOT/ssl/ssl_private_key.pem \ smtpd_tls_key_file=$STORAGE_ROOT/ssl/ssl_private_key.pem \
smtpd_tls_dh1024_param_file=$STORAGE_ROOT/ssl/dh2048.pem \ smtpd_tls_dh1024_param_file=$STORAGE_ROOT/ssl/dh2048.pem \
smtpd_tls_protocols=\!SSLv2,\!SSLv3 \ smtpd_tls_protocols="!SSLv2,!SSLv3" \
smtpd_tls_ciphers=medium \ smtpd_tls_ciphers=medium \
tls_medium_cipherlist=ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA \
smtpd_tls_exclude_ciphers=aNULL,RC4 \ smtpd_tls_exclude_ciphers=aNULL,RC4 \
smtpd_tls_mandatory_protocols="!SSLv2,!SSLv3,!TLSv1,!TLSv1.1" \
smtpd_tls_mandatory_ciphers=high \
tls_high_cipherlist=ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 \
smtpd_tls_mandatory_exclude_ciphers=aNULL,DES,3DES,MD5,DES+MD5,RC4 \
tls_preempt_cipherlist=no \
smtpd_tls_received_header=yes smtpd_tls_received_header=yes
# Prevent non-authenticated users from sending mail that requires being # Prevent non-authenticated users from sending mail that requires being
@@ -148,13 +156,17 @@ tools/editconf.py /etc/postfix/main.cf \
# offers it, otherwise it will transmit the message in the clear. Postfix will # offers it, otherwise it will transmit the message in the clear. Postfix will
# accept whatever SSL certificate the remote end provides. Opportunistic TLS # accept whatever SSL certificate the remote end provides. Opportunistic TLS
# protects against passive easvesdropping (but not man-in-the-middle attacks). # protects against passive easvesdropping (but not man-in-the-middle attacks).
# Since we'd rather have poor encryption than none at all, we use Mozilla's
# "Old" recommendations at https://ssl-config.mozilla.org/#server=postfix&server-version=3.3.0&config=old&openssl-version=1.1.1
# for opportunistic encryption but "Intermediate" recommendations when DANE
# is used (see next and above). The cipher lists are set above.
# DANE takes this a step further: # DANE takes this a step further:
#
# Postfix queries DNS for the TLSA record on the destination MX host. If no TLSA records are found, # Postfix queries DNS for the TLSA record on the destination MX host. If no TLSA records are found,
# then opportunistic TLS is used. Otherwise the server certificate must match the TLSA records # then opportunistic TLS is used. Otherwise the server certificate must match the TLSA records
# or else the mail bounces. TLSA also requires DNSSEC on the MX host. Postfix doesn't do DNSSEC # or else the mail bounces. TLSA also requires DNSSEC on the MX host. Postfix doesn't do DNSSEC
# itself but assumes the system's nameserver does and reports DNSSEC status. Thus this also # itself but assumes the system's nameserver does and reports DNSSEC status. Thus this also
# relies on our local bind9 server being present and `smtp_dns_support_level=dnssec`. # relies on our local DNS server (see system.sh) and `smtp_dns_support_level=dnssec`.
# #
# The `smtp_tls_CAfile` is superflous, but it eliminates warnings in the logs about untrusted certs, # The `smtp_tls_CAfile` is superflous, but it eliminates warnings in the logs about untrusted certs,
# which we don't care about seeing because Postfix is doing opportunistic TLS anyway. Better to encrypt, # which we don't care about seeing because Postfix is doing opportunistic TLS anyway. Better to encrypt,
@@ -162,11 +174,12 @@ tools/editconf.py /etc/postfix/main.cf \
# now see notices about trusted certs. The CA file is provided by the package `ca-certificates`. # now see notices about trusted certs. The CA file is provided by the package `ca-certificates`.
tools/editconf.py /etc/postfix/main.cf \ tools/editconf.py /etc/postfix/main.cf \
smtp_tls_protocols=\!SSLv2,\!SSLv3 \ smtp_tls_protocols=\!SSLv2,\!SSLv3 \
smtp_tls_mandatory_protocols=\!SSLv2,\!SSLv3 \
smtp_tls_ciphers=medium \ smtp_tls_ciphers=medium \
smtp_tls_exclude_ciphers=aNULL,RC4 \ smtp_tls_exclude_ciphers=aNULL,RC4 \
smtp_tls_security_level=dane \ smtp_tls_security_level=dane \
smtp_dns_support_level=dnssec \ smtp_dns_support_level=dnssec \
smtp_tls_mandatory_protocols="!SSLv2,!SSLv3,!TLSv1,!TLSv1.1" \
smtp_tls_mandatory_ciphers=high \
smtp_tls_CAfile=/etc/ssl/certs/ca-certificates.crt \ smtp_tls_CAfile=/etc/ssl/certs/ca-certificates.crt \
smtp_tls_loglevel=2 smtp_tls_loglevel=2
@@ -178,8 +191,11 @@ tools/editconf.py /etc/postfix/main.cf \
# #
# In a basic setup we would pass mail directly to Dovecot by setting # In a basic setup we would pass mail directly to Dovecot by setting
# virtual_transport to `lmtp:unix:private/dovecot-lmtp`. # virtual_transport to `lmtp:unix:private/dovecot-lmtp`.
#
tools/editconf.py /etc/postfix/main.cf virtual_transport=lmtp:[127.0.0.1]:10025 tools/editconf.py /etc/postfix/main.cf virtual_transport=lmtp:[127.0.0.1]:10025
# Because of a spampd bug, limit the number of recipients in each connection.
# See https://github.com/mail-in-a-box/mailinabox/issues/1523.
tools/editconf.py /etc/postfix/main.cf lmtp_destination_recipient_limit=1
# Who can send mail to us? Some basic filters. # Who can send mail to us? Some basic filters.
# #
@@ -212,6 +228,33 @@ tools/editconf.py /etc/postfix/main.cf \
tools/editconf.py /etc/default/postgrey \ tools/editconf.py /etc/default/postgrey \
POSTGREY_OPTS=\"'--inet=127.0.0.1:10023 --delay=180'\" POSTGREY_OPTS=\"'--inet=127.0.0.1:10023 --delay=180'\"
# We are going to setup a newer whitelist for postgrey, the version included in the distribution is old
cat > /etc/cron.daily/mailinabox-postgrey-whitelist << EOF;
#!/bin/bash
# Mail-in-a-Box
# check we have a postgrey_whitelist_clients file and that it is not older than 28 days
if [ ! -f /etc/postgrey/whitelist_clients ] || find /etc/postgrey/whitelist_clients -mtime +28 | grep -q '.' ; then
# ok we need to update the file, so lets try to fetch it
if curl https://postgrey.schweikert.ch/pub/postgrey_whitelist_clients --output /tmp/postgrey_whitelist_clients -sS --fail > /dev/null 2>&1 ; then
# if fetching hasn't failed yet then check it is a plain text file
# curl manual states that --fail sometimes still produces output
# this final check will at least check the output is not html
# before moving it into place
if [ "\$(file -b --mime-type /tmp/postgrey_whitelist_clients)" == "text/plain" ]; then
mv /tmp/postgrey_whitelist_clients /etc/postgrey/whitelist_clients
service postgrey restart
else
rm /tmp/postgrey_whitelist_clients
fi
fi
fi
EOF
chmod +x /etc/cron.daily/mailinabox-postgrey-whitelist
/etc/cron.daily/mailinabox-postgrey-whitelist
# Increase the message size limit from 10MB to 128MB. # Increase the message size limit from 10MB to 128MB.
# The same limit is specified in nginx.conf for mail submitted via webmail and Z-Push. # The same limit is specified in nginx.conf for mail submitted via webmail and Z-Push.
tools/editconf.py /etc/postfix/main.cf \ tools/editconf.py /etc/postfix/main.cf \

View File

@@ -65,11 +65,15 @@ service auth {
} }
EOF EOF
# And have Postfix use that service. # And have Postfix use that service. We *disable* it here
# so that authentication is not permitted on port 25 (which
# does not run DKIM on relayed mail, so outbound mail isn't
# correct, see #830), but we enable it specifically for the
# submission port.
tools/editconf.py /etc/postfix/main.cf \ tools/editconf.py /etc/postfix/main.cf \
smtpd_sasl_type=dovecot \ smtpd_sasl_type=dovecot \
smtpd_sasl_path=private/auth \ smtpd_sasl_path=private/auth \
smtpd_sasl_auth_enable=yes smtpd_sasl_auth_enable=no
# ### Sender Validation # ### Sender Validation

View File

@@ -6,60 +6,51 @@ echo "Installing Mail-in-a-Box system management daemon..."
# DEPENDENCIES # DEPENDENCIES
# We used to install management daemon-related Python packages
# directly to /usr/local/lib. We moved to a virtualenv because
# these packages might conflict with apt-installed packages.
# We may have a lingering version of acme that conflcits with
# certbot, which we're about to install below, so remove it
# first. Once acme is installed by an apt package, this might
# break the package version and `apt-get install --reinstall python3-acme`
# might be needed in that case.
while [ -d /usr/local/lib/python3.4/dist-packages/acme ]; do
pip3 uninstall -y acme;
done
# duplicity is used to make backups of user data. It uses boto # duplicity is used to make backups of user data. It uses boto
# (via Python 2) to do backups to AWS S3. boto from the Ubuntu # (via Python 2) to do backups to AWS S3. boto from the Ubuntu
# package manager is too out-of-date -- it doesn't support the newer # package manager is too out-of-date -- it doesn't support the newer
# S3 api used in some regions, which breaks backups to those regions. # S3 api used in some regions, which breaks backups to those regions.
# See #627, #653. # See #627, #653.
apt_install duplicity python-pip #
# virtualenv is used to isolate the Python 3 packages we
# install via pip from the system-installed packages.
#
# certbot installs EFF's certbot which we use to
# provision free TLS certificates.
apt_install duplicity python-pip virtualenv certbot
hide_output pip2 install --upgrade boto hide_output pip2 install --upgrade boto
# These are required to build/install the cryptography Python package
# used by our management daemon.
apt_install python-virtualenv build-essential libssl-dev libffi-dev python3-dev
# Create a virtualenv for the installation of Python 3 packages # Create a virtualenv for the installation of Python 3 packages
# used by the management daemon. # used by the management daemon.
inst_dir=/usr/local/lib/mailinabox inst_dir=/usr/local/lib/mailinabox
mkdir -p $inst_dir mkdir -p $inst_dir
venv=$inst_dir/env venv=$inst_dir/env
if [ ! -d $venv ]; then if [ ! -d $venv ]; then
virtualenv -ppython3 $venv hide_output virtualenv -ppython3 $venv
fi fi
# pip<6.1 + setuptools>=34 had a problem with packages that # Upgrade pip because the Ubuntu-packaged version is out of date.
# try to update setuptools during installation, like cryptography.
# See https://github.com/pypa/pip/issues/4253. The Ubuntu 14.04
# package versions are pip 1.5.4 and setuptools 3.3. When we used to
# instal cryptography system-wide under those versions, it updated
# setuptools to version 34, which created the conflict, and
# then pip gets permanently broken with errors like
# "ImportError: No module named 'packaging'".
#
# Let's test for the error:
if ! python3 -c "from pkg_resources import load_entry_point" 2&> /dev/null; then
# This system seems to be broken already.
echo "Fixing broken pip and setuptools..."
rm -rf /usr/local/lib/python3.4/dist-packages/{pkg_resources,setuptools}*
apt-get install --reinstall python3-setuptools python3-pip python3-pkg-resources
fi
#
# The easiest work-around on systems that aren't already broken is
# to upgrade pip (to >=9.0.1) and setuptools (to >=34.1) individually
# before we install any package that tries to update setuptools.
hide_output $venv/bin/pip install --upgrade pip hide_output $venv/bin/pip install --upgrade pip
hide_output $venv/bin/pip install --upgrade setuptools
# Install other Python 3 packages used by the management daemon. # Install other Python 3 packages used by the management daemon.
# The first line is the packages that Josh maintains himself! # The first line is the packages that Josh maintains himself!
# NOTE: email_validator is repeated in setup/questions.sh, so please keep the versions synced. # NOTE: email_validator is repeated in setup/questions.sh, so please keep the versions synced.
# Force acme to be updated because it seems to need it after the
# pip/setuptools breakage (see above) and the ACME protocol may
# have changed (I got an error on one of my systems).
hide_output $venv/bin/pip install --upgrade \ hide_output $venv/bin/pip install --upgrade \
rtyaml "email_validator>=1.0.0" "free_tls_certificates>=0.1.3" "exclusiveprocess" \ rtyaml "email_validator>=1.0.0" "exclusiveprocess" \
flask dnspython python-dateutil \ flask dnspython python-dateutil \
"idna>=2.0.0" "cryptography>=1.0.2" acme boto psutil "idna>=2.0.0" "cryptography==2.2.2" boto psutil
# CONFIGURATION # CONFIGURATION
@@ -96,28 +87,25 @@ rm -f /tmp/bootstrap.zip
# Create an init script to start the management daemon and keep it # Create an init script to start the management daemon and keep it
# running after a reboot. # running after a reboot.
rm -f /usr/local/bin/mailinabox-daemon # old path
cat > $inst_dir/start <<EOF; cat > $inst_dir/start <<EOF;
#!/bin/bash #!/bin/bash
source $venv/bin/activate source $venv/bin/activate
python `pwd`/management/daemon.py exec python `pwd`/management/daemon.py
EOF EOF
chmod +x $inst_dir/start chmod +x $inst_dir/start
rm -f /etc/init.d/mailinabox cp --remove-destination conf/mailinabox.service /lib/systemd/system/mailinabox.service # target was previously a symlink so remove it first
ln -s $(pwd)/conf/management-initscript /etc/init.d/mailinabox hide_output systemctl link -f /lib/systemd/system/mailinabox.service
hide_output update-rc.d mailinabox defaults hide_output systemctl daemon-reload
hide_output systemctl enable mailinabox.service
# Remove old files we no longer use.
rm -f /etc/cron.daily/mailinabox-backup
rm -f /etc/cron.daily/mailinabox-statuschecks
# 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

@@ -137,6 +137,51 @@ def migration_10(env):
shutil.move(sslcert, newname) shutil.move(sslcert, newname)
os.rmdir(d) os.rmdir(d)
def migration_11(env):
# Archive the old Let's Encrypt account directory managed by free_tls_certificates
# because we'll use that path now for the directory managed by certbot.
try:
old_path = os.path.join(env["STORAGE_ROOT"], 'ssl', 'lets_encrypt')
new_path = os.path.join(env["STORAGE_ROOT"], 'ssl', 'lets_encrypt-old')
shutil.move(old_path, new_path)
except:
# meh
pass
def migration_12(env):
# Upgrading to Carddav Roundcube plugin to version 3+, it requires the carddav_*
# tables to be dropped.
# Checking that the roundcube database already exists.
if os.path.exists(os.path.join(env["STORAGE_ROOT"], "mail/roundcube/roundcube.sqlite")):
import sqlite3
conn = sqlite3.connect(os.path.join(env["STORAGE_ROOT"], "mail/roundcube/roundcube.sqlite"))
c = conn.cursor()
# Get a list of all the tables that begin with 'carddav_'
c.execute("SELECT name FROM sqlite_master WHERE type = ? AND name LIKE ?", ('table', 'carddav_%'))
carddav_tables = c.fetchall()
# If there were tables that begin with 'carddav_', drop them
if carddav_tables:
for table in carddav_tables:
try:
table = table[0]
c = conn.cursor()
dropcmd = "DROP TABLE %s" % table
c.execute(dropcmd)
except:
print("Failed to drop table", table, e)
# Save.
conn.commit()
conn.close()
# Delete all sessions, requring users to login again to recreate carddav_*
# databases
conn = sqlite3.connect(os.path.join(env["STORAGE_ROOT"], "mail/roundcube/roundcube.sqlite"))
c = conn.cursor()
c.execute("delete from session;")
conn.commit()
conn.close()
def get_current_migration(): def get_current_migration():
ver = 0 ver = 0
while True: while True:

View File

@@ -29,7 +29,7 @@ address 127.0.0.1
# send alerts to the following address # send alerts to the following address
contacts admin contacts admin
contact.admin.command mail -s "Munin notification ${var:host}" administrator@$PRIMARY_HOSTNAME contact.admin.command mail -s "Munin notification \${var:host}" administrator@$PRIMARY_HOSTNAME
contact.admin.always_send warning critical contact.admin.always_send warning critical
EOF EOF
@@ -38,11 +38,13 @@ 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-graph.log
# ensure munin-node knows the name of this machine # ensure munin-node knows the name of this machine
# and reduce logging level to warning
tools/editconf.py /etc/munin/munin-node.conf -s \ tools/editconf.py /etc/munin/munin-node.conf -s \
host_name=$PRIMARY_HOSTNAME host_name=$PRIMARY_HOSTNAME \
log_level=1
# Update the activated plugins through munin's autoconfiguration. # Update the activated plugins through munin's autoconfiguration.
munin-node-configure --shell --remove-also 2>/dev/null | sh munin-node-configure --shell --remove-also 2>/dev/null | sh || /bin/true
# Deactivate monitoring of NTP peers. Not sure why anyone would want to monitor a NTP peer. The addresses seem to change # Deactivate monitoring of NTP peers. Not sure why anyone would want to monitor a NTP peer. The addresses seem to change
# (which is taken care of my munin-node-configure, but only when we re-run it.) # (which is taken care of my munin-node-configure, but only when we re-run it.)
@@ -51,7 +53,7 @@ find /etc/munin/plugins/ -lname /usr/share/munin/plugins/ntp_ -print0 | xargs -0
# Deactivate monitoring of network interfaces that are not up. Otherwise we can get a lot of empty charts. # Deactivate monitoring of network interfaces that are not up. Otherwise we can get a lot of empty charts.
for f in $(find /etc/munin/plugins/ \( -lname /usr/share/munin/plugins/if_ -o -lname /usr/share/munin/plugins/if_err_ -o -lname /usr/share/munin/plugins/bonding_err_ \)); do for f in $(find /etc/munin/plugins/ \( -lname /usr/share/munin/plugins/if_ -o -lname /usr/share/munin/plugins/if_err_ -o -lname /usr/share/munin/plugins/bonding_err_ \)); do
IF=$(echo $f | sed s/.*_//); IF=$(echo $f | sed s/.*_//);
if ! ifquery $IF >/dev/null 2>/dev/null; then if ! grep -qFx up /sys/class/net/$IF/operstate 2>/dev/null; then
rm $f; rm $f;
fi; fi;
done done
@@ -59,6 +61,15 @@ done
# Create a 'state' directory. Not sure why we need to do this manually. # Create a 'state' directory. Not sure why we need to do this manually.
mkdir -p /var/lib/munin-node/plugin-state/ mkdir -p /var/lib/munin-node/plugin-state/
# Create a systemd service for munin.
ln -sf $(pwd)/management/munin_start.sh /usr/local/lib/mailinabox/munin_start.sh
chmod 0744 /usr/local/lib/mailinabox/munin_start.sh
cp --remove-destination conf/munin.service /lib/systemd/system/munin.service # target was previously a symlink so remove first
hide_output systemctl link -f /lib/systemd/system/munin.service
hide_output systemctl daemon-reload
hide_output systemctl unmask munin.service
hide_output systemctl enable munin.service
# Restart services. # Restart services.
restart_service munin restart_service munin
restart_service munin-node restart_service munin-node
@@ -66,4 +77,8 @@ restart_service munin-node
# generate initial statistics so the directory isn't empty # generate initial statistics so the directory isn't empty
# (We get "Pango-WARNING **: error opening config file '/root/.config/pango/pangorc': Permission denied" # (We get "Pango-WARNING **: error opening config file '/root/.config/pango/pangorc': Permission denied"
# if we don't explicitly set the HOME directory when sudo'ing.) # if we don't explicitly set the HOME directory when sudo'ing.)
sudo -H -u munin munin-cron # We check to see if munin-cron is already running, if it is, there is no need to run it simultaneously
# generating an error.
if [ ! -f /var/run/munin/munin-update.lock ]; then
sudo -H -u munin munin-cron
fi

View File

@@ -9,29 +9,12 @@ source /etc/mailinabox.conf # load global vars
echo "Installing Nextcloud (contacts/calendar)..." echo "Installing Nextcloud (contacts/calendar)..."
# Keep the php5 dependancies for the owncloud upgrades apt-get purge -qq -y owncloud* # we used to use the package manager
apt_install \
dbconfig-common \
php5-cli php5-sqlite php5-gd php5-imap php5-curl php-pear php-apc curl libapr1 libtool libcurl4-openssl-dev php-xml-parser \
php5 php5-dev php5-gd php5-fpm memcached php5-memcached
apt-get purge -qq -y owncloud* apt_install php php-fpm \
php-cli php-sqlite3 php-gd php-imap php-curl php-pear curl \
apt_install php7.0 php7.0-fpm \ php-dev php-gd php-xml php-mbstring php-zip php-apcu php-json \
php7.0-cli php7.0-sqlite php7.0-gd php7.0-imap php7.0-curl php-pear php-apc curl \ php-intl php-imagick
php7.0-dev php7.0-gd memcached php7.0-memcached php7.0-xml php7.0-mbstring php7.0-zip php7.0-apcu
# Migrate <= v0.10 setups that stored the ownCloud config.php in /usr/local rather than
# in STORAGE_ROOT. Move the file to STORAGE_ROOT.
if [ ! -f $STORAGE_ROOT/owncloud/config.php ] \
&& [ -f /usr/local/lib/owncloud/config/config.php ]; then
# Move config.php and symlink back into previous location.
echo "Migrating owncloud/config.php to new location."
mv /usr/local/lib/owncloud/config/config.php $STORAGE_ROOT/owncloud/config.php \
&& \
ln -sf $STORAGE_ROOT/owncloud/config.php /usr/local/lib/owncloud/config/config.php
fi
InstallNextcloud() { InstallNextcloud() {
@@ -42,12 +25,12 @@ InstallNextcloud() {
echo "Upgrading to Nextcloud version $version" echo "Upgrading to Nextcloud version $version"
echo echo
# Remove the current owncloud/Nextcloud
rm -rf /usr/local/lib/owncloud
# Download and verify # Download and verify
wget_verify https://download.nextcloud.com/server/releases/nextcloud-$version.zip $hash /tmp/nextcloud.zip wget_verify https://download.nextcloud.com/server/releases/nextcloud-$version.zip $hash /tmp/nextcloud.zip
# Remove the current owncloud/Nextcloud
rm -rf /usr/local/lib/owncloud
# Extract ownCloud/Nextcloud # Extract ownCloud/Nextcloud
unzip -q /tmp/nextcloud.zip -d /usr/local/lib unzip -q /tmp/nextcloud.zip -d /usr/local/lib
mv /usr/local/lib/nextcloud /usr/local/lib/owncloud mv /usr/local/lib/nextcloud /usr/local/lib/owncloud
@@ -57,14 +40,22 @@ InstallNextcloud() {
# their github repositories. # their github repositories.
mkdir -p /usr/local/lib/owncloud/apps mkdir -p /usr/local/lib/owncloud/apps
wget_verify https://github.com/nextcloud/contacts/releases/download/v1.5.3/contacts.tar.gz 78c4d49e73f335084feecd4853bd8234cf32615e /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.5.3/calendar.tar.gz b370352d1f280805cc7128f78af4615f623827f8 /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
# Starting with Nextcloud 15, the app user_external is no longer included in Nextcloud core,
# we will install from their github repository.
if [[ $version =~ ^1[567] ]]; then
wget_verify https://github.com/nextcloud/user_external/releases/download/v0.7.0/user_external-0.7.0.tar.gz 555a94811daaf5bdd336c5e48a78aa8567b86437 /tmp/user_external.tgz
tar -xf /tmp/user_external.tgz -C /usr/local/lib/owncloud/apps/
rm /tmp/user_external.tgz
fi
# Fix weird permissions. # Fix weird permissions.
chmod 750 /usr/local/lib/owncloud/{apps,config} chmod 750 /usr/local/lib/owncloud/{apps,config}
@@ -75,7 +66,7 @@ InstallNextcloud() {
# Make sure permissions are correct or the upgrade step won't run. # Make sure permissions are correct or the upgrade step won't run.
# $STORAGE_ROOT/owncloud may not yet exist, so use -f to suppress # $STORAGE_ROOT/owncloud may not yet exist, so use -f to suppress
# that error. # that error.
chown -f -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud 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. # 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). # Then check for success (0=ok and 3=no upgrade needed, both are success).
@@ -90,158 +81,83 @@ InstallNextcloud() {
sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off
echo "...which seemed to work." echo "...which seemed to work."
fi fi
# Add missing indices. NextCloud didn't include this in the normal upgrade because it might take some time.
sudo -u www-data php /usr/local/lib/owncloud/occ db:add-missing-indices
# Run conversion to BigInt identifiers, this process may take some time on large tables.
sudo -u www-data php /usr/local/lib/owncloud/occ db:convert-filecache-bigint --no-interaction
fi fi
} }
# We only install ownCloud intermediate versions to be able to seemlesly upgrade to Nextcloud # Nextcloud Version to install. Checks are done down below to step through intermediate versions.
InstallOwncloud() { nextcloud_ver=17.0.6
nextcloud_hash=50b98d2c2f18510b9530e558ced9ab51eb4f11b0
version=$1 # Current Nextcloud Version, #1623
hash=$2 # Checking /usr/local/lib/owncloud/version.php shows version of the Nextcloud application, not the DB
# $STORAGE_ROOT/owncloud is kept together even during a backup. It is better to rely on config.php than
# version.php since the restore procedure can leave the system in a state where you have a newer Nextcloud
# application version than the database.
echo # If config.php exists, get version number, otherwise CURRENT_NEXTCLOUD_VER is empty.
echo "Upgrading to OwnCloud version $version" if [ -f "$STORAGE_ROOT/owncloud/config.php" ]; then
echo CURRENT_NEXTCLOUD_VER=$(php -r "include(\"$STORAGE_ROOT/owncloud/config.php\"); echo(\$CONFIG['version']);")
else
CURRENT_NEXTCLOUD_VER=""
fi
# Remove the current owncloud/Nextcloud # If the Nextcloud directory is missing (never been installed before, or the nextcloud version to be installed is different
rm -rf /usr/local/lib/owncloud # from the version currently installed, do the install/upgrade
if [ ! -d /usr/local/lib/owncloud/ ] || [[ ! ${CURRENT_NEXTCLOUD_VER} =~ ^$nextcloud_ver ]]; then
# Download and verify
wget_verify https://download.owncloud.org/community/owncloud-$version.zip $hash /tmp/owncloud.zip
# Extract ownCloud
unzip -q /tmp/owncloud.zip -d /usr/local/lib
rm -f /tmp/owncloud.zip
# The two apps we actually want are not in Nextcloud core. Download the releases from
# their github repositories.
mkdir -p /usr/local/lib/owncloud/apps
wget_verify https://github.com/owncloud/contacts/releases/download/v1.4.0.0/contacts.tar.gz c1c22d29699456a45db447281682e8bc3f10e3e7 /tmp/contacts.tgz
tar xf /tmp/contacts.tgz -C /usr/local/lib/owncloud/apps/
rm /tmp/contacts.tgz
wget_verify https://github.com/nextcloud/calendar/releases/download/v1.4.0/calendar.tar.gz c84f3170efca2a99ea6254de34b0af3cb0b3a821 /tmp/calendar.tgz
tar xf /tmp/calendar.tgz -C /usr/local/lib/owncloud/apps/
rm /tmp/calendar.tgz
# Fix weird permissions.
chmod 750 /usr/local/lib/owncloud/{apps,config}
# Create a symlink to the config.php in STORAGE_ROOT (for upgrades we're restoring the symlink we previously
# put in, and in new installs we're creating a symlink and will create the actual config later).
ln -sf $STORAGE_ROOT/owncloud/config.php /usr/local/lib/owncloud/config/config.php
# 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
# 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).
if [ -e $STORAGE_ROOT/owncloud/owncloud.db ]; then
# ownCloud 8.1.1 broke upgrades. It may fail on the first attempt, but
# that can be OK.
sudo -u www-data php5 /usr/local/lib/owncloud/occ upgrade
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then
echo "Trying ownCloud upgrade again to work around ownCloud upgrade bug..."
sudo -u www-data php5 /usr/local/lib/owncloud/occ upgrade
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
sudo -u www-data php5 /usr/local/lib/owncloud/occ maintenance:mode --off
echo "...which seemed to work."
fi
fi
}
owncloud_ver=12.0.3
owncloud_hash=beab41f6a748a43f0accfa6a9808387aef718c61
# Check if Nextcloud dir exist, and check if version matches owncloud_ver (if either doesn't - install/upgrade)
if [ ! -d /usr/local/lib/owncloud/ ] \
|| ! grep -q $owncloud_ver /usr/local/lib/owncloud/version.php; then
# Stop php-fpm if running. If theyre not running (which happens on a previously failed install), dont bail. # Stop php-fpm if running. If theyre not running (which happens on a previously failed install), dont bail.
service php7.0-fpm stop &> /dev/null || /bin/true service php7.2-fpm stop &> /dev/null || /bin/true
service php5-fpm stop &> /dev/null || /bin/true
# Backup the existing ownCloud/Nextcloud. # Backup the existing ownCloud/Nextcloud.
# Create a backup directory to store the current installation and database to # Create a backup directory to store the current installation and database to
BACKUP_DIRECTORY=$STORAGE_ROOT/owncloud-backup/`date +"%Y-%m-%d-%T"` BACKUP_DIRECTORY=$STORAGE_ROOT/owncloud-backup/`date +"%Y-%m-%d-%T"`
mkdir -p "$BACKUP_DIRECTORY" mkdir -p "$BACKUP_DIRECTORY"
if [ -d /usr/local/lib/owncloud/ ]; then if [ -d /usr/local/lib/owncloud/ ]; then
echo "upgrading ownCloud/Nextcloud to $owncloud_flavor $owncloud_ver (backing up existing installation, configuration and database to directory to $BACKUP_DIRECTORY..." echo "Upgrading Nextcloud --- backing up existing installation, configuration, and database to directory to $BACKUP_DIRECTORY..."
cp -r /usr/local/lib/owncloud "$BACKUP_DIRECTORY/owncloud-install" cp -r /usr/local/lib/owncloud "$BACKUP_DIRECTORY/owncloud-install"
fi fi
if [ -e /home/user-data/owncloud/owncloud.db ]; then if [ -e $STORAGE_ROOT/owncloud/owncloud.db ]; then
cp /home/user-data/owncloud/owncloud.db $BACKUP_DIRECTORY cp $STORAGE_ROOT/owncloud/owncloud.db $BACKUP_DIRECTORY
fi fi
if [ -e /home/user-data/owncloud/config.php ]; then if [ -e $STORAGE_ROOT/owncloud/config.php ]; then
cp /home/user-data/owncloud/config.php $BACKUP_DIRECTORY cp $STORAGE_ROOT/owncloud/config.php $BACKUP_DIRECTORY
fi fi
# We only need to check if we do upgrades when owncloud/Nextcloud was previously installed # If ownCloud or Nextcloud was previously installed....
if [ -e /usr/local/lib/owncloud/version.php ]; then if [ ! -z ${CURRENT_NEXTCLOUD_VER} ]; then
if grep -q "OC_VersionString = '8\.1\.[0-9]" /usr/local/lib/owncloud/version.php; then # Database migrations from ownCloud are no longer possible because ownCloud cannot be run under
echo "We are running 8.1.x, upgrading to 8.2.3 first" # PHP 7.
InstallOwncloud 8.2.3 bfdf6166fbf6fc5438dc358600e7239d1c970613 if [[ ${CURRENT_NEXTCLOUD_VER} =~ ^[89] ]]; then
echo "Upgrades from Mail-in-a-Box prior to v0.28 (dated July 30, 2018) with Nextcloud < 13.0.6 (you have ownCloud 8 or 9) are not supported. Upgrade to Mail-in-a-Box version v0.30 first. Setup aborting."
exit 1
elif [[ ${CURRENT_NEXTCLOUD_VER} =~ ^1[012] ]]; then
echo "Upgrades from Mail-in-a-Box prior to v0.28 (dated July 30, 2018) with Nextcloud < 13.0.6 (you have ownCloud 10, 11 or 12) are not supported. Upgrade to Mail-in-a-Box version v0.30 first. Setup aborting."
exit 1
elif [[ ${CURRENT_NEXTCLOUD_VER} =~ ^13 ]]; then
# If we are running Nextcloud 13, upgrade to Nextcloud 14
InstallNextcloud 14.0.6 4e43a57340f04c2da306c8eea98e30040399ae5a
CURRENT_NEXTCLOUD_VER="14.0.6"
fi fi
if [[ ${CURRENT_NEXTCLOUD_VER} =~ ^14 ]]; then
# If we are upgrading from 8.2.x we should go to 9.0 first. Owncloud doesn't support skipping minor versions # During the upgrade from Nextcloud 14 to 15, user_external may cause the upgrade to fail.
if grep -q "OC_VersionString = '8\.2\.[0-9]" /usr/local/lib/owncloud/version.php; then # We will disable it here before the upgrade and install it again after the upgrade.
echo "We are running version 8.2.x, upgrading to 9.0.2 first" hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:disable user_external
InstallNextcloud 15.0.8 4129d8d4021c435f2e86876225fb7f15adf764a3
# We need to disable memcached. The upgrade and install fails CURRENT_NEXTCLOUD_VER="15.0.8"
# with memcached
CONFIG_TEMP=$(/bin/mktemp)
php <<EOF > $CONFIG_TEMP && mv $CONFIG_TEMP $STORAGE_ROOT/owncloud/config.php;
<?php
include("$STORAGE_ROOT/owncloud/config.php");
\$CONFIG['memcache.local'] = '\OC\Memcache\APCu';
echo "<?php\n\\\$CONFIG = ";
var_export(\$CONFIG);
echo ";";
?>
EOF
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
# We can now install owncloud 9.0.2
InstallOwncloud 9.0.2 72a3d15d09f58c06fa8bee48b9e60c9cd356f9c5
# The owncloud 9 migration doesn't migrate calendars and contacts
# The option to migrate these are removed in 9.1
# So the migrations should be done when we have 9.0 installed
sudo -u www-data php5 /usr/local/lib/owncloud/occ dav:migrate-addressbooks
# The following migration has to be done for each owncloud user
for directory in $STORAGE_ROOT/owncloud/*@*/ ; do
username=$(basename "${directory}")
sudo -u www-data php5 /usr/local/lib/owncloud/occ dav:migrate-calendar $username
done
sudo -u www-data php5 /usr/local/lib/owncloud/occ dav:sync-birthday-calendar
fi fi
if [[ ${CURRENT_NEXTCLOUD_VER} =~ ^15 ]]; then
# If we are upgrading from 9.0.x we should go to 9.1 first. InstallNextcloud 16.0.6 0bb3098455ec89f5af77a652aad553ad40a88819
if grep -q "OC_VersionString = '9\.0\.[0-9]" /usr/local/lib/owncloud/version.php; then CURRENT_NEXTCLOUD_VER="16.0.6"
echo "We are running ownCloud 9.0.x, upgrading to ownCloud 9.1.4 first"
InstallOwncloud 9.1.4 e637cab7b2ca3346164f3506b1a0eb812b4e841a
fi
# If we are upgrading from 9.1.x we should go to Nextcloud 10.0 first.
if grep -q "OC_VersionString = '9\.1\.[0-9]" /usr/local/lib/owncloud/version.php; then
echo "We are running ownCloud 9.1.x, upgrading to Nextcloud 10.0.5 first"
InstallNextcloud 10.0.5 686f6a8e9d7867c32e3bf3ca63b3cc2020564bf6
fi
# If we are upgrading from 10.0.x we should go to Nextcloud 11.0 first.
if grep -q "OC_VersionString = '10\.0\.[0-9]" /usr/local/lib/owncloud/version.php; then
echo "We are running Nextcloud 10.0.x, upgrading to Nextcloud 11.0.3 first"
InstallNextcloud 11.0.3 a396aaa1c9f920099a90a86b4a9cd0ec13083c99
fi fi
fi fi
InstallNextcloud $owncloud_ver $owncloud_hash InstallNextcloud $nextcloud_ver $nextcloud_hash
fi fi
# ### Configuring Nextcloud # ### Configuring Nextcloud
@@ -267,9 +183,11 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
'overwrite.cli.url' => '/cloud', 'overwrite.cli.url' => '/cloud',
'user_backends' => array( 'user_backends' => array(
array( array(
'class'=>'OC_User_IMAP', 'class' => 'OC_User_IMAP',
'arguments'=>array('{127.0.0.1:993/imap/ssl/novalidate-cert}') 'arguments' => array(
) '127.0.0.1', 143, null
),
),
), ),
'memcache.local' => '\OC\Memcache\APCu', 'memcache.local' => '\OC\Memcache\APCu',
'mail_smtpmode' => 'sendmail', 'mail_smtpmode' => 'sendmail',
@@ -341,6 +259,8 @@ include("$STORAGE_ROOT/owncloud/config.php");
\$CONFIG['mail_domain'] = '$PRIMARY_HOSTNAME'; \$CONFIG['mail_domain'] = '$PRIMARY_HOSTNAME';
\$CONFIG['user_backends'] = array(array('class' => 'OC_User_IMAP','arguments' => array('127.0.0.1', 143, null),),);
echo "<?php\n\\\$CONFIG = "; echo "<?php\n\\\$CONFIG = ";
var_export(\$CONFIG); var_export(\$CONFIG);
echo ";"; echo ";";
@@ -365,7 +285,7 @@ if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
# Set PHP FPM values to support large file uploads # Set PHP FPM values to support large file uploads
# (semicolon is the comment character in this file, hashes produce deprecation warnings) # (semicolon is the comment character in this file, hashes produce deprecation warnings)
tools/editconf.py /etc/php/7.0/fpm/php.ini -c ';' \ tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
upload_max_filesize=16G \ upload_max_filesize=16G \
post_max_size=16G \ post_max_size=16G \
output_buffering=16384 \ output_buffering=16384 \
@@ -374,7 +294,7 @@ tools/editconf.py /etc/php/7.0/fpm/php.ini -c ';' \
short_open_tag=On short_open_tag=On
# Set Nextcloud recommended opcache settings # Set Nextcloud recommended opcache settings
tools/editconf.py /etc/php/7.0/cli/conf.d/10-opcache.ini -c ';' \ tools/editconf.py /etc/php/7.2/cli/conf.d/10-opcache.ini -c ';' \
opcache.enable=1 \ opcache.enable=1 \
opcache.enable_cli=1 \ opcache.enable_cli=1 \
opcache.interned_strings_buffer=8 \ opcache.interned_strings_buffer=8 \
@@ -383,23 +303,22 @@ tools/editconf.py /etc/php/7.0/cli/conf.d/10-opcache.ini -c ';' \
opcache.save_comments=1 \ opcache.save_comments=1 \
opcache.revalidate_freq=1 opcache.revalidate_freq=1
# Configure the path environment for php-fpm
tools/editconf.py /etc/php/7.0/fpm/pool.d/www.conf -c ';' \
env[PATH]=/usr/local/bin:/usr/bin:/bin
# If apc is explicitly disabled we need to enable it # If apc is explicitly disabled we need to enable it
if grep -q apc.enabled=0 /etc/php/7.0/mods-available/apcu.ini; then if grep -q apc.enabled=0 /etc/php/7.2/mods-available/apcu.ini; then
tools/editconf.py /etc/php/7.0/mods-available/apcu.ini -c ';' \ tools/editconf.py /etc/php/7.2/mods-available/apcu.ini -c ';' \
apc.enabled=1 apc.enabled=1
fi fi
# Set up a cron job for Nextcloud. # Set up a cron job for Nextcloud.
cat > /etc/cron.hourly/mailinabox-owncloud << EOF; cat > /etc/cron.d/mailinabox-nextcloud << EOF;
#!/bin/bash #!/bin/bash
# Mail-in-a-Box # Mail-in-a-Box
sudo -u www-data php -f /usr/local/lib/owncloud/cron.php */5 * * * * root sudo -u www-data php -f /usr/local/lib/owncloud/cron.php
EOF EOF
chmod +x /etc/cron.hourly/mailinabox-owncloud chmod +x /etc/cron.d/mailinabox-nextcloud
# Remove previous hourly cronjob
rm -f /etc/cron.hourly/mailinabox-owncloud
# There's nothing much of interest that a user could do as an admin for Nextcloud, # There's nothing much of interest that a user could do as an admin for Nextcloud,
# and there's a lot they could mess up, so we don't make any users admins of Nextcloud. # and there's a lot they could mess up, so we don't make any users admins of Nextcloud.
@@ -411,4 +330,4 @@ chmod +x /etc/cron.hourly/mailinabox-owncloud
# ``` # ```
# Enable PHP modules and restart PHP. # Enable PHP modules and restart PHP.
restart_service php7.0-fpm restart_service php7.2-fpm

View File

@@ -4,17 +4,17 @@ 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 14.04 LTS (or 14.04.xx). # Check that we are running on Ubuntu 18.04 LTS (or 18.04.xx).
if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" != "Ubuntu 14.04 LTS" ]; then if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" != "Ubuntu 18.04 LTS" ]; then
echo "Mail-in-a-Box only supports being installed on Ubuntu 14.04, sorry. You are running:" echo "Mail-in-a-Box only supports being installed on Ubuntu 18.04, sorry. You are running:"
echo echo
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.
@@ -26,7 +26,7 @@ fi
# #
# Skip the check if we appear to be running inside of Vagrant, because that's really just for testing. # Skip the check if we appear to be running inside of Vagrant, because that's really just for testing.
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}') TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}')
if [ $TOTAL_PHYSICAL_MEM -lt 500000 ]; then if [ $TOTAL_PHYSICAL_MEM -lt 490000 ]; then
if [ ! -d /vagrant ]; then if [ ! -d /vagrant ]; then
TOTAL_PHYSICAL_MEM=$(expr \( \( $TOTAL_PHYSICAL_MEM \* 1024 \) / 1000 \) / 1000) TOTAL_PHYSICAL_MEM=$(expr \( \( $TOTAL_PHYSICAL_MEM \* 1024 \) / 1000 \) / 1000)
echo "Your Mail-in-a-Box needs more memory (RAM) to function properly." echo "Your Mail-in-a-Box needs more memory (RAM) to function properly."
@@ -41,7 +41,7 @@ if [ $TOTAL_PHYSICAL_MEM -lt 750000 ]; then
fi fi
# Check that tempfs is mounted with exec # Check that tempfs is mounted with exec
MOUNTED_TMP_AS_NO_EXEC=$(grep "/tmp.*noexec" /proc/mounts) MOUNTED_TMP_AS_NO_EXEC=$(grep "/tmp.*noexec" /proc/mounts || /bin/true)
if [ -n "$MOUNTED_TMP_AS_NO_EXEC" ]; then if [ -n "$MOUNTED_TMP_AS_NO_EXEC" ]; then
echo "Mail-in-a-Box has to have exec rights on /tmp, please mount /tmp with exec" echo "Mail-in-a-Box has to have exec rights on /tmp, please mount /tmp with exec"
exit exit
@@ -53,16 +53,14 @@ if [ -e ~/.wgetrc ]; then
exit exit
fi fi
# Check that we are running on x86_64 or i686, any other architecture is unsupported and # Check that we are running on x86_64 or i686 architecture, which are the only
# will fail later in the setup when we try to install the custom build lucene packages. # ones we support / test.
#
# Set ARM=1 to ignore this check if you have built the packages yourself. If you do this
# you are on your own!
ARCHITECTURE=$(uname -m) ARCHITECTURE=$(uname -m)
if [ "$ARCHITECTURE" != "x86_64" ] && [ "$ARCHITECTURE" != "i686" ]; then if [ "$ARCHITECTURE" != "x86_64" ] && [ "$ARCHITECTURE" != "i686" ]; then
if [ -z "$ARM" ]; then echo
echo "Mail-in-a-Box only supports x86_64 or i686 and will not work on any other architecture, like ARM." echo "WARNING:"
echo "Your architecture is $ARCHITECTURE" echo "Mail-in-a-Box has only been tested on x86_64 and i686 platform"
exit echo "architectures. Your architecture, $ARCHITECTURE, may not work."
fi echo "You are on your own."
echo
fi fi

View File

@@ -1,4 +1,4 @@
if [ -z "$NONINTERACTIVE" ]; then if [ -z "${NONINTERACTIVE:-}" ]; then
# Install 'dialog' so we can ask the user questions. The original motivation for # Install 'dialog' so we can ask the user questions. The original motivation for
# this was being able to ask the user for input even if stdin has been redirected, # this was being able to ask the user for input even if stdin has been redirected,
# e.g. if we piped a bootstrapping install script to bash to get started. In that # e.g. if we piped a bootstrapping install script to bash to get started. In that
@@ -12,7 +12,9 @@ if [ -z "$NONINTERACTIVE" ]; then
apt_get_quiet install dialog python3 python3-pip || exit 1 apt_get_quiet install dialog python3 python3-pip || exit 1
fi fi
# email_validator is repeated in setup/management.sh # Installing email_validator is repeated in setup/management.sh, but in setup/management.sh
# we install it inside a virtualenv. In this script, we don't have the virtualenv yet
# so we install the python package globally.
hide_output pip3 install "email_validator>=1.0.0" || exit 1 hide_output pip3 install "email_validator>=1.0.0" || exit 1
message_box "Mail-in-a-Box Installation" \ message_box "Mail-in-a-Box Installation" \
@@ -23,8 +25,8 @@ if [ -z "$NONINTERACTIVE" ]; then
fi fi
# The box needs a name. # The box needs a name.
if [ -z "$PRIMARY_HOSTNAME" ]; then if [ -z "${PRIMARY_HOSTNAME:-}" ]; then
if [ -z "$DEFAULT_PRIMARY_HOSTNAME" ]; then if [ -z "${DEFAULT_PRIMARY_HOSTNAME:-}" ]; then
# We recommend to use box.example.com as this hosts name. The # We recommend to use box.example.com as this hosts name. The
# domain the user possibly wants to use is example.com then. # domain the user possibly wants to use is example.com then.
# We strip the string "box." from the hostname to get the mail # We strip the string "box." from the hostname to get the mail
@@ -49,7 +51,7 @@ you really want.
# user hit ESC/cancel # user hit ESC/cancel
exit exit
fi fi
while ! management/mailconfig.py validate-email "$EMAIL_ADDR" while ! python3 management/mailconfig.py validate-email "$EMAIL_ADDR"
do do
input_box "Your Email Address" \ input_box "Your Email Address" \
"That's not a valid email address.\n\nWhat email address are you setting this box up to manage?" \ "That's not a valid email address.\n\nWhat email address are you setting this box up to manage?" \
@@ -84,30 +86,30 @@ fi
# If the machine is behind a NAT, inside a VM, etc., it may not know # If the machine is behind a NAT, inside a VM, etc., it may not know
# its IP address on the public network / the Internet. Ask the Internet # its IP address on the public network / the Internet. Ask the Internet
# and possibly confirm with user. # and possibly confirm with user.
if [ -z "$PUBLIC_IP" ]; then if [ -z "${PUBLIC_IP:-}" ]; then
# Ask the Internet. # Ask the Internet.
GUESSED_IP=$(get_publicip_from_web_service 4) GUESSED_IP=$(get_publicip_from_web_service 4)
# On the first run, if we got an answer from the Internet then don't # On the first run, if we got an answer from the Internet then don't
# ask the user. # ask the user.
if [[ -z "$DEFAULT_PUBLIC_IP" && ! -z "$GUESSED_IP" ]]; then if [[ -z "${DEFAULT_PUBLIC_IP:-}" && ! -z "$GUESSED_IP" ]]; then
PUBLIC_IP=$GUESSED_IP PUBLIC_IP=$GUESSED_IP
# Otherwise on the first run at least provide a default. # Otherwise on the first run at least provide a default.
elif [[ -z "$DEFAULT_PUBLIC_IP" ]]; then elif [[ -z "${DEFAULT_PUBLIC_IP:-}" ]]; then
DEFAULT_PUBLIC_IP=$(get_default_privateip 4) DEFAULT_PUBLIC_IP=$(get_default_privateip 4)
# On later runs, if the previous value matches the guessed value then # On later runs, if the previous value matches the guessed value then
# don't ask the user either. # don't ask the user either.
elif [ "$DEFAULT_PUBLIC_IP" == "$GUESSED_IP" ]; then elif [ "${DEFAULT_PUBLIC_IP:-}" == "$GUESSED_IP" ]; then
PUBLIC_IP=$GUESSED_IP PUBLIC_IP=$GUESSED_IP
fi fi
if [ -z "$PUBLIC_IP" ]; then if [ -z "${PUBLIC_IP:-}" ]; then
input_box "Public IP Address" \ input_box "Public IP Address" \
"Enter the public IP address of this machine, as given to you by your ISP. "Enter the public IP address of this machine, as given to you by your ISP.
\n\nPublic IP address:" \ \n\nPublic IP address:" \
$DEFAULT_PUBLIC_IP \ ${DEFAULT_PUBLIC_IP:-} \
PUBLIC_IP PUBLIC_IP
if [ -z "$PUBLIC_IP" ]; then if [ -z "$PUBLIC_IP" ]; then
@@ -119,27 +121,27 @@ fi
# Same for IPv6. But it's optional. Also, if it looks like the system # Same for IPv6. But it's optional. Also, if it looks like the system
# doesn't have an IPv6, don't ask for one. # doesn't have an IPv6, don't ask for one.
if [ -z "$PUBLIC_IPV6" ]; then if [ -z "${PUBLIC_IPV6:-}" ]; then
# Ask the Internet. # Ask the Internet.
GUESSED_IP=$(get_publicip_from_web_service 6) GUESSED_IP=$(get_publicip_from_web_service 6)
MATCHED=0 MATCHED=0
if [[ -z "$DEFAULT_PUBLIC_IPV6" && ! -z "$GUESSED_IP" ]]; then if [[ -z "${DEFAULT_PUBLIC_IPV6:-}" && ! -z "$GUESSED_IP" ]]; then
PUBLIC_IPV6=$GUESSED_IP PUBLIC_IPV6=$GUESSED_IP
elif [[ "$DEFAULT_PUBLIC_IPV6" == "$GUESSED_IP" ]]; then elif [[ "${DEFAULT_PUBLIC_IPV6:-}" == "$GUESSED_IP" ]]; then
# No IPv6 entered and machine seems to have none, or what # No IPv6 entered and machine seems to have none, or what
# the user entered matches what the Internet tells us. # the user entered matches what the Internet tells us.
PUBLIC_IPV6=$GUESSED_IP PUBLIC_IPV6=$GUESSED_IP
MATCHED=1 MATCHED=1
elif [[ -z "$DEFAULT_PUBLIC_IPV6" ]]; then elif [[ -z "${DEFAULT_PUBLIC_IPV6:-}" ]]; then
DEFAULT_PUBLIC_IP=$(get_default_privateip 6) DEFAULT_PUBLIC_IP=$(get_default_privateip 6)
fi fi
if [[ -z "$PUBLIC_IPV6" && $MATCHED == 0 ]]; then if [[ -z "${PUBLIC_IPV6:-}" && $MATCHED == 0 ]]; then
input_box "IPv6 Address (Optional)" \ input_box "IPv6 Address (Optional)" \
"Enter the public IPv6 address of this machine, as given to you by your ISP. "Enter the public IPv6 address of this machine, as given to you by your ISP.
\n\nLeave blank if the machine does not have an IPv6 address. \n\nLeave blank if the machine does not have an IPv6 address.
\n\nPublic IPv6 address:" \ \n\nPublic IPv6 address:" \
$DEFAULT_PUBLIC_IPV6 \ ${DEFAULT_PUBLIC_IPV6:-} \
PUBLIC_IPV6 PUBLIC_IPV6
if [ ! $PUBLIC_IPV6_EXITCODE ]; then if [ ! $PUBLIC_IPV6_EXITCODE ]; then
@@ -152,10 +154,10 @@ fi
# Get the IP addresses of the local network interface(s) that are connected # Get the IP addresses of the local network interface(s) that are connected
# to the Internet. We need these when we want to have services bind only to # to the Internet. We need these when we want to have services bind only to
# the public network interfaces (not loopback, not tunnel interfaces). # the public network interfaces (not loopback, not tunnel interfaces).
if [ -z "$PRIVATE_IP" ]; then if [ -z "${PRIVATE_IP:-}" ]; then
PRIVATE_IP=$(get_default_privateip 4) PRIVATE_IP=$(get_default_privateip 4)
fi fi
if [ -z "$PRIVATE_IPV6" ]; then if [ -z "${PRIVATE_IPV6:-}" ]; then
PRIVATE_IPV6=$(get_default_privateip 6) PRIVATE_IPV6=$(get_default_privateip 6)
fi fi
if [[ -z "$PRIVATE_IP" && -z "$PRIVATE_IPV6" ]]; then if [[ -z "$PRIVATE_IP" && -z "$PRIVATE_IPV6" ]]; then
@@ -184,11 +186,11 @@ fi
# Set STORAGE_USER and STORAGE_ROOT to default values (user-data and /home/user-data), unless # Set STORAGE_USER and STORAGE_ROOT to default values (user-data and /home/user-data), unless
# we've already got those values from a previous run. # we've already got those values from a previous run.
if [ -z "$STORAGE_USER" ]; then if [ -z "${STORAGE_USER:-}" ]; then
STORAGE_USER=$([[ -z "$DEFAULT_STORAGE_USER" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER") STORAGE_USER=$([[ -z "${DEFAULT_STORAGE_USER:-}" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER")
fi fi
if [ -z "$STORAGE_ROOT" ]; then if [ -z "${STORAGE_ROOT:-}" ]; then
STORAGE_ROOT=$([[ -z "$DEFAULT_STORAGE_ROOT" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT") STORAGE_ROOT=$([[ -z "${DEFAULT_STORAGE_ROOT:-}" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT")
fi fi
# Show the configuration, since the user may have not entered it manually. # Show the configuration, since the user may have not entered it manually.

View File

@@ -64,8 +64,8 @@ tools/editconf.py /etc/default/spampd \
# the X-Spam-Status & X-Spam-Score mail headers and related headers. # the X-Spam-Status & X-Spam-Score mail headers and related headers.
tools/editconf.py /etc/spamassassin/local.cf -s \ tools/editconf.py /etc/spamassassin/local.cf -s \
report_safe=0 \ report_safe=0 \
add_header="all Report _REPORT_" \ "add_header all Report"=_REPORT_ \
add_header="all Score _SCORE_" "add_header all Score"=_SCORE_
# Bayesean learning # Bayesean learning
# ----------------- # -----------------

View File

@@ -4,7 +4,7 @@
source setup/functions.sh # load our functions source setup/functions.sh # load our functions
# Check system setup: Are we running as root on Ubuntu 14.04 on a # Check system setup: Are we running as root on Ubuntu 18.04 on a
# machine with enough memory? Is /tmp mounted with exec. # machine with enough memory? Is /tmp mounted with exec.
# If not, this shows an error and exits. # If not, this shows an error and exits.
source setup/preflight.sh source setup/preflight.sh
@@ -14,7 +14,7 @@ source setup/preflight.sh
# Python may not be able to read/write files. This is also # Python may not be able to read/write files. This is also
# in the management daemon startup script and the cron script. # in the management daemon startup script and the cron script.
if [ -z `locale -a | grep en_US.utf8` ]; then if ! locale -a | grep en_US.utf8 > /dev/null; then
# Generate locale if not exists # Generate locale if not exists
hide_output locale-gen en_US.UTF-8 hide_output locale-gen en_US.UTF-8
fi fi
@@ -60,8 +60,8 @@ source setup/questions.sh
# Run some network checks to make sure setup on this machine makes sense. # Run some network checks to make sure setup on this machine makes sense.
# Skip on existing installs since we don't want this to block the ability to # Skip on existing installs since we don't want this to block the ability to
# upgrade, and these checks are also in the control panel status checks. # upgrade, and these checks are also in the control panel status checks.
if [ -z "$DEFAULT_PRIMARY_HOSTNAME" ]; then if [ -z "${DEFAULT_PRIMARY_HOSTNAME:-}" ]; then
if [ -z "$SKIP_NETWORK_CHECKS" ]; then if [ -z "${SKIP_NETWORK_CHECKS:-}" ]; then
source setup/network-checks.sh source setup/network-checks.sh
fi fi
fi fi
@@ -106,7 +106,7 @@ source setup/dkim.sh
source setup/spamassassin.sh source setup/spamassassin.sh
source setup/web.sh source setup/web.sh
source setup/webmail.sh source setup/webmail.sh
source setup/owncloud.sh source setup/nextcloud.sh
source setup/zpush.sh source setup/zpush.sh
source setup/management.sh source setup/management.sh
source setup/munin.sh source setup/munin.sh
@@ -127,13 +127,23 @@ tools/web_update
# fail2ban was first configured, but they should exist now. # fail2ban was first configured, but they should exist now.
restart_service fail2ban restart_service fail2ban
# If DNS is already working, try to provision TLS certficates from Let's Encrypt.
# Suppress extra reasons why domains aren't getting a new certificate.
management/ssl_certificates.py -q
# If there aren't any mail users yet, create one. # If there aren't any mail users yet, create one.
source setup/firstuser.sh source setup/firstuser.sh
# Register with Let's Encrypt, including agreeing to the Terms of Service.
# We'd let certbot ask the user interactively, but when this script is
# run in the recommended curl-pipe-to-bash method there is no TTY and
# certbot will fail if it tries to ask.
if [ ! -d $STORAGE_ROOT/ssl/lets_encrypt/accounts/acme-v02.api.letsencrypt.org/ ]; then
echo
echo "-----------------------------------------------"
echo "Mail-in-a-Box uses Let's Encrypt to provision free SSL/TLS certificates"
echo "to enable HTTPS connections to your box. We're automatically"
echo "agreeing you to their subscriber agreement. See https://letsencrypt.org."
echo
certbot register --register-unsafely-without-email --agree-tos --config-dir $STORAGE_ROOT/ssl/lets_encrypt
fi
# Done. # Done.
echo echo
echo "-----------------------------------------------" echo "-----------------------------------------------"

View File

@@ -14,6 +14,13 @@ source setup/functions.sh # load our functions
echo $PRIMARY_HOSTNAME > /etc/hostname echo $PRIMARY_HOSTNAME > /etc/hostname
hostname $PRIMARY_HOSTNAME hostname $PRIMARY_HOSTNAME
# ### Fix permissions
# The default Ubuntu Bionic image on Scaleway throws warnings during setup about incorrect
# permissions (group writeable) set on the following directories.
chmod g-w /etc /etc/default /usr
# ### Add swap space to the system # ### Add swap space to the system
# If the physical memory of the system is below 2GB it is wise to create a # If the physical memory of the system is below 2GB it is wise to create a
@@ -37,9 +44,9 @@ hostname $PRIMARY_HOSTNAME
# for reference # for reference
SWAP_MOUNTED=$(cat /proc/swaps | tail -n+2) SWAP_MOUNTED=$(cat /proc/swaps | tail -n+2)
SWAP_IN_FSTAB=$(grep "swap" /etc/fstab) SWAP_IN_FSTAB=$(grep "swap" /etc/fstab || /bin/true)
ROOT_IS_BTRFS=$(grep "\/ .*btrfs" /proc/mounts) ROOT_IS_BTRFS=$(grep "\/ .*btrfs" /proc/mounts || /bin/true)
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}') TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}' || /bin/true)
AVAILABLE_DISK_SPACE=$(df / --output=avail | tail -n 1) AVAILABLE_DISK_SPACE=$(df / --output=avail | tail -n 1)
if if
[ -z "$SWAP_MOUNTED" ] && [ -z "$SWAP_MOUNTED" ] &&
@@ -68,17 +75,10 @@ then
fi fi
fi fi
# ### Add Mail-in-a-Box's PPA. # ### Add PPAs.
# We've built several .deb packages on our own that we want to include.
# One is a replacement for Ubuntu's stock postgrey package that makes
# some enhancements. The other is dovecot-lucene, a Lucene-based full
# text search plugin for (and by) dovecot, which is not available in
# Ubuntu currently.
#
# So, first ensure add-apt-repository is installed, then use it to install
# the [mail-in-a-box ppa](https://launchpad.net/~mail-in-a-box/+archive/ubuntu/ppa).
# We install some non-standard Ubuntu packages maintained by other
# third-party providers. First ensure add-apt-repository is installed.
if [ ! -f /usr/bin/add-apt-repository ]; then if [ ! -f /usr/bin/add-apt-repository ]; then
echo "Installing add-apt-repository..." echo "Installing add-apt-repository..."
@@ -86,11 +86,18 @@ if [ ! -f /usr/bin/add-apt-repository ]; then
apt_install software-properties-common apt_install software-properties-common
fi fi
hide_output add-apt-repository -y ppa:mail-in-a-box/ppa # Ensure the universe repository is enabled since some of our packages
# come from there and minimal Ubuntu installs may have it turned off.
hide_output add-apt-repository -y universe
# Install the certbot PPA.
hide_output add-apt-repository -y ppa:certbot/certbot
# ### Update Packages # ### Update Packages
# Update system packages to make sure we have the latest upstream versions of things from Ubuntu. # Update system packages to make sure we have the latest upstream versions
# of things from Ubuntu, as well as the directory of packages provide by the
# PPAs so we can install those packages later.
echo Updating system packages... echo Updating system packages...
hide_output apt-get update hide_output apt-get update
@@ -118,28 +125,17 @@ apt_get_quiet autoremove
# * sudo: allows privileged users to execute commands as root without being root # * sudo: allows privileged users to execute commands as root without being root
# * coreutils: includes `nproc` tool to report number of processors, mktemp # * coreutils: includes `nproc` tool to report number of processors, mktemp
# * bc: allows us to do math to compute sane defaults # * bc: allows us to do math to compute sane defaults
# * openssh-client: provides ssh-keygen
echo Installing system packages... echo Installing system packages...
apt_install python3 python3-dev python3-pip \ apt_install python3 python3-dev python3-pip \
netcat-openbsd wget curl git sudo coreutils bc \ netcat-openbsd wget curl git sudo coreutils bc \
haveged pollinate unzip \ haveged pollinate openssh-client unzip \
unattended-upgrades cron ntp fail2ban unattended-upgrades cron ntp fail2ban rsyslog
# ### Add PHP7 PPA
# Nextcloud requires PHP7, we will install the ppa from ubuntu php maintainer Ondřej Surý
# The PPA is located here https://launchpad.net/%7Eondrej/+archive/ubuntu/php
# Unattended upgrades are activated for the repository If it appears it's already
# installed, don't do it again so we can avoid an unnecessary call to apt-get update.
if [ ! -f /etc/apt/sources.list.d/ondrej-php-trusty.list ]; then
hide_output add-apt-repository -y ppa:ondrej/php
apt_add_repository_to_unattended_upgrades LP-PPA-ondrej-php:trusty
hide_output apt-get update
fi
# ### Suppress Upgrade Prompts # ### Suppress Upgrade Prompts
# Since Mail-in-a-Box might jump straight to 18.04 LTS, there's no need # When Ubuntu 20 comes out, we don't want users to be prompted to upgrade,
# to be reminded about 16.04 on every login. # because we don't yet support it.
if [ -f /etc/update-manager/release-upgrades ]; then if [ -f /etc/update-manager/release-upgrades ]; then
tools/editconf.py /etc/update-manager/release-upgrades Prompt=never tools/editconf.py /etc/update-manager/release-upgrades Prompt=never
rm -f /var/lib/ubuntu-release-upgrader/release-upgrade-available rm -f /var/lib/ubuntu-release-upgrader/release-upgrade-available
@@ -159,8 +155,8 @@ fi
# section) and syslog (see #328). There might be other issues, and it's # section) and syslog (see #328). There might be other issues, and it's
# not likely the user will want to change this, so we only ask on first # not likely the user will want to change this, so we only ask on first
# setup. # setup.
if [ -z "$NONINTERACTIVE" ]; then if [ -z "${NONINTERACTIVE:-}" ]; then
if [ ! -f /etc/timezone ] || [ ! -z $FIRST_TIME_SETUP ]; then if [ ! -f /etc/timezone ] || [ ! -z ${FIRST_TIME_SETUP:-} ]; then
# If the file is missing or this is the user's first time running # If the file is missing or this is the user's first time running
# Mail-in-a-Box setup, run the interactive timezone configuration # Mail-in-a-Box setup, run the interactive timezone configuration
# tool. # tool.
@@ -186,7 +182,6 @@ fi
# * DNSSEC signing keys (see `dns.sh`) # * DNSSEC signing keys (see `dns.sh`)
# * our management server's API key (via Python's os.urandom method) # * our management server's API key (via Python's os.urandom method)
# * Roundcube's SECRET_KEY (`webmail.sh`) # * Roundcube's SECRET_KEY (`webmail.sh`)
# * ownCloud's administrator account password (`owncloud.sh`)
# #
# Why /dev/urandom? It's the same as /dev/random, except that it doesn't wait # Why /dev/urandom? It's the same as /dev/random, except that it doesn't wait
# for a constant new stream of entropy. In practice, we only need a little # for a constant new stream of entropy. In practice, we only need a little
@@ -256,12 +251,12 @@ EOF
# Various virtualized environments like Docker and some VPSs don't provide #NODOC # Various virtualized environments like Docker and some VPSs don't provide #NODOC
# a kernel that supports iptables. To avoid error-like output in these cases, #NODOC # a kernel that supports iptables. To avoid error-like output in these cases, #NODOC
# we skip this if the user sets DISABLE_FIREWALL=1. #NODOC # we skip this if the user sets DISABLE_FIREWALL=1. #NODOC
if [ -z "$DISABLE_FIREWALL" ]; then if [ -z "${DISABLE_FIREWALL:-}" ]; then
# Install `ufw` which provides a simple firewall configuration. # Install `ufw` which provides a simple firewall configuration.
apt_install ufw apt_install ufw
# Allow incoming connections to SSH. # Allow incoming connections to SSH.
ufw_allow ssh; ufw_limit ssh;
# ssh might be running on an alternate port. Use sshd -T to dump sshd's #NODOC # ssh might be running on an alternate port. Use sshd -T to dump sshd's #NODOC
# settings, find the port it is supposedly running on, and open that port #NODOC # settings, find the port it is supposedly running on, and open that port #NODOC
@@ -271,7 +266,7 @@ if [ -z "$DISABLE_FIREWALL" ]; then
if [ "$SSH_PORT" != "22" ]; then if [ "$SSH_PORT" != "22" ]; then
echo Opening alternate SSH port $SSH_PORT. #NODOC echo Opening alternate SSH port $SSH_PORT. #NODOC
ufw_allow $SSH_PORT #NODOC ufw_limit $SSH_PORT #NODOC
fi fi
fi fi
@@ -281,50 +276,75 @@ fi #NODOC
# ### Local DNS Service # ### Local DNS Service
# Install a local DNS server, rather than using the DNS server provided by the # Install a local recursive DNS server --- i.e. for DNS queries made by
# ISP's network configuration. # local services running on this machine.
# #
# We do this to ensure that DNS queries # (This is unrelated to the box's public, non-recursive DNS server that
# that *we* make (i.e. looking up other external domains) perform DNSSEC checks. # answers remote queries about domain names hosted on this box. For that
# We could use Google's Public DNS, but we don't want to create a dependency on # see dns.sh.)
# Google per our goals of decentralization. `bind9`, as packaged for Ubuntu, has
# DNSSEC enabled by default via "dnssec-validation auto".
# #
# So we'll be running `bind9` bound to 127.0.0.1 for locally-issued DNS queries # The default systemd-resolved service provides local DNS name resolution. By default it
# and `nsd` bound to the public ethernet interface for remote DNS queries asking # is a recursive stub nameserver, which means it simply relays requests to an
# about our domain names. `nsd` is configured later. # external nameserver, usually provided by your ISP or configured in /etc/systemd/resolved.conf.
#
# This won't work for us for three reasons.
#
# 1) We have higher security goals --- we want DNSSEC to be enforced on all
# DNS queries (some upstream DNS servers do, some don't).
# 2) We will configure postfix to use DANE, which uses DNSSEC to find TLS
# certificates for remote servers. DNSSEC validation *must* be performed
# locally because we can't trust an unencrypted connection to an external
# DNS server.
# 3) DNS-based mail server blacklists (RBLs) typically block large ISP
# DNS servers because they only provide free data to small users. Since
# we use RBLs to block incoming mail from blacklisted IP addresses,
# we have to run our own DNS server. See #1424.
#
# systemd-resolved has a setting to perform local DNSSEC validation on all
# requests (in /etc/systemd/resolved.conf, set DNSSEC=yes), but because it's
# a stub server the main part of a request still goes through an upstream
# DNS server, which won't work for RBLs. So we really need a local recursive
# nameserver.
#
# We'll install `bind9`, which as packaged for Ubuntu, has DNSSEC enabled by default via "dnssec-validation auto".
# We'll have it be bound to 127.0.0.1 so that it does not interfere with
# the public, recursive nameserver `nsd` bound to the public ethernet interfaces.
# #
# About the settings: # About the settings:
# #
# * RESOLVCONF=yes will have `bind9` take over /etc/resolv.conf to tell
# local services that DNS queries are handled on localhost.
# * Adding -4 to OPTIONS will have `bind9` not listen on IPv6 addresses # * Adding -4 to OPTIONS will have `bind9` not listen on IPv6 addresses
# so that we're sure there's no conflict with nsd, our public domain # so that we're sure there's no conflict with nsd, our public domain
# name server, on IPV6. # name server, on IPV6.
# * The listen-on directive in named.conf.options restricts `bind9` to # * The listen-on directive in named.conf.options restricts `bind9` to
# binding to the loopback interface instead of all interfaces. # binding to the loopback interface instead of all interfaces.
apt_install bind9 resolvconf apt_install bind9
tools/editconf.py /etc/default/bind9 \ tools/editconf.py /etc/default/bind9 \
RESOLVCONF=yes \
"OPTIONS=\"-u bind -4\"" "OPTIONS=\"-u bind -4\""
if ! grep -q "listen-on " /etc/bind/named.conf.options; then if ! grep -q "listen-on " /etc/bind/named.conf.options; then
# Add a listen-on directive if it doesn't exist inside the options block. # Add a listen-on directive if it doesn't exist inside the options block.
sed -i "s/^}/\n\tlisten-on { 127.0.0.1; };\n}/" /etc/bind/named.conf.options sed -i "s/^}/\n\tlisten-on { 127.0.0.1; };\n}/" /etc/bind/named.conf.options
fi fi
if [ -f /etc/resolvconf/resolv.conf.d/original ]; then
echo "Archiving old resolv.conf (was /etc/resolvconf/resolv.conf.d/original, now /etc/resolvconf/resolv.conf.original)." #NODOC # First we'll disable systemd-resolved's management of resolv.conf and its stub server.
mv /etc/resolvconf/resolv.conf.d/original /etc/resolvconf/resolv.conf.original #NODOC # Breaking the symlink to /run/systemd/resolve/stub-resolv.conf means
fi # systemd-resolved will read it for DNS servers to use. Put in 127.0.0.1,
# which is where bind9 will be running. Obviously don't do this before
# installing bind9 or else apt won't be able to resolve a server to
# download bind9 from.
rm -f /etc/resolv.conf
tools/editconf.py /etc/systemd/resolved.conf DNSStubListener=no
echo "nameserver 127.0.0.1" > /etc/resolv.conf
# Restart the DNS services. # Restart the DNS services.
restart_service bind9 restart_service bind9
restart_service resolvconf systemctl restart systemd-resolved
# ### Fail2Ban Service # ### Fail2Ban Service
# Configure the Fail2Ban installation to prevent dumb bruce-force attacks against dovecot, postfix, ssh, etc. # Configure the Fail2Ban installation to prevent dumb bruce-force attacks against dovecot, postfix, ssh, etc.
rm -f /etc/fail2ban/jail.local # we used to use this file but don't anymore rm -f /etc/fail2ban/jail.local # we used to use this file but don't anymore
rm -f /etc/fail2ban/jail.d/defaults-debian.conf # removes default config so we can manage all of fail2ban rules in one config
cat conf/fail2ban/jails.conf \ cat conf/fail2ban/jails.conf \
| sed "s/PUBLIC_IP/$PUBLIC_IP/g" \ | sed "s/PUBLIC_IP/$PUBLIC_IP/g" \
| sed "s#STORAGE_ROOT#$STORAGE_ROOT#" \ | sed "s#STORAGE_ROOT#$STORAGE_ROOT#" \

View File

@@ -19,10 +19,7 @@ fi
echo "Installing Nginx (web server)..." echo "Installing Nginx (web server)..."
apt_install nginx php7.0-cli php7.0-fpm apt_install nginx php-cli php-fpm
# Set PHP7 as the default
update-alternatives --set php /usr/bin/php7.0
rm -f /etc/nginx/sites-enabled/default rm -f /etc/nginx/sites-enabled/default
@@ -34,30 +31,69 @@ sed "s#STORAGE_ROOT#$STORAGE_ROOT#" \
conf/nginx-ssl.conf > /etc/nginx/conf.d/ssl.conf conf/nginx-ssl.conf > /etc/nginx/conf.d/ssl.conf
# Fix some nginx defaults. # Fix some nginx defaults.
#
# The server_names_hash_bucket_size seems to prevent long domain names! # The server_names_hash_bucket_size seems to prevent long domain names!
# The default, according to nginx's docs, depends on "the size of the # The default, according to nginx's docs, depends on "the size of the
# processors cache line." It could be as low as 32. We fixed it at # processors cache line." It could be as low as 32. We fixed it at
# 64 in 2014 to accommodate a long domain name (20 characters?). But # 64 in 2014 to accommodate a long domain name (20 characters?). But
# even at 64, a 58-character domain name won't work (#93), so now # even at 64, a 58-character domain name won't work (#93), so now
# we're going up to 128. # we're going up to 128.
#
# Drop TLSv1.0, TLSv1.1, following the Mozilla "Intermediate" recommendations
# at https://ssl-config.mozilla.org/#server=nginx&server-version=1.17.0&config=intermediate&openssl-version=1.1.1.
tools/editconf.py /etc/nginx/nginx.conf -s \ tools/editconf.py /etc/nginx/nginx.conf -s \
server_names_hash_bucket_size="128;" server_names_hash_bucket_size="128;" \
ssl_protocols="TLSv1.2 TLSv1.3;"
# Tell PHP not to expose its version number in the X-Powered-By header. # Tell PHP not to expose its version number in the X-Powered-By header.
tools/editconf.py /etc/php/7.0/fpm/php.ini -c ';' \ tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
expose_php=Off expose_php=Off
# Set PHPs default charset to UTF-8, since we use it. See #367. # Set PHPs default charset to UTF-8, since we use it. See #367.
tools/editconf.py /etc/php/7.0/fpm/php.ini -c ';' \ tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
default_charset="UTF-8" default_charset="UTF-8"
# Switch from the dynamic process manager to the ondemand manager see #1216 # Configure the path environment for php-fpm
tools/editconf.py /etc/php/7.0/fpm/pool.d/www.conf -c ';' \ tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
pm=ondemand env[PATH]=/usr/local/bin:/usr/bin:/bin \
# Bump up PHP's max_children to support more concurrent connections # Configure php-fpm based on the amount of memory the machine has
tools/editconf.py /etc/php/7.0/fpm/pool.d/www.conf -c ';' \ # This is based on the nextcloud manual for performance tuning: https://docs.nextcloud.com/server/17/admin_manual/installation/server_tuning.html
pm.max_children=8 # Some synchronisation issues can occur when many people access the site at once.
# The pm=ondemand setting is used for memory constrained machines < 2GB, this is copied over from PR: 1216
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}' || /bin/true)
if [ $TOTAL_PHYSICAL_MEM -lt 1000000 ]
then
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
pm=ondemand \
pm.max_children=8 \
pm.start_servers=2 \
pm.min_spare_servers=1 \
pm.max_spare_servers=3
elif [ $TOTAL_PHYSICAL_MEM -lt 2000000 ]
then
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
pm=ondemand \
pm.max_children=16 \
pm.start_servers=4 \
pm.min_spare_servers=1 \
pm.max_spare_servers=6
elif [ $TOTAL_PHYSICAL_MEM -lt 3000000 ]
then
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
pm=dynamic \
pm.max_children=60 \
pm.start_servers=6 \
pm.min_spare_servers=3 \
pm.max_spare_servers=9
else
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
pm=dynamic \
pm.max_children=120 \
pm.start_servers=12 \
pm.min_spare_servers=6 \
pm.max_spare_servers=18
fi
# Other nginx settings will be configured by the management service # Other nginx settings will be configured by the management service
# since it depends on what domains we're serving, which we don't know # since it depends on what domains we're serving, which we don't know
@@ -94,24 +130,9 @@ if [ ! -f $STORAGE_ROOT/www/default/index.html ]; then
fi fi
chown -R $STORAGE_USER $STORAGE_ROOT/www chown -R $STORAGE_USER $STORAGE_ROOT/www
# We previously installed a custom init script to start the PHP FastCGI daemon. #NODOC
# Remove it now that we're using php5-fpm. #NODOC
if [ -L /etc/init.d/php-fastcgi ]; then
echo "Removing /etc/init.d/php-fastcgi, php5-cgi..." #NODOC
rm -f /etc/init.d/php-fastcgi #NODOC
hide_output update-rc.d php-fastcgi remove #NODOC
apt-get -y purge php5-cgi #NODOC
fi
# Remove obsoleted scripts. #NODOC
# exchange-autodiscover is now handled by Z-Push. #NODOC
for f in webfinger exchange-autodiscover; do #NODOC
rm -f /usr/local/bin/mailinabox-$f.php #NODOC
done #NODOC
# Start services. # Start services.
restart_service nginx restart_service nginx
restart_service php7.0-fpm restart_service php7.2-fpm
# Open ports. # Open ports.
ufw_allow http ufw_allow http

View File

@@ -22,25 +22,18 @@ source /etc/mailinabox.conf # load global vars
echo "Installing Roundcube (webmail)..." echo "Installing Roundcube (webmail)..."
apt_install \ apt_install \
dbconfig-common \ dbconfig-common \
php7.0-cli php7.0-sqlite php7.0-mcrypt php7.0-intl php7.0-json php7.0-common \ php-cli php-sqlite3 php-intl php-json php-common php-curl php-ldap \
php7.0-gd php7.0-pspell tinymce libjs-jquery libjs-jquery-mousewheel libmagic1 php7.0-mbstring php-gd php-pspell tinymce libjs-jquery libjs-jquery-mousewheel libmagic1 php-mbstring
apt_get_quiet remove php-mail-mimedecode # no longer needed since Roundcube 1.1.3
# We used to install Roundcube from Ubuntu, without triggering the dependencies #NODOC
# on Apache and MySQL, by downloading the debs and installing them manually. #NODOC
# Now that we're beyond that, get rid of those debs before installing from source. #NODOC
apt-get purge -qq -y roundcube* #NODOC
# Install Roundcube from source if it is not already present or if it is out of date. # Install Roundcube from source if it is not already present or if it is out of date.
# Combine the Roundcube version number with the commit hash of plugins to track # Combine the Roundcube version number with the commit hash of plugins to track
# whether we have the latest version of everything. # whether we have the latest version of everything.
VERSION=1.3.3 VERSION=1.4.7
HASH=903a4eb1bfc25e9a08d782a7f98502cddfa579de HASH=49F194D25AC7B9BF175BD52285BB61CDE7BAED44
PERSISTENT_LOGIN_VERSION=dc5ca3d3f4415cc41edb2fde533c8a8628a94c76 PERSISTENT_LOGIN_VERSION=6b3fc450cae23ccb2f393d0ef67aa319e877e435
HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5 HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5
CARDDAV_VERSION=2.0.4 CARDDAV_VERSION=3.0.3
CARDDAV_HASH=d93f3cfb3038a519e71c7c3212c1d16f5da609a4 CARDDAV_HASH=d1e3b0d851ffa2c6bd42bf0c04f70d0e1d0d78f8
UPDATE_KEY=$VERSION:$PERSISTENT_LOGIN_VERSION:$HTML5_NOTIFIER_VERSION:$CARDDAV_VERSION UPDATE_KEY=$VERSION:$PERSISTENT_LOGIN_VERSION:$HTML5_NOTIFIER_VERSION:$CARDDAV_VERSION
@@ -58,6 +51,13 @@ elif [[ "$UPDATE_KEY" != `cat /usr/local/lib/roundcubemail/version` ]]; then
needs_update=1 #NODOC needs_update=1 #NODOC
fi fi
if [ $needs_update == 1 ]; then if [ $needs_update == 1 ]; then
# if upgrading from 1.3.x, clear the temp_dir
if [ -f /usr/local/lib/roundcubemail/version ]; then
if [ "$(cat /usr/local/lib/roundcubemail/version | cut -c1-3)" == '1.3' ]; then
find /var/tmp/roundcubemail/ -type f ! -name 'RCMTEMP*' -delete
fi
fi
# install roundcube # install roundcube
wget_verify \ wget_verify \
https://github.com/roundcube/roundcubemail/releases/download/$VERSION/roundcubemail-$VERSION-complete.tar.gz \ https://github.com/roundcube/roundcubemail/releases/download/$VERSION/roundcubemail-$VERSION-complete.tar.gz \
@@ -117,9 +117,6 @@ cat > $RCM_CONFIG <<EOF;
); );
\$config['imap_timeout'] = 15; \$config['imap_timeout'] = 15;
\$config['smtp_server'] = 'tls://127.0.0.1'; \$config['smtp_server'] = 'tls://127.0.0.1';
\$config['smtp_port'] = 587;
\$config['smtp_user'] = '%u';
\$config['smtp_pass'] = '%p';
\$config['smtp_conn_options'] = array( \$config['smtp_conn_options'] = array(
'ssl' => array( 'ssl' => array(
'verify_peer' => false, 'verify_peer' => false,
@@ -130,7 +127,7 @@ cat > $RCM_CONFIG <<EOF;
\$config['product_name'] = '$PRIMARY_HOSTNAME Webmail'; \$config['product_name'] = '$PRIMARY_HOSTNAME Webmail';
\$config['des_key'] = '$SECRET_KEY'; \$config['des_key'] = '$SECRET_KEY';
\$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'persistent_login', 'carddav'); \$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'persistent_login', 'carddav');
\$config['skin'] = 'larry'; \$config['skin'] = 'elastic';
\$config['login_autocomplete'] = 2; \$config['login_autocomplete'] = 2;
\$config['password_charset'] = 'UTF-8'; \$config['password_charset'] = 'UTF-8';
\$config['junk_mbox'] = 'Spam'; \$config['junk_mbox'] = 'Spam';
@@ -155,6 +152,7 @@ cat > ${RCM_PLUGIN_DIR}/carddav/config.inc.php <<EOF;
'preemptive_auth' => '1', 'preemptive_auth' => '1',
'hide' => false, 'hide' => false,
); );
?>
EOF EOF
# Create writable directories. # Create writable directories.
@@ -162,7 +160,7 @@ mkdir -p /var/log/roundcubemail /var/tmp/roundcubemail $STORAGE_ROOT/mail/roundc
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. # Ensure the log file monitored by fail2ban exists, or else fail2ban can't start.
sudo -u www-data touch /var/log/roundcubemail/errors sudo -u www-data touch /var/log/roundcubemail/errors.log
# Password changing plugin settings # Password changing plugin settings
# The config comes empty by default, so we need the settings # The config comes empty by default, so we need the settings
@@ -199,5 +197,5 @@ chown www-data:www-data $STORAGE_ROOT/mail/roundcube/roundcube.sqlite
chmod 664 $STORAGE_ROOT/mail/roundcube/roundcube.sqlite chmod 664 $STORAGE_ROOT/mail/roundcube/roundcube.sqlite
# Enable PHP modules. # Enable PHP modules.
phpenmod -v php7.0 mcrypt imap phpenmod -v php mcrypt imap
restart_service php7.0-fpm restart_service php7.2-fpm

View File

@@ -17,12 +17,13 @@ source /etc/mailinabox.conf # load global vars
echo "Installing Z-Push (Exchange/ActiveSync server)..." echo "Installing Z-Push (Exchange/ActiveSync server)..."
apt_install \ apt_install \
php7.0-soap php7.0-imap libawl-php php7.0-xsl php-soap php-imap libawl-php php-xsl
phpenmod -v php7.0 imap phpenmod -v php imap
# Copy Z-Push into place. # Copy Z-Push into place.
VERSION=2.3.8 VERSION=2.5.2
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
@@ -31,13 +32,14 @@ elif [[ $VERSION != `cat /usr/local/lib/z-push/version` ]]; then
needs_update=1 #NODOC needs_update=1 #NODOC
fi fi
if [ $needs_update == 1 ]; then if [ $needs_update == 1 ]; then
rm -rf /usr/local/lib/z-push # Download
wget_verify "https://stash.z-hub.io/rest/api/latest/projects/ZP/repos/z-push/archive?at=refs%2Ftags%2F$VERSION&format=zip" $TARGETHASH /tmp/z-push.zip
git_clone https://stash.z-hub.io/scm/zp/z-push.git $VERSION '' /tmp/z-push # Extract into place.
rm -rf /usr/local/lib/z-push /tmp/z-push
mkdir /usr/local/lib/z-push unzip -q /tmp/z-push.zip -d /tmp/z-push
cp -r /tmp/z-push/src/* /usr/local/lib/z-push mv /tmp/z-push/src /usr/local/lib/z-push
rm -rf /tmp/z-push rm -rf /tmp/z-push.zip /tmp/z-push
rm -f /usr/sbin/z-push-{admin,top} rm -f /usr/sbin/z-push-{admin,top}
ln -s /usr/local/lib/z-push/z-push-admin.php /usr/sbin/z-push-admin ln -s /usr/local/lib/z-push/z-push-admin.php /usr/sbin/z-push-admin
@@ -100,7 +102,7 @@ EOF
# Restart service. # Restart service.
restart_service php7.0-fpm restart_service php7.2-fpm
# Fix states after upgrade # Fix states after upgrade

View File

@@ -90,6 +90,26 @@ def pop_test():
if M: if M:
M.quit() M.quit()
def managesieve_test():
# We don't have a Python sieve client, so we'll
# just run the IMAP client and see what happens.
import imaplib
try:
M = imaplib.IMAP4(hostname, 4190)
except ConnectionRefusedError:
# looks like fail2ban worked
raise IsBlocked()
try:
M.login("fakeuser", "fakepassword")
raise Exception("authentication didn't fail")
except imaplib.IMAP4.error:
# authentication should fail
pass
finally:
M.logout() # shuts down connection, has nothing to do with login()
def http_test(url, expected_status, postdata=None, qsargs=None, auth=None): def http_test(url, expected_status, postdata=None, qsargs=None, auth=None):
import urllib.parse import urllib.parse
import requests import requests
@@ -208,6 +228,9 @@ if __name__ == "__main__":
# POP # POP
run_test(pop_test, [], 20, 30, 4) run_test(pop_test, [], 20, 30, 4)
# Managesieve
run_test(managesieve_test, [], 20, 30, 4)
# Mail-in-a-Box control panel # Mail-in-a-Box control panel
run_test(http_test, ["/admin/me", 200], 20, 30, 1) run_test(http_test, ["/admin/me", 200], 20, 30, 1)

View File

@@ -17,7 +17,7 @@
# through some other host you can ssh into (maybe the box # through some other host you can ssh into (maybe the box
# itself?): # itself?):
# #
# python3 --proxy user@ssh_host yourservername # python3 tls.py --proxy user@ssh_host yourservername
# #
# (This will launch "ssh -N -L10023:yourservername:testport user@ssh_host" # (This will launch "ssh -N -L10023:yourservername:testport user@ssh_host"
# to create a tunnel.) # to create a tunnel.)
@@ -128,7 +128,7 @@ def sslyze(opts, port, ok_ciphers):
proxy_proc.terminate() proxy_proc.terminate()
try: try:
proxy_proc.wait(5) proxy_proc.wait(5)
except TimeoutExpired: except subprocess.TimeoutExpired:
proxy_proc.kill() proxy_proc.kill()
# Get a list of OpenSSL cipher names. # Get a list of OpenSSL cipher names.

View File

@@ -13,18 +13,18 @@ PORT 25
* Session Resumption: * Session Resumption:
With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts). With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: NOT SUPPORTED - TLS ticket not assigned. With TLS Session Tickets: OK - Supported
* SSLV2 Cipher Suites: * SSLV2 Cipher Suites:
Server rejected all cipher suites. Server rejected all cipher suites.
* TLSV1_2 Cipher Suites: * TLSV1_2 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES256-GCM-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok ECDHE-RSA-AES256-GCM-SHA384 ECDH-521 bits 256 bits 250 2.0.0 Ok
Accepted: Accepted:
ECDHE-RSA-AES256-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok ECDHE-RSA-AES256-SHA384 ECDH-521 bits 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES256-GCM-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok ECDHE-RSA-AES256-GCM-SHA384 ECDH-521 bits 256 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits 250 2.0.0 Ok DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
@@ -33,9 +33,9 @@ PORT 25
AES256-SHA256 - 256 bits 250 2.0.0 Ok AES256-SHA256 - 256 bits 250 2.0.0 Ok
AES256-SHA - 256 bits 250 2.0.0 Ok AES256-SHA - 256 bits 250 2.0.0 Ok
AES256-GCM-SHA384 - 256 bits 250 2.0.0 Ok AES256-GCM-SHA384 - 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA256 ECDH-256 bits 128 bits 250 2.0.0 Ok ECDHE-RSA-AES128-SHA256 ECDH-521 bits 128 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok ECDHE-RSA-AES128-SHA ECDH-521 bits 128 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-GCM-SHA256 ECDH-256 bits 128 bits 250 2.0.0 Ok ECDHE-RSA-AES128-GCM-SHA256 ECDH-521 bits 128 bits 250 2.0.0 Ok
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits 250 2.0.0 Ok DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits 250 2.0.0 Ok
@@ -46,56 +46,47 @@ PORT 25
AES128-SHA256 - 128 bits 250 2.0.0 Ok AES128-SHA256 - 128 bits 250 2.0.0 Ok
AES128-SHA - 128 bits 250 2.0.0 Ok AES128-SHA - 128 bits 250 2.0.0 Ok
AES128-GCM-SHA256 - 128 bits 250 2.0.0 Ok AES128-GCM-SHA256 - 128 bits 250 2.0.0 Ok
ECDHE-RSA-DES-CBC3-SHA ECDH-256 bits 112 bits 250 2.0.0 Ok
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits 250 2.0.0 Ok
DES-CBC3-SHA - 112 bits 250 2.0.0 Ok
* TLSV1_1 Cipher Suites: * TLSV1_1 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
Accepted: Accepted:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
AES256-SHA - 256 bits 250 2.0.0 Ok AES256-SHA - 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok ECDHE-RSA-AES128-SHA ECDH-521 bits 128 bits 250 2.0.0 Ok
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
SEED-SHA - 128 bits 250 2.0.0 Ok SEED-SHA - 128 bits 250 2.0.0 Ok
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
AES128-SHA - 128 bits 250 2.0.0 Ok AES128-SHA - 128 bits 250 2.0.0 Ok
ECDHE-RSA-DES-CBC3-SHA ECDH-256 bits 112 bits 250 2.0.0 Ok
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits 250 2.0.0 Ok
DES-CBC3-SHA - 112 bits 250 2.0.0 Ok
* TLSV1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
Accepted:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
AES256-SHA - 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
SEED-SHA - 128 bits 250 2.0.0 Ok
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
AES128-SHA - 128 bits 250 2.0.0 Ok
ECDHE-RSA-DES-CBC3-SHA ECDH-256 bits 112 bits 250 2.0.0 Ok
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits 250 2.0.0 Ok
DES-CBC3-SHA - 112 bits 250 2.0.0 Ok
* SSLV3 Cipher Suites: * SSLV3 Cipher Suites:
Server rejected all cipher suites. Server rejected all cipher suites.
* TLSV1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
Accepted:
ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
AES256-SHA - 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA ECDH-521 bits 128 bits 250 2.0.0 Ok
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
SEED-SHA - 128 bits 250 2.0.0 Ok
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
AES128-SHA - 128 bits 250 2.0.0 Ok
Should Not Offer: (none -- good) Should Not Offer: (none -- good)
Could Also Offer: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-DSS-AES256-SHA256, DHE-DSS-CAMELLIA128-SHA, DHE-DSS-CAMELLIA256-SHA, DHE-DSS-SEED-SHA, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-DES-CBC3-SHA Could Also Offer: AES128-CCM, AES128-CCM8, AES256-CCM, AES256-CCM8, CAMELLIA128-SHA256, CAMELLIA256-SHA256, DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-DSS-AES256-SHA256, DHE-DSS-CAMELLIA128-SHA, DHE-DSS-CAMELLIA128-SHA256, DHE-DSS-CAMELLIA256-SHA, DHE-DSS-CAMELLIA256-SHA256, DHE-DSS-SEED-SHA, DHE-RSA-AES128-CCM, DHE-RSA-AES128-CCM8, DHE-RSA-AES256-CCM, DHE-RSA-AES256-CCM8, DHE-RSA-CAMELLIA128-SHA256, DHE-RSA-CAMELLIA256-SHA256, DHE-RSA-CHACHA20-POLY1305, ECDHE-ECDSA-AES128-CCM, ECDHE-ECDSA-AES128-CCM8, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-CCM, ECDHE-ECDSA-AES256-CCM8, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-CAMELLIA128-SHA256, ECDHE-ECDSA-CAMELLIA256-SHA384, ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CAMELLIA128-SHA256, ECDHE-RSA-CAMELLIA256-SHA384, ECDHE-RSA-CHACHA20-POLY1305
Supported Clients: BingPreview/Jan 2015, OpenSSL/1.0.2, Yahoo Slurp/Jan 2015, OpenSSL/1.0.1l, YandexBot/Jan 2015, Android/4.4.2, Safari/7/iOS 7.1, Safari/8/iOS 8.1.2, Safari/6/iOS 6.0.1, Safari/7/OS X 10.9, Safari/8/OS X 10.10, Baidu/Jan 2015, Firefox/31.3.0 ESR/Win 7, IE/11/Win 7, IE/11/Win 8.1, IE Mobile/11/Win Phone 8.1, Java/8u31, Android/5.0.0, Googlebot/Feb 2015, Chrome/42/OS X, Android/4.1.1, Android/4.3, Android/4.0.4, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, Safari/6.0.4/OS X 10.8.4, Firefox/37/OS X, OpenSSL/0.9.8y, Java/7u25, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, Android/2.3.7, Java/6u45, IE/8/XP Supported Clients: Yahoo Slurp/Jan 2015, OpenSSL/1.0.2, BingPreview/Jan 2015, OpenSSL/1.0.1l, YandexBot/Jan 2015, Android/4.4.2, Safari/6/iOS 6.0.1, Safari/8/OS X 10.10, Safari/7/OS X 10.9, Safari/7/iOS 7.1, IE/11/Win 8.1, Safari/8/iOS 8.1.2, IE Mobile/11/Win Phone 8.1, IE/11/Win 7, Baidu/Jan 2015, Firefox/31.3.0 ESR/Win 7, Android/5.0.0, Chrome/42/OS X, Java/8u31, Googlebot/Feb 2015, Firefox/37/OS X, Android/4.3, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, Android/4.0.4, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, OpenSSL/0.9.8y, Java/7u25, Android/2.3.7, Java/6u45
PORT 587 PORT 587
-------- --------
@@ -112,18 +103,18 @@ PORT 587
* Session Resumption: * Session Resumption:
With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts). With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: NOT SUPPORTED - TLS ticket not assigned. With TLS Session Tickets: OK - Supported
* SSLV2 Cipher Suites: * SSLV2 Cipher Suites:
Server rejected all cipher suites. Server rejected all cipher suites.
* TLSV1_2 Cipher Suites: * TLSV1_2 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES256-GCM-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok ECDHE-RSA-AES256-GCM-SHA384 ECDH-521 bits 256 bits 250 2.0.0 Ok
Accepted: Accepted:
ECDHE-RSA-AES256-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok ECDHE-RSA-AES256-SHA384 ECDH-521 bits 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES256-GCM-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok ECDHE-RSA-AES256-GCM-SHA384 ECDH-521 bits 256 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits 250 2.0.0 Ok DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
@@ -132,9 +123,9 @@ PORT 587
AES256-SHA256 - 256 bits 250 2.0.0 Ok AES256-SHA256 - 256 bits 250 2.0.0 Ok
AES256-SHA - 256 bits 250 2.0.0 Ok AES256-SHA - 256 bits 250 2.0.0 Ok
AES256-GCM-SHA384 - 256 bits 250 2.0.0 Ok AES256-GCM-SHA384 - 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA256 ECDH-256 bits 128 bits 250 2.0.0 Ok ECDHE-RSA-AES128-SHA256 ECDH-521 bits 128 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok ECDHE-RSA-AES128-SHA ECDH-521 bits 128 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-GCM-SHA256 ECDH-256 bits 128 bits 250 2.0.0 Ok ECDHE-RSA-AES128-GCM-SHA256 ECDH-521 bits 128 bits 250 2.0.0 Ok
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits 250 2.0.0 Ok DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits 250 2.0.0 Ok
@@ -148,31 +139,14 @@ PORT 587
* TLSV1_1 Cipher Suites: * TLSV1_1 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
Accepted: Accepted:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
AES256-SHA - 256 bits 250 2.0.0 Ok AES256-SHA - 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok ECDHE-RSA-AES128-SHA ECDH-521 bits 128 bits 250 2.0.0 Ok
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
SEED-SHA - 128 bits 250 2.0.0 Ok
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
AES128-SHA - 128 bits 250 2.0.0 Ok
* TLSV1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
Accepted:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
AES256-SHA - 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
@@ -183,9 +157,26 @@ PORT 587
* SSLV3 Cipher Suites: * SSLV3 Cipher Suites:
Server rejected all cipher suites. Server rejected all cipher suites.
* TLSV1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
Accepted:
ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
AES256-SHA - 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA ECDH-521 bits 128 bits 250 2.0.0 Ok
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
SEED-SHA - 128 bits 250 2.0.0 Ok
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
AES128-SHA - 128 bits 250 2.0.0 Ok
Should Not Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, CAMELLIA128-SHA, CAMELLIA256-SHA, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA, DHE-RSA-AES256-SHA256, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA, DHE-RSA-SEED-SHA, ECDHE-RSA-AES128-SHA, ECDHE-RSA-AES256-SHA, SEED-SHA Should Not Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, CAMELLIA128-SHA, CAMELLIA256-SHA, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA, DHE-RSA-AES256-SHA256, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA, DHE-RSA-SEED-SHA, ECDHE-RSA-AES128-SHA, ECDHE-RSA-AES256-SHA, SEED-SHA
Could Also Offer: ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA384 Could Also Offer: ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305
Supported Clients: BingPreview/Jan 2015, OpenSSL/1.0.2, Yahoo Slurp/Jan 2015, OpenSSL/1.0.1l, YandexBot/Jan 2015, Android/4.4.2, Safari/7/iOS 7.1, IE/11/Win 7, IE/11/Win 8.1, Safari/8/iOS 8.1.2, Safari/6/iOS 6.0.1, Safari/7/OS X 10.9, IE Mobile/11/Win Phone 8.1, Safari/8/OS X 10.10, Baidu/Jan 2015, Firefox/31.3.0 ESR/Win 7, Java/8u31, Android/5.0.0, Chrome/42/OS X, Googlebot/Feb 2015, Firefox/37/OS X, Android/4.1.1, Android/4.3, Android/4.0.4, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, Safari/6.0.4/OS X 10.8.4, OpenSSL/0.9.8y, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, Java/7u25, Android/2.3.7, Java/6u45 Supported Clients: Yahoo Slurp/Jan 2015, OpenSSL/1.0.2, BingPreview/Jan 2015, OpenSSL/1.0.1l, YandexBot/Jan 2015, Android/4.4.2, Safari/6/iOS 6.0.1, Safari/8/OS X 10.10, Safari/7/OS X 10.9, Safari/7/iOS 7.1, IE/11/Win 8.1, Safari/8/iOS 8.1.2, IE Mobile/11/Win Phone 8.1, IE/11/Win 7, Baidu/Jan 2015, Firefox/31.3.0 ESR/Win 7, Android/5.0.0, Chrome/42/OS X, Java/8u31, Googlebot/Feb 2015, Firefox/37/OS X, Android/4.3, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, Android/4.0.4, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, OpenSSL/0.9.8y, Java/7u25, Android/2.3.7, Java/6u45
PORT 443 PORT 443
-------- --------
@@ -197,19 +188,19 @@ PORT 443
Client-initiated Renegotiations: OK - Rejected Client-initiated Renegotiations: OK - Rejected
Secure Renegotiation: OK - Supported Secure Renegotiation: OK - Supported
* OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed
* HTTP Strict Transport Security: * HTTP Strict Transport Security:
OK - HSTS header received: max-age=15768000 OK - HSTS header received: max-age=15768000
Unhandled exception when processing --chrome_sha1:
exceptions.TypeError - Incorrect padding
* Session Resumption: * Session Resumption:
With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts). With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: OK - Supported With TLS Session Tickets: OK - Supported
* OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed
Unhandled exception when processing --chrome_sha1:
exceptions.TypeError - Incorrect padding
* SSLV2 Cipher Suites: * SSLV2 Cipher Suites:
Server rejected all cipher suites. Server rejected all cipher suites.
@@ -235,9 +226,6 @@ exceptions.TypeError - Incorrect padding
AES128-SHA256 - 128 bits HTTP 200 OK AES128-SHA256 - 128 bits HTTP 200 OK
AES128-SHA - 128 bits HTTP 200 OK AES128-SHA - 128 bits HTTP 200 OK
AES128-GCM-SHA256 - 128 bits HTTP 200 OK AES128-GCM-SHA256 - 128 bits HTTP 200 OK
ECDHE-RSA-DES-CBC3-SHA ECDH-256 bits 112 bits HTTP 200 OK
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits HTTP 200 OK
DES-CBC3-SHA - 112 bits HTTP 200 OK
* TLSV1_1 Cipher Suites: * TLSV1_1 Cipher Suites:
Preferred: Preferred:
@@ -249,9 +237,9 @@ exceptions.TypeError - Incorrect padding
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK
DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK
AES128-SHA - 128 bits HTTP 200 OK AES128-SHA - 128 bits HTTP 200 OK
ECDHE-RSA-DES-CBC3-SHA ECDH-256 bits 112 bits HTTP 200 OK
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits HTTP 200 OK * SSLV3 Cipher Suites:
DES-CBC3-SHA - 112 bits HTTP 200 OK Server rejected all cipher suites.
* TLSV1 Cipher Suites: * TLSV1 Cipher Suites:
Preferred: Preferred:
@@ -263,16 +251,10 @@ exceptions.TypeError - Incorrect padding
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK
DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK
AES128-SHA - 128 bits HTTP 200 OK AES128-SHA - 128 bits HTTP 200 OK
ECDHE-RSA-DES-CBC3-SHA ECDH-256 bits 112 bits HTTP 200 OK
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits HTTP 200 OK
DES-CBC3-SHA - 112 bits HTTP 200 OK
* SSLV3 Cipher Suites:
Server rejected all cipher suites.
Should Not Offer: (none -- good) Should Not Offer: (none -- good)
Could Also Offer: ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-DES-CBC3-SHA Could Also Offer: ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305
Supported Clients: BingPreview/Jan 2015, OpenSSL/1.0.2, YandexBot/Jan 2015, OpenSSL/1.0.1l, Yahoo Slurp/Jan 2015, Android/4.4.2, Safari/7/iOS 7.1, Safari/8/iOS 8.1.2, Safari/6/iOS 6.0.1, Safari/7/OS X 10.9, Safari/8/OS X 10.10, IE/11/Win 7, IE/11/Win 8.1, IE Mobile/11/Win Phone 8.1, Java/8u31, Android/5.0.0, Googlebot/Feb 2015, Firefox/31.3.0 ESR/Win 7, Chrome/42/OS X, Baidu/Jan 2015, Android/4.1.1, Android/4.3, Android/4.0.4, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, Safari/6.0.4/OS X 10.8.4, Firefox/37/OS X, OpenSSL/0.9.8y, Java/7u25, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, Java/6u45, Android/2.3.7, IE/8/XP Supported Clients: Yahoo Slurp/Jan 2015, OpenSSL/1.0.2, YandexBot/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, Android/4.4.2, Safari/6/iOS 6.0.1, Safari/8/OS X 10.10, Safari/7/OS X 10.9, Safari/7/iOS 7.1, IE/11/Win 8.1, Safari/8/iOS 8.1.2, IE Mobile/11/Win Phone 8.1, IE/11/Win 7, Android/5.0.0, Chrome/42/OS X, Java/8u31, Googlebot/Feb 2015, Firefox/31.3.0 ESR/Win 7, Firefox/37/OS X, Android/4.3, Android/4.2.2, Baidu/Jan 2015, Safari/5.1.9/OS X 10.6.8, Android/4.0.4, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, OpenSSL/0.9.8y, Java/7u25, Android/2.3.7, Java/6u45
PORT 993 PORT 993
-------- --------
@@ -286,13 +268,13 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
* OpenSSL Heartbleed: * OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed OK - Not vulnerable to Heartbleed
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* Session Resumption: * Session Resumption:
With Session IDs: NOT SUPPORTED (0 successful, 5 failed, 0 errors, 5 total attempts). With Session IDs: NOT SUPPORTED (0 successful, 5 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: NOT SUPPORTED - TLS ticket assigned but not accepted. With TLS Session Tickets: NOT SUPPORTED - TLS ticket assigned but not accepted.
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* TLSV1_2 Cipher Suites: * TLSV1_2 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits
@@ -315,9 +297,6 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
AES128-SHA256 - 128 bits AES128-SHA256 - 128 bits
AES128-SHA - 128 bits AES128-SHA - 128 bits
AES128-GCM-SHA256 - 128 bits AES128-GCM-SHA256 - 128 bits
ECDHE-RSA-DES-CBC3-SHA ECDH-384 bits 112 bits
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits
DES-CBC3-SHA - 112 bits
* TLSV1_1 Cipher Suites: * TLSV1_1 Cipher Suites:
Preferred: Preferred:
@@ -329,9 +308,9 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-AES128-SHA DH-2048 bits 128 bits DHE-RSA-AES128-SHA DH-2048 bits 128 bits
AES128-SHA - 128 bits AES128-SHA - 128 bits
ECDHE-RSA-DES-CBC3-SHA ECDH-384 bits 112 bits
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits * SSLV3 Cipher Suites:
DES-CBC3-SHA - 112 bits Server rejected all cipher suites.
* TLSV1 Cipher Suites: * TLSV1 Cipher Suites:
Preferred: Preferred:
@@ -343,16 +322,10 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-AES128-SHA DH-2048 bits 128 bits DHE-RSA-AES128-SHA DH-2048 bits 128 bits
AES128-SHA - 128 bits AES128-SHA - 128 bits
ECDHE-RSA-DES-CBC3-SHA ECDH-384 bits 112 bits
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits
DES-CBC3-SHA - 112 bits
* SSLV3 Cipher Suites: Should Not Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA, DHE-RSA-AES256-SHA256, ECDHE-RSA-AES128-SHA, ECDHE-RSA-AES256-SHA
Server rejected all cipher suites. Could Also Offer: ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305
Supported Clients: Yahoo Slurp/Jan 2015, OpenSSL/1.0.2, YandexBot/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, Android/4.4.2, Safari/6/iOS 6.0.1, Safari/8/OS X 10.10, Safari/7/OS X 10.9, Safari/7/iOS 7.1, IE/11/Win 8.1, Safari/8/iOS 8.1.2, IE Mobile/11/Win Phone 8.1, IE/11/Win 7, Android/5.0.0, Chrome/42/OS X, Java/8u31, Googlebot/Feb 2015, Firefox/31.3.0 ESR/Win 7, Firefox/37/OS X, Android/4.3, Android/4.2.2, Baidu/Jan 2015, Safari/5.1.9/OS X 10.6.8, Android/4.0.4, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, OpenSSL/0.9.8y, Java/7u25, Android/2.3.7, Java/6u45
Should Not Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, DES-CBC3-SHA, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA, DHE-RSA-AES256-SHA256, ECDHE-RSA-AES128-SHA, ECDHE-RSA-AES256-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA
Could Also Offer: ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA384
Supported Clients: BingPreview/Jan 2015, OpenSSL/1.0.2, YandexBot/Jan 2015, OpenSSL/1.0.1l, Yahoo Slurp/Jan 2015, Android/4.4.2, Safari/7/iOS 7.1, Safari/8/iOS 8.1.2, Safari/6/iOS 6.0.1, Safari/7/OS X 10.9, Safari/8/OS X 10.10, IE/11/Win 7, IE/11/Win 8.1, IE Mobile/11/Win Phone 8.1, Java/8u31, Android/5.0.0, Googlebot/Feb 2015, Firefox/31.3.0 ESR/Win 7, Chrome/42/OS X, Baidu/Jan 2015, Android/4.1.1, Android/4.3, Android/4.0.4, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, Safari/6.0.4/OS X 10.8.4, Firefox/37/OS X, OpenSSL/0.9.8y, Java/7u25, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, Java/6u45, Android/2.3.7, IE/8/XP
PORT 995 PORT 995
-------- --------
@@ -366,13 +339,13 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
* OpenSSL Heartbleed: * OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed OK - Not vulnerable to Heartbleed
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* Session Resumption: * Session Resumption:
With Session IDs: NOT SUPPORTED (0 successful, 5 failed, 0 errors, 5 total attempts). With Session IDs: NOT SUPPORTED (0 successful, 5 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: NOT SUPPORTED - TLS ticket assigned but not accepted. With TLS Session Tickets: NOT SUPPORTED - TLS ticket assigned but not accepted.
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* TLSV1_2 Cipher Suites: * TLSV1_2 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits
@@ -395,9 +368,6 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
AES128-SHA256 - 128 bits AES128-SHA256 - 128 bits
AES128-SHA - 128 bits AES128-SHA - 128 bits
AES128-GCM-SHA256 - 128 bits AES128-GCM-SHA256 - 128 bits
ECDHE-RSA-DES-CBC3-SHA ECDH-384 bits 112 bits
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits
DES-CBC3-SHA - 112 bits
* TLSV1_1 Cipher Suites: * TLSV1_1 Cipher Suites:
Preferred: Preferred:
@@ -409,9 +379,9 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-AES128-SHA DH-2048 bits 128 bits DHE-RSA-AES128-SHA DH-2048 bits 128 bits
AES128-SHA - 128 bits AES128-SHA - 128 bits
ECDHE-RSA-DES-CBC3-SHA ECDH-384 bits 112 bits
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits * SSLV3 Cipher Suites:
DES-CBC3-SHA - 112 bits Server rejected all cipher suites.
* TLSV1 Cipher Suites: * TLSV1 Cipher Suites:
Preferred: Preferred:
@@ -423,14 +393,8 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-AES128-SHA DH-2048 bits 128 bits DHE-RSA-AES128-SHA DH-2048 bits 128 bits
AES128-SHA - 128 bits AES128-SHA - 128 bits
ECDHE-RSA-DES-CBC3-SHA ECDH-384 bits 112 bits
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits
DES-CBC3-SHA - 112 bits
* SSLV3 Cipher Suites: Should Not Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA, DHE-RSA-AES256-SHA256, ECDHE-RSA-AES128-SHA, ECDHE-RSA-AES256-SHA
Server rejected all cipher suites. Could Also Offer: ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305
Supported Clients: Yahoo Slurp/Jan 2015, OpenSSL/1.0.2, YandexBot/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, Android/4.4.2, Safari/6/iOS 6.0.1, Safari/8/OS X 10.10, Safari/7/OS X 10.9, Safari/7/iOS 7.1, IE/11/Win 8.1, Safari/8/iOS 8.1.2, IE Mobile/11/Win Phone 8.1, IE/11/Win 7, Android/5.0.0, Chrome/42/OS X, Java/8u31, Googlebot/Feb 2015, Firefox/31.3.0 ESR/Win 7, Firefox/37/OS X, Android/4.3, Android/4.2.2, Baidu/Jan 2015, Safari/5.1.9/OS X 10.6.8, Android/4.0.4, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, OpenSSL/0.9.8y, Java/7u25, Android/2.3.7, Java/6u45
Should Not Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, DES-CBC3-SHA, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA, DHE-RSA-AES256-SHA256, ECDHE-RSA-AES128-SHA, ECDHE-RSA-AES256-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA
Could Also Offer: ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA384
Supported Clients: BingPreview/Jan 2015, OpenSSL/1.0.2, YandexBot/Jan 2015, OpenSSL/1.0.1l, Yahoo Slurp/Jan 2015, Android/4.4.2, Safari/7/iOS 7.1, Safari/8/iOS 8.1.2, Safari/6/iOS 6.0.1, Safari/7/OS X 10.9, Safari/8/OS X 10.10, IE/11/Win 7, IE/11/Win 8.1, IE Mobile/11/Win Phone 8.1, Java/8u31, Android/5.0.0, Googlebot/Feb 2015, Firefox/31.3.0 ESR/Win 7, Chrome/42/OS X, Baidu/Jan 2015, Android/4.1.1, Android/4.3, Android/4.0.4, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, Safari/6.0.4/OS X 10.8.4, Firefox/37/OS X, OpenSSL/0.9.8y, Java/7u25, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, Java/6u45, Android/2.3.7, IE/8/XP

View File

@@ -22,12 +22,11 @@ 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"
service php5-fpm stop service php7.2-fpm stop
service php7.0-fpm stop
# remove the current ownCloud/Nextcloud installation # remove the current ownCloud/Nextcloud installation
rm -rf /usr/local/lib/owncloud/ rm -rf /usr/local/lib/owncloud/
@@ -46,6 +45,5 @@ chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off
service php5-fpm start service php7.2-fpm start
service php7.0-fpm start
echo "Done" echo "Done"

View File

@@ -20,4 +20,4 @@ echo
echo Press enter to continue. echo Press enter to continue.
read read
sqlite3 $STORAGE_ROOT/owncloud/owncloud.db "INSERT OR IGNORE INTO oc_group_user VALUES ('admin', '$ADMIN')" && echo Done. sudo -u www-data php /usr/local/lib/owncloud/occ group:adduser admin $ADMIN && echo Done.

View File

@@ -261,6 +261,10 @@ class UfwAllow(Grammar):
grammar = (ZERO_OR_MORE(SPACE), L("ufw_allow "), REST_OF_LINE, EOL) grammar = (ZERO_OR_MORE(SPACE), L("ufw_allow "), REST_OF_LINE, EOL)
def value(self): def value(self):
return shell_line("ufw allow " + self[2].string) return shell_line("ufw allow " + self[2].string)
class UfwLimit(Grammar):
grammar = (ZERO_OR_MORE(SPACE), L("ufw_limit "), REST_OF_LINE, EOL)
def value(self):
return shell_line("ufw limit " + self[2].string)
class RestartService(Grammar): class RestartService(Grammar):
grammar = (ZERO_OR_MORE(SPACE), L("restart_service "), REST_OF_LINE, EOL) grammar = (ZERO_OR_MORE(SPACE), L("restart_service "), REST_OF_LINE, EOL)
def value(self): def value(self):
@@ -275,7 +279,7 @@ class OtherLine(Grammar):
return "<pre class='shell'><div>" + recode_bash(self.string.strip()) + "</div></pre>\n" return "<pre class='shell'><div>" + recode_bash(self.string.strip()) + "</div></pre>\n"
class BashElement(Grammar): class BashElement(Grammar):
grammar = Comment | CatEOF | EchoPipe | EchoLine | HideOutput | EditConf | SedReplace | AptGet | UfwAllow | RestartService | OtherLine grammar = Comment | CatEOF | EchoPipe | EchoLine | HideOutput | EditConf | SedReplace | AptGet | UfwAllow | UfwLimit | RestartService | OtherLine
def value(self): def value(self):
return self[0].value() return self[0].value()

View File

@@ -1,24 +0,0 @@
#!/usr/bin/python3
# Updates subresource integrity attributes in management/templates/index.html
# to prevent CDN-hosted resources from being used as an attack vector. Run this
# after updating the Bootstrap and jQuery <link> and <script> to compute the
# appropriate hash and insert it into the template.
import re, urllib.request, hashlib, base64
fn = "management/templates/index.html"
with open(fn, 'r') as f:
content = f.read()
def make_integrity(url):
resource = urllib.request.urlopen(url).read()
return "sha256-" + base64.b64encode(hashlib.sha256(resource).digest()).decode('ascii')
content = re.sub(
r'<(link rel="stylesheet" href|script src)="(.*?)" integrity="(.*?)"',
lambda m : '<' + m.group(1) + '="' + m.group(2) + '" integrity="' + make_integrity(m.group(2)) + '"',
content)
with open(fn, 'w') as f:
f.write(content)