#!/usr/bin/perl use 5.010_000; use strict; use warnings; use utf8; =head1 NAME fripost-newalias - Add a new alias to the system =head1 SYNOPSIS B [B<--verbose>] [B<--debug>] [B<--pretend>] [B<--force>] [I [I...]] =head1 DESCRIPTION B adds a new virtual alias to the system, unless B<--pretend> is set. If I or I are not given, the user is prompted for them. If I is not fully qualified, C is appended. If I is already an existing username or alias, B raises an error. Inserted aliases conform to Postfix's B(5) alias table format, with the restriction that I has to be either in the form: =over 4 =item . I@I, to redirect emails for I@I to I, or =item . @I, to catch all emails for users in I and redirect them to I. This form has the lowest precedence: If there is an alias from I@I to I, emails to I@I will be redirected to I only. See B(5) for details and warnings. =back If serveral entries are matching, for instance if there are an alias from I@I to I and another for I@I to I, emails to I@I will be redirected to BOTH I and I. Note that B forbids the creation of such aliases, unless B<--force> is set. =head1 OPTIONS =over 8 =item B<--pretend> Only simulates the insertion. (But still query the LDAP server to ensure that the virtual domains of aliases are know, for example.) =item B<--force> Force creating the creation, even if I is already an alias. Also, disable the sending of the confirmation. =item B<--server_host=>I The LDAP URI to connect to. The default value is read from the configuration file, see B. =item B<--bind_dn=>I The Distinguished Name (DN) to bind to the LDAP directory. (If not set, B binds anonymously.) The default value is read from the configuration file, see B. =item B<--bind_pw=>I The password to to bind with. The default value is read from the configuration file, see B. =item B<--base_dn=>I The root DN for everything done by B. The default value is read from the configuration file, see B. =item B<-v>, B<--verbose> Verbose mode. =item B<--debug> Debug mode. =back =head1 CONFIGURATION The configuration is read from the file C<$HOME/.fripost.yml>. Valid keys include: =over 4 =item I The LDAP URI to connect to. Defaults to C. =item I The I e-mail address to use. Defaults to C. =item I The Distinguished Name (DN) to bind to the LDAP directory. (If not set, B binds anonymously.) =item I The password to to bind with. =item I The root DN for everything done by B. =back =cut use FindBin qw($Bin); use lib "$Bin/lib"; use Env qw /HOME/; use File::Spec::Functions; use Encode qw(encode); use Email::Valid; use Fripost::Prompt; use Fripost::Schema; use IO::Prompter; use Getopt::Long qw /:config noauto_abbrev no_ignore_case gnu_compat bundling permute nogetopt_compat auto_version auto_help/; use Pod::Usage; use MIME::Base64; use MIME::Lite; use MIME::QuotedPrint; use Template; use YAML::Syck; ## Get command line options our $conf = LoadFile( catfile ($HOME, '.fripost.yml') ); GetOptions( 'server_host=s' => \$conf->{server_host}, 'base_dn=s' => \$conf->{base_dn}, 'bind_dn=s' => \$conf->{bind_dn}, 'bind_pw=s' => \$conf->{bind_pw}, 'pretend' => \$conf->{pretend}, 'force' => \$conf->{force}, 'debug' => \$conf->{debug}, 'v|verbose' => \$conf->{verbose}, 'man' => sub { pod2usage(-exitstatus => 0, -verbose => 2) } ) or pod2usage(2); sub vsay { say STDERR @_ if $conf->{verbose} || $conf->{debug}; } # Connect to the LDAP server my $ldap = Fripost::Schema->new( $conf ); # Get information my $goto = fix_username(shift @ARGV); my @addr = @ARGV; $goto //= prompt_email("Alias goto address: ", 'is_user'); @addr || push @addr, (split /, */, prompt "Alias from address(es): "); # Show goto adress say "goto adress: $goto"; # Show from adresses @addr = grep { if (Email::Valid->address('fake'.$_)) { # Warn if the domain is unknown. my $domain = (split /\@/, $_, 2)[1]; if ($ldap->domain->search({ domain => $domain })->count) { 1; } else { warn "WARN: Skipping unknown domain `" .$domain. "'.\n"; undef; } } else { warn "WARN: Skipping invalid address `" .$_. "'.\n"; undef; } } @addr; if (@addr == 0) { warn "No valid destination adresses. Aborting...\n"; exit 1; } say "from adress(es): " . (join ", ", @addr); confirm_or_abort(); ## Insert alias into database for my $addr (@addr) { my ($u,$d) = split /\@/, $addr, 2; my $rs; # Ensure that the alias doesn't already exist. $rs = $ldap->alias->search({ address => $addr }); if ($rs->count and not (defined $conf->{force})) { print STDERR "Error: Alias $addr already exists. "; print STDERR "(Targetting to "; print STDERR (join ', ', map { $_->{goto} } $rs->entries); say STDERR ".)"; exit 1; } die "Error: Username $addr exists.\n" if ($ldap->user->search({ username => $addr })->count); if (!$conf->{pretend}) { $ldap->alias->add({ address => $addr, goto => $goto, isActive => 'TRUE' }); say "New alias added from $addr to $goto."; } else { vsay "Pretending, will not add alias." } } $ldap->unbind(); ### Prepare sending emails my $tt = Template->new({ INCLUDE_PATH => "$Bin/templ", INTERPOLATE => 1, }) || die "$Template::ERROR\n"; my $admin_email = $conf->{admin_email}; $admin_email //= 'admin@fripost.org'; my $msg = MIME::Lite->new( From => encode('MIME-Q', 'Friposts administratörer') . ' <' .$admin_email. '>', Subject => encode('MIME-Q', 'Nya alias till din inkorg'), Encoding => 'quoted-printable', ); unless (defined $conf->{force}) { my ($vars, $data); $vars = { addrs => \@addr, }; $tt->process('new_alias.tt', $vars, \$data) || die $tt->error(), '\n'; $msg->data($data); $msg->replace(To => $goto); if (!$conf->{pretend}) { confirm_or_abort("Send confirmation? "); $msg->send; say "Sent verification."; } else { vsay "Pretending, will not send verification."; } } =head1 AUTHOR Stefan Kangas C<< >> Guilhem Moulin C<< >> =head1 COPYRIGHT Copyright 2010,2011 Stefan Kangas. Copyright 2012 Guilhem Moulin. =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as perl itself. =cut