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

Compare commits

..

482 Commits
v0.15 ... v0.42

Author SHA1 Message Date
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
Joshua Tauberer
b5c0736d27 release v0.26 2018-01-18 17:10:23 -05:00
Joshua Tauberer
8ee7de6ff3 no need to do a second apt-get update after 'installing' the PHP7 PPA if the PPA was already installed 2018-01-15 13:28:18 -05:00
Joshua Tauberer
0088fb4553 install Python 3 packages in a virtualenv
The cryptography package has created all sorts of installation trouble over the last few years, probably because of mismatches between OS-installed packages and pip-installed packages. Using a virtualenv for all Python packages used by the management daemon should make sure everything is consistent.

See #1298, see #1264.
2018-01-15 13:27:04 -05:00
Joshua Tauberer
b2d103145f remove php5 packages from webmail.sh
The PHP5 packages have a dependency on (apache2 or php5-cgi or php5-fpm), and since removing php5-fpm apache2 started getting installed during setup, which caused a conflict with nginx of course.

These packages don't seem to be needed by Roundcube or Nextcloud --- Roundcube includes the ones it needs.

see #1264, #1298
2018-01-15 11:29:12 -05:00
Joshua Tauberer
fc9e279cec partial revert of 441bd350, accidentally uncommented something 2018-01-15 10:33:05 -05:00
yeah
257983d559 Fix typo in CHANGELOG.md (#1312) 2017-12-25 17:46:31 -05:00
Joshua Tauberer
e924459140 revert f25801e/#1233 - use Mozilla intermediate ciphers for IMAP/POP not modern ciphers
fixes #1300
2017-12-24 14:41:41 -05:00
Joshua Tauberer
441bd35053 update CHANGELOG 2017-12-23 18:01:41 -05:00
Michael Kroes
a0e603a3c6 Change z-push to use the git repository instead of the tar ball (#1305) 2017-12-23 17:51:18 -05:00
sam-banks
88604074d6 Bugfix for free command (#1278)
A quick fix - there's no "o" option for free.
2017-12-18 08:21:28 -05:00
yeah
d43111eb48 Add X-Spam-Score header to checked mail (#1292)
To enable users to do custom spam filtering based on score, it's helpful to render the actual spam score as a float in a separate header rather than as part of X-Spam-Status where it only appears in a comma separated list.
2017-12-18 08:17:47 -05:00
Jim Bailey
6729588d8c Changed temp_dir to /var/temp/roundcube to avoid loss on reboot. (#1302) 2017-12-18 08:12:45 -05:00
Joshua Tauberer
5f14eca67f merge v0.25 security release 2017-11-15 11:27:30 -05:00
Joshua Tauberer
8944cd7980 v0.25 2017-11-15 11:27:00 -05:00
yeah
2bbbc9dfa3 Update Roundcube to protect against CVE-2017-16651
See https://roundcube.net/news/2017/11/08/security-updates-1.3.3-1.2.7-and-1.1.10.

merges #1287
2017-11-15 11:14:21 -05:00
John Olten
544f155948 Add support for DNS wildcard [merges #1281] 2017-11-15 11:10:59 -05:00
Joshua Tauberer
f080eabb3a run apt-get autoremove after updating system packages
Old kernels can build up and some packages may not be needed anymore.

See https://discourse.mailinabox.email/t/storage-space-decreasing/2525/5.
2017-11-15 11:05:43 -05:00
Jānis (Yannis)
7bf377eed1 use RSASHA256 for .lv domains DNSSEC (#1277) 2017-10-31 18:01:47 -04:00
Nicolas North
cd554cf480 document the "local" alias pointing to this box in Custom DNS (#1261) 2017-10-20 17:20:21 -04:00
Michael Kroes
e5448405ae add php7.0-mbstring to webmail.sh (#1268) 2017-10-15 07:53:01 -04:00
Tristan Hill
a7eff8fb35 turn off apt verbose in unattended upgrades (#1255) 2017-10-06 08:16:40 -04:00
Fabian Bucher
341aa8695a update F-Droid DAVdroid link (#1253)
the information about the invalid link comes from here -> https://discourse.mailinabox.email/t/admin-sync-guide-contacts-and-calendar-davdroid-3-69-free-here/2528
2017-10-04 17:47:15 -04:00
Joshua Tauberer
5efdd72f41 update TLS test to record changes in the ciphers we offer on the open ports 2017-10-03 12:01:10 -04:00
Joshua Tauberer
f25801e88d Merge #1233 - Limit Dovecot ciphers to the Mozilla modern set 2017-10-03 11:55:16 -04:00
Joshua Tauberer
cc7be13098 update nginx cipher list to Mozilla's current intermediate ciphers and update HSTS header to be six months
* The Mozilla recommendations must have been updated in the last few years.
* The HSTS header must have >=6 months to get an A+ at ssllabs.com/ssltest.
2017-10-03 11:47:32 -04:00
Joshua Tauberer
2556e3fbc2 HSTS header does not belong here, will result in multiple headers 2017-10-03 11:38:15 -04:00
Joshua Tauberer
00898b2ff5 v0.24 2017-10-03 10:49:04 -04:00
Joshua Tauberer
35b8a149d8 fix dns regex: underscores are allowed in domain names even though they are not allowed in hostnames 2017-09-22 12:31:49 -04:00
Joshua Tauberer
d0423afd18 Nextcloud install shouldn't fail if php-fpm isn't already running 2017-09-22 11:10:48 -04:00
Joshua Tauberer
edf42df835 update Roundcube (1.3.1), persistent login plugin, Z-Push (2.3.8), and Nextcloud (12.0.3) 2017-09-22 11:10:40 -04:00
Joshua Tauberer
734745a4a6 Nextcloud 12.0.2, fix Nextcloud 12 upgrades seeing the wrong version
Nextcloud 12 adds a new OC_VersionCanBeUpgradedFrom field to /usr/local/lib/owncloud/version.php which lists
prior NC/OC version numbers, which confuses our check for what the installed version is. Make our regex more strict.

merges #1238
2017-09-01 07:58:07 -04:00
dofl
dbebaba8b9 switch PHP's process manager to on demand
merges #1216
2017-08-30 13:39:25 -04:00
Joshua Tauberer
cb765dfe2a changelog entries 2017-08-30 13:11:58 -04:00
Lloyd Smart
81258e2189 Implement upstream issue #1228 for stronger dh parameters in Dovecot. (#1232) 2017-08-30 13:04:22 -04:00
Lloyd Smart
4dd4b4232a Limited ciphers to the Mozilla modern set from https://mozilla.github.io/server-side-tls/ssl-config-generator/ as requested in issue #1228. 2017-08-29 15:02:58 +01:00
Marius Blüm
48ff664ee9 Remove the ? from "Log out" (#1231)
Signed-off-by: Marius Blüm <marius@lineone.io>
2017-08-23 19:46:45 -04:00
Michael Kroes
a52c56e571 only set the CN field when generating initial CSR to prevent issues with the php7 ppa version of openssl (#1223)
OpenSSL 1.1.0f now validates the other subject fields and rejects the empty string (for the country?) because it isn't two characters.
2017-07-30 08:11:39 -04:00
Jon Hermansen
6ace97e482 update PPA build URL for postgrey 1.35. Fixes #1211 (#1212) 2017-07-21 15:13:57 -04:00
Git Repository
19a928e4ec [Issue #1159] Remove any +tag name in email alias before checking privileges (#1181)
* [Issue #1159] Remove any +tag name in email alias before checking privileges

* Move priprivileged email check after the conversion to unicode so only IDNA serves as input
2017-07-21 11:10:16 -04:00
Michael Kroes
78f2fe213e Secondary name server could not be set (#1209) 2017-07-21 08:20:37 -04:00
Michael Kroes
a16855ecf0 Backup script should now stop php7.0-fpm instead of php5-fpm (#1206) 2017-07-17 09:45:40 -04:00
yodax
d773140502 Update to Nextcloud 12 using PHP7
* Install PHP7 via a PPA, enable unattended upgrades for the PPA, and switch all of our PHP configuration to the PHP7 install.
* Keep installing PHP5 for ownCloud/Nextcloud packages because we need it to possibly run transitional updates to ownCloud/Nextcloud versions less than 12. But replace PHP5 packages with PHP7 packages elsewhere.
* Update to Nextcloud 12 which requires PHP7, with a transitional upgrade to Nextcloud 11.0.3.
* Disable TLS cert validation by Roundcube when connecting to localhost IMAP and SMTP. Validation became the default in PHP7 but we don't necessarily have a (non-self-)signed certificate and it definitely isn't valid for the IP address 127.0.0.1.

Merges #1140
2017-07-14 06:48:22 -04:00
Michael Kroes
2c324d0bc9 web_domains should also normalize ipv6 addresses (#1201) 2017-07-13 07:16:12 -04:00
Joshua Tauberer
2bd6cc4d6b update to Z-Push 2.3.7 2017-07-10 18:01:21 -04:00
Joshua Tauberer
b11157e0b6 updated to Roundcube 1.3, but unfortunately dropping the vacation plugin
Switched to the -complete download which has vendored assets. See https://github.com/mail-in-a-box/mailinabox/pull/1140.
2017-07-10 17:31:59 -04:00
François Deppierraz
46ba62b7b1 Add support for NS records in custom domains (#1177) 2017-06-11 07:56:30 -04:00
Joshua Tauberer
4c36d6e6c9 release v0.23a 2017-05-31 07:42:18 -04:00
Michael Kroes
e49c99890b fetch whole bootstrap - fixes missing icons in admin (#1185) 2017-05-31 07:36:17 -04:00
Joshua Tauberer
a13fd90347 v0.23 2017-05-30 06:50:42 -04:00
Git Repository
18f1689f45 changed the location we store the web-assets for the admin pages to /usr/local/mailinabox (#1179) 2017-05-23 19:22:53 -04:00
Git Repository
8234a5a9f4 download jQuery and Bootstrap during setup and serve locally so that we don't rely on a CDN which is blocked in some parts of the world (#1167) (#1171) 2017-05-08 07:25:16 -04:00
Michael Kroes
1d9f9ea617 Fix two typos in setup/owncloud.sh regarding the setting of the hostname (#1172) 2017-05-08 07:23:59 -04:00
Michael Kroes
fbb38c3881 Add changelog for custom dns CAA records (#1173) 2017-05-08 07:23:12 -04:00
Git Repository
2caddb41eb #1161 Move the config line for mail_domain to always reset the PRIMARY_HOST (#1163) 2017-05-06 08:18:50 -04:00
Michael Kroes
d2b7204319 Add support for adding a custom "CAA" DNS record (#1155) 2017-04-30 08:58:00 -04:00
Michael Kroes
68ebca8a15 Update Z-Push to 2.3.6 (#1166) 2017-04-30 07:24:36 -04:00
Joshua Tauberer
9c9dcdbf0a update README to link to http://z-push.org/ now that we are on the main line 2017-04-24 17:34:53 -04:00
Joshua Tauberer
0c4c2e51bb bump to Nextcloud 10.0.5 2017-04-24 17:31:54 -04:00
Joshua Tauberer
828512b95a changelog entries 2017-04-17 07:51:01 -04:00
Joshua Tauberer
add985ce5d letencrypt now supports idna, remove the check/block 2017-04-17 07:45:08 -04:00
Michael Kroes
416dbebf45 update z-push to 2.3.5 on the upstream repository z-push.org (#1153) 2017-04-17 07:42:44 -04:00
Git Repository
2a046a22f4 changed roundcube theme to 'larry' (#1138)
Updated the setup file to use roundcube's 'larry' theme as the default.
2017-04-17 07:29:50 -04:00
yodax
b66f12dd4c Fix rsync backup. The path was not append properly 2017-04-17 07:25:47 -04:00
yodax
6e04eb490f Add check to prevent division by zero during backup status 2017-04-17 07:25:47 -04:00
Michael Kroes
cd39c2b53f Merge pull request #1151 from phol/master
Corrected typo in setup/dns.sh
2017-04-10 18:52:38 +02:00
Pieter
5da168466d Corrected typo in setup/dns.sh 2017-04-10 18:37:09 +02:00
Joas Schilling
a5f39784dd remove nginx error pages for nextcloud (#1141)
They are known to cause troubles, for more information see
https://github.com/nextcloud/server/issues/3847
2017-04-04 07:42:50 -04:00
Michael Kroes
a072730fb8 Wrap normalize_ip in try..except (#1139)
closes #1134
2017-04-03 16:53:53 -04:00
Joshua Tauberer
00c61dbcdd changelog entry for migration to Nextcloud 2017-04-02 07:53:56 -04:00
Joshua Tauberer
10bf40250b merge #1121 - migration from ownCloud to Nextcloud
branch 'nextcloud' of https://github.com/yeah/mailinabox
2017-04-02 07:47:31 -04:00
Joshua Tauberer
453091f1fb v0.22 released 2017-04-02 07:34:14 -04:00
Jan Schulz-Hofen
48e0f39179 Rename ownCloud to Nextcloud in safe places
e.g. code comments and user-facing prompts/outputs which can be safely changed without risking to break anything
2017-04-02 11:19:21 +02:00
Jan Schulz-Hofen
bb641cdfba Move from ownCloud to Nextcloud 2017-03-28 11:16:04 +07:00
Joshua Tauberer
255a65ac98 suppress rmcarddav's php version check
Since it says "RCMCardDAV requires at least PHP 5.6.18. Older versions might work", let's hope for the best.

Also hiding its preferences panel in settings since if it doesn't work, we don't want folks using it for anything but connecting to ownCloud contacts.
2017-03-27 08:18:05 -04:00
yeah
c7badb80d1 Set default user password length to 8 in non-interactive setups (#1123)
To comply with #1098 and avoid failed setups while testing with Vagrant
2017-03-26 13:23:34 -04:00
Joshua Tauberer
653cb7ce10 roundcube 1.2.4, persistent login plugin 2017-03-26 09:50:00 -04:00
Joshua Tauberer
d7d8964afc changlog entries 2017-03-26 09:31:35 -04:00
yeah
6c3696a54a Upgrade ownCloud to 9.1.4 to address security vulnerabilities, refs #1111 (#1120)
* Move variable assignment up and do not use call arguments directly

* Upgrade ownCloud to latest patch release 9.1.4

also move owncloud hash to its own variable
2017-03-26 09:20:27 -04:00
Rinze de Laat
9c9cae2096 Added an alternative mail log scanning script for use from the command line (and monitoring, at a later stage)
merges #970
2017-03-26 09:13:35 -04:00
Théo Segonds
423f1907d0 Fix zpush compatibility list link (#1076) 2017-03-26 09:09:00 -04:00
Sean Watson
86621392f6 support SSHFP records for custom domains (#1114) 2017-03-09 09:05:52 -05:00
Sean Watson
368b9c50d0 add DSA and ED25519 SSHFP records if those keys are present (#1078) 2017-03-01 08:02:41 -05:00
Jan Schulz-Hofen
3830facf78 set dovecot vsz_limit to 1/3 of available memory (#1096)
The `default_vsz_limit` is the maximum amount of virtual memory that can be allocated. It should be set *reasonably high* to avoid allocation issues with larger mailboxes. We're setting it to 1/3 of the total available memory (physical mem + swap) to be sure.

See here for discussion:
- https://www.dovecot.org/list/dovecot/2012-August/137569.html
- https://www.dovecot.org/list/dovecot/2011-December/132455.html
2017-03-01 07:59:48 -05:00
Manuel
d4baac2363 at the end of setup show SHA256 tls cert hash instead of SHA1 hash (#1108) 2017-03-01 07:57:03 -05:00
NatCC
f88c907a29 Update jails.conf - SSH fail2ban jail (#1105)
SSH fail2ban jail is not enabled by default and so the jail does not load.
2017-02-21 09:32:28 -05:00
Ian Beringer
89222d519a Fix date delta display for deltas greater than 1 year (#1099) 2017-02-15 18:24:32 -05:00
Dominik Murzynowski
36bef2ee16 Change password min-length to 8 characters (#1098) 2017-02-14 14:24:59 -05:00
Norman S
f6b20a810f Enforce pip to use python 2.7 for boto (#1093) 2017-02-10 09:44:40 -05:00
Norman S
f2ff14100e Change password min-length to four characters (#1094)
in order to correlate with the management interface.
2017-02-10 09:43:11 -05:00
Joshua Tauberer
2c86fa3755 merge v0.21c hot fix release 2017-02-01 11:26:32 -05:00
Joshua Tauberer
3c05fc94ff v0.21c 2017-02-01 11:01:11 -05:00
Joshua Tauberer
2e00530944 upgrade acme package 2017-02-01 11:01:11 -05:00
Joshua Tauberer
32d6728dc9 fix pip breaking due to setuptools/pip/cryptography problem
pip<6.1 + setuptools>=34 have a problem with packages that
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
install cryptography under those versions, it tries to update
setuptools to version 34, which became available about 10 days
ago, and then pip gets permanently broken with errors like
"ImportError: No module named 'packaging'".

The easiest work-around on systems that aren't already broken is
to upgrade pip and setuptools individually before we install any
package that tries to update setuptools.

Also try to detect a broken system and forcibly remove setuptools
first before trying to install/upgrade pip.

fixes #1080, fixes #1081, fixes #1086
see #1083
see https://discourse.mailinabox.email/t/error-with-pip-and-python/1880
see https://discourse.mailinabox.email/t/error-installing-mib/1875
2017-02-01 10:29:28 -05:00
wsteitz
a3c71fe14f move unzip installation from owncload to system setup (#1077) 2017-01-22 10:37:54 -05:00
Joshua Tauberer
a24977a96e normalize_ip for ipv6 still not correct, was broken if box has no IPv6 address 2017-01-18 07:51:59 -05:00
Joshua Tauberer
e694f57673 changelog entries 2017-01-15 11:23:59 -05:00
Joshua Tauberer
cd59de6314 update roundcube to 1.2.3 2017-01-15 11:17:17 -05:00
Joshua Tauberer
a081d04082 move the custom exclusive process code from utils.py into a new python package named exclusiveprocess 2017-01-15 11:02:23 -05:00
Bill Cromie
09577816f8 adds optional vagrant-cachier if you have the plugin installed (#1028) 2017-01-15 10:47:36 -05:00
Bill Cromie
2647febbf5 cardav plugin for roundcube (#1029) 2017-01-15 10:46:33 -05:00
guyzmo
bd0635728c added editorconfig setup (#1037) 2017-01-15 10:44:13 -05:00
Jonathan Chun
584cfe42c4 compare IPv6 addresses correctly with normalization (#1052) 2017-01-15 10:41:12 -05:00
Michael Kroes
41601a592f Improve error handling when doing update checks (#1065)
* Added an error message to handle exceptions when the setup script is trying to determine the latest Miab version
2017-01-15 10:35:33 -05:00
Bill Cromie
18c253eeda adding a fully qualified domain name for the hostname and ignoring the .vagrant dir (#1027) 2016-12-20 16:32:06 -05:00
guyzmo
34d58fb720 Fix/rsync issues (#1036)
* Fixed issue with relative path for rsync relative names

Actually using the parsed URL `path` part, instead of doing a lousy split().
Renamed the `p` variable into something more sensible (`target`).

Fixes: #1019

* Added more verbose error messages upon rsync failures

fixes #1033

* Added command to test file listing
2016-12-17 09:29:48 -05:00
Joshua Tauberer
99d0afd650 secondary nameserver check fails if domain has custom DNS (round-robin) multiple A records
fixes #834
2016-12-07 07:02:52 -05:00
Joshua Tauberer
cd717ec94e nightly TLS certificate provisioning should omit warnings about domains it cant provision for 2016-12-07 07:02:52 -05:00
Joshua Tauberer
0b7f477b96 merge hot-fix release v0.21b 2016-12-05 17:36:32 -05:00
Joshua Tauberer
ab2367e98a v0.21b 2016-12-05 17:36:11 -05:00
Corey Hinshaw
384c3b5e3d Change ownership of roundcube DB after running migrations (#1024)
* Fix #1023 by changing ownership of roundcube DB after running migrations

* Set mode of roundcube sqlite database during setup
2016-12-05 17:32:31 -05:00
Corey Hinshaw
d91368c478 Change ownership of roundcube DB after running migrations (#1024)
* Fix #1023 by changing ownership of roundcube DB after running migrations

* Set mode of roundcube sqlite database during setup
2016-12-05 17:31:20 -05:00
wsteitz
61105b1ec3 remove all references to justtesting.email (#1003) (#1005) 2016-11-30 12:55:18 -05:00
Leo Koppelkamm
b6f90e10c1 Allow larger messages to be checked by SpamAssassin (#1006)
Additionally, add the spam report headers to all emails, in order to make it easier to debug false negatives.
2016-11-30 12:55:03 -05:00
Michael Kroes
3af5e55035 Upgrade to ownCloud 9.1.2 (#1010)
* Update owncloud to 9.1.2

* Upgrade to ownCloud 9.1.2 from 9.1.1 would fail because the guid of 9.1.1 matched with the regex for the version of 8.x
2016-11-30 12:54:27 -05:00
Joshua Tauberer
e03b071e8b missed changelog header 2016-11-30 12:50:38 -05:00
Joshua Tauberer
df93d82d0f v0.21 released 2016-11-30 12:42:24 -05:00
Christian Koptein
59913a5e4c Added Hacker-News Reference Nov 2016 (#1014) 2016-11-28 07:24:57 -05:00
Michael Kroes
c3605f6211 Check if update-manager release-upgrades configuration file is present before editing (#996) 2016-11-13 17:36:33 -05:00
Joshua Tauberer
96b3a29800 rsync backup broke other things 2016-11-12 09:59:06 -05:00
Joshua Tauberer
abb6a1a070 changelog entries 2016-11-12 09:34:52 -05:00
guyzmo
041b5f883f Support for rsync+ssh backup target (#678)
* Added support for backup to a remote server using rsync

* updated web interface to get data from user
* added way to list files from server

It’s not using the “username” field of the yaml configuration
file to minimise the amount of patches needed. So the username
is actually sorted within the rsync URL.

Signed-off-by: Bernard `Guyzmo` Pratz <guyzmo+github@m0g.net>

* Added ssh key generation upon installation for root user.

Signed-off-by: Bernard `Guyzmo` Pratz <guyzmo+github@m0g.net>

* Removed stale blank lines, and fixed typo

Signed-off-by: Bernard `Guyzmo` Pratz <guyzmo+github@m0g.net>

* fix backup-location lines, by switching it from id to class

* Various web UI fixes

- fixed user field being shadowed ;
- fixed settings reading comparaison ;
- fixed forgotten min-age field.

Signed-off-by: Bernard `Guyzmo` Pratz <guyzmo+github@m0g.net>

* Added SSH Public Key shown on the web interface UI

Signed-off-by: Bernard `Guyzmo` Pratz <guyzmo+github@m0g.net>

* trailing spaces.

Signed-off-by: Bernard `Guyzmo` Pratz <guyzmo+github@m0g.net>

* fixed the extraneous environment

Signed-off-by: Bernard `Guyzmo` Pratz <guyzmo+github@m0g.net>

* Updated key setup

- made key lower in bits, but stronger (using -a option),
- made ssh-keygen run in background using nohup,
- added independent key file, as id_rsa_miab,
- added ssh-options to all duplicity calls to use the id_rsa_miab keyfile,
- changed path to the public key display

Signed-off-by: Bernard `Guyzmo` Pratz <guyzmo+github@m0g.net>

* added rsync options for ssh identity support

Signed-off-by: Bernard `Guyzmo` Pratz <guyzmo+github@m0g.net>

* removed strict host checking for all backup operations

Signed-off-by: Bernard `Guyzmo` Pratz <guyzmo+github@m0g.net>

* Remove nohup from ssh-keygen so errors aren't hidden. Also only generate a key if none exists yet

* Add trailing slash when checking a remote backup. Also check if we actually can read the remote size

* Factorisation of the repeated rsync/ssh options

cf https://github.com/mail-in-a-box/mailinabox/pull/678#discussion_r81478919

* Updated message SSH key creation

https://github.com/mail-in-a-box/mailinabox/pull/678#discussion_r81478886
2016-11-12 09:28:55 -05:00
yodax
3b78a8d9d6 If ufw isn't installed on the machine the status checks shouldn't fail 2016-11-12 09:25:34 -05:00
Scott Bronson
6ea1a06a12 suppress Ubuntu's upgrade prompts (#992)
On every login we're notified:

  New release '16.04.1 LTS' available.
  Run 'do-release-upgrade' to upgrade to it.

Disable this so that an eager yet inattentive admin
doesn't accidentally follow these instructions.
2016-11-08 21:41:02 -05:00
Michael Kroes
2b00478b8b Check if apc is disabled during ownCloud setup, if so enable it (#983) 2016-10-24 07:59:34 -04:00
Michael Kroes
155bcfc654 Add ownCloud 9.1.1 to the changelog (#984) 2016-10-21 10:12:46 -04:00
Tristan Hill
4b07a6aa8f disable nested checker checks (#972)
fixes #967
2016-10-18 14:15:33 -04:00
Michael Kroes
2151d81453 update to ownCloud 9.1.1 (with intermediate upgrades) (#894)
[this is a squashed merge from-]

* Install owncoud 9.1 and provide an upgrade path from 8.2. This also disables memcached and goes with apc. The upgrade fails with memcached.

* Remove php apc setting

* Add dav migrations for each user

* Add some comments to the code

* When upgrading owncloud from 8.2.3 to 9.1.0 the backup of 8.2.3 was overwritten when going from 9.0 to 9.1

* Add upgrade path from 8.1.1. Only do an upgrade check if owncloud was previously installed.

* Stop php5-fpm before owncloud upgrade to prevent database locks

* Fix fail2ban tests for owncloud 9

* When upgrading owncloud copy the database to the user-data/owncloud-backup directory

* Remove not need unzip directives during owncloud extraction. Directory is removed beforehand so a normal extraction is fine

* Improve backup of owncloud installation and provide a post installation restore script. Update the owncloud version number to 9.1.1. Update the calendar and contacts apps to the latest versions

* Separate the ownCloud upgrades visually in the console output.
2016-10-18 06:04:13 -04:00
Michael Kroes
fd6226187a lower memory requirements to 512MB, display a warning if system memory is below 768MB. (#952) 2016-10-15 15:41:25 -04:00
rxcomm
bbe27df413 SSHFP record creation should scan nonstandard SSH port if necessary (#974)
* sshfp records from nonstandard ports

If port 22 is not open, dns_update.py will not create SSHFP records
because it only scans port 22 for keys. This commit modifies
dns_update.py to parse the sshd_config file for open ports, and
then obtains keys from one of them (even if port 22 is not open).

* modified test of s per JoshData request

* edit CHANGELOG per JoshData

* fix typo
2016-10-15 15:36:13 -04:00
Michael Kroes
a658abc95f Fix status checks for ufw when the system doesn't support iptables (#961) 2016-10-08 14:35:19 -04:00
Joshua Tauberer
9331dbc519 merge z-push-from-name #940 2016-10-08 14:32:57 -04:00
Steve Gregg
8b5eba21c0 Correct typo of "PRIORITY" in the template (#965) 2016-10-05 18:43:50 -04:00
yodax
da5497cd1c Update changelog entries 2016-09-28 08:37:24 +02:00
Michael Kroes
a27ec68467 Merge pull request #951 from MariusBluem/remove-certificate-providers
Remove Certificate Providers / Fix #950
2016-09-28 08:33:11 +02:00
Marius Blüm
3ac4b8aca8 Remove Certificate Providers / Fix #950
Signed-off-by: Marius Blüm <marius@lineone.io>
2016-09-27 15:06:50 +02:00
Michael Kroes
02feeafe6a change bayes_file_mode to world writable (merges #931)
fixes #534, again, hopefully
2016-09-23 15:14:21 -04:00
Marius Blüm
5f0376bfbf Fix typo in alias-page, fixes #943 (merges #949)
Signed-off-by: Marius Blüm <marius@lineone.io>
2016-09-23 15:11:37 -04:00
Joshua Tauberer
4e4fe90fc7 v0.20 2016-09-23 07:49:13 -04:00
Joshua Tauberer
3cd5a6eee7 changelog entries 2016-09-23 07:46:01 -04:00
Joshua Tauberer
c26bc841a2 more for dnspython exception with IPv6 addresses
fixes #945, corrects prev commit (#947) in case of multiple AAAA records, adds changelog
2016-09-23 07:41:24 -04:00
Mathis Hoffmann
163daea41c dnspython exception with IPv6 addresses
see #945, merges #947
2016-09-23 07:35:53 -04:00
Corey Hinshaw
d8316119eb Use Roundcube identities to populate Z-Push From name 2016-09-19 11:10:44 -04:00
Scott Bronson
102b2d46ab typo fix: seconday -> secondary (#939) 2016-09-18 08:10:49 -04:00
Joshua Tauberer
58541c467f merge #936 - fix wonky free disk space messages - from cmsirbu/master
fix status_checks.py free disk space reporting, fixes #932
2016-09-16 07:31:57 -04:00
cs@twoflower
00bd23eb04 fix status_checks.py free disk space reporting #932 2016-09-15 17:01:21 +01:00
Joshua Tauberer
d73d1c6900 changelog typos 2016-08-24 07:47:55 -04:00
Joshua Tauberer
fc0abd5b4d confirm that fail2ban is protecting pop3s, closes #629 2016-08-22 19:18:23 -04:00
Joshua Tauberer
27b4edfc76 merge v0.19b hot fix release 2016-08-20 11:50:26 -04:00
Joshua Tauberer
ba75ff7820 v0.19b 2016-08-20 11:48:08 -04:00
Joshua Tauberer
a14b17794b simplify how munin-cgi-graph is called to reduce the attack surface area
Seems like if REQUEST_METHOD is set to GET, then we can drop two redundant ways the query string is given. munin-cgi-graph itself reads the environment variables only, but its calls to Perl's CGI::param will look at the command line if REQUEST_METHOD is not used, otherwise it uses environment variables like CGI used to work.

Since this is all behind admin auth anyway, there isn't a public vulnerability. #914 was opened without comment which lead me to notice the redundancy and worry about a vulnerability, before I realized this is admin-only anyway.

The vulnerability was created by 6d6f3ea391.

See #914.

This is the v0.19b hotfix commit.
2016-08-20 11:47:44 -04:00
Joshua Tauberer
35a360ef0b simplify how munin-cgi-graph is called to reduce the attack surface area
Seems like if REQUEST_METHOD is set to GET, then we can drop two redundant ways the query string is given. munin-cgi-graph itself reads the environment variables only, but its calls to Perl's CGI::param will look at the command line if REQUEST_METHOD is not used, otherwise it uses environment variables like CGI used to work.

Since this is all behind admin auth anyway, there isn't a public vulnerability. #914 was opened without comment which lead me to notice the redundancy and worry about a vulnerability, before I realized this is admin-only anyway.
2016-08-19 12:42:43 -04:00
Joshua Tauberer
86457e5bc4 merge: fail2ban broke, released v0.19a 2016-08-18 08:39:31 -04:00
Joshua Tauberer
7c9f3e0b23 v0.19a 2016-08-18 08:36:28 -04:00
Joshua Tauberer
83d8dbca3e fail2ban won't start until the roundcube log file is created
fixes #911
2016-08-18 08:32:14 -04:00
Joshua Tauberer
8cf2e468bd [merge #900] Adding a Code of Conduct
Merge pull request #900 from mail-in-a-box/code_of_conduct
2016-08-15 20:10:37 -04:00
Joshua Tauberer
440a545010 some improvements suggested by the community 2016-08-15 20:09:05 -04:00
Marius Blüm
942bcfc7c5 Update Bootstrap to 3.3.7 (#909)
Signed-off-by: Marius Blüm <marius@lineone.io>
2016-08-15 18:06:12 -04:00
ReadmeCritic
4f2d16a31d Update README URLs based on HTTP redirects (#908) 2016-08-15 11:07:09 -04:00
Joshua Tauberer
e9368de462 [merge #902] Upgrade ownCloud from 8.2.3 to 8.2.7
Merge https://github.com/mar1u5/mailinabox

fixes #901
2016-08-13 17:36:08 -04:00
Joshua Tauberer
cdd0a821eb v0.19
closes #898
2016-08-13 17:27:10 -04:00
Marius Blüm
6f165d0aeb Update Changelog
Signed-off-by: Marius Blüm <marius@lineone.io>
2016-08-09 00:58:10 +02:00
Marius Blüm
6c22c0533e Upgrade ownCloud from 8.2.3 to 8.2.7
Signed-off-by: Marius Blüm <marius@lineone.io>
2016-08-09 00:53:15 +02:00
Joshua Tauberer
d38b732b0a add a Code of Conduct 2016-08-08 08:19:42 -04:00
Joshua Tauberer
81b5af6b64 document fail2ban filters in security.md 2016-08-08 07:55:46 -04:00
Joshua Tauberer
fc5cc9753b roundcube 1.2.1 2016-08-08 07:32:02 -04:00
Joshua Tauberer
1aca6fe08f some minor tweaks to the new users/aliases API documentation 2016-08-08 07:28:10 -04:00
Joshua Tauberer
cf3e1cd595 add SRV records for CardDAV/CalDAV
DavDroid's latest version's account configuration no longer just asked for a hostname. Its email address & password configuration mode did not work without a SRV record.
2016-07-31 20:53:57 -04:00
Joshua Tauberer
b044dda28f put the ufw status checks in the network section, add a punctuation mark, add changelog entry 2016-07-29 09:23:36 -04:00
Joshua Tauberer
f66f39b61d Merge branch 'ufw_status_check' of https://github.com/yodax/mailinabox 2016-07-29 09:16:22 -04:00
Joshua Tauberer
6de7d59f14 changelog entries 2016-07-29 09:12:01 -04:00
Michael Kroes
9c8f2e75fc allow i686 as a supported architecture
This is checked during preflight. See https://github.com/mail-in-a-box/mailinabox/issues/885 (#889)
2016-07-29 09:07:16 -04:00
Joshua Tauberer
cbc4bf553d Merge pull request #880 from schlypel/master
Added information about API endpoints
2016-07-29 09:04:27 -04:00
Michael Kroes
4e3cfead46 Add HSTS to the control panel headers (#879) 2016-07-29 09:01:40 -04:00
Joshua Tauberer
8844a9185f Merge pull request #798 from mail-in-a-box/fail2banjails
add fail2ban jails for ownCloud, postfix submission, roundcube, and the Mail-in-a-Box management daemon
2016-07-29 08:52:44 -04:00
schlypel
3249a55f3a added API info to users page template 2016-06-29 13:35:42 +02:00
schlypel
b58fb54725 added API info to aliases page template 2016-06-29 13:34:54 +02:00
Joshua Tauberer
82903cd09e Merge pull request #857 from biermeester/master
Small extension to mail log management script
2016-06-27 06:17:16 -04:00
Michael Kroes
fb14e30feb Remove owncloud log configuration from initial setup and only apply it during the configuration updates. This applies to both the timezone and the log format 2016-06-27 06:03:24 -04:00
Michael Kroes
d9ac321f25 Owncloud needs more time to detect blocks. It doesn't respond as fast as the other services. Also owncloud logs UTC (since latest update) even though the timezone is not UTC. Also to detect a block, we get a timeout instead of a refused) 2016-06-27 06:03:19 -04:00
Michael Kroes
bf5e9200f8 Update owncloud url to use webdav and increase http timeout 2016-06-27 06:03:14 -04:00
Joshua Tauberer
5f5f00af4a for DANE, the smtp_tls_mandatory_protocols setting seems like it also needs to be set (unlike the cipher settings, this isn't documented to be in addition to the non-mandatory setting) 2016-06-12 09:11:55 -04:00
Joshua Tauberer
6b73bb5d80 outbound SMTP connections should use the same TLS settings as inbound: drop SSLv2, SSLv3, anonymous ciphers, RC4 2016-06-12 09:11:54 -04:00
Joshua Tauberer
3055f9a79c drop SSLv3, RC4 ciphers from SMTP port 25
Per http://googleappsupdates.blogspot.ro/2016/05/disabling-support-for-sslv3-and-rc4-for.html, Google is about to do the same.

fixes #611
2016-06-12 09:11:50 -04:00
Rinze
1c84e0aeb6 Added received mail count to hourly activity overview in mail log management script 2016-06-10 13:08:57 +02:00
Rinze
ae1b56d23f Added POP3 support to mail log management script 2016-06-10 11:19:03 +02:00
Rinze
946cd63e8e Mail log management script cleanup 2016-06-10 10:32:32 +02:00
Michael Kroes
01fa8cf72c add fail2ban jails for ownCloud, postfix submission, roundcube, and the Mail-in-a-Box management daemon
(tests squashed into this commit by josh)
2016-06-06 09:13:10 -04:00
Chris Blankenship
fac8477ba1 Configured Dovecot to log into its own logfile 2016-06-06 08:21:44 -04:00
aspdye
61744095a8 Update Roundcube to 1.2.0
closes #840
2016-06-06 07:32:54 -04:00
Joshua Tauberer
d5b38a27e6 run roundcube's database migration script on every update
There hasn't been a sqlite migration yet, since Mail-in-a-Box's creation, but with Roundcube 1.2 there will be.
2016-06-06 07:28:12 -04:00
Joshua Tauberer
6666d28c44 v0.18c 2016-06-02 15:47:45 -04:00
Joshua Tauberer
66675ff2e9 Dovecot LMTP accepted all mail regardless of whether destination was a user, broken by ae8cd4ef, fixes #852
In the earlier commit, I added a Dovecot userdb lookup. Without a userdb lookup, Dovecot would use the password db for user lookups. With a userdb lookup we can support iterating over users.

But I forgot the WHERE clause in the query, resulting in every incoming message being accepted if the user database contained any users at all. Since the mailbox path template is the same for all users, mail was delivered correctly except that mail that should have been rejected was delivered too.
2016-06-02 08:05:34 -04:00
Joshua Tauberer
9ee2d946b7 Merge pull request #821 from m4rcs/before-backup
Added a pre-backup script to complement post-backup script.
2016-05-17 19:48:14 -04:00
Arnaud
ff7d4196a6 target to blank for munin link in tempalte (#822)
adding :
target="_blank"
to 
<li><a href="/admin/munin">Munin Monitoring</a></li> on line 96
Why ?
Because when you click on munin link, and follow links, you lose your index, or click back many times...
So i propose my pull request.
Et voilà ^^
2016-05-17 19:46:45 -04:00
aspdye
490b36d86c Fix #819 (#823) 2016-05-17 19:46:10 -04:00
Joshua Tauberer
867d9c4669 v0.18b 2016-05-16 07:17:20 -04:00
Joshua Tauberer
1ad5892acd can't change roundcube's default_host setting, partially reverts 6d259a6e12
The default_host setting is a part of the internal username key. We can't change that without causing Roundcube to create new internal user accounts.
2016-05-16 07:14:45 -04:00
Joshua Tauberer
94b7c80792 v0.18 2016-05-15 20:41:31 -04:00
Marc Schiller
69bd137b4e Added a pre-backup script to complement post-backup script. 2016-05-11 10:11:16 +02:00
Joshua Tauberer
ae8cd4efdf support 'dovecot -A' iteration of all users 2016-05-06 09:16:48 -04:00
Joshua Tauberer
6d259a6e12 use "127.0.0.1" throughout rather than mixing use of an IP address and "localhost"
On some machines localhost is defined as something other than 127.0.0.1, and if we mix "127.0.0.1" and "localhost" then some connections won't be to to the address a service is actually running on.

This was the case with DKIM: It was running on "localhost" but Postfix was connecting to it at 127.0.0.1. (https://discourse.mailinabox.email/t/opendkim-is-not-running-port-8891/1188/12.)

I suppose "localhost" could be an alias to an IPv6 address? We don't really want local services binding on IPv6, so use "127.0.0.1" to be explicit and don't use "localhost" to be sure we get an IPv4 address.

Fixes #797
2016-05-06 09:10:38 -04:00
Joshua Tauberer
e7fffc66c7 changelog tweaks, fixes #805 2016-05-06 08:51:53 -04:00
aspdye
8548ede638 Merge pull #806 - Update Roundcube to 1.1.5 2016-04-24 06:31:28 -04:00
Joshua Tauberer
6eeb107ee3 Merge #795 - Upgrade Bootstrap 3.3.5 to 3.3.6 2016-04-24 06:27:50 -04:00
Joshua Tauberer
31eefa18da Merge #793 - Hostname as Roundcube Name 2016-04-23 13:45:09 -04:00
Joshua Tauberer
20adbb51cb Merge #804 - Make clear that Let's Encrypt is reccomended! 2016-04-23 09:51:44 -04:00
aspdye
79a39d86f9 reseller -> provider 2016-04-23 15:18:21 +02:00
aspdye
0ebf33e9df Make clear that Let's Encrypt is reccomended! 2016-04-23 11:35:02 +02:00
Joshua Tauberer
d3818d1db6 changelog entries 2016-04-13 18:42:53 -04:00
aspdye
f65d9d3196 Upgrade Bootstrap 3.3.5 to 3.3.6 2016-04-09 13:27:27 +02:00
aspdye
74fea6b93e Hostname as Roundcube Name 2016-04-09 10:23:20 +02:00
Joshua Tauberer
7a935d8385 Merge #791 - Add ownCloud 8.2.3 update to changelog 2016-04-08 08:20:06 -04:00
aspdye
7e0f534aea Add ownCloud update to changelog 2016-04-08 14:04:15 +02:00
Michael Kroes
736b3de221 Improve matching of ufw output. Reuse network service list. Improve messages 2016-04-07 16:03:28 +02:00
Joshua Tauberer
5628f8eecb Merge #773 - Set the hostname of the box during the setup 2016-04-07 09:44:39 -04:00
Joshua Tauberer
9cc5160c38 Merge #789 - Update to ownCloud v8.2.3 2016-04-07 09:32:24 -04:00
Michael Kroes
42f2e983e5 Merge branch 'master' into ufw_status_check 2016-04-07 15:13:59 +02:00
Michael Kroes
bc40134b7b Remove comment about loopback interface 2016-04-07 10:55:20 +02:00
Michael Kroes
3649ba1ce9 Merge branch 'master' into hostname 2016-04-07 10:54:53 +02:00
kurt89523
22395bdb8b Update to ownCloud v8.2.3 2016-04-06 17:31:59 -07:00
Joshua Tauberer
30c89be982 merge #771 - stop fail2ban recidive emails
The emails were not deliverable anyway.
2016-04-06 19:03:44 -04:00
Joshua Tauberer
853b641d1b Merge #787 - Add SRV record to the Custom DNS page 2016-04-05 07:17:12 -04:00
msgerbs
703a963ae5 Add SRV record to the Custom DNS page
Add SRV to the drop-down to add a custom DNS zone. I made this change on my up-to-date install and it worked without any issues.
2016-04-05 00:54:26 -05:00
Michael Kroes
c9f30e8059 Add status checks for ufw 2016-04-02 13:41:16 +02:00
Joshua Tauberer
1a1d125b31 merge hotfix release tag 'v0.17c' into master
The hotfixes were all already applied to master in original PRs. This merge merely brings over the CHANGELOG and the updated install instructions (v0.17b=>v0.17c), including to bootstrap.sh which is what triggers v0.17c being the latest release.
2016-04-01 08:00:10 -04:00
Joshua Tauberer
86881c0107 v0.17c 2016-04-01 07:58:28 -04:00
Joshua Tauberer
e65c77588e hotfix merge #776 - some owncloud paths were improperly exposed over http 2016-04-01 07:58:24 -04:00
Joshua Tauberer
3843f63416 hotfix merge #772 - yodax/generic-login-message
Make control panel login failed messages generic - don't reveal if an email address has an account on the system.
2016-03-31 10:46:38 -04:00
Joshua Tauberer
703e6795e8 hotfix merge #769 - update the Roundcube html5_notifier plugin from version 0.6 to 0.6.2
fixes Roundcube getting stuck for some people, hopefully fixes #693
2016-03-31 10:46:34 -04:00
Joshua Tauberer
b3223136f4 hotfix - install roundcube from our own mirror, hosted in Josh's AWS S3 account, because sourceforge is down all the time
fixes #750, see #701, see #370

was df92a10eba
2016-03-31 10:35:48 -04:00
Joshua Tauberer
aa1fdaddaf hotfix merge #755 - Prevent click jacking of the management interface 2016-03-31 10:34:52 -04:00
Joshua Tauberer
7fa9baf308 hotfix merge #744 - Fix for putty Line Drawing issues 2016-03-31 10:33:42 -04:00
Joshua Tauberer
36d51bbde0 merge #776 - some owncloud paths were improperly exposed over http 2016-03-31 10:20:21 -04:00
Joshua Tauberer
eb8cfaab75 changelog entry for html5_notifier bump 2016-03-31 10:20:13 -04:00
Tibor Blaho
c5e8a975cd Fix denied ownCloud nginx locations 2016-03-31 00:07:48 +02:00
Michael Kroes
3210ccdcac Don't set the hostname on the loopback 127.0.1.1 2016-03-26 15:41:20 +01:00
Joshua Tauberer
252c35c66e Merge pull request #772 from yodax/generic-login-message
Make control panel login failed messages generic - don't reveal if an email address has an account on the system.
2016-03-26 09:22:02 -04:00
Michael Kroes
c910a58f07 Set the hostname of the box during the setup 2016-03-26 14:15:28 +01:00
Michael Kroes
f292e8fc5b Add generic login failed message 2016-03-26 14:06:43 +01:00
Michael Kroes
4d7229ccb0 Add documentation on why the notification was removed from the recidive jail 2016-03-26 13:37:33 +01:00
Joshua Tauberer
1e1c3cbd00 Merge pull request #768 from yodax/web
Static web hosting, instructions weren't rendered
2016-03-26 07:46:49 -04:00
Joshua Tauberer
611e9cc84d merges #769 - update the Roundcube html5_notifier plugin from version 0.6 to 0.6.2
fixes Roundcube getting stuck for some people, hopefully fixes #693
2016-03-26 07:43:22 -04:00
Michael Kroes
454a2b167b Stop fail2ban recidive from sending emails, like all other jails 2016-03-26 09:04:51 +01:00
david
f6e0af124f updated html5_notifier version to 0.6.2 in setup 2016-03-25 20:16:51 +01:00
Michael Kroes
d7d8bda0a4 Instructions on how to create a web site for a domain weren't rendered. Users would miss the step about manually creating the directory to put files in there and wouldn't see anything happen 2016-03-25 13:37:55 +01:00
Joshua Tauberer
df92a10eba install roundcube from our own mirror, hosted in Josh's AWS S3 account, because sourceforge is down all the time
fixes #750, see #701, see #370
2016-03-23 17:31:24 -04:00
Joshua Tauberer
74a0359cec Merge pull request #763 from Neopallium/master
Fix creation of custom MX records.
2016-03-23 17:22:42 -04:00
Joshua Tauberer
336b95b3d5 Merge pull request #756 from yodax/preflight_arm
Add a preflight check for supported architecture
2016-03-23 17:19:21 -04:00
Joshua Tauberer
56591abbc2 merge #766 - Configure bayes_file_mode in spamassassin/local.cf 2016-03-23 17:17:30 -04:00
Joshua Tauberer
313a86d0fa add changelog entry for bayes file permissions 2016-03-23 17:16:50 -04:00
Joshua Tauberer
083e3cf755 merge #757 (squashed) - add swap space to low-memory systems 2016-03-23 17:07:40 -04:00
Michael Kroes
696bbe4e82 Add a swap file to the system if system memory is less than 2GB, 5GB of free disk space is available, and if no swap file yet exists 2016-03-23 17:07:04 -04:00
Joshua Tauberer
3d4cabbcd5 merge #755 - Prevent click jacking of the management interface 2016-03-23 16:53:48 -04:00
Joshua Tauberer
cdedaed3b0 merge #744 - Fix for putty Line Drawing issues 2016-03-23 16:51:01 -04:00
Joshua Tauberer
c01f903413 edit NCURSES_NO_UTF8_ACS's comment, add changelog entry 2016-03-23 16:50:27 -04:00
Joshua Tauberer
5edefbec27 merge #735 - Allow a server to be rebooted when a reboot is required 2016-03-23 16:39:40 -04:00
Joshua Tauberer
67555679bd move the reboot button, fix grammar, refactor check for DRY, add changelog entry 2016-03-23 16:37:15 -04:00
Joshua Tauberer
546d6f0026 merge #674 - Support munin's cgi dynazoom 2016-03-23 16:10:30 -04:00
Joshua Tauberer
bd86d44c8b simplify the munin_cgi wrapper / add changelog entry 2016-03-23 16:09:19 -04:00
Robert G. Jakabosky
72fcb005b2 Check MX priority. 2016-03-22 03:07:14 +08:00
Robert G. Jakabosky
84638ab11e Fix creation of custom MX records. 2016-03-21 21:12:08 +08:00
yodax
84f4509b48 Configure bayes_file_mode in spamassassin/local.cf 2016-03-20 05:55:58 +01:00
Michael Kroes
35a593af13 Improve preflight message 2016-03-14 07:14:09 +01:00
Michael Kroes
f69d6e9015 Add a preflight check for supported architecture 2016-03-14 07:00:33 +01:00
Michael Kroes
44705a32b7 Never allow admin panel to be inside a frame, use both modern and old headers. Also set no content sniffing 2016-03-13 18:40:02 +01:00
Michael Kroes
e343061cf4 Prevent clickjacking of management interface 2016-03-13 18:23:10 +01:00
Joshua Tauberer
65add24e2a Merge pull request #751 from yodax/wgetrc
Add a preflight check for ~/.wgetrc
2016-03-11 10:29:24 -05:00
Michael Kroes
33a9fb6aa2 Add a better message 2016-03-11 15:14:37 +01:00
Michael Kroes
0bc5d20e8f Add check for user overrides to wgetrc 2016-03-11 15:10:31 +01:00
Joshua Tauberer
49ea9cddd1 ssl_certificates: also forgot to catch free_tls_certificates.client.RateLimited 2016-03-06 14:39:34 -05:00
c0h1b4
6a48cdcdf3 Fix for putty Line Drawing issues
Fix for putty (Windows) Line Drawing characters to be shown correctly.
2016-03-01 10:40:39 -03:00
Joshua Tauberer
f78f039776 merge point release v0.17b
ownCloud moved their source code to a new location, breaking our installation script.
2016-03-01 07:24:06 -05:00
Joshua Tauberer
d881487d68 v0.17b 2016-03-01 07:23:20 -05:00
Joshua Tauberer
33d07b2b54 ownCloud moved their source code to a new location, breaking our installation script.
Fixes #741.
2016-03-01 07:23:16 -05:00
Joshua Tauberer
3bbec18ac6 Merge pull request #734 from yodax/dynamicpool
Create a temporary multiprocessing pool
2016-02-28 12:39:11 -05:00
Joshua Tauberer
fc5c198646 Merge pull request #728 from yodax/noexec
Add check to preflight for exec on tmp
2016-02-28 12:38:43 -05:00
Joshua Tauberer
2be373fd06 Merge pull request #727 from yodax/userlist
Allow files in /home/user-data/mail/mailboxes
2016-02-28 12:33:38 -05:00
Michael Kroes
b71ad85e9f Restore an empty line 2016-02-26 09:51:22 +01:00
Michael Kroes
86d3e9da86 Merge branch 'master' into reboot 2016-02-26 09:49:03 +01:00
yodax
f53d3bc390 Merge branch 'master' into dynamicpool 2016-02-26 09:20:23 +01:00
Joshua Tauberer
f9ca440ce8 v0.17 2016-02-25 18:36:14 -05:00
Michael Kroes
8ea2f5a766 Allow a server to be rebooted when a reboot is required 2016-02-25 21:56:27 +01:00
yodax
6c1357e16c Merge branch 'master' into dynamicpool 2016-02-23 17:01:13 +01:00
Joshua Tauberer
d880f088be fix changelog description of a bug, see #725 2016-02-23 10:24:26 -05:00
Joshua Tauberer
5cabfd591b (re-fix) mail sent from an address on a subdomain of a domain hosted by the box (a non-zone domain) would never be DKIM-signed because only zones were included in the openDKIM configuration, mistakenly
This was originally fixed in 143bbf37f4 (February 16, 2015). Then I broke it in 7a93d219ef (November 2015) while doing some refactoring ahead of v0.15.
2016-02-23 10:16:04 -05:00
yodax
721730f0e8 Create a temporary multiprocessing pool 2016-02-23 06:32:01 +01:00
Joshua Tauberer
af80849857 Merge pull request #732 from yodax/memory
Reduce percentages for required free memory checks
2016-02-22 15:02:50 -05:00
yodax
7a191e67b8 Add a changelog entry 2016-02-22 21:01:33 +01:00
Joshua Tauberer
4b2e48f2c0 Merge pull request #726 from yodax/login
When previous panel was login, move to system_status
2016-02-22 14:44:23 -05:00
Joshua Tauberer
eb545d7941 Merge pull request #733 from yodax/daemons
Reduce number of processes in the pool to 5
2016-02-22 14:42:20 -05:00
yodax
a2e6e81697 Add a changelog entry 2016-02-22 19:14:46 +01:00
yodax
1b24e2cbaf Reduce percentages for required memory checks 2016-02-22 17:49:19 +01:00
yodax
0843159fb4 Reduce number of processes in the pool to 5 2016-02-22 17:38:30 +01:00
Michael Kroes
a7e60af93f Update comments 2016-02-21 12:47:09 -05:00
Michael Kroes
42f879687f Add check to preflight for exec on tmp 2016-02-21 12:43:04 -05:00
yodax
057903a303 Allow files in /home/user-data/mail/mailboxes 2016-02-21 13:49:07 +01:00
yodax
b8e99c30a2 When previous panel was login, move to system_status 2016-02-20 18:42:28 +01:00
Joshua Tauberer
3d933c16d0 Merge pull request #718 from shakaran/patch-1
Fix small typo in comments
2016-02-18 17:49:35 -05:00
Ángel Guzmán Maeso
e785886447 Fix small typo in comments 2016-02-18 15:38:33 +01:00
Joshua Tauberer
23ecff04b8 the logic in 4ed23f44e6 for taking backups more often was partly backward 2016-02-18 07:50:59 -05:00
Joshua Tauberer
a0bae5db5c update changelog 2016-02-18 07:18:51 -05:00
Joshua Tauberer
86368ed165 clean up apt_install lines and comments in setup/management.sh 2016-02-18 06:59:38 -05:00
Joshua Tauberer
5e4c0ed825 Revert "install boto (py2) via the package manager, not pip (used by duplicity)"
This reverts commit b32cb6229b.

Fixes #627. Fixes #653. Closes #714.
2016-02-18 06:54:23 -05:00
Joshua Tauberer
ffa9dc5d67 Merge pull request #716 from pra85/patch-1
Fix a typo in Readme
2016-02-18 06:44:25 -05:00
Prayag Verma
43cb6c4995 Fix a typo in Readme
`matchs` → `matches`
2016-02-18 09:47:47 +05:30
Joshua Tauberer
36cb2ef41d missing elif 2016-02-16 09:11:54 -05:00
Joshua Tauberer
098e250cc4 bump free_tls_certificates, fixes #695, if a challenge fails dont cache it permanently (or at all) 2016-02-16 09:08:58 -05:00
Joshua Tauberer
3d5a35b184 typo 2016-02-15 18:47:19 -05:00
Joshua Tauberer
87d3f2641d merge #685 - tweak postfix mail queue/warn/bounce times 2016-02-15 18:44:56 -05:00
Joshua Tauberer
c6c75c5a17 document the default values for delay_warning_time, maximal_queue_lifetime, bounce_queue_lifetime 2016-02-15 18:38:55 -05:00
Joshua Tauberer
1ba44b02d4 forgot to catch free_tls_certificates.client.ChallengeFailed
Provisioning could crash if, e.g., the DNS we see is different from the DNS Let's Encrypt sees.

see #695, probably fixes it
2016-02-15 18:22:16 -05:00
Joshua Tauberer
6fd4cd85ca Merge pull request #712 from s4wny/patch-2
Added a warning to the installation / setup script
2016-02-14 14:29:13 -05:00
Sony?
6182347641 spelling box->Box 2016-02-14 20:24:00 +01:00
Sony?
401b0526a3 Added a warning to the installation / setup script
See pull request #638 and issue #635 for more information.
2016-02-14 19:40:43 +01:00
Joshua Tauberer
2f24328608 before the user agrees to Let's Encrypt's ToS the admin could get a nightly email with weird interactive text
Made a mistake refactoring the headless variable earlier.

fixes #696
2016-02-13 12:38:16 -05:00
Joshua Tauberer
8ea42847da nightly status checks could fail if any domains had non-ASCII characters
https://discourse.mailinabox.email/t/status-check-emails-empty-after-upgrading-to-v0-16/1082/3

A user on that thread suggests an alternate solution, adding `PYTHONIOENCODING=utf-8` to `/etc/environment`. Python docs say that affects stdin/out/err. But we also use these environment variables elsewhere to ensure that config files we read/write are opened with UTF8 too. Maybe all that can be simplified too.
2016-02-13 11:51:06 -05:00
Joshua Tauberer
4ed23f44e6 take a full backup more often so we don't keep backups around for so long 2016-02-05 11:08:33 -05:00
Joshua Tauberer
178527dab1 convert the backup increment time to the local timezone, fixes #700
Duplicity gives times in UTC. We were assuming times were in local time.
2016-02-05 08:58:07 -05:00
Joshua Tauberer
f5c376dca8 Merge pull request #699 from BastianPoe/patch-1
Fix: Correct IP is reported when using custom DNS
2016-02-04 15:42:10 -05:00
Wolf-Bastian Pöttner
239eac662c Fix: Correct IP is reported when using custom DNS
Fix bug that reports wrong ip, when custom DNS is enabled
2016-02-04 21:32:11 +01:00
Joshua Tauberer
4e18f66db6 tls control panel: only show integral seconds while waiting the requested time from Lets Encrypt, in case we got back a non-integral number of seconds to wait 2016-02-03 08:21:22 -05:00
Joshua Tauberer
77937df955 bind postfix to the right network interface when sending outbound mail so that SPF checks on the receiving end will pass
fixes #3 (again)
2016-02-01 12:36:52 -05:00
Joshua Tauberer
4db8efa0df bump Roundcube to 1.1.4 2016-02-01 12:31:42 -05:00
Joshua Tauberer
66c80bd16a Merge pull request #688 from OmgImAlexis/patch-1
fixed typo
2016-01-30 19:31:47 -05:00
X O
5895aeecd7 fixed typo 2016-01-31 11:01:00 +10:30
Joshua Tauberer
83ffc99b9c change the public URL of bootstrap.sh to setup.sh 2016-01-30 11:19:51 -05:00
Joshua Tauberer
3615772b2d v0.16 2016-01-30 11:15:14 -05:00
Joshua Tauberer
78729bd277 update CHANGELOG 2016-01-27 20:23:41 -05:00
dofl
85a9a1608c Update mail-postfix.sh 2016-01-21 16:05:43 +01:00
dofl
2e693f7011 Update mail-postfix.sh
Updated according to Josh's latest reaction. Sounds good.
2016-01-21 08:38:39 +01:00
dofl
6f0220da4b Update mail-postfix.sh
Same result as maximal_queue_lifetime and bounce_queue_lifetime, but complies with rfc2821.
2016-01-20 15:34:22 +01:00
dofl
09a45b4397 Update mail-postfix.sh
The default timeout for Postfix's maximal_queue_lifetime and bounce_queue_lifetime is 5 days. This is way too long if you expect someone to have an answer and after 5 days you'll get the message that it's not delivered. This disrupts communication. It would be more responsive if the user got the 'can't deliver' error after 24 hours.
2016-01-20 13:25:41 +01:00
mike
6b408ef824 Use utils.shell instead of subprocess.Popen 2016-01-14 10:24:04 -05:00
Joshua Tauberer
62b6117638 Merge pull request #675 from jeroenj/patch-1
Removes border and rounded corners from navbar
2016-01-14 09:53:36 -05:00
Jeroen Jacobs
70111dafbc Removes border and rounded corners from navbar 2016-01-14 15:48:39 +01:00
Joshua Tauberer
faaa74c3a7 tls: hide extra reasons why domains aren't getting a new certificate during setup 2016-01-14 07:21:08 -05:00
mike
8932aaf4ef needed libcgi-fast-perl and chown log files 2016-01-13 23:55:45 -05:00
mike
6d6f3ea391 Added ability to use munin's dynazoom 2016-01-13 22:20:33 -05:00
Joshua Tauberer
2ad7d0830e add exception handling for what_version_is_this, fixes #659 2016-01-09 09:23:07 -05:00
Joshua Tauberer
5045e206c2 roundcube file ownership should not preserve uid/gid from the release tarball, tar (when run as root) should always extract using --no-same-owner, fixes #667 2016-01-09 09:17:45 -05:00
Joshua Tauberer
07f9228694 Merge branch 'letsencrypt' for automatic provisioning of TLS certificates from Let's Encrypt 2016-01-09 08:58:35 -05:00
Joshua Tauberer
50b5b91216 Merge release branch v0.15a
v0.15a

Sending mail through Exchange/ActiveSync (Z-Push) had been broken since v0.14. This is now fixed.
2016-01-09 08:48:37 -05:00
Joshua Tauberer
72bfc0915c v0.15a (January 9, 2016)
Sending mail through Exchange/ActiveSync (Z-Push) had been broken since v0.14. This is now fixed.
2016-01-09 08:44:51 -05:00
Bernard `Guyzmo` Pratz
a7d7a9adbd Fixing issue making it impossible to send mail from Z-Push
* added IMAP_SMTP_METHOD to z_push/backend_imap
 * reverting that line accidentally deleted in commit 5055ef
 * cf pull request GH-580 that commit is part of

Signed-off-by: Bernard `Guyzmo` Pratz <guyzmo+github@m0g.net>
2016-01-09 08:42:18 -05:00
Joshua Tauberer
413af1fe67 Merge pull request #668 from guyzmo/z-push-smtp-fix
Fixing issue making it impossible to send mail from Z-Push
2016-01-09 08:41:59 -05:00
Bernard `Guyzmo` Pratz
b09cbb0ca4 Fixing issue making it impossible to send mail from Z-Push
* added IMAP_SMTP_METHOD to z_push/backend_imap
 * reverting that line accidentally deleted in commit 5055ef
 * cf pull request GH-580 that commit is part of

Signed-off-by: Bernard `Guyzmo` Pratz <guyzmo+github@m0g.net>
2016-01-08 16:43:09 +00:00
Joshua Tauberer
5b135738b4 Merge pull request #661 from baltoche/master
Gandi tells me that .be TLD only supports algorithms 8 and 10 for DNSSEC
2016-01-05 11:10:21 -05:00
baltoche
36e5772a8e Update dns_update.py 2016-01-05 16:56:16 +01:00
Joshua Tauberer
2b9fb9643d changelog entries for lets encrypt 2016-01-04 18:43:17 -05:00
Joshua Tauberer
2882e63dd8 second part of provisioning tls certificates from the control panel 2016-01-04 18:43:17 -05:00
Joshua Tauberer
812ef024ef status checks: check that the non-primary domains also resolve over IPv6, if configured 2016-01-04 18:43:17 -05:00
Joshua Tauberer
40cdc5aa30 status checks: if a domain's DNS isnt working dont check the TLS certificate because we cant automatically provision one now anyway 2016-01-04 18:43:17 -05:00
Joshua Tauberer
b8d6226a9a when provisioning tls certs from the command line, specify domain names as command line arguments to force getting certs for those domains 2016-01-04 18:43:17 -05:00
Joshua Tauberer
bac15d3919 provision tls certificates from the control panel 2016-01-04 18:43:16 -05:00
Joshua Tauberer
4b4f670adf s/SSL/TLS/ in user-visible text throughout the project 2016-01-04 18:43:16 -05:00
Joshua Tauberer
b1b57f9bfd don't try to get certs for IDNA domains and report all reasons for not fetching a certificate
fixes #646
2016-01-04 18:43:16 -05:00
Joshua Tauberer
b6933a73fa provision and install free SSL certificates from Let's Encrypt 2016-01-04 18:43:16 -05:00
Joshua Tauberer
5033042b8c backups: email the administrator when there's a problem
Refactor by moving the email-the-admin code out of the status checks and into a new separate tool.

This is why I suppressed non-error output of the backups last commit - so it doesn't send a daily email.
2016-01-04 18:43:02 -05:00
Joshua Tauberer
89a46089ee backups: suppress all output except errors 2016-01-04 18:43:02 -05:00
Joshua Tauberer
e288d7730b backups: trap an error that occurs as early as getting the current backup status 2016-01-04 18:43:02 -05:00
Joshua Tauberer
5ae75e723c Merge pull request #654 from ChlorideCull/cull-fix-typo
Fix miscellaneous typos in comments
2016-01-03 10:53:25 -05:00
Chloride Cull
d6d5009d23 Fix typos in questions.sh
sed s/supress/suppress/g
sed s/depencies/dependencies/g
2016-01-03 16:48:23 +01:00
Joshua Tauberer
06a0e7f3fe merge #584 - Add checks to the management interface to report memory usage 2016-01-01 18:13:21 -05:00
Joshua Tauberer
a9cd72bbf9 tighten the status text strings for free memory, add changelog entry 2016-01-01 18:12:36 -05:00
Joshua Tauberer
f184a74fa0 merge #647 - open the port for Sieve 2016-01-01 17:53:40 -05:00
Joshua Tauberer
682b1dea5e changelog/status checks updated for opening the sieve port 2016-01-01 17:53:05 -05:00
Ralph J.Mayer
2835d9b468 Merge pull request #1 from TabTwo/TabTwo-patch-1
Allow remote client for Sieve
2015-12-31 18:23:30 +01:00
Ralph J.Mayer
afd401c3d4 Allow remote client for Sieve 2015-12-31 18:22:31 +01:00
Michael Kroes
59f8aa1c31 Add checks to the management interface to report memory usage 2015-11-20 01:48:59 -05:00
88 changed files with 4596 additions and 2175 deletions

30
.editorconfig Normal file
View File

@@ -0,0 +1,30 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[Makefile]
indent_style = tabs
indent_size = 4
[Vagrantfile]
indent_size = 2
[*.rb]
indent_size = 2
[*.py]
indent_style = tabs
[*.js]
indent_size = 2

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ management/__pycache__/
tools/__pycache__/
externals/
.env
.vagrant

View File

@@ -1,6 +1,488 @@
CHANGELOG
=========
v0.42 (July 4, 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.
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)
------------------------
Security:
* HTTPS, IMAP, and POP's TLS settings have been updated to Mozilla's intermediate cipher list recommendation. Some extremely old devices that use less secure TLS ciphers may no longer be able to connect to IMAP/POP.
* Updated web HSTS header to use longer six month duration.
Mail:
* Adding attachments in Roundcube broke after the last update for some users after rebooting because a temporary directory was deleted on reboot. The temporary directory is now moved from /tmp to /var so that it is persistent.
* `X-Spam-Score` header is added to incoming mail.
Control panel:
* RSASHA256 is now used for DNSSEC for .lv domains.
* Some documentation/links improvements.
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 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.
* 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)
-------------------------
This update is a security update addressing [CVE-2017-16651, a vulnerability in Roundcube webmail that allows logged-in users to access files on the local filesystem](https://roundcube.net/news/2017/11/08/security-updates-1.3.3-1.2.7-and-1.1.10).
Mail:
* Update to Roundcube 1.3.3.
Control Panel:
* Allow custom DNS records to be set for DNS wildcard subdomains (i.e. `*`).
v0.24 (October 3, 2017)
-----------------------
System:
* Install PHP7 via a PPA. Switch to the on-demand process manager.
Mail:
* Updated to [Roundcube 1.3.1](https://roundcube.net/news/2017/06/26/roundcube-webmail-1.3.0-released), but unfortunately dropping the Vacation plugin because it has not been supported by its author and is not compatible with Roundcube 1.3, and updated the persistent login plugin.
* Updated to [Z-Push 2.3.8](http://download.z-push.org/final/2.3/z-push-2.3.8.txt).
* Dovecot now uses stronger 2048 bit DH params for better forward secrecy.
Nextcloud:
* Nextcloud updated to 12.0.3, using PHP7.
Control Panel:
* Nameserver (NS) records can now be set on custom domains.
* Fix an erroneous status check error due to IPv6 address formatting.
* Aliases for administrative addresses can now be set to send mail to +tag administrative addresses.
v0.23a (May 31, 2017)
---------------------
Corrects a problem in the new way third-party assets are downloaded during setup for the control panel, since v0.23.
v0.23 (May 30, 2017)
--------------------
Mail:
* The default theme for Roundcube was changed to the nicer Larry theme.
* Exchange/ActiveSync support has been replaced with z-push 2.3.6 from z-push.org (rather than z-push-contrib).
ownCloud (now Nextcloud):
* ownCloud is replaced with Nextcloud 10.0.5.
* Fixed an error in Owncloud/Nextcloud setup not updating domain when changing hostname.
Control Panel/Management:
* Fix an error in the control panel showing rsync backup status.
* Fix an error in the control panel related to IPv6 addresses.
* TLS certificates for internationalized domain names can now be provisioned from Let's Encrypt automatically.
* Third-party assets used in the control panel (jQuery/Bootstrap) are now downloaded during setup and served from the box rather than from a CDN.
DNS:
* Add support for custom CAA records.
v0.22 (April 2, 2017)
---------------------
Mail:
* The CardDAV plugin has been added to Roundcube so that your ownCloud contacts are available in webmail.
* Upgraded to Roundcube 1.2.4 and updated the persistent login plugin.
* Allow larger messages to be checked by SpamAssassin.
* Dovecot's vsz memory limit has been increased proportional to system memory.
* Newly set user passwords must be at least eight characters.
ownCloud:
* Upgraded to ownCloud 9.1.4.
Control Panel/Management:
* The status checks page crashed when the mailinabox.email website was down - that's fixed.
* Made nightly re-provisioning of TLS certificates less noisy.
* Fixed bugs in rsync backup method and in the list of recent backups.
* Fixed incorrect status checks errors about IPv6 addresses.
* Fixed incorrect status checks errors for secondary nameservers if round-robin custom A records are set.
* The management mail_log.py tool has been rewritten.
DNS:
* Added support for DSA, ED25519, and custom SSHFP records.
System:
* The SSH fail2ban jail was not activated.
Installation:
* At the end of installation, the SHA256 -- rather than SHA1 -- hash of the system's TLS certificate is shown.
v0.21c (February 1, 2017)
-------------------------
Installations and upgrades started failing about 10 days ago with the error "ImportError: No module named 'packaging'" after an upstream package (Python's setuptools) was updated by its maintainers. The updated package conflicted with Ubuntu 14.04's version of another package (Python's pip). This update upgrades both packages to remove the conflict.
If you already encountered the error during installation or upgrade of Mail-in-a-Box, this update may not correct the problem on your existing system. See https://discourse.mailinabox.email/t/v0-21c-release-fixes-python-package-installation-issue/1881 for help if the problem persists after upgrading to this version of Mail-in-a-Box.
v0.21b (December 4, 2016)
-------------------------
This update corrects a first-time installation issue introduced in v0.21 caused by the new Exchange/ActiveSync feature.
v0.21 (November 30, 2016)
-------------------------
This version updates ownCloud, which may include security fixes, and makes some other smaller improvements.
Mail:
* Header privacy filters were improperly running on the contents of forwarded email --- that's fixed.
* We have another go at fixing a long-standing issue with training the spam filter (because of a file permissions issue).
* Exchange/ActiveSync will now use your display name set in Roundcube in the From: line of outgoing email.
ownCloud:
* Updated ownCloud to version 9.1.1.
Control panel:
* Backups can now be made using rsync-over-ssh!
* Status checks failed if the system doesn't support iptables or doesn't have ufw installed.
* Added support for SSHFP records when sshd listens on non-standard ports.
* Recommendations for TLS certificate providers were removed now that everyone mostly uses Let's Encrypt.
System:
* Ubuntu's "Upgrade to 16.04" notice is suppressed since you should not do that.
* Lowered memory requirements to 512MB, display a warning if system memory is below 768MB.
v0.20 (September 23, 2016)
--------------------------
ownCloud:
* Updated to ownCloud to 8.2.7.
Control Panel:
* Fixed a crash that occurs when there are IPv6 DNS records due to a bug in dnspython 1.14.0.
* Improved the wonky low disk space check.
v0.19b (August 20, 2016)
------------------------
This update corrects a security issue introduced in v0.18.
* A remote code execution vulnerability is corrected in how the munin system monitoring graphs are generated for the control panel. The vulnerability involves an administrative user visiting a carefully crafted URL.
v0.19a (August 18, 2016)
------------------------
This update corrects a security issue in v0.19.
* fail2ban won't start if Roundcube had not yet been used - new installations probably do not have fail2ban running.
v0.19 (August 13, 2016)
-----------------------
Mail:
* Roundcube is updated to version 1.2.1.
* SSLv3 and RC4 are now no longer supported in incoming and outgoing mail (SMTP port 25).
Control panel:
* The users and aliases APIs are now documented on their control panel pages.
* The HSTS header was missing.
* New status checks were added for the ufw firewall.
DNS:
* Add SRV records for CardDAV/CalDAV to facilitate autoconfiguration (e.g. in DavDroid, whose latest version didn't seem to work to configure with entering just a hostname).
System:
* fail2ban jails added for SMTP submission, Roundcube, ownCloud, the control panel, and munin.
* Mail-in-a-Box can now be installed on the i686 architecture.
v0.18c (June 2, 2016)
---------------------
* Domain aliases (and misconfigured aliases/catch-alls with non-existent local targets) would accept mail and deliver it to new mailbox folders on disk even if the target address didn't correspond with an existing mail user, instead of rejecting the mail. This issue was introduced in v0.18.
* The Munin Monitoring link in the control panel now opens a new window.
* Added an undocumented before-backup script.
v0.18b (May 16, 2016)
---------------------
* Fixed a Roundcube user accounts issue introduced in v0.18.
v0.18 (May 15, 2016)
--------------------
ownCloud:
* Updated to ownCloud to 8.2.3
Mail:
* Roundcube is updated to version 1.1.5 and the Roundcube login screen now says "[hostname] Webmail" instead of "Mail-in-a-Box/Roundcube webmail".
* Fixed a long-standing issue with training the spam filter not working (because of a file permissions issue).
Control panel:
* Munin system monitoring graphs are now zoomable.
* When a reboot is required (due to Ubuntu security updates automatically installed), a Reboot Box button now appears on the System Status Checks page of the control panel.
* It is now possible to add SRV and secondary MX records in the Custom DNS page.
* Other minor fixes.
System:
* The fail2ban recidive jail, which blocks long-duration brute force attacks, now no longer sends the administrator emails (which were not helpful).
Setup:
* The system hostname is now set during setup.
* A swap file is now created if system memory is less than 2GB, 5GB of free disk space is available, and if no swap file yet exists.
* We now install Roundcube from the official GitHub repository instead of our own mirror, which we had previously created to solve problems with SourceForge.
* DKIM was incorrectly set up on machines where "localhost" was defined as something other than "127.0.0.1".
v0.17c (April 1, 2016)
----------------------
This update addresses some minor security concerns and some installation issues.
ownCoud:
* Block web access to the configuration parameters (config.php). There is no immediate impact (see [#776](https://github.com/mail-in-a-box/mailinabox/pull/776)), although advanced users may want to take note.
Mail:
* Roundcube html5_notifier plugin updated from version 0.6 to 0.6.2 to fix Roundcube getting stuck for some people.
Control panel:
* Prevent click-jacking of the management interface by adding HTTP headers.
* Failed login no longer reveals whether an account exists on the system.
Setup:
* Setup dialogs did not appear correctly when connecting to SSH using Putty on Windows.
* We now install Roundcube from our own mirror because Sourceforge's downloads experience frequent intermittant unavailability.
v0.17b (March 1, 2016)
----------------------
ownCloud moved their source code to a new location, breaking our installation script.
v0.17 (February 25, 2016)
-------------------------
Mail:
* Roundcube updated to version 1.1.4.
* When there's a problem delivering an outgoing message, a new 'warning' bounce will come after 3 hours and the box will stop trying after 2 days (instead of 5).
* On multi-homed machines, Postfix now binds to the right network interface when sending outbound mail so that SPF checks on the receiving end will pass.
* Mail sent from addresses on subdomains of other domains hosted by this box would not be DKIM-signed and so would fail DMARC checks by recipients, since version v0.15.
Control panel:
* TLS certificate provisioning would crash if DNS propagation was in progress and a challenge failed; might have shown the wrong error when provisioning fails.
* Backup times were displayed with the wrong time zone.
* Thresholds for displaying messages when the system is running low on memory have been reduced from 30% to 20% for a warning and from 15% to 10% for an error.
* Other minor fixes.
System:
* Backups to some AWS S3 regions broke in version 0.15 because we reverted the version of boto. That's now fixed.
* On low-usage systems, don't hold backups for quite so long by taking a full backup more often.
* Nightly status checks might fail on systems not configured with a default Unicode locale.
* If domains need a TLS certificate and the user hasn't installed one yet using Let's Encrypt, the administrator would get a nightly email with weird interactive text asking them to agree to Let's Encrypt's ToS. Now just say that the provisioning can't be done automatically.
* Reduce the number of background processes used by the management daemon to lower memory consumption.
Setup:
* The first screen now warns users not to install on a machine used for other things.
v0.16 (January 30, 2016)
------------------------
This update primarily adds automatic SSL (now "TLS") certificate provisioning from Let's Encrypt (https://letsencrypt.org/).
Control Panel:
* The SSL certificates (now referred to as "TLS ccertificates") page now supports provisioning free certificates from Let's Encrypt.
* Report free memory usage.
* Fix a crash when the git directory is not checked out to a tag.
* When IPv6 is enabled, check that all domains (besides the system hostname) resolve over IPv6.
* When a domain doesn't resolve to the box, don't bother checking if the TLS certificate is valid.
* Remove rounded border on the menu bar.
Other:
* The Sieve port is now open so tools like the Thunderbird Sieve extension can be used to edit mail filters.
* .be domains now offer DNSSEC options supported by the TLD
* The daily backup will now email the administrator if there is a problem.
* Expiring TLS certificates are now automatically renewed via Let's Encrypt.
* File ownership for installed Roundcube files is fixed.
* Typos fixed.
v0.15a (January 9, 2016)
------------------------
Mail:
* Sending mail through Exchange/ActiveSync (Z-Push) had been broken since v0.14 in some setups. This is now fixed.
v0.15 (January 1, 2016)
-----------------------
@@ -413,4 +895,4 @@ v0.02 (September 21, 2014)
v0.01 (August 19, 2014)
-----------------------
First release.
First versioned release after a year of unversioned development.

48
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,48 @@
# Mail-in-a-Box Code of Conduct
Mail-in-a-Box is an open source community project about working, as a group, to empower ourselves and others to have control over our own digital communications. Just as we hope to increase technological diversity on the Internet through decentralization, we also believe that diverse viewpoints and voices among our community members foster innovation and creative solutions to the challenges we face.
We are committed to providing a safe, welcoming, and harrassment-free space for collaboration, for everyone, without regard to age, disability, economic situation, ethnicity, gender identity and expression, language fluency, level of knowledge or experience, nationality, personal appearance, race, religion, sexual identity and orientation, or any other attribute. Community comes first. This policy supersedes all other project goals.
The maintainers of Mail-in-a-Box share the dual responsibility of leading by example and enforcing these policies as necessary to maintain an open and welcoming environment. All community members should be excellent to each other.
## Scope
This Code of Conduct applies to all places where Mail-in-a-Box community activity is ocurring, including on GitHub, in discussion forums, on Slack, on social media, and in real life. The Code of Conduct applies not only on websites/at events run by the Mail-in-a-Box community (e.g. our GitHub organization, our Slack team) but also at any other location where the Mail-in-a-Box community is present (e.g. in issues of other GitHub organizations where Mail-in-a-Box community members are discussing problems related to Mail-in-a-Box, or real-life professional conferences), or whenever a Mail-in-a-Box community member is representing Mail-in-a-Box to the public at large or acting on behalf of Mail-in-a-Box.
This code does not apply to activity on a server running Mail-in-a-Box software, unless your server is hosting a service for the Mail-in-a-Box community at large.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Showing empathy towards other community members
* Making room for new and quieter voices
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory/unwelcome comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Aggressive and micro-aggressive behavior, such as unconstructive criticism, providing corrections that do not improve the conversation (sometimes referred to as "well actually"s), repeatedly interrupting or talking over someone else, feigning surprise at someone's lack of knowledge or awareness about a topic, or subtle prejudice (for example, comments like "That's so easy my grandmother could do it.", which is prejudicial toward grandmothers).
* Other conduct which could reasonably be considered inappropriate in a professional setting
* Retaliating against anyone who reports a violation of this code.
We will not tolerate harassment. Harassment is any unwelcome or hostile behavior towards another person for any reason. This includes, but is not limited to, offensive verbal comments related to personal characteristics or choices, sexual images or comments, deliberate intimidation, bullying, stalking, following, harassing photography or recording, sustained disruption of discussion or events, nonconsensual publication of private comments, inappropriate physical contact, or unwelcome sexual attention. Conduct need not be intentional to be harassment.
## Enforcement
We will remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not consistent with this Code of Conduct. We may ban, temporarily or permanently, any contributor for violating this code, when appropriate.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project lead, [Joshua Tauberer](https://razor.occams.info/). All reports will be treated confidentially, impartially, consistently, and swiftly.
Because the need for confidentiality for all parties involved in an enforcement action outweighs the goals of openness, limited information will be shared with the Mail-in-a-Box community regarding enforcement actions that have taken place.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant, version 1.4](http://contributor-covenant.org/version/1/4) and the code of conduct of [Code for DC](http://codefordc.org/resources/codeofconduct.html).

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
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.
@@ -5,3 +52,7 @@ This project is in the public domain. Copyright and related rights in the work w
All contributions to this project must be released under the same CC0 wavier. By submitting a pull request or patch, you are agreeing to comply with this waiver of copyright interest.
[CC0]: http://creativecommons.org/publicdomain/zero/1.0/
## Code of Conduct
This project has a [Code of Conduct](CODE_OF_CONDUCT.md). Please review it when joining our community.

View File

@@ -9,35 +9,34 @@ Mail-in-a-Box helps individuals take back control of their email by defining a o
* * *
I am trying to:
Our goals are to:
* Make deploying a good mail server easy.
* Promote [decentralization](http://redecentralize.org/), innovation, and privacy on the web.
* Have automated, auditable, and [idempotent](http://sharknet.us/2014/02/01/automated-configuration-management-challenges-with-idempotency/) configuration.
* Have automated, auditable, and [idempotent](https://sharknet.us/2014/02/01/automated-configuration-management-challenges-with-idempotency/) configuration.
* **Not** make a totally unhackable, NSA-proof server.
* **Not** make something customizable by power users.
This setup is what has been powering my own personal email since September 2013.
Additionally, this project has a [Code of Conduct](CODE_OF_CONDUCT.md), which supersedes the goals above. Please review it when joining our community.
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".
The components installed are:
* SMTP ([postfix](http://www.postfix.org/)), IMAP ([dovecot](http://dovecot.org/)), CardDAV/CalDAV ([ownCloud](http://owncloud.org/)), Exchange ActiveSync ([z-push](https://github.com/fmbiete/Z-Push-contrib))
* SMTP ([postfix](http://www.postfix.org/)), IMAP ([dovecot](http://dovecot.org/)), CardDAV/CalDAV ([Nextcloud](https://nextcloud.com/)), Exchange ActiveSync ([z-push](http://z-push.org/))
* Webmail ([Roundcube](http://roundcube.net/)), static website hosting ([nginx](http://nginx.org/))
* Spam filtering ([spamassassin](https://spamassassin.apache.org/)), greylisting ([postgrey](http://postgrey.schweikert.ch/))
* DNS ([nsd4](http://www.nlnetlabs.nl/projects/nsd/)) with [SPF](https://en.wikipedia.org/wiki/Sender_Policy_Framework), DKIM ([OpenDKIM](http://www.opendkim.org/)), [DMARC](https://en.wikipedia.org/wiki/DMARC), [DNSSEC](https://en.wikipedia.org/wiki/DNSSEC), [DANE TLSA](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities), and [SSHFP](https://tools.ietf.org/html/rfc4255) records automatically set
* DNS ([nsd4](https://www.nlnetlabs.nl/projects/nsd/)) with [SPF](https://en.wikipedia.org/wiki/Sender_Policy_Framework), DKIM ([OpenDKIM](http://www.opendkim.org/)), [DMARC](https://en.wikipedia.org/wiki/DMARC), [DNSSEC](https://en.wikipedia.org/wiki/DNSSEC), [DANE TLSA](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities), and [SSHFP](https://tools.ietf.org/html/rfc4255) records automatically set
* Backups ([duplicity](http://duplicity.nongnu.org/)), firewall ([ufw](https://launchpad.net/ufw)), intrusion protection ([fail2ban](http://www.fail2ban.org/wiki/index.php/Main_Page)), system monitoring ([munin](http://munin-monitoring.org/))
It also includes:
* 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).
@@ -46,46 +45,52 @@ Installation
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:
$ git clone https://github.com/mail-in-a-box/mailinabox
$ cd mailinabox
_Optional:_ Download my PGP key and then verify that the sources were signed
by me:
_Optional:_ Download Josh's PGP key and then verify that the sources were signed
by him:
$ curl -s https://keybase.io/joshdata/key.asc | gpg --import
gpg: key C10BDD81: public key "Joshua Tauberer <jt@occams.info>" imported
$ git verify-tag v0.15
$ git verify-tag v0.42
gpg: Signature made ..... using RSA key ID C10BDD81
gpg: Good signature from "Joshua Tauberer <jt@occams.info>"
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: 5F4C 0E73 13CC D744 693B 2AEA B920 41F4 C10B DD81
You'll get a lot of warnings, but that's OK. Check that the primary key fingerprint matchs 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)
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:
$ git checkout v0.15
$ git checkout v0.42
Begin the installation.
$ 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
--------------------
This project was inspired in part by the ["NSA-proof your email in 2 hours"](http://sealedabstract.com/code/nsa-proof-your-e-mail-in-2-hours/) blog post by Drew Crawford, [Sovereign](https://github.com/al3x/sovereign) by Alex Payne, and conversations with <a href="http://twitter.com/shevski" target="_blank">@shevski</a>, <a href="https://github.com/konklone" target="_blank">@konklone</a>, and <a href="https://github.com/gregelin" target="_blank">@GregElin</a>.
This project was inspired in part by the ["NSA-proof your email in 2 hours"](http://sealedabstract.com/code/nsa-proof-your-e-mail-in-2-hours/) blog post by Drew Crawford, [Sovereign](https://github.com/sovereign/sovereign) by Alex Payne, and conversations with <a href="https://twitter.com/shevski" target="_blank">@shevski</a>, <a href="https://github.com/konklone" target="_blank">@konklone</a>, and <a href="https://github.com/gregelin" target="_blank">@GregElin</a>.
Mail-in-a-Box is similar to [iRedMail](http://www.iredmail.org/) and [Modoboa](https://github.com/tonioo/modoboa).
@@ -95,5 +100,5 @@ The History
* In 2007 I wrote a relatively popular Mozilla Thunderbird extension that added client-side SPF and DKIM checks to mail to warn users about possible phishing: [add-on page](https://addons.mozilla.org/en-us/thunderbird/addon/sender-verification-anti-phish/), [source](https://github.com/JoshData/thunderbird-spf).
* In August 2013 I began Mail-in-a-Box by combining my own mail server configuration with the setup in ["NSA-proof your email in 2 hours"](http://sealedabstract.com/code/nsa-proof-your-e-mail-in-2-hours/) and making the setup steps reproducible with bash scripts.
* Mail-in-a-Box was a semifinalist in the 2014 [Knight News Challenge](https://www.newschallenge.org/challenge/2014/submissions/mail-in-a-box), but it was not selected as a winner.
* Mail-in-a-Box hit the front page of Hacker News in [April](https://news.ycombinator.com/item?id=7634514) 2014, [September](https://news.ycombinator.com/item?id=8276171) 2014, and [May](https://news.ycombinator.com/item?id=9624267) 2015.
* Mail-in-a-Box hit the front page of Hacker News in [April](https://news.ycombinator.com/item?id=7634514) 2014, [September](https://news.ycombinator.com/item?id=8276171) 2014, [May](https://news.ycombinator.com/item?id=9624267) 2015, and [November](https://news.ycombinator.com/item?id=13050500) 2016.
* FastCompany mentioned Mail-in-a-Box a [roundup of privacy projects](http://www.fastcompany.com/3047645/your-own-private-cloud) on June 26, 2015.

15
Vagrantfile vendored
View File

@@ -2,26 +2,23 @@
# 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.box = "ubuntu/bionic64"
# Network config: Since it's a mail server, the machine must be connected
# to the public web. However, we currently don't want to expose SSH since
# the machine's box will let anyone log into it. So instead we'll put the
# machine on a private network.
config.vm.hostname = "mailinabox"
config.vm.hostname = "mailinabox.lan"
config.vm.network "private_network", ip: "192.168.50.4"
config.vm.provision :shell, :inline => <<-SH
# Set environment variables so that the setup script does
# not ask any questions during provisioning. We'll let the
# machine figure out its own public IP and it'll take a
# subdomain on our justtesting.email domain so we can get
# started quickly.
# Set environment variables so that the setup script does
# not ask any questions during provisioning. We'll let the
# machine figure out its own public IP.
export NONINTERACTIVE=1
export PUBLIC_IP=auto
export PUBLIC_IPV6=auto
export PRIMARY_HOSTNAME=auto-easy
export PRIMARY_HOSTNAME=auto
#export SKIP_NETWORK_CHECKS=1
# Start the setup script.

View File

@@ -0,0 +1,12 @@
# Fail2Ban filter Mail-in-a-Box management daemon
[INCLUDES]
before = common.conf
[Definition]
_daemon = mailinabox
failregex = Mail-in-a-Box Management Daemon: Failed login attempt from ip <HOST> - timestamp .*
ignoreregex =

View File

@@ -0,0 +1,7 @@
[INCLUDES]
before = common.conf
[Definition]
failregex=<HOST> - .*GET /admin/munin/.* HTTP/1.1\" 401.*
ignoreregex =

View File

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

View File

@@ -0,0 +1,7 @@
[INCLUDES]
before = common.conf
[Definition]
failregex=postfix/submission/smtpd.*warning.*\[<HOST>\]: .* authentication (failed|aborted)
ignoreregex =

View File

@@ -0,0 +1,9 @@
[INCLUDES]
before = common.conf
[Definition]
failregex = IMAP Error: Login failed for .*? from <HOST>\. AUTHENTICATE.*
ignoreregex =

View File

@@ -1,29 +0,0 @@
# Fail2Ban configuration file for Mail-in-a-Box
[DEFAULT]
# Whitelist our own IP addresses. 127.0.0.1/8 is the default. But our status checks
# ping services over the public interface so we should whitelist that address of
# ours too. The string is substituted during installation.
ignoreip = 127.0.0.1/8 PUBLIC_IP
# JAILS
[ssh]
maxretry = 7
bantime = 3600
[ssh-ddos]
enabled = true
[sasl]
enabled = true
[dovecot]
enabled = true
filter = dovecotimap
findtime = 30
maxretry = 20
[recidive]
enabled = true
maxretry = 10

78
conf/fail2ban/jails.conf Normal file
View File

@@ -0,0 +1,78 @@
# Fail2Ban configuration file for Mail-in-a-Box. Do not edit.
# This file is re-generated on updates.
[DEFAULT]
# Whitelist our own IP addresses. 127.0.0.1/8 is the default. But our status checks
# ping services over the public interface so we should whitelist that address of
# ours too. The string is substituted during installation.
ignoreip = 127.0.0.1/8 PUBLIC_IP
[dovecot]
enabled = true
filter = dovecotimap
logpath = /var/log/mail.log
findtime = 30
maxretry = 20
[miab-management]
enabled = true
filter = miab-management-daemon
port = http,https
logpath = /var/log/syslog
maxretry = 20
findtime = 30
[miab-munin]
enabled = true
port = http,https
filter = miab-munin
logpath = /var/log/nginx/access.log
maxretry = 20
findtime = 30
[miab-owncloud]
enabled = true
port = http,https
filter = miab-owncloud
logpath = STORAGE_ROOT/owncloud/nextcloud.log
maxretry = 20
findtime = 120
[miab-postfix587]
enabled = true
port = 587
filter = miab-postfix-submission
logpath = /var/log/mail.log
maxretry = 20
findtime = 30
[miab-roundcube]
enabled = true
port = http,https
filter = miab-roundcube
logpath = /var/log/roundcubemail/errors
maxretry = 20
findtime = 30
[recidive]
enabled = true
maxretry = 10
action = iptables-allports[name=recidive]
# In the recidive section of jail.conf the action contains:
#
# action = iptables-allports[name=recidive]
# sendmail-whois-lines[name=recidive, logpath=/var/log/fail2ban.log]
#
# The last line on the action will sent an email to the configured address. This mail will
# notify the administrator that someone has been repeatedly triggering one of the other jails.
# By default we don't configure this address and no action is required from the admin anyway.
# So the notification is ommited. This will prevent message appearing in the mail.log that mail
# can't be delivered to fail2ban@$HOSTNAME.
[postfix-sasl]
enabled = true
[sshd]
enabled = true
maxretry = 7
bantime = 3600

View File

@@ -18,8 +18,6 @@
<string>PRIMARY_HOSTNAME</string>
<key>CalDAVPort</key>
<real>443</real>
<key>CalDAVPrincipalURL</key>
<string>/cloud/remote.php/caldav/calendars/</string>
<key>CalDAVUseSSL</key>
<true/>
<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/bin/mailinabox-daemon
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. Here and in
# setup/start.sh (where the locale is also installed if not
# already present).
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

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 {
alias /var/lib/mailinabox/mozilla-autoconfig.xml;
}
location = /mail/config-v1.1.xml {
alias /var/lib/mailinabox/mozilla-autoconfig.xml;
}
# Roundcube Webmail configuration.
rewrite ^/mail$ /mail/ redirect;
@@ -70,7 +73,7 @@
# takes precedence over all non-regex matches and only regex matches that
# come after it (i.e. none of those, since this is the last one.) That means
# we're blocking dotfiles in the static hosted sites but not the FastCGI-
# handled locations for ownCloud (which serves user-uploaded files that might
# handled locations for Nextcloud (which serves user-uploaded files that might
# have this pattern, see #414) or some of the other services.
location ~ /\.(ht|svn|git|hg|bzr) {
log_not_found off;

View File

@@ -1,23 +1,41 @@
# Control Panel
# Proxy /admin to our Python based control panel daemon. It is
# listening on IPv4 only so use an IP address and not 'localhost'.
location /admin/assets {
alias /usr/local/lib/mailinabox/vendor/assets;
}
rewrite ^/admin$ /admin/;
rewrite ^/admin/munin$ /admin/munin/ redirect;
location /admin/ {
proxy_pass http://127.0.0.1:10222/;
proxy_set_header X-Forwarded-For $remote_addr;
add_header X-Frame-Options "DENY";
add_header X-Content-Type-Options nosniff;
add_header Content-Security-Policy "frame-ancestors 'none';";
}
# ownCloud configuration.
# Nextcloud configuration.
rewrite ^/cloud$ /cloud/ redirect;
rewrite ^/cloud/$ /cloud/index.php;
rewrite ^/cloud/(contacts|calendar|files)$ /cloud/index.php/apps/$1/ redirect;
rewrite ^(/cloud/core/doc/[^\/]+/)$ $1/index.html;
rewrite ^(/cloud/oc[sm]-provider)/$ $1/index.php redirect;
location /cloud/ {
alias /usr/local/lib/owncloud/;
location ~ ^/(data|config|\.ht|db_structure\.xml|README) {
deny all;
location ~ ^/cloud/(build|tests|config|lib|3rdparty|templates|data|README)/ {
deny all;
}
location ~ ^/cloud/(?:\.|autotest|occ|issue|indie|db_|console) {
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)(/.*)?$ {
# note: ~ has precendence over a regular location block
@@ -34,13 +52,11 @@
fastcgi_param MOD_X_ACCEL_REDIRECT_PREFIX /owncloud-xaccel;
fastcgi_read_timeout 630;
fastcgi_pass php-fpm;
error_page 403 /cloud/core/templates/403.php;
error_page 404 /cloud/core/templates/404.php;
client_max_body_size 1G;
fastcgi_buffers 64 4K;
}
location ^~ /owncloud-xaccel/ {
# This directory is for MOD_X_ACCEL_REDIRECT_ENABLED. ownCloud sends the full file
# This directory is for MOD_X_ACCEL_REDIRECT_ENABLED. Nextcloud sends the full file
# path on disk as a subdirectory under this virtual path.
# We must only allow 'internal' redirects within nginx so that the filesystem
# is not exposed to the world.

View File

@@ -1,76 +1,20 @@
# from: https://gist.github.com/konklone/6532544
###################################################################################
# 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-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_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 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !MD5 !EXP !DSS !PSK !SRP !kECDH !CAMELLIA !RC4 !SEED';
# 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 10 min cache shared across nginx processes,
# as recommended by http://nginx.org/en/docs/http/configuring_https_servers.html
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
#keepalive_timeout 70; # in Ubuntu 14.04/nginx 1.4.6 the default is 65, so plenty good
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
# Buffer size of 1400 bytes fits in one MTU.
# 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_verify on;
resolver 127.0.0.1 valid=86400;
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.
upstream php-fpm {
server unix:/var/run/php5-fpm.sock;
server unix:/var/run/php/php7.2-fpm.sock;
}

View File

@@ -1,6 +1,8 @@
## $HOSTNAME
# Redirect all HTTP to HTTPS.
# Redirect all HTTP to HTTPS *except* the ACME challenges (Let's Encrypt TLS certificate
# domain validation challenges) path, which must be served over HTTP per the ACME spec
# (due to some Apache vulnerability).
server {
listen 80;
listen [::]:80;
@@ -12,16 +14,25 @@ server {
# error pages and in the "Server" HTTP-Header.
server_tokens off;
# Redirect using the 'return' directive and the built-in
# variable '$request_uri' to avoid any capturing, matching
# or evaluation of regular expressions.
return 301 https://$HOSTNAME$request_uri;
location / {
# Redirect using the 'return' directive and the built-in
# variable '$request_uri' to avoid any capturing, matching
# or evaluation of regular expressions.
return 301 https://$HOSTNAME$request_uri;
}
location /.well-known/acme-challenge/ {
# This path must be served over HTTP for ACME domain validation.
# We map this to a special path where our TLS cert provisioning
# tool knows to store challenge response files.
alias $STORAGE_ROOT/ssl/lets_encrypt/webroot/.well-known/acme-challenge/;
}
}
# The secure HTTPS server.
server {
listen 443 ssl;
listen [::]:443 ssl;
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name $HOSTNAME;

View File

@@ -5,11 +5,12 @@
* Descr : Autodiscover configuration file
************************************************/
define('TIMEZONE', '');
// Defines the base path on the server
define('BASE_PATH', dirname($_SERVER['SCRIPT_FILENAME']). '/');
// The Z-Push server location for the autodiscover response
define('SERVERURL', 'https://PRIMARY_HOSTNAME/Microsoft-Server-ActiveSync');
define('ZPUSH_HOST', 'PRIMARY_HOSTNAME');
define('USE_FULLEMAIL_FOR_LOGIN', true);
@@ -18,6 +19,7 @@ define('LOGFILE', LOGFILEDIR . 'autodiscover.log');
define('LOGERRORFILE', LOGFILEDIR . 'autodiscover-error.log');
define('LOGLEVEL', LOGLEVEL_INFO);
define('LOGUSERLEVEL', LOGLEVEL);
$specialLogUsers = array();
// the backend data provider
define('BACKEND_PROVIDER', 'BackendCombined');

View File

@@ -6,7 +6,7 @@
************************************************/
define('CALDAV_PROTOCOL', 'https');
define('CALDAV_SERVER', 'localhost');
define('CALDAV_SERVER', '127.0.0.1');
define('CALDAV_PORT', '443');
define('CALDAV_PATH', '/caldav/calendars/%u/');
define('CALDAV_PERSONAL', 'PRINCIPAL');

View File

@@ -7,7 +7,7 @@
define('CARDDAV_PROTOCOL', 'https'); /* http or https */
define('CARDDAV_SERVER', 'localhost');
define('CARDDAV_SERVER', '127.0.0.1');
define('CARDDAV_PORT', '443');
define('CARDDAV_PATH', '/carddav/addressbooks/%u/');
define('CARDDAV_DEFAULT_PATH', '/carddav/addressbooks/%u/contacts/'); /* subdirectory of the main path */
@@ -17,7 +17,7 @@ define('CARDDAV_CONTACTS_FOLDER_NAME', '%u Addressbook');
define('CARDDAV_SUPPORTS_SYNC', false);
// If the CardDAV server supports the FN attribute for searches
// DAViCal supports it, but SabreDav, Owncloud and SOGo don't
// DAViCal supports it, but SabreDav, Nextcloud and SOGo don't
// Setting this to true will search by FN. If false will search by sn, givenName and email
// It's safe to leave it as false
define('CARDDAV_SUPPORTS_FN_SEARCH', false);

View File

@@ -5,10 +5,10 @@
* Descr : IMAP backend configuration file
************************************************/
define('IMAP_SERVER', 'localhost');
define('IMAP_SERVER', '127.0.0.1');
define('IMAP_PORT', 993);
define('IMAP_OPTIONS', '/ssl/norsh/novalidate-cert');
define('IMAP_DEFAULTFROM', '');
define('IMAP_DEFAULTFROM', 'sql');
define('SYSTEM_MIME_TYPES_MAPPING', '/etc/mime.types');
define('IMAP_AUTOSEEN_ON_DELETE', false);
@@ -23,15 +23,19 @@ define('IMAP_FOLDER_TRASH', 'TRASH');
define('IMAP_FOLDER_SPAM', 'SPAM');
define('IMAP_FOLDER_ARCHIVE', 'ARCHIVE');
define('IMAP_INLINE_FORWARD', true);
define('IMAP_EXCLUDED_FOLDERS', '');
// not used
define('IMAP_FROM_SQL_DSN', '');
define('IMAP_FROM_SQL_DSN', 'sqlite:STORAGE_ROOT/mail/roundcube/roundcube.sqlite');
define('IMAP_FROM_SQL_USER', '');
define('IMAP_FROM_SQL_PASSWORD', '');
define('IMAP_FROM_SQL_OPTIONS', serialize(array(PDO::ATTR_PERSISTENT => true)));
define('IMAP_FROM_SQL_QUERY', "select first_name, last_name, mail_address from users where mail_address = '#username@#domain'");
define('IMAP_FROM_SQL_FIELDS', serialize(array('first_name', 'last_name', 'mail_address')));
define('IMAP_FROM_SQL_FROM', '#first_name #last_name <#mail_address>');
define('IMAP_FROM_SQL_QUERY', "SELECT name, email FROM identities i INNER JOIN users u ON i.user_id = u.user_id WHERE u.username = '#username' AND i.standard = 1 AND i.del = 0 AND i.name <> ''");
define('IMAP_FROM_SQL_FIELDS', serialize(array('name', 'email')));
define('IMAP_FROM_SQL_FROM', '#name <#email>');
define('IMAP_FROM_SQL_FULLNAME', '#name');
// not used
define('IMAP_FROM_LDAP_SERVER', '');
define('IMAP_FROM_LDAP_SERVER_PORT', '389');
define('IMAP_FROM_LDAP_USER', 'cn=zpush,ou=servers,dc=zpush,dc=org');
@@ -40,11 +44,14 @@ define('IMAP_FROM_LDAP_BASE', 'dc=zpush,dc=org');
define('IMAP_FROM_LDAP_QUERY', '(mail=#username@#domain)');
define('IMAP_FROM_LDAP_FIELDS', serialize(array('givenname', 'sn', 'mail')));
define('IMAP_FROM_LDAP_FROM', '#givenname #sn <#mail>');
define('IMAP_FROM_LDAP_FULLNAME', '#givenname #sn');
define('IMAP_SMTP_METHOD', 'sendmail');
global $imap_smtp_params;
$imap_smtp_params = array('host' => 'ssl://localhost', 'port' => 587, 'auth' => true, 'username' => 'imap_username', 'password' => 'imap_password');
$imap_smtp_params = array('host' => 'ssl://127.0.0.1', 'port' => 587, 'auth' => true, 'username' => 'imap_username', 'password' => 'imap_password');
define('MAIL_MIMEPART_CRLF', "\r\n");
define('IMAP_MEETING_USE_CALDAV', true);
?>

View File

@@ -1,39 +1,43 @@
#!/usr/bin/python3
#!/usr/local/lib/mailinabox/env/bin/python
# This script performs a backup of all user data:
# 1) System services are stopped while a copy of user data is made.
# 2) An incremental encrypted backup is made using duplicity into the
# directory STORAGE_ROOT/backup/encrypted. The password used for
# encryption is stored in backup/secret_key.txt.
# 3) The stopped services are restarted.
# 5) STORAGE_ROOT/backup/after-backup is executd if it exists.
# 1) System services are stopped.
# 2) STORAGE_ROOT/backup/before-backup is executed if it exists.
# 3) An incremental encrypted backup is made using duplicity.
# 4) The stopped services are restarted.
# 5) STORAGE_ROOT/backup/after-backup is executed if it exists.
import os, os.path, shutil, glob, re, datetime
import os, os.path, shutil, glob, re, datetime, sys
import dateutil.parser, dateutil.relativedelta, dateutil.tz
import rtyaml
from exclusiveprocess import Lock
from utils import exclusive_process, load_environment, shell, wait_for_service, fix_boto
from utils import load_environment, shell, wait_for_service, fix_boto
rsync_ssh_options = [
"--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\"",
]
def backup_status(env):
# Root folder
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.
# If backups are dissbled, return no status.
config = get_backup_config(env)
now = datetime.datetime.now(dateutil.tz.tzlocal())
# Are backups dissbled?
if config["target"] == "off":
return { }
# Query duplicity to get a list of all full and incremental
# backups available.
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')
def reldate(date, ref, clip):
if ref < date: return clip
rd = dateutil.relativedelta.relativedelta(ref, date)
if rd.years > 1: return "%d years, %d months" % (rd.years, rd.months)
if rd.years == 1: return "%d year, %d months" % (rd.years, rd.months)
if rd.months > 1: return "%d months, %d days" % (rd.months, rd.days)
if rd.months == 1: return "%d month, %d days" % (rd.months, rd.days)
if rd.days >= 7: return "%d days" % rd.days
@@ -44,15 +48,16 @@ def backup_status(env):
# Get duplicity collection status and parse for a list of backups.
def parse_line(line):
keys = line.strip().split()
date = dateutil.parser.parse(keys[1])
date = dateutil.parser.parse(keys[1]).astimezone(dateutil.tz.tzlocal())
return {
"date": keys[1],
"date_str": date.strftime("%x %X"),
"date_str": date.strftime("%Y-%m-%d %X") + " " + now.tzname(),
"date_delta": reldate(date, now, "the future?"),
"full": keys[0] == "full",
"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', [
"/usr/bin/duplicity",
"collection-status",
@@ -60,73 +65,98 @@ def backup_status(env):
"--gpg-options", "--cipher-algo=AES256",
"--log-fd", "1",
config["target"],
],
] + rsync_ssh_options,
get_env(env),
trap=True)
if code != 0:
# Command failed. This is likely due to an improperly configured remote
# destination for the backups.
return { }
# destination for the backups or the last backup job terminated unexpectedly.
raise Exception("Something is wrong with the backup: " + collection_status)
for line in collection_status.split('\n'):
if line.startswith(" full") or line.startswith(" inc"):
backup = parse_line(line)
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):
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
key = m.group("date")
backups[key]["size"] += size
if key in backups:
backups[key]["size"] += size
else:
unmatched_file_size += size
# Ensure the rows are sorted reverse chronologically.
# This is relied on by should_force_full() and the next step.
backups = sorted(backups.values(), key = lambda b : b["date"], reverse=True)
# Get the average size of incremental backups and the size of the
# most recent full backup.
# Get the average size of incremental backups, the size of the
# most recent full backup, and the date of the most recent
# backup and the most recent full backup.
incremental_count = 0
incremental_size = 0
first_date = None
first_full_size = None
first_full_date = None
for bak in backups:
if first_date is None:
first_date = dateutil.parser.parse(bak["date"])
if bak["full"]:
first_full_size = bak["size"]
first_full_date = dateutil.parser.parse(bak["date"])
break
incremental_count += 1
incremental_size += bak["size"]
# Predict how many more increments until the next full backup,
# and add to that the time we hold onto backups, to predict
# how long the most recent full backup+increments will be held
# onto. Round up since the backup occurs on the night following
# when the threshold is met.
# When will the most recent backup be deleted? It won't be deleted if the next
# backup is incremental, because the increments rely on all past increments.
# So first guess how many more incremental backups will occur until the next
# full backup. That full backup frees up this one to be deleted. But, the backup
# must also be at least min_age_in_days old too.
deleted_in = None
if incremental_count > 0 and first_full_size is not None:
deleted_in = "approx. %d days" % round(config["min_age_in_days"] + (.5 * first_full_size - incremental_size) / (incremental_size/incremental_count) + .5)
if incremental_count > 0 and incremental_size > 0 and first_full_size is not None:
# How many days until the next incremental backup? First, the part of
# the algorithm based on increment sizes:
est_days_to_next_full = (.5 * first_full_size - incremental_size) / (incremental_size/incremental_count)
est_time_of_next_full = first_date + datetime.timedelta(days=est_days_to_next_full)
# When will a backup be deleted?
# ...And then the part of the algorithm based on full backup age:
est_time_of_next_full = min(est_time_of_next_full, first_full_date + datetime.timedelta(days=config["min_age_in_days"]*10+1))
# It still can't be deleted until it's old enough.
est_deleted_on = max(est_time_of_next_full, first_date + datetime.timedelta(days=config["min_age_in_days"]))
deleted_in = "approx. %d days" % round((est_deleted_on-now).total_seconds()/60/60/24 + .5)
# When will a backup be deleted? Set the deleted_in field of each backup.
saw_full = False
days_ago = now - datetime.timedelta(days=config["min_age_in_days"])
for bak in backups:
if deleted_in:
# Subsequent backups are deleted when the most recent increment
# in the chain would be deleted.
# The most recent increment in a chain and all of the previous backups
# it relies on are deleted at the same time.
bak["deleted_in"] = deleted_in
if bak["full"]:
# Reset when we get to a full backup. A new chain start next.
# Reset when we get to a full backup. A new chain start *next*.
saw_full = True
deleted_in = None
elif saw_full and not deleted_in:
# Mark deleted_in only on the first increment after a full backup.
deleted_in = reldate(days_ago, dateutil.parser.parse(bak["date"]), "on next daily backup")
# We're now on backups prior to the most recent full backup. These are
# free to be deleted as soon as they are min_age_in_days old.
deleted_in = reldate(now, dateutil.parser.parse(bak["date"]) + datetime.timedelta(days=config["min_age_in_days"]), "on next daily backup")
bak["deleted_in"] = deleted_in
return {
"tz": now.tzname(),
"backups": backups,
"unmatched_file_size": unmatched_file_size,
}
def should_force_full(env):
def should_force_full(config, env):
# Force a full backup when the total size of the increments
# since the last full backup is greater than half the size
# of that full backup.
@@ -138,8 +168,14 @@ def should_force_full(env):
inc_size += bak["size"]
else:
# ...until we reach the most recent full backup.
# Return if we should to a full backup.
return inc_size > .5*bak["size"]
# Return if we should to a full backup, which is based
# on the size of the increments relative to the full
# backup, as well as the age of the full backup.
if inc_size > .5*bak["size"]:
return True
if dateutil.parser.parse(bak["date"]) + datetime.timedelta(days=config["min_age_in_days"]*10+1) < datetime.datetime.now(dateutil.tz.tzlocal()):
return True
return False
else:
# If we got here there are no (full) backups, so make one.
# (I love for/else blocks. Here it's just to show off.)
@@ -156,73 +192,73 @@ def get_passphrase(env):
with open(os.path.join(backup_root, 'secret_key.txt')) as f:
passphrase = f.readline().strip()
if len(passphrase) < 43: raise Exception("secret_key.txt's first line is too short!")
return passphrase
def get_env(env):
config = get_backup_config(env)
env = { "PASSPHRASE" : get_passphrase(env) }
if get_target_type(config) == 's3':
env["AWS_ACCESS_KEY_ID"] = config["target_user"]
env["AWS_SECRET_ACCESS_KEY"] = config["target_pass"]
return env
def get_target_type(config):
protocol = config["target"].split(":")[0]
return protocol
def perform_backup(full_backup):
env = load_environment()
exclusive_process("backup")
# Create an global exclusive lock so that the backup script
# cannot be run more than one.
Lock(die=True).forever()
config = get_backup_config(env)
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
backup_cache_dir = os.path.join(backup_root, 'cache')
backup_dir = os.path.join(backup_root, 'encrypted')
# Are backups dissbled?
# Are backups disabled?
if config["target"] == "off":
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
# will fail. Otherwise do a full backup when the size of
# the increments since the most recent full backup are
# large.
full_backup = full_backup or should_force_full(env)
try:
full_backup = full_backup or should_force_full(config, env)
except Exception as e:
# This was the first call to duplicity, and there might
# be an error already.
print(e)
sys.exit(1)
# Stop services.
shell('check_call', ["/usr/sbin/service", "php5-fpm", "stop"])
shell('check_call', ["/usr/sbin/service", "postfix", "stop"])
shell('check_call', ["/usr/sbin/service", "dovecot", "stop"])
def service_command(service, command, quit=None):
# Execute silently, but if there is an error then display the output & exit.
code, ret = shell('check_output', ["/usr/sbin/service", service, command], capture_stderr=True, trap=True)
if code != 0:
print(ret)
if quit:
sys.exit(code)
service_command("php7.2-fpm", "stop", quit=True)
service_command("postfix", "stop", quit=True)
service_command("dovecot", "stop", quit=True)
# Execute a pre-backup script that copies files outside the homedir.
# Run as the STORAGE_USER user, not as root. Pass our settings in
# environment variables so the script has access to STORAGE_ROOT.
pre_script = os.path.join(backup_root, 'before-backup')
if os.path.exists(pre_script):
shell('check_call',
['su', env['STORAGE_USER'], '-c', pre_script, config["target"]],
env=env)
# Run a backup of STORAGE_ROOT (but excluding the backups themselves!).
# --allow-source-mismatch is needed in case the box's hostname is changed
@@ -231,6 +267,7 @@ def perform_backup(full_backup):
shell('check_call', [
"/usr/bin/duplicity",
"full" if full_backup else "incr",
"--verbosity", "warning", "--no-print-statistics",
"--archive-dir", backup_cache_dir,
"--exclude", backup_root,
"--volsize", "250",
@@ -238,17 +275,13 @@ def perform_backup(full_backup):
env["STORAGE_ROOT"],
config["target"],
"--allow-source-mismatch"
],
] + rsync_ssh_options,
get_env(env))
finally:
# Start services again.
shell('check_call', ["/usr/sbin/service", "dovecot", "start"])
shell('check_call', ["/usr/sbin/service", "postfix", "start"])
shell('check_call', ["/usr/sbin/service", "php5-fpm", "start"])
# 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)
service_command("dovecot", "start", quit=False)
service_command("postfix", "start", quit=False)
service_command("php7.2-fpm", "start", quit=False)
# Remove old backups. This deletes all backup data no longer needed
# from more than 3 days ago.
@@ -256,10 +289,11 @@ def perform_backup(full_backup):
"/usr/bin/duplicity",
"remove-older-than",
"%dD" % config["min_age_in_days"],
"--verbosity", "error",
"--archive-dir", backup_cache_dir,
"--force",
config["target"]
],
] + rsync_ssh_options,
get_env(env))
# From duplicity's manual:
@@ -270,10 +304,11 @@ def perform_backup(full_backup):
shell('check_call', [
"/usr/bin/duplicity",
"cleanup",
"--verbosity", "error",
"--archive-dir", backup_cache_dir,
"--force",
config["target"]
],
] + rsync_ssh_options,
get_env(env))
# Change ownership of backups to the user-data user, so that the after-bcakup
@@ -312,7 +347,7 @@ def run_duplicity_verification():
"--exclude", backup_root,
config["target"],
env["STORAGE_ROOT"],
], get_env(env))
] + rsync_ssh_options, get_env(env))
def run_duplicity_restore(args):
env = load_environment()
@@ -323,32 +358,82 @@ def run_duplicity_restore(args):
"restore",
"--archive-dir", backup_cache_dir,
config["target"],
] + args,
] + rsync_ssh_options + args,
get_env(env))
def list_target_files(config):
import urllib.parse
try:
p = urllib.parse.urlparse(config["target"])
target = urllib.parse.urlparse(config["target"])
except ValueError:
return "invalid target"
if p.scheme == "file":
return [(fn, os.path.getsize(os.path.join(p.path, fn))) for fn in os.listdir(p.path)]
if target.scheme == "file":
return [(fn, os.path.getsize(os.path.join(target.path, fn))) for fn in os.listdir(target.path)]
elif p.scheme == "s3":
elif target.scheme == "rsync":
rsync_fn_size_re = re.compile(r'.* ([^ ]*) [^ ]* [^ ]* (.*)')
rsync_target = '{host}:{path}'
target_path = target.path
if not target_path.endswith('/'):
target_path = target_path + '/'
if target_path.startswith('/'):
target_path = target_path[1:]
rsync_command = [ 'rsync',
'-e',
'/usr/bin/ssh -i /root/.ssh/id_rsa_miab -oStrictHostKeyChecking=no -oBatchMode=yes',
'--list-only',
'-r',
rsync_target.format(
host=target.netloc,
path=target_path)
]
code, listing = shell('check_output', rsync_command, trap=True, capture_stderr=True)
if code == 0:
ret = []
for l in listing.split('\n'):
match = rsync_fn_size_re.match(l)
if match:
ret.append( (match.groups()[1], int(match.groups()[0].replace(',',''))) )
return ret
else:
if 'Permission denied (publickey).' in listing:
reason = "Invalid user or check you correctly copied the SSH key."
elif 'No such file or directory' in listing:
reason = "Provided path {} is invalid.".format(target_path)
elif 'Network is unreachable' in listing:
reason = "The IP address {} is unreachable.".format(target.hostname)
elif 'Could not resolve hostname':
reason = "The hostname {} cannot be resolved.".format(target.hostname)
else:
reason = "Unknown error." \
"Please check running 'python management/backup.py --verify'" \
"from mailinabox sources to debug the issue."
raise ValueError("Connection to rsync host failed: {}".format(reason))
elif target.scheme == "s3":
# match to a Region
fix_boto() # must call prior to importing boto
import boto.s3
from boto.exception import BotoServerError
custom_region = False
for region in boto.s3.regions():
if region.endpoint == p.hostname:
if region.endpoint == target.hostname:
break
else:
raise ValueError("Invalid S3 region/host.")
# If region is not found this is a custom region
custom_region = True
bucket = p.path[1:].split('/')[0]
path = '/'.join(p.path[1:].split('/')[1:]) + '/'
bucket = target.path[1:].split('/')[0]
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 path == '/':
@@ -378,7 +463,7 @@ def list_target_files(config):
def backup_set_custom(env, target, target_user, target_pass, min_age):
config = get_backup_config(env, for_save=True)
# min_age must be an int
if isinstance(min_age, str):
min_age = int(min_age)
@@ -396,11 +481,11 @@ def backup_set_custom(env, target, target_user, target_pass, min_age):
list_target_files(config)
except ValueError as e:
return str(e)
write_backup_config(env, config)
return "OK"
def get_backup_config(env, for_save=False, for_ui=False):
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
@@ -435,6 +520,9 @@ def get_backup_config(env, for_save=False, for_ui=False):
if config["target"] == "local":
# Expand to the full URL.
config["target"] = "file://" + config["file_target_directory"]
ssh_pub_key = os.path.join('/root', '.ssh', 'id_rsa_miab.pub')
if os.path.exists(ssh_pub_key):
config["ssh_pub_key"] = open(ssh_pub_key, 'r').read()
return config
@@ -450,10 +538,16 @@ if __name__ == "__main__":
# are readable, and b) report if they are up to date.
run_duplicity_verification()
elif sys.argv[-1] == "--list":
# List the saved backup files.
for fn, size in list_target_files(get_backup_config(load_environment())):
print("{}\t{}".format(fn, size))
elif sys.argv[-1] == "--status":
# Show backup status.
ret = backup_status(load_environment())
print(rtyaml.dump(ret["backups"]))
print("Storage for unmatched files:", ret["unmatched_file_size"])
elif len(sys.argv) >= 2 and sys.argv[1] == "--restore":
# Run duplicity restore. Rest of command line passed as arguments

View File

@@ -1,22 +1,15 @@
#!/usr/bin/python3
import os, os.path, re, json
import os, os.path, re, json, time
import subprocess
from functools import wraps
from flask import Flask, request, render_template, abort, Response, send_from_directory
from flask import Flask, request, render_template, abort, Response, send_from_directory, make_response
import auth, utils
import auth, utils, multiprocessing.pool
from mailconfig import get_mail_users, get_mail_users_ex, get_admins, add_mail_user, set_mail_password, remove_mail_user
from mailconfig import get_mail_user_privileges, add_remove_mail_user_privilege
from mailconfig import get_mail_aliases, get_mail_aliases_ex, get_mail_domains, add_mail_alias, remove_mail_alias
# Create a worker pool for the status checks. The pool should
# live across http requests so we don't baloon the system with
# processes.
import multiprocessing.pool
pool = multiprocessing.pool.Pool(processes=10)
env = utils.load_environment()
auth_service = auth.KeyAuthService()
@@ -49,7 +42,10 @@ def authorized_personnel_only(viewfunc):
except ValueError as e:
# Authentication failed.
privs = []
error = str(e)
error = "Incorrect username or password"
# Write a line in the log recording the failed login
log_failed_login(request)
# Authorized to access an API view?
if "admin" in privs:
@@ -123,9 +119,12 @@ def me():
try:
email, privs = auth_service.authenticate(request, env)
except ValueError as e:
# Log the failed login
log_failed_login(request)
return json_response({
"status": "invalid",
"reason": str(e),
"reason": "Incorrect username or password",
})
resp = {
@@ -147,7 +146,7 @@ def me():
@authorized_personnel_only
def mail_users():
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:
return "".join(x+"\n" for x in get_mail_users(env))
@@ -327,6 +326,37 @@ def dns_get_dump():
# SSL
@app.route('/ssl/status')
@authorized_personnel_only
def ssl_get_status():
from ssl_certificates import get_certificates_to_provision
from web_update import get_web_domains_info, get_web_domains
# What domains can we provision certificates for? What unexpected problems do we have?
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?
domains_status = get_web_domains_info(env)
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.
for domain in set(get_web_domains(env, exclude_dns_elsewhere=False)) - set(get_web_domains(env)):
domains_status.append({
"domain": domain,
"status": "not-applicable",
"text": "The domain's website is hosted elsewhere.",
})
return json_response({
"can_provision": utils.sort_domains(provision, env),
"status": domains_status,
})
@app.route('/ssl/csr/<domain>', methods=['POST'])
@authorized_personnel_only
def ssl_get_csr(domain):
@@ -346,6 +376,14 @@ def ssl_install_cert():
return "Invalid domain name."
return install_cert(domain, ssl_cert, ssl_chain, env)
@app.route('/ssl/provision', methods=['POST'])
@authorized_personnel_only
def ssl_provision_certs():
from ssl_certificates import provision_certificates
requests = provision_certificates(env, limit_domains=None)
return json_response({ "requests": requests })
# WEB
@app.route('/web/domains')
@@ -398,7 +436,10 @@ def system_status():
def print_line(self, message, monospace=False):
self.items[-1]["extra"].append({ "text": message, "monospace": monospace })
output = WebOutput()
# Create a temporary pool of processes for the status checks
pool = multiprocessing.pool.Pool(processes=5)
run_checks(False, env, output, pool)
pool.terminate()
return json_response(output.items)
@app.route('/system/updates')
@@ -418,11 +459,35 @@ def do_updates():
"DEBIAN_FRONTEND": "noninteractive"
})
@app.route('/system/reboot', methods=["GET"])
@authorized_personnel_only
def needs_reboot():
from status_checks import is_reboot_needed_due_to_package_installation
if is_reboot_needed_due_to_package_installation():
return json_response(True)
else:
return json_response(False)
@app.route('/system/reboot', methods=["POST"])
@authorized_personnel_only
def do_reboot():
# To keep the attack surface low, we don't allow a remote reboot if one isn't necessary.
from status_checks import is_reboot_needed_due_to_package_installation
if is_reboot_needed_due_to_package_installation():
return utils.shell("check_output", ["/sbin/shutdown", "-r", "now"], capture_stderr=True)
else:
return "No reboot is required, so it is not allowed."
@app.route('/system/backup/status')
@authorized_personnel_only
def backup_status():
from backup import backup_status
return json_response(backup_status(env))
try:
return json_response(backup_status(env))
except Exception as e:
return json_response({ "error": str(e) })
@app.route('/system/backup/config', methods=["GET"])
@authorized_personnel_only
@@ -466,6 +531,77 @@ def munin(filename=""):
if filename == "": filename = "index.html"
return send_from_directory("/var/cache/munin/www", filename)
@app.route('/munin/cgi-graph/<path:filename>')
@authorized_personnel_only
def munin_cgi(filename):
""" Relay munin cgi dynazoom requests
/usr/lib/munin/cgi/munin-cgi-graph is a perl cgi script in the munin package
that is responsible for generating binary png images _and_ associated HTTP
headers based on parameters in the requesting URL. All output is written
to stdout which munin_cgi splits into response headers and binary response
data.
munin-cgi-graph reads environment variables to determine
what it should do. It expects a path to be in the env-var PATH_INFO, and a
querystring to be in the env-var QUERY_STRING.
munin-cgi-graph has several failure modes. Some write HTTP Status headers and
others return nonzero exit codes.
Situating munin_cgi between the user-agent and munin-cgi-graph enables keeping
the cgi script behind mailinabox's auth mechanisms and avoids additional
support infrastructure like spawn-fcgi.
"""
COMMAND = 'su - munin --preserve-environment --shell=/bin/bash -c /usr/lib/munin/cgi/munin-cgi-graph'
# su changes user, we use the munin user here
# --preserve-environment retains the environment, which is where Popen's `env` data is
# --shell=/bin/bash ensures the shell used is bash
# -c "/usr/lib/munin/cgi/munin-cgi-graph" passes the command to run as munin
# "%s" is a placeholder for where the request's querystring will be added
if filename == "":
return ("a path must be specified", 404)
query_str = request.query_string.decode("utf-8", 'ignore')
env = {'PATH_INFO': '/%s/' % filename, 'REQUEST_METHOD': 'GET', 'QUERY_STRING': query_str}
code, binout = utils.shell('check_output',
COMMAND.split(" ", 5),
# Using a maxsplit of 5 keeps the last arguments together
env=env,
return_bytes=True,
trap=True)
if code != 0:
# nonzero returncode indicates error
app.logger.error("munin_cgi: munin-cgi-graph returned nonzero exit code, %s", code)
return ("error processing graph image", 500)
# /usr/lib/munin/cgi/munin-cgi-graph returns both headers and binary png when successful.
# A double-Windows-style-newline always indicates the end of HTTP headers.
headers, image_bytes = binout.split(b'\r\n\r\n', 1)
response = make_response(image_bytes)
for line in headers.splitlines():
name, value = line.decode("utf8").split(':', 1)
response.headers[name] = value
if 'Status' in response.headers and '404' in response.headers['Status']:
app.logger.warning("munin_cgi: munin-cgi-graph returned 404 status code. PATH_INFO=%s", env['PATH_INFO'])
return response
def log_failed_login(request):
# We need to figure out the ip to list in the message, all our calls are routed
# through nginx who will put the original ip in X-Forwarded-For.
# During setup we call the management interface directly to determine the user
# status. So we can't always use X-Forwarded-For because during setup that header
# will not be present.
if request.headers.getlist("X-Forwarded-For"):
ip = request.headers.getlist("X-Forwarded-For")[0]
else:
ip = request.remote_addr
# We need to add a timestamp to the log message, otherwise /dev/log will eat the "duplicate"
# message.
app.logger.warning( "Mail-in-a-Box Management Daemon: Failed login attempt from ip %s - timestamp %s" % (ip, time.time()))
# APP
if __name__ == '__main__':

View File

@@ -1,8 +1,25 @@
#!/bin/bash
# This script is run daily (at 3am each night).
# Set character encoding flags to ensure that any non-ASCII
# characters don't cause problems. See setup/start.sh and
# the management daemon startup script.
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
# 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.
management/backup.py
management/backup.py | management/email_administrator.py "Backup Status"
# Provision any new certificates for new domains or domains with expiring certificates.
management/ssl_certificates.py -q | management/email_administrator.py "Error Provisioning TLS Certificate"
# Run status checks and email the administrator if anything changed.
management/status_checks.py --show-changes --smtp
management/status_checks.py --show-changes | management/email_administrator.py "Status Checks Change Notice"

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python3
#!/usr/local/lib/mailinabox/env/bin/python
# Creates DNS zone files for all of the domains of all of the mail users
# and mail aliases and restarts nsd.
@@ -12,6 +12,12 @@ import dns.resolver
from mailconfig import get_mail_domains
from utils import shell, load_env_vars_from_file, safe_domain_name, sort_domains
# From https://stackoverflow.com/questions/3026957/how-to-validate-a-domain-name-using-regex-php/16491074#16491074
# This regular expression matches domain names according to RFCs, it also accepts fqdn with an leading dot,
# underscores, as well as asteriks which are allowed in domain names but not hostnames (i.e. allowed in
# DNS but not in URLs), which are common in certain record types like for DKIM.
DOMAIN_RE = "^(?!\-)(?:[*][.])?(?:[a-zA-Z\d\-_]{0,62}[a-zA-Z\d_]\.){1,126}(?!\d+)[a-zA-Z\d_]{1,63}(\.?)$"
def get_dns_domains(env):
# Add all domain names in use by email users and mail aliases and ensure
# PRIMARY_HOSTNAME is in the list.
@@ -91,7 +97,7 @@ def do_dns_update(env, force=False):
shell('check_call', ["/usr/sbin/service", "nsd", "restart"])
# Write the OpenDKIM configuration tables for all of the domains.
if write_opendkim_tables([domain for domain, zonefile in zonefiles], env):
if write_opendkim_tables(get_mail_domains(env), env):
# Settings changed. Kick opendkim.
shell('check_call', ["/usr/sbin/service", "opendkim", "restart"])
if len(updated_domains) == 0:
@@ -144,7 +150,7 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
# Define ns2.PRIMARY_HOSTNAME or whatever the user overrides.
# User may provide one or more additional nameservers
secondary_ns_list = get_secondary_dns(additional_records, mode="NS") \
or ["ns2." + env["PRIMARY_HOSTNAME"]]
or ["ns2." + env["PRIMARY_HOSTNAME"]]
for secondary_ns in secondary_ns_list:
records.append((None, "NS", secondary_ns+'.', False))
@@ -175,9 +181,6 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
for value in build_sshfp_records():
records.append((None, "SSHFP", value, "Optional. Provides an out-of-band method for verifying an SSH key before connecting. Use 'VerifyHostKeyDNS yes' (or 'VerifyHostKeyDNS ask') when connecting with ssh."))
# The MX record says where email for the domain should be delivered: Here!
records.append((None, "MX", "10 %s." % env["PRIMARY_HOSTNAME"], "Required. Specifies the hostname (and priority) of the machine that handles @%s mail." % domain))
# Add DNS records for any subdomains of this domain. We should not have a zone for
# both a domain and one of its subdomains.
subdomains = [d for d in all_domains if d.endswith("." + domain)]
@@ -244,6 +247,10 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
# Don't pin the list of records that has_rec checks against anymore.
has_rec_base = records
# The MX record says where email for the domain should be delivered: Here!
if not has_rec(None, "MX", prefix="10 "):
records.append((None, "MX", "10 %s." % env["PRIMARY_HOSTNAME"], "Required. Specifies the hostname (and priority) of the machine that handles @%s mail." % domain))
# SPF record: Permit the box ('mx', see above) to send mail on behalf of
# the domain, and no one else.
# Skip if the user has set a custom SPF record.
@@ -273,6 +280,22 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
if not has_rec(dmarc_qname, "TXT", prefix="v=DMARC1; "):
records.append((dmarc_qname, "TXT", 'v=DMARC1; p=reject', "Recommended. Prevents use of this domain name for outbound mail by specifying that the SPF rule should be honoured for mail from @%s." % (qname + "." + domain)))
# Add CardDAV/CalDAV SRV records on the non-primary hostname that points to the primary hostname.
# The SRV record format is priority (0, whatever), weight (0, whatever), port, service provider hostname (w/ trailing dot).
if domain != env["PRIMARY_HOSTNAME"]:
for dav in ("card", "cal"):
qname = "_" + dav + "davs._tcp"
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."))
# 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
if not has_rec("autodiscover", "A"):
records.append(("autodiscover", "A", env["PUBLIC_IP"], "Provides email configuration autodiscovery support for Z-Push ActiveSync Autodiscover."))
if not has_rec("autoconfig", "A"):
records.append(("autoconfig", "A", env["PUBLIC_IP"], "Provides email configuration autodiscovery support for Thunderbird Autoconfig."))
# 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 ""))
@@ -334,13 +357,26 @@ def build_sshfp_records():
"ssh-rsa": 1,
"ssh-dss": 2,
"ecdsa-sha2-nistp256": 3,
"ssh-ed25519": 4,
}
# Get our local fingerprints by running ssh-keyscan. The output looks
# like the known_hosts file: hostname, keytype, fingerprint. The order
# of the output is arbitrary, so sort it to prevent spurrious updates
# to the zone file (that trigger bumping the serial number).
keys = shell("check_output", ["ssh-keyscan", "localhost"])
# to the zone file (that trigger bumping the serial number). However,
# if SSH has been configured to listen on a nonstandard port, we must
# specify that port to sshkeyscan.
port = 22
with open('/etc/ssh/sshd_config', 'r') as f:
for line in f:
s = line.rstrip().split()
if len(s) == 2 and s[0] == 'Port':
try:
port = int(s[1])
except ValueError:
pass
break
keys = shell("check_output", ["ssh-keyscan", "-t", "rsa,dsa,ecdsa,ed25519", "-p", str(port), "localhost"])
for key in sorted(keys.split("\n")):
if key.strip() == "" or key[0] == "#": continue
try:
@@ -502,11 +538,13 @@ zone:
def dnssec_choose_algo(domain, env):
if '.' in domain and domain.rsplit('.')[-1] in \
("email", "guide", "fund"):
("email", "guide", "fund", "be", "lv"):
# At GoDaddy, RSASHA256 is the only algorithm supported
# for .email and .guide.
# A variety of algorithms are supported for .fund. This
# is preferred.
# Gandi tells me that .be does not support RSASHA1-NSEC3-SHA1
# Nic.lv does not support RSASHA1-NSEC3-SHA1 for .lv tld's
return "RSASHA256"
# For any domain we were able to sign before, don't change the algorithm
@@ -741,12 +779,25 @@ def set_custom_dns_record(qname, rtype, value, action, env):
# validate rtype
rtype = rtype.upper()
if value is not None and qname != "_secondary_nameserver":
if not re.search(DOMAIN_RE, qname):
raise ValueError("Invalid name.")
if rtype in ("A", "AAAA"):
if value != "local": # "local" is a special flag for us
v = ipaddress.ip_address(value) # raises a ValueError if there's a problem
if rtype == "A" and not isinstance(v, ipaddress.IPv4Address): raise ValueError("That's an IPv6 address.")
if rtype == "AAAA" and not isinstance(v, ipaddress.IPv6Address): raise ValueError("That's an IPv4 address.")
elif rtype in ("CNAME", "TXT", "SRV", "MX"):
elif rtype in ("CNAME", "NS"):
if rtype == "NS" and qname == zone:
raise ValueError("NS records can only be set for subdomains.")
# ensure value has a trailing dot
if not value.endswith("."):
value = value + "."
if not re.search(DOMAIN_RE, value):
raise ValueError("Invalid value.")
elif rtype in ("CNAME", "TXT", "SRV", "MX", "SSHFP", "CAA"):
# anything goes
pass
else:
@@ -861,10 +912,10 @@ def set_secondary_dns(hostnames, env):
return do_dns_update(env)
def get_custom_dns_record(custom_dns, qname, rtype):
def get_custom_dns_records(custom_dns, qname, rtype):
for qname1, rtype1, value in custom_dns:
if qname1 == qname and rtype1 == rtype:
return value
yield value
return None
########################################################################

View File

@@ -0,0 +1,60 @@
#!/usr/local/lib/mailinabox/env/bin/python
# Reads in STDIN. If the stream is not empty, mail it to the system administrator.
import sys
import html
import smtplib
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
# Load system environment info.
env = load_environment()
# Process command line args.
subject = sys.argv[1]
# Administrator's email address.
admin_addr = "administrator@" + env['PRIMARY_HOSTNAME']
# Read in STDIN.
content = sys.stdin.read().strip()
# If there's nothing coming in, just exit.
if content == "":
sys.exit(0)
# create MIME message
msg = MIMEMultipart('alternative')
# In Python 3.6:
#msg = Message()
msg['From'] = "\"%s\" <%s>" % (env['PRIMARY_HOSTNAME'], admin_addr)
msg['To'] = admin_addr
msg['Subject'] = "[%s] %s" % (env['PRIMARY_HOSTNAME'], subject)
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
smtpclient = smtplib.SMTP('127.0.0.1', 25)
smtpclient.ehlo()
smtpclient.sendmail(
admin_addr, # MAIL FROM
admin_addr, # RCPT TO
msg.as_string())
smtpclient.quit()

View File

@@ -1,136 +1,895 @@
#!/usr/bin/python3
#!/usr/local/lib/mailinabox/env/bin/python
import argparse
import datetime
import gzip
import os.path
import re
import shutil
import tempfile
import textwrap
from collections import defaultdict, OrderedDict
from collections import defaultdict
import re, os.path
import dateutil.parser
import time
from dateutil.relativedelta import relativedelta
import mailconfig
import utils
def scan_mail_log(logger, env):
collector = {
"other-services": set(),
"imap-logins": { },
"postgrey": { },
"rejected-mail": { },
"activity-by-hour": { "imap-logins": defaultdict(int), "smtp-sends": defaultdict(int) },
}
collector["real_mail_addresses"] = set(mailconfig.get_mail_users(env)) | set(alias[0] for alias in mailconfig.get_mail_aliases(env))
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',
)
for fn in ('/var/log/mail.log.1', '/var/log/mail.log'):
if not os.path.exists(fn): continue
with open(fn, 'rb') as log:
for line in log:
line = line.decode("utf8", errors='replace')
scan_mail_log_line(line.strip(), collector)
TIME_DELTAS = OrderedDict([
('all', datetime.timedelta(weeks=52)),
('month', datetime.timedelta(weeks=4)),
('2weeks', datetime.timedelta(days=14)),
('week', datetime.timedelta(days=7)),
('2days', datetime.timedelta(days=2)),
('day', datetime.timedelta(days=1)),
('12hours', datetime.timedelta(hours=12)),
('6hours', datetime.timedelta(hours=6)),
('hour', datetime.timedelta(hours=1)),
('30min', datetime.timedelta(minutes=30)),
('10min', datetime.timedelta(minutes=10)),
('5min', datetime.timedelta(minutes=5)),
('min', datetime.timedelta(minutes=1)),
('today', datetime.datetime.now() - datetime.datetime.now().replace(hour=0, minute=0, second=0))
])
if collector["imap-logins"]:
logger.add_heading("Recent IMAP Logins")
logger.print_block("The most recent login from each remote IP adddress is show.")
for k in utils.sort_email_addresses(collector["imap-logins"], env):
for ip, date in sorted(collector["imap-logins"][k].items(), key = lambda kv : kv[1]):
logger.print_line(k + "\t" + str(date) + "\t" + ip)
# Start date > end date!
START_DATE = datetime.datetime.now()
END_DATE = None
if collector["postgrey"]:
logger.add_heading("Greylisted Mail")
logger.print_block("The following mail was greylisted, meaning the emails were temporarily rejected. Legitimate senders will try again within ten minutes.")
logger.print_line("recipient" + "\t" + "received" + "\t" + "sender" + "\t" + "delivered")
for recipient in utils.sort_email_addresses(collector["postgrey"], env):
for (client_address, sender), (first_date, delivered_date) in sorted(collector["postgrey"][recipient].items(), key = lambda kv : kv[1][0]):
logger.print_line(recipient + "\t" + str(first_date) + "\t" + sender + "\t" + (("delivered " + str(delivered_date)) if delivered_date else "no retry yet"))
VERBOSE = False
if collector["rejected-mail"]:
logger.add_heading("Rejected Mail")
logger.print_block("The following incoming mail was rejected.")
for k in utils.sort_email_addresses(collector["rejected-mail"], env):
for date, sender, message in collector["rejected-mail"][k]:
logger.print_line(k + "\t" + str(date) + "\t" + sender + "\t" + message)
# List of strings to filter users with
FILTERS = None
logger.add_heading("Activity by Hour")
for h in range(24):
logger.print_line("%d\t%d\t%d" % (h, collector["activity-by-hour"]["imap-logins"][h], collector["activity-by-hour"]["smtp-sends"][h] ))
# What to show (with defaults)
SCAN_OUT = True # Outgoing email
SCAN_IN = True # Incoming email
SCAN_DOVECOT_LOGIN = True # Dovecot Logins
SCAN_GREY = False # Greylisted email
SCAN_BLOCKED = False # Rejected email
def scan_files(collector):
""" Scan files until they run out or the earliest date is reached """
stop_scan = False
for fn in LOG_FILES:
tmp_file = None
if not os.path.exists(fn):
continue
elif fn[-3:] == '.gz':
tmp_file = tempfile.NamedTemporaryFile()
shutil.copyfileobj(gzip.open(fn), tmp_file)
if VERBOSE:
print("Processing file", fn, "...")
fn = tmp_file.name if tmp_file else fn
for line in reverse_readline(fn):
if scan_mail_log_line(line.strip(), collector) is False:
if stop_scan:
return
stop_scan = True
else:
stop_scan = False
def scan_mail_log(env):
""" Scan the system's mail log files and collect interesting data
This function scans the 2 most recent mail log files in /var/log/.
Args:
env (dict): Dictionary containing MiaB settings
"""
collector = {
"scan_count": 0, # Number of lines scanned
"parse_count": 0, # Number of lines parsed (i.e. that had their contents examined)
"scan_time": time.time(), # The time in seconds the scan took
"sent_mail": OrderedDict(), # Data about email sent by users
"received_mail": OrderedDict(), # Data about email received by users
"logins": OrderedDict(), # Data about login activity
"postgrey": {}, # Data about greylisting of email addresses
"rejected": OrderedDict(), # Emails that were blocked
"known_addresses": None, # Addresses handled by the Miab installation
"other-services": set(),
}
try:
import mailconfig
collector["known_addresses"] = (set(mailconfig.get_mail_users(env)) |
set(alias[0] for alias in mailconfig.get_mail_aliases(env)))
except ImportError:
pass
print("Scanning logs from {:%Y-%m-%d %H:%M:%S} to {:%Y-%m-%d %H:%M:%S}".format(
END_DATE, START_DATE)
)
# Scan the lines in the log files until the date goes out of range
scan_files(collector)
if not collector["scan_count"]:
print("No log lines scanned...")
return
collector["scan_time"] = time.time() - collector["scan_time"]
print("{scan_count} Log lines scanned, {parse_count} lines parsed in {scan_time:.2f} "
"seconds\n".format(**collector))
# Print Sent Mail report
if collector["sent_mail"]:
msg = "Sent email"
print_header(msg)
data = OrderedDict(sorted(collector["sent_mail"].items(), key=email_sort))
print_user_table(
data.keys(),
data=[
("sent", [u["sent_count"] for u in data.values()]),
("hosts", [len(u["hosts"]) for u in data.values()]),
],
sub_data=[
("sending hosts", [u["hosts"] for u in data.values()]),
],
activity=[
("sent", [u["activity-by-hour"] for u in data.values()]),
],
earliest=[u["earliest"] for u in data.values()],
latest=[u["latest"] for u in data.values()],
)
accum = defaultdict(int)
data = collector["sent_mail"].values()
for h in range(24):
accum[h] = sum(d["activity-by-hour"][h] for d in data)
print_time_table(
["sent"],
[accum]
)
# Print Received Mail report
if collector["received_mail"]:
msg = "Received email"
print_header(msg)
data = OrderedDict(sorted(collector["received_mail"].items(), key=email_sort))
print_user_table(
data.keys(),
data=[
("received", [u["received_count"] for u in data.values()]),
],
activity=[
("sent", [u["activity-by-hour"] for u in data.values()]),
],
earliest=[u["earliest"] for u in data.values()],
latest=[u["latest"] for u in data.values()],
)
accum = defaultdict(int)
for h in range(24):
accum[h] = sum(d["activity-by-hour"][h] for d in data.values())
print_time_table(
["received"],
[accum]
)
# Print login report
if collector["logins"]:
msg = "User logins per hour"
print_header(msg)
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(
data.keys(),
data=[
(protocol_name, [
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=[
("Protocol and Source", [[
"{} {}: {} times".format(protocol_name, host, count)
for (protocol_name, host), count
in sorted(u["totals_by_protocol_and_host"].items(), key=lambda kv:-kv[1])
] for u in data.values()])
],
activity=[
(protocol_name, [u["activity-by-hour"][protocol_name] for u in data.values()])
for protocol_name in all_protocols
],
earliest=[u["earliest"] for u in data.values()],
latest=[u["latest"] for u in data.values()],
numstr=lambda n : str(round(n, 1)),
)
accum = { protocol_name: defaultdict(int) for protocol_name in all_protocols }
for h in range(24):
for protocol_name in all_protocols:
accum[protocol_name][h] = sum(d["activity-by-hour"][protocol_name][h] for d in data.values())
print_time_table(
all_protocols,
[accum[protocol_name] for protocol_name in all_protocols]
)
if collector["postgrey"]:
msg = "Greylisted Email {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
print_header(msg.format(END_DATE, START_DATE))
print(textwrap.fill(
"The following mail was greylisted, meaning the emails were temporarily rejected. "
"Legitimate senders must try again after three minutes.",
width=80, initial_indent=" ", subsequent_indent=" "
), end='\n\n')
data = OrderedDict(sorted(collector["postgrey"].items(), key=email_sort))
users = []
received = []
senders = []
sender_clients = []
delivered_dates = []
for recipient in data:
sorted_recipients = sorted(data[recipient].items(), key=lambda kv: kv[1][0] or kv[1][1])
for (client_address, sender), (first_date, delivered_date) in sorted_recipients:
if first_date:
users.append(recipient)
received.append(first_date)
senders.append(sender)
delivered_dates.append(delivered_date)
sender_clients.append(client_address)
print_user_table(
users,
data=[
("received", received),
("sender", senders),
("delivered", [str(d) or "no retry yet" for d in delivered_dates]),
("sending host", sender_clients)
],
delimit=True,
)
if collector["rejected"]:
msg = "Blocked Email {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
print_header(msg.format(END_DATE, START_DATE))
data = OrderedDict(sorted(collector["rejected"].items(), key=email_sort))
rejects = []
if VERBOSE:
for user_data in data.values():
user_rejects = []
for date, sender, message in user_data["blocked"]:
if len(sender) > 64:
sender = sender[:32] + "" + sender[-32:]
user_rejects.append("%s - %s " % (date, sender))
user_rejects.append(" %s" % message)
rejects.append(user_rejects)
print_user_table(
data.keys(),
data=[
("blocked", [len(u["blocked"]) for u in data.values()]),
],
sub_data=[
("blocked emails", rejects),
],
earliest=[u["earliest"] for u in data.values()],
latest=[u["latest"] for u in data.values()],
)
if collector["other-services"] and VERBOSE and False:
print_header("Other services")
print("The following unkown services were found in the log file.")
print(" ", *sorted(list(collector["other-services"])), sep='\n')
if len(collector["other-services"]) > 0:
logger.add_heading("Other")
logger.print_block("Unrecognized services in the log: " + ", ".join(collector["other-services"]))
def scan_mail_log_line(line, collector):
m = re.match(r"(\S+ \d+ \d+:\d+:\d+) (\S+) (\S+?)(\[\d+\])?: (.*)", line)
if not m: return
""" Scan a log line and extract interesting data """
date, system, service, pid, log = m.groups()
date = dateutil.parser.parse(date)
if service == "dovecot":
scan_dovecot_line(date, log, collector)
m = re.match(r"(\w+[\s]+\d+ \d+:\d+:\d+) ([\w]+ )?([\w\-/]+)[^:]*: (.*)", line)
elif service == "postgrey":
scan_postgrey_line(date, log, collector)
if not m:
return True
elif service == "postfix/smtpd":
scan_postfix_smtpd_line(date, log, collector)
date, system, service, log = m.groups()
collector["scan_count"] += 1
elif service == "postfix/submission/smtpd":
scan_postfix_submission_line(date, log, collector)
# print()
# print("date:", date)
# print("host:", system)
# print("service:", service)
# print("log:", log)
elif service in ("postfix/qmgr", "postfix/pickup", "postfix/cleanup",
"postfix/scache", "spampd", "postfix/anvil", "postfix/master",
"opendkim", "postfix/lmtp", "postfix/tlsmgr"):
# nothing to look at
pass
# Replaced the dateutil parser for a less clever way of parser that is roughly 4 times faster.
# date = dateutil.parser.parse(date)
date = datetime.datetime.strptime(date, '%b %d %H:%M:%S')
date = date.replace(START_DATE.year)
else:
collector["other-services"].add(service)
# Check if the found date is within the time span we are scanning
if date > START_DATE:
# Don't process, but continue
return True
elif date < END_DATE:
# Don't process, and halt
return False
if service == "postfix/submission/smtpd":
if SCAN_OUT:
scan_postfix_submission_line(date, log, collector)
elif service == "postfix/lmtp":
if SCAN_IN:
scan_postfix_lmtp_line(date, log, collector)
elif service.endswith("-login"):
if SCAN_DOVECOT_LOGIN:
scan_dovecot_login_line(date, log, collector, service[:4])
elif service == "postgrey":
if SCAN_GREY:
scan_postgrey_line(date, log, collector)
elif service == "postfix/smtpd":
if SCAN_BLOCKED:
scan_postfix_smtpd_line(date, log, collector)
elif service in ("postfix/qmgr", "postfix/pickup", "postfix/cleanup", "postfix/scache",
"spampd", "postfix/anvil", "postfix/master", "opendkim", "postfix/lmtp",
"postfix/tlsmgr", "anvil"):
# nothing to look at
return True
else:
collector["other-services"].add(service)
return True
collector["parse_count"] += 1
return True
def scan_dovecot_line(date, log, collector):
m = re.match("imap-login: Login: user=<(.*?)>, method=PLAIN, rip=(.*?),", log)
if m:
login, ip = m.group(1), m.group(2)
if ip != "127.0.0.1": # local login from webmail/zpush
collector["imap-logins"].setdefault(login, {})[ip] = date
collector["activity-by-hour"]["imap-logins"][date.hour] += 1
def scan_postgrey_line(date, log, collector):
m = re.match("action=(greylist|pass), reason=(.*?), (?:delay=\d+, )?client_name=(.*), client_address=(.*), sender=(.*), recipient=(.*)", log)
if m:
action, reason, client_name, client_address, sender, recipient = m.groups()
key = (client_address, sender)
if action == "greylist" and reason == "new":
collector["postgrey"].setdefault(recipient, {})[key] = (date, None)
elif action == "pass" and reason == "triplet found" and key in collector["postgrey"].get(recipient, {}):
collector["postgrey"][recipient][key] = (collector["postgrey"][recipient][key][0], date)
""" Scan a postgrey log line and extract interesting data """
m = re.match("action=(greylist|pass), reason=(.*?), (?:delay=\d+, )?client_name=(.*), "
"client_address=(.*), sender=(.*), recipient=(.*)",
log)
if m:
action, reason, client_name, client_address, sender, user = m.groups()
if user_match(user):
# Might be useful to group services that use a lot of mail different servers on sub
# domains like <sub>1.domein.com
# if '.' in client_name:
# addr = client_name.split('.')
# if len(addr) > 2:
# client_name = '.'.join(addr[1:])
key = (client_address if client_name == 'unknown' else client_name, sender)
rep = collector["postgrey"].setdefault(user, {})
if action == "greylist" and reason == "new":
rep[key] = (date, rep[key][1] if key in rep else None)
elif action == "pass":
rep[key] = (rep[key][0] if key in rep else None, date)
def scan_postfix_smtpd_line(date, log, collector):
m = re.match("NOQUEUE: reject: RCPT from .*?: (.*?); from=<(.*?)> to=<(.*?)>", log)
if m:
message, sender, recipient = m.groups()
if recipient in collector["real_mail_addresses"]:
# only log mail to real recipients
""" Scan a postfix smtpd log line and extract interesting data """
# skip this, is reported in the greylisting report
if "Recipient address rejected: Greylisted" in message:
return
# Check if the incoming mail was rejected
# simplify this one
m = re.search(r"Client host \[(.*?)\] blocked using zen.spamhaus.org; (.*)", message)
if m:
message = "ip blocked: " + m.group(2)
m = re.match("NOQUEUE: reject: RCPT from .*?: (.*?); from=<(.*?)> to=<(.*?)>", log)
# simplify this one too
m = re.search(r"Sender address \[.*@(.*)\] blocked using dbl.spamhaus.org; (.*)", message)
if m:
message = "domain blocked: " + m.group(2)
if m:
message, sender, user = m.groups()
# skip this, if reported in the greylisting report
if "Recipient address rejected: Greylisted" in message:
return
# only log mail to known recipients
if user_match(user):
if collector["known_addresses"] is None or user in collector["known_addresses"]:
data = collector["rejected"].get(
user,
{
"blocked": [],
"earliest": None,
"latest": None,
}
)
# simplify this one
m = re.search(
r"Client host \[(.*?)\] blocked using zen.spamhaus.org; (.*)", message
)
if m:
message = "ip blocked: " + m.group(2)
else:
# simplify this one too
m = re.search(
r"Sender address \[.*@(.*)\] blocked using dbl.spamhaus.org; (.*)", message
)
if m:
message = "domain blocked: " + m.group(2)
if data["latest"] is None:
data["latest"] = date
data["earliest"] = date
data["blocked"].append((date, sender, message))
collector["rejected"][user] = data
def scan_dovecot_login_line(date, log, collector, protocol_name):
""" Scan a dovecot login log line and extract interesting data """
m = re.match("Info: Login: user=<(.*?)>, method=PLAIN, rip=(.*?),", log)
if m:
# TODO: CHECK DIT
user, host = m.groups()
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
data = collector["logins"].get(
user,
{
"earliest": None,
"latest": None,
"totals_by_protocol": defaultdict(int),
"totals_by_protocol_and_host": defaultdict(int),
"activity-by-hour": defaultdict(lambda : defaultdict(int)),
}
)
if data["latest"] is None:
data["latest"] = date
data["earliest"] = date
data["totals_by_protocol"][protocol_name] += 1
data["totals_by_protocol_and_host"][(protocol_name, host)] += 1
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):
""" Scan a postfix lmtp log line and extract interesting data
It is assumed that every log of postfix/lmtp indicates an email that was successfully
received by Postfix.
"""
m = re.match("([A-Z0-9]+): to=<(\S+)>, .* Saved", log)
if m:
_, user = m.groups()
if user_match(user):
# Get the user data, or create it if the user is new
data = collector["received_mail"].get(
user,
{
"received_count": 0,
"earliest": None,
"latest": None,
"activity-by-hour": defaultdict(int),
}
)
data["received_count"] += 1
data["activity-by-hour"][date.hour] += 1
if data["latest"] is None:
data["latest"] = date
data["earliest"] = date
collector["received_mail"][user] = data
collector["rejected-mail"].setdefault(recipient, []).append( (date, sender, message) )
def scan_postfix_submission_line(date, log, collector):
m = re.match("([A-Z0-9]+): client=(\S+), sasl_method=PLAIN, sasl_username=(\S+)", log)
if m:
procid, client, user = m.groups()
collector["activity-by-hour"]["smtp-sends"][date.hour] += 1
""" Scan a postfix submission log line and extract interesting data
Lines containing a sasl_method with the values PLAIN or LOGIN are assumed to indicate a sent
email.
"""
# Match both the 'plain' and 'login' sasl methods, since both authentication methods are
# allowed by Dovecot
m = re.match("([A-Z0-9]+): client=(\S+), sasl_method=(PLAIN|LOGIN), sasl_username=(\S+)", log)
if m:
_, client, method, user = m.groups()
if user_match(user):
# Get the user data, or create it if the user is new
data = collector["sent_mail"].get(
user,
{
"sent_count": 0,
"hosts": set(),
"earliest": None,
"latest": None,
"activity-by-hour": defaultdict(int),
}
)
data["sent_count"] += 1
data["hosts"].add(client)
data["activity-by-hour"][date.hour] += 1
if data["latest"] is None:
data["latest"] = date
data["earliest"] = date
collector["sent_mail"][user] = data
# Also log this as a login.
add_login(user, date, "smtp", client, collector)
# Utility functions
def reverse_readline(filename, buf_size=8192):
""" A generator that returns the lines of a file in reverse order
http://stackoverflow.com/a/23646049/801870
"""
with open(filename) as fh:
segment = None
offset = 0
fh.seek(0, os.SEEK_END)
file_size = remaining_size = fh.tell()
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):
""" Check if the given user matches any of the filters """
return FILTERS is None or any(u in user for u in FILTERS)
def email_sort(email):
""" Split the given email address into a reverse order tuple, for sorting i.e (domain, name) """
return tuple(reversed(email[0].split('@')))
def valid_date(string):
""" Validate the given date string fetched from the --startdate argument """
try:
date = dateutil.parser.parse(string)
except ValueError:
raise argparse.ArgumentTypeError("Unrecognized date and/or time '%s'" % string)
return date
# Print functions
def print_time_table(labels, data, do_print=True):
labels.insert(0, "hour")
data.insert(0, [str(h) for h in range(24)])
temp = "{:<%d} " % max(len(l) for l in labels)
lines = []
for label in labels:
lines.append(temp.format(label))
for h in range(24):
max_len = max(len(str(d[h])) for d in data)
base = "{:>%d} " % max(2, max_len)
for i, d in enumerate(data):
lines[i] += base.format(d[h])
lines.insert(0, "┬ totals by time of day:")
lines.append("" + (len(lines[-1]) - 2) * "")
if do_print:
print("\n".join(lines))
else:
return lines
def print_user_table(users, data=None, sub_data=None, activity=None, latest=None, earliest=None,
delimit=False, numstr=str):
str_temp = "{:<32} "
lines = []
data = data or []
col_widths = len(data) * [0]
col_left = len(data) * [False]
vert_pos = 0
do_accum = all(isinstance(n, (int, float)) for _, d in data for n in d)
data_accum = len(data) * ([0] if do_accum else [" "])
last_user = None
for row, user in enumerate(users):
if delimit:
if last_user and last_user != user:
lines.append(len(lines[-1]) * "")
last_user = user
line = "{:<32} ".format(user[:31] + "" if len(user) > 32 else user)
for col, (l, d) in enumerate(data):
if isinstance(d[row], str):
col_str = str_temp.format(d[row][:31] + "" if len(d[row]) > 32 else d[row])
col_left[col] = True
elif isinstance(d[row], datetime.datetime):
col_str = "{:<20}".format(str(d[row]))
col_left[col] = True
else:
temp = "{:>%s}" % max(5, len(l) + 1, len(str(d[row])) + 1)
col_str = temp.format(str(d[row]))
col_widths[col] = max(col_widths[col], len(col_str))
line += col_str
if do_accum:
data_accum[col] += d[row]
try:
if None not in [latest, earliest]:
vert_pos = len(line)
e = earliest[row]
l = latest[row]
timespan = relativedelta(l, e)
if timespan.months:
temp = "{:0.1f} months"
line += temp.format(timespan.months + timespan.days / 30.0)
elif timespan.days:
temp = "{:0.1f} days"
line += temp.format(timespan.days + timespan.hours / 24.0)
elif (e.hour, e.minute) == (l.hour, l.minute):
temp = "{:%H:%M}"
line += temp.format(e)
else:
temp = "{:%H:%M} - {:%H:%M}"
line += temp.format(e, l)
except KeyError:
pass
lines.append(line.rstrip())
try:
if VERBOSE:
if sub_data is not None:
for l, d in sub_data:
if d[row]:
lines.append("")
lines.append("%s" % l)
lines.append("├─%s" % (len(l) * ""))
lines.append("")
max_len = 0
for v in list(d[row]):
lines.append("%s" % v)
max_len = max(max_len, len(v))
lines.append("" + (max_len + 1) * "")
if activity is not None:
lines.extend(print_time_table(
[label for label, _ in activity],
[data[row] for _, data in activity],
do_print=False
))
except KeyError:
pass
header = str_temp.format("")
for col, (l, _) in enumerate(data):
if col_left[col]:
header += l.ljust(max(5, len(l) + 1, col_widths[col]))
else:
header += l.rjust(max(5, len(l) + 1, col_widths[col]))
if None not in (latest, earliest):
header += " │ timespan "
lines.insert(0, header.rstrip())
table_width = max(len(l) for l in lines)
t_line = table_width * ""
b_line = table_width * ""
if vert_pos:
t_line = t_line[:vert_pos + 1] + "" + t_line[vert_pos + 2:]
b_line = b_line[:vert_pos + 1] + ("" if VERBOSE else "") + b_line[vert_pos + 2:]
lines.insert(1, t_line)
lines.append(b_line)
# Print totals
data_accum = [numstr(a) for a in data_accum]
footer = str_temp.format("Totals:" if do_accum else " ")
for row, (l, _) in enumerate(data):
temp = "{:>%d}" % max(5, len(l) + 1)
footer += temp.format(data_accum[row])
try:
if None not in [latest, earliest]:
max_l = max(latest)
min_e = min(earliest)
timespan = relativedelta(max_l, min_e)
if timespan.days:
temp = "{:0.2f} days"
footer += temp.format(timespan.days + timespan.hours / 24.0)
elif (min_e.hour, min_e.minute) == (max_l.hour, max_l.minute):
temp = "{:%H:%M}"
footer += temp.format(min_e)
else:
temp = "{:%H:%M} - {:%H:%M}"
footer += temp.format(min_e, max_l)
except KeyError:
pass
lines.append(footer)
print("\n".join(lines))
def print_header(msg):
print('\n' + msg)
print("" * len(msg), '\n')
if __name__ == "__main__":
from status_checks import ConsoleOutput
env = utils.load_environment()
scan_mail_log(ConsoleOutput(), env)
try:
env_vars = utils.load_environment()
except FileNotFoundError:
env_vars = {}
parser = argparse.ArgumentParser(
description="Scan the mail log files for interesting data. By default, this script "
"shows today's incoming and outgoing mail statistics. This script was ("
"re)written for the Mail-in-a-box email server."
"https://github.com/mail-in-a-box/mailinabox",
add_help=False
)
# Switches to determine what to parse and what to ignore
parser.add_argument("-r", "--received", help="Scan for received emails.",
action="store_true")
parser.add_argument("-s", "--sent", help="Scan for sent emails.",
action="store_true")
parser.add_argument("-l", "--logins", help="Scan for user logins to IMAP/POP3.",
action="store_true")
parser.add_argument("-g", "--grey", help="Scan for greylisted emails.",
action="store_true")
parser.add_argument("-b", "--blocked", help="Scan for blocked emails.",
action="store_true")
parser.add_argument("-t", "--timespan", choices=TIME_DELTAS.keys(), default='today',
metavar='<time span>',
help="Time span to scan, going back from the start date. Possible values: "
"{}. Defaults to 'today'.".format(", ".join(list(TIME_DELTAS.keys()))))
parser.add_argument("-d", "--startdate", action="store", dest="startdate",
type=valid_date, metavar='<start date>',
help="Date and time to start scanning the log file from. If no date is "
"provided, scanning will start from the current date and time.")
parser.add_argument("-u", "--users", action="store", dest="users",
metavar='<email1,email2,email...>',
help="Comma separated list of (partial) email addresses to filter the "
"output with.")
parser.add_argument('-h', '--help', action='help', help="Print this message and exit.")
parser.add_argument("-v", "--verbose", help="Output extra data where available.",
action="store_true")
args = parser.parse_args()
if args.startdate is not None:
START_DATE = args.startdate
if args.timespan == 'today':
args.timespan = 'day'
print("Setting start date to {}".format(START_DATE))
END_DATE = START_DATE - TIME_DELTAS[args.timespan]
VERBOSE = args.verbose
if args.received or args.sent or args.logins or args.grey or args.blocked:
SCAN_IN = args.received
if not SCAN_IN:
print("Ignoring received emails")
SCAN_OUT = args.sent
if not SCAN_OUT:
print("Ignoring sent emails")
SCAN_DOVECOT_LOGIN = args.logins
if not SCAN_DOVECOT_LOGIN:
print("Ignoring logins")
SCAN_GREY = args.grey
if SCAN_GREY:
print("Showing greylisted emails")
SCAN_BLOCKED = args.blocked
if SCAN_BLOCKED:
print("Showing blocked emails")
if args.users is not None:
FILTERS = args.users.strip().split(',')
scan_mail_log(env_vars)

View File

@@ -1,4 +1,13 @@
#!/usr/bin/python3
#!/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 utils
@@ -96,7 +105,7 @@ def get_mail_users(env):
users = [ row[0] for row in c.fetchall() ]
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
# including archived (status="inactive") accounts.
#
@@ -130,26 +139,22 @@ def get_mail_users_ex(env, with_archived=False, with_slow_info=False):
}
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.
if with_archived:
root = os.path.join(env['STORAGE_ROOT'], 'mail/mailboxes')
for domain in os.listdir(root):
for user in os.listdir(os.path.join(root, domain)):
email = user + "@" + domain
mbox = os.path.join(root, domain, user)
if email in active_accounts: continue
user = {
"email": email,
"privileges": "",
"status": "inactive",
"mailbox": mbox,
}
users.append(user)
if with_slow_info:
user["mailbox_size"] = utils.du(mbox)
if os.path.isdir(os.path.join(root, domain)):
for user in os.listdir(os.path.join(root, domain)):
email = user + "@" + domain
mbox = os.path.join(root, domain, user)
if email in active_accounts: continue
user = {
"email": email,
"privileges": [],
"status": "inactive",
"mailbox": mbox,
}
users.append(user)
# Group by domain.
domains = { }
@@ -434,9 +439,11 @@ def add_mail_alias(address, forwards_to, permitted_senders, env, update_if_exist
email = email.strip()
if email == "": continue
email = sanitize_idn_email_address(email) # Unicode => IDNA
# Strip any +tag from email alias and check privileges
privileged_email = re.sub(r"(?=\+)[^@]*(?=@)",'',email)
if not validate_email(email):
return ("Invalid receiver email address (%s)." % email, 400)
if is_dcv_source and not is_dcv_address(email) and "admin" not in get_mail_user_privileges(email, env, empty_on_error=True):
if is_dcv_source and not is_dcv_address(email) and "admin" not in get_mail_user_privileges(privileged_email, env, empty_on_error=True):
# Make domain control validation hijacking a little harder to mess up by
# requiring aliases for email addresses typically used in DCV to forward
# only to accounts that are administrators on this system.
@@ -598,8 +605,8 @@ def validate_password(pw):
raise ValueError("No password provided.")
if re.search(r"[\s]", pw):
raise ValueError("Passwords cannot contain spaces.")
if len(pw) < 4:
raise ValueError("Passwords must be at least four characters.")
if len(pw) < 8:
raise ValueError("Passwords must be at least eight characters.")
if __name__ == "__main__":

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

369
management/ssl_certificates.py Normal file → Executable file
View File

@@ -1,8 +1,12 @@
#!/usr/local/lib/mailinabox/env/bin/python
# 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
from utils import shell, safe_domain_name, sort_domains
import idna
# SELECTING SSL CERTIFICATES FOR USE IN WEB
def get_ssl_certificates(env):
# Scan all of the installed SSL certificates and map every domain
@@ -17,7 +21,19 @@ def get_ssl_certificates(env):
# List all of the files in the SSL directory and one level deep.
def get_file_list():
if not os.path.exists(ssl_root):
return
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)
if os.path.isfile(fn):
yield fn
@@ -68,6 +84,12 @@ def get_ssl_certificates(env):
# Add this cert to the list of certs usable for the 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)
# Sort the certificates to prefer good ones.
@@ -75,6 +97,7 @@ def get_ssl_certificates(env):
now = datetime.datetime.utcnow()
ret = { }
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 : (
# must be valid NOW
cert.not_valid_before <= now <= cert.not_valid_after,
@@ -82,10 +105,27 @@ def get_ssl_certificates(env):
# prefer one that is not self-signed
cert.issuer != cert.subject,
###########################################################
# The above lines ensure that valid certificates are chosen
# over invalid certificates. The lines below choose between
# multiple valid certificates available for this domain.
###########################################################
# prefer one with the expiration furthest into the future so
# that we can easily rotate to new certs as we get them
cert.not_valid_after,
###########################################################
# We always choose the certificate that is good for the
# longest period of time. This is important for how we
# provision certificates for Let's Encrypt. To ensure that
# we don't re-provision every night, we have to ensure that
# if we choose to provison a certificate that it will
# *actually* be used so the provisioning logic knows it
# doesn't still need to provision a certificate for the
# domain.
###########################################################
# in case a certificate is installed in multiple paths,
# prefer the... lexicographically last one?
cert._filename,
@@ -96,55 +136,264 @@ def get_ssl_certificates(env):
"private-key": cert._private_key._filename,
"certificate": cert._filename,
"primary-domain": cert._primary_domain,
"certificate_object": cert,
}
return ret
def get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=False):
# Get the default paths.
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'))
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.
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'))
system_certificate = {
"private-key": ssl_private_key,
"certificate": ssl_certificate,
"primary-domain": env['PRIMARY_HOSTNAME'],
"certificate_object": load_pem(load_cert_chain(ssl_certificate)[0]),
}
if domain == env['PRIMARY_HOSTNAME']:
# The primary domain must use the server certificate because
# it is hard-coded in some service configuration files.
return ssl_private_key, ssl_certificate, None
if use_main_cert:
if domain == env['PRIMARY_HOSTNAME']:
# The primary domain must use the server certificate because
# it is hard-coded in some service configuration files.
return system_certificate
wildcard_domain = re.sub("^[^\.]+", "*", domain)
if domain in ssl_certificates:
cert_info = ssl_certificates[domain]
cert_type = "multi-domain"
return ssl_certificates[domain]
elif wildcard_domain in ssl_certificates:
cert_info = ssl_certificates[wildcard_domain]
cert_type = "wildcard"
return ssl_certificates[wildcard_domain]
elif not allow_missing_cert:
# No certificate is available for this domain! Return default files.
ssl_via = "Using certificate for %s." % env['PRIMARY_HOSTNAME']
return ssl_private_key, ssl_certificate, ssl_via
# No valid certificate is available for this domain! Return default files.
return system_certificate
else:
# No certificate is available - and warn appropriately.
# No valid certificate is available for this domain.
return None
# 'via' is a hint to the user about which certificate is in use for the domain
if cert_info['certificate'] == os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem'):
# Using the server certificate.
via = "Using same %s certificate as for %s." % (cert_type, env['PRIMARY_HOSTNAME'])
elif cert_info['primary-domain'] != domain and cert_info['primary-domain'] in ssl_certificates and cert_info == ssl_certificates[cert_info['primary-domain']]:
via = "Using same %s certificate as for %s." % (cert_type, cert_info['primary-domain'])
else:
via = None # don't show a hint - show expiration info instead
return cert_info['private-key'], cert_info['certificate'], via
# PROVISIONING CERTIFICATES FROM LETSENCRYPT
def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True):
# Get a set of domain names that we can provision certificates for
# using certbot. We start with domains that the box is serving web
# for and subtract:
# * domains not in limit_domains if limit_domains is not empty
# * domains with custom "A" records, i.e. they are hosted elsewhere
# * 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 status_checks import query_dns, normalize_ip
existing_certs = get_ssl_certificates(env)
plausible_web_domains = get_web_domains(env, exclude_dns_elsewhere=False)
actual_web_domains = get_web_domains(env)
domains_to_provision = set()
domains_cant_provision = { }
for domain in plausible_web_domains:
# Skip domains that the user doesn't want to provision now.
if limit_domains and domain not in limit_domains:
continue
# Check that there isn't an explicit A/AAAA record.
if domain not in actual_web_domains:
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)."
# Check that the DNS resolves to here.
else:
# Does the domain resolve to this machine in public DNS? If not,
# we can't do domain control validation. For IPv6 is configured,
# make sure both IPv4 and IPv6 are correct because we don't know
# how Let's Encrypt will connect.
bad_dns = []
for rtype, value in [("A", env["PUBLIC_IP"]), ("AAAA", env.get("PUBLIC_IPV6"))]:
if not value: continue # IPv6 is not configured
response = query_dns(domain, rtype)
if response != normalize_ip(value):
bad_dns.append("%s (%s)" % (response, rtype))
if bad_dns:
domains_cant_provision[domain] = "The domain name does not resolve to this machine: " \
+ (", ".join(bad_dns)) \
+ "."
else:
# DNS is all good.
# 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_to_provision.add(domain)
return (domains_to_provision, domains_cant_provision)
def provision_certificates(env, limit_domains):
# What domains should we provision certificates for? And what
# errors prevent provisioning for other domains.
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",
})
# 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.
max_domains_per_group = 100
domains = sort_domains(domains, env)
certs = []
while len(domains) > 0:
certs.append( domains[:max_domains_per_group] )
domains = domains[max_domains_per_group:]
# Prepare to provision.
# Where should we put our Let's Encrypt account info and state cache.
account_path = os.path.join(env['STORAGE_ROOT'], 'ssl/lets_encrypt')
if not os.path.exists(account_path):
os.mkdir(account_path)
# Provision certificates.
for domain_list in certs:
ret.append({
"domains": domain_list,
"log": [],
})
try:
# Create a CSR file for our master private key so that certbot
# uses our private key.
key_file = os.path.join(env['STORAGE_ROOT'], 'ssl', 'ssl_private_key.pem')
with tempfile.NamedTemporaryFile() as csr_file:
# We could use openssl, but certbot requires
# that the CN domain and SAN domains match
# 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))
# Provision, writing to a temporary file.
webroot = os.path.join(account_path, 'webroot')
os.makedirs(webroot, exist_ok=True)
with tempfile.TemporaryDirectory() as d:
cert_file = os.path.join(d, 'cert_and_chain.pem')
print("Provisioning TLS certificates for " + ", ".join(domain_list) + ".")
certbotret = subprocess.check_output([
"certbot",
"certonly",
#"-v", # just enough to see ACME errors
"--non-interactive", # will fail if user hasn't registered during Mail-in-a-Box setup
"-d", ",".join(domain_list), # first will be main domain
"--csr", csr_file.name, # use our private key; unfortunately this doesn't work with auto-renew so we need to save cert manually
"--cert-path", os.path.join(d, 'cert'), # we only use the full chain
"--chain-path", os.path.join(d, 'chain'), # we only use the full chain
"--fullchain-path", cert_file,
"--webroot", "--webroot-path", webroot,
"--config-dir", account_path,
#"--staging",
], stderr=subprocess.STDOUT).decode("utf8")
install_cert_copy_file(cert_file, env)
ret[-1]["log"].append(certbotret)
ret[-1]["result"] = "installed"
except subprocess.CalledProcessError as e:
ret[-1]["log"].append(e.output.decode("utf8"))
ret[-1]["result"] = "error"
except Exception as e:
ret[-1]["log"].append(str(e))
ret[-1]["result"] = "error"
# Run post-install steps.
ret.extend(post_install_func(env))
# Return what happened with each certificate request.
return ret
def provision_certificates_cmdline():
import sys
from exclusiveprocess import Lock
from utils import load_environment
Lock(die=True).forever()
env = load_environment()
quiet = False
domains = []
for arg in sys.argv[1:]:
if arg == "-q":
quiet = True
else:
domains.append(arg)
# Go.
status = provision_certificates(env, limit_domains=domains)
# Show what happened.
for request in status:
if isinstance(request, str):
print(request)
else:
if quiet and request['result'] == 'skipped':
continue
print(request['result'] + ":", ", ".join(request['domains']) + ":")
for line in request["log"]:
print(line)
print()
# INSTALLING A NEW CERTIFICATE FROM THE CONTROL PANEL
def create_csr(domain, ssl_key, country_code, env):
return shell("check_output", [
"openssl", "req", "-new",
"-key", ssl_key,
"-sha256",
"-subj", "/C=%s/ST=/L=/O=/CN=%s" % (country_code, domain)])
"openssl", "req", "-new",
"-key", ssl_key,
"-sha256",
"-subj", "/C=%s/CN=%s" % (country_code, domain)])
def install_cert(domain, ssl_cert, ssl_chain, env):
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.
# The certificate always goes above the chain.
import tempfile
@@ -163,6 +412,16 @@ def install_cert(domain, ssl_cert, ssl_chain, env):
cert_status += " " + cert_status_details
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?
# Make a unique path for the certificate.
from cryptography.hazmat.primitives import hashes
@@ -180,14 +439,26 @@ def install_cert(domain, ssl_cert, ssl_chain, env):
os.makedirs(os.path.dirname(ssl_certificate), exist_ok=True)
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
# restart postfix and dovecot.
if domain == env['PRIMARY_HOSTNAME']:
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.
system_ssl_certificate = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem'))
ret.append("updating primary certificate")
ssl_certificate = cert['certificate']
os.unlink(system_ssl_certificate)
os.symlink(ssl_certificate, system_ssl_certificate)
@@ -203,10 +474,12 @@ def install_cert(domain, ssl_cert, ssl_chain, env):
# Update the web configuration so nginx picks up the new certificate file.
from web_update import do_web_update
ret.append( do_web_update(env) )
return "\n".join(ret)
return ret
def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring_soon=True, rounded_time=False, just_check_domain=False):
# VALIDATION OF CERTIFICATES
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
# for the provided domain.
@@ -296,7 +569,7 @@ def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring
return ("The certificate is missing an intermediate chain or the intermediate chain is incorrect or incomplete. (%s)" % verifyoutput, None)
# There is some unknown problem. Return the `openssl verify` raw output.
return ("There is a problem with the SSL certificate.", verifyoutput.strip())
return ("There is a problem with the certificate.", verifyoutput.strip())
else:
# `openssl verify` returned a zero exit status so the cert is currently
@@ -305,16 +578,16 @@ def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring
# But is it expiring soon?
cert_expiration_date = cert.not_valid_after
ndays = (cert_expiration_date-now).days
if not rounded_time or ndays < 7:
if not rounded_time or ndays <= 10:
# Yikes better renew soon!
expiry_info = "The certificate expires in %d days on %s." % (ndays, cert_expiration_date.strftime("%x"))
elif ndays <= 14:
expiry_info = "The certificate expires in less than two weeks, on %s." % cert_expiration_date.strftime("%x")
elif ndays <= 31:
expiry_info = "The certificate expires in less than a month, on %s." % cert_expiration_date.strftime("%x")
else:
# We'll renew it with Lets Encrypt.
expiry_info = "The certificate expires on %s." % cert_expiration_date.strftime("%x")
if ndays <= 31 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
# certificate, which occurs on day 14.
return ("The certificate is expiring soon: " + expiry_info, None)
# Return the special OK code.
@@ -381,3 +654,7 @@ def get_certificate_domains(cert):
pass
return names, cn
if __name__ == "__main__":
# Provision certificates.
provision_certificates_cmdline()

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python3
#!/usr/local/lib/mailinabox/env/bin/python
#
# Checks that the upstream DNS has been set correctly and that
# SSL certificates have been signed, etc., and if not tells the user
# TLS certificates have been signed, etc., and if not tells the user
# what to do next.
import sys, os, os.path, re, subprocess, datetime, multiprocessing.pool
@@ -9,14 +9,37 @@ import sys, os, os.path, re, subprocess, datetime, multiprocessing.pool
import dns.reversename, dns.resolver
import dateutil.parser, dateutil.tz
import idna
import psutil
from dns_update import get_dns_zones, build_tlsa_record, get_custom_dns_config, get_secondary_dns, get_custom_dns_record
from dns_update import get_dns_zones, build_tlsa_record, get_custom_dns_config, get_secondary_dns, get_custom_dns_records
from web_update import get_web_domains, get_domains_with_a_records
from ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate
from mailconfig import get_mail_domains, get_mail_aliases
from utils import shell, sort_domains, load_env_vars_from_file, load_settings
def get_services():
return [
{ "name": "Local DNS (bind9)", "port": 53, "public": False, },
#{ "name": "NSD Control", "port": 8952, "public": False, },
{ "name": "Local DNS Control (bind9/rndc)", "port": 953, "public": False, },
{ "name": "Dovecot LMTP LDA", "port": 10026, "public": False, },
{ "name": "Postgrey", "port": 10023, "public": False, },
{ "name": "Spamassassin", "port": 10025, "public": False, },
{ "name": "OpenDKIM", "port": 8891, "public": False, },
{ "name": "OpenDMARC", "port": 8893, "public": False, },
{ "name": "Mail-in-a-Box Management Daemon", "port": 10222, "public": False, },
{ "name": "SSH Login (ssh)", "port": get_ssh_port(), "public": True, },
{ "name": "Public DNS (nsd4)", "port": 53, "public": True, },
{ "name": "Incoming Mail (SMTP/postfix)", "port": 25, "public": True, },
{ "name": "Outgoing Mail (SMTP 587/postfix)", "port": 587, "public": True, },
#{ "name": "Postfix/master", "port": 10587, "public": True, },
{ "name": "IMAPS (dovecot)", "port": 993, "public": True, },
{ "name": "Mail Filters (Sieve/dovecot)", "port": 4190, "public": True, },
{ "name": "HTTP Web (nginx)", "port": 80, "public": True, },
{ "name": "HTTPS Web (nginx)", "port": 443, "public": True, },
]
def run_checks(rounded_values, env, output, pool):
# run systems checks
output.add_heading("System")
@@ -60,33 +83,9 @@ def get_ssh_port():
def run_services_checks(env, output, pool):
# Check that system services are running.
services = [
{ "name": "Local DNS (bind9)", "port": 53, "public": False, },
#{ "name": "NSD Control", "port": 8952, "public": False, },
{ "name": "Local DNS Control (bind9/rndc)", "port": 953, "public": False, },
{ "name": "Dovecot LMTP LDA", "port": 10026, "public": False, },
{ "name": "Postgrey", "port": 10023, "public": False, },
{ "name": "Spamassassin", "port": 10025, "public": False, },
{ "name": "OpenDKIM", "port": 8891, "public": False, },
{ "name": "OpenDMARC", "port": 8893, "public": False, },
{ "name": "Memcached", "port": 11211, "public": False, },
{ "name": "Sieve (dovecot)", "port": 4190, "public": False, },
{ "name": "Mail-in-a-Box Management Daemon", "port": 10222, "public": False, },
{ "name": "SSH Login (ssh)", "port": get_ssh_port(), "public": True, },
{ "name": "Public DNS (nsd4)", "port": 53, "public": True, },
{ "name": "Incoming Mail (SMTP/postfix)", "port": 25, "public": True, },
{ "name": "Outgoing Mail (SMTP 587/postfix)", "port": 587, "public": True, },
#{ "name": "Postfix/master", "port": 10587, "public": True, },
{ "name": "IMAPS (dovecot)", "port": 993, "public": True, },
{ "name": "HTTP Web (nginx)", "port": 80, "public": True, },
{ "name": "HTTPS Web (nginx)", "port": 443, "public": True, },
]
all_running = True
fatal = False
ret = pool.starmap(check_service, ((i, service, env) for i, service in enumerate(services)), chunksize=1)
ret = pool.starmap(check_service, ((i, service, env) for i, service in enumerate(get_services())), chunksize=1)
for i, running, fatal2, output2 in sorted(ret):
if output2 is None: continue # skip check (e.g. no port was set, e.g. no sshd)
all_running = all_running and running
@@ -166,6 +165,38 @@ def run_system_checks(rounded_values, env, output):
check_miab_version(env, output)
check_system_aliases(env, output)
check_free_disk_space(rounded_values, env, output)
check_free_memory(rounded_values, env, output)
def check_ufw(env, output):
if not os.path.isfile('/usr/sbin/ufw'):
output.print_warning("""The ufw program was not installed. If your system is able to run iptables, rerun the setup.""")
return
code, ufw = shell('check_output', ['ufw', 'status'], trap=True)
if code != 0:
# The command failed, it's safe to say the firewall is disabled
output.print_warning("""The firewall is not working on this machine. An error was received
while trying to check the firewall. To investigate run 'sudo ufw status'.""")
return
ufw = ufw.splitlines()
if ufw[0] == "Status: active":
not_allowed_ports = 0
for service in get_services():
if service["public"] and not is_port_allowed(ufw, service["port"]):
not_allowed_ports += 1
output.print_error("Port %s (%s) should be allowed in the firewall, please re-run the setup." % (service["port"], service["name"]))
if not_allowed_ports == 0:
output.print_ok("Firewall is active.")
else:
output.print_warning("""The firewall is disabled on this machine. This might be because the system
is protected by an external firewall. We can't protect the system against bruteforce attacks
without the local firewall active. Connect to the system via ssh and try to run: ufw enable.""")
def is_port_allowed(ufw, port):
return any(re.match(str(port) +"[/ \t].*", item) for item in ufw)
def check_ssh_password(env, output):
# Check that SSH login with password is disabled. The openssh-server
@@ -183,10 +214,13 @@ def check_ssh_password(env, output):
else:
output.print_ok("SSH disallows password-based login.")
def is_reboot_needed_due_to_package_installation():
return os.path.exists("/var/run/reboot-required")
def check_software_updates(env, output):
# Check for any software package updates.
pkgs = list_apt_updates(apt_update=False)
if os.path.exists("/var/run/reboot-required"):
if is_reboot_needed_due_to_package_installation():
output.print_error("System updates have been installed and a reboot of the machine is required.")
elif len(pkgs) == 0:
output.print_ok("System software is up to date.")
@@ -205,22 +239,38 @@ def check_free_disk_space(rounded_values, env, output):
st = os.statvfs(env['STORAGE_ROOT'])
bytes_total = st.f_blocks * st.f_frsize
bytes_free = st.f_bavail * st.f_frsize
if not rounded_values:
disk_msg = "The disk has %s GB space remaining." % str(round(bytes_free/1024.0/1024.0/1024.0*10.0)/10)
else:
disk_msg = "The disk has less than %s%% space left." % str(round(bytes_free/bytes_total/10 + .5)*10)
disk_msg = "The disk has %.2f GB space remaining." % (bytes_free/1024.0/1024.0/1024.0)
if bytes_free > .3 * bytes_total:
if rounded_values: disk_msg = "The disk has more than 30% free space."
output.print_ok(disk_msg)
elif bytes_free > .15 * bytes_total:
if rounded_values: disk_msg = "The disk has less than 30% free space."
output.print_warning(disk_msg)
else:
if rounded_values: disk_msg = "The disk has less than 15% free space."
output.print_error(disk_msg)
def check_free_memory(rounded_values, env, output):
# Check free memory.
percent_free = 100 - psutil.virtual_memory().percent
memory_msg = "System memory is %s%% free." % str(round(percent_free))
if percent_free >= 20:
if rounded_values: memory_msg = "System free memory is at least 20%."
output.print_ok(memory_msg)
elif percent_free >= 10:
if rounded_values: memory_msg = "System free memory is below 20%."
output.print_warning(memory_msg)
else:
if rounded_values: memory_msg = "System free memory is below 10%."
output.print_error(memory_msg)
def run_network_checks(env, output):
# Also see setup/network-checks.sh.
output.add_heading("Network")
check_ufw(env, output)
# Stop if we cannot make an outbound connection on port 25. Many residential
# networks block outbound port 25 to prevent their network from sending spam.
# See if we can reach one of Google's MTAs with a 5-second timeout.
@@ -262,23 +312,24 @@ def run_domain_checks(rounded_time, env, output, pool):
# Get the list of domains that we don't serve web for because of a custom CNAME/A record.
domains_with_a_records = get_domains_with_a_records(env)
ssl_certificates = get_ssl_certificates(env)
# Serial version:
#for domain in sort_domains(domains_to_check, env):
# run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains)
# Parallelize the checks across a worker pool.
args = ((domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records, ssl_certificates)
args = ((domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records)
for domain in domains_to_check)
ret = pool.starmap(run_domain_checks_on_domain, args, chunksize=1)
ret = dict(ret) # (domain, output) => { domain: output }
for domain in sort_domains(ret, env):
ret[domain].playback(output)
def run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records, ssl_certificates):
def run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records):
output = BufferedOutput()
# we'd move this up, but this returns non-pickleable values
ssl_certificates = get_ssl_certificates(env)
# The domain is IDNA-encoded in the database, but for display use Unicode.
try:
domain_display = idna.decode(domain.encode('ascii'))
@@ -341,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.
ipv6 = query_dns(domain, "AAAA") if env.get("PUBLIC_IPV6") else None
if ip == env['PUBLIC_IP'] and ipv6 in (None, 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))
else:
output.print_error("""This domain must resolve to your box's IP address (%s) in public DNS but it currently resolves
@@ -407,7 +458,7 @@ def check_dns_zone(domain, env, output, dns_zonefiles):
# half working.)
custom_dns_records = list(get_custom_dns_config(env)) # generator => list so we can reuse it
correct_ip = get_custom_dns_record(custom_dns_records, domain, "A") or env['PUBLIC_IP']
correct_ip = "; ".join(sorted(get_custom_dns_records(custom_dns_records, domain, "A"))) or env['PUBLIC_IP']
custom_secondary_ns = get_secondary_dns(custom_dns_records, mode="NS")
secondary_ns = custom_secondary_ns or ["ns2." + env['PRIMARY_HOSTNAME']]
@@ -431,7 +482,7 @@ def check_dns_zone(domain, env, output, dns_zonefiles):
% (existing_ns, correct_ns) )
# Check that each custom secondary nameserver resolves the IP address.
if custom_secondary_ns and not probably_external_dns:
for ns in custom_secondary_ns:
# We must first resolve the nameserver to an IP address so we can query it.
@@ -447,7 +498,7 @@ def check_dns_zone(domain, env, output, dns_zonefiles):
elif ip is None:
output.print_error("Secondary nameserver %s is not configured to resolve this domain." % ns)
else:
output.print_error("Secondary nameserver %s is not configured correctly. (It resolved this domain as %s. It should be %s.)" % (ns, ip, env['PUBLIC_IP']))
output.print_error("Secondary nameserver %s is not configured correctly. (It resolved this domain as %s. It should be %s.)" % (ns, ip, correct_ip))
def check_dns_zone_suggestions(domain, env, output, dns_zonefiles, domains_with_a_records):
# Warn if a custom DNS record is preventing this or the automatic www redirect from
@@ -584,15 +635,23 @@ def check_web_domain(domain, rounded_time, ssl_certificates, env, output):
# for PRIMARY_HOSTNAME, for which it is required for mail specifically. For it and
# other domains, it is required to access its website.
if domain != env['PRIMARY_HOSTNAME']:
ip = query_dns(domain, "A")
if ip == env['PUBLIC_IP']:
output.print_ok("Domain resolves to this box's IP address. [%s%s]" % (domain, env['PUBLIC_IP']))
else:
output.print_error("""This domain should resolve to your box's IP address (%s) if you would like the box to serve
webmail or a website on this domain. The domain currently resolves to %s in public DNS. It may take several hours for
public DNS to update after a change. This problem may result from other issues listed here.""" % (env['PUBLIC_IP'], ip))
ok_values = []
for (rtype, expected) in (("A", env['PUBLIC_IP']), ("AAAA", env.get('PUBLIC_IPV6'))):
if not expected: continue # IPv6 is not configured
value = query_dns(domain, rtype)
if value == normalize_ip(expected):
ok_values.append(value)
else:
output.print_error("""This domain should resolve to your box's IP address (%s %s) if you would like the box to serve
webmail or a website on this domain. The domain currently resolves to %s in public DNS. It may take several hours for
public DNS to update after a change. This problem may result from other issues listed here.""" % (rtype, expected, value))
return
# We need a SSL certificate for PRIMARY_HOSTNAME because that's where the
# If both A and AAAA are correct...
output.print_ok("Domain resolves to this box's IP address. [%s%s]" % (domain, '; '.join(ok_values)))
# We need a TLS certificate for PRIMARY_HOSTNAME because that's where the
# user will log in with IMAP or webmail. Any other domain we serve a
# website for also needs a signed certificate.
check_ssl_cert(domain, rounded_time, ssl_certificates, env, output)
@@ -627,6 +686,13 @@ def query_dns(qname, rtype, nxdomain='[Not Set]', at=None):
except dns.exception.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
# 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
@@ -634,56 +700,39 @@ def query_dns(qname, rtype, nxdomain='[Not Set]', at=None):
return "; ".join(sorted(str(r).rstrip('.') for r in response))
def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output):
# Check that SSL certificate is signed.
# Check that TLS certificate is signed.
# Skip the check if the A record is not pointed here.
if query_dns(domain, "A", None) not in (env['PUBLIC_IP'], None): return
# Where is the SSL stored?
x = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
if x is None:
output.print_warning("""No SSL certificate is installed for this domain. Visitors to a website on
# Where is the certificate file stored?
tls_cert = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
if tls_cert is None:
output.print_warning("""No TLS (SSL) certificate is installed for this domain. Visitors to a website on
this domain will get a security warning. If you are not serving a website on this domain, you do
not need to take any action. Use the SSL Certificates page in the control panel to install a
SSL certificate.""")
not need to take any action. Use the TLS Certificates page in the control panel to install a
TLS certificate.""")
return
ssl_key, ssl_certificate, ssl_via = x
# Check that the certificate is good.
cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key, rounded_time=rounded_time)
cert_status, cert_status_details = check_certificate(domain, tls_cert["certificate"], tls_cert["private-key"], rounded_time=rounded_time)
if cert_status == "OK":
# The certificate is ok. The details has expiry info.
output.print_ok("SSL certificate is signed & valid. %s %s" % (ssl_via if ssl_via else "", cert_status_details))
output.print_ok("TLS (SSL) certificate is signed & valid. " + cert_status_details)
elif cert_status == "SELF-SIGNED":
# Offer instructions for purchasing a signed certificate.
fingerprint = shell('check_output', [
"openssl",
"x509",
"-in", ssl_certificate,
"-noout",
"-fingerprint"
])
fingerprint = re.sub(".*Fingerprint=", "", fingerprint).strip()
if domain == env['PRIMARY_HOSTNAME']:
output.print_error("""The SSL certificate for this domain is currently self-signed. You will get a security
output.print_error("""The TLS (SSL) certificate for this domain is currently self-signed. You will get a security
warning when you check or send email and when visiting this domain in a web browser (for webmail or
static site hosting). Use the SSL Certificates page in the control panel to install a signed SSL certificate.
You may choose to leave the self-signed certificate in place and confirm the security exception, but check that
the certificate fingerprint matches the following:""")
output.print_line("")
output.print_line(" " + fingerprint, monospace=True)
static site hosting).""")
else:
output.print_error("""The SSL certificate for this domain is self-signed.""")
output.print_error("""The TLS (SSL) certificate for this domain is self-signed.""")
else:
output.print_error("The SSL certificate has a problem: " + cert_status)
output.print_error("The TLS (SSL) certificate has a problem: " + cert_status)
if cert_status_details:
output.print_line("")
output.print_line(cert_status_details)
@@ -732,34 +781,42 @@ def what_version_is_this(env):
return tag
def get_latest_miab_version():
# This pings https://mailinabox.email/bootstrap.sh and extracts the tag named in
# This pings https://mailinabox.email/setup.sh and extracts the tag named in
# the script to determine the current product version.
import urllib.request
return re.search(b'TAG=(.*)', urllib.request.urlopen("https://mailinabox.email/bootstrap.sh?ping=1").read()).group(1).decode("utf8")
from urllib.request import urlopen, HTTPError, URLError
from socket import timeout
try:
return re.search(b'TAG=(.*)', urlopen("https://mailinabox.email/setup.sh?ping=1", timeout=5).read()).group(1).decode("utf8")
except (HTTPError, URLError, timeout):
return None
def check_miab_version(env, output):
config = load_settings(env)
if config.get("privacy", True):
output.print_warning("Mail-in-a-Box version check disabled by privacy setting.")
else:
try:
this_ver = what_version_is_this(env)
except:
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()
if this_ver == latest_ver:
output.print_ok("Mail-in-a-Box is up to date. You are running version %s." % this_ver)
elif latest_ver is None:
output.print_error("Latest Mail-in-a-Box version could not be determined. You are running version %s." % this_ver)
else:
output.print_error("A new version of Mail-in-a-Box is available. You are running version %s. The latest version is %s. For upgrade instructions, see https://mailinabox.email. "
% (this_ver, latest_ver))
def run_and_output_changes(env, pool, send_via_email):
def run_and_output_changes(env, pool):
import json
from difflib import SequenceMatcher
if not send_via_email:
out = ConsoleOutput()
else:
import io
out = FileOutput(io.StringIO(""), 70)
out = ConsoleOutput()
# Run status checks.
cur = BufferedOutput()
@@ -818,33 +875,21 @@ def run_and_output_changes(env, pool, send_via_email):
out.add_heading(category)
out.print_warning("This section was removed.")
if send_via_email:
# If there were changes, send off an email.
buf = out.buf.getvalue()
if len(buf) > 0:
# create MIME message
from email.message import Message
msg = Message()
msg['From'] = "\"%s\" <administrator@%s>" % (env['PRIMARY_HOSTNAME'], env['PRIMARY_HOSTNAME'])
msg['To'] = "administrator@%s" % env['PRIMARY_HOSTNAME']
msg['Subject'] = "[%s] Status Checks Change Notice" % env['PRIMARY_HOSTNAME']
msg.set_payload(buf, "UTF-8")
# send to administrator@
import smtplib
mailserver = smtplib.SMTP('localhost', 25)
mailserver.ehlo()
mailserver.sendmail(
"administrator@%s" % env['PRIMARY_HOSTNAME'], # MAIL FROM
"administrator@%s" % env['PRIMARY_HOSTNAME'], # RCPT TO
msg.as_string())
mailserver.quit()
# Store the current status checks output for next time.
os.makedirs(os.path.dirname(cache_fn), exist_ok=True)
with open(cache_fn, "w") as f:
json.dump(cur.buf, f, indent=True)
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.
import ipaddress
try:
return str(ipaddress.ip_address(ip))
except:
return ip
class FileOutput:
def __init__(self, buf, width):
self.buf = buf
@@ -870,7 +915,7 @@ class FileOutput:
words = re.split("(\s+)", message)
linelen = 0
for w in words:
if linelen + len(w) > self.width-1-len(first_line):
if self.width and (linelen + len(w) > self.width-1-len(first_line)):
print(file=self.buf)
print(" ", end="", file=self.buf)
linelen = 0
@@ -886,10 +931,22 @@ class FileOutput:
class ConsoleOutput(FileOutput):
def __init__(self):
self.buf = sys.stdout
try:
self.width = int(shell('check_output', ['stty', 'size']).split()[1])
except:
self.width = 76
# Do nice line-wrapping according to the size of the terminal.
# The 'stty' program queries standard input for terminal information.
if sys.stdin.isatty():
try:
self.width = int(shell('check_output', ['stty', 'size']).split()[1])
except:
self.width = 76
else:
# However if standard input is not a terminal, we would get
# "stty: standard input: Inappropriate ioctl for device". So
# we test with sys.stdin.isatty first, and if it is not a
# terminal don't do any line wrapping. When this script is
# run from cron, or if stdin has been redirected, this happens.
self.width = None
class BufferedOutput:
# Record all of the instance method calls so we can play them back later.
@@ -917,7 +974,7 @@ if __name__ == "__main__":
run_checks(False, env, ConsoleOutput(), pool)
elif sys.argv[1] == "--show-changes":
run_and_output_changes(env, pool, sys.argv[-1] == "--smtp")
run_and_output_changes(env, pool)
elif sys.argv[1] == "--check-primary-hostname":
# See if the primary hostname appears resolvable and has a signed certificate.
@@ -925,10 +982,10 @@ if __name__ == "__main__":
if query_dns(domain, "A") != env['PUBLIC_IP']:
sys.exit(1)
ssl_certificates = get_ssl_certificates(env)
ssl_key, ssl_certificate, ssl_via = get_domain_ssl_files(domain, ssl_certificates, env)
if not os.path.exists(ssl_certificate):
tls_cert = get_domain_ssl_files(domain, ssl_certificates, env)
if not os.path.exists(tls_cert["certificate"]):
sys.exit(1)
cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key, warn_if_expiring_soon=False)
cert_status, cert_status_details = check_certificate(domain, tls_cert["certificate"], tls_cert["private-key"], warn_if_expiring_soon=False)
if cert_status != "OK":
sys.exit(1)
sys.exit(0)

View File

@@ -39,8 +39,9 @@
<label for="addaliasForwardsTo" class="col-sm-1 control-label">Forwards To</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" id="addaliasForwardsTo"></textarea>
<div style="margin-top: 3px; padding-left: 3px; font-size: 90%" class="text-muted">
<span class="domainalias">Enter just the part of an email address starting with the @-sign.</span>
<div style="margin-top: 3px; padding-left: 3px; font-size: 90%">
<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>
@@ -50,7 +51,7 @@
<div class="radio">
<label>
<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>
</div>
<div class="radio">
@@ -106,6 +107,41 @@
</table>
</div>
<h3>Mail aliases API (advanced)</h3>
<p>Use your box&rsquo;s mail aliases API to add and remove mail aliases from the command-line or custom services you build.</p>
<p>Usage:</p>
<pre>curl -X <b>VERB</b> [-d "<b>parameters</b>"] --user {email}:{password} https://{{hostname}}/admin/mail/aliases[<b>action</b>]</pre>
<p>Brackets denote an optional argument. Please note that the POST body <code>parameters</code> must be URL-encoded.</p>
<p>The email and password given to the <code>--user</code> option must be an administrative user on this system.</p>
<h4 style="margin-bottom: 0">Verbs</h4>
<table class="table" style="margin-top: .5em">
<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 aliases. 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 alias. Required POST-body parameters are <code>address</code> and <code>forwards_to</code>.</td></tr>
<tr><td>POST</td><td>/remove</td> <td>Removes a mail alias. Required POST-body parameter is <code>address</code>.</td></tr>
</table>
<h4>Examples:</h4>
<p>Try these examples. For simplicity the examples omit the <code>--user me@mydomain.com:yourpassword</code> command line argument which you must fill in with your email address and password.</p>
<pre># Gives a JSON-encoded list of all mail aliases
curl -X GET https://{{hostname}}/admin/mail/aliases?format=json
# Adds a new alias
curl -X POST -d "address=new_alias@mydomail.com" -d "forwards_to=my_email@mydomain.com" https://{{hostname}}/admin/mail/aliases/add
# Removes an alias
curl -X POST -d "address=new_alias@mydomail.com" https://{{hostname}}/admin/mail/aliases/remove
</pre>
<script>
function show_aliases() {

View File

@@ -10,7 +10,7 @@
<p>It is possible to set custom DNS records on domains hosted here.</p>
<h3>Set Custom DNS Records</h3>
<h3>Set custom DNS records</h3>
<p>You can set additional DNS records, such as if you have a website running on another server, to add DKIM records for external mail providers, or for various confirmation-of-ownership tests.</p>
@@ -31,11 +31,15 @@
<label for="customdnsType" class="col-sm-1 control-label">Type</label>
<div class="col-sm-10">
<select id="customdnsType" class="form-control" style="max-width: 400px" onchange="show_customdns_rtype_hint()">
<option value="A" data-hint="Enter an IPv4 address (i.e. a dotted quad, such as 123.456.789.012).">A (IPv4 address)</option>
<option value="AAAA" data-hint="Enter an IPv6 address.">AAAA (IPv6 address)</option>
<option value="A" data-hint="Enter an IPv4 address (i.e. a dotted quad, such as 123.456.789.012). The 'local' alias sets the record to this box's public IPv4 address.">A (IPv4 address)</option>
<option value="AAAA" data-hint="Enter an IPv6 address. The 'local' alias sets the record to this box's public IPv6 address.">AAAA (IPv6 address)</option>
<option value="CAA" data-hint="Enter a CA that can issue certificates for this domain in the form of FLAG TAG VALUE. (0 issuewild &quot;letsencrypt.org&quot;)">CAA (Certificate Authority Authorization)</option>
<option value="CNAME" data-hint="Enter another domain name followed by a period at the end (e.g. mypage.github.io.).">CNAME (DNS forwarding)</option>
<option value="TXT" data-hint="Enter arbitrary text.">TXT (text record)</option>
<option value="MX" data-hint="Enter record in the form of PRIORIY DOMAIN., including trailing period (e.g. 20 mx.example.com.).">MX (mail exchanger)</option>
<option value="MX" data-hint="Enter record in the form of PRIORITY DOMAIN., including trailing period (e.g. 20 mx.example.com.).">MX (mail exchanger)</option>
<option value="SRV" data-hint="Enter record in the form of PRIORITY WEIGHT PORT TARGET., including trailing period (e.g. 10 10 5060 sip.example.com.).">SRV (service record)</option>
<option value="SSHFP" data-hint="Enter record in the form of ALGORITHM TYPE FINGERPRINT.">SSHFP (SSH fingerprint record)</option>
<option value="NS" data-hint="Enter a hostname to which this subdomain should be delegated to">NS (DNS subdomain delegation)</option>
</select>
</div>
</div>
@@ -65,10 +69,10 @@
</tbody>
</table>
<h3>Using a Secondary Nameserver</h3>
<h3>Using a secondary nameserver</h3>
<p>If your TLD requires you to have two separate nameservers, you can either set up <a href="#" onclick="return show_panel('external_dns')">external DNS</a> and ignore the DNS server on this box entirely, or use the DNS server on this box but add a secondary (aka &ldquo;slave&rdquo;) nameserver.</p>
<p>If you choose to use a seconday nameserver, you must find a seconday nameserver service provider. Your domain name registrar or virtual cloud provider may provide this service for you. Once you set up the seconday nameserver service, enter the hostname (not the IP address) of <em>their</em> secondary nameserver in the box below.</p>
<p>If you choose to use a secondary nameserver, you must find a secondary nameserver service provider. Your domain name registrar or virtual cloud provider may provide this service for you. Once you set up the secondary nameserver service, enter the hostname (not the IP address) of <em>their</em> secondary nameserver in the box below.</p>
<form class="form-horizontal" role="form" onsubmit="do_set_secondary_dns(); return false;">
<div class="form-group">
@@ -123,7 +127,7 @@
<tr><td>email</td> <td>The email address of any administrative user here.</td></tr>
<tr><td>password</td> <td>That user&rsquo;s password.</td></tr>
<tr><td>qname</td> <td>The fully qualified domain name for the record you are trying to set. It must be one of the domain names or a subdomain of one of the domain names hosted on this box. (Add mail users or aliases to add new domains.)</td></tr>
<tr><td>rtype</td> <td>The resource type. Defaults to <code>A</code> if omitted. Possible values: <code>A</code> (an IPv4 address), <code>AAAA</code> (an IPv6 address), <code>TXT</code> (a text string), <code>CNAME</code> (an alias, which is a fully qualified domain name &mdash; don&rsquo;t forget the final period), <code>MX</code>, or <code>SRV</code>.</td></tr>
<tr><td>rtype</td> <td>The resource type. Defaults to <code>A</code> if omitted. Possible values: <code>A</code> (an IPv4 address), <code>AAAA</code> (an IPv6 address), <code>TXT</code> (a text string), <code>CNAME</code> (an alias, which is a fully qualified domain name &mdash; don&rsquo;t forget the final period), <code>MX</code>, <code>SRV</code>, <code>SSHFP</code>, <code>CAA</code> or <code>NS</code>.</td></tr>
<tr><td>value</td> <td>For PUT, POST, and DELETE, the record&rsquo;s value. If the <code>rtype</code> is <code>A</code> or <code>AAAA</code> and <code>value</code> is empty or omitted, the IPv4 or IPv6 address of the remote host is used (be sure to use the <code>-4</code> or <code>-6</code> options to curl). This is handy for dynamic DNS!</td></tr>
</table>

View File

@@ -9,7 +9,7 @@
<meta name="robots" content="noindex, nofollow">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" integrity="sha256-MfvZlkHCEqatNoGiOXveE8FIwMzZg4W85qfrfIFBfYc=" crossorigin="anonymous">
<link rel="stylesheet" href="/admin/assets/bootstrap/css/bootstrap.min.css">
<style>
body {
overflow-y: scroll;
@@ -63,14 +63,14 @@
margin-bottom: 1em;
}
</style>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css" integrity="sha256-bHQiqcFbnJb1Qhh61RY9cMh6kR0gTuQY6iFOBj1yj00=" crossorigin="anonymous">
<link rel="stylesheet" href="/admin/assets/bootstrap/css/bootstrap-theme.min.css">
</head>
<body>
<!--[if lt IE 8]><p>Internet Explorer version 8 or any modern web browser is required to use this website, sorry.<![endif]-->
<!--[if gt IE 7]><!-->
<div class="navbar navbar-inverse" role="navigation">
<div class="navbar navbar-inverse navbar-static-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
@@ -87,13 +87,13 @@
<a href="#" class="dropdown-toggle" data-toggle="dropdown">System <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="#system_status" onclick="return show_panel(this);">Status Checks</a></li>
<li><a href="#ssl" onclick="return show_panel(this);">SSL Certificates</a></li>
<li><a href="#tls" onclick="return show_panel(this);">TLS (SSL) Certificates</a></li>
<li><a href="#system_backup" onclick="return show_panel(this);">Backup Status</a></li>
<li class="divider"></li>
<li class="dropdown-header">Advanced Pages</li>
<li><a href="#custom_dns" onclick="return show_panel(this);">Custom DNS</a></li>
<li><a href="#external_dns" onclick="return show_panel(this);">External DNS</a></li>
<li><a href="/admin/munin">Munin Monitoring</a></li>
<li><a href="/admin/munin" target="_blank">Munin Monitoring</a></li>
</ul>
</li>
<li class="dropdown">
@@ -108,7 +108,7 @@
<li><a href="#web" onclick="return show_panel(this);">Web</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="#" onclick="do_logout(); return false;" style="color: white">Log out?</a></li>
<li><a href="#" onclick="do_logout(); return false;" style="color: white">Log out</a></li>
</ul>
</div><!--/.navbar-collapse -->
</div>
@@ -155,7 +155,7 @@
{% include "web.html" %}
</div>
<div id="panel_ssl" class="admin_panel">
<div id="panel_tls" class="admin_panel">
{% include "ssl.html" %}
</div>
@@ -191,8 +191,8 @@
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js" integrity="sha256-rsPUGdUPBXgalvIj4YKJrrUlmLXbOb6Cp7cdxn1qeUc=" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js" integrity="sha256-Sk3nkD6mLTMOF0EOpNtsIry+s1CsaqQC1rVLTAy+0yc=" crossorigin="anonymous"></script>
<script src="/admin/assets/jquery.min.js"></script>
<script src="/admin/assets/bootstrap/js/bootstrap.min.js"></script>
<script>
var global_modal_state = null;
@@ -218,7 +218,7 @@ $(function() {
if (global_modal_state == null) global_modal_state = 1; // cancel if the user hit ESC or clicked outside of the modal
if (global_modal_funcs && global_modal_funcs[global_modal_state])
global_modal_funcs[global_modal_state]();
})
})
})
function show_modal_error(title, message, callback) {
@@ -281,7 +281,7 @@ function ajax_with_indicator(options) {
};
options.error = function(jqxhr) {
hide_loading_indicator();
if (!old_error)
if (!old_error)
show_modal_error("Error", "Something went wrong, sorry.")
else
old_error(jqxhr.responseText, jqxhr);

View File

@@ -17,13 +17,13 @@ sudo tools/mail.py user make-admin me@{{hostname}}</pre>
{% endif %}
<hr>
</div>
</div>
</div>
{% endif %}
<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;">
<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">
<label for="inputEmail3" class="col-sm-3 control-label">Email</label>
<div class="col-sm-9">
@@ -76,7 +76,7 @@ function do_login() {
"/me",
"GET",
{ },
function(response){
function(response){
// This API call always succeeds. It returns a JSON object indicating
// whether the request was authenticated or not.
if (response.status != "ok") {
@@ -117,7 +117,7 @@ function do_login() {
// Open the next panel the user wants to go to. Do this after the XHR response
// is over so that we don't start a new XHR request while this one is finishing,
// which confuses the loading indicator.
setTimeout(function() { show_panel(!switch_back_to_panel ? 'system_status' : switch_back_to_panel) }, 300);
setTimeout(function() { show_panel(!switch_back_to_panel || switch_back_to_panel == "login" ? 'system_status' : switch_back_to_panel) }, 300);
}
})
}

View File

@@ -42,7 +42,7 @@
<h4>Exchange/ActiveSync settings</h4>
<p>On iOS devices, devices on this <a href="http://z-push.org/compatibility/">compatibility list</a>, or using Outlook 2007 or later on Windows 7 and later, you may set up your mail as an Exchange or ActiveSync server. However, we&rsquo;ve found this to be more buggy than using IMAP as described above. If you encounter any problems, please use the manual settings above.</p>
<p>On iOS devices, devices on this <a href="https://wiki.z-hub.io/display/ZP/Compatibility">compatibility list</a>, or using Outlook 2007 or later on Windows 7 and later, you may set up your mail as an Exchange or ActiveSync server. However, we&rsquo;ve found this to be more buggy than using IMAP as described above. If you encounter any problems, please use the manual settings above.</p>
<table class="table">
<tr><th>Server</th> <td>{{hostname}}</td></tr>
@@ -59,7 +59,7 @@
</div>
<div class="panel-body">
<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>
<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

@@ -1,12 +1,31 @@
<style>
</style>
<h2>SSL Certificates</h2>
<h2>TLS (SSL) Certificates</h2>
<h3>Certificate Status</h3>
<p>A TLS (formerly called SSL) certificate is a cryptographic file that proves to anyone connecting to a web address that the connection is secure between you and the owner of that address.</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>
<table id="ssl_domains" class="table" style="margin-bottom: 2em; width: auto;">
<div id="ssl_provision">
<h3>Provision certificates</h3>
<div id="ssl_provision_p" style="display: none; margin-top: 1.5em">
<button onclick='return provision_tls_cert();' class='btn btn-primary' style="float: left; margin: 0 1.5em 1em 0;">Provision</button>
<p>A TLS certificate can be automatically provisioned from <a href="https://letsencrypt.org/" target="_blank">Let&rsquo;s Encrypt</a>, a free TLS certificate provider, for:<br>
<span class="text-primary"></span></p>
</div>
<div class="clearfix"> </div>
<div id="ssl_provision_result"></div>
</div>
<h3>Certificate status</h3>
<p style="margin-top: 1.5em">Certificates expire after a period of time. All certificates will be automatically renewed through <a href="https://letsencrypt.org/" target="_blank">Let&rsquo;s Encrypt</a> 14 days prior to expiration.</p>
<table id="ssl_domains" class="table" style="margin-bottom: 2em; width: auto; display: none">
<thead>
<tr>
<th>Domain</th>
@@ -18,17 +37,18 @@
</tbody>
</table>
<p>A multi-domain or wildcard certificate will be automatically applied to any domains it is valid for.</p>
<h3 id="ssl_install_header">Install SSL Certificate</h3>
<h3 id="ssl_install_header">Install certificate</h3>
<p>There are many places where you can get a free or cheap SSL certificate. We recommend <a href="https://www.namecheap.com/security/ssl-certificates/domain-validation.aspx">Namecheap&rsquo;s $9 certificate</a>, <a href="https://www.startssl.com/">StartSSL&rsquo;s free express lane</a> or <a href="https://buy.wosign.com/free/">WoSign&rsquo;s free SSL</a></a>.</p>
<p>If you don't want to use our automatic Let's Encrypt integration, you can give any other certificate provider a try. You can generate the needed CSR below.</p>
<p>Which domain are you getting an SSL certificate for?</p>
<p>Which domain are you getting a certificate for?</p>
<p><select id="ssldomain" onchange="show_csr()" class="form-control" style="width: auto"></select></p>
<p>What country are you in? This is required by some SSL certificate providers. You may leave this blank if you know your SSL certificate provider doesn't require it.</p>
<p>(A multi-domain or wildcard certificate will be automatically applied to any domains it is valid for besides the one you choose above.)</p>
<p>What country are you in? This is required by some TLS certificate providers. You may leave this blank if you know your TLS certificate provider doesn't require it.</p>
<p><select id="sslcc" onchange="show_csr()" class="form-control" style="width: auto">
<option value="">(Select)</option>
@@ -38,18 +58,18 @@
</select></p>
<div id="csr_info" style="display: none">
<p>You will need to provide the SSL certificate provider this Certificate Signing Request (CSR):</p>
<p>You will need to provide the certificate provider this Certificate Signing Request (CSR):</p>
<pre id="ssl_csr"></pre>
<p><small>The CSR is safe to share. It can only be used in combination with a secret key stored on this machine.</small></p>
<p>The SSL certificate provider will then provide you with an SSL certificate. They may also provide you with an intermediate chain. Paste each separately into the boxes below:</p>
<p>The certificate provider will then provide you with a TLS/SSL certificate. They may also provide you with an intermediate chain. Paste each separately into the boxes below:</p>
<p style="margin-bottom: .5em">SSL certificate:</p>
<p style="margin-bottom: .5em">TLS/SSL certificate:</p>
<p><textarea id="ssl_paste_cert" class="form-control" style="max-width: 40em; height: 8em" placeholder="-----BEGIN CERTIFICATE-----&#xA;stuff here&#xA;-----END CERTIFICATE-----"></textarea></p>
<p style="margin-bottom: .5em">SSL intermediate chain (if provided):</p>
<p style="margin-bottom: .5em">TLS/SSL intermediate chain (if provided):</p>
<p><textarea id="ssl_paste_chain" class="form-control" style="max-width: 40em; height: 8em" placeholder="-----BEGIN CERTIFICATE-----&#xA;stuff here&#xA;-----END CERTIFICATE-----&#xA;-----BEGIN CERTIFICATE-----&#xA;more stuff here&#xA;-----END CERTIFICATE-----"></textarea></p>
<p>After you paste in the information, click the install button.</p>
@@ -58,26 +78,41 @@
</div>
<script>
function show_ssl() {
function show_tls(keep_provisioning_shown) {
api(
"/web/domains",
"/ssl/status",
"GET",
{
},
function(domains) {
function(res) {
// provisioning status
if (!keep_provisioning_shown)
$('#ssl_provision').toggle(res.can_provision.length > 0)
$('#ssl_provision_p').toggle(res.can_provision.length > 0);
if (res.can_provision.length > 0)
$('#ssl_provision_p span').text(res.can_provision.join(", "));
// certificate status
var domains = res.status;
var tb = $('#ssl_domains tbody');
tb.text('');
$('#ssldomain').html('<option value="">(select)</option>');
$('#ssl_domains').show();
for (var i = 0; i < domains.length; i++) {
var row = $("<tr><th class='domain'><a href=''></a></th><td class='status'></td> <td class='actions'><a href='#' onclick='return ssl_install(this);' class='btn btn-xs'>Install Certificate</a></td></tr>");
tb.append(row);
row.attr('data-domain', domains[i].domain);
row.find('.domain a').text(domains[i].domain);
row.find('.domain a').attr('href', 'https://' + domains[i].domain);
row.addClass("text-" + domains[i].ssl_certificate[0]);
row.find('.status').text(domains[i].ssl_certificate[1]);
if (domains[i].ssl_certificate[0] == "success") {
if (domains[i].status == "not-applicable") {
domains[i].status = "muted"; // text-muted css class
row.find('.actions a').remove(); // no actions applicable
}
row.addClass("text-" + domains[i].status);
row.find('.status').text(domains[i].text);
if (domains[i].status == "success") {
row.find('.actions a').addClass('btn-default').text('Replace Certificate');
} else {
row.find('.actions a').addClass('btn-primary').text('Install Certificate');
@@ -91,14 +126,19 @@ function show_ssl() {
function ssl_install(elem) {
var domain = $(elem).parents('tr').attr('data-domain');
$('#ssldomain').val(domain);
$('#csr_info').slideDown();
$('#ssl_csr').text('Loading...');
show_csr();
$('html, body').animate({ scrollTop: $('#ssl_install_header').offset().top - $('.navbar-fixed-top').height() - 20 })
return false;
}
function show_csr() {
// Can't show a CSR until both inputs are entered.
if ($('#ssldomain').val() == "") return;
if ($('#sslcc').val() == "") return;
// Scroll to it and fetch.
$('#csr_info').slideDown();
$('#ssl_csr').text('Loading...');
api(
"/ssl/csr/" + $('#ssldomain').val(),
"POST",
@@ -122,10 +162,75 @@ function install_cert() {
function(status) {
if (/^OK($|\n)/.test(status)) {
console.log(status)
show_modal_error("SSL Certificate Installation", "Certificate has been installed. Check that you have no connection problems to the domain.", function() { show_ssl(); $('#csr_info').slideUp(); });
show_modal_error("TLS Certificate Installation", "Certificate has been installed. Check that you have no connection problems to the domain.", function() { show_ssl(); $('#csr_info').slideUp(); });
} else {
show_modal_error("SSL Certificate Installation", status);
show_modal_error("TLS Certificate Installation", status);
}
});
}
function provision_tls_cert() {
// Automatically provision any certs.
$('#ssl_provision_p .btn').attr('disabled', '1'); // prevent double-clicks
api(
"/ssl/provision",
"POST",
{ },
function(status) {
// Clear last attempt.
$('#ssl_provision_result').text("");
may_reenable_provision_button = true;
// Nothing was done. There might also be problem domains, but we've already displayed those.
if (status.requests.length == 0) {
show_modal_error("TLS Certificate Provisioning", "There were no domain names to provision certificates for.");
// don't return - haven't re-enabled the provision button
}
// Each provisioning API call returns zero or more "requests" which represent
// a request to Let's Encrypt for a single certificate. Normally there is just
// one request (for a single multi-domain certificate).
for (var i = 0; i < status.requests.length; 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
var n = $("<div><h4/><p/></div>");
$('#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
if (status.requests.length > 0)
n.find("h4").text(r.domains.join(", "));
if (r.result == "error") {
n.find("p").addClass("text-danger").text(r.message);
} else if (r.result == "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
}
// display the detailed log info in case of problems
var trace = $("<div class='small text-muted' style='margin-top: 1.5em'>Log:</div>");
n.append(trace);
for (var j = 0; j < r.log.length; j++)
trace.append($("<div/>").text(r.log[j]));
}
if (may_reenable_provision_button)
$('#ssl_provision_p .btn').removeAttr("disabled");
});
}
</script>

View File

@@ -30,7 +30,7 @@
<table class="table">
<thead><tr><th>For...</th> <th>Use...</th></tr></thead>
<tr><td>Contacts and Calendar</td> <td><a href="https://play.google.com/store/apps/details?id=at.bitfire.davdroid">DAVdroid</a> ($3.69; free <a href="https://f-droid.org/repository/browse/?fdfilter=dav&fdid=at.bitfire.davdroid">here</a>)</td></tr>
<tr><td>Contacts and Calendar</td> <td><a href="https://play.google.com/store/apps/details?id=at.bitfire.davdroid">DAVdroid</a> ($3.69; free <a href="https://f-droid.org/packages/at.bitfire.davdroid/">here</a>)</td></tr>
<tr><td>Only Contacts</td> <td><a href="https://play.google.com/store/apps/details?id=org.dmfs.carddav.sync">CardDAV-Sync free beta</a> (free)</td></tr>
<tr><td>Only Calendar</td> <td><a href="https://play.google.com/store/apps/details?id=org.dmfs.caldav.lib">CalDAV-Sync</a> ($2.89)</td></tr>
</table>

View File

@@ -16,16 +16,60 @@
<select class="form-control" rows="1" id="backup-target-type" onchange="toggle_form()">
<option value="off">Nowhere (Disable Backups)</option>
<option value="local">{{hostname}}</option>
<option value="rsync">rsync</option>
<option value="s3">Amazon S3</option>
</select>
</div>
</div>
<!-- LOCAL BACKUP -->
<div class="form-group backup-target-local">
<div class="col-sm-10 col-sm-offset-2">
<p>Backups are stored on this machine&rsquo;s own hard disk. You are responsible for periodically using SFTP (FTP over SSH) to copy the backup files from <tt id="backup-location"></tt> to a safe location. These files are encrypted, so they are safe to store anywhere.</p>
<p>Backups are stored on this machine&rsquo;s own hard disk. You are responsible for periodically using SFTP (FTP over SSH) to copy the backup files from <tt class="backup-location"></tt> to a safe location. These files are encrypted, so they are safe to store anywhere.</p>
<p>Separately copy the encryption password from <tt class="backup-encpassword-file"></tt> to a safe and secure location. You will need this file to decrypt backup files.</p>
</div>
</div>
<!-- RSYNC BACKUP -->
<div class="form-group backup-target-rsync">
<div class="col-sm-10 col-sm-offset-2">
<p>Backups synced to a remote machine using rsync over SSH, with local
copies in <tt class="backup-location"></tt>. These files are encrypted, so
they are safe to store anywhere.</p> <p>Separately copy the encryption
password from <tt class="backup-encpassword-file"></tt> to a safe and
secure location. You will need this file to decrypt backup files.</p>
</div>
</div>
<div class="form-group backup-target-rsync">
<label for="backup-target-rsync-host" class="col-sm-2 control-label">Hostname</label>
<div class="col-sm-8">
<input type="text" placeholder="hostname.local" class="form-control" rows="1" id="backup-target-rsync-host">
</div>
</div>
<div class="form-group backup-target-rsync">
<label for="backup-target-rsync-path" class="col-sm-2 control-label">Path</label>
<div class="col-sm-8">
<input type="text" placeholder="/backups/{{hostname}}" class="form-control" rows="1" id="backup-target-rsync-path">
</div>
</div>
<div class="form-group backup-target-rsync">
<label for="backup-target-rsync-user" class="col-sm-2 control-label">Username</label>
<div class="col-sm-8">
<input type="text" class="form-control" rows="1" id="backup-target-rsync-user">
</div>
</div>
<div class="form-group backup-target-rsync">
<label for="ssh-pub-key" class="col-sm-2 control-label">Public SSH Key</label>
<div class="col-sm-8">
<input type="text" class="form-control" rows="1" id="ssh-pub-key" readonly>
<div class="small" style="margin-top: 2px">
Copy the Public SSH Key above, and paste it within the <tt>~/.ssh/authorized_keys</tt>
of target user on the backup server specified above. That way you'll enable secure and
passwordless authentication from your mail-in-a-box server and your backup server.
</div>
</div>
</div>
<!-- S3 BACKUP -->
<div class="form-group backup-target-s3">
<div class="col-sm-10 col-sm-offset-2">
<p>Backups are stored in an Amazon Web Services S3 bucket. You must have an AWS account already.</p>
@@ -33,15 +77,22 @@
</div>
</div>
<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">
<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 %}
<option value="{{host}}">{{name}}</option>
{% endfor %}
<option value="other">Other</option>
</select>
</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">
<label for="backup-target-s3-path" class="col-sm-2 control-label">S3 Path</label>
<div class="col-sm-8">
@@ -60,7 +111,8 @@
<input type="text" class="form-control" rows="1" id="backup-target-pass">
</div>
</div>
<div class="form-group backup-target-local backup-target-s3">
<!-- Common -->
<div class="form-group backup-target-local backup-target-rsync backup-target-s3">
<label for="min-age" class="col-sm-2 control-label">Days:</label>
<div class="col-sm-8">
<input type="number" class="form-control" rows="1" id="min-age">
@@ -74,7 +126,7 @@
</div>
</form>
<h3>Available Backups</h3>
<h3>Available backups</h3>
<p>The backup location currently contains the backups listed below. The total size of the backups is currently <span id="backup-total-size"></span>.</p>
@@ -92,8 +144,10 @@
function toggle_form() {
var target_type = $("#backup-target-type").val();
$(".backup-target-local, .backup-target-s3").hide();
$(".backup-target-local, .backup-target-rsync, .backup-target-s3").hide();
$(".backup-target-" + target_type).show();
init_inputs(target_type);
}
function nice_size(bytes) {
@@ -114,13 +168,18 @@ function nice_size(bytes) {
function show_system_backup() {
show_custom_backup()
$('#backup-status tbody').html("<tr><td colspan='2' class='text-muted'>Loading...</td></tr>")
api(
"/system/backup/status",
"GET",
{ },
function(r) {
if (r.error) {
show_modal_error("Backup Error", $("<pre/>").text(r.error));
return;
}
$('#backup-status tbody').html("");
var total_disk_size = 0;
@@ -137,7 +196,7 @@ function show_system_backup() {
var b = r.backups[i];
var tr = $('<tr/>');
if (b.full) tr.addClass("full-backup");
tr.append( $('<td/>').text(b.date_str + " " + r.tz) );
tr.append( $('<td/>').text(b.date_str) );
tr.append( $('<td/>').text(b.date_delta + " ago") );
tr.append( $('<td/>').text(b.full ? "full" : "increment") );
tr.append( $('<td style="text-align: right"/>').text( nice_size(b.size)) );
@@ -150,33 +209,43 @@ function show_system_backup() {
total_disk_size += b.size;
}
total_disk_size += r.unmatched_file_size;
$('#backup-total-size').text(nice_size(total_disk_size));
})
}
function show_custom_backup() {
$(".backup-target-local, .backup-target-s3").hide();
$(".backup-target-local, .backup-target-rsync, .backup-target-s3").hide();
api(
"/system/backup/config",
"GET",
{ },
function(r) {
$("#backup-target-user").val(r.target_user);
$("#backup-target-pass").val(r.target_pass);
$("#min-age").val(r.min_age_in_days);
$(".backup-location").text(r.file_target_directory);
$(".backup-encpassword-file").text(r.enc_pw_file);
$("#ssh-pub-key").val(r.ssh_pub_key);
if (r.target == "file://" + r.file_target_directory) {
$("#backup-target-type").val("local");
} else if (r.target == "off") {
$("#backup-target-type").val("off");
} else if (r.target.substring(0, 8) == "rsync://") {
$("#backup-target-type").val("rsync");
var path = r.target.substring(8).split('//');
var host_parts = path.shift().split('@');
$("#backup-target-rsync-user").val(host_parts[0]);
$("#backup-target-rsync-host").val(host_parts[1]);
$("#backup-target-rsync-path").val('/'+path[0]);
} else if (r.target.substring(0, 5) == "s3://") {
$("#backup-target-type").val("s3");
var hostpath = r.target.substring(5).split('/');
var hostpath = r.target.substring(5).split('/');
var host = hostpath.shift();
$("#backup-target-s3-host").val(host);
$("#backup-target-s3-path").val(hostpath.join('/'));
}
$("#backup-target-user").val(r.target_user);
$("#backup-target-pass").val(r.target_pass);
$("#min-age").val(r.min_age_in_days);
$('#backup-location').text(r.file_target_directory);
$('.backup-encpassword-file').text(r.enc_pw_file);
toggle_form()
})
}
@@ -185,12 +254,18 @@ function set_custom_backup() {
var target_type = $("#backup-target-type").val();
var target_user = $("#backup-target-user").val();
var target_pass = $("#backup-target-pass").val();
var target;
if (target_type == "local" || target_type == "off")
target = target_type;
else if (target_type == "s3")
target = "s3://" + $("#backup-target-s3-host").val() + "/" + $("#backup-target-s3-path").val();
else if (target_type == "rsync") {
target = "rsync://" + $("#backup-target-rsync-user").val() + "@" + $("#backup-target-rsync-host").val()
+ "/" + $("#backup-target-rsync-path").val();
target_user = '';
}
var min_age = $("#min-age").val();
api(
@@ -212,4 +287,20 @@ function set_custom_backup() {
});
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>

View File

@@ -34,19 +34,23 @@
font-family: monospace;
white-space: pre-wrap;
}
#system-privacy-setting {
float: right;
max-width: 20em;
margin-bottom: 1em;
}
</style>
<div class="row">
<div class="col-md-push-9 col-md-3">
<div id="system-reboot-required" style="display: none; margin-bottom: 1em;">
<button type="button" class="btn btn-danger" onclick="confirm_reboot(); return false;">Reboot Box</button>
<div>No reboot is necessary.</div>
</div>
<div id="system-privacy-setting" style="display: none">
<div><a onclick="return enable_privacy(!current_privacy_setting)" href="#"><span>Enable/Disable</span> New-Version Check</a></div>
<p style="line-height: 125%"><small>(When enabled, status checks phone-home to check for a new release of Mail-in-a-Box.)</small></p>
</div>
</div> <!-- /col -->
<div class="col-md-pull-3 col-md-8">
<table id="system-checks" class="table" style="max-width: 60em">
<thead>
@@ -55,6 +59,9 @@
</tbody>
</table>
</div> <!-- /col -->
</div> <!-- /row -->
<script>
function show_system_status() {
$('#system-checks tbody').html("<tr><td colspan='2' class='text-muted'>Loading...</td></tr>")
@@ -70,6 +77,16 @@ function show_system_status() {
$('#system-privacy-setting p').toggle(r);
});
api(
"/system/reboot",
"GET",
{ },
function(r) {
$('#system-reboot-required').show(); // show when r becomes available
$('#system-reboot-required').find('button').toggle(r);
$('#system-reboot-required').find('div').toggle(!r);
});
api(
"/system/status",
"POST",
@@ -122,4 +139,22 @@ function enable_privacy(status) {
});
return false; // disable link
}
function confirm_reboot() {
show_modal_confirm(
"Reboot",
$("<p>This will reboot your Mail-in-a-Box <code>{{hostname}}</code>.</p> <p>Until the machine is fully restarted, your users will not be able to send and receive email, and you will not be able to connect to this control panel or with SSH. The reboot cannot be cancelled.</p>"),
"Reboot Now",
function() {
api(
"/system/reboot",
"POST",
{ },
function(r) {
var msg = "<p>Please reload this page after a minute or so.</p>";
if (r) msg = "<p>The reboot command said:</p> <pre>" + $("<pre/>").text(r).html() + "</pre>"; // successful reboots don't produce any output; the output must be HTML-escaped
show_modal_error("Reboot", msg);
});
});
}
</script>

View File

@@ -31,7 +31,7 @@
<button type="submit" class="btn btn-primary">Add User</button>
</form>
<ul style="margin-top: 1em; padding-left: 1.5em; font-size: 90%;">
<li>Passwords must be at least four 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>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>
@@ -43,7 +43,6 @@
<tr>
<th width="50%">Email Address</th>
<th>Actions</th>
<th>Mailbox Size</th>
</tr>
</thead>
<tbody>
@@ -73,8 +72,6 @@
archive account
</a>
</td>
<td class='mailboxsize'>
</td>
</tr>
<tr id="user-extra-template" class="if_inactive">
<td colspan="3" style="border: 0; padding-top: 0">
@@ -84,6 +81,48 @@
</table>
</div>
<h3>Mail user API (advanced)</h3>
<p>Use your box&rsquo;s mail user API to add/change/remove users from the command-line or custom services you build.</p>
<p>Usage:</p>
<pre>curl -X <b>VERB</b> [-d "<b>parameters</b>"] --user {email}:{password} https://{{hostname}}/admin/mail/users[<b>action</b>]</pre>
<p>Brackets denote an optional argument. Please note that the POST body <code>parameters</code> must be URL-encoded.</p>
<p>The email and password given to the <code>--user</code> option must be an administrative user on this system.</p>
<h4 style="margin-bottom: 0">Verbs</h4>
<table class="table" style="margin-top: .5em">
<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>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>/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>
</table>
<h4>Examples:</h4>
<p>Try these examples. For simplicity the examples omit the <code>--user me@mydomain.com:yourpassword</code> command line argument which you must fill in with your administrative email address and password.</p>
<pre># Gives a JSON-encoded list of all mail users
curl -X GET https://{{hostname}}/admin/mail/users?format=json
# Adds a new email user
curl -X POST -d "email=new_user@mydomail.com" -d "password=s3curE_pa5Sw0rD" https://{{hostname}}/admin/mail/users/add
# Removes a email user
curl -X POST -d "email=new_user@mydomail.com" https://{{hostname}}/admin/mail/users/remove
# Adds admin privilege to an email user
curl -X POST -d "email=new_user@mydomail.com" -d "privilege=admin" https://{{hostname}}/admin/mail/users/privileges/add
# Removes admin privilege from an email user
curl -X POST -d "email=new_user@mydomail.com" https://{{hostname}}/admin/mail/users/privileges/remove
</pre>
<script>
function show_users() {
@@ -114,7 +153,6 @@ function show_users() {
n.attr('data-email', 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);
if (user.status == 'inactive') continue;
@@ -170,8 +208,8 @@ function users_set_password(elem) {
yourpw = "<p class='text-danger'>If you change your own password, you will be logged out of this control panel and will need to log in again.</p>";
show_modal_confirm(
"Archive User",
$("<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>"),
"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 eight characters and may not contain spaces.</small>" + yourpw + "</p>"),
"Set Password",
function() {
api(
@@ -254,7 +292,7 @@ function mod_priv(elem, add_remove) {
function generate_random_password() {
var pw = "";
var charset = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; // confusable characters skipped
for (var i = 0; i < 10; i++)
for (var i = 0; i < 12; i++)
pw += charset.charAt(Math.floor(Math.random() * charset.length));
show_modal_error("Random Password", "<p>Here, try this:</p> <p><code style='font-size: 110%'>" + pw + "</code></pr");
return false; // cancel click

View File

@@ -82,7 +82,7 @@ function show_change_web_root(elem) {
var root = $(elem).parents('tr').attr('data-custom-web-root');
show_modal_confirm(
'Change Root Directory for ' + domain,
$('<p>You can change the static directory for <tt>' + domain + '</tt> to:</p> <p><tt>' + root + '</tt></p> <p>First create this directory on the server. Then click Update to scan for the directory and update web settings.'),
$('<p>You can change the static directory for <tt>' + domain + '</tt> to:</p> <p><tt>' + root + '</tt></p> <p>First create this directory on the server. Then click Update to scan for the directory and update web settings.</p>'),
'Update',
function() { do_web_update(); });
}

View File

@@ -106,76 +106,6 @@ def sort_email_addresses(email_addresses, env):
ret.extend(sorted(email_addresses)) # whatever is left
return ret
def exclusive_process(name):
# Ensure that a process named `name` does not execute multiple
# times concurrently.
import os, sys, atexit
pidfile = '/var/run/mailinabox-%s.pid' % name
mypid = os.getpid()
# Attempt to get a lock on ourself so that the concurrency check
# itself is not executed in parallel.
with open(__file__, 'r+') as flock:
# Try to get a lock. This blocks until a lock is acquired. The
# lock is held until the flock file is closed at the end of the
# with block.
os.lockf(flock.fileno(), os.F_LOCK, 0)
# While we have a lock, look at the pid file. First attempt
# to write our pid to a pidfile if no file already exists there.
try:
with open(pidfile, 'x') as f:
# Successfully opened a new file. Since the file is new
# there is no concurrent process. Write our pid.
f.write(str(mypid))
atexit.register(clear_my_pid, pidfile)
return
except FileExistsError:
# The pid file already exixts, but it may contain a stale
# pid of a terminated process.
with open(pidfile, 'r+') as f:
# Read the pid in the file.
existing_pid = None
try:
existing_pid = int(f.read().strip())
except ValueError:
pass # No valid integer in the file.
# Check if the pid in it is valid.
if existing_pid:
if is_pid_valid(existing_pid):
print("Another %s is already running (pid %d)." % (name, existing_pid), file=sys.stderr)
sys.exit(1)
# Write our pid.
f.seek(0)
f.write(str(mypid))
f.truncate()
atexit.register(clear_my_pid, pidfile)
def clear_my_pid(pidfile):
import os
os.unlink(pidfile)
def is_pid_valid(pid):
"""Checks whether a pid is a valid process ID of a currently running process."""
# adapted from http://stackoverflow.com/questions/568271/how-to-check-if-there-exists-a-process-with-a-given-pid
import os, errno
if pid <= 0: raise ValueError('Invalid PID.')
try:
os.kill(pid, 0)
except OSError as err:
if err.errno == errno.ESRCH: # No such process
return False
elif err.errno == errno.EPERM: # Not permitted to send signal
return True
else: # EINVAL
raise
else:
return True
def shell(method, cmd_args, env={}, capture_stderr=False, return_bytes=False, trap=False, input=None):
# A safe way to execute processes.
# Some processes like apt-get require being given a sane PATH.

View File

@@ -9,7 +9,7 @@ from dns_update import get_custom_dns_config, get_dns_zones
from ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate
from utils import shell, safe_domain_name, sort_domains
def get_web_domains(env, include_www_redirects=True):
def get_web_domains(env, include_www_redirects=True, exclude_dns_elsewhere=True):
# What domains should we serve HTTP(S) for?
domains = set()
@@ -24,9 +24,16 @@ def get_web_domains(env, include_www_redirects=True):
# the topmost of each domain we serve.
domains |= set('www.' + zone for zone, zonefile in get_dns_zones(env))
# ...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.
domains -= get_domains_with_a_records(env)
if exclude_dns_elsewhere:
# ...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.
domains -= get_domains_with_a_records(env)
# Add Autoconfiguration domains, allowing us to serve correct SSL certs.
# 'autoconfig.' for Mozilla Thunderbird auto setup.
# 'autodiscover.' for Activesync autodiscovery.
domains |= set('autoconfig.' + maildomain for maildomain in get_mail_domains(env))
domains |= set('autodiscover.' + maildomain for maildomain in get_mail_domains(env))
# Ensure the PRIMARY_HOSTNAME is in the list so we can serve webmail
# as well as Z-Push for Exchange ActiveSync. This can't be removed
@@ -119,7 +126,7 @@ def make_domain_config(domain, templates, ssl_certificates, env):
root = get_web_root(domain, env)
# What private key and SSL certificate will we use for this domain?
ssl_key, ssl_certificate, ssl_via = get_domain_ssl_files(domain, ssl_certificates, env)
tls_cert = get_domain_ssl_files(domain, ssl_certificates, env)
# ADDITIONAL DIRECTIVES.
@@ -136,7 +143,7 @@ def make_domain_config(domain, templates, ssl_certificates, env):
finally:
f.close()
return sha1.hexdigest()
nginx_conf_extra += "# ssl files sha1: %s / %s\n" % (hashfile(ssl_key), hashfile(ssl_certificate))
nginx_conf_extra += "# ssl files sha1: %s / %s\n" % (hashfile(tls_cert["private-key"]), hashfile(tls_cert["certificate"]))
# Add in any user customizations in YAML format.
hsts = "yes"
@@ -148,7 +155,10 @@ def make_domain_config(domain, templates, ssl_certificates, env):
# any proxy or redirect here?
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, url in yaml.get("redirects", {}).items():
nginx_conf_extra += "\trewrite %s %s permanent;\n" % (path, url)
@@ -157,9 +167,9 @@ def make_domain_config(domain, templates, ssl_certificates, env):
# Add the HSTS header.
if hsts == "yes":
nginx_conf_extra += "add_header Strict-Transport-Security max-age=31536000;\n"
nginx_conf_extra += "add_header Strict-Transport-Security max-age=15768000;\n"
elif hsts == "preload":
nginx_conf_extra += "add_header Strict-Transport-Security \"max-age=10886400; includeSubDomains; preload\";\n"
nginx_conf_extra += "add_header Strict-Transport-Security \"max-age=15768000; includeSubDomains; preload\";\n"
# Add in any user customizations in the includes/ folder.
nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf")
@@ -177,8 +187,8 @@ def make_domain_config(domain, templates, ssl_certificates, env):
nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
nginx_conf = nginx_conf.replace("$ROOT", root)
nginx_conf = nginx_conf.replace("$SSL_KEY", ssl_key)
nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", ssl_certificate)
nginx_conf = nginx_conf.replace("$SSL_KEY", tls_cert["private-key"])
nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", tls_cert["certificate"])
nginx_conf = nginx_conf.replace("$REDIRECT_DOMAIN", re.sub(r"^www\.", "", domain)) # for default www redirects to parent domain
return nginx_conf
@@ -193,20 +203,18 @@ def get_web_root(domain, env, test_exists=True):
def get_web_domains_info(env):
www_redirects = set(get_web_domains(env)) - set(get_web_domains(env, include_www_redirects=False))
has_root_proxy_or_redirect = set(get_web_domains_with_root_overrides(env))
ssl_certificates = get_ssl_certificates(env)
# for the SSL config panel, get cert status
def check_cert(domain):
ssl_certificates = get_ssl_certificates(env)
x = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
if x is None: return ("danger", "No Certificate Installed")
ssl_key, ssl_certificate, ssl_via = x
cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key)
try:
tls_cert = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
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"])
if cert_status == "OK":
if not ssl_via:
return ("success", "Signed & valid. " + cert_status_details)
else:
# This is an alternate domain but using the same cert as the primary domain.
return ("success", "Signed & valid. " + ssl_via)
return ("success", "Signed & valid. " + cert_status_details)
elif cert_status == "SELF-SIGNED":
return ("warning", "Self-signed. Get a signed certificate to stop warnings.")
else:
@@ -221,4 +229,4 @@ def get_web_domains_info(env):
"static_enabled": domain not in (www_redirects | has_root_proxy_or_redirect),
}
for domain in get_web_domains(env)
]
]

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/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 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.
@@ -38,23 +38,16 @@ These services are protected by [TLS](https://en.wikipedia.org/wiki/Transport_La
The services all follow these rules:
* SSL 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).
* Export-grade ciphers, the anonymous DH/ECDH algorithms (aNULL), and clear-text ciphers (eNULL) are not offered.
* The minimum cipher key length offered is 112 bits. The maximum is 256 bits. Diffie-Hellman ciphers use a 2048-bit key for forward secrecy.
* 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).
* 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:
* SMTP Submission (port 587) will not accept user credentials without STARTTLS (true also of SMTP on port 25 in case of client misconfiguration), and the submission port won't accept mail without encryption. The minimum cipher key length is 128 bits. (The box is of course configured not to be an open relay. User credentials are required to send outbound mail.) ([source](setup/mail-postfix.sh))
* HTTPS (port 443): The HTTPS Strict Transport Security header is set. A redirect from HTTP to HTTPS is offered. The [Qualys SSL Labs test](https://www.ssllabs.com/ssltest) should report an A+ grade. ([source 1](conf/nginx-ssl.conf), [source 2](conf/nginx.conf))
For more details, see the [output of SSLyze for these ports](tests/tls_results.txt).
The cipher and protocol selection are chosen to support the following clients:
* For HTTPS: Firefox 1, Chrome 1, IE 7, Opera 5, Safari 1, Windows XP IE8, Android 2.3, Java 7.
* For other protocols: TBD.
### Password Storage
The passwords for mail users are stored on disk using the [SHA512-CRYPT](http://man7.org/linux/man-pages/man3/crypt.3.html) hashing scheme. ([source](management/mailconfig.py))
@@ -69,6 +62,16 @@ The [setup guide video](https://mailinabox.email/) explains how to verify the ho
If DNSSEC is enabled at the box's domain name's registrar, the SSHFP record that the box automatically puts into DNS can also be used to verify the host key fingerprint by setting `VerifyHostKeyDNS yes` in your `ssh/.config` file or by logging in with `ssh -o VerifyHostKeyDNS=yes`. ([source](management/dns_update.py))
### Brute-force attack mitigation
`fail2ban` provides some protection from brute-force login attacks (repeated logins that guess account passwords) by blocking offending IP addresses at the network level.
The following services are protected: SSH, IMAP (dovecot), SMTP submission (postfix), webmail (roundcube), Nextcloud/CalDAV/CardDAV (over HTTP), and the Mail-in-a-Box control panel & munin (over HTTP).
Some other services running on the box may be missing fail2ban filters.
`fail2ban` only blocks IPv4 addresses, however. If the box has a public IPv6 address, it is not protected from these attacks.
Outbound Mail
-------------
@@ -80,7 +83,7 @@ The first step in resolving the destination server for an email address is perfo
### Encryption
The box (along with the vast majority of mail servers) uses [opportunistic encryption](https://en.wikipedia.org/wiki/Opportunistic_encryption), meaning the mail is encrypted in transit and protected from passive eavesdropping, but it is not protected from an active man-in-the-middle attack. Modern encryption settings will be used to the extent the recipient server supports them. ([source](setup/mail-postfix.sh))
The box (along with the vast majority of mail servers) uses [opportunistic encryption](https://en.wikipedia.org/wiki/Opportunistic_encryption), meaning the mail is encrypted in transit and protected from passive eavesdropping, but it is not protected from an active man-in-the-middle attack. Modern encryption settings (TLSv1 and later, no RC4) will be used to the extent the recipient server supports them. ([source](setup/mail-postfix.sh))
### DANE
@@ -101,7 +104,7 @@ Incoming Mail
### Encryption
As discussed above, there is no way to require on-the-wire encryption of mail. When the box receives an incoming email (SMTP on port 25), it offers encryption (STARTTLS) but cannot require that senders use it because some senders may not support STARTTLS at all and other senders may support STARTTLS but not with the latest protocols/ciphers. To give senders the best chance at making use of encryption, the box offers protocols back to SSLv3 and ciphers with key lengths as low as 112 bits. Modern clients (senders) will make use of the 256-bit ciphers and Diffie-Hellman ciphers with a 2048-bit key for forward secrecy, however. ([source](setup/mail-postfix.sh))
As discussed above, there is no way to require on-the-wire encryption of mail. When the box receives an incoming email (SMTP on port 25), it offers encryption (STARTTLS) but cannot require that senders use it because some senders may not support STARTTLS at all and other senders may support STARTTLS but not with the latest protocols/ciphers. To give senders the best chance at making use of encryption, the box offers protocols back to TLSv1 and ciphers with key lengths as low as 112 bits. Modern clients (senders) will make use of the 256-bit ciphers and Diffie-Hellman ciphers with a 2048-bit key for perfect forward secrecy, however. ([source](setup/mail-postfix.sh))
### DANE

View File

@@ -2,12 +2,41 @@
#########################################################
# This script is intended to be run like this:
#
# curl https://.../bootstrap.sh | sudo bash
# curl https://mailinabox.email/setup.sh | sudo bash
#
#########################################################
if [ -z "$TAG" ]; then
TAG=v0.15
# 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.42
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
fi
fi
# Are we running as root?

View File

@@ -21,6 +21,11 @@ mkdir -p $STORAGE_ROOT/mail/dkim
# Not quite sure why.
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
true # already done #NODOC
else
@@ -31,7 +36,7 @@ ExternalIgnoreList refile:/etc/opendkim/TrustedHosts
InternalHosts refile:/etc/opendkim/TrustedHosts
KeyTable refile:/etc/opendkim/KeyTable
SigningTable refile:/etc/opendkim/SigningTable
Socket inet:8891@localhost
Socket inet:8891@127.0.0.1
RequireSafeKeys false
EOF
fi
@@ -39,7 +44,7 @@ fi
# Create a new DKIM key. This creates mail.private and mail.txt
# in $STORAGE_ROOT/mail/dkim. The former is the private key and
# the latter is the suggested DNS TXT entry which we'll include
# in our DNS setup. Note tha the files are named after the
# in our DNS setup. Note that the files are named after the
# 'selector' of the key, which we can change later on to support
# key rotation.
#
@@ -75,6 +80,9 @@ tools/editconf.py /etc/postfix/main.cf \
non_smtpd_milters=\$smtpd_milters \
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_service opendkim
restart_service opendmarc

View File

@@ -23,9 +23,10 @@ apt_install nsd ldnsutils openssh-client
mkdir -p /var/run/nsd
cat > /etc/nsd/nsd.conf << EOF;
# No not edit. Overwritten by Mail-in-a-Box setup.
# Do not edit. Overwritten by Mail-in-a-Box setup.
server:
hide-version: yes
logfile: "/var/log/nsd.log"
# identify the server (CH TXT ID.SERVER entry).
identity: ""
@@ -41,6 +42,18 @@ server:
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
# DNS queries that require a recursive nameserver, and the system
# 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.
# 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.
if [ -z "$NONINTERACTIVE" ]; then
if [ -z "${NONINTERACTIVE:-}" ]; then
input_box "Mail Account" \
"Let's create your first mail account.
\n\nWhat email address do you want?" \
@@ -35,7 +35,7 @@ if [ -z "`tools/mail.py user`" ]; then
else
# Use me@PRIMARY_HOSTNAME
EMAIL_ADDR=me@$PRIMARY_HOSTNAME
EMAIL_PW=1234
EMAIL_PW=12345678
echo
echo "Creating a new administrative mail account for $EMAIL_ADDR with password $EMAIL_PW."
echo
@@ -47,11 +47,11 @@ if [ -z "`tools/mail.py user`" ]; then
fi
# 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.
hide_output tools/mail.py user make-admin $EMAIL_ADDR
# Create an alias to which we'll direct all automatically-created administrative aliases.
tools/mail.py alias add administrator@$PRIMARY_HOSTNAME $EMAIL_ADDR > /dev/null
fi
fi

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 {
# This function hides the output of a command unless the command fails
# and returns a non-zero exit code.
@@ -5,11 +11,14 @@ function hide_output {
# Get a temporary file.
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
E=$?
set -e
# If the command failed, show the output that was captured in the temporary file.
E=$?
if [ $E != 0 ]; then
# Something failed.
echo
@@ -48,6 +57,15 @@ function apt_install {
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 {
# Guess the machine's hostname. It should be a fully qualified
# domain name suitable for DNS. None of these calls may provide
@@ -66,7 +84,7 @@ function get_publicip_from_web_service {
#
# Pass '4' or '6' as an argument to this function to specify
# 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 {
@@ -109,7 +127,7 @@ function get_default_privateip {
if [ "$1" == "6" ]; then target=2001:4860:4860::8888; fi
# 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.
address=$(echo $route | sed "s/.* src \([^ ]*\).*/\1/")
@@ -122,11 +140,10 @@ function get_default_privateip {
fi
echo $address
}
function ufw_allow {
if [ -z "$DISABLE_FIREWALL" ]; then
if [ -z "${DISABLE_FIREWALL:-}" ]; then
# ufw has completely unhelpful output
ufw allow $1 > /dev/null;
fi
@@ -145,10 +162,13 @@ function input_box {
# input_box "title" "prompt" "defaultvalue" VARIABLE
# The user's input will be stored in the variable VARIABLE.
# 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_code=$4_EXITCODE
set +e
result=$(dialog --stdout --title "$1" --inputbox "$2" 0 0 "$3")
result_code=$?
set -e
}
function input_menu {
@@ -158,8 +178,10 @@ function input_menu {
declare -n result=$4
declare -n result_code=$4_EXITCODE
local IFS=^$'\n'
set +e
result=$(dialog --stdout --title "$1" --menu "$2" 0 0 0 $3)
result_code=$?
set -e
}
function wget_verify {
@@ -170,7 +192,7 @@ function wget_verify {
DEST=$3
CHECKSUM="$HASH $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
echo "------------------------------------------------------------"
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)..."
apt_install \
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
# enable IMAP, POP and LMTP protocols.
@@ -37,8 +37,17 @@ apt_install \
# of active IMAP connections (at, say, 5 open connections per user that
# would be 20 users). Set it to 250 times the number of cores this
# machine has, so on a two-core machine that's 500 processes/100 users).
# The `default_vsz_limit` is the maximum amount of virtual memory that
# can be allocated. It should be set *reasonably high* to avoid allocation
# issues with larger mailboxes. We're setting it to 1/3 of the total
# available memory (physical mem + swap) to be sure.
# See here for discussion:
# - https://www.dovecot.org/list/dovecot/2012-August/137569.html
# - https://www.dovecot.org/list/dovecot/2011-December/132455.html
tools/editconf.py /etc/dovecot/conf.d/10-master.conf \
default_process_limit=$(echo "`nproc` * 250" | bc)
default_process_limit=$(echo "`nproc` * 250" | bc) \
default_vsz_limit=$(echo "`free -tm | tail -1 | awk '{print $2}'` / 3" | bc)M \
log_path=/var/log/mail.log
# The inotify `max_user_instances` default is 128, which constrains
# the total number of watched (IMAP IDLE push) folders by open connections.
@@ -70,12 +79,16 @@ tools/editconf.py /etc/dovecot/conf.d/10-auth.conf \
# 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/.
# Enable strong ssl dh parameters
tools/editconf.py /etc/dovecot/conf.d/10-ssl.conf \
ssl=required \
"ssl_cert=<$STORAGE_ROOT/ssl/ssl_certificate.pem" \
"ssl_key=<$STORAGE_ROOT/ssl/ssl_private_key.pem" \
"ssl_protocols=!SSLv3 !SSLv2" \
"ssl_cipher_list=TLSv1+HIGH !SSLv2 !RC4 !aNULL !eNULL !3DES @STRENGTH"
"ssl_protocols=!SSLv3" \
"ssl_cipher_list=ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS" \
"ssl_prefer_server_ciphers = yes" \
"ssl_dh_parameters_length = 2048"
# 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
@@ -100,17 +113,6 @@ tools/editconf.py /etc/dovecot/conf.d/20-imap.conf \
tools/editconf.py /etc/dovecot/conf.d/20-pop3.conf \
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)
# Enable Dovecot's LDA service with the LMTP protocol. It will listen
@@ -134,6 +136,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 {
mail_max_userip_connections = 20
}
@@ -203,5 +213,8 @@ chown -R mail.mail $STORAGE_ROOT/mail/sieve
ufw_allow imaps
ufw_allow pop3s
# Allow the Sieve port in the firewall.
ufw_allow sieve
# Restart services.
restart_service dovecot

View File

@@ -41,35 +41,40 @@ source /etc/mailinabox.conf # load global vars
# always will.
# * `ca-certificates`: A trust store used to squelch postfix warnings about
# 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)..."
apt_install postfix postfix-pcre postgrey ca-certificates
apt_install postfix postfix-sqlite postfix-pcre postgrey ca-certificates
# ### Basic Settings
# Set some basic settings...
#
# * Have postfix listen on all network interfaces.
# * Make outgoing connections on a particular interface (if multihomed) so that SPF passes on the receiving side.
# * Set our name (the Debian default seems to be "localhost" but make it our hostname).
# * Set the name of the local machine to localhost, which means xxx@localhost is delivered locally, although we don't use it.
# * Set the SMTP banner (which must have the hostname first, then anything).
tools/editconf.py /etc/postfix/main.cf \
inet_interfaces=all \
smtp_bind_address=$PRIVATE_IP \
smtp_bind_address6=$PRIVATE_IPV6 \
myhostname=$PRIMARY_HOSTNAME\
smtpd_banner="\$myhostname ESMTP Hi, I'm a Mail-in-a-Box (Ubuntu/Postfix; see https://mailinabox.email/)" \
mydestination=localhost
# Tweak some queue settings:
# * Inform users when their e-mail delivery is delayed more than 3 hours (default is not to warn).
# * Stop trying to send an undeliverable e-mail after 2 days (instead of 5), and for bounce messages just try for 1 day.
tools/editconf.py /etc/postfix/main.cf \
delay_warning_time=3h \
maximal_queue_lifetime=2d \
bounce_queue_lifetime=1d
# ### Outgoing Mail
# 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
# on incoming mail. Omit the OpenDMARC milter by re-setting smtpd_milters to the
# OpenDKIM milter only. See dkim.sh.
@@ -80,16 +85,19 @@ tools/editconf.py /etc/postfix/main.cf \
# * 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')
# that filters out privacy-sensitive headers on mail being sent out by
# authenticated users.
# authenticated users. By default Postfix also applies this to attached
# emails but we turn this off by setting nested_header_checks empty.
tools/editconf.py /etc/postfix/master.cf -s -w \
"submission=inet n - - - - smtpd
-o smtpd_sasl_auth_enable=yes
-o syslog_name=postfix/submission
-o smtpd_milters=inet:127.0.0.1:8891
-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" \
"authclean=unix n - - - 0 cleanup
-o header_checks=pcre:/etc/postfix/outgoing_mail_header_filters"
-o header_checks=pcre:/etc/postfix/outgoing_mail_header_filters
-o nested_header_checks="
# Install the `outgoing_mail_header_filters` file required by the new 'authclean' service.
cp conf/postfix_outgoing_mail_header_filters /etc/postfix/outgoing_mail_header_filters
@@ -111,8 +119,9 @@ tools/editconf.py /etc/postfix/main.cf \
smtpd_tls_cert_file=$STORAGE_ROOT/ssl/ssl_certificate.pem \
smtpd_tls_key_file=$STORAGE_ROOT/ssl/ssl_private_key.pem \
smtpd_tls_dh1024_param_file=$STORAGE_ROOT/ssl/dh2048.pem \
smtpd_tls_protocols=\!SSLv2,\!SSLv3 \
smtpd_tls_ciphers=medium \
smtpd_tls_exclude_ciphers=aNULL \
smtpd_tls_exclude_ciphers=aNULL,RC4 \
smtpd_tls_received_header=yes
# Prevent non-authenticated users from sending mail that requires being
@@ -140,13 +149,17 @@ tools/editconf.py /etc/postfix/main.cf \
# 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
# 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,
# which we don't care about seeing because Postfix is doing opportunistic TLS anyway. Better to encrypt,
# even if we don't know if it's to the right party, than to not encrypt at all. Instead we'll
# now see notices about trusted certs. The CA file is provided by the package `ca-certificates`.
tools/editconf.py /etc/postfix/main.cf \
smtp_tls_protocols=\!SSLv2,\!SSLv3 \
smtp_tls_mandatory_protocols=\!SSLv2,\!SSLv3 \
smtp_tls_ciphers=medium \
smtp_tls_exclude_ciphers=aNULL,RC4 \
smtp_tls_security_level=dane \
smtp_dns_support_level=dnssec \
smtp_tls_CAfile=/etc/ssl/certs/ca-certificates.crt \
@@ -160,8 +173,11 @@ tools/editconf.py /etc/postfix/main.cf \
#
# In a basic setup we would pass mail directly to Dovecot by setting
# virtual_transport to `lmtp:unix:private/dovecot-lmtp`.
#
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.
#

View File

@@ -38,17 +38,19 @@ passdb {
args = /etc/dovecot/dovecot-sql.conf.ext
}
userdb {
driver = static
args = uid=mail gid=mail home=$STORAGE_ROOT/mail/mailboxes/%d/%n
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}
EOF
# Configure the SQL to query for a user's password.
# Configure the SQL to query for a user's metadata and password.
cat > /etc/dovecot/dovecot-sql.conf.ext << EOF;
driver = sqlite
connect = $db_path
default_pass_scheme = SHA512-CRYPT
password_query = SELECT email as user, password FROM users WHERE email='%u';
user_query = SELECT email AS user, "mail" as uid, "mail" as gid, "$STORAGE_ROOT/mail/mailboxes/%d/%n" as home FROM users WHERE email='%u';
iterate_query = SELECT email AS user FROM users;
EOF
chmod 0600 /etc/dovecot/dovecot-sql.conf.ext # per Dovecot instructions
@@ -63,11 +65,15 @@ service auth {
}
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 \
smtpd_sasl_type=dovecot \
smtpd_sasl_path=private/auth \
smtpd_sasl_auth_enable=yes
smtpd_sasl_auth_enable=no
# ### Sender Validation

View File

@@ -4,16 +4,55 @@ source setup/functions.sh
echo "Installing Mail-in-a-Box system management daemon..."
# Switching python 2 boto to package manager's, not pypi's.
if [ -f /usr/local/lib/python2.7/dist-packages/boto/__init__.py ]; then hide_output pip uninstall -y boto; fi
# DEPENDENCIES
# duplicity uses python 2 so we need to use the python 2 package of boto
# build-essential libssl-dev libffi-dev python3-dev: Required to pip install cryptography.
apt_install python3-flask links duplicity python-boto libyaml-dev python3-dnspython python3-dateutil \
build-essential libssl-dev libffi-dev python3-dev python-pip
hide_output pip3 install --upgrade rtyaml "email_validator>=1.0.0" "idna>=2.0.0" "cryptography>=1.0.2" boto
# 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
# email_validator is repeated in setup/questions.sh
# 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
# 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.
# See #627, #653.
#
# 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
# Create a virtualenv for the installation of Python 3 packages
# used by the management daemon.
inst_dir=/usr/local/lib/mailinabox
mkdir -p $inst_dir
venv=$inst_dir/env
if [ ! -d $venv ]; then
hide_output virtualenv -ppython3 $venv
fi
# Upgrade pip because the Ubuntu-packaged version is out of date.
hide_output $venv/bin/pip install --upgrade pip
# Install other Python 3 packages used by the management daemon.
# 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.
hide_output $venv/bin/pip install --upgrade \
rtyaml "email_validator>=1.0.0" "exclusiveprocess" \
flask dnspython python-dateutil \
"idna>=2.0.0" "cryptography==2.2.2" boto psutil
# CONFIGURATION
# Create a backup directory and a random key for encrypting backups.
mkdir -p $STORAGE_ROOT/backup
@@ -21,19 +60,42 @@ if [ ! -f $STORAGE_ROOT/backup/secret_key.txt ]; then
$(umask 077; openssl rand -base64 2048 > $STORAGE_ROOT/backup/secret_key.txt)
fi
# Link the management server daemon into a well known location.
rm -f /usr/local/bin/mailinabox-daemon
ln -s `pwd`/management/daemon.py /usr/local/bin/mailinabox-daemon
# Download jQuery and Bootstrap local files
# Make sure we have the directory to save to.
assets_dir=$inst_dir/vendor/assets
rm -rf $assets_dir
mkdir -p $assets_dir
# jQuery CDN URL
jquery_version=2.1.4
jquery_url=https://code.jquery.com
# Get jQuery
wget_verify $jquery_url/jquery-$jquery_version.min.js 43dc554608df885a59ddeece1598c6ace434d747 $assets_dir/jquery.min.js
# Bootstrap CDN URL
bootstrap_version=3.3.7
bootstrap_url=https://github.com/twbs/bootstrap/releases/download/v$bootstrap_version/bootstrap-$bootstrap_version-dist.zip
# Get Bootstrap
wget_verify $bootstrap_url e6b1000b94e835ffd37f4c6dcbdad43f4b48a02a /tmp/bootstrap.zip
unzip -q /tmp/bootstrap.zip -d $assets_dir
mv $assets_dir/bootstrap-$bootstrap_version-dist $assets_dir/bootstrap
rm -f /tmp/bootstrap.zip
# Create an init script to start the management daemon and keep it
# running after a reboot.
rm -f /etc/init.d/mailinabox
ln -s $(pwd)/conf/management-initscript /etc/init.d/mailinabox
hide_output update-rc.d mailinabox defaults
# Remove old files we no longer use.
rm -f /etc/cron.daily/mailinabox-backup
rm -f /etc/cron.daily/mailinabox-statuschecks
cat > $inst_dir/start <<EOF;
#!/bin/bash
source $venv/bin/activate
exec python `pwd`/management/daemon.py
EOF
chmod +x $inst_dir/start
hide_output systemctl link -f conf/mailinabox.service
hide_output systemctl daemon-reload
hide_output systemctl enable mailinabox.service
# Perform nightly tasks at 3am in system time: take a backup, run
# status checks and email the administrator any changes.
@@ -44,5 +106,5 @@ cat > /etc/cron.d/mailinabox-nightly << EOF;
0 3 * * * root (cd `pwd` && management/daily_tasks.sh)
EOF
# Start it.
# Start the management server.
restart_service mailinabox

View File

@@ -137,6 +137,51 @@ def migration_10(env):
shutil.move(sslcert, newname)
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():
ver = 0
while True:

View File

@@ -7,7 +7,8 @@ source /etc/mailinabox.conf # load global vars
# install Munin
echo "Installing Munin (system monitoring)..."
apt_install munin munin-node
apt_install munin munin-node libcgi-fast-perl
# libcgi-fast-perl is needed by /usr/lib/munin/cgi/munin-cgi-graph
# edit config
cat > /etc/munin/munin.conf <<EOF;
@@ -19,22 +20,31 @@ tmpldir /etc/munin/templates
includedir /etc/munin/munin-conf.d
# path dynazoom uses for requests
cgiurl_graph /admin/munin/cgi-graph
# a simple host tree
[$PRIMARY_HOSTNAME]
address 127.0.0.1
# send alerts to the following address
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
EOF
# The Debian installer touches these files and chowns them to www-data:adm for use with spawn-fcgi
chown munin. /var/log/munin/munin-cgi-html.log
chown munin. /var/log/munin/munin-cgi-graph.log
# ensure munin-node knows the name of this machine
# and reduce logging level to warning
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.
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
# (which is taken care of my munin-node-configure, but only when we re-run it.)
@@ -51,6 +61,14 @@ done
# Create a 'state' directory. Not sure why we need to do this manually.
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
hide_output systemctl link -f conf/munin.service
hide_output systemctl daemon-reload
hide_output systemctl unmask munin.service
hide_output systemctl enable munin.service
# Restart services.
restart_service munin
restart_service munin-node
@@ -58,4 +76,8 @@ restart_service munin-node
# generate initial statistics so the directory isn't empty
# (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.)
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

308
setup/nextcloud.sh Executable file
View File

@@ -0,0 +1,308 @@
#!/bin/bash
# Nextcloud
##########################
source setup/functions.sh # load our functions
source /etc/mailinabox.conf # load global vars
# ### Installing Nextcloud
echo "Installing Nextcloud (contacts/calendar)..."
apt-get purge -qq -y owncloud* # we used to use the package manager
apt_install php php-fpm \
php-cli php-sqlite3 php-gd php-imap php-curl php-pear curl \
php-dev php-gd php-xml php-mbstring php-zip php-apcu php-json \
php-intl php-imagick
InstallNextcloud() {
version=$1
hash=$2
echo
echo "Upgrading to Nextcloud version $version"
echo
# Remove the current owncloud/Nextcloud
rm -rf /usr/local/lib/owncloud
# Download and verify
wget_verify https://download.nextcloud.com/server/releases/nextcloud-$version.zip $hash /tmp/nextcloud.zip
# Extract ownCloud/Nextcloud
unzip -q /tmp/nextcloud.zip -d /usr/local/lib
mv /usr/local/lib/nextcloud /usr/local/lib/owncloud
rm -f /tmp/nextcloud.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/nextcloud/contacts/releases/download/v3.1.1/contacts.tar.gz a06bd967197dcb03c94ec1dbd698c037018669e5 /tmp/contacts.tgz
tar xf /tmp/contacts.tgz -C /usr/local/lib/owncloud/apps/
rm /tmp/contacts.tgz
wget_verify https://github.com/nextcloud/calendar/releases/download/v1.6.5/calendar.tar.gz 79941255521a5172f7e4ce42dc7773838b5ede2f /tmp/calendar.tgz
tar xf /tmp/calendar.tgz -C /usr/local/lib/owncloud/apps/
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.
wget_verify https://github.com/nextcloud/user_external/releases/download/v0.6.3/user_external-0.6.3.tar.gz 0f756d35fef6b64a177d6a16020486b76ea5799c /tmp/user_external.tgz
tar -xf /tmp/user_external.tgz -C /usr/local/lib/owncloud/apps/
rm /tmp/user_external.tgz
# 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 || /bin/true
# If this isn't a new installation, immediately run the upgrade script.
# Then check for success (0=ok and 3=no upgrade needed, both are success).
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 php /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 php /usr/local/lib/owncloud/occ upgrade
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off
echo "...which seemed to work."
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
}
nextcloud_ver=15.0.8
nextcloud_hash=4129d8d4021c435f2e86876225fb7f15adf764a3
# Check if Nextcloud dir exist, and check if version matches nextcloud_ver (if either doesn't - install/upgrade)
if [ ! -d /usr/local/lib/owncloud/ ] \
|| ! grep -q $nextcloud_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.
service php7.2-fpm stop &> /dev/null || /bin/true
# Backup the existing ownCloud/Nextcloud.
# Create a backup directory to store the current installation and database to
BACKUP_DIRECTORY=$STORAGE_ROOT/owncloud-backup/`date +"%Y-%m-%d-%T"`
mkdir -p "$BACKUP_DIRECTORY"
if [ -d /usr/local/lib/owncloud/ ]; then
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"
fi
if [ -e $STORAGE_ROOT/owncloud/owncloud.db ]; then
cp $STORAGE_ROOT/owncloud/owncloud.db $BACKUP_DIRECTORY
fi
if [ -e $STORAGE_ROOT/owncloud/config.php ]; then
cp $STORAGE_ROOT/owncloud/config.php $BACKUP_DIRECTORY
fi
# If ownCloud or Nextcloud was previously installed....
if [ -e /usr/local/lib/owncloud/version.php ]; then
# Database migrations from ownCloud are no longer possible because ownCloud cannot be run under
# PHP 7.
if grep -q "OC_VersionString = '[89]\." /usr/local/lib/owncloud/version.php; then
echo "Upgrades from Mail-in-a-Box prior to v0.28 (dated July 30, 2018) with Nextcloud < 13.0.6 (you have ownCloud 8 or 9) are not supported. Upgrade to Mail-in-a-Box version v0.30 first. Setup aborting."
exit 1
fi
if grep -q "OC_VersionString = '1[012]\." /usr/local/lib/owncloud/version.php; then
echo "Upgrades from Mail-in-a-Box prior to v0.28 (dated July 30, 2018) with Nextcloud < 13.0.6 (you have ownCloud 10, 11 or 12) are not supported. Upgrade to Mail-in-a-Box version v0.30 first. Setup aborting."
exit 1
fi
# During the upgrade from Nextcloud 14 to 15, user_external may cause the upgrade to fail.
# We will disable it here before the upgrade and install it again after the upgrade.
if grep -q "OC_VersionString = '14\." /usr/local/lib/owncloud/version.php; then
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:disable user_external
fi
fi
InstallNextcloud $nextcloud_ver $nextcloud_hash
fi
# ### Configuring Nextcloud
# Setup Nextcloud if the Nextcloud database does not yet exist. Running setup when
# the database does exist wipes the database and user data.
if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
# Create user data directory
mkdir -p $STORAGE_ROOT/owncloud
# Create an initial configuration file.
instanceid=oc$(echo $PRIMARY_HOSTNAME | sha1sum | fold -w 10 | head -n 1)
cat > $STORAGE_ROOT/owncloud/config.php <<EOF;
<?php
\$CONFIG = array (
'datadirectory' => '$STORAGE_ROOT/owncloud',
'instanceid' => '$instanceid',
'forcessl' => true, # if unset/false, Nextcloud sends a HSTS=0 header, which conflicts with nginx config
'overwritewebroot' => '/cloud',
'overwrite.cli.url' => '/cloud',
'user_backends' => array(
array(
'class' => 'OC_User_IMAP',
'arguments' => array(
'127.0.0.1', 143, null
),
),
),
'memcache.local' => '\OC\Memcache\APCu',
'mail_smtpmode' => 'sendmail',
'mail_smtpsecure' => '',
'mail_smtpauthtype' => 'LOGIN',
'mail_smtpauth' => false,
'mail_smtphost' => '',
'mail_smtpport' => '',
'mail_smtpname' => '',
'mail_smtppassword' => '',
'mail_from_address' => 'owncloud',
);
?>
EOF
# Create an auto-configuration file to fill in database settings
# when the install script is run. Make an administrator account
# here or else the install can't finish.
adminpassword=$(dd if=/dev/urandom bs=1 count=40 2>/dev/null | sha1sum | fold -w 30 | head -n 1)
cat > /usr/local/lib/owncloud/config/autoconfig.php <<EOF;
<?php
\$AUTOCONFIG = array (
# storage/database
'directory' => '$STORAGE_ROOT/owncloud',
'dbtype' => 'sqlite3',
# create an administrator account with a random password so that
# the user does not have to enter anything on first load of Nextcloud
'adminlogin' => 'root',
'adminpass' => '$adminpassword',
);
?>
EOF
# Set permissions
chown -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
# Execute Nextcloud's setup step, which creates the Nextcloud sqlite database.
# It also wipes it if it exists. And it updates config.php with database
# settings and deletes the autoconfig.php file.
(cd /usr/local/lib/owncloud; sudo -u www-data php /usr/local/lib/owncloud/index.php;)
fi
# Update config.php.
# * trusted_domains is reset to localhost by autoconfig starting with ownCloud 8.1.1,
# so set it here. It also can change if the box's PRIMARY_HOSTNAME changes, so
# this will make sure it has the right value.
# * Some settings weren't included in previous versions of Mail-in-a-Box.
# * We need to set the timezone to the system timezone to allow fail2ban to ban
# users within the proper timeframe
# * We need to set the logdateformat to something that will work correctly with fail2ban
# * mail_domain' needs to be set every time we run the setup. Making sure we are setting
# the correct domain name if the domain is being change from the previous setup.
# Use PHP to read the settings file, modify it, and write out the new settings array.
TIMEZONE=$(cat /etc/timezone)
CONFIG_TEMP=$(/bin/mktemp)
php <<EOF > $CONFIG_TEMP && mv $CONFIG_TEMP $STORAGE_ROOT/owncloud/config.php;
<?php
include("$STORAGE_ROOT/owncloud/config.php");
\$CONFIG['trusted_domains'] = array('$PRIMARY_HOSTNAME');
\$CONFIG['memcache.local'] = '\OC\Memcache\APCu';
\$CONFIG['overwrite.cli.url'] = '/cloud';
\$CONFIG['mail_from_address'] = 'administrator'; # just the local part, matches our master administrator address
\$CONFIG['logtimezone'] = '$TIMEZONE';
\$CONFIG['logdateformat'] = 'Y-m-d H:i:s';
\$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 = ";
var_export(\$CONFIG);
echo ";";
?>
EOF
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
# Enable/disable apps. Note that this must be done after the Nextcloud setup.
# The firstrunwizard gave Josh all sorts of problems, so disabling that.
# user_external is what allows Nextcloud to use IMAP for login. The contacts
# and calendar apps are the extensions we really care about here.
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:disable firstrunwizard
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable user_external
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable contacts
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable calendar
# When upgrading, run the upgrade script again now that apps are enabled. It seems like
# the first upgrade at the top won't work because apps may be disabled during upgrade?
# Check for success (0=ok, 3=no upgrade needed).
sudo -u www-data php /usr/local/lib/owncloud/occ upgrade
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
# Set PHP FPM values to support large file uploads
# (semicolon is the comment character in this file, hashes produce deprecation warnings)
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
upload_max_filesize=16G \
post_max_size=16G \
output_buffering=16384 \
memory_limit=512M \
max_execution_time=600 \
short_open_tag=On
# Set Nextcloud recommended opcache settings
tools/editconf.py /etc/php/7.2/cli/conf.d/10-opcache.ini -c ';' \
opcache.enable=1 \
opcache.enable_cli=1 \
opcache.interned_strings_buffer=8 \
opcache.max_accelerated_files=10000 \
opcache.memory_consumption=128 \
opcache.save_comments=1 \
opcache.revalidate_freq=1
# Configure the path environment for php-fpm
tools/editconf.py /etc/php/7.2/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 grep -q apc.enabled=0 /etc/php/7.2/mods-available/apcu.ini; then
tools/editconf.py /etc/php/7.2/mods-available/apcu.ini -c ';' \
apc.enabled=1
fi
# Set up a cron job for Nextcloud.
cat > /etc/cron.hourly/mailinabox-owncloud << EOF;
#!/bin/bash
# Mail-in-a-Box
sudo -u www-data php -f /usr/local/lib/owncloud/cron.php
EOF
chmod +x /etc/cron.hourly/mailinabox-owncloud
# 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.
# But if we wanted to, we would do this:
# ```
# for user in $(tools/mail.py user admins); do
# sqlite3 $STORAGE_ROOT/owncloud/owncloud.db "INSERT OR IGNORE INTO oc_group_user VALUES ('admin', '$user')"
# done
# ```
# Enable PHP modules and restart PHP.
restart_service php7.2-fpm

View File

@@ -1,229 +0,0 @@
#!/bin/bash
# Owncloud
##########################
source setup/functions.sh # load our functions
source /etc/mailinabox.conf # load global vars
# ### Installing ownCloud
echo "Installing ownCloud (contacts/calendar)..."
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 unzip
apt-get purge -qq -y owncloud*
# Install ownCloud from source of this version:
owncloud_ver=8.1.1
owncloud_hash=34077e78575a3e689825a00964ee37fbf83fbdda
# 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
# Check if ownCloud 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
# Download and verify
wget_verify https://download.owncloud.org/community/owncloud-$owncloud_ver.zip $owncloud_hash /tmp/owncloud.zip
# Clear out the existing ownCloud.
if [ -d /usr/local/lib/owncloud/ ]; then
echo "upgrading ownCloud to $owncloud_ver (backing up existing ownCloud directory to /tmp/owncloud-backup-$$)..."
mv /usr/local/lib/owncloud /tmp/owncloud-backup-$$
fi
# Extract ownCloud
unzip -u -o -q /tmp/owncloud.zip -d /usr/local/lib #either extracts new or replaces current files
rm -f /tmp/owncloud.zip
# The two apps we actually want are not in ownCloud core. Clone them from
# their github repositories.
mkdir -p /usr/local/lib/owncloud/apps
git_clone https://github.com/owncloud/contacts 4ff855e7c2075309041bead09fbb9eb7df678244 '' /usr/local/lib/owncloud/apps/contacts
git_clone https://github.com/owncloud/calendar ec53139b144c0f842c33813305612e8006c42ea5 '' /usr/local/lib/owncloud/apps/calendar
# 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 [ -f $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 php /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 php /usr/local/lib/owncloud/occ upgrade
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off
echo "...which seemed to work."
fi
fi
fi
# ### Configuring ownCloud
# Setup ownCloud if the ownCloud database does not yet exist. Running setup when
# the database does exist wipes the database and user data.
if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
# Create user data directory
mkdir -p $STORAGE_ROOT/owncloud
# Create an initial configuration file.
TIMEZONE=$(cat /etc/timezone)
instanceid=oc$(echo $PRIMARY_HOSTNAME | sha1sum | fold -w 10 | head -n 1)
cat > $STORAGE_ROOT/owncloud/config.php <<EOF;
<?php
\$CONFIG = array (
'datadirectory' => '$STORAGE_ROOT/owncloud',
'instanceid' => '$instanceid',
'forcessl' => true, # if unset/false, ownCloud sends a HSTS=0 header, which conflicts with nginx config
'overwritewebroot' => '/cloud',
'overwrite.cli.url' => '/cloud',
'user_backends' => array(
array(
'class'=>'OC_User_IMAP',
'arguments'=>array('{localhost:993/imap/ssl/novalidate-cert}')
)
),
'memcache.local' => '\\OC\\Memcache\\Memcached',
"memcached_servers" => array (
array('localhost', 11211),
),
'mail_smtpmode' => 'sendmail',
'mail_smtpsecure' => '',
'mail_smtpauthtype' => 'LOGIN',
'mail_smtpauth' => false,
'mail_smtphost' => '',
'mail_smtpport' => '',
'mail_smtpname' => '',
'mail_smtppassword' => '',
'mail_from_address' => 'owncloud',
'mail_domain' => '$PRIMARY_HOSTNAME',
'logtimezone' => '$TIMEZONE',
);
?>
EOF
# Create an auto-configuration file to fill in database settings
# when the install script is run. Make an administrator account
# here or else the install can't finish.
adminpassword=$(dd if=/dev/urandom bs=1 count=40 2>/dev/null | sha1sum | fold -w 30 | head -n 1)
cat > /usr/local/lib/owncloud/config/autoconfig.php <<EOF;
<?php
\$AUTOCONFIG = array (
# storage/database
'directory' => '$STORAGE_ROOT/owncloud',
'dbtype' => 'sqlite3',
# create an administrator account with a random password so that
# the user does not have to enter anything on first load of ownCloud
'adminlogin' => 'root',
'adminpass' => '$adminpassword',
);
?>
EOF
# Set permissions
chown -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
# Execute ownCloud's setup step, which creates the ownCloud sqlite database.
# It also wipes it if it exists. And it updates config.php with database
# settings and deletes the autoconfig.php file.
(cd /usr/local/lib/owncloud; sudo -u www-data php /usr/local/lib/owncloud/index.php;)
fi
# Update config.php.
# * trusted_domains is reset to localhost by autoconfig starting with ownCloud 8.1.1,
# so set it here. It also can change if the box's PRIMARY_HOSTNAME changes, so
# this will make sure it has the right value.
# * Some settings weren't included in previous versions of Mail-in-a-Box.
# Use PHP to read the settings file, modify it, and write out the new settings array.
CONFIG_TEMP=$(/bin/mktemp)
php <<EOF > $CONFIG_TEMP && mv $CONFIG_TEMP $STORAGE_ROOT/owncloud/config.php;
<?php
include("$STORAGE_ROOT/owncloud/config.php");
\$CONFIG['trusted_domains'] = array('$PRIMARY_HOSTNAME');
\$CONFIG['memcache.local'] = '\\OC\\Memcache\\Memcached';
\$CONFIG['overwrite.cli.url'] = '/cloud';
\$CONFIG['mail_from_address'] = 'administrator'; # just the local part, matches our master administrator address
echo "<?php\n\\\$CONFIG = ";
var_export(\$CONFIG);
echo ";";
?>
EOF
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
# Enable/disable apps. Note that this must be done after the ownCloud setup.
# The firstrunwizard gave Josh all sorts of problems, so disabling that.
# user_external is what allows ownCloud to use IMAP for login. The contacts
# and calendar apps are the extensions we really care about here.
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:disable firstrunwizard
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable user_external
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable contacts
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable calendar
# When upgrading, run the upgrade script again now that apps are enabled. It seems like
# the first upgrade at the top won't work because apps may be disabled during upgrade?
# Check for success (0=ok, 3=no upgrade needed).
sudo -u www-data php /usr/local/lib/owncloud/occ upgrade
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
# Set PHP FPM values to support large file uploads
# (semicolon is the comment character in this file, hashes produce deprecation warnings)
tools/editconf.py /etc/php5/fpm/php.ini -c ';' \
upload_max_filesize=16G \
post_max_size=16G \
output_buffering=16384 \
memory_limit=512M \
max_execution_time=600 \
short_open_tag=On
# Set up a cron job for owncloud.
cat > /etc/cron.hourly/mailinabox-owncloud << EOF;
#!/bin/bash
# Mail-in-a-Box
sudo -u www-data php -f /usr/local/lib/owncloud/cron.php
EOF
chmod +x /etc/cron.hourly/mailinabox-owncloud
# There's nothing much of interest that a user could do as an admin for ownCloud,
# and there's a lot they could mess up, so we don't make any users admins of ownCloud.
# But if we wanted to, we would do this:
# ```
# for user in $(tools/mail.py user admins); do
# sqlite3 $STORAGE_ROOT/owncloud/owncloud.db "INSERT OR IGNORE INTO oc_group_user VALUES ('admin', '$user')"
# done
# ```
# Enable PHP modules and restart PHP.
php5enmod imap
restart_service php5-fpm

View File

@@ -7,9 +7,9 @@ if [[ $EUID -ne 0 ]]; then
exit
fi
# Check that we are running on Ubuntu 14.04 LTS (or 14.04.xx).
if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" != "Ubuntu 14.04 LTS" ]; then
echo "Mail-in-a-Box only supports being installed on Ubuntu 14.04, sorry. You are running:"
# Check that we are running on Ubuntu 18.04 LTS (or 18.04.xx).
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 18.04, sorry. You are running:"
echo
lsb_release -d | sed 's/.*:\s*//'
echo
@@ -19,17 +19,48 @@ fi
# Check that we have enough memory.
#
# /proc/meminfo reports free memory in kibibytes. Our baseline will be 768 MB,
# which is 750000 kibibytes.
# /proc/meminfo reports free memory in kibibytes. Our baseline will be 512 MB,
# which is 500000 kibibytes.
#
# We will display a warning if the memory is below 768 MB which is 750000 kibibytes
#
# 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}')
if [ $TOTAL_PHYSICAL_MEM -lt 750000 ]; then
if [ $TOTAL_PHYSICAL_MEM -lt 490000 ]; then
if [ ! -d /vagrant ]; then
TOTAL_PHYSICAL_MEM=$(expr \( \( $TOTAL_PHYSICAL_MEM \* 1024 \) / 1000 \) / 1000)
echo "Your Mail-in-a-Box needs more memory (RAM) to function properly."
echo "Please provision a machine with at least 768 MB, 1 GB recommended."
echo "Please provision a machine with at least 512 MB, 1 GB recommended."
echo "This machine has $TOTAL_PHYSICAL_MEM MB memory."
exit
fi
fi
if [ $TOTAL_PHYSICAL_MEM -lt 750000 ]; then
echo "WARNING: Your Mail-in-a-Box has less than 768 MB of memory."
echo " It might run unreliably when under heavy load."
fi
# Check that tempfs is mounted with exec
MOUNTED_TMP_AS_NO_EXEC=$(grep "/tmp.*noexec" /proc/mounts || /bin/true)
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"
exit
fi
# Check that no .wgetrc exists
if [ -e ~/.wgetrc ]; then
echo "Mail-in-a-Box expects no overrides to wget defaults, ~/.wgetrc exists"
exit
fi
# Check that we are running on x86_64 or i686 architecture, which are the only
# ones we support / test.
ARCHITECTURE=$(uname -m)
if [ "$ARCHITECTURE" != "x86_64" ] && [ "$ARCHITECTURE" != "i686" ]; then
echo
echo "WARNING:"
echo "Mail-in-a-Box has only been tested on x86_64 and i686 platform"
echo "architectures. Your architecture, $ARCHITECTURE, may not work."
echo "You are on your own."
echo
fi

View File

@@ -1,29 +1,32 @@
if [ -z "$NONINTERACTIVE" ]; then
if [ -z "${NONINTERACTIVE:-}" ]; then
# 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,
# e.g. if we piped a bootstrapping install script to bash to get started. In that
# case, the nifty '[ -t 0 ]' test won't work. But with Vagrant we must suppress so we
# use a shell flag instead. Really supress any output from installing dialog.
# use a shell flag instead. Really suppress any output from installing dialog.
#
# Also install depencies needed to validate the email address.
# Also install dependencies needed to validate the email address.
if [ ! -f /usr/bin/dialog ] || [ ! -f /usr/bin/python3 ] || [ ! -f /usr/bin/pip3 ]; then
echo Installing packages needed for setup...
apt-get -q -q update
apt_get_quiet install dialog python3 python3-pip || exit 1
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
message_box "Mail-in-a-Box Installation" \
"Hello and thanks for deploying a Mail-in-a-Box!
\n\nI'm going to ask you a few questions.
\n\nTo change your answers later, just run 'sudo mailinabox' from the command line."
\n\nTo change your answers later, just run 'sudo mailinabox' from the command line.
\n\nNOTE: You should only install this on a brand new Ubuntu installation 100% dedicated to Mail-in-a-Box. Mail-in-a-Box will, for example, remove apache2."
fi
# The box needs a name.
if [ -z "$PRIMARY_HOSTNAME" ]; then
if [ -z "$DEFAULT_PRIMARY_HOSTNAME" ]; then
if [ -z "${PRIMARY_HOSTNAME:-}" ]; then
if [ -z "${DEFAULT_PRIMARY_HOSTNAME:-}" ]; then
# We recommend to use box.example.com as this hosts name. The
# domain the user possibly wants to use is example.com then.
# We strip the string "box." from the hostname to get the mail
@@ -48,7 +51,7 @@ you really want.
# user hit ESC/cancel
exit
fi
while ! management/mailconfig.py validate-email "$EMAIL_ADDR"
while ! python3 management/mailconfig.py validate-email "$EMAIL_ADDR"
do
input_box "Your Email Address" \
"That's not a valid email address.\n\nWhat email address are you setting this box up to manage?" \
@@ -83,30 +86,30 @@ fi
# 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
# and possibly confirm with user.
if [ -z "$PUBLIC_IP" ]; then
if [ -z "${PUBLIC_IP:-}" ]; then
# Ask the Internet.
GUESSED_IP=$(get_publicip_from_web_service 4)
# On the first run, if we got an answer from the Internet then don't
# 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
# 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)
# On later runs, if the previous value matches the guessed value then
# don't ask the user either.
elif [ "$DEFAULT_PUBLIC_IP" == "$GUESSED_IP" ]; then
elif [ "${DEFAULT_PUBLIC_IP:-}" == "$GUESSED_IP" ]; then
PUBLIC_IP=$GUESSED_IP
fi
if [ -z "$PUBLIC_IP" ]; then
if [ -z "${PUBLIC_IP:-}" ]; then
input_box "Public IP Address" \
"Enter the public IP address of this machine, as given to you by your ISP.
\n\nPublic IP address:" \
$DEFAULT_PUBLIC_IP \
${DEFAULT_PUBLIC_IP:-} \
PUBLIC_IP
if [ -z "$PUBLIC_IP" ]; then
@@ -118,27 +121,27 @@ fi
# Same for IPv6. But it's optional. Also, if it looks like the system
# doesn't have an IPv6, don't ask for one.
if [ -z "$PUBLIC_IPV6" ]; then
if [ -z "${PUBLIC_IPV6:-}" ]; then
# Ask the Internet.
GUESSED_IP=$(get_publicip_from_web_service 6)
MATCHED=0
if [[ -z "$DEFAULT_PUBLIC_IPV6" && ! -z "$GUESSED_IP" ]]; then
if [[ -z "${DEFAULT_PUBLIC_IPV6:-}" && ! -z "$GUESSED_IP" ]]; then
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
# the user entered matches what the Internet tells us.
PUBLIC_IPV6=$GUESSED_IP
MATCHED=1
elif [[ -z "$DEFAULT_PUBLIC_IPV6" ]]; then
elif [[ -z "${DEFAULT_PUBLIC_IPV6:-}" ]]; then
DEFAULT_PUBLIC_IP=$(get_default_privateip 6)
fi
if [[ -z "$PUBLIC_IPV6" && $MATCHED == 0 ]]; then
if [[ -z "${PUBLIC_IPV6:-}" && $MATCHED == 0 ]]; then
input_box "IPv6 Address (Optional)" \
"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\nPublic IPv6 address:" \
$DEFAULT_PUBLIC_IPV6 \
${DEFAULT_PUBLIC_IPV6:-} \
PUBLIC_IPV6
if [ ! $PUBLIC_IPV6_EXITCODE ]; then
@@ -151,10 +154,10 @@ fi
# 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
# 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)
fi
if [ -z "$PRIVATE_IPV6" ]; then
if [ -z "${PRIVATE_IPV6:-}" ]; then
PRIVATE_IPV6=$(get_default_privateip 6)
fi
if [[ -z "$PRIVATE_IP" && -z "$PRIVATE_IPV6" ]]; then
@@ -179,18 +182,15 @@ if [ "$PUBLIC_IPV6" = "auto" ]; then
fi
if [ "$PRIMARY_HOSTNAME" = "auto" ]; then
PRIMARY_HOSTNAME=$(get_default_hostname)
elif [ "$PRIMARY_HOSTNAME" = "auto-easy" ]; then
# Generate a probably-unique subdomain under our justtesting.email domain.
PRIMARY_HOSTNAME=`echo $PUBLIC_IP | sha1sum | cut -c1-5`.justtesting.email
fi
# 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.
if [ -z "$STORAGE_USER" ]; then
STORAGE_USER=$([[ -z "$DEFAULT_STORAGE_USER" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER")
if [ -z "${STORAGE_USER:-}" ]; then
STORAGE_USER=$([[ -z "${DEFAULT_STORAGE_USER:-}" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER")
fi
if [ -z "$STORAGE_ROOT" ]; then
STORAGE_ROOT=$([[ -z "$DEFAULT_STORAGE_ROOT" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT")
if [ -z "${STORAGE_ROOT:-}" ]; then
STORAGE_ROOT=$([[ -z "${DEFAULT_STORAGE_ROOT:-}" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT")
fi
# Show the configuration, since the user may have not entered it manually.

View File

@@ -48,7 +48,7 @@ echo "public.pyzor.org:24441" > /etc/spamassassin/pyzor/servers
# * Disable localmode so Pyzor, DKIM and DNS checks can be used.
tools/editconf.py /etc/default/spampd \
DESTPORT=10026 \
ADDOPTS="\"--maxsize=500\"" \
ADDOPTS="\"--maxsize=2000\"" \
LOCALONLY=0
# Spamassassin normally wraps spam as an attachment inside a fresh
@@ -61,9 +61,11 @@ tools/editconf.py /etc/default/spampd \
# content or execute scripts, and it is probably confusing to most users.
#
# Tell Spamassassin not to modify the original message except for adding
# the X-Spam-Status mail header and related headers.
# the X-Spam-Status & X-Spam-Score mail headers and related headers.
tools/editconf.py /etc/spamassassin/local.cf -s \
report_safe=0
report_safe=0 \
add_header="all Report _REPORT_" \
add_header="all Score _SCORE_"
# Bayesean learning
# -----------------
@@ -78,9 +80,13 @@ tools/editconf.py /etc/spamassassin/local.cf -s \
# * Writable by the debian-spamd user, which runs /etc/cron.daily/spamassassin.
#
# We'll have these files owned by spampd and grant access to the other two processes.
#
# Spamassassin will change the access rights back to the defaults, so we must also configure
# the filemode in the config file.
tools/editconf.py /etc/spamassassin/local.cf -s \
bayes_path=$STORAGE_ROOT/mail/spamassassin/bayes
bayes_path=$STORAGE_ROOT/mail/spamassassin/bayes \
bayes_file_mode=0666
mkdir -p $STORAGE_ROOT/mail/spamassassin
chown -R spampd:spampd $STORAGE_ROOT/mail/spamassassin

View File

@@ -74,7 +74,7 @@ if [ ! -f $STORAGE_ROOT/ssl/ssl_certificate.pem ]; then
CSR=/tmp/ssl_cert_sign_req-$$.csr
hide_output \
openssl req -new -key $STORAGE_ROOT/ssl/ssl_private_key.pem -out $CSR \
-sha256 -subj "/C=/ST=/L=/O=/CN=$PRIMARY_HOSTNAME"
-sha256 -subj "/CN=$PRIMARY_HOSTNAME"
# Generate the self-signed certificate.
CERT=$STORAGE_ROOT/ssl/$PRIMARY_HOSTNAME-selfsigned-$(date --rfc-3339=date | sed s/-//g).pem

View File

@@ -4,16 +4,17 @@
source setup/functions.sh # load our functions
# Check system setup: Are we running as root on Ubuntu 14.04 on a
# machine with enough memory? If not, this shows an error and exits.
# Check system setup: Are we running as root on Ubuntu 18.04 on a
# machine with enough memory? Is /tmp mounted with exec.
# If not, this shows an error and exits.
source setup/preflight.sh
# 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. Here and in
# the management daemon startup script.
# Python may not be able to read/write files. This is also
# 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
hide_output locale-gen en_US.UTF-8
fi
@@ -23,6 +24,9 @@ export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
export LC_TYPE=en_US.UTF-8
# Fix so line drawing characters are shown correctly in Putty on Windows. See #744.
export NCURSES_NO_UTF8_ACS=1
# Recall the last settings used if we're running this a second time.
if [ -f /etc/mailinabox.conf ]; then
# Run any system migrations before proceeding. Since this is a second run,
@@ -56,8 +60,8 @@ source setup/questions.sh
# 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
# upgrade, and these checks are also in the control panel status checks.
if [ -z "$DEFAULT_PRIMARY_HOSTNAME" ]; then
if [ -z "$SKIP_NETWORK_CHECKS" ]; then
if [ -z "${DEFAULT_PRIMARY_HOSTNAME:-}" ]; then
if [ -z "${SKIP_NETWORK_CHECKS:-}" ]; then
source setup/network-checks.sh
fi
fi
@@ -102,23 +106,44 @@ source setup/dkim.sh
source setup/spamassassin.sh
source setup/web.sh
source setup/webmail.sh
source setup/owncloud.sh
source setup/nextcloud.sh
source setup/zpush.sh
source setup/management.sh
source setup/munin.sh
# Ping the management daemon to write the DNS and nginx configuration files.
until nc -z -w 4 localhost 10222
# Wait for the management daemon to start...
until nc -z -w 4 127.0.0.1 10222
do
echo Waiting for the Mail-in-a-Box management daemon to start...
sleep 2
done
# ...and then have it write the DNS and nginx configuration files and start those
# services.
tools/dns_update
tools/web_update
# Give fail2ban another restart. The log files may not all have been present when
# fail2ban was first configured, but they should exist now.
restart_service fail2ban
# If there aren't any mail users yet, create one.
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.
echo
echo "-----------------------------------------------"
@@ -132,17 +157,17 @@ if management/status_checks.py --check-primary-hostname; then
echo https://$PRIMARY_HOSTNAME/admin
echo
echo "If you have a DNS problem put the box's IP address in the URL"
echo "(https://$PUBLIC_IP/admin) but then check the SSL fingerprint:"
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint \
| sed "s/SHA1 Fingerprint=//"
echo "(https://$PUBLIC_IP/admin) but then check the TLS fingerprint:"
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint -sha256\
| sed "s/SHA256 Fingerprint=//"
else
echo https://$PUBLIC_IP/admin
echo
echo You will be alerted that the website has an invalid certificate. Check that
echo the certificate fingerprint matches:
echo
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint \
| sed "s/SHA1 Fingerprint=//"
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint -sha256\
| sed "s/SHA256 Fingerprint=//"
echo
echo Then you can confirm the security exception and continue.
echo

View File

@@ -4,17 +4,81 @@ source setup/functions.sh # load our functions
# Basic System Configuration
# -------------------------
# ### Add Mail-in-a-Box's PPA.
# ### Set hostname of the box
# 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.
# If the hostname is not correctly resolvable sudo can't be used. This will result in
# errors during the install
#
# 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).
# First set the hostname in the configuration file, then activate the setting
echo $PRIMARY_HOSTNAME > /etc/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
# If the physical memory of the system is below 2GB it is wise to create a
# swap file. This will make the system more resiliant to memory spikes and
# prevent for instance spam filtering from crashing
# We will create a 1G file, this should be a good balance between disk usage
# and buffers for the system. We will only allocate this file if there is more
# than 5GB of disk space available
# The following checks are performed:
# - Check if swap is currently mountend by looking at /proc/swaps
# - Check if the user intents to activate swap on next boot by checking fstab entries.
# - Check if a swapfile already exists
# - Check if the root file system is not btrfs, might be an incompatible version with
# swapfiles. User should hanle it them selves.
# - Check the memory requirements
# - Check available diskspace
# See https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04
# for reference
SWAP_MOUNTED=$(cat /proc/swaps | tail -n+2)
SWAP_IN_FSTAB=$(grep "swap" /etc/fstab || /bin/true)
ROOT_IS_BTRFS=$(grep "\/ .*btrfs" /proc/mounts || /bin/true)
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}' || /bin/true)
AVAILABLE_DISK_SPACE=$(df / --output=avail | tail -n 1)
if
[ -z "$SWAP_MOUNTED" ] &&
[ -z "$SWAP_IN_FSTAB" ] &&
[ ! -e /swapfile ] &&
[ -z "$ROOT_IS_BTRFS" ] &&
[ $TOTAL_PHYSICAL_MEM -lt 1900000 ] &&
[ $AVAILABLE_DISK_SPACE -gt 5242880 ]
then
echo "Adding a swap file to the system..."
# Allocate and activate the swap file. Allocate in 1KB chuncks
# doing it in one go, could fail on low memory systems
dd if=/dev/zero of=/swapfile bs=1024 count=$[1024*1024] status=none
if [ -e /swapfile ]; then
chmod 600 /swapfile
hide_output mkswap /swapfile
swapon /swapfile
fi
# Check if swap is mounted then activate on boot
if swapon -s | grep -q "\/swapfile"; then
echo "/swapfile none swap sw 0 0" >> /etc/fstab
else
echo "ERROR: Swap allocation failed"
fi
fi
# ### Add PPAs.
# 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
echo "Installing add-apt-repository..."
@@ -22,16 +86,25 @@ if [ ! -f /usr/bin/add-apt-repository ]; then
apt_install software-properties-common
fi
hide_output add-apt-repository -y ppa:mail-in-a-box/ppa
# Install the certbot PPA.
hide_output add-apt-repository -y ppa:certbot/certbot
# ### 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...
hide_output apt-get update
apt_get_quiet upgrade
# Old kernels pile up over time and take up a lot of disk space, and because of Mail-in-a-Box
# changes there may be other packages that are no longer needed. Clear out anything apt knows
# is safe to delete.
apt_get_quiet autoremove
# ### Install System Packages
# Install basic utilities.
@@ -52,8 +125,16 @@ apt_get_quiet upgrade
echo Installing system packages...
apt_install python3 python3-dev python3-pip \
netcat-openbsd wget curl git sudo coreutils bc \
haveged pollinate \
unattended-upgrades cron ntp fail2ban
haveged pollinate unzip \
unattended-upgrades cron ntp fail2ban rsyslog
# ### Suppress Upgrade Prompts
# When Ubuntu 20 comes out, we don't want users to be prompted to upgrade,
# because we don't yet support it.
if [ -f /etc/update-manager/release-upgrades ]; then
tools/editconf.py /etc/update-manager/release-upgrades Prompt=never
rm -f /var/lib/ubuntu-release-upgrader/release-upgrade-available
fi
# ### Set the system timezone
#
@@ -69,8 +150,8 @@ apt_install python3 python3-dev python3-pip \
# 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
# setup.
if [ -z "$NONINTERACTIVE" ]; then
if [ ! -f /etc/timezone ] || [ ! -z $FIRST_TIME_SETUP ]; then
if [ -z "${NONINTERACTIVE:-}" ]; then
if [ ! -f /etc/timezone ] || [ ! -z ${FIRST_TIME_SETUP:-} ]; then
# If the file is missing or this is the user's first time running
# Mail-in-a-Box setup, run the interactive timezone configuration
# tool.
@@ -96,7 +177,6 @@ fi
# * DNSSEC signing keys (see `dns.sh`)
# * our management server's API key (via Python's os.urandom method)
# * 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
# for a constant new stream of entropy. In practice, we only need a little
@@ -144,6 +224,12 @@ pollinate -q -r
# Between these two, we really ought to be all set.
# We need an ssh key to store backups via rsync, if it doesn't exist create one
if [ ! -f /root/.ssh/id_rsa_miab ]; then
echo 'Creating SSH key for backup…'
ssh-keygen -t rsa -b 2048 -a 100 -f /root/.ssh/id_rsa_miab -N '' -q
fi
# ### Package maintenance
#
# Allow apt to install system updates automatically every day.
@@ -152,7 +238,7 @@ cat > /etc/apt/apt.conf.d/02periodic <<EOF;
APT::Periodic::MaxAge "7";
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::Verbose "1";
APT::Periodic::Verbose "0";
EOF
# ### Firewall
@@ -160,7 +246,7 @@ EOF
# 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
# 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.
apt_install ufw
@@ -185,52 +271,84 @@ fi #NODOC
# ### Local DNS Service
# Install a local DNS server, rather than using the DNS server provided by the
# ISP's network configuration.
# Install a local recursive DNS server --- i.e. for DNS queries made by
# local services running on this machine.
#
# We do this to ensure that DNS queries
# that *we* make (i.e. looking up other external domains) perform DNSSEC checks.
# We could use Google's Public DNS, but we don't want to create a dependency on
# Google per our goals of decentralization. `bind9`, as packaged for Ubuntu, has
# DNSSEC enabled by default via "dnssec-validation auto".
# (This is unrelated to the box's public, non-recursive DNS server that
# answers remote queries about domain names hosted on this box. For that
# see dns.sh.)
#
# So we'll be running `bind9` bound to 127.0.0.1 for locally-issued DNS queries
# and `nsd` bound to the public ethernet interface for remote DNS queries asking
# about our domain names. `nsd` is configured later.
# The default systemd-resolved service provides local DNS name resolution. By default it
# is a recursive stub nameserver, which means it simply relays requests to an
# 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:
#
# * 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
# so that we're sure there's no conflict with nsd, our public domain
# name server, on IPV6.
# * The listen-on directive in named.conf.options restricts `bind9` to
# binding to the loopback interface instead of all interfaces.
apt_install bind9 resolvconf
apt_install bind9
tools/editconf.py /etc/default/bind9 \
RESOLVCONF=yes \
"OPTIONS=\"-u bind -4\""
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.
sed -i "s/^}/\n\tlisten-on { 127.0.0.1; };\n}/" /etc/bind/named.conf.options
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
mv /etc/resolvconf/resolv.conf.d/original /etc/resolvconf/resolv.conf.original #NODOC
fi
# First we'll disable systemd-resolved's management of resolv.conf and its stub server.
# Breaking the symlink to /run/systemd/resolve/stub-resolv.conf means
# 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_service bind9
restart_service resolvconf
systemctl restart systemd-resolved
# ### Fail2Ban Service
# Configure the Fail2Ban installation to prevent dumb bruce-force attacks against dovecot, postfix and ssh
cat conf/fail2ban/jail.local \
# 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.d/defaults-debian.conf # removes default config so we can manage all of fail2ban rules in one config
cat conf/fail2ban/jails.conf \
| sed "s/PUBLIC_IP/$PUBLIC_IP/g" \
> /etc/fail2ban/jail.local
cp conf/fail2ban/dovecotimap.conf /etc/fail2ban/filter.d/dovecotimap.conf
| sed "s#STORAGE_ROOT#$STORAGE_ROOT#" \
> /etc/fail2ban/jail.d/mailinabox.conf
cp -f conf/fail2ban/filter.d/* /etc/fail2ban/filter.d/
# On first installation, the log files that the jails look at don't all exist.
# e.g., The roundcube error log isn't normally created until someone logs into
# Roundcube for the first time. This causes fail2ban to fail to start. Later
# scripts will ensure the files exist and then fail2ban is given another
# restart at the very end of setup.
restart_service fail2ban

View File

@@ -18,7 +18,8 @@ fi
# Turn off nginx's default website.
echo "Installing Nginx (web server)..."
apt_install nginx php5-fpm
apt_install nginx php-cli php-fpm
rm -f /etc/nginx/sites-enabled/default
@@ -40,15 +41,19 @@ tools/editconf.py /etc/nginx/nginx.conf -s \
server_names_hash_bucket_size="128;"
# Tell PHP not to expose its version number in the X-Powered-By header.
tools/editconf.py /etc/php5/fpm/php.ini -c ';' \
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
expose_php=Off
# Set PHPs default charset to UTF-8, since we use it. See #367.
tools/editconf.py /etc/php5/fpm/php.ini -c ';' \
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
default_charset="UTF-8"
# Switch from the dynamic process manager to the ondemand manager see #1216
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
pm=ondemand
# Bump up PHP's max_children to support more concurrent connections
tools/editconf.py /etc/php5/fpm/pool.d/www.conf -c ';' \
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
pm.max_children=8
# Other nginx settings will be configured by the management service
@@ -86,24 +91,9 @@ if [ ! -f $STORAGE_ROOT/www/default/index.html ]; then
fi
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.
restart_service nginx
restart_service php5-fpm
restart_service php7.2-fpm
# Open ports.
ufw_allow http

View File

@@ -22,24 +22,26 @@ source /etc/mailinabox.conf # load global vars
echo "Installing Roundcube (webmail)..."
apt_install \
dbconfig-common \
php5 php5-sqlite php5-mcrypt php5-intl php5-json php5-common php-auth php-net-smtp php-net-socket php-net-sieve php-mail-mime php-crypt-gpg php5-gd php5-pspell \
tinymce libjs-jquery libjs-jquery-mousewheel libmagic1
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
php-cli php-sqlite3 php-intl php-json php-common php-curl \
php-gd php-pspell tinymce libjs-jquery libjs-jquery-mousewheel libmagic1 php-mbstring
# 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 vacation_sieve to track
# whether we have the latest version.
VERSION=1.1.3
HASH=4513227bd64eb8564f056817341b1dfe478e215e
VACATION_SIEVE_VERSION=91ea6f52216390073d1f5b70b5f6bea0bfaee7e5
PERSISTENT_LOGIN_VERSION=117fbd8f93b56b2bf72ad055193464803ef3bc36
HTML5_NOTIFIER_VERSION=046eb388dd63b1ec77a3ee485757fc25ae9e684d
UPDATE_KEY=$VERSION:$VACATION_SIEVE_VERSION:$PERSISTENT_LOGIN_VERSION:$HTML5_NOTIFIER_VERSION
# Combine the Roundcube version number with the commit hash of plugins to track
# whether we have the latest version of everything.
VERSION=1.3.9
HASH=02850972b416bbfa1c13580f16d06fd7ae2774aa
PERSISTENT_LOGIN_VERSION=dc5ca3d3f4415cc41edb2fde533c8a8628a94c76
HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5
CARDDAV_VERSION=3.0.3
CARDDAV_HASH=d1e3b0d851ffa2c6bd42bf0c04f70d0e1d0d78f8
UPDATE_KEY=$VERSION:$PERSISTENT_LOGIN_VERSION:$HTML5_NOTIFIER_VERSION:$CARDDAV_VERSION
# paths that are often reused.
RCM_DIR=/usr/local/lib/roundcubemail
RCM_PLUGIN_DIR=${RCM_DIR}/plugins
RCM_CONFIG=${RCM_DIR}/config/config.inc.php
needs_update=0 #NODOC
if [ ! -f /usr/local/lib/roundcubemail/version ]; then
# not installed yet #NODOC
@@ -51,25 +53,32 @@ fi
if [ $needs_update == 1 ]; then
# install roundcube
wget_verify \
https://downloads.sourceforge.net/project/roundcubemail/roundcubemail/$VERSION/roundcubemail-$VERSION.tar.gz \
https://github.com/roundcube/roundcubemail/releases/download/$VERSION/roundcubemail-$VERSION-complete.tar.gz \
$HASH \
/tmp/roundcube.tgz
tar -C /usr/local/lib -zxf /tmp/roundcube.tgz
tar -C /usr/local/lib --no-same-owner -zxf /tmp/roundcube.tgz
rm -rf /usr/local/lib/roundcubemail
mv /usr/local/lib/roundcubemail-$VERSION/ /usr/local/lib/roundcubemail
mv /usr/local/lib/roundcubemail-$VERSION/ $RCM_DIR
rm -f /tmp/roundcube.tgz
# install roundcube autoreply/vacation plugin
git_clone https://github.com/arodier/Roundcube-Plugins.git $VACATION_SIEVE_VERSION plugins/vacation_sieve /usr/local/lib/roundcubemail/plugins/vacation_sieve
# install roundcube persistent_login plugin
git_clone https://github.com/mfreiholz/Roundcube-Persistent-Login-Plugin.git $PERSISTENT_LOGIN_VERSION '' /usr/local/lib/roundcubemail/plugins/persistent_login
git_clone https://github.com/mfreiholz/Roundcube-Persistent-Login-Plugin.git $PERSISTENT_LOGIN_VERSION '' ${RCM_PLUGIN_DIR}/persistent_login
# install roundcube html5_notifier plugin
git_clone https://github.com/kitist/html5_notifier.git $HTML5_NOTIFIER_VERSION '' /usr/local/lib/roundcubemail/plugins/html5_notifier
git_clone https://github.com/kitist/html5_notifier.git $HTML5_NOTIFIER_VERSION '' ${RCM_PLUGIN_DIR}/html5_notifier
# download and verify the full release of the carddav plugin
wget_verify \
https://github.com/blind-coder/rcmcarddav/releases/download/v${CARDDAV_VERSION}/carddav-${CARDDAV_VERSION}.zip \
$CARDDAV_HASH \
/tmp/carddav.zip
# unzip and cleanup
unzip -q /tmp/carddav.zip -d ${RCM_PLUGIN_DIR}
rm -f /tmp/carddav.zip
# record the version we've installed
echo $UPDATE_KEY > /usr/local/lib/roundcubemail/version
echo $UPDATE_KEY > ${RCM_DIR}/version
fi
# ### Configuring Roundcube
@@ -82,65 +91,81 @@ SECRET_KEY=$(dd if=/dev/urandom bs=1 count=18 2>/dev/null | base64 | fold -w 24
# For security, temp and log files are not stored in the default locations
# which are inside the roundcube sources directory. We put them instead
# in normal places.
cat > /usr/local/lib/roundcubemail/config/config.inc.php <<EOF;
cat > $RCM_CONFIG <<EOF;
<?php
/*
* Do not edit. Written by Mail-in-a-Box. Regenerated on updates.
*/
\$config = array();
\$config['log_dir'] = '/var/log/roundcubemail/';
\$config['temp_dir'] = '/tmp/roundcubemail/';
\$config['temp_dir'] = '/var/tmp/roundcubemail/';
\$config['db_dsnw'] = 'sqlite:///$STORAGE_ROOT/mail/roundcube/roundcube.sqlite?mode=0640';
\$config['default_host'] = 'ssl://localhost';
\$config['default_port'] = 993;
\$config['imap_conn_options'] = array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
),
);
\$config['imap_timeout'] = 15;
\$config['smtp_server'] = 'tls://localhost';
\$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(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
),
);
\$config['support_url'] = 'https://mailinabox.email/';
\$config['product_name'] = 'Mail-in-a-Box/Roundcube Webmail';
\$config['product_name'] = '$PRIMARY_HOSTNAME Webmail';
\$config['des_key'] = '$SECRET_KEY';
\$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'vacation_sieve', 'persistent_login');
\$config['skin'] = 'classic';
\$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'persistent_login', 'carddav');
\$config['skin'] = 'larry';
\$config['login_autocomplete'] = 2;
\$config['password_charset'] = 'UTF-8';
\$config['junk_mbox'] = 'Spam';
?>
EOF
# Configure vaction_sieve.
cat > /usr/local/lib/roundcubemail/plugins/vacation_sieve/config.inc.php <<EOF;
# Configure CardDav
cat > ${RCM_PLUGIN_DIR}/carddav/config.inc.php <<EOF;
<?php
/* Do not edit. Written by Mail-in-a-Box. Regenerated on updates. */
\$rcmail_config['vacation_sieve'] = array(
'date_format' => 'd/m/Y',
'working_hours' => array(8,18),
'msg_format' => 'text',
'logon_transform' => array('#([a-z])[a-z]+(\.|\s)([a-z])#i', '\$1\$3'),
'transfer' => array(
'mode' => 'managesieve',
'ms_activate_script' => true,
'host' => 'localhost',
'port' => '4190',
'usetls' => false,
'path' => 'vacation',
)
\$prefs['_GLOBAL']['hide_preferences'] = true;
\$prefs['_GLOBAL']['suppress_version_warning'] = true;
\$prefs['ownCloud'] = array(
'name' => 'ownCloud',
'username' => '%u', // login username
'password' => '%p', // login password
'url' => 'https://${PRIMARY_HOSTNAME}/cloud/remote.php/carddav/addressbooks/%u/contacts',
'active' => true,
'readonly' => false,
'refresh_time' => '02:00:00',
'fixed' => array('username','password'),
'preemptive_auth' => '1',
'hide' => false,
);
?>
EOF
# Create writable directories.
mkdir -p /var/log/roundcubemail /tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
chown -R www-data.www-data /var/log/roundcubemail /tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
mkdir -p /var/log/roundcubemail /var/tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
chown -R www-data.www-data /var/log/roundcubemail /var/tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
# Ensure the log file monitored by fail2ban exists, or else fail2ban can't start.
sudo -u www-data touch /var/log/roundcubemail/errors
# 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
# we're not planning to change in config.inc.dist...
cp /usr/local/lib/roundcubemail/plugins/password/config.inc.php.dist \
/usr/local/lib/roundcubemail/plugins/password/config.inc.php
cp ${RCM_PLUGIN_DIR}/password/config.inc.php.dist \
${RCM_PLUGIN_DIR}/password/config.inc.php
tools/editconf.py /usr/local/lib/roundcubemail/plugins/password/config.inc.php \
"\$config['password_minimum_length']=6;" \
tools/editconf.py ${RCM_PLUGIN_DIR}/password/config.inc.php \
"\$config['password_minimum_length']=8;" \
"\$config['password_db_dsn']='sqlite:///$STORAGE_ROOT/mail/users.sqlite';" \
"\$config['password_query']='UPDATE users SET password=%D WHERE email=%u';" \
"\$config['password_dovecotpw']='/usr/bin/doveadm pw';" \
@@ -154,9 +179,19 @@ usermod -a -G dovecot www-data
# could use dovecot instead of www-data, but not sure it matters
chown root.www-data $STORAGE_ROOT/mail
chmod 775 $STORAGE_ROOT/mail
chown root.www-data $STORAGE_ROOT/mail/users.sqlite
chmod 664 $STORAGE_ROOT/mail/users.sqlite
chown root.www-data $STORAGE_ROOT/mail/users.sqlite
chmod 664 $STORAGE_ROOT/mail/users.sqlite
# Fix Carddav permissions:
chown -f -R root.www-data ${RCM_PLUGIN_DIR}/carddav
# root.www-data need all permissions, others only read
chmod -R 774 ${RCM_PLUGIN_DIR}/carddav
# Run Roundcube database migration script (database is created if it does not exist)
${RCM_DIR}/bin/updatedb.sh --dir ${RCM_DIR}/SQL --package roundcube
chown www-data:www-data $STORAGE_ROOT/mail/roundcube/roundcube.sqlite
chmod 664 $STORAGE_ROOT/mail/roundcube/roundcube.sqlite
# Enable PHP modules.
php5enmod mcrypt
restart_service php5-fpm
phpenmod -v php mcrypt imap
restart_service php7.2-fpm

View File

@@ -17,25 +17,34 @@ source /etc/mailinabox.conf # load global vars
echo "Installing Z-Push (Exchange/ActiveSync server)..."
apt_install \
php-soap php5-imap libawl-php php5-xsl
php-soap php-imap libawl-php php-xsl
php5enmod imap
phpenmod -v php imap
# Copy Z-Push into place.
TARGETHASH=80cbe53de4ab8dd598d1f2af6f0a23fa396c529a
VERSION=2.5.0
TARGETHASH=30ce5c1af3f10939036361b6032d1187651b621e
needs_update=0 #NODOC
if [ ! -f /usr/local/lib/z-push/version ]; then
needs_update=1 #NODOC
elif [[ $TARGETHASH != `cat /usr/local/lib/z-push/version` ]]; then
elif [[ $VERSION != `cat /usr/local/lib/z-push/version` ]]; then
# checks if the version
needs_update=1 #NODOC
fi
if [ $needs_update == 1 ]; then
git_clone https://github.com/fmbiete/Z-Push-contrib $TARGETHASH '' /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
# Extract into place.
rm -rf /usr/local/lib/z-push /tmp/z-push
unzip -q /tmp/z-push.zip -d /tmp/z-push
mv /tmp/z-push/src /usr/local/lib/z-push
rm -rf /tmp/z-push.zip /tmp/z-push
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-top.php /usr/sbin/z-push-top
echo $TARGETHASH > /usr/local/lib/z-push/version
echo $VERSION > /usr/local/lib/z-push/version
fi
# Configure default config.
@@ -53,6 +62,7 @@ cp conf/zpush/backend_combined.php /usr/local/lib/z-push/backend/combined/config
# Configure IMAP
rm -f /usr/local/lib/z-push/backend/imap/config.php
cp conf/zpush/backend_imap.php /usr/local/lib/z-push/backend/imap/config.php
sed -i "s%STORAGE_ROOT%$STORAGE_ROOT%" /usr/local/lib/z-push/backend/imap/config.php
# Configure CardDav
rm -f /usr/local/lib/z-push/backend/carddav/config.php
@@ -66,6 +76,7 @@ cp conf/zpush/backend_caldav.php /usr/local/lib/z-push/backend/caldav/config.php
rm -f /usr/local/lib/z-push/autodiscover/config.php
cp conf/zpush/autodiscover_config.php /usr/local/lib/z-push/autodiscover/config.php
sed -i "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" /usr/local/lib/z-push/autodiscover/config.php
sed -i "s^define('TIMEZONE', .*^define('TIMEZONE', '$(cat /etc/timezone)');^" /usr/local/lib/z-push/autodiscover/config.php
# Some directories it will use.
@@ -91,4 +102,8 @@ EOF
# Restart service.
restart_service php5-fpm
restart_service php7.2-fpm
# Fix states after upgrade
hide_output z-push-admin -a fixstates

221
tests/fail2ban.py Normal file
View File

@@ -0,0 +1,221 @@
# Test that a box's fail2ban setting are working
# correctly by attempting a bunch of failed logins.
#
# Specify a SSH login command (which we use to reset
# fail2ban after each test) and the hostname to
# try to log in to.
######################################################################
import sys, os, time, functools
# parse command line
if len(sys.argv) != 4:
print("Usage: tests/fail2ban.py \"ssh user@hostname\" hostname owncloud_user")
sys.exit(1)
ssh_command, hostname, owncloud_user = sys.argv[1:4]
# define some test types
import socket
socket.setdefaulttimeout(10)
class IsBlocked(Exception):
"""Tests raise this exception when it appears that a fail2ban
jail is in effect, i.e. on a connection refused error."""
pass
def smtp_test():
import smtplib
try:
server = smtplib.SMTP(hostname, 587)
except ConnectionRefusedError:
# looks like fail2ban worked
raise IsBlocked()
server.starttls()
server.ehlo_or_helo_if_needed()
try:
server.login("fakeuser", "fakepassword")
raise Exception("authentication didn't fail")
except smtplib.SMTPAuthenticationError:
# athentication should fail
pass
try:
server.quit()
except:
# ignore errors here
pass
def imap_test():
import imaplib
try:
M = imaplib.IMAP4_SSL(hostname)
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 pop_test():
import poplib
try:
M = poplib.POP3_SSL(hostname)
except ConnectionRefusedError:
# looks like fail2ban worked
raise IsBlocked()
try:
M.user('fakeuser')
try:
M.pass_('fakepassword')
except poplib.error_proto as e:
# Authentication should fail.
M = None # don't .quit()
return
M.list()
raise Exception("authentication didn't fail")
finally:
if M:
M.quit()
def http_test(url, expected_status, postdata=None, qsargs=None, auth=None):
import urllib.parse
import requests
from requests.auth import HTTPBasicAuth
# form request
url = urllib.parse.urljoin("https://" + hostname, url)
if qsargs: url += "?" + urllib.parse.urlencode(qsargs)
urlopen = requests.get if not postdata else requests.post
try:
# issue request
r = urlopen(
url,
auth=HTTPBasicAuth(*auth) if auth else None,
data=postdata,
headers={'User-Agent': 'Mail-in-a-Box fail2ban tester'},
timeout=8,
verify=False) # don't bother with HTTPS validation, it may not be configured yet
except requests.exceptions.ConnectTimeout as e:
raise IsBlocked()
except requests.exceptions.ConnectionError as e:
if "Connection refused" in str(e):
raise IsBlocked()
raise # some other unexpected condition
# return response status code
if r.status_code != expected_status:
r.raise_for_status() # anything but 200
raise IOError("Got unexpected status code %s." % r.status_code)
# define how to run a test
def restart_fail2ban_service(final=False):
# Log in over SSH to restart fail2ban.
command = "sudo fail2ban-client reload"
if not final:
# Stop recidive jails during testing.
command += " && sudo fail2ban-client stop recidive"
os.system("%s \"%s\"" % (ssh_command, command))
def testfunc_runner(i, testfunc, *args):
print(i+1, end=" ", flush=True)
testfunc(*args)
def run_test(testfunc, args, count, within_seconds, parallel):
# Run testfunc count times in within_seconds seconds (and actually
# within a little less time so we're sure we're under the limit).
#
# Because some services are slow, like IMAP, we can't necessarily
# run testfunc sequentially and still get to count requests within
# the required time. So we split the requests across threads.
import requests.exceptions
from multiprocessing import Pool
restart_fail2ban_service()
# Log.
print(testfunc.__name__, " ".join(str(a) for a in args), "...")
# Record the start time so we can know how to evenly space our
# calls to testfunc.
start_time = time.time()
with Pool(parallel) as p:
# Distribute the requests across the pool.
asyncresults = []
for i in range(count):
ar = p.apply_async(testfunc_runner, [i, testfunc] + list(args))
asyncresults.append(ar)
# Wait for all runs to finish.
p.close()
p.join()
# Check for errors.
for ar in asyncresults:
try:
ar.get()
except IsBlocked:
print("Test machine prematurely blocked!")
return False
# Did we make enough requests within the limit?
if (time.time()-start_time) > within_seconds:
raise Exception("Test failed to make %s requests in %d seconds." % (count, within_seconds))
# Wait a moment for the block to be put into place.
time.sleep(4)
# The next call should fail.
print("*", end=" ", flush=True)
try:
testfunc(*args)
except IsBlocked:
# Success -- this one is supposed to be refused.
print("blocked [OK]")
return True # OK
print("not blocked!")
return False
######################################################################
if __name__ == "__main__":
# run tests
# SMTP bans at 10 even though we say 20 in the config because we get
# doubled-up warnings in the logs, we'll let that be for now
run_test(smtp_test, [], 10, 30, 8)
# IMAP
run_test(imap_test, [], 20, 30, 4)
# POP
run_test(pop_test, [], 20, 30, 4)
# Mail-in-a-Box control panel
run_test(http_test, ["/admin/me", 200], 20, 30, 1)
# Munin via the Mail-in-a-Box control panel
run_test(http_test, ["/admin/munin/", 401], 20, 30, 1)
# ownCloud
run_test(http_test, ["/cloud/remote.php/webdav", 401, None, None, [owncloud_user, "aa"]], 20, 120, 1)
# restart fail2ban so that this client machine is no longer blocked
restart_fail2ban_service(final=True)

View File

@@ -17,7 +17,7 @@
# through some other host you can ssh into (maybe the box
# 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"
# to create a tunnel.)
@@ -61,9 +61,9 @@ common_opts = ["--sslv2", "--sslv3", "--tlsv1", "--tlsv1_1", "--tlsv1_2", "--ren
# Assumes TLSv1, TLSv1.1, TLSv1.2.
#
# The 'old' ciphers bring compatibility back to Win XP IE 6.
MOZILLA_CIPHERS_MODERN = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK"
MOZILLA_CIPHERS_INTERMEDIATE = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
MOZILLA_CIPHERS_OLD = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
MOZILLA_CIPHERS_MODERN = "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"
MOZILLA_CIPHERS_INTERMEDIATE = "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"
MOZILLA_CIPHERS_OLD = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP"
######################################################################
@@ -128,7 +128,7 @@ def sslyze(opts, port, ok_ciphers):
proxy_proc.terminate()
try:
proxy_proc.wait(5)
except TimeoutExpired:
except subprocess.TimeoutExpired:
proxy_proc.kill()
# Get a list of OpenSSL cipher names.

View File

@@ -13,18 +13,18 @@ PORT 25
* Session Resumption:
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:
Server rejected all cipher suites.
* TLSV1_2 Cipher Suites:
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:
ECDHE-RSA-AES256-SHA384 ECDH-256 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-GCM-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-521 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-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
@@ -33,98 +33,60 @@ PORT 25
AES256-SHA256 - 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
ECDHE-RSA-RC4-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA256 ECDH-256 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-GCM-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-521 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-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-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-AES128-GCM-SHA256 DH-2048 bits 128 bits 250 2.0.0 Ok
SEED-SHA - 128 bits 250 2.0.0 Ok
RC4-SHA - 128 bits 250 2.0.0 Ok
RC4-MD5 - 128 bits 250 2.0.0 Ok
CAMELLIA128-SHA - 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-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:
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:
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-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-RC4-SHA ECDH-256 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
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
RC4-SHA - 128 bits 250 2.0.0 Ok
RC4-MD5 - 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:
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-RC4-SHA ECDH-256 bits 128 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
RC4-SHA - 128 bits 250 2.0.0 Ok
RC4-MD5 - 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
Server rejected all cipher suites.
* TLSV1 Cipher Suites:
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:
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-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-RC4-SHA ECDH-256 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
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
RC4-SHA - 128 bits 250 2.0.0 Ok
RC4-MD5 - 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
AES128-SHA - 128 bits 250 2.0.0 Ok
Should Not Offer: DHE-RSA-SEED-SHA, ECDHE-RSA-RC4-SHA, EDH-RSA-DES-CBC3-SHA, RC4-MD5, RC4-SHA, SEED-SHA
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, 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, SRP-3DES-EDE-CBC-SHA, SRP-AES-128-CBC-SHA, SRP-AES-256-CBC-SHA, SRP-DSS-3DES-EDE-CBC-SHA, SRP-DSS-AES-128-CBC-SHA, SRP-DSS-AES-256-CBC-SHA, SRP-RSA-3DES-EDE-CBC-SHA, SRP-RSA-AES-128-CBC-SHA, SRP-RSA-AES-256-CBC-SHA
Supported Clients: OpenSSL/1.0.2, Yahoo Slurp/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, YandexBot/Jan 2015, Android/4.4.2, Safari/8/iOS 8.1.2, Safari/7/OS X 10.9, Safari/8/OS X 10.10, Safari/7/iOS 7.1, Safari/6/iOS 6.0.1, Baidu/Jan 2015, Firefox/31.3.0 ESR/Win 7, Android/5.0.0, IE/11/Win 7, Java/8u31, Googlebot/Feb 2015, Chrome/42/OS X, IE Mobile/11/Win Phone 8.1, IE/11/Win 8.1, Android/4.0.4, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, Android/4.3, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, Java/7u25, OpenSSL/0.9.8y, Firefox/37/OS X, IE/7/Vista, IE/8-10/Win 7, IE Mobile/10/Win Phone 8.0, Java/6u45, Android/2.3.7, IE/8/XP
Should Not Offer: (none -- good)
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: 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
--------
@@ -141,18 +103,18 @@ PORT 587
* Session Resumption:
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:
Server rejected all cipher suites.
* TLSV1_2 Cipher Suites:
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:
ECDHE-RSA-AES256-SHA384 ECDH-256 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-GCM-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-521 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-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
@@ -161,9 +123,9 @@ PORT 587
AES256-SHA256 - 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
ECDHE-RSA-AES128-SHA256 ECDH-256 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-GCM-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-521 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-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
@@ -177,14 +139,14 @@ PORT 587
* TLSV1_1 Cipher Suites:
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:
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-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
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
@@ -197,14 +159,14 @@ PORT 587
* TLSV1 Cipher Suites:
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:
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-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
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
@@ -212,9 +174,9 @@ PORT 587
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-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA, DHE-RSA-SEED-SHA, SEED-SHA
Could Also Offer: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-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
Supported Clients: OpenSSL/1.0.2, Yahoo Slurp/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, YandexBot/Jan 2015, Android/4.4.2, Safari/8/iOS 8.1.2, Safari/7/OS X 10.9, Safari/8/OS X 10.10, Safari/7/iOS 7.1, IE Mobile/11/Win Phone 8.1, IE/11/Win 8.1, IE/11/Win 7, Safari/6/iOS 6.0.1, Firefox/31.3.0 ESR/Win 7, Baidu/Jan 2015, Android/5.0.0, Chrome/42/OS X, Java/8u31, Googlebot/Feb 2015, Firefox/37/OS X, Android/4.0.4, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, Android/4.3, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, OpenSSL/0.9.8y, IE/7/Vista, IE/8-10/Win 7, IE Mobile/10/Win Phone 8.0, Java/7u25, Java/6u45, Android/2.3.7
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, ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305
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
--------
@@ -227,7 +189,7 @@ PORT 443
Secure Renegotiation: OK - Supported
* HTTP Strict Transport Security:
OK - HSTS header received: max-age=31536000
OK - HSTS header received: max-age=15768000
* Session Resumption:
With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts).
@@ -236,12 +198,12 @@ PORT 443
* OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed
Unhandled exception when processing --chrome_sha1:
exceptions.TypeError - Incorrect padding
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* Google Chrome SHA-1 Deprecation Status:
OK - Leaf certificate expires before 2016.
* TLSV1_2 Cipher Suites:
Preferred:
ECDHE-RSA-AES128-GCM-SHA256 ECDH-256 bits 128 bits HTTP 200 OK
@@ -252,13 +214,18 @@ PORT 443
DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits HTTP 200 OK
DHE-RSA-AES256-SHA DH-2048 bits 256 bits HTTP 200 OK
DHE-RSA-AES256-GCM-SHA384 DH-2048 bits 256 bits HTTP 200 OK
AES256-SHA256 - 256 bits HTTP 200 OK
AES256-SHA - 256 bits HTTP 200 OK
AES256-GCM-SHA384 - 256 bits HTTP 200 OK
ECDHE-RSA-AES128-SHA256 ECDH-256 bits 128 bits HTTP 200 OK
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK
ECDHE-RSA-AES128-GCM-SHA256 ECDH-256 bits 128 bits HTTP 200 OK
DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits HTTP 200 OK
DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK
DHE-RSA-AES128-GCM-SHA256 DH-2048 bits 128 bits HTTP 200 OK
DES-CBC3-SHA - 112 bits HTTP 200 OK
AES128-SHA256 - 128 bits HTTP 200 OK
AES128-SHA - 128 bits HTTP 200 OK
AES128-GCM-SHA256 - 128 bits HTTP 200 OK
* TLSV1_1 Cipher Suites:
Preferred:
@@ -266,9 +233,10 @@ PORT 443
Accepted:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits HTTP 200 OK
DHE-RSA-AES256-SHA DH-2048 bits 256 bits HTTP 200 OK
AES256-SHA - 256 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
DES-CBC3-SHA - 112 bits HTTP 200 OK
AES128-SHA - 128 bits HTTP 200 OK
* SSLV3 Cipher Suites:
Server rejected all cipher suites.
@@ -279,13 +247,14 @@ PORT 443
Accepted:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits HTTP 200 OK
DHE-RSA-AES256-SHA DH-2048 bits 256 bits HTTP 200 OK
AES256-SHA - 256 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
DES-CBC3-SHA - 112 bits HTTP 200 OK
AES128-SHA - 128 bits HTTP 200 OK
Should Not Offer: (none -- good)
Could Also Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, CAMELLIA128-SHA, CAMELLIA256-SHA, 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-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-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, SRP-AES-128-CBC-SHA, SRP-AES-256-CBC-SHA, SRP-DSS-AES-128-CBC-SHA, SRP-DSS-AES-256-CBC-SHA, SRP-RSA-AES-128-CBC-SHA, SRP-RSA-AES-256-CBC-SHA
Supported Clients: YandexBot/Jan 2015, OpenSSL/1.0.2, Yahoo Slurp/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, Android/4.4.2, Safari/8/iOS 8.1.2, Safari/8/OS X 10.10, Safari/7/OS X 10.9, Safari/7/iOS 7.1, Safari/6/iOS 6.0.1, Android/5.0.0, Chrome/42/OS X, IE/11/Win 8.1, IE/11/Win 7, Java/8u31, IE Mobile/11/Win Phone 8.1, Googlebot/Feb 2015, Firefox/37/OS X, Firefox/31.3.0 ESR/Win 7, Android/4.2.2, Android/4.0.4, Baidu/Jan 2015, Safari/5.1.9/OS X 10.6.8, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, Android/4.3, OpenSSL/0.9.8y, IE/7/Vista, IE/8-10/Win 7, IE Mobile/10/Win Phone 8.0, Java/7u25, Java/6u45, Android/2.3.7, IE/8/XP
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: 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
--------
@@ -299,41 +268,45 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
* OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* Session Resumption:
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.
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* TLSV1_2 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits
Accepted:
ECDHE-RSA-AES256-SHA384 ECDH-384 bits 256 bits
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
ECDHE-RSA-AES256-GCM-SHA384 ECDH-384 bits 256 bits
DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits
DHE-RSA-AES256-SHA DH-2048 bits 256 bits
DHE-RSA-AES256-GCM-SHA384 DH-2048 bits 256 bits
AES256-SHA256 - 256 bits
AES256-SHA - 256 bits
AES256-GCM-SHA384 - 256 bits
ECDHE-RSA-AES128-SHA256 ECDH-384 bits 128 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits
DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits
DHE-RSA-AES128-SHA DH-2048 bits 128 bits
DHE-RSA-AES128-GCM-SHA256 DH-2048 bits 128 bits
AES128-SHA256 - 128 bits
AES128-SHA - 128 bits
AES128-GCM-SHA256 - 128 bits
* TLSV1_1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
Accepted:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
DHE-RSA-AES256-SHA DH-2048 bits 256 bits
AES256-SHA - 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
DHE-RSA-AES128-SHA DH-2048 bits 128 bits
AES128-SHA - 128 bits
* SSLV3 Cipher Suites:
@@ -341,22 +314,18 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
* TLSV1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
Accepted:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
DHE-RSA-AES256-SHA DH-2048 bits 256 bits
AES256-SHA - 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
DHE-RSA-AES128-SHA DH-2048 bits 128 bits
AES128-SHA - 128 bits
Should Not Offer: AES128-SHA, AES256-SHA, CAMELLIA128-SHA, CAMELLIA256-SHA, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA
Could Also Offer: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA256, 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-RSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-SHA256, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-RSA-AES256-SHA384
Supported Clients: OpenSSL/1.0.2, Baidu/Jan 2015, Yahoo Slurp/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, Firefox/31.3.0 ESR/Win 7, Googlebot/Feb 2015, Android/4.2.2, Android/5.0.0, Android/4.0.4, Safari/8/iOS 8.1.2, Safari/7/OS X 10.9, YandexBot/Jan 2015, Safari/8/OS X 10.10, Safari/7/iOS 7.1, Chrome/42/OS X, Safari/5.1.9/OS X 10.6.8, Android/4.1.1, Firefox/37/OS X, Safari/6.0.4/OS X 10.8.4, Android/4.3, Safari/6/iOS 6.0.1, Android/4.4.2, OpenSSL/0.9.8y, IE Mobile/11/Win Phone 8.1, IE/7/Vista, IE/11/Win 8.1, IE/11/Win 7, IE/8-10/Win 7, IE Mobile/10/Win Phone 8.0, Java/8u31, Java/7u25, Java/6u45, Android/2.3.7
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
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
PORT 995
--------
@@ -370,41 +339,45 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
* OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* Session Resumption:
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.
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* TLSV1_2 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits
Accepted:
ECDHE-RSA-AES256-SHA384 ECDH-384 bits 256 bits
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
ECDHE-RSA-AES256-GCM-SHA384 ECDH-384 bits 256 bits
DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits
DHE-RSA-AES256-SHA DH-2048 bits 256 bits
DHE-RSA-AES256-GCM-SHA384 DH-2048 bits 256 bits
AES256-SHA256 - 256 bits
AES256-SHA - 256 bits
AES256-GCM-SHA384 - 256 bits
ECDHE-RSA-AES128-SHA256 ECDH-384 bits 128 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits
DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits
DHE-RSA-AES128-SHA DH-2048 bits 128 bits
DHE-RSA-AES128-GCM-SHA256 DH-2048 bits 128 bits
AES128-SHA256 - 128 bits
AES128-SHA - 128 bits
AES128-GCM-SHA256 - 128 bits
* TLSV1_1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
Accepted:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
DHE-RSA-AES256-SHA DH-2048 bits 256 bits
AES256-SHA - 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
DHE-RSA-AES128-SHA DH-2048 bits 128 bits
AES128-SHA - 128 bits
* SSLV3 Cipher Suites:
@@ -412,20 +385,16 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
* TLSV1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
Accepted:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
DHE-RSA-AES256-SHA DH-2048 bits 256 bits
AES256-SHA - 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
DHE-RSA-AES128-SHA DH-2048 bits 128 bits
AES128-SHA - 128 bits
Should Not Offer: AES128-SHA, AES256-SHA, CAMELLIA128-SHA, CAMELLIA256-SHA, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA
Could Also Offer: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA256, 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-RSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-SHA256, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-RSA-AES256-SHA384
Supported Clients: OpenSSL/1.0.2, Baidu/Jan 2015, Yahoo Slurp/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, Firefox/31.3.0 ESR/Win 7, Googlebot/Feb 2015, Android/4.2.2, Android/5.0.0, Android/4.0.4, Safari/8/iOS 8.1.2, Safari/7/OS X 10.9, YandexBot/Jan 2015, Safari/8/OS X 10.10, Safari/7/iOS 7.1, Chrome/42/OS X, Safari/5.1.9/OS X 10.6.8, Android/4.1.1, Firefox/37/OS X, Safari/6.0.4/OS X 10.8.4, Android/4.3, Safari/6/iOS 6.0.1, Android/4.4.2, OpenSSL/0.9.8y, IE Mobile/11/Win Phone 8.1, IE/7/Vista, IE/11/Win 8.1, IE/11/Win 7, IE/8-10/Win 7, IE Mobile/10/Win Phone 8.0, Java/8u31, Java/7u25, Java/6u45, Android/2.3.7
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
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

View File

@@ -30,8 +30,8 @@ def mgmt(cmd, data=None, is_json=False):
def read_password():
while True:
first = getpass.getpass('password: ')
if len(first) < 4:
print("Passwords must be at least four characters.")
if len(first) < 8:
print("Passwords must be at least eight characters.")
continue
if re.search(r'[\s]', first):
print("Passwords cannot contain spaces.")

49
tools/owncloud-restore.sh Executable file
View File

@@ -0,0 +1,49 @@
#!/bin/bash
#
# This script will restore the backup made during an installation
source /etc/mailinabox.conf # load global vars
if [ -z "$1" ]; then
echo "Usage: owncloud-restore.sh <backup directory>"
echo
echo "WARNING: This will restore the database to the point of the installation!"
echo " This means that you will lose all changes made by users after that point"
echo
echo
echo "Backups are stored here: $STORAGE_ROOT/owncloud-backup/"
echo
echo "Available backups:"
echo
find $STORAGE_ROOT/owncloud-backup/* -maxdepth 0 -type d
echo
echo "Supply the directory that was created during the last installation as the only commandline argument"
exit
fi
if [ ! -f $1/config.php ]; then
echo "This isn't a valid backup location"
exit
fi
echo "Restoring backup from $1"
service php7.2-fpm stop
# remove the current ownCloud/Nextcloud installation
rm -rf /usr/local/lib/owncloud/
# restore the current ownCloud/Nextcloud application
cp -r "$1/owncloud-install" /usr/local/lib/owncloud
# restore access rights
chmod 750 /usr/local/lib/owncloud/{apps,config}
cp "$1/owncloud.db" $STORAGE_ROOT/owncloud/
cp "$1/config.php" $STORAGE_ROOT/owncloud/
ln -sf $STORAGE_ROOT/owncloud/config.php /usr/local/lib/owncloud/config/config.php
chown -f -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off
service php7.2-fpm start
echo "Done"

View File

@@ -1,6 +1,6 @@
#!/bin/bash
#
# This script will give you administrative access to the ownCloud
# This script will give you administrative access to the Nextcloud
# instance running here.
#
# Run this at your own risk. This is for testing & experimentation
@@ -13,11 +13,11 @@ test -z "$1" || ADMIN=$1
echo I am going to unlock admin features for $ADMIN.
echo You can provide another user to unlock as the first argument of this script.
echo
echo WARNING: you could break mail-in-a-box when fiddling around with owncloud\'s admin interface
echo
echo WARNING: you could break mail-in-a-box when fiddling around with Nextcloud\'s admin interface
echo If in doubt, press CTRL-C to cancel.
echo
echo Press enter to continue.
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

@@ -2,7 +2,8 @@
#
# This is a tool Josh uses on his box serving mailinabox.email to parse the nginx
# access log to see how many people are installing Mail-in-a-Box each day, by
# looking at accesses to the bootstrap.sh script.
# looking at accesses to the bootstrap.sh script (which is currently at the URL
# .../setup.sh).
import re, glob, gzip, os.path, json
import dateutil.parser
@@ -24,9 +25,10 @@ for fn in glob.glob("/var/log/nginx/access.log*"):
# Loop through the lines in the access log.
with f:
for line in f:
# Find lines that are GETs on /bootstrap.sh by either curl or wget.
# Find lines that are GETs on the bootstrap script by either curl or wget.
# (Note that we purposely skip ...?ping=1 requests which is the admin panel querying us for updates.)
m = re.match(rb"(?P<ip>\S+) - - \[(?P<date>.*?)\] \"GET /bootstrap.sh HTTP/.*\" 200 \d+ .* \"(?:curl|wget)", line, re.I)
# (Also, the URL changed in January 2016, but we'll accept both.)
m = re.match(rb"(?P<ip>\S+) - - \[(?P<date>.*?)\] \"GET /(bootstrap.sh|setup.sh) HTTP/.*\" 200 \d+ .* \"(?:curl|wget)", line, re.I)
if m:
date, time = m.group("date").decode("ascii").split(":", 1)
date = dateutil.parser.parse(date).date().isoformat()

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)