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

Compare commits

...

131 Commits

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

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

The vulnerability was created by 6d6f3ea391.

See #914.

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

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

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

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

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

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

was df92a10eba
2016-03-31 10:35:48 -04:00
Joshua Tauberer
aa1fdaddaf hotfix merge #755 - Prevent click jacking of the management interface 2016-03-31 10:34:52 -04:00
Joshua Tauberer
7fa9baf308 hotfix merge #744 - Fix for putty Line Drawing issues 2016-03-31 10:33:42 -04:00
Joshua Tauberer
36d51bbde0 merge #776 - some owncloud paths were improperly exposed over http 2016-03-31 10:20:21 -04:00
Joshua Tauberer
eb8cfaab75 changelog entry for html5_notifier bump 2016-03-31 10:20:13 -04:00
Tibor Blaho
c5e8a975cd Fix denied ownCloud nginx locations 2016-03-31 00:07:48 +02:00
Michael Kroes
3210ccdcac Don't set the hostname on the loopback 127.0.1.1 2016-03-26 15:41:20 +01:00
Joshua Tauberer
252c35c66e Merge pull request #772 from yodax/generic-login-message
Make control panel login failed messages generic - don't reveal if an email address has an account on the system.
2016-03-26 09:22:02 -04:00
Michael Kroes
c910a58f07 Set the hostname of the box during the setup 2016-03-26 14:15:28 +01:00
Michael Kroes
f292e8fc5b Add generic login failed message 2016-03-26 14:06:43 +01:00
Michael Kroes
4d7229ccb0 Add documentation on why the notification was removed from the recidive jail 2016-03-26 13:37:33 +01:00
Joshua Tauberer
1e1c3cbd00 Merge pull request #768 from yodax/web
Static web hosting, instructions weren't rendered
2016-03-26 07:46:49 -04:00
Joshua Tauberer
611e9cc84d merges #769 - update the Roundcube html5_notifier plugin from version 0.6 to 0.6.2
fixes Roundcube getting stuck for some people, hopefully fixes #693
2016-03-26 07:43:22 -04:00
Michael Kroes
454a2b167b Stop fail2ban recidive from sending emails, like all other jails 2016-03-26 09:04:51 +01:00
david
f6e0af124f updated html5_notifier version to 0.6.2 in setup 2016-03-25 20:16:51 +01:00
Michael Kroes
d7d8bda0a4 Instructions on how to create a web site for a domain weren't rendered. Users would miss the step about manually creating the directory to put files in there and wouldn't see anything happen 2016-03-25 13:37:55 +01:00
Joshua Tauberer
df92a10eba install roundcube from our own mirror, hosted in Josh's AWS S3 account, because sourceforge is down all the time
fixes #750, see #701, see #370
2016-03-23 17:31:24 -04:00
Joshua Tauberer
74a0359cec Merge pull request #763 from Neopallium/master
Fix creation of custom MX records.
2016-03-23 17:22:42 -04:00
Joshua Tauberer
336b95b3d5 Merge pull request #756 from yodax/preflight_arm
Add a preflight check for supported architecture
2016-03-23 17:19:21 -04:00
Joshua Tauberer
56591abbc2 merge #766 - Configure bayes_file_mode in spamassassin/local.cf 2016-03-23 17:17:30 -04:00
Joshua Tauberer
313a86d0fa add changelog entry for bayes file permissions 2016-03-23 17:16:50 -04:00
Joshua Tauberer
083e3cf755 merge #757 (squashed) - add swap space to low-memory systems 2016-03-23 17:07:40 -04:00
Michael Kroes
696bbe4e82 Add a swap file to the system if system memory is less than 2GB, 5GB of free disk space is available, and if no swap file yet exists 2016-03-23 17:07:04 -04:00
Joshua Tauberer
3d4cabbcd5 merge #755 - Prevent click jacking of the management interface 2016-03-23 16:53:48 -04:00
Joshua Tauberer
cdedaed3b0 merge #744 - Fix for putty Line Drawing issues 2016-03-23 16:51:01 -04:00
Joshua Tauberer
c01f903413 edit NCURSES_NO_UTF8_ACS's comment, add changelog entry 2016-03-23 16:50:27 -04:00
Joshua Tauberer
5edefbec27 merge #735 - Allow a server to be rebooted when a reboot is required 2016-03-23 16:39:40 -04:00
Joshua Tauberer
67555679bd move the reboot button, fix grammar, refactor check for DRY, add changelog entry 2016-03-23 16:37:15 -04:00
Joshua Tauberer
546d6f0026 merge #674 - Support munin's cgi dynazoom 2016-03-23 16:10:30 -04:00
Joshua Tauberer
bd86d44c8b simplify the munin_cgi wrapper / add changelog entry 2016-03-23 16:09:19 -04:00
Robert G. Jakabosky
72fcb005b2 Check MX priority. 2016-03-22 03:07:14 +08:00
Robert G. Jakabosky
84638ab11e Fix creation of custom MX records. 2016-03-21 21:12:08 +08:00
yodax
84f4509b48 Configure bayes_file_mode in spamassassin/local.cf 2016-03-20 05:55:58 +01:00
Michael Kroes
35a593af13 Improve preflight message 2016-03-14 07:14:09 +01:00
Michael Kroes
f69d6e9015 Add a preflight check for supported architecture 2016-03-14 07:00:33 +01:00
Michael Kroes
44705a32b7 Never allow admin panel to be inside a frame, use both modern and old headers. Also set no content sniffing 2016-03-13 18:40:02 +01:00
Michael Kroes
e343061cf4 Prevent clickjacking of management interface 2016-03-13 18:23:10 +01:00
Joshua Tauberer
65add24e2a Merge pull request #751 from yodax/wgetrc
Add a preflight check for ~/.wgetrc
2016-03-11 10:29:24 -05:00
Michael Kroes
33a9fb6aa2 Add a better message 2016-03-11 15:14:37 +01:00
Michael Kroes
0bc5d20e8f Add check for user overrides to wgetrc 2016-03-11 15:10:31 +01:00
Joshua Tauberer
49ea9cddd1 ssl_certificates: also forgot to catch free_tls_certificates.client.RateLimited 2016-03-06 14:39:34 -05:00
c0h1b4
6a48cdcdf3 Fix for putty Line Drawing issues
Fix for putty (Windows) Line Drawing characters to be shown correctly.
2016-03-01 10:40:39 -03:00
Joshua Tauberer
f78f039776 merge point release v0.17b
ownCloud moved their source code to a new location, breaking our installation script.
2016-03-01 07:24:06 -05:00
Joshua Tauberer
d881487d68 v0.17b 2016-03-01 07:23:20 -05:00
Joshua Tauberer
33d07b2b54 ownCloud moved their source code to a new location, breaking our installation script.
Fixes #741.
2016-03-01 07:23:16 -05:00
Joshua Tauberer
3bbec18ac6 Merge pull request #734 from yodax/dynamicpool
Create a temporary multiprocessing pool
2016-02-28 12:39:11 -05:00
Joshua Tauberer
fc5c198646 Merge pull request #728 from yodax/noexec
Add check to preflight for exec on tmp
2016-02-28 12:38:43 -05:00
Joshua Tauberer
2be373fd06 Merge pull request #727 from yodax/userlist
Allow files in /home/user-data/mail/mailboxes
2016-02-28 12:33:38 -05:00
Michael Kroes
b71ad85e9f Restore an empty line 2016-02-26 09:51:22 +01:00
Michael Kroes
86d3e9da86 Merge branch 'master' into reboot 2016-02-26 09:49:03 +01:00
yodax
f53d3bc390 Merge branch 'master' into dynamicpool 2016-02-26 09:20:23 +01:00
Michael Kroes
8ea2f5a766 Allow a server to be rebooted when a reboot is required 2016-02-25 21:56:27 +01:00
yodax
6c1357e16c Merge branch 'master' into dynamicpool 2016-02-23 17:01:13 +01:00
yodax
721730f0e8 Create a temporary multiprocessing pool 2016-02-23 06:32:01 +01:00
Michael Kroes
a7e60af93f Update comments 2016-02-21 12:47:09 -05:00
Michael Kroes
42f879687f Add check to preflight for exec on tmp 2016-02-21 12:43:04 -05:00
yodax
057903a303 Allow files in /home/user-data/mail/mailboxes 2016-02-21 13:49:07 +01:00
mike
6b408ef824 Use utils.shell instead of subprocess.Popen 2016-01-14 10:24:04 -05:00
mike
8932aaf4ef needed libcgi-fast-perl and chown log files 2016-01-13 23:55:45 -05:00
mike
6d6f3ea391 Added ability to use munin's dynazoom 2016-01-13 22:20:33 -05:00
45 changed files with 1170 additions and 316 deletions

View File

@@ -1,6 +1,113 @@
CHANGELOG CHANGELOG
========= =========
v0.19b (August 20, 2016)
------------------------
This update corrects a security issue introduced in v0.18.
A remote code execution vulnerability is corrected in how the munin system monitoring graphs are generated for the control panel. The vulnerability involves an administrative user visiting a carefully crafted URL.
v0.19a (August 18, 2016)
------------------------
This update corrects a security issue in v0.19.
* fail2ban won't start if Roundcube had not yet been used - new installations probably do not have fail2ban running.
v0.19 (August 13, 2016)
-----------------------
Mail:
* Roundcube is updated to version 1.2.1.
* SSLv3 and RC4 are now no longer supported in incoming and outgoing mail (SMTP port 25).
Control panel:
* The users and aliases APIs are now documented on their control panel pages.
* The HSTS header was missing.
* New status checks were added for the ufw firewall.
DNS:
* Add SRV records for CardDAV/CalDAV to facilitate autoconfiguration (e.g. in DavDroid, whose latest version didn't seem to work to configure with entering just a hostname).
System:
* fail2ban jails added for SMTP submission, Roundcube, ownCloud, the control panel, and munin.
* Mail-in-a-Box can now be installed on the i686 architecture.
v0.18c (June 2, 2016)
---------------------
* Domain aliases (and misconfigured aliases/catch-alls with non-existent local targets) would accept mail and deliver it to new mailbox folders on disk even if the target address didn't correspond with an existing mail user, instead of rejecting the mail. This issue was introduced in v0.18.
* The Munin Monitoring link in the control panel now opens a new window.
* Added an undocumented before-backup script.
v0.18b (May 16, 2016)
---------------------
* Fixed a Roundcube user accounts issue introduced in v0.18.
v0.18 (May 15, 2016)
--------------------
ownCloud:
* Updated to ownCloud to 8.2.3
Mail:
* Roundcube is updated to version 1.1.5 and the Roundcube login screen now says "[hostname] Webmail" instead of "Mail-in-a-Box/Roundcube webmail".
* Fixed a long-standing issue with training the spam filter not working (because of a file permissions issue).
Control panel:
* Munin system monitoring graphs are now zoomable.
* When a reboot is required (due to Ubuntu security updates automatically installed), a Reboot Box button now appears on the System Status Checks page of the control panel.
* It is now possible to add SRV and secondary MX records in the Custom DNS page.
* Other minor fixes.
System:
* The fail2ban recidive jail, which blocks long-duration brute force attacks, now no longer sends the administrator emails (which were not helpful).
Setup:
* The system hostname is now set during setup.
* A swap file is now created if system memory is less than 2GB, 5GB of free disk space is available, and if no swap file yet exists.
* We now install Roundcube from the official GitHub repository instead of our own mirror, which we had previously created to solve problems with SourceForge.
* DKIM was incorrectly set up on machines where "localhost" was defined as something other than "127.0.0.1".
v0.17c (April 1, 2016)
----------------------
This update addresses some minor security concerns and some installation issues.
ownCoud:
* Block web access to the configuration parameters (config.php). There is no immediate impact (see [#776](https://github.com/mail-in-a-box/mailinabox/pull/776)), although advanced users may want to take note.
Mail:
* Roundcube html5_notifier plugin updated from version 0.6 to 0.6.2 to fix Roundcube getting stuck for some people.
Control panel:
* Prevent click-jacking of the management interface by adding HTTP headers.
* Failed login no longer reveals whether an account exists on the system.
Setup:
* Setup dialogs did not appear correctly when connecting to SSH using Putty on Windows.
* We now install Roundcube from our own mirror because Sourceforge's downloads experience frequent intermittant unavailability.
v0.17b (March 1, 2016)
----------------------
ownCloud moved their source code to a new location, breaking our installation script.
v0.17 (February 25, 2016) v0.17 (February 25, 2016)
------------------------- -------------------------

View File

@@ -59,7 +59,7 @@ by me:
$ curl -s https://keybase.io/joshdata/key.asc | gpg --import $ curl -s https://keybase.io/joshdata/key.asc | gpg --import
gpg: key C10BDD81: public key "Joshua Tauberer <jt@occams.info>" imported gpg: key C10BDD81: public key "Joshua Tauberer <jt@occams.info>" imported
$ git verify-tag v0.17 $ git verify-tag v0.19b
gpg: Signature made ..... using RSA key ID C10BDD81 gpg: Signature made ..... using RSA key ID C10BDD81
gpg: Good signature from "Joshua Tauberer <jt@occams.info>" gpg: Good signature from "Joshua Tauberer <jt@occams.info>"
gpg: WARNING: This key is not certified with a trusted signature! gpg: WARNING: This key is not certified with a trusted signature!
@@ -72,7 +72,7 @@ and on my [personal homepage](https://razor.occams.info/). (Of course, if this r
Checkout the tag corresponding to the most recent release: Checkout the tag corresponding to the most recent release:
$ git checkout v0.17 $ git checkout v0.19b
Begin the installation. Begin the installation.

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
[INCLUDES]
before = common.conf
[Definition]
failregex=Login failed: .*Remote IP: '<HOST>[\)']
ignoreregex =

View File

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

View File

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

View File

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

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

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

View File

@@ -6,6 +6,10 @@
location /admin/ { location /admin/ {
proxy_pass http://127.0.0.1:10222/; proxy_pass http://127.0.0.1:10222/;
proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-For $remote_addr;
add_header X-Frame-Options "DENY";
add_header X-Content-Type-Options nosniff;
add_header Content-Security-Policy "frame-ancestors 'none';";
add_header Strict-Transport-Security max-age=31536000;
} }
# ownCloud configuration. # ownCloud configuration.
@@ -15,8 +19,11 @@
rewrite ^(/cloud/core/doc/[^\/]+/)$ $1/index.html; rewrite ^(/cloud/core/doc/[^\/]+/)$ $1/index.html;
location /cloud/ { location /cloud/ {
alias /usr/local/lib/owncloud/; alias /usr/local/lib/owncloud/;
location ~ ^/(data|config|\.ht|db_structure\.xml|README) { location ~ ^/cloud/(build|tests|config|lib|3rdparty|templates|data|README)/ {
deny all; deny all;
}
location ~ ^/cloud/(?:\.|autotest|occ|issue|indie|db_|console) {
deny all;
} }
} }
location ~ ^(/cloud)((?:/ocs)?/[^/]+\.php)(/.*)?$ { location ~ ^(/cloud)((?:/ocs)?/[^/]+\.php)(/.*)?$ {

View File

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

View File

@@ -7,7 +7,7 @@
define('CARDDAV_PROTOCOL', 'https'); /* http or https */ define('CARDDAV_PROTOCOL', 'https'); /* http or https */
define('CARDDAV_SERVER', 'localhost'); define('CARDDAV_SERVER', '127.0.0.1');
define('CARDDAV_PORT', '443'); define('CARDDAV_PORT', '443');
define('CARDDAV_PATH', '/carddav/addressbooks/%u/'); define('CARDDAV_PATH', '/carddav/addressbooks/%u/');
define('CARDDAV_DEFAULT_PATH', '/carddav/addressbooks/%u/contacts/'); /* subdirectory of the main path */ define('CARDDAV_DEFAULT_PATH', '/carddav/addressbooks/%u/contacts/'); /* subdirectory of the main path */

View File

@@ -5,7 +5,7 @@
* Descr : IMAP backend configuration file * Descr : IMAP backend configuration file
************************************************/ ************************************************/
define('IMAP_SERVER', 'localhost'); define('IMAP_SERVER', '127.0.0.1');
define('IMAP_PORT', 993); define('IMAP_PORT', 993);
define('IMAP_OPTIONS', '/ssl/norsh/novalidate-cert'); define('IMAP_OPTIONS', '/ssl/norsh/novalidate-cert');
define('IMAP_DEFAULTFROM', ''); define('IMAP_DEFAULTFROM', '');
@@ -44,7 +44,7 @@ define('IMAP_FROM_LDAP_FROM', '#givenname #sn <#mail>');
define('IMAP_SMTP_METHOD', 'sendmail'); define('IMAP_SMTP_METHOD', 'sendmail');
global $imap_smtp_params; global $imap_smtp_params;
$imap_smtp_params = array('host' => 'ssl://localhost', 'port' => 587, 'auth' => true, 'username' => 'imap_username', 'password' => 'imap_password'); $imap_smtp_params = array('host' => 'ssl://127.0.0.1', 'port' => 587, 'auth' => true, 'username' => 'imap_username', 'password' => 'imap_password');
define('MAIL_MIMEPART_CRLF', "\r\n"); define('MAIL_MIMEPART_CRLF', "\r\n");

View File

@@ -2,9 +2,10 @@
# This script performs a backup of all user data: # This script performs a backup of all user data:
# 1) System services are stopped. # 1) System services are stopped.
# 2) An incremental encrypted backup is made using duplicity. # 2) STORAGE_ROOT/backup/before-backup is executed if it exists.
# 3) The stopped services are restarted. # 3) An incremental encrypted backup is made using duplicity.
# 4) STORAGE_ROOT/backup/after-backup is executd if it exists. # 4) The stopped services are restarted.
# 5) STORAGE_ROOT/backup/after-backup is executed if it exists.
import os, os.path, shutil, glob, re, datetime, sys import os, os.path, shutil, glob, re, datetime, sys
import dateutil.parser, dateutil.relativedelta, dateutil.tz import dateutil.parser, dateutil.relativedelta, dateutil.tz
@@ -258,6 +259,15 @@ def perform_backup(full_backup):
service_command("postfix", "stop", quit=True) service_command("postfix", "stop", quit=True)
service_command("dovecot", "stop", quit=True) service_command("dovecot", "stop", quit=True)
# Execute a pre-backup script that copies files outside the homedir.
# Run as the STORAGE_USER user, not as root. Pass our settings in
# environment variables so the script has access to STORAGE_ROOT.
pre_script = os.path.join(backup_root, 'before-backup')
if os.path.exists(pre_script):
shell('check_call',
['su', env['STORAGE_USER'], '-c', pre_script, config["target"]],
env=env)
# Run a backup of STORAGE_ROOT (but excluding the backups themselves!). # Run a backup of STORAGE_ROOT (but excluding the backups themselves!).
# --allow-source-mismatch is needed in case the box's hostname is changed # --allow-source-mismatch is needed in case the box's hostname is changed
# after the first backup. See #396. # after the first backup. See #396.

View File

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

View File

@@ -175,9 +175,6 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
for value in build_sshfp_records(): for value in build_sshfp_records():
records.append((None, "SSHFP", value, "Optional. Provides an out-of-band method for verifying an SSH key before connecting. Use 'VerifyHostKeyDNS yes' (or 'VerifyHostKeyDNS ask') when connecting with ssh.")) records.append((None, "SSHFP", value, "Optional. Provides an out-of-band method for verifying an SSH key before connecting. Use 'VerifyHostKeyDNS yes' (or 'VerifyHostKeyDNS ask') when connecting with ssh."))
# The MX record says where email for the domain should be delivered: Here!
records.append((None, "MX", "10 %s." % env["PRIMARY_HOSTNAME"], "Required. Specifies the hostname (and priority) of the machine that handles @%s mail." % domain))
# Add DNS records for any subdomains of this domain. We should not have a zone for # Add DNS records for any subdomains of this domain. We should not have a zone for
# both a domain and one of its subdomains. # both a domain and one of its subdomains.
subdomains = [d for d in all_domains if d.endswith("." + domain)] subdomains = [d for d in all_domains if d.endswith("." + domain)]
@@ -244,6 +241,10 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
# Don't pin the list of records that has_rec checks against anymore. # Don't pin the list of records that has_rec checks against anymore.
has_rec_base = records has_rec_base = records
# The MX record says where email for the domain should be delivered: Here!
if not has_rec(None, "MX", prefix="10 "):
records.append((None, "MX", "10 %s." % env["PRIMARY_HOSTNAME"], "Required. Specifies the hostname (and priority) of the machine that handles @%s mail." % domain))
# SPF record: Permit the box ('mx', see above) to send mail on behalf of # SPF record: Permit the box ('mx', see above) to send mail on behalf of
# the domain, and no one else. # the domain, and no one else.
# Skip if the user has set a custom SPF record. # Skip if the user has set a custom SPF record.
@@ -273,6 +274,13 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
if not has_rec(dmarc_qname, "TXT", prefix="v=DMARC1; "): if not has_rec(dmarc_qname, "TXT", prefix="v=DMARC1; "):
records.append((dmarc_qname, "TXT", 'v=DMARC1; p=reject', "Recommended. Prevents use of this domain name for outbound mail by specifying that the SPF rule should be honoured for mail from @%s." % (qname + "." + domain))) records.append((dmarc_qname, "TXT", 'v=DMARC1; p=reject', "Recommended. Prevents use of this domain name for outbound mail by specifying that the SPF rule should be honoured for mail from @%s." % (qname + "." + domain)))
# Add CardDAV/CalDAV SRV records on the non-primary hostname that points to the primary hostname.
# The SRV record format is priority (0, whatever), weight (0, whatever), port, service provider hostname (w/ trailing dot).
if domain != env["PRIMARY_HOSTNAME"]:
for dav in ("card", "cal"):
qname = "_" + dav + "davs._tcp"
if not has_rec(qname, "SRV"):
records.append((qname, "SRV", "0 0 443 " + env["PRIMARY_HOSTNAME"] + ".", "Recommended. Specifies the hostname of the server that handles CardDAV/CalDAV services for email addresses on this domain."))
# Sort the records. The None records *must* go first in the nsd zone file. Otherwise it doesn't matter. # Sort the records. The None records *must* go first in the nsd zone file. Otherwise it doesn't matter.
records.sort(key = lambda rec : list(reversed(rec[0].split(".")) if rec[0] is not None else "")) records.sort(key = lambda rec : list(reversed(rec[0].split(".")) if rec[0] is not None else ""))

View File

@@ -33,7 +33,7 @@ msg['Subject'] = "[%s] %s" % (env['PRIMARY_HOSTNAME'], subject)
msg.set_payload(content, "UTF-8") msg.set_payload(content, "UTF-8")
# send # send
smtpclient = smtplib.SMTP('localhost', 25) smtpclient = smtplib.SMTP('127.0.0.1', 25)
smtpclient.ehlo() smtpclient.ehlo()
smtpclient.sendmail( smtpclient.sendmail(
admin_addr, # MAIL FROM admin_addr, # MAIL FROM

View File

@@ -1,136 +1,211 @@
#!/usr/bin/python3 #!/usr/bin/python3
import os.path
import re
from collections import defaultdict from collections import defaultdict
import re, os.path
import dateutil.parser import dateutil.parser
import mailconfig import mailconfig
import utils import utils
def scan_mail_log(logger, env): def scan_mail_log(logger, env):
collector = { """ Scan the system's mail log files and collect interesting data
"other-services": set(),
"imap-logins": { },
"postgrey": { },
"rejected-mail": { },
"activity-by-hour": { "imap-logins": defaultdict(int), "smtp-sends": defaultdict(int) },
}
collector["real_mail_addresses"] = set(mailconfig.get_mail_users(env)) | set(alias[0] for alias in mailconfig.get_mail_aliases(env)) This function scans the 2 most recent mail log files in /var/log/.
for fn in ('/var/log/mail.log.1', '/var/log/mail.log'): Args:
if not os.path.exists(fn): continue logger (ConsoleOutput): Object used for writing messages to the console
with open(fn, 'rb') as log: env (dict): Dictionary containing MiaB settings
for line in log: """
line = line.decode("utf8", errors='replace')
scan_mail_log_line(line.strip(), collector)
if collector["imap-logins"]: collector = {
logger.add_heading("Recent IMAP Logins") "other-services": set(),
logger.print_block("The most recent login from each remote IP adddress is show.") "imap-logins": {},
for k in utils.sort_email_addresses(collector["imap-logins"], env): "pop3-logins": {},
for ip, date in sorted(collector["imap-logins"][k].items(), key = lambda kv : kv[1]): "postgrey": {},
logger.print_line(k + "\t" + str(date) + "\t" + ip) "rejected-mail": {},
"activity-by-hour": {
"imap-logins": defaultdict(int),
"pop3-logins": defaultdict(int),
"smtp-sends": defaultdict(int),
"smtp-receives": defaultdict(int),
},
"real_mail_addresses": (
set(mailconfig.get_mail_users(env)) | set(alias[0] for alias in mailconfig.get_mail_aliases(env))
)
}
if collector["postgrey"]: for fn in ('/var/log/mail.log.1', '/var/log/mail.log'):
logger.add_heading("Greylisted Mail") if not os.path.exists(fn):
logger.print_block("The following mail was greylisted, meaning the emails were temporarily rejected. Legitimate senders will try again within ten minutes.") continue
logger.print_line("recipient" + "\t" + "received" + "\t" + "sender" + "\t" + "delivered") with open(fn, 'rb') as log:
for recipient in utils.sort_email_addresses(collector["postgrey"], env): for line in log:
for (client_address, sender), (first_date, delivered_date) in sorted(collector["postgrey"][recipient].items(), key = lambda kv : kv[1][0]): line = line.decode("utf8", errors='replace')
logger.print_line(recipient + "\t" + str(first_date) + "\t" + sender + "\t" + (("delivered " + str(delivered_date)) if delivered_date else "no retry yet")) scan_mail_log_line(line.strip(), collector)
if collector["rejected-mail"]: if collector["imap-logins"]:
logger.add_heading("Rejected Mail") logger.add_heading("Recent IMAP Logins")
logger.print_block("The following incoming mail was rejected.") logger.print_block("The most recent login from each remote IP adddress is shown.")
for k in utils.sort_email_addresses(collector["rejected-mail"], env): for k in utils.sort_email_addresses(collector["imap-logins"], env):
for date, sender, message in collector["rejected-mail"][k]: for ip, date in sorted(collector["imap-logins"][k].items(), key=lambda kv: kv[1]):
logger.print_line(k + "\t" + str(date) + "\t" + sender + "\t" + message) logger.print_line(k + "\t" + str(date) + "\t" + ip)
logger.add_heading("Activity by Hour") if collector["pop3-logins"]:
for h in range(24): logger.add_heading("Recent POP3 Logins")
logger.print_line("%d\t%d\t%d" % (h, collector["activity-by-hour"]["imap-logins"][h], collector["activity-by-hour"]["smtp-sends"][h] )) logger.print_block("The most recent login from each remote IP adddress is shown.")
for k in utils.sort_email_addresses(collector["pop3-logins"], env):
for ip, date in sorted(collector["pop3-logins"][k].items(), key=lambda kv: kv[1]):
logger.print_line(k + "\t" + str(date) + "\t" + ip)
if collector["postgrey"]:
logger.add_heading("Greylisted Mail")
logger.print_block("The following mail was greylisted, meaning the emails were temporarily rejected. "
"Legitimate senders will try again within ten minutes.")
logger.print_line("recipient" + "\t" + "received" + 3 * "\t" + "sender" + 6 * "\t" + "delivered")
for recipient in utils.sort_email_addresses(collector["postgrey"], env):
sorted_recipients = sorted(collector["postgrey"][recipient].items(), key=lambda kv: kv[1][0])
for (client_address, sender), (first_date, delivered_date) in sorted_recipients:
logger.print_line(
recipient + "\t" + str(first_date) + "\t" + sender + "\t" +
(("delivered " + str(delivered_date)) if delivered_date else "no retry yet")
)
if collector["rejected-mail"]:
logger.add_heading("Rejected Mail")
logger.print_block("The following incoming mail was rejected.")
for k in utils.sort_email_addresses(collector["rejected-mail"], env):
for date, sender, message in collector["rejected-mail"][k]:
logger.print_line(k + "\t" + str(date) + "\t" + sender + "\t" + message)
logger.add_heading("Activity by Hour")
logger.print_block("Dovecot logins and Postfix mail traffic per hour.")
logger.print_block("Hour\tIMAP\tPOP3\tSent\tReceived")
for h in range(24):
logger.print_line(
"%d\t%d\t\t%d\t\t%d\t\t%d" % (
h,
collector["activity-by-hour"]["imap-logins"][h],
collector["activity-by-hour"]["pop3-logins"][h],
collector["activity-by-hour"]["smtp-sends"][h],
collector["activity-by-hour"]["smtp-receives"][h],
)
)
if len(collector["other-services"]) > 0:
logger.add_heading("Other")
logger.print_block("Unrecognized services in the log: " + ", ".join(collector["other-services"]))
if len(collector["other-services"]) > 0:
logger.add_heading("Other")
logger.print_block("Unrecognized services in the log: " + ", ".join(collector["other-services"]))
def scan_mail_log_line(line, collector): def scan_mail_log_line(line, collector):
m = re.match(r"(\S+ \d+ \d+:\d+:\d+) (\S+) (\S+?)(\[\d+\])?: (.*)", line) """ Scan a log line and extract interesting data """
if not m: return
date, system, service, pid, log = m.groups() m = re.match(r"(\S+ \d+ \d+:\d+:\d+) (\S+) (\S+?)(\[\d+\])?: (.*)", line)
date = dateutil.parser.parse(date)
if service == "dovecot": if not m:
scan_dovecot_line(date, log, collector) return
elif service == "postgrey": date, system, service, pid, log = m.groups()
scan_postgrey_line(date, log, collector) date = dateutil.parser.parse(date)
elif service == "postfix/smtpd": if service == "dovecot":
scan_postfix_smtpd_line(date, log, collector) scan_dovecot_line(date, log, collector)
elif service == "postgrey":
scan_postgrey_line(date, log, collector)
elif service == "postfix/smtpd":
scan_postfix_smtpd_line(date, log, collector)
elif service == "postfix/cleanup":
scan_postfix_cleanup_line(date, log, collector)
elif service == "postfix/submission/smtpd":
scan_postfix_submission_line(date, log, collector)
elif service in ("postfix/qmgr", "postfix/pickup", "postfix/cleanup", "postfix/scache", "spampd", "postfix/anvil",
"postfix/master", "opendkim", "postfix/lmtp", "postfix/tlsmgr"):
# nothing to look at
pass
else:
collector["other-services"].add(service)
elif service == "postfix/submission/smtpd":
scan_postfix_submission_line(date, log, collector)
elif service in ("postfix/qmgr", "postfix/pickup", "postfix/cleanup", def scan_dovecot_line(date, line, collector):
"postfix/scache", "spampd", "postfix/anvil", "postfix/master", """ Scan a dovecot log line and extract interesting data """
"opendkim", "postfix/lmtp", "postfix/tlsmgr"):
# nothing to look at
pass
else: m = re.match("(imap|pop3)-login: Login: user=<(.*?)>, method=PLAIN, rip=(.*?),", line)
collector["other-services"].add(service)
if m:
prot, login, ip = m.group(1), m.group(2), m.group(3)
logins_key = "%s-logins" % prot
if ip != "127.0.0.1": # local login from webmail/zpush
collector[logins_key].setdefault(login, {})[ip] = date
collector["activity-by-hour"][logins_key][date.hour] += 1
def scan_dovecot_line(date, log, collector):
m = re.match("imap-login: Login: user=<(.*?)>, method=PLAIN, rip=(.*?),", log)
if m:
login, ip = m.group(1), m.group(2)
if ip != "127.0.0.1": # local login from webmail/zpush
collector["imap-logins"].setdefault(login, {})[ip] = date
collector["activity-by-hour"]["imap-logins"][date.hour] += 1
def scan_postgrey_line(date, log, collector): def scan_postgrey_line(date, log, collector):
m = re.match("action=(greylist|pass), reason=(.*?), (?:delay=\d+, )?client_name=(.*), client_address=(.*), sender=(.*), recipient=(.*)", log) """ Scan a postgrey log line and extract interesting data """
if m:
action, reason, client_name, client_address, sender, recipient = m.groups() m = re.match("action=(greylist|pass), reason=(.*?), (?:delay=\d+, )?client_name=(.*), client_address=(.*), "
key = (client_address, sender) "sender=(.*), recipient=(.*)",
if action == "greylist" and reason == "new": log)
collector["postgrey"].setdefault(recipient, {})[key] = (date, None)
elif action == "pass" and reason == "triplet found" and key in collector["postgrey"].get(recipient, {}): if m:
collector["postgrey"][recipient][key] = (collector["postgrey"][recipient][key][0], date) action, reason, client_name, client_address, sender, recipient = m.groups()
key = (client_address, sender)
if action == "greylist" and reason == "new":
collector["postgrey"].setdefault(recipient, {})[key] = (date, None)
elif action == "pass" and reason == "triplet found" and key in collector["postgrey"].get(recipient, {}):
collector["postgrey"][recipient][key] = (collector["postgrey"][recipient][key][0], date)
def scan_postfix_smtpd_line(date, log, collector): def scan_postfix_smtpd_line(date, log, collector):
m = re.match("NOQUEUE: reject: RCPT from .*?: (.*?); from=<(.*?)> to=<(.*?)>", log) """ Scan a postfix smtpd log line and extract interesting data """
if m:
message, sender, recipient = m.groups()
if recipient in collector["real_mail_addresses"]:
# only log mail to real recipients
# skip this, is reported in the greylisting report # Check if the incomming mail was rejected
if "Recipient address rejected: Greylisted" in message:
return
# simplify this one m = re.match("NOQUEUE: reject: RCPT from .*?: (.*?); from=<(.*?)> to=<(.*?)>", log)
m = re.search(r"Client host \[(.*?)\] blocked using zen.spamhaus.org; (.*)", message)
if m:
message = "ip blocked: " + m.group(2)
# simplify this one too if m:
m = re.search(r"Sender address \[.*@(.*)\] blocked using dbl.spamhaus.org; (.*)", message) message, sender, recipient = m.groups()
if m: if recipient in collector["real_mail_addresses"]:
message = "domain blocked: " + m.group(2) # only log mail to real recipients
collector["rejected-mail"].setdefault(recipient, []).append( (date, sender, message) ) # skip this, if reported in the greylisting report
if "Recipient address rejected: Greylisted" in message:
return
# simplify this one
m = re.search(r"Client host \[(.*?)\] blocked using zen.spamhaus.org; (.*)", message)
if m:
message = "ip blocked: " + m.group(2)
# simplify this one too
m = re.search(r"Sender address \[.*@(.*)\] blocked using dbl.spamhaus.org; (.*)", message)
if m:
message = "domain blocked: " + m.group(2)
collector["rejected-mail"].setdefault(recipient, []).append((date, sender, message))
def scan_postfix_cleanup_line(date, _, collector):
""" Scan a postfix cleanup log line and extract interesting data
It is assumed that every log of postfix/cleanup indicates an email that was successfulfy received by Postfix.
"""
collector["activity-by-hour"]["smtp-receives"][date.hour] += 1
def scan_postfix_submission_line(date, log, collector): def scan_postfix_submission_line(date, log, collector):
m = re.match("([A-Z0-9]+): client=(\S+), sasl_method=PLAIN, sasl_username=(\S+)", log) """ Scan a postfix submission log line and extract interesting data """
if m:
procid, client, user = m.groups() m = re.match("([A-Z0-9]+): client=(\S+), sasl_method=PLAIN, sasl_username=(\S+)", log)
collector["activity-by-hour"]["smtp-sends"][date.hour] += 1
if m:
# procid, client, user = m.groups()
collector["activity-by-hour"]["smtp-sends"][date.hour] += 1
if __name__ == "__main__": if __name__ == "__main__":
from status_checks import ConsoleOutput from status_checks import ConsoleOutput
env = utils.load_environment()
scan_mail_log(ConsoleOutput(), env) env_vars = utils.load_environment()
scan_mail_log(ConsoleOutput(), env_vars)

View File

@@ -137,19 +137,20 @@ def get_mail_users_ex(env, with_archived=False, with_slow_info=False):
if with_archived: if with_archived:
root = os.path.join(env['STORAGE_ROOT'], 'mail/mailboxes') root = os.path.join(env['STORAGE_ROOT'], 'mail/mailboxes')
for domain in os.listdir(root): for domain in os.listdir(root):
for user in os.listdir(os.path.join(root, domain)): if os.path.isdir(os.path.join(root, domain)):
email = user + "@" + domain for user in os.listdir(os.path.join(root, domain)):
mbox = os.path.join(root, domain, user) email = user + "@" + domain
if email in active_accounts: continue mbox = os.path.join(root, domain, user)
user = { if email in active_accounts: continue
"email": email, user = {
"privileges": "", "email": email,
"status": "inactive", "privileges": "",
"mailbox": mbox, "status": "inactive",
} "mailbox": mbox,
users.append(user) }
if with_slow_info: users.append(user)
user["mailbox_size"] = utils.du(mbox) if with_slow_info:
user["mailbox_size"] = utils.du(mbox)
# Group by domain. # Group by domain.
domains = { } domains = { }

View File

@@ -365,7 +365,7 @@ def provision_certificates(env, agree_to_tos_url=None, logger=None, show_extende
"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 + ".", "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 + ".",
}) })
except (client.InvalidDomainName, client.NeedToTakeAction, client.ChallengeFailed, acme.messages.Error, requests.exceptions.RequestException) as e: except (client.InvalidDomainName, client.NeedToTakeAction, client.ChallengeFailed, client.RateLimited, acme.messages.Error, requests.exceptions.RequestException) as e:
ret_item.update({ ret_item.update({
"result": "error", "result": "error",
"message": "Something unexpected went wrong: " + str(e), "message": "Something unexpected went wrong: " + str(e),

View File

@@ -18,6 +18,29 @@ from mailconfig import get_mail_domains, get_mail_aliases
from utils import shell, sort_domains, load_env_vars_from_file, load_settings from utils import shell, sort_domains, load_env_vars_from_file, load_settings
def get_services():
return [
{ "name": "Local DNS (bind9)", "port": 53, "public": False, },
#{ "name": "NSD Control", "port": 8952, "public": False, },
{ "name": "Local DNS Control (bind9/rndc)", "port": 953, "public": False, },
{ "name": "Dovecot LMTP LDA", "port": 10026, "public": False, },
{ "name": "Postgrey", "port": 10023, "public": False, },
{ "name": "Spamassassin", "port": 10025, "public": False, },
{ "name": "OpenDKIM", "port": 8891, "public": False, },
{ "name": "OpenDMARC", "port": 8893, "public": False, },
{ "name": "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, },
{ "name": "Incoming Mail (SMTP/postfix)", "port": 25, "public": True, },
{ "name": "Outgoing Mail (SMTP 587/postfix)", "port": 587, "public": True, },
#{ "name": "Postfix/master", "port": 10587, "public": True, },
{ "name": "IMAPS (dovecot)", "port": 993, "public": True, },
{ "name": "Mail Filters (Sieve/dovecot)", "port": 4190, "public": True, },
{ "name": "HTTP Web (nginx)", "port": 80, "public": True, },
{ "name": "HTTPS Web (nginx)", "port": 443, "public": True, },
]
def run_checks(rounded_values, env, output, pool): def run_checks(rounded_values, env, output, pool):
# run systems checks # run systems checks
output.add_heading("System") output.add_heading("System")
@@ -61,33 +84,9 @@ def get_ssh_port():
def run_services_checks(env, output, pool): def run_services_checks(env, output, pool):
# Check that system services are running. # Check that system services are running.
services = [
{ "name": "Local DNS (bind9)", "port": 53, "public": False, },
#{ "name": "NSD Control", "port": 8952, "public": False, },
{ "name": "Local DNS Control (bind9/rndc)", "port": 953, "public": False, },
{ "name": "Dovecot LMTP LDA", "port": 10026, "public": False, },
{ "name": "Postgrey", "port": 10023, "public": False, },
{ "name": "Spamassassin", "port": 10025, "public": False, },
{ "name": "OpenDKIM", "port": 8891, "public": False, },
{ "name": "OpenDMARC", "port": 8893, "public": False, },
{ "name": "Memcached", "port": 11211, "public": False, },
{ "name": "Mail-in-a-Box Management Daemon", "port": 10222, "public": False, },
{ "name": "SSH Login (ssh)", "port": get_ssh_port(), "public": True, },
{ "name": "Public DNS (nsd4)", "port": 53, "public": True, },
{ "name": "Incoming Mail (SMTP/postfix)", "port": 25, "public": True, },
{ "name": "Outgoing Mail (SMTP 587/postfix)", "port": 587, "public": True, },
#{ "name": "Postfix/master", "port": 10587, "public": True, },
{ "name": "IMAPS (dovecot)", "port": 993, "public": True, },
{ "name": "Mail Filters (Sieve/dovecot)", "port": 4190, "public": True, },
{ "name": "HTTP Web (nginx)", "port": 80, "public": True, },
{ "name": "HTTPS Web (nginx)", "port": 443, "public": True, },
]
all_running = True all_running = True
fatal = False fatal = False
ret = pool.starmap(check_service, ((i, service, env) for i, service in enumerate(services)), chunksize=1) ret = pool.starmap(check_service, ((i, service, env) for i, service in enumerate(get_services())), chunksize=1)
for i, running, fatal2, output2 in sorted(ret): for i, running, fatal2, output2 in sorted(ret):
if output2 is None: continue # skip check (e.g. no port was set, e.g. no sshd) if output2 is None: continue # skip check (e.g. no port was set, e.g. no sshd)
all_running = all_running and running all_running = all_running and running
@@ -169,6 +168,26 @@ def run_system_checks(rounded_values, env, output):
check_free_disk_space(rounded_values, env, output) check_free_disk_space(rounded_values, env, output)
check_free_memory(rounded_values, env, output) check_free_memory(rounded_values, env, output)
def check_ufw(env, output):
ufw = shell('check_output', ['ufw', 'status']).splitlines()
if ufw[0] == "Status: active":
not_allowed_ports = 0
for service in get_services():
if service["public"] and not is_port_allowed(ufw, service["port"]):
not_allowed_ports += 1
output.print_error("Port %s (%s) should be allowed in the firewall, please re-run the setup." % (service["port"], service["name"]))
if not_allowed_ports == 0:
output.print_ok("Firewall is active.")
else:
output.print_warning("""The firewall is disabled on this machine. This might be because the system
is protected by an external firewall. We can't protect the system against bruteforce attacks
without the local firewall active. Connect to the system via ssh and try to run: ufw enable.""")
def is_port_allowed(ufw, port):
return any(re.match(str(port) +"[/ \t].*", item) for item in ufw)
def check_ssh_password(env, output): def check_ssh_password(env, output):
# Check that SSH login with password is disabled. The openssh-server # Check that SSH login with password is disabled. The openssh-server
# package may not be installed so check that before trying to access # package may not be installed so check that before trying to access
@@ -185,10 +204,13 @@ def check_ssh_password(env, output):
else: else:
output.print_ok("SSH disallows password-based login.") output.print_ok("SSH disallows password-based login.")
def is_reboot_needed_due_to_package_installation():
return os.path.exists("/var/run/reboot-required")
def check_software_updates(env, output): def check_software_updates(env, output):
# Check for any software package updates. # Check for any software package updates.
pkgs = list_apt_updates(apt_update=False) pkgs = list_apt_updates(apt_update=False)
if os.path.exists("/var/run/reboot-required"): if is_reboot_needed_due_to_package_installation():
output.print_error("System updates have been installed and a reboot of the machine is required.") output.print_error("System updates have been installed and a reboot of the machine is required.")
elif len(pkgs) == 0: elif len(pkgs) == 0:
output.print_ok("System software is up to date.") output.print_ok("System software is up to date.")
@@ -237,6 +259,8 @@ def run_network_checks(env, output):
output.add_heading("Network") output.add_heading("Network")
check_ufw(env, output)
# Stop if we cannot make an outbound connection on port 25. Many residential # Stop if we cannot make an outbound connection on port 25. Many residential
# networks block outbound port 25 to prevent their network from sending spam. # networks block outbound port 25 to prevent their network from sending spam.
# See if we can reach one of Google's MTAs with a 5-second timeout. # See if we can reach one of Google's MTAs with a 5-second timeout.

View File

@@ -106,6 +106,41 @@
</table> </table>
</div> </div>
<h3>Mail aliases API (advanced)</h3>
<p>Use your box&rsquo;s mail aliases API to add and remove mail aliases from the command-line or custom services you build.</p>
<p>Usage:</p>
<pre>curl -X <b>VERB</b> [-d "<b>parameters</b>"] --user {email}:{password} https://{{hostname}}/admin/mail/aliases[<b>action</b>]</pre>
<p>Brackets denote an optional argument. Please note that the POST body <code>parameters</code> must be URL-encoded.</p>
<p>The email and password given to the <code>--user</code> option must be an administrative user on this system.</p>
<h4 style="margin-bottom: 0">Verbs</h4>
<table class="table" style="margin-top: .5em">
<thead><th>Verb</th> <th>Action</th><th></th></thead>
<tr><td>GET</td><td><i>(none)</i></td> <td>Returns a list of existing mail aliases. Adding <code>?format=json</code> to the URL will give JSON-encoded results.</td></tr>
<tr><td>POST</td><td>/add</td> <td>Adds a new mail alias. Required POST-body parameters are <code>address</code> and <code>forward_to</code>.</td></tr>
<tr><td>POST</td><td>/remove</td> <td>Removes a mail alias. Required POST-body parameter is <code>address</code>.</td></tr>
</table>
<h4>Examples:</h4>
<p>Try these examples. For simplicity the examples omit the <code>--user me@mydomain.com:yourpassword</code> command line argument which you must fill in with your email address and password.</p>
<pre># Gives a JSON-encoded list of all mail aliases
curl -X GET https://{{hostname}}/admin/mail/aliases?format=json
# Adds a new alias
curl -X POST -d "address=new_alias@mydomail.com" -d "forward_to=my_email@mydomain.com" https://{{hostname}}/admin/mail/aliases/add
# Removes an alias
curl -X POST -d "address=new_alias@mydomail.com" https://{{hostname}}/admin/mail/aliases/remove
</pre>
<script> <script>
function show_aliases() { function show_aliases() {

View File

@@ -10,7 +10,7 @@
<p>It is possible to set custom DNS records on domains hosted here.</p> <p>It is possible to set custom DNS records on domains hosted here.</p>
<h3>Set Custom DNS Records</h3> <h3>Set custom DNS records</h3>
<p>You can set additional DNS records, such as if you have a website running on another server, to add DKIM records for external mail providers, or for various confirmation-of-ownership tests.</p> <p>You can set additional DNS records, such as if you have a website running on another server, to add DKIM records for external mail providers, or for various confirmation-of-ownership tests.</p>
@@ -36,6 +36,7 @@
<option value="CNAME" data-hint="Enter another domain name followed by a period at the end (e.g. mypage.github.io.).">CNAME (DNS forwarding)</option> <option value="CNAME" data-hint="Enter another domain name followed by a period at the end (e.g. mypage.github.io.).">CNAME (DNS forwarding)</option>
<option value="TXT" data-hint="Enter arbitrary text.">TXT (text record)</option> <option value="TXT" data-hint="Enter arbitrary text.">TXT (text record)</option>
<option value="MX" data-hint="Enter record in the form of PRIORIY DOMAIN., including trailing period (e.g. 20 mx.example.com.).">MX (mail exchanger)</option> <option value="MX" data-hint="Enter record in the form of 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>
</select> </select>
</div> </div>
</div> </div>
@@ -65,7 +66,7 @@
</tbody> </tbody>
</table> </table>
<h3>Using a Secondary Nameserver</h3> <h3>Using a secondary nameserver</h3>
<p>If your TLD requires you to have two separate nameservers, you can either set up <a href="#" onclick="return show_panel('external_dns')">external DNS</a> and ignore the DNS server on this box entirely, or use the DNS server on this box but add a secondary (aka &ldquo;slave&rdquo;) nameserver.</p> <p>If your TLD requires you to have two separate nameservers, you can either set up <a href="#" onclick="return show_panel('external_dns')">external DNS</a> and ignore the DNS server on this box entirely, or use the DNS server on this box but add a secondary (aka &ldquo;slave&rdquo;) nameserver.</p>
<p>If you choose to use a seconday nameserver, you must find a seconday nameserver service provider. Your domain name registrar or virtual cloud provider may provide this service for you. Once you set up the seconday nameserver service, enter the hostname (not the IP address) of <em>their</em> secondary nameserver in the box below.</p> <p>If you choose to use a seconday nameserver, you must find a seconday nameserver service provider. Your domain name registrar or virtual cloud provider may provide this service for you. Once you set up the seconday nameserver service, enter the hostname (not the IP address) of <em>their</em> secondary nameserver in the box below.</p>

View File

@@ -9,7 +9,7 @@
<meta name="robots" content="noindex, nofollow"> <meta name="robots" content="noindex, nofollow">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" integrity="sha256-MfvZlkHCEqatNoGiOXveE8FIwMzZg4W85qfrfIFBfYc=" crossorigin="anonymous"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<style> <style>
body { body {
overflow-y: scroll; overflow-y: scroll;
@@ -63,7 +63,7 @@
margin-bottom: 1em; margin-bottom: 1em;
} }
</style> </style>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css" integrity="sha256-bHQiqcFbnJb1Qhh61RY9cMh6kR0gTuQY6iFOBj1yj00=" crossorigin="anonymous"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
</head> </head>
<body> <body>
@@ -93,7 +93,7 @@
<li class="dropdown-header">Advanced Pages</li> <li class="dropdown-header">Advanced Pages</li>
<li><a href="#custom_dns" onclick="return show_panel(this);">Custom DNS</a></li> <li><a href="#custom_dns" onclick="return show_panel(this);">Custom DNS</a></li>
<li><a href="#external_dns" onclick="return show_panel(this);">External DNS</a></li> <li><a href="#external_dns" onclick="return show_panel(this);">External DNS</a></li>
<li><a href="/admin/munin">Munin Monitoring</a></li> <li><a href="/admin/munin" target="_blank">Munin Monitoring</a></li>
</ul> </ul>
</li> </li>
<li class="dropdown"> <li class="dropdown">
@@ -192,7 +192,7 @@
</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://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js" integrity="sha256-rsPUGdUPBXgalvIj4YKJrrUlmLXbOb6Cp7cdxn1qeUc=" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js" integrity="sha256-Sk3nkD6mLTMOF0EOpNtsIry+s1CsaqQC1rVLTAy+0yc=" crossorigin="anonymous"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
<script> <script>
var global_modal_state = null; var global_modal_state = null;

View File

@@ -8,7 +8,7 @@
<p>You need a TLS certificate for this box&rsquo;s hostname ({{hostname}}) and every other domain name and subdomain that this box is hosting a website for (see the list below).</p> <p>You need a TLS certificate for this box&rsquo;s hostname ({{hostname}}) and every other domain name and subdomain that this box is hosting a website for (see the list below).</p>
<div id="ssl_provision"> <div id="ssl_provision">
<h3>Provision a Certificate</h3> <h3>Provision a certificate</h3>
<div id="ssl_provision_p" style="display: none; margin-top: 1.5em"> <div id="ssl_provision_p" style="display: none; margin-top: 1.5em">
<button onclick='return provision_tls_cert();' class='btn btn-primary' style="float: left; margin: 0 1.5em 1em 0;">Provision</button> <button onclick='return provision_tls_cert();' class='btn btn-primary' style="float: left; margin: 0 1.5em 1em 0;">Provision</button>
@@ -36,7 +36,7 @@
</div> </div>
</div> </div>
<h3>Certificate Status</h3> <h3>Certificate status</h3>
<p style="margin-top: 1.5em">Certificates expire after a period of time. All certificates will be automatically renewed through <a href="https://letsencrypt.org/" target="_blank">Let&rsquo;s Encrypt</a> 14 days prior to expiration.</p> <p style="margin-top: 1.5em">Certificates expire after a period of time. All certificates will be automatically renewed through <a href="https://letsencrypt.org/" target="_blank">Let&rsquo;s Encrypt</a> 14 days prior to expiration.</p>
@@ -53,9 +53,9 @@
</table> </table>
<h3 id="ssl_install_header">Install Certificate</h3> <h3 id="ssl_install_header">Install certificate</h3>
<p>There are many places where you can get a free or cheap certificate. We recommend <a href="https://www.namecheap.com/security/ssl-certificates/domain-validation.aspx">Namecheap&rsquo;s $9 certificate</a>, <a href="https://www.startssl.com/">StartSSL&rsquo;s free express lane</a> or <a href="https://buy.wosign.com/free/">WoSign&rsquo;s free TLS</a></a>.</p> <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&rsquo;s $9 certificate</a>, <a href="https://www.startssl.com/">StartSSL&rsquo;s free express lane</a>, <a href="https://buy.wosign.com/free/">WoSign&rsquo;s free TLS</a></a> or any other certificate provider a try.</p>
<p>Which domain are you getting a certificate for?</p> <p>Which domain are you getting a certificate for?</p>

View File

@@ -74,7 +74,7 @@
</div> </div>
</form> </form>
<h3>Available Backups</h3> <h3>Available backups</h3>
<p>The backup location currently contains the backups listed below. The total size of the backups is currently <span id="backup-total-size"></span>.</p> <p>The backup location currently contains the backups listed below. The total size of the backups is currently <span id="backup-total-size"></span>.</p>

View File

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

View File

@@ -84,6 +84,48 @@
</table> </table>
</div> </div>
<h3>Mail user API (advanced)</h3>
<p>Use your box&rsquo;s mail user API to add/change/remove users from the command-line or custom services you build.</p>
<p>Usage:</p>
<pre>curl -X <b>VERB</b> [-d "<b>parameters</b>"] --user {email}:{password} https://{{hostname}}/admin/mail/users[<b>action</b>]</pre>
<p>Brackets denote an optional argument. Please note that the POST body <code>parameters</code> must be URL-encoded.</p>
<p>The email and password given to the <code>--user</code> option must be an administrative user on this system.</p>
<h4 style="margin-bottom: 0">Verbs</h4>
<table class="table" style="margin-top: .5em">
<thead><th>Verb</th> <th>Action</th><th></th></thead>
<tr><td>GET</td><td><i>(none)</i></td> <td>Returns a list of existing mail users. Adding <code>?format=json</code> to the URL will give JSON-encoded results.</td></tr>
<tr><td>POST</td><td>/add</td> <td>Adds a new mail user. Required POST-body parameters are <code>email</code> and <code>password</code>.</td></tr>
<tr><td>POST</td><td>/remove</td> <td>Removes a mail user. Required POST-by parameter is <code>email</code>.</td></tr>
<tr><td>POST</td><td>/privileges/add</td> <td>Used to make a mail user an admin. Required POST-body parameters are <code>email</code> and <code>privilege=admin</code>.</td></tr>
<tr><td>POST</td><td>/privileges/remove</td> <td>Used to remove the admin privilege from a mail user. Required POST-body parameter is <code>email</code>.</td></tr>
</table>
<h4>Examples:</h4>
<p>Try these examples. For simplicity the examples omit the <code>--user me@mydomain.com:yourpassword</code> command line argument which you must fill in with your administrative email address and password.</p>
<pre># Gives a JSON-encoded list of all mail users
curl -X GET https://{{hostname}}/admin/mail/users?format=json
# Adds a new email user
curl -X POST -d "email=new_user@mydomail.com" -d "password=s3curE_pa5Sw0rD" https://{{hostname}}/admin/mail/users/add
# Removes a email user
curl -X POST -d "email=new_user@mydomail.com" https://{{hostname}}/admin/mail/users/remove
# Adds admin privilege to an email user
curl -X POST -d "email=new_user@mydomail.com" -d "privilege=admin" https://{{hostname}}/admin/mail/users/privileges/add
# Removes admin privilege from an email user
curl -X POST -d "email=new_user@mydomail.com" https://{{hostname}}/admin/mail/users/privileges/remove
</pre>
<script> <script>
function show_users() { function show_users() {
@@ -170,7 +212,7 @@ function users_set_password(elem) {
yourpw = "<p class='text-danger'>If you change your own password, you will be logged out of this control panel and will need to log in again.</p>"; yourpw = "<p class='text-danger'>If you change your own password, you will be logged out of this control panel and will need to log in again.</p>";
show_modal_confirm( show_modal_confirm(
"Archive User", "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 four characters and may not contain spaces.</small>" + yourpw + "</p>"),
"Set Password", "Set Password",
function() { function() {

View File

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

View File

@@ -69,6 +69,16 @@ The [setup guide video](https://mailinabox.email/) explains how to verify the ho
If DNSSEC is enabled at the box's domain name's registrar, the SSHFP record that the box automatically puts into DNS can also be used to verify the host key fingerprint by setting `VerifyHostKeyDNS yes` in your `ssh/.config` file or by logging in with `ssh -o VerifyHostKeyDNS=yes`. ([source](management/dns_update.py)) If DNSSEC is enabled at the box's domain name's registrar, the SSHFP record that the box automatically puts into DNS can also be used to verify the host key fingerprint by setting `VerifyHostKeyDNS yes` in your `ssh/.config` file or by logging in with `ssh -o VerifyHostKeyDNS=yes`. ([source](management/dns_update.py))
### Brute-force attack mitigation
`fail2ban` provides some protection from brute-force login attacks (repeated logins that guess account passwords) by blocking offending IP addresses at the network level.
The following services are protected: SSH, IMAP (dovecot), SMTP submission (postfix), webmail (roundcube), ownCloud/CalDAV/CardDAV (over HTTP), and the Mail-in-a-Box control panel & munin (over HTTP).
Some other services running on the box may be missing fail2ban filters.
`fail2ban` only blocks IPv4 addresses, however. If the box has a public IPv6 address, it is not protected from these attacks.
Outbound Mail Outbound Mail
------------- -------------
@@ -80,7 +90,7 @@ The first step in resolving the destination server for an email address is perfo
### Encryption ### Encryption
The box (along with the vast majority of mail servers) uses [opportunistic encryption](https://en.wikipedia.org/wiki/Opportunistic_encryption), meaning the mail is encrypted in transit and protected from passive eavesdropping, but it is not protected from an active man-in-the-middle attack. Modern encryption settings will be used to the extent the recipient server supports them. ([source](setup/mail-postfix.sh)) The box (along with the vast majority of mail servers) uses [opportunistic encryption](https://en.wikipedia.org/wiki/Opportunistic_encryption), meaning the mail is encrypted in transit and protected from passive eavesdropping, but it is not protected from an active man-in-the-middle attack. Modern encryption settings (TLSv1 and later, no RC4) will be used to the extent the recipient server supports them. ([source](setup/mail-postfix.sh))
### DANE ### DANE
@@ -101,7 +111,7 @@ Incoming Mail
### Encryption ### Encryption
As discussed above, there is no way to require on-the-wire encryption of mail. When the box receives an incoming email (SMTP on port 25), it offers encryption (STARTTLS) but cannot require that senders use it because some senders may not support STARTTLS at all and other senders may support STARTTLS but not with the latest protocols/ciphers. To give senders the best chance at making use of encryption, the box offers protocols back to SSLv3 and ciphers with key lengths as low as 112 bits. Modern clients (senders) will make use of the 256-bit ciphers and Diffie-Hellman ciphers with a 2048-bit key for forward secrecy, however. ([source](setup/mail-postfix.sh)) As discussed above, there is no way to require on-the-wire encryption of mail. When the box receives an incoming email (SMTP on port 25), it offers encryption (STARTTLS) but cannot require that senders use it because some senders may not support STARTTLS at all and other senders may support STARTTLS but not with the latest protocols/ciphers. To give senders the best chance at making use of encryption, the box offers protocols back to TLSv1 and ciphers with key lengths as low as 112 bits. Modern clients (senders) will make use of the 256-bit ciphers and Diffie-Hellman ciphers with a 2048-bit key for perfect forward secrecy, however. ([source](setup/mail-postfix.sh))
### DANE ### DANE

View File

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

View File

@@ -31,7 +31,7 @@ ExternalIgnoreList refile:/etc/opendkim/TrustedHosts
InternalHosts refile:/etc/opendkim/TrustedHosts InternalHosts refile:/etc/opendkim/TrustedHosts
KeyTable refile:/etc/opendkim/KeyTable KeyTable refile:/etc/opendkim/KeyTable
SigningTable refile:/etc/opendkim/SigningTable SigningTable refile:/etc/opendkim/SigningTable
Socket inet:8891@localhost Socket inet:8891@127.0.0.1
RequireSafeKeys false RequireSafeKeys false
EOF EOF
fi fi

View File

@@ -38,7 +38,8 @@ apt_install \
# would be 20 users). Set it to 250 times the number of cores this # would be 20 users). Set it to 250 times the number of cores this
# machine has, so on a two-core machine that's 500 processes/100 users). # machine has, so on a two-core machine that's 500 processes/100 users).
tools/editconf.py /etc/dovecot/conf.d/10-master.conf \ tools/editconf.py /etc/dovecot/conf.d/10-master.conf \
default_process_limit=$(echo "`nproc` * 250" | bc) default_process_limit=$(echo "`nproc` * 250" | bc) \
log_path=/var/log/mail.log
# The inotify `max_user_instances` default is 128, which constrains # The inotify `max_user_instances` default is 128, which constrains
# the total number of watched (IMAP IDLE push) folders by open connections. # the total number of watched (IMAP IDLE push) folders by open connections.

View File

@@ -122,8 +122,9 @@ tools/editconf.py /etc/postfix/main.cf \
smtpd_tls_cert_file=$STORAGE_ROOT/ssl/ssl_certificate.pem \ smtpd_tls_cert_file=$STORAGE_ROOT/ssl/ssl_certificate.pem \
smtpd_tls_key_file=$STORAGE_ROOT/ssl/ssl_private_key.pem \ smtpd_tls_key_file=$STORAGE_ROOT/ssl/ssl_private_key.pem \
smtpd_tls_dh1024_param_file=$STORAGE_ROOT/ssl/dh2048.pem \ smtpd_tls_dh1024_param_file=$STORAGE_ROOT/ssl/dh2048.pem \
smtpd_tls_protocols=\!SSLv2,\!SSLv3 \
smtpd_tls_ciphers=medium \ smtpd_tls_ciphers=medium \
smtpd_tls_exclude_ciphers=aNULL \ smtpd_tls_exclude_ciphers=aNULL,RC4 \
smtpd_tls_received_header=yes smtpd_tls_received_header=yes
# Prevent non-authenticated users from sending mail that requires being # Prevent non-authenticated users from sending mail that requires being
@@ -158,6 +159,10 @@ tools/editconf.py /etc/postfix/main.cf \
# even if we don't know if it's to the right party, than to not encrypt at all. Instead we'll # even if we don't know if it's to the right party, than to not encrypt at all. Instead we'll
# now see notices about trusted certs. The CA file is provided by the package `ca-certificates`. # now see notices about trusted certs. The CA file is provided by the package `ca-certificates`.
tools/editconf.py /etc/postfix/main.cf \ tools/editconf.py /etc/postfix/main.cf \
smtp_tls_protocols=\!SSLv2,\!SSLv3 \
smtp_tls_mandatory_protocols=\!SSLv2,\!SSLv3 \
smtp_tls_ciphers=medium \
smtp_tls_exclude_ciphers=aNULL,RC4 \
smtp_tls_security_level=dane \ smtp_tls_security_level=dane \
smtp_dns_support_level=dnssec \ smtp_dns_support_level=dnssec \
smtp_tls_CAfile=/etc/ssl/certs/ca-certificates.crt \ smtp_tls_CAfile=/etc/ssl/certs/ca-certificates.crt \

View File

@@ -38,17 +38,19 @@ passdb {
args = /etc/dovecot/dovecot-sql.conf.ext args = /etc/dovecot/dovecot-sql.conf.ext
} }
userdb { userdb {
driver = static driver = sql
args = uid=mail gid=mail home=$STORAGE_ROOT/mail/mailboxes/%d/%n args = /etc/dovecot/dovecot-sql.conf.ext
} }
EOF EOF
# Configure the SQL to query for a user's password. # Configure the SQL to query for a user's metadata and password.
cat > /etc/dovecot/dovecot-sql.conf.ext << EOF; cat > /etc/dovecot/dovecot-sql.conf.ext << EOF;
driver = sqlite driver = sqlite
connect = $db_path connect = $db_path
default_pass_scheme = SHA512-CRYPT default_pass_scheme = SHA512-CRYPT
password_query = SELECT email as user, password FROM users WHERE email='%u'; password_query = SELECT email as user, password FROM users WHERE email='%u';
user_query = SELECT email AS user, "mail" as uid, "mail" as gid, "$STORAGE_ROOT/mail/mailboxes/%d/%n" as home FROM users WHERE email='%u';
iterate_query = SELECT email AS user FROM users;
EOF EOF
chmod 0600 /etc/dovecot/dovecot-sql.conf.ext # per Dovecot instructions chmod 0600 /etc/dovecot/dovecot-sql.conf.ext # per Dovecot instructions

View File

@@ -7,7 +7,8 @@ source /etc/mailinabox.conf # load global vars
# install Munin # install Munin
echo "Installing Munin (system monitoring)..." echo "Installing Munin (system monitoring)..."
apt_install munin munin-node apt_install munin munin-node libcgi-fast-perl
# libcgi-fast-perl is needed by /usr/lib/munin/cgi/munin-cgi-graph
# edit config # edit config
cat > /etc/munin/munin.conf <<EOF; cat > /etc/munin/munin.conf <<EOF;
@@ -19,6 +20,9 @@ tmpldir /etc/munin/templates
includedir /etc/munin/munin-conf.d includedir /etc/munin/munin-conf.d
# path dynazoom uses for requests
cgiurl_graph /admin/munin/cgi-graph
# a simple host tree # a simple host tree
[$PRIMARY_HOSTNAME] [$PRIMARY_HOSTNAME]
address 127.0.0.1 address 127.0.0.1
@@ -29,6 +33,10 @@ contact.admin.command mail -s "Munin notification ${var:host}" administrator@$PR
contact.admin.always_send warning critical contact.admin.always_send warning critical
EOF EOF
# The Debian installer touches these files and chowns them to www-data:adm for use with spawn-fcgi
chown munin. /var/log/munin/munin-cgi-html.log
chown munin. /var/log/munin/munin-cgi-graph.log
# ensure munin-node knows the name of this machine # ensure munin-node knows the name of this machine
tools/editconf.py /etc/munin/munin-node.conf -s \ tools/editconf.py /etc/munin/munin-node.conf -s \
host_name=$PRIMARY_HOSTNAME host_name=$PRIMARY_HOSTNAME

View File

@@ -17,8 +17,8 @@ apt_install \
apt-get purge -qq -y owncloud* apt-get purge -qq -y owncloud*
# Install ownCloud from source of this version: # Install ownCloud from source of this version:
owncloud_ver=8.1.1 owncloud_ver=8.2.3
owncloud_hash=34077e78575a3e689825a00964ee37fbf83fbdda owncloud_hash=bfdf6166fbf6fc5438dc358600e7239d1c970613
# Migrate <= v0.10 setups that stored the ownCloud config.php in /usr/local rather than # Migrate <= v0.10 setups that stored the ownCloud config.php in /usr/local rather than
# in STORAGE_ROOT. Move the file to STORAGE_ROOT. # in STORAGE_ROOT. Move the file to STORAGE_ROOT.
@@ -52,8 +52,8 @@ if [ ! -d /usr/local/lib/owncloud/ ] \
# The two apps we actually want are not in ownCloud core. Clone them from # The two apps we actually want are not in ownCloud core. Clone them from
# their github repositories. # their github repositories.
mkdir -p /usr/local/lib/owncloud/apps mkdir -p /usr/local/lib/owncloud/apps
git_clone https://github.com/owncloud/contacts 4ff855e7c2075309041bead09fbb9eb7df678244 '' /usr/local/lib/owncloud/apps/contacts git_clone https://github.com/owncloudarchive/contacts 9ba2e667ae8c7ea36d8c4a4c3413c374beb24b1b '' /usr/local/lib/owncloud/apps/contacts
git_clone https://github.com/owncloud/calendar ec53139b144c0f842c33813305612e8006c42ea5 '' /usr/local/lib/owncloud/apps/calendar git_clone https://github.com/owncloudarchive/calendar 2086e738a3b7b868ec59cd61f0f88b49c3f21dd1 '' /usr/local/lib/owncloud/apps/calendar
# Fix weird permissions. # Fix weird permissions.
chmod 750 /usr/local/lib/owncloud/{apps,config} chmod 750 /usr/local/lib/owncloud/{apps,config}
@@ -92,7 +92,6 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
mkdir -p $STORAGE_ROOT/owncloud mkdir -p $STORAGE_ROOT/owncloud
# Create an initial configuration file. # Create an initial configuration file.
TIMEZONE=$(cat /etc/timezone)
instanceid=oc$(echo $PRIMARY_HOSTNAME | sha1sum | fold -w 10 | head -n 1) instanceid=oc$(echo $PRIMARY_HOSTNAME | sha1sum | fold -w 10 | head -n 1)
cat > $STORAGE_ROOT/owncloud/config.php <<EOF; cat > $STORAGE_ROOT/owncloud/config.php <<EOF;
<?php <?php
@@ -108,12 +107,12 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
'user_backends' => array( 'user_backends' => array(
array( array(
'class'=>'OC_User_IMAP', 'class'=>'OC_User_IMAP',
'arguments'=>array('{localhost:993/imap/ssl/novalidate-cert}') 'arguments'=>array('{127.0.0.1:993/imap/ssl/novalidate-cert}')
) )
), ),
'memcache.local' => '\\OC\\Memcache\\Memcached', 'memcache.local' => '\\OC\\Memcache\\Memcached',
"memcached_servers" => array ( "memcached_servers" => array (
array('localhost', 11211), array('127.0.0.1', 11211),
), ),
'mail_smtpmode' => 'sendmail', 'mail_smtpmode' => 'sendmail',
'mail_smtpsecure' => '', 'mail_smtpsecure' => '',
@@ -125,7 +124,6 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
'mail_smtppassword' => '', 'mail_smtppassword' => '',
'mail_from_address' => 'owncloud', 'mail_from_address' => 'owncloud',
'mail_domain' => '$PRIMARY_HOSTNAME', 'mail_domain' => '$PRIMARY_HOSTNAME',
'logtimezone' => '$TIMEZONE',
); );
?> ?>
EOF EOF
@@ -163,7 +161,11 @@ fi
# so set it here. It also can change if the box's PRIMARY_HOSTNAME changes, so # so set it here. It also can change if the box's PRIMARY_HOSTNAME changes, so
# this will make sure it has the right value. # this will make sure it has the right value.
# * Some settings weren't included in previous versions of Mail-in-a-Box. # * Some settings weren't included in previous versions of Mail-in-a-Box.
# * We need to set the timezone to the system timezone to allow fail2ban to ban
# users within the proper timeframe
# * We need to set the logdateformat to something that will work correctly with fail2ban
# Use PHP to read the settings file, modify it, and write out the new settings array. # Use PHP to read the settings file, modify it, and write out the new settings array.
TIMEZONE=$(cat /etc/timezone)
CONFIG_TEMP=$(/bin/mktemp) CONFIG_TEMP=$(/bin/mktemp)
php <<EOF > $CONFIG_TEMP && mv $CONFIG_TEMP $STORAGE_ROOT/owncloud/config.php; php <<EOF > $CONFIG_TEMP && mv $CONFIG_TEMP $STORAGE_ROOT/owncloud/config.php;
<?php <?php
@@ -175,6 +177,9 @@ include("$STORAGE_ROOT/owncloud/config.php");
\$CONFIG['overwrite.cli.url'] = '/cloud'; \$CONFIG['overwrite.cli.url'] = '/cloud';
\$CONFIG['mail_from_address'] = 'administrator'; # just the local part, matches our master administrator address \$CONFIG['mail_from_address'] = 'administrator'; # just the local part, matches our master administrator address
\$CONFIG['logtimezone'] = '$TIMEZONE';
\$CONFIG['logdateformat'] = 'Y-m-d H:i:s';
echo "<?php\n\\\$CONFIG = "; echo "<?php\n\\\$CONFIG = ";
var_export(\$CONFIG); var_export(\$CONFIG);
echo ";"; echo ";";

View File

@@ -33,3 +33,30 @@ if [ ! -d /vagrant ]; then
exit exit
fi fi
fi fi
# Check that tempfs is mounted with exec
MOUNTED_TMP_AS_NO_EXEC=$(grep "/tmp.*noexec" /proc/mounts)
if [ -n "$MOUNTED_TMP_AS_NO_EXEC" ]; then
echo "Mail-in-a-Box has to have exec rights on /tmp, please mount /tmp with exec"
exit
fi
# Check that no .wgetrc exists
if [ -e ~/.wgetrc ]; then
echo "Mail-in-a-Box expects no overrides to wget defaults, ~/.wgetrc exists"
exit
fi
# Check that we are running on x86_64 or i686, 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!
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
fi

View File

@@ -78,9 +78,13 @@ tools/editconf.py /etc/spamassassin/local.cf -s \
# * Writable by the debian-spamd user, which runs /etc/cron.daily/spamassassin. # * Writable by the debian-spamd user, which runs /etc/cron.daily/spamassassin.
# #
# We'll have these files owned by spampd and grant access to the other two processes. # We'll have these files owned by spampd and grant access to the other two processes.
#
# Spamassassin will change the access rights back to the defaults, so we must also configure
# the filemode in the config file.
tools/editconf.py /etc/spamassassin/local.cf -s \ tools/editconf.py /etc/spamassassin/local.cf -s \
bayes_path=$STORAGE_ROOT/mail/spamassassin/bayes bayes_path=$STORAGE_ROOT/mail/spamassassin/bayes \
bayes_file_mode=0660
mkdir -p $STORAGE_ROOT/mail/spamassassin mkdir -p $STORAGE_ROOT/mail/spamassassin
chown -R spampd:spampd $STORAGE_ROOT/mail/spamassassin chown -R spampd:spampd $STORAGE_ROOT/mail/spamassassin

View File

@@ -5,7 +5,8 @@
source setup/functions.sh # load our functions 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 14.04 on a
# machine with enough memory? If not, this shows an error and exits. # machine with enough memory? Is /tmp mounted with exec.
# If not, this shows an error and exits.
source setup/preflight.sh source setup/preflight.sh
# Ensure Python reads/writes files in UTF-8. If the machine # Ensure Python reads/writes files in UTF-8. If the machine
@@ -23,6 +24,9 @@ export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8 export LANG=en_US.UTF-8
export LC_TYPE=en_US.UTF-8 export LC_TYPE=en_US.UTF-8
# Fix so line drawing characters are shown correctly in Putty on Windows. See #744.
export NCURSES_NO_UTF8_ACS=1
# Recall the last settings used if we're running this a second time. # Recall the last settings used if we're running this a second time.
if [ -f /etc/mailinabox.conf ]; then if [ -f /etc/mailinabox.conf ]; then
# Run any system migrations before proceeding. Since this is a second run, # Run any system migrations before proceeding. Since this is a second run,
@@ -107,15 +111,22 @@ source setup/zpush.sh
source setup/management.sh source setup/management.sh
source setup/munin.sh source setup/munin.sh
# Ping the management daemon to write the DNS and nginx configuration files. # Wait for the management daemon to start...
until nc -z -w 4 localhost 10222 until nc -z -w 4 127.0.0.1 10222
do do
echo Waiting for the Mail-in-a-Box management daemon to start... echo Waiting for the Mail-in-a-Box management daemon to start...
sleep 2 sleep 2
done done
# ...and then have it write the DNS and nginx configuration files and start those
# services.
tools/dns_update tools/dns_update
tools/web_update tools/web_update
# Give fail2ban another restart. The log files may not all have been present when
# fail2ban was first configured, but they should exist now.
restart_service fail2ban
# If DNS is already working, try to provision TLS certficates from Let's Encrypt. # 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. # Suppress extra reasons why domains aren't getting a new certificate.
management/ssl_certificates.py -q management/ssl_certificates.py -q

View File

@@ -4,6 +4,70 @@ source setup/functions.sh # load our functions
# Basic System Configuration # Basic System Configuration
# ------------------------- # -------------------------
# ### Set hostname of the box
# If the hostname is not correctly resolvable sudo can't be used. This will result in
# errors during the install
#
# First set the hostname in the configuration file, then activate the setting
echo $PRIMARY_HOSTNAME > /etc/hostname
hostname $PRIMARY_HOSTNAME
# ### Add swap space to the system
# If the physical memory of the system is below 2GB it is wise to create a
# swap file. This will make the system more resiliant to memory spikes and
# prevent for instance spam filtering from crashing
# We will create a 1G file, this should be a good balance between disk usage
# and buffers for the system. We will only allocate this file if there is more
# than 5GB of disk space available
# The following checks are performed:
# - Check if swap is currently mountend by looking at /proc/swaps
# - Check if the user intents to activate swap on next boot by checking fstab entries.
# - Check if a swapfile already exists
# - Check if the root file system is not btrfs, might be an incompatible version with
# swapfiles. User should hanle it them selves.
# - Check the memory requirements
# - Check available diskspace
# See https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04
# for reference
SWAP_MOUNTED=$(cat /proc/swaps | tail -n+2)
SWAP_IN_FSTAB=$(grep "swap" /etc/fstab)
ROOT_IS_BTRFS=$(grep "\/ .*btrfs" /proc/mounts)
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}')
AVAILABLE_DISK_SPACE=$(df / --output=avail | tail -n 1)
if
[ -z "$SWAP_MOUNTED" ] &&
[ -z "$SWAP_IN_FSTAB" ] &&
[ ! -e /swapfile ] &&
[ -z "$ROOT_IS_BTRFS" ] &&
[ $TOTAL_PHYSICAL_MEM -lt 1900000 ] &&
[ $AVAILABLE_DISK_SPACE -gt 5242880 ]
then
echo "Adding a swap file to the system..."
# Allocate and activate the swap file. Allocate in 1KB chuncks
# doing it in one go, could fail on low memory systems
dd if=/dev/zero of=/swapfile bs=1024 count=$[1024*1024] status=none
if [ -e /swapfile ]; then
chmod 600 /swapfile
hide_output mkswap /swapfile
swapon /swapfile
fi
# Check if swap is mounted then activate on boot
if swapon -s | grep -q "\/swapfile"; then
echo "/swapfile none swap sw 0 0" >> /etc/fstab
else
echo "ERROR: Swap allocation failed"
fi
fi
# ### Add Mail-in-a-Box's PPA. # ### Add Mail-in-a-Box's PPA.
# We've built several .deb packages on our own that we want to include. # We've built several .deb packages on our own that we want to include.
@@ -227,10 +291,17 @@ restart_service resolvconf
# ### Fail2Ban Service # ### Fail2Ban Service
# Configure the Fail2Ban installation to prevent dumb bruce-force attacks against dovecot, postfix and ssh # Configure the Fail2Ban installation to prevent dumb bruce-force attacks against dovecot, postfix, ssh, etc.
cat conf/fail2ban/jail.local \ rm -f /etc/fail2ban/jail.local # we used to use this file but don't anymore
cat conf/fail2ban/jails.conf \
| sed "s/PUBLIC_IP/$PUBLIC_IP/g" \ | sed "s/PUBLIC_IP/$PUBLIC_IP/g" \
> /etc/fail2ban/jail.local | sed "s#STORAGE_ROOT#$STORAGE_ROOT#" \
cp conf/fail2ban/dovecotimap.conf /etc/fail2ban/filter.d/dovecotimap.conf > /etc/fail2ban/jail.d/mailinabox.conf
cp -f conf/fail2ban/filter.d/* /etc/fail2ban/filter.d/
# On first installation, the log files that the jails look at don't all exist.
# e.g., The roundcube error log isn't normally created until someone logs into
# Roundcube for the first time. This causes fail2ban to fail to start. Later
# scripts will ensure the files exist and then fail2ban is given another
# restart at the very end of setup.
restart_service fail2ban restart_service fail2ban

View File

@@ -34,11 +34,11 @@ apt-get purge -qq -y roundcube* #NODOC
# Install Roundcube from source if it is not already present or if it is out of date. # Install Roundcube from source if it is not already present or if it is out of date.
# Combine the Roundcube version number with the commit hash of vacation_sieve to track # Combine the Roundcube version number with the commit hash of vacation_sieve to track
# whether we have the latest version. # whether we have the latest version.
VERSION=1.1.4 VERSION=1.2.1
HASH=4883c8bb39fadf8af94ffb09ee426cba9f8ef2e3 HASH=81fbfba4683522f6e54006d0300a48e6da3f3bbd
VACATION_SIEVE_VERSION=91ea6f52216390073d1f5b70b5f6bea0bfaee7e5 VACATION_SIEVE_VERSION=91ea6f52216390073d1f5b70b5f6bea0bfaee7e5
PERSISTENT_LOGIN_VERSION=1e9d724476a370ce917a2fcd5b3217b0c306c24e PERSISTENT_LOGIN_VERSION=1e9d724476a370ce917a2fcd5b3217b0c306c24e
HTML5_NOTIFIER_VERSION=046eb388dd63b1ec77a3ee485757fc25ae9e684d HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5
UPDATE_KEY=$VERSION:$VACATION_SIEVE_VERSION:$PERSISTENT_LOGIN_VERSION:$HTML5_NOTIFIER_VERSION:a UPDATE_KEY=$VERSION:$VACATION_SIEVE_VERSION:$PERSISTENT_LOGIN_VERSION:$HTML5_NOTIFIER_VERSION:a
needs_update=0 #NODOC needs_update=0 #NODOC
if [ ! -f /usr/local/lib/roundcubemail/version ]; then if [ ! -f /usr/local/lib/roundcubemail/version ]; then
@@ -51,7 +51,7 @@ fi
if [ $needs_update == 1 ]; then if [ $needs_update == 1 ]; then
# install roundcube # install roundcube
wget_verify \ wget_verify \
https://downloads.sourceforge.net/project/roundcubemail/roundcubemail/$VERSION/roundcubemail-$VERSION.tar.gz \ https://github.com/roundcube/roundcubemail/releases/download/$VERSION/roundcubemail-$VERSION.tar.gz \
$HASH \ $HASH \
/tmp/roundcube.tgz /tmp/roundcube.tgz
tar -C /usr/local/lib --no-same-owner -zxf /tmp/roundcube.tgz tar -C /usr/local/lib --no-same-owner -zxf /tmp/roundcube.tgz
@@ -94,12 +94,12 @@ cat > /usr/local/lib/roundcubemail/config/config.inc.php <<EOF;
\$config['default_host'] = 'ssl://localhost'; \$config['default_host'] = 'ssl://localhost';
\$config['default_port'] = 993; \$config['default_port'] = 993;
\$config['imap_timeout'] = 15; \$config['imap_timeout'] = 15;
\$config['smtp_server'] = 'tls://localhost'; \$config['smtp_server'] = 'tls://127.0.0.1';
\$config['smtp_port'] = 587; \$config['smtp_port'] = 587;
\$config['smtp_user'] = '%u'; \$config['smtp_user'] = '%u';
\$config['smtp_pass'] = '%p'; \$config['smtp_pass'] = '%p';
\$config['support_url'] = 'https://mailinabox.email/'; \$config['support_url'] = 'https://mailinabox.email/';
\$config['product_name'] = 'Mail-in-a-Box/Roundcube Webmail'; \$config['product_name'] = '$PRIMARY_HOSTNAME Webmail';
\$config['des_key'] = '$SECRET_KEY'; \$config['des_key'] = '$SECRET_KEY';
\$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'vacation_sieve', 'persistent_login'); \$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'vacation_sieve', 'persistent_login');
\$config['skin'] = 'classic'; \$config['skin'] = 'classic';
@@ -121,7 +121,7 @@ cat > /usr/local/lib/roundcubemail/plugins/vacation_sieve/config.inc.php <<EOF;
'transfer' => array( 'transfer' => array(
'mode' => 'managesieve', 'mode' => 'managesieve',
'ms_activate_script' => true, 'ms_activate_script' => true,
'host' => 'localhost', 'host' => '127.0.0.1',
'port' => '4190', 'port' => '4190',
'usetls' => false, 'usetls' => false,
'path' => 'vacation', 'path' => 'vacation',
@@ -133,6 +133,9 @@ EOF
mkdir -p /var/log/roundcubemail /tmp/roundcubemail $STORAGE_ROOT/mail/roundcube 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 chown -R www-data.www-data /var/log/roundcubemail /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 # Password changing plugin settings
# The config comes empty by default, so we need the settings # The config comes empty by default, so we need the settings
# we're not planning to change in config.inc.dist... # we're not planning to change in config.inc.dist...
@@ -157,6 +160,12 @@ chmod 775 $STORAGE_ROOT/mail
chown root.www-data $STORAGE_ROOT/mail/users.sqlite chown root.www-data $STORAGE_ROOT/mail/users.sqlite
chmod 664 $STORAGE_ROOT/mail/users.sqlite chmod 664 $STORAGE_ROOT/mail/users.sqlite
# 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
# Enable PHP modules. # Enable PHP modules.
php5enmod mcrypt php5enmod mcrypt
restart_service php5-fpm restart_service php5-fpm

196
tests/fail2ban.py Normal file
View File

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

View File

@@ -33,7 +33,6 @@ PORT 25
AES256-SHA256 - 256 bits 250 2.0.0 Ok AES256-SHA256 - 256 bits 250 2.0.0 Ok
AES256-SHA - 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 AES256-GCM-SHA384 - 256 bits 250 2.0.0 Ok
ECDHE-RSA-RC4-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA256 ECDH-256 bits 128 bits 250 2.0.0 Ok ECDHE-RSA-AES128-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-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-GCM-SHA256 ECDH-256 bits 128 bits 250 2.0.0 Ok
@@ -43,8 +42,6 @@ PORT 25
DHE-RSA-AES128-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
DHE-RSA-AES128-GCM-SHA256 DH-2048 bits 128 bits 250 2.0.0 Ok DHE-RSA-AES128-GCM-SHA256 DH-2048 bits 128 bits 250 2.0.0 Ok
SEED-SHA - 128 bits 250 2.0.0 Ok SEED-SHA - 128 bits 250 2.0.0 Ok
RC4-SHA - 128 bits 250 2.0.0 Ok
RC4-MD5 - 128 bits 250 2.0.0 Ok
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
AES128-SHA256 - 128 bits 250 2.0.0 Ok AES128-SHA256 - 128 bits 250 2.0.0 Ok
AES128-SHA - 128 bits 250 2.0.0 Ok AES128-SHA - 128 bits 250 2.0.0 Ok
@@ -62,37 +59,11 @@ PORT 25
DHE-RSA-AES256-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 CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
AES256-SHA - 256 bits 250 2.0.0 Ok AES256-SHA - 256 bits 250 2.0.0 Ok
ECDHE-RSA-RC4-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok ECDHE-RSA-AES128-SHA ECDH-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-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-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 DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
SEED-SHA - 128 bits 250 2.0.0 Ok SEED-SHA - 128 bits 250 2.0.0 Ok
RC4-SHA - 128 bits 250 2.0.0 Ok
RC4-MD5 - 128 bits 250 2.0.0 Ok
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
AES128-SHA - 128 bits 250 2.0.0 Ok
ECDHE-RSA-DES-CBC3-SHA ECDH-256 bits 112 bits 250 2.0.0 Ok
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits 250 2.0.0 Ok
DES-CBC3-SHA - 112 bits 250 2.0.0 Ok
* SSLV3 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
Accepted:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
AES256-SHA - 256 bits 250 2.0.0 Ok
ECDHE-RSA-RC4-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
SEED-SHA - 128 bits 250 2.0.0 Ok
RC4-SHA - 128 bits 250 2.0.0 Ok
RC4-MD5 - 128 bits 250 2.0.0 Ok
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
AES128-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 ECDHE-RSA-DES-CBC3-SHA ECDH-256 bits 112 bits 250 2.0.0 Ok
@@ -108,23 +79,23 @@ PORT 25
DHE-RSA-AES256-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 CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
AES256-SHA - 256 bits 250 2.0.0 Ok AES256-SHA - 256 bits 250 2.0.0 Ok
ECDHE-RSA-RC4-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok ECDHE-RSA-AES128-SHA ECDH-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-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-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 DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
SEED-SHA - 128 bits 250 2.0.0 Ok SEED-SHA - 128 bits 250 2.0.0 Ok
RC4-SHA - 128 bits 250 2.0.0 Ok
RC4-MD5 - 128 bits 250 2.0.0 Ok
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
AES128-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 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 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 DES-CBC3-SHA - 112 bits 250 2.0.0 Ok
Should Not Offer: DHE-RSA-SEED-SHA, ECDHE-RSA-RC4-SHA, EDH-RSA-DES-CBC3-SHA, RC4-MD5, RC4-SHA, SEED-SHA * SSLV3 Cipher Suites:
Could Also Offer: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-DSS-AES256-SHA256, DHE-DSS-CAMELLIA128-SHA, DHE-DSS-CAMELLIA256-SHA, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-DES-CBC3-SHA, SRP-3DES-EDE-CBC-SHA, SRP-AES-128-CBC-SHA, SRP-AES-256-CBC-SHA, SRP-DSS-3DES-EDE-CBC-SHA, SRP-DSS-AES-128-CBC-SHA, SRP-DSS-AES-256-CBC-SHA, SRP-RSA-3DES-EDE-CBC-SHA, SRP-RSA-AES-128-CBC-SHA, SRP-RSA-AES-256-CBC-SHA Server rejected all cipher suites.
Supported Clients: OpenSSL/1.0.2, Yahoo Slurp/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, YandexBot/Jan 2015, Android/4.4.2, Safari/8/iOS 8.1.2, Safari/7/OS X 10.9, Safari/8/OS X 10.10, Safari/7/iOS 7.1, Safari/6/iOS 6.0.1, Baidu/Jan 2015, Firefox/31.3.0 ESR/Win 7, Android/5.0.0, IE/11/Win 7, Java/8u31, Googlebot/Feb 2015, Chrome/42/OS X, IE Mobile/11/Win Phone 8.1, IE/11/Win 8.1, Android/4.0.4, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, Android/4.3, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, Java/7u25, OpenSSL/0.9.8y, Firefox/37/OS X, IE/7/Vista, IE/8-10/Win 7, IE Mobile/10/Win Phone 8.0, Java/6u45, Android/2.3.7, IE/8/XP
Should Not Offer: 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
PORT 587 PORT 587
-------- --------
@@ -192,9 +163,6 @@ PORT 587
CAMELLIA128-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 AES128-SHA - 128 bits 250 2.0.0 Ok
* SSLV3 Cipher Suites:
Server rejected all cipher suites.
* TLSV1 Cipher Suites: * TLSV1 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
@@ -212,9 +180,12 @@ PORT 587
CAMELLIA128-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 AES128-SHA - 128 bits 250 2.0.0 Ok
* 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 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 Could Also Offer: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384
Supported Clients: OpenSSL/1.0.2, Yahoo Slurp/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, YandexBot/Jan 2015, Android/4.4.2, Safari/8/iOS 8.1.2, Safari/7/OS X 10.9, Safari/8/OS X 10.10, Safari/7/iOS 7.1, IE Mobile/11/Win Phone 8.1, IE/11/Win 8.1, IE/11/Win 7, Safari/6/iOS 6.0.1, Firefox/31.3.0 ESR/Win 7, Baidu/Jan 2015, Android/5.0.0, Chrome/42/OS X, Java/8u31, Googlebot/Feb 2015, Firefox/37/OS X, Android/4.0.4, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, Android/4.3, Android/4.2.2, Safari/5.1.9/OS X 10.6.8, OpenSSL/0.9.8y, IE/7/Vista, IE/8-10/Win 7, IE Mobile/10/Win Phone 8.0, Java/7u25, Java/6u45, Android/2.3.7 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
PORT 443 PORT 443
-------- --------
@@ -226,22 +197,22 @@ PORT 443
Client-initiated Renegotiations: OK - Rejected Client-initiated Renegotiations: OK - Rejected
Secure Renegotiation: OK - Supported Secure Renegotiation: OK - Supported
* HTTP Strict Transport Security: * OpenSSL Heartbleed:
OK - HSTS header received: max-age=31536000 OK - Not vulnerable to Heartbleed
* Session Resumption: * Session Resumption:
With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts). With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: OK - Supported With TLS Session Tickets: OK - Supported
* OpenSSL Heartbleed: * HTTP Strict Transport Security:
OK - Not vulnerable to Heartbleed OK - HSTS header received: max-age=31536000
Unhandled exception when processing --chrome_sha1:
exceptions.TypeError - Incorrect padding
* SSLV2 Cipher Suites: * SSLV2 Cipher Suites:
Server rejected all cipher suites. Server rejected all cipher suites.
* Google Chrome SHA-1 Deprecation Status:
OK - Leaf certificate expires before 2016.
* TLSV1_2 Cipher Suites: * TLSV1_2 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES128-GCM-SHA256 ECDH-256 bits 128 bits HTTP 200 OK ECDHE-RSA-AES128-GCM-SHA256 ECDH-256 bits 128 bits HTTP 200 OK
@@ -270,9 +241,6 @@ PORT 443
DHE-RSA-AES128-SHA DH-2048 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 DES-CBC3-SHA - 112 bits HTTP 200 OK
* SSLV3 Cipher Suites:
Server rejected all cipher suites.
* TLSV1 Cipher Suites: * TLSV1 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK
@@ -283,9 +251,12 @@ PORT 443
DHE-RSA-AES128-SHA DH-2048 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 DES-CBC3-SHA - 112 bits HTTP 200 OK
* SSLV3 Cipher Suites:
Server rejected all cipher suites.
Should Not Offer: (none -- good) Should Not Offer: (none -- good)
Could Also Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, CAMELLIA128-SHA, CAMELLIA256-SHA, DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-DSS-AES256-SHA256, DHE-DSS-CAMELLIA128-SHA, DHE-DSS-CAMELLIA256-SHA, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, SRP-AES-128-CBC-SHA, SRP-AES-256-CBC-SHA, SRP-DSS-AES-128-CBC-SHA, SRP-DSS-AES-256-CBC-SHA, SRP-RSA-AES-128-CBC-SHA, SRP-RSA-AES-256-CBC-SHA Could Also Offer: 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: YandexBot/Jan 2015, OpenSSL/1.0.2, Yahoo Slurp/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, Android/4.4.2, Safari/8/iOS 8.1.2, Safari/8/OS X 10.10, Safari/7/OS X 10.9, Safari/7/iOS 7.1, Safari/6/iOS 6.0.1, Android/5.0.0, Chrome/42/OS X, IE/11/Win 8.1, IE/11/Win 7, Java/8u31, IE Mobile/11/Win Phone 8.1, Googlebot/Feb 2015, Firefox/37/OS X, Firefox/31.3.0 ESR/Win 7, Android/4.2.2, Android/4.0.4, Baidu/Jan 2015, Safari/5.1.9/OS X 10.6.8, Android/4.1.1, Safari/6.0.4/OS X 10.8.4, Android/4.3, OpenSSL/0.9.8y, IE/7/Vista, IE/8-10/Win 7, IE Mobile/10/Win Phone 8.0, Java/7u25, Java/6u45, Android/2.3.7, IE/8/XP 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
PORT 993 PORT 993
-------- --------
@@ -299,13 +270,13 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
* OpenSSL Heartbleed: * OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed OK - Not vulnerable to Heartbleed
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* Session Resumption: * Session Resumption:
With Session IDs: NOT SUPPORTED (0 successful, 5 failed, 0 errors, 5 total attempts). With Session IDs: NOT SUPPORTED (0 successful, 5 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: NOT SUPPORTED - TLS ticket assigned but not accepted. With TLS Session Tickets: NOT SUPPORTED - TLS ticket assigned but not accepted.
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* TLSV1_2 Cipher Suites: * TLSV1_2 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
@@ -336,9 +307,6 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
CAMELLIA128-SHA - 128 bits CAMELLIA128-SHA - 128 bits
AES128-SHA - 128 bits AES128-SHA - 128 bits
* SSLV3 Cipher Suites:
Server rejected all cipher suites.
* TLSV1 Cipher Suites: * TLSV1 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
@@ -354,9 +322,12 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
CAMELLIA128-SHA - 128 bits CAMELLIA128-SHA - 128 bits
AES128-SHA - 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 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 Could Also Offer: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA256, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-RSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-SHA256, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-RSA-AES256-SHA384
Supported Clients: OpenSSL/1.0.2, Baidu/Jan 2015, Yahoo Slurp/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, Firefox/31.3.0 ESR/Win 7, Googlebot/Feb 2015, Android/4.2.2, Android/5.0.0, Android/4.0.4, Safari/8/iOS 8.1.2, Safari/7/OS X 10.9, YandexBot/Jan 2015, Safari/8/OS X 10.10, Safari/7/iOS 7.1, Chrome/42/OS X, Safari/5.1.9/OS X 10.6.8, Android/4.1.1, Firefox/37/OS X, Safari/6.0.4/OS X 10.8.4, Android/4.3, Safari/6/iOS 6.0.1, Android/4.4.2, OpenSSL/0.9.8y, IE Mobile/11/Win Phone 8.1, IE/7/Vista, IE/11/Win 8.1, IE/11/Win 7, IE/8-10/Win 7, IE Mobile/10/Win Phone 8.0, Java/8u31, Java/7u25, Java/6u45, Android/2.3.7 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
PORT 995 PORT 995
-------- --------
@@ -370,13 +341,13 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
* OpenSSL Heartbleed: * OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed OK - Not vulnerable to Heartbleed
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* Session Resumption: * Session Resumption:
With Session IDs: NOT SUPPORTED (0 successful, 5 failed, 0 errors, 5 total attempts). With Session IDs: NOT SUPPORTED (0 successful, 5 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: NOT SUPPORTED - TLS ticket assigned but not accepted. With TLS Session Tickets: NOT SUPPORTED - TLS ticket assigned but not accepted.
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* TLSV1_2 Cipher Suites: * TLSV1_2 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
@@ -407,9 +378,6 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
CAMELLIA128-SHA - 128 bits CAMELLIA128-SHA - 128 bits
AES128-SHA - 128 bits AES128-SHA - 128 bits
* SSLV3 Cipher Suites:
Server rejected all cipher suites.
* TLSV1 Cipher Suites: * TLSV1 Cipher Suites:
Preferred: Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
@@ -425,7 +393,10 @@ _nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected rec
CAMELLIA128-SHA - 128 bits CAMELLIA128-SHA - 128 bits
AES128-SHA - 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 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 Could Also Offer: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA256, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-RSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-SHA256, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-RSA-AES256-SHA384
Supported Clients: OpenSSL/1.0.2, Baidu/Jan 2015, Yahoo Slurp/Jan 2015, BingPreview/Jan 2015, OpenSSL/1.0.1l, Firefox/31.3.0 ESR/Win 7, Googlebot/Feb 2015, Android/4.2.2, Android/5.0.0, Android/4.0.4, Safari/8/iOS 8.1.2, Safari/7/OS X 10.9, YandexBot/Jan 2015, Safari/8/OS X 10.10, Safari/7/iOS 7.1, Chrome/42/OS X, Safari/5.1.9/OS X 10.6.8, Android/4.1.1, Firefox/37/OS X, Safari/6.0.4/OS X 10.8.4, Android/4.3, Safari/6/iOS 6.0.1, Android/4.4.2, OpenSSL/0.9.8y, IE Mobile/11/Win Phone 8.1, IE/7/Vista, IE/11/Win 8.1, IE/11/Win 7, IE/8-10/Win 7, IE Mobile/10/Win Phone 8.0, Java/8u31, Java/7u25, Java/6u45, Android/2.3.7 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