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

Compare commits

...

40 Commits
v0.04 ... v0.05

Author SHA1 Message Date
Joshua Tauberer
3133dcd5a3 release 0.05 2014-11-18 16:52:02 +00:00
Joshua Tauberer
057c1dd913 recommend IMAP/SMTP for everyone 2014-11-18 16:47:42 +00:00
Joshua Tauberer
06f2477cfd the new iOS configuration profile also is used on OS X 10.10.1, see #261 2014-11-18 16:32:37 +00:00
Joshua Tauberer
1abc8ed469 add CHANGELOG entries for recent work 2014-11-14 14:13:24 +00:00
Joshua Tauberer
cdaa2c847d [merge] iOS Mobile Configuration Profile 2014-11-14 13:56:18 +00:00
Joshua Tauberer
b04addda9a move the mobileconfig into the conf directory as a plain XML file and handle substitutions and copying to /var in web.sh 2014-11-14 13:52:29 +00:00
Joshua Tauberer
7e7abf3b53 support "domain aliases" (@domain => @domain aliases)
This seemed to already be technically supported but the validation is now stricter and the admin is more helpful:

* Postfix seems to allow @domain.tld as an alias destination address but only if it is the only destination address (see the virtual man page).
 * Allow @domain.tld if it is the whole destination address string.
 * Otherwise, do not allow email addresses without local parts in the destination.
* In the admin, add a third tab for making it clear how to add a domain alias.

closes #265
2014-11-14 13:35:58 +00:00
Joshua Tauberer
9b9f5abf8f update to ownCloud 7.0.3 2014-11-14 13:35:58 +00:00
Norman
7db80458dd fix description 2014-11-06 15:42:22 +01:00
Norman
5775cab175 various fixes 2014-11-06 15:33:08 +01:00
Norman
c872e6a9f0 iOS Configuration Profile
change name

removed .vagrant

fix guide layout
2014-11-05 18:42:04 +01:00
Joshua Tauberer
995b7c4d2b Merge pull request #260 from fpgaminer/master
Fix typo in zpush.sh comment
2014-11-05 06:43:46 -05:00
fpgaminer
f797eecaca Fix typo in zpush.sh comment 2014-11-04 19:53:24 -08:00
Joshua Tauberer
de0ccd0632 [merge] Disable encapsulation of spam and marking of it as seen
is #254 plus a longer comment, fixes #243
2014-10-31 12:15:58 +00:00
David Piggott
be9d97902f Disable encapsulation of spam and marking of it as seen 2014-10-28 15:15:21 +00:00
Joshua Tauberer
20c5471a89 expose the ownCloud API, fixes #240, fixes #242 2014-10-28 12:05:07 +00:00
Joshua Tauberer
ec73c171c7 when installing a ssl cert for the primary hostname, dns, postfix, and dovecot all need to be updated/kicked
see https://discourse.mailinabox.email/t/there-is-a-problem-with-the-ssl-certificate/144/4
2014-10-28 11:38:04 +00:00
Joshua Tauberer
f9acf0adec better errors for ssl certificates 2014-10-24 21:30:33 +00:00
Joshua Tauberer
8b65c11cdf the namecheap link was bad 2014-10-23 17:17:26 +00:00
Joshua Tauberer
34fca29dd3 fix the animated scroll target on the ssl panel to scroll so that the header is actually visible and not covered by the nav bar 2014-10-23 17:10:21 +00:00
Joshua Tauberer
b75fbf22ca clear the local dns cache each time the status checks are run by restarting bind9 2014-10-23 17:06:33 +00:00
Joshua Tauberer
d790cae0e2 DNSSEC: use RSASHA256 for the .guide tld too 2014-10-23 17:03:23 +00:00
Joshua Tauberer
a68703dfb3 add a tool for me to see how many installations are happening by scanning for accesses to bootstrap.sh 2014-10-21 14:02:19 +00:00
Joshua Tauberer
f35b2081a1 s/os.rename/shutil.move/ so that the file can be moved across filesystem boundaries, fxies #246 2014-10-21 11:45:14 +00:00
David Piggott
f0508d8cc9 Improve wrapping of external DNS value column to prevent layout overflow
see #244

Conflicts:
	management/templates/external-dns.html
2014-10-21 11:33:42 +00:00
Joshua Tauberer
47dd59c2a7 admin mail guide: use bootstrap .panel to style the tips
also give more space for the login settings and less space to the tips
2014-10-21 11:17:49 +00:00
Joshua Tauberer
c2fe1bc2e3 document +tag addresses in the mail guide 2014-10-21 11:17:49 +00:00
Joshua Tauberer
cce1184090 admin: change the css class name around the panels to not invoke the bootstrap 'panel' css 2014-10-21 11:17:49 +00:00
Joshua Tauberer
1adb1d8307 admin: there is no need to make each panel a separate bootstrap container
* also fixes the footer alignment to be within a container rather than a container-fluid
* this changed the width of the login form slightly, so am cleaning that up too

see #244
2014-10-21 11:17:28 +00:00
Joshua Tauberer
c2174e10a6 some admin pages had a container within a container
see #244
2014-10-21 11:17:15 +00:00
Joshua Tauberer
18283c7df0 Merge pull request #248 from dhpiggott/bash-fix
Add shebangs to enable running dkim and webmail scripts separately
2014-10-20 16:35:57 -04:00
David Piggott
3ff74c8dc5 Add source line so dkim should actually work when run separately 2014-10-20 21:33:20 +01:00
David Piggott
e997114d6e Add shebangs to enable running dkim and webmail scripts separately 2014-10-20 21:26:14 +01:00
Joshua Tauberer
e9aecba4df update to roundcube 1.0.3, and really update
Updating existing installed was broken. The new roundcube would be copied into a subdirectory of /usr/local/lib/roundcubemail.

Also fixes the image thumbnail issue raised on the forum (https://discourse.mailinabox.email/t/roundcube-thumbnails-not-showing/136), see http://trac.roundcube.net/wiki/Changelog.
2014-10-20 12:48:15 +00:00
Joshua Tauberer
6585384daa bring the max outgoing mail size via webmail and z-push in line with the limit set in postfix: 128 MB
The limit was previously the nginx default (2MB?).

fixes #236
2014-10-16 22:11:10 +00:00
Joshua Tauberer
6bc821676c add the sa-learn permissions changes to the CHANGELOG 2014-10-16 22:11:10 +00:00
Joshua Tauberer
f38ef0223d update version number in CHANGELOG 2014-10-15 12:36:34 -04:00
Joshua Tauberer
8902e9d1fc bump bootstrap to incoming v0.04 tag 2014-10-15 12:33:20 -04:00
Joshua Tauberer
86a5394f07 fix control panel when no backup has been made yet 2014-10-15 12:31:08 -04:00
Joshua Tauberer
df5df18820 fixes for bootstrap.sh for upgrading
* `git fetch` wasn't done right for shallow clones
* the test for whether mailinabox has already been cloned wasn't looking at the right directory if the script was not run from $HOME
2014-10-15 12:22:48 -04:00
29 changed files with 429 additions and 111 deletions

View File

@@ -1,8 +1,35 @@
CHANGELOG
=========
To-be-released
--------------
v0.05 (November 18, 2014)
-------------------------
Mail:
* The maximum size of outbound mail sent via webmail and Exchange/ActiveSync has been increased to 128 MB, the same as when using SMTP.
* Spam is no longer wrapped as an attachment inside a scary Spamassassin explanation. The original message is simply moved straight to the Spam folder unchanged.
* There is a new iOS/Mac OS X Configuration Profile link in the control panel which makes it easier to configure IMAP/SMTP/CalDAV/CardDAV on iOS devices and Macs.
* "Domain aliases" can now be configured in the control panel.
* Updated to [Roundcube 1.0.3](http://trac.roundcube.net/wiki/Changelog).
* IMAP/SMTP is now recommended even on iOS devices as Exchange/ActiveSync is terribly buggy.
Control panel:
* Installing an SSL certificate for the primary hostname would cause problems until a restart (services needed to be restarted).
* Installing SSL certificates would fail if /tmp was on a different filesystem.
* Better error messages when installing a SSL certificate fails.
* The local DNS cache is now cleared each time the system status checks are run.
* Documented how to use +tag addressing.
* Minor UI tweaks.
Other:
* Updated to [ownCloud 7.0.3](http://owncloud.org/changelog/).
* The ownCloud API is now exposed properly.
* DNSSEC now works on `.guide` domains now too (RSASHA256).
v0.04 (October 15, 2014)
------------------------
Breaking changes:
@@ -31,6 +58,7 @@ Security:
Other:
* Spam filter learning by dragging mail in and out of the Spam folder should hopefully be working now.
* Some things were broken if the machine had an IPv6 address.
* Other things were broken if the machine was on a non-utf8 locale.
* No longer implementing webfinger.

128
conf/ios-profile.xml Normal file
View File

@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!--
iOS/OS X Configuration Profile
Mobileconfig for iOS/OS X users to setup IMAP, SMTP, Contacts & Calendar
https://developer.apple.com/library/ios/featuredarticles/iPhoneConfigurationProfileRef/Introduction/Introduction.html
-->
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>CalDAVAccountDescription</key>
<string>PRIMARY_HOSTNAME calendar</string>
<key>CalDAVHostName</key>
<string>PRIMARY_HOSTNAME</string>
<key>CalDAVPort</key>
<real>443</real>
<key>CalDAVPrincipalURL</key>
<string>/cloud/remote.php/caldav/calendars/</string>
<key>CalDAVUseSSL</key>
<true/>
<key>PayloadDescription</key>
<string>PRIMARY_HOSTNAME (Mail-in-a-Box)</string>
<key>PayloadDisplayName</key>
<string>PRIMARY_HOSTNAME calendar</string>
<key>PayloadIdentifier</key>
<string>email.mailinabox.mobileconfig.PRIMARY_HOSTNAME.CalDAV</string>
<key>PayloadOrganization</key>
<string></string>
<key>PayloadType</key>
<string>com.apple.caldav.account</string>
<key>PayloadUUID</key>
<string>UUID1</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
<dict>
<key>EmailAccountDescription</key>
<string>PRIMARY_HOSTNAME mail</string>
<key>EmailAccountType</key>
<string>EmailTypeIMAP</string>
<key>IncomingMailServerAuthentication</key>
<string>EmailAuthPassword</string>
<key>IncomingMailServerHostName</key>
<string>PRIMARY_HOSTNAME</string>
<key>IncomingMailServerPortNumber</key>
<integer>993</integer>
<key>IncomingMailServerUseSSL</key>
<true/>
<key>OutgoingMailServerAuthentication</key>
<string>EmailAuthPassword</string>
<key>OutgoingMailServerHostName</key>
<string>PRIMARY_HOSTNAME</string>
<key>OutgoingMailServerPortNumber</key>
<integer>587</integer>
<key>OutgoingMailServerUseSSL</key>
<true/>
<key>OutgoingPasswordSameAsIncomingPassword</key>
<true/>
<key>PayloadDescription</key>
<string>PRIMARY_HOSTNAME (Mail-in-a-Box)</string>
<key>PayloadDisplayName</key>
<string>PRIMARY_HOSTNAME mail</string>
<key>PayloadIdentifier</key>
<string>email.mailinabox.mobileconfig.PRIMARY_HOSTNAME.E-Mail</string>
<key>PayloadOrganization</key>
<string></string>
<key>PayloadType</key>
<string>com.apple.mail.managed</string>
<key>PayloadUUID</key>
<string>UUID2</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PreventAppSheet</key>
<false/>
<key>PreventMove</key>
<false/>
<key>SMIMEEnabled</key>
<false/>
</dict>
<dict>
<key>CardDAVAccountDescription</key>
<string>PRIMARY_HOSTNAME contacts</string>
<key>CardDAVHostName</key>
<string>PRIMARY_HOSTNAME</string>
<key>CardDAVPort</key>
<integer>443</integer>
<key>CardDAVPrincipalURL</key>
<string>/cloud/remote.php/carddav/addressbooks/</string>
<key>CardDAVUseSSL</key>
<true/>
<key>PayloadDescription</key>
<string>PRIMARY_HOSTNAME (Mail-in-a-Box)</string>
<key>PayloadDisplayName</key>
<string>PRIMARY_HOSTNAME contacts</string>
<key>PayloadIdentifier</key>
<string>email.mailinabox.mobileconfig.PRIMARY_HOSTNAME.carddav</string>
<key>PayloadOrganization</key>
<string></string>
<key>PayloadType</key>
<string>com.apple.carddav.account</string>
<key>PayloadUUID</key>
<string>UUID3</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDescription</key>
<string>PRIMARY_HOSTNAME (Mail-in-a-Box)</string>
<key>PayloadDisplayName</key>
<string>PRIMARY_HOSTNAME</string>
<key>PayloadIdentifier</key>
<string>email.mailinabox.mobileconfig.PRIMARY_HOSTNAME</string>
<key>PayloadOrganization</key>
<string></string>
<key>PayloadRemovalDisallowed</key>
<false/>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>UUID4</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

View File

@@ -18,8 +18,12 @@
deny all;
}
}
location ~ ^(/cloud)(/[^/]+\.php)(/.*)?$ {
location ~ ^(/cloud)((?:/ocs)?/[^/]+\.php)(/.*)?$ {
# note: ~ has precendence over a regular location block
# Accept URLs like:
# /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)
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/local/lib/owncloud/$2;
fastcgi_param SCRIPT_NAME $1$2;

View File

@@ -34,6 +34,10 @@ server {
access_log off;
}
location = /mailinabox.mobileconfig {
alias /var/lib/mailinabox/mobileconfig.xml;
}
# Roundcube Webmail configuration.
rewrite ^/mail$ /mail/ redirect;
rewrite ^/mail/$ /mail/index.php;
@@ -52,7 +56,10 @@ server {
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /usr/local/lib/roundcubemail/$fastcgi_script_name;
fastcgi_pass php-fpm;
client_max_body_size 20M;
# 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)
@@ -62,6 +69,10 @@ server {
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;
@@ -73,4 +84,3 @@ server {
# ADDITIONAL DIRECTIVES HERE
}

View File

@@ -1,7 +1,6 @@
require ["regex", "fileinto", "imap4flags"];
if allof (header :regex "X-Spam-Status" "^Yes") {
setflag "\\Seen";
fileinto "Spam";
stop;
}

View File

@@ -38,6 +38,7 @@ def backup_status(env):
backups = { }
basedir = os.path.join(env['STORAGE_ROOT'], 'backup/duplicity/')
encdir = os.path.join(env['STORAGE_ROOT'], 'backup/encrypted/')
os.makedirs(basedir, exist_ok=True) # os.listdir fails if directory does not exist
for fn in os.listdir(basedir):
m = re.match(r"duplicity-(full|full-signatures|(inc|new-signatures)\.(?P<incbase>\d+T\d+Z)\.to)\.(?P<date>\d+T\d+Z)\.", fn)
if not m: raise ValueError(fn)

View File

@@ -511,7 +511,7 @@ zone:
########################################################################
def dnssec_choose_algo(domain, env):
if domain.endswith(".email"):
if domain.endswith(".email") or domain.endswith(".guide"):
# At least at GoDaddy, this is the only algorithm supported.
return "RSASHA256"

View File

@@ -15,7 +15,7 @@ def validate_email(email, mode=None):
if mode == 'user':
# For Dovecot's benefit, only allow basic characters.
ATEXT = r'[\w\-]'
elif mode == 'alias':
elif mode in (None, 'alias'):
# For aliases, we can allow any valid email address.
# Based on RFC 2822 and https://github.com/SyrusAkbary/validate_email/blob/master/validate_email.py,
# these characters are permitted in email addresses.
@@ -27,7 +27,8 @@ def validate_email(email, mode=None):
DOT_ATOM_TEXT_LOCAL = ATEXT + r'+(?:\.' + ATEXT + r'+)*'
if mode == 'alias':
# For aliases, Postfix accepts '@domain.tld' format for
# catch-all addresses. Make the local part optional.
# catch-all addresses on the source side and domain aliases
# on the destination side. Make the local part optional.
DOT_ATOM_TEXT_LOCAL = '(?:' + DOT_ATOM_TEXT_LOCAL + ')?'
# as above, but we can require that the host part have at least
@@ -356,19 +357,28 @@ def add_mail_alias(source, destination, env, update_if_exists=False, do_kick=Tru
if not validate_email(source, mode='alias'):
return ("Invalid incoming email address (%s)." % source, 400)
# parse comma and \n-separated destination emails & validate
# validate destination
dests = []
for line in destination.split("\n"):
for email in line.split(","):
email = email.strip()
if email == "": continue
if not validate_email(email, mode='alias'):
return ("Invalid destination email address (%s)." % email, 400)
dests.append(email)
destination = destination.strip()
if validate_email(destination, mode='alias'):
# Oostfix allows a single @domain.tld as the destination, which means
# the local part on the address is preserved in the rewrite.
dests.append(destination)
else:
# Parse comma and \n-separated destination emails & validate. In this
# case, the recipients must be complete email addresses.
for line in destination.split("\n"):
for email in line.split(","):
email = email.strip()
if email == "": continue
if not validate_email(email):
return ("Invalid destination email address (%s)." % email, 400)
dests.append(email)
if len(destination) == 0:
return ("No destination email address(es) provided.", 400)
destination = ",".join(dests)
# save to db
conn, c = open_database(env, with_connection=True)
try:
c.execute("INSERT INTO aliases (source, destination) VALUES (?, ?)", (source, destination))

View File

@@ -18,6 +18,10 @@ from mailconfig import get_mail_domains, get_mail_aliases
from utils import shell, sort_domains, load_env_vars_from_file
def run_checks(env, output):
# clear the DNS cache so our DNS checks are most up to date
shell('check_call', ["/usr/sbin/service", "bind9", "restart"])
# perform checks
env["out"] = output
run_system_checks(env)
run_network_checks(env)
@@ -175,9 +179,8 @@ def check_primary_hostname_dns(domain, env, dns_domains, dns_zonefiles):
elif tlsa25 is None:
env['out'].print_error("""The DANE TLSA record for incoming mail is not set. This is optional.""")
else:
env['out'].print_error("""The DANE TLSA record for incoming mail (%s) is not correct. It is '%s' but it should be '%s'. Try running tools/dns_update to
regenerate the record. It may take several hours for
public DNS to update after a change."""
env['out'].print_error("""The DANE TLSA record for incoming mail (%s) is not correct. It is '%s' but it should be '%s'.
It may take several hours for public DNS to update after a change."""
% (tlsa_qname, tlsa25, tlsa25_expected))
# Check that the hostmaster@ email address exists.
@@ -510,6 +513,9 @@ def check_certificate(domain, ssl_certificate, ssl_private_key):
# 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.", None)
# There is some unknown problem. Return the `openssl verify` raw output.
return ("There is a problem with the SSL certificate.", verifyoutput.strip())
else:

View File

@@ -13,22 +13,26 @@
<div class="form-group">
<div class="col-sm-offset-1 col-sm-11">
<div id="alias_type_buttons" class="btn-group btn-group-xs">
<button type="button" class="btn btn-default active">Regular</button>
<button type="button" class="btn btn-default">Catch-All</button>
<button type="button" class="btn btn-default active" data-mode="regular">Regular</button>
<button type="button" class="btn btn-default" data-mode="catchall">Catch-All</button>
<button type="button" class="btn btn-default" data-mode="domainalias">Domain Alias</button>
</div>
<div id="alias_mode_info" class="text-info small" style="display: none; margin: .5em 0 0 0;">
<span class="catchall hidden">A catch-all alias captures all otherwise unmatched email to a domain. Enter just a part of an email address starting with the @-sign.</span>
<span class="domainalias hidden">A domain alias forwards all otherwise unmatched mail from one domain to another domain, preserving the part before the @-sign.</span>
</div>
<div id="alias_catchall_info" class="text-info small" style="display: none; margin: .5em 0 0 0;">A catch-all alias captures all otherwise unmatched email to a domain. Enter just a part of an email address starting with the @-sign.</div>
</div>
</div>
<div class="form-group">
<label for="addaliasEmail" class="col-sm-1 control-label">Alias</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="addaliasEmail" placeholder="incoming email address (you@yourdomain.com)">
<input type="email" class="form-control" id="addaliasEmail">
</div>
</div>
<div class="form-group">
<label for="addaliasTargets" class="col-sm-1 control-label">Forward To</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" id="addaliasTargets" placeholder="forward to these email addresses (one per line or separated by commas)"></textarea>
<textarea class="form-control" rows="3" id="addaliasTargets"></textarea>
</div>
</div>
<div class="form-group">
@@ -106,16 +110,28 @@ function show_aliases() {
$('#alias_type_buttons button').off('click').click(function() {
$('#alias_type_buttons button').removeClass('active');
$(this).addClass('active');
if ($(this).text() == "Regular") {
if ($(this).attr('data-mode') == "regular") {
$('#addaliasEmail').attr('type', 'email');
$('#addaliasEmail').attr('placeholder', 'incoming email address (you@yourdomain.com)');
$('#alias_catchall_info').slideUp();
} else {
$('#addaliasEmail').attr('placeholder', 'incoming email address (e.g. you@yourdomain.com)');
$('#addaliasTargets').attr('placeholder', 'forward to these email addresses (one per line or separated by commas)');
$('#alias_mode_info').slideUp();
} else if ($(this).attr('data-mode') == "catchall") {
$('#addaliasEmail').attr('type', 'text');
$('#addaliasEmail').attr('placeholder', 'incoming catch-all address (@yourdomain.com)');
$('#alias_catchall_info').slideDown();
$('#addaliasEmail').attr('placeholder', 'incoming catch-all address (e.g. @yourdomain.com)');
$('#addaliasTargets').attr('placeholder', 'forward to these email addresses (one per line or separated by commas)');
$('#alias_mode_info').slideDown();
$('#alias_mode_info span').addClass('hidden');
$('#alias_mode_info span.catchall').removeClass('hidden');
} else if ($(this).attr('data-mode') == "domainalias") {
$('#addaliasEmail').attr('type', 'text');
$('#addaliasEmail').attr('placeholder', 'incoming domain (@yourdomain.com)');
$('#addaliasTargets').attr('placeholder', 'forward to domain (@yourdomain.com)');
$('#alias_mode_info').slideDown();
$('#alias_mode_info span').addClass('hidden');
$('#alias_mode_info span.domainalias').removeClass('hidden');
}
})
$('#alias_type_buttons button[data-mode="regular"]').click(); // init
})
}
@@ -166,6 +182,12 @@ function aliases_edit(elem) {
$("#addaliasEmail").val(email);
$("#addaliasTargets").val(targets);
$('#add-alias-button').text('Update');
if (email.charAt(0) == '@' && targets.charAt(0) == '@')
$('#alias_type_buttons button[data-mode="domainalias"]').click();
else if (email.charAt(0) == '@')
$('#alias_type_buttons button[data-mode="catchall"]').click();
else
$('#alias_type_buttons button[data-mode="regular"]').click();
$('body').animate({ scrollTop: 0 })
}

View File

@@ -12,8 +12,9 @@
border: 0;
padding-top: .75em;
padding-bottom: 0;
max-width: 50vw;
word-wrap: break-word;
}
#external_dns_settings .value {
word-break: break-all;
}
#external_dns_settings .explanation td {
border: 0;

View File

@@ -46,14 +46,22 @@
margin-bottom: 13px;
margin-top: 30px;
}
.panel-heading h3 {
border: none;
padding: 0;
margin: 0;
}
h4 {
font-size: 110%;
margin-bottom: 13px;
margin-top: 18px;
}
h4:first-child {
margin-top: 6px;
}
.panel {
.admin_panel {
display: none;
}
@@ -115,48 +123,48 @@
</div>
</div>
<div class="container-fluid">
<div id="panel_system_status" class="container panel">
<div class="container">
<div id="panel_system_status" class="admin_panel">
{% include "system-status.html" %}
</div>
<div id="panel_system_backup" class="container panel">
<div id="panel_system_backup" class="admin_panel">
{% include "system-backup.html" %}
</div>
<div id="panel_external_dns" class="container panel">
<div id="panel_external_dns" class="admin_panel">
{% include "external-dns.html" %}
</div>
<div id="panel_custom_dns" class="container panel">
<div id="panel_custom_dns" class="admin_panel">
{% include "custom-dns.html" %}
</div>
<div id="panel_login" class="panel">
<div id="panel_login" class="admin_panel">
{% include "login.html" %}
</div>
<div id="panel_mail-guide" class="container panel">
<div id="panel_mail-guide" class="admin_panel">
{% include "mail-guide.html" %}
</div>
<div id="panel_users" class="container panel">
<div id="panel_users" class="admin_panel">
{% include "users.html" %}
</div>
<div id="panel_aliases" class="container panel">
<div id="panel_aliases" class="admin_panel">
{% include "aliases.html" %}
</div>
<div id="panel_sync_guide" class="container panel">
<div id="panel_sync_guide" class="admin_panel">
{% include "sync-guide.html" %}
</div>
<div id="panel_web" class="container panel">
<div id="panel_web" class="admin_panel">
{% include "web.html" %}
</div>
<div id="panel_ssl" class="container panel">
<div id="panel_ssl" class="admin_panel">
{% include "ssl.html" %}
</div>
@@ -349,7 +357,7 @@ function show_panel(panelid) {
// we might be passed an HTMLElement <a>.
panelid = panelid.getAttribute('href').substring(1);
$('.panel').hide();
$('.admin_panel').hide();
$('#panel_' + panelid).show();
if (typeof localStorage != 'undefined')
localStorage.setItem("miab-cp-lastpanel", panelid);

View File

@@ -1,7 +1,7 @@
<h1 style="margin: 1em; text-align: center">{{hostname}}</h1>
{% if no_admins_exist %}
<div class="container">
<div class="row">
<div class="col-md-offset-2 col-md-8">
<p class="text-danger">There are no administrative users on this system! To make an administrative user,
log into this machine using SSH (like when you first set it up) and run:</p>
@@ -12,27 +12,24 @@ sudo tools/mail.py user make-admin your@emailaddress.com</pre>
</div>
{% endif %}
<div class="row">
<div class="col-sm-offset-2 col-sm-8 col-md-offset-3 col-md-6 col-lg-offset-4 col-lg-4">
<center>
<p style="margin: 2em">Log in here for your Mail-in-a-Box control panel.</p>
</center>
<p style="margin: 2em; text-align: center;">Log in here for your Mail-in-a-Box control panel.</p>
<div style="margin: 0 auto; max-width: 32em;">
<form class="form-horizontal" role="form" onsubmit="do_login(); return false;">
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">Email</label>
<div class="col-sm-10">
<label for="inputEmail3" class="col-sm-3 control-label">Email</label>
<div class="col-sm-9">
<input name="email" type="email" class="form-control" id="loginEmail" placeholder="Email">
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">Password</label>
<div class="col-sm-10">
<label for="inputPassword3" class="col-sm-3 control-label">Password</label>
<div class="col-sm-9">
<input name="password" type="password" class="form-control" id="loginPassword" placeholder="Password">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="col-sm-offset-3 col-sm-9">
<div class="checkbox">
<label>
<input name='remember' type="checkbox" id="loginRemember"> Remember me
@@ -41,12 +38,11 @@ sudo tools/mail.py user make-admin your@emailaddress.com</pre>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="col-sm-offset-3 col-sm-9">
<button type="submit" class="btn btn-default">Sign in</button>
</div>
</div>
</form>
</div>
</div>

View File

@@ -1,56 +1,46 @@
<style>#panel_mail-guide table.table { width: auto; margin-left: .5em; }</style>
<div class="container">
<div>
<h2 style="margin-bottom: 0">Checking and Sending Mail</h2>
<div class="row">
<div class="col-sm-6">
<h3>How to log in</h3>
<p>Your username and password are the same no matter how you check your mail:</p>
<table class="table" style="max-width: 30em">
<tr><th>Username:</th> <td>Your whole email address.</td></tr>
<tr><th>Password:</th> <td>Your mail password.</td></tr>
</table>
<div class="col-sm-7">
<h3>Webmail</h3>
<p>Webmail lets you check your email from any web browser. Your webmail site is:</p>
<p style="margin-left: 2em"><strong><a href="https://{{hostname}}/mail">https://{{hostname}}/mail</a></strong></p>
<p>Your username is your whole email address.</p>
<h3>Mobile/desktop apps</h3>
<p>When you set up your email on your phone, desktop, or other device, you will be asked to choose a protocol.</p>
<h4>Automatic configuration</h4>
<ul>
<li>On Android devices, look for IMAP and SMTP.</li>
<li>On iOS devices, look for Exchange or ActiveSync.</li>
</ul>
<p>iOS and OS X only: Open <a style="font-weight: bold" href="https://{{hostname}}/mailinabox.mobileconfig">this configuration link</a> on your iOS device or on your Mac desktop to easily set up mail (IMAP/SMTP), Contacts, and Calendar. Your username is your whole email address.</p>
<h4>Manual configuration</h4>
<div class="row">
<div class="col-lg-6">
<h4>IMAP/SMTP settings</h4>
<p>This method is preferred on Android devices but is not available on iOS devices.</p>
<p>Your mail server is <strong>{{hostname}}</strong>. Use the following settings when prompted:</p>
<p>Use the following settings when you set up your email on your phone, desktop, or other device:</p>
<table class="table">
<thead>
<tr><th>Protocol</th> <th>Port</th> <th>Options</th></tr>
<tr><th>Option</th> <th>Value</th></tr>
</thead>
<tr><th>IMAP</th> <td>993</td> <td>SSL</td></tr>
<tr><th>SMTP</th> <td>587</td> <td>STARTTLS <span>(&ldquo;always&rdquo; or &ldquo;required&rdquo;, if prompted)</span></td></tr>
<tr><th>Protocol/Method</th> <td>IMAP</td></tr>
<tr><th>Mail server</th> <td>{{hostname}}</td>
<tr><th>IMAP Port</th> <td>993</td></tr>
<tr><th>IMAP Sercurity</th> <td>SSL</td></tr>
<tr><th>SMTP Port</th> <td>587</td></tr>
<tr><th>SMTP Security</td> <td>STARTTLS <small>(&ldquo;always&rdquo; or &ldquo;required&rdquo;, if prompted)</small></td></tr>
<tr><th>Username:</th> <td>Your whole email address.</td></tr>
<tr><th>Password:</th> <td>Your mail password.</td></tr>
</table>
<p>In addition to setting up your email, you&rsquo;ll also need to set up <a href="#sync_guide" onclick="return show_panel(this);">contacts and calendar synchronization</a> separately.</p>
</div>
<div class="col-lg-6">
<h4>Exchange/ActiveSync settings</h4>
<h4>Exchange/ActiveSync settings</h4>
<p>On iOS devices and devices on this <a href="http://z-push.org/compatibility/">compatibility list</a>, set up your mail as an Exchange or ActiveSync server. Use these settings when prompted:</p>
<p>On iOS devices and devices on this <a href="http://z-push.org/compatibility/">compatibility list</a>, you may set up your mail as an Exchange or ActiveSync server. However, we&rsquo;ve found this to be more buggy than using IMAP. If you encounter any problems, please use the manual settings above.</p>
<table class="table">
<tr><th>Server</th> <td>{{hostname}}</td></tr>
@@ -58,18 +48,24 @@
</table>
<p>Your device should also provide a contacts list and calendar that syncs to this box when you use this method.</p>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="col-sm-5">
<div class="panel panel-default">
<div class="panel-heading">
<h3>Other information about mail on your box</h3>
</div>
<div class="panel-body">
<h4>Greylisting</h4>
<p>Your box using a technique called greylisting to cut down on spam. Greylisting works by delaying mail from people you haven&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>Use this box to send as you</h4>
<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>
<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>
</div>
</div>
</div>
</div>
</div>

View File

@@ -20,7 +20,7 @@
<h3 id="ssl_install_header">Install SSL Certificate</h3>
<p>There are many places where you can get a free or cheap SSL certificate. We recommend <a href="https://www.namecheap.com/cart/remove.aspx?itemid=47016639&i=i2">Namecheap&rsquo;s $9 certificate</a> or <a href="https://www.startssl.com/">StartSSL&rsquo;s free express lane</a>.</p>
<p>There are many places where you can get a free or cheap SSL certificate. We recommend <a href="https://www.namecheap.com/security/ssl-certificates/domain-validation.aspx">Namecheap&rsquo;s $9 certificate</a> or <a href="https://www.startssl.com/">StartSSL&rsquo;s free express lane</a>.</p>
<p>Which domain are you getting an SSL certificate for?</p>
@@ -83,7 +83,7 @@ function ssl_install(elem) {
$('#csr_info').slideDown();
$('#ssl_csr').text('Loading...');
show_csr();
$('html, body').animate({ scrollTop: $('#ssl_install_header').offset().top })
$('html, body').animate({ scrollTop: $('#ssl_install_header').offset().top - $('.navbar-fixed-top').height() - 20 })
return false;
}

View File

@@ -1,4 +1,4 @@
<div class="container">
<div>
<h2>Contacts &amp; Calendar Synchronization</h2>
<p>This box can hold your contacts and calendar, just like it holds your email.</p>

View File

@@ -28,7 +28,7 @@
</tbody>
</table>
<p><small>The size column in the table indicates the size of the encrpyted backup, but the total size on disk shown above includes storage for unencrpyted intermediate files.</small></p>
<p style="margin-top: 2em"><small>The size column in the table indicates the size of the encrpyted backup, but the total size on disk shown above includes storage for unencrpyted intermediate files.</small></p>
<script>
function nice_size(bytes) {
@@ -59,6 +59,12 @@ function show_system_backup() {
$('#backup-status tbody').html("");
var total_disk_size = 0;
if (r.backups.length == 0) {
var tr = $('<tr><td colspan="3">No backups have been made yet.</td></tr>');
$('#backup-status tbody').append(tr);
}
for (var i = 0; i < r.backups.length; i++) {
var b = r.backups[i];
var tr = $('<tr/>');

View File

@@ -2,10 +2,10 @@
# domains for which a mail account has been set up.
########################################################################
import os, os.path, re, rtyaml
import os, os.path, shutil, re, rtyaml
from mailconfig import get_mail_domains
from dns_update import get_custom_dns_config
from dns_update import get_custom_dns_config, do_dns_update
from utils import shell, safe_domain_name, sort_domains
def get_web_domains(env):
@@ -229,14 +229,29 @@ def install_cert(domain, ssl_cert, ssl_chain, env):
if cert_status == "SELF-SIGNED":
cert_status = "This is a self-signed certificate. I can't install that."
os.unlink(fn)
if cert_status_details is not None:
cert_status += " " + cert_status_details
return cert_status
# Copy the certificate to its expected location.
os.makedirs(os.path.dirname(ssl_certificate), exist_ok=True)
os.rename(fn, ssl_certificate)
shutil.move(fn, ssl_certificate)
ret = []
# 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
# that certificate.
if domain == env['PRIMARY_HOSTNAME']:
ret.append( do_dns_update(env) )
shell('check_call', ["/usr/sbin/service", "postfix", "restart"])
shell('check_call', ["/usr/sbin/service", "dovecot", "restart"])
ret.append("mail services restarted")
# Kick nginx so it sees the cert.
return do_web_update(env, ok_status="")
ret.append( do_web_update(env, ok_status="") )
return "\n".join(r for r in ret if r.strip() != "")
def get_web_domains_info(env):
def check_cert(domain):

View File

@@ -7,7 +7,7 @@
#########################################################
if [ -z "$TAG" ]; then
TAG=v0.03
TAG=v0.05
fi
# Are we running as root?
@@ -17,12 +17,12 @@ if [[ $EUID -ne 0 ]]; then
fi
# Clone the Mail-in-a-Box repository if it doesn't exist.
if [ ! -d mailinabox ]; then
if [ ! -d $HOME/mailinabox ]; then
echo Installing git . . .
DEBIAN_FRONTEND=noninteractive apt-get -q -q install -y git < /dev/null
echo
echo Downloading Mail-in-a-Box . . .
echo Downloading Mail-in-a-Box $TAG. . .
git clone \
-b $TAG --depth 1 \
https://github.com/mail-in-a-box/mailinabox \
@@ -38,7 +38,7 @@ cd $HOME/mailinabox
# Update it.
if [ "$TAG" != `git describe` ]; then
echo Updating Mail-in-a-Box to $TAG . . .
git fetch
git fetch --depth 1 --force --prune origin tag $TAG
if ! git checkout -q $TAG; then
echo "Update failed. Did you modify something in `pwd`?"
exit

View File

@@ -1,3 +1,4 @@
#!/bin/bash
# OpenDKIM
# --------
#
@@ -6,6 +7,7 @@
# The DNS configuration for DKIM is done in the management daemon.
source setup/functions.sh # load our functions
source /etc/mailinabox.conf # load global vars
# Install DKIM...
apt_install opendkim opendkim-tools

View File

@@ -51,6 +51,7 @@ mkdir -p "$STORAGE_ROOT/dns/dnssec";
# Requires `RSASHA256`
#
# * .email
# * .guide
FIRST=1 #NODOC
for algo in RSASHA1-NSEC3-SHA1 RSASHA256; do

View File

@@ -161,6 +161,7 @@ tools/editconf.py /etc/postfix/main.cf \
smtpd_recipient_restrictions=permit_sasl_authenticated,permit_mynetworks,"reject_rbl_client zen.spamhaus.org",reject_unlisted_recipient,"check_policy_service inet:127.0.0.1:10023"
# Increase the message size limit from 10MB to 128MB.
# The same limit is specified in nginx.conf for mail submitted via webmail and Z-Push.
tools/editconf.py /etc/postfix/main.cf \
message_size_limit=134217728

View File

@@ -15,7 +15,7 @@ apt_install \
apt-get purge -qq -y owncloud*
# Install ownCloud from source of this version:
owncloud_ver=7.0.2
owncloud_ver=7.0.3
# Check if ownCloud dir exist, and check if version matches owncloud_ver (if either doesn't - install/upgrade)
if [ ! -d /usr/local/lib/owncloud/ ] \

View File

@@ -30,6 +30,20 @@ hide_output pyzor discover
# We've already configured Dovecot to listen on this port.
tools/editconf.py /etc/default/spampd DESTPORT=10026
# Spamassassin normally wraps spam as an attachment inside a fresh
# email with a report about the message. This also protects the user
# from accidentally openening a message with embedded malware.
#
# It's nice to see what rules caused the message to be marked as spam,
# but it's also annoying to get to the original message when it is an
# attachment, modern mail clients are safer now and don't load remote
# content or execute scripts, and it is probably confusing to most users.
#
# Tell Spamassassin not to modify the original message except for adding
# the X-Spam-Status mail header and related headers.
tools/editconf.py /etc/spamassassin/local.cf -s \
report_safe=0
# Bayesean learning
# -----------------
#

View File

@@ -160,4 +160,3 @@ openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint \
echo
echo Then you can confirm the security exception and continue.
echo

View File

@@ -40,6 +40,19 @@ tools/editconf.py /etc/php5/fpm/pool.d/www.conf -c ';' \
# since it depends on what domains we're serving, which we don't know
# until mail accounts have been created.
# Create the iOS/OS X Mobile Configuration file which is exposed via the
# nginx configuration at /mailinabox-mobileconfig.
mkdir -p /var/lib/mailinabox
chmod a+rx /var/lib/mailinabox
cat conf/ios-profile.xml \
| sed "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" \
| sed "s/UUID1/$(cat /proc/sys/kernel/random/uuid)/" \
| sed "s/UUID2/$(cat /proc/sys/kernel/random/uuid)/" \
| sed "s/UUID3/$(cat /proc/sys/kernel/random/uuid)/" \
| sed "s/UUID4/$(cat /proc/sys/kernel/random/uuid)/" \
> /var/lib/mailinabox/mobileconfig.xml
chmod a+r /var/lib/mailinabox/mobileconfig.xml
# make a default homepage
if [ -d $STORAGE_ROOT/www/static ]; then mv $STORAGE_ROOT/www/static $STORAGE_ROOT/www/default; fi # migration #NODOC
mkdir -p $STORAGE_ROOT/www/default

View File

@@ -1,3 +1,4 @@
#!/bin/bash
# Webmail with Roundcube
# ----------------------
@@ -29,7 +30,7 @@ apt_install \
apt-get purge -qq -y roundcube* #NODOC
# Install Roundcube from source if it is not already present or if it is out of date.
VERSION=1.0.2
VERSION=1.0.3
needs_update=0 #NODOC
if [ ! -f /usr/local/lib/roundcubemail/version ]; then
# not installed yet #NODOC
@@ -43,6 +44,7 @@ if [ $needs_update == 1 ]; then
rm -f /tmp/roundcube.tgz
wget -qO /tmp/roundcube.tgz http://downloads.sourceforge.net/project/roundcubemail/roundcubemail/$VERSION/roundcubemail-$VERSION.tar.gz
tar -C /usr/local/lib -zxf /tmp/roundcube.tgz
rm -rf /usr/local/lib/roundcubemail
mv /usr/local/lib/roundcubemail-$VERSION/ /usr/local/lib/roundcubemail
rm -f /tmp/roundcube.tgz
echo $VERSION > /usr/local/lib/roundcubemail/version

View File

@@ -3,7 +3,7 @@
# Z-Push: The Microsoft Exchange protocol server
# ----------------------------------------------
#
# Mostly for use on iOS which doesn't support IMAP.
# Mostly for use on iOS which doesn't support IMAP IDLE.
#
# Although Ubuntu ships Z-Push (as d-push) it has a dependency on Apache
# so we won't install it that way.

View File

@@ -0,0 +1,56 @@
#!/usr/bin/python3
#
# This is a tool Josh uses on his box serving mailinabox.email to parse the nginx
# access log to see how many people are installing Mail-in-a-Box each day, by
# looking at accesses to the bootstrap.sh script.
import re, glob, gzip, os.path, json
import dateutil.parser
outfn = "/home/user-data/www/mailinabox.email/install-stats.json"
# Make a unique list of (date, ip address) pairs so we don't double-count
# accesses that are for the same install.
accesses = set()
# Scan the current and rotated access logs.
for fn in glob.glob("/var/log/nginx/access.log*"):
# Gunzip if necessary.
if fn.endswith(".gz"):
f = gzip.open(fn)
else:
f = open(fn, "rb")
# Loop through the lines in the access log.
with f:
for line in f:
# Find lines that are GETs on /bootstrap.sh by either curl or wget.
m = re.match(rb"(?P<ip>\S+) - - \[(?P<date>.*?)\] \"GET /bootstrap.sh HTTP/.*\" 200 \d+ .* \"(?:curl|wget)", line, re.I)
if m:
date, time = m.group("date").decode("ascii").split(":", 1)
date = dateutil.parser.parse(date).date().isoformat()
ip = m.group("ip").decode("ascii")
accesses.add( (date, ip) )
# Aggregate by date.
by_date = { }
for date, ip in accesses:
by_date[date] = by_date.get(date, 0) + 1
# Since logs are rotated, store the statistics permanently in a JSON file.
# Load in the stats from an existing file.
if os.path.exists(outfn):
existing_data = json.load(open(outfn))
for date, count in existing_data:
if date not in by_date:
by_date[date] = count
# Turn into a list rather than a dict structure to make it ordered.
by_date = sorted(by_date.items())
# Pop the last one because today's stats are incomplete.
by_date.pop(-1)
# Write out.
with open(outfn, "w") as f:
json.dump(by_date, f, sort_keys=True, indent=True)