mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2026-03-12 17:07:23 +01:00
Compare commits
237 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
453091f1fb | ||
|
|
48e0f39179 | ||
|
|
bb641cdfba | ||
|
|
255a65ac98 | ||
|
|
c7badb80d1 | ||
|
|
653cb7ce10 | ||
|
|
d7d8964afc | ||
|
|
6c3696a54a | ||
|
|
9c9cae2096 | ||
|
|
423f1907d0 | ||
|
|
86621392f6 | ||
|
|
368b9c50d0 | ||
|
|
3830facf78 | ||
|
|
d4baac2363 | ||
|
|
f88c907a29 | ||
|
|
89222d519a | ||
|
|
36bef2ee16 | ||
|
|
f6b20a810f | ||
|
|
f2ff14100e | ||
|
|
2c86fa3755 | ||
|
|
3c05fc94ff | ||
|
|
2e00530944 | ||
|
|
32d6728dc9 | ||
|
|
a3c71fe14f | ||
|
|
a24977a96e | ||
|
|
e694f57673 | ||
|
|
cd59de6314 | ||
|
|
a081d04082 | ||
|
|
09577816f8 | ||
|
|
2647febbf5 | ||
|
|
bd0635728c | ||
|
|
584cfe42c4 | ||
|
|
41601a592f | ||
|
|
18c253eeda | ||
|
|
34d58fb720 | ||
|
|
99d0afd650 | ||
|
|
cd717ec94e | ||
|
|
0b7f477b96 | ||
|
|
ab2367e98a | ||
|
|
384c3b5e3d | ||
|
|
d91368c478 | ||
|
|
61105b1ec3 | ||
|
|
b6f90e10c1 | ||
|
|
3af5e55035 | ||
|
|
e03b071e8b | ||
|
|
df93d82d0f | ||
|
|
59913a5e4c | ||
|
|
c3605f6211 | ||
|
|
96b3a29800 | ||
|
|
abb6a1a070 | ||
|
|
041b5f883f | ||
|
|
3b78a8d9d6 | ||
|
|
6ea1a06a12 | ||
|
|
2b00478b8b | ||
|
|
155bcfc654 | ||
|
|
4b07a6aa8f | ||
|
|
2151d81453 | ||
|
|
fd6226187a | ||
|
|
bbe27df413 | ||
|
|
a658abc95f | ||
|
|
9331dbc519 | ||
|
|
8b5eba21c0 | ||
|
|
da5497cd1c | ||
|
|
a27ec68467 | ||
|
|
3ac4b8aca8 | ||
|
|
02feeafe6a | ||
|
|
5f0376bfbf | ||
|
|
d8316119eb |
30
.editorconfig
Normal file
30
.editorconfig
Normal file
@@ -0,0 +1,30 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[Makefile]
|
||||
indent_style = tabs
|
||||
indent_size = 4
|
||||
|
||||
[Vagrantfile]
|
||||
indent_size = 2
|
||||
|
||||
[*.rb]
|
||||
indent_size = 2
|
||||
|
||||
[*.py]
|
||||
indent_style = tabs
|
||||
|
||||
[*.js]
|
||||
indent_size = 2
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ management/__pycache__/
|
||||
tools/__pycache__/
|
||||
externals/
|
||||
.env
|
||||
.vagrant
|
||||
|
||||
288
CHANGELOG.md
288
CHANGELOG.md
@@ -1,6 +1,292 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
v0.41 (February 26, 2019)
|
||||
-------------------------
|
||||
|
||||
System:
|
||||
|
||||
* Missing brute force login attack prevention (fail2ban) filters which stopped working on Ubuntu 18.04 were added back.
|
||||
* Upgrades would fail if Mail-in-a-Box moved to a different directory in `systemctl link`.
|
||||
|
||||
Mail:
|
||||
|
||||
* Incoming messages addressed to more than one local user were rejected because of a bug in spampd packaged by Ubuntu 18.04. A workaround was added.
|
||||
|
||||
Contacts/Calendar:
|
||||
|
||||
* Upgraded Nextcloud from 13.0.6 to 14.0.6.
|
||||
* Upgraded Contacts from 2.1.5 to 2.1.8.
|
||||
* Upgraded Calendar from 1.6.1 to 1.6.4.
|
||||
|
||||
v0.40 (January 12, 2019)
|
||||
------------------------
|
||||
|
||||
This is the first release for Ubuntu 18.04. This version and versions going forward can **only** be installed on Ubuntu 18.04; however, upgrades of existing Ubuntu 14.04 boxes to the latest version supporting Ubuntu 14.04 (v0.30) continue to work as normal.
|
||||
|
||||
When **upgrading**, you **must first upgrade your existing Ubuntu 14.04 Mail-in-a-Box box** to the latest release supporting Ubuntu 14.04 --- that's v0.30 --- before you migrate to Ubuntu 18.04. If you are running an older version of Mail-in-a-Box which has an old version of ownCloud or Nextcloud, you will *not* be able to upgrade your data because older versions of ownCloud and Nextcloud that are required to perform the upgrade *cannot* be run on Ubuntu 18.04. To upgrade from Ubuntu 14.04 to Ubuntu 18.04, you **must create a fresh Ubuntu 18.04 machine** before installing this version. In-place upgrades of servers are not supported. Since Ubuntu's support for Ubuntu 14.04 has almost ended, everyone is encouraged to create a new Ubuntu 18.04 machine and migrate to it.
|
||||
|
||||
For complete upgrade instructions, see:
|
||||
|
||||
https://discourse.mailinabox.email/t/mail-in-a-box-version-v0-40-and-moving-to-ubuntu-18-04/4289
|
||||
|
||||
The changelog for this release follows.
|
||||
|
||||
Setup:
|
||||
|
||||
* Mail-in-a-Box now targets Ubuntu 18.04 LTS, which will have support from Ubuntu through 2022.
|
||||
* Some of the system packages updated in virtue of using Ubuntu 18.04 include postfix (2.11=>3.3) nsd (4.0=>4.1), nginx (1.4=>1.14), PHP (7.0=>7.2), Python (3.4=>3.6), fail2ban (0.8=>0.10), Duplicity (0.6=>0.7).
|
||||
* [Unofficial Bash Strict Mode](http://redsymbol.net/articles/unofficial-bash-strict-mode/) is turned on for setup, which might catch previously uncaught issues during setup.
|
||||
|
||||
Mail:
|
||||
|
||||
* IMAP server-side full text search is no longer supported because we were using a custom-built `dovecot-lucene` package that we are no longer maintaining.
|
||||
* Sending email is now disabled on port 25 --- you must log in to port 587 to send email, per the long-standing mail instructions.
|
||||
* Greylisting may delay more emails from new senders. We were using a custom-built postgrey package previously that whitelisted sending domains in dnswl.org, but we are no longer maintaining that package.
|
||||
|
||||
v0.30 (January 9, 2019)
|
||||
-----------------------
|
||||
|
||||
Setup:
|
||||
|
||||
* Update to Roundcube 1.3.8 and the CardDAV plugin to 3.0.3.
|
||||
* Add missing rsyslog package to install line since some OS images don't have it installed by default.
|
||||
* A log file for nsd was added.
|
||||
|
||||
Control Panel:
|
||||
|
||||
* The users page now documents that passwords should only have ASCII characters to prevent character encoding mismaches between clients and the server.
|
||||
* The users page no longer shows user mailbox sizes because this was extremely slow for very large mailboxes.
|
||||
* The Mail-in-a-Box version is now shown in the system status checks even when the new-version check is disabled.
|
||||
* The alises page now warns that alises should not be used to forward mail off of the box. Mail filters within Roundcube are better for that.
|
||||
* The explanation of greylisting has been improved.
|
||||
|
||||
v0.29 (October 25, 2018)
|
||||
------------------------
|
||||
|
||||
* Starting with v0.28, TLS certificate provisioning wouldn't work on new boxes until the mailinabox setup command was run a second time because of a problem with the non-interactive setup.
|
||||
* Update to Nextcloud 13.0.6.
|
||||
* Update to Roundcube 1.3.7.
|
||||
* Update to Z-Push 2.4.4.
|
||||
* Backup dates listed in the control panel now use an internationalized format.
|
||||
|
||||
v0.28 (July 30, 2018)
|
||||
---------------------
|
||||
|
||||
System:
|
||||
|
||||
* We now use EFF's `certbot` to provision TLS certificates (from Let's Encrypt) instead of our home-grown ACME library.
|
||||
|
||||
Contacts/Calendar:
|
||||
|
||||
* Fix for Mac OS X autoconfig of the calendar.
|
||||
|
||||
Setup:
|
||||
|
||||
* Installing Z-Push broke because of what looks like a change or problem in their git server HTTPS certificate. That's fixed.
|
||||
|
||||
v0.27 (June 14, 2018)
|
||||
---------------------
|
||||
|
||||
Mail:
|
||||
|
||||
* A report of box activity, including sent/received mail totals and logins by user, is now emailed to the box's administrator user each week.
|
||||
* Update Roundcube to version 1.3.6 and Z-Push to version 2.3.9.
|
||||
|
||||
Control Panel:
|
||||
|
||||
* The undocumented feature for proxying web requests to another server now sets X-Forwarded-For.
|
||||
|
||||
v0.26c (February 13, 2018)
|
||||
--------------------------
|
||||
|
||||
Setup:
|
||||
|
||||
* Upgrades from v0.21c (February 1, 2017) or earlier were broken because the intermediate versions of ownCloud used in setup were no longer available from ownCloud.
|
||||
* Some download errors had no output --- there is more output on error now.
|
||||
|
||||
Control Panel:
|
||||
|
||||
* The background service for the control panel was not restarting on updates, leaving the old version running. This was broken in v0.26 and is now fixed.
|
||||
* Installing your own TLS/SSL certificate had been broken since v0.24 because the new version of openssl became stricter about CSR generation parameters.
|
||||
* Fixed password length help text.
|
||||
|
||||
Contacts/Calendar:
|
||||
|
||||
* Upgraded Nextcloud from 12.0.3 to 12.0.5.
|
||||
|
||||
v0.26b (January 25, 2018)
|
||||
-------------------------
|
||||
|
||||
* Fix new installations which broke at the step of asking for the user's desired email address, which was broken by v0.26's changes related to the control panel.
|
||||
* Fix the provisioning of TLS certificates by pinning a Python package we rely on (acme) to an earlier version because our code isn't yet compatible with its current version.
|
||||
* Reduce munin's log_level from debug to warning to prevent massive log files.
|
||||
|
||||
v0.26 (January 18, 2018)
|
||||
------------------------
|
||||
|
||||
Security:
|
||||
|
||||
* HTTPS, IMAP, and POP's TLS settings have been updated to Mozilla's intermediate cipher list recommendation. Some extremely old devices that use less secure TLS ciphers may no longer be able to connect to IMAP/POP.
|
||||
* Updated web HSTS header to use longer six month duration.
|
||||
|
||||
Mail:
|
||||
|
||||
* Adding attachments in Roundcube broke after the last update for some users after rebooting because a temporary directory was deleted on reboot. The temporary directory is now moved from /tmp to /var so that it is persistent.
|
||||
* `X-Spam-Score` header is added to incoming mail.
|
||||
|
||||
Control panel:
|
||||
|
||||
* RSASHA256 is now used for DNSSEC for .lv domains.
|
||||
* Some documentation/links improvements.
|
||||
|
||||
Installer:
|
||||
|
||||
* We now run `apt-get autoremove` at the start of setup to clear out old packages, especially old kernels that take up a lot of space. On the first run, this step may take a long time.
|
||||
* We now fetch Z-Push from its tagged git repository, fixing an installation problem.
|
||||
* Some old PHP5 packages are removed from setup, fixing an installation bug where Apache would get installed.
|
||||
* Python 3 packages for the control panel are now installed using a virtualenv to prevent installation errors due to conflicts in the cryptography/openssl packages between OS-installed packages and pip-installed packages.
|
||||
|
||||
v0.25 (November 15, 2017)
|
||||
-------------------------
|
||||
|
||||
This update is a security update addressing [CVE-2017-16651, a vulnerability in Roundcube webmail that allows logged-in users to access files on the local filesystem](https://roundcube.net/news/2017/11/08/security-updates-1.3.3-1.2.7-and-1.1.10).
|
||||
|
||||
Mail:
|
||||
|
||||
* Update to Roundcube 1.3.3.
|
||||
|
||||
Control Panel:
|
||||
|
||||
* Allow custom DNS records to be set for DNS wildcard subdomains (i.e. `*`).
|
||||
|
||||
v0.24 (October 3, 2017)
|
||||
-----------------------
|
||||
|
||||
System:
|
||||
|
||||
* Install PHP7 via a PPA. Switch to the on-demand process manager.
|
||||
|
||||
Mail:
|
||||
|
||||
* Updated to [Roundcube 1.3.1](https://roundcube.net/news/2017/06/26/roundcube-webmail-1.3.0-released), but unfortunately dropping the Vacation plugin because it has not been supported by its author and is not compatible with Roundcube 1.3, and updated the persistent login plugin.
|
||||
* Updated to [Z-Push 2.3.8](http://download.z-push.org/final/2.3/z-push-2.3.8.txt).
|
||||
* Dovecot now uses stronger 2048 bit DH params for better forward secrecy.
|
||||
|
||||
Nextcloud:
|
||||
|
||||
* Nextcloud updated to 12.0.3, using PHP7.
|
||||
|
||||
Control Panel:
|
||||
|
||||
* Nameserver (NS) records can now be set on custom domains.
|
||||
* Fix an erroneous status check error due to IPv6 address formatting.
|
||||
* Aliases for administrative addresses can now be set to send mail to +tag administrative addresses.
|
||||
|
||||
v0.23a (May 31, 2017)
|
||||
---------------------
|
||||
|
||||
Corrects a problem in the new way third-party assets are downloaded during setup for the control panel, since v0.23.
|
||||
|
||||
v0.23 (May 30, 2017)
|
||||
--------------------
|
||||
|
||||
Mail:
|
||||
|
||||
* The default theme for Roundcube was changed to the nicer Larry theme.
|
||||
* Exchange/ActiveSync support has been replaced with z-push 2.3.6 from z-push.org (rather than z-push-contrib).
|
||||
|
||||
ownCloud (now Nextcloud):
|
||||
|
||||
* ownCloud is replaced with Nextcloud 10.0.5.
|
||||
* Fixed an error in Owncloud/Nextcloud setup not updating domain when changing hostname.
|
||||
|
||||
Control Panel/Management:
|
||||
|
||||
* Fix an error in the control panel showing rsync backup status.
|
||||
* Fix an error in the control panel related to IPv6 addresses.
|
||||
* TLS certificates for internationalized domain names can now be provisioned from Let's Encrypt automatically.
|
||||
* Third-party assets used in the control panel (jQuery/Bootstrap) are now downloaded during setup and served from the box rather than from a CDN.
|
||||
|
||||
DNS:
|
||||
|
||||
* Add support for custom CAA records.
|
||||
|
||||
v0.22 (April 2, 2017)
|
||||
---------------------
|
||||
|
||||
Mail:
|
||||
|
||||
* The CardDAV plugin has been added to Roundcube so that your ownCloud contacts are available in webmail.
|
||||
* Upgraded to Roundcube 1.2.4 and updated the persistent login plugin.
|
||||
* Allow larger messages to be checked by SpamAssassin.
|
||||
* Dovecot's vsz memory limit has been increased proportional to system memory.
|
||||
* Newly set user passwords must be at least eight characters.
|
||||
|
||||
ownCloud:
|
||||
|
||||
* Upgraded to ownCloud 9.1.4.
|
||||
|
||||
Control Panel/Management:
|
||||
|
||||
* The status checks page crashed when the mailinabox.email website was down - that's fixed.
|
||||
* Made nightly re-provisioning of TLS certificates less noisy.
|
||||
* Fixed bugs in rsync backup method and in the list of recent backups.
|
||||
* Fixed incorrect status checks errors about IPv6 addresses.
|
||||
* Fixed incorrect status checks errors for secondary nameservers if round-robin custom A records are set.
|
||||
* The management mail_log.py tool has been rewritten.
|
||||
|
||||
DNS:
|
||||
|
||||
* Added support for DSA, ED25519, and custom SSHFP records.
|
||||
|
||||
System:
|
||||
|
||||
* The SSH fail2ban jail was not activated.
|
||||
|
||||
Installation:
|
||||
|
||||
* At the end of installation, the SHA256 -- rather than SHA1 -- hash of the system's TLS certificate is shown.
|
||||
|
||||
v0.21c (February 1, 2017)
|
||||
-------------------------
|
||||
|
||||
Installations and upgrades started failing about 10 days ago with the error "ImportError: No module named 'packaging'" after an upstream package (Python's setuptools) was updated by its maintainers. The updated package conflicted with Ubuntu 14.04's version of another package (Python's pip). This update upgrades both packages to remove the conflict.
|
||||
|
||||
If you already encountered the error during installation or upgrade of Mail-in-a-Box, this update may not correct the problem on your existing system. See https://discourse.mailinabox.email/t/v0-21c-release-fixes-python-package-installation-issue/1881 for help if the problem persists after upgrading to this version of Mail-in-a-Box.
|
||||
|
||||
v0.21b (December 4, 2016)
|
||||
-------------------------
|
||||
|
||||
This update corrects a first-time installation issue introduced in v0.21 caused by the new Exchange/ActiveSync feature.
|
||||
|
||||
v0.21 (November 30, 2016)
|
||||
-------------------------
|
||||
|
||||
This version updates ownCloud, which may include security fixes, and makes some other smaller improvements.
|
||||
|
||||
Mail:
|
||||
|
||||
* Header privacy filters were improperly running on the contents of forwarded email --- that's fixed.
|
||||
* We have another go at fixing a long-standing issue with training the spam filter (because of a file permissions issue).
|
||||
* Exchange/ActiveSync will now use your display name set in Roundcube in the From: line of outgoing email.
|
||||
|
||||
ownCloud:
|
||||
|
||||
* Updated ownCloud to version 9.1.1.
|
||||
|
||||
Control panel:
|
||||
|
||||
* Backups can now be made using rsync-over-ssh!
|
||||
* Status checks failed if the system doesn't support iptables or doesn't have ufw installed.
|
||||
* Added support for SSHFP records when sshd listens on non-standard ports.
|
||||
* Recommendations for TLS certificate providers were removed now that everyone mostly uses Let's Encrypt.
|
||||
|
||||
System:
|
||||
|
||||
* Ubuntu's "Upgrade to 16.04" notice is suppressed since you should not do that.
|
||||
* Lowered memory requirements to 512MB, display a warning if system memory is below 768MB.
|
||||
|
||||
v0.20 (September 23, 2016)
|
||||
--------------------------
|
||||
|
||||
@@ -67,7 +353,7 @@ v0.18 (May 15, 2016)
|
||||
|
||||
ownCloud:
|
||||
|
||||
* Updated to ownCloud to 8.2.3
|
||||
* Updated to ownCloud to 8.2.3
|
||||
|
||||
Mail:
|
||||
|
||||
|
||||
@@ -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
@@ -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.20
|
||||
$ git verify-tag v0.41
|
||||
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.20
|
||||
$ git checkout v0.41
|
||||
|
||||
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
|
||||
--------------------
|
||||
@@ -95,5 +100,5 @@ The History
|
||||
* In 2007 I wrote a relatively popular Mozilla Thunderbird extension that added client-side SPF and DKIM checks to mail to warn users about possible phishing: [add-on page](https://addons.mozilla.org/en-us/thunderbird/addon/sender-verification-anti-phish/), [source](https://github.com/JoshData/thunderbird-spf).
|
||||
* In August 2013 I began Mail-in-a-Box by combining my own mail server configuration with the setup in ["NSA-proof your email in 2 hours"](http://sealedabstract.com/code/nsa-proof-your-e-mail-in-2-hours/) and making the setup steps reproducible with bash scripts.
|
||||
* Mail-in-a-Box was a semifinalist in the 2014 [Knight News Challenge](https://www.newschallenge.org/challenge/2014/submissions/mail-in-a-box), but it was not selected as a winner.
|
||||
* Mail-in-a-Box hit the front page of Hacker News in [April](https://news.ycombinator.com/item?id=7634514) 2014, [September](https://news.ycombinator.com/item?id=8276171) 2014, and [May](https://news.ycombinator.com/item?id=9624267) 2015.
|
||||
* Mail-in-a-Box hit the front page of Hacker News in [April](https://news.ycombinator.com/item?id=7634514) 2014, [September](https://news.ycombinator.com/item?id=8276171) 2014, [May](https://news.ycombinator.com/item?id=9624267) 2015, and [November](https://news.ycombinator.com/item?id=13050500) 2016.
|
||||
* FastCompany mentioned Mail-in-a-Box a [roundup of privacy projects](http://www.fastcompany.com/3047645/your-own-private-cloud) on June 26, 2015.
|
||||
|
||||
15
Vagrantfile
vendored
15
Vagrantfile
vendored
@@ -2,26 +2,23 @@
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "ubuntu14.04"
|
||||
config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"
|
||||
config.vm.box = "ubuntu/bionic64"
|
||||
|
||||
# Network config: Since it's a mail server, the machine must be connected
|
||||
# to the public web. However, we currently don't want to expose SSH since
|
||||
# the machine's box will let anyone log into it. So instead we'll put the
|
||||
# machine on a private network.
|
||||
config.vm.hostname = "mailinabox"
|
||||
config.vm.hostname = "mailinabox.lan"
|
||||
config.vm.network "private_network", ip: "192.168.50.4"
|
||||
|
||||
config.vm.provision :shell, :inline => <<-SH
|
||||
# Set environment variables so that the setup script does
|
||||
# not ask any questions during provisioning. We'll let the
|
||||
# machine figure out its own public IP and it'll take a
|
||||
# subdomain on our justtesting.email domain so we can get
|
||||
# started quickly.
|
||||
# Set environment variables so that the setup script does
|
||||
# not ask any questions during provisioning. We'll let the
|
||||
# machine figure out its own public IP.
|
||||
export NONINTERACTIVE=1
|
||||
export PUBLIC_IP=auto
|
||||
export PUBLIC_IPV6=auto
|
||||
export PRIMARY_HOSTNAME=auto-easy
|
||||
export PRIMARY_HOSTNAME=auto
|
||||
#export SKIP_NETWORK_CHECKS=1
|
||||
|
||||
# Start the setup script.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -69,12 +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
|
||||
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
|
||||
@@ -70,7 +70,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,10 +12,9 @@
|
||||
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;
|
||||
@@ -41,13 +43,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-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
|
||||
ssl_dhparam STORAGE_ROOT/ssl/dh2048.pem;
|
||||
|
||||
# Basically the nginx configuration I use at konklone.com.
|
||||
# I check it using https://www.ssllabs.com/ssltest/analyze.html?d=konklone.com
|
||||
#
|
||||
# To provide feedback, please tweet at @konklone or email eric@konklone.com.
|
||||
# Comments on gists don't notify the author.
|
||||
#
|
||||
# Thanks to WubTheCaptain (https://wubthecaptain.eu) for his help and ciphersuites.
|
||||
# Thanks to Ilya Grigorik (https://www.igvita.com) for constant inspiration.
|
||||
|
||||
# Path to certificate and private key.
|
||||
# The .crt may omit the root CA cert, if it's a standard CA that ships with clients.
|
||||
#ssl_certificate /path/to/unified.crt;
|
||||
#ssl_certificate_key /path/to/my-private-decrypted.key;
|
||||
|
||||
# Tell browsers to require SSL (warning: difficult to change your mind)
|
||||
# Handled by the management daemon because we can toggle this version or a
|
||||
# preload version.
|
||||
#add_header Strict-Transport-Security max-age=31536000;
|
||||
|
||||
# Prefer certain ciphersuites, to enforce Forward Secrecy and avoid known vulnerabilities.
|
||||
#
|
||||
# Forces forward secrecy in all browsers and clients that can use TLS,
|
||||
# but with a small exception (DES-CBC3-SHA) for IE8/XP users.
|
||||
#
|
||||
# Reference client: https://www.ssllabs.com/ssltest/analyze.html
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !MD5 !EXP !DSS !PSK !SRP !kECDH !CAMELLIA !RC4 !SEED';
|
||||
|
||||
# Cut out (the old, broken) SSLv3 entirely.
|
||||
# This **excludes IE6 users** and (apparently) Yandexbot.
|
||||
# Just comment out if you need to support IE6, bless your soul.
|
||||
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
|
||||
|
||||
# Turn on session resumption, using a 10 min cache shared across nginx processes,
|
||||
# as recommended by http://nginx.org/en/docs/http/configuring_https_servers.html
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
#keepalive_timeout 70; # in Ubuntu 14.04/nginx 1.4.6 the default is 65, so plenty good
|
||||
ssl_session_cache shared:SSL:50m;
|
||||
ssl_session_timeout 1d;
|
||||
|
||||
# Buffer size of 1400 bytes fits in one MTU.
|
||||
# nginx 1.5.9+ ONLY
|
||||
#ssl_buffer_size 1400;
|
||||
ssl_buffer_size 1400;
|
||||
|
||||
# SPDY header compression (0 for none, 9 for slow/heavy compression). Preferred is 6.
|
||||
#
|
||||
# BUT: header compression is flawed and vulnerable in SPDY versions 1 - 3.
|
||||
# Disable with 0, until using a version of nginx with SPDY 4.
|
||||
spdy_headers_comp 0;
|
||||
|
||||
# Now let's really get fancy, and pre-generate a 2048 bit random parameter
|
||||
# for DH elliptic curves. If not created and specified, default is only 1024 bits.
|
||||
#
|
||||
# Generated by OpenSSL with the following command:
|
||||
# openssl dhparam -outform pem -out dhparam2048.pem 2048
|
||||
#
|
||||
# Note: raising the bits to 2048 excludes Java 6 clients. Comment out if a problem.
|
||||
ssl_dhparam STORAGE_ROOT/ssl/dh2048.pem;
|
||||
|
||||
|
||||
# OCSP stapling - means nginx will poll the CA for signed OCSP responses,
|
||||
# and send them to clients so clients don't make their own OCSP calls.
|
||||
# http://en.wikipedia.org/wiki/OCSP_stapling
|
||||
#
|
||||
# while the ssl_certificate above may omit the root cert if the CA is trusted,
|
||||
# ssl_trusted_certificate below must point to a chain of **all** certs
|
||||
# in the trust path - (your cert, intermediary certs, root cert)
|
||||
#
|
||||
# 8.8.8.8 and 8.8.4.4 below are Google's public IPv4 DNS servers.
|
||||
# nginx will use them to talk to the CA.
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
resolver 127.0.0.1 valid=86400;
|
||||
resolver_timeout 10;
|
||||
|
||||
# h/t https://gist.github.com/konklone/6532544
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
define('IMAP_SERVER', '127.0.0.1');
|
||||
define('IMAP_PORT', 993);
|
||||
define('IMAP_OPTIONS', '/ssl/norsh/novalidate-cert');
|
||||
define('IMAP_DEFAULTFROM', '');
|
||||
define('IMAP_DEFAULTFROM', 'sql');
|
||||
|
||||
define('SYSTEM_MIME_TYPES_MAPPING', '/etc/mime.types');
|
||||
define('IMAP_AUTOSEEN_ON_DELETE', false);
|
||||
@@ -23,15 +23,19 @@ define('IMAP_FOLDER_TRASH', 'TRASH');
|
||||
define('IMAP_FOLDER_SPAM', 'SPAM');
|
||||
define('IMAP_FOLDER_ARCHIVE', 'ARCHIVE');
|
||||
|
||||
define('IMAP_INLINE_FORWARD', true);
|
||||
define('IMAP_EXCLUDED_FOLDERS', '');
|
||||
|
||||
// not used
|
||||
define('IMAP_FROM_SQL_DSN', '');
|
||||
define('IMAP_FROM_SQL_DSN', 'sqlite:STORAGE_ROOT/mail/roundcube/roundcube.sqlite');
|
||||
define('IMAP_FROM_SQL_USER', '');
|
||||
define('IMAP_FROM_SQL_PASSWORD', '');
|
||||
define('IMAP_FROM_SQL_OPTIONS', serialize(array(PDO::ATTR_PERSISTENT => true)));
|
||||
define('IMAP_FROM_SQL_QUERY', "select first_name, last_name, mail_address from users where mail_address = '#username@#domain'");
|
||||
define('IMAP_FROM_SQL_FIELDS', serialize(array('first_name', 'last_name', 'mail_address')));
|
||||
define('IMAP_FROM_SQL_FROM', '#first_name #last_name <#mail_address>');
|
||||
define('IMAP_FROM_SQL_QUERY', "SELECT name, email FROM identities i INNER JOIN users u ON i.user_id = u.user_id WHERE u.username = '#username' AND i.standard = 1 AND i.del = 0 AND i.name <> ''");
|
||||
define('IMAP_FROM_SQL_FIELDS', serialize(array('name', 'email')));
|
||||
define('IMAP_FROM_SQL_FROM', '#name <#email>');
|
||||
define('IMAP_FROM_SQL_FULLNAME', '#name');
|
||||
|
||||
// not used
|
||||
define('IMAP_FROM_LDAP_SERVER', '');
|
||||
define('IMAP_FROM_LDAP_SERVER_PORT', '389');
|
||||
define('IMAP_FROM_LDAP_USER', 'cn=zpush,ou=servers,dc=zpush,dc=org');
|
||||
@@ -40,6 +44,7 @@ define('IMAP_FROM_LDAP_BASE', 'dc=zpush,dc=org');
|
||||
define('IMAP_FROM_LDAP_QUERY', '(mail=#username@#domain)');
|
||||
define('IMAP_FROM_LDAP_FIELDS', serialize(array('givenname', 'sn', 'mail')));
|
||||
define('IMAP_FROM_LDAP_FROM', '#givenname #sn <#mail>');
|
||||
define('IMAP_FROM_LDAP_FULLNAME', '#givenname #sn');
|
||||
|
||||
define('IMAP_SMTP_METHOD', 'sendmail');
|
||||
|
||||
@@ -47,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.
|
||||
@@ -10,29 +10,34 @@
|
||||
import os, os.path, shutil, glob, re, datetime, sys
|
||||
import dateutil.parser, dateutil.relativedelta, dateutil.tz
|
||||
import rtyaml
|
||||
from exclusiveprocess import Lock
|
||||
|
||||
from utils import exclusive_process, load_environment, shell, wait_for_service, fix_boto
|
||||
from utils import load_environment, shell, wait_for_service, fix_boto
|
||||
|
||||
rsync_ssh_options = [
|
||||
"--ssh-options='-i /root/.ssh/id_rsa_miab'",
|
||||
"--rsync-options=-e \"/usr/bin/ssh -oStrictHostKeyChecking=no -oBatchMode=yes -p 22 -i /root/.ssh/id_rsa_miab\"",
|
||||
]
|
||||
|
||||
def backup_status(env):
|
||||
# Root folder
|
||||
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
|
||||
|
||||
# What is the current status of backups?
|
||||
# Query duplicity to get a list of all backups.
|
||||
# Use the number of volumes to estimate the size.
|
||||
# If backups are dissbled, return no status.
|
||||
config = get_backup_config(env)
|
||||
now = datetime.datetime.now(dateutil.tz.tzlocal())
|
||||
|
||||
# Are backups dissbled?
|
||||
if config["target"] == "off":
|
||||
return { }
|
||||
|
||||
# Query duplicity to get a list of all full and incremental
|
||||
# backups available.
|
||||
|
||||
backups = { }
|
||||
now = datetime.datetime.now(dateutil.tz.tzlocal())
|
||||
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
|
||||
backup_cache_dir = os.path.join(backup_root, 'cache')
|
||||
|
||||
def reldate(date, ref, clip):
|
||||
if ref < date: return clip
|
||||
rd = dateutil.relativedelta.relativedelta(ref, date)
|
||||
if rd.years > 1: return "%d years, %d months" % (rd.years, rd.months)
|
||||
if rd.years == 1: return "%d year, %d months" % (rd.years, rd.months)
|
||||
if rd.months > 1: return "%d months, %d days" % (rd.months, rd.days)
|
||||
if rd.months == 1: return "%d month, %d days" % (rd.months, rd.days)
|
||||
if rd.days >= 7: return "%d days" % rd.days
|
||||
@@ -46,12 +51,13 @@ 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', [
|
||||
"/usr/bin/duplicity",
|
||||
"collection-status",
|
||||
@@ -59,7 +65,7 @@ def backup_status(env):
|
||||
"--gpg-options", "--cipher-algo=AES256",
|
||||
"--log-fd", "1",
|
||||
config["target"],
|
||||
],
|
||||
] + rsync_ssh_options,
|
||||
get_env(env),
|
||||
trap=True)
|
||||
if code != 0:
|
||||
@@ -71,12 +77,20 @@ def backup_status(env):
|
||||
backup = parse_line(line)
|
||||
backups[backup["date"]] = backup
|
||||
|
||||
# Look at the target to get the sizes of each of the backups. There is more than one file per backup.
|
||||
# Look at the target directly to get the sizes of each of the backups. There is more than one file per backup.
|
||||
# Starting with duplicity in Ubuntu 18.04, "signatures" files have dates in their
|
||||
# filenames that are a few seconds off the backup date and so don't line up
|
||||
# with the list of backups we have. Track unmatched files so we know how much other
|
||||
# space is used for those.
|
||||
unmatched_file_size = 0
|
||||
for fn, size in list_target_files(config):
|
||||
m = re.match(r"duplicity-(full|full-signatures|(inc|new-signatures)\.(?P<incbase>\d+T\d+Z)\.to)\.(?P<date>\d+T\d+Z)\.", fn)
|
||||
if not m: continue # not a part of a current backup chain
|
||||
key = m.group("date")
|
||||
backups[key]["size"] += size
|
||||
if key in backups:
|
||||
backups[key]["size"] += size
|
||||
else:
|
||||
unmatched_file_size += size
|
||||
|
||||
# Ensure the rows are sorted reverse chronologically.
|
||||
# This is relied on by should_force_full() and the next step.
|
||||
@@ -106,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)
|
||||
@@ -139,6 +153,7 @@ def backup_status(env):
|
||||
|
||||
return {
|
||||
"backups": backups,
|
||||
"unmatched_file_size": unmatched_file_size,
|
||||
}
|
||||
|
||||
def should_force_full(config, env):
|
||||
@@ -177,63 +192,40 @@ def get_passphrase(env):
|
||||
with open(os.path.join(backup_root, 'secret_key.txt')) as f:
|
||||
passphrase = f.readline().strip()
|
||||
if len(passphrase) < 43: raise Exception("secret_key.txt's first line is too short!")
|
||||
|
||||
|
||||
return passphrase
|
||||
|
||||
def get_env(env):
|
||||
config = get_backup_config(env)
|
||||
|
||||
|
||||
env = { "PASSPHRASE" : get_passphrase(env) }
|
||||
|
||||
|
||||
if get_target_type(config) == 's3':
|
||||
env["AWS_ACCESS_KEY_ID"] = config["target_user"]
|
||||
env["AWS_SECRET_ACCESS_KEY"] = config["target_pass"]
|
||||
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def get_target_type(config):
|
||||
protocol = config["target"].split(":")[0]
|
||||
return protocol
|
||||
|
||||
|
||||
def perform_backup(full_backup):
|
||||
env = load_environment()
|
||||
|
||||
exclusive_process("backup")
|
||||
# Create an global exclusive lock so that the backup script
|
||||
# cannot be run more than one.
|
||||
Lock(die=True).forever()
|
||||
|
||||
config = get_backup_config(env)
|
||||
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
|
||||
backup_cache_dir = os.path.join(backup_root, 'cache')
|
||||
backup_dir = os.path.join(backup_root, 'encrypted')
|
||||
|
||||
# Are backups dissbled?
|
||||
# Are backups disabled?
|
||||
if config["target"] == "off":
|
||||
return
|
||||
|
||||
# In an older version of this script, duplicity was called
|
||||
# such that it did not encrypt the backups it created (in
|
||||
# backup/duplicity), and instead openssl was called separately
|
||||
# after each backup run, creating AES256 encrypted copies of
|
||||
# each file created by duplicity in backup/encrypted.
|
||||
#
|
||||
# We detect the transition by the presence of backup/duplicity
|
||||
# and handle it by 'dupliception': we move all the old *un*encrypted
|
||||
# duplicity files up out of the backup/duplicity directory (as
|
||||
# backup/ is excluded from duplicity runs) in order that it is
|
||||
# included in the next run, and we delete backup/encrypted (which
|
||||
# duplicity will output files directly to, post-transition).
|
||||
old_backup_dir = os.path.join(backup_root, 'duplicity')
|
||||
migrated_unencrypted_backup_dir = os.path.join(env["STORAGE_ROOT"], "migrated_unencrypted_backup")
|
||||
if os.path.isdir(old_backup_dir):
|
||||
# Move the old unencrypted files to a new location outside of
|
||||
# the backup root so they get included in the next (new) backup.
|
||||
# Then we'll delete them. Also so that they do not get in the
|
||||
# way of duplicity doing a full backup on the first run after
|
||||
# we take care of this.
|
||||
shutil.move(old_backup_dir, migrated_unencrypted_backup_dir)
|
||||
|
||||
# The backup_dir (backup/encrypted) now has a new purpose.
|
||||
# Clear it out.
|
||||
shutil.rmtree(backup_dir)
|
||||
|
||||
# On the first run, always do a full backup. Incremental
|
||||
# will fail. Otherwise do a full backup when the size of
|
||||
# the increments since the most recent full backup are
|
||||
@@ -255,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)
|
||||
|
||||
@@ -283,17 +275,13 @@ def perform_backup(full_backup):
|
||||
env["STORAGE_ROOT"],
|
||||
config["target"],
|
||||
"--allow-source-mismatch"
|
||||
],
|
||||
] + rsync_ssh_options,
|
||||
get_env(env))
|
||||
finally:
|
||||
# Start services again.
|
||||
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.
|
||||
@@ -305,7 +293,7 @@ def perform_backup(full_backup):
|
||||
"--archive-dir", backup_cache_dir,
|
||||
"--force",
|
||||
config["target"]
|
||||
],
|
||||
] + rsync_ssh_options,
|
||||
get_env(env))
|
||||
|
||||
# From duplicity's manual:
|
||||
@@ -320,7 +308,7 @@ def perform_backup(full_backup):
|
||||
"--archive-dir", backup_cache_dir,
|
||||
"--force",
|
||||
config["target"]
|
||||
],
|
||||
] + rsync_ssh_options,
|
||||
get_env(env))
|
||||
|
||||
# Change ownership of backups to the user-data user, so that the after-bcakup
|
||||
@@ -359,7 +347,7 @@ def run_duplicity_verification():
|
||||
"--exclude", backup_root,
|
||||
config["target"],
|
||||
env["STORAGE_ROOT"],
|
||||
], get_env(env))
|
||||
] + rsync_ssh_options, get_env(env))
|
||||
|
||||
def run_duplicity_restore(args):
|
||||
env = load_environment()
|
||||
@@ -370,32 +358,75 @@ def run_duplicity_restore(args):
|
||||
"restore",
|
||||
"--archive-dir", backup_cache_dir,
|
||||
config["target"],
|
||||
] + args,
|
||||
] + rsync_ssh_options + args,
|
||||
get_env(env))
|
||||
|
||||
def list_target_files(config):
|
||||
import urllib.parse
|
||||
try:
|
||||
p = urllib.parse.urlparse(config["target"])
|
||||
target = urllib.parse.urlparse(config["target"])
|
||||
except ValueError:
|
||||
return "invalid target"
|
||||
|
||||
if p.scheme == "file":
|
||||
return [(fn, os.path.getsize(os.path.join(p.path, fn))) for fn in os.listdir(p.path)]
|
||||
if target.scheme == "file":
|
||||
return [(fn, os.path.getsize(os.path.join(target.path, fn))) for fn in os.listdir(target.path)]
|
||||
|
||||
elif p.scheme == "s3":
|
||||
elif target.scheme == "rsync":
|
||||
rsync_fn_size_re = re.compile(r'.* ([^ ]*) [^ ]* [^ ]* (.*)')
|
||||
rsync_target = '{host}:{path}'
|
||||
|
||||
target_path = target.path
|
||||
if not target_path.endswith('/'):
|
||||
target_path = target_path + '/'
|
||||
if target_path.startswith('/'):
|
||||
target_path = target_path[1:]
|
||||
|
||||
rsync_command = [ 'rsync',
|
||||
'-e',
|
||||
'/usr/bin/ssh -i /root/.ssh/id_rsa_miab -oStrictHostKeyChecking=no -oBatchMode=yes',
|
||||
'--list-only',
|
||||
'-r',
|
||||
rsync_target.format(
|
||||
host=target.netloc,
|
||||
path=target_path)
|
||||
]
|
||||
|
||||
code, listing = shell('check_output', rsync_command, trap=True, capture_stderr=True)
|
||||
if code == 0:
|
||||
ret = []
|
||||
for l in listing.split('\n'):
|
||||
match = rsync_fn_size_re.match(l)
|
||||
if match:
|
||||
ret.append( (match.groups()[1], int(match.groups()[0].replace(',',''))) )
|
||||
return ret
|
||||
else:
|
||||
if 'Permission denied (publickey).' in listing:
|
||||
reason = "Invalid user or check you correctly copied the SSH key."
|
||||
elif 'No such file or directory' in listing:
|
||||
reason = "Provided path {} is invalid.".format(target_path)
|
||||
elif 'Network is unreachable' in listing:
|
||||
reason = "The IP address {} is unreachable.".format(target.hostname)
|
||||
elif 'Could not resolve hostname':
|
||||
reason = "The hostname {} cannot be resolved.".format(target.hostname)
|
||||
else:
|
||||
reason = "Unknown error." \
|
||||
"Please check running 'python management/backup.py --verify'" \
|
||||
"from mailinabox sources to debug the issue."
|
||||
raise ValueError("Connection to rsync host failed: {}".format(reason))
|
||||
|
||||
elif target.scheme == "s3":
|
||||
# match to a Region
|
||||
fix_boto() # must call prior to importing boto
|
||||
import boto.s3
|
||||
from boto.exception import BotoServerError
|
||||
for region in boto.s3.regions():
|
||||
if region.endpoint == p.hostname:
|
||||
if region.endpoint == target.hostname:
|
||||
break
|
||||
else:
|
||||
raise ValueError("Invalid S3 region/host.")
|
||||
|
||||
bucket = p.path[1:].split('/')[0]
|
||||
path = '/'.join(p.path[1:].split('/')[1:]) + '/'
|
||||
bucket = target.path[1:].split('/')[0]
|
||||
path = '/'.join(target.path[1:].split('/')[1:]) + '/'
|
||||
|
||||
# If no prefix is specified, set the path to '', otherwise boto won't list the files
|
||||
if path == '/':
|
||||
@@ -425,7 +456,7 @@ def list_target_files(config):
|
||||
|
||||
def backup_set_custom(env, target, target_user, target_pass, min_age):
|
||||
config = get_backup_config(env, for_save=True)
|
||||
|
||||
|
||||
# min_age must be an int
|
||||
if isinstance(min_age, str):
|
||||
min_age = int(min_age)
|
||||
@@ -443,11 +474,11 @@ def backup_set_custom(env, target, target_user, target_pass, min_age):
|
||||
list_target_files(config)
|
||||
except ValueError as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
write_backup_config(env, config)
|
||||
|
||||
return "OK"
|
||||
|
||||
|
||||
def get_backup_config(env, for_save=False, for_ui=False):
|
||||
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
|
||||
|
||||
@@ -482,6 +513,9 @@ def get_backup_config(env, for_save=False, for_ui=False):
|
||||
if config["target"] == "local":
|
||||
# Expand to the full URL.
|
||||
config["target"] = "file://" + config["file_target_directory"]
|
||||
ssh_pub_key = os.path.join('/root', '.ssh', 'id_rsa_miab.pub')
|
||||
if os.path.exists(ssh_pub_key):
|
||||
config["ssh_pub_key"] = open(ssh_pub_key, 'r').read()
|
||||
|
||||
return config
|
||||
|
||||
@@ -497,10 +531,16 @@ if __name__ == "__main__":
|
||||
# are readable, and b) report if they are up to date.
|
||||
run_duplicity_verification()
|
||||
|
||||
elif sys.argv[-1] == "--list":
|
||||
# List the saved backup files.
|
||||
for fn, size in list_target_files(get_backup_config(load_environment())):
|
||||
print("{}\t{}".format(fn, size))
|
||||
|
||||
elif sys.argv[-1] == "--status":
|
||||
# Show backup status.
|
||||
ret = backup_status(load_environment())
|
||||
print(rtyaml.dump(ret["backups"]))
|
||||
print("Storage for unmatched files:", ret["unmatched_file_size"])
|
||||
|
||||
elif len(sys.argv) >= 2 and sys.argv[1] == "--restore":
|
||||
# Run duplicity restore. Rest of command line passed as arguments
|
||||
|
||||
@@ -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 --headless | management/email_administrator.py "Error Provisioning TLS Certificate"
|
||||
management/ssl_certificates.py -q | management/email_administrator.py "Error Provisioning TLS Certificate"
|
||||
|
||||
# Run status checks and email the administrator if anything changed.
|
||||
management/status_checks.py --show-changes | management/email_administrator.py "Status Checks Change Notice"
|
||||
|
||||
@@ -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.
|
||||
@@ -144,7 +150,7 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
|
||||
# Define ns2.PRIMARY_HOSTNAME or whatever the user overrides.
|
||||
# User may provide one or more additional nameservers
|
||||
secondary_ns_list = get_secondary_dns(additional_records, mode="NS") \
|
||||
or ["ns2." + env["PRIMARY_HOSTNAME"]]
|
||||
or ["ns2." + env["PRIMARY_HOSTNAME"]]
|
||||
for secondary_ns in secondary_ns_list:
|
||||
records.append((None, "NS", secondary_ns+'.', False))
|
||||
|
||||
@@ -342,13 +348,26 @@ def build_sshfp_records():
|
||||
"ssh-rsa": 1,
|
||||
"ssh-dss": 2,
|
||||
"ecdsa-sha2-nistp256": 3,
|
||||
"ssh-ed25519": 4,
|
||||
}
|
||||
|
||||
# Get our local fingerprints by running ssh-keyscan. The output looks
|
||||
# like the known_hosts file: hostname, keytype, fingerprint. The order
|
||||
# of the output is arbitrary, so sort it to prevent spurrious updates
|
||||
# to the zone file (that trigger bumping the serial number).
|
||||
keys = shell("check_output", ["ssh-keyscan", "localhost"])
|
||||
# to the zone file (that trigger bumping the serial number). However,
|
||||
# if SSH has been configured to listen on a nonstandard port, we must
|
||||
# specify that port to sshkeyscan.
|
||||
port = 22
|
||||
with open('/etc/ssh/sshd_config', 'r') as f:
|
||||
for line in f:
|
||||
s = line.rstrip().split()
|
||||
if len(s) == 2 and s[0] == 'Port':
|
||||
try:
|
||||
port = int(s[1])
|
||||
except ValueError:
|
||||
pass
|
||||
break
|
||||
keys = shell("check_output", ["ssh-keyscan", "-t", "rsa,dsa,ecdsa,ed25519", "-p", str(port), "localhost"])
|
||||
for key in sorted(keys.split("\n")):
|
||||
if key.strip() == "" or key[0] == "#": continue
|
||||
try:
|
||||
@@ -510,12 +529,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
|
||||
@@ -750,12 +770,25 @@ def set_custom_dns_record(qname, rtype, value, action, env):
|
||||
# validate rtype
|
||||
rtype = rtype.upper()
|
||||
if value is not None and qname != "_secondary_nameserver":
|
||||
if not re.search(DOMAIN_RE, qname):
|
||||
raise ValueError("Invalid name.")
|
||||
|
||||
if rtype in ("A", "AAAA"):
|
||||
if value != "local": # "local" is a special flag for us
|
||||
v = ipaddress.ip_address(value) # raises a ValueError if there's a problem
|
||||
if rtype == "A" and not isinstance(v, ipaddress.IPv4Address): raise ValueError("That's an IPv6 address.")
|
||||
if rtype == "AAAA" and not isinstance(v, ipaddress.IPv6Address): raise ValueError("That's an IPv4 address.")
|
||||
elif rtype in ("CNAME", "TXT", "SRV", "MX"):
|
||||
elif rtype in ("CNAME", "NS"):
|
||||
if rtype == "NS" and qname == zone:
|
||||
raise ValueError("NS records can only be set for subdomains.")
|
||||
|
||||
# ensure value has a trailing dot
|
||||
if not value.endswith("."):
|
||||
value = value + "."
|
||||
|
||||
if not re.search(DOMAIN_RE, value):
|
||||
raise ValueError("Invalid value.")
|
||||
elif rtype in ("CNAME", "TXT", "SRV", "MX", "SSHFP", "CAA"):
|
||||
# anything goes
|
||||
pass
|
||||
else:
|
||||
@@ -870,10 +903,10 @@ def set_secondary_dns(hostnames, env):
|
||||
return do_dns_update(env)
|
||||
|
||||
|
||||
def get_custom_dns_record(custom_dns, qname, rtype):
|
||||
def get_custom_dns_records(custom_dns, qname, rtype):
|
||||
for qname1, rtype1, value in custom_dns:
|
||||
if qname1 == qname and rtype1 == rtype:
|
||||
return value
|
||||
yield value
|
||||
return None
|
||||
|
||||
########################################################################
|
||||
|
||||
@@ -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)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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')
|
||||
@@ -149,8 +155,6 @@ def get_mail_users_ex(env, with_archived=False, with_slow_info=False):
|
||||
"mailbox": mbox,
|
||||
}
|
||||
users.append(user)
|
||||
if with_slow_info:
|
||||
user["mailbox_size"] = utils.du(mbox)
|
||||
|
||||
# Group by domain.
|
||||
domains = { }
|
||||
@@ -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.
|
||||
@@ -599,8 +605,8 @@ def validate_password(pw):
|
||||
raise ValueError("No password provided.")
|
||||
if re.search(r"[\s]", pw):
|
||||
raise ValueError("Passwords cannot contain spaces.")
|
||||
if len(pw) < 4:
|
||||
raise ValueError("Passwords must be at least four characters.")
|
||||
if len(pw) < 8:
|
||||
raise ValueError("Passwords must be at least eight characters.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
2
management/munin_start.sh
Executable file
2
management/munin_start.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
mkdir -p /var/run/munin && chown munin /var/run/munin
|
||||
@@ -1,10 +1,9 @@
|
||||
#!/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
|
||||
|
||||
# SELECTING SSL CERTIFICATES FOR USE IN WEB
|
||||
@@ -25,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
|
||||
@@ -75,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.
|
||||
@@ -82,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,
|
||||
@@ -125,21 +141,23 @@ def get_ssl_certificates(env):
|
||||
|
||||
return ret
|
||||
|
||||
def get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=False, raw=False):
|
||||
# Get the system certificate info.
|
||||
ssl_private_key = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_private_key.pem'))
|
||||
ssl_certificate = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem'))
|
||||
system_certificate = {
|
||||
"private-key": ssl_private_key,
|
||||
"certificate": ssl_certificate,
|
||||
"primary-domain": env['PRIMARY_HOSTNAME'],
|
||||
"certificate_object": load_pem(load_cert_chain(ssl_certificate)[0]),
|
||||
}
|
||||
def get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=False, use_main_cert=True):
|
||||
if use_main_cert or not allow_missing_cert:
|
||||
# Get the system certificate info.
|
||||
ssl_private_key = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_private_key.pem'))
|
||||
ssl_certificate = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem'))
|
||||
system_certificate = {
|
||||
"private-key": ssl_private_key,
|
||||
"certificate": ssl_certificate,
|
||||
"primary-domain": env['PRIMARY_HOSTNAME'],
|
||||
"certificate_object": load_pem(load_cert_chain(ssl_certificate)[0]),
|
||||
}
|
||||
|
||||
if domain == env['PRIMARY_HOSTNAME']:
|
||||
# The primary domain must use the server certificate because
|
||||
# it is hard-coded in some service configuration files.
|
||||
return system_certificate
|
||||
if use_main_cert:
|
||||
if domain == env['PRIMARY_HOSTNAME']:
|
||||
# The primary domain must use the server certificate because
|
||||
# it is hard-coded in some service configuration files.
|
||||
return system_certificate
|
||||
|
||||
wildcard_domain = re.sub("^[^\.]+", "*", domain)
|
||||
if domain in ssl_certificates:
|
||||
@@ -156,141 +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)
|
||||
|
||||
# Does the domain resolve to this machine in public DNS? If not,
|
||||
# we can't do domain control validation. For IPv6 is configured,
|
||||
# make sure both IPv4 and IPv6 are correct because we don't know
|
||||
# how Let's Encrypt will connect.
|
||||
bad_dns = []
|
||||
for rtype, value in [("A", env["PUBLIC_IP"]), ("AAAA", env.get("PUBLIC_IPV6"))]:
|
||||
if not value: continue # IPv6 is not configured
|
||||
response = query_dns(domain, rtype)
|
||||
if response != normalize_ip(value):
|
||||
bad_dns.append("%s (%s)" % (response, rtype))
|
||||
|
||||
if bad_dns:
|
||||
domains_cant_provision[domain] = "The domain name does not resolve to this machine: " \
|
||||
+ (", ".join(bad_dns)) \
|
||||
+ "."
|
||||
|
||||
# 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)
|
||||
else:
|
||||
# DNS is all good.
|
||||
|
||||
# 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."
|
||||
# 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
|
||||
|
||||
# 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)."
|
||||
domains_to_provision.add(domain)
|
||||
|
||||
# Filter out domains that we can't provision a certificate for.
|
||||
def can_provision_for_domain(domain):
|
||||
# Let's Encrypt doesn't yet support IDNA domains.
|
||||
# We store domains in IDNA (ASCII). To see if this domain is IDNA,
|
||||
# we'll see if its IDNA-decoded form is different.
|
||||
if idna.decode(domain.encode("ascii")) != domain:
|
||||
problems[domain] = "Let's Encrypt does not yet support provisioning certificates for internationalized domains."
|
||||
return False
|
||||
|
||||
# Does the domain resolve to this machine in public DNS? If not,
|
||||
# we can't do domain control validation. For IPv6 is configured,
|
||||
# make sure both IPv4 and IPv6 are correct because we don't know
|
||||
# how Let's Encrypt will connect.
|
||||
import dns.resolver
|
||||
for rtype, value in [("A", env["PUBLIC_IP"]), ("AAAA", env.get("PUBLIC_IPV6"))]:
|
||||
if not value: continue # IPv6 is not configured
|
||||
try:
|
||||
# Must make the qname absolute to prevent a fall-back lookup with a
|
||||
# search domain appended, by adding a period to the end.
|
||||
response = dns.resolver.query(domain + ".", rtype)
|
||||
except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as e:
|
||||
problems[domain] = "DNS isn't configured properly for this domain: DNS resolution failed (%s: %s)." % (rtype, str(e) or repr(e)) # NoAnswer's str is empty
|
||||
return False
|
||||
except Exception as e:
|
||||
problems[domain] = "DNS isn't configured properly for this domain: DNS lookup had an error: %s." % str(e)
|
||||
return False
|
||||
|
||||
# Unfortunately, the response.__str__ returns bytes
|
||||
# instead of string, if it resulted from an AAAA-query.
|
||||
# We need to convert manually, until this is fixed:
|
||||
# https://github.com/rthalley/dnspython/issues/204
|
||||
#
|
||||
# BEGIN HOTFIX
|
||||
def rdata__str__(r):
|
||||
s = r.to_text()
|
||||
if isinstance(s, bytes):
|
||||
s = s.decode('utf-8')
|
||||
return s
|
||||
# END HOTFIX
|
||||
|
||||
if len(response) != 1 or rdata__str__(response[0]) != value:
|
||||
problems[domain] = "Domain control validation cannot be performed for this domain because DNS points the domain to another machine (%s %s)." % (rtype, ", ".join(rdata__str__(r) for r in response))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
domains = set(filter(can_provision_for_domain, domains))
|
||||
|
||||
# If there are any domains we definitely will provision for, add in
|
||||
# additional domains to do at this time.
|
||||
if len(domains) > 0:
|
||||
domains |= set(filter(can_provision_for_domain, domains_if_any))
|
||||
|
||||
return (domains, problems)
|
||||
|
||||
def provision_certificates(env, agree_to_tos_url=None, logger=None, show_extended_problems=True, force_domains=None, jsonable=False):
|
||||
import requests.exceptions
|
||||
import acme.messages
|
||||
|
||||
from free_tls_certificates import client
|
||||
return (domains_to_provision, domains_cant_provision)
|
||||
|
||||
def provision_certificates(env, limit_domains):
|
||||
# What domains should we provision certificates for? And what
|
||||
# errors prevent provisioning for other domains.
|
||||
domains, problems = get_certificates_to_provision(env, force_domains=force_domains, show_extended_problems=show_extended_problems)
|
||||
domains, domains_cant_provision = get_certificates_to_provision(env, limit_domains=limit_domains)
|
||||
|
||||
# Build a list of what happened on each domain or domain-set.
|
||||
ret = []
|
||||
for domain, error in domains_cant_provision.items():
|
||||
ret.append({
|
||||
"domains": [domain],
|
||||
"log": [error],
|
||||
"result": "skipped",
|
||||
})
|
||||
|
||||
# Exit fast if there is nothing to do.
|
||||
if len(domains) == 0:
|
||||
return {
|
||||
"requests": [],
|
||||
"problems": problems,
|
||||
}
|
||||
|
||||
# Break into groups of up to 100 certificates at a time, which is Let's Encrypt's
|
||||
# limit for a single certificate. We'll sort to put related domains together.
|
||||
max_domains_per_group = 100
|
||||
domains = sort_domains(domains, env)
|
||||
certs = []
|
||||
while len(domains) > 0:
|
||||
certs.append( domains[0:100] )
|
||||
domains = domains[100:]
|
||||
certs.append( domains[:max_domains_per_group] )
|
||||
domains = domains[max_domains_per_group:]
|
||||
|
||||
# Prepare to provision.
|
||||
|
||||
@@ -299,268 +273,125 @@ 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
|
||||
from utils import load_environment, exclusive_process
|
||||
from exclusiveprocess import Lock
|
||||
|
||||
exclusive_process("update_tls_certificates")
|
||||
from utils import load_environment
|
||||
|
||||
Lock(die=True).forever()
|
||||
env = load_environment()
|
||||
|
||||
verbose = False
|
||||
headless = False
|
||||
force_domains = None
|
||||
show_extended_problems = True
|
||||
|
||||
args = list(sys.argv)
|
||||
args.pop(0) # program name
|
||||
if args and args[0] == "-v":
|
||||
verbose = True
|
||||
args.pop(0)
|
||||
if args and args[0] == "q":
|
||||
show_extended_problems = False
|
||||
args.pop(0)
|
||||
if args and args[0] == "--headless":
|
||||
headless = True
|
||||
args.pop(0)
|
||||
if args and args[0] == "--force":
|
||||
force_domains = "ALL"
|
||||
args.pop(0)
|
||||
else:
|
||||
force_domains = args
|
||||
quiet = False
|
||||
domains = []
|
||||
|
||||
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
|
||||
for arg in sys.argv[1:]:
|
||||
if arg == "-q":
|
||||
quiet = True
|
||||
else:
|
||||
domains.append(arg)
|
||||
|
||||
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]))
|
||||
# Go.
|
||||
status = provision_certificates(env, limit_domains=domains)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
# What happened?
|
||||
wait_until = None
|
||||
wait_domains = []
|
||||
for request in status["requests"]:
|
||||
if request["result"] == "agree-to-tos":
|
||||
# We may have asked already in a previous iteration.
|
||||
if agree_to_tos_url is not None:
|
||||
continue
|
||||
|
||||
# Can't ask the user a question in this mode. Warn the user that something
|
||||
# needs to be done.
|
||||
if headless:
|
||||
print(", ".join(request["domains"]) + " need a new or renewed TLS certificate.")
|
||||
print()
|
||||
print("This box can't do that automatically for you until you agree to Let's Encrypt's")
|
||||
print("Terms of Service agreement. Use the Mail-in-a-Box control panel to provision")
|
||||
print("certificates for these domains.")
|
||||
sys.exit(1)
|
||||
|
||||
print("""
|
||||
I'm going to provision a TLS certificate (formerly called a SSL certificate)
|
||||
for you from Let's Encrypt (letsencrypt.org).
|
||||
|
||||
TLS certificates are cryptographic keys that ensure communication between
|
||||
you and this box are secure when getting and sending mail and visiting
|
||||
websites hosted on this box. Let's Encrypt is a free provider of TLS
|
||||
certificates.
|
||||
|
||||
Please open this document in your web browser:
|
||||
|
||||
%s
|
||||
|
||||
It is Let's Encrypt's terms of service agreement. If you agree, I can
|
||||
provision that TLS certificate. If you don't agree, you will have an
|
||||
opportunity to install your own TLS certificate from the Mail-in-a-Box
|
||||
control panel.
|
||||
|
||||
Do you agree to the agreement? Type Y or N and press <ENTER>: """
|
||||
% request["url"], end='', flush=True)
|
||||
|
||||
if sys.stdin.readline().strip().upper() != "Y":
|
||||
print("\nYou didn't agree. Quitting.")
|
||||
sys.exit(1)
|
||||
|
||||
# Okay, indicate agreement on next iteration.
|
||||
agree_to_tos_url = request["url"]
|
||||
|
||||
if request["result"] == "wait":
|
||||
# Must wait. We'll record until when. The wait occurs below.
|
||||
if wait_until is None:
|
||||
wait_until = request["until"]
|
||||
else:
|
||||
wait_until = max(wait_until, request["until"])
|
||||
wait_domains += request["domains"]
|
||||
|
||||
if request["result"] == "error":
|
||||
print(", ".join(request["domains"]) + ":")
|
||||
print(request["message"])
|
||||
|
||||
if request["result"] == "installed":
|
||||
print("A TLS certificate was successfully installed for " + ", ".join(request["domains"]) + ".")
|
||||
|
||||
if wait_until:
|
||||
# Wait, then loop.
|
||||
import time, datetime
|
||||
# Show what happened.
|
||||
for request in status:
|
||||
if isinstance(request, str):
|
||||
print(request)
|
||||
else:
|
||||
if quiet and request['result'] == 'skipped':
|
||||
continue
|
||||
print(request['result'] + ":", ", ".join(request['domains']) + ":")
|
||||
for line in request["log"]:
|
||||
print(line)
|
||||
print()
|
||||
print("A TLS certificate was requested for: " + ", ".join(wait_domains) + ".")
|
||||
first = True
|
||||
while wait_until > datetime.datetime.now():
|
||||
if not headless or first:
|
||||
print ("We have to wait", int(round((wait_until - datetime.datetime.now()).total_seconds())), "seconds for the certificate to be issued...")
|
||||
time.sleep(10)
|
||||
first = False
|
||||
|
||||
continue # Loop!
|
||||
|
||||
if agree_to_tos_url:
|
||||
# The user agrees to the TOS. Loop to try again by agreeing.
|
||||
continue # Loop!
|
||||
|
||||
# Unless we were instructed to wait, or we just agreed to the TOS,
|
||||
# we're done for now.
|
||||
break
|
||||
|
||||
# And finally show the domains with problems.
|
||||
if len(status["problems"]) > 0:
|
||||
print("TLS certificates could not be provisoned for:")
|
||||
for domain in sort_domains(status["problems"], env):
|
||||
print("%s: %s" % (domain, status["problems"][domain]))
|
||||
|
||||
# INSTALLING A NEW CERTIFICATE FROM THE CONTROL PANEL
|
||||
|
||||
def create_csr(domain, ssl_key, country_code, env):
|
||||
return shell("check_output", [
|
||||
"openssl", "req", "-new",
|
||||
"-key", ssl_key,
|
||||
"-sha256",
|
||||
"-subj", "/C=%s/ST=/L=/O=/CN=%s" % (country_code, domain)])
|
||||
"openssl", "req", "-new",
|
||||
"-key", ssl_key,
|
||||
"-sha256",
|
||||
"-subj", "/C=%s/CN=%s" % (country_code, domain)])
|
||||
|
||||
def install_cert(domain, ssl_cert, ssl_chain, env, raw=False):
|
||||
# Write the combined cert+chain to a temporary path and validate that it is OK.
|
||||
@@ -581,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
|
||||
@@ -598,14 +439,26 @@ def install_cert(domain, ssl_cert, ssl_chain, env, raw=False):
|
||||
os.makedirs(os.path.dirname(ssl_certificate), exist_ok=True)
|
||||
shutil.move(fn, ssl_certificate)
|
||||
|
||||
ret = ["OK"]
|
||||
|
||||
# When updating the cert for PRIMARY_HOSTNAME, symlink it from the system
|
||||
def post_install_func(env):
|
||||
ret = []
|
||||
|
||||
# Get the certificate to use for PRIMARY_HOSTNAME.
|
||||
ssl_certificates = get_ssl_certificates(env)
|
||||
cert = get_domain_ssl_files(env['PRIMARY_HOSTNAME'], ssl_certificates, env, use_main_cert=False)
|
||||
if not cert:
|
||||
# Ruh-row, we don't have any certificate usable
|
||||
# for the primary hostname.
|
||||
ret.append("there is no valid certificate for " + env['PRIMARY_HOSTNAME'])
|
||||
|
||||
# Symlink the best cert for PRIMARY_HOSTNAME to the system
|
||||
# certificate path, which is hard-coded for various purposes, and then
|
||||
# restart postfix and dovecot.
|
||||
if domain == env['PRIMARY_HOSTNAME']:
|
||||
system_ssl_certificate = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem'))
|
||||
if cert and os.readlink(system_ssl_certificate) != cert['certificate']:
|
||||
# Update symlink.
|
||||
system_ssl_certificate = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem'))
|
||||
ret.append("updating primary certificate")
|
||||
ssl_certificate = cert['certificate']
|
||||
os.unlink(system_ssl_certificate)
|
||||
os.symlink(ssl_certificate, system_ssl_certificate)
|
||||
|
||||
@@ -621,12 +474,12 @@ def install_cert(domain, ssl_cert, ssl_chain, env, raw=False):
|
||||
# Update the web configuration so nginx picks up the new certificate file.
|
||||
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.
|
||||
|
||||
@@ -732,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
|
||||
@@ -11,7 +11,7 @@ import dateutil.parser, dateutil.tz
|
||||
import idna
|
||||
import psutil
|
||||
|
||||
from dns_update import get_dns_zones, build_tlsa_record, get_custom_dns_config, get_secondary_dns, get_custom_dns_record
|
||||
from dns_update import get_dns_zones, build_tlsa_record, get_custom_dns_config, get_secondary_dns, get_custom_dns_records
|
||||
from web_update import get_web_domains, get_domains_with_a_records
|
||||
from ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate
|
||||
from mailconfig import get_mail_domains, get_mail_aliases
|
||||
@@ -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, },
|
||||
@@ -169,8 +168,19 @@ def run_system_checks(rounded_values, env, output):
|
||||
check_free_memory(rounded_values, env, output)
|
||||
|
||||
def check_ufw(env, output):
|
||||
ufw = shell('check_output', ['ufw', 'status']).splitlines()
|
||||
if not os.path.isfile('/usr/sbin/ufw'):
|
||||
output.print_warning("""The ufw program was not installed. If your system is able to run iptables, rerun the setup.""")
|
||||
return
|
||||
|
||||
code, ufw = shell('check_output', ['ufw', 'status'], trap=True)
|
||||
|
||||
if code != 0:
|
||||
# The command failed, it's safe to say the firewall is disabled
|
||||
output.print_warning("""The firewall is not working on this machine. An error was received
|
||||
while trying to check the firewall. To investigate run 'sudo ufw status'.""")
|
||||
return
|
||||
|
||||
ufw = ufw.splitlines()
|
||||
if ufw[0] == "Status: active":
|
||||
not_allowed_ports = 0
|
||||
for service in get_services():
|
||||
@@ -382,7 +392,7 @@ def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles):
|
||||
|
||||
# Check that PRIMARY_HOSTNAME resolves to PUBLIC_IP[V6] in public DNS.
|
||||
ipv6 = query_dns(domain, "AAAA") if env.get("PUBLIC_IPV6") else None
|
||||
if ip == env['PUBLIC_IP'] and ipv6 in (None, env['PUBLIC_IPV6']):
|
||||
if ip == env['PUBLIC_IP'] and not (ipv6 and env['PUBLIC_IPV6'] and ipv6 != normalize_ip(env['PUBLIC_IPV6'])):
|
||||
output.print_ok("Domain resolves to box's IP address. [%s ↦ %s]" % (env['PRIMARY_HOSTNAME'], my_ips))
|
||||
else:
|
||||
output.print_error("""This domain must resolve to your box's IP address (%s) in public DNS but it currently resolves
|
||||
@@ -448,7 +458,7 @@ def check_dns_zone(domain, env, output, dns_zonefiles):
|
||||
# half working.)
|
||||
|
||||
custom_dns_records = list(get_custom_dns_config(env)) # generator => list so we can reuse it
|
||||
correct_ip = get_custom_dns_record(custom_dns_records, domain, "A") or env['PUBLIC_IP']
|
||||
correct_ip = "; ".join(sorted(get_custom_dns_records(custom_dns_records, domain, "A"))) or env['PUBLIC_IP']
|
||||
custom_secondary_ns = get_secondary_dns(custom_dns_records, mode="NS")
|
||||
secondary_ns = custom_secondary_ns or ["ns2." + env['PRIMARY_HOSTNAME']]
|
||||
|
||||
@@ -629,7 +639,7 @@ def check_web_domain(domain, rounded_time, ssl_certificates, env, output):
|
||||
for (rtype, expected) in (("A", env['PUBLIC_IP']), ("AAAA", env.get('PUBLIC_IPV6'))):
|
||||
if not expected: continue # IPv6 is not configured
|
||||
value = query_dns(domain, rtype)
|
||||
if 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
|
||||
@@ -676,26 +686,17 @@ def query_dns(qname, rtype, nxdomain='[Not Set]', at=None):
|
||||
except dns.exception.Timeout:
|
||||
return "[timeout]"
|
||||
|
||||
# Normalize IP addresses. IP address --- especially IPv6 addresses --- can
|
||||
# be expressed in equivalent string forms. Canonicalize the form before
|
||||
# returning them. The caller should normalize any IP addresses the result
|
||||
# of this method is compared with.
|
||||
if rtype in ("A", "AAAA"):
|
||||
response = [normalize_ip(str(r)) for r in response]
|
||||
|
||||
# There may be multiple answers; concatenate the response. Remove trailing
|
||||
# periods from responses since that's how qnames are encoded in DNS but is
|
||||
# confusing for us. The order of the answers doesn't matter, so sort so we
|
||||
# can compare to a well known order.
|
||||
|
||||
# Unfortunately, the response.__str__ returns bytes
|
||||
# instead of string, if it resulted from an AAAA-query.
|
||||
# We need to convert manually, until this is fixed:
|
||||
# https://github.com/rthalley/dnspython/issues/204
|
||||
#
|
||||
# BEGIN HOTFIX
|
||||
response_new = []
|
||||
for r in response:
|
||||
if isinstance(r.to_text(), bytes):
|
||||
response_new.append(r.to_text().decode('utf-8'))
|
||||
else:
|
||||
response_new.append(r)
|
||||
response = response_new
|
||||
# END HOTFIX
|
||||
|
||||
return "; ".join(sorted(str(r).rstrip('.') for r in response))
|
||||
|
||||
def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output):
|
||||
@@ -782,24 +783,31 @@ def what_version_is_this(env):
|
||||
def get_latest_miab_version():
|
||||
# This pings https://mailinabox.email/setup.sh and extracts the tag named in
|
||||
# the script to determine the current product version.
|
||||
import urllib.request
|
||||
return re.search(b'TAG=(.*)', urllib.request.urlopen("https://mailinabox.email/setup.sh?ping=1").read()).group(1).decode("utf8")
|
||||
from urllib.request import urlopen, HTTPError, URLError
|
||||
from socket import timeout
|
||||
|
||||
try:
|
||||
return re.search(b'TAG=(.*)', urlopen("https://mailinabox.email/setup.sh?ping=1", timeout=5).read()).group(1).decode("utf8")
|
||||
except (HTTPError, URLError, timeout):
|
||||
return None
|
||||
|
||||
def check_miab_version(env, output):
|
||||
config = load_settings(env)
|
||||
|
||||
if config.get("privacy", True):
|
||||
output.print_warning("Mail-in-a-Box version check disabled by privacy setting.")
|
||||
else:
|
||||
try:
|
||||
this_ver = what_version_is_this(env)
|
||||
except:
|
||||
this_ver = "Unknown"
|
||||
try:
|
||||
this_ver = what_version_is_this(env)
|
||||
except:
|
||||
this_ver = "Unknown"
|
||||
|
||||
if config.get("privacy", True):
|
||||
output.print_warning("You are running version Mail-in-a-Box %s. Mail-in-a-Box version check disabled by privacy setting." % this_ver)
|
||||
else:
|
||||
latest_ver = get_latest_miab_version()
|
||||
|
||||
if this_ver == latest_ver:
|
||||
output.print_ok("Mail-in-a-Box is up to date. You are running version %s." % this_ver)
|
||||
elif latest_ver is None:
|
||||
output.print_error("Latest Mail-in-a-Box version could not be determined. You are running version %s." % this_ver)
|
||||
else:
|
||||
output.print_error("A new version of Mail-in-a-Box is available. You are running version %s. The latest version is %s. For upgrade instructions, see https://mailinabox.email. "
|
||||
% (this_ver, latest_ver))
|
||||
@@ -872,6 +880,16 @@ def run_and_output_changes(env, pool):
|
||||
with open(cache_fn, "w") as f:
|
||||
json.dump(cur.buf, f, indent=True)
|
||||
|
||||
def normalize_ip(ip):
|
||||
# Use ipaddress module to normalize the IPv6 notation and
|
||||
# ensure we are matching IPv6 addresses written in different
|
||||
# representations according to rfc5952.
|
||||
import ipaddress
|
||||
try:
|
||||
return str(ipaddress.ip_address(ip))
|
||||
except:
|
||||
return ip
|
||||
|
||||
class FileOutput:
|
||||
def __init__(self, buf, width):
|
||||
self.buf = buf
|
||||
|
||||
@@ -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">
|
||||
@@ -123,7 +124,7 @@
|
||||
<table class="table" style="margin-top: .5em">
|
||||
<thead><th>Verb</th> <th>Action</th><th></th></thead>
|
||||
<tr><td>GET</td><td><i>(none)</i></td> <td>Returns a list of existing mail aliases. Adding <code>?format=json</code> to the URL will give JSON-encoded results.</td></tr>
|
||||
<tr><td>POST</td><td>/add</td> <td>Adds a new mail alias. Required POST-body parameters are <code>address</code> and <code>forward_to</code>.</td></tr>
|
||||
<tr><td>POST</td><td>/add</td> <td>Adds a new mail alias. Required POST-body parameters are <code>address</code> and <code>forwards_to</code>.</td></tr>
|
||||
<tr><td>POST</td><td>/remove</td> <td>Removes a mail alias. Required POST-body parameter is <code>address</code>.</td></tr>
|
||||
</table>
|
||||
|
||||
@@ -135,7 +136,7 @@
|
||||
curl -X GET https://{{hostname}}/admin/mail/aliases?format=json
|
||||
|
||||
# Adds a new alias
|
||||
curl -X POST -d "address=new_alias@mydomail.com" -d "forward_to=my_email@mydomain.com" https://{{hostname}}/admin/mail/aliases/add
|
||||
curl -X POST -d "address=new_alias@mydomail.com" -d "forwards_to=my_email@mydomain.com" https://{{hostname}}/admin/mail/aliases/add
|
||||
|
||||
# Removes an alias
|
||||
curl -X POST -d "address=new_alias@mydomail.com" https://{{hostname}}/admin/mail/aliases/remove
|
||||
|
||||
@@ -31,12 +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 PRIORIY 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 PRIORIY WEIGHT PORT TARGET., including trailing period (e.g. 10 10 5060 sip.example.com.).">SRV (service 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>
|
||||
@@ -124,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>, or <code>SRV</code>.</td></tr>
|
||||
<tr><td>rtype</td> <td>The resource type. Defaults to <code>A</code> if omitted. Possible values: <code>A</code> (an IPv4 address), <code>AAAA</code> (an IPv6 address), <code>TXT</code> (a text string), <code>CNAME</code> (an alias, which is a fully qualified domain name — 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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -218,7 +218,7 @@ $(function() {
|
||||
if (global_modal_state == null) global_modal_state = 1; // cancel if the user hit ESC or clicked outside of the modal
|
||||
if (global_modal_funcs && global_modal_funcs[global_modal_state])
|
||||
global_modal_funcs[global_modal_state]();
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function show_modal_error(title, message, callback) {
|
||||
@@ -281,7 +281,7 @@ function ajax_with_indicator(options) {
|
||||
};
|
||||
options.error = function(jqxhr) {
|
||||
hide_loading_indicator();
|
||||
if (!old_error)
|
||||
if (!old_error)
|
||||
show_modal_error("Error", "Something went wrong, sorry.")
|
||||
else
|
||||
old_error(jqxhr.responseText, jqxhr);
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
<h4>Exchange/ActiveSync settings</h4>
|
||||
|
||||
<p>On iOS devices, devices on this <a href="http://z-push.org/compatibility/">compatibility list</a>, or using Outlook 2007 or later on Windows 7 and later, you may set up your mail as an Exchange or ActiveSync server. However, we’ve found this to be more buggy than using IMAP as described above. If you encounter any problems, please use the manual settings above.</p>
|
||||
<p>On iOS devices, devices on this <a href="https://wiki.z-hub.io/display/ZP/Compatibility">compatibility list</a>, or using Outlook 2007 or later on Windows 7 and later, you may set up your mail as an Exchange or ActiveSync server. However, we’ve found this to be more buggy than using IMAP as described above. If you encounter any problems, please use the manual settings above.</p>
|
||||
|
||||
<table class="table">
|
||||
<tr><th>Server</th> <td>{{hostname}}</td></tr>
|
||||
@@ -59,7 +59,7 @@
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h4>Greylisting</h4>
|
||||
<p>Your box using a technique called greylisting to cut down on spam. Greylisting works by delaying mail from people you haven’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>
|
||||
@@ -55,7 +40,7 @@
|
||||
|
||||
<h3 id="ssl_install_header">Install certificate</h3>
|
||||
|
||||
<p>There are many other places where you can get a free or cheap certificate. If you don't want to use our automatic Let's Encrypt integration, you can give <a href="https://www.namecheap.com/security/ssl-certificates/domain-validation.aspx">Namecheap’s $9 certificate</a>, <a href="https://www.startssl.com/">StartSSL’s free express lane</a>, <a href="https://buy.wosign.com/free/">WoSign’s free TLS</a></a> or any other certificate provider a try.</p>
|
||||
<p>If you don't want to use our automatic Let's Encrypt integration, you can give any other certificate provider a try. You can generate the needed CSR below.</p>
|
||||
|
||||
<p>Which domain are you getting a certificate for?</p>
|
||||
|
||||
@@ -103,23 +88,11 @@ 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;
|
||||
@@ -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,60 +193,40 @@ 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
|
||||
var trace = $("<div class='small text-muted' style='margin-top: 1.5em'>Log:</div>");
|
||||
n.append(trace);
|
||||
for (var j = 0; j < r.log.length; j++)
|
||||
trace.append($("<div/>").text(r.log[j]));
|
||||
|
||||
}
|
||||
|
||||
if (may_reenable_provision_button)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -16,16 +16,60 @@
|
||||
<select class="form-control" rows="1" id="backup-target-type" onchange="toggle_form()">
|
||||
<option value="off">Nowhere (Disable Backups)</option>
|
||||
<option value="local">{{hostname}}</option>
|
||||
<option value="rsync">rsync</option>
|
||||
<option value="s3">Amazon S3</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- LOCAL BACKUP -->
|
||||
<div class="form-group backup-target-local">
|
||||
<div class="col-sm-10 col-sm-offset-2">
|
||||
<p>Backups are stored on this machine’s own hard disk. You are responsible for periodically using SFTP (FTP over SSH) to copy the backup files from <tt id="backup-location"></tt> to a safe location. These files are encrypted, so they are safe to store anywhere.</p>
|
||||
<p>Backups are stored on this machine’s own hard disk. You are responsible for periodically using SFTP (FTP over SSH) to copy the backup files from <tt class="backup-location"></tt> to a safe location. These files are encrypted, so they are safe to store anywhere.</p>
|
||||
<p>Separately copy the encryption password from <tt class="backup-encpassword-file"></tt> to a safe and secure location. You will need this file to decrypt backup files.</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- RSYNC BACKUP -->
|
||||
<div class="form-group backup-target-rsync">
|
||||
<div class="col-sm-10 col-sm-offset-2">
|
||||
|
||||
<p>Backups synced to a remote machine using rsync over SSH, with local
|
||||
copies in <tt class="backup-location"></tt>. These files are encrypted, so
|
||||
they are safe to store anywhere.</p> <p>Separately copy the encryption
|
||||
password from <tt class="backup-encpassword-file"></tt> to a safe and
|
||||
secure location. You will need this file to decrypt backup files.</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group backup-target-rsync">
|
||||
<label for="backup-target-rsync-host" class="col-sm-2 control-label">Hostname</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" placeholder="hostname.local" class="form-control" rows="1" id="backup-target-rsync-host">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group backup-target-rsync">
|
||||
<label for="backup-target-rsync-path" class="col-sm-2 control-label">Path</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" placeholder="/backups/{{hostname}}" class="form-control" rows="1" id="backup-target-rsync-path">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group backup-target-rsync">
|
||||
<label for="backup-target-rsync-user" class="col-sm-2 control-label">Username</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" class="form-control" rows="1" id="backup-target-rsync-user">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group backup-target-rsync">
|
||||
<label for="ssh-pub-key" class="col-sm-2 control-label">Public SSH Key</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" class="form-control" rows="1" id="ssh-pub-key" readonly>
|
||||
<div class="small" style="margin-top: 2px">
|
||||
Copy the Public SSH Key above, and paste it within the <tt>~/.ssh/authorized_keys</tt>
|
||||
of target user on the backup server specified above. That way you'll enable secure and
|
||||
passwordless authentication from your mail-in-a-box server and your backup server.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- S3 BACKUP -->
|
||||
<div class="form-group backup-target-s3">
|
||||
<div class="col-sm-10 col-sm-offset-2">
|
||||
<p>Backups are stored in an Amazon Web Services S3 bucket. You must have an AWS account already.</p>
|
||||
@@ -60,7 +104,8 @@
|
||||
<input type="text" class="form-control" rows="1" id="backup-target-pass">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group backup-target-local backup-target-s3">
|
||||
<!-- Common -->
|
||||
<div class="form-group backup-target-local backup-target-rsync backup-target-s3">
|
||||
<label for="min-age" class="col-sm-2 control-label">Days:</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" class="form-control" rows="1" id="min-age">
|
||||
@@ -92,7 +137,7 @@
|
||||
|
||||
function toggle_form() {
|
||||
var target_type = $("#backup-target-type").val();
|
||||
$(".backup-target-local, .backup-target-s3").hide();
|
||||
$(".backup-target-local, .backup-target-rsync, .backup-target-s3").hide();
|
||||
$(".backup-target-" + target_type).show();
|
||||
}
|
||||
|
||||
@@ -114,7 +159,7 @@ function nice_size(bytes) {
|
||||
|
||||
function show_system_backup() {
|
||||
show_custom_backup()
|
||||
|
||||
|
||||
$('#backup-status tbody').html("<tr><td colspan='2' class='text-muted'>Loading...</td></tr>")
|
||||
api(
|
||||
"/system/backup/status",
|
||||
@@ -155,33 +200,43 @@ function show_system_backup() {
|
||||
total_disk_size += b.size;
|
||||
}
|
||||
|
||||
total_disk_size += r.unmatched_file_size;
|
||||
$('#backup-total-size').text(nice_size(total_disk_size));
|
||||
})
|
||||
}
|
||||
|
||||
function show_custom_backup() {
|
||||
$(".backup-target-local, .backup-target-s3").hide();
|
||||
$(".backup-target-local, .backup-target-rsync, .backup-target-s3").hide();
|
||||
api(
|
||||
"/system/backup/config",
|
||||
"GET",
|
||||
{ },
|
||||
function(r) {
|
||||
$("#backup-target-user").val(r.target_user);
|
||||
$("#backup-target-pass").val(r.target_pass);
|
||||
$("#min-age").val(r.min_age_in_days);
|
||||
$(".backup-location").text(r.file_target_directory);
|
||||
$(".backup-encpassword-file").text(r.enc_pw_file);
|
||||
$("#ssh-pub-key").val(r.ssh_pub_key);
|
||||
|
||||
if (r.target == "file://" + r.file_target_directory) {
|
||||
$("#backup-target-type").val("local");
|
||||
} else if (r.target == "off") {
|
||||
$("#backup-target-type").val("off");
|
||||
} else if (r.target.substring(0, 8) == "rsync://") {
|
||||
$("#backup-target-type").val("rsync");
|
||||
var path = r.target.substring(8).split('//');
|
||||
var host_parts = path.shift().split('@');
|
||||
$("#backup-target-rsync-user").val(host_parts[0]);
|
||||
$("#backup-target-rsync-host").val(host_parts[1]);
|
||||
$("#backup-target-rsync-path").val('/'+path[0]);
|
||||
} else if (r.target.substring(0, 5) == "s3://") {
|
||||
$("#backup-target-type").val("s3");
|
||||
var hostpath = r.target.substring(5).split('/');
|
||||
var hostpath = r.target.substring(5).split('/');
|
||||
var host = hostpath.shift();
|
||||
$("#backup-target-s3-host").val(host);
|
||||
$("#backup-target-s3-path").val(hostpath.join('/'));
|
||||
}
|
||||
$("#backup-target-user").val(r.target_user);
|
||||
$("#backup-target-pass").val(r.target_pass);
|
||||
$("#min-age").val(r.min_age_in_days);
|
||||
$('#backup-location').text(r.file_target_directory);
|
||||
$('.backup-encpassword-file').text(r.enc_pw_file);
|
||||
toggle_form()
|
||||
})
|
||||
}
|
||||
@@ -190,12 +245,18 @@ function set_custom_backup() {
|
||||
var target_type = $("#backup-target-type").val();
|
||||
var target_user = $("#backup-target-user").val();
|
||||
var target_pass = $("#backup-target-pass").val();
|
||||
|
||||
|
||||
var target;
|
||||
if (target_type == "local" || target_type == "off")
|
||||
target = target_type;
|
||||
else if (target_type == "s3")
|
||||
target = "s3://" + $("#backup-target-s3-host").val() + "/" + $("#backup-target-s3-path").val();
|
||||
else if (target_type == "rsync") {
|
||||
target = "rsync://" + $("#backup-target-rsync-user").val() + "@" + $("#backup-target-rsync-host").val()
|
||||
+ "/" + $("#backup-target-rsync-path").val();
|
||||
target_user = '';
|
||||
}
|
||||
|
||||
|
||||
var min_age = $("#min-age").val();
|
||||
api(
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<button type="submit" class="btn btn-primary">Add User</button>
|
||||
</form>
|
||||
<ul style="margin-top: 1em; padding-left: 1.5em; font-size: 90%;">
|
||||
<li>Passwords must be at least four characters and may not contain spaces. For best results, <a href="#" onclick="return generate_random_password()">generate a random password</a>.</li>
|
||||
<li>Passwords must be at least eight characters consisting of English lettters and numbers only. For best results, <a href="#" onclick="return generate_random_password()">generate a random password</a>.</li>
|
||||
<li>Use <a href="#" onclick="return show_panel('aliases')">aliases</a> to create email addresses that forward to existing accounts.</li>
|
||||
<li>Administrators get access to this control panel.</li>
|
||||
<li>User accounts cannot contain any international (non-ASCII) characters, but <a href="#" onclick="return show_panel('aliases');">aliases</a> can.</li>
|
||||
@@ -43,7 +43,6 @@
|
||||
<tr>
|
||||
<th width="50%">Email Address</th>
|
||||
<th>Actions</th>
|
||||
<th>Mailbox Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -73,8 +72,6 @@
|
||||
archive account
|
||||
</a>
|
||||
</td>
|
||||
<td class='mailboxsize'>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="user-extra-template" class="if_inactive">
|
||||
<td colspan="3" style="border: 0; padding-top: 0">
|
||||
@@ -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(
|
||||
@@ -296,7 +292,7 @@ function mod_priv(elem, add_remove) {
|
||||
function generate_random_password() {
|
||||
var pw = "";
|
||||
var charset = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; // confusable characters skipped
|
||||
for (var i = 0; i < 10; i++)
|
||||
for (var i = 0; i < 12; i++)
|
||||
pw += charset.charAt(Math.floor(Math.random() * charset.length));
|
||||
show_modal_error("Random Password", "<p>Here, try this:</p> <p><code style='font-size: 110%'>" + pw + "</code></pr");
|
||||
return false; // cancel click
|
||||
|
||||
@@ -106,76 +106,6 @@ def sort_email_addresses(email_addresses, env):
|
||||
ret.extend(sorted(email_addresses)) # whatever is left
|
||||
return ret
|
||||
|
||||
def exclusive_process(name):
|
||||
# Ensure that a process named `name` does not execute multiple
|
||||
# times concurrently.
|
||||
import os, sys, atexit
|
||||
pidfile = '/var/run/mailinabox-%s.pid' % name
|
||||
mypid = os.getpid()
|
||||
|
||||
# Attempt to get a lock on ourself so that the concurrency check
|
||||
# itself is not executed in parallel.
|
||||
with open(__file__, 'r+') as flock:
|
||||
# Try to get a lock. This blocks until a lock is acquired. The
|
||||
# lock is held until the flock file is closed at the end of the
|
||||
# with block.
|
||||
os.lockf(flock.fileno(), os.F_LOCK, 0)
|
||||
|
||||
# While we have a lock, look at the pid file. First attempt
|
||||
# to write our pid to a pidfile if no file already exists there.
|
||||
try:
|
||||
with open(pidfile, 'x') as f:
|
||||
# Successfully opened a new file. Since the file is new
|
||||
# there is no concurrent process. Write our pid.
|
||||
f.write(str(mypid))
|
||||
atexit.register(clear_my_pid, pidfile)
|
||||
return
|
||||
except FileExistsError:
|
||||
# The pid file already exixts, but it may contain a stale
|
||||
# pid of a terminated process.
|
||||
with open(pidfile, 'r+') as f:
|
||||
# Read the pid in the file.
|
||||
existing_pid = None
|
||||
try:
|
||||
existing_pid = int(f.read().strip())
|
||||
except ValueError:
|
||||
pass # No valid integer in the file.
|
||||
|
||||
# Check if the pid in it is valid.
|
||||
if existing_pid:
|
||||
if is_pid_valid(existing_pid):
|
||||
print("Another %s is already running (pid %d)." % (name, existing_pid), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Write our pid.
|
||||
f.seek(0)
|
||||
f.write(str(mypid))
|
||||
f.truncate()
|
||||
atexit.register(clear_my_pid, pidfile)
|
||||
|
||||
|
||||
def clear_my_pid(pidfile):
|
||||
import os
|
||||
os.unlink(pidfile)
|
||||
|
||||
|
||||
def is_pid_valid(pid):
|
||||
"""Checks whether a pid is a valid process ID of a currently running process."""
|
||||
# adapted from http://stackoverflow.com/questions/568271/how-to-check-if-there-exists-a-process-with-a-given-pid
|
||||
import os, errno
|
||||
if pid <= 0: raise ValueError('Invalid PID.')
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
except OSError as err:
|
||||
if err.errno == errno.ESRCH: # No such process
|
||||
return False
|
||||
elif err.errno == errno.EPERM: # Not permitted to send signal
|
||||
return True
|
||||
else: # EINVAL
|
||||
raise
|
||||
else:
|
||||
return True
|
||||
|
||||
def shell(method, cmd_args, env={}, capture_stderr=False, return_bytes=False, trap=False, input=None):
|
||||
# A safe way to execute processes.
|
||||
# Some processes like apt-get require being given a sane PATH.
|
||||
|
||||
@@ -149,7 +149,10 @@ def make_domain_config(domain, templates, ssl_certificates, env):
|
||||
|
||||
# any proxy or redirect here?
|
||||
for path, url in yaml.get("proxies", {}).items():
|
||||
nginx_conf_extra += "\tlocation %s {\n\t\tproxy_pass %s;\n\t}\n" % (path, url)
|
||||
nginx_conf_extra += "\tlocation %s {" % path
|
||||
nginx_conf_extra += "\n\t\tproxy_pass %s;" % url
|
||||
nginx_conf_extra += "\n\t\tproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;"
|
||||
nginx_conf_extra += "\n\t}\n"
|
||||
for path, url in yaml.get("redirects", {}).items():
|
||||
nginx_conf_extra += "\trewrite %s %s permanent;\n" % (path, url)
|
||||
|
||||
@@ -158,9 +161,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 +201,11 @@ def get_web_domains_info(env):
|
||||
|
||||
# for the SSL config panel, get cert status
|
||||
def check_cert(domain):
|
||||
tls_cert = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
|
||||
if tls_cert is None: return ("danger", "No Certificate Installed")
|
||||
try:
|
||||
tls_cert = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
|
||||
except OSError: # PRIMARY_HOSTNAME cert is missing
|
||||
tls_cert = None
|
||||
if tls_cert is None: return ("danger", "No certificate installed.")
|
||||
cert_status, cert_status_details = check_certificate(domain, tls_cert["certificate"], tls_cert["private-key"])
|
||||
if cert_status == "OK":
|
||||
return ("success", "Signed & valid. " + cert_status_details)
|
||||
|
||||
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}) {
|
||||
15
security.md
15
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.
|
||||
|
||||
@@ -40,21 +40,14 @@ The services all follow these rules:
|
||||
|
||||
* TLS certificates are generated with 2048-bit RSA keys and SHA-256 fingerprints. The box provides a self-signed certificate by default. The [setup guide](https://mailinabox.email/guide.html) explains how to verify the certificate fingerprint on first login. Users are encouraged to replace the certificate with a proper CA-signed one. ([source](setup/ssl.sh))
|
||||
* Only TLSv1, TLSv1.1 and TLSv1.2 are offered (the older SSL protocols are not offered).
|
||||
* Export-grade ciphers, the anonymous DH/ECDH algorithms (aNULL), and clear-text ciphers (eNULL) are not offered.
|
||||
* The minimum cipher key length offered is 112 bits. The maximum is 256 bits. Diffie-Hellman ciphers use a 2048-bit key for forward secrecy.
|
||||
* HTTPS, IMAP, and POP track the [Mozilla Intermediate Ciphers Recommendation](https://wiki.mozilla.org/Security/Server_Side_TLS), balancing security with supporting a wide range of mail clients. Diffie-Hellman ciphers use a 2048-bit key for forward secrecy. For more details, see the [output of SSLyze for these ports](tests/tls_results.txt).
|
||||
* SMTP (port 25) uses the Postfix medium grade ciphers and SMTP Submission (port 587) uses the Postfix high grade ciphers ([more info](http://www.postfix.org/postconf.5.html#smtpd_tls_mandatory_ciphers)).
|
||||
|
||||
Additionally:
|
||||
|
||||
* SMTP Submission (port 587) will not accept user credentials without STARTTLS (true also of SMTP on port 25 in case of client misconfiguration), and the submission port won't accept mail without encryption. The minimum cipher key length is 128 bits. (The box is of course configured not to be an open relay. User credentials are required to send outbound mail.) ([source](setup/mail-postfix.sh))
|
||||
* HTTPS (port 443): The HTTPS Strict Transport Security header is set. A redirect from HTTP to HTTPS is offered. The [Qualys SSL Labs test](https://www.ssllabs.com/ssltest) should report an A+ grade. ([source 1](conf/nginx-ssl.conf), [source 2](conf/nginx.conf))
|
||||
|
||||
For more details, see the [output of SSLyze for these ports](tests/tls_results.txt).
|
||||
|
||||
The cipher and protocol selection are chosen to support the following clients:
|
||||
|
||||
* For HTTPS: Firefox 1, Chrome 1, IE 7, Opera 5, Safari 1, Windows XP IE8, Android 2.3, Java 7.
|
||||
* For other protocols: TBD.
|
||||
|
||||
### Password Storage
|
||||
|
||||
The passwords for mail users are stored on disk using the [SHA512-CRYPT](http://man7.org/linux/man-pages/man3/crypt.3.html) hashing scheme. ([source](management/mailconfig.py))
|
||||
@@ -73,7 +66,7 @@ If DNSSEC is enabled at the box's domain name's registrar, the SSHFP record that
|
||||
|
||||
`fail2ban` provides some protection from brute-force login attacks (repeated logins that guess account passwords) by blocking offending IP addresses at the network level.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -7,7 +7,36 @@
|
||||
#########################################################
|
||||
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG=v0.20
|
||||
# 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.41
|
||||
|
||||
elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then
|
||||
# This machine is running Ubuntu 14.04.
|
||||
echo "You are installing the last version of Mail-in-a-Box that will"
|
||||
echo "support Ubuntu 14.04. If this is a new installation of Mail-in-a-Box,"
|
||||
echo "stop now and switch to a machine running Ubuntu 18.04. If you are"
|
||||
echo "upgrading an existing Mail-in-a-Box --- great. After upgrading this"
|
||||
echo "box, please visit https://mailinabox.email for notes on how to upgrade"
|
||||
echo "to Ubuntu 18.04."
|
||||
echo ""
|
||||
TAG=v0.30
|
||||
|
||||
else
|
||||
echo "This script must be run on a system running Ubuntu 18.04 or Ubuntu 14.04."
|
||||
exit
|
||||
fi
|
||||
fi
|
||||
|
||||
# Are we running as root?
|
||||
|
||||
@@ -21,6 +21,11 @@ mkdir -p $STORAGE_ROOT/mail/dkim
|
||||
# Not quite sure why.
|
||||
echo "127.0.0.1" > /etc/opendkim/TrustedHosts
|
||||
|
||||
# We need to at least create these files, since we reference them later.
|
||||
# Otherwise, opendkim startup will fail
|
||||
touch /etc/opendkim/KeyTable
|
||||
touch /etc/opendkim/SigningTable
|
||||
|
||||
if grep -q "ExternalIgnoreList" /etc/opendkim.conf; then
|
||||
true # already done #NODOC
|
||||
else
|
||||
@@ -75,6 +80,9 @@ tools/editconf.py /etc/postfix/main.cf \
|
||||
non_smtpd_milters=\$smtpd_milters \
|
||||
milter_default_action=accept
|
||||
|
||||
# We need to explicitly enable the opendmarc service, or it will not start
|
||||
hide_output systemctl enable opendmarc
|
||||
|
||||
# Restart services.
|
||||
restart_service opendkim
|
||||
restart_service opendmarc
|
||||
|
||||
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?" \
|
||||
@@ -35,7 +35,7 @@ if [ -z "`tools/mail.py user`" ]; then
|
||||
else
|
||||
# Use me@PRIMARY_HOSTNAME
|
||||
EMAIL_ADDR=me@$PRIMARY_HOSTNAME
|
||||
EMAIL_PW=1234
|
||||
EMAIL_PW=12345678
|
||||
echo
|
||||
echo "Creating a new administrative mail account for $EMAIL_ADDR with password $EMAIL_PW."
|
||||
echo
|
||||
@@ -47,11 +47,11 @@ if [ -z "`tools/mail.py user`" ]; then
|
||||
fi
|
||||
|
||||
# Create the user's mail account. This will ask for a password if none was given above.
|
||||
tools/mail.py user add $EMAIL_ADDR $EMAIL_PW
|
||||
tools/mail.py user add $EMAIL_ADDR ${EMAIL_PW:-}
|
||||
|
||||
# Make it an admin.
|
||||
hide_output tools/mail.py user make-admin $EMAIL_ADDR
|
||||
|
||||
# Create an alias to which we'll direct all automatically-created administrative aliases.
|
||||
tools/mail.py alias add administrator@$PRIMARY_HOSTNAME $EMAIL_ADDR > /dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# Turn on "strict mode." See http://redsymbol.net/articles/unofficial-bash-strict-mode/.
|
||||
# -e: exit if any command unexpectedly fails.
|
||||
# -u: exit if we have a variable typo.
|
||||
# -o pipefail: don't ignore errors in the non-last command in a pipeline
|
||||
set -euo pipefail
|
||||
|
||||
function hide_output {
|
||||
# This function hides the output of a command unless the command fails
|
||||
# and returns a non-zero exit code.
|
||||
@@ -5,11 +11,14 @@ function hide_output {
|
||||
# Get a temporary file.
|
||||
OUTPUT=$(tempfile)
|
||||
|
||||
# Execute command, redirecting stderr/stdout to the temporary file.
|
||||
# Execute command, redirecting stderr/stdout to the temporary file. Since we
|
||||
# check the return code ourselves, disable 'set -e' temporarily.
|
||||
set +e
|
||||
$@ &> $OUTPUT
|
||||
E=$?
|
||||
set -e
|
||||
|
||||
# If the command failed, show the output that was captured in the temporary file.
|
||||
E=$?
|
||||
if [ $E != 0 ]; then
|
||||
# Something failed.
|
||||
echo
|
||||
@@ -48,6 +57,15 @@ function apt_install {
|
||||
apt_get_quiet install $PACKAGES
|
||||
}
|
||||
|
||||
function apt_add_repository_to_unattended_upgrades {
|
||||
if [ -f /etc/apt/apt.conf.d/50unattended-upgrades ]; then
|
||||
if ! grep -q "$1" /etc/apt/apt.conf.d/50unattended-upgrades; then
|
||||
sed -i "/Allowed-Origins/a \
|
||||
\"$1\";" /etc/apt/apt.conf.d/50unattended-upgrades
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function get_default_hostname {
|
||||
# Guess the machine's hostname. It should be a fully qualified
|
||||
# domain name suitable for DNS. None of these calls may provide
|
||||
@@ -66,7 +84,7 @@ function get_publicip_from_web_service {
|
||||
#
|
||||
# Pass '4' or '6' as an argument to this function to specify
|
||||
# what type of address to get (IPv4, IPv6).
|
||||
curl -$1 --fail --silent --max-time 15 icanhazip.com 2>/dev/null
|
||||
curl -$1 --fail --silent --max-time 15 icanhazip.com 2>/dev/null || /bin/true
|
||||
}
|
||||
|
||||
function get_default_privateip {
|
||||
@@ -122,11 +140,10 @@ function get_default_privateip {
|
||||
fi
|
||||
|
||||
echo $address
|
||||
|
||||
}
|
||||
|
||||
function ufw_allow {
|
||||
if [ -z "$DISABLE_FIREWALL" ]; then
|
||||
if [ -z "${DISABLE_FIREWALL:-}" ]; then
|
||||
# ufw has completely unhelpful output
|
||||
ufw allow $1 > /dev/null;
|
||||
fi
|
||||
@@ -145,10 +162,13 @@ function input_box {
|
||||
# input_box "title" "prompt" "defaultvalue" VARIABLE
|
||||
# The user's input will be stored in the variable VARIABLE.
|
||||
# The exit code from dialog will be stored in VARIABLE_EXITCODE.
|
||||
# Temporarily turn off 'set -e' because we need the dialog return code.
|
||||
declare -n result=$4
|
||||
declare -n result_code=$4_EXITCODE
|
||||
set +e
|
||||
result=$(dialog --stdout --title "$1" --inputbox "$2" 0 0 "$3")
|
||||
result_code=$?
|
||||
set -e
|
||||
}
|
||||
|
||||
function input_menu {
|
||||
@@ -158,8 +178,10 @@ function input_menu {
|
||||
declare -n result=$4
|
||||
declare -n result_code=$4_EXITCODE
|
||||
local IFS=^$'\n'
|
||||
set +e
|
||||
result=$(dialog --stdout --title "$1" --menu "$2" 0 0 0 $3)
|
||||
result_code=$?
|
||||
set -e
|
||||
}
|
||||
|
||||
function wget_verify {
|
||||
@@ -170,7 +192,7 @@ function wget_verify {
|
||||
DEST=$3
|
||||
CHECKSUM="$HASH $DEST"
|
||||
rm -f $DEST
|
||||
wget -q -O $DEST $URL || exit 1
|
||||
hide_output wget -O $DEST $URL
|
||||
if ! echo "$CHECKSUM" | sha1sum --check --strict > /dev/null; then
|
||||
echo "------------------------------------------------------------"
|
||||
echo "Download of $URL did not match expected checksum."
|
||||
|
||||
@@ -26,7 +26,7 @@ source /etc/mailinabox.conf # load global vars
|
||||
echo "Installing Dovecot (IMAP server)..."
|
||||
apt_install \
|
||||
dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-sqlite sqlite3 \
|
||||
dovecot-sieve dovecot-managesieved dovecot-lucene
|
||||
dovecot-sieve dovecot-managesieved
|
||||
|
||||
# The `dovecot-imapd`, `dovecot-pop3d`, and `dovecot-lmtpd` packages automatically
|
||||
# enable IMAP, POP and LMTP protocols.
|
||||
@@ -37,8 +37,16 @@ apt_install \
|
||||
# of active IMAP connections (at, say, 5 open connections per user that
|
||||
# would be 20 users). Set it to 250 times the number of cores this
|
||||
# machine has, so on a two-core machine that's 500 processes/100 users).
|
||||
# The `default_vsz_limit` is the maximum amount of virtual memory that
|
||||
# can be allocated. It should be set *reasonably high* to avoid allocation
|
||||
# issues with larger mailboxes. We're setting it to 1/3 of the total
|
||||
# available memory (physical mem + swap) to be sure.
|
||||
# See here for discussion:
|
||||
# - https://www.dovecot.org/list/dovecot/2012-August/137569.html
|
||||
# - https://www.dovecot.org/list/dovecot/2011-December/132455.html
|
||||
tools/editconf.py /etc/dovecot/conf.d/10-master.conf \
|
||||
default_process_limit=$(echo "`nproc` * 250" | bc) \
|
||||
default_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
|
||||
@@ -71,12 +79,15 @@ tools/editconf.py /etc/dovecot/conf.d/10-auth.conf \
|
||||
|
||||
# Enable SSL, specify the location of the SSL certificate and private key files.
|
||||
# Disable obsolete SSL protocols and allow only good ciphers per http://baldric.net/2013/12/07/tls-ciphers-in-postfix-and-dovecot/.
|
||||
# Enable strong ssl dh parameters
|
||||
tools/editconf.py /etc/dovecot/conf.d/10-ssl.conf \
|
||||
ssl=required \
|
||||
"ssl_cert=<$STORAGE_ROOT/ssl/ssl_certificate.pem" \
|
||||
"ssl_key=<$STORAGE_ROOT/ssl/ssl_private_key.pem" \
|
||||
"ssl_protocols=!SSLv3 !SSLv2" \
|
||||
"ssl_cipher_list=TLSv1+HIGH !SSLv2 !RC4 !aNULL !eNULL !3DES @STRENGTH"
|
||||
"ssl_cipher_list=ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS" \
|
||||
"ssl_prefer_server_ciphers = yes" \
|
||||
"ssl_dh_parameters_length = 2048"
|
||||
|
||||
# Disable in-the-clear IMAP/POP because there is no reason for a user to transmit
|
||||
# login credentials outside of an encrypted connection. Only the over-TLS versions
|
||||
@@ -101,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
|
||||
|
||||
@@ -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,6 +73,8 @@ tools/editconf.py /etc/postfix/main.cf \
|
||||
|
||||
# Enable the 'submission' port 587 smtpd server and tweak its settings.
|
||||
#
|
||||
# * Enable authentication. It's disabled globally so that it is disabled on port 25,
|
||||
# so we need to explicitly enable it here.
|
||||
# * Do not add the OpenDMAC Authentication-Results header. That should only be added
|
||||
# on incoming mail. Omit the OpenDMARC milter by re-setting smtpd_milters to the
|
||||
# OpenDKIM milter only. See dkim.sh.
|
||||
@@ -91,16 +85,19 @@ tools/editconf.py /etc/postfix/main.cf \
|
||||
# * Give it a different name in syslog to distinguish it from the port 25 smtpd server.
|
||||
# * Add a new cleanup service specific to the submission service ('authclean')
|
||||
# that filters out privacy-sensitive headers on mail being sent out by
|
||||
# authenticated users.
|
||||
# authenticated users. By default Postfix also applies this to attached
|
||||
# emails but we turn this off by setting nested_header_checks empty.
|
||||
tools/editconf.py /etc/postfix/master.cf -s -w \
|
||||
"submission=inet n - - - - smtpd
|
||||
-o smtpd_sasl_auth_enable=yes
|
||||
-o syslog_name=postfix/submission
|
||||
-o smtpd_milters=inet:127.0.0.1:8891
|
||||
-o smtpd_tls_security_level=encrypt
|
||||
-o smtpd_tls_ciphers=high -o smtpd_tls_exclude_ciphers=aNULL,DES,3DES,MD5,DES+MD5,RC4 -o smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
|
||||
-o cleanup_service_name=authclean" \
|
||||
"authclean=unix n - - - 0 cleanup
|
||||
-o header_checks=pcre:/etc/postfix/outgoing_mail_header_filters"
|
||||
-o header_checks=pcre:/etc/postfix/outgoing_mail_header_filters
|
||||
-o nested_header_checks="
|
||||
|
||||
# Install the `outgoing_mail_header_filters` file required by the new 'authclean' service.
|
||||
cp conf/postfix_outgoing_mail_header_filters /etc/postfix/outgoing_mail_header_filters
|
||||
@@ -152,7 +149,7 @@ tools/editconf.py /etc/postfix/main.cf \
|
||||
# then opportunistic TLS is used. Otherwise the server certificate must match the TLSA records
|
||||
# or else the mail bounces. TLSA also requires DNSSEC on the MX host. Postfix doesn't do DNSSEC
|
||||
# itself but assumes the system's nameserver does and reports DNSSEC status. Thus this also
|
||||
# relies on our local bind9 server being present and `smtp_dns_support_level=dnssec`.
|
||||
# relies on our local DNS server (see system.sh) and `smtp_dns_support_level=dnssec`.
|
||||
#
|
||||
# The `smtp_tls_CAfile` is superflous, but it eliminates warnings in the logs about untrusted certs,
|
||||
# which we don't care about seeing because Postfix is doing opportunistic TLS anyway. Better to encrypt,
|
||||
@@ -176,8 +173,11 @@ tools/editconf.py /etc/postfix/main.cf \
|
||||
#
|
||||
# In a basic setup we would pass mail directly to Dovecot by setting
|
||||
# virtual_transport to `lmtp:unix:private/dovecot-lmtp`.
|
||||
#
|
||||
tools/editconf.py /etc/postfix/main.cf virtual_transport=lmtp:[127.0.0.1]:10025
|
||||
# Because of a spampd bug, limit the number of recipients in each connection.
|
||||
# See https://github.com/mail-in-a-box/mailinabox/issues/1523.
|
||||
tools/editconf.py /etc/postfix/main.cf lmtp_destination_recipient_limit=1
|
||||
|
||||
|
||||
# Who can send mail to us? Some basic filters.
|
||||
#
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -4,25 +4,55 @@ source setup/functions.sh
|
||||
|
||||
echo "Installing Mail-in-a-Box system management daemon..."
|
||||
|
||||
# Install packages.
|
||||
# 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
|
||||
# DEPENDENCIES
|
||||
|
||||
# These are required to pip install cryptography.
|
||||
apt_install build-essential libssl-dev libffi-dev python3-dev
|
||||
# We used to install management daemon-related Python packages
|
||||
# directly to /usr/local/lib. We moved to a virtualenv because
|
||||
# these packages might conflict with apt-installed packages.
|
||||
# We may have a lingering version of acme that conflcits with
|
||||
# certbot, which we're about to install below, so remove it
|
||||
# first. Once acme is installed by an apt package, this might
|
||||
# break the package version and `apt-get install --reinstall python3-acme`
|
||||
# might be needed in that case.
|
||||
while [ -d /usr/local/lib/python3.4/dist-packages/acme ]; do
|
||||
pip3 uninstall -y acme;
|
||||
done
|
||||
|
||||
# duplicity is used to make backups of user data. It uses boto
|
||||
# (via Python 2) to do backups to AWS S3. boto from the Ubuntu
|
||||
# package manager is too out-of-date -- it doesn't support the newer
|
||||
# S3 api used in some regions, which breaks backups to those regions.
|
||||
# See #627, #653.
|
||||
#
|
||||
# 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
|
||||
virtualenv -ppython3 $venv
|
||||
fi
|
||||
|
||||
# Upgrade pip because the Ubuntu-packaged version is out of date.
|
||||
hide_output $venv/bin/pip install --upgrade pip
|
||||
|
||||
# Install other Python 3 packages used by the management daemon.
|
||||
# The first line is the packages that Josh maintains himself!
|
||||
# NOTE: email_validator is repeated in setup/questions.sh, so please keep the versions synced.
|
||||
hide_output pip3 install --upgrade \
|
||||
rtyaml "email_validator>=1.0.0" "free_tls_certificates>=0.1.3" \
|
||||
"idna>=2.0.0" "cryptography>=1.0.2" boto psutil
|
||||
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
|
||||
|
||||
# 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 pip install --upgrade boto
|
||||
# CONFIGURATION
|
||||
|
||||
# Create a backup directory and a random key for encrypting backups.
|
||||
mkdir -p $STORAGE_ROOT/backup
|
||||
@@ -30,19 +60,42 @@ if [ ! -f $STORAGE_ROOT/backup/secret_key.txt ]; then
|
||||
$(umask 077; openssl rand -base64 2048 > $STORAGE_ROOT/backup/secret_key.txt)
|
||||
fi
|
||||
|
||||
# Link the management server daemon into a well known location.
|
||||
rm -f /usr/local/bin/mailinabox-daemon
|
||||
ln -s `pwd`/management/daemon.py /usr/local/bin/mailinabox-daemon
|
||||
|
||||
# Download jQuery and Bootstrap local files
|
||||
|
||||
# Make sure we have the directory to save to.
|
||||
assets_dir=$inst_dir/vendor/assets
|
||||
rm -rf $assets_dir
|
||||
mkdir -p $assets_dir
|
||||
|
||||
# jQuery CDN URL
|
||||
jquery_version=2.1.4
|
||||
jquery_url=https://code.jquery.com
|
||||
|
||||
# Get jQuery
|
||||
wget_verify $jquery_url/jquery-$jquery_version.min.js 43dc554608df885a59ddeece1598c6ace434d747 $assets_dir/jquery.min.js
|
||||
|
||||
# Bootstrap CDN URL
|
||||
bootstrap_version=3.3.7
|
||||
bootstrap_url=https://github.com/twbs/bootstrap/releases/download/v$bootstrap_version/bootstrap-$bootstrap_version-dist.zip
|
||||
|
||||
# Get Bootstrap
|
||||
wget_verify $bootstrap_url e6b1000b94e835ffd37f4c6dcbdad43f4b48a02a /tmp/bootstrap.zip
|
||||
unzip -q /tmp/bootstrap.zip -d $assets_dir
|
||||
mv $assets_dir/bootstrap-$bootstrap_version-dist $assets_dir/bootstrap
|
||||
rm -f /tmp/bootstrap.zip
|
||||
|
||||
# Create an init script to start the management daemon and keep it
|
||||
# running after a reboot.
|
||||
rm -f /etc/init.d/mailinabox
|
||||
ln -s $(pwd)/conf/management-initscript /etc/init.d/mailinabox
|
||||
hide_output update-rc.d mailinabox defaults
|
||||
|
||||
# Remove old files we no longer use.
|
||||
rm -f /etc/cron.daily/mailinabox-backup
|
||||
rm -f /etc/cron.daily/mailinabox-statuschecks
|
||||
cat > $inst_dir/start <<EOF;
|
||||
#!/bin/bash
|
||||
source $venv/bin/activate
|
||||
exec python `pwd`/management/daemon.py
|
||||
EOF
|
||||
chmod +x $inst_dir/start
|
||||
hide_output systemctl link -f conf/mailinabox.service
|
||||
hide_output systemctl daemon-reload
|
||||
hide_output systemctl enable mailinabox.service
|
||||
|
||||
# Perform nightly tasks at 3am in system time: take a backup, run
|
||||
# status checks and email the administrator any changes.
|
||||
|
||||
@@ -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.)
|
||||
@@ -59,6 +61,14 @@ done
|
||||
# Create a 'state' directory. Not sure why we need to do this manually.
|
||||
mkdir -p /var/lib/munin-node/plugin-state/
|
||||
|
||||
# Create a systemd service for munin.
|
||||
ln -sf $(pwd)/management/munin_start.sh /usr/local/lib/mailinabox/munin_start.sh
|
||||
chmod 0744 /usr/local/lib/mailinabox/munin_start.sh
|
||||
hide_output systemctl link -f conf/munin.service
|
||||
hide_output systemctl daemon-reload
|
||||
hide_output systemctl unmask munin.service
|
||||
hide_output systemctl enable munin.service
|
||||
|
||||
# Restart services.
|
||||
restart_service munin
|
||||
restart_service munin-node
|
||||
|
||||
@@ -1,59 +1,51 @@
|
||||
#!/bin/bash
|
||||
# Owncloud
|
||||
# Nextcloud
|
||||
##########################
|
||||
|
||||
source setup/functions.sh # load our functions
|
||||
source /etc/mailinabox.conf # load global vars
|
||||
|
||||
# ### Installing ownCloud
|
||||
# ### Installing Nextcloud
|
||||
|
||||
echo "Installing ownCloud (contacts/calendar)..."
|
||||
echo "Installing Nextcloud (contacts/calendar)..."
|
||||
|
||||
apt_install \
|
||||
dbconfig-common \
|
||||
php5-cli php5-sqlite php5-gd php5-imap php5-curl php-pear php-apc curl libapr1 libtool libcurl4-openssl-dev php-xml-parser \
|
||||
php5 php5-dev php5-gd php5-fpm memcached php5-memcached unzip
|
||||
apt-get purge -qq -y owncloud* # we used to use the package manager
|
||||
|
||||
apt-get purge -qq -y owncloud*
|
||||
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
|
||||
|
||||
# Install ownCloud from source of this version:
|
||||
owncloud_ver=8.2.7
|
||||
owncloud_hash=723ba3f46dad219109cdf28dcc016fcd8a6bc434
|
||||
InstallNextcloud() {
|
||||
|
||||
# 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
|
||||
version=$1
|
||||
hash=$2
|
||||
|
||||
# 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
|
||||
echo
|
||||
echo "Upgrading to Nextcloud version $version"
|
||||
echo
|
||||
|
||||
# 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
|
||||
# Remove the current owncloud/Nextcloud
|
||||
rm -rf /usr/local/lib/owncloud
|
||||
|
||||
# Download and verify
|
||||
wget_verify https://download.owncloud.org/community/owncloud-$owncloud_ver.zip $owncloud_hash /tmp/owncloud.zip
|
||||
wget_verify https://download.nextcloud.com/server/releases/nextcloud-$version.zip $hash /tmp/nextcloud.zip
|
||||
|
||||
# Clear out the existing ownCloud.
|
||||
if [ -d /usr/local/lib/owncloud/ ]; then
|
||||
echo "upgrading ownCloud to $owncloud_ver (backing up existing ownCloud directory to /tmp/owncloud-backup-$$)..."
|
||||
mv /usr/local/lib/owncloud /tmp/owncloud-backup-$$
|
||||
fi
|
||||
# Extract ownCloud/Nextcloud
|
||||
unzip -q /tmp/nextcloud.zip -d /usr/local/lib
|
||||
mv /usr/local/lib/nextcloud /usr/local/lib/owncloud
|
||||
rm -f /tmp/nextcloud.zip
|
||||
|
||||
# Extract ownCloud
|
||||
unzip -u -o -q /tmp/owncloud.zip -d /usr/local/lib #either extracts new or replaces current files
|
||||
rm -f /tmp/owncloud.zip
|
||||
|
||||
# The two apps we actually want are not in ownCloud core. Clone them from
|
||||
# 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
|
||||
git_clone https://github.com/owncloudarchive/contacts 9ba2e667ae8c7ea36d8c4a4c3413c374beb24b1b '' /usr/local/lib/owncloud/apps/contacts
|
||||
git_clone https://github.com/owncloudarchive/calendar 2086e738a3b7b868ec59cd61f0f88b49c3f21dd1 '' /usr/local/lib/owncloud/apps/calendar
|
||||
|
||||
wget_verify https://github.com/nextcloud/contacts/releases/download/v2.1.8/contacts.tar.gz b5d5bbee33f0c32b124b46cb6aaab90c695ac170 /tmp/contacts.tgz
|
||||
tar xf /tmp/contacts.tgz -C /usr/local/lib/owncloud/apps/
|
||||
rm /tmp/contacts.tgz
|
||||
|
||||
wget_verify https://github.com/nextcloud/calendar/releases/download/v1.6.4/calendar.tar.gz d8a7950dba14803472b6c19625a8ceb23d6fd4ef /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}
|
||||
@@ -65,11 +57,11 @@ if [ ! -d /usr/local/lib/owncloud/ ] \
|
||||
# Make sure permissions are correct or the upgrade step won't run.
|
||||
# $STORAGE_ROOT/owncloud may not yet exist, so use -f to suppress
|
||||
# that error.
|
||||
chown -f -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
|
||||
chown -f -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud || /bin/true
|
||||
|
||||
# If this isn't a new installation, immediately run the upgrade script.
|
||||
# Then check for success (0=ok and 3=no upgrade needed, both are success).
|
||||
if [ -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
||||
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
|
||||
@@ -80,12 +72,57 @@ if [ ! -d /usr/local/lib/owncloud/ ] \
|
||||
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
|
||||
fi
|
||||
}
|
||||
|
||||
nextcloud_ver=14.0.6
|
||||
nextcloud_hash=4e43a57340f04c2da306c8eea98e30040399ae5a
|
||||
|
||||
# Check if Nextcloud dir exist, and check if version matches nextcloud_ver (if either doesn't - install/upgrade)
|
||||
if [ ! -d /usr/local/lib/owncloud/ ] \
|
||||
|| ! grep -q $nextcloud_ver /usr/local/lib/owncloud/version.php; then
|
||||
|
||||
# Stop php-fpm if running. If theyre not running (which happens on a previously failed install), dont bail.
|
||||
service php7.2-fpm stop &> /dev/null || /bin/true
|
||||
|
||||
# Backup the existing ownCloud/Nextcloud.
|
||||
# Create a backup directory to store the current installation and database to
|
||||
BACKUP_DIRECTORY=$STORAGE_ROOT/owncloud-backup/`date +"%Y-%m-%d-%T"`
|
||||
mkdir -p "$BACKUP_DIRECTORY"
|
||||
if [ -d /usr/local/lib/owncloud/ ]; then
|
||||
echo "Upgrading Nextcloud --- backing up existing installation, configuration, and database to directory to $BACKUP_DIRECTORY..."
|
||||
cp -r /usr/local/lib/owncloud "$BACKUP_DIRECTORY/owncloud-install"
|
||||
fi
|
||||
if [ -e $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
||||
cp $STORAGE_ROOT/owncloud/owncloud.db $BACKUP_DIRECTORY
|
||||
fi
|
||||
if [ -e $STORAGE_ROOT/owncloud/config.php ]; then
|
||||
cp $STORAGE_ROOT/owncloud/config.php $BACKUP_DIRECTORY
|
||||
fi
|
||||
|
||||
# If ownCloud or Nextcloud was previously installed....
|
||||
if [ -e /usr/local/lib/owncloud/version.php ]; then
|
||||
# Database migrations from ownCloud are no longer possible because ownCloud cannot be run under
|
||||
# PHP 7.
|
||||
if grep -q "OC_VersionString = '[89]\." /usr/local/lib/owncloud/version.php; then
|
||||
echo "Upgrades from Mail-in-a-Box prior to v0.28 (dated July 30, 2018) with Nextcloud < 13.0.6 (you have ownCloud 8 or 9) are not supported. Upgrade to Mail-in-a-Box version v0.30 first. Setup aborting."
|
||||
exit 1
|
||||
fi
|
||||
if grep -q "OC_VersionString = '1[012]\." /usr/local/lib/owncloud/version.php; then
|
||||
echo "Upgrades from Mail-in-a-Box prior to v0.28 (dated July 30, 2018) with Nextcloud < 13.0.6 (you have ownCloud 10, 11 or 12) are not supported. Upgrade to Mail-in-a-Box version v0.30 first. Setup aborting."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
InstallNextcloud $nextcloud_ver $nextcloud_hash
|
||||
fi
|
||||
|
||||
# ### Configuring ownCloud
|
||||
# ### Configuring Nextcloud
|
||||
|
||||
# Setup ownCloud if the ownCloud database does not yet exist. Running setup when
|
||||
# Setup Nextcloud if the Nextcloud database does not yet exist. Running setup when
|
||||
# the database does exist wipes the database and user data.
|
||||
if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
||||
# Create user data directory
|
||||
@@ -100,20 +137,17 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
||||
|
||||
'instanceid' => '$instanceid',
|
||||
|
||||
'forcessl' => true, # if unset/false, ownCloud sends a HSTS=0 header, which conflicts with nginx config
|
||||
'forcessl' => true, # if unset/false, Nextcloud sends a HSTS=0 header, which conflicts with nginx config
|
||||
|
||||
'overwritewebroot' => '/cloud',
|
||||
'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\\Memcached',
|
||||
"memcached_servers" => array (
|
||||
array('127.0.0.1', 11211),
|
||||
array(
|
||||
'class'=>'OC_User_IMAP',
|
||||
'arguments'=>array('{127.0.0.1:993/imap/ssl/novalidate-cert}')
|
||||
)
|
||||
),
|
||||
'memcache.local' => '\OC\Memcache\APCu',
|
||||
'mail_smtpmode' => 'sendmail',
|
||||
'mail_smtpsecure' => '',
|
||||
'mail_smtpauthtype' => 'LOGIN',
|
||||
@@ -123,7 +157,6 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
||||
'mail_smtpname' => '',
|
||||
'mail_smtppassword' => '',
|
||||
'mail_from_address' => 'owncloud',
|
||||
'mail_domain' => '$PRIMARY_HOSTNAME',
|
||||
);
|
||||
?>
|
||||
EOF
|
||||
@@ -140,7 +173,7 @@ EOF
|
||||
'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
|
||||
# the user does not have to enter anything on first load of Nextcloud
|
||||
'adminlogin' => 'root',
|
||||
'adminpass' => '$adminpassword',
|
||||
);
|
||||
@@ -150,7 +183,7 @@ 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.
|
||||
# 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;)
|
||||
@@ -164,6 +197,8 @@ fi
|
||||
# * 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)
|
||||
@@ -173,13 +208,15 @@ include("$STORAGE_ROOT/owncloud/config.php");
|
||||
|
||||
\$CONFIG['trusted_domains'] = array('$PRIMARY_HOSTNAME');
|
||||
|
||||
\$CONFIG['memcache.local'] = '\\OC\\Memcache\\Memcached';
|
||||
\$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';
|
||||
|
||||
echo "<?php\n\\\$CONFIG = ";
|
||||
var_export(\$CONFIG);
|
||||
echo ";";
|
||||
@@ -187,9 +224,9 @@ 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.
|
||||
# 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 ownCloud to use IMAP for login. The contacts
|
||||
# user_external is what allows Nextcloud to use IMAP for login. The contacts
|
||||
# and calendar apps are the extensions we really care about here.
|
||||
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
|
||||
@@ -204,7 +241,7 @@ if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
|
||||
|
||||
# Set PHP FPM values to support large file uploads
|
||||
# (semicolon is the comment character in this file, hashes produce deprecation warnings)
|
||||
tools/editconf.py /etc/php5/fpm/php.ini -c ';' \
|
||||
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
|
||||
upload_max_filesize=16G \
|
||||
post_max_size=16G \
|
||||
output_buffering=16384 \
|
||||
@@ -212,7 +249,27 @@ tools/editconf.py /etc/php5/fpm/php.ini -c ';' \
|
||||
max_execution_time=600 \
|
||||
short_open_tag=On
|
||||
|
||||
# Set up a cron job for owncloud.
|
||||
# Set Nextcloud recommended opcache settings
|
||||
tools/editconf.py /etc/php/7.2/cli/conf.d/10-opcache.ini -c ';' \
|
||||
opcache.enable=1 \
|
||||
opcache.enable_cli=1 \
|
||||
opcache.interned_strings_buffer=8 \
|
||||
opcache.max_accelerated_files=10000 \
|
||||
opcache.memory_consumption=128 \
|
||||
opcache.save_comments=1 \
|
||||
opcache.revalidate_freq=1
|
||||
|
||||
# Configure the path environment for php-fpm
|
||||
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
||||
env[PATH]=/usr/local/bin:/usr/bin:/bin
|
||||
|
||||
# If apc is explicitly disabled we need to enable it
|
||||
if grep -q apc.enabled=0 /etc/php/7.2/mods-available/apcu.ini; then
|
||||
tools/editconf.py /etc/php/7.2/mods-available/apcu.ini -c ';' \
|
||||
apc.enabled=1
|
||||
fi
|
||||
|
||||
# Set up a cron job for Nextcloud.
|
||||
cat > /etc/cron.hourly/mailinabox-owncloud << EOF;
|
||||
#!/bin/bash
|
||||
# Mail-in-a-Box
|
||||
@@ -220,8 +277,8 @@ 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.
|
||||
# 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
|
||||
@@ -230,5 +287,4 @@ chmod +x /etc/cron.hourly/mailinabox-owncloud
|
||||
# ```
|
||||
|
||||
# Enable PHP modules and restart PHP.
|
||||
php5enmod imap
|
||||
restart_service php5-fpm
|
||||
restart_service php7.2-fpm
|
||||
@@ -7,9 +7,9 @@ if [[ $EUID -ne 0 ]]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
# Check that we are running on Ubuntu 14.04 LTS (or 14.04.xx).
|
||||
if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" != "Ubuntu 14.04 LTS" ]; then
|
||||
echo "Mail-in-a-Box only supports being installed on Ubuntu 14.04, sorry. You are running:"
|
||||
# Check that we are running on Ubuntu 18.04 LTS (or 18.04.xx).
|
||||
if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" != "Ubuntu 18.04 LTS" ]; then
|
||||
echo "Mail-in-a-Box only supports being installed on Ubuntu 18.04, sorry. You are running:"
|
||||
echo
|
||||
lsb_release -d | sed 's/.*:\s*//'
|
||||
echo
|
||||
@@ -19,23 +19,29 @@ fi
|
||||
|
||||
# Check that we have enough memory.
|
||||
#
|
||||
# /proc/meminfo reports free memory in kibibytes. Our baseline will be 768 MB,
|
||||
# which is 750000 kibibytes.
|
||||
# /proc/meminfo reports free memory in kibibytes. Our baseline will be 512 MB,
|
||||
# which is 500000 kibibytes.
|
||||
#
|
||||
# We will display a warning if the memory is below 768 MB which is 750000 kibibytes
|
||||
#
|
||||
# Skip the check if we appear to be running inside of Vagrant, because that's really just for testing.
|
||||
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}')
|
||||
if [ $TOTAL_PHYSICAL_MEM -lt 750000 ]; then
|
||||
if [ $TOTAL_PHYSICAL_MEM -lt 500000 ]; then
|
||||
if [ ! -d /vagrant ]; then
|
||||
TOTAL_PHYSICAL_MEM=$(expr \( \( $TOTAL_PHYSICAL_MEM \* 1024 \) / 1000 \) / 1000)
|
||||
echo "Your Mail-in-a-Box needs more memory (RAM) to function properly."
|
||||
echo "Please provision a machine with at least 768 MB, 1 GB recommended."
|
||||
echo "Please provision a machine with at least 512 MB, 1 GB recommended."
|
||||
echo "This machine has $TOTAL_PHYSICAL_MEM MB memory."
|
||||
exit
|
||||
fi
|
||||
fi
|
||||
if [ $TOTAL_PHYSICAL_MEM -lt 750000 ]; then
|
||||
echo "WARNING: Your Mail-in-a-Box has less than 768 MB of memory."
|
||||
echo " It might run unreliably when under heavy load."
|
||||
fi
|
||||
|
||||
# Check that tempfs is mounted with exec
|
||||
MOUNTED_TMP_AS_NO_EXEC=$(grep "/tmp.*noexec" /proc/mounts)
|
||||
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
|
||||
@@ -47,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
|
||||
@@ -180,18 +182,15 @@ if [ "$PUBLIC_IPV6" = "auto" ]; then
|
||||
fi
|
||||
if [ "$PRIMARY_HOSTNAME" = "auto" ]; then
|
||||
PRIMARY_HOSTNAME=$(get_default_hostname)
|
||||
elif [ "$PRIMARY_HOSTNAME" = "auto-easy" ]; then
|
||||
# Generate a probably-unique subdomain under our justtesting.email domain.
|
||||
PRIMARY_HOSTNAME=`echo $PUBLIC_IP | sha1sum | cut -c1-5`.justtesting.email
|
||||
fi
|
||||
|
||||
# Set STORAGE_USER and STORAGE_ROOT to default values (user-data and /home/user-data), unless
|
||||
# we've already got those values from a previous run.
|
||||
if [ -z "$STORAGE_USER" ]; then
|
||||
STORAGE_USER=$([[ -z "$DEFAULT_STORAGE_USER" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER")
|
||||
if [ -z "${STORAGE_USER:-}" ]; then
|
||||
STORAGE_USER=$([[ -z "${DEFAULT_STORAGE_USER:-}" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER")
|
||||
fi
|
||||
if [ -z "$STORAGE_ROOT" ]; then
|
||||
STORAGE_ROOT=$([[ -z "$DEFAULT_STORAGE_ROOT" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT")
|
||||
if [ -z "${STORAGE_ROOT:-}" ]; then
|
||||
STORAGE_ROOT=$([[ -z "${DEFAULT_STORAGE_ROOT:-}" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT")
|
||||
fi
|
||||
|
||||
# Show the configuration, since the user may have not entered it manually.
|
||||
|
||||
@@ -48,7 +48,7 @@ echo "public.pyzor.org:24441" > /etc/spamassassin/pyzor/servers
|
||||
# * Disable localmode so Pyzor, DKIM and DNS checks can be used.
|
||||
tools/editconf.py /etc/default/spampd \
|
||||
DESTPORT=10026 \
|
||||
ADDOPTS="\"--maxsize=500\"" \
|
||||
ADDOPTS="\"--maxsize=2000\"" \
|
||||
LOCALONLY=0
|
||||
|
||||
# Spamassassin normally wraps spam as an attachment inside a fresh
|
||||
@@ -61,9 +61,11 @@ tools/editconf.py /etc/default/spampd \
|
||||
# content or execute scripts, and it is probably confusing to most users.
|
||||
#
|
||||
# Tell Spamassassin not to modify the original message except for adding
|
||||
# the X-Spam-Status mail header and related headers.
|
||||
# the X-Spam-Status & X-Spam-Score mail headers and related headers.
|
||||
tools/editconf.py /etc/spamassassin/local.cf -s \
|
||||
report_safe=0
|
||||
report_safe=0 \
|
||||
add_header="all Report _REPORT_" \
|
||||
add_header="all Score _SCORE_"
|
||||
|
||||
# Bayesean learning
|
||||
# -----------------
|
||||
@@ -84,7 +86,7 @@ tools/editconf.py /etc/spamassassin/local.cf -s \
|
||||
|
||||
tools/editconf.py /etc/spamassassin/local.cf -s \
|
||||
bayes_path=$STORAGE_ROOT/mail/spamassassin/bayes \
|
||||
bayes_file_mode=0660
|
||||
bayes_file_mode=0666
|
||||
|
||||
mkdir -p $STORAGE_ROOT/mail/spamassassin
|
||||
chown -R spampd:spampd $STORAGE_ROOT/mail/spamassassin
|
||||
|
||||
@@ -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 "-----------------------------------------------"
|
||||
@@ -147,17 +157,17 @@ if management/status_checks.py --check-primary-hostname; then
|
||||
echo https://$PRIMARY_HOSTNAME/admin
|
||||
echo
|
||||
echo "If you have a DNS problem put the box's IP address in the URL"
|
||||
echo "(https://$PUBLIC_IP/admin) but then check the SSL fingerprint:"
|
||||
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint \
|
||||
| sed "s/SHA1 Fingerprint=//"
|
||||
echo "(https://$PUBLIC_IP/admin) but then check the TLS fingerprint:"
|
||||
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint -sha256\
|
||||
| sed "s/SHA256 Fingerprint=//"
|
||||
else
|
||||
echo https://$PUBLIC_IP/admin
|
||||
echo
|
||||
echo You will be alerted that the website has an invalid certificate. Check that
|
||||
echo the certificate fingerprint matches:
|
||||
echo
|
||||
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint \
|
||||
| sed "s/SHA1 Fingerprint=//"
|
||||
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint -sha256\
|
||||
| sed "s/SHA256 Fingerprint=//"
|
||||
echo
|
||||
echo Then you can confirm the security exception and continue.
|
||||
echo
|
||||
|
||||
129
setup/system.sh
129
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,25 @@ if [ ! -f /usr/bin/add-apt-repository ]; then
|
||||
apt_install software-properties-common
|
||||
fi
|
||||
|
||||
hide_output add-apt-repository -y ppa:mail-in-a-box/ppa
|
||||
# Install the certbot PPA.
|
||||
hide_output add-apt-repository -y ppa:certbot/certbot
|
||||
|
||||
# ### Update Packages
|
||||
|
||||
# Update system packages to make sure we have the latest upstream versions of things from Ubuntu.
|
||||
# Update system packages to make sure we have the latest upstream versions
|
||||
# of things from Ubuntu, as well as the directory of packages provide by the
|
||||
# PPAs so we can install those packages later.
|
||||
|
||||
echo Updating system packages...
|
||||
hide_output apt-get update
|
||||
apt_get_quiet upgrade
|
||||
|
||||
# Old kernels pile up over time and take up a lot of disk space, and because of Mail-in-a-Box
|
||||
# changes there may be other packages that are no longer needed. Clear out anything apt knows
|
||||
# is safe to delete.
|
||||
|
||||
apt_get_quiet autoremove
|
||||
|
||||
# ### Install System Packages
|
||||
|
||||
# Install basic utilities.
|
||||
@@ -116,8 +125,16 @@ apt_get_quiet upgrade
|
||||
echo Installing system packages...
|
||||
apt_install python3 python3-dev python3-pip \
|
||||
netcat-openbsd wget curl git sudo coreutils bc \
|
||||
haveged pollinate \
|
||||
unattended-upgrades cron ntp fail2ban
|
||||
haveged pollinate unzip \
|
||||
unattended-upgrades cron ntp fail2ban rsyslog
|
||||
|
||||
# ### Suppress Upgrade Prompts
|
||||
# When Ubuntu 20 comes out, we don't want users to be prompted to upgrade,
|
||||
# because we don't yet support it.
|
||||
if [ -f /etc/update-manager/release-upgrades ]; then
|
||||
tools/editconf.py /etc/update-manager/release-upgrades Prompt=never
|
||||
rm -f /var/lib/ubuntu-release-upgrader/release-upgrade-available
|
||||
fi
|
||||
|
||||
# ### Set the system timezone
|
||||
#
|
||||
@@ -133,8 +150,8 @@ apt_install python3 python3-dev python3-pip \
|
||||
# section) and syslog (see #328). There might be other issues, and it's
|
||||
# not likely the user will want to change this, so we only ask on first
|
||||
# setup.
|
||||
if [ -z "$NONINTERACTIVE" ]; then
|
||||
if [ ! -f /etc/timezone ] || [ ! -z $FIRST_TIME_SETUP ]; then
|
||||
if [ -z "${NONINTERACTIVE:-}" ]; then
|
||||
if [ ! -f /etc/timezone ] || [ ! -z ${FIRST_TIME_SETUP:-} ]; then
|
||||
# If the file is missing or this is the user's first time running
|
||||
# Mail-in-a-Box setup, run the interactive timezone configuration
|
||||
# tool.
|
||||
@@ -160,7 +177,6 @@ fi
|
||||
# * DNSSEC signing keys (see `dns.sh`)
|
||||
# * our management server's API key (via Python's os.urandom method)
|
||||
# * Roundcube's SECRET_KEY (`webmail.sh`)
|
||||
# * ownCloud's administrator account password (`owncloud.sh`)
|
||||
#
|
||||
# Why /dev/urandom? It's the same as /dev/random, except that it doesn't wait
|
||||
# for a constant new stream of entropy. In practice, we only need a little
|
||||
@@ -208,6 +224,12 @@ pollinate -q -r
|
||||
|
||||
# Between these two, we really ought to be all set.
|
||||
|
||||
# We need an ssh key to store backups via rsync, if it doesn't exist create one
|
||||
if [ ! -f /root/.ssh/id_rsa_miab ]; then
|
||||
echo 'Creating SSH key for backup…'
|
||||
ssh-keygen -t rsa -b 2048 -a 100 -f /root/.ssh/id_rsa_miab -N '' -q
|
||||
fi
|
||||
|
||||
# ### Package maintenance
|
||||
#
|
||||
# Allow apt to install system updates automatically every day.
|
||||
@@ -216,7 +238,7 @@ cat > /etc/apt/apt.conf.d/02periodic <<EOF;
|
||||
APT::Periodic::MaxAge "7";
|
||||
APT::Periodic::Update-Package-Lists "1";
|
||||
APT::Periodic::Unattended-Upgrade "1";
|
||||
APT::Periodic::Verbose "1";
|
||||
APT::Periodic::Verbose "0";
|
||||
EOF
|
||||
|
||||
# ### Firewall
|
||||
@@ -224,7 +246,7 @@ EOF
|
||||
# Various virtualized environments like Docker and some VPSs don't provide #NODOC
|
||||
# a kernel that supports iptables. To avoid error-like output in these cases, #NODOC
|
||||
# we skip this if the user sets DISABLE_FIREWALL=1. #NODOC
|
||||
if [ -z "$DISABLE_FIREWALL" ]; then
|
||||
if [ -z "${DISABLE_FIREWALL:-}" ]; then
|
||||
# Install `ufw` which provides a simple firewall configuration.
|
||||
apt_install ufw
|
||||
|
||||
@@ -249,50 +271,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#" \
|
||||
|
||||
30
setup/web.sh
30
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
|
||||
|
||||
@@ -40,15 +41,19 @@ tools/editconf.py /etc/nginx/nginx.conf -s \
|
||||
server_names_hash_bucket_size="128;"
|
||||
|
||||
# Tell PHP not to expose its version number in the X-Powered-By header.
|
||||
tools/editconf.py /etc/php5/fpm/php.ini -c ';' \
|
||||
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
|
||||
expose_php=Off
|
||||
|
||||
# Set PHPs default charset to UTF-8, since we use it. See #367.
|
||||
tools/editconf.py /etc/php5/fpm/php.ini -c ';' \
|
||||
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
|
||||
default_charset="UTF-8"
|
||||
|
||||
# Switch from the dynamic process manager to the ondemand manager see #1216
|
||||
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
||||
pm=ondemand
|
||||
|
||||
# Bump up PHP's max_children to support more concurrent connections
|
||||
tools/editconf.py /etc/php5/fpm/pool.d/www.conf -c ';' \
|
||||
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
||||
pm.max_children=8
|
||||
|
||||
# Other nginx settings will be configured by the management service
|
||||
@@ -86,24 +91,9 @@ if [ ! -f $STORAGE_ROOT/www/default/index.html ]; then
|
||||
fi
|
||||
chown -R $STORAGE_USER $STORAGE_ROOT/www
|
||||
|
||||
# We previously installed a custom init script to start the PHP FastCGI daemon. #NODOC
|
||||
# Remove it now that we're using php5-fpm. #NODOC
|
||||
if [ -L /etc/init.d/php-fastcgi ]; then
|
||||
echo "Removing /etc/init.d/php-fastcgi, php5-cgi..." #NODOC
|
||||
rm -f /etc/init.d/php-fastcgi #NODOC
|
||||
hide_output update-rc.d php-fastcgi remove #NODOC
|
||||
apt-get -y purge php5-cgi #NODOC
|
||||
fi
|
||||
|
||||
# Remove obsoleted scripts. #NODOC
|
||||
# exchange-autodiscover is now handled by Z-Push. #NODOC
|
||||
for f in webfinger exchange-autodiscover; do #NODOC
|
||||
rm -f /usr/local/bin/mailinabox-$f.php #NODOC
|
||||
done #NODOC
|
||||
|
||||
# Start services.
|
||||
restart_service nginx
|
||||
restart_service php5-fpm
|
||||
restart_service php7.2-fpm
|
||||
|
||||
# Open ports.
|
||||
ufw_allow http
|
||||
|
||||
142
setup/webmail.sh
142
setup/webmail.sh
@@ -22,24 +22,26 @@ source /etc/mailinabox.conf # load global vars
|
||||
echo "Installing Roundcube (webmail)..."
|
||||
apt_install \
|
||||
dbconfig-common \
|
||||
php5 php5-sqlite php5-mcrypt php5-intl php5-json php5-common php-auth php-net-smtp php-net-socket php-net-sieve php-mail-mime php-crypt-gpg php5-gd php5-pspell \
|
||||
tinymce libjs-jquery libjs-jquery-mousewheel libmagic1
|
||||
apt_get_quiet remove php-mail-mimedecode # no longer needed since Roundcube 1.1.3
|
||||
|
||||
# We used to install Roundcube from Ubuntu, without triggering the dependencies #NODOC
|
||||
# on Apache and MySQL, by downloading the debs and installing them manually. #NODOC
|
||||
# Now that we're beyond that, get rid of those debs before installing from source. #NODOC
|
||||
apt-get purge -qq -y roundcube* #NODOC
|
||||
php-cli php-sqlite3 php-intl php-json php-common php-curl \
|
||||
php-gd php-pspell tinymce libjs-jquery libjs-jquery-mousewheel libmagic1 php-mbstring
|
||||
|
||||
# Install Roundcube from source if it is not already present or if it is out of date.
|
||||
# Combine the Roundcube version number with the commit hash of vacation_sieve to track
|
||||
# whether we have the latest version.
|
||||
VERSION=1.2.1
|
||||
HASH=81fbfba4683522f6e54006d0300a48e6da3f3bbd
|
||||
VACATION_SIEVE_VERSION=91ea6f52216390073d1f5b70b5f6bea0bfaee7e5
|
||||
PERSISTENT_LOGIN_VERSION=1e9d724476a370ce917a2fcd5b3217b0c306c24e
|
||||
# Combine the Roundcube version number with the commit hash of plugins to track
|
||||
# whether we have the latest version of everything.
|
||||
VERSION=1.3.8
|
||||
HASH=90c7900ccf7b2f46fe49c650d5adb9b85ee9cc22
|
||||
PERSISTENT_LOGIN_VERSION=dc5ca3d3f4415cc41edb2fde533c8a8628a94c76
|
||||
HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5
|
||||
UPDATE_KEY=$VERSION:$VACATION_SIEVE_VERSION:$PERSISTENT_LOGIN_VERSION:$HTML5_NOTIFIER_VERSION:a
|
||||
CARDDAV_VERSION=3.0.3
|
||||
CARDDAV_HASH=d1e3b0d851ffa2c6bd42bf0c04f70d0e1d0d78f8
|
||||
|
||||
UPDATE_KEY=$VERSION:$PERSISTENT_LOGIN_VERSION:$HTML5_NOTIFIER_VERSION:$CARDDAV_VERSION
|
||||
|
||||
# paths that are often reused.
|
||||
RCM_DIR=/usr/local/lib/roundcubemail
|
||||
RCM_PLUGIN_DIR=${RCM_DIR}/plugins
|
||||
RCM_CONFIG=${RCM_DIR}/config/config.inc.php
|
||||
|
||||
needs_update=0 #NODOC
|
||||
if [ ! -f /usr/local/lib/roundcubemail/version ]; then
|
||||
# not installed yet #NODOC
|
||||
@@ -51,25 +53,32 @@ fi
|
||||
if [ $needs_update == 1 ]; then
|
||||
# install roundcube
|
||||
wget_verify \
|
||||
https://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
|
||||
rm -rf /usr/local/lib/roundcubemail
|
||||
mv /usr/local/lib/roundcubemail-$VERSION/ /usr/local/lib/roundcubemail
|
||||
mv /usr/local/lib/roundcubemail-$VERSION/ $RCM_DIR
|
||||
rm -f /tmp/roundcube.tgz
|
||||
|
||||
# install roundcube autoreply/vacation plugin
|
||||
git_clone https://github.com/arodier/Roundcube-Plugins.git $VACATION_SIEVE_VERSION plugins/vacation_sieve /usr/local/lib/roundcubemail/plugins/vacation_sieve
|
||||
|
||||
# install roundcube persistent_login plugin
|
||||
git_clone https://github.com/mfreiholz/Roundcube-Persistent-Login-Plugin.git $PERSISTENT_LOGIN_VERSION '' /usr/local/lib/roundcubemail/plugins/persistent_login
|
||||
git_clone https://github.com/mfreiholz/Roundcube-Persistent-Login-Plugin.git $PERSISTENT_LOGIN_VERSION '' ${RCM_PLUGIN_DIR}/persistent_login
|
||||
|
||||
# install roundcube html5_notifier plugin
|
||||
git_clone https://github.com/kitist/html5_notifier.git $HTML5_NOTIFIER_VERSION '' /usr/local/lib/roundcubemail/plugins/html5_notifier
|
||||
git_clone https://github.com/kitist/html5_notifier.git $HTML5_NOTIFIER_VERSION '' ${RCM_PLUGIN_DIR}/html5_notifier
|
||||
|
||||
# download and verify the full release of the carddav plugin
|
||||
wget_verify \
|
||||
https://github.com/blind-coder/rcmcarddav/releases/download/v${CARDDAV_VERSION}/carddav-${CARDDAV_VERSION}.zip \
|
||||
$CARDDAV_HASH \
|
||||
/tmp/carddav.zip
|
||||
|
||||
# unzip and cleanup
|
||||
unzip -q /tmp/carddav.zip -d ${RCM_PLUGIN_DIR}
|
||||
rm -f /tmp/carddav.zip
|
||||
|
||||
# record the version we've installed
|
||||
echo $UPDATE_KEY > /usr/local/lib/roundcubemail/version
|
||||
echo $UPDATE_KEY > ${RCM_DIR}/version
|
||||
fi
|
||||
|
||||
# ### Configuring Roundcube
|
||||
@@ -82,68 +91,81 @@ SECRET_KEY=$(dd if=/dev/urandom bs=1 count=18 2>/dev/null | base64 | fold -w 24
|
||||
# For security, temp and log files are not stored in the default locations
|
||||
# which are inside the roundcube sources directory. We put them instead
|
||||
# in normal places.
|
||||
cat > /usr/local/lib/roundcubemail/config/config.inc.php <<EOF;
|
||||
cat > $RCM_CONFIG <<EOF;
|
||||
<?php
|
||||
/*
|
||||
* Do not edit. Written by Mail-in-a-Box. Regenerated on updates.
|
||||
*/
|
||||
\$config = array();
|
||||
\$config['log_dir'] = '/var/log/roundcubemail/';
|
||||
\$config['temp_dir'] = '/tmp/roundcubemail/';
|
||||
\$config['temp_dir'] = '/var/tmp/roundcubemail/';
|
||||
\$config['db_dsnw'] = 'sqlite:///$STORAGE_ROOT/mail/roundcube/roundcube.sqlite?mode=0640';
|
||||
\$config['default_host'] = 'ssl://localhost';
|
||||
\$config['default_port'] = 993;
|
||||
\$config['imap_conn_options'] = array(
|
||||
'ssl' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
),
|
||||
);
|
||||
\$config['imap_timeout'] = 15;
|
||||
\$config['smtp_server'] = 'tls://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');
|
||||
\$config['skin'] = 'classic';
|
||||
\$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'persistent_login', 'carddav');
|
||||
\$config['skin'] = 'larry';
|
||||
\$config['login_autocomplete'] = 2;
|
||||
\$config['password_charset'] = 'UTF-8';
|
||||
\$config['junk_mbox'] = 'Spam';
|
||||
?>
|
||||
EOF
|
||||
|
||||
# Configure vaction_sieve.
|
||||
cat > /usr/local/lib/roundcubemail/plugins/vacation_sieve/config.inc.php <<EOF;
|
||||
# Configure CardDav
|
||||
cat > ${RCM_PLUGIN_DIR}/carddav/config.inc.php <<EOF;
|
||||
<?php
|
||||
/* Do not edit. Written by Mail-in-a-Box. Regenerated on updates. */
|
||||
\$rcmail_config['vacation_sieve'] = array(
|
||||
'date_format' => 'd/m/Y',
|
||||
'working_hours' => array(8,18),
|
||||
'msg_format' => 'text',
|
||||
'logon_transform' => array('#([a-z])[a-z]+(\.|\s)([a-z])#i', '\$1\$3'),
|
||||
'transfer' => array(
|
||||
'mode' => 'managesieve',
|
||||
'ms_activate_script' => true,
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '4190',
|
||||
'usetls' => false,
|
||||
'path' => 'vacation',
|
||||
)
|
||||
\$prefs['_GLOBAL']['hide_preferences'] = true;
|
||||
\$prefs['_GLOBAL']['suppress_version_warning'] = true;
|
||||
\$prefs['ownCloud'] = array(
|
||||
'name' => 'ownCloud',
|
||||
'username' => '%u', // login username
|
||||
'password' => '%p', // login password
|
||||
'url' => 'https://${PRIMARY_HOSTNAME}/cloud/remote.php/carddav/addressbooks/%u/contacts',
|
||||
'active' => true,
|
||||
'readonly' => false,
|
||||
'refresh_time' => '02:00:00',
|
||||
'fixed' => array('username','password'),
|
||||
'preemptive_auth' => '1',
|
||||
'hide' => false,
|
||||
);
|
||||
?>
|
||||
EOF
|
||||
|
||||
# Create writable directories.
|
||||
mkdir -p /var/log/roundcubemail /tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
|
||||
chown -R www-data.www-data /var/log/roundcubemail /tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
|
||||
mkdir -p /var/log/roundcubemail /var/tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
|
||||
chown -R www-data.www-data /var/log/roundcubemail /var/tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
|
||||
|
||||
# Ensure the log file monitored by fail2ban exists, or else fail2ban can't start.
|
||||
sudo -u www-data touch /var/log/roundcubemail/errors
|
||||
|
||||
# Password changing plugin settings
|
||||
# The config comes empty by default, so we need the settings
|
||||
# The config comes empty by default, so we need the settings
|
||||
# we're not planning to change in config.inc.dist...
|
||||
cp /usr/local/lib/roundcubemail/plugins/password/config.inc.php.dist \
|
||||
/usr/local/lib/roundcubemail/plugins/password/config.inc.php
|
||||
cp ${RCM_PLUGIN_DIR}/password/config.inc.php.dist \
|
||||
${RCM_PLUGIN_DIR}/password/config.inc.php
|
||||
|
||||
tools/editconf.py /usr/local/lib/roundcubemail/plugins/password/config.inc.php \
|
||||
"\$config['password_minimum_length']=6;" \
|
||||
tools/editconf.py ${RCM_PLUGIN_DIR}/password/config.inc.php \
|
||||
"\$config['password_minimum_length']=8;" \
|
||||
"\$config['password_db_dsn']='sqlite:///$STORAGE_ROOT/mail/users.sqlite';" \
|
||||
"\$config['password_query']='UPDATE users SET password=%D WHERE email=%u';" \
|
||||
"\$config['password_dovecotpw']='/usr/bin/doveadm pw';" \
|
||||
@@ -157,15 +179,19 @@ usermod -a -G dovecot www-data
|
||||
# could use dovecot instead of www-data, but not sure it matters
|
||||
chown root.www-data $STORAGE_ROOT/mail
|
||||
chmod 775 $STORAGE_ROOT/mail
|
||||
chown root.www-data $STORAGE_ROOT/mail/users.sqlite
|
||||
chmod 664 $STORAGE_ROOT/mail/users.sqlite
|
||||
chown root.www-data $STORAGE_ROOT/mail/users.sqlite
|
||||
chmod 664 $STORAGE_ROOT/mail/users.sqlite
|
||||
|
||||
# Run Roundcube database migration script, if the database exists (it's created by
|
||||
# Roundcube on first use).
|
||||
if [ -f $STORAGE_ROOT/mail/roundcube/roundcube.sqlite ]; then
|
||||
/usr/local/lib/roundcubemail/bin/updatedb.sh --dir /usr/local/lib/roundcubemail/SQL --package roundcube
|
||||
fi
|
||||
# Fix Carddav permissions:
|
||||
chown -f -R root.www-data ${RCM_PLUGIN_DIR}/carddav
|
||||
# root.www-data need all permissions, others only read
|
||||
chmod -R 774 ${RCM_PLUGIN_DIR}/carddav
|
||||
|
||||
# Run Roundcube database migration script (database is created if it does not exist)
|
||||
${RCM_DIR}/bin/updatedb.sh --dir ${RCM_DIR}/SQL --package roundcube
|
||||
chown www-data:www-data $STORAGE_ROOT/mail/roundcube/roundcube.sqlite
|
||||
chmod 664 $STORAGE_ROOT/mail/roundcube/roundcube.sqlite
|
||||
|
||||
# Enable PHP modules.
|
||||
php5enmod mcrypt
|
||||
restart_service php5-fpm
|
||||
phpenmod -v php mcrypt imap
|
||||
restart_service php7.2-fpm
|
||||
|
||||
@@ -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.4.4
|
||||
TARGETHASH=104d44426852429dac8ec2783a4e9ad7752d4682
|
||||
needs_update=0 #NODOC
|
||||
if [ ! -f /usr/local/lib/z-push/version ]; then
|
||||
needs_update=1 #NODOC
|
||||
elif [[ $TARGETHASH != `cat /usr/local/lib/z-push/version` ]]; then
|
||||
elif [[ $VERSION != `cat /usr/local/lib/z-push/version` ]]; then
|
||||
# checks if the version
|
||||
needs_update=1 #NODOC
|
||||
fi
|
||||
if [ $needs_update == 1 ]; then
|
||||
git_clone https://github.com/fmbiete/Z-Push-contrib $TARGETHASH '' /usr/local/lib/z-push
|
||||
# Download
|
||||
wget_verify "https://stash.z-hub.io/rest/api/latest/projects/ZP/repos/z-push/archive?at=refs%2Ftags%2F$VERSION&format=zip" $TARGETHASH /tmp/z-push.zip
|
||||
|
||||
# Extract into place.
|
||||
rm -rf /usr/local/lib/z-push /tmp/z-push
|
||||
unzip -q /tmp/z-push.zip -d /tmp/z-push
|
||||
mv /tmp/z-push/src /usr/local/lib/z-push
|
||||
rm -rf /tmp/z-push.zip /tmp/z-push
|
||||
|
||||
rm -f /usr/sbin/z-push-{admin,top}
|
||||
ln -s /usr/local/lib/z-push/z-push-admin.php /usr/sbin/z-push-admin
|
||||
ln -s /usr/local/lib/z-push/z-push-top.php /usr/sbin/z-push-top
|
||||
echo $TARGETHASH > /usr/local/lib/z-push/version
|
||||
echo $VERSION > /usr/local/lib/z-push/version
|
||||
fi
|
||||
|
||||
# Configure default config.
|
||||
@@ -53,6 +62,7 @@ cp conf/zpush/backend_combined.php /usr/local/lib/z-push/backend/combined/config
|
||||
# Configure IMAP
|
||||
rm -f /usr/local/lib/z-push/backend/imap/config.php
|
||||
cp conf/zpush/backend_imap.php /usr/local/lib/z-push/backend/imap/config.php
|
||||
sed -i "s%STORAGE_ROOT%$STORAGE_ROOT%" /usr/local/lib/z-push/backend/imap/config.php
|
||||
|
||||
# Configure CardDav
|
||||
rm -f /usr/local/lib/z-push/backend/carddav/config.php
|
||||
@@ -66,6 +76,7 @@ cp conf/zpush/backend_caldav.php /usr/local/lib/z-push/backend/caldav/config.php
|
||||
rm -f /usr/local/lib/z-push/autodiscover/config.php
|
||||
cp conf/zpush/autodiscover_config.php /usr/local/lib/z-push/autodiscover/config.php
|
||||
sed -i "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" /usr/local/lib/z-push/autodiscover/config.php
|
||||
sed -i "s^define('TIMEZONE', .*^define('TIMEZONE', '$(cat /etc/timezone)');^" /usr/local/lib/z-push/autodiscover/config.php
|
||||
|
||||
# Some directories it will use.
|
||||
|
||||
@@ -91,4 +102,8 @@ EOF
|
||||
|
||||
# Restart service.
|
||||
|
||||
restart_service php5-fpm
|
||||
restart_service php7.2-fpm
|
||||
|
||||
# Fix states after upgrade
|
||||
|
||||
hide_output z-push-admin -a fixstates
|
||||
|
||||
@@ -10,11 +10,11 @@ import sys, os, time, functools
|
||||
|
||||
# parse command line
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: tests/fail2ban.py \"ssh user@hostname\" hostname")
|
||||
if len(sys.argv) != 4:
|
||||
print("Usage: tests/fail2ban.py \"ssh user@hostname\" hostname owncloud_user")
|
||||
sys.exit(1)
|
||||
|
||||
ssh_command, hostname = sys.argv[1:3]
|
||||
ssh_command, hostname, owncloud_user = sys.argv[1:4]
|
||||
|
||||
# define some test types
|
||||
|
||||
@@ -215,7 +215,7 @@ if __name__ == "__main__":
|
||||
run_test(http_test, ["/admin/munin/", 401], 20, 30, 1)
|
||||
|
||||
# ownCloud
|
||||
run_test(http_test, ["/cloud/remote.php/webdav", 401, None, None, ["aa", "aa"]], 20, 120, 1)
|
||||
run_test(http_test, ["/cloud/remote.php/webdav", 401, None, None, [owncloud_user, "aa"]], 20, 120, 1)
|
||||
|
||||
# restart fail2ban so that this client machine is no longer blocked
|
||||
restart_fail2ban_service(final=True)
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ def mgmt(cmd, data=None, is_json=False):
|
||||
def read_password():
|
||||
while True:
|
||||
first = getpass.getpass('password: ')
|
||||
if len(first) < 4:
|
||||
print("Passwords must be at least four characters.")
|
||||
if len(first) < 8:
|
||||
print("Passwords must be at least eight characters.")
|
||||
continue
|
||||
if re.search(r'[\s]', first):
|
||||
print("Passwords cannot contain spaces.")
|
||||
|
||||
49
tools/owncloud-restore.sh
Executable file
49
tools/owncloud-restore.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This script will restore the backup made during an installation
|
||||
source /etc/mailinabox.conf # load global vars
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: owncloud-restore.sh <backup directory>"
|
||||
echo
|
||||
echo "WARNING: This will restore the database to the point of the installation!"
|
||||
echo " This means that you will lose all changes made by users after that point"
|
||||
echo
|
||||
echo
|
||||
echo "Backups are stored here: $STORAGE_ROOT/owncloud-backup/"
|
||||
echo
|
||||
echo "Available backups:"
|
||||
echo
|
||||
find $STORAGE_ROOT/owncloud-backup/* -maxdepth 0 -type d
|
||||
echo
|
||||
echo "Supply the directory that was created during the last installation as the only commandline argument"
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ ! -f $1/config.php ]; then
|
||||
echo "This isn't a valid backup location"
|
||||
exit
|
||||
fi
|
||||
|
||||
echo "Restoring backup from $1"
|
||||
service php7.2-fpm stop
|
||||
|
||||
# remove the current ownCloud/Nextcloud installation
|
||||
rm -rf /usr/local/lib/owncloud/
|
||||
# restore the current ownCloud/Nextcloud application
|
||||
cp -r "$1/owncloud-install" /usr/local/lib/owncloud
|
||||
|
||||
# restore access rights
|
||||
chmod 750 /usr/local/lib/owncloud/{apps,config}
|
||||
|
||||
cp "$1/owncloud.db" $STORAGE_ROOT/owncloud/
|
||||
cp "$1/config.php" $STORAGE_ROOT/owncloud/
|
||||
|
||||
ln -sf $STORAGE_ROOT/owncloud/config.php /usr/local/lib/owncloud/config/config.php
|
||||
chown -f -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
|
||||
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
|
||||
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off
|
||||
|
||||
service php7.2-fpm start
|
||||
echo "Done"
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This script will give you administrative access to the ownCloud
|
||||
# This script will give you administrative access to the Nextcloud
|
||||
# instance running here.
|
||||
#
|
||||
# Run this at your own risk. This is for testing & experimentation
|
||||
@@ -13,11 +13,11 @@ test -z "$1" || ADMIN=$1
|
||||
|
||||
echo I am going to unlock admin features for $ADMIN.
|
||||
echo You can provide another user to unlock as the first argument of this script.
|
||||
echo
|
||||
echo WARNING: you could break mail-in-a-box when fiddling around with owncloud\'s admin interface
|
||||
echo
|
||||
echo WARNING: you could break mail-in-a-box when fiddling around with Nextcloud\'s admin interface
|
||||
echo If in doubt, press CTRL-C to cancel.
|
||||
echo
|
||||
echo Press enter to continue.
|
||||
read
|
||||
|
||||
sqlite3 $STORAGE_ROOT/owncloud/owncloud.db "INSERT OR IGNORE INTO oc_group_user VALUES ('admin', '$ADMIN')" && echo Done.
|
||||
sudo -u www-data php /usr/local/lib/owncloud/occ group:adduser admin $ADMIN && echo Done.
|
||||
|
||||
@@ -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