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

Compare commits

...

93 Commits
v0.26 ... v0.40

Author SHA1 Message Date
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
58 changed files with 1093 additions and 1994 deletions

View File

@@ -1,6 +1,109 @@
CHANGELOG
=========
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)
------------------------
@@ -24,7 +127,7 @@ Installer:
* We now run `apt-get autoremove` at the start of setup to clear out old packages, especially old kernels that take up a lot of space. On the first run, this step may take a long time.
* We now 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.
* 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)
-------------------------

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.

View File

@@ -22,7 +22,7 @@ Additionally, this project has a [Code of Conduct](CODE_OF_CONDUCT.md), which su
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".
@@ -37,7 +37,6 @@ The components installed are:
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,20 +45,20 @@ 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.26
$ git verify-tag v0.40
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!
@@ -68,19 +67,25 @@ by me:
You'll get a lot of warnings, but that's OK. Check that the primary key fingerprint matches the
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.26
$ git checkout v0.40
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
--------------------

15
Vagrantfile vendored
View File

@@ -2,14 +2,7 @@
# 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"
if Vagrant.has_plugin?("vagrant-cachier")
# Configure cached packages to be shared between instances of the same base box.
# More info on http://fgrehm.viewdocs.io/vagrant-cachier/usage
config.cache.scope = :box
end
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
@@ -19,9 +12,9 @@ Vagrant.configure("2") do |config|
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.
# 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

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

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

@@ -1,76 +1,20 @@
# from https://gist.github.com/konklone/6532544 and https://mozilla.github.io/server-side-tls/ssl-config-generator/
###################################################################################################################
# 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;
# 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;
# Cut out (the old, broken) SSLv3 entirely.
# This **excludes IE6 users** and (apparently) Yandexbot.
# Just comment out if you need to support IE6, bless your soul.
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
# Turn on session resumption, using a cache shared across nginx processes,
# as recommended by http://nginx.org/en/docs/http/configuring_https_servers.html
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
#keepalive_timeout 70; # in Ubuntu 14.04/nginx 1.4.6 the default is 65, so plenty good
# Buffer size of 1400 bytes fits in one MTU.
# 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/php/php7.0-fpm.sock;
server unix:/var/run/php/php7.2-fpm.sock;
}

View File

@@ -25,14 +25,14 @@ server {
# This path must be served over HTTP for ACME domain validation.
# 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/acme_challenges/;
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

@@ -20,20 +20,17 @@ rsync_ssh_options = [
]
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):
@@ -54,11 +51,11 @@ def backup_status(env):
date = dateutil.parser.parse(keys[1]).astimezone(dateutil.tz.tzlocal())
return {
"date": keys[1],
"date_str": date.strftime("%x %X") + " " + now.tzname(),
"date_str": date.strftime("%Y-%m-%d %X") + " " + now.tzname(),
"date_delta": reldate(date, now, "the future?"),
"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', [
@@ -80,12 +77,20 @@ def backup_status(env):
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.
@@ -148,6 +153,7 @@ def backup_status(env):
return {
"backups": backups,
"unmatched_file_size": unmatched_file_size,
}
def should_force_full(config, env):
@@ -220,32 +226,6 @@ def perform_backup(full_backup):
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
@@ -267,7 +247,7 @@ def perform_backup(full_backup):
if quit:
sys.exit(code)
service_command("php7.0-fpm", "stop", quit=True)
service_command("php7.2-fpm", "stop", quit=True)
service_command("postfix", "stop", quit=True)
service_command("dovecot", "stop", quit=True)
@@ -301,11 +281,7 @@ def perform_backup(full_backup):
# Start services again.
service_command("dovecot", "start", quit=False)
service_command("postfix", "start", quit=False)
service_command("php7.0-fpm", "start", quit=False)
# Once the migrated backup is included in a new backup, it can be deleted.
if os.path.isdir(migrated_unencrypted_backup_dir):
shutil.rmtree(migrated_unencrypted_backup_dir)
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.
@@ -556,8 +532,7 @@ if __name__ == "__main__":
run_duplicity_verification()
elif sys.argv[-1] == "--list":
# Run duplicity's verification command to check a) the backup files
# are readable, and b) report if they are up to date.
# List the saved backup files.
for fn, size in list_target_files(get_backup_config(load_environment())):
print("{}\t{}".format(fn, size))
@@ -565,6 +540,7 @@ if __name__ == "__main__":
# 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

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

View File

@@ -9,11 +9,17 @@ export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
export 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/email_administrator.py "Backup Status"
# Provision any new certificates for new domains or domains with expiring certificates.
management/ssl_certificates.py -q --headless | management/email_administrator.py "Error Provisioning TLS Certificate"
management/ssl_certificates.py -q | management/email_administrator.py "Error Provisioning TLS Certificate"
# Run status checks and email the administrator if anything changed.
management/status_checks.py --show-changes | management/email_administrator.py "Status Checks Change Notice"

View File

@@ -354,19 +354,20 @@ def build_sshfp_records():
# 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).
# scan the sshd_config and find the ssh ports (port 22 may be closed)
# 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:
ports = []
t = f.readlines()
for line in t:
s = line.split()
for line in f:
s = line.rstrip().split()
if len(s) == 2 and s[0] == 'Port':
ports = ports + [s[1]]
# the keys are the same at each port, so we only need to get
# them at the first port found (may not be port 22)
keys = shell("check_output", ["ssh-keyscan", "-t", "rsa,dsa,ecdsa,ed25519", "-p", ports[0], "localhost"])
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:

View File

@@ -4,8 +4,14 @@
import sys
import html
import smtplib
from email.message import Message
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# In Python 3.6:
#from email.message import Message
from utils import load_environment
@@ -26,11 +32,23 @@ if content == "":
sys.exit(0)
# create MIME message
msg = 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)
msg.set_payload(content, "UTF-8")
content_html = "<html><body><pre>{}</pre></body></html>".format(html.escape(content))
msg.attach(MIMEText(content, 'plain'))
msg.attach(MIMEText(content_html, 'html'))
# In Python 3.6:
#msg.set_content(content)
#msg.add_alternative(content_html, "html")
# send
smtpclient = smtplib.SMTP('127.0.0.1', 25)

View File

@@ -53,10 +53,10 @@ VERBOSE = False
# List of strings to filter users with
FILTERS = None
# What to show by default
# What to show (with defaults)
SCAN_OUT = True # Outgoing email
SCAN_IN = True # Incoming email
SCAN_CONN = False # IMAP and POP3 logins
SCAN_DOVECOT_LOGIN = True # Dovecot Logins
SCAN_GREY = False # Greylisted email
SCAN_BLOCKED = False # Rejected email
@@ -76,7 +76,8 @@ def scan_files(collector):
tmp_file = tempfile.NamedTemporaryFile()
shutil.copyfileobj(gzip.open(fn), tmp_file)
print("Processing file", fn, "...")
if VERBOSE:
print("Processing file", fn, "...")
fn = tmp_file.name if tmp_file else fn
for line in reverse_readline(fn):
@@ -105,7 +106,7 @@ def scan_mail_log(env):
"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
"dovecot": OrderedDict(), # Data about Dovecot activity
"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
@@ -119,8 +120,8 @@ def scan_mail_log(env):
except ImportError:
pass
print("Scanning from {:%Y-%m-%d %H:%M:%S} back to {:%Y-%m-%d %H:%M:%S}".format(
START_DATE, END_DATE)
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
@@ -138,8 +139,8 @@ def scan_mail_log(env):
# Print Sent Mail report
if collector["sent_mail"]:
msg = "Sent email between {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
print_header(msg.format(END_DATE, START_DATE))
msg = "Sent email"
print_header(msg)
data = OrderedDict(sorted(collector["sent_mail"].items(), key=email_sort))
@@ -173,8 +174,8 @@ def scan_mail_log(env):
# Print Received Mail report
if collector["received_mail"]:
msg = "Received email between {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
print_header(msg.format(END_DATE, START_DATE))
msg = "Received email"
print_header(msg)
data = OrderedDict(sorted(collector["received_mail"].items(), key=email_sort))
@@ -199,43 +200,55 @@ def scan_mail_log(env):
[accum]
)
# Print Dovecot report
# Print login report
if collector["dovecot"]:
msg = "Email client logins between {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
print_header(msg.format(END_DATE, START_DATE))
if collector["logins"]:
msg = "User logins per hour"
print_header(msg)
data = OrderedDict(sorted(collector["dovecot"].items(), key=email_sort))
data = OrderedDict(sorted(collector["logins"].items(), key=email_sort))
# Get a list of all of the protocols seen in the logs in reverse count order.
all_protocols = defaultdict(int)
for u in data.values():
for protocol_name, count in u["totals_by_protocol"].items():
all_protocols[protocol_name] += count
all_protocols = [k for k, v in sorted(all_protocols.items(), key=lambda kv : -kv[1])]
print_user_table(
data.keys(),
data=[
("imap", [u["imap"] for u in data.values()]),
("pop3", [u["pop3"] for u in data.values()]),
(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=[
("IMAP IP addresses", [[k + " (%d)" % v for k, v in u["imap-logins"].items()]
for u in data.values()]),
("POP3 IP addresses", [[k + " (%d)" % v for k, v in u["pop3-logins"].items()]
for u in data.values()]),
("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=[
("imap", [u["activity-by-hour"]["imap"] for u in data.values()]),
("pop3", [u["activity-by-hour"]["pop3"] for u in data.values()]),
(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 = {"imap": defaultdict(int), "pop3": defaultdict(int), "both": defaultdict(int)}
accum = { protocol_name: defaultdict(int) for protocol_name in all_protocols }
for h in range(24):
accum["imap"][h] = sum(d["activity-by-hour"]["imap"][h] for d in data.values())
accum["pop3"][h] = sum(d["activity-by-hour"]["pop3"][h] for d in data.values())
accum["both"][h] = accum["imap"][h] + accum["pop3"][h]
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(
["imap", "pop3", " +"],
[accum["imap"], accum["pop3"], accum["both"]]
all_protocols,
[accum[protocol_name] for protocol_name in all_protocols]
)
if collector["postgrey"]:
@@ -244,7 +257,7 @@ def scan_mail_log(env):
print(textwrap.fill(
"The following mail was greylisted, meaning the emails were temporarily rejected. "
"Legitimate senders will try again within ten minutes.",
"Legitimate senders must try again after three minutes.",
width=80, initial_indent=" ", subsequent_indent=" "
), end='\n\n')
@@ -348,9 +361,9 @@ def scan_mail_log_line(line, collector):
elif service == "postfix/lmtp":
if SCAN_IN:
scan_postfix_lmtp_line(date, log, collector)
elif service in ("imap-login", "pop3-login"):
if SCAN_CONN:
scan_dovecot_line(date, log, collector, service[:4])
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)
@@ -448,44 +461,43 @@ def scan_postfix_smtpd_line(date, log, collector):
collector["rejected"][user] = data
def scan_dovecot_line(date, log, collector, prot):
""" Scan a dovecot log line and extract interesting 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, rip = m.groups()
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["dovecot"].get(
data = collector["logins"].get(
user,
{
"imap": 0,
"pop3": 0,
"earliest": None,
"latest": None,
"imap-logins": defaultdict(int),
"pop3-logins": defaultdict(int),
"activity-by-hour": {
"imap": defaultdict(int),
"pop3": defaultdict(int),
},
"totals_by_protocol": defaultdict(int),
"totals_by_protocol_and_host": defaultdict(int),
"activity-by-hour": defaultdict(lambda : defaultdict(int)),
}
)
data[prot] += 1
data["activity-by-hour"][prot][date.hour] += 1
if data["latest"] is None:
data["latest"] = date
data["earliest"] = date
if rip not in ("127.0.0.1", "::1") or True:
data["%s-logins" % prot][rip] += 1
data["totals_by_protocol"][protocol_name] += 1
data["totals_by_protocol_and_host"][(protocol_name, host)] += 1
collector["dovecot"][user] = data
if host not in ("127.0.0.1", "::1") or True:
data["activity-by-hour"][protocol_name][date.hour] += 1
collector["logins"][user] = data
def scan_postfix_lmtp_line(date, log, collector):
@@ -561,6 +573,8 @@ def scan_postfix_submission_line(date, log, collector):
collector["sent_mail"][user] = data
# Also log this as a login.
add_login(user, date, "smtp", client, collector)
# Utility functions
@@ -640,7 +654,7 @@ def print_time_table(labels, data, do_print=True):
for i, d in enumerate(data):
lines[i] += base.format(d[h])
lines.insert(0, "")
lines.insert(0, " totals by time of day:")
lines.append("" + (len(lines[-1]) - 2) * "")
if do_print:
@@ -650,7 +664,7 @@ def print_time_table(labels, data, do_print=True):
def print_user_table(users, data=None, sub_data=None, activity=None, latest=None, earliest=None,
delimit=False):
delimit=False, numstr=str):
str_temp = "{:<32} "
lines = []
data = data or []
@@ -764,7 +778,7 @@ def print_user_table(users, data=None, sub_data=None, activity=None, latest=None
# Print totals
data_accum = [str(a) for a in data_accum]
data_accum = [numstr(a) for a in data_accum]
footer = str_temp.format("Totals:" if do_accum else " ")
for row, (l, _) in enumerate(data):
temp = "{:>%d}" % max(5, len(l) + 1)
@@ -818,7 +832,7 @@ if __name__ == "__main__":
action="store_true")
parser.add_argument("-s", "--sent", help="Scan for sent emails.",
action="store_true")
parser.add_argument("-l", "--logins", help="Scan for IMAP/POP logins.",
parser.add_argument("-l", "--logins", help="Scan for user logins to IMAP/POP3.",
action="store_true")
parser.add_argument("-g", "--grey", help="Scan for greylisted emails.",
action="store_true")
@@ -863,8 +877,8 @@ if __name__ == "__main__":
if not SCAN_OUT:
print("Ignoring sent emails")
SCAN_CONN = args.logins
if not SCAN_CONN:
SCAN_DOVECOT_LOGIN = args.logins
if not SCAN_DOVECOT_LOGIN:
print("Ignoring logins")
SCAN_GREY = args.grey

View File

@@ -1,5 +1,14 @@
#!/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
from email_validator import validate_email as validate_email_, EmailNotValidError
@@ -96,7 +105,7 @@ def get_mail_users(env):
users = [ row[0] for row in c.fetchall() ]
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,9 +139,6 @@ 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')
@@ -149,8 +155,6 @@ def get_mail_users_ex(env, with_archived=False, with_slow_info=False):
"mailbox": mbox,
}
users.append(user)
if with_slow_info:
user["mailbox_size"] = utils.du(mbox)
# Group by domain.
domains = { }

2
management/munin_start.sh Executable file
View File

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

View File

@@ -1,7 +1,7 @@
#!/usr/local/lib/mailinabox/env/bin/python
# Utilities for installing and selecting SSL certificates.
import os, os.path, re, shutil
import os, os.path, re, shutil, subprocess, tempfile
from utils import shell, safe_domain_name, sort_domains
import idna
@@ -24,6 +24,16 @@ def get_ssl_certificates(env):
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
@@ -74,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.
@@ -81,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,
@@ -124,21 +141,23 @@ def get_ssl_certificates(env):
return ret
def get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=False, raw=False):
# 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]),
}
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 system_certificate
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:
@@ -155,136 +174,97 @@ def get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=False
# PROVISIONING CERTIFICATES FROM LETSENCRYPT
def get_certificates_to_provision(env, show_extended_problems=True, force_domains=None):
# Get a set of domain names that we should now provision certificates
# for. Provision if a domain name has no valid certificate or if any
# certificate is expiring in 14 days. If provisioning anything, also
# provision certificates expiring within 30 days. The period between
# 14 and 30 days allows us to consolidate domains into multi-domain
# certificates for domains expiring around the same time.
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
import datetime
now = datetime.datetime.utcnow()
existing_certs = get_ssl_certificates(env)
# Get domains with missing & expiring certificates.
certs = get_ssl_certificates(env)
domains = set()
domains_if_any = set()
problems = { }
for domain in get_web_domains(env):
# If the user really wants a cert for certain domains, include it.
if force_domains:
if force_domains == "ALL" or (isinstance(force_domains, list) and domain in force_domains):
domains.add(domain)
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
# Include this domain if its certificate is missing, self-signed, or expiring soon.
try:
cert = get_domain_ssl_files(domain, certs, env, allow_missing_cert=True)
except FileNotFoundError as e:
# system certificate is not present
problems[domain] = "Error: " + str(e)
continue
if cert is None:
# No valid certificate available.
domains.add(domain)
# 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:
cert = cert["certificate_object"]
if cert.issuer == cert.subject:
# This is self-signed. Get a real one.
domains.add(domain)
# Valid certificate today, but is it expiring soon?
elif cert.not_valid_after-now < datetime.timedelta(days=14):
domains.add(domain)
elif cert.not_valid_after-now < datetime.timedelta(days=30):
domains_if_any.add(domain)
# 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))
# It's valid. Should we report its validness?
elif show_extended_problems:
problems[domain] = "The certificate is valid for at least another 30 days --- no need to replace."
if bad_dns:
domains_cant_provision[domain] = "The domain name does not resolve to this machine: " \
+ (", ".join(bad_dns)) \
+ "."
# Warn the user about domains hosted elsewhere.
if not force_domains and show_extended_problems:
for domain in set(get_web_domains(env, exclude_dns_elsewhere=False)) - set(get_web_domains(env)):
problems[domain] = "The domain's DNS is pointed elsewhere, so there is no point to installing a TLS certificate here and we could not automatically provision one anyway because provisioning requires access to the website (which isn't here)."
else:
# DNS is all good.
# Filter out domains that we can't provision a certificate for.
def can_provision_for_domain(domain):
from status_checks import normalize_ip
# 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
# 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.
import dns.resolver
for rtype, value in [("A", env["PUBLIC_IP"]), ("AAAA", env.get("PUBLIC_IPV6"))]:
if not value: continue # IPv6 is not configured
try:
# Must make the qname absolute to prevent a fall-back lookup with a
# search domain appended, by adding a period to the end.
response = dns.resolver.query(domain + ".", rtype)
except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as e:
problems[domain] = "DNS isn't configured properly for this domain: DNS resolution failed (%s: %s)." % (rtype, str(e) or repr(e)) # NoAnswer's str is empty
return False
except Exception as e:
problems[domain] = "DNS isn't configured properly for this domain: DNS lookup had an error: %s." % str(e)
return False
domains_to_provision.add(domain)
# Unfortunately, the response.__str__ returns bytes
# instead of string, if it resulted from an AAAA-query.
# We need to convert manually, until this is fixed:
# https://github.com/rthalley/dnspython/issues/204
#
# BEGIN HOTFIX
def rdata__str__(r):
s = r.to_text()
if isinstance(s, bytes):
s = s.decode('utf-8')
return s
# END HOTFIX
if len(response) != 1 or normalize_ip(rdata__str__(response[0])) != normalize_ip(value):
problems[domain] = "Domain control validation cannot be performed for this domain because DNS points the domain to another machine (%s %s)." % (rtype, ", ".join(rdata__str__(r) for r in response))
return False
return True
domains = set(filter(can_provision_for_domain, domains))
# If there are any domains we definitely will provision for, add in
# additional domains to do at this time.
if len(domains) > 0:
domains |= set(filter(can_provision_for_domain, domains_if_any))
return (domains, problems)
def provision_certificates(env, agree_to_tos_url=None, logger=None, show_extended_problems=True, force_domains=None, jsonable=False):
import requests.exceptions
import acme.messages
from free_tls_certificates import client
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, problems = get_certificates_to_provision(env, force_domains=force_domains, show_extended_problems=show_extended_problems)
domains, domains_cant_provision = get_certificates_to_provision(env, limit_domains=limit_domains)
# Build a list of what happened on each domain or domain-set.
ret = []
for domain, error in domains_cant_provision.items():
ret.append({
"domains": [domain],
"log": [error],
"result": "skipped",
})
# Exit fast if there is nothing to do.
if len(domains) == 0:
return {
"requests": [],
"problems": problems,
}
# Break into groups of up to 100 certificates at a time, which is Let's Encrypt's
# 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[0:100] )
domains = domains[100:]
certs.append( domains[:max_domains_per_group] )
domains = domains[max_domains_per_group:]
# Prepare to provision.
@@ -293,115 +273,82 @@ def provision_certificates(env, agree_to_tos_url=None, logger=None, show_extende
if not os.path.exists(account_path):
os.mkdir(account_path)
# Where should we put ACME challenge files. This is mapped to /.well-known/acme_challenge
# by the nginx configuration.
challenges_path = os.path.join(account_path, 'acme_challenges')
if not os.path.exists(challenges_path):
os.mkdir(challenges_path)
# Read in the private key that we use for all TLS certificates. We'll need that
# to generate a CSR (done by free_tls_certificates).
with open(os.path.join(env['STORAGE_ROOT'], 'ssl/ssl_private_key.pem'), 'rb') as f:
private_key = f.read()
# Provision certificates.
ret = []
for domain_list in certs:
# For return.
ret_item = {
ret.append({
"domains": domain_list,
"log": [],
}
ret.append(ret_item)
# Logging for free_tls_certificates.
def my_logger(message):
if logger: logger(message)
ret_item["log"].append(message)
# Attempt to provision a certificate.
})
try:
try:
cert = client.issue_certificate(
domain_list,
account_path,
agree_to_tos_url=agree_to_tos_url,
private_key=private_key,
logger=my_logger)
# 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))
except client.NeedToTakeAction as e:
# Write out the ACME challenge files.
for action in e.actions:
if isinstance(action, client.NeedToInstallFile):
fn = os.path.join(challenges_path, action.file_name)
with open(fn, 'w') as f:
f.write(action.contents)
else:
raise ValueError(str(action))
# 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
# Try to provision now that the challenge files are installed.
"-d", ",".join(domain_list), # first will be main domain
cert = client.issue_certificate(
domain_list,
account_path,
private_key=private_key,
logger=my_logger)
"--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,
except client.NeedToAgreeToTOS as e:
# The user must agree to the Let's Encrypt terms of service agreement
# before any further action can be taken.
ret_item.update({
"result": "agree-to-tos",
"url": e.url,
})
"--webroot", "--webroot-path", webroot,
except client.WaitABit as e:
# We need to hold on for a bit before querying again to see if we can
# acquire a provisioned certificate.
import time, datetime
ret_item.update({
"result": "wait",
"until": e.until_when if not jsonable else e.until_when.isoformat(),
"seconds": (e.until_when - datetime.datetime.now()).total_seconds()
})
"--config-dir", account_path,
#"--staging",
], stderr=subprocess.STDOUT).decode("utf8")
install_cert_copy_file(cert_file, env)
except client.AccountDataIsCorrupt as e:
# This is an extremely rare condition.
ret_item.update({
"result": "error",
"message": "Something unexpected went wrong. It looks like your local Let's Encrypt account data is corrupted. There was a problem with the file " + e.account_file_path + ".",
})
ret[-1]["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"
except (client.InvalidDomainName, client.NeedToTakeAction, client.ChallengeFailed, client.RateLimited, acme.messages.Error, requests.exceptions.RequestException) as e:
ret_item.update({
"result": "error",
"message": "Something unexpected went wrong: " + str(e),
})
else:
# A certificate was issued.
install_status = install_cert(domain_list[0], cert['cert'].decode("ascii"), b"\n".join(cert['chain']).decode("ascii"), env, raw=True)
# str indicates the certificate was not installed.
if isinstance(install_status, str):
ret_item.update({
"result": "error",
"message": "Something unexpected was wrong with the provisioned certificate: " + install_status,
})
else:
# A list indicates success and what happened next.
ret_item["log"].extend(install_status)
ret_item.update({
"result": "installed",
})
# Run post-install steps.
ret.extend(post_install_func(env))
# Return what happened with each certificate request.
return {
"requests": ret,
"problems": problems,
}
return ret
def provision_certificates_cmdline():
import sys
@@ -412,151 +359,39 @@ def provision_certificates_cmdline():
Lock(die=True).forever()
env = load_environment()
verbose = False
headless = False
force_domains = None
show_extended_problems = True
quiet = False
domains = []
args = list(sys.argv)
args.pop(0) # program name
if args and args[0] == "-v":
verbose = True
args.pop(0)
if args and args[0] == "-q":
show_extended_problems = False
args.pop(0)
if args and args[0] == "--headless":
headless = True
args.pop(0)
if args and args[0] == "--force":
force_domains = "ALL"
args.pop(0)
else:
force_domains = args
for arg in sys.argv[1:]:
if arg == "-q":
quiet = True
else:
domains.append(arg)
agree_to_tos_url = None
while True:
# Run the provisioning script. This installs certificates. If there are
# a very large number of domains on this box, it issues separate
# certificates for groups of domains. We have to check the result for
# each group.
def my_logger(message):
if verbose:
print(">", message)
status = provision_certificates(env, agree_to_tos_url=agree_to_tos_url, logger=my_logger, force_domains=force_domains, show_extended_problems=show_extended_problems)
agree_to_tos_url = None # reset to prevent infinite looping
# Go.
status = provision_certificates(env, limit_domains=domains)
if not status["requests"]:
# No domains need certificates.
if not headless or verbose:
if len(status["problems"]) == 0:
print("No domains hosted on this box need a new TLS certificate at this time.")
elif len(status["problems"]) > 0:
print("No TLS certificates could be provisoned at this time:")
print()
for domain in sort_domains(status["problems"], env):
print("%s: %s" % (domain, status["problems"][domain]))
sys.exit(0)
# What happened?
wait_until = None
wait_domains = []
for request in status["requests"]:
if request["result"] == "agree-to-tos":
# We may have asked already in a previous iteration.
if agree_to_tos_url is not None:
continue
# Can't ask the user a question in this mode. Warn the user that something
# needs to be done.
if headless:
print(", ".join(request["domains"]) + " need a new or renewed TLS certificate.")
print()
print("This box can't do that automatically for you until you agree to Let's Encrypt's")
print("Terms of Service agreement. Use the Mail-in-a-Box control panel to provision")
print("certificates for these domains.")
sys.exit(1)
print("""
I'm going to provision a TLS certificate (formerly called a SSL certificate)
for you from Let's Encrypt (letsencrypt.org).
TLS certificates are cryptographic keys that ensure communication between
you and this box are secure when getting and sending mail and visiting
websites hosted on this box. Let's Encrypt is a free provider of TLS
certificates.
Please open this document in your web browser:
%s
It is Let's Encrypt's terms of service agreement. If you agree, I can
provision that TLS certificate. If you don't agree, you will have an
opportunity to install your own TLS certificate from the Mail-in-a-Box
control panel.
Do you agree to the agreement? Type Y or N and press <ENTER>: """
% request["url"], end='', flush=True)
if sys.stdin.readline().strip().upper() != "Y":
print("\nYou didn't agree. Quitting.")
sys.exit(1)
# Okay, indicate agreement on next iteration.
agree_to_tos_url = request["url"]
if request["result"] == "wait":
# Must wait. We'll record until when. The wait occurs below.
if wait_until is None:
wait_until = request["until"]
else:
wait_until = max(wait_until, request["until"])
wait_domains += request["domains"]
if request["result"] == "error":
print(", ".join(request["domains"]) + ":")
print(request["message"])
if request["result"] == "installed":
print("A TLS certificate was successfully installed for " + ", ".join(request["domains"]) + ".")
if wait_until:
# Wait, then loop.
import time, datetime
# 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()
print("A TLS certificate was requested for: " + ", ".join(wait_domains) + ".")
first = True
while wait_until > datetime.datetime.now():
if not headless or first:
print ("We have to wait", int(round((wait_until - datetime.datetime.now()).total_seconds())), "seconds for the certificate to be issued...")
time.sleep(10)
first = False
continue # Loop!
if agree_to_tos_url:
# The user agrees to the TOS. Loop to try again by agreeing.
continue # Loop!
# Unless we were instructed to wait, or we just agreed to the TOS,
# we're done for now.
break
# And finally show the domains with problems.
if len(status["problems"]) > 0:
print("TLS certificates could not be provisoned for:")
for domain in sort_domains(status["problems"], env):
print("%s: %s" % (domain, status["problems"][domain]))
# INSTALLING A NEW CERTIFICATE FROM THE CONTROL PANEL
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, raw=False):
# Write the combined cert+chain to a temporary path and validate that it is OK.
@@ -577,6 +412,16 @@ def install_cert(domain, ssl_cert, ssl_chain, env, raw=False):
cert_status += " " + cert_status_details
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
@@ -594,14 +439,26 @@ def install_cert(domain, ssl_cert, ssl_chain, env, raw=False):
os.makedirs(os.path.dirname(ssl_certificate), exist_ok=True)
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)
@@ -617,12 +474,12 @@ def install_cert(domain, ssl_cert, ssl_chain, env, raw=False):
# Update the web configuration so nginx picks up the new certificate file.
from web_update import do_web_update
ret.append( do_web_update(env) )
if raw: return ret
return "\n".join(ret)
return ret
# VALIDATION OF CERTIFICATES
def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring_soon=True, rounded_time=False, just_check_domain=False):
def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring_soon=10, rounded_time=False, just_check_domain=False):
# Check that the ssl_certificate & ssl_private_key files are good
# for the provided domain.
@@ -728,7 +585,7 @@ def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring
# We'll renew it with Lets Encrypt.
expiry_info = "The certificate expires on %s." % cert_expiration_date.strftime("%x")
if ndays <= 10 and warn_if_expiring_soon:
if warn_if_expiring_soon and ndays <= warn_if_expiring_soon:
# Warn on day 10 to give 4 days for us to automatically renew the
# certificate, which occurs on day 14.
return ("The certificate is expiring soon: " + expiry_info, None)

View File

@@ -28,7 +28,6 @@ def get_services():
{ "name": "Spamassassin", "port": 10025, "public": False, },
{ "name": "OpenDKIM", "port": 8891, "public": False, },
{ "name": "OpenDMARC", "port": 8893, "public": False, },
{ "name": "Memcached", "port": 11211, "public": False, },
{ "name": "Mail-in-a-Box Management Daemon", "port": 10222, "public": False, },
{ "name": "SSH Login (ssh)", "port": get_ssh_port(), "public": True, },
{ "name": "Public DNS (nsd4)", "port": 53, "public": True, },
@@ -393,7 +392,7 @@ def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles):
# Check that PRIMARY_HOSTNAME resolves to PUBLIC_IP[V6] in public DNS.
ipv6 = query_dns(domain, "AAAA") if env.get("PUBLIC_IPV6") else None
if ip == env['PUBLIC_IP'] and not (ipv6 and env['PUBLIC_IPV6'] and normalize_ip(ipv6) != normalize_ip(env['PUBLIC_IPV6'])):
if ip == env['PUBLIC_IP'] and not (ipv6 and env['PUBLIC_IPV6'] and ipv6 != normalize_ip(env['PUBLIC_IPV6'])):
output.print_ok("Domain resolves to box's IP address. [%s%s]" % (env['PRIMARY_HOSTNAME'], my_ips))
else:
output.print_error("""This domain must resolve to your box's IP address (%s) in public DNS but it currently resolves
@@ -640,7 +639,7 @@ def check_web_domain(domain, rounded_time, ssl_certificates, env, output):
for (rtype, expected) in (("A", env['PUBLIC_IP']), ("AAAA", env.get('PUBLIC_IPV6'))):
if not expected: continue # IPv6 is not configured
value = query_dns(domain, rtype)
if normalize_ip(value) == normalize_ip(expected):
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
@@ -687,27 +686,17 @@ 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
# can compare to a well known order.
# Unfortunately, the response.__str__ returns bytes
# instead of string, if it resulted from an AAAA-query.
# We need to convert manually, until this is fixed:
# https://github.com/rthalley/dnspython/issues/204
#
# BEGIN HOTFIX
response_new = []
for r in response:
s = r.to_text()
if isinstance(s, bytes):
s = s.decode('utf-8')
response_new.append(s)
response = response_new
# END HOTFIX
return "; ".join(sorted(str(r).rstrip('.') for r in response))
def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output):
@@ -805,14 +794,14 @@ def get_latest_miab_version():
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"
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:
@@ -892,7 +881,9 @@ def run_and_output_changes(env, pool):
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.
# 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))

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>

View File

@@ -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

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

View File

@@ -200,6 +200,7 @@ 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));
})
}

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 eight characters and may not contain spaces. For best results, <a href="#" onclick="return generate_random_password()">generate a random password</a>.</li>
<li>Passwords must be at least eight characters consisting of English lettters and numbers only. For best results, <a href="#" onclick="return generate_random_password()">generate a random password</a>.</li>
<li>Use <a href="#" onclick="return show_panel('aliases')">aliases</a> to create email addresses that forward to existing accounts.</li>
<li>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">
@@ -156,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;
@@ -213,7 +209,7 @@ function users_set_password(elem) {
show_modal_confirm(
"Set Password",
$("<p>Set a new password for <b>" + email + "</b>?</p> <p><label for='users_set_password_pw' style='display: block; font-weight: normal'>New Password:</label><input type='password' id='users_set_password_pw'></p><p><small>Passwords must be at least four characters and may not contain spaces.</small>" + yourpw + "</p>"),
$("<p>Set a new password for <b>" + email + "</b>?</p> <p><label for='users_set_password_pw' style='display: block; font-weight: normal'>New Password:</label><input type='password' id='users_set_password_pw'></p><p><small>Passwords must be at least eight characters and may not contain spaces.</small>" + yourpw + "</p>"),
"Set Password",
function() {
api(

View File

@@ -149,7 +149,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)
@@ -198,8 +201,11 @@ def get_web_domains_info(env):
# for the SSL config panel, get cert status
def check_cert(domain):
tls_cert = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
if tls_cert is None: return ("danger", "No Certificate Installed")
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":
return ("success", "Signed & valid. " + cert_status_details)

View File

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

View File

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

12
ppa/Vagrantfile vendored
View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
Mail-in-a-Box Security Guide
============================
Mail-in-a-Box 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.

View File

@@ -7,7 +7,36 @@
#########################################################
if [ -z "$TAG" ]; then
TAG=v0.26
# If a version to install isn't explicitly given as an environment
# variable, then install the latest version. But the latest version
# depends on the operating system. Existing Ubuntu 14.04 users need
# to be able to upgrade to the latest version supporting Ubuntu 14.04,
# in part because an upgrade is required before jumping to Ubuntu 18.04.
# New users on Ubuntu 18.04 need to get the latest version number too.
#
# Also, the system status checks read this script for TAG = (without the
# space, but if we put it in a comment it would confuse the status checks!)
# to get the latest version, so the first such line must be the one that we
# want to display in status checks.
if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then
# This machine is running Ubuntu 18.04.
TAG=v0.40
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
@@ -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

@@ -26,6 +26,7 @@ cat > /etc/nsd/nsd.conf << EOF;
# 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?" \
@@ -47,7 +47,7 @@ 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

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
@@ -75,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 {
@@ -131,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
@@ -154,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 {
@@ -167,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 {
@@ -179,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.
@@ -112,17 +112,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

View File

@@ -41,16 +41,8 @@ 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
@@ -81,6 +73,8 @@ tools/editconf.py /etc/postfix/main.cf \
# 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.
@@ -95,6 +89,7 @@ tools/editconf.py /etc/postfix/main.cf \
# 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
@@ -154,7 +149,7 @@ 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,

View File

@@ -65,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

@@ -6,18 +6,32 @@ echo "Installing Mail-in-a-Box system management daemon..."
# DEPENDENCIES
# We used to install management daemon-related Python packages
# directly to /usr/local/lib. We moved to a virtualenv because
# these packages might conflict with apt-installed packages.
# We may have a lingering version of acme that conflcits with
# certbot, which we're about to install below, so remove it
# first. Once acme is installed by an apt package, this might
# break the package version and `apt-get install --reinstall python3-acme`
# might be needed in that case.
while [ -d /usr/local/lib/python3.4/dist-packages/acme ]; do
pip3 uninstall -y acme;
done
# duplicity is used to make backups of user data. It uses boto
# (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.
apt_install duplicity python-pip
#
# virtualenv is used to isolate the Python 3 packages we
# install via pip from the system-installed packages.
#
# certbot installs EFF's certbot which we use to
# provision free TLS certificates.
apt_install duplicity python-pip virtualenv certbot
hide_output pip2 install --upgrade boto
# These are required to build/install the cryptography Python package
# used by our management daemon.
apt_install python-virtualenv build-essential libssl-dev libffi-dev python3-dev
# Create a virtualenv for the installation of Python 3 packages
# used by the management daemon.
inst_dir=/usr/local/lib/mailinabox
@@ -27,39 +41,16 @@ if [ ! -d $venv ]; then
virtualenv -ppython3 $venv
fi
# pip<6.1 + setuptools>=34 had 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 used to
# instal cryptography system-wide under those versions, it updated
# setuptools to version 34, which created the conflict, and
# then pip gets permanently broken with errors like
# "ImportError: No module named 'packaging'".
#
# Let's test for the error:
if ! python3 -c "from pkg_resources import load_entry_point" 2&> /dev/null; then
# This system seems to be broken already.
echo "Fixing broken pip and setuptools..."
rm -rf /usr/local/lib/python3.4/dist-packages/{pkg_resources,setuptools}*
apt-get install --reinstall python3-setuptools python3-pip python3-pkg-resources
fi
#
# The easiest work-around on systems that aren't already broken is
# to upgrade pip (to >=9.0.1) and setuptools (to >=34.1) individually
# before we install any package that tries to update setuptools.
# Upgrade pip because the Ubuntu-packaged version is out of date.
hide_output $venv/bin/pip install --upgrade pip
hide_output $venv/bin/pip install --upgrade setuptools
# Install other Python 3 packages used by the management daemon.
# 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.
# Force acme to be updated because it seems to need it after the
# pip/setuptools breakage (see above) and the ACME protocol may
# have changed (I got an error on one of my systems).
hide_output $venv/bin/pip install --upgrade \
rtyaml "email_validator>=1.0.0" "free_tls_certificates>=0.1.3" "exclusiveprocess" \
rtyaml "email_validator>=1.0.0" "exclusiveprocess" \
flask dnspython python-dateutil \
"idna>=2.0.0" "cryptography>=1.0.2" acme boto psutil
"idna>=2.0.0" "cryptography==2.2.2" boto psutil
# CONFIGURATION
@@ -96,20 +87,15 @@ rm -f /tmp/bootstrap.zip
# Create an init script to start the management daemon and keep it
# running after a reboot.
rm -f /usr/local/bin/mailinabox-daemon # old path
cat > $inst_dir/start <<EOF;
#!/bin/bash
source $venv/bin/activate
python `pwd`/management/daemon.py
exec python `pwd`/management/daemon.py
EOF
chmod +x $inst_dir/start
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
hide_output systemctl link 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.

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

@@ -29,7 +29,7 @@ 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
@@ -38,11 +38,13 @@ 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.)
@@ -59,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 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

View File

@@ -9,29 +9,11 @@ source /etc/mailinabox.conf # load global vars
echo "Installing Nextcloud (contacts/calendar)..."
# Keep the php5 dependancies for the owncloud upgrades
apt_install \
dbconfig-common \
php5-cli php5-sqlite php5-gd php5-imap php5-curl php-pear php-apc curl libapr1 libtool libcurl4-openssl-dev php-xml-parser \
php5 php5-dev php5-gd php5-fpm memcached php5-memcached
apt-get purge -qq -y owncloud* # we used to use the package manager
apt-get purge -qq -y owncloud*
apt_install php7.0 php7.0-fpm \
php7.0-cli php7.0-sqlite php7.0-gd php7.0-imap php7.0-curl php-pear php-apc curl \
php7.0-dev php7.0-gd memcached php7.0-memcached php7.0-xml php7.0-mbstring php7.0-zip php7.0-apcu
# Migrate <= v0.10 setups that stored the ownCloud config.php in /usr/local rather than
# in STORAGE_ROOT. Move the file to STORAGE_ROOT.
if [ ! -f $STORAGE_ROOT/owncloud/config.php ] \
&& [ -f /usr/local/lib/owncloud/config/config.php ]; then
# Move config.php and symlink back into previous location.
echo "Migrating owncloud/config.php to new location."
mv /usr/local/lib/owncloud/config/config.php $STORAGE_ROOT/owncloud/config.php \
&& \
ln -sf $STORAGE_ROOT/owncloud/config.php /usr/local/lib/owncloud/config/config.php
fi
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
InstallNextcloud() {
@@ -57,11 +39,11 @@ InstallNextcloud() {
# their github repositories.
mkdir -p /usr/local/lib/owncloud/apps
wget_verify https://github.com/nextcloud/contacts/releases/download/v1.5.3/contacts.tar.gz 78c4d49e73f335084feecd4853bd8234cf32615e /tmp/contacts.tgz
wget_verify https://github.com/nextcloud/contacts/releases/download/v2.1.5/contacts.tar.gz b7460d15f1b78d492ed502d778c0c458d503ba17 /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.5.3/calendar.tar.gz b370352d1f280805cc7128f78af4615f623827f8 /tmp/calendar.tgz
wget_verify https://github.com/nextcloud/calendar/releases/download/v1.6.1/calendar.tar.gz f93a247cbd18bc624f427ba2a967d93ebb941f21 /tmp/calendar.tgz
tar xf /tmp/calendar.tgz -C /usr/local/lib/owncloud/apps/
rm /tmp/calendar.tgz
@@ -75,7 +57,7 @@ InstallNextcloud() {
# Make sure permissions are correct or the upgrade step won't run.
# $STORAGE_ROOT/owncloud may not yet exist, so use -f to suppress
# that error.
chown -f -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
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).
@@ -93,155 +75,52 @@ InstallNextcloud() {
fi
}
# We only install ownCloud intermediate versions to be able to seemlesly upgrade to Nextcloud
InstallOwncloud() {
nextcloud_ver=13.0.6
nextcloud_hash=33e41f476f0e2be5dc7cdb9d496673d9647aa3d6
version=$1
hash=$2
echo
echo "Upgrading to OwnCloud version $version"
echo
# Remove the current owncloud/Nextcloud
rm -rf /usr/local/lib/owncloud
# Download and verify
wget_verify https://download.owncloud.org/community/owncloud-$version.zip $hash /tmp/owncloud.zip
# Extract ownCloud
unzip -q /tmp/owncloud.zip -d /usr/local/lib
rm -f /tmp/owncloud.zip
# The two apps we actually want are not in Nextcloud core. Download the releases from
# their github repositories.
mkdir -p /usr/local/lib/owncloud/apps
wget_verify https://github.com/owncloud/contacts/releases/download/v1.4.0.0/contacts.tar.gz c1c22d29699456a45db447281682e8bc3f10e3e7 /tmp/contacts.tgz
tar xf /tmp/contacts.tgz -C /usr/local/lib/owncloud/apps/
rm /tmp/contacts.tgz
wget_verify https://github.com/nextcloud/calendar/releases/download/v1.4.0/calendar.tar.gz c84f3170efca2a99ea6254de34b0af3cb0b3a821 /tmp/calendar.tgz
tar xf /tmp/calendar.tgz -C /usr/local/lib/owncloud/apps/
rm /tmp/calendar.tgz
# Fix weird permissions.
chmod 750 /usr/local/lib/owncloud/{apps,config}
# Create a symlink to the config.php in STORAGE_ROOT (for upgrades we're restoring the symlink we previously
# put in, and in new installs we're creating a symlink and will create the actual config later).
ln -sf $STORAGE_ROOT/owncloud/config.php /usr/local/lib/owncloud/config/config.php
# Make sure permissions are correct or the upgrade step won't run.
# $STORAGE_ROOT/owncloud may not yet exist, so use -f to suppress
# that error.
chown -f -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
# If this isn't a new installation, immediately run the upgrade script.
# Then check for success (0=ok and 3=no upgrade needed, both are success).
if [ -e $STORAGE_ROOT/owncloud/owncloud.db ]; then
# ownCloud 8.1.1 broke upgrades. It may fail on the first attempt, but
# that can be OK.
sudo -u www-data php5 /usr/local/lib/owncloud/occ upgrade
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then
echo "Trying ownCloud upgrade again to work around ownCloud upgrade bug..."
sudo -u www-data php5 /usr/local/lib/owncloud/occ upgrade
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
sudo -u www-data php5 /usr/local/lib/owncloud/occ maintenance:mode --off
echo "...which seemed to work."
fi
fi
}
owncloud_ver=12.0.3
owncloud_hash=beab41f6a748a43f0accfa6a9808387aef718c61
# Check if Nextcloud dir exist, and check if version matches owncloud_ver (if either doesn't - install/upgrade)
# 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 $owncloud_ver /usr/local/lib/owncloud/version.php; then
|| ! 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.0-fpm stop &> /dev/null || /bin/true
service php5-fpm stop &> /dev/null || /bin/true
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 ownCloud/Nextcloud to $owncloud_flavor $owncloud_ver (backing up existing installation, configuration and database to directory to $BACKUP_DIRECTORY..."
echo "Upgrading Nextcloud --- backing up existing installation, configuration, and database to directory to $BACKUP_DIRECTORY..."
cp -r /usr/local/lib/owncloud "$BACKUP_DIRECTORY/owncloud-install"
fi
if [ -e /home/user-data/owncloud/owncloud.db ]; then
cp /home/user-data/owncloud/owncloud.db $BACKUP_DIRECTORY
fi
if [ -e /home/user-data/owncloud/config.php ]; then
cp /home/user-data/owncloud/config.php $BACKUP_DIRECTORY
fi
fi
if [ -e /home/user-data/owncloud/config.php ]; then
cp /home/user-data/owncloud/config.php $BACKUP_DIRECTORY
fi
# We only need to check if we do upgrades when owncloud/Nextcloud was previously installed
# If ownCloud or Nextcloud was previously installed....
if [ -e /usr/local/lib/owncloud/version.php ]; then
if grep -q "OC_VersionString = '8\.1\.[0-9]" /usr/local/lib/owncloud/version.php; then
echo "We are running 8.1.x, upgrading to 8.2.3 first"
InstallOwncloud 8.2.3 bfdf6166fbf6fc5438dc358600e7239d1c970613
# 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.26c (dated February 13, 2018) with Nextcloud < 12.0.5 (you have ownCloud 8 or 9) are not supported. Upgrade to Mail-in-a-Box version v0.28 first. Setup aborting."
exit 1
fi
if grep -q "OC_VersionString = '10\." /usr/local/lib/owncloud/version.php; then
echo "Upgrades from Mail-in-a-Box prior to v0.26c (dated February 13, 2018) with Nextcloud < 12.0.5 (you have ownCloud 10) are not supported. Upgrade to Mail-in-a-Box version v0.28 first. Setup aborting."
exit 1
fi
# If we are upgrading from 8.2.x we should go to 9.0 first. Owncloud doesn't support skipping minor versions
if grep -q "OC_VersionString = '8\.2\.[0-9]" /usr/local/lib/owncloud/version.php; then
echo "We are running version 8.2.x, upgrading to 9.0.2 first"
# We need to disable memcached. The upgrade and install fails
# with memcached
CONFIG_TEMP=$(/bin/mktemp)
php <<EOF > $CONFIG_TEMP && mv $CONFIG_TEMP $STORAGE_ROOT/owncloud/config.php;
<?php
include("$STORAGE_ROOT/owncloud/config.php");
\$CONFIG['memcache.local'] = '\OC\Memcache\APCu';
echo "<?php\n\\\$CONFIG = ";
var_export(\$CONFIG);
echo ";";
?>
EOF
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
# We can now install owncloud 9.0.2
InstallOwncloud 9.0.2 72a3d15d09f58c06fa8bee48b9e60c9cd356f9c5
# The owncloud 9 migration doesn't migrate calendars and contacts
# The option to migrate these are removed in 9.1
# So the migrations should be done when we have 9.0 installed
sudo -u www-data php5 /usr/local/lib/owncloud/occ dav:migrate-addressbooks
# The following migration has to be done for each owncloud user
for directory in $STORAGE_ROOT/owncloud/*@*/ ; do
username=$(basename "${directory}")
sudo -u www-data php5 /usr/local/lib/owncloud/occ dav:migrate-calendar $username
done
sudo -u www-data php5 /usr/local/lib/owncloud/occ dav:sync-birthday-calendar
fi
# If we are upgrading from 9.0.x we should go to 9.1 first.
if grep -q "OC_VersionString = '9\.0\.[0-9]" /usr/local/lib/owncloud/version.php; then
echo "We are running ownCloud 9.0.x, upgrading to ownCloud 9.1.4 first"
InstallOwncloud 9.1.4 e637cab7b2ca3346164f3506b1a0eb812b4e841a
fi
# If we are upgrading from 9.1.x we should go to Nextcloud 10.0 first.
if grep -q "OC_VersionString = '9\.1\.[0-9]" /usr/local/lib/owncloud/version.php; then
echo "We are running ownCloud 9.1.x, upgrading to Nextcloud 10.0.5 first"
InstallNextcloud 10.0.5 686f6a8e9d7867c32e3bf3ca63b3cc2020564bf6
fi
# If we are upgrading from 10.0.x we should go to Nextcloud 11.0 first.
if grep -q "OC_VersionString = '10\.0\.[0-9]" /usr/local/lib/owncloud/version.php; then
echo "We are running Nextcloud 10.0.x, upgrading to Nextcloud 11.0.3 first"
InstallNextcloud 11.0.3 a396aaa1c9f920099a90a86b4a9cd0ec13083c99
# If we are upgrading from Nextcloud 11 we should go to Nextcloud 12 first.
if grep -q "OC_VersionString = '11\." /usr/local/lib/owncloud/version.php; then
echo "We are running Nextcloud 11, upgrading to Nextcloud 12.0.5 first"
InstallNextcloud 12.0.5 d25afbac977a4e331f5e38df50aed0844498ca86
fi
fi
InstallNextcloud $owncloud_ver $owncloud_hash
InstallNextcloud $nextcloud_ver $nextcloud_hash
fi
# ### Configuring Nextcloud
@@ -266,10 +145,10 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
'overwritewebroot' => '/cloud',
'overwrite.cli.url' => '/cloud',
'user_backends' => array(
array(
'class'=>'OC_User_IMAP',
'arguments'=>array('{127.0.0.1:993/imap/ssl/novalidate-cert}')
)
array(
'class'=>'OC_User_IMAP',
'arguments'=>array('{127.0.0.1:993/imap/ssl/novalidate-cert}')
)
),
'memcache.local' => '\OC\Memcache\APCu',
'mail_smtpmode' => 'sendmail',
@@ -365,7 +244,7 @@ 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.0/fpm/php.ini -c ';' \
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
upload_max_filesize=16G \
post_max_size=16G \
output_buffering=16384 \
@@ -374,7 +253,7 @@ tools/editconf.py /etc/php/7.0/fpm/php.ini -c ';' \
short_open_tag=On
# Set Nextcloud recommended opcache settings
tools/editconf.py /etc/php/7.0/cli/conf.d/10-opcache.ini -c ';' \
tools/editconf.py /etc/php/7.2/cli/conf.d/10-opcache.ini -c ';' \
opcache.enable=1 \
opcache.enable_cli=1 \
opcache.interned_strings_buffer=8 \
@@ -384,12 +263,12 @@ tools/editconf.py /etc/php/7.0/cli/conf.d/10-opcache.ini -c ';' \
opcache.revalidate_freq=1
# Configure the path environment for php-fpm
tools/editconf.py /etc/php/7.0/fpm/pool.d/www.conf -c ';' \
env[PATH]=/usr/local/bin:/usr/bin:/bin
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.0/mods-available/apcu.ini; then
tools/editconf.py /etc/php/7.0/mods-available/apcu.ini -c ';' \
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
@@ -411,4 +290,4 @@ chmod +x /etc/cron.hourly/mailinabox-owncloud
# ```
# Enable PHP modules and restart PHP.
restart_service php7.0-fpm
restart_service php7.2-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
@@ -41,7 +41,7 @@ if [ $TOTAL_PHYSICAL_MEM -lt 750000 ]; then
fi
# Check that tempfs is mounted with exec
MOUNTED_TMP_AS_NO_EXEC=$(grep "/tmp.*noexec" /proc/mounts)
MOUNTED_TMP_AS_NO_EXEC=$(grep "/tmp.*noexec" /proc/mounts || /bin/true)
if [ -n "$MOUNTED_TMP_AS_NO_EXEC" ]; then
echo "Mail-in-a-Box has to have exec rights on /tmp, please mount /tmp with exec"
exit
@@ -53,16 +53,14 @@ if [ -e ~/.wgetrc ]; then
exit
fi
# Check that we are running on x86_64 or i686, any other architecture is unsupported and
# will fail later in the setup when we try to install the custom build lucene packages.
#
# Set ARM=1 to ignore this check if you have built the packages yourself. If you do this
# you are on your own!
# 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
if [ -z "$ARM" ]; then
echo "Mail-in-a-Box only supports x86_64 or i686 and will not work on any other architecture, like ARM."
echo "Your architecture is $ARCHITECTURE"
exit
fi
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,4 +1,4 @@
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
@@ -12,7 +12,9 @@ if [ -z "$NONINTERACTIVE" ]; then
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" \
@@ -23,8 +25,8 @@ if [ -z "$NONINTERACTIVE" ]; then
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
@@ -49,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?" \
@@ -84,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
@@ -119,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
@@ -152,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
@@ -184,11 +186,11 @@ 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

@@ -4,7 +4,7 @@
source setup/functions.sh # load our functions
# Check system setup: Are we running as root on Ubuntu 14.04 on a
# Check system setup: Are we running as root on Ubuntu 18.04 on a
# machine with enough memory? Is /tmp mounted with exec.
# If not, this shows an error and exits.
source setup/preflight.sh
@@ -14,7 +14,7 @@ source setup/preflight.sh
# 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
@@ -60,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
@@ -106,7 +106,7 @@ 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
@@ -127,13 +127,23 @@ tools/web_update
# fail2ban was first configured, but they should exist now.
restart_service fail2ban
# If DNS is already working, try to provision TLS certficates from Let's Encrypt.
# Suppress extra reasons why domains aren't getting a new certificate.
management/ssl_certificates.py -q
# If there aren't any mail users yet, create one.
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 "-----------------------------------------------"

View File

@@ -14,6 +14,13 @@ source setup/functions.sh # load our functions
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
@@ -37,9 +44,9 @@ hostname $PRIMARY_HOSTNAME
# for reference
SWAP_MOUNTED=$(cat /proc/swaps | tail -n+2)
SWAP_IN_FSTAB=$(grep "swap" /etc/fstab)
ROOT_IS_BTRFS=$(grep "\/ .*btrfs" /proc/mounts)
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $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" ] &&
@@ -68,17 +75,10 @@ then
fi
fi
# ### Add Mail-in-a-Box's PPA.
# We've built several .deb packages on our own that we want to include.
# One is a replacement for Ubuntu's stock postgrey package that makes
# some enhancements. The other is dovecot-lucene, a Lucene-based full
# text search plugin for (and by) dovecot, which is not available in
# Ubuntu currently.
#
# So, first ensure add-apt-repository is installed, then use it to install
# the [mail-in-a-box ppa](https://launchpad.net/~mail-in-a-box/+archive/ubuntu/ppa).
# ### 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..."
@@ -86,11 +86,14 @@ 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
@@ -123,23 +126,11 @@ echo Installing system packages...
apt_install python3 python3-dev python3-pip \
netcat-openbsd wget curl git sudo coreutils bc \
haveged pollinate unzip \
unattended-upgrades cron ntp fail2ban
# ### Add PHP7 PPA
# Nextcloud requires PHP7, we will install the ppa from ubuntu php maintainer Ondřej Surý
# The PPA is located here https://launchpad.net/%7Eondrej/+archive/ubuntu/php
# Unattended upgrades are activated for the repository If it appears it's already
# installed, don't do it again so we can avoid an unnecessary call to apt-get update.
if [ ! -f /etc/apt/sources.list.d/ondrej-php-trusty.list ]; then
hide_output add-apt-repository -y ppa:ondrej/php
apt_add_repository_to_unattended_upgrades LP-PPA-ondrej-php:trusty
hide_output apt-get update
fi
unattended-upgrades cron ntp fail2ban rsyslog
# ### Suppress Upgrade Prompts
# Since Mail-in-a-Box might jump straight to 18.04 LTS, there's no need
# to be reminded about 16.04 on every login.
# 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
@@ -159,8 +150,8 @@ fi
# 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.
@@ -186,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
@@ -256,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
@@ -281,45 +271,69 @@ 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

View File

@@ -19,10 +19,7 @@ fi
echo "Installing Nginx (web server)..."
apt_install nginx php7.0-cli php7.0-fpm
# Set PHP7 as the default
update-alternatives --set php /usr/bin/php7.0
apt_install nginx php-cli php-fpm
rm -f /etc/nginx/sites-enabled/default
@@ -44,19 +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/php/7.0/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/php/7.0/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.0/fpm/pool.d/www.conf -c ';' \
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/php/7.0/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
@@ -94,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 php7.0-fpm
restart_service php7.2-fpm
# Open ports.
ufw_allow http

View File

@@ -22,25 +22,18 @@ source /etc/mailinabox.conf # load global vars
echo "Installing Roundcube (webmail)..."
apt_install \
dbconfig-common \
php7.0-cli php7.0-sqlite php7.0-mcrypt php7.0-intl php7.0-json php7.0-common \
php7.0-gd php7.0-pspell tinymce libjs-jquery libjs-jquery-mousewheel libmagic1 php7.0-mbstring
apt_get_quiet remove php-mail-mimedecode # no longer needed since Roundcube 1.1.3
# We used to install Roundcube from Ubuntu, without triggering the dependencies #NODOC
# on Apache and MySQL, by downloading the debs and installing them manually. #NODOC
# Now that we're beyond that, get rid of those debs before installing from source. #NODOC
apt-get purge -qq -y roundcube* #NODOC
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 plugins to track
# whether we have the latest version of everything.
VERSION=1.3.3
HASH=903a4eb1bfc25e9a08d782a7f98502cddfa579de
VERSION=1.3.8
HASH=90c7900ccf7b2f46fe49c650d5adb9b85ee9cc22
PERSISTENT_LOGIN_VERSION=dc5ca3d3f4415cc41edb2fde533c8a8628a94c76
HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5
CARDDAV_VERSION=2.0.4
CARDDAV_HASH=d93f3cfb3038a519e71c7c3212c1d16f5da609a4
CARDDAV_VERSION=3.0.3
CARDDAV_HASH=d1e3b0d851ffa2c6bd42bf0c04f70d0e1d0d78f8
UPDATE_KEY=$VERSION:$PERSISTENT_LOGIN_VERSION:$HTML5_NOTIFIER_VERSION:$CARDDAV_VERSION
@@ -155,6 +148,7 @@ cat > ${RCM_PLUGIN_DIR}/carddav/config.inc.php <<EOF;
'preemptive_auth' => '1',
'hide' => false,
);
?>
EOF
# Create writable directories.
@@ -199,5 +193,5 @@ chown www-data:www-data $STORAGE_ROOT/mail/roundcube/roundcube.sqlite
chmod 664 $STORAGE_ROOT/mail/roundcube/roundcube.sqlite
# Enable PHP modules.
phpenmod -v php7.0 mcrypt imap
restart_service php7.0-fpm
phpenmod -v php mcrypt imap
restart_service php7.2-fpm

View File

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

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

@@ -26,8 +26,7 @@ if [ ! -f $1/config.php ]; then
fi
echo "Restoring backup from $1"
service php5-fpm stop
service php7.0-fpm stop
service php7.2-fpm stop
# remove the current ownCloud/Nextcloud installation
rm -rf /usr/local/lib/owncloud/
@@ -46,6 +45,5 @@ chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off
service php5-fpm start
service php7.0-fpm start
service php7.2-fpm start
echo "Done"

View File

@@ -20,4 +20,4 @@ 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

@@ -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)