various cleanup related to the new permitted_senders column for aliases

This commit is contained in:
Joshua Tauberer 2015-08-14 23:05:08 +00:00
parent 123ac4fd33
commit 5924d0fe0d
6 changed files with 136 additions and 135 deletions

View File

@ -179,15 +179,15 @@ def mail_aliases():
if request.args.get("format", "") == "json":
return json_response(get_mail_aliases_ex(env))
else:
return "".join(address+"\t"+receivers+"\t"+senders+"\n" for address, receivers, senders in get_mail_aliases(env))
return "".join(address+"\t"+receivers+"\t"+(senders or "")+"\n" for address, receivers, senders in get_mail_aliases(env))
@app.route('/mail/aliases/add', methods=['POST'])
@authorized_personnel_only
def mail_aliases_add():
return add_mail_alias(
request.form.get('address', ''),
request.form.get('receivers', ''),
request.form.get('senders', ''),
request.form.get('forwards_to', ''),
request.form.get('permitted_senders', ''),
env,
update_if_exists=(request.form.get('update_if_exists', '') == '1')
)

View File

@ -183,11 +183,11 @@ def get_admins(env):
def get_mail_aliases(env):
# Returns a sorted list of tuples of (address, forward-tos, permitted-senders).
c = open_database(env)
c.execute('SELECT address, receivers, senders FROM aliases')
aliases = { row[0]: row[1:3] for row in c.fetchall() } # make dict
c.execute('SELECT source, destination, permitted_senders FROM aliases')
aliases = { row[0]: row for row in c.fetchall() } # make dict
# put in a canonical order: sort by domain, then by email address lexicographically
aliases = [ (address,) + aliases[address] for address in utils.sort_email_addresses(aliases.keys(), env) ]
aliases = [ aliases[address] for address in utils.sort_email_addresses(aliases.keys(), env) ]
return aliases
def get_mail_aliases_ex(env):
@ -201,8 +201,8 @@ def get_mail_aliases_ex(env):
# {
# address: "name@domain.tld", # IDNA-encoded
# address_display: "name@domain.tld", # full Unicode
# receivers: ["user1@domain.com", "receiver-only1@domain.com", ...],
# senders: ["user1@domain.com", "sender-only1@domain.com", ...],
# forwards_to: ["user1@domain.com", "receiver-only1@domain.com", ...],
# permitted_senders: ["user1@domain.com", "sender-only1@domain.com", ...] OR null,
# required: True|False
# },
# ...
@ -213,7 +213,7 @@ def get_mail_aliases_ex(env):
required_aliases = get_required_aliases(env)
domains = {}
for address, receivers, senders in get_mail_aliases(env):
for address, forwards_to, permitted_senders in get_mail_aliases(env):
# get alias info
domain = get_domain(address)
required = (address in required_aliases)
@ -227,8 +227,8 @@ def get_mail_aliases_ex(env):
domains[domain]["aliases"].append({
"address": address,
"address_display": prettify_idn_email_address(address),
"receivers": [prettify_idn_email_address(r.strip()) for r in receivers.split(",")],
"senders": [prettify_idn_email_address(s.strip()) for s in senders.split(",")],
"forwards_to": [prettify_idn_email_address(r.strip()) for r in forwards_to.split(",")],
"permitted_senders": [prettify_idn_email_address(s.strip()) for s in permitted_senders.split(",")] if permitted_senders is not None else None,
"required": required,
})
@ -408,7 +408,7 @@ def add_remove_mail_user_privilege(email, priv, action, env):
return "OK"
def add_mail_alias(address, receivers, senders, env, update_if_exists=False, do_kick=True):
def add_mail_alias(address, forwards_to, permitted_senders, env, update_if_exists=False, do_kick=True):
# convert Unicode domain to IDNA
address = sanitize_idn_email_address(address)
@ -423,9 +423,9 @@ def add_mail_alias(address, receivers, senders, env, update_if_exists=False, do_
if not validate_email(address, mode='alias'):
return ("Invalid email address (%s)." % address, 400)
# validate receivers
validated_receivers = []
receivers = receivers.strip()
# validate forwards_to
validated_forwards_to = []
forwards_to = forwards_to.strip()
# extra checks for email addresses used in domain control validation
is_dcv_source = is_dcv_address(address)
@ -435,14 +435,14 @@ def add_mail_alias(address, receivers, senders, env, update_if_exists=False, do_
# try to convert Unicode to IDNA first before validating that it's a
# legitimate alias address. Don't allow this sort of rewriting for
# DCV source addresses.
r1 = sanitize_idn_email_address(receivers)
r1 = sanitize_idn_email_address(forwards_to)
if validate_email(r1, mode='alias') and not is_dcv_source:
validated_receivers.append(r1)
validated_forwards_to.append(r1)
else:
# Parse comma and \n-separated destination emails & validate. In this
# case, the receivers must be complete email addresses.
for line in receivers.split("\n"):
# case, the forwards_to must be complete email addresses.
for line in forwards_to.split("\n"):
for email in line.split(","):
email = email.strip()
if email == "": continue
@ -454,36 +454,45 @@ def add_mail_alias(address, receivers, senders, env, update_if_exists=False, do_
# requiring aliases for email addresses typically used in DCV to forward
# only to accounts that are administrators on this system.
return ("This alias can only have administrators of this system as destinations because the address is frequently used for domain control validation.", 400)
validated_receivers.append(email)
receivers = ",".join(validated_receivers)
validated_forwards_to.append(email)
# validate permitted_senders
valid_logins = get_mail_users(env)
validated_permitted_senders = []
permitted_senders = permitted_senders.strip()
# validate senders
validated_senders = []
senders = senders.strip()
# Parse comma and \n-separated sender logins & validate. The senders must be
# Parse comma and \n-separated sender logins & validate. The permitted_senders must be
# valid usernames.
for line in senders.split("\n"):
for line in permitted_senders.split("\n"):
for login in line.split(","):
login = login.strip()
if login == "": continue
if login not in valid_logins:
return ("Invalid sender login (%s)." % login, 400)
validated_senders.append(login)
senders = ",".join(validated_senders)
return ("Invalid permitted sender: %s is not a user on this system." % login, 400)
validated_permitted_senders.append(login)
# Make sure the alias has either a forwards_to or a permitted_sender.
if len(validated_forwards_to) + len(validated_permitted_senders) == 0:
return ("The alias must either forward to an address or have a permitted sender.", 400)
# save to db
forwards_to = ",".join(validated_forwards_to)
if len(validated_permitted_senders) == 0:
permitted_senders = None
else:
permitted_senders = ",".join(validated_permitted_senders)
conn, c = open_database(env, with_connection=True)
try:
c.execute("INSERT INTO aliases (address, receivers, senders) VALUES (?, ?, ?)", (address, receivers, senders))
c.execute("INSERT INTO aliases (source, destination, permitted_senders) VALUES (?, ?, ?)", (address, forwards_to, permitted_senders))
return_status = "alias added"
except sqlite3.IntegrityError:
if not update_if_exists:
return ("Alias already exists (%s)." % address, 400)
else:
c.execute("UPDATE aliases SET receivers = ?, senders = ? WHERE address = ?", (receivers, senders, address))
c.execute("UPDATE aliases SET destination = ?, permitted_senders = ? WHERE source = ?", (forwards_to, permitted_senders, address))
return_status = "alias updated"
conn.commit()
@ -498,7 +507,7 @@ def remove_mail_alias(address, env, do_kick=True):
# remove
conn, c = open_database(env, with_connection=True)
c.execute("DELETE FROM aliases WHERE address=?", (address,))
c.execute("DELETE FROM aliases WHERE source=?", (address,))
if c.rowcount != 1:
return ("That's not an alias (%s)." % address, 400)
conn.commit()
@ -573,13 +582,13 @@ def kick(env, mail_result=None):
# Remove auto-generated postmaster/admin on domains we no
# longer have any other email addresses for.
for address, receivers, *_ in existing_aliases:
for address, forwards_to, *_ in existing_aliases:
user, domain = address.split("@")
if user in ("postmaster", "admin") \
and address not in required_aliases \
and receivers == get_system_administrator(env):
and forwards_to == get_system_administrator(env):
remove_mail_alias(address, env, do_kick=False)
results.append("removed alias %s (was to %s; domain no longer used for email)\n" % (address, receivers))
results.append("removed alias %s (was to %s; domain no longer used for email)\n" % (address, forwards_to))
# Update DNS and nginx in case any domains are added/removed.

View File

@ -7,9 +7,11 @@
<h3>Add a mail alias</h3>
<p>An alias can forward email to a <a href="javascript:show_panel('users')">mail user</a> or to any email address. You can separately grant permission to one or more users to send as an alias.</p>
<p>Aliases are email forwarders. An alias can forward email to a <a href="javascript:show_panel('users')">mail user</a> or to any email address.</p>
<form class="form-horizontal" role="form" onsubmit="do_add_alias(); return false;">
<p>To use an alias or any address besides your own login username in outbound mail, the sending user must be included as a permitted sender for the alias.</p>
<form id="addalias-form" class="form-horizontal" role="form" onsubmit="do_add_alias(); return false;">
<div class="form-group">
<div class="col-sm-offset-1 col-sm-11">
<div id="alias_type_buttons" class="btn-group btn-group-xs">
@ -18,8 +20,8 @@
<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>
<span class="catchall hidden">A catch-all alias captures all otherwise unmatched email to a domain.</span>
<span class="domainalias hidden">A domain alias forwards all otherwise unmatched email from one domain to another domain, preserving the part before the @-sign.</span>
</div>
</div>
</div>
@ -27,19 +29,39 @@
<label for="addaliasAddress" class="col-sm-1 control-label">Alias</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="addaliasAddress">
<div style="margin-top: 3px; padding-left: 3px; font-size: 90%" class="text-muted">You may use international (non-ASCII) characters for the domain part of the email address only.</div>
<div style="margin-top: 3px; padding-left: 3px; font-size: 90%" class="text-muted">
<span class="catchall domainalias">Enter just the part of an email address starting with the @-sign.</span>
You may use international (non-ASCII) characters for the domain part of the email address only.
</div>
</div>
</div>
<div class="form-group">
<label for="addaliasReceivers" class="col-sm-1 control-label">Forwards To</label>
<label for="addaliasForwardsTo" class="col-sm-1 control-label">Forwards To</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" id="addaliasReceivers"></textarea>
<textarea class="form-control" rows="3" id="addaliasForwardsTo"></textarea>
<div style="margin-top: 3px; padding-left: 3px; font-size: 90%" class="text-muted">
<span class="domainalias">Enter just the part of an email address starting with the @-sign.</span>
</div>
</div>
</div>
<div class="form-group">
<label for="addaliasSenders" class="col-sm-1 control-label">Permitted Senders</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" id="addaliasSenders"></textarea>
<div class="radio">
<label>
<input id="addaliasForwardsToNotAdvanced" name="addaliasForwardsToDivToggle" type="radio" checked onclick="$('#addaliasForwardsToDiv').toggle(false)">
Any mail user listed in the Fowards To box can send mail claiming to be from <span class="regularalias">the alias address</span><span class="catchall domainalias">any address on the alias domain</span>.
</label>
</div>
<div class="radio">
<label>
<input id="addaliasForwardsToAdvanced" name="addaliasForwardsToDivToggle" type="radio" id="addaliasForwardsToDivShower" onclick="$('#addaliasForwardsToDiv').toggle(true)">
I&rsquo;ll enter the mail users that can send mail claiming to be from <span class="regularalias">the alias address</span><span class="catchall domainalias">any address on the alias domain</span>.
</label>
</div>
<div id="addaliasForwardsToDiv" style="margin-top: .5em; margin-left: 1.4em; display: none;">
<textarea class="form-control" rows="3" id="addaliasSenders" placeholder="one user per line or separated by commas"></textarea>
</div>
</div>
</div>
<div class="form-group">
@ -78,7 +100,7 @@
</a>
</td>
<td class='address'> </td>
<td class='receivers'> </td>
<td class='forwardsTo'> </td>
<td class='senders'> </td>
</tr>
</table>
@ -108,10 +130,10 @@ function show_aliases() {
if (alias.required) n.addClass('alias-required');
n.attr('data-address', alias.address_display); // this is decoded from IDNA, but will get re-coded to IDNA on the backend
n.find('td.address').text(alias.address_display)
for (var j = 0; j < alias.receivers.length; j++)
n.find('td.receivers').append($("<div></div>").text(alias.receivers[j]))
for (var j = 0; j < alias.senders.length; j++)
n.find('td.senders').append($("<div></div>").text(alias.senders[j]))
for (var j = 0; j < alias.forwards_to.length; j++)
n.find('td.forwardsTo').append($("<div></div>").text(alias.forwards_to[j]))
for (var j = 0; j < (alias.permitted_senders ? alias.permitted_senders.length : 0); j++)
n.find('td.senders').append($("<div></div>").text(alias.permitted_senders[j]))
$('#alias_table tbody').append(n);
}
}
@ -121,28 +143,25 @@ function show_aliases() {
$('#alias_type_buttons button').off('click').click(function() {
$('#alias_type_buttons button').removeClass('active');
$(this).addClass('active');
$('#addalias-form .regularalias, #addalias-form .catchall, #addalias-form .domainalias').addClass('hidden');
if ($(this).attr('data-mode') == "regular") {
$('#addaliasAddress').attr('type', 'email');
$('#addaliasAddress').attr('placeholder', 'incoming email address (e.g. you@yourdomain.com)');
$('#addaliasReceivers').attr('placeholder', 'forward to these email addresses (one per line or separated by commas)');
$('#addaliasSenders').attr('placeholder', 'allow these users to send as this alias (one per line or separated by commas)');
$('#addaliasAddress').attr('placeholder', 'you@yourdomain.com (incoming email address)');
$('#addaliasForwardsTo').attr('placeholder', 'one address per line or separated by commas');
$('#alias_mode_info').slideUp();
$('#addalias-form .regularalias').removeClass('hidden');
} else if ($(this).attr('data-mode') == "catchall") {
$('#addaliasAddress').attr('type', 'text');
$('#addaliasAddress').attr('placeholder', 'incoming catch-all address (e.g. @yourdomain.com)');
$('#addaliasReceivers').attr('placeholder', 'forward to these email addresses (one per line or separated by commas)');
$('#addaliasSenders').attr('placeholder', 'allow these users to send as any address on this domain (one per line or separated by commas)');
$('#addaliasAddress').attr('placeholder', '@yourdomain.com (incoming catch-all domain)');
$('#addaliasForwardsTo').attr('placeholder', 'one address per line or separated by commas');
$('#alias_mode_info').slideDown();
$('#alias_mode_info span').addClass('hidden');
$('#alias_mode_info span.catchall').removeClass('hidden');
$('#addalias-form .catchall').removeClass('hidden');
} else if ($(this).attr('data-mode') == "domainalias") {
$('#addaliasAddress').attr('type', 'text');
$('#addaliasAddress').attr('placeholder', 'incoming domain (@yourdomain.com)');
$('#addaliasReceivers').attr('placeholder', 'forward to domain (@yourdomain.com)');
$('#addaliasSenders').attr('placeholder', 'allow these users to send as any address on this domain (one per line or separated by commas)');
$('#addaliasAddress').attr('placeholder', '@yourdomain.com (incoming catch-all domain)');
$('#addaliasForwardsTo').attr('placeholder', '@otherdomain.com (forward to other domain)');
$('#alias_mode_info').slideDown();
$('#alias_mode_info span').addClass('hidden');
$('#alias_mode_info span.domainalias').removeClass('hidden');
$('#addalias-form .domainalias').removeClass('hidden');
}
})
$('#alias_type_buttons button[data-mode="regular"]').click(); // init
@ -153,16 +172,20 @@ var is_alias_add_update = false;
function do_add_alias() {
var title = (!is_alias_add_update) ? "Add Alias" : "Update Alias";
var form_address = $("#addaliasAddress").val();
var form_receivers = $("#addaliasReceivers").val();
var form_senders = $("#addaliasSenders").val();
var form_forwardsto = $("#addaliasForwardsTo").val();
var form_senders = ($('#addaliasForwardsToAdvanced').prop('checked') ? $("#addaliasSenders").val() : '');
if ($('#addaliasForwardsToAdvanced').prop('checked') && !/\S/.exec($("#addaliasSenders").val())) {
show_modal_error(title, "You did not enter any permitted senders.");
return false;
}
api(
"/mail/aliases/add",
"POST",
{
update_if_exists: is_alias_add_update ? '1' : '0',
address: form_address,
receivers: form_receivers,
senders: form_senders
forwards_to: form_forwardsto,
permitted_senders: form_senders
},
function(r) {
// Responses are multiple lines of pre-formatted text.
@ -179,7 +202,7 @@ function do_add_alias() {
function aliases_reset_form() {
$("#addaliasAddress").prop('disabled', false);
$("#addaliasAddress").val('')
$("#addaliasReceivers").val('')
$("#addaliasForwardsTo").val('')
$("#addaliasSenders").val('')
$('#alias-cancel').addClass('hidden');
$('#add-alias-button').text('Add Alias');
@ -188,15 +211,15 @@ function aliases_reset_form() {
function aliases_edit(elem) {
var address = $(elem).parents('tr').attr('data-address');
var receiverdivs = $(elem).parents('tr').find('.receivers div');
var receiverdivs = $(elem).parents('tr').find('.forwardsTo div');
var senderdivs = $(elem).parents('tr').find('.senders div');
var receivers = "";
var forwardsTo = "";
for (var i = 0; i < receiverdivs.length; i++)
receivers += $(receiverdivs[i]).text() + "\n";
forwardsTo += $(receiverdivs[i]).text() + "\n";
var senders = "";
for (var i = 0; i < senderdivs.length; i++)
senders += $(senderdivs[i]).text() + "\n";
if (address.charAt(0) == '@' && receivers.charAt(0) == '@')
if (address.charAt(0) == '@' && forwardsTo.charAt(0) == '@')
$('#alias_type_buttons button[data-mode="domainalias"]').click();
else if (address.charAt(0) == '@')
$('#alias_type_buttons button[data-mode="catchall"]').click();
@ -205,7 +228,9 @@ function aliases_edit(elem) {
$('#alias-cancel').removeClass('hidden');
$("#addaliasAddress").prop('disabled', true);
$("#addaliasAddress").val(address);
$("#addaliasReceivers").val(receivers);
$("#addaliasForwardsTo").val(forwardsTo);
$('#addaliasForwardsToAdvanced').prop('checked', senders != "");
$('#addaliasForwardsToNotAdvanced').prop('checked', senders == "");
$("#addaliasSenders").val(senders);
$('#add-alias-button').text('Update');
$('body').animate({ scrollTop: 0 })

View File

@ -21,7 +21,7 @@ db_path=$STORAGE_ROOT/mail/users.sqlite
if [ ! -f $db_path ]; then
echo Creating new user database: $db_path;
echo "CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL UNIQUE, password TEXT NOT NULL, extra, privileges TEXT NOT NULL DEFAULT '');" | sqlite3 $db_path;
echo "CREATE TABLE aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, address TEXT NOT NULL UNIQUE, receivers TEXT NOT NULL, senders TEXT NOT NULL);" | sqlite3 $db_path;
echo "CREATE TABLE aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT NOT NULL UNIQUE, destination TEXT NOT NULL, permitted_senders TEXT);" | sqlite3 $db_path;
fi
# ### User Authentication
@ -71,18 +71,23 @@ tools/editconf.py /etc/postfix/main.cf \
# ### Sender Validation
# Use a Sqlite3 database to set login maps. This is used with
# reject_authenticated_sender_login_mismatch to see if the user is
# allowed to send mail as the FROM address specified in the request.
# We use Postfix's reject_authenticated_sender_login_mismatch filter to
# prevent intra-domain spoofing by logged in but untrusted users in outbound
# email. In all outbound mail (the sender has authenticated), the MAIL FROM
# address (aka envelope or return path address) must be "owned" by the user
# who authenticated. An SQL query will find who are the owners of any given
# address.
tools/editconf.py /etc/postfix/main.cf \
smtpd_sender_login_maps=sqlite:/etc/postfix/sender-login-maps.cf
# SQL statement that returns a list of addresses/domains the logged in username
# is allowed to send as. This is similar to virtual-alias-maps.cf (see below).
# Matches from the users table take priority over (direct) aliases.
# Postfix will query the exact address first, where the priority will be alias
# records first, then user records. If there are no matches for the exact
# address, then Postfix will query just the domain part, which we call
# catch-alls and domain aliases. A NULL permitted_senders column means to
# take the value from the destination column.
cat > /etc/postfix/sender-login-maps.cf << EOF;
dbpath=$db_path
query = SELECT senders from (SELECT senders, 0 as priority FROM aliases WHERE address='%s' UNION SELECT email as senders, 1 as priority FROM users WHERE email='%s') ORDER BY priority LIMIT 1;
query = SELECT permitted_senders FROM (SELECT permitted_senders, 0 AS priority FROM aliases WHERE source='%s' AND permitted_senders IS NOT NULL UNION SELECT destination AS permitted_senders, 1 AS priority FROM aliases WHERE source='%s' AND permitted_senders IS NULL UNION SELECT email as permitted_senders, 2 AS priority FROM users WHERE email='%s') ORDER BY priority LIMIT 1;
EOF
# ### Destination Validation
@ -95,13 +100,13 @@ tools/editconf.py /etc/postfix/main.cf \
virtual_alias_maps=sqlite:/etc/postfix/virtual-alias-maps.cf \
local_recipient_maps=\$virtual_mailbox_maps
# SQL statement to check if we handle mail for a domain, either for users or aliases.
# SQL statement to check if we handle incoming mail for a domain, either for users or aliases.
cat > /etc/postfix/virtual-mailbox-domains.cf << EOF;
dbpath=$db_path
query = SELECT 1 FROM users WHERE email LIKE '%%@%s' UNION SELECT 1 FROM aliases WHERE address LIKE '%%@%s'
query = SELECT 1 FROM users WHERE email LIKE '%%@%s' UNION SELECT 1 FROM aliases WHERE source LIKE '%%@%s'
EOF
# SQL statement to check if we handle mail for a user.
# SQL statement to check if we handle incoming mail for a user.
cat > /etc/postfix/virtual-mailbox-maps.cf << EOF;
dbpath=$db_path
query = SELECT 1 FROM users WHERE email='%s'
@ -127,9 +132,13 @@ EOF
# might be returned by the UNION, so the whole query is wrapped in
# another select that prioritizes the alias definition to preserve
# postfix's preference for aliases for whole email addresses.
#
# Since we might have alias records with an empty destination because
# it might have just permitted_senders, skip any records with an
# empty destination here so that other lower priority rules might match.
cat > /etc/postfix/virtual-alias-maps.cf << EOF;
dbpath=$db_path
query = SELECT receivers from (SELECT receivers, 0 as priority FROM aliases WHERE address='%s' UNION SELECT email as receivers, 1 as priority FROM users WHERE email='%s') ORDER BY priority LIMIT 1;
query = SELECT destination from (SELECT destination, 0 as priority FROM aliases WHERE source='%s' AND destination<>'' UNION SELECT email as destination, 1 as priority FROM users WHERE email='%s') ORDER BY priority LIMIT 1;
EOF
# Restart Services

View File

@ -102,56 +102,14 @@ def migration_8(env):
os.unlink(os.path.join(env['STORAGE_ROOT'], 'mail/dkim/mail.private'))
def migration_9(env):
# Switch from storing alias ownership in one column (used for both
# directions) to two columns (one for determining inbound forward-tos and
# one for determining outbound permitted-senders). This was motivated by the
# addition of #427 ("Reject outgoing mail if FROM does not match Login") -
# which introduced the notion of outbound permitted-senders.
# Add a column to the aliases table to store permitted_senders,
# which is a list of user account email addresses that are
# permitted to send mail using this alias instead of their own
# address. This was motivated by the addition of #427 ("Reject
# outgoing mail if FROM does not match Login") - which introduced
# the notion of outbound permitted-senders.
db = os.path.join(env["STORAGE_ROOT"], 'mail/users.sqlite')
# Move the old aliases table to one side.
shell("check_call", ["sqlite3", db, "ALTER TABLE aliases RENAME TO aliases_8"])
# Create the new aliases table, initially empty.
shell("check_call", ["sqlite3", db, "CREATE TABLE aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, address TEXT NOT NULL UNIQUE, receivers TEXT NOT NULL, senders TEXT NOT NULL)"])
import sqlite3
conn = sqlite3.connect(os.path.join(env["STORAGE_ROOT"], "mail/users.sqlite"))
c = conn.cursor()
c.execute('SELECT email FROM users')
valid_logins = [ row[0] for row in c.fetchall() ]
c = conn.cursor()
c.execute('SELECT source, destination FROM aliases_8')
aliases = { row[0]: row[1] for row in c.fetchall() }
# Populate the new aliases table. Forward-to addresses (receivers) is taken
# directly from the old destination column. Permitted-sender logins
# (senders) is made up of only those addresses in the old destination column
# that are valid logins, as other values are not relevant. Their presence
# would not do any harm, except that it would make the aliases UI confusing
# on upgraded boxes.
for source in aliases:
address = source
receivers = aliases[source]
validated_senders = []
for login in aliases[source].split(","):
login = login.strip()
if login == "": continue
if login in valid_logins:
validated_senders.append(login)
senders = ",".join(validated_senders)
c = conn.cursor()
c.execute("INSERT INTO aliases (address, receivers, senders) VALUES (?, ?, ?)", (address, receivers, senders))
# Save.
conn.commit()
# Delete the old aliases table.
shell("check_call", ["sqlite3", db, "DROP TABLE aliases_8"])
shell("check_call", ["sqlite3", db, "ALTER TABLE aliases ADD permitted_senders TEXT"])
def get_current_migration():
ver = 0

View File

@ -120,10 +120,10 @@ elif sys.argv[1] == "alias" and len(sys.argv) == 2:
print(mgmt("/mail/aliases"))
elif sys.argv[1] == "alias" and sys.argv[2] == "add" and len(sys.argv) == 5:
print(mgmt("/mail/aliases/add", { "source": sys.argv[3], "destination": sys.argv[4] }))
print(mgmt("/mail/aliases/add", { "address": sys.argv[3], "forwards_to": sys.argv[4] }))
elif sys.argv[1] == "alias" and sys.argv[2] == "remove" and len(sys.argv) == 4:
print(mgmt("/mail/aliases/remove", { "source": sys.argv[3] }))
print(mgmt("/mail/aliases/remove", { "address": sys.argv[3] }))
else:
print("Invalid command-line arguments.")