mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2026-03-12 17:07:23 +01:00
Compare commits
254 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
049bfb6f7f | ||
|
|
12d60d102b | ||
|
|
41642f2f59 | ||
|
|
7de8fc9bc0 | ||
|
|
c87b62b8c2 | ||
|
|
8fe33da85d | ||
|
|
c202a5cbc6 | ||
|
|
1353949e42 | ||
|
|
c19f8c9ee6 | ||
|
|
40b21c466d | ||
|
|
f52749b403 | ||
|
|
d67e09f334 | ||
|
|
e224fc6656 | ||
|
|
5e47677f7a | ||
|
|
db9637ce4f | ||
|
|
f908bc364e | ||
|
|
30c2c60f59 | ||
|
|
ab5ce01bdd | ||
|
|
ddadb6c28a | ||
|
|
23be1031b8 | ||
|
|
faee29ba8b | ||
|
|
b86bf07d57 | ||
|
|
e6294049bc | ||
|
|
30885bcc8a | ||
|
|
a67f90593d | ||
|
|
385340da46 | ||
|
|
0271e549bb | ||
|
|
f53b18ebb9 | ||
|
|
8567a9b719 | ||
|
|
ad9d732608 | ||
|
|
aa15670dc2 | ||
|
|
81176c8e4b | ||
|
|
960b5d5bbd | ||
|
|
802e7a1f4d | ||
|
|
52c68c6510 | ||
|
|
54b1ee9a3d | ||
|
|
6e3dee8b3b | ||
|
|
cd62fd9826 | ||
|
|
91638c7fe0 | ||
|
|
ff8170d5ab | ||
|
|
f6f75f6fab | ||
|
|
2f54f39f31 | ||
|
|
fa792f664e | ||
|
|
b50dfb7f93 | ||
|
|
cde4e0caca | ||
|
|
df80b9fc71 | ||
|
|
7558ffd4f3 | ||
|
|
50e9e8af30 | ||
|
|
ed02e2106b | ||
|
|
24a567c3be | ||
|
|
70f05e9d52 | ||
|
|
889118aeb6 | ||
|
|
a70ba94b0c | ||
|
|
9e29564f48 | ||
|
|
5aeced5c2e | ||
|
|
46f64e0e0a | ||
|
|
4971b63501 | ||
|
|
3ff9817325 | ||
|
|
d6becddbe5 | ||
|
|
1d6793d124 | ||
|
|
c7377e602d | ||
|
|
08021ea19f | ||
|
|
295d481603 | ||
|
|
c4cb828f65 | ||
|
|
0657f9e875 | ||
|
|
e37768ca86 | ||
|
|
bea5eb0dda | ||
|
|
fd5b11823c | ||
|
|
5fc1944f04 | ||
|
|
39fd4ce16c | ||
|
|
c0f4d5479f | ||
|
|
193763f8f0 | ||
|
|
79759ea5a3 | ||
|
|
6e5ceab0f8 | ||
|
|
c6fa0d23df | ||
|
|
85e59245fd | ||
|
|
4232a1205c | ||
|
|
0d4c693792 | ||
|
|
77b2246010 | ||
|
|
aff80ac58c | ||
|
|
25fec63a03 | ||
|
|
9b46637aff | ||
|
|
fb25013334 | ||
|
|
dd7a2aa8a6 | ||
|
|
149552f79b | ||
|
|
adddd95e38 | ||
|
|
bad38840d8 | ||
|
|
10050aa601 | ||
|
|
c60e3dc842 | ||
|
|
c7659d9053 | ||
|
|
cd3fb1b487 | ||
|
|
29e77d25fc | ||
|
|
e56c55efe8 | ||
|
|
8e0d9b9f21 | ||
|
|
6e60b47cb5 | ||
|
|
a3add03706 | ||
|
|
7b592b1e99 | ||
|
|
a67aa4cfd4 | ||
|
|
31b743b164 | ||
|
|
71f1c92b9e | ||
|
|
e80a1dd4b7 | ||
|
|
b7e9a90005 | ||
|
|
0d4565e71d | ||
|
|
703a9376ef | ||
|
|
b3b798adf2 | ||
|
|
bd54b41041 | ||
|
|
a211ad422b | ||
|
|
ef28a1defd | ||
|
|
c5c413b447 | ||
|
|
d2beb3919b | ||
|
|
a7dded8182 | ||
|
|
000363492e | ||
|
|
5be74dec6e | ||
|
|
9ddca42c91 | ||
|
|
ff6d8fc672 | ||
|
|
870b82637a | ||
|
|
dc6458623d | ||
|
|
60f9c9e3b7 | ||
|
|
e5e0c64395 | ||
|
|
aa52f52d02 | ||
|
|
b05b06c74a | ||
|
|
7f8f4518e3 | ||
|
|
86e2cfb6c8 | ||
|
|
0335595e7e | ||
|
|
8d5670068a | ||
|
|
c9b3d88108 | ||
|
|
16f38042ec | ||
|
|
2f494e9a1c | ||
|
|
f739662392 | ||
|
|
6eb9055275 | ||
|
|
3dbd6c994a | ||
|
|
bc4bdca752 | ||
|
|
bbfa01f33a | ||
|
|
f6a641ad23 | ||
|
|
51972fd129 | ||
|
|
bb43a2127c | ||
|
|
d96613b8fe | ||
|
|
504a9b0abc | ||
|
|
842fbb3d72 | ||
|
|
a5d5a073c7 | ||
|
|
d4b122ee94 | ||
|
|
052a1f3b26 | ||
|
|
180b054dbc | ||
|
|
cb162da5fe | ||
|
|
de9c556ad7 | ||
|
|
f420294819 | ||
|
|
738e0a6e17 | ||
|
|
e0d46d1eb5 | ||
|
|
7f37abca05 | ||
|
|
2f467556bd | ||
|
|
15583ec10d | ||
|
|
78d1c9be6e | ||
|
|
b0b5d8e792 | ||
|
|
82844ca651 | ||
|
|
2a72c800f6 | ||
|
|
8be23d5ef6 | ||
|
|
f9a0e39cc9 | ||
|
|
0c0a079354 | ||
|
|
42e86610ba | ||
|
|
7c62f4b8e9 | ||
|
|
1eba7b0616 | ||
|
|
9c7820f422 | ||
|
|
87ec4e9f82 | ||
|
|
08becf7fa3 | ||
|
|
5eb4a53de1 | ||
|
|
598ade3f7a | ||
|
|
8f399df5bb | ||
|
|
ae73dc5d30 | ||
|
|
c409b2efd0 | ||
|
|
6961840c0e | ||
|
|
6162a9637c | ||
|
|
47c968e71b | ||
|
|
ed3e2aa712 | ||
|
|
fe597da7aa | ||
|
|
61e9888a85 | ||
|
|
35fed8606e | ||
|
|
ef6f121491 | ||
|
|
ec3aab0eaa | ||
|
|
8c69b9e261 | ||
|
|
e7150e3bc6 | ||
|
|
8d6d84d87f | ||
|
|
a6a1cc7ae0 | ||
|
|
b5c0736d27 | ||
|
|
8ee7de6ff3 | ||
|
|
0088fb4553 | ||
|
|
b2d103145f | ||
|
|
fc9e279cec | ||
|
|
257983d559 | ||
|
|
e924459140 | ||
|
|
441bd35053 | ||
|
|
a0e603a3c6 | ||
|
|
88604074d6 | ||
|
|
d43111eb48 | ||
|
|
6729588d8c | ||
|
|
5f14eca67f | ||
|
|
8944cd7980 | ||
|
|
2bbbc9dfa3 | ||
|
|
544f155948 | ||
|
|
f080eabb3a | ||
|
|
7bf377eed1 | ||
|
|
cd554cf480 | ||
|
|
e5448405ae | ||
|
|
a7eff8fb35 | ||
|
|
341aa8695a | ||
|
|
5efdd72f41 | ||
|
|
f25801e88d | ||
|
|
cc7be13098 | ||
|
|
2556e3fbc2 | ||
|
|
00898b2ff5 | ||
|
|
35b8a149d8 | ||
|
|
d0423afd18 | ||
|
|
edf42df835 | ||
|
|
734745a4a6 | ||
|
|
dbebaba8b9 | ||
|
|
cb765dfe2a | ||
|
|
81258e2189 | ||
|
|
4dd4b4232a | ||
|
|
48ff664ee9 | ||
|
|
a52c56e571 | ||
|
|
6ace97e482 | ||
|
|
19a928e4ec | ||
|
|
78f2fe213e | ||
|
|
a16855ecf0 | ||
|
|
d773140502 | ||
|
|
2c324d0bc9 | ||
|
|
2bd6cc4d6b | ||
|
|
b11157e0b6 | ||
|
|
46ba62b7b1 | ||
|
|
4c36d6e6c9 | ||
|
|
e49c99890b | ||
|
|
a13fd90347 | ||
|
|
18f1689f45 | ||
|
|
8234a5a9f4 | ||
|
|
1d9f9ea617 | ||
|
|
fbb38c3881 | ||
|
|
2caddb41eb | ||
|
|
d2b7204319 | ||
|
|
68ebca8a15 | ||
|
|
9c9dcdbf0a | ||
|
|
0c4c2e51bb | ||
|
|
828512b95a | ||
|
|
add985ce5d | ||
|
|
416dbebf45 | ||
|
|
2a046a22f4 | ||
|
|
b66f12dd4c | ||
|
|
6e04eb490f | ||
|
|
cd39c2b53f | ||
|
|
5da168466d | ||
|
|
a5f39784dd | ||
|
|
a072730fb8 | ||
|
|
00c61dbcdd | ||
|
|
10bf40250b | ||
|
|
48e0f39179 | ||
|
|
bb641cdfba |
@@ -13,7 +13,7 @@ trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[Makefile]
|
||||
indent_style = tabs
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[Vagrantfile]
|
||||
@@ -23,7 +23,7 @@ indent_size = 2
|
||||
indent_size = 2
|
||||
|
||||
[*.py]
|
||||
indent_style = tabs
|
||||
indent_style = tab
|
||||
|
||||
[*.js]
|
||||
indent_size = 2
|
||||
|
||||
322
CHANGELOG.md
322
CHANGELOG.md
@@ -1,6 +1,328 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
v0.46 (June 11, 2020)
|
||||
---------------------
|
||||
|
||||
Security fixes:
|
||||
|
||||
* Roundcube is updated to version 1.4.6 (https://roundcube.net/news/2020/06/02/security-updates-1.4.5-and-1.3.12).
|
||||
|
||||
v0.45 (May 16, 2020)
|
||||
--------------------
|
||||
|
||||
Security fixes:
|
||||
|
||||
* Fix missing brute force login protection for Roundcube logins.
|
||||
|
||||
Software updates:
|
||||
|
||||
* Upgraded Roundcube from 1.4.2 to 1.4.4.
|
||||
* Upgraded Nextcloud from 17.0.2 to 17.0.6 (with Contacts from 3.1.6 to 3.3.0 and Calendar from 1.7.1 to v2.0.3)
|
||||
* Upgraded Z-Push to 2.5.2.
|
||||
|
||||
System:
|
||||
|
||||
* Nightly backups now occur on a random minute in the 3am hour (in the system time zone). The minute is chosen during Mail-in-a-Box installation/upgrade and remains the same until the next upgrade.
|
||||
* Fix for mail log statistics report on leap days.
|
||||
* Fix Mozilla autoconfig useGlobalPreferredServer setting.
|
||||
|
||||
Web:
|
||||
|
||||
* Add a new hidden feature to set nginx alias in www/custom.yaml.
|
||||
|
||||
Setup:
|
||||
|
||||
* Improved error handling.
|
||||
|
||||
v0.44 (February 15, 2020)
|
||||
-------------------------
|
||||
|
||||
System:
|
||||
|
||||
* TLS settings have been upgraded following Mozilla's recommendations for servers. TLS1.2 and 1.3 are now the only supported protocols for web, IMAP, and SMTP (submission).
|
||||
* Fixed an issue starting services when Mail-in-a-Box isn't on the root filesystem.
|
||||
* Changed some performance options affecting Roundcube and Nextcloud.
|
||||
|
||||
Software updates:
|
||||
|
||||
* Upgraded Nextcloud from 15.0.8 to 17.0.2 (with Contacts from 3.1.1 to 3.1.6 and Calendar from 1.6.5 to 1.7.1)
|
||||
* Upgraded Z-Push to 2.5.1.
|
||||
* Upgraded Roundcube from 1.3.10 to 1.4.2 and changed the default skin (theme) to Elastic.
|
||||
|
||||
Control panel:
|
||||
|
||||
* The Custom DNS list of records is now sorted.
|
||||
* The emails that report TLS provisioning results now has a less scary subject line.
|
||||
|
||||
Mail:
|
||||
|
||||
* Fetching of updated whitelist for greylisting was fetching each day instead of every month.
|
||||
* OpenDKIM signing has been changed to 'relaxed' mode so that some old mail lists that forward mail can do so.
|
||||
|
||||
DNS:
|
||||
|
||||
* Automatic autoconfig.* subdomains can now be suppressed with custom DNS records.
|
||||
* DNS zone transfer now works with IPv6 addresses.
|
||||
|
||||
Setup:
|
||||
|
||||
* An Ubuntu package source was missing on systems where it defaults off.
|
||||
|
||||
v0.43 (September 1, 2019)
|
||||
-------------------------
|
||||
|
||||
Security fixes:
|
||||
|
||||
* A security issue was discovered in rsync backups. If you have enabled rsync backups, the file `id_rsa_miab` may have been copied to your backup destination. This file can be used to access your backup destination. If the file was copied to your backup destination, we recommend that you delete the file on your backup destination, delete `/root/.ssh/id_rsa_miab` on your Mail-in-a-Box, then re-run Mail-in-a-Box setup, and re-configure your SSH public key at your backup destination according to the instructions in the Mail-in-a-Box control panel.
|
||||
* Brute force attack prevention was missing for the managesieve service.
|
||||
|
||||
Setup:
|
||||
|
||||
* Nextcloud was not upgraded properly after restoring Mail-in-a-Box from a backup from v0.40 or earlier.
|
||||
|
||||
Mail:
|
||||
|
||||
* Upgraded Roundcube to 1.3.10.
|
||||
* Fetch an updated whitelist for greylisting on a monthly basis to reduce the number of delayed incoming emails.
|
||||
|
||||
Control panel:
|
||||
|
||||
* When using secondary DNS, it is now possible to specify a subnet range with the `xfr:` option.
|
||||
* Fixed an issue when the secondary DNS option is used and the secondary DNS hostname resolves to multiple IP addresses.
|
||||
* Fix a bug in how a backup configuration error is shown.
|
||||
|
||||
v0.42b (August 3, 2019)
|
||||
-----------------------
|
||||
|
||||
Changes:
|
||||
|
||||
* Decreased the minimum supported RAM to 502 Mb.
|
||||
* Improved mail client autoconfiguration.
|
||||
* Added support for S3-compatible backup services besides Amazon S3.
|
||||
* Fixed the control panel login page to let LastPass save passwords.
|
||||
* Fixed an error in the user privileges API.
|
||||
* Silenced some spurrious messages.
|
||||
|
||||
Software updates:
|
||||
|
||||
* Upgraded Roundcube from 1.3.8 to 1.3.9.
|
||||
* Upgraded Nextcloud from 14.0.6 to 15.0.8 (with Contacts from 2.1.8 to 3.1.1 and Calendar from 1.6.4 to 1.6.5).
|
||||
* Upgraded Z-Push from 2.4.4 to 2.5.0.
|
||||
|
||||
Note that v0.42 (July 4, 2019) was pulled shortly after it was released to fix a Nextcloud upgrade issue.
|
||||
|
||||
v0.41 (February 26, 2019)
|
||||
-------------------------
|
||||
|
||||
System:
|
||||
|
||||
* Missing brute force login attack prevention (fail2ban) filters which stopped working on Ubuntu 18.04 were added back.
|
||||
* Upgrades would fail if Mail-in-a-Box moved to a different directory in `systemctl link`.
|
||||
|
||||
Mail:
|
||||
|
||||
* Incoming messages addressed to more than one local user were rejected because of a bug in spampd packaged by Ubuntu 18.04. A workaround was added.
|
||||
|
||||
Contacts/Calendar:
|
||||
|
||||
* Upgraded Nextcloud from 13.0.6 to 14.0.6.
|
||||
* Upgraded Contacts from 2.1.5 to 2.1.8.
|
||||
* Upgraded Calendar from 1.6.1 to 1.6.4.
|
||||
|
||||
v0.40 (January 12, 2019)
|
||||
------------------------
|
||||
|
||||
This is the first release for Ubuntu 18.04. This version and versions going forward can **only** be installed on Ubuntu 18.04; however, upgrades of existing Ubuntu 14.04 boxes to the latest version supporting Ubuntu 14.04 (v0.30) continue to work as normal.
|
||||
|
||||
When **upgrading**, you **must first upgrade your existing Ubuntu 14.04 Mail-in-a-Box box** to the latest release supporting Ubuntu 14.04 --- that's v0.30 --- before you migrate to Ubuntu 18.04. If you are running an older version of Mail-in-a-Box which has an old version of ownCloud or Nextcloud, you will *not* be able to upgrade your data because older versions of ownCloud and Nextcloud that are required to perform the upgrade *cannot* be run on Ubuntu 18.04. To upgrade from Ubuntu 14.04 to Ubuntu 18.04, you **must create a fresh Ubuntu 18.04 machine** before installing this version. In-place upgrades of servers are not supported. Since Ubuntu's support for Ubuntu 14.04 has almost ended, everyone is encouraged to create a new Ubuntu 18.04 machine and migrate to it.
|
||||
|
||||
For complete upgrade instructions, see:
|
||||
|
||||
https://discourse.mailinabox.email/t/mail-in-a-box-version-v0-40-and-moving-to-ubuntu-18-04/4289
|
||||
|
||||
The changelog for this release follows.
|
||||
|
||||
Setup:
|
||||
|
||||
* Mail-in-a-Box now targets Ubuntu 18.04 LTS, which will have support from Ubuntu through 2022.
|
||||
* Some of the system packages updated in virtue of using Ubuntu 18.04 include postfix (2.11=>3.3) nsd (4.0=>4.1), nginx (1.4=>1.14), PHP (7.0=>7.2), Python (3.4=>3.6), fail2ban (0.8=>0.10), Duplicity (0.6=>0.7).
|
||||
* [Unofficial Bash Strict Mode](http://redsymbol.net/articles/unofficial-bash-strict-mode/) is turned on for setup, which might catch previously uncaught issues during setup.
|
||||
|
||||
Mail:
|
||||
|
||||
* IMAP server-side full text search is no longer supported because we were using a custom-built `dovecot-lucene` package that we are no longer maintaining.
|
||||
* Sending email is now disabled on port 25 --- you must log in to port 587 to send email, per the long-standing mail instructions.
|
||||
* Greylisting may delay more emails from new senders. We were using a custom-built postgrey package previously that whitelisted sending domains in dnswl.org, but we are no longer maintaining that package.
|
||||
|
||||
v0.30 (January 9, 2019)
|
||||
-----------------------
|
||||
|
||||
Setup:
|
||||
|
||||
* Update to Roundcube 1.3.8 and the CardDAV plugin to 3.0.3.
|
||||
* Add missing rsyslog package to install line since some OS images don't have it installed by default.
|
||||
* A log file for nsd was added.
|
||||
|
||||
Control Panel:
|
||||
|
||||
* The users page now documents that passwords should only have ASCII characters to prevent character encoding mismaches between clients and the server.
|
||||
* The users page no longer shows user mailbox sizes because this was extremely slow for very large mailboxes.
|
||||
* The Mail-in-a-Box version is now shown in the system status checks even when the new-version check is disabled.
|
||||
* The alises page now warns that alises should not be used to forward mail off of the box. Mail filters within Roundcube are better for that.
|
||||
* The explanation of greylisting has been improved.
|
||||
|
||||
v0.29 (October 25, 2018)
|
||||
------------------------
|
||||
|
||||
* Starting with v0.28, TLS certificate provisioning wouldn't work on new boxes until the mailinabox setup command was run a second time because of a problem with the non-interactive setup.
|
||||
* Update to Nextcloud 13.0.6.
|
||||
* Update to Roundcube 1.3.7.
|
||||
* Update to Z-Push 2.4.4.
|
||||
* Backup dates listed in the control panel now use an internationalized format.
|
||||
|
||||
v0.28 (July 30, 2018)
|
||||
---------------------
|
||||
|
||||
System:
|
||||
|
||||
* We now use EFF's `certbot` to provision TLS certificates (from Let's Encrypt) instead of our home-grown ACME library.
|
||||
|
||||
Contacts/Calendar:
|
||||
|
||||
* Fix for Mac OS X autoconfig of the calendar.
|
||||
|
||||
Setup:
|
||||
|
||||
* Installing Z-Push broke because of what looks like a change or problem in their git server HTTPS certificate. That's fixed.
|
||||
|
||||
v0.27 (June 14, 2018)
|
||||
---------------------
|
||||
|
||||
Mail:
|
||||
|
||||
* A report of box activity, including sent/received mail totals and logins by user, is now emailed to the box's administrator user each week.
|
||||
* Update Roundcube to version 1.3.6 and Z-Push to version 2.3.9.
|
||||
|
||||
Control Panel:
|
||||
|
||||
* The undocumented feature for proxying web requests to another server now sets X-Forwarded-For.
|
||||
|
||||
v0.26c (February 13, 2018)
|
||||
--------------------------
|
||||
|
||||
Setup:
|
||||
|
||||
* Upgrades from v0.21c (February 1, 2017) or earlier were broken because the intermediate versions of ownCloud used in setup were no longer available from ownCloud.
|
||||
* Some download errors had no output --- there is more output on error now.
|
||||
|
||||
Control Panel:
|
||||
|
||||
* The background service for the control panel was not restarting on updates, leaving the old version running. This was broken in v0.26 and is now fixed.
|
||||
* Installing your own TLS/SSL certificate had been broken since v0.24 because the new version of openssl became stricter about CSR generation parameters.
|
||||
* Fixed password length help text.
|
||||
|
||||
Contacts/Calendar:
|
||||
|
||||
* Upgraded Nextcloud from 12.0.3 to 12.0.5.
|
||||
|
||||
v0.26b (January 25, 2018)
|
||||
-------------------------
|
||||
|
||||
* Fix new installations which broke at the step of asking for the user's desired email address, which was broken by v0.26's changes related to the control panel.
|
||||
* Fix the provisioning of TLS certificates by pinning a Python package we rely on (acme) to an earlier version because our code isn't yet compatible with its current version.
|
||||
* Reduce munin's log_level from debug to warning to prevent massive log files.
|
||||
|
||||
v0.26 (January 18, 2018)
|
||||
------------------------
|
||||
|
||||
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)
|
||||
---------------------
|
||||
|
||||
|
||||
@@ -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 provider–they're almost all listed._
|
||||
|
||||
### Modifying your `hosts` file
|
||||
|
||||
After a while, Mail-in-a-Box will be available at `192.168.50.4` (unless you changed that in your `Vagrantfile`). To be able to use the web-based bits, we recommend to add a hostname to your `hosts` file:
|
||||
|
||||
$ echo "192.168.50.4 mailinabox.lan" | sudo tee -a /etc/hosts
|
||||
|
||||
You should now be able to navigate to https://mailinabox.lan/admin using your browser. There should be an initial admin user with the name `me@mailinabox.lan` and the password `12345678`.
|
||||
|
||||
### Making changes
|
||||
|
||||
Your working copy of Mail-in-a-Box will be mounted inside your VM at `/vagrant`. Any change you make locally will appear inside your VM automatically.
|
||||
|
||||
Running `vagrant up --provision` again will repeat the installation with your modifications.
|
||||
|
||||
Alternatively, you can also ssh into the VM using:
|
||||
|
||||
$ vagrant ssh
|
||||
|
||||
Once inside the VM, you can re-run individual parts of the setup like in this example:
|
||||
|
||||
vm$ cd /vagrant
|
||||
vm$ sudo setup/owncloud.sh # replace with script you'd like to re-run
|
||||
|
||||
### Tests
|
||||
|
||||
Mail-in-a-Box needs more tests. If you're still looking for a way to help out, writing and contributing tests would be a great start!
|
||||
|
||||
## Public domain
|
||||
|
||||
This project is in the public domain. Copyright and related rights in the work worldwide are waived through the [CC0 1.0 Universal public domain dedication][CC0]. See the LICENSE file in this directory.
|
||||
|
||||
29
README.md
29
README.md
@@ -13,7 +13,7 @@ Our goals are to:
|
||||
|
||||
* Make deploying a good mail server easy.
|
||||
* Promote [decentralization](http://redecentralize.org/), innovation, and privacy on the web.
|
||||
* Have automated, auditable, and [idempotent](https://sharknet.us/2014/02/01/automated-configuration-management-challenges-with-idempotency/) configuration.
|
||||
* Have automated, auditable, and [idempotent](https://web.archive.org/web/20190518072631/https://sharknet.us/2014/02/01/automated-configuration-management-challenges-with-idempotency/) configuration.
|
||||
* **Not** make a totally unhackable, NSA-proof server.
|
||||
* **Not** make something customizable by power users.
|
||||
|
||||
@@ -22,13 +22,13 @@ Additionally, this project has a [Code of Conduct](CODE_OF_CONDUCT.md), which su
|
||||
The Box
|
||||
-------
|
||||
|
||||
Mail-in-a-Box turns a fresh Ubuntu 14.04 LTS 64-bit machine into a working mail server by installing and configuring various components.
|
||||
Mail-in-a-Box turns a fresh Ubuntu 18.04 LTS 64-bit machine into a working mail server by installing and configuring various components.
|
||||
|
||||
It is a one-click email appliance. There are no user-configurable setup options. It "just works".
|
||||
|
||||
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/))
|
||||
* 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
|
||||
@@ -37,7 +37,6 @@ The components installed are:
|
||||
It also includes:
|
||||
|
||||
* A control panel and API for adding/removing mail users, aliases, custom DNS records, etc. and detailed system monitoring.
|
||||
* Our own builds of postgrey (adding better whitelisting) and dovecot-lucene (faster search for mail) distributed via the [Mail-in-a-Box PPA](https://launchpad.net/~mail-in-a-box/+archive/ubuntu/ppa) on Launchpad.
|
||||
|
||||
For more information on how Mail-in-a-Box handles your privacy, see the [security details page](security.md).
|
||||
|
||||
@@ -46,20 +45,20 @@ Installation
|
||||
|
||||
See the [setup guide](https://mailinabox.email/guide.html) for detailed, user-friendly instructions.
|
||||
|
||||
For experts, start with a completely fresh (really, I mean it) Ubuntu 14.04 LTS 64-bit machine. On the machine...
|
||||
For experts, start with a completely fresh (really, I mean it) Ubuntu 18.04 LTS 64-bit machine. On the machine...
|
||||
|
||||
Clone this repository:
|
||||
|
||||
$ git clone https://github.com/mail-in-a-box/mailinabox
|
||||
$ cd mailinabox
|
||||
|
||||
_Optional:_ Download my PGP key and then verify that the sources were signed
|
||||
by me:
|
||||
_Optional:_ Download Josh's PGP key and then verify that the sources were signed
|
||||
by him:
|
||||
|
||||
$ curl -s https://keybase.io/joshdata/key.asc | gpg --import
|
||||
gpg: key C10BDD81: public key "Joshua Tauberer <jt@occams.info>" imported
|
||||
|
||||
$ git verify-tag v0.22
|
||||
$ git verify-tag v0.46
|
||||
gpg: Signature made ..... using RSA key ID C10BDD81
|
||||
gpg: Good signature from "Joshua Tauberer <jt@occams.info>"
|
||||
gpg: WARNING: This key is not certified with a trusted signature!
|
||||
@@ -68,19 +67,25 @@ by me:
|
||||
|
||||
You'll get a lot of warnings, but that's OK. Check that the primary key fingerprint matches the
|
||||
fingerprint in the key details at [https://keybase.io/joshdata](https://keybase.io/joshdata)
|
||||
and on my [personal homepage](https://razor.occams.info/). (Of course, if this repository has been compromised you can't trust these instructions.)
|
||||
and on his [personal homepage](https://razor.occams.info/). (Of course, if this repository has been compromised you can't trust these instructions.)
|
||||
|
||||
Checkout the tag corresponding to the most recent release:
|
||||
|
||||
$ git checkout v0.22
|
||||
$ git checkout v0.46
|
||||
|
||||
Begin the installation.
|
||||
|
||||
$ sudo setup/start.sh
|
||||
|
||||
For help, DO NOT contact me directly --- I don't do tech support by email or tweet (no exceptions).
|
||||
For help, DO NOT contact Josh directly --- I don't do tech support by email or tweet (no exceptions).
|
||||
|
||||
Post your question on the [discussion forum](https://discourse.mailinabox.email/) instead, where maintainers and Mail-in-a-Box users may be able to help you.
|
||||
|
||||
Contributing and Development
|
||||
----------------------------
|
||||
|
||||
Mail-in-a-Box is an open source project. Your contributions and pull requests are welcome. See [CONTRIBUTING](CONTRIBUTING.md) to get started.
|
||||
|
||||
Post your question on the [discussion forum](https://discourse.mailinabox.email/) instead, where me and other Mail-in-a-Box users may be able to help you.
|
||||
|
||||
The Acknowledgements
|
||||
--------------------
|
||||
|
||||
9
Vagrantfile
vendored
9
Vagrantfile
vendored
@@ -2,14 +2,7 @@
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "ubuntu14.04"
|
||||
config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"
|
||||
|
||||
if Vagrant.has_plugin?("vagrant-cachier")
|
||||
# Configure cached packages to be shared between instances of the same base box.
|
||||
# More info on http://fgrehm.viewdocs.io/vagrant-cachier/usage
|
||||
config.cache.scope = :box
|
||||
end
|
||||
config.vm.box = "ubuntu/bionic64"
|
||||
|
||||
# Network config: Since it's a mail server, the machine must be connected
|
||||
# to the public web. However, we currently don't want to expose SSH since
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Fail2Ban filter Dovecot authentication and pop3/imap server
|
||||
# Fail2Ban filter Dovecot authentication and pop3/imap/managesieve server
|
||||
# For Mail-in-a-Box
|
||||
|
||||
[INCLUDES]
|
||||
@@ -9,7 +9,7 @@ before = common.conf
|
||||
|
||||
_daemon = (auth|dovecot(-auth)?|auth-worker)
|
||||
|
||||
failregex = ^%(__prefix_line)s(pop3|imap)-login: (Info: )?(Aborted login|Disconnected)(: Inactivity)? \(((no auth attempts|auth failed, \d+ attempts)( in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<\S*>,)?( method=\S+,)? rip=<HOST>, lip=(\d{1,3}\.){3}\d{1,3}(, TLS( handshaking)?(: Disconnected)?)?(, session=<\S+>)?\s*$
|
||||
failregex = ^%(__prefix_line)s(pop3|imap|managesieve)-login: (Info: )?(Aborted login|Disconnected)(: Inactivity)? \(((no auth attempts|auth failed, \d+ attempts)( in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<\S*>,)?( method=\S+,)? rip=<HOST>, lip=(\d{1,3}\.){3}\d{1,3}(, TLS( handshaking)?(: Disconnected)?)?(, session=<\S+>)?\s*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
before = common.conf
|
||||
|
||||
[Definition]
|
||||
datepattern = %%Y-%%m-%%d %%H:%%M:%%S
|
||||
failregex=Login failed: .*Remote IP: '<HOST>[\)']
|
||||
ignoreregex =
|
||||
|
||||
@@ -34,7 +34,7 @@ findtime = 30
|
||||
enabled = true
|
||||
port = http,https
|
||||
filter = miab-owncloud
|
||||
logpath = STORAGE_ROOT/owncloud/owncloud.log
|
||||
logpath = STORAGE_ROOT/owncloud/nextcloud.log
|
||||
maxretry = 20
|
||||
findtime = 120
|
||||
|
||||
@@ -50,7 +50,7 @@ findtime = 30
|
||||
enabled = true
|
||||
port = http,https
|
||||
filter = miab-roundcube
|
||||
logpath = /var/log/roundcubemail/errors
|
||||
logpath = /var/log/roundcubemail/errors.log
|
||||
maxretry = 20
|
||||
findtime = 30
|
||||
|
||||
@@ -69,13 +69,10 @@ action = iptables-allports[name=recidive]
|
||||
# So the notification is ommited. This will prevent message appearing in the mail.log that mail
|
||||
# can't be delivered to fail2ban@$HOSTNAME.
|
||||
|
||||
[sasl]
|
||||
[postfix-sasl]
|
||||
enabled = true
|
||||
|
||||
[ssh]
|
||||
[sshd]
|
||||
enabled = true
|
||||
maxretry = 7
|
||||
bantime = 3600
|
||||
|
||||
[ssh-ddos]
|
||||
enabled = true
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
<string>PRIMARY_HOSTNAME</string>
|
||||
<key>CalDAVPort</key>
|
||||
<real>443</real>
|
||||
<key>CalDAVPrincipalURL</key>
|
||||
<string>/cloud/remote.php/caldav/calendars/</string>
|
||||
<key>CalDAVUseSSL</key>
|
||||
<true/>
|
||||
<key>PayloadDescription</key>
|
||||
|
||||
10
conf/mailinabox.service
Normal file
10
conf/mailinabox.service
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Mail-in-a-Box System Management Service
|
||||
After=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=idle
|
||||
ExecStart=/usr/local/lib/mailinabox/start
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,135 +0,0 @@
|
||||
#! /bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: mailinabox
|
||||
# Required-Start: $all
|
||||
# Required-Stop: $all
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Start and stop the Mail-in-a-Box management daemon.
|
||||
# Description: Start and stop the Mail-in-a-Box management daemon.
|
||||
### END INIT INFO
|
||||
|
||||
# Adapted from http://blog.codefront.net/2007/06/11/nginx-php-and-a-php-fastcgi-daemon-init-script/
|
||||
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC="Mail-in-a-Box Management Daemon"
|
||||
NAME=mailinabox
|
||||
DAEMON=/usr/local/bin/mailinabox-daemon
|
||||
PIDFILE=/var/run/$NAME.pid
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
|
||||
# Exit if the package is not installed
|
||||
[ -x "$DAEMON" ] || exit 0
|
||||
|
||||
# Set defaults.
|
||||
START=yes
|
||||
EXEC_AS_USER=root
|
||||
|
||||
# Ensure Python reads/writes files in UTF-8. If the machine
|
||||
# triggers some other locale in Python, like ASCII encoding,
|
||||
# Python may not be able to read/write files. Set also
|
||||
# setup/start.sh (where the locale is also installed if not
|
||||
# already present) and management/daily_tasks.sh.
|
||||
export LANGUAGE=en_US.UTF-8
|
||||
export LC_ALL=en_US.UTF-8
|
||||
export LANG=en_US.UTF-8
|
||||
export LC_TYPE=en_US.UTF-8
|
||||
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
# If the daemon is not enabled, give the user a warning and then exit,
|
||||
# unless we are stopping the daemon
|
||||
if [ "$START" != "yes" -a "$1" != "stop" ]; then
|
||||
log_warning_msg "To enable $NAME, edit /etc/default/$NAME and set START=yes"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Process configuration
|
||||
#export ...
|
||||
DAEMON_ARGS=""
|
||||
|
||||
|
||||
do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|
||||
|| return 1
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
|
||||
--background --make-pidfile --chuid $EXEC_AS_USER --startas $DAEMON -- \
|
||||
$DAEMON_ARGS \
|
||||
|| return 2
|
||||
}
|
||||
|
||||
do_stop()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE > /dev/null # --name $DAEMON
|
||||
RETVAL="$?"
|
||||
[ "$RETVAL" = 2 ] && return 2
|
||||
# Wait for children to finish too if this is a daemon that forks
|
||||
# and if the daemon is only ever run from this initscript.
|
||||
# If the above conditions are not satisfied then add some other code
|
||||
# that waits for the process to drop all resources that could be
|
||||
# needed by services started subsequently. A last resort is to
|
||||
# sleep for some time.
|
||||
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
|
||||
[ "$?" = 2 ] && return 2
|
||||
# Many daemons don't delete their pidfiles when they exit.
|
||||
rm -f $PIDFILE
|
||||
return "$RETVAL"
|
||||
}
|
||||
case "$1" in
|
||||
start)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
restart|force-reload)
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
@@ -21,7 +21,7 @@
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<authentication>password-cleartext</authentication>
|
||||
<addThisServer>true</addThisServer>
|
||||
<useGlobalPreferredServer>true</useGlobalPreferredServer>
|
||||
<useGlobalPreferredServer>false</useGlobalPreferredServer>
|
||||
</outgoingServer>
|
||||
|
||||
<documentation url="https://PRIMARY_HOSTNAME/">
|
||||
|
||||
10
conf/munin.service
Normal file
10
conf/munin.service
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Munin System Monitoring Startup Script
|
||||
After=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=idle
|
||||
ExecStart=/usr/local/lib/mailinabox/munin_start.sh
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -18,6 +18,9 @@
|
||||
location = /.well-known/autoconfig/mail/config-v1.1.xml {
|
||||
alias /var/lib/mailinabox/mozilla-autoconfig.xml;
|
||||
}
|
||||
location = /mail/config-v1.1.xml {
|
||||
alias /var/lib/mailinabox/mozilla-autoconfig.xml;
|
||||
}
|
||||
|
||||
# Roundcube Webmail configuration.
|
||||
rewrite ^/mail$ /mail/ redirect;
|
||||
@@ -70,7 +73,7 @@
|
||||
# takes precedence over all non-regex matches and only regex matches that
|
||||
# come after it (i.e. none of those, since this is the last one.) That means
|
||||
# we're blocking dotfiles in the static hosted sites but not the FastCGI-
|
||||
# handled locations for ownCloud (which serves user-uploaded files that might
|
||||
# handled locations for Nextcloud (which serves user-uploaded files that might
|
||||
# have this pattern, see #414) or some of the other services.
|
||||
location ~ /\.(ht|svn|git|hg|bzr) {
|
||||
log_not_found off;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Control Panel
|
||||
# Proxy /admin to our Python based control panel daemon. It is
|
||||
# listening on IPv4 only so use an IP address and not 'localhost'.
|
||||
location /admin/assets {
|
||||
alias /usr/local/lib/mailinabox/vendor/assets;
|
||||
}
|
||||
rewrite ^/admin$ /admin/;
|
||||
rewrite ^/admin/munin$ /admin/munin/ redirect;
|
||||
location /admin/ {
|
||||
@@ -9,14 +12,14 @@
|
||||
add_header X-Frame-Options "DENY";
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
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/index.php;
|
||||
rewrite ^/cloud/(contacts|calendar|files)$ /cloud/index.php/apps/$1/ redirect;
|
||||
rewrite ^(/cloud/core/doc/[^\/]+/)$ $1/index.html;
|
||||
rewrite ^(/cloud/oc[sm]-provider)/$ $1/index.php redirect;
|
||||
location /cloud/ {
|
||||
alias /usr/local/lib/owncloud/;
|
||||
location ~ ^/cloud/(build|tests|config|lib|3rdparty|templates|data|README)/ {
|
||||
@@ -25,6 +28,14 @@
|
||||
location ~ ^/cloud/(?:\.|autotest|occ|issue|indie|db_|console) {
|
||||
deny all;
|
||||
}
|
||||
# Enable paths for service and cloud federation discovery
|
||||
# Resolves warning in Nextcloud Settings panel
|
||||
location ~ ^/cloud/(oc[sm]-provider)?/([^/]+\.php)$ {
|
||||
index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME /usr/local/lib/owncloud/$1/$2;
|
||||
fastcgi_pass php-fpm;
|
||||
}
|
||||
}
|
||||
location ~ ^(/cloud)((?:/ocs)?/[^/]+\.php)(/.*)?$ {
|
||||
# note: ~ has precendence over a regular location block
|
||||
@@ -41,13 +52,11 @@
|
||||
fastcgi_param MOD_X_ACCEL_REDIRECT_PREFIX /owncloud-xaccel;
|
||||
fastcgi_read_timeout 630;
|
||||
fastcgi_pass php-fpm;
|
||||
error_page 403 /cloud/core/templates/403.php;
|
||||
error_page 404 /cloud/core/templates/404.php;
|
||||
client_max_body_size 1G;
|
||||
fastcgi_buffers 64 4K;
|
||||
}
|
||||
location ^~ /owncloud-xaccel/ {
|
||||
# This directory is for MOD_X_ACCEL_REDIRECT_ENABLED. ownCloud sends the full file
|
||||
# This directory is for MOD_X_ACCEL_REDIRECT_ENABLED. Nextcloud sends the full file
|
||||
# path on disk as a subdirectory under this virtual path.
|
||||
# We must only allow 'internal' redirects within nginx so that the filesystem
|
||||
# is not exposed to the world.
|
||||
|
||||
@@ -1,76 +1,20 @@
|
||||
# from: https://gist.github.com/konklone/6532544
|
||||
###################################################################################
|
||||
# We track the Mozilla "intermediate" compatibility TLS recommendations.
|
||||
# Note that these settings are repeated in the SMTP and IMAP configuration.
|
||||
# ssl_protocols has moved to nginx.conf in bionic, check there for enabled protocols.
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_dhparam STORAGE_ROOT/ssl/dh2048.pem;
|
||||
|
||||
# Basically the nginx configuration I use at konklone.com.
|
||||
# I check it using https://www.ssllabs.com/ssltest/analyze.html?d=konklone.com
|
||||
#
|
||||
# To provide feedback, please tweet at @konklone or email eric@konklone.com.
|
||||
# Comments on gists don't notify the author.
|
||||
#
|
||||
# Thanks to WubTheCaptain (https://wubthecaptain.eu) for his help and ciphersuites.
|
||||
# Thanks to Ilya Grigorik (https://www.igvita.com) for constant inspiration.
|
||||
|
||||
# Path to certificate and private key.
|
||||
# The .crt may omit the root CA cert, if it's a standard CA that ships with clients.
|
||||
#ssl_certificate /path/to/unified.crt;
|
||||
#ssl_certificate_key /path/to/my-private-decrypted.key;
|
||||
|
||||
# Tell browsers to require SSL (warning: difficult to change your mind)
|
||||
# Handled by the management daemon because we can toggle this version or a
|
||||
# preload version.
|
||||
#add_header Strict-Transport-Security max-age=31536000;
|
||||
|
||||
# Prefer certain ciphersuites, to enforce Forward Secrecy and avoid known vulnerabilities.
|
||||
#
|
||||
# Forces forward secrecy in all browsers and clients that can use TLS,
|
||||
# but with a small exception (DES-CBC3-SHA) for IE8/XP users.
|
||||
#
|
||||
# Reference client: https://www.ssllabs.com/ssltest/analyze.html
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !MD5 !EXP !DSS !PSK !SRP !kECDH !CAMELLIA !RC4 !SEED';
|
||||
|
||||
# Cut out (the old, broken) SSLv3 entirely.
|
||||
# This **excludes IE6 users** and (apparently) Yandexbot.
|
||||
# Just comment out if you need to support IE6, bless your soul.
|
||||
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
|
||||
|
||||
# Turn on session resumption, using a 10 min cache shared across nginx processes,
|
||||
# as recommended by http://nginx.org/en/docs/http/configuring_https_servers.html
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
#keepalive_timeout 70; # in Ubuntu 14.04/nginx 1.4.6 the default is 65, so plenty good
|
||||
ssl_session_cache shared:SSL:50m;
|
||||
ssl_session_timeout 1d;
|
||||
|
||||
# Buffer size of 1400 bytes fits in one MTU.
|
||||
# nginx 1.5.9+ ONLY
|
||||
#ssl_buffer_size 1400;
|
||||
ssl_buffer_size 1400;
|
||||
|
||||
# SPDY header compression (0 for none, 9 for slow/heavy compression). Preferred is 6.
|
||||
#
|
||||
# BUT: header compression is flawed and vulnerable in SPDY versions 1 - 3.
|
||||
# Disable with 0, until using a version of nginx with SPDY 4.
|
||||
spdy_headers_comp 0;
|
||||
|
||||
# Now let's really get fancy, and pre-generate a 2048 bit random parameter
|
||||
# for DH elliptic curves. If not created and specified, default is only 1024 bits.
|
||||
#
|
||||
# Generated by OpenSSL with the following command:
|
||||
# openssl dhparam -outform pem -out dhparam2048.pem 2048
|
||||
#
|
||||
# Note: raising the bits to 2048 excludes Java 6 clients. Comment out if a problem.
|
||||
ssl_dhparam STORAGE_ROOT/ssl/dh2048.pem;
|
||||
|
||||
|
||||
# OCSP stapling - means nginx will poll the CA for signed OCSP responses,
|
||||
# and send them to clients so clients don't make their own OCSP calls.
|
||||
# http://en.wikipedia.org/wiki/OCSP_stapling
|
||||
#
|
||||
# while the ssl_certificate above may omit the root cert if the CA is trusted,
|
||||
# ssl_trusted_certificate below must point to a chain of **all** certs
|
||||
# in the trust path - (your cert, intermediary certs, root cert)
|
||||
#
|
||||
# 8.8.8.8 and 8.8.4.4 below are Google's public IPv4 DNS servers.
|
||||
# nginx will use them to talk to the CA.
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
resolver 127.0.0.1 valid=86400;
|
||||
resolver_timeout 10;
|
||||
|
||||
# h/t https://gist.github.com/konklone/6532544
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
## your own --- please do not ask for help from us.
|
||||
|
||||
upstream php-fpm {
|
||||
server unix:/var/run/php5-fpm.sock;
|
||||
server unix:/var/run/php/php7.2-fpm.sock;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,14 +25,14 @@ server {
|
||||
# This path must be served over HTTP for ACME domain validation.
|
||||
# We map this to a special path where our TLS cert provisioning
|
||||
# tool knows to store challenge response files.
|
||||
alias $STORAGE_ROOT/ssl/lets_encrypt/acme_challenges/;
|
||||
alias $STORAGE_ROOT/ssl/lets_encrypt/webroot/.well-known/acme-challenge/;
|
||||
}
|
||||
}
|
||||
|
||||
# The secure HTTPS server.
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
|
||||
server_name $HOSTNAME;
|
||||
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
* Descr : Autodiscover configuration file
|
||||
************************************************/
|
||||
|
||||
define('TIMEZONE', '');
|
||||
|
||||
// Defines the base path on the server
|
||||
define('BASE_PATH', dirname($_SERVER['SCRIPT_FILENAME']). '/');
|
||||
|
||||
// The Z-Push server location for the autodiscover response
|
||||
define('SERVERURL', 'https://PRIMARY_HOSTNAME/Microsoft-Server-ActiveSync');
|
||||
define('ZPUSH_HOST', 'PRIMARY_HOSTNAME');
|
||||
|
||||
define('USE_FULLEMAIL_FOR_LOGIN', true);
|
||||
|
||||
@@ -18,6 +19,7 @@ define('LOGFILE', LOGFILEDIR . 'autodiscover.log');
|
||||
define('LOGERRORFILE', LOGFILEDIR . 'autodiscover-error.log');
|
||||
define('LOGLEVEL', LOGLEVEL_INFO);
|
||||
define('LOGUSERLEVEL', LOGLEVEL);
|
||||
$specialLogUsers = array();
|
||||
|
||||
// the backend data provider
|
||||
define('BACKEND_PROVIDER', 'BackendCombined');
|
||||
|
||||
@@ -17,7 +17,7 @@ define('CARDDAV_CONTACTS_FOLDER_NAME', '%u Addressbook');
|
||||
define('CARDDAV_SUPPORTS_SYNC', false);
|
||||
|
||||
// If the CardDAV server supports the FN attribute for searches
|
||||
// DAViCal supports it, but SabreDav, Owncloud and SOGo don't
|
||||
// DAViCal supports it, but SabreDav, Nextcloud and SOGo don't
|
||||
// Setting this to true will search by FN. If false will search by sn, givenName and email
|
||||
// It's safe to leave it as false
|
||||
define('CARDDAV_SUPPORTS_FN_SEARCH', false);
|
||||
|
||||
@@ -23,6 +23,9 @@ define('IMAP_FOLDER_TRASH', 'TRASH');
|
||||
define('IMAP_FOLDER_SPAM', 'SPAM');
|
||||
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_USER', '');
|
||||
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');
|
||||
|
||||
define('MAIL_MIMEPART_CRLF', "\r\n");
|
||||
define('IMAP_MEETING_USE_CALDAV', true);
|
||||
|
||||
?>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python3
|
||||
#!/usr/local/lib/mailinabox/env/bin/python
|
||||
|
||||
# This script performs a backup of all user data:
|
||||
# 1) System services are stopped.
|
||||
@@ -15,25 +15,22 @@ from exclusiveprocess import Lock
|
||||
from utils import load_environment, shell, wait_for_service, fix_boto
|
||||
|
||||
rsync_ssh_options = [
|
||||
"--ssh-options='-i /root/.ssh/id_rsa_miab'",
|
||||
"--rsync-options=-e \"/usr/bin/ssh -oStrictHostKeyChecking=no -oBatchMode=yes -p 22 -i /root/.ssh/id_rsa_miab\"",
|
||||
"--ssh-options= -i /root/.ssh/id_rsa_miab",
|
||||
"--rsync-options= -e \"/usr/bin/ssh -oStrictHostKeyChecking=no -oBatchMode=yes -p 22 -i /root/.ssh/id_rsa_miab\"",
|
||||
]
|
||||
|
||||
def backup_status(env):
|
||||
# Root folder
|
||||
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
|
||||
|
||||
# What is the current status of backups?
|
||||
# Query duplicity to get a list of all backups.
|
||||
# Use the number of volumes to estimate the size.
|
||||
# If backups are dissbled, return no status.
|
||||
config = get_backup_config(env)
|
||||
now = datetime.datetime.now(dateutil.tz.tzlocal())
|
||||
|
||||
# Are backups dissbled?
|
||||
if config["target"] == "off":
|
||||
return { }
|
||||
|
||||
# Query duplicity to get a list of all full and incremental
|
||||
# backups available.
|
||||
|
||||
backups = { }
|
||||
now = datetime.datetime.now(dateutil.tz.tzlocal())
|
||||
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
|
||||
backup_cache_dir = os.path.join(backup_root, 'cache')
|
||||
|
||||
def reldate(date, ref, clip):
|
||||
@@ -54,11 +51,11 @@ def backup_status(env):
|
||||
date = dateutil.parser.parse(keys[1]).astimezone(dateutil.tz.tzlocal())
|
||||
return {
|
||||
"date": keys[1],
|
||||
"date_str": date.strftime("%x %X") + " " + now.tzname(),
|
||||
"date_str": date.strftime("%Y-%m-%d %X") + " " + now.tzname(),
|
||||
"date_delta": reldate(date, now, "the future?"),
|
||||
"full": keys[0] == "full",
|
||||
"size": 0, # collection-status doesn't give us the size
|
||||
"volumes": keys[2], # number of archive volumes for this backup (not really helpful)
|
||||
"volumes": int(keys[2]), # number of archive volumes for this backup (not really helpful)
|
||||
}
|
||||
|
||||
code, collection_status = shell('check_output', [
|
||||
@@ -80,12 +77,20 @@ def backup_status(env):
|
||||
backup = parse_line(line)
|
||||
backups[backup["date"]] = backup
|
||||
|
||||
# Look at the target to get the sizes of each of the backups. There is more than one file per backup.
|
||||
# Look at the target directly to get the sizes of each of the backups. There is more than one file per backup.
|
||||
# Starting with duplicity in Ubuntu 18.04, "signatures" files have dates in their
|
||||
# filenames that are a few seconds off the backup date and so don't line up
|
||||
# with the list of backups we have. Track unmatched files so we know how much other
|
||||
# space is used for those.
|
||||
unmatched_file_size = 0
|
||||
for fn, size in list_target_files(config):
|
||||
m = re.match(r"duplicity-(full|full-signatures|(inc|new-signatures)\.(?P<incbase>\d+T\d+Z)\.to)\.(?P<date>\d+T\d+Z)\.", fn)
|
||||
if not m: continue # not a part of a current backup chain
|
||||
key = m.group("date")
|
||||
if key in backups:
|
||||
backups[key]["size"] += size
|
||||
else:
|
||||
unmatched_file_size += size
|
||||
|
||||
# Ensure the rows are sorted reverse chronologically.
|
||||
# This is relied on by should_force_full() and the next step.
|
||||
@@ -115,7 +120,7 @@ def backup_status(env):
|
||||
# full backup. That full backup frees up this one to be deleted. But, the backup
|
||||
# must also be at least min_age_in_days old too.
|
||||
deleted_in = None
|
||||
if incremental_count > 0 and first_full_size is not None:
|
||||
if incremental_count > 0 and incremental_size > 0 and first_full_size is not None:
|
||||
# How many days until the next incremental backup? First, the part of
|
||||
# the algorithm based on increment sizes:
|
||||
est_days_to_next_full = (.5 * first_full_size - incremental_size) / (incremental_size/incremental_count)
|
||||
@@ -148,6 +153,7 @@ def backup_status(env):
|
||||
|
||||
return {
|
||||
"backups": backups,
|
||||
"unmatched_file_size": unmatched_file_size,
|
||||
}
|
||||
|
||||
def should_force_full(config, env):
|
||||
@@ -220,32 +226,6 @@ def perform_backup(full_backup):
|
||||
if config["target"] == "off":
|
||||
return
|
||||
|
||||
# In an older version of this script, duplicity was called
|
||||
# such that it did not encrypt the backups it created (in
|
||||
# backup/duplicity), and instead openssl was called separately
|
||||
# after each backup run, creating AES256 encrypted copies of
|
||||
# each file created by duplicity in backup/encrypted.
|
||||
#
|
||||
# We detect the transition by the presence of backup/duplicity
|
||||
# and handle it by 'dupliception': we move all the old *un*encrypted
|
||||
# duplicity files up out of the backup/duplicity directory (as
|
||||
# backup/ is excluded from duplicity runs) in order that it is
|
||||
# included in the next run, and we delete backup/encrypted (which
|
||||
# duplicity will output files directly to, post-transition).
|
||||
old_backup_dir = os.path.join(backup_root, 'duplicity')
|
||||
migrated_unencrypted_backup_dir = os.path.join(env["STORAGE_ROOT"], "migrated_unencrypted_backup")
|
||||
if os.path.isdir(old_backup_dir):
|
||||
# Move the old unencrypted files to a new location outside of
|
||||
# the backup root so they get included in the next (new) backup.
|
||||
# Then we'll delete them. Also so that they do not get in the
|
||||
# way of duplicity doing a full backup on the first run after
|
||||
# we take care of this.
|
||||
shutil.move(old_backup_dir, migrated_unencrypted_backup_dir)
|
||||
|
||||
# The backup_dir (backup/encrypted) now has a new purpose.
|
||||
# Clear it out.
|
||||
shutil.rmtree(backup_dir)
|
||||
|
||||
# On the first run, always do a full backup. Incremental
|
||||
# will fail. Otherwise do a full backup when the size of
|
||||
# the increments since the most recent full backup are
|
||||
@@ -267,7 +247,7 @@ def perform_backup(full_backup):
|
||||
if quit:
|
||||
sys.exit(code)
|
||||
|
||||
service_command("php5-fpm", "stop", quit=True)
|
||||
service_command("php7.2-fpm", "stop", quit=True)
|
||||
service_command("postfix", "stop", quit=True)
|
||||
service_command("dovecot", "stop", quit=True)
|
||||
|
||||
@@ -301,11 +281,7 @@ def perform_backup(full_backup):
|
||||
# Start services again.
|
||||
service_command("dovecot", "start", quit=False)
|
||||
service_command("postfix", "start", quit=False)
|
||||
service_command("php5-fpm", "start", quit=False)
|
||||
|
||||
# Once the migrated backup is included in a new backup, it can be deleted.
|
||||
if os.path.isdir(migrated_unencrypted_backup_dir):
|
||||
shutil.rmtree(migrated_unencrypted_backup_dir)
|
||||
service_command("php7.2-fpm", "start", quit=False)
|
||||
|
||||
# Remove old backups. This deletes all backup data no longer needed
|
||||
# from more than 3 days ago.
|
||||
@@ -399,10 +375,11 @@ def list_target_files(config):
|
||||
rsync_fn_size_re = re.compile(r'.* ([^ ]*) [^ ]* [^ ]* (.*)')
|
||||
rsync_target = '{host}:{path}'
|
||||
|
||||
if not target.path.endswith('/'):
|
||||
target_path = target.path + '/'
|
||||
if target.path.startswith('/'):
|
||||
target_path = target.path[1:]
|
||||
target_path = target.path
|
||||
if not target_path.endswith('/'):
|
||||
target_path = target_path + '/'
|
||||
if target_path.startswith('/'):
|
||||
target_path = target_path[1:]
|
||||
|
||||
rsync_command = [ 'rsync',
|
||||
'-e',
|
||||
@@ -429,11 +406,11 @@ def list_target_files(config):
|
||||
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':
|
||||
elif 'Could not resolve hostname' in listing:
|
||||
reason = "The hostname {} cannot be resolved.".format(target.hostname)
|
||||
else:
|
||||
reason = "Unknown error." \
|
||||
"Please check running 'python management/backup.py --verify'" \
|
||||
"Please check running 'management/backup.py --verify'" \
|
||||
"from mailinabox sources to debug the issue."
|
||||
raise ValueError("Connection to rsync host failed: {}".format(reason))
|
||||
|
||||
@@ -442,15 +419,22 @@ def list_target_files(config):
|
||||
fix_boto() # must call prior to importing boto
|
||||
import boto.s3
|
||||
from boto.exception import BotoServerError
|
||||
custom_region = False
|
||||
for region in boto.s3.regions():
|
||||
if region.endpoint == target.hostname:
|
||||
break
|
||||
else:
|
||||
raise ValueError("Invalid S3 region/host.")
|
||||
# If region is not found this is a custom region
|
||||
custom_region = True
|
||||
|
||||
bucket = target.path[1:].split('/')[0]
|
||||
path = '/'.join(target.path[1:].split('/')[1:]) + '/'
|
||||
|
||||
# Create a custom region with custom endpoint
|
||||
if custom_region:
|
||||
from boto.s3.connection import S3Connection
|
||||
region = boto.s3.S3RegionInfo(name=bucket, endpoint=target.hostname, connection_cls=S3Connection)
|
||||
|
||||
# If no prefix is specified, set the path to '', otherwise boto won't list the files
|
||||
if path == '/':
|
||||
path = ''
|
||||
@@ -555,8 +539,7 @@ if __name__ == "__main__":
|
||||
run_duplicity_verification()
|
||||
|
||||
elif sys.argv[-1] == "--list":
|
||||
# Run duplicity's verification command to check a) the backup files
|
||||
# are readable, and b) report if they are up to date.
|
||||
# List the saved backup files.
|
||||
for fn, size in list_target_files(get_backup_config(load_environment())):
|
||||
print("{}\t{}".format(fn, size))
|
||||
|
||||
@@ -564,6 +547,7 @@ if __name__ == "__main__":
|
||||
# Show backup status.
|
||||
ret = backup_status(load_environment())
|
||||
print(rtyaml.dump(ret["backups"]))
|
||||
print("Storage for unmatched files:", ret["unmatched_file_size"])
|
||||
|
||||
elif len(sys.argv) >= 2 and sys.argv[1] == "--restore":
|
||||
# Run duplicity restore. Rest of command line passed as arguments
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import os, os.path, re, json, time
|
||||
import subprocess
|
||||
|
||||
@@ -148,7 +146,7 @@ def me():
|
||||
@authorized_personnel_only
|
||||
def mail_users():
|
||||
if request.args.get("format", "") == "json":
|
||||
return json_response(get_mail_users_ex(env, with_archived=True, with_slow_info=True))
|
||||
return json_response(get_mail_users_ex(env, with_archived=True))
|
||||
else:
|
||||
return "".join(x+"\n" for x in get_mail_users(env))
|
||||
|
||||
@@ -335,11 +333,16 @@ def ssl_get_status():
|
||||
from web_update import get_web_domains_info, get_web_domains
|
||||
|
||||
# What domains can we provision certificates for? What unexpected problems do we have?
|
||||
provision, cant_provision = get_certificates_to_provision(env, show_extended_problems=False)
|
||||
provision, cant_provision = get_certificates_to_provision(env, show_valid_certs=False)
|
||||
|
||||
# What's the current status of TLS certificates on all of the domain?
|
||||
domains_status = get_web_domains_info(env)
|
||||
domains_status = [{ "domain": d["domain"], "status": d["ssl_certificate"][0], "text": d["ssl_certificate"][1] } for d in domains_status ]
|
||||
domains_status = [
|
||||
{
|
||||
"domain": d["domain"],
|
||||
"status": d["ssl_certificate"][0],
|
||||
"text": d["ssl_certificate"][1] + ((" " + cant_provision[d["domain"]] if d["domain"] in cant_provision else ""))
|
||||
} for d in domains_status ]
|
||||
|
||||
# Warn the user about domain names not hosted here because of other settings.
|
||||
for domain in set(get_web_domains(env, exclude_dns_elsewhere=False)) - set(get_web_domains(env)):
|
||||
@@ -351,7 +354,6 @@ def ssl_get_status():
|
||||
|
||||
return json_response({
|
||||
"can_provision": utils.sort_domains(provision, env),
|
||||
"cant_provision": [{ "domain": domain, "problem": cant_provision[domain] } for domain in utils.sort_domains(cant_provision, env) ],
|
||||
"status": domains_status,
|
||||
})
|
||||
|
||||
@@ -378,11 +380,8 @@ def ssl_install_cert():
|
||||
@authorized_personnel_only
|
||||
def ssl_provision_certs():
|
||||
from ssl_certificates import provision_certificates
|
||||
agree_to_tos_url = request.form.get('agree_to_tos_url')
|
||||
status = provision_certificates(env,
|
||||
agree_to_tos_url=agree_to_tos_url,
|
||||
jsonable=True)
|
||||
return json_response(status)
|
||||
requests = provision_certificates(env, limit_domains=None)
|
||||
return json_response({ "requests": requests })
|
||||
|
||||
|
||||
# WEB
|
||||
@@ -573,7 +572,7 @@ def munin_cgi(filename):
|
||||
|
||||
if code != 0:
|
||||
# nonzero returncode indicates error
|
||||
app.logger.error("munin_cgi: munin-cgi-graph returned nonzero exit code, %s", process.returncode)
|
||||
app.logger.error("munin_cgi: munin-cgi-graph returned nonzero exit code, %s", code)
|
||||
return ("error processing graph image", 500)
|
||||
|
||||
# /usr/lib/munin/cgi/munin-cgi-graph returns both headers and binary png when successful.
|
||||
|
||||
@@ -9,11 +9,17 @@ export LC_ALL=en_US.UTF-8
|
||||
export LANG=en_US.UTF-8
|
||||
export LC_TYPE=en_US.UTF-8
|
||||
|
||||
# On Mondays, i.e. once a week, send the administrator a report of total emails
|
||||
# sent and received so the admin might notice server abuse.
|
||||
if [ `date "+%u"` -eq 1 ]; then
|
||||
management/mail_log.py -t week | management/email_administrator.py "Mail-in-a-Box Usage Report"
|
||||
fi
|
||||
|
||||
# Take a backup.
|
||||
management/backup.py | management/email_administrator.py "Backup Status"
|
||||
|
||||
# Provision any new certificates for new domains or domains with expiring certificates.
|
||||
management/ssl_certificates.py -q --headless | management/email_administrator.py "Error Provisioning TLS Certificate"
|
||||
management/ssl_certificates.py -q | management/email_administrator.py "TLS Certificate Provisioning Result"
|
||||
|
||||
# Run status checks and email the administrator if anything changed.
|
||||
management/status_checks.py --show-changes | management/email_administrator.py "Status Checks Change Notice"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python3
|
||||
#!/usr/local/lib/mailinabox/env/bin/python
|
||||
|
||||
# Creates DNS zone files for all of the domains of all of the mail users
|
||||
# and mail aliases and restarts nsd.
|
||||
@@ -12,6 +12,12 @@ import dns.resolver
|
||||
from mailconfig import get_mail_domains
|
||||
from utils import shell, load_env_vars_from_file, safe_domain_name, sort_domains
|
||||
|
||||
# From https://stackoverflow.com/questions/3026957/how-to-validate-a-domain-name-using-regex-php/16491074#16491074
|
||||
# This regular expression matches domain names according to RFCs, it also accepts fqdn with an leading dot,
|
||||
# underscores, as well as asteriks which are allowed in domain names but not hostnames (i.e. allowed in
|
||||
# DNS but not in URLs), which are common in certain record types like for DKIM.
|
||||
DOMAIN_RE = "^(?!\-)(?:[*][.])?(?:[a-zA-Z\d\-_]{0,62}[a-zA-Z\d_]\.){1,126}(?!\d+)[a-zA-Z\d_]{1,63}(\.?)$"
|
||||
|
||||
def get_dns_domains(env):
|
||||
# Add all domain names in use by email users and mail aliases and ensure
|
||||
# PRIMARY_HOSTNAME is in the list.
|
||||
@@ -282,6 +288,21 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
|
||||
if not has_rec(qname, "SRV"):
|
||||
records.append((qname, "SRV", "0 0 443 " + env["PRIMARY_HOSTNAME"] + ".", "Recommended. Specifies the hostname of the server that handles CardDAV/CalDAV services for email addresses on this domain."))
|
||||
|
||||
# Adds autoconfiguration A records for all domains.
|
||||
# This allows the following clients to automatically configure email addresses in the respective applications.
|
||||
# autodiscover.* - Z-Push ActiveSync Autodiscover
|
||||
# autoconfig.* - Thunderbird Autoconfig
|
||||
autodiscover_records = [
|
||||
("autodiscover", "A", env["PUBLIC_IP"], "Provides email configuration autodiscovery support for Z-Push ActiveSync Autodiscover."),
|
||||
("autodiscover", "AAAA", env["PUBLIC_IPV6"], "Provides email configuration autodiscovery support for Z-Push ActiveSync Autodiscover."),
|
||||
("autoconfig", "A", env["PUBLIC_IP"], "Provides email configuration autodiscovery support for Thunderbird Autoconfig."),
|
||||
("autoconfig", "AAAA", env["PUBLIC_IPV6"], "Provides email configuration autodiscovery support for Thunderbird Autoconfig.")
|
||||
]
|
||||
for qname, rtype, value, explanation in autodiscover_records:
|
||||
if value is None or value.strip() == "": continue # skip IPV6 if not set
|
||||
if not has_rec(qname, rtype):
|
||||
records.append((qname, rtype, value, explanation))
|
||||
|
||||
# Sort the records. The None records *must* go first in the nsd zone file. Otherwise it doesn't matter.
|
||||
records.sort(key = lambda rec : list(reversed(rec[0].split(".")) if rec[0] is not None else ""))
|
||||
|
||||
@@ -348,19 +369,20 @@ def build_sshfp_records():
|
||||
# Get our local fingerprints by running ssh-keyscan. The output looks
|
||||
# like the known_hosts file: hostname, keytype, fingerprint. The order
|
||||
# of the output is arbitrary, so sort it to prevent spurrious updates
|
||||
# to the zone file (that trigger bumping the serial number).
|
||||
|
||||
# scan the sshd_config and find the ssh ports (port 22 may be closed)
|
||||
# to the zone file (that trigger bumping the serial number). However,
|
||||
# if SSH has been configured to listen on a nonstandard port, we must
|
||||
# specify that port to sshkeyscan.
|
||||
port = 22
|
||||
with open('/etc/ssh/sshd_config', 'r') as f:
|
||||
ports = []
|
||||
t = f.readlines()
|
||||
for line in t:
|
||||
s = line.split()
|
||||
for line in f:
|
||||
s = line.rstrip().split()
|
||||
if len(s) == 2 and s[0] == 'Port':
|
||||
ports = ports + [s[1]]
|
||||
# the keys are the same at each port, so we only need to get
|
||||
# them at the first port found (may not be port 22)
|
||||
keys = shell("check_output", ["ssh-keyscan", "-t", "rsa,dsa,ecdsa,ed25519", "-p", ports[0], "localhost"])
|
||||
try:
|
||||
port = int(s[1])
|
||||
except ValueError:
|
||||
pass
|
||||
break
|
||||
keys = shell("check_output", ["ssh-keyscan", "-t", "rsa,dsa,ecdsa,ed25519", "-p", str(port), "localhost"])
|
||||
for key in sorted(keys.split("\n")):
|
||||
if key.strip() == "" or key[0] == "#": continue
|
||||
try:
|
||||
@@ -501,9 +523,11 @@ zone:
|
||||
""" % (domain, zonefile)
|
||||
|
||||
# If custom secondary nameservers have been set, allow zone transfers
|
||||
# and notifies to them.
|
||||
# and, if not a subnet, notifies to them.
|
||||
for ipaddr in get_secondary_dns(additional_records, mode="xfr"):
|
||||
nsdconf += "\n\tnotify: %s NOKEY\n\tprovide-xfr: %s NOKEY\n" % (ipaddr, ipaddr)
|
||||
if "/" not in ipaddr:
|
||||
nsdconf += "\n\tnotify: %s NOKEY" % (ipaddr)
|
||||
nsdconf += "\n\tprovide-xfr: %s NOKEY\n" % (ipaddr)
|
||||
|
||||
# Check if the file is changing. If it isn't changing,
|
||||
# return False to flag that no change was made.
|
||||
@@ -522,12 +546,13 @@ zone:
|
||||
|
||||
def dnssec_choose_algo(domain, env):
|
||||
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
|
||||
# for .email and .guide.
|
||||
# A variety of algorithms are supported for .fund. This
|
||||
# is preferred.
|
||||
# Gandi tells me that .be does not support RSASHA1-NSEC3-SHA1
|
||||
# Nic.lv does not support RSASHA1-NSEC3-SHA1 for .lv tld's
|
||||
return "RSASHA256"
|
||||
|
||||
# For any domain we were able to sign before, don't change the algorithm
|
||||
@@ -762,12 +787,25 @@ def set_custom_dns_record(qname, rtype, value, action, env):
|
||||
# validate rtype
|
||||
rtype = rtype.upper()
|
||||
if value is not None and qname != "_secondary_nameserver":
|
||||
if not re.search(DOMAIN_RE, qname):
|
||||
raise ValueError("Invalid name.")
|
||||
|
||||
if rtype in ("A", "AAAA"):
|
||||
if value != "local": # "local" is a special flag for us
|
||||
v = ipaddress.ip_address(value) # raises a ValueError if there's a problem
|
||||
if rtype == "A" and not isinstance(v, ipaddress.IPv4Address): raise ValueError("That's an IPv6 address.")
|
||||
if rtype == "AAAA" and not isinstance(v, ipaddress.IPv6Address): raise ValueError("That's an IPv4 address.")
|
||||
elif rtype in ("CNAME", "TXT", "SRV", "MX", "SSHFP"):
|
||||
elif rtype in ("CNAME", "NS"):
|
||||
if rtype == "NS" and qname == zone:
|
||||
raise ValueError("NS records can only be set for subdomains.")
|
||||
|
||||
# ensure value has a trailing dot
|
||||
if not value.endswith("."):
|
||||
value = value + "."
|
||||
|
||||
if not re.search(DOMAIN_RE, value):
|
||||
raise ValueError("Invalid value.")
|
||||
elif rtype in ("CNAME", "TXT", "SRV", "MX", "SSHFP", "CAA"):
|
||||
# anything goes
|
||||
pass
|
||||
else:
|
||||
@@ -837,10 +875,15 @@ def get_secondary_dns(custom_dns, mode=None):
|
||||
|
||||
# This is a hostname. Before including in zone xfr lines,
|
||||
# resolve to an IP address. Otherwise just return the hostname.
|
||||
# It may not resolve to IPv6, so don't throw an exception if it
|
||||
# doesn't.
|
||||
if not hostname.startswith("xfr:"):
|
||||
if mode == "xfr":
|
||||
response = dns.resolver.query(hostname+'.', "A")
|
||||
hostname = str(response[0])
|
||||
response = dns.resolver.query(hostname+'.', "A", raise_on_no_answer=False)
|
||||
values.extend(map(str, response))
|
||||
response = dns.resolver.query(hostname+'.', "AAAA", raise_on_no_answer=False)
|
||||
values.extend(map(str, response))
|
||||
continue
|
||||
values.append(hostname)
|
||||
|
||||
# This is a zone-xfer-only IP address. Do not return if
|
||||
@@ -867,10 +910,14 @@ def set_secondary_dns(hostnames, env):
|
||||
else:
|
||||
# Validate IP address.
|
||||
try:
|
||||
if "/" in item[4:]:
|
||||
v = ipaddress.ip_network(item[4:]) # raises a ValueError if there's a problem
|
||||
if not isinstance(v, ipaddress.IPv4Network): raise ValueError("That's an IPv6 subnet.")
|
||||
else:
|
||||
v = ipaddress.ip_address(item[4:]) # raises a ValueError if there's a problem
|
||||
if not isinstance(v, ipaddress.IPv4Address): raise ValueError("That's an IPv6 address.")
|
||||
except ValueError:
|
||||
raise ValueError("'%s' is not an IPv4 address." % item[4:])
|
||||
raise ValueError("'%s' is not an IPv4 address or subnet." % item[4:])
|
||||
|
||||
# Set.
|
||||
set_custom_dns_record("_secondary_nameserver", "A", " ".join(hostnames), "set", env)
|
||||
|
||||
@@ -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.
|
||||
|
||||
import sys
|
||||
|
||||
import html
|
||||
import smtplib
|
||||
from email.message import Message
|
||||
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
# In Python 3.6:
|
||||
#from email.message import Message
|
||||
|
||||
from utils import load_environment
|
||||
|
||||
@@ -26,11 +32,23 @@ if content == "":
|
||||
sys.exit(0)
|
||||
|
||||
# create MIME message
|
||||
msg = Message()
|
||||
msg = MIMEMultipart('alternative')
|
||||
|
||||
# In Python 3.6:
|
||||
#msg = Message()
|
||||
|
||||
msg['From'] = "\"%s\" <%s>" % (env['PRIMARY_HOSTNAME'], admin_addr)
|
||||
msg['To'] = admin_addr
|
||||
msg['Subject'] = "[%s] %s" % (env['PRIMARY_HOSTNAME'], subject)
|
||||
msg.set_payload(content, "UTF-8")
|
||||
|
||||
content_html = "<html><body><pre>{}</pre></body></html>".format(html.escape(content))
|
||||
|
||||
msg.attach(MIMEText(content, 'plain'))
|
||||
msg.attach(MIMEText(content_html, 'html'))
|
||||
|
||||
# In Python 3.6:
|
||||
#msg.set_content(content)
|
||||
#msg.add_alternative(content_html, "html")
|
||||
|
||||
# send
|
||||
smtpclient = smtplib.SMTP('127.0.0.1', 25)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python3
|
||||
#!/usr/local/lib/mailinabox/env/bin/python
|
||||
import argparse
|
||||
import datetime
|
||||
import gzip
|
||||
@@ -18,13 +18,13 @@ import utils
|
||||
|
||||
|
||||
LOG_FILES = (
|
||||
'/var/log/mail.log',
|
||||
'/var/log/mail.log.1',
|
||||
'/var/log/mail.log.2.gz',
|
||||
'/var/log/mail.log.3.gz',
|
||||
'/var/log/mail.log.4.gz',
|
||||
'/var/log/mail.log.5.gz',
|
||||
'/var/log/mail.log.6.gz',
|
||||
'/var/log/mail.log.5.gz',
|
||||
'/var/log/mail.log.4.gz',
|
||||
'/var/log/mail.log.3.gz',
|
||||
'/var/log/mail.log.2.gz',
|
||||
'/var/log/mail.log.1',
|
||||
'/var/log/mail.log',
|
||||
)
|
||||
|
||||
TIME_DELTAS = OrderedDict([
|
||||
@@ -53,10 +53,10 @@ VERBOSE = False
|
||||
# List of strings to filter users with
|
||||
FILTERS = None
|
||||
|
||||
# What to show by default
|
||||
# What to show (with defaults)
|
||||
SCAN_OUT = True # Outgoing email
|
||||
SCAN_IN = True # Incoming email
|
||||
SCAN_CONN = False # IMAP and POP3 logins
|
||||
SCAN_DOVECOT_LOGIN = True # Dovecot Logins
|
||||
SCAN_GREY = False # Greylisted email
|
||||
SCAN_BLOCKED = False # Rejected email
|
||||
|
||||
@@ -76,10 +76,11 @@ def scan_files(collector):
|
||||
tmp_file = tempfile.NamedTemporaryFile()
|
||||
shutil.copyfileobj(gzip.open(fn), tmp_file)
|
||||
|
||||
if VERBOSE:
|
||||
print("Processing file", fn, "...")
|
||||
fn = tmp_file.name if tmp_file else fn
|
||||
|
||||
for line in reverse_readline(fn):
|
||||
for line in readline(fn):
|
||||
if scan_mail_log_line(line.strip(), collector) is False:
|
||||
if stop_scan:
|
||||
return
|
||||
@@ -105,7 +106,7 @@ def scan_mail_log(env):
|
||||
"scan_time": time.time(), # The time in seconds the scan took
|
||||
"sent_mail": OrderedDict(), # Data about email sent by users
|
||||
"received_mail": OrderedDict(), # Data about email received by users
|
||||
"dovecot": OrderedDict(), # Data about Dovecot activity
|
||||
"logins": OrderedDict(), # Data about login activity
|
||||
"postgrey": {}, # Data about greylisting of email addresses
|
||||
"rejected": OrderedDict(), # Emails that were blocked
|
||||
"known_addresses": None, # Addresses handled by the Miab installation
|
||||
@@ -119,8 +120,8 @@ def scan_mail_log(env):
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
print("Scanning from {:%Y-%m-%d %H:%M:%S} back to {:%Y-%m-%d %H:%M:%S}".format(
|
||||
START_DATE, END_DATE)
|
||||
print("Scanning logs from {:%Y-%m-%d %H:%M:%S} to {:%Y-%m-%d %H:%M:%S}".format(
|
||||
END_DATE, START_DATE)
|
||||
)
|
||||
|
||||
# Scan the lines in the log files until the date goes out of range
|
||||
@@ -138,8 +139,8 @@ def scan_mail_log(env):
|
||||
# Print Sent Mail report
|
||||
|
||||
if collector["sent_mail"]:
|
||||
msg = "Sent email between {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
|
||||
print_header(msg.format(END_DATE, START_DATE))
|
||||
msg = "Sent email"
|
||||
print_header(msg)
|
||||
|
||||
data = OrderedDict(sorted(collector["sent_mail"].items(), key=email_sort))
|
||||
|
||||
@@ -173,8 +174,8 @@ def scan_mail_log(env):
|
||||
# Print Received Mail report
|
||||
|
||||
if collector["received_mail"]:
|
||||
msg = "Received email between {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
|
||||
print_header(msg.format(END_DATE, START_DATE))
|
||||
msg = "Received email"
|
||||
print_header(msg)
|
||||
|
||||
data = OrderedDict(sorted(collector["received_mail"].items(), key=email_sort))
|
||||
|
||||
@@ -199,43 +200,55 @@ def scan_mail_log(env):
|
||||
[accum]
|
||||
)
|
||||
|
||||
# Print Dovecot report
|
||||
# Print login report
|
||||
|
||||
if collector["dovecot"]:
|
||||
msg = "Email client logins between {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
|
||||
print_header(msg.format(END_DATE, START_DATE))
|
||||
if collector["logins"]:
|
||||
msg = "User logins per hour"
|
||||
print_header(msg)
|
||||
|
||||
data = OrderedDict(sorted(collector["dovecot"].items(), key=email_sort))
|
||||
data = OrderedDict(sorted(collector["logins"].items(), key=email_sort))
|
||||
|
||||
# Get a list of all of the protocols seen in the logs in reverse count order.
|
||||
all_protocols = defaultdict(int)
|
||||
for u in data.values():
|
||||
for protocol_name, count in u["totals_by_protocol"].items():
|
||||
all_protocols[protocol_name] += count
|
||||
all_protocols = [k for k, v in sorted(all_protocols.items(), key=lambda kv : -kv[1])]
|
||||
|
||||
print_user_table(
|
||||
data.keys(),
|
||||
data=[
|
||||
("imap", [u["imap"] for u in data.values()]),
|
||||
("pop3", [u["pop3"] for u in data.values()]),
|
||||
(protocol_name, [
|
||||
round(u["totals_by_protocol"][protocol_name] / (u["latest"]-u["earliest"]).total_seconds() * 60*60, 1)
|
||||
if (u["latest"]-u["earliest"]).total_seconds() > 0
|
||||
else 0 # prevent division by zero
|
||||
for u in data.values()])
|
||||
for protocol_name in all_protocols
|
||||
],
|
||||
sub_data=[
|
||||
("IMAP IP addresses", [[k + " (%d)" % v for k, v in u["imap-logins"].items()]
|
||||
for u in data.values()]),
|
||||
("POP3 IP addresses", [[k + " (%d)" % v for k, v in u["pop3-logins"].items()]
|
||||
for u in data.values()]),
|
||||
("Protocol and Source", [[
|
||||
"{} {}: {} times".format(protocol_name, host, count)
|
||||
for (protocol_name, host), count
|
||||
in sorted(u["totals_by_protocol_and_host"].items(), key=lambda kv:-kv[1])
|
||||
] for u in data.values()])
|
||||
],
|
||||
activity=[
|
||||
("imap", [u["activity-by-hour"]["imap"] for u in data.values()]),
|
||||
("pop3", [u["activity-by-hour"]["pop3"] for u in data.values()]),
|
||||
(protocol_name, [u["activity-by-hour"][protocol_name] for u in data.values()])
|
||||
for protocol_name in all_protocols
|
||||
],
|
||||
earliest=[u["earliest"] for u in data.values()],
|
||||
latest=[u["latest"] for u in data.values()],
|
||||
numstr=lambda n : str(round(n, 1)),
|
||||
)
|
||||
|
||||
accum = {"imap": defaultdict(int), "pop3": defaultdict(int), "both": defaultdict(int)}
|
||||
accum = { protocol_name: defaultdict(int) for protocol_name in all_protocols }
|
||||
for h in range(24):
|
||||
accum["imap"][h] = sum(d["activity-by-hour"]["imap"][h] for d in data.values())
|
||||
accum["pop3"][h] = sum(d["activity-by-hour"]["pop3"][h] for d in data.values())
|
||||
accum["both"][h] = accum["imap"][h] + accum["pop3"][h]
|
||||
for protocol_name in all_protocols:
|
||||
accum[protocol_name][h] = sum(d["activity-by-hour"][protocol_name][h] for d in data.values())
|
||||
|
||||
print_time_table(
|
||||
["imap", "pop3", " +"],
|
||||
[accum["imap"], accum["pop3"], accum["both"]]
|
||||
all_protocols,
|
||||
[accum[protocol_name] for protocol_name in all_protocols]
|
||||
)
|
||||
|
||||
if collector["postgrey"]:
|
||||
@@ -244,7 +257,7 @@ def scan_mail_log(env):
|
||||
|
||||
print(textwrap.fill(
|
||||
"The following mail was greylisted, meaning the emails were temporarily rejected. "
|
||||
"Legitimate senders will try again within ten minutes.",
|
||||
"Legitimate senders must try again after three minutes.",
|
||||
width=80, initial_indent=" ", subsequent_indent=" "
|
||||
), end='\n\n')
|
||||
|
||||
@@ -331,16 +344,22 @@ def scan_mail_log_line(line, collector):
|
||||
|
||||
# Replaced the dateutil parser for a less clever way of parser that is roughly 4 times faster.
|
||||
# date = dateutil.parser.parse(date)
|
||||
date = datetime.datetime.strptime(date, '%b %d %H:%M:%S')
|
||||
date = date.replace(START_DATE.year)
|
||||
|
||||
# date = datetime.datetime.strptime(date, '%b %d %H:%M:%S')
|
||||
# date = date.replace(START_DATE.year)
|
||||
|
||||
# strptime fails on Feb 29 if correct year is not provided. See https://bugs.python.org/issue26460
|
||||
date = datetime.datetime.strptime(str(START_DATE.year) + ' ' + date, '%Y %b %d %H:%M:%S')
|
||||
# print("date:", date)
|
||||
|
||||
# Check if the found date is within the time span we are scanning
|
||||
# END_DATE < START_DATE
|
||||
if date > START_DATE:
|
||||
# Don't process, but continue
|
||||
return True
|
||||
elif date < END_DATE:
|
||||
# Don't process, and halt
|
||||
return False
|
||||
elif date < END_DATE:
|
||||
# Don't process, but continue
|
||||
return True
|
||||
|
||||
if service == "postfix/submission/smtpd":
|
||||
if SCAN_OUT:
|
||||
@@ -348,9 +367,9 @@ def scan_mail_log_line(line, collector):
|
||||
elif service == "postfix/lmtp":
|
||||
if SCAN_IN:
|
||||
scan_postfix_lmtp_line(date, log, collector)
|
||||
elif service in ("imap-login", "pop3-login"):
|
||||
if SCAN_CONN:
|
||||
scan_dovecot_line(date, log, collector, service[:4])
|
||||
elif service.endswith("-login"):
|
||||
if SCAN_DOVECOT_LOGIN:
|
||||
scan_dovecot_login_line(date, log, collector, service[:4])
|
||||
elif service == "postgrey":
|
||||
if SCAN_GREY:
|
||||
scan_postgrey_line(date, log, collector)
|
||||
@@ -440,52 +459,51 @@ def scan_postfix_smtpd_line(date, log, collector):
|
||||
if m:
|
||||
message = "domain blocked: " + m.group(2)
|
||||
|
||||
if data["latest"] is None:
|
||||
data["latest"] = date
|
||||
if data["earliest"] is None:
|
||||
data["earliest"] = date
|
||||
data["latest"] = date
|
||||
data["blocked"].append((date, sender, message))
|
||||
|
||||
collector["rejected"][user] = data
|
||||
|
||||
|
||||
def scan_dovecot_line(date, log, collector, prot):
|
||||
""" Scan a dovecot log line and extract interesting data """
|
||||
def scan_dovecot_login_line(date, log, collector, protocol_name):
|
||||
""" Scan a dovecot login log line and extract interesting data """
|
||||
|
||||
m = re.match("Info: Login: user=<(.*?)>, method=PLAIN, rip=(.*?),", log)
|
||||
|
||||
if m:
|
||||
# TODO: CHECK DIT
|
||||
user, rip = m.groups()
|
||||
user, host = m.groups()
|
||||
|
||||
if user_match(user):
|
||||
add_login(user, date, protocol_name, host, collector)
|
||||
|
||||
|
||||
def add_login(user, date, protocol_name, host, collector):
|
||||
# Get the user data, or create it if the user is new
|
||||
data = collector["dovecot"].get(
|
||||
data = collector["logins"].get(
|
||||
user,
|
||||
{
|
||||
"imap": 0,
|
||||
"pop3": 0,
|
||||
"earliest": None,
|
||||
"latest": None,
|
||||
"imap-logins": defaultdict(int),
|
||||
"pop3-logins": defaultdict(int),
|
||||
"activity-by-hour": {
|
||||
"imap": defaultdict(int),
|
||||
"pop3": defaultdict(int),
|
||||
},
|
||||
"totals_by_protocol": defaultdict(int),
|
||||
"totals_by_protocol_and_host": defaultdict(int),
|
||||
"activity-by-hour": defaultdict(lambda : defaultdict(int)),
|
||||
}
|
||||
)
|
||||
|
||||
data[prot] += 1
|
||||
data["activity-by-hour"][prot][date.hour] += 1
|
||||
|
||||
if data["latest"] is None:
|
||||
data["latest"] = date
|
||||
if data["earliest"] is None:
|
||||
data["earliest"] = date
|
||||
data["latest"] = date
|
||||
|
||||
if rip not in ("127.0.0.1", "::1") or True:
|
||||
data["%s-logins" % prot][rip] += 1
|
||||
data["totals_by_protocol"][protocol_name] += 1
|
||||
data["totals_by_protocol_and_host"][(protocol_name, host)] += 1
|
||||
|
||||
collector["dovecot"][user] = data
|
||||
if host not in ("127.0.0.1", "::1") or True:
|
||||
data["activity-by-hour"][protocol_name][date.hour] += 1
|
||||
|
||||
collector["logins"][user] = data
|
||||
|
||||
|
||||
def scan_postfix_lmtp_line(date, log, collector):
|
||||
@@ -516,9 +534,9 @@ def scan_postfix_lmtp_line(date, log, collector):
|
||||
data["received_count"] += 1
|
||||
data["activity-by-hour"][date.hour] += 1
|
||||
|
||||
if data["latest"] is None:
|
||||
data["latest"] = date
|
||||
if data["earliest"] is None:
|
||||
data["earliest"] = date
|
||||
data["latest"] = date
|
||||
|
||||
collector["received_mail"][user] = data
|
||||
|
||||
@@ -555,51 +573,26 @@ def scan_postfix_submission_line(date, log, collector):
|
||||
data["hosts"].add(client)
|
||||
data["activity-by-hour"][date.hour] += 1
|
||||
|
||||
if data["latest"] is None:
|
||||
data["latest"] = date
|
||||
if data["earliest"] is None:
|
||||
data["earliest"] = date
|
||||
data["latest"] = date
|
||||
|
||||
collector["sent_mail"][user] = data
|
||||
|
||||
# Also log this as a login.
|
||||
add_login(user, date, "smtp", client, collector)
|
||||
|
||||
# Utility functions
|
||||
|
||||
def reverse_readline(filename, buf_size=8192):
|
||||
""" A generator that returns the lines of a file in reverse order
|
||||
|
||||
http://stackoverflow.com/a/23646049/801870
|
||||
|
||||
def readline(filename):
|
||||
""" A generator that returns the lines of a file
|
||||
"""
|
||||
|
||||
with open(filename) as fh:
|
||||
segment = None
|
||||
offset = 0
|
||||
fh.seek(0, os.SEEK_END)
|
||||
file_size = remaining_size = fh.tell()
|
||||
while remaining_size > 0:
|
||||
offset = min(file_size, offset + buf_size)
|
||||
fh.seek(file_size - offset)
|
||||
buff = fh.read(min(remaining_size, buf_size))
|
||||
remaining_size -= buf_size
|
||||
lines = buff.split('\n')
|
||||
# the first line of the buffer is probably not a complete line so
|
||||
# we'll save it and append it to the last line of the next buffer
|
||||
# we read
|
||||
if segment is not None:
|
||||
# if the previous chunk starts right from the beginning of line
|
||||
# do not concat the segment to the last line of new chunk
|
||||
# instead, yield the segment first
|
||||
if buff[-1] is not '\n':
|
||||
lines[-1] += segment
|
||||
else:
|
||||
yield segment
|
||||
segment = lines[0]
|
||||
for index in range(len(lines) - 1, 0, -1):
|
||||
if len(lines[index]):
|
||||
yield lines[index]
|
||||
# Don't yield None if the file was empty
|
||||
if segment is not None:
|
||||
yield segment
|
||||
with open(filename) as file:
|
||||
while True:
|
||||
line = file.readline()
|
||||
if not line:
|
||||
break
|
||||
yield line
|
||||
|
||||
|
||||
def user_match(user):
|
||||
@@ -640,7 +633,7 @@ def print_time_table(labels, data, do_print=True):
|
||||
for i, d in enumerate(data):
|
||||
lines[i] += base.format(d[h])
|
||||
|
||||
lines.insert(0, "┬")
|
||||
lines.insert(0, "┬ totals by time of day:")
|
||||
lines.append("└" + (len(lines[-1]) - 2) * "─")
|
||||
|
||||
if do_print:
|
||||
@@ -650,7 +643,7 @@ def print_time_table(labels, data, do_print=True):
|
||||
|
||||
|
||||
def print_user_table(users, data=None, sub_data=None, activity=None, latest=None, earliest=None,
|
||||
delimit=False):
|
||||
delimit=False, numstr=str):
|
||||
str_temp = "{:<32} "
|
||||
lines = []
|
||||
data = data or []
|
||||
@@ -764,7 +757,7 @@ def print_user_table(users, data=None, sub_data=None, activity=None, latest=None
|
||||
|
||||
# Print totals
|
||||
|
||||
data_accum = [str(a) for a in data_accum]
|
||||
data_accum = [numstr(a) for a in data_accum]
|
||||
footer = str_temp.format("Totals:" if do_accum else " ")
|
||||
for row, (l, _) in enumerate(data):
|
||||
temp = "{:>%d}" % max(5, len(l) + 1)
|
||||
@@ -818,7 +811,7 @@ if __name__ == "__main__":
|
||||
action="store_true")
|
||||
parser.add_argument("-s", "--sent", help="Scan for sent emails.",
|
||||
action="store_true")
|
||||
parser.add_argument("-l", "--logins", help="Scan for IMAP/POP logins.",
|
||||
parser.add_argument("-l", "--logins", help="Scan for user logins to IMAP/POP3.",
|
||||
action="store_true")
|
||||
parser.add_argument("-g", "--grey", help="Scan for greylisted emails.",
|
||||
action="store_true")
|
||||
@@ -863,8 +856,8 @@ if __name__ == "__main__":
|
||||
if not SCAN_OUT:
|
||||
print("Ignoring sent emails")
|
||||
|
||||
SCAN_CONN = args.logins
|
||||
if not SCAN_CONN:
|
||||
SCAN_DOVECOT_LOGIN = args.logins
|
||||
if not SCAN_DOVECOT_LOGIN:
|
||||
print("Ignoring logins")
|
||||
|
||||
SCAN_GREY = args.grey
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
#!/usr/bin/python3
|
||||
#!/usr/local/lib/mailinabox/env/bin/python
|
||||
|
||||
# NOTE:
|
||||
# This script is run both using the system-wide Python 3
|
||||
# interpreter (/usr/bin/python3) as well as through the
|
||||
# virtualenv (/usr/local/lib/mailinabox/env). So only
|
||||
# import packages at the top level of this script that
|
||||
# are installed in *both* contexts. We use the system-wide
|
||||
# Python 3 in setup/questions.sh to validate the email
|
||||
# address entered by the user.
|
||||
|
||||
import subprocess, shutil, os, sqlite3, re
|
||||
import utils
|
||||
@@ -96,7 +105,7 @@ def get_mail_users(env):
|
||||
users = [ row[0] for row in c.fetchall() ]
|
||||
return utils.sort_email_addresses(users, env)
|
||||
|
||||
def get_mail_users_ex(env, with_archived=False, with_slow_info=False):
|
||||
def get_mail_users_ex(env, with_archived=False):
|
||||
# Returns a complex data structure of all user accounts, optionally
|
||||
# including archived (status="inactive") accounts.
|
||||
#
|
||||
@@ -130,9 +139,6 @@ def get_mail_users_ex(env, with_archived=False, with_slow_info=False):
|
||||
}
|
||||
users.append(user)
|
||||
|
||||
if with_slow_info:
|
||||
user["mailbox_size"] = utils.du(os.path.join(env['STORAGE_ROOT'], 'mail/mailboxes', *reversed(email.split("@"))))
|
||||
|
||||
# Add in archived accounts.
|
||||
if with_archived:
|
||||
root = os.path.join(env['STORAGE_ROOT'], 'mail/mailboxes')
|
||||
@@ -144,13 +150,11 @@ def get_mail_users_ex(env, with_archived=False, with_slow_info=False):
|
||||
if email in active_accounts: continue
|
||||
user = {
|
||||
"email": email,
|
||||
"privileges": "",
|
||||
"privileges": [],
|
||||
"status": "inactive",
|
||||
"mailbox": mbox,
|
||||
}
|
||||
users.append(user)
|
||||
if with_slow_info:
|
||||
user["mailbox_size"] = utils.du(mbox)
|
||||
|
||||
# Group by domain.
|
||||
domains = { }
|
||||
@@ -435,9 +439,11 @@ def add_mail_alias(address, forwards_to, permitted_senders, env, update_if_exist
|
||||
email = email.strip()
|
||||
if email == "": continue
|
||||
email = sanitize_idn_email_address(email) # Unicode => IDNA
|
||||
# Strip any +tag from email alias and check privileges
|
||||
privileged_email = re.sub(r"(?=\+)[^@]*(?=@)",'',email)
|
||||
if not validate_email(email):
|
||||
return ("Invalid receiver email address (%s)." % email, 400)
|
||||
if is_dcv_source and not is_dcv_address(email) and "admin" not in get_mail_user_privileges(email, env, empty_on_error=True):
|
||||
if is_dcv_source and not is_dcv_address(email) and "admin" not in get_mail_user_privileges(privileged_email, env, empty_on_error=True):
|
||||
# Make domain control validation hijacking a little harder to mess up by
|
||||
# requiring aliases for email addresses typically used in DCV to forward
|
||||
# only to accounts that are administrators on this system.
|
||||
|
||||
2
management/munin_start.sh
Executable file
2
management/munin_start.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
mkdir -p /var/run/munin && chown munin /var/run/munin
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python3
|
||||
#!/usr/local/lib/mailinabox/env/bin/python
|
||||
# Utilities for installing and selecting SSL certificates.
|
||||
|
||||
import os, os.path, re, shutil
|
||||
import os, os.path, re, shutil, subprocess, tempfile
|
||||
|
||||
from utils import shell, safe_domain_name, sort_domains
|
||||
import idna
|
||||
@@ -24,6 +24,16 @@ def get_ssl_certificates(env):
|
||||
if not os.path.exists(ssl_root):
|
||||
return
|
||||
for fn in os.listdir(ssl_root):
|
||||
if fn == 'ssl_certificate.pem':
|
||||
# This is always a symbolic link
|
||||
# to the certificate to use for
|
||||
# PRIMARY_HOSTNAME. Don't let it
|
||||
# be eligible for use because we
|
||||
# could end up creating a symlink
|
||||
# to itself --- we want to find
|
||||
# the cert that it should be a
|
||||
# symlink to.
|
||||
continue
|
||||
fn = os.path.join(ssl_root, fn)
|
||||
if os.path.isfile(fn):
|
||||
yield fn
|
||||
@@ -74,6 +84,12 @@ def get_ssl_certificates(env):
|
||||
|
||||
# Add this cert to the list of certs usable for the domains.
|
||||
for domain in cert_domains:
|
||||
# The primary hostname can only use a certificate mapped
|
||||
# to the system private key.
|
||||
if domain == env['PRIMARY_HOSTNAME']:
|
||||
if cert._private_key._filename != os.path.join(env['STORAGE_ROOT'], 'ssl', 'ssl_private_key.pem'):
|
||||
continue
|
||||
|
||||
domains.setdefault(domain, []).append(cert)
|
||||
|
||||
# Sort the certificates to prefer good ones.
|
||||
@@ -81,6 +97,7 @@ def get_ssl_certificates(env):
|
||||
now = datetime.datetime.utcnow()
|
||||
ret = { }
|
||||
for domain, cert_list in domains.items():
|
||||
#for c in cert_list: print(domain, c.not_valid_before, c.not_valid_after, "("+str(now)+")", c.issuer, c.subject, c._filename)
|
||||
cert_list.sort(key = lambda cert : (
|
||||
# must be valid NOW
|
||||
cert.not_valid_before <= now <= cert.not_valid_after,
|
||||
@@ -124,7 +141,8 @@ def get_ssl_certificates(env):
|
||||
|
||||
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.
|
||||
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'))
|
||||
@@ -135,6 +153,7 @@ def get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=False
|
||||
"certificate_object": load_pem(load_cert_chain(ssl_certificate)[0]),
|
||||
}
|
||||
|
||||
if use_main_cert:
|
||||
if domain == env['PRIMARY_HOSTNAME']:
|
||||
# The primary domain must use the server certificate because
|
||||
# it is hard-coded in some service configuration files.
|
||||
@@ -155,142 +174,97 @@ def get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=False
|
||||
|
||||
# PROVISIONING CERTIFICATES FROM LETSENCRYPT
|
||||
|
||||
def get_certificates_to_provision(env, show_extended_problems=True, force_domains=None):
|
||||
# Get a set of domain names that we should now provision certificates
|
||||
# for. Provision if a domain name has no valid certificate or if any
|
||||
# certificate is expiring in 14 days. If provisioning anything, also
|
||||
# provision certificates expiring within 30 days. The period between
|
||||
# 14 and 30 days allows us to consolidate domains into multi-domain
|
||||
# certificates for domains expiring around the same time.
|
||||
def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True):
|
||||
# Get a set of domain names that we can provision certificates for
|
||||
# using certbot. We start with domains that the box is serving web
|
||||
# for and subtract:
|
||||
# * domains not in limit_domains if limit_domains is not empty
|
||||
# * domains with custom "A" records, i.e. they are hosted elsewhere
|
||||
# * domains with actual "A" records that point elsewhere
|
||||
# * domains that already have certificates that will be valid for a while
|
||||
|
||||
from web_update import get_web_domains
|
||||
from status_checks import query_dns, normalize_ip
|
||||
|
||||
import datetime
|
||||
now = datetime.datetime.utcnow()
|
||||
existing_certs = get_ssl_certificates(env)
|
||||
|
||||
# Get domains with missing & expiring certificates.
|
||||
certs = get_ssl_certificates(env)
|
||||
domains = set()
|
||||
domains_if_any = set()
|
||||
problems = { }
|
||||
for domain in get_web_domains(env):
|
||||
# If the user really wants a cert for certain domains, include it.
|
||||
if force_domains:
|
||||
if force_domains == "ALL" or (isinstance(force_domains, list) and domain in force_domains):
|
||||
domains.add(domain)
|
||||
plausible_web_domains = get_web_domains(env, exclude_dns_elsewhere=False)
|
||||
actual_web_domains = get_web_domains(env)
|
||||
|
||||
domains_to_provision = set()
|
||||
domains_cant_provision = { }
|
||||
|
||||
for domain in plausible_web_domains:
|
||||
# Skip domains that the user doesn't want to provision now.
|
||||
if limit_domains and domain not in limit_domains:
|
||||
continue
|
||||
|
||||
# Include this domain if its certificate is missing, self-signed, or expiring soon.
|
||||
try:
|
||||
cert = get_domain_ssl_files(domain, certs, env, allow_missing_cert=True)
|
||||
except FileNotFoundError as e:
|
||||
# system certificate is not present
|
||||
problems[domain] = "Error: " + str(e)
|
||||
continue
|
||||
if cert is None:
|
||||
# No valid certificate available.
|
||||
domains.add(domain)
|
||||
# Check that there isn't an explicit A/AAAA record.
|
||||
if domain not in actual_web_domains:
|
||||
domains_cant_provision[domain] = "The domain has a custom DNS A/AAAA record that points the domain elsewhere, so there is no point to installing a TLS certificate here and we could not automatically provision one anyway because provisioning requires access to the website (which isn't here)."
|
||||
|
||||
# Check that the DNS resolves to here.
|
||||
else:
|
||||
cert = cert["certificate_object"]
|
||||
if cert.issuer == cert.subject:
|
||||
# This is self-signed. Get a real one.
|
||||
domains.add(domain)
|
||||
|
||||
# Valid certificate today, but is it expiring soon?
|
||||
elif cert.not_valid_after-now < datetime.timedelta(days=14):
|
||||
domains.add(domain)
|
||||
elif cert.not_valid_after-now < datetime.timedelta(days=30):
|
||||
domains_if_any.add(domain)
|
||||
|
||||
# It's valid. Should we report its validness?
|
||||
elif show_extended_problems:
|
||||
problems[domain] = "The certificate is valid for at least another 30 days --- no need to replace."
|
||||
|
||||
# Warn the user about domains hosted elsewhere.
|
||||
if not force_domains and show_extended_problems:
|
||||
for domain in set(get_web_domains(env, exclude_dns_elsewhere=False)) - set(get_web_domains(env)):
|
||||
problems[domain] = "The domain's DNS is pointed elsewhere, so there is no point to installing a TLS certificate here and we could not automatically provision one anyway because provisioning requires access to the website (which isn't here)."
|
||||
|
||||
# Filter out domains that we can't provision a certificate for.
|
||||
def can_provision_for_domain(domain):
|
||||
from status_checks import normalize_ip
|
||||
# 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,
|
||||
# we can't do domain control validation. For IPv6 is configured,
|
||||
# make sure both IPv4 and IPv6 are correct because we don't know
|
||||
# how Let's Encrypt will connect.
|
||||
import dns.resolver
|
||||
bad_dns = []
|
||||
for rtype, value in [("A", env["PUBLIC_IP"]), ("AAAA", env.get("PUBLIC_IPV6"))]:
|
||||
if not value: continue # IPv6 is not configured
|
||||
try:
|
||||
# Must make the qname absolute to prevent a fall-back lookup with a
|
||||
# search domain appended, by adding a period to the end.
|
||||
response = dns.resolver.query(domain + ".", rtype)
|
||||
except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as e:
|
||||
problems[domain] = "DNS isn't configured properly for this domain: DNS resolution failed (%s: %s)." % (rtype, str(e) or repr(e)) # NoAnswer's str is empty
|
||||
return False
|
||||
except Exception as e:
|
||||
problems[domain] = "DNS isn't configured properly for this domain: DNS lookup had an error: %s." % str(e)
|
||||
return False
|
||||
response = query_dns(domain, rtype)
|
||||
if response != normalize_ip(value):
|
||||
bad_dns.append("%s (%s)" % (response, rtype))
|
||||
|
||||
# Unfortunately, the response.__str__ returns bytes
|
||||
# instead of string, if it resulted from an AAAA-query.
|
||||
# We need to convert manually, until this is fixed:
|
||||
# https://github.com/rthalley/dnspython/issues/204
|
||||
#
|
||||
# BEGIN HOTFIX
|
||||
def rdata__str__(r):
|
||||
s = r.to_text()
|
||||
if isinstance(s, bytes):
|
||||
s = s.decode('utf-8')
|
||||
return s
|
||||
# END HOTFIX
|
||||
if bad_dns:
|
||||
domains_cant_provision[domain] = "The domain name does not resolve to this machine: " \
|
||||
+ (", ".join(bad_dns)) \
|
||||
+ "."
|
||||
|
||||
if len(response) != 1 or normalize_ip(rdata__str__(response[0])) != normalize_ip(value):
|
||||
problems[domain] = "Domain control validation cannot be performed for this domain because DNS points the domain to another machine (%s %s)." % (rtype, ", ".join(rdata__str__(r) for r in response))
|
||||
return False
|
||||
else:
|
||||
# DNS is all good.
|
||||
|
||||
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
|
||||
# additional domains to do at this time.
|
||||
if len(domains) > 0:
|
||||
domains |= set(filter(can_provision_for_domain, domains_if_any))
|
||||
|
||||
return (domains, problems)
|
||||
|
||||
def provision_certificates(env, agree_to_tos_url=None, logger=None, show_extended_problems=True, force_domains=None, jsonable=False):
|
||||
import requests.exceptions
|
||||
import acme.messages
|
||||
|
||||
from free_tls_certificates import client
|
||||
return (domains_to_provision, domains_cant_provision)
|
||||
|
||||
def provision_certificates(env, limit_domains):
|
||||
# What domains should we provision certificates for? And what
|
||||
# errors prevent provisioning for other domains.
|
||||
domains, problems = get_certificates_to_provision(env, force_domains=force_domains, show_extended_problems=show_extended_problems)
|
||||
domains, domains_cant_provision = get_certificates_to_provision(env, limit_domains=limit_domains)
|
||||
|
||||
# Build a list of what happened on each domain or domain-set.
|
||||
ret = []
|
||||
for domain, error in domains_cant_provision.items():
|
||||
ret.append({
|
||||
"domains": [domain],
|
||||
"log": [error],
|
||||
"result": "skipped",
|
||||
})
|
||||
|
||||
# Exit fast if there is nothing to do.
|
||||
if len(domains) == 0:
|
||||
return {
|
||||
"requests": [],
|
||||
"problems": problems,
|
||||
}
|
||||
|
||||
# Break into groups of up to 100 certificates at a time, which is Let's Encrypt's
|
||||
# limit for a single certificate. We'll sort to put related domains together.
|
||||
max_domains_per_group = 100
|
||||
domains = sort_domains(domains, env)
|
||||
certs = []
|
||||
while len(domains) > 0:
|
||||
certs.append( domains[0:100] )
|
||||
domains = domains[100:]
|
||||
certs.append( domains[:max_domains_per_group] )
|
||||
domains = domains[max_domains_per_group:]
|
||||
|
||||
# Prepare to provision.
|
||||
|
||||
@@ -299,115 +273,82 @@ def provision_certificates(env, agree_to_tos_url=None, logger=None, show_extende
|
||||
if not os.path.exists(account_path):
|
||||
os.mkdir(account_path)
|
||||
|
||||
# Where should we put ACME challenge files. This is mapped to /.well-known/acme_challenge
|
||||
# by the nginx configuration.
|
||||
challenges_path = os.path.join(account_path, 'acme_challenges')
|
||||
if not os.path.exists(challenges_path):
|
||||
os.mkdir(challenges_path)
|
||||
|
||||
# Read in the private key that we use for all TLS certificates. We'll need that
|
||||
# to generate a CSR (done by free_tls_certificates).
|
||||
with open(os.path.join(env['STORAGE_ROOT'], 'ssl/ssl_private_key.pem'), 'rb') as f:
|
||||
private_key = f.read()
|
||||
|
||||
# Provision certificates.
|
||||
|
||||
ret = []
|
||||
for domain_list in certs:
|
||||
# For return.
|
||||
ret_item = {
|
||||
ret.append({
|
||||
"domains": domain_list,
|
||||
"log": [],
|
||||
}
|
||||
ret.append(ret_item)
|
||||
|
||||
# Logging for free_tls_certificates.
|
||||
def my_logger(message):
|
||||
if logger: logger(message)
|
||||
ret_item["log"].append(message)
|
||||
|
||||
# Attempt to provision a certificate.
|
||||
})
|
||||
try:
|
||||
try:
|
||||
cert = client.issue_certificate(
|
||||
domain_list,
|
||||
account_path,
|
||||
agree_to_tos_url=agree_to_tos_url,
|
||||
private_key=private_key,
|
||||
logger=my_logger)
|
||||
# Create a CSR file for our master private key so that certbot
|
||||
# uses our private key.
|
||||
key_file = os.path.join(env['STORAGE_ROOT'], 'ssl', 'ssl_private_key.pem')
|
||||
with tempfile.NamedTemporaryFile() as csr_file:
|
||||
# We could use openssl, but certbot requires
|
||||
# that the CN domain and SAN domains match
|
||||
# the domain list passed to certbot, and adding
|
||||
# SAN domains openssl req is ridiculously complicated.
|
||||
# subprocess.check_output([
|
||||
# "openssl", "req", "-new",
|
||||
# "-key", key_file,
|
||||
# "-out", csr_file.name,
|
||||
# "-subj", "/CN=" + domain_list[0],
|
||||
# "-sha256" ])
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.serialization import Encoding
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.x509.oid import NameOID
|
||||
builder = x509.CertificateSigningRequestBuilder()
|
||||
builder = builder.subject_name(x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, domain_list[0]) ]))
|
||||
builder = builder.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
|
||||
builder = builder.add_extension(x509.SubjectAlternativeName(
|
||||
[x509.DNSName(d) for d in domain_list]
|
||||
), critical=False)
|
||||
request = builder.sign(load_pem(load_cert_chain(key_file)[0]), hashes.SHA256(), default_backend())
|
||||
with open(csr_file.name, "wb") as f:
|
||||
f.write(request.public_bytes(Encoding.PEM))
|
||||
|
||||
except client.NeedToTakeAction as e:
|
||||
# Write out the ACME challenge files.
|
||||
for action in e.actions:
|
||||
if isinstance(action, client.NeedToInstallFile):
|
||||
fn = os.path.join(challenges_path, action.file_name)
|
||||
with open(fn, 'w') as f:
|
||||
f.write(action.contents)
|
||||
else:
|
||||
raise ValueError(str(action))
|
||||
# Provision, writing to a temporary file.
|
||||
webroot = os.path.join(account_path, 'webroot')
|
||||
os.makedirs(webroot, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
cert_file = os.path.join(d, 'cert_and_chain.pem')
|
||||
print("Provisioning TLS certificates for " + ", ".join(domain_list) + ".")
|
||||
certbotret = subprocess.check_output([
|
||||
"certbot",
|
||||
"certonly",
|
||||
#"-v", # just enough to see ACME errors
|
||||
"--non-interactive", # will fail if user hasn't registered during Mail-in-a-Box setup
|
||||
|
||||
# Try to provision now that the challenge files are installed.
|
||||
"-d", ",".join(domain_list), # first will be main domain
|
||||
|
||||
cert = client.issue_certificate(
|
||||
domain_list,
|
||||
account_path,
|
||||
private_key=private_key,
|
||||
logger=my_logger)
|
||||
"--csr", csr_file.name, # use our private key; unfortunately this doesn't work with auto-renew so we need to save cert manually
|
||||
"--cert-path", os.path.join(d, 'cert'), # we only use the full chain
|
||||
"--chain-path", os.path.join(d, 'chain'), # we only use the full chain
|
||||
"--fullchain-path", cert_file,
|
||||
|
||||
except client.NeedToAgreeToTOS as e:
|
||||
# The user must agree to the Let's Encrypt terms of service agreement
|
||||
# before any further action can be taken.
|
||||
ret_item.update({
|
||||
"result": "agree-to-tos",
|
||||
"url": e.url,
|
||||
})
|
||||
"--webroot", "--webroot-path", webroot,
|
||||
|
||||
except client.WaitABit as e:
|
||||
# We need to hold on for a bit before querying again to see if we can
|
||||
# acquire a provisioned certificate.
|
||||
import time, datetime
|
||||
ret_item.update({
|
||||
"result": "wait",
|
||||
"until": e.until_when if not jsonable else e.until_when.isoformat(),
|
||||
"seconds": (e.until_when - datetime.datetime.now()).total_seconds()
|
||||
})
|
||||
"--config-dir", account_path,
|
||||
#"--staging",
|
||||
], stderr=subprocess.STDOUT).decode("utf8")
|
||||
install_cert_copy_file(cert_file, env)
|
||||
|
||||
except client.AccountDataIsCorrupt as e:
|
||||
# This is an extremely rare condition.
|
||||
ret_item.update({
|
||||
"result": "error",
|
||||
"message": "Something unexpected went wrong. It looks like your local Let's Encrypt account data is corrupted. There was a problem with the file " + e.account_file_path + ".",
|
||||
})
|
||||
ret[-1]["log"].append(certbotret)
|
||||
ret[-1]["result"] = "installed"
|
||||
except subprocess.CalledProcessError as e:
|
||||
ret[-1]["log"].append(e.output.decode("utf8"))
|
||||
ret[-1]["result"] = "error"
|
||||
except Exception as e:
|
||||
ret[-1]["log"].append(str(e))
|
||||
ret[-1]["result"] = "error"
|
||||
|
||||
except (client.InvalidDomainName, client.NeedToTakeAction, client.ChallengeFailed, client.RateLimited, acme.messages.Error, requests.exceptions.RequestException) as e:
|
||||
ret_item.update({
|
||||
"result": "error",
|
||||
"message": "Something unexpected went wrong: " + str(e),
|
||||
})
|
||||
|
||||
else:
|
||||
# A certificate was issued.
|
||||
|
||||
install_status = install_cert(domain_list[0], cert['cert'].decode("ascii"), b"\n".join(cert['chain']).decode("ascii"), env, raw=True)
|
||||
|
||||
# str indicates the certificate was not installed.
|
||||
if isinstance(install_status, str):
|
||||
ret_item.update({
|
||||
"result": "error",
|
||||
"message": "Something unexpected was wrong with the provisioned certificate: " + install_status,
|
||||
})
|
||||
else:
|
||||
# A list indicates success and what happened next.
|
||||
ret_item["log"].extend(install_status)
|
||||
ret_item.update({
|
||||
"result": "installed",
|
||||
})
|
||||
# Run post-install steps.
|
||||
ret.extend(post_install_func(env))
|
||||
|
||||
# Return what happened with each certificate request.
|
||||
return {
|
||||
"requests": ret,
|
||||
"problems": problems,
|
||||
}
|
||||
return ret
|
||||
|
||||
def provision_certificates_cmdline():
|
||||
import sys
|
||||
@@ -418,142 +359,30 @@ def provision_certificates_cmdline():
|
||||
Lock(die=True).forever()
|
||||
env = load_environment()
|
||||
|
||||
verbose = False
|
||||
headless = False
|
||||
force_domains = None
|
||||
show_extended_problems = True
|
||||
quiet = False
|
||||
domains = []
|
||||
|
||||
args = list(sys.argv)
|
||||
args.pop(0) # program name
|
||||
if args and args[0] == "-v":
|
||||
verbose = True
|
||||
args.pop(0)
|
||||
if args and args[0] == "-q":
|
||||
show_extended_problems = False
|
||||
args.pop(0)
|
||||
if args and args[0] == "--headless":
|
||||
headless = True
|
||||
args.pop(0)
|
||||
if args and args[0] == "--force":
|
||||
force_domains = "ALL"
|
||||
args.pop(0)
|
||||
for arg in sys.argv[1:]:
|
||||
if arg == "-q":
|
||||
quiet = True
|
||||
else:
|
||||
force_domains = args
|
||||
domains.append(arg)
|
||||
|
||||
agree_to_tos_url = None
|
||||
while True:
|
||||
# Run the provisioning script. This installs certificates. If there are
|
||||
# a very large number of domains on this box, it issues separate
|
||||
# certificates for groups of domains. We have to check the result for
|
||||
# each group.
|
||||
def my_logger(message):
|
||||
if verbose:
|
||||
print(">", message)
|
||||
status = provision_certificates(env, agree_to_tos_url=agree_to_tos_url, logger=my_logger, force_domains=force_domains, show_extended_problems=show_extended_problems)
|
||||
agree_to_tos_url = None # reset to prevent infinite looping
|
||||
# Go.
|
||||
status = provision_certificates(env, limit_domains=domains)
|
||||
|
||||
if not status["requests"]:
|
||||
# No domains need certificates.
|
||||
if not headless or verbose:
|
||||
if len(status["problems"]) == 0:
|
||||
print("No domains hosted on this box need a new TLS certificate at this time.")
|
||||
elif len(status["problems"]) > 0:
|
||||
print("No TLS certificates could be provisoned at this time:")
|
||||
print()
|
||||
for domain in sort_domains(status["problems"], env):
|
||||
print("%s: %s" % (domain, status["problems"][domain]))
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
# What happened?
|
||||
wait_until = None
|
||||
wait_domains = []
|
||||
for request in status["requests"]:
|
||||
if request["result"] == "agree-to-tos":
|
||||
# We may have asked already in a previous iteration.
|
||||
if agree_to_tos_url is not None:
|
||||
# Show what happened.
|
||||
for request in status:
|
||||
if isinstance(request, str):
|
||||
print(request)
|
||||
else:
|
||||
if quiet and request['result'] == 'skipped':
|
||||
continue
|
||||
|
||||
# Can't ask the user a question in this mode. Warn the user that something
|
||||
# needs to be done.
|
||||
if headless:
|
||||
print(", ".join(request["domains"]) + " need a new or renewed TLS certificate.")
|
||||
print(request['result'] + ":", ", ".join(request['domains']) + ":")
|
||||
for line in request["log"]:
|
||||
print(line)
|
||||
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
|
||||
|
||||
@@ -562,7 +391,7 @@ def create_csr(domain, ssl_key, country_code, env):
|
||||
"openssl", "req", "-new",
|
||||
"-key", ssl_key,
|
||||
"-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):
|
||||
# Write the combined cert+chain to a temporary path and validate that it is OK.
|
||||
@@ -583,6 +412,16 @@ def install_cert(domain, ssl_cert, ssl_chain, env, raw=False):
|
||||
cert_status += " " + cert_status_details
|
||||
return cert_status
|
||||
|
||||
# Copy certifiate into ssl directory.
|
||||
install_cert_copy_file(fn, env)
|
||||
|
||||
# Run post-install steps.
|
||||
ret = post_install_func(env)
|
||||
if raw: return ret
|
||||
return "\n".join(ret)
|
||||
|
||||
|
||||
def install_cert_copy_file(fn, env):
|
||||
# Where to put it?
|
||||
# Make a unique path for the certificate.
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
@@ -600,14 +439,26 @@ def install_cert(domain, ssl_cert, ssl_chain, env, raw=False):
|
||||
os.makedirs(os.path.dirname(ssl_certificate), exist_ok=True)
|
||||
shutil.move(fn, ssl_certificate)
|
||||
|
||||
ret = ["OK"]
|
||||
|
||||
# When updating the cert for PRIMARY_HOSTNAME, symlink it from the system
|
||||
def post_install_func(env):
|
||||
ret = []
|
||||
|
||||
# Get the certificate to use for PRIMARY_HOSTNAME.
|
||||
ssl_certificates = get_ssl_certificates(env)
|
||||
cert = get_domain_ssl_files(env['PRIMARY_HOSTNAME'], ssl_certificates, env, use_main_cert=False)
|
||||
if not cert:
|
||||
# Ruh-row, we don't have any certificate usable
|
||||
# for the primary hostname.
|
||||
ret.append("there is no valid certificate for " + env['PRIMARY_HOSTNAME'])
|
||||
|
||||
# Symlink the best cert for PRIMARY_HOSTNAME to the system
|
||||
# certificate path, which is hard-coded for various purposes, and then
|
||||
# restart postfix and dovecot.
|
||||
if domain == env['PRIMARY_HOSTNAME']:
|
||||
# Update symlink.
|
||||
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.symlink(ssl_certificate, system_ssl_certificate)
|
||||
|
||||
@@ -623,12 +474,12 @@ def install_cert(domain, ssl_cert, ssl_chain, env, raw=False):
|
||||
# Update the web configuration so nginx picks up the new certificate file.
|
||||
from web_update import do_web_update
|
||||
ret.append( do_web_update(env) )
|
||||
if raw: return ret
|
||||
return "\n".join(ret)
|
||||
|
||||
return ret
|
||||
|
||||
# VALIDATION OF CERTIFICATES
|
||||
|
||||
def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring_soon=True, rounded_time=False, just_check_domain=False):
|
||||
def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring_soon=10, rounded_time=False, just_check_domain=False):
|
||||
# Check that the ssl_certificate & ssl_private_key files are good
|
||||
# for the provided domain.
|
||||
|
||||
@@ -734,7 +585,7 @@ def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring
|
||||
# We'll renew it with Lets Encrypt.
|
||||
expiry_info = "The certificate expires on %s." % cert_expiration_date.strftime("%x")
|
||||
|
||||
if ndays <= 10 and warn_if_expiring_soon:
|
||||
if warn_if_expiring_soon and ndays <= warn_if_expiring_soon:
|
||||
# Warn on day 10 to give 4 days for us to automatically renew the
|
||||
# certificate, which occurs on day 14.
|
||||
return ("The certificate is expiring soon: " + expiry_info, None)
|
||||
|
||||
@@ -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
|
||||
# TLS certificates have been signed, etc., and if not tells the user
|
||||
@@ -28,7 +28,6 @@ def get_services():
|
||||
{ "name": "Spamassassin", "port": 10025, "public": False, },
|
||||
{ "name": "OpenDKIM", "port": 8891, "public": False, },
|
||||
{ "name": "OpenDMARC", "port": 8893, "public": False, },
|
||||
{ "name": "Memcached", "port": 11211, "public": False, },
|
||||
{ "name": "Mail-in-a-Box Management Daemon", "port": 10222, "public": False, },
|
||||
{ "name": "SSH Login (ssh)", "port": get_ssh_port(), "public": True, },
|
||||
{ "name": "Public DNS (nsd4)", "port": 53, "public": True, },
|
||||
@@ -393,7 +392,7 @@ def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles):
|
||||
|
||||
# Check that PRIMARY_HOSTNAME resolves to PUBLIC_IP[V6] in public DNS.
|
||||
ipv6 = query_dns(domain, "AAAA") if env.get("PUBLIC_IPV6") else None
|
||||
if ip == env['PUBLIC_IP'] and not (ipv6 and env['PUBLIC_IPV6'] and normalize_ip(ipv6) != normalize_ip(env['PUBLIC_IPV6'])):
|
||||
if ip == env['PUBLIC_IP'] and not (ipv6 and env['PUBLIC_IPV6'] and ipv6 != normalize_ip(env['PUBLIC_IPV6'])):
|
||||
output.print_ok("Domain resolves to box's IP address. [%s ↦ %s]" % (env['PRIMARY_HOSTNAME'], my_ips))
|
||||
else:
|
||||
output.print_error("""This domain must resolve to your box's IP address (%s) in public DNS but it currently resolves
|
||||
@@ -487,10 +486,12 @@ def check_dns_zone(domain, env, output, dns_zonefiles):
|
||||
if custom_secondary_ns and not probably_external_dns:
|
||||
for ns in custom_secondary_ns:
|
||||
# We must first resolve the nameserver to an IP address so we can query it.
|
||||
ns_ip = query_dns(ns, "A")
|
||||
if not ns_ip:
|
||||
ns_ips = query_dns(ns, "A")
|
||||
if not ns_ips:
|
||||
output.print_error("Secondary nameserver %s is not valid (it doesn't resolve to an IP address)." % ns)
|
||||
continue
|
||||
# Choose the first IP if nameserver returns multiple
|
||||
ns_ip = ns_ips.split('; ')[0]
|
||||
|
||||
# Now query it to see what it says about this domain.
|
||||
ip = query_dns(domain, "A", at=ns_ip, nxdomain=None)
|
||||
@@ -640,7 +641,7 @@ def check_web_domain(domain, rounded_time, ssl_certificates, env, output):
|
||||
for (rtype, expected) in (("A", env['PUBLIC_IP']), ("AAAA", env.get('PUBLIC_IPV6'))):
|
||||
if not expected: continue # IPv6 is not configured
|
||||
value = query_dns(domain, rtype)
|
||||
if value == expected:
|
||||
if value == normalize_ip(expected):
|
||||
ok_values.append(value)
|
||||
else:
|
||||
output.print_error("""This domain should resolve to your box's IP address (%s %s) if you would like the box to serve
|
||||
@@ -687,27 +688,17 @@ def query_dns(qname, rtype, nxdomain='[Not Set]', at=None):
|
||||
except dns.exception.Timeout:
|
||||
return "[timeout]"
|
||||
|
||||
# Normalize IP addresses. IP address --- especially IPv6 addresses --- can
|
||||
# be expressed in equivalent string forms. Canonicalize the form before
|
||||
# returning them. The caller should normalize any IP addresses the result
|
||||
# of this method is compared with.
|
||||
if rtype in ("A", "AAAA"):
|
||||
response = [normalize_ip(str(r)) for r in response]
|
||||
|
||||
# There may be multiple answers; concatenate the response. Remove trailing
|
||||
# periods from responses since that's how qnames are encoded in DNS but is
|
||||
# confusing for us. The order of the answers doesn't matter, so sort so we
|
||||
# can compare to a well known order.
|
||||
|
||||
# Unfortunately, the response.__str__ returns bytes
|
||||
# instead of string, if it resulted from an AAAA-query.
|
||||
# We need to convert manually, until this is fixed:
|
||||
# https://github.com/rthalley/dnspython/issues/204
|
||||
#
|
||||
# BEGIN HOTFIX
|
||||
response_new = []
|
||||
for r in response:
|
||||
s = r.to_text()
|
||||
if isinstance(s, bytes):
|
||||
s = s.decode('utf-8')
|
||||
response_new.append(s)
|
||||
|
||||
response = response_new
|
||||
# END HOTFIX
|
||||
|
||||
return "; ".join(sorted(str(r).rstrip('.') for r in response))
|
||||
|
||||
def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output):
|
||||
@@ -805,14 +796,14 @@ def get_latest_miab_version():
|
||||
def check_miab_version(env, output):
|
||||
config = load_settings(env)
|
||||
|
||||
if config.get("privacy", True):
|
||||
output.print_warning("Mail-in-a-Box version check disabled by privacy setting.")
|
||||
else:
|
||||
try:
|
||||
this_ver = what_version_is_this(env)
|
||||
except:
|
||||
this_ver = "Unknown"
|
||||
|
||||
if config.get("privacy", True):
|
||||
output.print_warning("You are running version Mail-in-a-Box %s. Mail-in-a-Box version check disabled by privacy setting." % this_ver)
|
||||
else:
|
||||
latest_ver = get_latest_miab_version()
|
||||
|
||||
if this_ver == latest_ver:
|
||||
@@ -892,9 +883,14 @@ def run_and_output_changes(env, pool):
|
||||
json.dump(cur.buf, f, indent=True)
|
||||
|
||||
def normalize_ip(ip):
|
||||
# Use ipaddress module to normalize the IPv6 notation and ensure we are matching IPv6 addresses written in different representations according to rfc5952.
|
||||
# Use ipaddress module to normalize the IPv6 notation and
|
||||
# ensure we are matching IPv6 addresses written in different
|
||||
# representations according to rfc5952.
|
||||
import ipaddress
|
||||
try:
|
||||
return str(ipaddress.ip_address(ip))
|
||||
except:
|
||||
return ip
|
||||
|
||||
class FileOutput:
|
||||
def __init__(self, buf, width):
|
||||
|
||||
@@ -39,8 +39,9 @@
|
||||
<label for="addaliasForwardsTo" class="col-sm-1 control-label">Forwards To</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea class="form-control" rows="3" id="addaliasForwardsTo"></textarea>
|
||||
<div style="margin-top: 3px; padding-left: 3px; font-size: 90%" class="text-muted">
|
||||
<span class="domainalias">Enter just the part of an email address starting with the @-sign.</span>
|
||||
<div style="margin-top: 3px; padding-left: 3px; font-size: 90%">
|
||||
<span class="domainalias text-muted">Enter just the part of an email address starting with the @-sign.</span>
|
||||
<span class="text-danger">Only forward mail to addresses handled by this Mail-in-a-Box, since mail forwarded by aliases to other domains may be rejected or filtered by the receiver. To forward mail to other domains, create a mail user and then log into webmail for the user and create a filter rule to forward mail.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,7 +51,7 @@
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input id="addaliasForwardsToNotAdvanced" name="addaliasForwardsToDivToggle" type="radio" checked onclick="$('#addaliasForwardsToDiv').toggle(false)">
|
||||
Any mail user listed in the Fowards To box can send mail claiming to be from <span class="regularalias">the alias address</span><span class="catchall domainalias">any address on the alias domain</span>.
|
||||
Any mail user listed in the Forwards To box can send mail claiming to be from <span class="regularalias">the alias address</span><span class="catchall domainalias">any address on the alias domain</span>.
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
|
||||
@@ -31,13 +31,15 @@
|
||||
<label for="customdnsType" class="col-sm-1 control-label">Type</label>
|
||||
<div class="col-sm-10">
|
||||
<select id="customdnsType" class="form-control" style="max-width: 400px" onchange="show_customdns_rtype_hint()">
|
||||
<option value="A" data-hint="Enter an IPv4 address (i.e. a dotted quad, such as 123.456.789.012).">A (IPv4 address)</option>
|
||||
<option value="AAAA" data-hint="Enter an IPv6 address.">AAAA (IPv6 address)</option>
|
||||
<option value="A" data-hint="Enter an IPv4 address (i.e. a dotted quad, such as 123.456.789.012). The 'local' alias sets the record to this box's public IPv4 address.">A (IPv4 address)</option>
|
||||
<option value="AAAA" data-hint="Enter an IPv6 address. The 'local' alias sets the record to this box's public IPv6 address.">AAAA (IPv6 address)</option>
|
||||
<option value="CAA" data-hint="Enter a CA that can issue certificates for this domain in the form of FLAG TAG VALUE. (0 issuewild "letsencrypt.org")">CAA (Certificate Authority Authorization)</option>
|
||||
<option value="CNAME" data-hint="Enter another domain name followed by a period at the end (e.g. mypage.github.io.).">CNAME (DNS forwarding)</option>
|
||||
<option value="TXT" data-hint="Enter arbitrary text.">TXT (text record)</option>
|
||||
<option value="MX" data-hint="Enter record in the form of PRIORITY DOMAIN., including trailing period (e.g. 20 mx.example.com.).">MX (mail exchanger)</option>
|
||||
<option value="SRV" data-hint="Enter record in the form of PRIORITY WEIGHT PORT TARGET., including trailing period (e.g. 10 10 5060 sip.example.com.).">SRV (service record)</option>
|
||||
<option value="SSHFP" data-hint="Enter record in the form of ALGORITHM TYPE FINGERPRINT.">SSHFP (SSH fingerprint record)</option>
|
||||
<option value="NS" data-hint="Enter a hostname to which this subdomain should be delegated to">NS (DNS subdomain delegation)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,7 +90,7 @@
|
||||
<div class="col-sm-offset-1 col-sm-11">
|
||||
<p class="small">
|
||||
Multiple secondary servers can be separated with commas or spaces (i.e., <code>ns2.hostingcompany.com ns3.hostingcompany.com</code>).
|
||||
To enable zone transfers to additional servers without listing them as secondary nameservers, add <code>xfr:IPADDRESS</code>.
|
||||
To enable zone transfers to additional servers without listing them as secondary nameservers, add an IP address or subnet using <code>xfr:10.20.30.40</code> or <code>xfr:10.20.30.40/24</code>.
|
||||
</p>
|
||||
<p id="secondarydns-clear-instructions" style="display: none" class="small">
|
||||
Clear the input field above and click Update to use this machine itself as secondary DNS, which is the default/normal setup.
|
||||
@@ -125,7 +127,7 @@
|
||||
<tr><td>email</td> <td>The email address of any administrative user here.</td></tr>
|
||||
<tr><td>password</td> <td>That user’s password.</td></tr>
|
||||
<tr><td>qname</td> <td>The fully qualified domain name for the record you are trying to set. It must be one of the domain names or a subdomain of one of the domain names hosted on this box. (Add mail users or aliases to add new domains.)</td></tr>
|
||||
<tr><td>rtype</td> <td>The resource type. Defaults to <code>A</code> if omitted. Possible values: <code>A</code> (an IPv4 address), <code>AAAA</code> (an IPv6 address), <code>TXT</code> (a text string), <code>CNAME</code> (an alias, which is a fully qualified domain name — don’t forget the final period), <code>MX</code>, <code>SRV</code>, or <code>SSHFP</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 — don’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’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>
|
||||
|
||||
@@ -191,6 +193,22 @@ function show_current_custom_dns() {
|
||||
else
|
||||
$('#custom-dns-current').fadeOut();
|
||||
|
||||
var reverse_fqdn = function(el) {
|
||||
el.qname = el.qname.split('.').reverse().join('.');
|
||||
return el;
|
||||
}
|
||||
var sort = function(a, b) {
|
||||
if(a.qname === b.qname) {
|
||||
if(a.rtype === b.rtype) {
|
||||
return a.value > b.value ? 1 : -1;
|
||||
}
|
||||
return a.rtype > b.rtype ? 1 : -1;
|
||||
}
|
||||
return a.qname > b.qname ? 1 : -1;
|
||||
}
|
||||
|
||||
data = data.map(reverse_fqdn).sort(sort).map(reverse_fqdn);
|
||||
|
||||
$('#custom-dns-current').find("tbody").text('');
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var tr = $("<tr/>");
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<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>
|
||||
body {
|
||||
overflow-y: scroll;
|
||||
@@ -63,7 +63,7 @@
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
</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>
|
||||
<body>
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
<li><a href="#web" onclick="return show_panel(this);">Web</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li><a href="#" onclick="do_logout(); return false;" style="color: white">Log out?</a></li>
|
||||
<li><a href="#" onclick="do_logout(); return false;" style="color: white">Log out</a></li>
|
||||
</ul>
|
||||
</div><!--/.navbar-collapse -->
|
||||
</div>
|
||||
@@ -191,8 +191,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js" integrity="sha256-rsPUGdUPBXgalvIj4YKJrrUlmLXbOb6Cp7cdxn1qeUc=" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||
<script src="/admin/assets/jquery.min.js"></script>
|
||||
<script src="/admin/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
|
||||
<script>
|
||||
var global_modal_state = null;
|
||||
|
||||
@@ -23,7 +23,7 @@ sudo tools/mail.py user make-admin me@{{hostname}}</pre>
|
||||
<p style="margin: 2em; text-align: center;">Log in here for your Mail-in-a-Box control panel.</p>
|
||||
|
||||
<div style="margin: 0 auto; max-width: 32em;">
|
||||
<form class="form-horizontal" role="form" onsubmit="do_login(); return false;">
|
||||
<form class="form-horizontal" role="form" onsubmit="do_login(); return false;" method="get">
|
||||
<div class="form-group">
|
||||
<label for="inputEmail3" class="col-sm-3 control-label">Email</label>
|
||||
<div class="col-sm-9">
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h4>Greylisting</h4>
|
||||
<p>Your box using a technique called greylisting to cut down on spam. Greylisting works by delaying mail from people you haven’t received mail from before for up to about 10 minutes. The vast majority of spam gets tricked by this. If you are waiting for an email from someone new, such as if you are registering on a new website and are waiting for an email confirmation, please give it up to 10-15 minutes to arrive.</p>
|
||||
<p>Your box uses a technique called greylisting to cut down on spam. Greylisting works by initially rejecting mail from people you haven’t received mail from before. Legitimate mail servers will attempt redelivery shortly afterwards, but the vast majority of spam gets tricked by this. If you are waiting for an email from someone new, such as if you are registering on a new website and are waiting for an email confirmation, please be aware there will be a minimum of 3 minutes delay, depending how soon the remote server attempts redelivery.</p>
|
||||
|
||||
<h4>+tag addresses</h4>
|
||||
<p>Every incoming email address also receives mail for <code>+tag</code> addresses. If your email address is <code>you@yourdomain.com</code>, you’ll also automatically get mail sent to <code>you+anythinghere@yourdomain.com</code>. Use this as a fast way to segment incoming mail for your own filtering rules without having to create aliases in this control panel.</p>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<p>You need a TLS certificate for this box’s hostname ({{hostname}}) and every other domain name and subdomain that this box is hosting a website for (see the list below).</p>
|
||||
|
||||
<div id="ssl_provision">
|
||||
<h3>Provision a certificate</h3>
|
||||
<h3>Provision certificates</h3>
|
||||
|
||||
<div id="ssl_provision_p" style="display: none; margin-top: 1.5em">
|
||||
<button onclick='return provision_tls_cert();' class='btn btn-primary' style="float: left; margin: 0 1.5em 1em 0;">Provision</button>
|
||||
@@ -19,21 +19,6 @@
|
||||
<div class="clearfix"> </div>
|
||||
|
||||
<div id="ssl_provision_result"></div>
|
||||
|
||||
<div id="ssl_provision_problems_div" style="display: none;">
|
||||
<p style="margin-bottom: .5em;">Certificates cannot be automatically provisioned for:</p>
|
||||
<table id="ssl_provision_problems" style="margin-top: 0;" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<th>Problem</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Use the <em>Install Certificate</em> button below for these domains.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Certificate status</h3>
|
||||
@@ -103,24 +88,12 @@ function show_tls(keep_provisioning_shown) {
|
||||
// provisioning status
|
||||
|
||||
if (!keep_provisioning_shown)
|
||||
$('#ssl_provision').toggle(res.can_provision.length + res.cant_provision.length > 0)
|
||||
$('#ssl_provision').toggle(res.can_provision.length > 0)
|
||||
|
||||
$('#ssl_provision_p').toggle(res.can_provision.length > 0);
|
||||
if (res.can_provision.length > 0)
|
||||
$('#ssl_provision_p span').text(res.can_provision.join(", "));
|
||||
|
||||
$('#ssl_provision_problems_div').toggle(res.cant_provision.length > 0);
|
||||
$('#ssl_provision_problems tbody').text("");
|
||||
for (var i = 0; i < res.cant_provision.length; i++) {
|
||||
var domain = res.cant_provision[i];
|
||||
var row = $("<tr><th class='domain'><a href=''></a></th><td class='status'></td></tr>");
|
||||
$('#ssl_provision_problems tbody').append(row);
|
||||
row.attr('data-domain', domain.domain);
|
||||
row.find('.domain a').text(domain.domain);
|
||||
row.find('.domain a').attr('href', 'https://' + domain.domain);
|
||||
row.find('.status').text(domain.problem);
|
||||
}
|
||||
|
||||
// certificate status
|
||||
var domains = res.status;
|
||||
var tb = $('#ssl_domains tbody');
|
||||
@@ -159,7 +132,11 @@ function ssl_install(elem) {
|
||||
}
|
||||
|
||||
function show_csr() {
|
||||
// Can't show a CSR until both inputs are entered.
|
||||
if ($('#ssldomain').val() == "") return;
|
||||
if ($('#sslcc').val() == "") return;
|
||||
|
||||
// Scroll to it and fetch.
|
||||
$('#csr_info').slideDown();
|
||||
$('#ssl_csr').text('Loading...');
|
||||
api(
|
||||
@@ -192,20 +169,15 @@ function install_cert() {
|
||||
});
|
||||
}
|
||||
|
||||
var agree_to_tos_url_prompt = null;
|
||||
var agree_to_tos_url = null;
|
||||
function provision_tls_cert() {
|
||||
// Automatically provision any certs.
|
||||
$('#ssl_provision_p .btn').attr('disabled', '1'); // prevent double-clicks
|
||||
api(
|
||||
"/ssl/provision",
|
||||
"POST",
|
||||
{
|
||||
agree_to_tos_url: agree_to_tos_url
|
||||
},
|
||||
{ },
|
||||
function(status) {
|
||||
// Clear last attempt.
|
||||
agree_to_tos_url = null;
|
||||
$('#ssl_provision_result').text("");
|
||||
may_reenable_provision_button = true;
|
||||
|
||||
@@ -221,52 +193,33 @@ function provision_tls_cert() {
|
||||
for (var i = 0; i < status.requests.length; i++) {
|
||||
var r = status.requests[i];
|
||||
|
||||
if (r.result == "skipped") {
|
||||
// not interested --- this domain wasn't in the table
|
||||
// to begin with
|
||||
continue;
|
||||
}
|
||||
|
||||
// create an HTML block to display the results of this request
|
||||
var n = $("<div><h4/><p/></div>");
|
||||
$('#ssl_provision_result').append(n);
|
||||
|
||||
// plain log line
|
||||
if (typeof r === "string") {
|
||||
n.find("p").text(r);
|
||||
continue;
|
||||
}
|
||||
|
||||
// show a header only to disambiguate request blocks
|
||||
if (status.requests.length > 0)
|
||||
n.find("h4").text(r.domains.join(", "));
|
||||
|
||||
if (r.result == "agree-to-tos") {
|
||||
// user needs to agree to Let's Encrypt's TOS
|
||||
agree_to_tos_url_prompt = r.url;
|
||||
$('#ssl_provision_p .btn').attr('disabled', '1');
|
||||
n.find("p").html("Please open and review <a href='" + r.url + "' target='_blank'>Let's Encrypt's terms of service agreement</a>. You must agree to their terms for a certificate to be automatically provisioned from them.");
|
||||
n.append($('<button onclick="agree_to_tos_url = agree_to_tos_url_prompt; return provision_tls_cert();" class="btn btn-success" style="margin-left: 2em">Agree & Try Again</button>'));
|
||||
|
||||
// don't re-enable the Provision button -- user must use the Agree button
|
||||
may_reenable_provision_button = false;
|
||||
|
||||
} else if (r.result == "error") {
|
||||
if (r.result == "error") {
|
||||
n.find("p").addClass("text-danger").text(r.message);
|
||||
|
||||
} else if (r.result == "wait") {
|
||||
// Show a button that counts down to zero, at which point it becomes enabled.
|
||||
n.find("p").text("A certificate is now in the process of being provisioned, but it takes some time. Please wait until the Finish button is enabled, and then click it to acquire the certificate.");
|
||||
var b = $('<button onclick="return provision_tls_cert();" class="btn btn-success" style="margin-left: 2em">Finish</button>');
|
||||
b.attr("disabled", "1");
|
||||
var now = new Date();
|
||||
n.append(b);
|
||||
function ready_to_finish() {
|
||||
var remaining = Math.round(r.seconds - (new Date() - now)/1000);
|
||||
if (remaining > 0) {
|
||||
setTimeout(ready_to_finish, 1000);
|
||||
b.text("Finish (" + remaining + "...)")
|
||||
} else {
|
||||
b.text("Finish (ready)")
|
||||
b.removeAttr("disabled");
|
||||
}
|
||||
}
|
||||
ready_to_finish();
|
||||
|
||||
// don't re-enable the Provision button -- user must use the Retry button when it becomes enabled
|
||||
may_reenable_provision_button = false;
|
||||
|
||||
} else if (r.result == "installed") {
|
||||
n.find("p").addClass("text-success").text("The TLS certificate was provisioned and installed.");
|
||||
setTimeout("show_tls(true)", 1); // update main table of certificate statuses, call with arg keep_provisioning_shown true so that we don't clear what we just outputted
|
||||
|
||||
}
|
||||
|
||||
// display the detailed log info in case of problems
|
||||
@@ -274,7 +227,6 @@ function provision_tls_cert() {
|
||||
n.append(trace);
|
||||
for (var j = 0; j < r.log.length; j++)
|
||||
trace.append($("<div/>").text(r.log[j]));
|
||||
|
||||
}
|
||||
|
||||
if (may_reenable_provision_button)
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
<table class="table">
|
||||
<thead><tr><th>For...</th> <th>Use...</th></tr></thead>
|
||||
<tr><td>Contacts and Calendar</td> <td><a href="https://play.google.com/store/apps/details?id=at.bitfire.davdroid">DAVdroid</a> ($3.69; free <a href="https://f-droid.org/repository/browse/?fdfilter=dav&fdid=at.bitfire.davdroid">here</a>)</td></tr>
|
||||
<tr><td>Contacts and Calendar</td> <td><a href="https://play.google.com/store/apps/details?id=at.bitfire.davdroid">DAVdroid</a> ($3.69; free <a href="https://f-droid.org/packages/at.bitfire.davdroid/">here</a>)</td></tr>
|
||||
<tr><td>Only Contacts</td> <td><a href="https://play.google.com/store/apps/details?id=org.dmfs.carddav.sync">CardDAV-Sync free beta</a> (free)</td></tr>
|
||||
<tr><td>Only Calendar</td> <td><a href="https://play.google.com/store/apps/details?id=org.dmfs.caldav.lib">CalDAV-Sync</a> ($2.89)</td></tr>
|
||||
</table>
|
||||
|
||||
@@ -77,15 +77,22 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group backup-target-s3">
|
||||
<label for="backup-target-s3-host" class="col-sm-2 control-label">S3 Region</label>
|
||||
<label for="backup-target-s3-host-select" class="col-sm-2 control-label">S3 Region</label>
|
||||
<div class="col-sm-8">
|
||||
<select class="form-control" rows="1" id="backup-target-s3-host">
|
||||
<select class="form-control" rows="1" id="backup-target-s3-host-select">
|
||||
{% for name, host in backup_s3_hosts %}
|
||||
<option value="{{host}}">{{name}}</option>
|
||||
{% endfor %}
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group backup-target-s3">
|
||||
<label for="backup-target-s3-host" class="col-sm-2 control-label">S3 Host / Endpoint</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" placeholder="Endpoint" class="form-control" rows="1" id="backup-target-s3-host">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group backup-target-s3">
|
||||
<label for="backup-target-s3-path" class="col-sm-2 control-label">S3 Path</label>
|
||||
<div class="col-sm-8">
|
||||
@@ -139,6 +146,8 @@ function toggle_form() {
|
||||
var target_type = $("#backup-target-type").val();
|
||||
$(".backup-target-local, .backup-target-rsync, .backup-target-s3").hide();
|
||||
$(".backup-target-" + target_type).show();
|
||||
|
||||
init_inputs(target_type);
|
||||
}
|
||||
|
||||
function nice_size(bytes) {
|
||||
@@ -200,6 +209,7 @@ function show_system_backup() {
|
||||
total_disk_size += b.size;
|
||||
}
|
||||
|
||||
total_disk_size += r.unmatched_file_size;
|
||||
$('#backup-total-size').text(nice_size(total_disk_size));
|
||||
})
|
||||
}
|
||||
@@ -277,4 +287,20 @@ function set_custom_backup() {
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function init_inputs(target_type) {
|
||||
function set_host(host) {
|
||||
if(host !== 'other') {
|
||||
$("#backup-target-s3-host").val(host);
|
||||
} else {
|
||||
$("#backup-target-s3-host").val('');
|
||||
}
|
||||
}
|
||||
if (target_type == "s3") {
|
||||
$('#backup-target-s3-host-select').off('change').on('change', function() {
|
||||
set_host($('#backup-target-s3-host-select').val());
|
||||
});
|
||||
set_host($('#backup-target-s3-host-select').val());
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<button type="submit" class="btn btn-primary">Add User</button>
|
||||
</form>
|
||||
<ul style="margin-top: 1em; padding-left: 1.5em; font-size: 90%;">
|
||||
<li>Passwords must be at least eight characters and may not contain spaces. For best results, <a href="#" onclick="return generate_random_password()">generate a random password</a>.</li>
|
||||
<li>Passwords must be at least eight characters consisting of English lettters and numbers only. For best results, <a href="#" onclick="return generate_random_password()">generate a random password</a>.</li>
|
||||
<li>Use <a href="#" onclick="return show_panel('aliases')">aliases</a> to create email addresses that forward to existing accounts.</li>
|
||||
<li>Administrators get access to this control panel.</li>
|
||||
<li>User accounts cannot contain any international (non-ASCII) characters, but <a href="#" onclick="return show_panel('aliases');">aliases</a> can.</li>
|
||||
@@ -43,7 +43,6 @@
|
||||
<tr>
|
||||
<th width="50%">Email Address</th>
|
||||
<th>Actions</th>
|
||||
<th>Mailbox Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -73,8 +72,6 @@
|
||||
archive account
|
||||
</a>
|
||||
</td>
|
||||
<td class='mailboxsize'>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="user-extra-template" class="if_inactive">
|
||||
<td colspan="3" style="border: 0; padding-top: 0">
|
||||
@@ -102,7 +99,7 @@
|
||||
<thead><th>Verb</th> <th>Action</th><th></th></thead>
|
||||
<tr><td>GET</td><td><i>(none)</i></td> <td>Returns a list of existing mail users. Adding <code>?format=json</code> to the URL will give JSON-encoded results.</td></tr>
|
||||
<tr><td>POST</td><td>/add</td> <td>Adds a new mail user. Required POST-body parameters are <code>email</code> and <code>password</code>.</td></tr>
|
||||
<tr><td>POST</td><td>/remove</td> <td>Removes a mail user. Required POST-by parameter is <code>email</code>.</td></tr>
|
||||
<tr><td>POST</td><td>/remove</td> <td>Removes a mail user. Required POST-body parameter is <code>email</code>.</td></tr>
|
||||
<tr><td>POST</td><td>/privileges/add</td> <td>Used to make a mail user an admin. Required POST-body parameters are <code>email</code> and <code>privilege=admin</code>.</td></tr>
|
||||
<tr><td>POST</td><td>/privileges/remove</td> <td>Used to remove the admin privilege from a mail user. Required POST-body parameter is <code>email</code>.</td></tr>
|
||||
</table>
|
||||
@@ -156,7 +153,6 @@ function show_users() {
|
||||
|
||||
n.attr('data-email', user.email);
|
||||
n.find('.address').text(user.email)
|
||||
n.find('.mailboxsize').text(nice_size(user.mailbox_size))
|
||||
n2.find('.restore_info tt').text(user.mailbox);
|
||||
|
||||
if (user.status == 'inactive') continue;
|
||||
@@ -213,7 +209,7 @@ function users_set_password(elem) {
|
||||
|
||||
show_modal_confirm(
|
||||
"Set Password",
|
||||
$("<p>Set a new password for <b>" + email + "</b>?</p> <p><label for='users_set_password_pw' style='display: block; font-weight: normal'>New Password:</label><input type='password' id='users_set_password_pw'></p><p><small>Passwords must be at least four characters and may not contain spaces.</small>" + yourpw + "</p>"),
|
||||
$("<p>Set a new password for <b>" + email + "</b>?</p> <p><label for='users_set_password_pw' style='display: block; font-weight: normal'>New Password:</label><input type='password' id='users_set_password_pw'></p><p><small>Passwords must be at least eight characters and may not contain spaces.</small>" + yourpw + "</p>"),
|
||||
"Set Password",
|
||||
function() {
|
||||
api(
|
||||
|
||||
@@ -24,6 +24,12 @@ def get_web_domains(env, include_www_redirects=True, exclude_dns_elsewhere=True)
|
||||
# the topmost of each domain we serve.
|
||||
domains |= set('www.' + zone for zone, zonefile in get_dns_zones(env))
|
||||
|
||||
# Add Autoconfiguration domains, allowing us to serve correct SSL certs.
|
||||
# 'autoconfig.' for Mozilla Thunderbird auto setup.
|
||||
# 'autodiscover.' for Activesync autodiscovery.
|
||||
domains |= set('autoconfig.' + maildomain for maildomain in get_mail_domains(env))
|
||||
domains |= set('autodiscover.' + maildomain for maildomain in get_mail_domains(env))
|
||||
|
||||
if exclude_dns_elsewhere:
|
||||
# ...Unless the domain has an A/AAAA record that maps it to a different
|
||||
# IP address than this box. Remove those domains from our list.
|
||||
@@ -149,7 +155,14 @@ def make_domain_config(domain, templates, ssl_certificates, env):
|
||||
|
||||
# any proxy or redirect here?
|
||||
for path, url in yaml.get("proxies", {}).items():
|
||||
nginx_conf_extra += "\tlocation %s {\n\t\tproxy_pass %s;\n\t}\n" % (path, url)
|
||||
nginx_conf_extra += "\tlocation %s {" % path
|
||||
nginx_conf_extra += "\n\t\tproxy_pass %s;" % url
|
||||
nginx_conf_extra += "\n\t\tproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;"
|
||||
nginx_conf_extra += "\n\t}\n"
|
||||
for path, alias in yaml.get("aliases", {}).items():
|
||||
nginx_conf_extra += "\tlocation %s {" % path
|
||||
nginx_conf_extra += "\n\t\talias %s;" % alias
|
||||
nginx_conf_extra += "\n\t}\n"
|
||||
for path, url in yaml.get("redirects", {}).items():
|
||||
nginx_conf_extra += "\trewrite %s %s permanent;\n" % (path, url)
|
||||
|
||||
@@ -158,9 +171,9 @@ def make_domain_config(domain, templates, ssl_certificates, env):
|
||||
|
||||
# Add the HSTS header.
|
||||
if hsts == "yes":
|
||||
nginx_conf_extra += "add_header Strict-Transport-Security max-age=31536000;\n"
|
||||
nginx_conf_extra += "add_header Strict-Transport-Security max-age=15768000;\n"
|
||||
elif hsts == "preload":
|
||||
nginx_conf_extra += "add_header Strict-Transport-Security \"max-age=10886400; includeSubDomains; preload\";\n"
|
||||
nginx_conf_extra += "add_header Strict-Transport-Security \"max-age=15768000; includeSubDomains; preload\";\n"
|
||||
|
||||
# Add in any user customizations in the includes/ folder.
|
||||
nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf")
|
||||
@@ -198,8 +211,11 @@ def get_web_domains_info(env):
|
||||
|
||||
# for the SSL config panel, get cert status
|
||||
def check_cert(domain):
|
||||
try:
|
||||
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"])
|
||||
if cert_status == "OK":
|
||||
return ("success", "Signed & valid. " + cert_status_details)
|
||||
|
||||
62
ppa/Makefile
62
ppa/Makefile
@@ -1,62 +0,0 @@
|
||||
POSTGREY_VERSION=1.35-1+miab1
|
||||
DOVECOT_VERSION=2.2.9-1ubuntu2.1+miab1
|
||||
|
||||
all: clean build_postgrey build_dovecot_lucene
|
||||
|
||||
clean:
|
||||
# Clean.
|
||||
rm -rf /tmp/build
|
||||
mkdir -p /tmp/build
|
||||
|
||||
build_postgrey: clean
|
||||
# Download the latest Debian postgrey package. It is ahead of Ubuntu,
|
||||
# and we might as well jump ahead.
|
||||
git clone git://git.debian.org/git/collab-maint/postgrey.git /tmp/build/postgrey
|
||||
|
||||
# Download the corresponding upstream package.
|
||||
wget -O /tmp/build/postgrey_1.35.orig.tar.gz http://postgrey.schweikert.ch/pub/postgrey-1.35.tar.gz
|
||||
|
||||
# Add our source patch to the debian packaging listing.
|
||||
cp postgrey_sources.diff /tmp/build/postgrey/debian/patches/mailinabox
|
||||
|
||||
# Patch the packaging to give it a new version.
|
||||
patch -p1 -d /tmp/build/postgrey < postgrey.diff
|
||||
|
||||
# Build the source package.
|
||||
(cd /tmp/build/postgrey; dpkg-buildpackage -S -us -uc -nc)
|
||||
|
||||
# Sign the packages.
|
||||
debsign /tmp/build/postgrey_$(POSTGREY_VERSION)_source.changes
|
||||
|
||||
# Upload to PPA.
|
||||
dput ppa:mail-in-a-box/ppa /tmp/build/postgrey_$(POSTGREY_VERSION)_source.changes
|
||||
|
||||
# Clear the intermediate files.
|
||||
rm -rf /tmp/build/postgrey
|
||||
|
||||
# TESTING BINARY PACKAGE
|
||||
#sudo apt-get build-dep -y postgrey
|
||||
#(cd /tmp/build/postgrey; dpkg-buildpackage -us -uc -nc)
|
||||
|
||||
build_dovecot_lucene: clean
|
||||
# Get the upstream source.
|
||||
(cd /tmp/build; apt-get source dovecot)
|
||||
|
||||
# Patch it so that we build dovecot-lucene (and nothing else).
|
||||
patch -p1 -d /tmp/build/dovecot-2.2.9 < dovecot_lucene.diff
|
||||
|
||||
# Build the source package.
|
||||
(cd /tmp/build/dovecot-2.2.9; dpkg-buildpackage -S -us -uc -nc)
|
||||
|
||||
# Sign the packages.
|
||||
debsign /tmp/build/dovecot_$(DOVECOT_VERSION)_source.changes
|
||||
|
||||
# Upload it.
|
||||
dput ppa:mail-in-a-box/ppa /tmp/build/dovecot_$(DOVECOT_VERSION)_source.changes
|
||||
|
||||
# TESTING BINARY PACKAGE
|
||||
# Install build dependencies and build dependencies we've added in our patch,
|
||||
# and then build the binary package.
|
||||
#sudo apt-get build-dep -y dovecot
|
||||
#sudo apt-get install libclucene-dev liblzma-dev libexttextcat-dev libstemmer-dev
|
||||
#(cd /tmp/build/dovecot-2.2.9; dpkg-buildpackage -us -uc -nc)
|
||||
@@ -1,40 +0,0 @@
|
||||
ppa instructions
|
||||
================
|
||||
|
||||
Mail-in-a-Box maintains a Launchpad.net PPA ([Mail-in-a-Box PPA](https://launchpad.net/~mail-in-a-box/+archive/ubuntu/ppa)) for additional deb's that we want to have installed on systems.
|
||||
|
||||
Packages
|
||||
--------
|
||||
|
||||
* postgrey, a fork of [postgrey](http://postgrey.schweikert.ch/) based on the [latest Debian package](http://git.debian.org/?p=collab-maint/postgrey.git), with a modification to whitelist senders that are whitelisted by [dnswl.org](https://www.dnswl.org/) (i.e. don't greylist mail from known good senders).
|
||||
|
||||
* dovecot-lucene, [dovecot's lucene full text search plugin](http://wiki2.dovecot.org/Plugins/FTS/Lucene), which isn't built by Ubuntu's dovecot package maintainer unfortunately.
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
To rebuild the packages in the PPA, you'll need to be @JoshData.
|
||||
|
||||
First:
|
||||
|
||||
* You should have an account on Launchpad.net.
|
||||
* Your account should have your GPG key set (to the fingerprint of a GPG key on your system matching the identity at the top of the debian/changelog files).
|
||||
* You should have write permission to the PPA.
|
||||
|
||||
To build:
|
||||
|
||||
# Start a clean VM.
|
||||
vagrant up
|
||||
|
||||
# Put your signing keys (on the host machine) into the VM (so it can sign the debs).
|
||||
gpg --export-secret-keys | vagrant ssh -- gpg --import
|
||||
|
||||
# Build & upload to launchpad.
|
||||
vagrant ssh -- "cd /vagrant && make"
|
||||
|
||||
Mail-in-a-Box adds our PPA during setup, but if you need to do that yourself for testing:
|
||||
|
||||
apt-add-repository ppa:mail-in-a-box/ppa
|
||||
apt-get update
|
||||
apt-get install postgrey dovecot-lucene
|
||||
|
||||
12
ppa/Vagrantfile
vendored
12
ppa/Vagrantfile
vendored
@@ -1,12 +0,0 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "ubuntu14.04"
|
||||
config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"
|
||||
|
||||
config.vm.provision :shell, :inline => <<-SH
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y git dpkg-dev devscripts dput
|
||||
SH
|
||||
end
|
||||
@@ -1,319 +0,0 @@
|
||||
--- a/debian/control
|
||||
+++ b/debian/control
|
||||
@@ -1,210 +1,23 @@
|
||||
Source: dovecot
|
||||
Section: mail
|
||||
Priority: optional
|
||||
-Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
|
||||
-XSBC-Original-Maintainer: Dovecot Maintainers <jaldhar-dovecot@debian.org>
|
||||
-Uploaders: Jaldhar H. Vyas <jaldhar@debian.org>, Fabio Tranchitella <kobold@debian.org>, Joel Johnson <mrjoel@lixil.net>, Marco Nenciarini <mnencia@debian.org>
|
||||
-Build-Depends: debhelper (>= 7.2.3~), dpkg-dev (>= 1.16.1), pkg-config, libssl-dev, libpam0g-dev, libldap2-dev, libpq-dev, libmysqlclient-dev, libsqlite3-dev, libsasl2-dev, zlib1g-dev, libkrb5-dev, drac-dev (>= 1.12-5), libbz2-dev, libdb-dev, libcurl4-gnutls-dev, libexpat-dev, libwrap0-dev, dh-systemd, po-debconf, lsb-release, hardening-wrapper, dh-autoreconf, autotools-dev
|
||||
+Maintainer: Joshua Tauberer <jt@occams.info>
|
||||
+XSBC-Original-Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
|
||||
+Build-Depends: debhelper (>= 7.2.3~), dpkg-dev (>= 1.16.1), pkg-config, libssl-dev, libpam0g-dev, libldap2-dev, libpq-dev, libmysqlclient-dev, libsqlite3-dev, libsasl2-dev, zlib1g-dev, libkrb5-dev, drac-dev (>= 1.12-5), libbz2-dev, libdb-dev, libcurl4-gnutls-dev, libexpat-dev, libwrap0-dev, dh-systemd, po-debconf, lsb-release, libclucene-dev (>= 2.3), liblzma-dev, libexttextcat-dev, libstemmer-dev, hardening-wrapper, dh-autoreconf, autotools-dev
|
||||
Standards-Version: 3.9.4
|
||||
Homepage: http://dovecot.org/
|
||||
-Vcs-Git: git://git.debian.org/git/collab-maint/dovecot.git
|
||||
-Vcs-Browser: http://git.debian.org/?p=collab-maint/dovecot.git
|
||||
+Vcs-Git: https://github.com/mail-in-a-box/mailinabox
|
||||
+Vcs-Browser: https://github.com/mail-in-a-box/mailinabox
|
||||
|
||||
-Package: dovecot-core
|
||||
+Package: dovecot-lucene
|
||||
Architecture: any
|
||||
-Depends: ${shlibs:Depends}, ${misc:Depends}, libpam-runtime (>= 0.76-13.1), openssl, adduser, ucf (>= 2.0020), ssl-cert (>= 1.0-11ubuntu1), lsb-base (>= 3.2-12ubuntu3)
|
||||
-Suggests: ntp, dovecot-gssapi, dovecot-sieve, dovecot-pgsql, dovecot-mysql, dovecot-sqlite, dovecot-ldap, dovecot-imapd, dovecot-pop3d, dovecot-lmtpd, dovecot-managesieved, dovecot-solr, ufw
|
||||
-Recommends: ntpdate
|
||||
-Provides: dovecot-common
|
||||
-Replaces: dovecot-common (<< 1:2.0.14-2~), mailavenger (<< 0.8.1-4)
|
||||
-Breaks: dovecot-common (<< 1:2.0.14-2~), mailavenger (<< 0.8.1-4)
|
||||
-Description: secure POP3/IMAP server - core files
|
||||
+Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (>= 1:2.2.9-1ubuntu2.1)
|
||||
+Description: secure POP3/IMAP server - Lucene support
|
||||
Dovecot is a mail server whose major goals are security and extreme
|
||||
reliability. It tries very hard to handle all error conditions and verify
|
||||
that all data is valid, making it nearly impossible to crash. It supports
|
||||
mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
|
||||
fast, extensible, and portable.
|
||||
.
|
||||
- This package contains the Dovecot main server and its command line utility.
|
||||
-
|
||||
-Package: dovecot-dev
|
||||
-Architecture: any
|
||||
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version})
|
||||
-Replaces: dovecot-common (<< 1:2.0.14-2~)
|
||||
-Breaks: dovecot-common (<< 1:2.0.14-2~)
|
||||
-Description: secure POP3/IMAP server - header files
|
||||
- Dovecot is a mail server whose major goals are security and extreme
|
||||
- reliability. It tries very hard to handle all error conditions and verify
|
||||
- that all data is valid, making it nearly impossible to crash. It supports
|
||||
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
|
||||
- fast, extensible, and portable.
|
||||
- .
|
||||
- This package contains header files needed to compile plugins for the Dovecot
|
||||
- mail server.
|
||||
-
|
||||
-Package: dovecot-imapd
|
||||
-Architecture: any
|
||||
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version}), ucf (>= 2.0020)
|
||||
-Provides: imap-server
|
||||
-Description: secure POP3/IMAP server - IMAP daemon
|
||||
- Dovecot is a mail server whose major goals are security and extreme
|
||||
- reliability. It tries very hard to handle all error conditions and verify
|
||||
- that all data is valid, making it nearly impossible to crash. It supports
|
||||
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
|
||||
- fast, extensible, and portable.
|
||||
- .
|
||||
- This package contains the Dovecot IMAP server.
|
||||
-
|
||||
-Package: dovecot-pop3d
|
||||
-Architecture: any
|
||||
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version}), ucf (>= 2.0020)
|
||||
-Provides: pop3-server
|
||||
-Description: secure POP3/IMAP server - POP3 daemon
|
||||
- Dovecot is a mail server whose major goals are security and extreme
|
||||
- reliability. It tries very hard to handle all error conditions and verify
|
||||
- that all data is valid, making it nearly impossible to crash. It supports
|
||||
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
|
||||
- fast, extensible, and portable.
|
||||
- .
|
||||
- This package contains the Dovecot POP3 server.
|
||||
-
|
||||
-Package: dovecot-lmtpd
|
||||
-Architecture: any
|
||||
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version}), ucf (>= 2.0020)
|
||||
-Replaces: dovecot-common (<< 1:2.0.14-2~)
|
||||
-Breaks: dovecot-common (<< 1:2.0.14-2~)
|
||||
-Description: secure POP3/IMAP server - LMTP server
|
||||
- Dovecot is a mail server whose major goals are security and extreme
|
||||
- reliability. It tries very hard to handle all error conditions and verify
|
||||
- that all data is valid, making it nearly impossible to crash. It supports
|
||||
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
|
||||
- fast, extensible, and portable.
|
||||
- .
|
||||
- This package contains the Dovecot LMTP server.
|
||||
-
|
||||
-Package: dovecot-managesieved
|
||||
-Architecture: any
|
||||
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version}), dovecot-sieve (= ${binary:Version}), ucf (>= 2.0020)
|
||||
-Replaces: dovecot-common (<< 1:2.0.14-2~)
|
||||
-Breaks: dovecot-common (<< 1:2.0.14-2~)
|
||||
-Description: secure POP3/IMAP server - ManageSieve server
|
||||
- Dovecot is a mail server whose major goals are security and extreme
|
||||
- reliability. It tries very hard to handle all error conditions and verify
|
||||
- that all data is valid, making it nearly impossible to crash. It supports
|
||||
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
|
||||
- fast, extensible, and portable.
|
||||
- .
|
||||
- This package contains the Dovecot ManageSieve server.
|
||||
-
|
||||
-Package: dovecot-pgsql
|
||||
-Architecture: any
|
||||
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version})
|
||||
-Description: secure POP3/IMAP server - PostgreSQL support
|
||||
- Dovecot is a mail server whose major goals are security and extreme
|
||||
- reliability. It tries very hard to handle all error conditions and verify
|
||||
- that all data is valid, making it nearly impossible to crash. It supports
|
||||
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
|
||||
- fast, extensible, and portable.
|
||||
- .
|
||||
- This package provides PostgreSQL support for Dovecot.
|
||||
-
|
||||
-Package: dovecot-mysql
|
||||
-Architecture: any
|
||||
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version})
|
||||
-Description: secure POP3/IMAP server - MySQL support
|
||||
- Dovecot is a mail server whose major goals are security and extreme
|
||||
- reliability. It tries very hard to handle all error conditions and verify
|
||||
- that all data is valid, making it nearly impossible to crash. It supports
|
||||
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
|
||||
- fast, extensible, and portable.
|
||||
- .
|
||||
- This package provides MySQL support for Dovecot.
|
||||
-
|
||||
-Package: dovecot-sqlite
|
||||
-Architecture: any
|
||||
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version})
|
||||
-Description: secure POP3/IMAP server - SQLite support
|
||||
- Dovecot is a mail server whose major goals are security and extreme
|
||||
- reliability. It tries very hard to handle all error conditions and verify
|
||||
- that all data is valid, making it nearly impossible to crash. It supports
|
||||
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
|
||||
- fast, extensible, and portable.
|
||||
- .
|
||||
- This package provides SQLite support for Dovecot.
|
||||
-
|
||||
-Package: dovecot-ldap
|
||||
-Architecture: any
|
||||
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version}), ucf (>= 2.0020)
|
||||
-Description: secure POP3/IMAP server - LDAP support
|
||||
- Dovecot is a mail server whose major goals are security and extreme
|
||||
- reliability. It tries very hard to handle all error conditions and verify
|
||||
- that all data is valid, making it nearly impossible to crash. It supports
|
||||
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
|
||||
- fast, extensible, and portable.
|
||||
- .
|
||||
- This package provides LDAP support for Dovecot.
|
||||
-
|
||||
-Package: dovecot-gssapi
|
||||
-Architecture: any
|
||||
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version})
|
||||
-Description: secure POP3/IMAP server - GSSAPI support
|
||||
- Dovecot is a mail server whose major goals are security and extreme
|
||||
- reliability. It tries very hard to handle all error conditions and verify
|
||||
- that all data is valid, making it nearly impossible to crash. It supports
|
||||
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
|
||||
- fast, extensible, and portable.
|
||||
- .
|
||||
- This package provides GSSAPI authentication support for Dovecot.
|
||||
-
|
||||
-Package: dovecot-sieve
|
||||
-Architecture: any
|
||||
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version}), ucf (>= 2.0020)
|
||||
-Description: secure POP3/IMAP server - Sieve filters support
|
||||
- Dovecot is a mail server whose major goals are security and extreme
|
||||
- reliability. It tries very hard to handle all error conditions and verify
|
||||
- that all data is valid, making it nearly impossible to crash. It supports
|
||||
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
|
||||
- fast, extensible, and portable.
|
||||
- .
|
||||
- This package provides Sieve filters support for Dovecot.
|
||||
-
|
||||
-Package: dovecot-solr
|
||||
-Architecture: any
|
||||
-Depends: ${shlibs:Depends}, ${misc:Depends}, dovecot-core (= ${binary:Version})
|
||||
-Description: secure POP3/IMAP server - Solr support
|
||||
- Dovecot is a mail server whose major goals are security and extreme
|
||||
- reliability. It tries very hard to handle all error conditions and verify
|
||||
- that all data is valid, making it nearly impossible to crash. It supports
|
||||
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
|
||||
- fast, extensible, and portable.
|
||||
- .
|
||||
- This package provides Solr full text search support for Dovecot.
|
||||
-
|
||||
-Package: dovecot-dbg
|
||||
-Section: debug
|
||||
-Priority: extra
|
||||
-Architecture: any
|
||||
-Depends: ${misc:Depends}, dovecot-core (= ${binary:Version})
|
||||
-Description: secure POP3/IMAP server - debug symbols
|
||||
- Dovecot is a mail server whose major goals are security and extreme
|
||||
- reliability. It tries very hard to handle all error conditions and verify
|
||||
- that all data is valid, making it nearly impossible to crash. It supports
|
||||
- mbox/Maildir and its own dbox/mdbox formats, and should also be pretty
|
||||
- fast, extensible, and portable.
|
||||
- .
|
||||
- This package contains debug symbols for Dovecot.
|
||||
-
|
||||
-Package: mail-stack-delivery
|
||||
-Architecture: all
|
||||
-Depends: dovecot-core, dovecot-imapd, dovecot-pop3d, dovecot-managesieved,
|
||||
- postfix, ${misc:Depends}
|
||||
-Replaces: dovecot-postfix (<< 1:1.2.12-0ubuntu1~)
|
||||
-Description: mail server delivery agent stack provided by Ubuntu server team
|
||||
- Ubuntu's mail stack provides fully operational delivery with
|
||||
- safe defaults and additional options. Out of the box it supports IMAP,
|
||||
- POP3 and SMTP services with SASL authentication and Maildir as default
|
||||
- storage engine.
|
||||
- .
|
||||
- This package contains configuration files for dovecot.
|
||||
- .
|
||||
- This package modifies postfix's configuration to integrate with dovecot
|
||||
+ This package provides Lucene full text search support for Dovecot. It has been modified by Mail-in-a-Box
|
||||
+ to supply a dovecot-lucene package compatible with the official ubuntu trusty dovecot-core.
|
||||
|
||||
diff --git a/debian/dovecot-lucene.links b/debian/dovecot-lucene.links
|
||||
new file mode 100644
|
||||
index 0000000..6ffcbeb
|
||||
--- /dev/null
|
||||
+++ b/debian/dovecot-lucene.links
|
||||
@@ -0,0 +1 @@
|
||||
+/usr/share/bug/dovecot-core /usr/share/bug/dovecot-lucene
|
||||
diff --git a/debian/dovecot-lucene.lintian-overrides b/debian/dovecot-lucene.lintian-overrides
|
||||
new file mode 100644
|
||||
index 0000000..60d90fd
|
||||
--- /dev/null
|
||||
+++ b/debian/dovecot-lucene.lintian-overrides
|
||||
@@ -0,0 +1,2 @@
|
||||
+dovecot-lucene: hardening-no-fortify-functions usr/lib/dovecot/modules/lib21_fts_lucene_plugin.so
|
||||
+
|
||||
diff --git a/debian/dovecot-lucene.substvars b/debian/dovecot-lucene.substvars
|
||||
new file mode 100644
|
||||
index 0000000..ed54f36
|
||||
--- /dev/null
|
||||
+++ b/debian/dovecot-lucene.substvars
|
||||
@@ -0,0 +1,2 @@
|
||||
+shlibs:Depends=libc6 (>= 2.4), libclucene-core1 (>= 2.3.3.4), libgcc1 (>= 1:4.1.1), libstdc++6 (>= 4.1.1), libstemmer0d (>= 0+svn527)
|
||||
+misc:Depends=
|
||||
diff --git a/debian/dovecot-lucene.triggers b/debian/dovecot-lucene.triggers
|
||||
new file mode 100644
|
||||
index 0000000..3d933a5
|
||||
--- /dev/null
|
||||
+++ b/debian/dovecot-lucene.triggers
|
||||
@@ -0,0 +1 @@
|
||||
+activate register-dovecot-plugin
|
||||
--- a/debian/rules
|
||||
+++ b/debian/rules
|
||||
@@ -40,6 +40,7 @@
|
||||
--with-solr \
|
||||
--with-ioloop=best \
|
||||
--with-libwrap \
|
||||
+ --with-lucene \
|
||||
--host=$(DEB_HOST_GNU_TYPE) \
|
||||
--build=$(DEB_BUILD_GNU_TYPE) \
|
||||
--prefix=/usr \
|
||||
@@ -95,6 +96,10 @@
|
||||
dh_testroot
|
||||
dh_clean -k
|
||||
dh_installdirs
|
||||
+ mkdir -p $(CURDIR)/debian/dovecot-lucene/usr/lib/dovecot/modules
|
||||
+ mv $(CURDIR)/src/plugins/fts-lucene/.libs/* $(CURDIR)/debian/dovecot-lucene/usr/lib/dovecot/modules/
|
||||
+
|
||||
+rest_disabled_by_miab:
|
||||
$(MAKE) install DESTDIR=$(CURDIR)/debian/dovecot-core
|
||||
$(MAKE) -C $(PIGEONHOLE_DIR) install DESTDIR=$(CURDIR)/debian/dovecot-core
|
||||
rm `find $(CURDIR)/debian -name '*.la'`
|
||||
@@ -209,7 +214,7 @@
|
||||
dh_installdocs -a
|
||||
dh_installexamples -a
|
||||
dh_installpam -a
|
||||
- mv $(CURDIR)/debian/dovecot-core/etc/pam.d/dovecot-core $(CURDIR)/debian/dovecot-core/etc/pam.d/dovecot
|
||||
+ # mv $(CURDIR)/debian/dovecot-core/etc/pam.d/dovecot-core $(CURDIR)/debian/dovecot-core/etc/pam.d/dovecot
|
||||
dh_systemd_enable
|
||||
dh_installinit -pdovecot-core --name=dovecot
|
||||
dh_systemd_start
|
||||
@@ -220,10 +225,10 @@
|
||||
dh_lintian -a
|
||||
dh_installchangelogs -a ChangeLog
|
||||
dh_link -a
|
||||
- dh_strip -a --dbg-package=dovecot-dbg
|
||||
+ #dh_strip -a --dbg-package=dovecot-dbg
|
||||
dh_compress -a
|
||||
dh_fixperms -a
|
||||
- chmod 0700 debian/dovecot-core/etc/dovecot/private
|
||||
+ #chmod 0700 debian/dovecot-core/etc/dovecot/private
|
||||
dh_makeshlibs -a -n
|
||||
dh_installdeb -a
|
||||
dh_shlibdeps -a
|
||||
--- a/debian/changelog
|
||||
+++ a/debian/changelog
|
||||
@@ -1,3 +1,9 @@
|
||||
+dovecot (1:2.2.9-1ubuntu2.1+miab1) trusty; urgency=low
|
||||
+
|
||||
+ * Changed to just build dovecot-lucene for Mail-in-a-box PPA
|
||||
+
|
||||
+ -- Joshua Tauberer <jt@occams.info> Sat, 14 May 2015 16:13:00 -0400
|
||||
+
|
||||
dovecot (1:2.2.9-1ubuntu2.1) trusty-security; urgency=medium
|
||||
|
||||
* SECURITY UPDATE: denial of service via SSL connection exhaustion
|
||||
--- a/debian/copyright 2014-03-07 07:26:37.000000000 -0500
|
||||
+++ b/debian/copyright 2015-05-23 18:17:42.668005535 -0400
|
||||
@@ -1,3 +1,7 @@
|
||||
+This package is a fork by Mail-in-a-box (https://mailinabox.email). Original
|
||||
+copyright statement follows:
|
||||
+----------------------------------------------------------------------------
|
||||
+
|
||||
This package was debianized by Jaldhar H. Vyas <jaldhar@debian.org> on
|
||||
Tue, 3 Dec 2002 01:10:07 -0500.
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
diff --git a/debian/NEWS b/debian/NEWS
|
||||
index dd09744..de7b640 100644
|
||||
--- a/debian/NEWS
|
||||
+++ b/debian/NEWS
|
||||
@@ -1,3 +1,9 @@
|
||||
+postgrey (1.35-1+miab1)
|
||||
+
|
||||
+ Added DNSWL.org whitelisting.
|
||||
+
|
||||
+ -- Joshua Tauberer <jt@occams.info> Mon May 18 18:58:40 EDT 2015
|
||||
+
|
||||
postgrey (1.32-1) unstable; urgency=low
|
||||
|
||||
Postgrey is now listening to port 10023 and not 60000. The latter was an
|
||||
diff --git a/debian/changelog b/debian/changelog
|
||||
index 1058e15..e5e3557 100644
|
||||
--- a/debian/changelog
|
||||
+++ b/debian/changelog
|
||||
@@ -1,3 +1,9 @@
|
||||
+postgrey (1.35-1+miab1) trusty; urgency=low
|
||||
+
|
||||
+ * Added DNSWL.org whitelisting.
|
||||
+
|
||||
+ -- Joshua Tauberer <jt@occams.info> Mon, 18 May 2015 21:58:40 +0000
|
||||
+
|
||||
postgrey (1.35-1) unstable; urgency=low
|
||||
|
||||
* New upstream release (Closes: 756486)
|
||||
diff --git a/debian/control b/debian/control
|
||||
index ce12ba6..0a82855 100644
|
||||
--- a/debian/control
|
||||
+++ b/debian/control
|
||||
@@ -1,14 +1,11 @@
|
||||
Source: postgrey
|
||||
Section: mail
|
||||
Priority: optional
|
||||
-Maintainer: Antonio Radici <antonio@debian.org>
|
||||
-Uploaders: Jon Daley <jondaley-guest@alioth.debian.org>
|
||||
+Maintainer: Joshua Tauberer <jt@occams.info>
|
||||
Build-Depends: debhelper (>= 7), quilt
|
||||
Build-Depends-Indep: po-debconf
|
||||
Standards-Version: 3.9.6
|
||||
Homepage: http://postgrey.schweikert.ch/
|
||||
-Vcs-Browser: http://git.debian.org/?p=collab-maint/postgrey.git
|
||||
-Vcs-Git: git://git.debian.org/git/collab-maint/postgrey.git
|
||||
|
||||
Package: postgrey
|
||||
Architecture: all
|
||||
@@ -25,3 +22,6 @@ Description: greylisting implementation for Postfix
|
||||
.
|
||||
While Postgrey is designed for use with Postfix, it can also be used
|
||||
with Exim.
|
||||
+ .
|
||||
+ This version has been modified by Mail-in-a-Box to whitelist senders
|
||||
+ in the DNSWL.org list. See https://mailinabox.email.
|
||||
diff --git a/debian/copyright b/debian/copyright
|
||||
index 3cbe377..bf09b89 100644
|
||||
--- a/debian/copyright
|
||||
+++ b/debian/copyright
|
||||
@@ -1,6 +1,10 @@
|
||||
+This package is a fork by Mail-in-a-Box (https://mailinabox.email). Original
|
||||
+copyright statement follows:
|
||||
+----------------------------------------------------------------------------
|
||||
+
|
||||
This Debian package was prepared by Adrian von Bidder <cmot@debian.org> in
|
||||
July 2004, then the package was adopted by Antonio Radici <antonio@dyne.org>
|
||||
-in Sept 2009
|
||||
+in Sept 2009.
|
||||
|
||||
It was downloaded from http://postgrey.schweikert.ch/
|
||||
|
||||
diff --git a/debian/patches/series b/debian/patches/series
|
||||
index f4c5e31..3cd62b8 100644
|
||||
--- a/debian/patches/series
|
||||
+++ b/debian/patches/series
|
||||
@@ -1,3 +1,3 @@
|
||||
imported-upstream-diff
|
||||
disable-transaction-logic
|
||||
-
|
||||
+mailinabox
|
||||
@@ -1,100 +0,0 @@
|
||||
Description: whitelist whatever dnswl.org whitelists
|
||||
.
|
||||
postgrey (1.35-1+miab1) unstable; urgency=low
|
||||
.
|
||||
* Added DNSWL.org whitelisting.
|
||||
Author: Joshua Tauberer <jt@occams.info>
|
||||
|
||||
--- postgrey-1.35.orig/README
|
||||
+++ postgrey-1.35/README
|
||||
@@ -13,7 +13,7 @@ Requirements
|
||||
- BerkeleyDB (Perl Module)
|
||||
- Berkeley DB >= 4.1 (Library)
|
||||
- Digest::SHA (Perl Module, only for --privacy option)
|
||||
-
|
||||
+- Net::DNS (Perl Module)
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
--- postgrey-1.35.orig/postgrey
|
||||
+++ postgrey-1.35/postgrey
|
||||
@@ -18,6 +18,7 @@ use Fcntl ':flock'; # import LOCK_* cons
|
||||
use Sys::Hostname;
|
||||
use Sys::Syslog; # used only to find out which version we use
|
||||
use POSIX qw(strftime setlocale LC_ALL);
|
||||
+use Net::DNS; # for DNSWL.org whitelisting
|
||||
|
||||
use vars qw(@ISA);
|
||||
@ISA = qw(Net::Server::Multiplex);
|
||||
@@ -26,6 +27,8 @@ my $VERSION = '1.35';
|
||||
my $DEFAULT_DBDIR = '/var/lib/postgrey';
|
||||
my $CONFIG_DIR = '/etc/postgrey';
|
||||
|
||||
+my $dns_resolver = Net::DNS::Resolver->new;
|
||||
+
|
||||
sub cidr_parse($)
|
||||
{
|
||||
defined $_[0] or return undef;
|
||||
@@ -48,6 +51,36 @@ sub cidr_match($$$)
|
||||
return ($addr & $mask) == $net;
|
||||
}
|
||||
|
||||
+sub reverseDottedQuad {
|
||||
+ # This is the sub _chkValidPublicIP from Net::DNSBL by PJ Goodwin
|
||||
+ # at http://www.the42.net/net-dnsbl.
|
||||
+ my ($quad) = @_;
|
||||
+ if ($quad =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
|
||||
+ my ($ip1,$ip2,$ip3,$ip4) = ($1, $2, $3, $4);
|
||||
+ if (
|
||||
+ $ip1 == 10 || #10.0.0.0/8 (10/8)
|
||||
+ ($ip1 == 172 && $ip2 >= 16 && $ip2 <= 31) || #172.16.0.0/12 (172.16/12)
|
||||
+ ($ip1 == 192 && $ip2 == 168) || #192.168.0.0/16 (192.168/16)
|
||||
+ $quad eq '127.0.0.1' # localhost
|
||||
+ ) {
|
||||
+ # toss the RFC1918 specified privates
|
||||
+ return undef;
|
||||
+ } elsif (
|
||||
+ ($ip1 <= 1 || $ip1 > 254) ||
|
||||
+ ($ip2 < 0 || $ip2 > 255) ||
|
||||
+ ($ip3 < 0 || $ip3 > 255) ||
|
||||
+ ($ip4 < 0 || $ip4 > 255)
|
||||
+ ) {
|
||||
+ #invalid oct, toss it;
|
||||
+ return undef;
|
||||
+ }
|
||||
+ my $revquad = $ip4 . "." . $ip3 . "." . $ip2 . "." . $ip1;
|
||||
+ return $revquad;
|
||||
+ } else { # invalid quad
|
||||
+ return undef;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
sub read_clients_whitelists($)
|
||||
{
|
||||
my ($self) = @_;
|
||||
@@ -361,6 +394,25 @@ sub smtpd_access_policy($$)
|
||||
}
|
||||
}
|
||||
|
||||
+ # whitelist clients in dnswl.org
|
||||
+ my $revip = reverseDottedQuad($attr->{client_address});
|
||||
+ if ($revip) { # valid IP / plausibly in DNSWL
|
||||
+ my $answer = $dns_resolver->send($revip . '.list.dnswl.org');
|
||||
+ if ($answer && scalar($answer->answer) > 0) {
|
||||
+ my @rrs = $answer->answer;
|
||||
+ if ($rrs[0]->type eq 'A' && $rrs[0]->address ne '127.0.0.255') {
|
||||
+ # Address appears in DNSWL. (127.0.0.255 means we were rate-limited.)
|
||||
+ my $code = $rrs[0]->address;
|
||||
+ if ($code =~ /^127.0.(\d+)\.([0-3])$/) {
|
||||
+ my %dnswltrust = (0 => 'legitimate', 1 => 'occasional spam', 2 => 'rare spam', 3 => 'highly unlikely to send spam');
|
||||
+ $code = $2 . '/' . $dnswltrust{$2};
|
||||
+ }
|
||||
+ $self->mylog_action($attr, 'pass', 'client whitelisted by dnswl.org (' . $code . ')');
|
||||
+ return 'DUNNO';
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
# auto whitelist clients (see below for explanation)
|
||||
my ($cawl_db, $cawl_key, $cawl_count, $cawl_last);
|
||||
if($self->{postgrey}{awl_clients}) {
|
||||
18
security.md
18
security.md
@@ -1,7 +1,7 @@
|
||||
Mail-in-a-Box Security Guide
|
||||
============================
|
||||
|
||||
Mail-in-a-Box turns a fresh Ubuntu 14.04 LTS 64-bit machine into a mail server appliance by installing and configuring various components.
|
||||
Mail-in-a-Box turns a fresh Ubuntu 18.04 LTS 64-bit machine into a mail server appliance by installing and configuring various components.
|
||||
|
||||
This page documents the security features of Mail-in-a-Box. The term “box” is used below to mean a configured Mail-in-a-Box.
|
||||
|
||||
@@ -39,22 +39,14 @@ These services are protected by [TLS](https://en.wikipedia.org/wiki/Transport_La
|
||||
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))
|
||||
* Only TLSv1, TLSv1.1 and TLSv1.2 are offered (the older SSL protocols are not offered).
|
||||
* Export-grade ciphers, the anonymous DH/ECDH algorithms (aNULL), and clear-text ciphers (eNULL) are not offered.
|
||||
* The minimum cipher key length offered is 112 bits. The maximum is 256 bits. Diffie-Hellman ciphers use a 2048-bit key for forward secrecy.
|
||||
* Only TLSv1.2+ are offered (the older SSL protocols are not offered).
|
||||
* We track the [Mozilla Intermediate Ciphers Recommendation](https://wiki.mozilla.org/Security/Server_Side_TLS), balancing security with supporting a wide range of mail clients. Diffie-Hellman ciphers use a 2048-bit key for forward secrecy. For more details, see the [output of SSLyze for these ports](tests/tls_results.txt).
|
||||
|
||||
Additionally:
|
||||
|
||||
* SMTP Submission (port 587) will not accept user credentials without STARTTLS (true also of SMTP on port 25 in case of client misconfiguration), and the submission port won't accept mail without encryption. The minimum cipher key length is 128 bits. (The box is of course configured not to be an open relay. User credentials are required to send outbound mail.) ([source](setup/mail-postfix.sh))
|
||||
* HTTPS (port 443): The HTTPS Strict Transport Security header is set. A redirect from HTTP to HTTPS is offered. The [Qualys SSL Labs test](https://www.ssllabs.com/ssltest) should report an A+ grade. ([source 1](conf/nginx-ssl.conf), [source 2](conf/nginx.conf))
|
||||
|
||||
For more details, see the [output of SSLyze for these ports](tests/tls_results.txt).
|
||||
|
||||
The cipher and protocol selection are chosen to support the following clients:
|
||||
|
||||
* For HTTPS: Firefox 1, Chrome 1, IE 7, Opera 5, Safari 1, Windows XP IE8, Android 2.3, Java 7.
|
||||
* For other protocols: TBD.
|
||||
|
||||
### Password Storage
|
||||
|
||||
The passwords for mail users are stored on disk using the [SHA512-CRYPT](http://man7.org/linux/man-pages/man3/crypt.3.html) hashing scheme. ([source](management/mailconfig.py))
|
||||
@@ -73,7 +65,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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -102,7 +94,7 @@ Domain policy records allow recipient MTAs to detect when the _domain_ part of o
|
||||
|
||||
### User Policy
|
||||
|
||||
While domain policy records prevent other servers from sending mail with a "From:" header that matches a domain hosted on the box (see above), those policy records do not guarnatee that the user portion of the sender email address matches the actual sender. In enterprise environments where the box may host the mail of untrusted users, it is important to guard against users impersonating other users.
|
||||
While domain policy records prevent other servers from sending mail with a "From:" header that matches a domain hosted on the box (see above), those policy records do not guarantee that the user portion of the sender email address matches the actual sender. In enterprise environments where the box may host the mail of untrusted users, it is important to guard against users impersonating other users.
|
||||
|
||||
The box restricts the envelope sender address (also called the return path or MAIL FROM address --- this is different from the "From:" header) that users may put into outbound mail. The envelope sender address must be either their own email address (their SMTP login username) or any alias that they are listed as a permitted sender of. (There is currently no restriction on the contents of the "From:" header.)
|
||||
|
||||
|
||||
@@ -7,13 +7,42 @@
|
||||
#########################################################
|
||||
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG=v0.22
|
||||
# If a version to install isn't explicitly given as an environment
|
||||
# variable, then install the latest version. But the latest version
|
||||
# depends on the operating system. Existing Ubuntu 14.04 users need
|
||||
# to be able to upgrade to the latest version supporting Ubuntu 14.04,
|
||||
# in part because an upgrade is required before jumping to Ubuntu 18.04.
|
||||
# New users on Ubuntu 18.04 need to get the latest version number too.
|
||||
#
|
||||
# Also, the system status checks read this script for TAG = (without the
|
||||
# space, but if we put it in a comment it would confuse the status checks!)
|
||||
# to get the latest version, so the first such line must be the one that we
|
||||
# want to display in status checks.
|
||||
if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then
|
||||
# This machine is running Ubuntu 18.04.
|
||||
TAG=v0.46
|
||||
|
||||
elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then
|
||||
# This machine is running Ubuntu 14.04.
|
||||
echo "You are installing the last version of Mail-in-a-Box that will"
|
||||
echo "support Ubuntu 14.04. If this is a new installation of Mail-in-a-Box,"
|
||||
echo "stop now and switch to a machine running Ubuntu 18.04. If you are"
|
||||
echo "upgrading an existing Mail-in-a-Box --- great. After upgrading this"
|
||||
echo "box, please visit https://mailinabox.email for notes on how to upgrade"
|
||||
echo "to Ubuntu 18.04."
|
||||
echo ""
|
||||
TAG=v0.30
|
||||
|
||||
else
|
||||
echo "This script must be run on a system running Ubuntu 18.04 or Ubuntu 14.04."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Are we running as root?
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "This script must be run as root. Did you leave out sudo?"
|
||||
exit
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clone the Mail-in-a-Box repository if it doesn't exist.
|
||||
@@ -44,7 +73,7 @@ if [ "$TAG" != `git describe` ]; then
|
||||
git fetch --depth 1 --force --prune origin tag $TAG
|
||||
if ! git checkout -q $TAG; then
|
||||
echo "Update failed. Did you modify something in `pwd`?"
|
||||
exit
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
fi
|
||||
|
||||
@@ -21,11 +21,17 @@ mkdir -p $STORAGE_ROOT/mail/dkim
|
||||
# Not quite sure why.
|
||||
echo "127.0.0.1" > /etc/opendkim/TrustedHosts
|
||||
|
||||
# We need to at least create these files, since we reference them later.
|
||||
# Otherwise, opendkim startup will fail
|
||||
touch /etc/opendkim/KeyTable
|
||||
touch /etc/opendkim/SigningTable
|
||||
|
||||
if grep -q "ExternalIgnoreList" /etc/opendkim.conf; then
|
||||
true # already done #NODOC
|
||||
else
|
||||
# Add various configuration options to the end of `opendkim.conf`.
|
||||
cat >> /etc/opendkim.conf << EOF;
|
||||
Canonicalization relaxed/simple
|
||||
MinimumKeyBits 1024
|
||||
ExternalIgnoreList refile:/etc/opendkim/TrustedHosts
|
||||
InternalHosts refile:/etc/opendkim/TrustedHosts
|
||||
@@ -75,6 +81,9 @@ tools/editconf.py /etc/postfix/main.cf \
|
||||
non_smtpd_milters=\$smtpd_milters \
|
||||
milter_default_action=accept
|
||||
|
||||
# We need to explicitly enable the opendmarc service, or it will not start
|
||||
hide_output systemctl enable opendmarc
|
||||
|
||||
# Restart services.
|
||||
restart_service opendkim
|
||||
restart_service opendmarc
|
||||
|
||||
15
setup/dns.sh
15
setup/dns.sh
@@ -23,9 +23,10 @@ apt_install nsd ldnsutils openssh-client
|
||||
mkdir -p /var/run/nsd
|
||||
|
||||
cat > /etc/nsd/nsd.conf << EOF;
|
||||
# No not edit. Overwritten by Mail-in-a-Box setup.
|
||||
# Do not edit. Overwritten by Mail-in-a-Box setup.
|
||||
server:
|
||||
hide-version: yes
|
||||
logfile: "/var/log/nsd.log"
|
||||
|
||||
# identify the server (CH TXT ID.SERVER entry).
|
||||
identity: ""
|
||||
@@ -41,6 +42,18 @@ server:
|
||||
|
||||
EOF
|
||||
|
||||
# Add log rotation
|
||||
cat > /etc/logrotate.d/nsd <<EOF;
|
||||
/var/log/nsd.log {
|
||||
weekly
|
||||
missingok
|
||||
rotate 12
|
||||
compress
|
||||
delaycompress
|
||||
notifempty
|
||||
}
|
||||
EOF
|
||||
|
||||
# Since we have bind9 listening on localhost for locally-generated
|
||||
# DNS queries that require a recursive nameserver, and the system
|
||||
# might have other network interfaces for e.g. tunnelling, we have
|
||||
|
||||
@@ -4,9 +4,9 @@ if [ -z "`tools/mail.py user`" ]; then
|
||||
# aren't any yet, it'll be empty.
|
||||
|
||||
# If we didn't ask for an email address at the start, do so now.
|
||||
if [ -z "$EMAIL_ADDR" ]; then
|
||||
if [ -z "${EMAIL_ADDR:-}" ]; then
|
||||
# In an interactive shell, ask the user for an email address.
|
||||
if [ -z "$NONINTERACTIVE" ]; then
|
||||
if [ -z "${NONINTERACTIVE:-}" ]; then
|
||||
input_box "Mail Account" \
|
||||
"Let's create your first mail account.
|
||||
\n\nWhat email address do you want?" \
|
||||
@@ -47,7 +47,7 @@ if [ -z "`tools/mail.py user`" ]; then
|
||||
fi
|
||||
|
||||
# Create the user's mail account. This will ask for a password if none was given above.
|
||||
tools/mail.py user add $EMAIL_ADDR $EMAIL_PW
|
||||
tools/mail.py user add $EMAIL_ADDR ${EMAIL_PW:-}
|
||||
|
||||
# Make it an admin.
|
||||
hide_output tools/mail.py user make-admin $EMAIL_ADDR
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# Turn on "strict mode." See http://redsymbol.net/articles/unofficial-bash-strict-mode/.
|
||||
# -e: exit if any command unexpectedly fails.
|
||||
# -u: exit if we have a variable typo.
|
||||
# -o pipefail: don't ignore errors in the non-last command in a pipeline
|
||||
set -euo pipefail
|
||||
|
||||
function hide_output {
|
||||
# This function hides the output of a command unless the command fails
|
||||
# and returns a non-zero exit code.
|
||||
@@ -5,11 +11,14 @@ function hide_output {
|
||||
# Get a temporary file.
|
||||
OUTPUT=$(tempfile)
|
||||
|
||||
# Execute command, redirecting stderr/stdout to the temporary file.
|
||||
# Execute command, redirecting stderr/stdout to the temporary file. Since we
|
||||
# check the return code ourselves, disable 'set -e' temporarily.
|
||||
set +e
|
||||
$@ &> $OUTPUT
|
||||
E=$?
|
||||
set -e
|
||||
|
||||
# If the command failed, show the output that was captured in the temporary file.
|
||||
E=$?
|
||||
if [ $E != 0 ]; then
|
||||
# Something failed.
|
||||
echo
|
||||
@@ -66,7 +75,7 @@ function get_publicip_from_web_service {
|
||||
#
|
||||
# Pass '4' or '6' as an argument to this function to specify
|
||||
# what type of address to get (IPv4, IPv6).
|
||||
curl -$1 --fail --silent --max-time 15 icanhazip.com 2>/dev/null
|
||||
curl -$1 --fail --silent --max-time 15 icanhazip.com 2>/dev/null || /bin/true
|
||||
}
|
||||
|
||||
function get_default_privateip {
|
||||
@@ -109,7 +118,7 @@ function get_default_privateip {
|
||||
if [ "$1" == "6" ]; then target=2001:4860:4860::8888; fi
|
||||
|
||||
# Get the route information.
|
||||
route=$(ip -$1 -o route get $target | grep -v unreachable)
|
||||
route=$(ip -$1 -o route get $target 2>/dev/null | grep -v unreachable)
|
||||
|
||||
# Parse the address out of the route information.
|
||||
address=$(echo $route | sed "s/.* src \([^ ]*\).*/\1/")
|
||||
@@ -122,11 +131,10 @@ function get_default_privateip {
|
||||
fi
|
||||
|
||||
echo $address
|
||||
|
||||
}
|
||||
|
||||
function ufw_allow {
|
||||
if [ -z "$DISABLE_FIREWALL" ]; then
|
||||
if [ -z "${DISABLE_FIREWALL:-}" ]; then
|
||||
# ufw has completely unhelpful output
|
||||
ufw allow $1 > /dev/null;
|
||||
fi
|
||||
@@ -145,10 +153,13 @@ function input_box {
|
||||
# input_box "title" "prompt" "defaultvalue" VARIABLE
|
||||
# The user's input will be stored in the variable VARIABLE.
|
||||
# The exit code from dialog will be stored in VARIABLE_EXITCODE.
|
||||
# Temporarily turn off 'set -e' because we need the dialog return code.
|
||||
declare -n result=$4
|
||||
declare -n result_code=$4_EXITCODE
|
||||
set +e
|
||||
result=$(dialog --stdout --title "$1" --inputbox "$2" 0 0 "$3")
|
||||
result_code=$?
|
||||
set -e
|
||||
}
|
||||
|
||||
function input_menu {
|
||||
@@ -158,8 +169,10 @@ function input_menu {
|
||||
declare -n result=$4
|
||||
declare -n result_code=$4_EXITCODE
|
||||
local IFS=^$'\n'
|
||||
set +e
|
||||
result=$(dialog --stdout --title "$1" --menu "$2" 0 0 0 $3)
|
||||
result_code=$?
|
||||
set -e
|
||||
}
|
||||
|
||||
function wget_verify {
|
||||
@@ -170,7 +183,7 @@ function wget_verify {
|
||||
DEST=$3
|
||||
CHECKSUM="$HASH $DEST"
|
||||
rm -f $DEST
|
||||
wget -q -O $DEST $URL || exit 1
|
||||
hide_output wget -O $DEST $URL
|
||||
if ! echo "$CHECKSUM" | sha1sum --check --strict > /dev/null; then
|
||||
echo "------------------------------------------------------------"
|
||||
echo "Download of $URL did not match expected checksum."
|
||||
|
||||
@@ -26,7 +26,7 @@ source /etc/mailinabox.conf # load global vars
|
||||
echo "Installing Dovecot (IMAP server)..."
|
||||
apt_install \
|
||||
dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-sqlite sqlite3 \
|
||||
dovecot-sieve dovecot-managesieved dovecot-lucene
|
||||
dovecot-sieve dovecot-managesieved
|
||||
|
||||
# The `dovecot-imapd`, `dovecot-pop3d`, and `dovecot-lmtpd` packages automatically
|
||||
# enable IMAP, POP and LMTP protocols.
|
||||
@@ -46,7 +46,7 @@ apt_install \
|
||||
# - https://www.dovecot.org/list/dovecot/2011-December/132455.html
|
||||
tools/editconf.py /etc/dovecot/conf.d/10-master.conf \
|
||||
default_process_limit=$(echo "`nproc` * 250" | bc) \
|
||||
default_vsz_limit=$(echo "`free -tom | tail -1 | awk '{print $2}'` / 3" | bc)M \
|
||||
default_vsz_limit=$(echo "`free -tm | tail -1 | awk '{print $2}'` / 3" | bc)M \
|
||||
log_path=/var/log/mail.log
|
||||
|
||||
# The inotify `max_user_instances` default is 128, which constrains
|
||||
@@ -78,13 +78,16 @@ tools/editconf.py /etc/dovecot/conf.d/10-auth.conf \
|
||||
"auth_mechanisms=plain login"
|
||||
|
||||
# Enable SSL, specify the location of the SSL certificate and private key files.
|
||||
# Disable obsolete SSL protocols and allow only good ciphers per http://baldric.net/2013/12/07/tls-ciphers-in-postfix-and-dovecot/.
|
||||
# Use Mozilla's "Intermediate" recommendations at https://ssl-config.mozilla.org/#server=dovecot&server-version=2.2.33&config=intermediate&openssl-version=1.1.1,
|
||||
# except that the current version of Dovecot does not have a TLSv1.3 setting, so we only use TLSv1.2.
|
||||
tools/editconf.py /etc/dovecot/conf.d/10-ssl.conf \
|
||||
ssl=required \
|
||||
"ssl_cert=<$STORAGE_ROOT/ssl/ssl_certificate.pem" \
|
||||
"ssl_key=<$STORAGE_ROOT/ssl/ssl_private_key.pem" \
|
||||
"ssl_protocols=!SSLv3 !SSLv2" \
|
||||
"ssl_cipher_list=TLSv1+HIGH !SSLv2 !RC4 !aNULL !eNULL !3DES @STRENGTH"
|
||||
"ssl_protocols=TLSv1.2" \
|
||||
"ssl_cipher_list=ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384" \
|
||||
"ssl_prefer_server_ciphers=no" \
|
||||
"ssl_dh_parameters_length=2048"
|
||||
|
||||
# Disable in-the-clear IMAP/POP because there is no reason for a user to transmit
|
||||
# login credentials outside of an encrypted connection. Only the over-TLS versions
|
||||
@@ -109,17 +112,6 @@ tools/editconf.py /etc/dovecot/conf.d/20-imap.conf \
|
||||
tools/editconf.py /etc/dovecot/conf.d/20-pop3.conf \
|
||||
pop3_uidl_format="%08Xu%08Xv"
|
||||
|
||||
# Full Text Search - Enable full text search of mail using dovecot's lucene plugin,
|
||||
# which *we* package and distribute (dovecot-lucene package).
|
||||
tools/editconf.py /etc/dovecot/conf.d/10-mail.conf \
|
||||
mail_plugins="\$mail_plugins fts fts_lucene"
|
||||
cat > /etc/dovecot/conf.d/90-plugin-fts.conf << EOF;
|
||||
plugin {
|
||||
fts = lucene
|
||||
fts_lucene = whitespace_chars=@.
|
||||
}
|
||||
EOF
|
||||
|
||||
# ### LDA (LMTP)
|
||||
|
||||
# Enable Dovecot's LDA service with the LMTP protocol. It will listen
|
||||
@@ -143,6 +135,14 @@ service lmtp {
|
||||
}
|
||||
}
|
||||
|
||||
# Enable imap-login on localhost to allow the user_external plugin
|
||||
# for Nextcloud to do imap authentication. (See #1577)
|
||||
service imap-login {
|
||||
inet_listener imap {
|
||||
address = 127.0.0.1
|
||||
port = 143
|
||||
}
|
||||
}
|
||||
protocol imap {
|
||||
mail_max_userip_connections = 20
|
||||
}
|
||||
|
||||
@@ -41,16 +41,8 @@ source /etc/mailinabox.conf # load global vars
|
||||
# always will.
|
||||
# * `ca-certificates`: A trust store used to squelch postfix warnings about
|
||||
# untrusted opportunistically-encrypted connections.
|
||||
#
|
||||
# postgrey is going to come in via the Mail-in-a-Box PPA, which publishes
|
||||
# a modified version of postgrey that lets senders whitelisted by dnswl.org
|
||||
# pass through without being greylisted. So please note [dnswl's license terms](https://www.dnswl.org/?page_id=9):
|
||||
# > Every user with more than 100’000 queries per day on the public nameserver
|
||||
# > infrastructure and every commercial vendor of dnswl.org data (eg through
|
||||
# > anti-spam solutions) must register with dnswl.org and purchase a subscription.
|
||||
|
||||
echo "Installing Postfix (SMTP server)..."
|
||||
apt_install postfix postfix-pcre postgrey ca-certificates
|
||||
apt_install postfix postfix-sqlite postfix-pcre postgrey ca-certificates
|
||||
|
||||
# ### Basic Settings
|
||||
|
||||
@@ -81,13 +73,14 @@ tools/editconf.py /etc/postfix/main.cf \
|
||||
|
||||
# Enable the 'submission' port 587 smtpd server and tweak its settings.
|
||||
#
|
||||
# * Enable authentication. It's disabled globally so that it is disabled on port 25,
|
||||
# so we need to explicitly enable it here.
|
||||
# * Do not add the OpenDMAC Authentication-Results header. That should only be added
|
||||
# on incoming mail. Omit the OpenDMARC milter by re-setting smtpd_milters to the
|
||||
# OpenDKIM milter only. See dkim.sh.
|
||||
# * Even though we dont allow auth over non-TLS connections (smtpd_tls_auth_only below, and without auth the client cant
|
||||
# send outbound mail), don't allow non-TLS mail submission on this port anyway to prevent accidental misconfiguration.
|
||||
# * Require the best ciphers for incoming connections per http://baldric.net/2013/12/07/tls-ciphers-in-postfix-and-dovecot/.
|
||||
# By putting this setting here we leave opportunistic TLS on incoming mail at default cipher settings (any cipher is better than none).
|
||||
# Setting smtpd_tls_security_level=encrypt also triggers the use of the 'mandatory' settings below.
|
||||
# * Give it a different name in syslog to distinguish it from the port 25 smtpd server.
|
||||
# * Add a new cleanup service specific to the submission service ('authclean')
|
||||
# that filters out privacy-sensitive headers on mail being sent out by
|
||||
@@ -95,10 +88,10 @@ tools/editconf.py /etc/postfix/main.cf \
|
||||
# emails but we turn this off by setting nested_header_checks empty.
|
||||
tools/editconf.py /etc/postfix/master.cf -s -w \
|
||||
"submission=inet n - - - - smtpd
|
||||
-o smtpd_sasl_auth_enable=yes
|
||||
-o syslog_name=postfix/submission
|
||||
-o smtpd_milters=inet:127.0.0.1:8891
|
||||
-o smtpd_tls_security_level=encrypt
|
||||
-o smtpd_tls_ciphers=high -o smtpd_tls_exclude_ciphers=aNULL,DES,3DES,MD5,DES+MD5,RC4 -o smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
|
||||
-o cleanup_service_name=authclean" \
|
||||
"authclean=unix n - - - 0 cleanup
|
||||
-o header_checks=pcre:/etc/postfix/outgoing_mail_header_filters
|
||||
@@ -113,20 +106,35 @@ cp conf/postfix_outgoing_mail_header_filters /etc/postfix/outgoing_mail_header_f
|
||||
sed -i "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" /etc/postfix/outgoing_mail_header_filters
|
||||
sed -i "s/PUBLIC_IP/$PUBLIC_IP/" /etc/postfix/outgoing_mail_header_filters
|
||||
|
||||
# Enable TLS on these and all other connections (i.e. ports 25 *and* 587) and
|
||||
# require TLS before a user is allowed to authenticate. This also makes
|
||||
# opportunistic TLS available on *incoming* mail.
|
||||
# Set stronger DH parameters, which via openssl tend to default to 1024 bits
|
||||
# (see ssl.sh).
|
||||
# Enable TLS on incoming connections. It is not required on port 25, allowing for opportunistic
|
||||
# encryption. On port 587 it is mandatory (see above). Shared and non-shared settings are
|
||||
# given here. Shared settings include:
|
||||
# * Require TLS before a user is allowed to authenticate.
|
||||
# * Set the path to the server TLS certificate and 2048-bit DH parameters for old DH ciphers.
|
||||
# For port 25 only:
|
||||
# * Disable extremely old versions of TLS and extremely unsafe ciphers, but some mail servers out in
|
||||
# the world are very far behind and if we disable too much, they may not be able to use TLS and
|
||||
# won't fall back to cleartext. So we don't disable too much. smtpd_tls_exclude_ciphers applies to
|
||||
# both port 25 and port 587, but because we override the cipher list for both, it probably isn't used.
|
||||
# Use Mozilla's "Old" recommendations at https://ssl-config.mozilla.org/#server=postfix&server-version=3.3.0&config=old&openssl-version=1.1.1
|
||||
# For port 587 (via the 'mandatory' settings):
|
||||
# * Use Mozilla's "Intermediate" TLS recommendations from https://ssl-config.mozilla.org/#server=postfix&server-version=3.3.0&config=intermediate&openssl-version=1.1.1
|
||||
# using and overriding the "high" cipher list so we don't conflict with the more permissive settings for port 25.
|
||||
tools/editconf.py /etc/postfix/main.cf \
|
||||
smtpd_tls_security_level=may\
|
||||
smtpd_tls_auth_only=yes \
|
||||
smtpd_tls_cert_file=$STORAGE_ROOT/ssl/ssl_certificate.pem \
|
||||
smtpd_tls_key_file=$STORAGE_ROOT/ssl/ssl_private_key.pem \
|
||||
smtpd_tls_dh1024_param_file=$STORAGE_ROOT/ssl/dh2048.pem \
|
||||
smtpd_tls_protocols=\!SSLv2,\!SSLv3 \
|
||||
smtpd_tls_protocols="!SSLv2,!SSLv3" \
|
||||
smtpd_tls_ciphers=medium \
|
||||
tls_medium_cipherlist=ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA \
|
||||
smtpd_tls_exclude_ciphers=aNULL,RC4 \
|
||||
smtpd_tls_mandatory_protocols="!SSLv2,!SSLv3,!TLSv1,!TLSv1.1" \
|
||||
smtpd_tls_mandatory_ciphers=high \
|
||||
tls_high_cipherlist=ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 \
|
||||
smtpd_tls_mandatory_exclude_ciphers=aNULL,DES,3DES,MD5,DES+MD5,RC4 \
|
||||
tls_preempt_cipherlist=no \
|
||||
smtpd_tls_received_header=yes
|
||||
|
||||
# Prevent non-authenticated users from sending mail that requires being
|
||||
@@ -148,13 +156,17 @@ tools/editconf.py /etc/postfix/main.cf \
|
||||
# offers it, otherwise it will transmit the message in the clear. Postfix will
|
||||
# accept whatever SSL certificate the remote end provides. Opportunistic TLS
|
||||
# protects against passive easvesdropping (but not man-in-the-middle attacks).
|
||||
# Since we'd rather have poor encryption than none at all, we use Mozilla's
|
||||
# "Old" recommendations at https://ssl-config.mozilla.org/#server=postfix&server-version=3.3.0&config=old&openssl-version=1.1.1
|
||||
# for opportunistic encryption but "Intermediate" recommendations when DANE
|
||||
# is used (see next and above). The cipher lists are set above.
|
||||
|
||||
# DANE takes this a step further:
|
||||
#
|
||||
# Postfix queries DNS for the TLSA record on the destination MX host. If no TLSA records are found,
|
||||
# then opportunistic TLS is used. Otherwise the server certificate must match the TLSA records
|
||||
# or else the mail bounces. TLSA also requires DNSSEC on the MX host. Postfix doesn't do DNSSEC
|
||||
# itself but assumes the system's nameserver does and reports DNSSEC status. Thus this also
|
||||
# relies on our local bind9 server being present and `smtp_dns_support_level=dnssec`.
|
||||
# relies on our local DNS server (see system.sh) and `smtp_dns_support_level=dnssec`.
|
||||
#
|
||||
# The `smtp_tls_CAfile` is superflous, but it eliminates warnings in the logs about untrusted certs,
|
||||
# which we don't care about seeing because Postfix is doing opportunistic TLS anyway. Better to encrypt,
|
||||
@@ -162,11 +174,12 @@ tools/editconf.py /etc/postfix/main.cf \
|
||||
# now see notices about trusted certs. The CA file is provided by the package `ca-certificates`.
|
||||
tools/editconf.py /etc/postfix/main.cf \
|
||||
smtp_tls_protocols=\!SSLv2,\!SSLv3 \
|
||||
smtp_tls_mandatory_protocols=\!SSLv2,\!SSLv3 \
|
||||
smtp_tls_ciphers=medium \
|
||||
smtp_tls_exclude_ciphers=aNULL,RC4 \
|
||||
smtp_tls_security_level=dane \
|
||||
smtp_dns_support_level=dnssec \
|
||||
smtp_tls_mandatory_protocols="!SSLv2,!SSLv3,!TLSv1,!TLSv1.1" \
|
||||
smtp_tls_mandatory_ciphers=high \
|
||||
smtp_tls_CAfile=/etc/ssl/certs/ca-certificates.crt \
|
||||
smtp_tls_loglevel=2
|
||||
|
||||
@@ -178,8 +191,11 @@ tools/editconf.py /etc/postfix/main.cf \
|
||||
#
|
||||
# In a basic setup we would pass mail directly to Dovecot by setting
|
||||
# virtual_transport to `lmtp:unix:private/dovecot-lmtp`.
|
||||
#
|
||||
tools/editconf.py /etc/postfix/main.cf virtual_transport=lmtp:[127.0.0.1]:10025
|
||||
# Because of a spampd bug, limit the number of recipients in each connection.
|
||||
# See https://github.com/mail-in-a-box/mailinabox/issues/1523.
|
||||
tools/editconf.py /etc/postfix/main.cf lmtp_destination_recipient_limit=1
|
||||
|
||||
|
||||
# Who can send mail to us? Some basic filters.
|
||||
#
|
||||
@@ -212,6 +228,33 @@ tools/editconf.py /etc/postfix/main.cf \
|
||||
tools/editconf.py /etc/default/postgrey \
|
||||
POSTGREY_OPTS=\"'--inet=127.0.0.1:10023 --delay=180'\"
|
||||
|
||||
|
||||
# We are going to setup a newer whitelist for postgrey, the version included in the distribution is old
|
||||
cat > /etc/cron.daily/mailinabox-postgrey-whitelist << EOF;
|
||||
#!/bin/bash
|
||||
|
||||
# Mail-in-a-Box
|
||||
|
||||
# check we have a postgrey_whitelist_clients file and that it is not older than 28 days
|
||||
if [ ! -f /etc/postgrey/whitelist_clients ] || find /etc/postgrey/whitelist_clients -mtime +28 | grep -q '.' ; then
|
||||
# ok we need to update the file, so lets try to fetch it
|
||||
if curl https://postgrey.schweikert.ch/pub/postgrey_whitelist_clients --output /tmp/postgrey_whitelist_clients -sS --fail > /dev/null 2>&1 ; then
|
||||
# if fetching hasn't failed yet then check it is a plain text file
|
||||
# curl manual states that --fail sometimes still produces output
|
||||
# this final check will at least check the output is not html
|
||||
# before moving it into place
|
||||
if [ "\$(file -b --mime-type /tmp/postgrey_whitelist_clients)" == "text/plain" ]; then
|
||||
mv /tmp/postgrey_whitelist_clients /etc/postgrey/whitelist_clients
|
||||
service postgrey restart
|
||||
else
|
||||
rm /tmp/postgrey_whitelist_clients
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
EOF
|
||||
chmod +x /etc/cron.daily/mailinabox-postgrey-whitelist
|
||||
/etc/cron.daily/mailinabox-postgrey-whitelist
|
||||
|
||||
# Increase the message size limit from 10MB to 128MB.
|
||||
# The same limit is specified in nginx.conf for mail submitted via webmail and Z-Push.
|
||||
tools/editconf.py /etc/postfix/main.cf \
|
||||
|
||||
@@ -65,11 +65,15 @@ service auth {
|
||||
}
|
||||
EOF
|
||||
|
||||
# And have Postfix use that service.
|
||||
# And have Postfix use that service. We *disable* it here
|
||||
# so that authentication is not permitted on port 25 (which
|
||||
# does not run DKIM on relayed mail, so outbound mail isn't
|
||||
# correct, see #830), but we enable it specifically for the
|
||||
# submission port.
|
||||
tools/editconf.py /etc/postfix/main.cf \
|
||||
smtpd_sasl_type=dovecot \
|
||||
smtpd_sasl_path=private/auth \
|
||||
smtpd_sasl_auth_enable=yes
|
||||
smtpd_sasl_auth_enable=no
|
||||
|
||||
# ### Sender Validation
|
||||
|
||||
|
||||
@@ -6,52 +6,51 @@ echo "Installing Mail-in-a-Box system management daemon..."
|
||||
|
||||
# DEPENDENCIES
|
||||
|
||||
# Install Python packages that are available from the Ubuntu
|
||||
# apt repository:
|
||||
# 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
|
||||
# We used to install management daemon-related Python packages
|
||||
# directly to /usr/local/lib. We moved to a virtualenv because
|
||||
# these packages might conflict with apt-installed packages.
|
||||
# We may have a lingering version of acme that conflcits with
|
||||
# certbot, which we're about to install below, so remove it
|
||||
# first. Once acme is installed by an apt package, this might
|
||||
# break the package version and `apt-get install --reinstall python3-acme`
|
||||
# might be needed in that case.
|
||||
while [ -d /usr/local/lib/python3.4/dist-packages/acme ]; do
|
||||
pip3 uninstall -y acme;
|
||||
done
|
||||
|
||||
# These are required to pip install cryptography.
|
||||
apt_install build-essential libssl-dev libffi-dev python3-dev
|
||||
|
||||
# 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 now creates the conflict, and
|
||||
# then pip gets permanently broken with errors like
|
||||
# "ImportError: No module named 'packaging'".
|
||||
# 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.
|
||||
#
|
||||
# Let's test for the error:
|
||||
if ! python3 -c "from pkg_resources import load_entry_point" 2&> /dev/null; then
|
||||
# This system seems to be broken already.
|
||||
echo "Fixing broken pip and setuptools..."
|
||||
rm -rf /usr/local/lib/python3.4/dist-packages/{pkg_resources,setuptools}*
|
||||
apt-get install --reinstall python3-setuptools python3-pip python3-pkg-resources
|
||||
# virtualenv is used to isolate the Python 3 packages we
|
||||
# install via pip from the system-installed packages.
|
||||
#
|
||||
# certbot installs EFF's certbot which we use to
|
||||
# provision free TLS certificates.
|
||||
apt_install duplicity python-pip virtualenv certbot
|
||||
hide_output pip2 install --upgrade boto
|
||||
|
||||
# Create a virtualenv for the installation of Python 3 packages
|
||||
# used by the management daemon.
|
||||
inst_dir=/usr/local/lib/mailinabox
|
||||
mkdir -p $inst_dir
|
||||
venv=$inst_dir/env
|
||||
if [ ! -d $venv ]; then
|
||||
hide_output virtualenv -ppython3 $venv
|
||||
fi
|
||||
#
|
||||
# The easiest work-around on systems that aren't already broken is
|
||||
# to upgrade pip (to >=9.0.1) and setuptools (to >=34.1) individually
|
||||
# before we install any package that tries to update setuptools.
|
||||
hide_output pip3 install --upgrade pip
|
||||
hide_output pip3 install --upgrade setuptools
|
||||
|
||||
# Upgrade pip because the Ubuntu-packaged version is out of date.
|
||||
hide_output $venv/bin/pip install --upgrade pip
|
||||
|
||||
# Install other Python 3 packages used by the management daemon.
|
||||
# The first line is the packages that Josh maintains himself!
|
||||
# NOTE: email_validator is repeated in setup/questions.sh, so please keep the versions synced.
|
||||
# Force acme to be updated because it seems to need it after the
|
||||
# pip/setuptools breakage (see above) and the ACME protocol may
|
||||
# have changed (I got an error on one of my systems).
|
||||
hide_output pip3 install --upgrade \
|
||||
rtyaml "email_validator>=1.0.0" "free_tls_certificates>=0.1.3" "exclusiveprocess" \
|
||||
"idna>=2.0.0" "cryptography>=1.0.2" acme boto psutil
|
||||
|
||||
# duplicity uses python 2 so we need to get the python 2 package of boto to have backups to 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.
|
||||
hide_output pip2 install --upgrade boto
|
||||
hide_output $venv/bin/pip install --upgrade \
|
||||
rtyaml "email_validator>=1.0.0" "exclusiveprocess" \
|
||||
flask dnspython python-dateutil \
|
||||
"idna>=2.0.0" "cryptography==2.2.2" boto psutil
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
@@ -61,27 +60,52 @@ if [ ! -f $STORAGE_ROOT/backup/secret_key.txt ]; then
|
||||
$(umask 077; openssl rand -base64 2048 > $STORAGE_ROOT/backup/secret_key.txt)
|
||||
fi
|
||||
|
||||
# Link the management server daemon into a well known location.
|
||||
rm -f /usr/local/bin/mailinabox-daemon
|
||||
ln -s `pwd`/management/daemon.py /usr/local/bin/mailinabox-daemon
|
||||
|
||||
# Download jQuery and Bootstrap local files
|
||||
|
||||
# Make sure we have the directory to save to.
|
||||
assets_dir=$inst_dir/vendor/assets
|
||||
rm -rf $assets_dir
|
||||
mkdir -p $assets_dir
|
||||
|
||||
# jQuery CDN URL
|
||||
jquery_version=2.1.4
|
||||
jquery_url=https://code.jquery.com
|
||||
|
||||
# Get jQuery
|
||||
wget_verify $jquery_url/jquery-$jquery_version.min.js 43dc554608df885a59ddeece1598c6ace434d747 $assets_dir/jquery.min.js
|
||||
|
||||
# Bootstrap CDN URL
|
||||
bootstrap_version=3.3.7
|
||||
bootstrap_url=https://github.com/twbs/bootstrap/releases/download/v$bootstrap_version/bootstrap-$bootstrap_version-dist.zip
|
||||
|
||||
# Get Bootstrap
|
||||
wget_verify $bootstrap_url e6b1000b94e835ffd37f4c6dcbdad43f4b48a02a /tmp/bootstrap.zip
|
||||
unzip -q /tmp/bootstrap.zip -d $assets_dir
|
||||
mv $assets_dir/bootstrap-$bootstrap_version-dist $assets_dir/bootstrap
|
||||
rm -f /tmp/bootstrap.zip
|
||||
|
||||
# Create an init script to start the management daemon and keep it
|
||||
# running after a reboot.
|
||||
rm -f /etc/init.d/mailinabox
|
||||
ln -s $(pwd)/conf/management-initscript /etc/init.d/mailinabox
|
||||
hide_output update-rc.d mailinabox defaults
|
||||
|
||||
# Remove old files we no longer use.
|
||||
rm -f /etc/cron.daily/mailinabox-backup
|
||||
rm -f /etc/cron.daily/mailinabox-statuschecks
|
||||
cat > $inst_dir/start <<EOF;
|
||||
#!/bin/bash
|
||||
source $venv/bin/activate
|
||||
exec python `pwd`/management/daemon.py
|
||||
EOF
|
||||
chmod +x $inst_dir/start
|
||||
cp --remove-destination conf/mailinabox.service /lib/systemd/system/mailinabox.service # target was previously a symlink so remove it first
|
||||
hide_output systemctl link -f /lib/systemd/system/mailinabox.service
|
||||
hide_output systemctl daemon-reload
|
||||
hide_output systemctl enable mailinabox.service
|
||||
|
||||
# Perform nightly tasks at 3am in system time: take a backup, run
|
||||
# status checks and email the administrator any changes.
|
||||
|
||||
minute=$((RANDOM % 60)) # avoid overloading mailinabox.email
|
||||
cat > /etc/cron.d/mailinabox-nightly << EOF;
|
||||
# Mail-in-a-Box --- Do not edit / will be overwritten on update.
|
||||
# Run nightly tasks: backup, status checks.
|
||||
0 3 * * * root (cd `pwd` && management/daily_tasks.sh)
|
||||
$minute 3 * * * root (cd `pwd` && management/daily_tasks.sh)
|
||||
EOF
|
||||
|
||||
# Start the management server.
|
||||
|
||||
@@ -137,6 +137,51 @@ def migration_10(env):
|
||||
shutil.move(sslcert, newname)
|
||||
os.rmdir(d)
|
||||
|
||||
def migration_11(env):
|
||||
# Archive the old Let's Encrypt account directory managed by free_tls_certificates
|
||||
# because we'll use that path now for the directory managed by certbot.
|
||||
try:
|
||||
old_path = os.path.join(env["STORAGE_ROOT"], 'ssl', 'lets_encrypt')
|
||||
new_path = os.path.join(env["STORAGE_ROOT"], 'ssl', 'lets_encrypt-old')
|
||||
shutil.move(old_path, new_path)
|
||||
except:
|
||||
# meh
|
||||
pass
|
||||
|
||||
def migration_12(env):
|
||||
# Upgrading to Carddav Roundcube plugin to version 3+, it requires the carddav_*
|
||||
# tables to be dropped.
|
||||
# Checking that the roundcube database already exists.
|
||||
if os.path.exists(os.path.join(env["STORAGE_ROOT"], "mail/roundcube/roundcube.sqlite")):
|
||||
import sqlite3
|
||||
conn = sqlite3.connect(os.path.join(env["STORAGE_ROOT"], "mail/roundcube/roundcube.sqlite"))
|
||||
c = conn.cursor()
|
||||
# Get a list of all the tables that begin with 'carddav_'
|
||||
c.execute("SELECT name FROM sqlite_master WHERE type = ? AND name LIKE ?", ('table', 'carddav_%'))
|
||||
carddav_tables = c.fetchall()
|
||||
# If there were tables that begin with 'carddav_', drop them
|
||||
if carddav_tables:
|
||||
for table in carddav_tables:
|
||||
try:
|
||||
table = table[0]
|
||||
c = conn.cursor()
|
||||
dropcmd = "DROP TABLE %s" % table
|
||||
c.execute(dropcmd)
|
||||
except:
|
||||
print("Failed to drop table", table, e)
|
||||
# Save.
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
# Delete all sessions, requring users to login again to recreate carddav_*
|
||||
# databases
|
||||
conn = sqlite3.connect(os.path.join(env["STORAGE_ROOT"], "mail/roundcube/roundcube.sqlite"))
|
||||
c = conn.cursor()
|
||||
c.execute("delete from session;")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_current_migration():
|
||||
ver = 0
|
||||
while True:
|
||||
|
||||
@@ -29,7 +29,7 @@ address 127.0.0.1
|
||||
|
||||
# send alerts to the following address
|
||||
contacts admin
|
||||
contact.admin.command mail -s "Munin notification ${var:host}" administrator@$PRIMARY_HOSTNAME
|
||||
contact.admin.command mail -s "Munin notification \${var:host}" administrator@$PRIMARY_HOSTNAME
|
||||
contact.admin.always_send warning critical
|
||||
EOF
|
||||
|
||||
@@ -38,11 +38,13 @@ chown munin. /var/log/munin/munin-cgi-html.log
|
||||
chown munin. /var/log/munin/munin-cgi-graph.log
|
||||
|
||||
# ensure munin-node knows the name of this machine
|
||||
# and reduce logging level to warning
|
||||
tools/editconf.py /etc/munin/munin-node.conf -s \
|
||||
host_name=$PRIMARY_HOSTNAME
|
||||
host_name=$PRIMARY_HOSTNAME \
|
||||
log_level=1
|
||||
|
||||
# Update the activated plugins through munin's autoconfiguration.
|
||||
munin-node-configure --shell --remove-also 2>/dev/null | sh
|
||||
munin-node-configure --shell --remove-also 2>/dev/null | sh || /bin/true
|
||||
|
||||
# Deactivate monitoring of NTP peers. Not sure why anyone would want to monitor a NTP peer. The addresses seem to change
|
||||
# (which is taken care of my munin-node-configure, but only when we re-run it.)
|
||||
@@ -51,7 +53,7 @@ find /etc/munin/plugins/ -lname /usr/share/munin/plugins/ntp_ -print0 | xargs -0
|
||||
# Deactivate monitoring of network interfaces that are not up. Otherwise we can get a lot of empty charts.
|
||||
for f in $(find /etc/munin/plugins/ \( -lname /usr/share/munin/plugins/if_ -o -lname /usr/share/munin/plugins/if_err_ -o -lname /usr/share/munin/plugins/bonding_err_ \)); do
|
||||
IF=$(echo $f | sed s/.*_//);
|
||||
if ! ifquery $IF >/dev/null 2>/dev/null; then
|
||||
if ! grep -qFx up /sys/class/net/$IF/operstate 2>/dev/null; then
|
||||
rm $f;
|
||||
fi;
|
||||
done
|
||||
@@ -59,6 +61,15 @@ done
|
||||
# Create a 'state' directory. Not sure why we need to do this manually.
|
||||
mkdir -p /var/lib/munin-node/plugin-state/
|
||||
|
||||
# Create a systemd service for munin.
|
||||
ln -sf $(pwd)/management/munin_start.sh /usr/local/lib/mailinabox/munin_start.sh
|
||||
chmod 0744 /usr/local/lib/mailinabox/munin_start.sh
|
||||
cp --remove-destination conf/munin.service /lib/systemd/system/munin.service # target was previously a symlink so remove first
|
||||
hide_output systemctl link -f /lib/systemd/system/munin.service
|
||||
hide_output systemctl daemon-reload
|
||||
hide_output systemctl unmask munin.service
|
||||
hide_output systemctl enable munin.service
|
||||
|
||||
# Restart services.
|
||||
restart_service munin
|
||||
restart_service munin-node
|
||||
@@ -66,4 +77,8 @@ restart_service munin-node
|
||||
# generate initial statistics so the directory isn't empty
|
||||
# (We get "Pango-WARNING **: error opening config file '/root/.config/pango/pangorc': Permission denied"
|
||||
# if we don't explicitly set the HOME directory when sudo'ing.)
|
||||
sudo -H -u munin munin-cron
|
||||
# We check to see if munin-cron is already running, if it is, there is no need to run it simultaneously
|
||||
# generating an error.
|
||||
if [ ! -f /var/run/munin/munin-update.lock ]; then
|
||||
sudo -H -u munin munin-cron
|
||||
fi
|
||||
|
||||
333
setup/nextcloud.sh
Executable file
333
setup/nextcloud.sh
Executable file
@@ -0,0 +1,333 @@
|
||||
#!/bin/bash
|
||||
# Nextcloud
|
||||
##########################
|
||||
|
||||
source setup/functions.sh # load our functions
|
||||
source /etc/mailinabox.conf # load global vars
|
||||
|
||||
# ### Installing Nextcloud
|
||||
|
||||
echo "Installing Nextcloud (contacts/calendar)..."
|
||||
|
||||
apt-get purge -qq -y owncloud* # we used to use the package manager
|
||||
|
||||
apt_install php php-fpm \
|
||||
php-cli php-sqlite3 php-gd php-imap php-curl php-pear curl \
|
||||
php-dev php-gd php-xml php-mbstring php-zip php-apcu php-json \
|
||||
php-intl php-imagick
|
||||
|
||||
InstallNextcloud() {
|
||||
|
||||
version=$1
|
||||
hash=$2
|
||||
|
||||
echo
|
||||
echo "Upgrading to Nextcloud version $version"
|
||||
echo
|
||||
|
||||
# Download and verify
|
||||
wget_verify https://download.nextcloud.com/server/releases/nextcloud-$version.zip $hash /tmp/nextcloud.zip
|
||||
|
||||
# Remove the current owncloud/Nextcloud
|
||||
rm -rf /usr/local/lib/owncloud
|
||||
|
||||
# Extract ownCloud/Nextcloud
|
||||
unzip -q /tmp/nextcloud.zip -d /usr/local/lib
|
||||
mv /usr/local/lib/nextcloud /usr/local/lib/owncloud
|
||||
rm -f /tmp/nextcloud.zip
|
||||
|
||||
# The two apps we actually want are not in Nextcloud core. Download the releases from
|
||||
# their github repositories.
|
||||
mkdir -p /usr/local/lib/owncloud/apps
|
||||
|
||||
wget_verify https://github.com/nextcloud/contacts/releases/download/v3.3.0/contacts.tar.gz e55d0357c6785d3b1f3b5f21780cb6d41d32443a /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/v2.0.3/calendar.tar.gz 9d9717b29337613b72c74e9914c69b74b346c466 /tmp/calendar.tgz
|
||||
tar xf /tmp/calendar.tgz -C /usr/local/lib/owncloud/apps/
|
||||
rm /tmp/calendar.tgz
|
||||
|
||||
# Starting with Nextcloud 15, the app user_external is no longer included in Nextcloud core,
|
||||
# we will install from their github repository.
|
||||
if [[ $version =~ ^1[567] ]]; then
|
||||
wget_verify https://github.com/nextcloud/user_external/releases/download/v0.7.0/user_external-0.7.0.tar.gz 555a94811daaf5bdd336c5e48a78aa8567b86437 /tmp/user_external.tgz
|
||||
tar -xf /tmp/user_external.tgz -C /usr/local/lib/owncloud/apps/
|
||||
rm /tmp/user_external.tgz
|
||||
fi
|
||||
|
||||
# Fix weird permissions.
|
||||
chmod 750 /usr/local/lib/owncloud/{apps,config}
|
||||
|
||||
# Create a symlink to the config.php in STORAGE_ROOT (for upgrades we're restoring the symlink we previously
|
||||
# put in, and in new installs we're creating a symlink and will create the actual config later).
|
||||
ln -sf $STORAGE_ROOT/owncloud/config.php /usr/local/lib/owncloud/config/config.php
|
||||
|
||||
# Make sure permissions are correct or the upgrade step won't run.
|
||||
# $STORAGE_ROOT/owncloud may not yet exist, so use -f to suppress
|
||||
# that error.
|
||||
chown -f -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud || /bin/true
|
||||
|
||||
# If this isn't a new installation, immediately run the upgrade script.
|
||||
# Then check for success (0=ok and 3=no upgrade needed, both are success).
|
||||
if [ -e $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
||||
# ownCloud 8.1.1 broke upgrades. It may fail on the first attempt, but
|
||||
# that can be OK.
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ upgrade
|
||||
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then
|
||||
echo "Trying ownCloud upgrade again to work around ownCloud upgrade bug..."
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ upgrade
|
||||
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off
|
||||
echo "...which seemed to work."
|
||||
fi
|
||||
|
||||
# Add missing indices. NextCloud didn't include this in the normal upgrade because it might take some time.
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ db:add-missing-indices
|
||||
|
||||
# Run conversion to BigInt identifiers, this process may take some time on large tables.
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ db:convert-filecache-bigint --no-interaction
|
||||
fi
|
||||
}
|
||||
|
||||
# Nextcloud Version to install. Checks are done down below to step through intermediate versions.
|
||||
nextcloud_ver=17.0.6
|
||||
nextcloud_hash=50b98d2c2f18510b9530e558ced9ab51eb4f11b0
|
||||
|
||||
# Current Nextcloud Version, #1623
|
||||
# Checking /usr/local/lib/owncloud/version.php shows version of the Nextcloud application, not the DB
|
||||
# $STORAGE_ROOT/owncloud is kept together even during a backup. It is better to rely on config.php than
|
||||
# version.php since the restore procedure can leave the system in a state where you have a newer Nextcloud
|
||||
# application version than the database.
|
||||
|
||||
# If config.php exists, get version number, otherwise CURRENT_NEXTCLOUD_VER is empty.
|
||||
if [ -f "$STORAGE_ROOT/owncloud/config.php" ]; then
|
||||
CURRENT_NEXTCLOUD_VER=$(php -r "include(\"$STORAGE_ROOT/owncloud/config.php\"); echo(\$CONFIG['version']);")
|
||||
else
|
||||
CURRENT_NEXTCLOUD_VER=""
|
||||
fi
|
||||
|
||||
# If the Nextcloud directory is missing (never been installed before, or the nextcloud version to be installed is different
|
||||
# from the version currently installed, do the install/upgrade
|
||||
if [ ! -d /usr/local/lib/owncloud/ ] || [[ ! ${CURRENT_NEXTCLOUD_VER} =~ ^$nextcloud_ver ]]; then
|
||||
|
||||
# Stop php-fpm if running. If theyre not running (which happens on a previously failed install), dont bail.
|
||||
service php7.2-fpm stop &> /dev/null || /bin/true
|
||||
|
||||
# Backup the existing ownCloud/Nextcloud.
|
||||
# Create a backup directory to store the current installation and database to
|
||||
BACKUP_DIRECTORY=$STORAGE_ROOT/owncloud-backup/`date +"%Y-%m-%d-%T"`
|
||||
mkdir -p "$BACKUP_DIRECTORY"
|
||||
if [ -d /usr/local/lib/owncloud/ ]; then
|
||||
echo "Upgrading Nextcloud --- backing up existing installation, configuration, and database to directory to $BACKUP_DIRECTORY..."
|
||||
cp -r /usr/local/lib/owncloud "$BACKUP_DIRECTORY/owncloud-install"
|
||||
fi
|
||||
if [ -e $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
||||
cp $STORAGE_ROOT/owncloud/owncloud.db $BACKUP_DIRECTORY
|
||||
fi
|
||||
if [ -e $STORAGE_ROOT/owncloud/config.php ]; then
|
||||
cp $STORAGE_ROOT/owncloud/config.php $BACKUP_DIRECTORY
|
||||
fi
|
||||
|
||||
# If ownCloud or Nextcloud was previously installed....
|
||||
if [ ! -z ${CURRENT_NEXTCLOUD_VER} ]; then
|
||||
# Database migrations from ownCloud are no longer possible because ownCloud cannot be run under
|
||||
# PHP 7.
|
||||
if [[ ${CURRENT_NEXTCLOUD_VER} =~ ^[89] ]]; then
|
||||
echo "Upgrades from Mail-in-a-Box prior to v0.28 (dated July 30, 2018) with Nextcloud < 13.0.6 (you have ownCloud 8 or 9) are not supported. Upgrade to Mail-in-a-Box version v0.30 first. Setup aborting."
|
||||
exit 1
|
||||
elif [[ ${CURRENT_NEXTCLOUD_VER} =~ ^1[012] ]]; then
|
||||
echo "Upgrades from Mail-in-a-Box prior to v0.28 (dated July 30, 2018) with Nextcloud < 13.0.6 (you have ownCloud 10, 11 or 12) are not supported. Upgrade to Mail-in-a-Box version v0.30 first. Setup aborting."
|
||||
exit 1
|
||||
elif [[ ${CURRENT_NEXTCLOUD_VER} =~ ^13 ]]; then
|
||||
# If we are running Nextcloud 13, upgrade to Nextcloud 14
|
||||
InstallNextcloud 14.0.6 4e43a57340f04c2da306c8eea98e30040399ae5a
|
||||
CURRENT_NEXTCLOUD_VER="14.0.6"
|
||||
fi
|
||||
if [[ ${CURRENT_NEXTCLOUD_VER} =~ ^14 ]]; then
|
||||
# During the upgrade from Nextcloud 14 to 15, user_external may cause the upgrade to fail.
|
||||
# We will disable it here before the upgrade and install it again after the upgrade.
|
||||
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:disable user_external
|
||||
InstallNextcloud 15.0.8 4129d8d4021c435f2e86876225fb7f15adf764a3
|
||||
CURRENT_NEXTCLOUD_VER="15.0.8"
|
||||
fi
|
||||
if [[ ${CURRENT_NEXTCLOUD_VER} =~ ^15 ]]; then
|
||||
InstallNextcloud 16.0.6 0bb3098455ec89f5af77a652aad553ad40a88819
|
||||
CURRENT_NEXTCLOUD_VER="16.0.6"
|
||||
fi
|
||||
fi
|
||||
|
||||
InstallNextcloud $nextcloud_ver $nextcloud_hash
|
||||
fi
|
||||
|
||||
# ### Configuring Nextcloud
|
||||
|
||||
# Setup Nextcloud if the Nextcloud database does not yet exist. Running setup when
|
||||
# the database does exist wipes the database and user data.
|
||||
if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
||||
# Create user data directory
|
||||
mkdir -p $STORAGE_ROOT/owncloud
|
||||
|
||||
# Create an initial configuration file.
|
||||
instanceid=oc$(echo $PRIMARY_HOSTNAME | sha1sum | fold -w 10 | head -n 1)
|
||||
cat > $STORAGE_ROOT/owncloud/config.php <<EOF;
|
||||
<?php
|
||||
\$CONFIG = array (
|
||||
'datadirectory' => '$STORAGE_ROOT/owncloud',
|
||||
|
||||
'instanceid' => '$instanceid',
|
||||
|
||||
'forcessl' => true, # if unset/false, Nextcloud sends a HSTS=0 header, which conflicts with nginx config
|
||||
|
||||
'overwritewebroot' => '/cloud',
|
||||
'overwrite.cli.url' => '/cloud',
|
||||
'user_backends' => array(
|
||||
array(
|
||||
'class' => 'OC_User_IMAP',
|
||||
'arguments' => array(
|
||||
'127.0.0.1', 143, null
|
||||
),
|
||||
),
|
||||
),
|
||||
'memcache.local' => '\OC\Memcache\APCu',
|
||||
'mail_smtpmode' => 'sendmail',
|
||||
'mail_smtpsecure' => '',
|
||||
'mail_smtpauthtype' => 'LOGIN',
|
||||
'mail_smtpauth' => false,
|
||||
'mail_smtphost' => '',
|
||||
'mail_smtpport' => '',
|
||||
'mail_smtpname' => '',
|
||||
'mail_smtppassword' => '',
|
||||
'mail_from_address' => 'owncloud',
|
||||
);
|
||||
?>
|
||||
EOF
|
||||
|
||||
# Create an auto-configuration file to fill in database settings
|
||||
# when the install script is run. Make an administrator account
|
||||
# here or else the install can't finish.
|
||||
adminpassword=$(dd if=/dev/urandom bs=1 count=40 2>/dev/null | sha1sum | fold -w 30 | head -n 1)
|
||||
cat > /usr/local/lib/owncloud/config/autoconfig.php <<EOF;
|
||||
<?php
|
||||
\$AUTOCONFIG = array (
|
||||
# storage/database
|
||||
'directory' => '$STORAGE_ROOT/owncloud',
|
||||
'dbtype' => 'sqlite3',
|
||||
|
||||
# create an administrator account with a random password so that
|
||||
# the user does not have to enter anything on first load of Nextcloud
|
||||
'adminlogin' => 'root',
|
||||
'adminpass' => '$adminpassword',
|
||||
);
|
||||
?>
|
||||
EOF
|
||||
|
||||
# Set permissions
|
||||
chown -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
|
||||
|
||||
# Execute Nextcloud's setup step, which creates the Nextcloud sqlite database.
|
||||
# It also wipes it if it exists. And it updates config.php with database
|
||||
# settings and deletes the autoconfig.php file.
|
||||
(cd /usr/local/lib/owncloud; sudo -u www-data php /usr/local/lib/owncloud/index.php;)
|
||||
fi
|
||||
|
||||
# Update config.php.
|
||||
# * trusted_domains is reset to localhost by autoconfig starting with ownCloud 8.1.1,
|
||||
# so set it here. It also can change if the box's PRIMARY_HOSTNAME changes, so
|
||||
# this will make sure it has the right value.
|
||||
# * Some settings weren't included in previous versions of Mail-in-a-Box.
|
||||
# * We need to set the timezone to the system timezone to allow fail2ban to ban
|
||||
# users within the proper timeframe
|
||||
# * We need to set the logdateformat to something that will work correctly with fail2ban
|
||||
# * mail_domain' needs to be set every time we run the setup. Making sure we are setting
|
||||
# the correct domain name if the domain is being change from the previous setup.
|
||||
# Use PHP to read the settings file, modify it, and write out the new settings array.
|
||||
TIMEZONE=$(cat /etc/timezone)
|
||||
CONFIG_TEMP=$(/bin/mktemp)
|
||||
php <<EOF > $CONFIG_TEMP && mv $CONFIG_TEMP $STORAGE_ROOT/owncloud/config.php;
|
||||
<?php
|
||||
include("$STORAGE_ROOT/owncloud/config.php");
|
||||
|
||||
\$CONFIG['trusted_domains'] = array('$PRIMARY_HOSTNAME');
|
||||
|
||||
\$CONFIG['memcache.local'] = '\OC\Memcache\APCu';
|
||||
\$CONFIG['overwrite.cli.url'] = '/cloud';
|
||||
\$CONFIG['mail_from_address'] = 'administrator'; # just the local part, matches our master administrator address
|
||||
|
||||
\$CONFIG['logtimezone'] = '$TIMEZONE';
|
||||
\$CONFIG['logdateformat'] = 'Y-m-d H:i:s';
|
||||
|
||||
\$CONFIG['mail_domain'] = '$PRIMARY_HOSTNAME';
|
||||
|
||||
\$CONFIG['user_backends'] = array(array('class' => 'OC_User_IMAP','arguments' => array('127.0.0.1', 143, null),),);
|
||||
|
||||
echo "<?php\n\\\$CONFIG = ";
|
||||
var_export(\$CONFIG);
|
||||
echo ";";
|
||||
?>
|
||||
EOF
|
||||
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
|
||||
|
||||
# Enable/disable apps. Note that this must be done after the Nextcloud setup.
|
||||
# The firstrunwizard gave Josh all sorts of problems, so disabling that.
|
||||
# user_external is what allows Nextcloud to use IMAP for login. The contacts
|
||||
# and calendar apps are the extensions we really care about here.
|
||||
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:disable firstrunwizard
|
||||
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable user_external
|
||||
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable contacts
|
||||
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable calendar
|
||||
|
||||
# When upgrading, run the upgrade script again now that apps are enabled. It seems like
|
||||
# the first upgrade at the top won't work because apps may be disabled during upgrade?
|
||||
# Check for success (0=ok, 3=no upgrade needed).
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ upgrade
|
||||
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
|
||||
|
||||
# Set PHP FPM values to support large file uploads
|
||||
# (semicolon is the comment character in this file, hashes produce deprecation warnings)
|
||||
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
|
||||
upload_max_filesize=16G \
|
||||
post_max_size=16G \
|
||||
output_buffering=16384 \
|
||||
memory_limit=512M \
|
||||
max_execution_time=600 \
|
||||
short_open_tag=On
|
||||
|
||||
# Set Nextcloud recommended opcache settings
|
||||
tools/editconf.py /etc/php/7.2/cli/conf.d/10-opcache.ini -c ';' \
|
||||
opcache.enable=1 \
|
||||
opcache.enable_cli=1 \
|
||||
opcache.interned_strings_buffer=8 \
|
||||
opcache.max_accelerated_files=10000 \
|
||||
opcache.memory_consumption=128 \
|
||||
opcache.save_comments=1 \
|
||||
opcache.revalidate_freq=1
|
||||
|
||||
# If apc is explicitly disabled we need to enable it
|
||||
if grep -q apc.enabled=0 /etc/php/7.2/mods-available/apcu.ini; then
|
||||
tools/editconf.py /etc/php/7.2/mods-available/apcu.ini -c ';' \
|
||||
apc.enabled=1
|
||||
fi
|
||||
|
||||
# Set up a cron job for Nextcloud.
|
||||
cat > /etc/cron.d/mailinabox-nextcloud << EOF;
|
||||
#!/bin/bash
|
||||
# Mail-in-a-Box
|
||||
*/5 * * * * root sudo -u www-data php -f /usr/local/lib/owncloud/cron.php
|
||||
EOF
|
||||
chmod +x /etc/cron.d/mailinabox-nextcloud
|
||||
|
||||
# Remove previous hourly cronjob
|
||||
rm -f /etc/cron.hourly/mailinabox-owncloud
|
||||
|
||||
# There's nothing much of interest that a user could do as an admin for Nextcloud,
|
||||
# and there's a lot they could mess up, so we don't make any users admins of Nextcloud.
|
||||
# But if we wanted to, we would do this:
|
||||
# ```
|
||||
# for user in $(tools/mail.py user admins); do
|
||||
# sqlite3 $STORAGE_ROOT/owncloud/owncloud.db "INSERT OR IGNORE INTO oc_group_user VALUES ('admin', '$user')"
|
||||
# done
|
||||
# ```
|
||||
|
||||
# Enable PHP modules and restart PHP.
|
||||
restart_service php7.2-fpm
|
||||
@@ -1,311 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Owncloud
|
||||
##########################
|
||||
|
||||
source setup/functions.sh # load our functions
|
||||
source /etc/mailinabox.conf # load global vars
|
||||
|
||||
# ### Installing ownCloud
|
||||
|
||||
echo "Installing ownCloud (contacts/calendar)..."
|
||||
|
||||
apt_install \
|
||||
dbconfig-common \
|
||||
php5-cli php5-sqlite php5-gd php5-imap php5-curl php-pear php-apc curl libapr1 libtool libcurl4-openssl-dev php-xml-parser \
|
||||
php5 php5-dev php5-gd php5-fpm memcached php5-memcached
|
||||
|
||||
apt-get purge -qq -y owncloud*
|
||||
|
||||
# Migrate <= v0.10 setups that stored the ownCloud config.php in /usr/local rather than
|
||||
# in STORAGE_ROOT. Move the file to STORAGE_ROOT.
|
||||
if [ ! -f $STORAGE_ROOT/owncloud/config.php ] \
|
||||
&& [ -f /usr/local/lib/owncloud/config/config.php ]; then
|
||||
|
||||
# Move config.php and symlink back into previous location.
|
||||
echo "Migrating owncloud/config.php to new location."
|
||||
mv /usr/local/lib/owncloud/config/config.php $STORAGE_ROOT/owncloud/config.php \
|
||||
&& \
|
||||
ln -sf $STORAGE_ROOT/owncloud/config.php /usr/local/lib/owncloud/config/config.php
|
||||
fi
|
||||
|
||||
InstallOwncloud() {
|
||||
|
||||
version=$1
|
||||
hash=$2
|
||||
|
||||
echo
|
||||
echo "Upgrading to ownCloud version $version"
|
||||
echo
|
||||
|
||||
# Remove the current owncloud
|
||||
rm -rf /usr/local/lib/owncloud
|
||||
|
||||
# Download and verify
|
||||
wget_verify https://download.owncloud.org/community/owncloud-$version.zip $hash /tmp/owncloud.zip
|
||||
|
||||
# Extract ownCloud
|
||||
unzip -q /tmp/owncloud.zip -d /usr/local/lib
|
||||
rm -f /tmp/owncloud.zip
|
||||
|
||||
# The two apps we actually want are not in ownCloud 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 php /usr/local/lib/owncloud/occ upgrade
|
||||
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then
|
||||
echo "Trying ownCloud upgrade again to work around ownCloud upgrade bug..."
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ upgrade
|
||||
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off
|
||||
echo "...which seemed to work."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
owncloud_ver=9.1.4
|
||||
owncloud_hash=e637cab7b2ca3346164f3506b1a0eb812b4e841a
|
||||
|
||||
# Check if ownCloud dir exist, and check if version matches owncloud_ver (if either doesn't - install/upgrade)
|
||||
if [ ! -d /usr/local/lib/owncloud/ ] \
|
||||
|| ! grep -q $owncloud_ver /usr/local/lib/owncloud/version.php; then
|
||||
|
||||
# Stop php-fpm
|
||||
hide_output service php5-fpm stop
|
||||
|
||||
# Backup the existing ownCloud.
|
||||
# Create a backup directory to store the current installation and database to
|
||||
BACKUP_DIRECTORY=$STORAGE_ROOT/owncloud-backup/`date +"%Y-%m-%d-%T"`
|
||||
mkdir -p "$BACKUP_DIRECTORY"
|
||||
if [ -d /usr/local/lib/owncloud/ ]; then
|
||||
echo "upgrading ownCloud to $owncloud_ver (backing up existing ownCloud installation, configuration and database to directory to $BACKUP_DIRECTORY..."
|
||||
cp -r /usr/local/lib/owncloud "$BACKUP_DIRECTORY/owncloud-install"
|
||||
fi
|
||||
if [ -e /home/user-data/owncloud/owncloud.db ]; then
|
||||
cp /home/user-data/owncloud/owncloud.db $BACKUP_DIRECTORY
|
||||
fi
|
||||
if [ -e /home/user-data/owncloud/config.php ]; then
|
||||
cp /home/user-data/owncloud/config.php $BACKUP_DIRECTORY
|
||||
fi
|
||||
|
||||
# We only need to check if we do upgrades when owncloud was previously installed
|
||||
if [ -e /usr/local/lib/owncloud/version.php ]; then
|
||||
if grep -q "8\.1\.[0-9]" /usr/local/lib/owncloud/version.php; then
|
||||
echo "We are running 8.1.x, upgrading to 8.2.3 first"
|
||||
InstallOwncloud 8.2.3 bfdf6166fbf6fc5438dc358600e7239d1c970613
|
||||
fi
|
||||
|
||||
# If we are upgrading from 8.2.x we should go to 9.0 first. Owncloud doesn't support skipping minor versions
|
||||
if grep -q "8\.2\.[0-9]" /usr/local/lib/owncloud/version.php; then
|
||||
echo "We are running version 8.2.x, upgrading to 9.0.2 first"
|
||||
|
||||
# We need to disable memcached. The upgrade and install fails
|
||||
# with memcached
|
||||
CONFIG_TEMP=$(/bin/mktemp)
|
||||
php <<EOF > $CONFIG_TEMP && mv $CONFIG_TEMP $STORAGE_ROOT/owncloud/config.php;
|
||||
<?php
|
||||
include("$STORAGE_ROOT/owncloud/config.php");
|
||||
|
||||
\$CONFIG['memcache.local'] = '\OC\Memcache\APC';
|
||||
|
||||
echo "<?php\n\\\$CONFIG = ";
|
||||
var_export(\$CONFIG);
|
||||
echo ";";
|
||||
?>
|
||||
EOF
|
||||
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
|
||||
|
||||
# We can now install owncloud 9.0.2
|
||||
InstallOwncloud 9.0.2 72a3d15d09f58c06fa8bee48b9e60c9cd356f9c5
|
||||
|
||||
# The owncloud 9 migration doesn't migrate calendars and contacts
|
||||
# The option to migrate these are removed in 9.1
|
||||
# So the migrations should be done when we have 9.0 installed
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ dav:migrate-addressbooks
|
||||
# The following migration has to be done for each owncloud user
|
||||
for directory in $STORAGE_ROOT/owncloud/*@*/ ; do
|
||||
username=$(basename "${directory}")
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ dav:migrate-calendar $username
|
||||
done
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ dav:sync-birthday-calendar
|
||||
fi
|
||||
fi
|
||||
|
||||
InstallOwncloud $owncloud_ver $owncloud_hash
|
||||
fi
|
||||
|
||||
# ### Configuring ownCloud
|
||||
|
||||
# Setup ownCloud if the ownCloud database does not yet exist. Running setup when
|
||||
# the database does exist wipes the database and user data.
|
||||
if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
||||
# Create user data directory
|
||||
mkdir -p $STORAGE_ROOT/owncloud
|
||||
|
||||
# Create an initial configuration file.
|
||||
instanceid=oc$(echo $PRIMARY_HOSTNAME | sha1sum | fold -w 10 | head -n 1)
|
||||
cat > $STORAGE_ROOT/owncloud/config.php <<EOF;
|
||||
<?php
|
||||
\$CONFIG = array (
|
||||
'datadirectory' => '$STORAGE_ROOT/owncloud',
|
||||
|
||||
'instanceid' => '$instanceid',
|
||||
|
||||
'forcessl' => true, # if unset/false, ownCloud sends a HSTS=0 header, which conflicts with nginx config
|
||||
|
||||
'overwritewebroot' => '/cloud',
|
||||
'overwrite.cli.url' => '/cloud',
|
||||
'user_backends' => array(
|
||||
array(
|
||||
'class'=>'OC_User_IMAP',
|
||||
'arguments'=>array('{127.0.0.1:993/imap/ssl/novalidate-cert}')
|
||||
)
|
||||
),
|
||||
'memcache.local' => '\OC\Memcache\APC',
|
||||
'mail_smtpmode' => 'sendmail',
|
||||
'mail_smtpsecure' => '',
|
||||
'mail_smtpauthtype' => 'LOGIN',
|
||||
'mail_smtpauth' => false,
|
||||
'mail_smtphost' => '',
|
||||
'mail_smtpport' => '',
|
||||
'mail_smtpname' => '',
|
||||
'mail_smtppassword' => '',
|
||||
'mail_from_address' => 'owncloud',
|
||||
'mail_domain' => '$PRIMARY_HOSTNAME',
|
||||
);
|
||||
?>
|
||||
EOF
|
||||
|
||||
# Create an auto-configuration file to fill in database settings
|
||||
# when the install script is run. Make an administrator account
|
||||
# here or else the install can't finish.
|
||||
adminpassword=$(dd if=/dev/urandom bs=1 count=40 2>/dev/null | sha1sum | fold -w 30 | head -n 1)
|
||||
cat > /usr/local/lib/owncloud/config/autoconfig.php <<EOF;
|
||||
<?php
|
||||
\$AUTOCONFIG = array (
|
||||
# storage/database
|
||||
'directory' => '$STORAGE_ROOT/owncloud',
|
||||
'dbtype' => 'sqlite3',
|
||||
|
||||
# create an administrator account with a random password so that
|
||||
# the user does not have to enter anything on first load of ownCloud
|
||||
'adminlogin' => 'root',
|
||||
'adminpass' => '$adminpassword',
|
||||
);
|
||||
?>
|
||||
EOF
|
||||
|
||||
# Set permissions
|
||||
chown -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
|
||||
|
||||
# Execute ownCloud's setup step, which creates the ownCloud sqlite database.
|
||||
# It also wipes it if it exists. And it updates config.php with database
|
||||
# settings and deletes the autoconfig.php file.
|
||||
(cd /usr/local/lib/owncloud; sudo -u www-data php /usr/local/lib/owncloud/index.php;)
|
||||
fi
|
||||
|
||||
# Update config.php.
|
||||
# * trusted_domains is reset to localhost by autoconfig starting with ownCloud 8.1.1,
|
||||
# so set it here. It also can change if the box's PRIMARY_HOSTNAME changes, so
|
||||
# this will make sure it has the right value.
|
||||
# * Some settings weren't included in previous versions of Mail-in-a-Box.
|
||||
# * We need to set the timezone to the system timezone to allow fail2ban to ban
|
||||
# users within the proper timeframe
|
||||
# * We need to set the logdateformat to something that will work correctly with fail2ban
|
||||
# Use PHP to read the settings file, modify it, and write out the new settings array.
|
||||
TIMEZONE=$(cat /etc/timezone)
|
||||
CONFIG_TEMP=$(/bin/mktemp)
|
||||
php <<EOF > $CONFIG_TEMP && mv $CONFIG_TEMP $STORAGE_ROOT/owncloud/config.php;
|
||||
<?php
|
||||
include("$STORAGE_ROOT/owncloud/config.php");
|
||||
|
||||
\$CONFIG['trusted_domains'] = array('$PRIMARY_HOSTNAME');
|
||||
|
||||
\$CONFIG['memcache.local'] = '\OC\Memcache\APC';
|
||||
\$CONFIG['overwrite.cli.url'] = '/cloud';
|
||||
\$CONFIG['mail_from_address'] = 'administrator'; # just the local part, matches our master administrator address
|
||||
|
||||
\$CONFIG['logtimezone'] = '$TIMEZONE';
|
||||
\$CONFIG['logdateformat'] = 'Y-m-d H:i:s';
|
||||
|
||||
echo "<?php\n\\\$CONFIG = ";
|
||||
var_export(\$CONFIG);
|
||||
echo ";";
|
||||
?>
|
||||
EOF
|
||||
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
|
||||
|
||||
# Enable/disable apps. Note that this must be done after the ownCloud setup.
|
||||
# The firstrunwizard gave Josh all sorts of problems, so disabling that.
|
||||
# user_external is what allows ownCloud to use IMAP for login. The contacts
|
||||
# and calendar apps are the extensions we really care about here.
|
||||
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:disable firstrunwizard
|
||||
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable user_external
|
||||
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable contacts
|
||||
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable calendar
|
||||
|
||||
# When upgrading, run the upgrade script again now that apps are enabled. It seems like
|
||||
# the first upgrade at the top won't work because apps may be disabled during upgrade?
|
||||
# Check for success (0=ok, 3=no upgrade needed).
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ upgrade
|
||||
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
|
||||
|
||||
# Set PHP FPM values to support large file uploads
|
||||
# (semicolon is the comment character in this file, hashes produce deprecation warnings)
|
||||
tools/editconf.py /etc/php5/fpm/php.ini -c ';' \
|
||||
upload_max_filesize=16G \
|
||||
post_max_size=16G \
|
||||
output_buffering=16384 \
|
||||
memory_limit=512M \
|
||||
max_execution_time=600 \
|
||||
short_open_tag=On
|
||||
|
||||
# If apc is explicitly disabled we need to enable it
|
||||
if grep -q apc.enabled=0 /etc/php5/mods-available/apcu.ini; then
|
||||
tools/editconf.py /etc/php5/mods-available/apcu.ini -c ';' \
|
||||
apc.enabled=1
|
||||
fi
|
||||
|
||||
# Set up a cron job for owncloud.
|
||||
cat > /etc/cron.hourly/mailinabox-owncloud << EOF;
|
||||
#!/bin/bash
|
||||
# Mail-in-a-Box
|
||||
sudo -u www-data php -f /usr/local/lib/owncloud/cron.php
|
||||
EOF
|
||||
chmod +x /etc/cron.hourly/mailinabox-owncloud
|
||||
|
||||
# There's nothing much of interest that a user could do as an admin for ownCloud,
|
||||
# and there's a lot they could mess up, so we don't make any users admins of ownCloud.
|
||||
# But if we wanted to, we would do this:
|
||||
# ```
|
||||
# for user in $(tools/mail.py user admins); do
|
||||
# sqlite3 $STORAGE_ROOT/owncloud/owncloud.db "INSERT OR IGNORE INTO oc_group_user VALUES ('admin', '$user')"
|
||||
# done
|
||||
# ```
|
||||
|
||||
# Enable PHP modules and restart PHP.
|
||||
php5enmod imap
|
||||
restart_service php5-fpm
|
||||
@@ -4,17 +4,17 @@ if [[ $EUID -ne 0 ]]; then
|
||||
echo
|
||||
echo "sudo $0"
|
||||
echo
|
||||
exit
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check that we are running on Ubuntu 14.04 LTS (or 14.04.xx).
|
||||
if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" != "Ubuntu 14.04 LTS" ]; then
|
||||
echo "Mail-in-a-Box only supports being installed on Ubuntu 14.04, sorry. You are running:"
|
||||
# Check that we are running on Ubuntu 18.04 LTS (or 18.04.xx).
|
||||
if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" != "Ubuntu 18.04 LTS" ]; then
|
||||
echo "Mail-in-a-Box only supports being installed on Ubuntu 18.04, sorry. You are running:"
|
||||
echo
|
||||
lsb_release -d | sed 's/.*:\s*//'
|
||||
echo
|
||||
echo "We can't write scripts that run on every possible setup, sorry."
|
||||
exit
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check that we have enough memory.
|
||||
@@ -26,7 +26,7 @@ fi
|
||||
#
|
||||
# Skip the check if we appear to be running inside of Vagrant, because that's really just for testing.
|
||||
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}')
|
||||
if [ $TOTAL_PHYSICAL_MEM -lt 500000 ]; then
|
||||
if [ $TOTAL_PHYSICAL_MEM -lt 490000 ]; then
|
||||
if [ ! -d /vagrant ]; then
|
||||
TOTAL_PHYSICAL_MEM=$(expr \( \( $TOTAL_PHYSICAL_MEM \* 1024 \) / 1000 \) / 1000)
|
||||
echo "Your Mail-in-a-Box needs more memory (RAM) to function properly."
|
||||
@@ -41,7 +41,7 @@ if [ $TOTAL_PHYSICAL_MEM -lt 750000 ]; then
|
||||
fi
|
||||
|
||||
# Check that tempfs is mounted with exec
|
||||
MOUNTED_TMP_AS_NO_EXEC=$(grep "/tmp.*noexec" /proc/mounts)
|
||||
MOUNTED_TMP_AS_NO_EXEC=$(grep "/tmp.*noexec" /proc/mounts || /bin/true)
|
||||
if [ -n "$MOUNTED_TMP_AS_NO_EXEC" ]; then
|
||||
echo "Mail-in-a-Box has to have exec rights on /tmp, please mount /tmp with exec"
|
||||
exit
|
||||
@@ -53,16 +53,14 @@ if [ -e ~/.wgetrc ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
# Check that we are running on x86_64 or i686, any other architecture is unsupported and
|
||||
# will fail later in the setup when we try to install the custom build lucene packages.
|
||||
#
|
||||
# Set ARM=1 to ignore this check if you have built the packages yourself. If you do this
|
||||
# you are on your own!
|
||||
# Check that we are running on x86_64 or i686 architecture, which are the only
|
||||
# ones we support / test.
|
||||
ARCHITECTURE=$(uname -m)
|
||||
if [ "$ARCHITECTURE" != "x86_64" ] && [ "$ARCHITECTURE" != "i686" ]; then
|
||||
if [ -z "$ARM" ]; then
|
||||
echo "Mail-in-a-Box only supports x86_64 or i686 and will not work on any other architecture, like ARM."
|
||||
echo "Your architecture is $ARCHITECTURE"
|
||||
exit
|
||||
fi
|
||||
echo
|
||||
echo "WARNING:"
|
||||
echo "Mail-in-a-Box has only been tested on x86_64 and i686 platform"
|
||||
echo "architectures. Your architecture, $ARCHITECTURE, may not work."
|
||||
echo "You are on your own."
|
||||
echo
|
||||
fi
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
if [ -z "$NONINTERACTIVE" ]; then
|
||||
if [ -z "${NONINTERACTIVE:-}" ]; then
|
||||
# Install 'dialog' so we can ask the user questions. The original motivation for
|
||||
# this was being able to ask the user for input even if stdin has been redirected,
|
||||
# e.g. if we piped a bootstrapping install script to bash to get started. In that
|
||||
@@ -12,7 +12,9 @@ if [ -z "$NONINTERACTIVE" ]; then
|
||||
apt_get_quiet install dialog python3 python3-pip || exit 1
|
||||
fi
|
||||
|
||||
# email_validator is repeated in setup/management.sh
|
||||
# Installing email_validator is repeated in setup/management.sh, but in setup/management.sh
|
||||
# we install it inside a virtualenv. In this script, we don't have the virtualenv yet
|
||||
# so we install the python package globally.
|
||||
hide_output pip3 install "email_validator>=1.0.0" || exit 1
|
||||
|
||||
message_box "Mail-in-a-Box Installation" \
|
||||
@@ -23,8 +25,8 @@ if [ -z "$NONINTERACTIVE" ]; then
|
||||
fi
|
||||
|
||||
# The box needs a name.
|
||||
if [ -z "$PRIMARY_HOSTNAME" ]; then
|
||||
if [ -z "$DEFAULT_PRIMARY_HOSTNAME" ]; then
|
||||
if [ -z "${PRIMARY_HOSTNAME:-}" ]; then
|
||||
if [ -z "${DEFAULT_PRIMARY_HOSTNAME:-}" ]; then
|
||||
# We recommend to use box.example.com as this hosts name. The
|
||||
# domain the user possibly wants to use is example.com then.
|
||||
# We strip the string "box." from the hostname to get the mail
|
||||
@@ -49,7 +51,7 @@ you really want.
|
||||
# user hit ESC/cancel
|
||||
exit
|
||||
fi
|
||||
while ! management/mailconfig.py validate-email "$EMAIL_ADDR"
|
||||
while ! python3 management/mailconfig.py validate-email "$EMAIL_ADDR"
|
||||
do
|
||||
input_box "Your Email Address" \
|
||||
"That's not a valid email address.\n\nWhat email address are you setting this box up to manage?" \
|
||||
@@ -84,30 +86,30 @@ fi
|
||||
# If the machine is behind a NAT, inside a VM, etc., it may not know
|
||||
# its IP address on the public network / the Internet. Ask the Internet
|
||||
# and possibly confirm with user.
|
||||
if [ -z "$PUBLIC_IP" ]; then
|
||||
if [ -z "${PUBLIC_IP:-}" ]; then
|
||||
# Ask the Internet.
|
||||
GUESSED_IP=$(get_publicip_from_web_service 4)
|
||||
|
||||
# On the first run, if we got an answer from the Internet then don't
|
||||
# ask the user.
|
||||
if [[ -z "$DEFAULT_PUBLIC_IP" && ! -z "$GUESSED_IP" ]]; then
|
||||
if [[ -z "${DEFAULT_PUBLIC_IP:-}" && ! -z "$GUESSED_IP" ]]; then
|
||||
PUBLIC_IP=$GUESSED_IP
|
||||
|
||||
# Otherwise on the first run at least provide a default.
|
||||
elif [[ -z "$DEFAULT_PUBLIC_IP" ]]; then
|
||||
elif [[ -z "${DEFAULT_PUBLIC_IP:-}" ]]; then
|
||||
DEFAULT_PUBLIC_IP=$(get_default_privateip 4)
|
||||
|
||||
# On later runs, if the previous value matches the guessed value then
|
||||
# don't ask the user either.
|
||||
elif [ "$DEFAULT_PUBLIC_IP" == "$GUESSED_IP" ]; then
|
||||
elif [ "${DEFAULT_PUBLIC_IP:-}" == "$GUESSED_IP" ]; then
|
||||
PUBLIC_IP=$GUESSED_IP
|
||||
fi
|
||||
|
||||
if [ -z "$PUBLIC_IP" ]; then
|
||||
if [ -z "${PUBLIC_IP:-}" ]; then
|
||||
input_box "Public IP Address" \
|
||||
"Enter the public IP address of this machine, as given to you by your ISP.
|
||||
\n\nPublic IP address:" \
|
||||
$DEFAULT_PUBLIC_IP \
|
||||
${DEFAULT_PUBLIC_IP:-} \
|
||||
PUBLIC_IP
|
||||
|
||||
if [ -z "$PUBLIC_IP" ]; then
|
||||
@@ -119,27 +121,27 @@ fi
|
||||
|
||||
# Same for IPv6. But it's optional. Also, if it looks like the system
|
||||
# doesn't have an IPv6, don't ask for one.
|
||||
if [ -z "$PUBLIC_IPV6" ]; then
|
||||
if [ -z "${PUBLIC_IPV6:-}" ]; then
|
||||
# Ask the Internet.
|
||||
GUESSED_IP=$(get_publicip_from_web_service 6)
|
||||
MATCHED=0
|
||||
if [[ -z "$DEFAULT_PUBLIC_IPV6" && ! -z "$GUESSED_IP" ]]; then
|
||||
if [[ -z "${DEFAULT_PUBLIC_IPV6:-}" && ! -z "$GUESSED_IP" ]]; then
|
||||
PUBLIC_IPV6=$GUESSED_IP
|
||||
elif [[ "$DEFAULT_PUBLIC_IPV6" == "$GUESSED_IP" ]]; then
|
||||
elif [[ "${DEFAULT_PUBLIC_IPV6:-}" == "$GUESSED_IP" ]]; then
|
||||
# No IPv6 entered and machine seems to have none, or what
|
||||
# the user entered matches what the Internet tells us.
|
||||
PUBLIC_IPV6=$GUESSED_IP
|
||||
MATCHED=1
|
||||
elif [[ -z "$DEFAULT_PUBLIC_IPV6" ]]; then
|
||||
elif [[ -z "${DEFAULT_PUBLIC_IPV6:-}" ]]; then
|
||||
DEFAULT_PUBLIC_IP=$(get_default_privateip 6)
|
||||
fi
|
||||
|
||||
if [[ -z "$PUBLIC_IPV6" && $MATCHED == 0 ]]; then
|
||||
if [[ -z "${PUBLIC_IPV6:-}" && $MATCHED == 0 ]]; then
|
||||
input_box "IPv6 Address (Optional)" \
|
||||
"Enter the public IPv6 address of this machine, as given to you by your ISP.
|
||||
\n\nLeave blank if the machine does not have an IPv6 address.
|
||||
\n\nPublic IPv6 address:" \
|
||||
$DEFAULT_PUBLIC_IPV6 \
|
||||
${DEFAULT_PUBLIC_IPV6:-} \
|
||||
PUBLIC_IPV6
|
||||
|
||||
if [ ! $PUBLIC_IPV6_EXITCODE ]; then
|
||||
@@ -152,10 +154,10 @@ fi
|
||||
# Get the IP addresses of the local network interface(s) that are connected
|
||||
# to the Internet. We need these when we want to have services bind only to
|
||||
# the public network interfaces (not loopback, not tunnel interfaces).
|
||||
if [ -z "$PRIVATE_IP" ]; then
|
||||
if [ -z "${PRIVATE_IP:-}" ]; then
|
||||
PRIVATE_IP=$(get_default_privateip 4)
|
||||
fi
|
||||
if [ -z "$PRIVATE_IPV6" ]; then
|
||||
if [ -z "${PRIVATE_IPV6:-}" ]; then
|
||||
PRIVATE_IPV6=$(get_default_privateip 6)
|
||||
fi
|
||||
if [[ -z "$PRIVATE_IP" && -z "$PRIVATE_IPV6" ]]; then
|
||||
@@ -184,11 +186,11 @@ fi
|
||||
|
||||
# Set STORAGE_USER and STORAGE_ROOT to default values (user-data and /home/user-data), unless
|
||||
# we've already got those values from a previous run.
|
||||
if [ -z "$STORAGE_USER" ]; then
|
||||
STORAGE_USER=$([[ -z "$DEFAULT_STORAGE_USER" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER")
|
||||
if [ -z "${STORAGE_USER:-}" ]; then
|
||||
STORAGE_USER=$([[ -z "${DEFAULT_STORAGE_USER:-}" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER")
|
||||
fi
|
||||
if [ -z "$STORAGE_ROOT" ]; then
|
||||
STORAGE_ROOT=$([[ -z "$DEFAULT_STORAGE_ROOT" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT")
|
||||
if [ -z "${STORAGE_ROOT:-}" ]; then
|
||||
STORAGE_ROOT=$([[ -z "${DEFAULT_STORAGE_ROOT:-}" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT")
|
||||
fi
|
||||
|
||||
# Show the configuration, since the user may have not entered it manually.
|
||||
|
||||
@@ -61,10 +61,11 @@ tools/editconf.py /etc/default/spampd \
|
||||
# content or execute scripts, and it is probably confusing to most users.
|
||||
#
|
||||
# Tell Spamassassin not to modify the original message except for adding
|
||||
# the X-Spam-Status mail header and related headers.
|
||||
# the X-Spam-Status & X-Spam-Score mail headers and related headers.
|
||||
tools/editconf.py /etc/spamassassin/local.cf -s \
|
||||
report_safe=0 \
|
||||
add_header="all Report _REPORT_"
|
||||
"add_header all Report"=_REPORT_ \
|
||||
"add_header all Score"=_SCORE_
|
||||
|
||||
# Bayesean learning
|
||||
# -----------------
|
||||
|
||||
@@ -74,7 +74,7 @@ if [ ! -f $STORAGE_ROOT/ssl/ssl_certificate.pem ]; then
|
||||
CSR=/tmp/ssl_cert_sign_req-$$.csr
|
||||
hide_output \
|
||||
openssl req -new -key $STORAGE_ROOT/ssl/ssl_private_key.pem -out $CSR \
|
||||
-sha256 -subj "/C=/ST=/L=/O=/CN=$PRIMARY_HOSTNAME"
|
||||
-sha256 -subj "/CN=$PRIMARY_HOSTNAME"
|
||||
|
||||
# Generate the self-signed certificate.
|
||||
CERT=$STORAGE_ROOT/ssl/$PRIMARY_HOSTNAME-selfsigned-$(date --rfc-3339=date | sed s/-//g).pem
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
source setup/functions.sh # load our functions
|
||||
|
||||
# Check system setup: Are we running as root on Ubuntu 14.04 on a
|
||||
# Check system setup: Are we running as root on Ubuntu 18.04 on a
|
||||
# machine with enough memory? Is /tmp mounted with exec.
|
||||
# If not, this shows an error and exits.
|
||||
source setup/preflight.sh
|
||||
@@ -14,7 +14,7 @@ source setup/preflight.sh
|
||||
# Python may not be able to read/write files. This is also
|
||||
# in the management daemon startup script and the cron script.
|
||||
|
||||
if [ -z `locale -a | grep en_US.utf8` ]; then
|
||||
if ! locale -a | grep en_US.utf8 > /dev/null; then
|
||||
# Generate locale if not exists
|
||||
hide_output locale-gen en_US.UTF-8
|
||||
fi
|
||||
@@ -60,8 +60,8 @@ source setup/questions.sh
|
||||
# Run some network checks to make sure setup on this machine makes sense.
|
||||
# Skip on existing installs since we don't want this to block the ability to
|
||||
# upgrade, and these checks are also in the control panel status checks.
|
||||
if [ -z "$DEFAULT_PRIMARY_HOSTNAME" ]; then
|
||||
if [ -z "$SKIP_NETWORK_CHECKS" ]; then
|
||||
if [ -z "${DEFAULT_PRIMARY_HOSTNAME:-}" ]; then
|
||||
if [ -z "${SKIP_NETWORK_CHECKS:-}" ]; then
|
||||
source setup/network-checks.sh
|
||||
fi
|
||||
fi
|
||||
@@ -106,7 +106,7 @@ source setup/dkim.sh
|
||||
source setup/spamassassin.sh
|
||||
source setup/web.sh
|
||||
source setup/webmail.sh
|
||||
source setup/owncloud.sh
|
||||
source setup/nextcloud.sh
|
||||
source setup/zpush.sh
|
||||
source setup/management.sh
|
||||
source setup/munin.sh
|
||||
@@ -127,13 +127,23 @@ tools/web_update
|
||||
# fail2ban was first configured, but they should exist now.
|
||||
restart_service fail2ban
|
||||
|
||||
# If DNS is already working, try to provision TLS certficates from Let's Encrypt.
|
||||
# Suppress extra reasons why domains aren't getting a new certificate.
|
||||
management/ssl_certificates.py -q
|
||||
|
||||
# If there aren't any mail users yet, create one.
|
||||
source setup/firstuser.sh
|
||||
|
||||
# Register with Let's Encrypt, including agreeing to the Terms of Service.
|
||||
# We'd let certbot ask the user interactively, but when this script is
|
||||
# run in the recommended curl-pipe-to-bash method there is no TTY and
|
||||
# certbot will fail if it tries to ask.
|
||||
if [ ! -d $STORAGE_ROOT/ssl/lets_encrypt/accounts/acme-v02.api.letsencrypt.org/ ]; then
|
||||
echo
|
||||
echo "-----------------------------------------------"
|
||||
echo "Mail-in-a-Box uses Let's Encrypt to provision free SSL/TLS certificates"
|
||||
echo "to enable HTTPS connections to your box. We're automatically"
|
||||
echo "agreeing you to their subscriber agreement. See https://letsencrypt.org."
|
||||
echo
|
||||
certbot register --register-unsafely-without-email --agree-tos --config-dir $STORAGE_ROOT/ssl/lets_encrypt
|
||||
fi
|
||||
|
||||
# Done.
|
||||
echo
|
||||
echo "-----------------------------------------------"
|
||||
|
||||
124
setup/system.sh
124
setup/system.sh
@@ -14,6 +14,13 @@ source setup/functions.sh # load our functions
|
||||
echo $PRIMARY_HOSTNAME > /etc/hostname
|
||||
hostname $PRIMARY_HOSTNAME
|
||||
|
||||
# ### Fix permissions
|
||||
|
||||
# The default Ubuntu Bionic image on Scaleway throws warnings during setup about incorrect
|
||||
# permissions (group writeable) set on the following directories.
|
||||
|
||||
chmod g-w /etc /etc/default /usr
|
||||
|
||||
# ### Add swap space to the system
|
||||
|
||||
# If the physical memory of the system is below 2GB it is wise to create a
|
||||
@@ -37,9 +44,9 @@ hostname $PRIMARY_HOSTNAME
|
||||
# for reference
|
||||
|
||||
SWAP_MOUNTED=$(cat /proc/swaps | tail -n+2)
|
||||
SWAP_IN_FSTAB=$(grep "swap" /etc/fstab)
|
||||
ROOT_IS_BTRFS=$(grep "\/ .*btrfs" /proc/mounts)
|
||||
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}')
|
||||
SWAP_IN_FSTAB=$(grep "swap" /etc/fstab || /bin/true)
|
||||
ROOT_IS_BTRFS=$(grep "\/ .*btrfs" /proc/mounts || /bin/true)
|
||||
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}' || /bin/true)
|
||||
AVAILABLE_DISK_SPACE=$(df / --output=avail | tail -n 1)
|
||||
if
|
||||
[ -z "$SWAP_MOUNTED" ] &&
|
||||
@@ -68,17 +75,10 @@ then
|
||||
fi
|
||||
fi
|
||||
|
||||
# ### Add Mail-in-a-Box's PPA.
|
||||
|
||||
# We've built several .deb packages on our own that we want to include.
|
||||
# One is a replacement for Ubuntu's stock postgrey package that makes
|
||||
# some enhancements. The other is dovecot-lucene, a Lucene-based full
|
||||
# text search plugin for (and by) dovecot, which is not available in
|
||||
# Ubuntu currently.
|
||||
#
|
||||
# So, first ensure add-apt-repository is installed, then use it to install
|
||||
# the [mail-in-a-box ppa](https://launchpad.net/~mail-in-a-box/+archive/ubuntu/ppa).
|
||||
# ### Add PPAs.
|
||||
|
||||
# We install some non-standard Ubuntu packages maintained by other
|
||||
# third-party providers. First ensure add-apt-repository is installed.
|
||||
|
||||
if [ ! -f /usr/bin/add-apt-repository ]; then
|
||||
echo "Installing add-apt-repository..."
|
||||
@@ -86,16 +86,29 @@ if [ ! -f /usr/bin/add-apt-repository ]; then
|
||||
apt_install software-properties-common
|
||||
fi
|
||||
|
||||
hide_output add-apt-repository -y ppa:mail-in-a-box/ppa
|
||||
# Ensure the universe repository is enabled since some of our packages
|
||||
# come from there and minimal Ubuntu installs may have it turned off.
|
||||
hide_output add-apt-repository -y universe
|
||||
|
||||
# Install the certbot PPA.
|
||||
hide_output add-apt-repository -y ppa:certbot/certbot
|
||||
|
||||
# ### Update Packages
|
||||
|
||||
# Update system packages to make sure we have the latest upstream versions of things from Ubuntu.
|
||||
# Update system packages to make sure we have the latest upstream versions
|
||||
# of things from Ubuntu, as well as the directory of packages provide by the
|
||||
# PPAs so we can install those packages later.
|
||||
|
||||
echo Updating system packages...
|
||||
hide_output apt-get update
|
||||
apt_get_quiet upgrade
|
||||
|
||||
# Old kernels pile up over time and take up a lot of disk space, and because of Mail-in-a-Box
|
||||
# changes there may be other packages that are no longer needed. Clear out anything apt knows
|
||||
# is safe to delete.
|
||||
|
||||
apt_get_quiet autoremove
|
||||
|
||||
# ### Install System Packages
|
||||
|
||||
# Install basic utilities.
|
||||
@@ -112,16 +125,17 @@ apt_get_quiet upgrade
|
||||
# * sudo: allows privileged users to execute commands as root without being root
|
||||
# * coreutils: includes `nproc` tool to report number of processors, mktemp
|
||||
# * bc: allows us to do math to compute sane defaults
|
||||
# * openssh-client: provides ssh-keygen
|
||||
|
||||
echo Installing system packages...
|
||||
apt_install python3 python3-dev python3-pip \
|
||||
netcat-openbsd wget curl git sudo coreutils bc \
|
||||
haveged pollinate unzip \
|
||||
unattended-upgrades cron ntp fail2ban
|
||||
haveged pollinate openssh-client unzip \
|
||||
unattended-upgrades cron ntp fail2ban rsyslog
|
||||
|
||||
# ### Suppress Upgrade Prompts
|
||||
# Since Mail-in-a-Box might jump straight to 18.04 LTS, there's no need
|
||||
# to be reminded about 16.04 on every login.
|
||||
# When Ubuntu 20 comes out, we don't want users to be prompted to upgrade,
|
||||
# because we don't yet support it.
|
||||
if [ -f /etc/update-manager/release-upgrades ]; then
|
||||
tools/editconf.py /etc/update-manager/release-upgrades Prompt=never
|
||||
rm -f /var/lib/ubuntu-release-upgrader/release-upgrade-available
|
||||
@@ -141,8 +155,8 @@ fi
|
||||
# section) and syslog (see #328). There might be other issues, and it's
|
||||
# not likely the user will want to change this, so we only ask on first
|
||||
# setup.
|
||||
if [ -z "$NONINTERACTIVE" ]; then
|
||||
if [ ! -f /etc/timezone ] || [ ! -z $FIRST_TIME_SETUP ]; then
|
||||
if [ -z "${NONINTERACTIVE:-}" ]; then
|
||||
if [ ! -f /etc/timezone ] || [ ! -z ${FIRST_TIME_SETUP:-} ]; then
|
||||
# If the file is missing or this is the user's first time running
|
||||
# Mail-in-a-Box setup, run the interactive timezone configuration
|
||||
# tool.
|
||||
@@ -168,7 +182,6 @@ fi
|
||||
# * DNSSEC signing keys (see `dns.sh`)
|
||||
# * our management server's API key (via Python's os.urandom method)
|
||||
# * Roundcube's SECRET_KEY (`webmail.sh`)
|
||||
# * ownCloud's administrator account password (`owncloud.sh`)
|
||||
#
|
||||
# Why /dev/urandom? It's the same as /dev/random, except that it doesn't wait
|
||||
# for a constant new stream of entropy. In practice, we only need a little
|
||||
@@ -230,7 +243,7 @@ cat > /etc/apt/apt.conf.d/02periodic <<EOF;
|
||||
APT::Periodic::MaxAge "7";
|
||||
APT::Periodic::Update-Package-Lists "1";
|
||||
APT::Periodic::Unattended-Upgrade "1";
|
||||
APT::Periodic::Verbose "1";
|
||||
APT::Periodic::Verbose "0";
|
||||
EOF
|
||||
|
||||
# ### Firewall
|
||||
@@ -238,7 +251,7 @@ EOF
|
||||
# Various virtualized environments like Docker and some VPSs don't provide #NODOC
|
||||
# a kernel that supports iptables. To avoid error-like output in these cases, #NODOC
|
||||
# we skip this if the user sets DISABLE_FIREWALL=1. #NODOC
|
||||
if [ -z "$DISABLE_FIREWALL" ]; then
|
||||
if [ -z "${DISABLE_FIREWALL:-}" ]; then
|
||||
# Install `ufw` which provides a simple firewall configuration.
|
||||
apt_install ufw
|
||||
|
||||
@@ -263,50 +276,75 @@ fi #NODOC
|
||||
|
||||
# ### Local DNS Service
|
||||
|
||||
# Install a local DNS server, rather than using the DNS server provided by the
|
||||
# ISP's network configuration.
|
||||
# Install a local recursive DNS server --- i.e. for DNS queries made by
|
||||
# local services running on this machine.
|
||||
#
|
||||
# We do this to ensure that DNS queries
|
||||
# that *we* make (i.e. looking up other external domains) perform DNSSEC checks.
|
||||
# We could use Google's Public DNS, but we don't want to create a dependency on
|
||||
# Google per our goals of decentralization. `bind9`, as packaged for Ubuntu, has
|
||||
# DNSSEC enabled by default via "dnssec-validation auto".
|
||||
# (This is unrelated to the box's public, non-recursive DNS server that
|
||||
# answers remote queries about domain names hosted on this box. For that
|
||||
# see dns.sh.)
|
||||
#
|
||||
# So we'll be running `bind9` bound to 127.0.0.1 for locally-issued DNS queries
|
||||
# and `nsd` bound to the public ethernet interface for remote DNS queries asking
|
||||
# about our domain names. `nsd` is configured later.
|
||||
# The default systemd-resolved service provides local DNS name resolution. By default it
|
||||
# is a recursive stub nameserver, which means it simply relays requests to an
|
||||
# external nameserver, usually provided by your ISP or configured in /etc/systemd/resolved.conf.
|
||||
#
|
||||
# This won't work for us for three reasons.
|
||||
#
|
||||
# 1) We have higher security goals --- we want DNSSEC to be enforced on all
|
||||
# DNS queries (some upstream DNS servers do, some don't).
|
||||
# 2) We will configure postfix to use DANE, which uses DNSSEC to find TLS
|
||||
# certificates for remote servers. DNSSEC validation *must* be performed
|
||||
# locally because we can't trust an unencrypted connection to an external
|
||||
# DNS server.
|
||||
# 3) DNS-based mail server blacklists (RBLs) typically block large ISP
|
||||
# DNS servers because they only provide free data to small users. Since
|
||||
# we use RBLs to block incoming mail from blacklisted IP addresses,
|
||||
# we have to run our own DNS server. See #1424.
|
||||
#
|
||||
# systemd-resolved has a setting to perform local DNSSEC validation on all
|
||||
# requests (in /etc/systemd/resolved.conf, set DNSSEC=yes), but because it's
|
||||
# a stub server the main part of a request still goes through an upstream
|
||||
# DNS server, which won't work for RBLs. So we really need a local recursive
|
||||
# nameserver.
|
||||
#
|
||||
# We'll install `bind9`, which as packaged for Ubuntu, has DNSSEC enabled by default via "dnssec-validation auto".
|
||||
# We'll have it be bound to 127.0.0.1 so that it does not interfere with
|
||||
# the public, recursive nameserver `nsd` bound to the public ethernet interfaces.
|
||||
#
|
||||
# About the settings:
|
||||
#
|
||||
# * RESOLVCONF=yes will have `bind9` take over /etc/resolv.conf to tell
|
||||
# local services that DNS queries are handled on localhost.
|
||||
# * Adding -4 to OPTIONS will have `bind9` not listen on IPv6 addresses
|
||||
# so that we're sure there's no conflict with nsd, our public domain
|
||||
# name server, on IPV6.
|
||||
# * The listen-on directive in named.conf.options restricts `bind9` to
|
||||
# binding to the loopback interface instead of all interfaces.
|
||||
apt_install bind9 resolvconf
|
||||
apt_install bind9
|
||||
tools/editconf.py /etc/default/bind9 \
|
||||
RESOLVCONF=yes \
|
||||
"OPTIONS=\"-u bind -4\""
|
||||
if ! grep -q "listen-on " /etc/bind/named.conf.options; then
|
||||
# Add a listen-on directive if it doesn't exist inside the options block.
|
||||
sed -i "s/^}/\n\tlisten-on { 127.0.0.1; };\n}/" /etc/bind/named.conf.options
|
||||
fi
|
||||
if [ -f /etc/resolvconf/resolv.conf.d/original ]; then
|
||||
echo "Archiving old resolv.conf (was /etc/resolvconf/resolv.conf.d/original, now /etc/resolvconf/resolv.conf.original)." #NODOC
|
||||
mv /etc/resolvconf/resolv.conf.d/original /etc/resolvconf/resolv.conf.original #NODOC
|
||||
fi
|
||||
|
||||
# First we'll disable systemd-resolved's management of resolv.conf and its stub server.
|
||||
# Breaking the symlink to /run/systemd/resolve/stub-resolv.conf means
|
||||
# systemd-resolved will read it for DNS servers to use. Put in 127.0.0.1,
|
||||
# which is where bind9 will be running. Obviously don't do this before
|
||||
# installing bind9 or else apt won't be able to resolve a server to
|
||||
# download bind9 from.
|
||||
rm -f /etc/resolv.conf
|
||||
tools/editconf.py /etc/systemd/resolved.conf DNSStubListener=no
|
||||
echo "nameserver 127.0.0.1" > /etc/resolv.conf
|
||||
|
||||
# Restart the DNS services.
|
||||
|
||||
restart_service bind9
|
||||
restart_service resolvconf
|
||||
systemctl restart systemd-resolved
|
||||
|
||||
# ### Fail2Ban Service
|
||||
|
||||
# Configure the Fail2Ban installation to prevent dumb bruce-force attacks against dovecot, postfix, ssh, etc.
|
||||
rm -f /etc/fail2ban/jail.local # we used to use this file but don't anymore
|
||||
rm -f /etc/fail2ban/jail.d/defaults-debian.conf # removes default config so we can manage all of fail2ban rules in one config
|
||||
cat conf/fail2ban/jails.conf \
|
||||
| sed "s/PUBLIC_IP/$PUBLIC_IP/g" \
|
||||
| sed "s#STORAGE_ROOT#$STORAGE_ROOT#" \
|
||||
|
||||
75
setup/web.sh
75
setup/web.sh
@@ -18,7 +18,8 @@ fi
|
||||
# Turn off nginx's default website.
|
||||
|
||||
echo "Installing Nginx (web server)..."
|
||||
apt_install nginx php5-fpm
|
||||
|
||||
apt_install nginx php-cli php-fpm
|
||||
|
||||
rm -f /etc/nginx/sites-enabled/default
|
||||
|
||||
@@ -30,26 +31,69 @@ sed "s#STORAGE_ROOT#$STORAGE_ROOT#" \
|
||||
conf/nginx-ssl.conf > /etc/nginx/conf.d/ssl.conf
|
||||
|
||||
# Fix some nginx defaults.
|
||||
#
|
||||
# The server_names_hash_bucket_size seems to prevent long domain names!
|
||||
# The default, according to nginx's docs, depends on "the size of the
|
||||
# processor’s cache line." It could be as low as 32. We fixed it at
|
||||
# 64 in 2014 to accommodate a long domain name (20 characters?). But
|
||||
# even at 64, a 58-character domain name won't work (#93), so now
|
||||
# we're going up to 128.
|
||||
#
|
||||
# Drop TLSv1.0, TLSv1.1, following the Mozilla "Intermediate" recommendations
|
||||
# at https://ssl-config.mozilla.org/#server=nginx&server-version=1.17.0&config=intermediate&openssl-version=1.1.1.
|
||||
tools/editconf.py /etc/nginx/nginx.conf -s \
|
||||
server_names_hash_bucket_size="128;"
|
||||
server_names_hash_bucket_size="128;" \
|
||||
ssl_protocols="TLSv1.2 TLSv1.3;"
|
||||
|
||||
# Tell PHP not to expose its version number in the X-Powered-By header.
|
||||
tools/editconf.py /etc/php5/fpm/php.ini -c ';' \
|
||||
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
|
||||
expose_php=Off
|
||||
|
||||
# Set PHPs default charset to UTF-8, since we use it. See #367.
|
||||
tools/editconf.py /etc/php5/fpm/php.ini -c ';' \
|
||||
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
|
||||
default_charset="UTF-8"
|
||||
|
||||
# Bump up PHP's max_children to support more concurrent connections
|
||||
tools/editconf.py /etc/php5/fpm/pool.d/www.conf -c ';' \
|
||||
pm.max_children=8
|
||||
# Configure the path environment for php-fpm
|
||||
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
||||
env[PATH]=/usr/local/bin:/usr/bin:/bin \
|
||||
|
||||
# Configure php-fpm based on the amount of memory the machine has
|
||||
# This is based on the nextcloud manual for performance tuning: https://docs.nextcloud.com/server/17/admin_manual/installation/server_tuning.html
|
||||
# Some synchronisation issues can occur when many people access the site at once.
|
||||
# The pm=ondemand setting is used for memory constrained machines < 2GB, this is copied over from PR: 1216
|
||||
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}' || /bin/true)
|
||||
if [ $TOTAL_PHYSICAL_MEM -lt 1000000 ]
|
||||
then
|
||||
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
||||
pm=ondemand \
|
||||
pm.max_children=8 \
|
||||
pm.start_servers=2 \
|
||||
pm.min_spare_servers=1 \
|
||||
pm.max_spare_servers=3
|
||||
elif [ $TOTAL_PHYSICAL_MEM -lt 2000000 ]
|
||||
then
|
||||
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
||||
pm=ondemand \
|
||||
pm.max_children=16 \
|
||||
pm.start_servers=4 \
|
||||
pm.min_spare_servers=1 \
|
||||
pm.max_spare_servers=6
|
||||
elif [ $TOTAL_PHYSICAL_MEM -lt 3000000 ]
|
||||
then
|
||||
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
||||
pm=dynamic \
|
||||
pm.max_children=60 \
|
||||
pm.start_servers=6 \
|
||||
pm.min_spare_servers=3 \
|
||||
pm.max_spare_servers=9
|
||||
else
|
||||
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
||||
pm=dynamic \
|
||||
pm.max_children=120 \
|
||||
pm.start_servers=12 \
|
||||
pm.min_spare_servers=6 \
|
||||
pm.max_spare_servers=18
|
||||
fi
|
||||
|
||||
# Other nginx settings will be configured by the management service
|
||||
# since it depends on what domains we're serving, which we don't know
|
||||
@@ -86,24 +130,9 @@ if [ ! -f $STORAGE_ROOT/www/default/index.html ]; then
|
||||
fi
|
||||
chown -R $STORAGE_USER $STORAGE_ROOT/www
|
||||
|
||||
# We previously installed a custom init script to start the PHP FastCGI daemon. #NODOC
|
||||
# Remove it now that we're using php5-fpm. #NODOC
|
||||
if [ -L /etc/init.d/php-fastcgi ]; then
|
||||
echo "Removing /etc/init.d/php-fastcgi, php5-cgi..." #NODOC
|
||||
rm -f /etc/init.d/php-fastcgi #NODOC
|
||||
hide_output update-rc.d php-fastcgi remove #NODOC
|
||||
apt-get -y purge php5-cgi #NODOC
|
||||
fi
|
||||
|
||||
# Remove obsoleted scripts. #NODOC
|
||||
# exchange-autodiscover is now handled by Z-Push. #NODOC
|
||||
for f in webfinger exchange-autodiscover; do #NODOC
|
||||
rm -f /usr/local/bin/mailinabox-$f.php #NODOC
|
||||
done #NODOC
|
||||
|
||||
# Start services.
|
||||
restart_service nginx
|
||||
restart_service php5-fpm
|
||||
restart_service php7.2-fpm
|
||||
|
||||
# Open ports.
|
||||
ufw_allow http
|
||||
|
||||
@@ -22,27 +22,20 @@ source /etc/mailinabox.conf # load global vars
|
||||
echo "Installing Roundcube (webmail)..."
|
||||
apt_install \
|
||||
dbconfig-common \
|
||||
php5 php5-sqlite php5-mcrypt php5-intl php5-json php5-common php-auth php-net-smtp php-net-socket php-net-sieve php-mail-mime php-crypt-gpg php5-gd php5-pspell \
|
||||
tinymce libjs-jquery libjs-jquery-mousewheel libmagic1
|
||||
apt_get_quiet remove php-mail-mimedecode # no longer needed since Roundcube 1.1.3
|
||||
|
||||
# We used to install Roundcube from Ubuntu, without triggering the dependencies #NODOC
|
||||
# on Apache and MySQL, by downloading the debs and installing them manually. #NODOC
|
||||
# Now that we're beyond that, get rid of those debs before installing from source. #NODOC
|
||||
apt-get purge -qq -y roundcube* #NODOC
|
||||
php-cli php-sqlite3 php-intl php-json php-common php-curl php-ldap \
|
||||
php-gd php-pspell tinymce libjs-jquery libjs-jquery-mousewheel libmagic1 php-mbstring
|
||||
|
||||
# Install Roundcube from source if it is not already present or if it is out of date.
|
||||
# Combine the Roundcube version number with the commit hash of vacation_sieve to track
|
||||
# whether we have the latest version.
|
||||
VERSION=1.2.4
|
||||
HASH=e2091ea775b80eda43ab225130d5a2e888c3789a
|
||||
VACATION_SIEVE_VERSION=91ea6f52216390073d1f5b70b5f6bea0bfaee7e5
|
||||
PERSISTENT_LOGIN_VERSION=c4516c4be37d12ef653de86497304e073a863c2a
|
||||
# Combine the Roundcube version number with the commit hash of plugins to track
|
||||
# whether we have the latest version of everything.
|
||||
VERSION=1.4.6
|
||||
HASH=44961ef62bb9c9875141ca34704bbc7d6f36373d
|
||||
PERSISTENT_LOGIN_VERSION=6b3fc450cae23ccb2f393d0ef67aa319e877e435
|
||||
HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5
|
||||
CARDDAV_VERSION=2.0.4
|
||||
CARDDAV_HASH=d93f3cfb3038a519e71c7c3212c1d16f5da609a4
|
||||
CARDDAV_VERSION=3.0.3
|
||||
CARDDAV_HASH=d1e3b0d851ffa2c6bd42bf0c04f70d0e1d0d78f8
|
||||
|
||||
UPDATE_KEY=$VERSION:$VACATION_SIEVE_VERSION:$PERSISTENT_LOGIN_VERSION:$HTML5_NOTIFIER_VERSION:$CARDDAV_VERSION:a
|
||||
UPDATE_KEY=$VERSION:$PERSISTENT_LOGIN_VERSION:$HTML5_NOTIFIER_VERSION:$CARDDAV_VERSION
|
||||
|
||||
# paths that are often reused.
|
||||
RCM_DIR=/usr/local/lib/roundcubemail
|
||||
@@ -58,9 +51,16 @@ elif [[ "$UPDATE_KEY" != `cat /usr/local/lib/roundcubemail/version` ]]; then
|
||||
needs_update=1 #NODOC
|
||||
fi
|
||||
if [ $needs_update == 1 ]; then
|
||||
# if upgrading from 1.3.x, clear the temp_dir
|
||||
if [ -f /usr/local/lib/roundcubemail/version ]; then
|
||||
if [ "$(cat /usr/local/lib/roundcubemail/version | cut -c1-3)" == '1.3' ]; then
|
||||
find /var/tmp/roundcubemail/ -type f ! -name 'RCMTEMP*' -delete
|
||||
fi
|
||||
fi
|
||||
|
||||
# install roundcube
|
||||
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 \
|
||||
/tmp/roundcube.tgz
|
||||
tar -C /usr/local/lib --no-same-owner -zxf /tmp/roundcube.tgz
|
||||
@@ -68,9 +68,6 @@ if [ $needs_update == 1 ]; then
|
||||
mv /usr/local/lib/roundcubemail-$VERSION/ $RCM_DIR
|
||||
rm -f /tmp/roundcube.tgz
|
||||
|
||||
# install roundcube autoreply/vacation plugin
|
||||
git_clone https://github.com/arodier/Roundcube-Plugins.git $VACATION_SIEVE_VERSION plugins/vacation_sieve ${RCM_PLUGIN_DIR}/vacation_sieve
|
||||
|
||||
# install roundcube persistent_login plugin
|
||||
git_clone https://github.com/mfreiholz/Roundcube-Persistent-Login-Plugin.git $PERSISTENT_LOGIN_VERSION '' ${RCM_PLUGIN_DIR}/persistent_login
|
||||
|
||||
@@ -108,20 +105,29 @@ cat > $RCM_CONFIG <<EOF;
|
||||
*/
|
||||
\$config = array();
|
||||
\$config['log_dir'] = '/var/log/roundcubemail/';
|
||||
\$config['temp_dir'] = '/tmp/roundcubemail/';
|
||||
\$config['temp_dir'] = '/var/tmp/roundcubemail/';
|
||||
\$config['db_dsnw'] = 'sqlite:///$STORAGE_ROOT/mail/roundcube/roundcube.sqlite?mode=0640';
|
||||
\$config['default_host'] = 'ssl://localhost';
|
||||
\$config['default_port'] = 993;
|
||||
\$config['imap_conn_options'] = array(
|
||||
'ssl' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
),
|
||||
);
|
||||
\$config['imap_timeout'] = 15;
|
||||
\$config['smtp_server'] = 'tls://127.0.0.1';
|
||||
\$config['smtp_port'] = 587;
|
||||
\$config['smtp_user'] = '%u';
|
||||
\$config['smtp_pass'] = '%p';
|
||||
\$config['smtp_conn_options'] = array(
|
||||
'ssl' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
),
|
||||
);
|
||||
\$config['support_url'] = 'https://mailinabox.email/';
|
||||
\$config['product_name'] = '$PRIMARY_HOSTNAME Webmail';
|
||||
\$config['des_key'] = '$SECRET_KEY';
|
||||
\$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'vacation_sieve', 'persistent_login', 'carddav');
|
||||
\$config['skin'] = 'classic';
|
||||
\$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'persistent_login', 'carddav');
|
||||
\$config['skin'] = 'elastic';
|
||||
\$config['login_autocomplete'] = 2;
|
||||
\$config['password_charset'] = 'UTF-8';
|
||||
\$config['junk_mbox'] = 'Spam';
|
||||
@@ -146,34 +152,15 @@ cat > ${RCM_PLUGIN_DIR}/carddav/config.inc.php <<EOF;
|
||||
'preemptive_auth' => '1',
|
||||
'hide' => false,
|
||||
);
|
||||
EOF
|
||||
|
||||
# Configure vaction_sieve.
|
||||
cat > /usr/local/lib/roundcubemail/plugins/vacation_sieve/config.inc.php <<EOF;
|
||||
<?php
|
||||
/* Do not edit. Written by Mail-in-a-Box. Regenerated on updates. */
|
||||
\$rcmail_config['vacation_sieve'] = array(
|
||||
'date_format' => 'd/m/Y',
|
||||
'working_hours' => array(8,18),
|
||||
'msg_format' => 'text',
|
||||
'logon_transform' => array('#([a-z])[a-z]+(\.|\s)([a-z])#i', '\$1\$3'),
|
||||
'transfer' => array(
|
||||
'mode' => 'managesieve',
|
||||
'ms_activate_script' => true,
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '4190',
|
||||
'usetls' => false,
|
||||
'path' => 'vacation',
|
||||
)
|
||||
);
|
||||
?>
|
||||
EOF
|
||||
|
||||
# Create writable directories.
|
||||
mkdir -p /var/log/roundcubemail /tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
|
||||
chown -R www-data.www-data /var/log/roundcubemail /tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
|
||||
mkdir -p /var/log/roundcubemail /var/tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
|
||||
chown -R www-data.www-data /var/log/roundcubemail /var/tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
|
||||
|
||||
# Ensure the log file monitored by fail2ban exists, or else fail2ban can't start.
|
||||
sudo -u www-data touch /var/log/roundcubemail/errors
|
||||
sudo -u www-data touch /var/log/roundcubemail/errors.log
|
||||
|
||||
# Password changing plugin settings
|
||||
# The config comes empty by default, so we need the settings
|
||||
@@ -210,5 +197,5 @@ chown www-data:www-data $STORAGE_ROOT/mail/roundcube/roundcube.sqlite
|
||||
chmod 664 $STORAGE_ROOT/mail/roundcube/roundcube.sqlite
|
||||
|
||||
# Enable PHP modules.
|
||||
php5enmod mcrypt
|
||||
restart_service php5-fpm
|
||||
phpenmod -v php mcrypt imap
|
||||
restart_service php7.2-fpm
|
||||
|
||||
@@ -17,25 +17,34 @@ source /etc/mailinabox.conf # load global vars
|
||||
|
||||
echo "Installing Z-Push (Exchange/ActiveSync server)..."
|
||||
apt_install \
|
||||
php-soap php5-imap libawl-php php5-xsl
|
||||
php-soap php-imap libawl-php php-xsl
|
||||
|
||||
php5enmod imap
|
||||
phpenmod -v php imap
|
||||
|
||||
# Copy Z-Push into place.
|
||||
TARGETHASH=80cbe53de4ab8dd598d1f2af6f0a23fa396c529a
|
||||
VERSION=2.5.2
|
||||
TARGETHASH=2dc3dbd791b96b0ba2638df0d3d1e03c7e1cbab2
|
||||
needs_update=0 #NODOC
|
||||
if [ ! -f /usr/local/lib/z-push/version ]; then
|
||||
needs_update=1 #NODOC
|
||||
elif [[ $TARGETHASH != `cat /usr/local/lib/z-push/version` ]]; then
|
||||
elif [[ $VERSION != `cat /usr/local/lib/z-push/version` ]]; then
|
||||
# checks if the version
|
||||
needs_update=1 #NODOC
|
||||
fi
|
||||
if [ $needs_update == 1 ]; then
|
||||
git_clone https://github.com/fmbiete/Z-Push-contrib $TARGETHASH '' /usr/local/lib/z-push
|
||||
# Download
|
||||
wget_verify "https://stash.z-hub.io/rest/api/latest/projects/ZP/repos/z-push/archive?at=refs%2Ftags%2F$VERSION&format=zip" $TARGETHASH /tmp/z-push.zip
|
||||
|
||||
# Extract into place.
|
||||
rm -rf /usr/local/lib/z-push /tmp/z-push
|
||||
unzip -q /tmp/z-push.zip -d /tmp/z-push
|
||||
mv /tmp/z-push/src /usr/local/lib/z-push
|
||||
rm -rf /tmp/z-push.zip /tmp/z-push
|
||||
|
||||
rm -f /usr/sbin/z-push-{admin,top}
|
||||
ln -s /usr/local/lib/z-push/z-push-admin.php /usr/sbin/z-push-admin
|
||||
ln -s /usr/local/lib/z-push/z-push-top.php /usr/sbin/z-push-top
|
||||
echo $TARGETHASH > /usr/local/lib/z-push/version
|
||||
echo $VERSION > /usr/local/lib/z-push/version
|
||||
fi
|
||||
|
||||
# Configure default config.
|
||||
@@ -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
|
||||
cp conf/zpush/autodiscover_config.php /usr/local/lib/z-push/autodiscover/config.php
|
||||
sed -i "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" /usr/local/lib/z-push/autodiscover/config.php
|
||||
sed -i "s^define('TIMEZONE', .*^define('TIMEZONE', '$(cat /etc/timezone)');^" /usr/local/lib/z-push/autodiscover/config.php
|
||||
|
||||
# Some directories it will use.
|
||||
|
||||
@@ -92,4 +102,8 @@ EOF
|
||||
|
||||
# Restart service.
|
||||
|
||||
restart_service php5-fpm
|
||||
restart_service php7.2-fpm
|
||||
|
||||
# Fix states after upgrade
|
||||
|
||||
hide_output z-push-admin -a fixstates
|
||||
|
||||
@@ -90,6 +90,26 @@ def pop_test():
|
||||
if M:
|
||||
M.quit()
|
||||
|
||||
def managesieve_test():
|
||||
# We don't have a Python sieve client, so we'll
|
||||
# just run the IMAP client and see what happens.
|
||||
import imaplib
|
||||
|
||||
try:
|
||||
M = imaplib.IMAP4(hostname, 4190)
|
||||
except ConnectionRefusedError:
|
||||
# looks like fail2ban worked
|
||||
raise IsBlocked()
|
||||
|
||||
try:
|
||||
M.login("fakeuser", "fakepassword")
|
||||
raise Exception("authentication didn't fail")
|
||||
except imaplib.IMAP4.error:
|
||||
# authentication should fail
|
||||
pass
|
||||
finally:
|
||||
M.logout() # shuts down connection, has nothing to do with login()
|
||||
|
||||
def http_test(url, expected_status, postdata=None, qsargs=None, auth=None):
|
||||
import urllib.parse
|
||||
import requests
|
||||
@@ -208,6 +228,9 @@ if __name__ == "__main__":
|
||||
# POP
|
||||
run_test(pop_test, [], 20, 30, 4)
|
||||
|
||||
# Managesieve
|
||||
run_test(managesieve_test, [], 20, 30, 4)
|
||||
|
||||
# Mail-in-a-Box control panel
|
||||
run_test(http_test, ["/admin/me", 200], 20, 30, 1)
|
||||
|
||||
|
||||
10
tests/tls.py
10
tests/tls.py
@@ -17,7 +17,7 @@
|
||||
# through some other host you can ssh into (maybe the box
|
||||
# itself?):
|
||||
#
|
||||
# python3 --proxy user@ssh_host yourservername
|
||||
# python3 tls.py --proxy user@ssh_host yourservername
|
||||
#
|
||||
# (This will launch "ssh -N -L10023:yourservername:testport user@ssh_host"
|
||||
# to create a tunnel.)
|
||||
@@ -61,9 +61,9 @@ common_opts = ["--sslv2", "--sslv3", "--tlsv1", "--tlsv1_1", "--tlsv1_2", "--ren
|
||||
# Assumes TLSv1, TLSv1.1, TLSv1.2.
|
||||
#
|
||||
# The 'old' ciphers bring compatibility back to Win XP IE 6.
|
||||
MOZILLA_CIPHERS_MODERN = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK"
|
||||
MOZILLA_CIPHERS_INTERMEDIATE = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
|
||||
MOZILLA_CIPHERS_OLD = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
|
||||
MOZILLA_CIPHERS_MODERN = "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"
|
||||
MOZILLA_CIPHERS_INTERMEDIATE = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"
|
||||
MOZILLA_CIPHERS_OLD = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP"
|
||||
|
||||
######################################################################
|
||||
|
||||
@@ -128,7 +128,7 @@ def sslyze(opts, port, ok_ciphers):
|
||||
proxy_proc.terminate()
|
||||
try:
|
||||
proxy_proc.wait(5)
|
||||
except TimeoutExpired:
|
||||
except subprocess.TimeoutExpired:
|
||||
proxy_proc.kill()
|
||||
|
||||
# Get a list of OpenSSL cipher names.
|
||||
|
||||
@@ -13,18 +13,18 @@ PORT 25
|
||||
|
||||
* Session Resumption:
|
||||
With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts).
|
||||
With TLS Session Tickets: NOT SUPPORTED - TLS ticket not assigned.
|
||||
With TLS Session Tickets: OK - Supported
|
||||
|
||||
* SSLV2 Cipher Suites:
|
||||
Server rejected all cipher suites.
|
||||
|
||||
* TLSV1_2 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES256-GCM-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-GCM-SHA384 ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-GCM-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-SHA384 ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-GCM-SHA384 ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
@@ -33,9 +33,9 @@ PORT 25
|
||||
AES256-SHA256 - 256 bits 250 2.0.0 Ok
|
||||
AES256-SHA - 256 bits 250 2.0.0 Ok
|
||||
AES256-GCM-SHA384 - 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA256 ECDH-256 bits 128 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-GCM-SHA256 ECDH-256 bits 128 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA256 ECDH-521 bits 128 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA ECDH-521 bits 128 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-GCM-SHA256 ECDH-521 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
@@ -46,56 +46,47 @@ PORT 25
|
||||
AES128-SHA256 - 128 bits 250 2.0.0 Ok
|
||||
AES128-SHA - 128 bits 250 2.0.0 Ok
|
||||
AES128-GCM-SHA256 - 128 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-DES-CBC3-SHA ECDH-256 bits 112 bits 250 2.0.0 Ok
|
||||
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits 250 2.0.0 Ok
|
||||
DES-CBC3-SHA - 112 bits 250 2.0.0 Ok
|
||||
|
||||
* TLSV1_1 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
|
||||
AES256-SHA - 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA ECDH-521 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
SEED-SHA - 128 bits 250 2.0.0 Ok
|
||||
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
|
||||
AES128-SHA - 128 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-DES-CBC3-SHA ECDH-256 bits 112 bits 250 2.0.0 Ok
|
||||
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits 250 2.0.0 Ok
|
||||
DES-CBC3-SHA - 112 bits 250 2.0.0 Ok
|
||||
|
||||
* TLSV1 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
|
||||
AES256-SHA - 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
SEED-SHA - 128 bits 250 2.0.0 Ok
|
||||
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
|
||||
AES128-SHA - 128 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-DES-CBC3-SHA ECDH-256 bits 112 bits 250 2.0.0 Ok
|
||||
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits 250 2.0.0 Ok
|
||||
DES-CBC3-SHA - 112 bits 250 2.0.0 Ok
|
||||
|
||||
* SSLV3 Cipher Suites:
|
||||
Server rejected all cipher suites.
|
||||
|
||||
Should Not Offer: DHE-RSA-SEED-SHA, EDH-RSA-DES-CBC3-SHA, SEED-SHA
|
||||
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
|
||||
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
|
||||
* TLSV1 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
|
||||
AES256-SHA - 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA ECDH-521 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
SEED-SHA - 128 bits 250 2.0.0 Ok
|
||||
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
|
||||
AES128-SHA - 128 bits 250 2.0.0 Ok
|
||||
|
||||
Should Not Offer: (none -- good)
|
||||
Could Also Offer: AES128-CCM, AES128-CCM8, AES256-CCM, AES256-CCM8, CAMELLIA128-SHA256, CAMELLIA256-SHA256, DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-DSS-AES256-SHA256, DHE-DSS-CAMELLIA128-SHA, DHE-DSS-CAMELLIA128-SHA256, DHE-DSS-CAMELLIA256-SHA, DHE-DSS-CAMELLIA256-SHA256, DHE-DSS-SEED-SHA, DHE-RSA-AES128-CCM, DHE-RSA-AES128-CCM8, DHE-RSA-AES256-CCM, DHE-RSA-AES256-CCM8, DHE-RSA-CAMELLIA128-SHA256, DHE-RSA-CAMELLIA256-SHA256, DHE-RSA-CHACHA20-POLY1305, ECDHE-ECDSA-AES128-CCM, ECDHE-ECDSA-AES128-CCM8, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-CCM, ECDHE-ECDSA-AES256-CCM8, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-CAMELLIA128-SHA256, ECDHE-ECDSA-CAMELLIA256-SHA384, ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CAMELLIA128-SHA256, ECDHE-RSA-CAMELLIA256-SHA384, ECDHE-RSA-CHACHA20-POLY1305
|
||||
Supported Clients: Yahoo Slurp/Jan 2015, OpenSSL/1.0.2, BingPreview/Jan 2015, OpenSSL/1.0.1l, YandexBot/Jan 2015, Android/4.4.2, Safari/6/iOS 6.0.1, Safari/8/OS X 10.10, Safari/7/OS X 10.9, Safari/7/iOS 7.1, IE/11/Win 8.1, Safari/8/iOS 8.1.2, IE Mobile/11/Win Phone 8.1, IE/11/Win 7, Baidu/Jan 2015, Firefox/31.3.0 ESR/Win 7, Android/5.0.0, Chrome/42/OS X, Java/8u31, Googlebot/Feb 2015, Firefox/37/OS X, Android/4.3, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, Android/4.0.4, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, OpenSSL/0.9.8y, Java/7u25, Android/2.3.7, Java/6u45
|
||||
|
||||
PORT 587
|
||||
--------
|
||||
@@ -112,18 +103,18 @@ PORT 587
|
||||
|
||||
* Session Resumption:
|
||||
With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts).
|
||||
With TLS Session Tickets: NOT SUPPORTED - TLS ticket not assigned.
|
||||
With TLS Session Tickets: OK - Supported
|
||||
|
||||
* SSLV2 Cipher Suites:
|
||||
Server rejected all cipher suites.
|
||||
|
||||
* TLSV1_2 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES256-GCM-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-GCM-SHA384 ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-GCM-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-SHA384 ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-GCM-SHA384 ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
@@ -132,9 +123,9 @@ PORT 587
|
||||
AES256-SHA256 - 256 bits 250 2.0.0 Ok
|
||||
AES256-SHA - 256 bits 250 2.0.0 Ok
|
||||
AES256-GCM-SHA384 - 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA256 ECDH-256 bits 128 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-GCM-SHA256 ECDH-256 bits 128 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA256 ECDH-521 bits 128 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA ECDH-521 bits 128 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-GCM-SHA256 ECDH-521 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
@@ -148,31 +139,14 @@ PORT 587
|
||||
|
||||
* TLSV1_1 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
|
||||
AES256-SHA - 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
SEED-SHA - 128 bits 250 2.0.0 Ok
|
||||
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
|
||||
AES128-SHA - 128 bits 250 2.0.0 Ok
|
||||
|
||||
* TLSV1 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
|
||||
AES256-SHA - 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA ECDH-521 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
@@ -183,9 +157,26 @@ PORT 587
|
||||
* SSLV3 Cipher Suites:
|
||||
Server rejected all cipher suites.
|
||||
|
||||
Should Not Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, CAMELLIA128-SHA, CAMELLIA256-SHA, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA, DHE-RSA-SEED-SHA, SEED-SHA
|
||||
Could Also Offer: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384
|
||||
Supported Clients: OpenSSL/1.0.2, 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
|
||||
* TLSV1 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA ECDH-521 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
|
||||
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
|
||||
AES256-SHA - 256 bits 250 2.0.0 Ok
|
||||
ECDHE-RSA-AES128-SHA ECDH-521 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
|
||||
SEED-SHA - 128 bits 250 2.0.0 Ok
|
||||
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
|
||||
AES128-SHA - 128 bits 250 2.0.0 Ok
|
||||
|
||||
Should Not Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, CAMELLIA128-SHA, CAMELLIA256-SHA, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA, DHE-RSA-AES256-SHA256, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA, DHE-RSA-SEED-SHA, ECDHE-RSA-AES128-SHA, ECDHE-RSA-AES256-SHA, SEED-SHA
|
||||
Could Also Offer: ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305
|
||||
Supported Clients: Yahoo Slurp/Jan 2015, OpenSSL/1.0.2, BingPreview/Jan 2015, OpenSSL/1.0.1l, YandexBot/Jan 2015, Android/4.4.2, Safari/6/iOS 6.0.1, Safari/8/OS X 10.10, Safari/7/OS X 10.9, Safari/7/iOS 7.1, IE/11/Win 8.1, Safari/8/iOS 8.1.2, IE Mobile/11/Win Phone 8.1, IE/11/Win 7, Baidu/Jan 2015, Firefox/31.3.0 ESR/Win 7, Android/5.0.0, Chrome/42/OS X, Java/8u31, Googlebot/Feb 2015, Firefox/37/OS X, Android/4.3, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, Android/4.0.4, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, OpenSSL/0.9.8y, Java/7u25, Android/2.3.7, Java/6u45
|
||||
|
||||
PORT 443
|
||||
--------
|
||||
@@ -197,15 +188,15 @@ PORT 443
|
||||
Client-initiated Renegotiations: OK - Rejected
|
||||
Secure Renegotiation: OK - Supported
|
||||
|
||||
* OpenSSL Heartbleed:
|
||||
OK - Not vulnerable to Heartbleed
|
||||
* HTTP Strict Transport Security:
|
||||
OK - HSTS header received: max-age=15768000
|
||||
|
||||
* 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:
|
||||
OK - HSTS header received: max-age=31536000
|
||||
* OpenSSL Heartbleed:
|
||||
OK - Not vulnerable to Heartbleed
|
||||
|
||||
Unhandled exception when processing --chrome_sha1:
|
||||
exceptions.TypeError - Incorrect padding
|
||||
@@ -223,13 +214,18 @@ exceptions.TypeError - Incorrect padding
|
||||
DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits HTTP 200 OK
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits HTTP 200 OK
|
||||
DHE-RSA-AES256-GCM-SHA384 DH-2048 bits 256 bits HTTP 200 OK
|
||||
AES256-SHA256 - 256 bits HTTP 200 OK
|
||||
AES256-SHA - 256 bits HTTP 200 OK
|
||||
AES256-GCM-SHA384 - 256 bits HTTP 200 OK
|
||||
ECDHE-RSA-AES128-SHA256 ECDH-256 bits 128 bits HTTP 200 OK
|
||||
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK
|
||||
ECDHE-RSA-AES128-GCM-SHA256 ECDH-256 bits 128 bits HTTP 200 OK
|
||||
DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits HTTP 200 OK
|
||||
DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK
|
||||
DHE-RSA-AES128-GCM-SHA256 DH-2048 bits 128 bits HTTP 200 OK
|
||||
DES-CBC3-SHA - 112 bits HTTP 200 OK
|
||||
AES128-SHA256 - 128 bits HTTP 200 OK
|
||||
AES128-SHA - 128 bits HTTP 200 OK
|
||||
AES128-GCM-SHA256 - 128 bits HTTP 200 OK
|
||||
|
||||
* TLSV1_1 Cipher Suites:
|
||||
Preferred:
|
||||
@@ -237,9 +233,13 @@ exceptions.TypeError - Incorrect padding
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits HTTP 200 OK
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits HTTP 200 OK
|
||||
AES256-SHA - 256 bits HTTP 200 OK
|
||||
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK
|
||||
DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK
|
||||
DES-CBC3-SHA - 112 bits HTTP 200 OK
|
||||
AES128-SHA - 128 bits HTTP 200 OK
|
||||
|
||||
* SSLV3 Cipher Suites:
|
||||
Server rejected all cipher suites.
|
||||
|
||||
* TLSV1 Cipher Suites:
|
||||
Preferred:
|
||||
@@ -247,16 +247,14 @@ exceptions.TypeError - Incorrect padding
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits HTTP 200 OK
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits HTTP 200 OK
|
||||
AES256-SHA - 256 bits HTTP 200 OK
|
||||
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK
|
||||
DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK
|
||||
DES-CBC3-SHA - 112 bits HTTP 200 OK
|
||||
|
||||
* SSLV3 Cipher Suites:
|
||||
Server rejected all cipher suites.
|
||||
AES128-SHA - 128 bits HTTP 200 OK
|
||||
|
||||
Should Not Offer: (none -- good)
|
||||
Could Also Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, CAMELLIA128-SHA, CAMELLIA256-SHA, 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
|
||||
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
|
||||
Could Also Offer: ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305
|
||||
Supported Clients: Yahoo Slurp/Jan 2015, OpenSSL/1.0.2, YandexBot/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, Android/4.4.2, Safari/6/iOS 6.0.1, Safari/8/OS X 10.10, Safari/7/OS X 10.9, Safari/7/iOS 7.1, IE/11/Win 8.1, Safari/8/iOS 8.1.2, IE Mobile/11/Win Phone 8.1, IE/11/Win 7, Android/5.0.0, Chrome/42/OS X, Java/8u31, Googlebot/Feb 2015, Firefox/31.3.0 ESR/Win 7, Firefox/37/OS X, Android/4.3, Android/4.2.2, Baidu/Jan 2015, Safari/5.1.9/OS X 10.6.8, Android/4.0.4, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, OpenSSL/0.9.8y, Java/7u25, Android/2.3.7, Java/6u45
|
||||
|
||||
PORT 993
|
||||
--------
|
||||
@@ -279,55 +277,55 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
|
||||
|
||||
* TLSV1_2 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
|
||||
ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA384 ECDH-384 bits 256 bits
|
||||
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
|
||||
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
|
||||
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
|
||||
CAMELLIA256-SHA - 256 bits
|
||||
ECDHE-RSA-AES256-GCM-SHA384 ECDH-384 bits 256 bits
|
||||
DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits
|
||||
DHE-RSA-AES256-GCM-SHA384 DH-2048 bits 256 bits
|
||||
AES256-SHA256 - 256 bits
|
||||
AES256-SHA - 256 bits
|
||||
AES256-GCM-SHA384 - 256 bits
|
||||
ECDHE-RSA-AES128-SHA256 ECDH-384 bits 128 bits
|
||||
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
|
||||
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
|
||||
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
|
||||
CAMELLIA128-SHA - 128 bits
|
||||
ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits
|
||||
DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits
|
||||
DHE-RSA-AES128-SHA DH-2048 bits 128 bits
|
||||
DHE-RSA-AES128-GCM-SHA256 DH-2048 bits 128 bits
|
||||
AES128-SHA256 - 128 bits
|
||||
AES128-SHA - 128 bits
|
||||
AES128-GCM-SHA256 - 128 bits
|
||||
|
||||
* TLSV1_1 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
|
||||
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
|
||||
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
|
||||
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
|
||||
CAMELLIA256-SHA - 256 bits
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits
|
||||
AES256-SHA - 256 bits
|
||||
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
|
||||
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
|
||||
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
|
||||
CAMELLIA128-SHA - 128 bits
|
||||
AES128-SHA - 128 bits
|
||||
|
||||
* TLSV1 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
|
||||
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
|
||||
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
|
||||
CAMELLIA256-SHA - 256 bits
|
||||
AES256-SHA - 256 bits
|
||||
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
|
||||
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
|
||||
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
|
||||
CAMELLIA128-SHA - 128 bits
|
||||
DHE-RSA-AES128-SHA DH-2048 bits 128 bits
|
||||
AES128-SHA - 128 bits
|
||||
|
||||
* SSLV3 Cipher Suites:
|
||||
Server rejected all cipher suites.
|
||||
|
||||
Should Not Offer: AES128-SHA, AES256-SHA, CAMELLIA128-SHA, CAMELLIA256-SHA, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA
|
||||
Could Also Offer: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA256, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-RSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-SHA256, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-RSA-AES256-SHA384
|
||||
Supported Clients: OpenSSL/1.0.2, 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
|
||||
* TLSV1 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits
|
||||
AES256-SHA - 256 bits
|
||||
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
|
||||
DHE-RSA-AES128-SHA DH-2048 bits 128 bits
|
||||
AES128-SHA - 128 bits
|
||||
|
||||
Should Not Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA, DHE-RSA-AES256-SHA256, ECDHE-RSA-AES128-SHA, ECDHE-RSA-AES256-SHA
|
||||
Could Also Offer: ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305
|
||||
Supported Clients: Yahoo Slurp/Jan 2015, OpenSSL/1.0.2, YandexBot/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, Android/4.4.2, Safari/6/iOS 6.0.1, Safari/8/OS X 10.10, Safari/7/OS X 10.9, Safari/7/iOS 7.1, IE/11/Win 8.1, Safari/8/iOS 8.1.2, IE Mobile/11/Win Phone 8.1, IE/11/Win 7, Android/5.0.0, Chrome/42/OS X, Java/8u31, Googlebot/Feb 2015, Firefox/31.3.0 ESR/Win 7, Firefox/37/OS X, Android/4.3, Android/4.2.2, Baidu/Jan 2015, Safari/5.1.9/OS X 10.6.8, Android/4.0.4, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, OpenSSL/0.9.8y, Java/7u25, Android/2.3.7, Java/6u45
|
||||
|
||||
PORT 995
|
||||
--------
|
||||
@@ -350,53 +348,53 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
|
||||
|
||||
* TLSV1_2 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
|
||||
ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA384 ECDH-384 bits 256 bits
|
||||
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
|
||||
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
|
||||
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
|
||||
CAMELLIA256-SHA - 256 bits
|
||||
ECDHE-RSA-AES256-GCM-SHA384 ECDH-384 bits 256 bits
|
||||
DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits
|
||||
DHE-RSA-AES256-GCM-SHA384 DH-2048 bits 256 bits
|
||||
AES256-SHA256 - 256 bits
|
||||
AES256-SHA - 256 bits
|
||||
AES256-GCM-SHA384 - 256 bits
|
||||
ECDHE-RSA-AES128-SHA256 ECDH-384 bits 128 bits
|
||||
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
|
||||
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
|
||||
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
|
||||
CAMELLIA128-SHA - 128 bits
|
||||
ECDHE-RSA-AES128-GCM-SHA256 ECDH-384 bits 128 bits
|
||||
DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits
|
||||
DHE-RSA-AES128-SHA DH-2048 bits 128 bits
|
||||
DHE-RSA-AES128-GCM-SHA256 DH-2048 bits 128 bits
|
||||
AES128-SHA256 - 128 bits
|
||||
AES128-SHA - 128 bits
|
||||
AES128-GCM-SHA256 - 128 bits
|
||||
|
||||
* TLSV1_1 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
|
||||
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
|
||||
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
|
||||
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
|
||||
CAMELLIA256-SHA - 256 bits
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits
|
||||
AES256-SHA - 256 bits
|
||||
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
|
||||
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
|
||||
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
|
||||
CAMELLIA128-SHA - 128 bits
|
||||
AES128-SHA - 128 bits
|
||||
|
||||
* TLSV1 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
|
||||
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
|
||||
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
|
||||
CAMELLIA256-SHA - 256 bits
|
||||
AES256-SHA - 256 bits
|
||||
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
|
||||
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
|
||||
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
|
||||
CAMELLIA128-SHA - 128 bits
|
||||
DHE-RSA-AES128-SHA DH-2048 bits 128 bits
|
||||
AES128-SHA - 128 bits
|
||||
|
||||
* SSLV3 Cipher Suites:
|
||||
Server rejected all cipher suites.
|
||||
|
||||
Should Not Offer: AES128-SHA, AES256-SHA, CAMELLIA128-SHA, CAMELLIA256-SHA, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA
|
||||
Could Also Offer: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA256, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-RSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-SHA256, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-RSA-AES256-SHA384
|
||||
Supported Clients: OpenSSL/1.0.2, 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
|
||||
* TLSV1 Cipher Suites:
|
||||
Preferred:
|
||||
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
|
||||
Accepted:
|
||||
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
|
||||
DHE-RSA-AES256-SHA DH-2048 bits 256 bits
|
||||
AES256-SHA - 256 bits
|
||||
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
|
||||
DHE-RSA-AES128-SHA DH-2048 bits 128 bits
|
||||
AES128-SHA - 128 bits
|
||||
|
||||
Should Not Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA, DHE-RSA-AES256-SHA256, ECDHE-RSA-AES128-SHA, ECDHE-RSA-AES256-SHA
|
||||
Could Also Offer: ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305
|
||||
Supported Clients: Yahoo Slurp/Jan 2015, OpenSSL/1.0.2, YandexBot/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, Android/4.4.2, Safari/6/iOS 6.0.1, Safari/8/OS X 10.10, Safari/7/OS X 10.9, Safari/7/iOS 7.1, IE/11/Win 8.1, Safari/8/iOS 8.1.2, IE Mobile/11/Win Phone 8.1, IE/11/Win 7, Android/5.0.0, Chrome/42/OS X, Java/8u31, Googlebot/Feb 2015, Firefox/31.3.0 ESR/Win 7, Firefox/37/OS X, Android/4.3, Android/4.2.2, Baidu/Jan 2015, Safari/5.1.9/OS X 10.6.8, Android/4.0.4, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, IE Mobile/10/Win Phone 8.0, IE/8-10/Win 7, IE/7/Vista, OpenSSL/0.9.8y, Java/7u25, Android/2.3.7, Java/6u45
|
||||
|
||||
|
||||
@@ -22,15 +22,15 @@ fi
|
||||
|
||||
if [ ! -f $1/config.php ]; then
|
||||
echo "This isn't a valid backup location"
|
||||
exit
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Restoring backup from $1"
|
||||
service php5-fpm stop
|
||||
service php7.2-fpm stop
|
||||
|
||||
# remove the current owncloud installation
|
||||
# remove the current ownCloud/Nextcloud installation
|
||||
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
|
||||
|
||||
# restore access rights
|
||||
@@ -45,5 +45,5 @@ chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
|
||||
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off
|
||||
|
||||
service php5-fpm start
|
||||
service php7.2-fpm start
|
||||
echo "Done"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This script will give you administrative access to the ownCloud
|
||||
# This script will give you administrative access to the Nextcloud
|
||||
# instance running here.
|
||||
#
|
||||
# Run this at your own risk. This is for testing & experimentation
|
||||
@@ -14,10 +14,10 @@ test -z "$1" || ADMIN=$1
|
||||
echo I am going to unlock admin features for $ADMIN.
|
||||
echo You can provide another user to unlock as the first argument of this script.
|
||||
echo
|
||||
echo WARNING: you could break mail-in-a-box when fiddling around with owncloud\'s admin interface
|
||||
echo WARNING: you could break mail-in-a-box when fiddling around with Nextcloud\'s admin interface
|
||||
echo If in doubt, press CTRL-C to cancel.
|
||||
echo
|
||||
echo Press enter to continue.
|
||||
read
|
||||
|
||||
sqlite3 $STORAGE_ROOT/owncloud/owncloud.db "INSERT OR IGNORE INTO oc_group_user VALUES ('admin', '$ADMIN')" && echo Done.
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ group:adduser admin $ADMIN && echo Done.
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user