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

Compare commits

..

96 Commits

Author SHA1 Message Date
Toilal
e004041de2 more dockerization work
[From @joshdata: This is part of @toilal's work in #377 and 1eb77c332b. The changes are:

* Separates out the runit configuration from starting Mail-in-a-Box setup so that Mail-in-a-Box setup does not block the starting of runit services and we can assume that runit is running during setup (i.e. we can restart services).
* Adds a SKIP_INSTALL flag so that the container can be restarted without re-running the whole Mail-in-a-Box setup.
* Made containers/docker/run more flexible.
* I'm also adding some "|| exit 0"s to the run script to stop if there are any docker errors.
* I'm also adding the prereqs installs from questions.sh into Dockerfile so we don't have to reinstall each time.

]
2015-06-18 08:05:38 -04:00
Joshua Tauberer
4eb9af2ebd simplify dockerization 2015-06-18 08:05:38 -04:00
Morteza Milani
51d89a780d Fully working docker! 2015-06-18 08:05:38 -04:00
Joshua Tauberer
299a5c6355 dockerize (work in progress)
Docker support was initially worked on in 2bbb7a5e7e, but it never really worked.

This extends f7d7434012800c3572049af82a501743d4aed583 which was an old branch for docker work.
2015-06-18 08:05:38 -04:00
Toilal
ce17c12ca2 Use netcat to check if mailinabox webservice is available
[JT added installing netcat-openbsd in system.sh]
2015-06-18 08:04:46 -04:00
Joshua Tauberer
5edaeb8c7b add a new autoconfiguration option PRIMARY_HOSTNAME=auto to simply grab the hostname from reverse DNS
drawn from 5b23a06a74.
2015-06-18 07:46:09 -04:00
Joshua Tauberer
3a28d1b073 showing the Mail-in-a-Box version using git describe was broken since dd6a8d99 2015-06-18 07:45:55 -04:00
Joshua Tauberer
6f2226bfcd move more of start.sh into questions.sh to keep start.sh cleaner and encapsulate all of the variable setting in a single script
Based on 5b23a06a74.
2015-06-18 07:38:18 -04:00
Joshua Tauberer
97cd4c64ad don't expose PHP version in the X-Powered-By header, closes #439, fixes #433 2015-06-18 11:12:03 +00:00
Joshua Tauberer
43d50d0667 Merge pull request #445 from bizonix/patch-1
fix wrong redirect for automatic www subdomain redirects
2015-06-18 07:05:01 -04:00
Joshua Tauberer
6258a7f311 status checks were broken if sshd was not present, fixes #444 2015-06-18 11:01:11 +00:00
Joshua Tauberer
ab36cc8968 whitespace=>tabs 2015-06-18 10:54:51 +00:00
bizonix
33b71c6b3c fix wrong redirect
$ curl -I https://www.site.co.il/static/images/1.png?a=b | grep Location
Location: https://site.co.il?a=b
but should be something like 
Location: https://site.co.il/static/images/1.png?a=b
2015-06-18 01:48:15 +03:00
Joshua Tauberer
34e821c102 Roundcube 1.1.2 2015-06-17 11:00:15 +00:00
Joshua Tauberer
2af557139d default IPv6 AAAA records were missing
This was broken by the ability to have multiple TXT records in 9f1d633ae4.
2015-06-17 06:47:22 -04:00
Joshua Tauberer
9e0dcd8718 security.md: add a section on DNSSEC specifically 2015-06-15 10:24:16 -04:00
Joshua Tauberer
be2b5a62de ownCloud updated to version 8.0.4 2015-06-14 16:04:07 +00:00
Joshua Tauberer
0cbba71c72 merge #429 - Move OwnCloud's config to Storage Root 2015-06-14 15:48:09 +00:00
Joshua Tauberer
d28563fb45 tweak the ownCloud config location migration (no need for third ln) 2015-06-14 15:42:32 +00:00
Norman Stanke
38632f0f90 Move OwnCloud's config to Storage Root 2015-06-12 14:53:02 +02:00
Joshua Tauberer
0754ce01b1 questions.sh needs to apt-get update before it does an apt-get install, see #431, see #438 2015-06-10 09:43:22 -04:00
Joshua Tauberer
1ef455d37d bootstrap.sh needs to apt-get update before it does an apt-get install, fixes #431 2015-06-10 09:33:47 -04:00
Joshua Tauberer
d152603abd changelog entries and mention our forks of postgrey and dovecot in the README 2015-06-10 09:27:29 -04:00
Joshua Tauberer
9e125aec00 Merge pull request #436 from bizonix/patch-1
fix loop redirecting
2015-06-07 16:30:58 -04:00
bizonix
2c90c267bd fix loop redirecting
server is redirecting the request for this address in a way that will never complete
2015-06-07 21:50:41 +03:00
Joshua Tauberer
47de93961e OCSP improvements
* Set ssl_stapling_verify to off per https://sslmate.com/blog/post/ocsp_stapling_in_apache_and_nginx ('on' has no security benefits).
* Set resolver to 127.0.0.1, instead of Google Public DNS, because we might as well use our local nameserver anyway.
* Remove the commented line which per the link above would never be necessary anyway.

OCSP seems to work just fine after these changes.
2015-06-06 23:24:09 +00:00
Joshua Tauberer
1990f32ca4 typo, fixes #435 2015-06-06 13:22:50 +00:00
Joshua Tauberer
807939c0e4 make the +tag address tips clearer 2015-06-06 13:02:23 +00:00
Joshua Tauberer
a1c7bf0883 add munin to readme 2015-06-06 12:55:13 +00:00
Joshua Tauberer
5008cc603e merge - munin system monitoring 2015-06-06 12:52:22 +00:00
Joshua Tauberer
9857db96cd add a link to the /admin/munin page from the control panel nav bar 2015-06-06 12:52:16 +00:00
Joshua Tauberer
e9e6d94e3b the control panel auth hmac message should also include the user's password so that resetting a password in the database forces that user to log in to the control panel again; also use a sha256 hmac 2015-06-06 12:38:19 +00:00
Joshua Tauberer
462a79cf47 fix what counts as a required alias, fixes #434 2015-06-06 12:12:10 +00:00
Joshua Tauberer
f792deeebd when the undocumented custom web settings has a redirect or proxy at the root of a domain, use a minimal nginx config template (same as the new default www redirects) 2015-06-04 12:32:00 +00:00
Joshua Tauberer
95173bb327 provide redirects from www subdomains of zones to their parent domain
* Split the nginx templates again so we have just the part needed to make a domain do a redirect separate from the rest.
* Add server blocks to the nginx config for these domains.
* List these domains in the SSL certificate install admin panel.
* Generate default 'www' records just for domains we provide default redirects for.

Fixes #321.
2015-06-04 12:19:01 +00:00
Joshua Tauberer
1d09e2406b refactor how the nginx config file is assembled
This doesn't change anything. Just preparation for the next commit.
2015-06-04 12:19:01 +00:00
Joshua Tauberer
c9add7a8bf if a user sets a custom A record on PRIMARY_HOSTNAME, which is ignored anyway, don't let that cause PRIMARY_HOSTNAME from being dropped from nginx.conf
Could be related to https://discourse.mailinabox.email/t/nginx-lost-admin-record-after-install-ssl-cert-problem/528.
2015-06-04 12:19:01 +00:00
Joshua Tauberer
e4caed9277 add a note in the setup script about the use of our postgrey fork and dnswl's license terms 2015-06-03 16:28:20 -04:00
Joshua Tauberer
1760eaa601 merge #406 - dovecot-lucene & packaging 2015-06-03 15:51:16 -04:00
Joshua Tauberer
b25ce67fe1 bring the postgrey patches into this repository rather than maintaining them in a separate postgrey fork repository 2015-06-03 15:50:25 -04:00
Joshua Tauberer
b23ba6f75e simplify build/setup of dovecot-lucene package 2015-06-03 15:48:35 -04:00
Joshua Tauberer
47a5a44b9e v0.10
* SMTP Submission (port 587) began offering the insecure SSLv3 protocol due to a misconfiguration in the previous version.
* Roundcube now allows persistent logins using Roundcube-Persistent-Login-Plugin.
* ownCloud is updated to version 8.0.3.
* SPF records for non-mail domains were tightened.
* The minimum greylisting delay has been reduced from 5 minutes to 3 minutes.
* Users and aliases weren't working if they were entered with any uppercase letters. Now only lowercase is allowed.
* After installing an SSL certificate from the control panel, the page wasn't being refreshed.
* Backups broke if the box's hostname was changed after installation.
* Dotfiles (i.e. .svn) stored in ownCloud Files were not accessible from ownCloud's mobile/desktop clients.
* Fix broken install on OVH VPS's.
2015-06-01 18:05:41 -04:00
Joshua Tauberer
a0e6c7ceb6 fix downloading dotfiles through ownCloud's webdav
fixes #414
2015-05-30 18:03:37 +00:00
Joshua Tauberer
49aa367ffa merge #422 - Add persistent login functionality to roundcube 2015-05-30 14:07:50 +00:00
Joshua Tauberer
83b36f2c3a simplify the roundcube updating logic, changelog entry for roundcube persistent login 2015-05-30 14:07:36 +00:00
Joshua Tauberer
2b341d884f merge #396 - allow the backup process to work after a hostname change 2015-05-30 13:55:08 +00:00
Joshua Tauberer
141a09b31e changelog, comments for duplicity --allow-source-mismatch 2015-05-30 13:46:39 +00:00
Joshua Tauberer
6378ec4bbd Merge pull request #423 from BrianZachary/master
Update README.md
2015-05-29 16:53:38 -04:00
BrianZachary
603fb1c698 Update README.md
Added latest front page appearance of Mail-In-A-Box to README.md
2015-05-29 16:43:14 -04:00
Joaquin Bravo
67b4ea947b Add persistent login functionality to roundcube 2015-05-29 14:49:40 -05:00
Joshua Tauberer
4075b7c78a Merge pull request #421 from samrobotmesh/patch-1
Echange -> Exchange
2015-05-29 10:59:25 -04:00
Sam
6499eba0cb Echange -> Exchange 2015-05-29 07:36:53 -07:00
Joshua Tauberer
980626aa40 Merge branch 'postgrey/delay/clean' of https://github.com/Xoib/mailinabox
Closes #413.
2015-05-29 13:00:51 +00:00
Eric Mill
3f329bc1a8 fix typos 2015-05-29 01:38:42 -04:00
Joshua Tauberer
69de67b1c2 link security.md from the readme 2015-05-28 21:41:23 -04:00
Joshua Tauberer
7158f9a8d9 security.md: add links to appropriate source files in various places to make it easier to inspect the code to verify the statements; unfortunately line numbers will drift but it would be nice if we could link right to line numbers 2015-05-28 21:39:50 -04:00
Joshua Tauberer
bb75bd7167 more security details 2015-05-28 21:39:50 -04:00
Joshua Tauberer
4fa58169f1 after installing an SSL certificate from the control panel the page wasn't being refreshed, broken in ec73c171c7 2015-05-28 18:45:53 +00:00
Joshua Tauberer
564040897f Merge pull request #420 from dhpiggott/increase-dmarc-and-spf-strictness
Make SPF forbid any outbound mail from non-mail domains
2015-05-28 13:17:14 -04:00
David Piggott
f78bbab289 Make SPF forbid any outbound mail from non-mail domains 2015-05-28 18:11:44 +01:00
Joshua Tauberer
d3c82d7363 Merge pull request #419 from dhpiggott/improve-dmarc-and-spf-descriptions
Improve DMARC and SPF record descriptions
2015-05-28 13:06:44 -04:00
David Piggott
7b9b978a6d Improve DMARC and SPF record descriptions 2015-05-28 16:34:58 +01:00
Joshua Tauberer
45d47818ca add changelog entry for 4f98d470a0 2015-05-28 13:12:57 +00:00
Joshua Tauberer
202c4a948b our users/aliases database is case sensitive - force new users/aliases to lowercase
Unfortunately our users/aliases database is case sensitive. (Perhaps I should have defined the columns with COLLATE NOCASE, see https://www.sqlite.org/datatype3.html.) Postfix always queries the tables in lowecase, so mail delivery would fail if a user or alias were defined with any capital letters. It would have also been possible to add multiple euqivalent addresses into the database with different case.

This commit rejects new mail users that have capital letters and forces new aliases to lowecase. I prefer to reject rather than casefold user accounts so that the login credentials the user gave are exactly what goes into the database.

https://discourse.mailinabox.email/t/recipient-address-rejected-user-unknown-in-virtual-mailbox-table/512/4
2015-05-28 13:11:30 +00:00
Joshua Tauberer
b5269bb28e Merge pull request #418 from dhpiggott/aliases-template-tweak
Use lowercase h for consistency in aliases template - it reads better…
2015-05-28 08:56:44 -04:00
David Piggott
d6c5f09a1a Use lowercase h for consistency in aliases template - it reads better (IMO!)
This also includes fixes for a typo and some whitespace inconsistencies in
mailconfig.py. In fact the capitalisation change and those fixes are the
remnants of a patch I had been running that changed the default aliases - it
was through developing it that I found the issues.

(I wanted to bring the number of patches I apply before deploying to zero and
in the case of this one I've come to view the way MIAB already is as superior,
so I've undone the core of my patch and these tiny issues are all that remain).
2015-05-28 13:46:15 +01:00
Xoib
11546b97bb softer the greylisting delay restriction
A lot of legit mail servers try again between 200 and 285 seconds, then
3 hours later. Why? RFC is not strict about retry timer so postfix and
other MTA have their own intervals. To fix the problem of receiving
these e-mail really latter, I reduced the delay of postgrey to
180 seconds (default is 300 seconds).
2015-05-26 16:10:14 +02:00
Joshua Tauberer
cac6a251cc Merge pull request #411 from nstanke/munin
remove unnecessary source call
2015-05-26 07:12:54 -04:00
Norman Stanke
31d26a7bad remove unnecessary source call 2015-05-26 13:06:50 +02:00
Joshua Tauberer
a9ed9ae936 more work on munin
* install the munin-node package
* don't install munin-plugins-extra (if the user wants it they can add it)
* expose the munin www directory via the management daemon so that it can handle authorization, rather than manintaining a separate password file
2015-05-25 17:03:52 +00:00
Joshua Tauberer
a9892efe38 Merge branch 'master' of https://github.com/nstanke/mailinabox into munin 2015-05-25 16:03:45 +00:00
Brock Tice
f02e0a3ccb Fixed comment that still referenced solr. 2015-05-25 08:50:41 -06:00
Joshua Tauberer
d6f26609fc Merge pull request #410 from stevesbrain/master
Fixing minor misspelling of the word: encrypted
2015-05-24 22:12:16 -04:00
StevesMonkey
05438d047d Fixing minor misspelling of the word: encrypted 2015-05-25 10:15:57 +09:30
Brock Tice
32f5632620 Switch to official PPA 2015-05-23 20:09:50 -04:00
Brock Tice
005cc08b40 Modified makefile to work with updated debian-lucene patch, use official locations.
Fixed conflicting edits to ppa/Makefile due to cherry-pick.
2015-05-23 20:09:06 -04:00
Brock Tice
6a659fe10d Fixed patches for pull request.
Resolved conflict between two patches.
2015-05-23 20:07:33 -04:00
Brock Tice
6941ca2f63 Added apt-get update before installing dovecot-lucene 2015-05-23 20:01:58 -04:00
Brock Tice
e4eba49c1b Added lucene.sh to start script. 2015-05-23 20:01:45 -04:00
Brock Tice
f289439d1d Adapted Jonty's original solr.sh script to instead set up lucene full-text search in dovecot. 2015-05-23 20:01:32 -04:00
Joshua Tauberer
a5ef64919a ppa: build the dovecot-lucene package 2015-05-23 14:03:14 -04:00
Joshua Tauberer
e132125cf3 ppa: move build to /tmp 2015-05-23 12:55:55 -04:00
Joshua Tauberer
01b5512ac7 add @Jonty's .deb patch for building dovecot lucene 2015-05-23 12:14:31 -04:00
Joshua Tauberer
a0c7e63d78 best guess at what clients are supported by the tls settings used 2015-05-22 17:36:55 -04:00
Joshua Tauberer
8ba5f2ffa7 add security.md and clean up README 2015-05-22 16:53:13 -04:00
Joshua Tauberer
2c44333679 compare tls ciphers against Mozilla's recommendations 2015-05-20 19:41:04 -04:00
Joshua Tauberer
610be9cf17 record current TLS settings from my box 2015-05-20 18:31:46 -04:00
Joshua Tauberer
eb5e8fe388 the switch of smtpd_tls_security_level may to encrypt for submission broke smtpd_tls_protocols
The submission port began offering SSLv3.

With `encrypt`, the smtpd_tls_protocols option is ignored and smtpd_tls_mandatory_protocols must be set instead.

see e39b777abc
2015-05-20 22:27:11 +00:00
Joshua Tauberer
c999c6082f tweak unhackable language, see #402 2015-05-19 11:18:53 -04:00
Joshua Tauberer
3b86b3fe66 bump to email_validator 0.1.0-rc5 2015-05-19 08:37:17 -04:00
Joshua Tauberer
0a71dca825 add preliminary build tools for a Mail-in-a-Box PPA
Starting with my dnswl.org modifications to postgrey.
2015-05-18 22:15:09 -04:00
Joshua Tauberer
4f98d470a0 '/dev/stdout' does not exist on some systems (!)
The OVH VPS provider creates systems without /dev/stdout. I have never seen that before. But fine. We were passing it as a command line option to `openssl req`, but outputting to stdout is the default so it's not necessary to specify /dev/stdout.

Fixes #277. Also https://discourse.mailinabox.email/t/500-internal-server-error/475/10.
2015-05-16 13:34:47 +00:00
Joshua Tauberer
57abae3999 if the main ssl cert is expiring soon, the end of setup would display the control panel instructions as if the cert were self-signed 2015-05-14 19:16:31 +00:00
Xoib
202e49a897 allow the backup process to work after a hostname change 2015-05-13 13:52:23 +02:00
Joshua Tauberer
13093f1732 update to ownCloud 8.0.3
see #375
2015-05-11 13:00:40 +00:00
Norman
f78cff225b Add Munin
removed testing source

fixed typo & dns

oh cat

more fixes

forgot root

more nginx stuff

nginx munin.conf fix

more fixes

set dns record
2015-01-28 21:42:16 +01:00
49 changed files with 2367 additions and 310 deletions

View File

@@ -1,6 +1,46 @@
CHANGELOG
=========
In Development
--------------
Mail:
* Greylisting will now let some reputable senders pass through immediately.
* Searching mail (via IMAP) will now be much faster using the dovecot lucene full text search plugin.
* Fix for deleting admin@ and postmaster@ addresses.
* Roundcube is updated to version 1.1.2, plugins updated.
Web:
* 'www' subdomains now automatically redirect to their parent domain (but you'll need to install an SSL certificate).
* OCSP no longer uses Google Public DNS.
DNS:
* Default IPv6 AAAA records were missing since version 0.09.
Control panel:
* Resetting a user's password now forces them to log in again everywhere.
* Status checks were not working if an ssh server was not installed.
System:
* The munin system monitoring tool is now installed and accessible at /admin/munin.
* ownCloud updated to version 8.0.4.
v0.10 (June 1, 2015)
--------------------
* SMTP Submission (port 587) began offering the insecure SSLv3 protocol due to a misconfiguration in the previous version.
* Roundcube now allows persistent logins using Roundcube-Persistent-Login-Plugin.
* ownCloud is updated to version 8.0.3.
* SPF records for non-mail domains were tightened.
* The minimum greylisting delay has been reduced from 5 minutes to 3 minutes.
* Users and aliases weren't working if they were entered with any uppercase letters. Now only lowercase is allowed.
* After installing an SSL certificate from the control panel, the page wasn't being refreshed.
* Backups broke if the box's hostname was changed after installation.
* Dotfiles (i.e. .svn) stored in ownCloud Files were not accessible from ownCloud's mobile/desktop clients.
* Fix broken install on OVH VPS's.
v0.09 (May 8, 2015)
-------------------

57
Dockerfile Normal file
View File

@@ -0,0 +1,57 @@
# Mail-in-a-Box Dockerfile
###########################
#
# This file lets Mail-in-a-Box run inside of Docker (https://docker.io),
# a virtualization/containerization manager.
#
# Run:
# $ containers/docker/run.sh
# to build the image, launch a storage container, and launch a Mail-in-a-Box
# container.
#
###########################################
# We need a better starting image than docker's ubuntu image because that
# base image doesn't provide enough to run most Ubuntu services. See
# http://phusion.github.io/baseimage-docker/ for an explanation.
FROM phusion/baseimage:0.9.16
# Dockerfile metadata.
MAINTAINER Joshua Tauberer (http://razor.occams.info)
EXPOSE 25 53/udp 53/tcp 80 443 587 993 4190
VOLUME /home/user-data
# Use baseimage's init system. A correct init process is required for
# process #1 in order to have a functioning Linux system.
CMD ["/sbin/my_init"]
# Create the user-data user, so the start script doesn't have to.
RUN useradd -m user-data
# Docker has a beautiful way to cache images after each step. The next few
# steps of installing system packages are very intensive, so we take care
# of them early and let docker cache the image after that, before doing
# any Mail-in-a-Box specific system configuration. That makes rebuilds
# of the image extremely fast.
# Update system packages.
RUN apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get upgrade -y
# Install packages needed by Mail-in-a-Box.
ADD containers/docker/apt_package_list.txt /tmp/mailinabox_apt_package_list.txt
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y $(cat /tmp/mailinabox_apt_package_list.txt)
# from questions.sh -- needs merging into the above line
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y dialog python3 python3-pip
RUN pip3 install "email_validator==0.1.0-rc4"
# Now add Mail-in-a-Box to the system.
ADD . /usr/local/mailinabox
# Configure runit services.
RUN /usr/local/mailinabox/containers/docker/tools/configure_services.sh
# Add my_init scripts
ADD containers/docker/my_init.d/* /etc/my_init.d/

View File

@@ -14,24 +14,39 @@ I am trying to:
* Make deploying a good mail server easy.
* Promote [decentralization](http://redecentralize.org/), innovation, and privacy on the web.
* Have automated, auditable, and [idempotent](http://sharknet.us/2014/02/01/automated-configuration-management-challenges-with-idempotency/) configuration.
* **Not** be a mail server that the NSA cannot hack.
* **Not** be customizable by power users.
The long-term goal is to have this be a one-click email appliance with *no* user-configurable setup options.
For more background, see [The Rationale](https://github.com/mail-in-a-box/mailinabox/wiki).
* **Not** make a totally unhackable, NSA-proof server.
* **Not** make something customizable by power users.
This setup is what has been powering my own personal email since September 2013.
The Box
-------
Mail-in-a-Box turns a fresh Ubuntu 14.04 LTS 64-bit machine into a working mail server, including SMTP ([postfix](http://www.postfix.org/)), IMAP ([dovecot](http://dovecot.org/)), Exchange ActiveSync ([z-push](https://github.com/fmbiete/Z-Push-contrib)), webmail ([Roundcube](http://roundcube.net/)), spam filtering ([spamassassin](https://spamassassin.apache.org/)), greylisting ([postgrey](http://postgrey.schweikert.ch/)), CardDAV/CalDAV ([ownCloud](http://owncloud.org/)), DNS, [SPF](https://en.wikipedia.org/wiki/Sender_Policy_Framework), DKIM ([OpenDKIM](http://www.opendkim.org/)), [DMARC](https://en.wikipedia.org/wiki/DMARC), [DNSSEC](https://en.wikipedia.org/wiki/DNSSEC), [DANE TLSA](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities), [SSHFP](https://tools.ietf.org/html/rfc4255), and basic system services like a firewall, intrusion protection, and setting the system clock.
Mail-in-a-Box turns a fresh Ubuntu 14.04 LTS 64-bit machine into a working mail server by installing and configuring various components.
Authenticity
It is a one-click email appliance (see the [setup guide](https://mailinabox.email/guide.html)). There are no user-configurable setup options. It "just works".
The components installed are:
* SMTP ([postfix](http://www.postfix.org/)), IMAP ([dovecot](http://dovecot.org/)), CardDAV/CalDAV ([ownCloud](http://owncloud.org/)), Exchange ActiveSync ([z-push](https://github.com/fmbiete/Z-Push-contrib))
* Webmail ([Roundcube](http://roundcube.net/)), static website hosting ([nginx](http://nginx.org/))
* Spam filtering ([spamassassin](https://spamassassin.apache.org/)), greylisting ([postgrey](http://postgrey.schweikert.ch/))
* DNS ([nsd4](http://www.nlnetlabs.nl/projects/nsd/)) with [SPF](https://en.wikipedia.org/wiki/Sender_Policy_Framework), DKIM ([OpenDKIM](http://www.opendkim.org/)), [DMARC](https://en.wikipedia.org/wiki/DMARC), [DNSSEC](https://en.wikipedia.org/wiki/DNSSEC), [DANE TLSA](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities), and [SSHFP](https://tools.ietf.org/html/rfc4255) records automatically set
* Firewall ([ufw](https://launchpad.net/ufw)), intrusion protection ([fail2ban](http://www.fail2ban.org/wiki/index.php/Main_Page)), system monitoring ([munin](http://munin-monitoring.org/))
It also includes:
* A control panel and API for adding/removing mail users, aliases, custom DNS records, etc. and detailed system monitoring.
* Our own builds of postgrey and dovecot-lucene distributed via the [Mail-in-a-Box PPA](https://launchpad.net/~mail-in-a-box/+archive/ubuntu/ppa) on Launchpad.
For more information on how Mail-in-a-Box handles your privacy, see the [security details page](security.md).
The Security
------------
I sign the release tags. To verify that a tag is signed by me, you can perform the following steps:
See the [security guide](security.md) for more information about the box's security configuration (TLS, password storage, etc).
I sign the release tags on git. To verify that a tag is signed by me, you can perform the following steps:
# Download my PGP key.
$ curl -s https://keybase.io/joshdata/key.asc | gpg --import
@@ -42,7 +57,7 @@ I sign the release tags. To verify that a tag is signed by me, you can perform t
$ cd mailinabox
# Verify the tag.
$ git verify-tag v0.08
$ git verify-tag v0.10
gpg: Signature made ..... using RSA key ID C10BDD81
gpg: Good signature from "Joshua Tauberer <jt@occams.info>"
gpg: WARNING: This key is not certified with a trusted signature!
@@ -62,5 +77,6 @@ The History
-----------
* In 2007 I wrote a relatively popular Mozilla Thunderbird extension that added client-side SPF and DKIM checks to mail to warn users about possible phishing: [add-on page](https://addons.mozilla.org/en-us/thunderbird/addon/sender-verification-anti-phish/), [source](https://github.com/JoshData/thunderbird-spf).
* In August 2013 I began Mail-in-a-Box by combining my own mail server configuration with the setup in ["NSA-proof your email in 2 hours"](http://sealedabstract.com/code/nsa-proof-your-e-mail-in-2-hours/) and making the setup steps reproducible with bash scripts.
* Mail-in-a-Box was a semifinalist in the 2014 [Knight News Challenge](https://www.newschallenge.org/challenge/2014/submissions/mail-in-a-box), but it was not selected as a winner.
* Mail-in-a-Box hit the front page of Hacker News in [April](https://news.ycombinator.com/item?id=7634514) and [September](https://news.ycombinator.com/item?id=8276171) 2014.
* Mail-in-a-Box hit the front page of Hacker News in [April](https://news.ycombinator.com/item?id=7634514) 2014, [September](https://news.ycombinator.com/item?id=8276171) 2014, and [May](https://news.ycombinator.com/item?id=9624267) 2015.

View File

@@ -0,0 +1,79 @@
# Expose this directory as static files.
root $ROOT;
index index.html index.htm;
location = /robots.txt {
log_not_found off;
access_log off;
}
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /mailinabox.mobileconfig {
alias /var/lib/mailinabox/mobileconfig.xml;
}
location = /.well-known/autoconfig/mail/config-v1.1.xml {
alias /var/lib/mailinabox/mozilla-autoconfig.xml;
}
# Roundcube Webmail configuration.
rewrite ^/mail$ /mail/ redirect;
rewrite ^/mail/$ /mail/index.php;
location /mail/ {
index index.php;
alias /usr/local/lib/roundcubemail/;
}
location ~ /mail/config/.* {
# A ~-style location is needed to give this precedence over the next block.
return 403;
}
location ~ /mail/.*\.php {
# note: ~ has precendence over a regular location block
include fastcgi_params;
fastcgi_split_path_info ^/mail(/.*)()$;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /usr/local/lib/roundcubemail/$fastcgi_script_name;
fastcgi_pass php-fpm;
# Outgoing mail also goes through this endpoint, so increase the maximum
# file upload limit to match the corresponding Postfix limit.
client_max_body_size 128M;
}
# Z-Push (Microsoft Exchange ActiveSync)
location /Microsoft-Server-ActiveSync {
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/local/lib/z-push/index.php;
fastcgi_param PHP_VALUE "include_path=.:/usr/share/php:/usr/share/pear:/usr/share/awl/inc";
fastcgi_read_timeout 630;
fastcgi_pass php-fpm;
# Outgoing mail also goes through this endpoint, so increase the maximum
# file upload limit to match the corresponding Postfix limit.
client_max_body_size 128M;
}
location /autodiscover/autodiscover.xml {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/local/lib/z-push/autodiscover/autodiscover.php;
fastcgi_param PHP_VALUE "include_path=.:/usr/share/php:/usr/share/pear:/usr/share/awl/inc";
fastcgi_pass php-fpm;
}
# ADDITIONAL DIRECTIVES HERE
# Disable viewing dotfiles (.htaccess, .svn, .git, etc.)
# This block is placed at the end. Nginx's precedence rules means this block
# takes precedence over all non-regex matches and only regex matches that
# come after it (i.e. none of those, since this is the last one.) That means
# we're blocking dotfiles in the static hosted sites but not the FastCGI-
# handled locations for ownCloud (which serves user-uploaded files that might
# have this pattern, see #414) or some of the other services.
location ~ /\.(ht|svn|git|hg|bzr) {
log_not_found off;
access_log off;
deny all;
}

View File

@@ -2,6 +2,7 @@
# Proxy /admin to our Python based control panel daemon. It is
# listening on IPv4 only so use an IP address and not 'localhost'.
rewrite ^/admin$ /admin/;
rewrite ^/admin/munin$ /admin/munin/ redirect;
location /admin/ {
proxy_pass http://127.0.0.1:10222/;
proxy_set_header X-Forwarded-For $remote_addr;
@@ -24,12 +25,13 @@
# /cloud/index.php/apps/files/
# /cloud/index.php/apps/files/ajax/scan.php (it's really index.php; see 6fdef379adfdeac86cc2220209bdf4eb9562268d)
# /cloud/ocs/v1.php/apps/files_sharing/api/v1 (see #240)
# /cloud/remote.php/webdav/yourfilehere...
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/local/lib/owncloud/$2;
fastcgi_param SCRIPT_NAME $1$2;
fastcgi_param PATH_INFO $3;
# TODO: see the dispreferred "method 2" for xaccel at https://doc.owncloud.org/server/8.1/admin_manual/configuration_files/serving_static_files_configuration.html
fastcgi_param MOD_X_ACCEL_REDIRECT_ENABLED on;
fastcgi_param MOD_X_ACCEL_REDIRECT_PREFIX /owncloud-xaccel;
fastcgi_read_timeout 630;
fastcgi_pass php-fpm;
error_page 403 /cloud/core/templates/403.php;
@@ -37,15 +39,13 @@
client_max_body_size 1G;
fastcgi_buffers 64 4K;
}
location ^~ $STORAGE_ROOT/owncloud {
# This directory is for MOD_X_ACCEL_REDIRECT_ENABLED. It works a little weird.
# The full path on disk of the file is passed as the URL path. ownCloud 8 totally
# busted the sane way this worked in ownCloud 7. There's a pending change using
# a new parameter to make this make more sense.
# We need to only allow 'internal' redirects within nginx so that the filesystem
location ^~ /owncloud-xaccel/ {
# This directory is for MOD_X_ACCEL_REDIRECT_ENABLED. ownCloud sends the full file
# path on disk as a subdirectory under this virtual path.
# We must only allow 'internal' redirects within nginx so that the filesystem
# is not exposed to the world.
internal;
alias $STORAGE_ROOT/owncloud;
alias /;
}
location ~ ^/((caldav|carddav|webdav).*)$ {
# Z-Push doesn't like getting a redirect, and a plain rewrite didn't work either.
@@ -57,3 +57,4 @@
rewrite ^/.well-known/carddav /cloud/remote.php/carddav/ redirect;
rewrite ^/.well-known/caldav /cloud/remote.php/caldav/ redirect;
# ADDITIONAL DIRECTIVES HERE

View File

@@ -69,7 +69,6 @@ ssl_dhparam STORAGE_ROOT/ssl/dh2048.pem;
# 8.8.8.8 and 8.8.4.4 below are Google's public IPv4 DNS servers.
# nginx will use them to talk to the CA.
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=86400;
ssl_stapling_verify off;
resolver 127.0.0.1 valid=86400;
resolver_timeout 10;
#ssl_trusted_certificate /path/to/all-certs-in-chain.crt;

View File

@@ -33,77 +33,5 @@ server {
ssl_certificate_key $SSL_KEY;
include /etc/nginx/nginx-ssl.conf;
# Expose this directory as static files.
root $ROOT;
index index.html index.htm;
location = /robots.txt {
log_not_found off;
access_log off;
}
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /mailinabox.mobileconfig {
alias /var/lib/mailinabox/mobileconfig.xml;
}
location = /.well-known/autoconfig/mail/config-v1.1.xml {
alias /var/lib/mailinabox/mozilla-autoconfig.xml;
}
# Disable viewing dotfiles (.htaccess, .svn, .git, etc.)
location ~ /\.(ht|svn|git|hg|bzr) {
log_not_found off;
access_log off;
deny all;
}
# Roundcube Webmail configuration.
rewrite ^/mail$ /mail/ redirect;
rewrite ^/mail/$ /mail/index.php;
location /mail/ {
index index.php;
alias /usr/local/lib/roundcubemail/;
}
location ~ /mail/config/.* {
# A ~-style location is needed to give this precedence over the next block.
return 403;
}
location ~ /mail/.*\.php {
# note: ~ has precendence over a regular location block
include fastcgi_params;
fastcgi_split_path_info ^/mail(/.*)()$;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /usr/local/lib/roundcubemail/$fastcgi_script_name;
fastcgi_pass php-fpm;
# Outgoing mail also goes through this endpoint, so increase the maximum
# file upload limit to match the corresponding Postfix limit.
client_max_body_size 128M;
}
# Z-Push (Microsoft Exchange ActiveSync)
location /Microsoft-Server-ActiveSync {
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/local/lib/z-push/index.php;
fastcgi_param PHP_VALUE "include_path=.:/usr/share/php:/usr/share/pear:/usr/share/awl/inc";
fastcgi_read_timeout 630;
fastcgi_pass php-fpm;
# Outgoing mail also goes through this endpoint, so increase the maximum
# file upload limit to match the corresponding Postfix limit.
client_max_body_size 128M;
}
location /autodiscover/autodiscover.xml {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/local/lib/z-push/autodiscover/autodiscover.php;
fastcgi_param PHP_VALUE "include_path=.:/usr/share/php:/usr/share/pear:/usr/share/awl/inc";
fastcgi_pass php-fpm;
}
# ADDITIONAL DIRECTIVES HERE
}

View File

@@ -0,0 +1,86 @@
bc
bind9
ca-certificates
coreutils
cron
curl
dbconfig-common
dovecot-antispam
dovecot-core
dovecot-imapd
dovecot-lmtpd
dovecot-lucene
dovecot-managesieved
dovecot-pop3d
dovecot-sieve
dovecot-sqlite
duplicity
fail2ban
git
haveged
ldnsutils
libapr1
libawl-php
libcurl4-openssl-dev
libjs-jquery
libjs-jquery-mousewheel
libmagic1
libtool
libyaml-dev
links
memcached
munin
munin-node
nginx
nsd
ntp
opendkim
opendkim-tools
opendmarc
openssh-client
openssl
php-apc
php-auth
php-crypt-gpg
php-mail-mime
php-net-sieve
php-net-smtp
php-net-socket
php-pear
php-soap
php-xml-parser
php5
php5-cli
php5-common
php5-curl
php5-dev
php5-fpm
php5-gd
php5-imap
php5-intl
php5-json
php5-mcrypt
php5-memcache
php5-pspell
php5-sqlite
php5-xsl
postfix
postfix-pcre
postgrey
python3
python3-dateutil
python3-dev
python3-dnspython
python3-flask
python3-pip
pyzor
razor
resolvconf
spampd
sqlite3
sudo
tinymce
ufw
unattended-upgrades
unzip
wget

View File

@@ -0,0 +1,58 @@
#!/bin/bash
# This script is used within containers to turn it into a Mail-in-a-Box.
# It is referenced by the Dockerfile. You should not run it directly.
########################################################################
# Local configuration details were not known at the time the Docker
# image was created, so all setup is defered until the container
# is started. That's when this script runs.
# If we're not in an interactive shell, set defaults.
if [ ! -t 0 ]; then
echo '*** Non interactive shell detected...'
export PUBLIC_IP=auto
export PUBLIC_IPV6=auto
export PRIMARY_HOSTNAME=auto
export CSR_COUNTRY=US
export NONINTERACTIVE=1
fi
if ([ -z "$FORCE_INSTALL" ] && [ -f /var/lib/mailinabox/api.key ]); then
# Mailinabox is already installed and we don't want to reinstall
export SKIP_INSTALL=1
fi
# If we are skipping install, reload from /etc/mailinabox.conf if exists
if ([ -f /var/lib/mailinabox/api.key ] && [ ! -z "$SKIP_INSTALL" ]); then
echo '*** Loading variables from "/etc/mailinabox.conf"...'
source /etc/mailinabox.conf
unset PRIVATE_IP
unset PRIVATE_IPV6
export SKIP_NETWORK_CHECKS=1
export NONINTERACTIVE=1
fi
export DISABLE_FIREWALL=1
cd /usr/local/mailinabox
if [ -z "$SKIP_INSTALL" ]; then
echo "*** Starting mailinabox installation..."
# Run in background to avoid blocking runit initialization while installing.
source setup/start.sh &
else
echo "*** Configuring mailinabox..."
# Run in foreground for services to be started after configuration is re-written.
source setup/questions.sh
cat > /etc/mailinabox.conf << EOF;
STORAGE_USER=$STORAGE_USER
STORAGE_ROOT=$STORAGE_ROOT
PRIMARY_HOSTNAME=$PRIMARY_HOSTNAME
PUBLIC_IP=$PUBLIC_IP
PUBLIC_IPV6=$PUBLIC_IPV6
PRIVATE_IP=$PRIVATE_IP
PRIVATE_IPV6=$PRIVATE_IPV6
CSR_COUNTRY=$CSR_COUNTRY
EOF
fi

114
containers/docker/run Executable file
View File

@@ -0,0 +1,114 @@
#!/bin/bash
# Use this script to launch Mail-in-a-Box within a docker container.
# ==================================================================
#
# Run this script from the base directory of the Mail-in-a-Box
# repository (i.e. run as './containers/docker/run').
#
# Set these optional environment variables as needed:
# * HOST_HTTP_PORT: Host http: port to bind (default: 80).
# * HOST_HTTPS_PORT: Host https: port to bind (default: 443).
# * SKIP_BUILD: Skip the build of docker image (default: unset).
# * NODNS: Skip mapping of DNS ports (53 tcp/upd). They are not always available on host, as another DNS server can be running (default: unset).
# * CONTAINER_NAME: Name of the main container (default: mailinabox).
# * CONTAINER_DATA_NAME: Name of the data container (default: mailinabox-data).
# * NONINTERACTIVE: Use this when mailinabox is already installed on the volume container. Else, it's not recommanded (default: unset).
#
# A base image is created first. The base image installs Ubuntu
# packages and pulls in the Mail-in-a-Box source code. This is
# defined in Dockerfile at the root of this repository.
#
# A mailinabox-data container is created next. This container
# contains nothing but a shared volume for storing user data.
# It is segregated from the rest of the live system to make backups
# easier.
#
# The mailinabox container is started last. It is the
# real thing: it runs the mailinabox image. This container will
# initialize itself and will initialize the mailinabox-data
# volume if the volume is new.
# Build or rebuild the image.
# Rebuilds are very fast.
HOST_HTTP_PORT=${HOST_HTTP_PORT:-80}
HOST_HTTPS_PORT=${HOST_HTTPS_PORT:-443}
CONTAINER_NAME=${CONTAINER_NAME:-mailinabox}
CONTAINER_DATA_NAME=${CONTAINER_DATA_NAME:-${CONTAINER_NAME}-data}
if [ -z "$SKIP_BUILD" ]; then
tput setaf 2
echo "Building/updating base image (mailinabox)..."
tput setaf 7
docker build -q -t mailinabox . || exit 1
fi;
if ! docker inspect ${CONTAINER_DATA_NAME} > /dev/null; then
tput setaf 2
echo
echo "Creating a new container for your data (${CONTAINER_DATA_NAME})..."
tput setaf 7
docker create \
--name ${CONTAINER_DATA_NAME} \
-v /home/user-data \
phusion/baseimage:0.9.16 || exit 1
else
tput setaf 2
echo
echo "Using existing container ${CONTAINER_DATA_NAME} for your data."
tput setaf 7
fi
# End a running container.
if docker inspect ${CONTAINER_NAME} > /dev/null; then
tput setaf 2
echo
echo "Destroying ${CONTAINER_NAME} container..."
tput setaf 7
docker rm -f ${CONTAINER_NAME}
fi
# Start container.
tput setaf 2
echo
echo "Starting new container (${CONTAINER_NAME})..."
tput setaf 7
# Run the services container
# detached if NONINTERACTIVE is set,
# interactively if NONINTERACTIVE is not set,
# Notes:
# * Passing through SKIP_NETWORK_CHECKS makes it easier to do testing
# on a residential network.
# * --privileged flag cause an issue with bind9/named failing to start in this case
# see docker/docker#7318
docker run \
-v /dev/urandom:/dev/random \
-p 25:25 \
$([ -z "$NODNS" ] && echo "-p 53:53/udp -p 53:53/tcp") \
-p $HOST_HTTP_PORT:80 \
-p $HOST_HTTPS_PORT:443 \
-p 587:587 \
-p 993:993 \
-p 4190:4190 \
--name ${CONTAINER_NAME} \
--volumes-from ${CONTAINER_DATA_NAME} \
--restart always \
$([ ! -z "$NONINTERACTIVE" ] && echo "-d") \
-it \
-e "IS_DOCKER=1" \
-e "SKIP_NETWORK_CHECKS=$SKIP_NETWORK_CHECKS" \
mailinabox \
|| exit 1
if [ -z "$NONINTERACTIVE" ]; then
tput setaf 2
echo
echo "Restarting container ${CONTAINER_NAME}..."
tput setaf 7
docker restart ${CONTAINER_NAME} || exit 1
fi

View File

@@ -0,0 +1,3 @@
#!/bin/bash
/usr/sbin/dovecot -F -c /etc/dovecot/dovecot.conf &> /var/log/dovecot.log

View File

@@ -0,0 +1,103 @@
#!/bin/bash
# The phusion/baseimage base image we use for a working Ubuntu
# replaces the normal Upstart system service management with
# a ligher-weight service management system called runit that
# requires a different configuration. We need to create service
# run files that do not daemonize.
# This removes /etc/init.d service if service exists in runit.
# It also creates a symlink from /usr/bin/sv to /etc/init.d/$service
# to support SysV syntax: service $service <command> or /etc/init.d/$service <command>
SERVICES=/etc/service/*
for f in $SERVICES
do
service=$(basename "$f")
if [ -d /etc/service/$service ]; then
if [ -f /etc/init.d/$service ]; then
mv /etc/init.d/$service /etc/init.d/$service.lsb
chmod -x /etc/init.d/$service.lsb
fi
ln -s /usr/bin/sv /etc/init.d/$service
fi
done
# Create runit services from sysv services. For most of the services,
# there is a common pattern we can use: execute the init.d script that
# the Ubuntu package installs, and then poll for the termination of
# the daemon.
function make_runit_service {
INITD_NAME=$1
WAIT_ON_PROCESS_NAME=$2
mkdir -p /etc/service/$INITD_NAME
cat > /etc/service/$INITD_NAME/run <<EOF;
#!/bin/bash
source /usr/local/mailinabox/setup/functions.sh
hide_output /etc/init.d/$INITD_NAME restart
while [ \`ps a -C $WAIT_ON_PROCESS_NAME -o pid= | wc -l\` -gt 0 ]; do
sleep 30
done
echo $WAIT_ON_PROCESS_NAME died.
sleep 20
EOF
chmod +x /etc/service/$INITD_NAME/run
}
make_runit_service bind9 named
make_runit_service resolvconf resolvconf
make_runit_service fail2ban fail2ban
make_runit_service mailinabox mailinabox-daemon
make_runit_service memcached memcached
make_runit_service nginx nginx
make_runit_service nsd nsd
make_runit_service opendkim opendkim
make_runit_service opendmarc opendmarc
make_runit_service php5-fpm php5-fpm
make_runit_service postfix postfix
make_runit_service postgrey postgrey
make_runit_service spampd spampd
# Dovecot doesn't provide an init.d script, but it does provide
# a way to launch without daemonization. We wrote a script for
# that specifically.
for service in dovecot; do
mkdir -p /etc/service/$service
cp /usr/local/mailinabox/containers/docker/runit/$service.sh /etc/service/$service/run
chmod +x /etc/service/$service/run
done
# This adds a log/run file on each runit service directory.
# This file make services stdout/stderr output to svlogd log
# directory located in /var/log/runit/$service.
SERVICES=/etc/service/*
for f in $SERVICES
do
service=$(basename "$f")
if [ -d /etc/service/$service ]; then
mkdir -p /etc/service/$service/log
cat > /etc/service/$service/log/run <<EOF;
#!/bin/bash
mkdir -p /var/log/runit
chmod o-wrx /var/log/runit
mkdir -p /var/log/runit/$service
chmod o-wrx /var/log/runit/$service
exec svlogd -tt /var/log/runit/$service/
EOF
chmod +x /etc/service/$service/log/run
fi
done
# Disable services for now. Until Mail-in-a-Box is installed the
# services won't be configured right and there would be errors if
# they got run prematurely.
SERVICES=/etc/service/*
for f in $SERVICES
do
service=$(basename "$f")
if [ "$service" = "syslog-ng" ]; then continue; fi;
if [ "$service" = "syslog-forwarder" ]; then continue; fi;
if [ "$service" = "ssh" ]; then continue; fi;
if [ "$service" = "cron" ]; then continue; fi;
if ([ -d /etc/service/$service ] && [ ! -f /etc/service/$service/down ]); then
touch /etc/service/$service/down
fi
done

View File

@@ -88,8 +88,9 @@ class KeyAuthService:
if email == "" or pw == "":
raise ValueError("Enter an email address and password.")
# The password might be a user-specific API key.
if hmac.compare_digest(self.create_user_key(email), pw):
# The password might be a user-specific API key. create_user_key raises
# a ValueError if the user does not exist.
if hmac.compare_digest(self.create_user_key(email, env), pw):
# OK.
pass
else:
@@ -111,18 +112,26 @@ class KeyAuthService:
# Login failed.
raise ValueError("Invalid password.")
# Get privileges for authorization. This call should never fail on a valid user,
# but if the caller passed a user-specific API key then the user may no longer
# exist --- in that case, get_mail_user_privileges will return a tuple of an
# error message and an HTTP status code.
# Get privileges for authorization. This call should never fail because by this
# point we know the email address is a valid user. But on error the call will
# return a tuple of an error message and an HTTP status code.
privs = get_mail_user_privileges(email, env)
if isinstance(privs, tuple): raise ValueError(privs[0])
# Return a list of privileges.
return privs
def create_user_key(self, email):
return hmac.new(self.key.encode('ascii'), b"AUTH:" + email.encode("utf8"), digestmod="sha1").hexdigest()
def create_user_key(self, email, env):
# Store an HMAC with the client. The hashed message of the HMAC will be the user's
# email address & hashed password and the key will be the master API key. The user of
# course has their own email address and password. We assume they do not have the master
# API key (unless they are trusted anyway). The HMAC proves that they authenticated
# with us in some other way to get the HMAC. Including the password means that when
# a user's password is reset, the HMAC changes and they will correctly need to log
# in to the control panel again. This method raises a ValueError if the user does
# not exist, due to get_mail_password.
msg = b"AUTH:" + email.encode("utf8") + b" " + get_mail_password(email, env).encode("utf8")
return hmac.new(self.key.encode('ascii'), msg, digestmod="sha256").hexdigest()
def _generate_key(self):
raw_key = os.urandom(32)

View File

@@ -148,7 +148,7 @@ def perform_backup(full_backup):
old_backup_dir = os.path.join(backup_root, 'duplicity')
migrated_unencrypted_backup_dir = os.path.join(env["STORAGE_ROOT"], "migrated_unencrypted_backup")
if os.path.isdir(old_backup_dir):
# Move the old unencrpyted files to a new location outside of
# Move the old unencrypted files to a new location outside of
# the backup root so they get included in the next (new) backup.
# Then we'll delete them. Also so that they do not get in the
# way of duplicity doing a full backup on the first run after
@@ -180,8 +180,9 @@ def perform_backup(full_backup):
if len(passphrase) < 43: raise Exception("secret_key.txt's first line is too short!")
env_with_passphrase = { "PASSPHRASE" : passphrase }
# Update the backup mirror directory which mirrors the current
# 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
# after the first backup. See #396.
try:
shell('check_call', [
"/usr/bin/duplicity",
@@ -191,7 +192,8 @@ def perform_backup(full_backup):
"--volsize", "250",
"--gpg-options", "--cipher-algo=AES256",
env["STORAGE_ROOT"],
"file://" + backup_dir
"file://" + backup_dir,
"--allow-source-mismatch"
],
env_with_passphrase)
finally:

View File

@@ -4,7 +4,7 @@ import os, os.path, re, json
from functools import wraps
from flask import Flask, request, render_template, abort, Response
from flask import Flask, request, render_template, abort, Response, send_from_directory
import auth, utils
from mailconfig import get_mail_users, get_mail_users_ex, get_admins, add_mail_user, set_mail_password, remove_mail_user
@@ -118,7 +118,7 @@ def me():
# Is authorized as admin? Return an API key for future use.
if "admin" in privs:
resp["api_key"] = auth_service.create_user_key(email)
resp["api_key"] = auth_service.create_user_key(email, env)
# Return.
return json_response(resp)
@@ -384,6 +384,17 @@ def backup_status():
from backup import backup_status
return json_response(backup_status(env))
# MUNIN
@app.route('/munin/')
@app.route('/munin/<path:filename>')
@authorized_personnel_only
def munin(filename=""):
# Checks administrative access (@authorized_personnel_only) and then just proxies
# the request to static files.
if filename == "": filename = "index.html"
return send_from_directory("/var/cache/munin/www", filename)
# APP
if __name__ == '__main__':

View File

@@ -57,13 +57,15 @@ def do_dns_update(env, force=False):
# Custom records to add to zones.
additional_records = list(get_custom_dns_config(env))
from web_update import get_default_www_redirects
www_redirect_domains = get_default_www_redirects(env)
# Write zone files.
os.makedirs('/etc/nsd/zones', exist_ok=True)
updated_domains = []
for i, (domain, zonefile) in enumerate(zonefiles):
# Build the records to put in the zone.
records = build_zone(domain, domains, additional_records, env)
records = build_zone(domain, domains, additional_records, www_redirect_domains, env)
# See if the zone has changed, and if so update the serial number
# and write the zone file.
@@ -126,7 +128,7 @@ def do_dns_update(env, force=False):
########################################################################
def build_zone(domain, all_domains, additional_records, env, is_zone=True):
def build_zone(domain, all_domains, additional_records, www_redirect_domains, env, is_zone=True):
records = []
# For top-level zones, define the authoritative name servers.
@@ -177,7 +179,7 @@ def build_zone(domain, all_domains, additional_records, env, is_zone=True):
subdomains = [d for d in all_domains if d.endswith("." + domain)]
for subdomain in subdomains:
subdomain_qname = subdomain[0:-len("." + domain)]
subzone = build_zone(subdomain, [], additional_records, env, is_zone=False)
subzone = build_zone(subdomain, [], additional_records, www_redirect_domains, env, is_zone=False)
for child_qname, child_rtype, child_value, child_explanation in subzone:
if child_qname == None:
child_qname = subdomain_qname
@@ -211,13 +213,19 @@ def build_zone(domain, all_domains, additional_records, env, is_zone=True):
records.append((qname, rtype, value, "(Set by user.)"))
# Add defaults if not overridden by the user's custom settings (and not otherwise configured).
# Any "CNAME" record on the qname overrides A and AAAA.
has_rec_base = records
# Any CNAME or A record on the qname overrides A and AAAA. But when we set the default A record,
# we should not cause the default AAAA record to be skipped because it thinks a custom A record
# was set. So set has_rec_base to a clone of the current set of DNS settings, and don't update
# during this process.
has_rec_base = list(records)
defaults = [
(None, "A", env["PUBLIC_IP"], "Required. May have a different value. Sets the IP address that %s resolves to for web hosting and other services besides mail. The A record must be present but its value does not affect mail delivery." % domain),
("www", "A", env["PUBLIC_IP"], "Optional. Sets the IP address that www.%s resolves to, e.g. for web hosting." % domain),
(None, "AAAA", env.get('PUBLIC_IPV6'), "Optional. Sets the IPv6 address that %s resolves to, e.g. for web hosting. (It is not necessary for receiving mail on this domain.)" % domain),
("www", "AAAA", env.get('PUBLIC_IPV6'), "Optional. Sets the IPv6 address that www.%s resolves to, e.g. for web hosting." % domain),
]
if "www." + domain in www_redirect_domains:
defaults += [
("www", "A", env["PUBLIC_IP"], "Optional. Sets the IP address that www.%s resolves to so that the box can provide a redirect to the parent domain." % domain),
("www", "AAAA", env.get('PUBLIC_IPV6'), "Optional. Sets the IPv6 address that www.%s resolves to so that the box can provide a redirect to the parent domain." % domain),
]
for qname, rtype, value, explanation in defaults:
if value is None or value.strip() == "": continue # skip IPV6 if not set
@@ -229,6 +237,9 @@ def build_zone(domain, all_domains, additional_records, env, is_zone=True):
if not has_rec(qname, rtype) and not has_rec(qname, "CNAME") and not has_rec(qname, "A"):
records.append((qname, rtype, value, explanation))
# Don't pin the list of records that has_rec checks against anymore.
has_rec_base = records
# SPF record: Permit the box ('mx', see above) to send mail on behalf of
# the domain, and no one else.
# Skip if the user has set a custom SPF record.
@@ -247,16 +258,16 @@ def build_zone(domain, all_domains, additional_records, env, is_zone=True):
# Append a DMARC record.
# Skip if the user has set a DMARC record already.
if not has_rec("_dmarc", "TXT", prefix="v=DMARC1; "):
records.append(("_dmarc", "TXT", 'v=DMARC1; p=quarantine', "Optional. Specifies that mail that does not originate from the box but claims to be from @%s is suspect and should be quarantined by the recipient's mail system." % domain))
records.append(("_dmarc", "TXT", 'v=DMARC1; p=quarantine', "Recommended. Specifies that mail that does not originate from the box but claims to be from @%s or which does not have a valid DKIM signature is suspect and should be quarantined by the recipient's mail system." % domain))
# For any subdomain with an A record but no SPF or DMARC record, add strict policy records.
all_resolvable_qnames = set(r[0] for r in records if r[1] in ("A", "AAAA"))
for qname in all_resolvable_qnames:
if not has_rec(qname, "TXT", prefix="v=spf1 "):
records.append((qname, "TXT", 'v=spf1 a mx -all', "Prevents unauthorized use of this domain name for outbound mail by requiring outbound mail to originate from the indicated host(s)."))
records.append((qname, "TXT", 'v=spf1 -all', "Recommended. Prevents use of this domain name for outbound mail by specifying that no servers are valid sources for mail from @%s. If you do send email from this domain name you should either override this record such that the SPF rule does allow the originating server, or, take the recommended approach and have the box handle mail for this domain (simply add any receiving alias at this domain name to make this machine treat the domain name as one of its mail domains)." % (qname + "." + domain)))
dmarc_qname = "_dmarc" + ("" if qname is None else "." + qname)
if not has_rec(dmarc_qname, "TXT", prefix="v=DMARC1; "):
records.append((dmarc_qname, "TXT", 'v=DMARC1; p=reject', "Prevents unauthorized use of this domain name for outbound mail by requiring a valid DKIM signature."))
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)))
# Sort the records. The None records *must* go first in the nsd zone file. Otherwise it doesn't matter.
@@ -847,8 +858,10 @@ def build_recommended_dns(env):
domains = get_dns_domains(env)
zonefiles = get_dns_zones(env)
additional_records = list(get_custom_dns_config(env))
from web_update import get_default_www_redirects
www_redirect_domains = get_default_www_redirects(env)
for domain, zonefile in zonefiles:
records = build_zone(domain, domains, additional_records, env)
records = build_zone(domain, domains, additional_records, www_redirect_domains, env)
# remove records that we don't dislay
records = [r for r in records if r[3] is not False]

View File

@@ -32,8 +32,11 @@ def validate_email(email, mode=None):
# unusual characters in the address. Bah. Also note that since
# the mailbox path name is based on the email address, the address
# shouldn't be absurdly long and must not have a forward slash.
# Our database is case sensitive (oops), which affects mail delivery
# (Postfix always queries in lowercase?), so also only permit lowercase
# letters.
if len(email) > 255: return False
if re.search(r'[^\@\.a-zA-Z0-9_\-]+', email):
if re.search(r'[^\@\.a-z0-9_\-]+', email):
return False
# Everything looks good.
@@ -208,7 +211,7 @@ def get_mail_aliases_ex(env):
for source, destination in get_mail_aliases(env):
# get alias info
domain = get_domain(source)
required = ((source in required_aliases) or (source == get_system_administrator(env)))
required = (source in required_aliases)
# add to list
if not domain in domains:
@@ -253,7 +256,7 @@ def add_mail_user(email, pw, privs, env):
elif not validate_email(email):
return ("Invalid email address.", 400)
elif not validate_email(email, mode='user'):
return ("User account email addresses may only use the ASCII letters A-Z, the digits 0-9, underscore (_), hyphen (-), and period (.).", 400)
return ("User account email addresses may only use the lowercase ASCII letters a-z, the digits 0-9, underscore (_), hyphen (-), and period (.).", 400)
elif is_dcv_address(email) and len(get_mail_users(env)) > 0:
# Make domain control validation hijacking a little harder to mess up by preventing the usual
# addresses used for DCV from being user accounts. Except let it be the first account because
@@ -403,6 +406,10 @@ def add_mail_alias(source, destination, env, update_if_exists=False, do_kick=Tru
# convert Unicode domain to IDNA
source = sanitize_idn_email_address(source)
# Our database is case sensitive (oops), which affects mail delivery
# (Postfix always queries in lowercase?), so force lowercase.
source = source.lower()
# validate source
source = source.strip()
if source == "":
@@ -486,15 +493,17 @@ def get_required_aliases(env):
# These are the aliases that must exist.
aliases = set()
# The system administrator alias is required.
aliases.add(get_system_administrator(env))
# The hostmaster alias is exposed in the DNS SOA for each zone.
aliases.add("hostmaster@" + env['PRIMARY_HOSTNAME'])
# Get a list of domains we serve mail for, except ones for which the only
# email on that domain is a postmaster/admin alias to the administrator
# or a wildcard alias (since it will forward postmaster/admin).
# email on that domain are the required aliases or a catch-all/domain-forwarder.
real_mail_domains = get_mail_domains(env,
filter_aliases = lambda alias :
((not alias[0].startswith("postmaster@") and not alias[0].startswith("admin@")) or alias[1] != get_system_administrator(env))
not alias[0].startswith("postmaster@") and not alias[0].startswith("admin@")
and not alias[0].startswith("@")
)
@@ -511,7 +520,7 @@ def get_required_aliases(env):
def kick(env, mail_result=None):
results = []
# Inclde the current operation's result in output.
# Include the current operation's result in output.
if mail_result is not None:
results.append(mail_result + "\n")
@@ -531,12 +540,12 @@ def kick(env, mail_result=None):
for s, t in existing_aliases:
if s == source:
return
# Doesn't exist.
administrator = get_system_administrator(env)
add_mail_alias(source, administrator, env, do_kick=False)
results.append("added alias %s (=> %s)\n" % (source, administrator))
for alias in required_aliases:
ensure_admin_alias_exists(alias)

View File

@@ -12,7 +12,7 @@ import dns.reversename, dns.resolver
import dateutil.parser, dateutil.tz
from dns_update import get_dns_zones, build_tlsa_record, get_custom_dns_config, get_secondary_dns
from web_update import get_web_domains, get_domain_ssl_files
from web_update import get_web_domains, get_default_www_redirects, get_domain_ssl_files
from mailconfig import get_mail_domains, get_mail_aliases
from utils import shell, sort_domains, load_env_vars_from_file
@@ -42,15 +42,22 @@ def run_checks(rounded_values, env, output, pool):
def get_ssh_port():
# Returns ssh port
try:
output = shell('check_output', ['sshd', '-T'])
returnNext = False
except FileNotFoundError:
# sshd is not installed. That's ok.
return None
returnNext = False
for e in output.split():
if returnNext:
return int(e)
if e == "port":
returnNext = True
# Did not find port!
return None
def run_services_checks(env, output, pool):
# Check that system services are running.
@@ -81,6 +88,7 @@ def run_services_checks(env, output, pool):
fatal = False
ret = pool.starmap(check_service, ((i, service, env) for i, service in enumerate(services)), chunksize=1)
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)
all_running = all_running and running
fatal = fatal or fatal2
output2.playback(output)
@@ -91,6 +99,10 @@ def run_services_checks(env, output, pool):
return not fatal
def check_service(i, service, env):
if not service["port"]:
# Skip check (no port, e.g. no sshd).
return (i, None, None, None)
import socket
output = BufferedOutput()
running = False
@@ -227,7 +239,7 @@ def run_domain_checks(rounded_time, env, output, pool):
dns_domains = set(dns_zonefiles)
# Get the list of domains we serve HTTPS for.
web_domains = set(get_web_domains(env))
web_domains = set(get_web_domains(env) + get_default_www_redirects(env))
domains_to_check = mail_domains | dns_domains | web_domains
@@ -593,7 +605,7 @@ def check_ssl_cert(domain, rounded_time, env, output):
output.print_line(cert_status_details)
output.print_line("")
def check_certificate(domain, ssl_certificate, ssl_private_key, rounded_time=False):
def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring_soon=True, rounded_time=False):
# Use openssl verify to check the status of a certificate.
# First check that the certificate is for the right domain. The domain
@@ -636,6 +648,7 @@ def check_certificate(domain, ssl_certificate, ssl_private_key, rounded_time=Fal
if m:
certificate_names.add(m.group(1))
# Grab the expiration date for testing later.
m = re.match(" Not After : (.*)", line)
if m:
cert_expiration_date = dateutil.parser.parse(m.group(1))
@@ -690,12 +703,14 @@ def check_certificate(domain, ssl_certificate, ssl_private_key, rounded_time=Fal
if "self signed" in verifyoutput:
# Certificate is self-signed.
return ("SELF-SIGNED", None)
elif retcode != 0:
if "unable to get local issuer certificate" in verifyoutput:
return ("The certificate is missing an intermediate chain or the intermediate chain is incorrect or incomplete. (%s)" % verifyoutput, None)
# There is some unknown problem. Return the `openssl verify` raw output.
return ("There is a problem with the SSL certificate.", verifyoutput.strip())
else:
# `openssl verify` returned a zero exit status so the cert is currently
# good.
@@ -712,7 +727,7 @@ def check_certificate(domain, ssl_certificate, ssl_private_key, rounded_time=Fal
else:
expiry_info = "The certificate expires on %s." % cert_expiration_date.strftime("%x")
if ndays <= 31:
if ndays <= 31 and warn_if_expiring_soon:
return ("The certificate is expiring soon: " + expiry_info, None)
# Return the special OK code.
@@ -928,7 +943,7 @@ if __name__ == "__main__":
ssl_key, ssl_certificate, ssl_via = get_domain_ssl_files(domain, env)
if not os.path.exists(ssl_certificate):
sys.exit(1)
cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key)
cert_status, cert_status_details = check_certificate(domain, ssl_certificate, ssl_key, warn_if_expiring_soon=False)
if cert_status != "OK":
sys.exit(1)
sys.exit(0)

View File

@@ -57,7 +57,7 @@
</tbody>
</table>
<p style="margin-top: 1.5em"><small>Hostmaster@, postmaster@, and admin@ email addresses are required on some domains.</small></p>
<p style="margin-top: 1.5em"><small>hostmaster@, postmaster@, and admin@ email addresses are required on some domains.</small></p>
<div style="display: none">
<table>

View File

@@ -98,9 +98,10 @@
<li><a href="#ssl" onclick="return show_panel(this);">SSL Certificates</a></li>
<li><a href="#system_backup" onclick="return show_panel(this);">Backup Status</a></li>
<li class="divider"></li>
<li class="dropdown-header">Advanced Options</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="#external_dns" onclick="return show_panel(this);">External DNS</a></li>
<li><a href="/admin/munin">Munin Monitoring</a></li>
</ul>
</li>
<li class="dropdown">

View File

@@ -60,7 +60,7 @@
<p>Your box using a technique called greylisting to cut down on spam. Greylisting works by delaying mail from people you haven&rsquo;t received mail from before for up to about 10 minutes. The vast majority of spam gets tricked by this. If you are waiting for an email from someone new, such as if you are registering on a new website and are waiting for an email confirmation, please give it up to 10-15 minutes to arrive.</p>
<h4>+tag addresses</h4>
<p>Every incoming email address also receives mail for <code>+tag</code> addresses. If your email address is <code>you@yourdomain.com</code>, you can also accept mail at <code>you+anythinghere@yourdomain.com</code>. Use this as a fast way to create aliases or to segment incoming mail for your own filtering rules.</p>
<p>Every incoming email address also receives mail for <code>+tag</code> addresses. If your email address is <code>you@yourdomain.com</code>, you&rsquo;ll also automatically get mail sent to <code>you+anythinghere@yourdomain.com</code>. Use this as a fast way to segment incoming mail for your own filtering rules without having to create aliases in this control panel.</p>
<h4>Use only this box to send as you</h4>
<p>Your box sets strict email sending policies for your domain names to make it harder for spam and other fraudulent mail to claim to be you. Only this machine is authorized to send email on behalf of your domain names. If you use any other service to send email as you, it will likely get spam filtered by recipients.</p>

View File

@@ -110,7 +110,8 @@ function install_cert() {
chain: $('#ssl_paste_chain').val()
},
function(status) {
if (status == "") {
if (/^OK($|\n)/.test(status)) {
console.log(status)
show_modal_error("SSL Certificate Installation", "Certificate has been installed. Check that you have no connection problems to the domain.", function() { show_ssl(); $('#csr_info').slideUp(); });
} else {
show_modal_error("SSL Certificate Installation", status);

View File

@@ -11,7 +11,7 @@
<p>Many cloud providers make this easy by allowing you to take snapshots of the machine's disk.</p>
<p>You can also use SFTP (FTP over SSH) to copy files from <tt id="backup-location"></tt>. These files are encrpyted, so they are safe to store anywhere. Copy the encryption password from <tt id="backup-encpassword-file"></tt> also but keep it in a safe location.</p>
<p>You can also use SFTP (FTP over SSH) to copy files from <tt id="backup-location"></tt>. These files are encrypted, so they are safe to store anywhere. Copy the encryption password from <tt id="backup-encpassword-file"></tt> also but keep it in a safe location.</p>
<h3>Current Backups</h3>

View File

@@ -164,9 +164,14 @@ function do_add_user() {
function users_set_password(elem) {
var email = $(elem).parents('tr').attr('data-email');
var yourpw = "";
if (api_credentials != null && email == api_credentials[0])
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(
"Archive User",
$("<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></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",
function() {
api(

View File

@@ -5,7 +5,7 @@
import os, os.path, shutil, re, tempfile, rtyaml
from mailconfig import get_mail_domains
from dns_update import get_custom_dns_config, do_dns_update
from dns_update import get_custom_dns_config, do_dns_update, get_dns_zones
from utils import shell, safe_domain_name, sort_domains
def get_web_domains(env):
@@ -19,31 +19,71 @@ def get_web_domains(env):
# Also serve web for all mail domains so that we might at least
# provide auto-discover of email settings, and also a static website
# if the user wants to make one. These will require an SSL cert.
domains |= get_mail_domains(env)
# ...Unless the domain has an A/AAAA record that maps it to a different
# IP address than this box. Remove those domains from our list.
dns = get_custom_dns_config(env)
for domain, rtype, value in dns:
if domain not in domains: continue
if rtype == "CNAME" or (rtype in ("A", "AAAA") and value != "local"):
domains.remove(domain)
domains |= (get_mail_domains(env) - get_domains_with_a_records(env))
# Sort the list. Put PRIMARY_HOSTNAME first so it becomes the
# default server (nginx's default_server).
# Sort the list so the nginx conf gets written in a stable order.
domains = sort_domains(domains, env)
return domains
def do_web_update(env, ok_status="web updated\n"):
def get_domains_with_a_records(env):
domains = set()
dns = get_custom_dns_config(env)
for domain, rtype, value in dns:
if rtype == "CNAME" or (rtype in ("A", "AAAA") and value != "local"):
domains.add(domain)
return domains
def get_web_domains_with_root_overrides(env):
# Load custom settings so we can tell what domains have a redirect or proxy set up on '/',
# which means static hosting is not happening.
root_overrides = { }
nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
if os.path.exists(nginx_conf_custom_fn):
custom_settings = rtyaml.load(open(nginx_conf_custom_fn))
for domain, settings in custom_settings.items():
for type, value in [('redirect', settings.get('redirects', {}).get('/')),
('proxy', settings.get('proxies', {}).get('/'))]:
if value:
root_overrides[domain] = (type, value)
return root_overrides
def get_default_www_redirects(env):
# Returns a list of www subdomains that we want to provide default redirects
# for, i.e. any www's that aren't domains the user has actually configured
# to serve for real. Which would be unusual.
web_domains = set(get_web_domains(env))
www_domains = set('www.' + zone for zone, zonefile in get_dns_zones(env))
return sort_domains(www_domains - web_domains - get_domains_with_a_records(env), env)
def do_web_update(env):
# Build an nginx configuration file.
nginx_conf = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-top.conf")).read()
# Add configuration for each web domain.
template1 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx.conf")).read()
# Load the templates.
template0 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx.conf")).read()
template1 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-alldomains.conf")).read()
template2 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-primaryonly.conf")).read()
template3 = "\trewrite ^(.*) https://$REDIRECT_DOMAIN$1 permanent;\n"
# Add the PRIMARY_HOST configuration first so it becomes nginx's default server.
nginx_conf += make_domain_config(env['PRIMARY_HOSTNAME'], [template0, template1, template2], env)
# Add configuration all other web domains.
has_root_proxy_or_redirect = get_web_domains_with_root_overrides(env)
for domain in get_web_domains(env):
nginx_conf += make_domain_config(domain, template1, template2, env)
if domain == env['PRIMARY_HOSTNAME']: continue # handled above
if domain not in has_root_proxy_or_redirect:
nginx_conf += make_domain_config(domain, [template0, template1], env)
else:
nginx_conf += make_domain_config(domain, [template0], env)
# Add default www redirects.
for domain in get_default_www_redirects(env):
nginx_conf += make_domain_config(domain, [template0, template3], env)
# Did the file change? If not, don't bother writing & restarting nginx.
nginx_conf_fn = "/etc/nginx/conf.d/local.conf"
@@ -62,13 +102,12 @@ def do_web_update(env, ok_status="web updated\n"):
# enough and doesn't break any open connections.
shell('check_call', ["/usr/sbin/service", "nginx", "reload"])
return ok_status
return "web updated\n"
def make_domain_config(domain, template, template_for_primaryhost, env):
# How will we configure this domain.
def make_domain_config(domain, templates, env):
# GET SOME VARIABLES
# Where will its root directory be for static files?
root = get_web_root(domain, env)
# What private key and SSL certificate will we use for this domain?
@@ -78,18 +117,9 @@ def make_domain_config(domain, template, template_for_primaryhost, env):
# available. Make a self-signed one now if one doesn't exist.
ensure_ssl_certificate_exists(domain, ssl_key, ssl_certificate, env)
# Put pieces together.
nginx_conf_parts = re.split("\s*# ADDITIONAL DIRECTIVES HERE\s*", template)
nginx_conf = nginx_conf_parts[0] + "\n"
if domain == env['PRIMARY_HOSTNAME']:
nginx_conf += template_for_primaryhost + "\n"
# ADDITIONAL DIRECTIVES.
# Replace substitution strings in the template & return.
nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
nginx_conf = nginx_conf.replace("$ROOT", root)
nginx_conf = nginx_conf.replace("$SSL_KEY", ssl_key)
nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", ssl_certificate)
nginx_conf_extra = ""
# Because the certificate may change, we should recognize this so we
# can trigger an nginx update.
@@ -102,7 +132,7 @@ def make_domain_config(domain, template, template_for_primaryhost, env):
finally:
f.close()
return sha1.hexdigest()
nginx_conf += "# ssl files sha1: %s / %s\n" % (hashfile(ssl_key), hashfile(ssl_certificate))
nginx_conf_extra += "# ssl files sha1: %s / %s\n" % (hashfile(ssl_key), hashfile(ssl_certificate))
# Add in any user customizations in YAML format.
nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
@@ -111,17 +141,29 @@ def make_domain_config(domain, template, template_for_primaryhost, env):
if domain in yaml:
yaml = yaml[domain]
for path, url in yaml.get("proxies", {}).items():
nginx_conf += "\tlocation %s {\n\t\tproxy_pass %s;\n\t}\n" % (path, url)
nginx_conf_extra += "\tlocation %s {\n\t\tproxy_pass %s;\n\t}\n" % (path, url)
for path, url in yaml.get("redirects", {}).items():
nginx_conf += "\trewrite %s %s permanent;\n" % (path, url)
nginx_conf_extra += "\trewrite %s %s permanent;\n" % (path, url)
# Add in any user customizations in the includes/ folder.
nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf")
if os.path.exists(nginx_conf_custom_include):
nginx_conf += "\tinclude %s;\n" % (nginx_conf_custom_include)
nginx_conf_extra += "\tinclude %s;\n" % (nginx_conf_custom_include)
# PUT IT ALL TOGETHER
# Ending.
nginx_conf += nginx_conf_parts[1]
# Combine the pieces. Iteratively place each template into the "# ADDITIONAL DIRECTIVES HERE" placeholder
# of the previous template.
nginx_conf = "# ADDITIONAL DIRECTIVES HERE\n"
for t in templates + [nginx_conf_extra]:
nginx_conf = re.sub("[ \t]*# ADDITIONAL DIRECTIVES HERE *\n", t, nginx_conf)
# Replace substitution strings in the template & return.
nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
nginx_conf = nginx_conf.replace("$ROOT", root)
nginx_conf = nginx_conf.replace("$SSL_KEY", ssl_key)
nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", ssl_certificate)
nginx_conf = nginx_conf.replace("$REDIRECT_DOMAIN", re.sub(r"^www\.", "", domain)) # for default www redirects to parent domain
return nginx_conf
@@ -208,7 +250,6 @@ def create_csr(domain, ssl_key, env):
return shell("check_output", [
"openssl", "req", "-new",
"-key", ssl_key,
"-out", "/dev/stdout",
"-sha256",
"-subj", "/C=%s/ST=/L=/O=/CN=%s" % (env["CSR_COUNTRY"], domain)])
@@ -239,7 +280,7 @@ def install_cert(domain, ssl_cert, ssl_chain, env):
os.makedirs(os.path.dirname(ssl_certificate), exist_ok=True)
shutil.move(fn, ssl_certificate)
ret = []
ret = ["OK"]
# When updating the cert for PRIMARY_HOSTNAME, also update DNS because it is
# used in the DANE TLSA record and restart postfix and dovecot which use
@@ -252,18 +293,11 @@ def install_cert(domain, ssl_cert, ssl_chain, env):
ret.append("mail services restarted")
# Kick nginx so it sees the cert.
ret.append( do_web_update(env, ok_status="") )
return "\n".join(r for r in ret if r.strip() != "")
ret.append( do_web_update(env) )
return "\n".join(ret)
def get_web_domains_info(env):
# load custom settings so we can tell what domains have a redirect or proxy set up on '/',
# which means static hosting is not happening
custom_settings = { }
nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
if os.path.exists(nginx_conf_custom_fn):
custom_settings = rtyaml.load(open(nginx_conf_custom_fn))
def has_root_proxy_or_redirect(domain):
return custom_settings.get(domain, {}).get('redirects', {}).get('/') or custom_settings.get(domain, {}).get('proxies', {}).get('/')
has_root_proxy_or_redirect = get_web_domains_with_root_overrides(env)
# for the SSL config panel, get cert status
def check_cert(domain):
@@ -289,7 +323,15 @@ def get_web_domains_info(env):
"root": get_web_root(domain, env),
"custom_root": get_web_root(domain, env, test_exists=False),
"ssl_certificate": check_cert(domain),
"static_enabled": not has_root_proxy_or_redirect(domain),
"static_enabled": domain not in has_root_proxy_or_redirect,
}
for domain in get_web_domains(env)
] + \
[
{
"domain": domain,
"ssl_certificate": check_cert(domain),
"static_enabled": False,
}
for domain in get_default_www_redirects(env)
]

62
ppa/Makefile Executable file
View File

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

40
ppa/README.md Normal file
View File

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

12
ppa/Vagrantfile vendored Normal file
View File

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

319
ppa/dovecot_lucene.diff Normal file
View File

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

80
ppa/postgrey.diff Normal file
View File

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

100
ppa/postgrey_sources.diff Normal file
View File

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

103
security.md Normal file
View File

@@ -0,0 +1,103 @@
Mail-in-a-Box Security Guide
============================
Mail-in-a-Box turns a fresh Ubuntu 14.04 LTS 64-bit machine into a mail server appliance by installing and configuring various components.
This page documents the security features of Mail-in-a-Box. The term “box” is used below to mean a configured Mail-in-a-Box.
Threat Model
------------
Nothing is perfectly secure, and an adversary with sufficient resources can always penetrate a system.
The primary goal of Mail-in-a-Box is to make deploying a good mail server easy, so we balance ― as everyone does ― privacy and security concerns with the practicality of actually deploying the system. That means we make certain assumptions about adversaries. We assume that adversaries . . .
* Do not have physical access to the box (i.e., we do not aim to protect the box from physical access).
* Have not been given Unix accounts on the box (i.e., we assume all users with shell access are trusted).
On the other hand, we do assume that adversaries are performing passive surveillance and, possibly, active man-in-the-middle attacks. And so:
* User credentials are always sent through SSH/TLS, never in the clear.
* Outbound mail is sent with the highest level of TLS possible (more on that below).
User Credentials
----------------
The box's administrator and its (non-administrative) mail users must sometimes communicate their credentials to the box.
### Services behind TLS
These services are protected by [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security):
* SMTP Submission (port 587). Mail users submit outbound mail through SMTP with STARTTLS on port 587.
* IMAP/POP (ports 993, 995). Mail users check for incoming mail through IMAP or POP over TLS.
* HTTPS (port 443). Webmail, the Exchange/ActiveSync protocol, the administrative control panel, and any static hosted websites are accessed over HTTPS.
The services all follow these rules:
* SSL certificates are generated with 2048-bit RSA keys and SHA-256 fingerprints. The box provides a self-signed certificate by default. The [setup guide](https://mailinabox.email/guide.html) explains how to verify the certificate fingerprint on first login. Users are encouraged to replace the certificate with a proper CA-signed one. ([source](setup/ssl.sh))
* Only TLSv1, TLSv1.1 and TLSv1.2 are offered (the older SSL protocols are not offered).
* Export-grade ciphers, the anonymous DH/ECDH algorithms (aNULL), and clear-text ciphers (eNULL) are not offered.
* The minimum cipher key length offered is 112 bits. The maximum is 256 bits. Diffie-Hellman ciphers use a 2048-bit key for forward secrecy.
Additionally:
* SMTP Submission (port 587) will not accept user credentials without STARTTLS (true also of SMTP on port 25 in case of client misconfiguration), and the submission port won't accept mail without encryption. The minimum cipher key length is 128 bits. (The box is of course configured not to be an open relay. User credentials are required to send outbound mail.) ([source](setup/mail-postfix.sh))
* HTTPS (port 443): The HTTPS Strict Transport Security header is set. A redirect from HTTP to HTTPS is offered. The [Qualys SSL Labs test](https://www.ssllabs.com/ssltest) should report an A+ grade. ([source 1](conf/nginx-ssl.conf), [source 2](conf/nginx.conf))
For more details, see the [output of SSLyze for these ports](tests/tls_results.txt).
The cipher and protocol selection are chosen to support the following clients:
* For HTTPS: Firefox 1, Chrome 1, IE 7, Opera 5, Safari 1, Windows XP IE8, Android 2.3, Java 7.
* For other protocols: TBD.
### Password Storage
The passwords for mail users are stored on disk using the [SHA512-CRYPT](http://man7.org/linux/man-pages/man3/crypt.3.html) hashing scheme. ([source](management/mailconfig.py))
When using the web-based administrative control panel, after logging in an API key is placed in the browser's local storage (rather than, say, the user's actual password). The API key is an HMAC based on the user's email address and current password, and it is keyed by a secret known only to the control panel service. By resetting an administrator's password, any HMACs previously generated for that user will expire.
### Console access
Console access (e.g. via SSH) is configured by the system image used to create the box, typically from by a cloud virtual machine provider (e.g. Digital Ocean). Mail-in-a-Box does not set any console access settings, although it will warn the administrator in the System Status Checks if password-based login is turned on.
The [setup guide video](https://mailinabox.email/) explains how to verify the host key fingerprint on first login.
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))
Outbound Mail
-------------
The basic protocols of email delivery did not plan for the presence of adversaries on the network. For a number of reasons it is not possible in most cases to guarantee that a connection to a recipient server is secure.
### DNSSEC
The first step in resolving the destination server for an email address is performing a DNS look-up for the MX record of the domain name. The box uses a locally-running [DNSSEC](https://en.wikipedia.org/wiki/DNSSEC)-aware nameserver to perform the lookup. If the domain name has DNSSEC enabled, DNSSEC guards against DNS records being tampered with.
### 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))
### DANE
If the recipient's domain name supports DNSSEC and has published a [DANE TLSA](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities) record, then on-the-wire encryption is forced between the box and the recipient MTA and this encryption is not subject to a man-in-the-middle attack. The TLSA record contains a certificate fingerprint which the receiving MTA (server) must present to the box. ([source](setup/mail-postfix.sh))
### Domain Policy Records
Domain policy records allow recipient MTAs to detect when the _domain_ part of incoming mail has been spoofed. All outbound mail is signed with [DKIM](https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail) and "quarantine" [DMARC](https://en.wikipedia.org/wiki/DMARC) records are automatically set in DNS. Receiving MTAs that implement DMARC will automatically quarantine mail that is "From:" a domain hosted by the box but which was not sent by the box. (Strong [SPF](https://en.wikipedia.org/wiki/Sender_Policy_Framework) records are also automatically set in DNS.) ([source](management/dns_update.py))
Incoming Mail
-------------
### 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))
### DANE
When DNSSEC is enabled at the box's domain name's registrar, [DANE TLSA](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities) records are automatically published in DNS. Senders supporting DANE will enforce encryption on-the-wire between them and the box --- see the section on DANE for outgoing mail above. ([source](management/dns_update.py))
### Filters
Incoming mail is run through several filters. Email is bounced if the sender's IP address is listed in the [Spamhaus Zen blacklist](http://www.spamhaus.org/zen/) or if the sender's domain is listed in the [Spamhaus Domain Block List](http://www.spamhaus.org/dbl/). Greylisting (with [postgrey](http://postgrey.schweikert.ch/)) is also used to cut down on spam. ([source](setup/mail-postfix.sh))

View File

@@ -7,7 +7,7 @@
#########################################################
if [ -z "$TAG" ]; then
TAG=v0.09
TAG=v0.10
fi
# Are we running as root?
@@ -18,9 +18,12 @@ fi
# Clone the Mail-in-a-Box repository if it doesn't exist.
if [ ! -d $HOME/mailinabox ]; then
if [ ! -f /usr/bin/git ]; then
echo Installing git . . .
apt-get -q -q update
DEBIAN_FRONTEND=noninteractive apt-get -q -q install -y git < /dev/null
echo
fi
echo Downloading Mail-in-a-Box $TAG. . .
git clone \

View File

@@ -127,6 +127,8 @@ EOF
chmod +x /etc/cron.daily/mailinabox-dnssec
# Permit DNS queries on TCP/UDP in the firewall.
ufw_allow domain
# Start nsd. None of the zones are configured until the management daemon is
# run later, though.
restart_service nsd

View File

@@ -39,8 +39,14 @@ function apt_get_quiet {
}
function apt_install {
# Report any packages already installed.
PACKAGES=$@
if [ ! -z "$IS_DOCKER" ]; then
# Speed things up because packages are already installed by the image.
PACKAGES=""
fi
# Report any packages already installed.
TO_INSTALL=""
ALREADY_INSTALLED=""
for pkg in $PACKAGES; do
@@ -72,12 +78,20 @@ function get_default_hostname {
# Guess the machine's hostname. It should be a fully qualified
# domain name suitable for DNS. None of these calls may provide
# the right value, but it's the best guess we can make.
set -- $(hostname --fqdn 2>/dev/null ||
set -- $(
get_hostname_from_reversedns ||
hostname --fqdn 2>/dev/null ||
hostname --all-fqdns 2>/dev/null ||
hostname 2>/dev/null)
printf '%s\n' "$1" # return this value
}
function get_hostname_from_reversedns {
# Do a reverse DNS lookup on our public IPv4 address. The output of
# `host` is complex -- use sed to get the FDQN.
host $(get_publicip_from_web_service 4) | sed "s/.*pointer \(.*\)\./\1/"
}
function get_publicip_from_web_service {
# This seems to be the most reliable way to determine the
# machine's public IP address: asking a very nice web API
@@ -153,7 +167,17 @@ function ufw_allow {
}
function restart_service {
if [ -z "$IS_DOCKER" ]; then
# Restart the service.
hide_output service $1 restart
else
# In Docker, make sure the service is not disabled by a down file.
if [ -f /etc/service/$1/down ]; then
rm /etc/service/$1/down
fi
sv restart $1
fi
}
## Dialog Functions ##

View File

@@ -18,11 +18,14 @@
source setup/functions.sh # load our functions
source /etc/mailinabox.conf # load global vars
# Install packages...
# Install packages for dovecot. These are all core dovecot plugins,
# but dovecot-lucene is packaged by *us* in the Mail-in-a-Box PPA,
# not by Ubuntu.
apt_install \
dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-sqlite sqlite3 \
dovecot-sieve dovecot-managesieved
dovecot-sieve dovecot-managesieved dovecot-lucene
# The `dovecot-imapd`, `dovecot-pop3d`, and `dovecot-lmtpd` packages automatically
# enable IMAP, POP and LMTP protocols.
@@ -92,6 +95,17 @@ tools/editconf.py /etc/dovecot/conf.d/20-imap.conf \
tools/editconf.py /etc/dovecot/conf.d/20-pop3.conf \
pop3_uidl_format="%08Xu%08Xv"
# Full Text Search - Enable full text search of mail using dovecot's lucene plugin,
# which *we* package and distribute (dovecot-lucene package).
tools/editconf.py /etc/dovecot/conf.d/10-mail.conf \
mail_plugins="\$mail_plugins fts fts_lucene"
cat > /etc/dovecot/conf.d/90-plugin-fts.conf << EOF;
plugin {
fts = lucene
fts_lucene = whitespace_chars=@.
}
EOF
# ### LDA (LMTP)
# Enable Dovecot's LDA service with the LMTP protocol. It will listen

View File

@@ -41,6 +41,13 @@ source /etc/mailinabox.conf # load global vars
# always will.
# * `ca-certificates`: A trust store used to squelch postfix warnings about
# untrusted opportunistically-encrypted connections.
#
# postgrey is going to come in via the Mail-in-a-Box PPA, which publishes
# a modified version of postgrey that lets senders whitelisted by dnswl.org
# pass through without being greylisted. So please note [dnswl's license terms](https://www.dnswl.org/?page_id=9):
# > Every user with more than 100000 queries per day on the public nameserver
# > infrastructure and every commercial vendor of dnswl.org data (eg through
# > anti-spam solutions) must register with dnswl.org and purchase a subscription.
apt_install postfix postfix-pcre postgrey ca-certificates
@@ -78,7 +85,7 @@ tools/editconf.py /etc/postfix/master.cf -s -w \
-o syslog_name=postfix/submission
-o smtpd_milters=inet:127.0.0.1:8891
-o smtpd_tls_security_level=encrypt
-o smtpd_tls_ciphers=high -o smtpd_tls_exclude_ciphers=aNULL,DES,3DES,MD5,DES+MD5,RC4 -o smtpd_tls_protocols=!SSLv2,!SSLv3
-o smtpd_tls_ciphers=high -o smtpd_tls_exclude_ciphers=aNULL,DES,3DES,MD5,DES+MD5,RC4 -o smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
-o cleanup_service_name=authclean" \
"authclean=unix n - - - 0 cleanup
-o header_checks=pcre:/etc/postfix/outgoing_mail_header_filters"
@@ -171,8 +178,13 @@ tools/editconf.py /etc/postfix/main.cf \
# Postfix connects to Postgrey on the 127.0.0.1 interface specifically. Ensure that
# Postgrey listens on the same interface (and not IPv6, for instance).
# A lot of legit mail servers try to resend before 300 seconds.
# As a matter of fact RFC is not strict about retry timer so postfix and
# other MTA have their own intervals. To fix the problem of receiving
# e-mails really latter, delay of greylisting has been set to
# 180 seconds (default is 300 seconds).
tools/editconf.py /etc/default/postgrey \
POSTGREY_OPTS=\"--inet=127.0.0.1:10023\"
POSTGREY_OPTS=\"'--inet=127.0.0.1:10023 --delay=180'\"
# Increase the message size limit from 10MB to 128MB.
# The same limit is specified in nginx.conf for mail submitted via webmail and Z-Push.
@@ -187,3 +199,4 @@ ufw_allow submission
# Restart services
restart_service postfix
restart_service postgrey

View File

@@ -3,7 +3,7 @@
source setup/functions.sh
apt_install python3-flask links duplicity libyaml-dev python3-dnspython python3-dateutil
hide_output pip3 install rtyaml "email_validator==0.1.0-rc4"
hide_output pip3 install rtyaml "email_validator==0.1.0-rc5"
# email_validator is repeated in setup/questions.sh
# Create a backup directory and a random key for encrypting backups.
@@ -42,8 +42,5 @@ EOF
chmod +x /etc/cron.daily/mailinabox-statuschecks
# Start it. Remove the api key file first so that start.sh
# can wait for it to be created to know that the management
# server is ready.
rm -f /var/lib/mailinabox/api.key
# Start it.
restart_service mailinabox

32
setup/munin.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/bin/bash
# Munin: resource monitoring tool
#################################################
source setup/functions.sh # load our functions
source /etc/mailinabox.conf # load global vars
# install Munin
apt_install munin munin-node
# edit config
cat > /etc/munin/munin.conf <<EOF;
dbdir /var/lib/munin
htmldir /var/cache/munin/www
logdir /var/log/munin
rundir /var/run/munin
tmpldir /etc/munin/templates
includedir /etc/munin/munin-conf.d
# a simple host tree
[$PRIMARY_HOSTNAME]
address 127.0.0.1
# send alerts to the following address
contacts admin
contact.admin.command mail -s "Munin notification ${var:host}" administrator@$PRIMARY_HOSTNAME
contact.admin.always_send warning critical
EOF
# generate initial statistics so the directory isn't empty
sudo -u munin munin-cron

View File

@@ -15,20 +15,30 @@ apt_install \
apt-get purge -qq -y owncloud*
# Install ownCloud from source of this version:
owncloud_ver=8.0.2
owncloud_hash=a4d1fc44bc40af87948458ae8f60ee427ecd9560
owncloud_ver=8.0.4
owncloud_hash=625b1c561ea51426047a3e79eda51ca05e9f978a
# Migrate <= v0.10 setups that stored the ownCloud config.php in /usr/local rather than
# in STORAGE_ROOT. Move the file to STORAGE_ROOT.
if [ ! -f $STORAGE_ROOT/owncloud/config.php ] \
&& [ -f /usr/local/lib/owncloud/config/config.php ]; then
# Move config.php and symlink back into previous location.
echo "Migrating owncloud/config.php to new location."
mv /usr/local/lib/owncloud/config/config.php $STORAGE_ROOT/owncloud/config.php \
&& \
ln -sf $STORAGE_ROOT/owncloud/config.php /usr/local/lib/owncloud/config/config.php
fi
# Check if ownCloud dir exist, and check if version matches owncloud_ver (if either doesn't - install/upgrade)
if [ ! -d /usr/local/lib/owncloud/ ] \
|| ! grep -q $owncloud_ver /usr/local/lib/owncloud/version.php; then
# Clear out the existing ownCloud.
rm -f /tmp/owncloud-config.php
if [ ! -d /usr/local/lib/owncloud/ ]; then
echo installing ownCloud...
else
echo "upgrading ownCloud to $owncloud_ver (backing up existing ownCloud directory to /tmp/owncloud-backup-$$)..."
cp /usr/local/lib/owncloud/config/config.php /tmp/owncloud-config.php
mv /usr/local/lib/owncloud /tmp/owncloud-backup-$$
fi
@@ -46,10 +56,9 @@ if [ ! -d /usr/local/lib/owncloud/ ] \
# Fix weird permissions.
chmod 750 /usr/local/lib/owncloud/{apps,config}
# Restore configuration file if we're doing an upgrade.
if [ -f /tmp/owncloud-config.php ]; then
mv /tmp/owncloud-config.php /usr/local/lib/owncloud/config/config.php
fi
# Create a symlink to the config.php in STORAGE_ROOT (for upgrades we're restoring the symlink we previously
# put in, and in new installs we're creating a symlink and will create the actual config later).
ln -sf $STORAGE_ROOT/owncloud/config.php /usr/local/lib/owncloud/config/config.php
# Make sure permissions are correct or the upgrade step won't run.
# $STORAGE_ROOT/owncloud may not yet exist, so use -f to suppress
@@ -65,10 +74,13 @@ fi
# Setup ownCloud if the ownCloud database does not yet exist. Running setup when
# the database does exist wipes the database and user data.
if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
# Create user data directory
mkdir -p $STORAGE_ROOT/owncloud
# Create a configuration file.
TIMEZONE=$(cat /etc/timezone)
instanceid=oc$(echo $PRIMARY_HOSTNAME | sha1sum | fold -w 10 | head -n 1)
cat > /usr/local/lib/owncloud/config/config.php <<EOF;
cat > $STORAGE_ROOT/owncloud/config.php <<EOF;
<?php
\$CONFIG = array (
'datadirectory' => '$STORAGE_ROOT/owncloud',
@@ -125,12 +137,12 @@ EOF
?>
EOF
# Create user data directory and set permissions
mkdir -p $STORAGE_ROOT/owncloud
# Set permissions
chown -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
# Execute ownCloud's setup step, which creates the ownCloud sqlite database.
# It also wipes it if it exists. And it deletes the autoconfig.php file.
# It also wipes it if it exists. And it updates config.php with database
# settings and deletes the autoconfig.php file.
(cd /usr/local/lib/owncloud; sudo -u www-data php /usr/local/lib/owncloud/index.php;)
fi
@@ -172,4 +184,5 @@ chmod +x /etc/cron.hourly/mailinabox-owncloud
# Enable PHP modules and restart PHP.
php5enmod imap
restart_service memcached
restart_service php5-fpm

View File

@@ -4,7 +4,7 @@ if [[ $EUID -ne 0 ]]; then
echo
echo "sudo $0"
echo
exit
exit 1
fi
# Check that we are running on Ubuntu 14.04 LTS (or 14.04.xx).
@@ -14,7 +14,7 @@ if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" != "U
lsb_release -d | sed 's/.*:\s*//'
echo
echo "We can't write scripts that run on every possible setup, sorry."
exit
exit 1
fi
# Check that we have enough memory.
@@ -30,6 +30,6 @@ if [ ! -d /vagrant ]; then
echo "Your Mail-in-a-Box needs more memory (RAM) to function properly."
echo "Please provision a machine with at least 768 MB, 1 GB recommended."
echo "This machine has $TOTAL_PHYSICAL_MEM MB memory."
exit
exit 1
fi
fi

View File

@@ -6,11 +6,16 @@ if [ -z "$NONINTERACTIVE" ]; then
# use a shell flag instead. Really supress any output from installing dialog.
#
# Also install depencies needed to validate the email address.
if [ ! -f /usr/bin/dialog ] || [ ! -f /usr/bin/python3 ] || [ ! -f /usr/bin/pip3 ]; then
echo Installing packages needed for setup...
apt-get -q -q update
apt_get_quiet install dialog python3 python3-pip || exit 1
fi
if [ -z "$IS_DOCKER" ]; then
# email_validator is repeated in setup/management.sh
hide_output pip3 install "email_validator==0.1.0-rc4" || exit 1
hide_output pip3 install "email_validator==0.1.0-rc5" || exit 1
fi
message_box "Mail-in-a-Box Installation" \
"Hello and thanks for deploying a Mail-in-a-Box!
@@ -193,3 +198,48 @@ if [ -z "$CSR_COUNTRY" ]; then
exit
fi
fi
# Automatic configuration, e.g. as used in our Vagrant configuration.
if [ "$PUBLIC_IP" = "auto" ]; then
# Use a public API to get our public IP address, or fall back to local network configuration.
PUBLIC_IP=$(get_publicip_from_web_service 4 || get_default_privateip 4)
fi
if [ "$PUBLIC_IPV6" = "auto" ]; then
# Use a public API to get our public IPv6 address, or fall back to local network configuration.
PUBLIC_IPV6=$(get_publicip_from_web_service 6 || get_default_privateip 6)
fi
if [ "$PRIMARY_HOSTNAME" = "auto" ]; then
# Use reverse DNS to get this machine's hostname. Install bind9-host early.
hide_output apt-get -y install bind9-host
PRIMARY_HOSTNAME=$(get_default_hostname)
elif [ "$PRIMARY_HOSTNAME" = "auto-easy" ]; then
# Generate a probably-unique subdomain under our justtesting.email domain.
PRIMARY_HOSTNAME=`echo $PUBLIC_IP | sha1sum | cut -c1-5`.justtesting.email
fi
# Set STORAGE_USER and STORAGE_ROOT to default values (user-data and /home/user-data), unless
# we've already got those values from a previous run.
if [ -z "$STORAGE_USER" ]; then
STORAGE_USER=$([[ -z "$DEFAULT_STORAGE_USER" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER")
fi
if [ -z "$STORAGE_ROOT" ]; then
STORAGE_ROOT=$([[ -z "$DEFAULT_STORAGE_ROOT" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT")
fi
# Show the configuration, since the user may have not entered it manually.
echo
echo "Primary Hostname: $PRIMARY_HOSTNAME"
echo "Public IP Address: $PUBLIC_IP"
if [ ! -z "$PUBLIC_IPV6" ]; then
echo "Public IPv6 Address: $PUBLIC_IPV6"
fi
if [ "$PRIVATE_IP" != "$PUBLIC_IP" ]; then
echo "Private IP Address: $PRIVATE_IP"
fi
if [ "$PRIVATE_IPV6" != "$PUBLIC_IPV6" ]; then
echo "Private IPv6 Address: $PRIVATE_IPV6"
fi
if [ -f /usr/bin/git ] && [ -d .git ]; then
echo "Mail-in-a-Box Version: " $(git describe)
fi
echo

View File

@@ -47,74 +47,29 @@ chmod +x /usr/local/bin/mailinabox
# Ask the user for the PRIMARY_HOSTNAME, PUBLIC_IP, PUBLIC_IPV6, and CSR_COUNTRY
# if values have not already been set in environment variables. When running
# non-interactively, be sure to set values for all!
# non-interactively, be sure to set values for all! Also sets STORAGE_USER and
# STORAGE_ROOT.
source setup/questions.sh
# Automatic configuration, e.g. as used in our Vagrant configuration.
if [ "$PUBLIC_IP" = "auto" ]; then
# Use a public API to get our public IP address, or fall back to local network configuration.
PUBLIC_IP=$(get_publicip_from_web_service 4 || get_default_privateip 4)
fi
if [ "$PUBLIC_IPV6" = "auto" ]; then
# Use a public API to get our public IPv6 address, or fall back to local network configuration.
PUBLIC_IPV6=$(get_publicip_from_web_service 6 || get_default_privateip 6)
fi
if [ "$PRIMARY_HOSTNAME" = "auto-easy" ]; then
# Generate a probably-unique subdomain under our justtesting.email domain.
PRIMARY_HOSTNAME=`echo $PUBLIC_IP | sha1sum | cut -c1-5`.justtesting.email
fi
# Show the configuration, since the user may have not entered it manually.
echo
echo "Primary Hostname: $PRIMARY_HOSTNAME"
echo "Public IP Address: $PUBLIC_IP"
if [ ! -z "$PUBLIC_IPV6" ]; then
echo "Public IPv6 Address: $PUBLIC_IPV6"
fi
if [ "$PRIVATE_IP" != "$PUBLIC_IP" ]; then
echo "Private IP Address: $PRIVATE_IP"
fi
if [ "$PRIVATE_IPV6" != "$PUBLIC_IPV6" ]; then
echo "Private IPv6 Address: $PRIVATE_IPV6"
fi
if [ -f .git ]; then
echo "Mail-in-a-Box Version: " $(git describe)
fi
echo
# Run some network checks to make sure setup on this machine makes sense.
if [ -z "$SKIP_NETWORK_CHECKS" ]; then
. setup/network-checks.sh
source setup/network-checks.sh
fi
# For the first time (if the config file (/etc/mailinabox.conf) not exists):
# Create the user named "user-data" and store all persistent user
# data (mailboxes, etc.) in that user's home directory.
#
# If the config file exists:
# Apply the existing configuration options for STORAGE_USER/ROOT
if [ -z "$STORAGE_USER" ]; then
STORAGE_USER=$([[ -z "$DEFAULT_STORAGE_USER" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER")
fi
if [ -z "$STORAGE_ROOT" ]; then
STORAGE_ROOT=$([[ -z "$DEFAULT_STORAGE_ROOT" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT")
fi
# Create the STORAGE_USER if it not exists
# Create the STORAGE_USER and STORAGE_ROOT directory if they don't already exist.
# If the STORAGE_ROOT is missing the mailinabox.version file that lists a
# migration (schema) number for the files stored there, assume this is a fresh
# installation to that directory and write the file to contain the current
# migration number for this version of Mail-in-a-Box.
if ! id -u $STORAGE_USER >/dev/null 2>&1; then
useradd -m $STORAGE_USER
fi
# Create the STORAGE_ROOT if it not exists
if [ ! -d $STORAGE_ROOT ]; then
mkdir -p $STORAGE_ROOT
fi
# Create mailinabox.version file if not exists
if [ ! -f $STORAGE_ROOT/mailinabox.version ]; then
echo $(setup/migrate.py --current) > $STORAGE_ROOT/mailinabox.version
chown $STORAGE_USER.$STORAGE_USER $STORAGE_ROOT/mailinabox.version
chown $STORAGE_USER:$STORAGE_USER $STORAGE_ROOT/mailinabox.version
fi
@@ -145,9 +100,11 @@ source setup/webmail.sh
source setup/owncloud.sh
source setup/zpush.sh
source setup/management.sh
source setup/munin.sh
# Ping the management daemon to write the DNS and nginx configuration files.
while [ ! -f /var/lib/mailinabox/api.key ]; do
until nc -z -w 4 localhost 10222
do
echo Waiting for the Mail-in-a-Box management daemon to start...
sleep 2
done

View File

@@ -3,7 +3,21 @@ source setup/functions.sh # load our functions
# Basic System Configuration
# -------------------------
# ### Install Packages
# ### Add Mail-in-a-Box's PPA.
# We've built several .deb packages on our own that we want to include.
# One is a replacement for Ubuntu's stock postgrey package that makes
# some enhancements. The other is dovecot-lucene, a Lucene-based full
# text search plugin for (and by) dovecot, which is not available in
# Ubuntu currently.
#
# Add that to the system's list of repositories:
hide_output add-apt-repository -y ppa:mail-in-a-box/ppa
# The apt-get update in the next step will pull in the PPA's index.
# ### Update Packages
# Update system packages to make sure we have the latest upstream versions of things from Ubuntu.
@@ -11,6 +25,8 @@ echo Updating system packages...
hide_output apt-get update
apt_get_quiet upgrade
# ### Install System Packages
# Install basic utilities.
#
# * haveged: Provides extra entropy to /dev/random so it doesn't stall
@@ -20,13 +36,14 @@ apt_get_quiet upgrade
# * cron: Runs background processes periodically.
# * ntp: keeps the system time correct
# * fail2ban: scans log files for repeated failed login attempts and blocks the remote IP at the firewall
# * netcat-openbsd: `nc` command line networking tool
# * git: we install some things directly from github
# * sudo: allows privileged users to execute commands as root without being root
# * coreutils: includes `nproc` tool to report number of processors
# * bc: allows us to do math to compute sane defaults
apt_install python3 python3-dev python3-pip \
wget curl git sudo coreutils bc \
netcat-openbsd wget curl git sudo coreutils bc \
haveged unattended-upgrades cron ntp fail2ban
# Allow apt to install system updates automatically every day.

View File

@@ -32,6 +32,10 @@ sed "s#STORAGE_ROOT#$STORAGE_ROOT#" \
tools/editconf.py /etc/nginx/nginx.conf -s \
server_names_hash_bucket_size="64;"
# Tell PHP not to expose its version number in the X-Powered-By header.
tools/editconf.py /etc/php5/fpm/php.ini -c ';' \
expose_php=Off
# Bump up PHP's max_children to support more concurrent connections
tools/editconf.py /etc/php5/fpm/pool.d/www.conf -c ';' \
pm.max_children=8

View File

@@ -32,14 +32,16 @@ apt-get purge -qq -y roundcube* #NODOC
# Install Roundcube from source if it is not already present or if it is out of date.
# Combine the Roundcube version number with the commit hash of vacation_sieve to track
# whether we have the latest version.
VERSION=1.1.1
HASH=08222f382a8dd89bba7dbbad595f48443bec0aa2
VERSION=1.1.2
HASH=df88deae691da3ecf3e9f0aee674c1f3042ea1eb
VACATION_SIEVE_VERSION=91ea6f52216390073d1f5b70b5f6bea0bfaee7e5
PERSISTENT_LOGIN_VERSION=117fbd8f93b56b2bf72ad055193464803ef3bc36
UPDATE_KEY=$VERSION:$VACATION_SIEVE_VERSION:$PERSISTENT_LOGIN_VERSION
needs_update=0 #NODOC
if [ ! -f /usr/local/lib/roundcubemail/version ]; then
# not installed yet #NODOC
needs_update=1 #NODOC
elif [[ "$VERSION:$VACATION_SIEVE_VERSION" != `cat /usr/local/lib/roundcubemail/version` ]]; then
elif [[ "$UPDATE_KEY" != `cat /usr/local/lib/roundcubemail/version` ]]; then
# checks if the version is what we want
needs_update=1 #NODOC
fi
@@ -58,8 +60,11 @@ if [ $needs_update == 1 ]; then
# install roundcube autoreply/vacation plugin
git_clone https://github.com/arodier/Roundcube-Plugins.git $VACATION_SIEVE_VERSION plugins/vacation_sieve /usr/local/lib/roundcubemail/plugins/vacation_sieve
# install roundcube persistent_login plugin
git_clone https://github.com/mfreiholz/Roundcube-Persistent-Login-Plugin.git $PERSISTENT_LOGIN_VERSION '' /usr/local/lib/roundcubemail/plugins/persistent_login
# record the version we've installed
echo $VERSION:$VACATION_SIEVE_VERSION > /usr/local/lib/roundcubemail/version
echo $UPDATE_KEY > /usr/local/lib/roundcubemail/version
fi
# ### Configuring Roundcube
@@ -91,7 +96,7 @@ cat > /usr/local/lib/roundcubemail/config/config.inc.php <<EOF;
\$config['support_url'] = 'https://mailinabox.email/';
\$config['product_name'] = 'Mail-in-a-Box/Roundcube Webmail';
\$config['des_key'] = '$SECRET_KEY';
\$config['plugins'] = array('archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'vacation_sieve');
\$config['plugins'] = array('archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'vacation_sieve', 'persistent_login');
\$config['skin'] = 'classic';
\$config['login_autocomplete'] = 2;
\$config['password_charset'] = 'UTF-8';
@@ -121,7 +126,7 @@ EOF
# Create writable directories.
mkdir -p /var/log/roundcubemail /tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
chown -R www-data.www-data /var/log/roundcubemail /tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
chown -R www-data:www-data /var/log/roundcubemail /tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
# Password changing plugin settings
# The config comes empty by default, so we need the settings
@@ -142,9 +147,9 @@ usermod -a -G dovecot www-data
# set permissions so that PHP can use users.sqlite
# could use dovecot instead of www-data, but not sure it matters
chown root.www-data $STORAGE_ROOT/mail
chown root:www-data $STORAGE_ROOT/mail
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
# Enable PHP modules.

164
tests/tls.py Normal file
View File

@@ -0,0 +1,164 @@
#!/usr/bin/python3
# Runs SSLyze on the TLS endpoints of a box and outputs
# the results so we can inspect the settings and compare
# against a known good version in tls_results.txt.
#
# Make sure you have SSLyze available:
# wget https://github.com/nabla-c0d3/sslyze/releases/download/release-0.11/sslyze-0_11-linux64.zip
# unzip sslyze-0_11-linux64.zip
#
# Then run:
#
# python3 tls.py yourservername
#
# If you are on a residential network that blocks outbound
# port 25 connections, then you can proxy the connections
# through some other host you can ssh into (maybe the box
# itself?):
#
# python3 --proxy user@ssh_host yourservername
#
# (This will launch "ssh -N -L10023:yourservername:testport user@ssh_host"
# to create a tunnel.)
import sys, subprocess, re, time, json, csv, io, urllib.request
######################################################################
# PARSE COMMAND LINE
proxy = None
args = list(sys.argv[1:])
while len(args) > 0:
if args[0] == "--proxy":
args.pop(0)
proxy = args.pop(0)
break
if len(args) == 0:
print("Usage: python3 tls.py [--proxy ssh_host] hostname")
sys.exit(0)
host = args[0]
######################################################################
SSLYZE = "sslyze-0_11-linux64/sslyze/sslyze.py"
common_opts = ["--sslv2", "--sslv3", "--tlsv1", "--tlsv1_1", "--tlsv1_2", "--reneg", "--resum",
"--hide_rejected_ciphers", "--compression", "--heartbleed"]
# Recommendations from Mozilla as of May 20, 2015 at
# https://wiki.mozilla.org/Security/Server_Side_TLS.
#
# The 'modern' ciphers support Firefox 27, Chrome 22, IE 11,
# Opera 14, Safari 7, Android 4.4, Java 8. Assumes TLSv1.1,
# TLSv1.2 only, though we may also be allowing TLSv3.
#
# The 'intermediate' ciphers support Firefox 1, Chrome 1, IE 7,
# Opera 5, Safari 1, Windows XP IE8, Android 2.3, Java 7.
# Assumes TLSv1, TLSv1.1, TLSv1.2.
#
# The 'old' ciphers bring compatibility back to Win XP IE 6.
MOZILLA_CIPHERS_MODERN = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK"
MOZILLA_CIPHERS_INTERMEDIATE = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
MOZILLA_CIPHERS_OLD = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
######################################################################
def sslyze(opts, port, ok_ciphers):
# Print header.
header = ("PORT %d" % port)
print(header)
print("-" * (len(header)))
# What ciphers should we expect?
ok_ciphers = subprocess.check_output(["openssl", "ciphers", ok_ciphers]).decode("utf8").strip().split(":")
# Form the SSLyze connection string.
connection_string = host + ":" + str(port)
# Proxy via SSH.
proxy_proc = None
if proxy:
connection_string = "localhost:10023"
proxy_proc = subprocess.Popen(["ssh", "-N", "-L10023:%s:%d" % (host, port), proxy])
time.sleep(3)
try:
# Execute SSLyze.
out = subprocess.check_output([SSLYZE] + common_opts + opts + [connection_string])
out = out.decode("utf8")
# Trim output to make better for storing in git.
if "SCAN RESULTS FOR" not in out:
# Failed. Just output the error.
out = re.sub("[\w\W]*CHECKING HOST\(S\) AVAILABILITY\n\s*-+\n", "", out) # chop off header that shows the host we queried
out = re.sub("[\w\W]*SCAN RESULTS FOR.*\n\s*-+\n", "", out) # chop off header that shows the host we queried
out = re.sub("SCAN COMPLETED IN .*", "", out)
out = out.rstrip(" \n-") + "\n"
# Print.
print(out)
# Pull out the accepted ciphers list for each SSL/TLS protocol
# version outputted.
accepted_ciphers = set()
for ciphers in re.findall(" Accepted:([\w\W]*?)\n *\n", out):
accepted_ciphers |= set(re.findall("\n\s*(\S*)", ciphers))
# Compare to what Mozilla recommends, for a given modernness-level.
print(" Should Not Offer: " + (", ".join(sorted(accepted_ciphers-set(ok_ciphers))) or "(none -- good)"))
print(" Could Also Offer: " + (", ".join(sorted(set(ok_ciphers)-accepted_ciphers)) or "(none -- good)"))
# What clients does that mean we support on this protocol?
supported_clients = { }
for cipher in accepted_ciphers:
if cipher in cipher_clients:
for client in cipher_clients[cipher]:
supported_clients[client] = supported_clients.get(client, 0) + 1
print(" Supported Clients: " + (", ".join(sorted(supported_clients.keys(), key = lambda client : -supported_clients[client]))))
# Blank line.
print()
finally:
if proxy_proc:
proxy_proc.terminate()
try:
proxy_proc.wait(5)
except TimeoutExpired:
proxy_proc.kill()
# Get a list of OpenSSL cipher names.
cipher_names = { }
for cipher in csv.DictReader(io.StringIO(urllib.request.urlopen("https://raw.githubusercontent.com/mail-in-a-box/user-agent-tls-capabilities/master/cipher_names.csv").read().decode("utf8"))):
# not sure why there are some multi-line values, use first line:
cipher["OpenSSL"] = cipher["OpenSSL"].split("\n")[0]
cipher_names[cipher["IANA"]] = cipher["OpenSSL"]
# Get a list of what clients support what ciphers, using OpenSSL cipher names.
client_compatibility = json.loads(urllib.request.urlopen("https://raw.githubusercontent.com/mail-in-a-box/user-agent-tls-capabilities/master/clients.json").read().decode("utf8"))
cipher_clients = { }
for client in client_compatibility:
if len(set(client['protocols']) & set(["TLS 1.0", "TLS 1.1", "TLS 1.2"])) == 0: continue # does not support TLS
for cipher in client['ciphers']:
cipher_clients.setdefault(cipher_names.get(cipher), set()).add("/".join(x for x in [client['client']['name'], client['client']['version'], client['client']['platform']] if x))
# Run SSLyze on various ports.
# SMTP
sslyze(["--starttls=smtp"], 25, MOZILLA_CIPHERS_OLD)
# SMTP Submission
sslyze(["--starttls=smtp"], 587, MOZILLA_CIPHERS_MODERN)
# HTTPS
sslyze(["--http_get", "--chrome_sha1", "--hsts"], 443, MOZILLA_CIPHERS_INTERMEDIATE)
# IMAP
sslyze([], 993, MOZILLA_CIPHERS_MODERN)
# POP3
sslyze([], 995, MOZILLA_CIPHERS_MODERN)

431
tests/tls_results.txt Normal file
View File

@@ -0,0 +1,431 @@
PORT 25
-------
* Deflate Compression:
OK - Compression disabled
* Session Renegotiation:
Client-initiated Renegotiations: VULNERABLE - Server honors client-initiated renegotiations
Secure Renegotiation: OK - Supported
* OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed
* Session Resumption:
With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: NOT SUPPORTED - TLS ticket not assigned.
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* TLSV1_2 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-GCM-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok
Accepted:
ECDHE-RSA-AES256-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES256-GCM-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-GCM-SHA384 DH-2048 bits 256 bits 250 2.0.0 Ok
CAMELLIA256-SHA - 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-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-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
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits 250 2.0.0 Ok
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
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-SHA256 - 128 bits 250 2.0.0 Ok
AES128-SHA - 128 bits 250 2.0.0 Ok
AES128-GCM-SHA256 - 128 bits 250 2.0.0 Ok
ECDHE-RSA-DES-CBC3-SHA ECDH-256 bits 112 bits 250 2.0.0 Ok
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits 250 2.0.0 Ok
DES-CBC3-SHA - 112 bits 250 2.0.0 Ok
* TLSV1_1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
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
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
AES128-SHA - 128 bits 250 2.0.0 Ok
ECDHE-RSA-DES-CBC3-SHA ECDH-256 bits 112 bits 250 2.0.0 Ok
EDH-RSA-DES-CBC3-SHA DH-2048 bits 112 bits 250 2.0.0 Ok
DES-CBC3-SHA - 112 bits 250 2.0.0 Ok
* TLSV1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
Accepted:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
AES256-SHA - 256 bits 250 2.0.0 Ok
ECDHE-RSA-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
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
Should Not Offer: DHE-RSA-SEED-SHA, ECDHE-RSA-RC4-SHA, EDH-RSA-DES-CBC3-SHA, RC4-MD5, RC4-SHA, SEED-SHA
Could Also Offer: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-DSS-AES256-SHA256, DHE-DSS-CAMELLIA128-SHA, DHE-DSS-CAMELLIA256-SHA, 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, 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
PORT 587
--------
* Deflate Compression:
OK - Compression disabled
* Session Renegotiation:
Client-initiated Renegotiations: VULNERABLE - Server honors client-initiated renegotiations
Secure Renegotiation: OK - Supported
* OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed
* Session Resumption:
With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: NOT SUPPORTED - TLS ticket not assigned.
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* TLSV1_2 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-GCM-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok
Accepted:
ECDHE-RSA-AES256-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES256-GCM-SHA384 ECDH-256 bits 256 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-GCM-SHA384 DH-2048 bits 256 bits 250 2.0.0 Ok
CAMELLIA256-SHA - 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-GCM-SHA384 - 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA256 ECDH-256 bits 128 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-GCM-SHA256 ECDH-256 bits 128 bits 250 2.0.0 Ok
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits 250 2.0.0 Ok
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
SEED-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-SHA - 128 bits 250 2.0.0 Ok
AES128-GCM-SHA256 - 128 bits 250 2.0.0 Ok
* TLSV1_1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
Accepted:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
AES256-SHA - 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
SEED-SHA - 128 bits 250 2.0.0 Ok
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
AES128-SHA - 128 bits 250 2.0.0 Ok
* SSLV3 Cipher Suites:
Server rejected all cipher suites.
* TLSV1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
Accepted:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
DHE-RSA-AES256-SHA DH-2048 bits 256 bits 250 2.0.0 Ok
CAMELLIA256-SHA - 256 bits 250 2.0.0 Ok
AES256-SHA - 256 bits 250 2.0.0 Ok
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits 250 2.0.0 Ok
DHE-RSA-SEED-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-CAMELLIA128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
DHE-RSA-AES128-SHA DH-2048 bits 128 bits 250 2.0.0 Ok
SEED-SHA - 128 bits 250 2.0.0 Ok
CAMELLIA128-SHA - 128 bits 250 2.0.0 Ok
AES128-SHA - 128 bits 250 2.0.0 Ok
Should Not Offer: AES128-GCM-SHA256, AES128-SHA, AES128-SHA256, AES256-GCM-SHA384, AES256-SHA, AES256-SHA256, CAMELLIA128-SHA, CAMELLIA256-SHA, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA, DHE-RSA-SEED-SHA, SEED-SHA
Could Also Offer: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384
Supported Clients: OpenSSL/1.0.2, 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
PORT 443
--------
* Deflate Compression:
OK - Compression disabled
* Session Renegotiation:
Client-initiated Renegotiations: OK - Rejected
Secure Renegotiation: OK - Supported
* HTTP Strict Transport Security:
OK - HSTS header received: max-age=31536000
* Session Resumption:
With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: OK - Supported
* OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* Google Chrome SHA-1 Deprecation Status:
OK - Leaf certificate expires before 2016.
* TLSV1_2 Cipher Suites:
Preferred:
ECDHE-RSA-AES128-GCM-SHA256 ECDH-256 bits 128 bits HTTP 200 OK
Accepted:
ECDHE-RSA-AES256-SHA384 ECDH-256 bits 256 bits HTTP 200 OK
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits HTTP 200 OK
ECDHE-RSA-AES256-GCM-SHA384 ECDH-256 bits 256 bits HTTP 200 OK
DHE-RSA-AES256-SHA256 DH-2048 bits 256 bits HTTP 200 OK
DHE-RSA-AES256-SHA DH-2048 bits 256 bits HTTP 200 OK
DHE-RSA-AES256-GCM-SHA384 DH-2048 bits 256 bits HTTP 200 OK
ECDHE-RSA-AES128-SHA256 ECDH-256 bits 128 bits HTTP 200 OK
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK
ECDHE-RSA-AES128-GCM-SHA256 ECDH-256 bits 128 bits HTTP 200 OK
DHE-RSA-AES128-SHA256 DH-2048 bits 128 bits HTTP 200 OK
DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK
DHE-RSA-AES128-GCM-SHA256 DH-2048 bits 128 bits HTTP 200 OK
DES-CBC3-SHA - 112 bits HTTP 200 OK
* TLSV1_1 Cipher Suites:
Preferred:
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK
Accepted:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits HTTP 200 OK
DHE-RSA-AES256-SHA DH-2048 bits 256 bits HTTP 200 OK
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK
DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK
DES-CBC3-SHA - 112 bits HTTP 200 OK
* SSLV3 Cipher Suites:
Server rejected all cipher suites.
* TLSV1 Cipher Suites:
Preferred:
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK
Accepted:
ECDHE-RSA-AES256-SHA ECDH-256 bits 256 bits HTTP 200 OK
DHE-RSA-AES256-SHA DH-2048 bits 256 bits HTTP 200 OK
ECDHE-RSA-AES128-SHA ECDH-256 bits 128 bits HTTP 200 OK
DHE-RSA-AES128-SHA DH-2048 bits 128 bits HTTP 200 OK
DES-CBC3-SHA - 112 bits HTTP 200 OK
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
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
PORT 993
--------
* Deflate Compression:
OK - Compression disabled
Unhandled exception when processing --reneg:
_nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected record
* OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* Session Resumption:
With Session IDs: NOT SUPPORTED (0 successful, 5 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: NOT SUPPORTED - TLS ticket assigned but not accepted.
* TLSV1_2 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
Accepted:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
AES256-SHA - 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
AES128-SHA - 128 bits
* TLSV1_1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
Accepted:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
AES256-SHA - 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
AES128-SHA - 128 bits
* SSLV3 Cipher Suites:
Server rejected all cipher suites.
* TLSV1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
Accepted:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
AES256-SHA - 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
AES128-SHA - 128 bits
Should Not Offer: AES128-SHA, AES256-SHA, CAMELLIA128-SHA, CAMELLIA256-SHA, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA
Could Also Offer: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA256, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-RSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-SHA256, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-RSA-AES256-SHA384
Supported Clients: OpenSSL/1.0.2, 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
PORT 995
--------
* Deflate Compression:
OK - Compression disabled
Unhandled exception when processing --reneg:
_nassl.OpenSSLError - error:140940F5:SSL routines:ssl3_read_bytes:unexpected record
* OpenSSL Heartbleed:
OK - Not vulnerable to Heartbleed
* SSLV2 Cipher Suites:
Server rejected all cipher suites.
* Session Resumption:
With Session IDs: NOT SUPPORTED (0 successful, 5 failed, 0 errors, 5 total attempts).
With TLS Session Tickets: NOT SUPPORTED - TLS ticket assigned but not accepted.
* TLSV1_2 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
Accepted:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
AES256-SHA - 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
AES128-SHA - 128 bits
* TLSV1_1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
Accepted:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
AES256-SHA - 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
AES128-SHA - 128 bits
* SSLV3 Cipher Suites:
Server rejected all cipher suites.
* TLSV1 Cipher Suites:
Preferred:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
Accepted:
ECDHE-RSA-AES256-SHA ECDH-384 bits 256 bits
DHE-RSA-CAMELLIA256-SHA DH-1024 bits 256 bits
DHE-RSA-AES256-SHA DH-1024 bits 256 bits
CAMELLIA256-SHA - 256 bits
AES256-SHA - 256 bits
ECDHE-RSA-AES128-SHA ECDH-384 bits 128 bits
DHE-RSA-CAMELLIA128-SHA DH-1024 bits 128 bits
DHE-RSA-AES128-SHA DH-1024 bits 128 bits
CAMELLIA128-SHA - 128 bits
AES128-SHA - 128 bits
Should Not Offer: AES128-SHA, AES256-SHA, CAMELLIA128-SHA, CAMELLIA256-SHA, DHE-RSA-CAMELLIA128-SHA, DHE-RSA-CAMELLIA256-SHA
Could Also Offer: DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES128-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-DSS-AES256-SHA, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA256, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-RSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-SHA256, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-RSA-AES256-SHA384
Supported Clients: OpenSSL/1.0.2, 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

View File

@@ -0,0 +1,23 @@
#!/usr/bin/python3
import os.path, glob, re
packages = set()
def add(line):
global packages
if line.endswith("\\"): line = line[:-1]
packages |= set(p for p in line.split(" ") if p not in("", "apt_install"))
for fn in glob.glob(os.path.join(os.path.dirname(__file__), "../setup/*.sh")):
with open(fn) as f:
in_apt_install = False
for line in f:
line = line.strip()
if line.startswith("apt_install "):
in_apt_install = True
if in_apt_install:
add(line)
in_apt_install = in_apt_install and line.endswith("\\")
print("\n".join(sorted(packages)))