mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2026-03-13 17:17:23 +01:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3133dcd5a3 | ||
|
|
057c1dd913 | ||
|
|
06f2477cfd | ||
|
|
1abc8ed469 | ||
|
|
cdaa2c847d | ||
|
|
b04addda9a | ||
|
|
7e7abf3b53 | ||
|
|
9b9f5abf8f | ||
|
|
7db80458dd | ||
|
|
5775cab175 | ||
|
|
c872e6a9f0 | ||
|
|
995b7c4d2b | ||
|
|
f797eecaca | ||
|
|
de0ccd0632 | ||
|
|
be9d97902f | ||
|
|
20c5471a89 | ||
|
|
ec73c171c7 | ||
|
|
f9acf0adec | ||
|
|
8b65c11cdf | ||
|
|
34fca29dd3 | ||
|
|
b75fbf22ca | ||
|
|
d790cae0e2 | ||
|
|
a68703dfb3 | ||
|
|
f35b2081a1 | ||
|
|
f0508d8cc9 | ||
|
|
47dd59c2a7 | ||
|
|
c2fe1bc2e3 | ||
|
|
cce1184090 | ||
|
|
1adb1d8307 | ||
|
|
c2174e10a6 | ||
|
|
18283c7df0 | ||
|
|
3ff74c8dc5 | ||
|
|
e997114d6e | ||
|
|
e9aecba4df | ||
|
|
6585384daa | ||
|
|
6bc821676c | ||
|
|
f38ef0223d | ||
|
|
8902e9d1fc | ||
|
|
86a5394f07 | ||
|
|
df5df18820 |
32
CHANGELOG.md
32
CHANGELOG.md
@@ -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
128
conf/ios-profile.xml
Normal 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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
require ["regex", "fileinto", "imap4flags"];
|
||||
|
||||
if allof (header :regex "X-Spam-Status" "^Yes") {
|
||||
setflag "\\Seen";
|
||||
fileinto "Spam";
|
||||
stop;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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>(“always” or “required”, 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>(“always” or “required”, 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’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’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’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>
|
||||
|
||||
@@ -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’s $9 certificate</a> or <a href="https://www.startssl.com/">StartSSL’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’s $9 certificate</a> or <a href="https://www.startssl.com/">StartSSL’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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="container">
|
||||
<div>
|
||||
<h2>Contacts & Calendar Synchronization</h2>
|
||||
|
||||
<p>This box can hold your contacts and calendar, just like it holds your email.</p>
|
||||
|
||||
@@ -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/>');
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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/ ] \
|
||||
|
||||
@@ -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
|
||||
# -----------------
|
||||
#
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
13
setup/web.sh
13
setup/web.sh
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
56
tools/parse-nginx-log-bootstrap-accesses.py
Executable file
56
tools/parse-nginx-log-bootstrap-accesses.py
Executable 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)
|
||||
Reference in New Issue
Block a user