aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/Fripost/Panel/Interface.pm309
-rw-r--r--lib/Fripost/Schema/Local.pm371
-rw-r--r--templates/add-alias.html18
-rw-r--r--templates/add-list.html23
-rw-r--r--templates/add-user.html18
-rw-r--r--templates/edit-alias.html20
-rw-r--r--templates/edit-list.html16
-rw-r--r--templates/edit-user.html26
-rw-r--r--templates/list-locals.html4
9 files changed, 426 insertions, 379 deletions
diff --git a/lib/Fripost/Panel/Interface.pm b/lib/Fripost/Panel/Interface.pm
index a0c9dd9..7f7d770 100644
--- a/lib/Fripost/Panel/Interface.pm
+++ b/lib/Fripost/Panel/Interface.pm
@@ -216,8 +216,8 @@ sub ListLocals : Runmode {
my @lists = grep { $_->{type} eq 'list' } @locals;
# Add a link to the list (external) homepage.
- map { $_->{listURL} = $CFG{'listurl_'.$_->{transport}}.
- email_to_ascii($_->{name}.'@'.$domainname) }
+ map { $_->{list_URL} = $CFG{'listurl_'.$_->{transport}}.
+ email_to_ascii($_->{name}.'@'.$domainname) }
@lists;
my $template = $self->load_tmpl( 'list-locals.html', cache => 1,
@@ -256,258 +256,183 @@ sub ListLocals : Runmode {
# Can the user add lists?
$template->param( canAddList => $domain->{permissions} =~ /[lop]/ );
$template->param( listCanAddList => [ map { {item => encode_entities($_)} }
- @{$domain->{canAddList}} ] )
+ @{$domain->{canAddList}} ] )
if $domain->{permissions} =~ /[op]/;
# Should we list lists?
$template->param( listLists => $#lists >= 0 ||
$domain->{permissions} =~ /[lop]/ );
$template->param( lists => [
- map { {&fill_HTML_template_from_entry ($_, -loop => ['destination'])} }
+ map { { &fill_HTML_template_from_entry ($_, -loop => ['destination'] )
+ , isPending => $_->{isPending}
+ } }
@lists ]);
return $template->output;
}
-# In this Run Mode authenticated users can edit the entry (if they have
-# the permission).
-sub EditLocal : Runmode {
+# In this Run Mode authenticated users can add users, aliases and lists
+# (if they have the permission).
+sub AddLocal : Runmode {
my $self = shift;
my %CFG = $self->cfg;
my $q = $self->query;
- return $self->redirect('../') if defined $q->param('cancel');
+ return $self->redirect('./') if defined $q->param('cancel'); # Cancellation
+ # Get the domain name from the URL.
+ my $domainname = ($self->split_path)[1];
+ my $t = $q->param('t') // return $self->redirect('./');
+ return $self->redirect('./') unless grep { $t eq $_ } qw/user alias list/;
my $fp = Fripost::Schema::->SASLauth( $self->authen->username, %CFG );
- # Search for *the* matching user, alias or list.
- my ($d,$l) = ($self->split_path)[1,2];
- $fp->domain->get ($d, -die => 404, -assert_exist => 1);
- my %local = $fp->local->get ($l.'@'.$d, -die => 404,
- -concat => "\x{0D}\x{0A}" );
- die "Unknown type" unless grep { $local{type} eq $_ }
- qw/user alias list/;
- die "404\n" if $local{ispending};
-
my $error; # Tells whether the change submission has failed.
- my $t = $local{type};
-
- if (defined $q->param('a') and $q->param('a') eq 'delete') {
- # Delete the entry
- $error = $fp->$t->delete($l.'@'.$d, -die => 0);
- unless ($error) {
- $fp->done;
- return $self->redirect('../');
- }
- }
if (defined $q->param('submit')) {
# Changes have been submitted: process them
- my %entry;
- if ($t eq 'user') {
- $entry{user} = $l.'@'.$d;
- $entry{forwards} = $q->param('forwards') // undef;
-
- if (($q->param('oldpw') // '') ne '' or
- ($q->param('newpw') // '') ne '' or
- ($q->param('newpw2') // '') ne '') {
- # If the user tries to change the password, we make her
- # bind first, to prevent an attacker from setting a
- # custom password and accessing the emails.
- if ($q->param('newpw') ne $q->param('newpw2')) {
- $error = "Passwords do not match";
- }
- elsif (length $q->param('newpw') < $CFG{password_min_length}) {
- $error = "Password should be at least "
- .$CFG{password_min_length}
- ." characters long.";
- }
- else {
- my $fp;
- eval {
- my $u = email_to_unicode($self->authen->username);
- $fp = Fripost::Schema::->auth(
- $u,
- $q->param('oldpw') // '',
- %CFG,
- -die => "Wrong password (for ‘".$u."’)." );
- };
- $error = $@ || $fp->user->passwd(
- $entry{user},
- Fripost::Password::hash($q->param('newpw') // '')
- );
- $fp->done if defined $fp;
- }
+ my $local = &parse_CGI_query($q);
+ $local->{type} = $q->param('t');
+ $local->{name} = $q->param('name').'@'.$domainname;
+ my %rest;
+
+ if ($q->param('password') || $q->param('password2')) {
+ if ($q->param('password') ne $q->param('password2')) {
+ $error = "Passwords do not match";
}
+ # TODO: ! move that to Password.pm
+ elsif (length $q->param('password') < $CFG{password_min_length}) {
+ $error = "Password should be at least "
+ .$CFG{password_min_length}
+ ." characters long.";
+ }
+ else {
+ $local->{password} = Fripost::Password::hash($q->param('password'));
+ }
+ # TODO: inherit the user quota from the postmaster's?
}
- elsif ($t eq 'alias') {
- $entry{alias} = $l.'@'.$d;
- $entry{maildrop} = $q->param('maildrop') // undef;
- }
- elsif ($t eq 'list') {
- $entry{list} = $l.'@'.$d;
- $entry{transport} = $q->param('transport') // undef;
- }
- $entry{isactive} = $q->param('isactive') // 1;
- $entry{description} = $q->param('description') // undef;
- $error = $fp->$t->replace( \%entry, -concat => "(\n|\x{0D}\x{0A})")
- unless $error;
- }
+ $local->{password} = $q->param('password') if $t eq 'list';
- my $template = $self->load_tmpl( "edit-$t.html", cache => 1 );
- $template->param( $self->userInfo );
- $template->param( domain => encode_entities($d) );
+ $rest{gpg} = { use_agent => 0
+ , keydir => $CFG{gpghome}
+ , key => $CFG{gpg_private_key_id}
+ , passphrase => $CFG{gpg_private_key_passphrase}
+ };
- if ($error and defined $q->param('submit')) {
- # Preserve the (incorrect) form, except the passwords
- if ($t eq 'user') {
- $template->param( user => encode_entities($l)
- , forwards => $q->param('forwards') // undef );
- }
- elsif ($t eq 'alias') {
- $template->param( alias => encode_entities($l)
- , maildrop => $q->param('maildrop') // undef );
- }
- elsif ($t eq 'list') {
- $template->param( list => encode_entities($l) );
- }
- $template->param( isactive => $q->param('isactive') // 1
- , description => $q->param('description') // undef );
- }
- else {
- %local = $fp->local->get ($l.'@'.$d, -die => 404,
- -concat => "\x{0D}\x{0A}" );
- if ($t eq 'user') {
- $template->param( user => encode_entities($local{user})
- , forwards => encode_entities($local{forwards}) );
- }
- elsif ($t eq 'alias') {
- $template->param( alias => encode_entities($local{alias})
- , maildrop => encode_entities($local{maildrop}) );
- }
- elsif ($t eq 'list') {
- $template->param( list => encode_entities($local{list}) );
+ unless ($error) {
+ my $fp = Fripost::Schema::->SASLauth( $self->authen->username, %CFG );
+ $fp->domain->search ($domainname, -filter => 'unlocked', -count => 1)
+ or die "404\n";
+ $fp->local->add( $local, %rest, -error => \$error );
+ $fp->done;
+ return $self->redirect('./') unless $error;
}
- $template->param( isactive => $local{isactive}
- , description => $local{description} );
}
- $fp->done;
- my $news = (defined $q->param('submit') or
- (defined $q->param('a') and $q->param('a') eq 'delete'));
- $template->param( newChanges => $news );
+
+ # Do not send passwords back to the sender.
+ $q->delete(qw/password password2/);
+
+ my $template = $self->load_tmpl( "add-$t.html", cache => 1 );
+ $template->param( $self->userInfo
+ , domainname => encode_entities($domainname)
+ , &fill_HTML_template_from_query ($q));
+ $template->param( transport =>
+ [ { item => 'mailman', selected => $q->param('transport') eq 'mailman', name => 'GNU Mailman' }
+ , { item => 'schleuder', selected => $q->param('transport') eq 'schleuder', name => 'Schleuder' }
+ ]) # TODO ugly
+ if $t eq 'list' and defined $q->param('transport');
$template->param( error => encode_entities ($error) ) if $error;
- $template->param( canDelete => 1 ) if $t eq 'alias';
- $template->param( listURL => $CFG{'listurl_'.$local{transport}}.
- email_to_ascii($l.'@'.$d) )
- if $t eq 'list';
- $q->delete('a');
return $template->output;
}
-
-# In this Run Mode authenticated users can add users, aliases and lists
-# (if they have the permission).
-sub AddLocal : Runmode {
+# In this Run Mode authenticated users can edit the entry (if they have
+# the permission).
+sub EditLocal : Runmode {
my $self = shift;
my %CFG = $self->cfg;
my $q = $self->query;
- return $self->redirect('./') if defined $q->param('cancel');
+ return $self->redirect('./') if defined $q->param('cancel'); # Cancellation
+
+ # Get the domain name from the URL.
+ my ($localname,$domainname) = ($self->split_path)[2,1];
+ my $name = $localname.'@'.$domainname;
+ my $fp = Fripost::Schema::->SASLauth( $self->authen->username, %CFG );
+
+ # Search for *the* matching user, alias or list.
+ $fp->domain->search ($domainname, -filter => 'unlocked', -count => 1)
+ or die "404\n";
+ my $local = $fp->local->search ($name, -filter => 'unlocked')
+ or die "404\n";
- my $d = ($self->split_path)[1];
- my $t = $q->param('t') // die "Undefined type";
my $error; # Tells whether the change submission has failed.
+ if (defined $q->param('a') and $q->param('a') eq 'delete') {
+ # Delete the entry
+ $fp->local->delete($name, -error => \$error );
+ unless ($error) {
+ $fp->done;
+ return $self->redirect('../');
+ }
+ }
+ $fp->done;
+
if (defined $q->param('submit')) {
# Changes have been submitted: process them
- my %entry;
+ my $local = &parse_CGI_query($q);
+ $local->{type} = $q->param('t');
+ $local->{name} = $name;
my %rest;
- if ($t eq 'user') {
- $entry{user} = $q->param('user').'@'.$d;
- $entry{forwards} = $q->param('forwards');
- if ($q->param('password') ne $q->param('password2')) {
- $error = "Passwords do not match";
- }
- elsif (length $q->param('password') < $CFG{password_min_length}) {
- $error = "Password should be at least "
- .$CFG{password_min_length}
- ." characters long.";
- }
- else {
- $entry{password} = Fripost::Password::hash($q->param('password'));
- }
- # TODO: inherit the quota from the postmaster's?
- }
- elsif ($t eq 'alias') {
- $entry{alias} = $q->param('alias').'@'.$d;
- $entry{maildrop} = $q->param('maildrop');
- }
- elsif ($t eq 'list') {
- $entry{list} = $q->param('list').'@'.$d;
- $entry{transport} = $q->param('transport');
+
+ if ($q->param('password') || $q->param('password2')) {
if ($q->param('password') ne $q->param('password2')) {
$error = "Passwords do not match";
}
+ # TODO: ! move that to Password.pm
+ # TODO: change password
elsif (length $q->param('password') < $CFG{password_min_length}) {
$error = "Password should be at least "
.$CFG{password_min_length}
." characters long.";
}
else {
- $rest{gpg} = { use_agent => 0
- , keydir => $CFG{gpghome}
- , key => $CFG{gpg_private_key_id}
- , passphrase => $CFG{gpg_private_key_passphrase}
- };
- $entry{password} = $q->param('password');
+ $local->{password} = Fripost::Password::hash($q->param('password'));
}
}
- else {
- # Unknown type
- return $self->redirect('./');
- }
- $entry{isactive} = $q->param('isactive') // 1;
- $entry{description} = $q->param('description') // undef;
-
- unless ($error) {
- my $fp = Fripost::Schema::->SASLauth( $self->authen->username, %CFG );
- $fp->domain->get ($d, -die => 404, -assert_exist => 1);
- $error = $fp->$t->add( \%entry, -concat => "(\n|\x{0D}\x{0A})", %rest);
- $fp->done;
- return $self->redirect('./') unless $error;
- }
}
- my $template = $self->load_tmpl( "add-$t.html", cache => 1 );
- $template->param( $self->userInfo );
- $template->param( domain => encode_entities($d) );
+ # Do not send passwords back to the sender.
+ $q->delete(qw/password password2/);
+
+ my $t = $local->{type};
+ my $template = $self->load_tmpl( "edit-$t.html", cache => 1 );
+ $template->param( $self->userInfo
+ , localpart => encode_entities($localname)
+ , domainpart => encode_entities($domainname) );
+
if ($error) {
# Preserve the (incorrect) form, except the passwords
- if ($t eq 'user') {
- $template->param( user => $q->param('user') // undef
- , forwards => $q->param('forwards') // undef );
- }
- elsif ($t eq 'alias') {
- $template->param( alias => $q->param('alias') // undef
- , maildrop => $q->param('maildrop') // undef );
- }
- elsif ($t eq 'list') {
- $template->param( list => $q->param('list') // undef
- , isenc => $q->param('transport') eq 'schleuder' );
- }
- else {
- # Unknown type
- return $self->redirect('./');
- }
- $template->param( isactive => $q->param('isactive') // 1
- , description => $q->param('description') // undef
- , error => encode_entities ($error) );
+ $template->param( &fill_HTML_template_from_query ($q) );
}
else {
- $template->param( isactive => 1 );
+ $template->param( &fill_HTML_template_from_entry ($local,
+ -hide => [qw/quota transport/]) );
}
+ # TODO: submit
+ my $news = (defined $q->param('submit') or
+ (defined $q->param('a') and $q->param('a') eq 'delete'));
+ $template->param( newChanges => $news );
+ $template->param( error => encode_entities ($error) ) if $error;
+ $template->param( canDelete => 1 ) if $t eq 'alias';
+ $template->param( list_URL => $CFG{'listurl_'.$local->{transport}}.
+ email_to_ascii($name) )
+ if $t eq 'list';
+
+ $q->delete('a');
return $template->output;
}
+
+
+
sub mkURL {
my $host = shift;
my @path = map { encodeURIComponent($_) } @_;
diff --git a/lib/Fripost/Schema/Local.pm b/lib/Fripost/Schema/Local.pm
index 1f09f66..d6e32a2 100644
--- a/lib/Fripost/Schema/Local.pm
+++ b/lib/Fripost/Schema/Local.pm
@@ -18,7 +18,8 @@ use utf8;
use parent 'Fripost::Schema';
use Fripost::Schema::Util qw/concat split_addr canonical_dn
- ldap_error dn2mail/;
+ ldap_error dn2mail softdie email_valid
+ ldap_assert_absent/;
use Net::IDN::Encode qw/email_to_ascii email_to_unicode/;
use Net::LDAP::Util 'escape_filter_value';
@@ -58,6 +59,15 @@ them.
(User only) A string e.g., C<100 MB> representing the current quota on
the user's mailboxes.
+=item B<password>
+
+(User and list only) The user or list administrator password. It is
+never given back by the server (actually noone has read access on that
+attribute), hence only makes sense upon creation. In users entries,
+I<password> can be hashed on the client side when prefixed with
+B<{SHA}>, B<{SSHA}>, B<{MD5}>, B<{SMD5}>, B<{CRYPT}> or B<{CLEARTEXT}>.
+(Otherwise the password will be automatically salted and SHA-1 hashed.)
+
=item B<owner>
(Alias and list only) An optional array reference containing the
@@ -140,15 +150,12 @@ B<Fripost::Schema::Util> for details.
sub search {
my $self = shift;
- my $in = shift;
+ my ($localname, $domainname) = split_addr(shift);
my %options = @_;
# Nothing to do after an error.
return if $options{'-error'} && ${$options{'-error'}};
- # If there is not '@', we interpret $in as a domain name.
- $in =~ s/^([^\@]+)$/\@$1/;
- my ($localname, $domainname) = split_addr($in);
my @filters;
if (defined $options{'-type'}) {
@@ -218,8 +225,8 @@ sub search {
my $count = 0;
my @resultset;
foreach my $domainname (@domainnames) {
- # For query the server for each matching domain.
- my $locals = $self->ldap->search( base => $self->mail2dn('@'.$domainname)
+ # We query the server for each matching domain.
+ my $locals = $self->ldap->search( base => $self->mail2dn($domainname)
, scope => 'one'
, deref => 'never'
, filter => $filter
@@ -313,7 +320,7 @@ sub _entries_to_locals {
if not @$keys or grep { $_ eq 'transport' } @$keys;
}
else {
- die "Missing translation for local attribute ‘".$attr."’.";
+ die "Missing translation for local attribute ‘".$attr."’";
}
}
@@ -324,153 +331,267 @@ sub _entries_to_locals {
return @locals;
}
+# Map our domain keys into the LDAP attribute(s) that are required to
+# fetch this information.
+sub _keys_to_attrs {
+ my %map = ( name => 'fvl'
+ , type => 'objectClass'
+ , isActive => 'fripostIsStatusActive'
+ , description => 'description'
+ , isPending => 'objectClass'
+ , quota => 'fripostUserQuota'
+ , owner => 'fripostOwner'
+ , forward => 'fripostOptionalMaildrop'
+ , destination => 'fripostMaildrop'
+ , transport => 'fripostListManager'
+ );
+ my %attrs;
+ foreach my $k (@_) {
+ die "Missing translation for key ‘".$k."’"
+ unless exists $map{$k};
+ if (ref $map{$k} eq 'ARRAY') {
+ $attrs{$_} = 1 for @{$map{$k}};
+ }
+ else {
+ $attrs{$map{$k}} = 1;
+ }
+ }
+ return keys %attrs;
+}
-=head1 METHODS
-
-=over 4
-
-=item B<get> (I<local>, I<OPTIONS>)
-
-Returns a hash with all the (visible) attributes for the given entry. An
-additional 'type' attribute gives the type of *the* found entry
-(possible values are 'user', 'alias', and 'list').
-
-=cut
+my %list_commands = ( mailman => [ qw/admin bounces confirm join leave
+ owner request subscribe unsubscribe/ ]
+ , schleuder => [ qw/bounce sendkey/ ]
+ );
-sub get {
+sub add {
my $self = shift;
- my $loc = shift;
+ my $local = shift;
my %options = @_;
- my $concat = $options{'-concat'};
-
- my ($l,$d) = split_addr( $loc, -encode => 'ascii' );
- $l = escape_filter_value($l);
- my $locals = $self->ldap->search(
- base => canonical_dn({fvd => $d}, @{$self->suffix}),
- scope => 'one',
- deref => 'never',
- filter => "(|(&(objectClass=FripostVirtualUser)(fvu=$l))
- (&(objectClass=FripostVirtualAlias)(fva=$l))
- (&(objectClass=FripostVirtualList)(fvl=$l)))",
- attrs => [ qw/fvu description
- fripostIsStatusActive
- fripostOptionalMaildrop
- fripostUserQuota
- fva fripostMaildrop
- fvl fripostListManager/ ]
- );
- if ($locals->code) {
- die $options{'-die'}."\n" if defined $options{'-die'};
- die $locals->error."\n";
- }
- # The following is not supposed to happen. Note that there is
- # nothing in the LDAP schema to prevent that, but it's not too
- # critical as Postfix searchs for user, aliases and lists in
- # that order.
- die "Error: Multiple matching entries found." if $locals->count > 1;
- my $local = $locals->pop_entry;
-
- unless (defined $local) {
- die $options{'-die'}."\n" if defined $options{'-die'};
- die "No such such entry ‘".$loc."’.\n";
+ # Nothing to do after an error.
+ return if $options{'-error'} && ${$options{'-error'}};
+ softdie ("No name specified", %options) // return
+ unless $local->{name} =~ /^.+\@[^\@]+$/;
+
+ my $name = $local->{name};
+ my ($localname, $domainname) = split_addr($name);
+ # Check validity.
+ &_assert_valid($local, %options) // return;
+
+ my $exists;
+ my $t = $local->{type};
+ if ($options{'-dry-run'} or $options{'-append'}) {
+ # Search for an existing entry with the same name. We can't
+ # use our previously defined method here, since the current user
+ # may not have read access to the entry. There is a race
+ # condition since someone could modify the directory between
+ # this check and the actual insertion, but then the insertion
+ # would fail.
+ $exists = ldap_assert_absent( $self, $name, undef, %options ) // return;
+
+ if ($t eq 'list') {
+ # Ensure that all commands are available.
+ foreach (@{$list_commands{$local->{transport}}}) {
+ my $name = $localname.'-'.$_.'@'.$domainname;
+ ldap_assert_absent( $self, $name, undef, %options ) // return;
+ }
+ }
+ return 1 if $options{'-dry-run'};
}
- my %ret;
- if ($local->dn =~ /^fvu=/) {
- $ret{type} = 'user';
- $ret{user} = $local->get_value('fvu');
- $ret{forwards} = concat($concat, map { email_to_unicode($_) }
- $local->get_value('fripostOptionalMaildrop'))
- }
- elsif ($local->dn =~ /^fva=/) {
- $ret{type} = 'alias';
- $ret{alias} = $local->get_value('fva');
- $ret{maildrop} = concat($concat, map { email_to_unicode($_) }
- $local->get_value('fripostMaildrop'))
+ # Convert the domain into a LDAP entry, and remove keys to empty values.
+ my %attrs = $self->_local_to_entry (%$local);
+ Fripost::Schema::Util::ldap_clean_entry( \%attrs );
+
+ my $mesg;
+ my $dn = $self->mail2dn( $local->{name} );
+ if ($options{'-append'} and $exists) {
+ # Replace single valued attributes; Add other attributes.
+ my %unique;
+ foreach (qw/fripostIsStatusActive userPassword fripostUserQuota/) {
+ $unique{$_} = delete $attrs{$_} if exists $attrs{$_};
+ }
+ $mesg = $self->ldap->modify( $dn, replace => \%unique, add => \%attrs );
}
- elsif ($local->dn =~ /^fvl=/) {
- $ret{type} = 'list';
- $ret{list} = $local->get_value('fvl');
- $ret{transport} = $local->get_value('fripostListManager');
+ else {
+ # The default owner is the current user.
+ $attrs{fripostOwner} //= [ $self->whoami ] unless $t eq 'user';
+ my $die = exists $options{'-die'};
+ $options{'-die'} = { Net::LDAP::Constant::LDAP_ALREADY_EXISTS =>
+ "‘".$name."’ exists"
+ , Net::LDAP::Constant::LDAP_SUCCESS => 0 }
+ unless $die;
+
+ if ($t eq 'list') {
+ # Lists need special care since we have to create the
+ # commands as well, and we need to communicate with the list
+ # manager.
+ my $pw = delete $attrs{userPassword};
+ $attrs{objectClass} = [ qw/FripostVirtualList FripostPendingEntry/ ];
+ $attrs{fripostLocalAlias} = &_mkLocalAlias($name);
+
+ my @done;
+ my $res = $self->ldap->add( $dn, attrs => [ %attrs ] );
+ push @done, $dn unless $res->code;
+
+ foreach (@{$list_commands{$local->{transport}}}) {
+ # Create the commands; Stop if something goes wrong
+ last if $res->code;
+ my $name = $localname.'-'.$_.'@'.$domainname;
+ $options{'-die'} = { Net::LDAP::Constant::LDAP_ALREADY_EXISTS =>
+ "‘".$name."’ exists"
+ , Net::LDAP::Constant::LDAP_SUCCESS => 0 }
+ unless $die;
+ my %attrs = ( objectClass => [ qw/FripostVirtualListCommand
+ FripostPendingEntry/ ]
+ , fripostLocalAlias => &_mkLocalAlias($name)
+ );
+ my $dn = $self->mail2dn( $name );
+ $res = $self->ldap->add( $dn, attrs => [ %attrs ] );
+ push @done, $dn unless $res->code;
+ }
+ $mesg = $res;
+ if ($mesg->code) {
+ # Something went wrong. We try to clean up after us, and
+ # delete the bogus entries we created.
+ # It's not too bad if it doesn't work out, because
+ # it'll be cleaned by our service hopefully.
+ $self->ldap->delete($_) for @done;
+ ldap_error($mesg, %options);
+ return;
+ }
+
+ # TODO: send a signed + encrypted mail
+ }
+ else {
+ $attrs{objectClass} = $t eq 'user' ? 'FripostVirtualUser' :
+ $t eq 'alias'? 'FripostVirtualAlias' :
+ '';
+ $mesg = $self->ldap->add( $dn, attrs => [ %attrs ] );
+ # TODO: send a welcome mail?
+ }
}
- $ret{isactive} = $local->get_value('fripostIsStatusActive') eq 'TRUE';
- $ret{description} = concat($concat, $local->get_value('description'));
- $ret{ispending} = ($local->get_value('fripostIsStatusPending') // '') eq 'TRUE';
- return %ret;
+ ldap_error($mesg, %options) // return;
+ 1;
}
-=item B<exists> (I<local>, I<OPTIONS>)
+# Convert our representation of local entries into a hash which keys are
+# LDAP attributes.
+sub _local_to_entry {
+ my $self = shift;
+ my %local = @_;
+ my %entry;
+
+ foreach my $key (keys %local) {
+ if ($key eq 'name') {
+ # Its value is forced by the DN.
+ }
+ elsif ($key eq 'type') {
+ # We fix that one later.
+ }
+ elsif ($key eq 'isActive') {
+ $entry{fripostIsStatusActive} = $local{isActive} ? 'TRUE' : 'FALSE';
+ }
+ elsif ($key eq 'description') {
+ $entry{description} = $local{description};
+ }
+ elsif ($key eq 'quota') {
+ $entry{fripostUserQuota} = $local{quota};
+ }
+ elsif ($key eq 'owner') {
+ $entry{fripostOwner} =
+ [ map { $self->mail2dn($_) } @{$local{owner}} ];
+ }
+ elsif ($key eq 'forward') {
+ $entry{fripostOptionalMaildrop} = $local{forward};
+ }
+ elsif ($key eq 'destination') {
+ $entry{fripostMaildrop} = $local{destination};
+ }
+ elsif ($key eq 'transport') {
+ $entry{fripostListManager} = $local{transport};
+ }
+ elsif ($key eq 'password') {
+ $entry{userPassword} = $local{password};
+ }
+ else {
+ die "Missing translation for key ‘".$key."’";
+ }
+ }
+ return %entry;
+}
-Returns 1 if the given I<local>@I<domain> exists, and 0 otherwise.
-The authenticated user needs to have search access to the 'entry'
-attribute.
-=cut
-sub exists {
- my $self = shift;
- my ($l,$d) = split_addr( shift, -encode => 'ascii' );
+sub _assert_valid {
+ my $l = shift;
my %options = @_;
+ eval {
+ die "Unspecified type\n" unless defined $l->{type};
+ die "Unknown type ‘".$l->{type}."’\n"
+ unless grep { $l->{type} eq $_ } qw/user alias list/;
+ my ($u, $d) = split_addr($l->{name}, -encode => 'ascii');
+ return unless $u && $d;
+ # ^ To avoid unicode issues.
+ die "Recipient delimiter ‘+’ is not allowed in locaparts\n"
+ if $u =~ /\+/; # TODO: should be a config option
+ $l->{name} = email_valid( $u.'@'.$d, -exact => 1 );
+
+ unless ($options{'-append'} or $options{'-replace'}) {
+ my @must = qw/name isActive/;
+ push @must, $l->{type} eq 'user' ? 'password' :
+ # TODO: ^ match 'quota' against the Dovecot specifications
+ $l->{type} eq 'alias' ? 'destination' :
+ $l->{type} eq 'list' ? qw/transport password/ :
+ ();
+ Fripost::Schema::Util::must_attrs( $l, @must );
+ }
- # We may not have read access to the list commands
- # The trick is somewhat dirty, but it's safe enough since postfix
- # delivers to users, aliases, and lists with different
- # priorities (and lists have the lowest).
- my @cmds = qw/admin bounces confirm join leave owner request
- subscribe unsubscribe bounce sendkey/;
- my @tests = ( {fvu => $l}, {fva => $l}, {fvl => $l} );
-
- foreach (@cmds) {
- # If the entry is of the form 'foo-command', we need to ensure
- # that no list 'foo' exists, otherwise the new entry would
- # override foo's command.
- if ($l =~ s/-$_$//) {
- push @tests, {fvl => $l};
- last;
+ if ($l->{type} eq 'user') {
+ $l->{forward} = [ map { email_valid($_) } @{$l->{forward}} ]
+ if $l->{forward};
}
- }
- if (defined $options{t} and $options{t} eq 'list') {
- # If that's a list that is to be created, we need to ensure that
- # none of its commands exists.
- foreach (@cmds) {
- my $l2 = $l.'-'.$_;
- push @tests, {fvu => $l2}, {fva => $l2};
+ elsif ($l->{type} eq 'alias') {
+ $a->{destination} = [ map { email_valid($_) } @{$l->{destination}} ]
+ if $l->{destination};
}
- }
+ elsif ($l->{type} eq 'list') {
+ die "Invalid list name: ‘".$l->{name}."’\n"
+ unless $u =~ /^[[:alnum:]_=\+\-\.]+$/;
+
+ die "Invalid list name: ‘".$l->{name}."’\n"
+ if defined $l->{transport} and
+ grep {$u =~ /-$_$/} @{$list_commands{$l->{transport}}};
- foreach (@tests) {
- my $dn = canonical_dn($_, {fvd => $d}, @{$self->suffix});
- my $mesg = $self->ldap->search( base => $dn
- , scope => 'base'
- , deref => 'never'
- , filter => 'objectClass=*'
- , attrs => [ '1.1' ]
- );
- return 1 unless $mesg->code; # 0 Success
- unless ($mesg->code == 32) { # 32 No such object
- die $options{'-die'}."\n" if defined $options{'-die'};
- die $mesg->error."\n";
+ die "Invalid transport: ‘".$l->{transport}."’\n"
+ if defined $l->{transport} and
+ not grep { $l->{transport} eq $_ } qw/schleuder mailman/;
+
+ $l->{transport} //= 'mailman'
+ unless $options{'-append'} or $options{'-replace'};
}
- }
- return 0;
+ };
+ softdie ($@, %options);
}
-=back
+sub _mkLocalAlias {
+ my $name = email_to_ascii(shift);
+ $name =~ /^(.+)@([^\@]+)/ or return;
+ return $1.'#'.$2;
+}
+
+
+
+
-=head1 GLOBAL OPTIONS
-If the B<-concat> option is present, it will intersperse multi-valued
-attributes. Otherwise, an array reference containing every values will
-be returned for these attributes.
-The B<-die> option, if present, overides LDAP croaks and errors.
-=cut
=head1 AUTHOR
diff --git a/templates/add-alias.html b/templates/add-alias.html
index 2844dad..1a7d7dd 100644
--- a/templates/add-alias.html
+++ b/templates/add-alias.html
@@ -2,17 +2,17 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
- <title>Add alias under <TMPL_VAR NAME=domain></title>
+ <title>Add alias under <TMPL_VAR NAME=domainname></title>
<link href="/css/style.css" media="all" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="header">
<div class="left column">
<a href="../">Root</a> /
- <a href="./"><TMPL_VAR NAME=domain></a> /
+ <a href="./"><TMPL_VAR NAME=domainname></a> /
</div>
<div class="right column">
- Logged as <a href="<TMPL_VAR NAME=userURL>/?a=edit"
+ Logged as <a href="<TMPL_VAR NAME=user_URL>/?a=edit"
><TMPL_VAR NAME=user_localpart>@<TMPL_VAR NAME=user_domainpart></a>
| <a href="../?a=logout">Log out</a>
</div>
@@ -21,7 +21,7 @@
<hr/>
<div id="content">
- <h1>Add alias under <span class="domain"><TMPL_VAR NAME=domain></span></h1>
+ <h1>Add alias under <span class="domain"><TMPL_VAR NAME=domainname></span></h1>
<TMPL_IF NAME=error>
<div class="fail">Error: <TMPL_VAR NAME=error></div>
@@ -37,14 +37,14 @@
<input type="hidden" name="t" value="alias" />
<h4 class="label">Alias name</h4>
- <input type="text" name="alias" size="15" value="<TMPL_VAR NAME=alias>"/>@<TMPL_VAR NAME=domain>
+ <input type="text" name="name" size="15" value="<TMPL_VAR NAME=name>"/>@<TMPL_VAR NAME=domainname>
<hr/>
<h4 class="label">Status</h4>
- <select name="isactive">
- <option value="1" <TMPL_IF NAME=isactive>selected="selected"</TMPL_IF>>Active</option>
- <option value="0" <TMPL_UNLESS NAME=isactive>selected="selected"</TMPL_UNLESS>>Inactive</option>
+ <select name="isActive">
+ <option value="1" <TMPL_IF NAME=isActive>selected="selected"</TMPL_IF>>Active</option>
+ <option value="0" <TMPL_UNLESS NAME=isActive>selected="selected"</TMPL_UNLESS>>Inactive</option>
</select>
<div class="help">
<b>Warning</b>: emails are <i>not</i> delivered to inactive entries.
@@ -61,7 +61,7 @@
<hr/>
<h4 class="label">Destination(s)</h4>
- <textarea name="maildrop" cols="50" rows="5" ><TMPL_VAR NAME=maildrop></textarea>
+ <textarea name="destination" cols="50" rows="5" ><TMPL_VAR NAME=destination></textarea>
<div class="help">
The list of destinations (one e-mail address per line) that
will receive mail for this alias.
diff --git a/templates/add-list.html b/templates/add-list.html
index 136899d..6e9f8bd 100644
--- a/templates/add-list.html
+++ b/templates/add-list.html
@@ -2,17 +2,17 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
- <title>Add list under <TMPL_VAR NAME=domain></title>
+ <title>Add list under <TMPL_VAR NAME=domainname></title>
<link href="/css/style.css" media="all" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="header">
<div class="left column">
<a href="../">Root</a> /
- <a href="./"><TMPL_VAR NAME=domain></a> /
+ <a href="./"><TMPL_VAR NAME=domainname></a> /
</div>
<div class="right column">
- Logged as <a href="<TMPL_VAR NAME=userURL>/?a=edit"
+ Logged as <a href="<TMPL_VAR NAME=user_URL>/?a=edit"
><TMPL_VAR NAME=user_localpart>@<TMPL_VAR NAME=user_domainpart></a>
| <a href="../?a=logout">Log out</a>
</div>
@@ -21,7 +21,7 @@
<hr/>
<div id="content">
- <h1>Add list under <span class="domain"><TMPL_VAR NAME=domain></span></h1>
+ <h1>Add list under <span class="domain"><TMPL_VAR NAME=domainname></span></h1>
<TMPL_IF NAME=error>
<div class="fail">Error: <TMPL_VAR NAME=error></div>
@@ -37,14 +37,14 @@
<input type="hidden" name="t" value="list" />
<h4 class="label">List name</h4>
- <input type="text" name="list" size="15" value="<TMPL_VAR NAME=list>"/>@<TMPL_VAR NAME=domain>
+ <input type="text" name="name" size="15" value="<TMPL_VAR NAME=name>"/>@<TMPL_VAR NAME=domainname>
<hr/>
<h4 class="label">Status</h4>
- <select name="isactive">
- <option value="1" <TMPL_IF NAME=isactive>selected="selected"</TMPL_IF>>Active</option>
- <option value="0" <TMPL_UNLESS NAME=isactive>selected="selected"</TMPL_UNLESS>>Inactive</option>
+ <select name="isActive">
+ <option value="1" <TMPL_IF NAME=isActive>selected="selected"</TMPL_IF>>Active</option>
+ <option value="0" <TMPL_UNLESS NAME=isActive>selected="selected"</TMPL_UNLESS>>Inactive</option>
</select>
<div class="help">
<b>Warning</b>: emails are <i>not</i> delivered to inactive entries.
@@ -82,8 +82,9 @@
<h4 class="label">Transport</h4>
<select name="transport">
- <option value="mailman" <TMPL_UNLESS NAME=isenc>selected="selected"</TMPL_UNLESS>>GNU Mailman</option>
- <option value="schleuder" <TMPL_IF NAME=isenc>selected="selected"</TMPL_IF>>Schleuder</option>
+ <TMPL_LOOP NAME=transport>
+ <option value="<TMPL_VAR NAME=item>" <TMPL_IF NAME=selected>selected="selected"</TMPL_IF>><TMPL_VAR NAME=name></option>
+ </TMPL_LOOP>
</select>
<div class="help">
The mailing list manager. (<i>Note</i>: It is not possible to
@@ -103,7 +104,7 @@
<i>Note</i>: No confirmation email will be sent. It may take a while for the list to be created (especially
for the Schleuder list manager, as it requires a GPG key creation); Once the list has succefully been created,
it will be visible under the
- <a href="./">management page for <span class="domain"><TMPL_VAR NAME=domain></span></a>.
+ <a href="./">management page for <span class="domain"><TMPL_VAR NAME=domainname></span></a>.
</div>
</div>
</form>
diff --git a/templates/add-user.html b/templates/add-user.html
index 8964c22..67493a1 100644
--- a/templates/add-user.html
+++ b/templates/add-user.html
@@ -2,17 +2,17 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
- <title>Add user under <TMPL_VAR NAME=domain></title>
+ <title>Add user under <TMPL_VAR NAME=domainname></title>
<link href="/css/style.css" media="all" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="header">
<div class="left column">
<a href="../">Root</a> /
- <a href="./"><TMPL_VAR NAME=domain></a> /
+ <a href="./"><TMPL_VAR NAME=domainname></a> /
</div>
<div class="right column">
- Logged as <a href="<TMPL_VAR NAME=userURL>/?a=edit"
+ Logged as <a href="<TMPL_VAR NAME=user_URL>/?a=edit"
><TMPL_VAR NAME=user_localpart>@<TMPL_VAR NAME=user_domainpart></a>
| <a href="../?a=logout">Log out</a>
</div>
@@ -21,7 +21,7 @@
<hr/>
<div id="content">
- <h1>Add user under <span class="domain"><TMPL_VAR NAME=domain></span></h1>
+ <h1>Add user under <span class="domain"><TMPL_VAR NAME=domainname></span></h1>
<TMPL_IF NAME=error>
<div class="fail">Error: <TMPL_VAR NAME=error></div>
@@ -37,14 +37,14 @@
<input type="hidden" name="t" value="user" />
<h4 class="label">User name</h4>
- <input type="text" name="user" size="15" value="<TMPL_VAR NAME=user>"/>@<TMPL_VAR NAME=domain>
+ <input type="text" name="name" size="15" value="<TMPL_VAR NAME=name>"/>@<TMPL_VAR NAME=domainname>
<hr/>
<h4 class="label">Status</h4>
- <select name="isactive">
- <option value="1" <TMPL_IF NAME=isactive>selected="selected"</TMPL_IF>>Active</option>
- <option value="0" <TMPL_UNLESS NAME=isactive>selected="selected"</TMPL_UNLESS>>Inactive</option>
+ <select name="isActive">
+ <option value="1" <TMPL_IF NAME=isActive>selected="selected"</TMPL_IF>>Active</option>
+ <option value="0" <TMPL_UNLESS NAME=isActive>selected="selected"</TMPL_UNLESS>>Inactive</option>
</select>
<div class="help">
<b>Warning</b>: emails are <i>not</i> delivered to inactive entries.
@@ -76,7 +76,7 @@
<hr/>
<h4 class="label">Mail forwarding</h4>
- <textarea name="forwards" cols="50" rows="5" ><TMPL_VAR NAME=forwards></textarea>
+ <textarea name="forward" cols="50" rows="5" ><TMPL_VAR NAME=forward></textarea>
<div class="help">
An optional list of destinations (one e-mail address per line) that
will <i>also</i> receive mail delivered to this user.
diff --git a/templates/edit-alias.html b/templates/edit-alias.html
index 34425a4..ba2f20d 100644
--- a/templates/edit-alias.html
+++ b/templates/edit-alias.html
@@ -2,18 +2,18 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
- <title>Edit alias <TMPL_VAR NAME=alias>@<TMPL_VAR NAME=domain></title>
+ <title>Edit alias <TMPL_VAR NAME=name></title>
<link href="/css/style.css" media="all" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="header">
<div class="left column">
<a href="../../">Root</a> /
- <a href="../"><TMPL_VAR NAME=domain></a> /
- <TMPL_VAR NAME=alias> /
+ <a href="../"><TMPL_VAR NAME=domainpart></a> /
+ <TMPL_VAR NAME=localpart> /
</div>
<div class="right column">
- Logged as <a href="<TMPL_VAR NAME=userURL>/?a=edit"
+ Logged as <a href="<TMPL_VAR NAME=user_URL>/?a=edit"
><TMPL_VAR NAME=user_localpart>@<TMPL_VAR NAME=user_domainpart></a>
| <a href="../../?a=logout">Log out</a>
</div>
@@ -22,7 +22,7 @@
<hr/>
<div id="content">
- <h1>Edit alias <span class="email"><TMPL_VAR NAME=alias>@<TMPL_VAR NAME=domain></span><TMPL_IF NAME=canDelete
+ <h1>Edit alias <span class="email"><TMPL_VAR NAME=name></span><TMPL_IF NAME=canDelete
><span class="action">[<a href="./?a=delete">delete</a>]</span
></TMPL_IF></h1>
@@ -43,9 +43,9 @@
<input type="hidden" name="a" value="edit" />
<h4 class="label" id="status">Status</h4>
- <select name="isactive">
- <option value="1" <TMPL_IF NAME=isactive>selected="selected"</TMPL_IF>>Active</option>
- <option value="0" <TMPL_UNLESS NAME=isactive>selected="selected"</TMPL_UNLESS>>Inactive</option>
+ <select name="isActive">
+ <option value="1" <TMPL_IF NAME=isActive>selected="selected"</TMPL_IF>>Active</option>
+ <option value="0" <TMPL_UNLESS NAME=isActive>selected="selected"</TMPL_UNLESS>>Inactive</option>
</select>
<div class="help">
<b>Warning</b>: emails are <i>not</i> delivered to inactive entries.
@@ -62,11 +62,11 @@
<hr/>
<h4 class="label" id="destination">Destination(s)</h4>
- <textarea name="maildrop" cols="50" rows="5" ><TMPL_VAR NAME=maildrop></textarea>
+ <textarea name="destination" cols="50" rows="5" ><TMPL_VAR NAME=destination></textarea>
<div class="help">
The list of destinations (one e-mail address per line) that
will receive mail sent to
- <span class="email"><TMPL_VAR NAME=alias>@<TMPL_VAR NAME=domain></span>.
+ <span class="email"><TMPL_VAR NAME=name></span>.
</div>
<hr/>
diff --git a/templates/edit-list.html b/templates/edit-list.html
index bb26eef..6930fd1 100644
--- a/templates/edit-list.html
+++ b/templates/edit-list.html
@@ -2,18 +2,18 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
- <title>Edit list <TMPL_VAR NAME=list>@<TMPL_VAR NAME=domain></title>
+ <title>Edit list <TMPL_VAR NAME=name></title>
<link href="/css/style.css" media="all" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="header">
<div class="left column">
<a href="../../">Root</a> /
- <a href="../"><TMPL_VAR NAME=domain></a> /
- <TMPL_VAR NAME=list> /
+ <a href="../"><TMPL_VAR NAME=domainpart></a> /
+ <TMPL_VAR NAME=localpart> /
</div>
<div class="right column">
- Logged as <a href="<TMPL_VAR NAME=userURL>/?a=edit"
+ Logged as <a href="<TMPL_VAR NAME=user_URL>/?a=edit"
><TMPL_VAR NAME=user_localpart>@<TMPL_VAR NAME=user_domainpart></a>
| <a href="../../?a=logout">Log out</a>
</div>
@@ -22,8 +22,8 @@
<hr/>
<div id="content">
- <h1>Edit list <a class="external" target="_blank" href="<TMPL_VAR NAME=listURL>"
- ><span class="email"><TMPL_VAR NAME=list>@<TMPL_VAR NAME=domain></span
+ <h1>Edit list <a class="external" target="_blank" href="<TMPL_VAR NAME=list_URL>"
+ ><span class="email"><TMPL_VAR NAME=name></span
></a></h1>
<TMPL_IF NAME=newChanges>
@@ -44,8 +44,8 @@
<h4 class="label" id="status">Status</h4>
<select name="isactive">
- <option value="1" <TMPL_IF NAME=isactive>selected="selected"</TMPL_IF>>Active</option>
- <option value="0" <TMPL_UNLESS NAME=isactive>selected="selected"</TMPL_UNLESS>>Inactive</option>
+ <option value="1" <TMPL_IF NAME=isActive>selected="selected"</TMPL_IF>>Active</option>
+ <option value="0" <TMPL_UNLESS NAME=isActive>selected="selected"</TMPL_UNLESS>>Inactive</option>
</select>
<div class="help">
<b>Warning</b>: emails are <i>not</i> delivered to inactive entries.
diff --git a/templates/edit-user.html b/templates/edit-user.html
index 38d3836..d4b3c86 100644
--- a/templates/edit-user.html
+++ b/templates/edit-user.html
@@ -2,18 +2,18 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
- <title>Edit user <TMPL_VAR NAME=user>@<TMPL_VAR NAME=domain></title>
+ <title>Edit user <TMPL_VAR NAME=name></title>
<link href="/css/style.css" media="all" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="header">
<div class="left column">
<a href="../../">Root</a> /
- <a href="../"><TMPL_VAR NAME=domain></a> /
- <TMPL_VAR NAME=user> /
+ <a href="../"><TMPL_VAR NAME=domainpart></a> /
+ <TMPL_VAR NAME=localpart> /
</div>
<div class="right column">
- Logged as <a href="<TMPL_VAR NAME=userURL>/?a=edit"
+ Logged as <a href="<TMPL_VAR NAME=user_URL>/?a=edit"
><TMPL_VAR NAME=user_localpart>@<TMPL_VAR NAME=user_domainpart></a>
| <a href="../../?a=logout">Log out</a>
</div>
@@ -22,7 +22,7 @@
<hr/>
<div id="content">
- <h1>Edit user <span class="user"><TMPL_VAR NAME=user>@<TMPL_VAR NAME=domain></span></h1>
+ <h1>Edit user <span class="user"><TMPL_VAR NAME=name></span></h1>
<TMPL_IF NAME=newChanges>
@@ -42,9 +42,9 @@
<input type="hidden" name="a" value="edit" />
<h4 class="label" id="status">Status</h4>
- <select name="isactive">
- <option value="1" <TMPL_IF NAME=isactive>selected="selected"</TMPL_IF>>Active</option>
- <option value="0" <TMPL_UNLESS NAME=isactive>selected="selected"</TMPL_UNLESS>>Inactive</option>
+ <select name="isActive">
+ <option value="1" <TMPL_IF NAME=isActive>selected="selected"</TMPL_IF>>Active</option>
+ <option value="0" <TMPL_UNLESS NAME=isActive>selected="selected"</TMPL_UNLESS>>Inactive</option>
</select>
<div class="help">
<b>Warning</b>: emails are <i>not</i> delivered to inactive entries.
@@ -89,14 +89,14 @@
<hr/>
<h4 class="label" id="forward">Mail forwarding</h4>
- <textarea name="forwards" cols="50" rows="5" ><TMPL_VAR NAME=forwards></textarea>
+ <textarea name="forward" cols="50" rows="5" ><TMPL_VAR NAME=forward></textarea>
<div class="help">
An optional list of destinations (one e-mail address per line) that
will receive mail for
- <span class="email"><TMPL_VAR NAME=user>@<TMPL_VAR NAME=domain></span>.
+ <span class="email"><TMPL_VAR NAME=name></span>.
(<i>Note</i>: When not empty, this list cancels delivery to
this user, so do not forget to list
- <span class="email"><TMPL_VAR NAME=user>@<TMPL_VAR NAME=domain></span>
+ <span class="email"><TMPL_VAR NAME=name></span>
here as well if you want this mailbox to be delivered too.)
</div>
@@ -262,9 +262,9 @@
the recipient(s), depending on how the message has been
classified.
(Adding the extension <span class="email">virus</span> to
- the recipient <span class="email"><TMPL_VAR NAME=user>@<TMPL_VAR NAME=domain></span>
+ the recipient <span class="email"><TMPL_VAR NAME=name></span>
will result into viruses being sent to
- <span class="email"><TMPL_VAR NAME=user>+virus@<TMPL_VAR NAME=domain></span>.)</td>
+ <span class="email"><TMPL_VAR NAME=localpart>+virus@<TMPL_VAR NAME=domainpart></span>.)</td>
<td><input type="text" name="amavisAddrExtensionVirus" size="10" /> Virus</td>
</tr>
<tr>
diff --git a/templates/list-locals.html b/templates/list-locals.html
index 44ef0be..e677dd8 100644
--- a/templates/list-locals.html
+++ b/templates/list-locals.html
@@ -11,7 +11,7 @@
<a href="../">Root</a> / <TMPL_VAR NAME=name> /
</div>
<div class="right column">
- Logged as <a href="<TMPL_VAR NAME=userURL>/?a=edit"
+ Logged as <a href="<TMPL_VAR NAME=user_URL>/?a=edit"
><TMPL_VAR NAME=user_localpart>@<TMPL_VAR NAME=user_domainpart></a>
| <a href="../?a=logout">Log out</a>
</div>
@@ -130,7 +130,7 @@
<TMPL_IF NAME=__even__><tr class="odd"><TMPL_ELSE><tr></TMPL_IF>
<td><span class="list"><TMPL_UNLESS NAME=isPending><a href="<TMPL_VAR NAME=URL>/"></TMPL_UNLESS
><TMPL_VAR NAME=name
- ><TMPL_UNLESS NAME=isPending></a> <a class="external" target="_blank" href="<TMPL_VAR NAME=listURL>">&#x27A0;</a></TMPL_UNLESS></span></td>
+ ><TMPL_UNLESS NAME=isPending></a> <a class="external" target="_blank" href="<TMPL_VAR NAME=list_URL>">&#x27A0;</a></TMPL_UNLESS></span></td>
<td><TMPL_IF NAME=description><TMPL_VAR NAME=description><TMPL_ELSE><span class="none">(none)</span></TMPL_IF></td>
<td><TMPL_IF NAME=isPending><span class="pending">&#x2691;</span>
<TMPL_ELSE><TMPL_IF NAME=isActive><span class="active">&#x2714;</span>