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

Compare commits

...

160 Commits

Author SHA1 Message Date
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
Michael Kroes
6eb9055275 Upgrade NextCloud to 13.06 (#1436) 2018-10-09 07:09:54 -04:00
Joshua Tauberer
504a9b0abc certbot uses a new directory path for API v02 accounts and we should check that before creating a new account or else we'll try to create a new account on each setup run (which certbot just fails on) 2018-09-03 13:07:24 -04:00
Joshua Tauberer
842fbb3d72 auto-agree to Let's Encrypt's terms of service during setup
fixes #1409

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

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

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

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

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

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

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

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

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

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

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

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

Broken by 0088fb4553.

Fixes #1326.

See https://discourse.mailinabox.email/t/that-is-not-a-valid-email-error-during-mailinabox-installation/2793.
2018-01-20 10:59:37 -05:00
barrybingo
a6a1cc7ae0 Reduce munin-node log level to warning (#1330) 2018-01-19 12:00:44 -05:00
Joshua Tauberer
b5c0736d27 release v0.26 2018-01-18 17:10:23 -05:00
Joshua Tauberer
8ee7de6ff3 no need to do a second apt-get update after 'installing' the PHP7 PPA if the PPA was already installed 2018-01-15 13:28:18 -05:00
Joshua Tauberer
0088fb4553 install Python 3 packages in a virtualenv
The cryptography package has created all sorts of installation trouble over the last few years, probably because of mismatches between OS-installed packages and pip-installed packages. Using a virtualenv for all Python packages used by the management daemon should make sure everything is consistent.

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

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

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

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

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

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

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

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

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

* Upgrade ownCloud to latest patch release 9.1.4

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

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

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

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

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

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

Fixes: #1019

* Added more verbose error messages upon rsync failures

fixes #1033

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

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

* Upgrade to ownCloud 9.1.2 from 9.1.1 would fail because the guid of 9.1.1 matched with the regex for the version of 8.x
2016-11-30 12:54:27 -05:00
59 changed files with 2154 additions and 1050 deletions

30
.editorconfig Normal file
View File

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

1
.gitignore vendored
View File

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

View File

@@ -1,6 +1,200 @@
CHANGELOG CHANGELOG
========= =========
v0.29 (October 25, 2018)
------------------------
* Starting with v0.28, TLS certificate provisioning wouldn't work on new boxes until the mailinabox setup command was run a second time because of a problem with the non-interactive setup.
* Update to Nextcloud 13.0.6.
* Update to Roundcube 1.3.7.
* Update to Z-Push 2.4.4.
* Backup dates listed in the control panel now use an internationalized format.
v0.28 (July 30, 2018)
---------------------
System:
* We now use EFF's `certbot` to provision TLS certificates (from Let's Encrypt) instead of our home-grown ACME library.
Contacts/Calendar:
* Fix for Mac OS X autoconfig of the calendar.
Setup:
* Installing Z-Push broke because of what looks like a change or problem in their git server HTTPS certificate. That's fixed.
v0.27 (June 14, 2018)
---------------------
Mail:
* A report of box activity, including sent/received mail totals and logins by user, is now emailed to the box's administrator user each week.
* Update Roundcube to version 1.3.6 and Z-Push to version 2.3.9.
Control Panel:
* The undocumented feature for proxying web requests to another server now sets X-Forwarded-For.
v0.26c (February 13, 2018)
--------------------------
Setup:
* Upgrades from v0.21c (February 1, 2017) or earlier were broken because the intermediate versions of ownCloud used in setup were no longer available from ownCloud.
* Some download errors had no output --- there is more output on error now.
Control Panel:
* The background service for the control panel was not restarting on updates, leaving the old version running. This was broken in v0.26 and is now fixed.
* Installing your own TLS/SSL certificate had been broken since v0.24 because the new version of openssl became stricter about CSR generation parameters.
* Fixed password length help text.
Contacts/Calendar:
* Upgraded Nextcloud from 12.0.3 to 12.0.5.
v0.26b (January 25, 2018)
-------------------------
* Fix new installations which broke at the step of asking for the user's desired email address, which was broken by v0.26's changes related to the control panel.
* Fix the provisioning of TLS certificates by pinning a Python package we rely on (acme) to an earlier version because our code isn't yet compatible with its current version.
* Reduce munin's log_level from debug to warning to prevent massive log files.
v0.26 (January 18, 2018)
------------------------
Security:
* HTTPS, IMAP, and POP's TLS settings have been updated to Mozilla's intermediate cipher list recommendation. Some extremely old devices that use less secure TLS ciphers may no longer be able to connect to IMAP/POP.
* Updated web HSTS header to use longer six month duration.
Mail:
* Adding attachments in Roundcube broke after the last update for some users after rebooting because a temporary directory was deleted on reboot. The temporary directory is now moved from /tmp to /var so that it is persistent.
* `X-Spam-Score` header is added to incoming mail.
Control panel:
* RSASHA256 is now used for DNSSEC for .lv domains.
* Some documentation/links improvements.
Installer:
* We now run `apt-get autoremove` at the start of setup to clear out old packages, especially old kernels that take up a lot of space. On the first run, this step may take a long time.
* We now fetch Z-Push from its tagged git repository, fixing an installation problem.
* Some old PHP5 packages are removed from setup, fixing an installation bug where Apache would get installed.
* Python 3 packages for the control panel are now installed using a virtualenv to prevent installation errors due to conflicts in the cryptography/openssl packages between OS-installed packages and pip-installed packages.
v0.25 (November 15, 2017)
-------------------------
This update is a security update addressing [CVE-2017-16651, a vulnerability in Roundcube webmail that allows logged-in users to access files on the local filesystem](https://roundcube.net/news/2017/11/08/security-updates-1.3.3-1.2.7-and-1.1.10).
Mail:
* Update to Roundcube 1.3.3.
Control Panel:
* Allow custom DNS records to be set for DNS wildcard subdomains (i.e. `*`).
v0.24 (October 3, 2017)
-----------------------
System:
* Install PHP7 via a PPA. Switch to the on-demand process manager.
Mail:
* Updated to [Roundcube 1.3.1](https://roundcube.net/news/2017/06/26/roundcube-webmail-1.3.0-released), but unfortunately dropping the Vacation plugin because it has not been supported by its author and is not compatible with Roundcube 1.3, and updated the persistent login plugin.
* Updated to [Z-Push 2.3.8](http://download.z-push.org/final/2.3/z-push-2.3.8.txt).
* Dovecot now uses stronger 2048 bit DH params for better forward secrecy.
Nextcloud:
* Nextcloud updated to 12.0.3, using PHP7.
Control Panel:
* Nameserver (NS) records can now be set on custom domains.
* Fix an erroneous status check error due to IPv6 address formatting.
* Aliases for administrative addresses can now be set to send mail to +tag administrative addresses.
v0.23a (May 31, 2017)
---------------------
Corrects a problem in the new way third-party assets are downloaded during setup for the control panel, since v0.23.
v0.23 (May 30, 2017)
--------------------
Mail:
* The default theme for Roundcube was changed to the nicer Larry theme.
* Exchange/ActiveSync support has been replaced with z-push 2.3.6 from z-push.org (rather than z-push-contrib).
ownCloud (now Nextcloud):
* ownCloud is replaced with Nextcloud 10.0.5.
* Fixed an error in Owncloud/Nextcloud setup not updating domain when changing hostname.
Control Panel/Management:
* Fix an error in the control panel showing rsync backup status.
* Fix an error in the control panel related to IPv6 addresses.
* TLS certificates for internationalized domain names can now be provisioned from Let's Encrypt automatically.
* Third-party assets used in the control panel (jQuery/Bootstrap) are now downloaded during setup and served from the box rather than from a CDN.
DNS:
* Add support for custom CAA records.
v0.22 (April 2, 2017)
---------------------
Mail:
* The CardDAV plugin has been added to Roundcube so that your ownCloud contacts are available in webmail.
* Upgraded to Roundcube 1.2.4 and updated the persistent login plugin.
* Allow larger messages to be checked by SpamAssassin.
* Dovecot's vsz memory limit has been increased proportional to system memory.
* Newly set user passwords must be at least eight characters.
ownCloud:
* Upgraded to ownCloud 9.1.4.
Control Panel/Management:
* The status checks page crashed when the mailinabox.email website was down - that's fixed.
* Made nightly re-provisioning of TLS certificates less noisy.
* Fixed bugs in rsync backup method and in the list of recent backups.
* Fixed incorrect status checks errors about IPv6 addresses.
* Fixed incorrect status checks errors for secondary nameservers if round-robin custom A records are set.
* The management mail_log.py tool has been rewritten.
DNS:
* Added support for DSA, ED25519, and custom SSHFP records.
System:
* The SSH fail2ban jail was not activated.
Installation:
* At the end of installation, the SHA256 -- rather than SHA1 -- hash of the system's TLS certificate is shown.
v0.21c (February 1, 2017)
-------------------------
Installations and upgrades started failing about 10 days ago with the error "ImportError: No module named 'packaging'" after an upstream package (Python's setuptools) was updated by its maintainers. The updated package conflicted with Ubuntu 14.04's version of another package (Python's pip). This update upgrades both packages to remove the conflict.
If you already encountered the error during installation or upgrade of Mail-in-a-Box, this update may not correct the problem on your existing system. See https://discourse.mailinabox.email/t/v0-21c-release-fixes-python-package-installation-issue/1881 for help if the problem persists after upgrading to this version of Mail-in-a-Box.
v0.21b (December 4, 2016) v0.21b (December 4, 2016)
------------------------- -------------------------

View File

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

View File

@@ -28,7 +28,7 @@ It is a one-click email appliance. There are no user-configurable setup options.
The components installed are: The components installed are:
* SMTP ([postfix](http://www.postfix.org/)), IMAP ([dovecot](http://dovecot.org/)), CardDAV/CalDAV ([ownCloud](https://owncloud.org/)), Exchange ActiveSync ([z-push](https://github.com/fmbiete/Z-Push-contrib)) * SMTP ([postfix](http://www.postfix.org/)), IMAP ([dovecot](http://dovecot.org/)), CardDAV/CalDAV ([Nextcloud](https://nextcloud.com/)), Exchange ActiveSync ([z-push](http://z-push.org/))
* Webmail ([Roundcube](http://roundcube.net/)), static website hosting ([nginx](http://nginx.org/)) * Webmail ([Roundcube](http://roundcube.net/)), static website hosting ([nginx](http://nginx.org/))
* Spam filtering ([spamassassin](https://spamassassin.apache.org/)), greylisting ([postgrey](http://postgrey.schweikert.ch/)) * Spam filtering ([spamassassin](https://spamassassin.apache.org/)), greylisting ([postgrey](http://postgrey.schweikert.ch/))
* DNS ([nsd4](https://www.nlnetlabs.nl/projects/nsd/)) with [SPF](https://en.wikipedia.org/wiki/Sender_Policy_Framework), DKIM ([OpenDKIM](http://www.opendkim.org/)), [DMARC](https://en.wikipedia.org/wiki/DMARC), [DNSSEC](https://en.wikipedia.org/wiki/DNSSEC), [DANE TLSA](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities), and [SSHFP](https://tools.ietf.org/html/rfc4255) records automatically set * DNS ([nsd4](https://www.nlnetlabs.nl/projects/nsd/)) with [SPF](https://en.wikipedia.org/wiki/Sender_Policy_Framework), DKIM ([OpenDKIM](http://www.opendkim.org/)), [DMARC](https://en.wikipedia.org/wiki/DMARC), [DNSSEC](https://en.wikipedia.org/wiki/DNSSEC), [DANE TLSA](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities), and [SSHFP](https://tools.ietf.org/html/rfc4255) records automatically set
@@ -59,7 +59,7 @@ by me:
$ curl -s https://keybase.io/joshdata/key.asc | gpg --import $ curl -s https://keybase.io/joshdata/key.asc | gpg --import
gpg: key C10BDD81: public key "Joshua Tauberer <jt@occams.info>" imported gpg: key C10BDD81: public key "Joshua Tauberer <jt@occams.info>" imported
$ git verify-tag v0.21b $ git verify-tag v0.29
gpg: Signature made ..... using RSA key ID C10BDD81 gpg: Signature made ..... using RSA key ID C10BDD81
gpg: Good signature from "Joshua Tauberer <jt@occams.info>" gpg: Good signature from "Joshua Tauberer <jt@occams.info>"
gpg: WARNING: This key is not certified with a trusted signature! gpg: WARNING: This key is not certified with a trusted signature!
@@ -72,7 +72,7 @@ and on my [personal homepage](https://razor.occams.info/). (Of course, if this r
Checkout the tag corresponding to the most recent release: Checkout the tag corresponding to the most recent release:
$ git checkout v0.21b $ git checkout v0.29
Begin the installation. Begin the installation.
@@ -82,6 +82,12 @@ For help, DO NOT contact me directly --- I don't do tech support by email or twe
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. 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.
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.
The Acknowledgements The Acknowledgements
-------------------- --------------------

14
Vagrantfile vendored
View File

@@ -5,23 +5,27 @@ Vagrant.configure("2") do |config|
config.vm.box = "ubuntu14.04" config.vm.box = "ubuntu14.04"
config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box" config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"
if Vagrant.has_plugin?("vagrant-cachier")
# Configure cached packages to be shared between instances of the same base box.
# More info on http://fgrehm.viewdocs.io/vagrant-cachier/usage
config.cache.scope = :box
end
# Network config: Since it's a mail server, the machine must be connected # Network config: Since it's a mail server, the machine must be connected
# to the public web. However, we currently don't want to expose SSH since # to the public web. However, we currently don't want to expose SSH since
# the machine's box will let anyone log into it. So instead we'll put the # the machine's box will let anyone log into it. So instead we'll put the
# machine on a private network. # machine on a private network.
config.vm.hostname = "mailinabox" config.vm.hostname = "mailinabox.lan"
config.vm.network "private_network", ip: "192.168.50.4" config.vm.network "private_network", ip: "192.168.50.4"
config.vm.provision :shell, :inline => <<-SH config.vm.provision :shell, :inline => <<-SH
# Set environment variables so that the setup script does # Set environment variables so that the setup script does
# not ask any questions during provisioning. We'll let the # not ask any questions during provisioning. We'll let the
# machine figure out its own public IP and it'll take a # machine figure out its own public IP.
# subdomain on our justtesting.email domain so we can get
# started quickly.
export NONINTERACTIVE=1 export NONINTERACTIVE=1
export PUBLIC_IP=auto export PUBLIC_IP=auto
export PUBLIC_IPV6=auto export PUBLIC_IPV6=auto
export PRIMARY_HOSTNAME=auto-easy export PRIMARY_HOSTNAME=auto
#export SKIP_NETWORK_CHECKS=1 #export SKIP_NETWORK_CHECKS=1
# Start the setup script. # Start the setup script.

View File

@@ -34,7 +34,7 @@ findtime = 30
enabled = true enabled = true
port = http,https port = http,https
filter = miab-owncloud filter = miab-owncloud
logpath = STORAGE_ROOT/owncloud/owncloud.log logpath = STORAGE_ROOT/owncloud/nextcloud.log
maxretry = 20 maxretry = 20
findtime = 120 findtime = 120
@@ -73,6 +73,7 @@ action = iptables-allports[name=recidive]
enabled = true enabled = true
[ssh] [ssh]
enabled = true
maxretry = 7 maxretry = 7
bantime = 3600 bantime = 3600

View File

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

View File

@@ -14,7 +14,7 @@
PATH=/sbin:/usr/sbin:/bin:/usr/bin PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Mail-in-a-Box Management Daemon" DESC="Mail-in-a-Box Management Daemon"
NAME=mailinabox NAME=mailinabox
DAEMON=/usr/local/bin/mailinabox-daemon DAEMON=/usr/local/lib/mailinabox/start
PIDFILE=/var/run/$NAME.pid PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME SCRIPTNAME=/etc/init.d/$NAME

View File

@@ -70,7 +70,7 @@
# takes precedence over all non-regex matches and only regex matches that # takes precedence over all non-regex matches and only regex matches that
# come after it (i.e. none of those, since this is the last one.) That means # come after it (i.e. none of those, since this is the last one.) That means
# we're blocking dotfiles in the static hosted sites but not the FastCGI- # we're blocking dotfiles in the static hosted sites but not the FastCGI-
# handled locations for ownCloud (which serves user-uploaded files that might # handled locations for Nextcloud (which serves user-uploaded files that might
# have this pattern, see #414) or some of the other services. # have this pattern, see #414) or some of the other services.
location ~ /\.(ht|svn|git|hg|bzr) { location ~ /\.(ht|svn|git|hg|bzr) {
log_not_found off; log_not_found off;

View File

@@ -1,6 +1,9 @@
# Control Panel # Control Panel
# Proxy /admin to our Python based control panel daemon. It is # Proxy /admin to our Python based control panel daemon. It is
# listening on IPv4 only so use an IP address and not 'localhost'. # listening on IPv4 only so use an IP address and not 'localhost'.
location /admin/assets {
alias /usr/local/lib/mailinabox/vendor/assets;
}
rewrite ^/admin$ /admin/; rewrite ^/admin$ /admin/;
rewrite ^/admin/munin$ /admin/munin/ redirect; rewrite ^/admin/munin$ /admin/munin/ redirect;
location /admin/ { location /admin/ {
@@ -9,10 +12,9 @@
add_header X-Frame-Options "DENY"; add_header X-Frame-Options "DENY";
add_header X-Content-Type-Options nosniff; add_header X-Content-Type-Options nosniff;
add_header Content-Security-Policy "frame-ancestors 'none';"; add_header Content-Security-Policy "frame-ancestors 'none';";
add_header Strict-Transport-Security max-age=31536000;
} }
# ownCloud configuration. # Nextcloud configuration.
rewrite ^/cloud$ /cloud/ redirect; rewrite ^/cloud$ /cloud/ redirect;
rewrite ^/cloud/$ /cloud/index.php; rewrite ^/cloud/$ /cloud/index.php;
rewrite ^/cloud/(contacts|calendar|files)$ /cloud/index.php/apps/$1/ redirect; rewrite ^/cloud/(contacts|calendar|files)$ /cloud/index.php/apps/$1/ redirect;
@@ -41,13 +43,11 @@
fastcgi_param MOD_X_ACCEL_REDIRECT_PREFIX /owncloud-xaccel; fastcgi_param MOD_X_ACCEL_REDIRECT_PREFIX /owncloud-xaccel;
fastcgi_read_timeout 630; fastcgi_read_timeout 630;
fastcgi_pass php-fpm; fastcgi_pass php-fpm;
error_page 403 /cloud/core/templates/403.php;
error_page 404 /cloud/core/templates/404.php;
client_max_body_size 1G; client_max_body_size 1G;
fastcgi_buffers 64 4K; fastcgi_buffers 64 4K;
} }
location ^~ /owncloud-xaccel/ { location ^~ /owncloud-xaccel/ {
# This directory is for MOD_X_ACCEL_REDIRECT_ENABLED. ownCloud sends the full file # This directory is for MOD_X_ACCEL_REDIRECT_ENABLED. Nextcloud sends the full file
# path on disk as a subdirectory under this virtual path. # path on disk as a subdirectory under this virtual path.
# We must only allow 'internal' redirects within nginx so that the filesystem # We must only allow 'internal' redirects within nginx so that the filesystem
# is not exposed to the world. # is not exposed to the world.

View File

@@ -1,5 +1,5 @@
# from: https://gist.github.com/konklone/6532544 # 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. # Basically the nginx configuration I use at konklone.com.
# I check it using https://www.ssllabs.com/ssltest/analyze.html?d=konklone.com # I check it using https://www.ssllabs.com/ssltest/analyze.html?d=konklone.com
@@ -27,17 +27,17 @@
# #
# Reference client: https://www.ssllabs.com/ssltest/analyze.html # Reference client: https://www.ssllabs.com/ssltest/analyze.html
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !MD5 !EXP !DSS !PSK !SRP !kECDH !CAMELLIA !RC4 !SEED'; ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
# Cut out (the old, broken) SSLv3 entirely. # Cut out (the old, broken) SSLv3 entirely.
# This **excludes IE6 users** and (apparently) Yandexbot. # This **excludes IE6 users** and (apparently) Yandexbot.
# Just comment out if you need to support IE6, bless your soul. # Just comment out if you need to support IE6, bless your soul.
ssl_protocols TLSv1.2 TLSv1.1 TLSv1; ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
# Turn on session resumption, using a 10 min cache shared across nginx processes, # Turn on session resumption, using a cache shared across nginx processes,
# as recommended by http://nginx.org/en/docs/http/configuring_https_servers.html # as recommended by http://nginx.org/en/docs/http/configuring_https_servers.html
ssl_session_cache shared:SSL:10m; ssl_session_cache shared:SSL:50m;
ssl_session_timeout 10m; ssl_session_timeout 1d;
#keepalive_timeout 70; # in Ubuntu 14.04/nginx 1.4.6 the default is 65, so plenty good #keepalive_timeout 70; # in Ubuntu 14.04/nginx 1.4.6 the default is 65, so plenty good
# Buffer size of 1400 bytes fits in one MTU. # Buffer size of 1400 bytes fits in one MTU.

View File

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

View File

@@ -25,7 +25,7 @@ server {
# This path must be served over HTTP for ACME domain validation. # This path must be served over HTTP for ACME domain validation.
# We map this to a special path where our TLS cert provisioning # We map this to a special path where our TLS cert provisioning
# tool knows to store challenge response files. # tool knows to store challenge response files.
alias $STORAGE_ROOT/ssl/lets_encrypt/acme_challenges/; alias $STORAGE_ROOT/ssl/lets_encrypt/webroot/.well-known/acme-challenge/;
} }
} }

View File

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

View File

@@ -17,7 +17,7 @@ define('CARDDAV_CONTACTS_FOLDER_NAME', '%u Addressbook');
define('CARDDAV_SUPPORTS_SYNC', false); define('CARDDAV_SUPPORTS_SYNC', false);
// If the CardDAV server supports the FN attribute for searches // If the CardDAV server supports the FN attribute for searches
// DAViCal supports it, but SabreDav, Owncloud and SOGo don't // DAViCal supports it, but SabreDav, Nextcloud and SOGo don't
// Setting this to true will search by FN. If false will search by sn, givenName and email // Setting this to true will search by FN. If false will search by sn, givenName and email
// It's safe to leave it as false // It's safe to leave it as false
define('CARDDAV_SUPPORTS_FN_SEARCH', false); define('CARDDAV_SUPPORTS_FN_SEARCH', false);

View File

@@ -23,6 +23,9 @@ define('IMAP_FOLDER_TRASH', 'TRASH');
define('IMAP_FOLDER_SPAM', 'SPAM'); define('IMAP_FOLDER_SPAM', 'SPAM');
define('IMAP_FOLDER_ARCHIVE', 'ARCHIVE'); define('IMAP_FOLDER_ARCHIVE', 'ARCHIVE');
define('IMAP_INLINE_FORWARD', true);
define('IMAP_EXCLUDED_FOLDERS', '');
define('IMAP_FROM_SQL_DSN', 'sqlite:STORAGE_ROOT/mail/roundcube/roundcube.sqlite'); define('IMAP_FROM_SQL_DSN', 'sqlite:STORAGE_ROOT/mail/roundcube/roundcube.sqlite');
define('IMAP_FROM_SQL_USER', ''); define('IMAP_FROM_SQL_USER', '');
define('IMAP_FROM_SQL_PASSWORD', ''); define('IMAP_FROM_SQL_PASSWORD', '');
@@ -49,5 +52,6 @@ global $imap_smtp_params;
$imap_smtp_params = array('host' => 'ssl://127.0.0.1', 'port' => 587, 'auth' => true, 'username' => 'imap_username', 'password' => 'imap_password'); $imap_smtp_params = array('host' => 'ssl://127.0.0.1', 'port' => 587, 'auth' => true, 'username' => 'imap_username', 'password' => 'imap_password');
define('MAIL_MIMEPART_CRLF', "\r\n"); define('MAIL_MIMEPART_CRLF', "\r\n");
define('IMAP_MEETING_USE_CALDAV', true);
?> ?>

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python3 #!/usr/local/lib/mailinabox/env/bin/python
# This script performs a backup of all user data: # This script performs a backup of all user data:
# 1) System services are stopped. # 1) System services are stopped.
@@ -10,8 +10,9 @@
import os, os.path, shutil, glob, re, datetime, sys import os, os.path, shutil, glob, re, datetime, sys
import dateutil.parser, dateutil.relativedelta, dateutil.tz import dateutil.parser, dateutil.relativedelta, dateutil.tz
import rtyaml import rtyaml
from exclusiveprocess import Lock
from utils import exclusive_process, load_environment, shell, wait_for_service, fix_boto from utils import load_environment, shell, wait_for_service, fix_boto
rsync_ssh_options = [ rsync_ssh_options = [
"--ssh-options='-i /root/.ssh/id_rsa_miab'", "--ssh-options='-i /root/.ssh/id_rsa_miab'",
@@ -38,6 +39,8 @@ def backup_status(env):
def reldate(date, ref, clip): def reldate(date, ref, clip):
if ref < date: return clip if ref < date: return clip
rd = dateutil.relativedelta.relativedelta(ref, date) rd = dateutil.relativedelta.relativedelta(ref, date)
if rd.years > 1: return "%d years, %d months" % (rd.years, rd.months)
if rd.years == 1: return "%d year, %d months" % (rd.years, rd.months)
if rd.months > 1: return "%d months, %d days" % (rd.months, rd.days) if rd.months > 1: return "%d months, %d days" % (rd.months, rd.days)
if rd.months == 1: return "%d month, %d days" % (rd.months, rd.days) if rd.months == 1: return "%d month, %d days" % (rd.months, rd.days)
if rd.days >= 7: return "%d days" % rd.days if rd.days >= 7: return "%d days" % rd.days
@@ -51,7 +54,7 @@ def backup_status(env):
date = dateutil.parser.parse(keys[1]).astimezone(dateutil.tz.tzlocal()) date = dateutil.parser.parse(keys[1]).astimezone(dateutil.tz.tzlocal())
return { return {
"date": keys[1], "date": keys[1],
"date_str": date.strftime("%x %X") + " " + now.tzname(), "date_str": date.strftime("%Y-%m-%d %X") + " " + now.tzname(),
"date_delta": reldate(date, now, "the future?"), "date_delta": reldate(date, now, "the future?"),
"full": keys[0] == "full", "full": keys[0] == "full",
"size": 0, # collection-status doesn't give us the size "size": 0, # collection-status doesn't give us the size
@@ -112,7 +115,7 @@ def backup_status(env):
# full backup. That full backup frees up this one to be deleted. But, the backup # full backup. That full backup frees up this one to be deleted. But, the backup
# must also be at least min_age_in_days old too. # must also be at least min_age_in_days old too.
deleted_in = None deleted_in = None
if incremental_count > 0 and first_full_size is not None: if incremental_count > 0 and incremental_size > 0 and first_full_size is not None:
# How many days until the next incremental backup? First, the part of # How many days until the next incremental backup? First, the part of
# the algorithm based on increment sizes: # the algorithm based on increment sizes:
est_days_to_next_full = (.5 * first_full_size - incremental_size) / (incremental_size/incremental_count) est_days_to_next_full = (.5 * first_full_size - incremental_size) / (incremental_size/incremental_count)
@@ -204,7 +207,10 @@ def get_target_type(config):
def perform_backup(full_backup): def perform_backup(full_backup):
env = load_environment() env = load_environment()
exclusive_process("backup") # Create an global exclusive lock so that the backup script
# cannot be run more than one.
Lock(die=True).forever()
config = get_backup_config(env) config = get_backup_config(env)
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup') backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
backup_cache_dir = os.path.join(backup_root, 'cache') backup_cache_dir = os.path.join(backup_root, 'cache')
@@ -261,7 +267,7 @@ def perform_backup(full_backup):
if quit: if quit:
sys.exit(code) sys.exit(code)
service_command("php5-fpm", "stop", quit=True) service_command("php7.0-fpm", "stop", quit=True)
service_command("postfix", "stop", quit=True) service_command("postfix", "stop", quit=True)
service_command("dovecot", "stop", quit=True) service_command("dovecot", "stop", quit=True)
@@ -295,7 +301,7 @@ def perform_backup(full_backup):
# Start services again. # Start services again.
service_command("dovecot", "start", quit=False) service_command("dovecot", "start", quit=False)
service_command("postfix", "start", quit=False) service_command("postfix", "start", quit=False)
service_command("php5-fpm", "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. # Once the migrated backup is included in a new backup, it can be deleted.
if os.path.isdir(migrated_unencrypted_backup_dir): if os.path.isdir(migrated_unencrypted_backup_dir):
@@ -382,21 +388,22 @@ def run_duplicity_restore(args):
def list_target_files(config): def list_target_files(config):
import urllib.parse import urllib.parse
try: try:
p = urllib.parse.urlparse(config["target"]) target = urllib.parse.urlparse(config["target"])
except ValueError: except ValueError:
return "invalid target" return "invalid target"
if p.scheme == "file": if target.scheme == "file":
return [(fn, os.path.getsize(os.path.join(p.path, fn))) for fn in os.listdir(p.path)] return [(fn, os.path.getsize(os.path.join(target.path, fn))) for fn in os.listdir(target.path)]
elif p.scheme == "rsync": elif target.scheme == "rsync":
rsync_fn_size_re = re.compile(r'.* ([^ ]*) [^ ]* [^ ]* (.*)') rsync_fn_size_re = re.compile(r'.* ([^ ]*) [^ ]* [^ ]* (.*)')
rsync_target = '{host}:{path}' rsync_target = '{host}:{path}'
_, target_host, target_path = config['target'].split('//') target_path = target.path
target_path = '/' + target_path
if not target_path.endswith('/'): if not target_path.endswith('/'):
target_path += '/' target_path = target_path + '/'
if target_path.startswith('/'):
target_path = target_path[1:]
rsync_command = [ 'rsync', rsync_command = [ 'rsync',
'-e', '-e',
@@ -404,11 +411,11 @@ def list_target_files(config):
'--list-only', '--list-only',
'-r', '-r',
rsync_target.format( rsync_target.format(
host=target_host, host=target.netloc,
path=target_path) path=target_path)
] ]
code, listing = shell('check_output', rsync_command, trap=True) code, listing = shell('check_output', rsync_command, trap=True, capture_stderr=True)
if code == 0: if code == 0:
ret = [] ret = []
for l in listing.split('\n'): for l in listing.split('\n'):
@@ -417,21 +424,33 @@ def list_target_files(config):
ret.append( (match.groups()[1], int(match.groups()[0].replace(',',''))) ) ret.append( (match.groups()[1], int(match.groups()[0].replace(',',''))) )
return ret return ret
else: else:
raise ValueError("Connection to rsync host failed") if 'Permission denied (publickey).' in listing:
reason = "Invalid user or check you correctly copied the SSH key."
elif 'No such file or directory' in listing:
reason = "Provided path {} is invalid.".format(target_path)
elif 'Network is unreachable' in listing:
reason = "The IP address {} is unreachable.".format(target.hostname)
elif 'Could not resolve hostname':
reason = "The hostname {} cannot be resolved.".format(target.hostname)
else:
reason = "Unknown error." \
"Please check running 'python management/backup.py --verify'" \
"from mailinabox sources to debug the issue."
raise ValueError("Connection to rsync host failed: {}".format(reason))
elif p.scheme == "s3": elif target.scheme == "s3":
# match to a Region # match to a Region
fix_boto() # must call prior to importing boto fix_boto() # must call prior to importing boto
import boto.s3 import boto.s3
from boto.exception import BotoServerError from boto.exception import BotoServerError
for region in boto.s3.regions(): for region in boto.s3.regions():
if region.endpoint == p.hostname: if region.endpoint == target.hostname:
break break
else: else:
raise ValueError("Invalid S3 region/host.") raise ValueError("Invalid S3 region/host.")
bucket = p.path[1:].split('/')[0] bucket = target.path[1:].split('/')[0]
path = '/'.join(p.path[1:].split('/')[1:]) + '/' path = '/'.join(target.path[1:].split('/')[1:]) + '/'
# If no prefix is specified, set the path to '', otherwise boto won't list the files # If no prefix is specified, set the path to '', otherwise boto won't list the files
if path == '/': if path == '/':
@@ -536,6 +555,12 @@ if __name__ == "__main__":
# are readable, and b) report if they are up to date. # are readable, and b) report if they are up to date.
run_duplicity_verification() 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.
for fn, size in list_target_files(get_backup_config(load_environment())):
print("{}\t{}".format(fn, size))
elif sys.argv[-1] == "--status": elif sys.argv[-1] == "--status":
# Show backup status. # Show backup status.
ret = backup_status(load_environment()) ret = backup_status(load_environment())

View File

@@ -1,5 +1,3 @@
#!/usr/bin/python3
import os, os.path, re, json, time import os, os.path, re, json, time
import subprocess import subprocess
@@ -335,11 +333,16 @@ def ssl_get_status():
from web_update import get_web_domains_info, get_web_domains from web_update import get_web_domains_info, get_web_domains
# What domains can we provision certificates for? What unexpected problems do we have? # What domains can we provision certificates for? What unexpected problems do we have?
provision, cant_provision = get_certificates_to_provision(env, show_extended_problems=False) provision, cant_provision = get_certificates_to_provision(env, show_valid_certs=False)
# What's the current status of TLS certificates on all of the domain? # What's the current status of TLS certificates on all of the domain?
domains_status = get_web_domains_info(env) domains_status = get_web_domains_info(env)
domains_status = [{ "domain": d["domain"], "status": d["ssl_certificate"][0], "text": d["ssl_certificate"][1] } for d in domains_status ] domains_status = [
{
"domain": d["domain"],
"status": d["ssl_certificate"][0],
"text": d["ssl_certificate"][1] + ((" " + cant_provision[d["domain"]] if d["domain"] in cant_provision else ""))
} for d in domains_status ]
# Warn the user about domain names not hosted here because of other settings. # Warn the user about domain names not hosted here because of other settings.
for domain in set(get_web_domains(env, exclude_dns_elsewhere=False)) - set(get_web_domains(env)): for domain in set(get_web_domains(env, exclude_dns_elsewhere=False)) - set(get_web_domains(env)):
@@ -351,7 +354,6 @@ def ssl_get_status():
return json_response({ return json_response({
"can_provision": utils.sort_domains(provision, env), "can_provision": utils.sort_domains(provision, env),
"cant_provision": [{ "domain": domain, "problem": cant_provision[domain] } for domain in utils.sort_domains(cant_provision, env) ],
"status": domains_status, "status": domains_status,
}) })
@@ -378,11 +380,8 @@ def ssl_install_cert():
@authorized_personnel_only @authorized_personnel_only
def ssl_provision_certs(): def ssl_provision_certs():
from ssl_certificates import provision_certificates from ssl_certificates import provision_certificates
agree_to_tos_url = request.form.get('agree_to_tos_url') requests = provision_certificates(env, limit_domains=None)
status = provision_certificates(env, return json_response({ "requests": requests })
agree_to_tos_url=agree_to_tos_url,
jsonable=True)
return json_response(status)
# WEB # WEB

View File

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

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python3 #!/usr/local/lib/mailinabox/env/bin/python
# Creates DNS zone files for all of the domains of all of the mail users # Creates DNS zone files for all of the domains of all of the mail users
# and mail aliases and restarts nsd. # and mail aliases and restarts nsd.
@@ -12,6 +12,12 @@ import dns.resolver
from mailconfig import get_mail_domains from mailconfig import get_mail_domains
from utils import shell, load_env_vars_from_file, safe_domain_name, sort_domains from utils import shell, load_env_vars_from_file, safe_domain_name, sort_domains
# From https://stackoverflow.com/questions/3026957/how-to-validate-a-domain-name-using-regex-php/16491074#16491074
# This regular expression matches domain names according to RFCs, it also accepts fqdn with an leading dot,
# underscores, as well as asteriks which are allowed in domain names but not hostnames (i.e. allowed in
# DNS but not in URLs), which are common in certain record types like for DKIM.
DOMAIN_RE = "^(?!\-)(?:[*][.])?(?:[a-zA-Z\d\-_]{0,62}[a-zA-Z\d_]\.){1,126}(?!\d+)[a-zA-Z\d_]{1,63}(\.?)$"
def get_dns_domains(env): def get_dns_domains(env):
# Add all domain names in use by email users and mail aliases and ensure # Add all domain names in use by email users and mail aliases and ensure
# PRIMARY_HOSTNAME is in the list. # PRIMARY_HOSTNAME is in the list.
@@ -342,6 +348,7 @@ def build_sshfp_records():
"ssh-rsa": 1, "ssh-rsa": 1,
"ssh-dss": 2, "ssh-dss": 2,
"ecdsa-sha2-nistp256": 3, "ecdsa-sha2-nistp256": 3,
"ssh-ed25519": 4,
} }
# Get our local fingerprints by running ssh-keyscan. The output looks # Get our local fingerprints by running ssh-keyscan. The output looks
@@ -359,7 +366,7 @@ def build_sshfp_records():
ports = ports + [s[1]] ports = ports + [s[1]]
# the keys are the same at each port, so we only need to get # 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) # them at the first port found (may not be port 22)
keys = shell("check_output", ["ssh-keyscan", "-p", ports[0], "localhost"]) keys = shell("check_output", ["ssh-keyscan", "-t", "rsa,dsa,ecdsa,ed25519", "-p", ports[0], "localhost"])
for key in sorted(keys.split("\n")): for key in sorted(keys.split("\n")):
if key.strip() == "" or key[0] == "#": continue if key.strip() == "" or key[0] == "#": continue
try: try:
@@ -521,12 +528,13 @@ zone:
def dnssec_choose_algo(domain, env): def dnssec_choose_algo(domain, env):
if '.' in domain and domain.rsplit('.')[-1] in \ if '.' in domain and domain.rsplit('.')[-1] in \
("email", "guide", "fund", "be"): ("email", "guide", "fund", "be", "lv"):
# At GoDaddy, RSASHA256 is the only algorithm supported # At GoDaddy, RSASHA256 is the only algorithm supported
# for .email and .guide. # for .email and .guide.
# A variety of algorithms are supported for .fund. This # A variety of algorithms are supported for .fund. This
# is preferred. # is preferred.
# Gandi tells me that .be does not support RSASHA1-NSEC3-SHA1 # Gandi tells me that .be does not support RSASHA1-NSEC3-SHA1
# Nic.lv does not support RSASHA1-NSEC3-SHA1 for .lv tld's
return "RSASHA256" return "RSASHA256"
# For any domain we were able to sign before, don't change the algorithm # For any domain we were able to sign before, don't change the algorithm
@@ -761,12 +769,25 @@ def set_custom_dns_record(qname, rtype, value, action, env):
# validate rtype # validate rtype
rtype = rtype.upper() rtype = rtype.upper()
if value is not None and qname != "_secondary_nameserver": if value is not None and qname != "_secondary_nameserver":
if not re.search(DOMAIN_RE, qname):
raise ValueError("Invalid name.")
if rtype in ("A", "AAAA"): if rtype in ("A", "AAAA"):
if value != "local": # "local" is a special flag for us if value != "local": # "local" is a special flag for us
v = ipaddress.ip_address(value) # raises a ValueError if there's a problem v = ipaddress.ip_address(value) # raises a ValueError if there's a problem
if rtype == "A" and not isinstance(v, ipaddress.IPv4Address): raise ValueError("That's an IPv6 address.") if rtype == "A" and not isinstance(v, ipaddress.IPv4Address): raise ValueError("That's an IPv6 address.")
if rtype == "AAAA" and not isinstance(v, ipaddress.IPv6Address): raise ValueError("That's an IPv4 address.") if rtype == "AAAA" and not isinstance(v, ipaddress.IPv6Address): raise ValueError("That's an IPv4 address.")
elif rtype in ("CNAME", "TXT", "SRV", "MX"): elif rtype in ("CNAME", "NS"):
if rtype == "NS" and qname == zone:
raise ValueError("NS records can only be set for subdomains.")
# ensure value has a trailing dot
if not value.endswith("."):
value = value + "."
if not re.search(DOMAIN_RE, value):
raise ValueError("Invalid value.")
elif rtype in ("CNAME", "TXT", "SRV", "MX", "SSHFP", "CAA"):
# anything goes # anything goes
pass pass
else: else:
@@ -881,10 +902,10 @@ def set_secondary_dns(hostnames, env):
return do_dns_update(env) return do_dns_update(env)
def get_custom_dns_record(custom_dns, qname, rtype): def get_custom_dns_records(custom_dns, qname, rtype):
for qname1, rtype1, value in custom_dns: for qname1, rtype1, value in custom_dns:
if qname1 == qname and rtype1 == rtype: if qname1 == qname and rtype1 == rtype:
return value yield value
return None return None
######################################################################## ########################################################################

View File

@@ -1,11 +1,17 @@
#!/usr/bin/python3 #!/usr/local/lib/mailinabox/env/bin/python
# Reads in STDIN. If the stream is not empty, mail it to the system administrator. # Reads in STDIN. If the stream is not empty, mail it to the system administrator.
import sys import sys
import html
import smtplib import smtplib
from email.message import Message
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# In Python 3.6:
#from email.message import Message
from utils import load_environment from utils import load_environment
@@ -26,11 +32,23 @@ if content == "":
sys.exit(0) sys.exit(0)
# create MIME message # create MIME message
msg = Message() msg = MIMEMultipart('alternative')
# In Python 3.6:
#msg = Message()
msg['From'] = "\"%s\" <%s>" % (env['PRIMARY_HOSTNAME'], admin_addr) msg['From'] = "\"%s\" <%s>" % (env['PRIMARY_HOSTNAME'], admin_addr)
msg['To'] = admin_addr msg['To'] = admin_addr
msg['Subject'] = "[%s] %s" % (env['PRIMARY_HOSTNAME'], subject) msg['Subject'] = "[%s] %s" % (env['PRIMARY_HOSTNAME'], subject)
msg.set_payload(content, "UTF-8")
content_html = "<html><body><pre>{}</pre></body></html>".format(html.escape(content))
msg.attach(MIMEText(content, 'plain'))
msg.attach(MIMEText(content_html, 'html'))
# In Python 3.6:
#msg.set_content(content)
#msg.add_alternative(content_html, "html")
# send # send
smtpclient = smtplib.SMTP('127.0.0.1', 25) smtpclient = smtplib.SMTP('127.0.0.1', 25)

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,13 @@
#!/usr/bin/python3 #!/usr/local/lib/mailinabox/env/bin/python
# NOTE:
# This script is run both using the system-wide Python 3
# interpreter (/usr/bin/python3) as well as through the
# virtualenv (/usr/local/lib/mailinabox/env). So only
# import packages at the top level of this script that
# are installed in *both* contexts. We use the system-wide
# Python 3 in setup/questions.sh to validate the email
# address entered by the user.
import subprocess, shutil, os, sqlite3, re import subprocess, shutil, os, sqlite3, re
import utils import utils
@@ -435,9 +444,11 @@ def add_mail_alias(address, forwards_to, permitted_senders, env, update_if_exist
email = email.strip() email = email.strip()
if email == "": continue if email == "": continue
email = sanitize_idn_email_address(email) # Unicode => IDNA email = sanitize_idn_email_address(email) # Unicode => IDNA
# Strip any +tag from email alias and check privileges
privileged_email = re.sub(r"(?=\+)[^@]*(?=@)",'',email)
if not validate_email(email): if not validate_email(email):
return ("Invalid receiver email address (%s)." % email, 400) return ("Invalid receiver email address (%s)." % email, 400)
if is_dcv_source and not is_dcv_address(email) and "admin" not in get_mail_user_privileges(email, env, empty_on_error=True): if is_dcv_source and not is_dcv_address(email) and "admin" not in get_mail_user_privileges(privileged_email, env, empty_on_error=True):
# Make domain control validation hijacking a little harder to mess up by # Make domain control validation hijacking a little harder to mess up by
# requiring aliases for email addresses typically used in DCV to forward # requiring aliases for email addresses typically used in DCV to forward
# only to accounts that are administrators on this system. # only to accounts that are administrators on this system.
@@ -599,8 +610,8 @@ def validate_password(pw):
raise ValueError("No password provided.") raise ValueError("No password provided.")
if re.search(r"[\s]", pw): if re.search(r"[\s]", pw):
raise ValueError("Passwords cannot contain spaces.") raise ValueError("Passwords cannot contain spaces.")
if len(pw) < 4: if len(pw) < 8:
raise ValueError("Passwords must be at least four characters.") raise ValueError("Passwords must be at least eight characters.")
if __name__ == "__main__": if __name__ == "__main__":

View File

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

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python3 #!/usr/local/lib/mailinabox/env/bin/python
# #
# Checks that the upstream DNS has been set correctly and that # Checks that the upstream DNS has been set correctly and that
# TLS certificates have been signed, etc., and if not tells the user # TLS certificates have been signed, etc., and if not tells the user
@@ -11,7 +11,7 @@ import dateutil.parser, dateutil.tz
import idna import idna
import psutil import psutil
from dns_update import get_dns_zones, build_tlsa_record, get_custom_dns_config, get_secondary_dns, get_custom_dns_record from dns_update import get_dns_zones, build_tlsa_record, get_custom_dns_config, get_secondary_dns, get_custom_dns_records
from web_update import get_web_domains, get_domains_with_a_records from web_update import get_web_domains, get_domains_with_a_records
from ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate from ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate
from mailconfig import get_mail_domains, get_mail_aliases from mailconfig import get_mail_domains, get_mail_aliases
@@ -393,7 +393,7 @@ def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles):
# Check that PRIMARY_HOSTNAME resolves to PUBLIC_IP[V6] in public DNS. # Check that PRIMARY_HOSTNAME resolves to PUBLIC_IP[V6] in public DNS.
ipv6 = query_dns(domain, "AAAA") if env.get("PUBLIC_IPV6") else None ipv6 = query_dns(domain, "AAAA") if env.get("PUBLIC_IPV6") else None
if ip == env['PUBLIC_IP'] and ipv6 in (None, env['PUBLIC_IPV6']): if ip == env['PUBLIC_IP'] and not (ipv6 and env['PUBLIC_IPV6'] and ipv6 != normalize_ip(env['PUBLIC_IPV6'])):
output.print_ok("Domain resolves to box's IP address. [%s%s]" % (env['PRIMARY_HOSTNAME'], my_ips)) output.print_ok("Domain resolves to box's IP address. [%s%s]" % (env['PRIMARY_HOSTNAME'], my_ips))
else: else:
output.print_error("""This domain must resolve to your box's IP address (%s) in public DNS but it currently resolves output.print_error("""This domain must resolve to your box's IP address (%s) in public DNS but it currently resolves
@@ -459,7 +459,7 @@ def check_dns_zone(domain, env, output, dns_zonefiles):
# half working.) # half working.)
custom_dns_records = list(get_custom_dns_config(env)) # generator => list so we can reuse it custom_dns_records = list(get_custom_dns_config(env)) # generator => list so we can reuse it
correct_ip = get_custom_dns_record(custom_dns_records, domain, "A") or env['PUBLIC_IP'] correct_ip = "; ".join(sorted(get_custom_dns_records(custom_dns_records, domain, "A"))) or env['PUBLIC_IP']
custom_secondary_ns = get_secondary_dns(custom_dns_records, mode="NS") custom_secondary_ns = get_secondary_dns(custom_dns_records, mode="NS")
secondary_ns = custom_secondary_ns or ["ns2." + env['PRIMARY_HOSTNAME']] secondary_ns = custom_secondary_ns or ["ns2." + env['PRIMARY_HOSTNAME']]
@@ -640,7 +640,7 @@ def check_web_domain(domain, rounded_time, ssl_certificates, env, output):
for (rtype, expected) in (("A", env['PUBLIC_IP']), ("AAAA", env.get('PUBLIC_IPV6'))): for (rtype, expected) in (("A", env['PUBLIC_IP']), ("AAAA", env.get('PUBLIC_IPV6'))):
if not expected: continue # IPv6 is not configured if not expected: continue # IPv6 is not configured
value = query_dns(domain, rtype) value = query_dns(domain, rtype)
if value == expected: if value == normalize_ip(expected):
ok_values.append(value) ok_values.append(value)
else: else:
output.print_error("""This domain should resolve to your box's IP address (%s %s) if you would like the box to serve output.print_error("""This domain should resolve to your box's IP address (%s %s) if you would like the box to serve
@@ -687,26 +687,17 @@ def query_dns(qname, rtype, nxdomain='[Not Set]', at=None):
except dns.exception.Timeout: except dns.exception.Timeout:
return "[timeout]" return "[timeout]"
# Normalize IP addresses. IP address --- especially IPv6 addresses --- can
# be expressed in equivalent string forms. Canonicalize the form before
# returning them. The caller should normalize any IP addresses the result
# of this method is compared with.
if rtype in ("A", "AAAA"):
response = [normalize_ip(str(r)) for r in response]
# There may be multiple answers; concatenate the response. Remove trailing # There may be multiple answers; concatenate the response. Remove trailing
# periods from responses since that's how qnames are encoded in DNS but is # periods from responses since that's how qnames are encoded in DNS but is
# confusing for us. The order of the answers doesn't matter, so sort so we # confusing for us. The order of the answers doesn't matter, so sort so we
# can compare to a well known order. # can compare to a well known order.
# Unfortunately, the response.__str__ returns bytes
# instead of string, if it resulted from an AAAA-query.
# We need to convert manually, until this is fixed:
# https://github.com/rthalley/dnspython/issues/204
#
# BEGIN HOTFIX
response_new = []
for r in response:
if isinstance(r.to_text(), bytes):
response_new.append(r.to_text().decode('utf-8'))
else:
response_new.append(r)
response = response_new
# END HOTFIX
return "; ".join(sorted(str(r).rstrip('.') for r in response)) return "; ".join(sorted(str(r).rstrip('.') for r in response))
def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output): def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output):
@@ -793,8 +784,13 @@ def what_version_is_this(env):
def get_latest_miab_version(): def get_latest_miab_version():
# This pings https://mailinabox.email/setup.sh and extracts the tag named in # This pings https://mailinabox.email/setup.sh and extracts the tag named in
# the script to determine the current product version. # the script to determine the current product version.
import urllib.request from urllib.request import urlopen, HTTPError, URLError
return re.search(b'TAG=(.*)', urllib.request.urlopen("https://mailinabox.email/setup.sh?ping=1").read()).group(1).decode("utf8") from socket import timeout
try:
return re.search(b'TAG=(.*)', urlopen("https://mailinabox.email/setup.sh?ping=1", timeout=5).read()).group(1).decode("utf8")
except (HTTPError, URLError, timeout):
return None
def check_miab_version(env, output): def check_miab_version(env, output):
config = load_settings(env) config = load_settings(env)
@@ -811,6 +807,8 @@ def check_miab_version(env, output):
if this_ver == latest_ver: if this_ver == latest_ver:
output.print_ok("Mail-in-a-Box is up to date. You are running version %s." % this_ver) output.print_ok("Mail-in-a-Box is up to date. You are running version %s." % this_ver)
elif latest_ver is None:
output.print_error("Latest Mail-in-a-Box version could not be determined. You are running version %s." % this_ver)
else: else:
output.print_error("A new version of Mail-in-a-Box is available. You are running version %s. The latest version is %s. For upgrade instructions, see https://mailinabox.email. " output.print_error("A new version of Mail-in-a-Box is available. You are running version %s. The latest version is %s. For upgrade instructions, see https://mailinabox.email. "
% (this_ver, latest_ver)) % (this_ver, latest_ver))
@@ -883,6 +881,16 @@ def run_and_output_changes(env, pool):
with open(cache_fn, "w") as f: with open(cache_fn, "w") as f:
json.dump(cur.buf, f, indent=True) json.dump(cur.buf, f, indent=True)
def normalize_ip(ip):
# Use ipaddress module to normalize the IPv6 notation and
# ensure we are matching IPv6 addresses written in different
# representations according to rfc5952.
import ipaddress
try:
return str(ipaddress.ip_address(ip))
except:
return ip
class FileOutput: class FileOutput:
def __init__(self, buf, width): def __init__(self, buf, width):
self.buf = buf self.buf = buf

View File

@@ -31,12 +31,15 @@
<label for="customdnsType" class="col-sm-1 control-label">Type</label> <label for="customdnsType" class="col-sm-1 control-label">Type</label>
<div class="col-sm-10"> <div class="col-sm-10">
<select id="customdnsType" class="form-control" style="max-width: 400px" onchange="show_customdns_rtype_hint()"> <select id="customdnsType" class="form-control" style="max-width: 400px" onchange="show_customdns_rtype_hint()">
<option value="A" data-hint="Enter an IPv4 address (i.e. a dotted quad, such as 123.456.789.012).">A (IPv4 address)</option> <option value="A" data-hint="Enter an IPv4 address (i.e. a dotted quad, such as 123.456.789.012). The 'local' alias sets the record to this box's public IPv4 address.">A (IPv4 address)</option>
<option value="AAAA" data-hint="Enter an IPv6 address.">AAAA (IPv6 address)</option> <option value="AAAA" data-hint="Enter an IPv6 address. The 'local' alias sets the record to this box's public IPv6 address.">AAAA (IPv6 address)</option>
<option value="CAA" data-hint="Enter a CA that can issue certificates for this domain in the form of FLAG TAG VALUE. (0 issuewild &quot;letsencrypt.org&quot;)">CAA (Certificate Authority Authorization)</option>
<option value="CNAME" data-hint="Enter another domain name followed by a period at the end (e.g. mypage.github.io.).">CNAME (DNS forwarding)</option> <option value="CNAME" data-hint="Enter another domain name followed by a period at the end (e.g. mypage.github.io.).">CNAME (DNS forwarding)</option>
<option value="TXT" data-hint="Enter arbitrary text.">TXT (text record)</option> <option value="TXT" data-hint="Enter arbitrary text.">TXT (text record)</option>
<option value="MX" data-hint="Enter record in the form of PRIORITY DOMAIN., including trailing period (e.g. 20 mx.example.com.).">MX (mail exchanger)</option> <option value="MX" data-hint="Enter record in the form of PRIORITY DOMAIN., including trailing period (e.g. 20 mx.example.com.).">MX (mail exchanger)</option>
<option value="SRV" data-hint="Enter record in the form of PRIORITY WEIGHT PORT TARGET., including trailing period (e.g. 10 10 5060 sip.example.com.).">SRV (service record)</option> <option value="SRV" data-hint="Enter record in the form of PRIORITY WEIGHT PORT TARGET., including trailing period (e.g. 10 10 5060 sip.example.com.).">SRV (service record)</option>
<option value="SSHFP" data-hint="Enter record in the form of ALGORITHM TYPE FINGERPRINT.">SSHFP (SSH fingerprint record)</option>
<option value="NS" data-hint="Enter a hostname to which this subdomain should be delegated to">NS (DNS subdomain delegation)</option>
</select> </select>
</div> </div>
</div> </div>
@@ -124,7 +127,7 @@
<tr><td>email</td> <td>The email address of any administrative user here.</td></tr> <tr><td>email</td> <td>The email address of any administrative user here.</td></tr>
<tr><td>password</td> <td>That user&rsquo;s password.</td></tr> <tr><td>password</td> <td>That user&rsquo;s password.</td></tr>
<tr><td>qname</td> <td>The fully qualified domain name for the record you are trying to set. It must be one of the domain names or a subdomain of one of the domain names hosted on this box. (Add mail users or aliases to add new domains.)</td></tr> <tr><td>qname</td> <td>The fully qualified domain name for the record you are trying to set. It must be one of the domain names or a subdomain of one of the domain names hosted on this box. (Add mail users or aliases to add new domains.)</td></tr>
<tr><td>rtype</td> <td>The resource type. Defaults to <code>A</code> if omitted. Possible values: <code>A</code> (an IPv4 address), <code>AAAA</code> (an IPv6 address), <code>TXT</code> (a text string), <code>CNAME</code> (an alias, which is a fully qualified domain name &mdash; don&rsquo;t forget the final period), <code>MX</code>, or <code>SRV</code>.</td></tr> <tr><td>rtype</td> <td>The resource type. Defaults to <code>A</code> if omitted. Possible values: <code>A</code> (an IPv4 address), <code>AAAA</code> (an IPv6 address), <code>TXT</code> (a text string), <code>CNAME</code> (an alias, which is a fully qualified domain name &mdash; don&rsquo;t forget the final period), <code>MX</code>, <code>SRV</code>, <code>SSHFP</code>, <code>CAA</code> or <code>NS</code>.</td></tr>
<tr><td>value</td> <td>For PUT, POST, and DELETE, the record&rsquo;s value. If the <code>rtype</code> is <code>A</code> or <code>AAAA</code> and <code>value</code> is empty or omitted, the IPv4 or IPv6 address of the remote host is used (be sure to use the <code>-4</code> or <code>-6</code> options to curl). This is handy for dynamic DNS!</td></tr> <tr><td>value</td> <td>For PUT, POST, and DELETE, the record&rsquo;s value. If the <code>rtype</code> is <code>A</code> or <code>AAAA</code> and <code>value</code> is empty or omitted, the IPv4 or IPv6 address of the remote host is used (be sure to use the <code>-4</code> or <code>-6</code> options to curl). This is handy for dynamic DNS!</td></tr>
</table> </table>

View File

@@ -9,7 +9,7 @@
<meta name="robots" content="noindex, nofollow"> <meta name="robots" content="noindex, nofollow">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <link rel="stylesheet" href="/admin/assets/bootstrap/css/bootstrap.min.css">
<style> <style>
body { body {
overflow-y: scroll; overflow-y: scroll;
@@ -63,7 +63,7 @@
margin-bottom: 1em; margin-bottom: 1em;
} }
</style> </style>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> <link rel="stylesheet" href="/admin/assets/bootstrap/css/bootstrap-theme.min.css">
</head> </head>
<body> <body>
@@ -108,7 +108,7 @@
<li><a href="#web" onclick="return show_panel(this);">Web</a></li> <li><a href="#web" onclick="return show_panel(this);">Web</a></li>
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li><a href="#" onclick="do_logout(); return false;" style="color: white">Log out?</a></li> <li><a href="#" onclick="do_logout(); return false;" style="color: white">Log out</a></li>
</ul> </ul>
</div><!--/.navbar-collapse --> </div><!--/.navbar-collapse -->
</div> </div>
@@ -191,8 +191,8 @@
</div> </div>
</div> </div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js" integrity="sha256-rsPUGdUPBXgalvIj4YKJrrUlmLXbOb6Cp7cdxn1qeUc=" crossorigin="anonymous"></script> <script src="/admin/assets/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script src="/admin/assets/bootstrap/js/bootstrap.min.js"></script>
<script> <script>
var global_modal_state = null; var global_modal_state = null;

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,7 @@
<button type="submit" class="btn btn-primary">Add User</button> <button type="submit" class="btn btn-primary">Add User</button>
</form> </form>
<ul style="margin-top: 1em; padding-left: 1.5em; font-size: 90%;"> <ul style="margin-top: 1em; padding-left: 1.5em; font-size: 90%;">
<li>Passwords must be at least four characters and may not contain spaces. For best results, <a href="#" onclick="return generate_random_password()">generate a random password</a>.</li> <li>Passwords must be at least eight characters and may not contain spaces. For best results, <a href="#" onclick="return generate_random_password()">generate a random password</a>.</li>
<li>Use <a href="#" onclick="return show_panel('aliases')">aliases</a> to create email addresses that forward to existing accounts.</li> <li>Use <a href="#" onclick="return show_panel('aliases')">aliases</a> to create email addresses that forward to existing accounts.</li>
<li>Administrators get access to this control panel.</li> <li>Administrators get access to this control panel.</li>
<li>User accounts cannot contain any international (non-ASCII) characters, but <a href="#" onclick="return show_panel('aliases');">aliases</a> can.</li> <li>User accounts cannot contain any international (non-ASCII) characters, but <a href="#" onclick="return show_panel('aliases');">aliases</a> can.</li>
@@ -213,7 +213,7 @@ function users_set_password(elem) {
show_modal_confirm( show_modal_confirm(
"Set Password", "Set Password",
$("<p>Set a new password for <b>" + email + "</b>?</p> <p><label for='users_set_password_pw' style='display: block; font-weight: normal'>New Password:</label><input type='password' id='users_set_password_pw'></p><p><small>Passwords must be at least four characters and may not contain spaces.</small>" + yourpw + "</p>"), $("<p>Set a new password for <b>" + email + "</b>?</p> <p><label for='users_set_password_pw' style='display: block; font-weight: normal'>New Password:</label><input type='password' id='users_set_password_pw'></p><p><small>Passwords must be at least eight characters and may not contain spaces.</small>" + yourpw + "</p>"),
"Set Password", "Set Password",
function() { function() {
api( api(
@@ -296,7 +296,7 @@ function mod_priv(elem, add_remove) {
function generate_random_password() { function generate_random_password() {
var pw = ""; var pw = "";
var charset = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; // confusable characters skipped var charset = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; // confusable characters skipped
for (var i = 0; i < 10; i++) for (var i = 0; i < 12; i++)
pw += charset.charAt(Math.floor(Math.random() * charset.length)); pw += charset.charAt(Math.floor(Math.random() * charset.length));
show_modal_error("Random Password", "<p>Here, try this:</p> <p><code style='font-size: 110%'>" + pw + "</code></pr"); show_modal_error("Random Password", "<p>Here, try this:</p> <p><code style='font-size: 110%'>" + pw + "</code></pr");
return false; // cancel click return false; // cancel click

View File

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

View File

@@ -149,7 +149,10 @@ def make_domain_config(domain, templates, ssl_certificates, env):
# any proxy or redirect here? # any proxy or redirect here?
for path, url in yaml.get("proxies", {}).items(): for path, url in yaml.get("proxies", {}).items():
nginx_conf_extra += "\tlocation %s {\n\t\tproxy_pass %s;\n\t}\n" % (path, url) nginx_conf_extra += "\tlocation %s {" % path
nginx_conf_extra += "\n\t\tproxy_pass %s;" % url
nginx_conf_extra += "\n\t\tproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;"
nginx_conf_extra += "\n\t}\n"
for path, url in yaml.get("redirects", {}).items(): for path, url in yaml.get("redirects", {}).items():
nginx_conf_extra += "\trewrite %s %s permanent;\n" % (path, url) nginx_conf_extra += "\trewrite %s %s permanent;\n" % (path, url)
@@ -158,9 +161,9 @@ def make_domain_config(domain, templates, ssl_certificates, env):
# Add the HSTS header. # Add the HSTS header.
if hsts == "yes": if hsts == "yes":
nginx_conf_extra += "add_header Strict-Transport-Security max-age=31536000;\n" nginx_conf_extra += "add_header Strict-Transport-Security max-age=15768000;\n"
elif hsts == "preload": elif hsts == "preload":
nginx_conf_extra += "add_header Strict-Transport-Security \"max-age=10886400; includeSubDomains; preload\";\n" nginx_conf_extra += "add_header Strict-Transport-Security \"max-age=15768000; includeSubDomains; preload\";\n"
# Add in any user customizations in the includes/ folder. # Add in any user customizations in the includes/ folder.
nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf") nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf")
@@ -198,8 +201,11 @@ def get_web_domains_info(env):
# for the SSL config panel, get cert status # for the SSL config panel, get cert status
def check_cert(domain): def check_cert(domain):
try:
tls_cert = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True) tls_cert = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
if tls_cert is None: return ("danger", "No Certificate Installed") except OSError: # PRIMARY_HOSTNAME cert is missing
tls_cert = None
if tls_cert is None: return ("danger", "No certificate installed.")
cert_status, cert_status_details = check_certificate(domain, tls_cert["certificate"], tls_cert["private-key"]) cert_status, cert_status_details = check_certificate(domain, tls_cert["certificate"], tls_cert["private-key"])
if cert_status == "OK": if cert_status == "OK":
return ("success", "Signed & valid. " + cert_status_details) return ("success", "Signed & valid. " + cert_status_details)

View File

@@ -14,7 +14,7 @@ build_postgrey: clean
git clone git://git.debian.org/git/collab-maint/postgrey.git /tmp/build/postgrey git clone git://git.debian.org/git/collab-maint/postgrey.git /tmp/build/postgrey
# Download the corresponding upstream package. # Download the corresponding upstream package.
wget -O /tmp/build/postgrey_1.35.orig.tar.gz http://postgrey.schweikert.ch/pub/postgrey-1.35.tar.gz 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. # Add our source patch to the debian packaging listing.
cp postgrey_sources.diff /tmp/build/postgrey/debian/patches/mailinabox cp postgrey_sources.diff /tmp/build/postgrey/debian/patches/mailinabox

View File

@@ -40,21 +40,14 @@ The services all follow these rules:
* TLS certificates are generated with 2048-bit RSA keys and SHA-256 fingerprints. The box provides a self-signed certificate by default. The [setup guide](https://mailinabox.email/guide.html) explains how to verify the certificate fingerprint on first login. Users are encouraged to replace the certificate with a proper CA-signed one. ([source](setup/ssl.sh)) * TLS certificates are generated with 2048-bit RSA keys and SHA-256 fingerprints. The box provides a self-signed certificate by default. The [setup guide](https://mailinabox.email/guide.html) explains how to verify the certificate fingerprint on first login. Users are encouraged to replace the certificate with a proper CA-signed one. ([source](setup/ssl.sh))
* Only TLSv1, TLSv1.1 and TLSv1.2 are offered (the older SSL protocols are not offered). * Only TLSv1, TLSv1.1 and TLSv1.2 are offered (the older SSL protocols are not offered).
* Export-grade ciphers, the anonymous DH/ECDH algorithms (aNULL), and clear-text ciphers (eNULL) are not offered. * HTTPS, IMAP, and POP track the [Mozilla Intermediate Ciphers Recommendation](https://wiki.mozilla.org/Security/Server_Side_TLS), balancing security with supporting a wide range of mail clients. Diffie-Hellman ciphers use a 2048-bit key for forward secrecy. For more details, see the [output of SSLyze for these ports](tests/tls_results.txt).
* The minimum cipher key length offered is 112 bits. The maximum is 256 bits. Diffie-Hellman ciphers use a 2048-bit key for forward secrecy. * SMTP (port 25) uses the Postfix medium grade ciphers and SMTP Submission (port 587) uses the Postfix high grade ciphers ([more info](http://www.postfix.org/postconf.5.html#smtpd_tls_mandatory_ciphers)).
Additionally: Additionally:
* SMTP Submission (port 587) will not accept user credentials without STARTTLS (true also of SMTP on port 25 in case of client misconfiguration), and the submission port won't accept mail without encryption. The minimum cipher key length is 128 bits. (The box is of course configured not to be an open relay. User credentials are required to send outbound mail.) ([source](setup/mail-postfix.sh)) * SMTP Submission (port 587) will not accept user credentials without STARTTLS (true also of SMTP on port 25 in case of client misconfiguration), and the submission port won't accept mail without encryption. The minimum cipher key length is 128 bits. (The box is of course configured not to be an open relay. User credentials are required to send outbound mail.) ([source](setup/mail-postfix.sh))
* HTTPS (port 443): The HTTPS Strict Transport Security header is set. A redirect from HTTP to HTTPS is offered. The [Qualys SSL Labs test](https://www.ssllabs.com/ssltest) should report an A+ grade. ([source 1](conf/nginx-ssl.conf), [source 2](conf/nginx.conf)) * HTTPS (port 443): The HTTPS Strict Transport Security header is set. A redirect from HTTP to HTTPS is offered. The [Qualys SSL Labs test](https://www.ssllabs.com/ssltest) should report an A+ grade. ([source 1](conf/nginx-ssl.conf), [source 2](conf/nginx.conf))
For more details, see the [output of SSLyze for these ports](tests/tls_results.txt).
The cipher and protocol selection are chosen to support the following clients:
* For HTTPS: Firefox 1, Chrome 1, IE 7, Opera 5, Safari 1, Windows XP IE8, Android 2.3, Java 7.
* For other protocols: TBD.
### Password Storage ### Password Storage
The passwords for mail users are stored on disk using the [SHA512-CRYPT](http://man7.org/linux/man-pages/man3/crypt.3.html) hashing scheme. ([source](management/mailconfig.py)) The passwords for mail users are stored on disk using the [SHA512-CRYPT](http://man7.org/linux/man-pages/man3/crypt.3.html) hashing scheme. ([source](management/mailconfig.py))
@@ -73,7 +66,7 @@ If DNSSEC is enabled at the box's domain name's registrar, the SSHFP record that
`fail2ban` provides some protection from brute-force login attacks (repeated logins that guess account passwords) by blocking offending IP addresses at the network level. `fail2ban` provides some protection from brute-force login attacks (repeated logins that guess account passwords) by blocking offending IP addresses at the network level.
The following services are protected: SSH, IMAP (dovecot), SMTP submission (postfix), webmail (roundcube), ownCloud/CalDAV/CardDAV (over HTTP), and the Mail-in-a-Box control panel & munin (over HTTP). The following services are protected: SSH, IMAP (dovecot), SMTP submission (postfix), webmail (roundcube), Nextcloud/CalDAV/CardDAV (over HTTP), and the Mail-in-a-Box control panel & munin (over HTTP).
Some other services running on the box may be missing fail2ban filters. Some other services running on the box may be missing fail2ban filters.

View File

@@ -7,7 +7,7 @@
######################################################### #########################################################
if [ -z "$TAG" ]; then if [ -z "$TAG" ]; then
TAG=v0.21b TAG=v0.29
fi fi
# Are we running as root? # Are we running as root?

View File

@@ -23,7 +23,7 @@ apt_install nsd ldnsutils openssh-client
mkdir -p /var/run/nsd mkdir -p /var/run/nsd
cat > /etc/nsd/nsd.conf << EOF; cat > /etc/nsd/nsd.conf << EOF;
# No not edit. Overwritten by Mail-in-a-Box setup. # Do not edit. Overwritten by Mail-in-a-Box setup.
server: server:
hide-version: yes hide-version: yes

View File

@@ -35,7 +35,7 @@ if [ -z "`tools/mail.py user`" ]; then
else else
# Use me@PRIMARY_HOSTNAME # Use me@PRIMARY_HOSTNAME
EMAIL_ADDR=me@$PRIMARY_HOSTNAME EMAIL_ADDR=me@$PRIMARY_HOSTNAME
EMAIL_PW=1234 EMAIL_PW=12345678
echo echo
echo "Creating a new administrative mail account for $EMAIL_ADDR with password $EMAIL_PW." echo "Creating a new administrative mail account for $EMAIL_ADDR with password $EMAIL_PW."
echo echo

View File

@@ -48,6 +48,15 @@ function apt_install {
apt_get_quiet install $PACKAGES apt_get_quiet install $PACKAGES
} }
function apt_add_repository_to_unattended_upgrades {
if [ -f /etc/apt/apt.conf.d/50unattended-upgrades ]; then
if ! grep -q "$1" /etc/apt/apt.conf.d/50unattended-upgrades; then
sed -i "/Allowed-Origins/a \
\"$1\";" /etc/apt/apt.conf.d/50unattended-upgrades
fi
fi
}
function get_default_hostname { function get_default_hostname {
# Guess the machine's hostname. It should be a fully qualified # Guess the machine's hostname. It should be a fully qualified
# domain name suitable for DNS. None of these calls may provide # domain name suitable for DNS. None of these calls may provide
@@ -170,7 +179,7 @@ function wget_verify {
DEST=$3 DEST=$3
CHECKSUM="$HASH $DEST" CHECKSUM="$HASH $DEST"
rm -f $DEST rm -f $DEST
wget -q -O $DEST $URL || exit 1 hide_output wget -O $DEST $URL
if ! echo "$CHECKSUM" | sha1sum --check --strict > /dev/null; then if ! echo "$CHECKSUM" | sha1sum --check --strict > /dev/null; then
echo "------------------------------------------------------------" echo "------------------------------------------------------------"
echo "Download of $URL did not match expected checksum." echo "Download of $URL did not match expected checksum."

View File

@@ -37,8 +37,16 @@ apt_install \
# of active IMAP connections (at, say, 5 open connections per user that # of active IMAP connections (at, say, 5 open connections per user that
# would be 20 users). Set it to 250 times the number of cores this # would be 20 users). Set it to 250 times the number of cores this
# machine has, so on a two-core machine that's 500 processes/100 users). # machine has, so on a two-core machine that's 500 processes/100 users).
# The `default_vsz_limit` is the maximum amount of virtual memory that
# can be allocated. It should be set *reasonably high* to avoid allocation
# issues with larger mailboxes. We're setting it to 1/3 of the total
# available memory (physical mem + swap) to be sure.
# See here for discussion:
# - https://www.dovecot.org/list/dovecot/2012-August/137569.html
# - https://www.dovecot.org/list/dovecot/2011-December/132455.html
tools/editconf.py /etc/dovecot/conf.d/10-master.conf \ tools/editconf.py /etc/dovecot/conf.d/10-master.conf \
default_process_limit=$(echo "`nproc` * 250" | bc) \ default_process_limit=$(echo "`nproc` * 250" | bc) \
default_vsz_limit=$(echo "`free -tm | tail -1 | awk '{print $2}'` / 3" | bc)M \
log_path=/var/log/mail.log log_path=/var/log/mail.log
# The inotify `max_user_instances` default is 128, which constrains # The inotify `max_user_instances` default is 128, which constrains
@@ -71,12 +79,15 @@ tools/editconf.py /etc/dovecot/conf.d/10-auth.conf \
# Enable SSL, specify the location of the SSL certificate and private key files. # Enable SSL, specify the location of the SSL certificate and private key files.
# Disable obsolete SSL protocols and allow only good ciphers per http://baldric.net/2013/12/07/tls-ciphers-in-postfix-and-dovecot/. # Disable obsolete SSL protocols and allow only good ciphers per http://baldric.net/2013/12/07/tls-ciphers-in-postfix-and-dovecot/.
# Enable strong ssl dh parameters
tools/editconf.py /etc/dovecot/conf.d/10-ssl.conf \ tools/editconf.py /etc/dovecot/conf.d/10-ssl.conf \
ssl=required \ ssl=required \
"ssl_cert=<$STORAGE_ROOT/ssl/ssl_certificate.pem" \ "ssl_cert=<$STORAGE_ROOT/ssl/ssl_certificate.pem" \
"ssl_key=<$STORAGE_ROOT/ssl/ssl_private_key.pem" \ "ssl_key=<$STORAGE_ROOT/ssl/ssl_private_key.pem" \
"ssl_protocols=!SSLv3 !SSLv2" \ "ssl_protocols=!SSLv3 !SSLv2" \
"ssl_cipher_list=TLSv1+HIGH !SSLv2 !RC4 !aNULL !eNULL !3DES @STRENGTH" "ssl_cipher_list=ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS" \
"ssl_prefer_server_ciphers = yes" \
"ssl_dh_parameters_length = 2048"
# Disable in-the-clear IMAP/POP because there is no reason for a user to transmit # Disable in-the-clear IMAP/POP because there is no reason for a user to transmit
# login credentials outside of an encrypted connection. Only the over-TLS versions # login credentials outside of an encrypted connection. Only the over-TLS versions

View File

@@ -4,25 +4,55 @@ source setup/functions.sh
echo "Installing Mail-in-a-Box system management daemon..." echo "Installing Mail-in-a-Box system management daemon..."
# Install packages. # DEPENDENCIES
# flask, yaml, dnspython, and dateutil are all for our Python 3 management daemon itself.
# duplicity does backups. python-pip is so we can 'pip install boto' for Python 2, for duplicity, so it can do backups to AWS S3.
apt_install python3-flask links duplicity libyaml-dev python3-dnspython python3-dateutil python-pip
# These are required to pip install cryptography. # We used to install management daemon-related Python packages
apt_install build-essential libssl-dev libffi-dev python3-dev # 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.
#
# python-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 python-virtualenv certbot
hide_output pip2 install --upgrade boto
# Create a virtualenv for the installation of Python 3 packages
# used by the management daemon.
inst_dir=/usr/local/lib/mailinabox
mkdir -p $inst_dir
venv=$inst_dir/env
if [ ! -d $venv ]; then
virtualenv -ppython3 $venv
fi
# Upgrade pip because the Ubuntu-packaged version is out of date.
hide_output $venv/bin/pip install --upgrade pip
# Install other Python 3 packages used by the management daemon. # Install other Python 3 packages used by the management daemon.
# The first line is the packages that Josh maintains himself! # The first line is the packages that Josh maintains himself!
# NOTE: email_validator is repeated in setup/questions.sh, so please keep the versions synced. # NOTE: email_validator is repeated in setup/questions.sh, so please keep the versions synced.
hide_output pip3 install --upgrade \ hide_output $venv/bin/pip install --upgrade \
rtyaml "email_validator>=1.0.0" "free_tls_certificates>=0.1.3" \ rtyaml "email_validator>=1.0.0" "exclusiveprocess" \
"idna>=2.0.0" "cryptography>=1.0.2" boto psutil flask dnspython python-dateutil \
"idna>=2.0.0" "cryptography==2.2.2" boto psutil
# duplicity uses python 2 so we need to get the python 2 package of boto to have backups to S3. # CONFIGURATION
# 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.
hide_output pip install --upgrade boto
# Create a backup directory and a random key for encrypting backups. # Create a backup directory and a random key for encrypting backups.
mkdir -p $STORAGE_ROOT/backup mkdir -p $STORAGE_ROOT/backup
@@ -30,12 +60,40 @@ if [ ! -f $STORAGE_ROOT/backup/secret_key.txt ]; then
$(umask 077; openssl rand -base64 2048 > $STORAGE_ROOT/backup/secret_key.txt) $(umask 077; openssl rand -base64 2048 > $STORAGE_ROOT/backup/secret_key.txt)
fi fi
# Link the management server daemon into a well known location.
rm -f /usr/local/bin/mailinabox-daemon # Download jQuery and Bootstrap local files
ln -s `pwd`/management/daemon.py /usr/local/bin/mailinabox-daemon
# Make sure we have the directory to save to.
assets_dir=$inst_dir/vendor/assets
rm -rf $assets_dir
mkdir -p $assets_dir
# jQuery CDN URL
jquery_version=2.1.4
jquery_url=https://code.jquery.com
# Get jQuery
wget_verify $jquery_url/jquery-$jquery_version.min.js 43dc554608df885a59ddeece1598c6ace434d747 $assets_dir/jquery.min.js
# Bootstrap CDN URL
bootstrap_version=3.3.7
bootstrap_url=https://github.com/twbs/bootstrap/releases/download/v$bootstrap_version/bootstrap-$bootstrap_version-dist.zip
# Get Bootstrap
wget_verify $bootstrap_url e6b1000b94e835ffd37f4c6dcbdad43f4b48a02a /tmp/bootstrap.zip
unzip -q /tmp/bootstrap.zip -d $assets_dir
mv $assets_dir/bootstrap-$bootstrap_version-dist $assets_dir/bootstrap
rm -f /tmp/bootstrap.zip
# Create an init script to start the management daemon and keep it # Create an init script to start the management daemon and keep it
# running after a reboot. # running after a reboot.
rm -f /usr/local/bin/mailinabox-daemon # old path
cat > $inst_dir/start <<EOF;
#!/bin/bash
source $venv/bin/activate
exec python `pwd`/management/daemon.py
EOF
chmod +x $inst_dir/start
rm -f /etc/init.d/mailinabox rm -f /etc/init.d/mailinabox
ln -s $(pwd)/conf/management-initscript /etc/init.d/mailinabox ln -s $(pwd)/conf/management-initscript /etc/init.d/mailinabox
hide_output update-rc.d mailinabox defaults hide_output update-rc.d mailinabox defaults

View File

@@ -137,6 +137,17 @@ def migration_10(env):
shutil.move(sslcert, newname) shutil.move(sslcert, newname)
os.rmdir(d) os.rmdir(d)
def migration_11(env):
# Archive the old Let's Encrypt account directory managed by free_tls_certificates
# because we'll use that path now for the directory managed by certbot.
try:
old_path = os.path.join(env["STORAGE_ROOT"], 'ssl', 'lets_encrypt')
new_path = os.path.join(env["STORAGE_ROOT"], 'ssl', 'lets_encrypt-old')
shutil.move(old_path, new_path)
except:
# meh
pass
def get_current_migration(): def get_current_migration():
ver = 0 ver = 0
while True: while True:

View File

@@ -38,8 +38,10 @@ chown munin. /var/log/munin/munin-cgi-html.log
chown munin. /var/log/munin/munin-cgi-graph.log chown munin. /var/log/munin/munin-cgi-graph.log
# ensure munin-node knows the name of this machine # ensure munin-node knows the name of this machine
# and reduce logging level to warning
tools/editconf.py /etc/munin/munin-node.conf -s \ tools/editconf.py /etc/munin/munin-node.conf -s \
host_name=$PRIMARY_HOSTNAME host_name=$PRIMARY_HOSTNAME \
log_level=1
# Update the activated plugins through munin's autoconfiguration. # Update the activated plugins through munin's autoconfiguration.
munin-node-configure --shell --remove-also 2>/dev/null | sh munin-node-configure --shell --remove-also 2>/dev/null | sh

View File

@@ -1,21 +1,26 @@
#!/bin/bash #!/bin/bash
# Owncloud # Nextcloud
########################## ##########################
source setup/functions.sh # load our functions source setup/functions.sh # load our functions
source /etc/mailinabox.conf # load global vars source /etc/mailinabox.conf # load global vars
# ### Installing ownCloud # ### Installing Nextcloud
echo "Installing ownCloud (contacts/calendar)..." echo "Installing Nextcloud (contacts/calendar)..."
# Keep the php5 dependancies for the owncloud upgrades
apt_install \ apt_install \
dbconfig-common \ 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-cli php5-sqlite php5-gd php5-imap php5-curl php-pear php-apc curl libapr1 libtool libcurl4-openssl-dev php-xml-parser \
php5 php5-dev php5-gd php5-fpm memcached php5-memcached unzip php5 php5-dev php5-gd php5-fpm memcached php5-memcached
apt-get purge -qq -y owncloud* 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 php7.0-xml php7.0-mbstring php7.0-zip php7.0-apcu php7.0-json php7.0-intl
# Migrate <= v0.10 setups that stored the ownCloud config.php in /usr/local rather than # Migrate <= v0.10 setups that stored the ownCloud config.php in /usr/local rather than
# in STORAGE_ROOT. Move the file to STORAGE_ROOT. # in STORAGE_ROOT. Move the file to STORAGE_ROOT.
if [ ! -f $STORAGE_ROOT/owncloud/config.php ] \ if [ ! -f $STORAGE_ROOT/owncloud/config.php ] \
@@ -28,32 +33,35 @@ if [ ! -f $STORAGE_ROOT/owncloud/config.php ] \
ln -sf $STORAGE_ROOT/owncloud/config.php /usr/local/lib/owncloud/config/config.php ln -sf $STORAGE_ROOT/owncloud/config.php /usr/local/lib/owncloud/config/config.php
fi fi
InstallOwncloud() { InstallNextcloud() {
echo
echo "Upgrading to ownCloud version $1"
echo
version=$1 version=$1
hash=$2 hash=$2
# Remove the current owncloud echo
echo "Upgrading to Nextcloud version $version"
echo
# Remove the current owncloud/Nextcloud
rm -rf /usr/local/lib/owncloud rm -rf /usr/local/lib/owncloud
# Download and verify # Download and verify
wget_verify https://download.owncloud.org/community/owncloud-$version.zip $hash /tmp/owncloud.zip wget_verify https://download.nextcloud.com/server/releases/nextcloud-$version.zip $hash /tmp/nextcloud.zip
# Extract ownCloud # Extract ownCloud/Nextcloud
unzip -q /tmp/owncloud.zip -d /usr/local/lib unzip -q /tmp/nextcloud.zip -d /usr/local/lib
rm -f /tmp/owncloud.zip mv /usr/local/lib/nextcloud /usr/local/lib/owncloud
rm -f /tmp/nextcloud.zip
# The two apps we actually want are not in ownCloud core. Download the releases from # The two apps we actually want are not in Nextcloud core. Download the releases from
# their github repositories. # their github repositories.
mkdir -p /usr/local/lib/owncloud/apps mkdir -p /usr/local/lib/owncloud/apps
wget_verify https://github.com/owncloud/contacts/releases/download/v1.4.0.0/contacts.tar.gz c1c22d29699456a45db447281682e8bc3f10e3e7 /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/ tar xf /tmp/contacts.tgz -C /usr/local/lib/owncloud/apps/
rm /tmp/contacts.tgz rm /tmp/contacts.tgz
wget_verify https://github.com/nextcloud/calendar/releases/download/v1.4.0/calendar.tar.gz c84f3170efca2a99ea6254de34b0af3cb0b3a821 /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/ tar xf /tmp/calendar.tgz -C /usr/local/lib/owncloud/apps/
rm /tmp/calendar.tgz rm /tmp/calendar.tgz
@@ -85,21 +93,84 @@ InstallOwncloud() {
fi fi
} }
owncloud_ver=9.1.1 # We only install ownCloud intermediate versions to be able to seemlesly upgrade to Nextcloud
InstallOwncloud() {
# Check if ownCloud dir exist, and check if version matches owncloud_ver (if either doesn't - install/upgrade) 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.tar.bz2 $hash /tmp/owncloud.tar.bz2
# Extract ownCloud
tar xjf /tmp/owncloud.tar.bz2 -C /usr/local/lib
rm -f /tmp/owncloud.tar.bz2
# 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=13.0.6
owncloud_hash=33e41f476f0e2be5dc7cdb9d496673d9647aa3d6
# Check if Nextcloud dir exist, and check if version matches owncloud_ver (if either doesn't - install/upgrade)
if [ ! -d /usr/local/lib/owncloud/ ] \ if [ ! -d /usr/local/lib/owncloud/ ] \
|| ! grep -q $owncloud_ver /usr/local/lib/owncloud/version.php; then || ! grep -q $owncloud_ver /usr/local/lib/owncloud/version.php; then
# Stop php-fpm # Stop php-fpm if running. If theyre not running (which happens on a previously failed install), dont bail.
hide_output service php5-fpm stop service php7.0-fpm stop &> /dev/null || /bin/true
service php5-fpm stop &> /dev/null || /bin/true
# Backup the existing ownCloud. # Backup the existing ownCloud/Nextcloud.
# Create a backup directory to store the current installation and database to # Create a backup directory to store the current installation and database to
BACKUP_DIRECTORY=$STORAGE_ROOT/owncloud-backup/`date +"%Y-%m-%d-%T"` BACKUP_DIRECTORY=$STORAGE_ROOT/owncloud-backup/`date +"%Y-%m-%d-%T"`
mkdir -p "$BACKUP_DIRECTORY" mkdir -p "$BACKUP_DIRECTORY"
if [ -d /usr/local/lib/owncloud/ ]; then if [ -d /usr/local/lib/owncloud/ ]; then
echo "upgrading ownCloud to $owncloud_ver (backing up existing ownCloud installation, configuration and database to directory to $BACKUP_DIRECTORY..." echo "upgrading ownCloud/Nextcloud to $owncloud_flavor $owncloud_ver (backing up existing installation, configuration and database to directory to $BACKUP_DIRECTORY..."
cp -r /usr/local/lib/owncloud "$BACKUP_DIRECTORY/owncloud-install" cp -r /usr/local/lib/owncloud "$BACKUP_DIRECTORY/owncloud-install"
fi fi
if [ -e /home/user-data/owncloud/owncloud.db ]; then if [ -e /home/user-data/owncloud/owncloud.db ]; then
@@ -109,16 +180,16 @@ if [ ! -d /usr/local/lib/owncloud/ ] \
cp /home/user-data/owncloud/config.php $BACKUP_DIRECTORY cp /home/user-data/owncloud/config.php $BACKUP_DIRECTORY
fi fi
# We only need to check if we do upgrades when owncloud was previously installed # We only need to check if we do upgrades when owncloud/Nextcloud was previously installed
if [ -e /usr/local/lib/owncloud/version.php ]; then if [ -e /usr/local/lib/owncloud/version.php ]; then
if grep -q "8.1.[0-9]" /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" echo "We are running 8.1.x, upgrading to 8.2.11 first"
InstallOwncloud 8.2.3 bfdf6166fbf6fc5438dc358600e7239d1c970613 InstallOwncloud 8.2.11 e4794938fc2f15a095018ba9d6ee18b53f6f299c
fi fi
# If we are upgrading from 8.2.x we should go to 9.0 first. Owncloud doesn't support skipping minor versions # 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 "8.2.[0-9]" /usr/local/lib/owncloud/version.php; then 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" echo "We are running version 8.2.x, upgrading to 9.0.11 first"
# We need to disable memcached. The upgrade and install fails # We need to disable memcached. The upgrade and install fails
# with memcached # with memcached
@@ -127,7 +198,7 @@ if [ ! -d /usr/local/lib/owncloud/ ] \
<?php <?php
include("$STORAGE_ROOT/owncloud/config.php"); include("$STORAGE_ROOT/owncloud/config.php");
\$CONFIG['memcache.local'] = '\OC\Memcache\APC'; \$CONFIG['memcache.local'] = '\OC\Memcache\APCu';
echo "<?php\n\\\$CONFIG = "; echo "<?php\n\\\$CONFIG = ";
var_export(\$CONFIG); var_export(\$CONFIG);
@@ -136,28 +207,58 @@ if [ ! -d /usr/local/lib/owncloud/ ] \
EOF EOF
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
# We can now install owncloud 9.0.2 # We can now install owncloud 9.0.11
InstallOwncloud 9.0.2 72a3d15d09f58c06fa8bee48b9e60c9cd356f9c5 InstallOwncloud 9.0.11 fc8bad8a62179089bc58c406b28997fb0329337b
# The owncloud 9 migration doesn't migrate calendars and contacts # The owncloud 9 migration doesn't migrate calendars and contacts
# The option to migrate these are removed in 9.1 # The option to migrate these are removed in 9.1
# So the migrations should be done when we have 9.0 installed # So the migrations should be done when we have 9.0 installed
sudo -u www-data php /usr/local/lib/owncloud/occ dav:migrate-addressbooks sudo -u www-data php5 /usr/local/lib/owncloud/occ dav:migrate-addressbooks
# The following migration has to be done for each owncloud user # The following migration has to be done for each owncloud user
for directory in $STORAGE_ROOT/owncloud/*@*/ ; do for directory in $STORAGE_ROOT/owncloud/*@*/ ; do
username=$(basename "${directory}") username=$(basename "${directory}")
sudo -u www-data php /usr/local/lib/owncloud/occ dav:migrate-calendar $username sudo -u www-data php5 /usr/local/lib/owncloud/occ dav:migrate-calendar $username
done done
sudo -u www-data php /usr/local/lib/owncloud/occ dav:sync-birthday-calendar 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.7 first"
InstallOwncloud 9.1.7 1307d997d0b23dc42742d315b3e2f11423a9c808
fi
# Newer ownCloud 9.1.x versions cannot be upgraded to Nextcloud 10 and have to be
# upgraded to Nextcloud 11 straight away, see:
# https://github.com/nextcloud/server/issues/2203
# However, for some reason, upgrading to the latest Nextcloud 11.0.7 doesn't
# work either. Therefore, we're upgrading to Nextcloud 11.0.0 in the interim.
# This should not be a problem since we're upgrading to the latest Nextcloud 12
# in the next step.
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 11.0.0 first"
InstallNextcloud 11.0.0 e8c9ebe72a4a76c047080de94743c5c11735e72e
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.7 first"
InstallNextcloud 11.0.7 f936ddcb2ae3dbb66ee4926eb8b2ebbddc3facbe
fi
# 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
fi fi
InstallOwncloud $owncloud_ver 72ed9812432f01b3a459c4afc33f5c76b71eec09 InstallNextcloud $owncloud_ver $owncloud_hash
fi fi
# ### Configuring ownCloud # ### Configuring Nextcloud
# Setup ownCloud if the ownCloud database does not yet exist. Running setup when # Setup Nextcloud if the Nextcloud database does not yet exist. Running setup when
# the database does exist wipes the database and user data. # the database does exist wipes the database and user data.
if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
# Create user data directory # Create user data directory
@@ -172,7 +273,7 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
'instanceid' => '$instanceid', 'instanceid' => '$instanceid',
'forcessl' => true, # if unset/false, ownCloud sends a HSTS=0 header, which conflicts with nginx config 'forcessl' => true, # if unset/false, Nextcloud sends a HSTS=0 header, which conflicts with nginx config
'overwritewebroot' => '/cloud', 'overwritewebroot' => '/cloud',
'overwrite.cli.url' => '/cloud', 'overwrite.cli.url' => '/cloud',
@@ -182,7 +283,7 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
'arguments'=>array('{127.0.0.1:993/imap/ssl/novalidate-cert}') 'arguments'=>array('{127.0.0.1:993/imap/ssl/novalidate-cert}')
) )
), ),
'memcache.local' => '\OC\Memcache\APC', 'memcache.local' => '\OC\Memcache\APCu',
'mail_smtpmode' => 'sendmail', 'mail_smtpmode' => 'sendmail',
'mail_smtpsecure' => '', 'mail_smtpsecure' => '',
'mail_smtpauthtype' => 'LOGIN', 'mail_smtpauthtype' => 'LOGIN',
@@ -192,7 +293,6 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
'mail_smtpname' => '', 'mail_smtpname' => '',
'mail_smtppassword' => '', 'mail_smtppassword' => '',
'mail_from_address' => 'owncloud', 'mail_from_address' => 'owncloud',
'mail_domain' => '$PRIMARY_HOSTNAME',
); );
?> ?>
EOF EOF
@@ -209,7 +309,7 @@ EOF
'dbtype' => 'sqlite3', 'dbtype' => 'sqlite3',
# create an administrator account with a random password so that # create an administrator account with a random password so that
# the user does not have to enter anything on first load of ownCloud # the user does not have to enter anything on first load of Nextcloud
'adminlogin' => 'root', 'adminlogin' => 'root',
'adminpass' => '$adminpassword', 'adminpass' => '$adminpassword',
); );
@@ -219,7 +319,7 @@ EOF
# Set permissions # Set permissions
chown -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud chown -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
# Execute ownCloud's setup step, which creates the ownCloud sqlite database. # Execute Nextcloud's setup step, which creates the Nextcloud sqlite database.
# It also wipes it if it exists. And it updates config.php with database # It also wipes it if it exists. And it updates config.php with database
# settings and deletes the autoconfig.php file. # settings and deletes the autoconfig.php file.
(cd /usr/local/lib/owncloud; sudo -u www-data php /usr/local/lib/owncloud/index.php;) (cd /usr/local/lib/owncloud; sudo -u www-data php /usr/local/lib/owncloud/index.php;)
@@ -233,6 +333,8 @@ fi
# * We need to set the timezone to the system timezone to allow fail2ban to ban # * We need to set the timezone to the system timezone to allow fail2ban to ban
# users within the proper timeframe # users within the proper timeframe
# * We need to set the logdateformat to something that will work correctly with fail2ban # * We need to set the logdateformat to something that will work correctly with fail2ban
# * mail_domain' needs to be set every time we run the setup. Making sure we are setting
# the correct domain name if the domain is being change from the previous setup.
# Use PHP to read the settings file, modify it, and write out the new settings array. # Use PHP to read the settings file, modify it, and write out the new settings array.
TIMEZONE=$(cat /etc/timezone) TIMEZONE=$(cat /etc/timezone)
CONFIG_TEMP=$(/bin/mktemp) CONFIG_TEMP=$(/bin/mktemp)
@@ -242,13 +344,15 @@ include("$STORAGE_ROOT/owncloud/config.php");
\$CONFIG['trusted_domains'] = array('$PRIMARY_HOSTNAME'); \$CONFIG['trusted_domains'] = array('$PRIMARY_HOSTNAME');
\$CONFIG['memcache.local'] = '\OC\Memcache\APC'; \$CONFIG['memcache.local'] = '\OC\Memcache\APCu';
\$CONFIG['overwrite.cli.url'] = '/cloud'; \$CONFIG['overwrite.cli.url'] = '/cloud';
\$CONFIG['mail_from_address'] = 'administrator'; # just the local part, matches our master administrator address \$CONFIG['mail_from_address'] = 'administrator'; # just the local part, matches our master administrator address
\$CONFIG['logtimezone'] = '$TIMEZONE'; \$CONFIG['logtimezone'] = '$TIMEZONE';
\$CONFIG['logdateformat'] = 'Y-m-d H:i:s'; \$CONFIG['logdateformat'] = 'Y-m-d H:i:s';
\$CONFIG['mail_domain'] = '$PRIMARY_HOSTNAME';
echo "<?php\n\\\$CONFIG = "; echo "<?php\n\\\$CONFIG = ";
var_export(\$CONFIG); var_export(\$CONFIG);
echo ";"; echo ";";
@@ -256,9 +360,9 @@ echo ";";
EOF EOF
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
# Enable/disable apps. Note that this must be done after the ownCloud setup. # Enable/disable apps. Note that this must be done after the Nextcloud setup.
# The firstrunwizard gave Josh all sorts of problems, so disabling that. # The firstrunwizard gave Josh all sorts of problems, so disabling that.
# user_external is what allows ownCloud to use IMAP for login. The contacts # user_external is what allows Nextcloud to use IMAP for login. The contacts
# and calendar apps are the extensions we really care about here. # and calendar apps are the extensions we really care about here.
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:disable firstrunwizard hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:disable firstrunwizard
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable user_external hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable user_external
@@ -273,7 +377,7 @@ if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
# Set PHP FPM values to support large file uploads # Set PHP FPM values to support large file uploads
# (semicolon is the comment character in this file, hashes produce deprecation warnings) # (semicolon is the comment character in this file, hashes produce deprecation warnings)
tools/editconf.py /etc/php5/fpm/php.ini -c ';' \ tools/editconf.py /etc/php/7.0/fpm/php.ini -c ';' \
upload_max_filesize=16G \ upload_max_filesize=16G \
post_max_size=16G \ post_max_size=16G \
output_buffering=16384 \ output_buffering=16384 \
@@ -281,13 +385,27 @@ tools/editconf.py /etc/php5/fpm/php.ini -c ';' \
max_execution_time=600 \ max_execution_time=600 \
short_open_tag=On short_open_tag=On
# Set Nextcloud recommended opcache settings
tools/editconf.py /etc/php/7.0/cli/conf.d/10-opcache.ini -c ';' \
opcache.enable=1 \
opcache.enable_cli=1 \
opcache.interned_strings_buffer=8 \
opcache.max_accelerated_files=10000 \
opcache.memory_consumption=128 \
opcache.save_comments=1 \
opcache.revalidate_freq=1
# Configure the path environment for php-fpm
tools/editconf.py /etc/php/7.0/fpm/pool.d/www.conf -c ';' \
env[PATH]=/usr/local/bin:/usr/bin:/bin
# If apc is explicitly disabled we need to enable it # If apc is explicitly disabled we need to enable it
if grep -q apc.enabled=0 /etc/php5/mods-available/apcu.ini; then if grep -q apc.enabled=0 /etc/php/7.0/mods-available/apcu.ini; then
tools/editconf.py /etc/php5/mods-available/apcu.ini -c ';' \ tools/editconf.py /etc/php/7.0/mods-available/apcu.ini -c ';' \
apc.enabled=1 apc.enabled=1
fi fi
# Set up a cron job for owncloud. # Set up a cron job for Nextcloud.
cat > /etc/cron.hourly/mailinabox-owncloud << EOF; cat > /etc/cron.hourly/mailinabox-owncloud << EOF;
#!/bin/bash #!/bin/bash
# Mail-in-a-Box # Mail-in-a-Box
@@ -295,8 +413,8 @@ sudo -u www-data php -f /usr/local/lib/owncloud/cron.php
EOF EOF
chmod +x /etc/cron.hourly/mailinabox-owncloud chmod +x /etc/cron.hourly/mailinabox-owncloud
# There's nothing much of interest that a user could do as an admin for ownCloud, # There's nothing much of interest that a user could do as an admin for Nextcloud,
# and there's a lot they could mess up, so we don't make any users admins of ownCloud. # and there's a lot they could mess up, so we don't make any users admins of Nextcloud.
# But if we wanted to, we would do this: # But if we wanted to, we would do this:
# ``` # ```
# for user in $(tools/mail.py user admins); do # for user in $(tools/mail.py user admins); do
@@ -305,5 +423,4 @@ chmod +x /etc/cron.hourly/mailinabox-owncloud
# ``` # ```
# Enable PHP modules and restart PHP. # Enable PHP modules and restart PHP.
php5enmod imap restart_service php7.0-fpm
restart_service php5-fpm

View File

@@ -12,7 +12,9 @@ if [ -z "$NONINTERACTIVE" ]; then
apt_get_quiet install dialog python3 python3-pip || exit 1 apt_get_quiet install dialog python3 python3-pip || exit 1
fi fi
# email_validator is repeated in setup/management.sh # Installing email_validator is repeated in setup/management.sh, but in setup/management.sh
# we install it inside a virtualenv. In this script, we don't have the virtualenv yet
# so we install the python package globally.
hide_output pip3 install "email_validator>=1.0.0" || exit 1 hide_output pip3 install "email_validator>=1.0.0" || exit 1
message_box "Mail-in-a-Box Installation" \ message_box "Mail-in-a-Box Installation" \
@@ -49,7 +51,7 @@ you really want.
# user hit ESC/cancel # user hit ESC/cancel
exit exit
fi fi
while ! management/mailconfig.py validate-email "$EMAIL_ADDR" while ! python3 management/mailconfig.py validate-email "$EMAIL_ADDR"
do do
input_box "Your Email Address" \ input_box "Your Email Address" \
"That's not a valid email address.\n\nWhat email address are you setting this box up to manage?" \ "That's not a valid email address.\n\nWhat email address are you setting this box up to manage?" \
@@ -180,9 +182,6 @@ if [ "$PUBLIC_IPV6" = "auto" ]; then
fi fi
if [ "$PRIMARY_HOSTNAME" = "auto" ]; then if [ "$PRIMARY_HOSTNAME" = "auto" ]; then
PRIMARY_HOSTNAME=$(get_default_hostname) PRIMARY_HOSTNAME=$(get_default_hostname)
elif [ "$PRIMARY_HOSTNAME" = "auto-easy" ]; then
# Generate a probably-unique subdomain under our justtesting.email domain.
PRIMARY_HOSTNAME=`echo $PUBLIC_IP | sha1sum | cut -c1-5`.justtesting.email
fi fi
# Set STORAGE_USER and STORAGE_ROOT to default values (user-data and /home/user-data), unless # Set STORAGE_USER and STORAGE_ROOT to default values (user-data and /home/user-data), unless

View File

@@ -48,7 +48,7 @@ echo "public.pyzor.org:24441" > /etc/spamassassin/pyzor/servers
# * Disable localmode so Pyzor, DKIM and DNS checks can be used. # * Disable localmode so Pyzor, DKIM and DNS checks can be used.
tools/editconf.py /etc/default/spampd \ tools/editconf.py /etc/default/spampd \
DESTPORT=10026 \ DESTPORT=10026 \
ADDOPTS="\"--maxsize=500\"" \ ADDOPTS="\"--maxsize=2000\"" \
LOCALONLY=0 LOCALONLY=0
# Spamassassin normally wraps spam as an attachment inside a fresh # Spamassassin normally wraps spam as an attachment inside a fresh
@@ -61,9 +61,11 @@ tools/editconf.py /etc/default/spampd \
# content or execute scripts, and it is probably confusing to most users. # content or execute scripts, and it is probably confusing to most users.
# #
# Tell Spamassassin not to modify the original message except for adding # Tell Spamassassin not to modify the original message except for adding
# the X-Spam-Status mail header and related headers. # the X-Spam-Status & X-Spam-Score mail headers and related headers.
tools/editconf.py /etc/spamassassin/local.cf -s \ tools/editconf.py /etc/spamassassin/local.cf -s \
report_safe=0 report_safe=0 \
add_header="all Report _REPORT_" \
add_header="all Score _SCORE_"
# Bayesean learning # Bayesean learning
# ----------------- # -----------------

View File

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

View File

@@ -14,7 +14,7 @@ source setup/preflight.sh
# Python may not be able to read/write files. This is also # Python may not be able to read/write files. This is also
# in the management daemon startup script and the cron script. # in the management daemon startup script and the cron script.
if [ -z `locale -a | grep en_US.utf8` ]; then if ! locale -a | grep en_US.utf8 > /dev/null; then
# Generate locale if not exists # Generate locale if not exists
hide_output locale-gen en_US.UTF-8 hide_output locale-gen en_US.UTF-8
fi fi
@@ -127,13 +127,23 @@ tools/web_update
# fail2ban was first configured, but they should exist now. # fail2ban was first configured, but they should exist now.
restart_service fail2ban restart_service fail2ban
# If DNS is already working, try to provision TLS certficates from Let's Encrypt.
# Suppress extra reasons why domains aren't getting a new certificate.
management/ssl_certificates.py -q
# If there aren't any mail users yet, create one. # If there aren't any mail users yet, create one.
source setup/firstuser.sh source setup/firstuser.sh
# Register with Let's Encrypt, including agreeing to the Terms of Service.
# We'd let certbot ask the user interactively, but when this script is
# run in the recommended curl-pipe-to-bash method there is no TTY and
# certbot will fail if it tries to ask.
if [ ! -d $STORAGE_ROOT/ssl/lets_encrypt/accounts/acme-v02.api.letsencrypt.org/ ]; then
echo
echo "-----------------------------------------------"
echo "Mail-in-a-Box uses Let's Encrypt to provision free SSL/TLS certificates"
echo "to enable HTTPS connections to your box. We're automatically"
echo "agreeing you to their subscriber agreement. See https://letsencrypt.org."
echo
certbot register --register-unsafely-without-email --agree-tos --config-dir $STORAGE_ROOT/ssl/lets_encrypt
fi
# Done. # Done.
echo echo
echo "-----------------------------------------------" echo "-----------------------------------------------"
@@ -147,17 +157,17 @@ if management/status_checks.py --check-primary-hostname; then
echo https://$PRIMARY_HOSTNAME/admin echo https://$PRIMARY_HOSTNAME/admin
echo echo
echo "If you have a DNS problem put the box's IP address in the URL" echo "If you have a DNS problem put the box's IP address in the URL"
echo "(https://$PUBLIC_IP/admin) but then check the SSL fingerprint:" echo "(https://$PUBLIC_IP/admin) but then check the TLS fingerprint:"
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint \ openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint -sha256\
| sed "s/SHA1 Fingerprint=//" | sed "s/SHA256 Fingerprint=//"
else else
echo https://$PUBLIC_IP/admin echo https://$PUBLIC_IP/admin
echo echo
echo You will be alerted that the website has an invalid certificate. Check that echo You will be alerted that the website has an invalid certificate. Check that
echo the certificate fingerprint matches: echo the certificate fingerprint matches:
echo echo
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint \ openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint -sha256\
| sed "s/SHA1 Fingerprint=//" | sed "s/SHA256 Fingerprint=//"
echo echo
echo Then you can confirm the security exception and continue. echo Then you can confirm the security exception and continue.
echo echo

View File

@@ -68,17 +68,10 @@ then
fi fi
fi fi
# ### Add Mail-in-a-Box's PPA. # ### Add PPAs.
# We've built several .deb packages on our own that we want to include.
# One is a replacement for Ubuntu's stock postgrey package that makes
# some enhancements. The other is dovecot-lucene, a Lucene-based full
# text search plugin for (and by) dovecot, which is not available in
# Ubuntu currently.
#
# So, first ensure add-apt-repository is installed, then use it to install
# the [mail-in-a-box ppa](https://launchpad.net/~mail-in-a-box/+archive/ubuntu/ppa).
# We install some non-standard Ubuntu packages maintained by us and other
# third-party providers. First ensure add-apt-repository is installed.
if [ ! -f /usr/bin/add-apt-repository ]; then if [ ! -f /usr/bin/add-apt-repository ]; then
echo "Installing add-apt-repository..." echo "Installing add-apt-repository..."
@@ -86,16 +79,32 @@ if [ ! -f /usr/bin/add-apt-repository ]; then
apt_install software-properties-common apt_install software-properties-common
fi fi
# [Main-in-a-Box's own PPA](https://launchpad.net/~mail-in-a-box/+archive/ubuntu/ppa)
# holds several .deb packages that we built on our own.
# 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.
hide_output add-apt-repository -y ppa:mail-in-a-box/ppa hide_output add-apt-repository -y ppa:mail-in-a-box/ppa
hide_output add-apt-repository -y ppa:certbot/certbot
# ### Update Packages # ### Update Packages
# Update system packages to make sure we have the latest upstream versions of things from Ubuntu. # Update system packages to make sure we have the latest upstream versions
# of things from Ubuntu, as well as the directory of packages provide by the
# PPAs so we can install those packages later.
echo Updating system packages... echo Updating system packages...
hide_output apt-get update hide_output apt-get update
apt_get_quiet upgrade apt_get_quiet upgrade
# Old kernels pile up over time and take up a lot of disk space, and because of Mail-in-a-Box
# changes there may be other packages that are no longer needed. Clear out anything apt knows
# is safe to delete.
apt_get_quiet autoremove
# ### Install System Packages # ### Install System Packages
# Install basic utilities. # Install basic utilities.
@@ -116,9 +125,21 @@ apt_get_quiet upgrade
echo Installing system packages... echo Installing system packages...
apt_install python3 python3-dev python3-pip \ apt_install python3 python3-dev python3-pip \
netcat-openbsd wget curl git sudo coreutils bc \ netcat-openbsd wget curl git sudo coreutils bc \
haveged pollinate \ haveged pollinate unzip \
unattended-upgrades cron ntp fail2ban 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
# ### Suppress Upgrade Prompts # ### Suppress Upgrade Prompts
# Since Mail-in-a-Box might jump straight to 18.04 LTS, there's no need # 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. # to be reminded about 16.04 on every login.
@@ -230,7 +251,7 @@ cat > /etc/apt/apt.conf.d/02periodic <<EOF;
APT::Periodic::MaxAge "7"; APT::Periodic::MaxAge "7";
APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1"; APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::Verbose "1"; APT::Periodic::Verbose "0";
EOF EOF
# ### Firewall # ### Firewall

View File

@@ -18,7 +18,11 @@ fi
# Turn off nginx's default website. # Turn off nginx's default website.
echo "Installing Nginx (web server)..." echo "Installing Nginx (web server)..."
apt_install nginx php5-fpm
apt_install nginx php7.0-cli php7.0-fpm
# Set PHP7 as the default
update-alternatives --set php /usr/bin/php7.0
rm -f /etc/nginx/sites-enabled/default rm -f /etc/nginx/sites-enabled/default
@@ -40,15 +44,19 @@ tools/editconf.py /etc/nginx/nginx.conf -s \
server_names_hash_bucket_size="128;" server_names_hash_bucket_size="128;"
# Tell PHP not to expose its version number in the X-Powered-By header. # Tell PHP not to expose its version number in the X-Powered-By header.
tools/editconf.py /etc/php5/fpm/php.ini -c ';' \ tools/editconf.py /etc/php/7.0/fpm/php.ini -c ';' \
expose_php=Off expose_php=Off
# Set PHPs default charset to UTF-8, since we use it. See #367. # Set PHPs default charset to UTF-8, since we use it. See #367.
tools/editconf.py /etc/php5/fpm/php.ini -c ';' \ tools/editconf.py /etc/php/7.0/fpm/php.ini -c ';' \
default_charset="UTF-8" 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 ';' \
pm=ondemand
# Bump up PHP's max_children to support more concurrent connections # Bump up PHP's max_children to support more concurrent connections
tools/editconf.py /etc/php5/fpm/pool.d/www.conf -c ';' \ tools/editconf.py /etc/php/7.0/fpm/pool.d/www.conf -c ';' \
pm.max_children=8 pm.max_children=8
# Other nginx settings will be configured by the management service # Other nginx settings will be configured by the management service
@@ -103,7 +111,7 @@ done #NODOC
# Start services. # Start services.
restart_service nginx restart_service nginx
restart_service php5-fpm restart_service php7.0-fpm
# Open ports. # Open ports.
ufw_allow http ufw_allow http

View File

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

View File

@@ -17,25 +17,34 @@ source /etc/mailinabox.conf # load global vars
echo "Installing Z-Push (Exchange/ActiveSync server)..." echo "Installing Z-Push (Exchange/ActiveSync server)..."
apt_install \ apt_install \
php-soap php5-imap libawl-php php5-xsl php7.0-soap php7.0-imap libawl-php php7.0-xsl
php5enmod imap phpenmod -v php7.0 imap
# Copy Z-Push into place. # Copy Z-Push into place.
TARGETHASH=80cbe53de4ab8dd598d1f2af6f0a23fa396c529a VERSION=2.4.4
TARGETHASH=104d44426852429dac8ec2783a4e9ad7752d4682
needs_update=0 #NODOC needs_update=0 #NODOC
if [ ! -f /usr/local/lib/z-push/version ]; then if [ ! -f /usr/local/lib/z-push/version ]; then
needs_update=1 #NODOC needs_update=1 #NODOC
elif [[ $TARGETHASH != `cat /usr/local/lib/z-push/version` ]]; then elif [[ $VERSION != `cat /usr/local/lib/z-push/version` ]]; then
# checks if the version # checks if the version
needs_update=1 #NODOC needs_update=1 #NODOC
fi fi
if [ $needs_update == 1 ]; then if [ $needs_update == 1 ]; then
git_clone https://github.com/fmbiete/Z-Push-contrib $TARGETHASH '' /usr/local/lib/z-push # Download
wget_verify "https://stash.z-hub.io/rest/api/latest/projects/ZP/repos/z-push/archive?at=refs%2Ftags%2F$VERSION&format=zip" $TARGETHASH /tmp/z-push.zip
# Extract into place.
rm -rf /usr/local/lib/z-push /tmp/z-push
unzip -q /tmp/z-push.zip -d /tmp/z-push
mv /tmp/z-push/src /usr/local/lib/z-push
rm -rf /tmp/z-push.zip /tmp/z-push
rm -f /usr/sbin/z-push-{admin,top} rm -f /usr/sbin/z-push-{admin,top}
ln -s /usr/local/lib/z-push/z-push-admin.php /usr/sbin/z-push-admin ln -s /usr/local/lib/z-push/z-push-admin.php /usr/sbin/z-push-admin
ln -s /usr/local/lib/z-push/z-push-top.php /usr/sbin/z-push-top ln -s /usr/local/lib/z-push/z-push-top.php /usr/sbin/z-push-top
echo $TARGETHASH > /usr/local/lib/z-push/version echo $VERSION > /usr/local/lib/z-push/version
fi fi
# Configure default config. # Configure default config.
@@ -67,6 +76,7 @@ cp conf/zpush/backend_caldav.php /usr/local/lib/z-push/backend/caldav/config.php
rm -f /usr/local/lib/z-push/autodiscover/config.php rm -f /usr/local/lib/z-push/autodiscover/config.php
cp conf/zpush/autodiscover_config.php /usr/local/lib/z-push/autodiscover/config.php cp conf/zpush/autodiscover_config.php /usr/local/lib/z-push/autodiscover/config.php
sed -i "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" /usr/local/lib/z-push/autodiscover/config.php sed -i "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" /usr/local/lib/z-push/autodiscover/config.php
sed -i "s^define('TIMEZONE', .*^define('TIMEZONE', '$(cat /etc/timezone)');^" /usr/local/lib/z-push/autodiscover/config.php
# Some directories it will use. # Some directories it will use.
@@ -92,4 +102,8 @@ EOF
# Restart service. # Restart service.
restart_service php5-fpm restart_service php7.0-fpm
# Fix states after upgrade
hide_output z-push-admin -a fixstates

View File

@@ -61,9 +61,9 @@ common_opts = ["--sslv2", "--sslv3", "--tlsv1", "--tlsv1_1", "--tlsv1_2", "--ren
# Assumes TLSv1, TLSv1.1, TLSv1.2. # Assumes TLSv1, TLSv1.1, TLSv1.2.
# #
# The 'old' ciphers bring compatibility back to Win XP IE 6. # The 'old' ciphers bring compatibility back to Win XP IE 6.
MOZILLA_CIPHERS_MODERN = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK" MOZILLA_CIPHERS_MODERN = "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"
MOZILLA_CIPHERS_INTERMEDIATE = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA" MOZILLA_CIPHERS_INTERMEDIATE = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"
MOZILLA_CIPHERS_OLD = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA" MOZILLA_CIPHERS_OLD = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP"
###################################################################### ######################################################################

View File

@@ -93,9 +93,9 @@ PORT 25
* SSLV3 Cipher Suites: * SSLV3 Cipher Suites:
Server rejected all cipher suites. Server rejected all cipher suites.
Should Not Offer: DHE-RSA-SEED-SHA, EDH-RSA-DES-CBC3-SHA, SEED-SHA Should Not Offer: (none -- good)
Could Also Offer: DH-DSS-AES128-GCM-SHA256, DH-DSS-AES128-SHA, DH-DSS-AES128-SHA256, DH-DSS-AES256-GCM-SHA384, DH-DSS-AES256-SHA, DH-DSS-AES256-SHA256, DH-DSS-CAMELLIA128-SHA, DH-DSS-CAMELLIA256-SHA, DH-DSS-DES-CBC3-SHA, DH-RSA-AES128-GCM-SHA256, DH-RSA-AES128-SHA, DH-RSA-AES128-SHA256, DH-RSA-AES256-GCM-SHA384, DH-RSA-AES256-SHA, DH-RSA-AES256-SHA256, DH-RSA-CAMELLIA128-SHA, DH-RSA-CAMELLIA256-SHA, DH-RSA-DES-CBC3-SHA, DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-DSS-AES256-SHA256, DHE-DSS-CAMELLIA128-SHA, DHE-DSS-CAMELLIA256-SHA, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-DES-CBC3-SHA, SRP-3DES-EDE-CBC-SHA, SRP-AES-128-CBC-SHA, SRP-AES-256-CBC-SHA, SRP-DSS-3DES-EDE-CBC-SHA, SRP-DSS-AES-128-CBC-SHA, SRP-DSS-AES-256-CBC-SHA, SRP-RSA-3DES-EDE-CBC-SHA, SRP-RSA-AES-128-CBC-SHA, SRP-RSA-AES-256-CBC-SHA 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: OpenSSL/1.0.2, OpenSSL/1.0.1l, BingPreview/Jan 2015, Yahoo Slurp/Jan 2015, YandexBot/Jan 2015, Android/4.4.2, Safari/7/iOS 7.1, Safari/8/OS X 10.10, Safari/8/iOS 8.1.2, Safari/7/OS X 10.9, Safari/6/iOS 6.0.1, Firefox/31.3.0 ESR/Win 7, Baidu/Jan 2015, IE/11/Win 8.1, IE/11/Win 7, IE Mobile/11/Win Phone 8.1, Android/5.0.0, Java/8u31, Chrome/42/OS X, Googlebot/Feb 2015, Android/4.1.1, Android/4.0.4, Safari/6.0.4/OS X 10.8.4, Android/4.2.2, Android/4.3, Safari/5.1.9/OS X 10.6.8, Firefox/37/OS X, OpenSSL/0.9.8y, Java/7u25, IE/8-10/Win 7, IE/7/Vista, IE Mobile/10/Win Phone 8.0, Android/2.3.7, Java/6u45, IE/8/XP 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
PORT 587 PORT 587
-------- --------
@@ -183,9 +183,9 @@ PORT 587
* SSLV3 Cipher Suites: * SSLV3 Cipher Suites:
Server rejected all cipher suites. Server rejected all cipher suites.
Should Not Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, CAMELLIA128-SHA, CAMELLIA256-SHA, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA, DHE-RSA-SEED-SHA, SEED-SHA 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: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384 Could Also Offer: ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA384
Supported Clients: OpenSSL/1.0.2, OpenSSL/1.0.1l, BingPreview/Jan 2015, Yahoo Slurp/Jan 2015, YandexBot/Jan 2015, Android/4.4.2, Safari/7/iOS 7.1, IE/11/Win 8.1, Safari/8/iOS 8.1.2, IE/11/Win 7, IE Mobile/11/Win Phone 8.1, Safari/8/OS X 10.10, Safari/7/OS X 10.9, Safari/6/iOS 6.0.1, Firefox/31.3.0 ESR/Win 7, Baidu/Jan 2015, Chrome/42/OS X, Android/5.0.0, Java/8u31, Googlebot/Feb 2015, Firefox/37/OS X, Android/4.0.4, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, Android/4.2.2, Android/4.3, Safari/5.1.9/OS X 10.6.8, IE/8-10/Win 7, IE/7/Vista, IE Mobile/10/Win Phone 8.0, OpenSSL/0.9.8y, Java/7u25, Java/6u45, Android/2.3.7 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
PORT 443 PORT 443
-------- --------
@@ -200,16 +200,16 @@ PORT 443
* OpenSSL Heartbleed: * OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed OK - Not vulnerable to Heartbleed
* Session Resumption:
With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: OK - Supported
* HTTP Strict Transport Security: * HTTP Strict Transport Security:
OK - HSTS header received: max-age=31536000 OK - HSTS header received: max-age=15768000
Unhandled exception when processing --chrome_sha1: Unhandled exception when processing --chrome_sha1:
exceptions.TypeError - Incorrect padding 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
* SSLV2 Cipher Suites: * SSLV2 Cipher Suites:
Server rejected all cipher suites. Server rejected all cipher suites.
@@ -223,12 +223,20 @@ exceptions.TypeError - Incorrect padding
DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits HTTP 200 OK DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits HTTP 200 OK
DHE-RSA-AES256-SHA DH-2048 bits 256 bits HTTP 200 OK DHE-RSA-AES256-SHA DH-2048 bits 256 bits HTTP 200 OK
DHE-RSA-AES256-GCM-SHA384 DH-2048 bits 256 bits HTTP 200 OK DHE-RSA-AES256-GCM-SHA384 DH-2048 bits 256 bits HTTP 200 OK
AES256-SHA256 - 256 bits HTTP 200 OK
AES256-SHA - 256 bits HTTP 200 OK
AES256-GCM-SHA384 - 256 bits HTTP 200 OK
ECDHE-RSA-AES128-SHA256 ECDH-256 bits 128 bits HTTP 200 OK ECDHE-RSA-AES128-SHA256 ECDH-256 bits 128 bits HTTP 200 OK
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK
ECDHE-RSA-AES128-GCM-SHA256 ECDH-256 bits 128 bits HTTP 200 OK ECDHE-RSA-AES128-GCM-SHA256 ECDH-256 bits 128 bits HTTP 200 OK
DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits HTTP 200 OK DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits HTTP 200 OK
DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK
DHE-RSA-AES128-GCM-SHA256 DH-2048 bits 128 bits HTTP 200 OK DHE-RSA-AES128-GCM-SHA256 DH-2048 bits 128 bits HTTP 200 OK
AES128-SHA256 - 128 bits HTTP 200 OK
AES128-SHA - 128 bits HTTP 200 OK
AES128-GCM-SHA256 - 128 bits HTTP 200 OK
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 DES-CBC3-SHA - 112 bits HTTP 200 OK
* TLSV1_1 Cipher Suites: * TLSV1_1 Cipher Suites:
@@ -237,8 +245,12 @@ exceptions.TypeError - Incorrect padding
Accepted: Accepted:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits HTTP 200 OK ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits HTTP 200 OK
DHE-RSA-AES256-SHA DH-2048 bits 256 bits HTTP 200 OK DHE-RSA-AES256-SHA DH-2048 bits 256 bits HTTP 200 OK
AES256-SHA - 256 bits HTTP 200 OK
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK
DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK
AES128-SHA - 128 bits HTTP 200 OK
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 DES-CBC3-SHA - 112 bits HTTP 200 OK
* TLSV1 Cipher Suites: * TLSV1 Cipher Suites:
@@ -247,16 +259,20 @@ exceptions.TypeError - Incorrect padding
Accepted: Accepted:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits HTTP 200 OK ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits HTTP 200 OK
DHE-RSA-AES256-SHA DH-2048 bits 256 bits HTTP 200 OK DHE-RSA-AES256-SHA DH-2048 bits 256 bits HTTP 200 OK
AES256-SHA - 256 bits HTTP 200 OK
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK
DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK
AES128-SHA - 128 bits HTTP 200 OK
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 DES-CBC3-SHA - 112 bits HTTP 200 OK
* SSLV3 Cipher Suites: * SSLV3 Cipher Suites:
Server rejected all cipher suites. Server rejected all cipher suites.
Should Not Offer: (none -- good) Should Not Offer: (none -- good)
Could Also Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, CAMELLIA128-SHA, CAMELLIA256-SHA, DH-DSS-AES128-GCM-SHA256, DH-DSS-AES128-SHA, DH-DSS-AES128-SHA256, DH-DSS-AES256-GCM-SHA384, DH-DSS-AES256-SHA, DH-DSS-AES256-SHA256, DH-DSS-CAMELLIA128-SHA, DH-DSS-CAMELLIA256-SHA, DH-RSA-AES128-GCM-SHA256, DH-RSA-AES128-SHA, DH-RSA-AES128-SHA256, DH-RSA-AES256-GCM-SHA384, DH-RSA-AES256-SHA, DH-RSA-AES256-SHA256, DH-RSA-CAMELLIA128-SHA, DH-RSA-CAMELLIA256-SHA, DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-DSS-AES256-SHA256, DHE-DSS-CAMELLIA128-SHA, DHE-DSS-CAMELLIA256-SHA, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, SRP-AES-128-CBC-SHA, SRP-AES-256-CBC-SHA, SRP-DSS-AES-128-CBC-SHA, SRP-DSS-AES-256-CBC-SHA, SRP-RSA-AES-128-CBC-SHA, SRP-RSA-AES-256-CBC-SHA 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: OpenSSL/1.0.2, OpenSSL/1.0.1l, BingPreview/Jan 2015, YandexBot/Jan 2015, Yahoo Slurp/Jan 2015, Android/4.4.2, Safari/7/iOS 7.1, Safari/8/OS X 10.10, Safari/8/iOS 8.1.2, Safari/7/OS X 10.9, Safari/6/iOS 6.0.1, Chrome/42/OS X, IE/11/Win 8.1, IE/11/Win 7, Android/5.0.0, Java/8u31, IE Mobile/11/Win Phone 8.1, Googlebot/Feb 2015, Firefox/31.3.0 ESR/Win 7, Firefox/37/OS X, Android/4.1.1, Android/4.0.4, Baidu/Jan 2015, Safari/6.0.4/OS X 10.8.4, Android/4.2.2, Android/4.3, Safari/5.1.9/OS X 10.6.8, IE/8-10/Win 7, IE/7/Vista, OpenSSL/0.9.8y, IE Mobile/10/Win Phone 8.0, Java/7u25, Android/2.3.7, Java/6u45, IE/8/XP Supported Clients: BingPreview/Jan 2015, OpenSSL/1.0.2, YandexBot/Jan 2015, OpenSSL/1.0.1l, Yahoo Slurp/Jan 2015, Android/4.4.2, Safari/7/iOS 7.1, Safari/8/iOS 8.1.2, Safari/6/iOS 6.0.1, Safari/7/OS X 10.9, Safari/8/OS X 10.10, IE/11/Win 7, IE/11/Win 8.1, IE Mobile/11/Win Phone 8.1, Java/8u31, Android/5.0.0, Googlebot/Feb 2015, Firefox/31.3.0 ESR/Win 7, Chrome/42/OS X, Baidu/Jan 2015, Android/4.1.1, Android/4.3, Android/4.0.4, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, Safari/6.0.4/OS X 10.8.4, Firefox/37/OS X, OpenSSL/0.9.8y, Java/7u25, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, Java/6u45, Android/2.3.7, IE/8/XP
PORT 993 PORT 993
-------- --------
@@ -270,64 +286,73 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
* OpenSSL Heartbleed: * OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed OK - Not vulnerable to Heartbleed
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* Session Resumption: * Session Resumption:
With Session IDs: NOT SUPPORTED (0 successful, 5 failed, 0 errors, 5 total attempts). With Session IDs: NOT SUPPORTED (0 successful, 5 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: NOT SUPPORTED - TLS ticket assigned but not accepted. With TLS Session Tickets: NOT SUPPORTED - TLS ticket assigned but not accepted.
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* TLSV1_2 Cipher Suites: * TLSV1_2 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits
Accepted: Accepted:
ECDHE-RSA-AES256-SHA384 ECDH-384 bits 256 bits
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits ECDHE-RSA-AES256-GCM-SHA384 ECDH-384 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits
CAMELLIA256-SHA - 256 bits DHE-RSA-AES256-SHA DH-2048 bits 256 bits
DHE-RSA-AES256-GCM-SHA384 DH-2048 bits 256 bits
AES256-SHA256 - 256 bits
AES256-SHA - 256 bits AES256-SHA - 256 bits
AES256-GCM-SHA384 - 256 bits
ECDHE-RSA-AES128-SHA256 ECDH-384 bits 128 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits
CAMELLIA128-SHA - 128 bits DHE-RSA-AES128-SHA DH-2048 bits 128 bits
DHE-RSA-AES128-GCM-SHA256 DH-2048 bits 128 bits
AES128-SHA256 - 128 bits
AES128-SHA - 128 bits AES128-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: * TLSV1_1 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
Accepted: Accepted:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits DHE-RSA-AES256-SHA DH-2048 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
AES256-SHA - 256 bits AES256-SHA - 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits DHE-RSA-AES128-SHA DH-2048 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
AES128-SHA - 128 bits AES128-SHA - 128 bits
ECDHE-RSA-DES-CBC3-SHA ECDH-384 bits 112 bits
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits
DES-CBC3-SHA - 112 bits
* TLSV1 Cipher Suites: * TLSV1 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
Accepted: Accepted:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits DHE-RSA-AES256-SHA DH-2048 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
AES256-SHA - 256 bits AES256-SHA - 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits DHE-RSA-AES128-SHA DH-2048 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
AES128-SHA - 128 bits AES128-SHA - 128 bits
ECDHE-RSA-DES-CBC3-SHA ECDH-384 bits 112 bits
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits
DES-CBC3-SHA - 112 bits
* SSLV3 Cipher Suites: * SSLV3 Cipher Suites:
Server rejected all cipher suites. Server rejected all cipher suites.
Should Not Offer: AES128-SHA, AES256-SHA, CAMELLIA128-SHA, CAMELLIA256-SHA, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA 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: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA256, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-RSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-SHA256, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-RSA-AES256-SHA384 Could Also Offer: ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA384
Supported Clients: OpenSSL/1.0.2, Firefox/31.3.0 ESR/Win 7, OpenSSL/1.0.1l, BingPreview/Jan 2015, Yahoo Slurp/Jan 2015, Baidu/Jan 2015, Safari/7/iOS 7.1, Chrome/42/OS X, Googlebot/Feb 2015, Android/4.0.4, Safari/8/iOS 8.1.2, Android/4.1.1, Android/5.0.0, Safari/6/iOS 6.0.1, YandexBot/Jan 2015, Safari/6.0.4/OS X 10.8.4, Android/4.2.2, Safari/8/OS X 10.10, Firefox/37/OS X, Safari/7/OS X 10.9, Android/4.3, Safari/5.1.9/OS X 10.6.8, Android/4.4.2, IE/8-10/Win 7, IE/7/Vista, IE/11/Win 8.1, IE/11/Win 7, OpenSSL/0.9.8y, IE Mobile/10/Win Phone 8.0, IE Mobile/11/Win Phone 8.1, Java/7u25, Java/8u31, Java/6u45, Android/2.3.7 Supported Clients: BingPreview/Jan 2015, OpenSSL/1.0.2, YandexBot/Jan 2015, OpenSSL/1.0.1l, Yahoo Slurp/Jan 2015, Android/4.4.2, Safari/7/iOS 7.1, Safari/8/iOS 8.1.2, Safari/6/iOS 6.0.1, Safari/7/OS X 10.9, Safari/8/OS X 10.10, IE/11/Win 7, IE/11/Win 8.1, IE Mobile/11/Win Phone 8.1, Java/8u31, Android/5.0.0, Googlebot/Feb 2015, Firefox/31.3.0 ESR/Win 7, Chrome/42/OS X, Baidu/Jan 2015, Android/4.1.1, Android/4.3, Android/4.0.4, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, Safari/6.0.4/OS X 10.8.4, Firefox/37/OS X, OpenSSL/0.9.8y, Java/7u25, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, Java/6u45, Android/2.3.7, IE/8/XP
PORT 995 PORT 995
-------- --------
@@ -341,62 +366,71 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
* OpenSSL Heartbleed: * OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed OK - Not vulnerable to Heartbleed
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* Session Resumption: * Session Resumption:
With Session IDs: NOT SUPPORTED (0 successful, 5 failed, 0 errors, 5 total attempts). With Session IDs: NOT SUPPORTED (0 successful, 5 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: NOT SUPPORTED - TLS ticket assigned but not accepted. With TLS Session Tickets: NOT SUPPORTED - TLS ticket assigned but not accepted.
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* TLSV1_2 Cipher Suites: * TLSV1_2 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits
Accepted: Accepted:
ECDHE-RSA-AES256-SHA384 ECDH-384 bits 256 bits
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits ECDHE-RSA-AES256-GCM-SHA384 ECDH-384 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits
CAMELLIA256-SHA - 256 bits DHE-RSA-AES256-SHA DH-2048 bits 256 bits
DHE-RSA-AES256-GCM-SHA384 DH-2048 bits 256 bits
AES256-SHA256 - 256 bits
AES256-SHA - 256 bits AES256-SHA - 256 bits
AES256-GCM-SHA384 - 256 bits
ECDHE-RSA-AES128-SHA256 ECDH-384 bits 128 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits
CAMELLIA128-SHA - 128 bits DHE-RSA-AES128-SHA DH-2048 bits 128 bits
DHE-RSA-AES128-GCM-SHA256 DH-2048 bits 128 bits
AES128-SHA256 - 128 bits
AES128-SHA - 128 bits AES128-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: * TLSV1_1 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
Accepted: Accepted:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits DHE-RSA-AES256-SHA DH-2048 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
AES256-SHA - 256 bits AES256-SHA - 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits DHE-RSA-AES128-SHA DH-2048 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
AES128-SHA - 128 bits AES128-SHA - 128 bits
ECDHE-RSA-DES-CBC3-SHA ECDH-384 bits 112 bits
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits
DES-CBC3-SHA - 112 bits
* TLSV1 Cipher Suites: * TLSV1 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
Accepted: Accepted:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits DHE-RSA-AES256-SHA DH-2048 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
AES256-SHA - 256 bits AES256-SHA - 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits DHE-RSA-AES128-SHA DH-2048 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
AES128-SHA - 128 bits AES128-SHA - 128 bits
ECDHE-RSA-DES-CBC3-SHA ECDH-384 bits 112 bits
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits
DES-CBC3-SHA - 112 bits
* SSLV3 Cipher Suites: * SSLV3 Cipher Suites:
Server rejected all cipher suites. Server rejected all cipher suites.
Should Not Offer: AES128-SHA, AES256-SHA, CAMELLIA128-SHA, CAMELLIA256-SHA, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA 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: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA256, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-RSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-SHA256, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-RSA-AES256-SHA384 Could Also Offer: ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA384
Supported Clients: OpenSSL/1.0.2, Firefox/31.3.0 ESR/Win 7, OpenSSL/1.0.1l, BingPreview/Jan 2015, Yahoo Slurp/Jan 2015, Baidu/Jan 2015, Safari/7/iOS 7.1, Chrome/42/OS X, Googlebot/Feb 2015, Android/4.0.4, Safari/8/iOS 8.1.2, Android/4.1.1, Android/5.0.0, Safari/6/iOS 6.0.1, YandexBot/Jan 2015, Safari/6.0.4/OS X 10.8.4, Android/4.2.2, Safari/8/OS X 10.10, Firefox/37/OS X, Safari/7/OS X 10.9, Android/4.3, Safari/5.1.9/OS X 10.6.8, Android/4.4.2, IE/8-10/Win 7, IE/7/Vista, IE/11/Win 8.1, IE/11/Win 7, OpenSSL/0.9.8y, IE Mobile/10/Win Phone 8.0, IE Mobile/11/Win Phone 8.1, Java/7u25, Java/8u31, Java/6u45, Android/2.3.7 Supported Clients: BingPreview/Jan 2015, OpenSSL/1.0.2, YandexBot/Jan 2015, OpenSSL/1.0.1l, Yahoo Slurp/Jan 2015, Android/4.4.2, Safari/7/iOS 7.1, Safari/8/iOS 8.1.2, Safari/6/iOS 6.0.1, Safari/7/OS X 10.9, Safari/8/OS X 10.10, IE/11/Win 7, IE/11/Win 8.1, IE Mobile/11/Win Phone 8.1, Java/8u31, Android/5.0.0, Googlebot/Feb 2015, Firefox/31.3.0 ESR/Win 7, Chrome/42/OS X, Baidu/Jan 2015, Android/4.1.1, Android/4.3, Android/4.0.4, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, Safari/6.0.4/OS X 10.8.4, Firefox/37/OS X, OpenSSL/0.9.8y, Java/7u25, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, Java/6u45, Android/2.3.7, IE/8/XP

View File

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

View File

@@ -27,10 +27,11 @@ fi
echo "Restoring backup from $1" echo "Restoring backup from $1"
service php5-fpm stop service php5-fpm stop
service php7.0-fpm stop
# remove the current owncloud installation # remove the current ownCloud/Nextcloud installation
rm -rf /usr/local/lib/owncloud/ rm -rf /usr/local/lib/owncloud/
# restore the current owncloud application # restore the current ownCloud/Nextcloud application
cp -r "$1/owncloud-install" /usr/local/lib/owncloud cp -r "$1/owncloud-install" /usr/local/lib/owncloud
# restore access rights # restore access rights
@@ -46,4 +47,5 @@ chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off
service php5-fpm start service php5-fpm start
service php7.0-fpm start
echo "Done" echo "Done"

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# #
# This script will give you administrative access to the ownCloud # This script will give you administrative access to the Nextcloud
# instance running here. # instance running here.
# #
# Run this at your own risk. This is for testing & experimentation # Run this at your own risk. This is for testing & experimentation
@@ -14,10 +14,10 @@ test -z "$1" || ADMIN=$1
echo I am going to unlock admin features for $ADMIN. echo I am going to unlock admin features for $ADMIN.
echo You can provide another user to unlock as the first argument of this script. echo You can provide another user to unlock as the first argument of this script.
echo echo
echo WARNING: you could break mail-in-a-box when fiddling around with owncloud\'s admin interface echo WARNING: you could break mail-in-a-box when fiddling around with Nextcloud\'s admin interface
echo If in doubt, press CTRL-C to cancel. echo If in doubt, press CTRL-C to cancel.
echo echo
echo Press enter to continue. echo Press enter to continue.
read read
sqlite3 $STORAGE_ROOT/owncloud/owncloud.db "INSERT OR IGNORE INTO oc_group_user VALUES ('admin', '$ADMIN')" && echo Done. sudo -u www-data php /usr/local/lib/owncloud/occ group:adduser admin $ADMIN && echo Done.

View File

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