1#!--PERL-- 2# -*- indent-tabs-mode: nil; -*- 3# vim:ft=perl:et:sw=4 4# $Id$ 5 6# Sympa - SYsteme de Multi-Postage Automatique 7# 8# Copyright 2017, 2018 The Sympa Community. See the AUTHORS.md file at the 9# top-level directory of this distribution and at 10# <https://github.com/sympa-community/sympa.git>. 11# 12# This program is free software; you can redistribute it and/or modify 13# it under the terms of the GNU General Public License as published by 14# the Free Software Foundation; either version 2 of the License, or 15# (at your option) any later version. 16# 17# This program is distributed in the hope that it will be useful, 18# but WITHOUT ANY WARRANTY; without even the implied warranty of 19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20# GNU General Public License for more details. 21# 22# You should have received a copy of the GNU General Public License 23# along with this program. If not, see <http://www.gnu.org/licenses/>. 24 25package Sympa::Aliases::Template; 26 27use strict; 28use warnings; 29use English qw(-no_match_vars); 30 31use Conf; 32use Sympa::Constants; 33use Sympa::Language; 34use Sympa::LockedFile; 35use Sympa::Log; 36use Sympa::Template; 37 38use base qw(Sympa::Aliases::CheckSMTP); 39 40my $language = Sympa::Language->instance; 41my $log = Sympa::Log->instance; 42 43sub _aliases { 44 my $self = shift; 45 my $list = shift; 46 47 my $domain = $list->{'domain'}; 48 my $listname = $list->{'name'}; 49 50 my $data = { 51 'date' => $language->gettext_strftime('%d %b %Y', localtime time), 52 'list' => { 53 'name' => $listname, 54 'domain' => $domain, 55 # Compat. < 6.2.32 56 'host' => $domain, 57 }, 58 'is_default_domain' => ($domain eq $Conf::Conf{'domain'}), 59 'return_path_suffix' => 60 Conf::get_robot_conf($domain, 'return_path_suffix'), 61 62 # No longer used by default. 63 'robot' => $domain, 64 'default_domain' => $Conf::Conf{'domain'}, 65 }; 66 67 my $aliases_dump; 68 my $template = Sympa::Template->new($domain); 69 unless ($template->parse($data, 'list_aliases.tt2', \$aliases_dump)) { 70 $log->syslog( 71 'err', 72 'Can\'t parse list_aliases.tt2: %s', 73 $template->{last_error} 74 ); 75 return; 76 } 77 78 my @aliases = split /\n/, $aliases_dump; 79 unless (@aliases) { 80 $log->syslog('err', 'No aliases defined'); 81 return; 82 } 83 return @aliases; 84} 85 86sub add { 87 my $self = shift; 88 my $list = shift; 89 90 return 0 91 if lc Conf::get_robot_conf($list->{'domain'}, 'sendmail_aliases') eq 92 'none'; 93 94 my @aliases = $self->_aliases($list); 95 return undef unless @aliases; 96 97 my $alias_file = 98 $self->{file} 99 || Conf::get_robot_conf($list->{'domain'}, 'sendmail_aliases') 100 || Sympa::Constants::SENDMAIL_ALIASES(); 101 # Create a lock 102 my $lock_fh; 103 unless ($lock_fh = Sympa::LockedFile->new($alias_file, 20, '+>>')) { 104 $log->syslog('err', 'Can\'t lock %s', $alias_file); 105 return undef; 106 } 107 108 # Check existing aliases 109 if (_already_defined($lock_fh, @aliases)) { 110 $log->syslog('err', 'Some alias already exist'); 111 return undef; 112 } 113 114 # Append new entries. 115 unless (seek $lock_fh, 0, 2) { 116 $log->syslog('err', 'Unable to seek: %m'); 117 return undef; 118 } 119 foreach (@aliases) { 120 print $lock_fh "$_\n"; 121 } 122 $lock_fh->flush; 123 124 # Newaliases 125 unless ($self->{file}) { 126 system(alias_wrapper($list), '--domain=' . $list->{'domain'}); 127 if ($CHILD_ERROR == -1) { 128 $log->syslog('err', 'Failed to execute sympa_newaliases: %m'); 129 return undef; 130 } elsif ($CHILD_ERROR & 127) { 131 $log->syslog( 132 'err', 133 'sympa_newaliases was terminated by signal %d', 134 $CHILD_ERROR & 127 135 ); 136 return undef; 137 } elsif ($CHILD_ERROR) { 138 $log->syslog( 139 'err', 140 'sympa_newaliases exited with status %d', 141 $CHILD_ERROR >> 8 142 ); 143 return undef; 144 } 145 } 146 147 # Unlock 148 $lock_fh->close; 149 150 return 1; 151} 152 153sub del { 154 my $self = shift; 155 my $list = shift; 156 157 return 0 158 if lc Conf::get_robot_conf($list->{'domain'}, 'sendmail_aliases') eq 159 'none'; 160 161 my @aliases = $self->_aliases($list); 162 return undef unless @aliases; 163 164 my $alias_file = 165 $self->{file} 166 || Conf::get_robot_conf($list->{'domain'}, 'sendmail_aliases') 167 || Sympa::Constants::SENDMAIL_ALIASES(); 168 # Create a lock 169 my $lock_fh; 170 unless ($lock_fh = Sympa::LockedFile->new($alias_file, 20, '+<')) { 171 $log->syslog('err', 'Can\'t lock %s', $alias_file); 172 return undef; 173 } 174 175 # Check existing aliases. 176 my (@deleted_lines, @new_aliases); 177 my @to_be_deleted = 178 grep { defined $_ } 179 map { ($_ and m{^([^\s:]+)[\s:]}) ? $1 : undef } @aliases; 180 while (my $alias = <$lock_fh>) { 181 my $left_side = ($alias =~ /^([^\s:]+)[\s:]/) ? $1 : ''; 182 if (grep { $left_side eq $_ } @to_be_deleted) { 183 push @deleted_lines, $alias; 184 } else { 185 push @new_aliases, $alias; 186 } 187 } 188 189 unless (@deleted_lines) { 190 $log->syslog('err', 'No matching line in %s', $alias_file); 191 return 0; 192 } 193 194 # Replace old aliases file. 195 unless (seek $lock_fh, 0, 0) { 196 $log->syslog('err', 'Could not seek: %m'); 197 return undef; 198 } 199 print $lock_fh join '', @new_aliases; 200 $lock_fh->flush; 201 truncate $lock_fh, tell $lock_fh; 202 203 # Newaliases 204 unless ($self->{file}) { 205 system(alias_wrapper($list), '--domain=' . $list->{'domain'}); 206 if ($CHILD_ERROR == -1) { 207 $log->syslog('err', 'Failed to execute sympa_newaliases: %m'); 208 return undef; 209 } elsif ($CHILD_ERROR & 127) { 210 $log->syslog( 211 'err', 212 'sympa_newaliases was terminated by signal %d', 213 $CHILD_ERROR & 127 214 ); 215 return undef; 216 } elsif ($CHILD_ERROR) { 217 $log->syslog( 218 'err', 219 'sympa_newaliases exited with status %d', 220 $CHILD_ERROR >> 8 221 ); 222 return undef; 223 } 224 } 225 226 # Unlock 227 $lock_fh->close; 228 229 return 1; 230} 231 232sub alias_wrapper { 233 my $list = shift; 234 my $command; 235 236 if (Conf::get_robot_conf($list->{'domain'}, 'aliases_wrapper') eq 'on' 237 and -e Sympa::Constants::LIBEXECDIR . '/sympa_newaliases-wrapper') { 238 return Sympa::Constants::LIBEXECDIR . '/sympa_newaliases-wrapper'; 239 } 240 241 return Sympa::Constants::SBINDIR . '/sympa_newaliases.pl'; 242} 243 244# Check if an alias is already defined. 245# Old name: already_defined() in alias_manager.pl. 246sub _already_defined { 247 my $fh = shift; 248 my @aliases = @_; 249 250 unless (seek $fh, 0, 0) { 251 $log->syslog('err', 'Could not seek: %m'); 252 return undef; 253 } 254 255 my $ret = 0; 256 while (my $alias = <$fh>) { 257 # skip comment 258 next if $alias =~ /^#/; 259 $alias =~ /^([^\s:]+)[\s:]/; 260 my $left_side = $1; 261 next unless ($left_side); 262 foreach (@aliases) { 263 next unless ($_ =~ /^([^\s:]+)[\s:]/); 264 my $new_left_side = $1; 265 if ($left_side eq $new_left_side) { 266 $log->syslog('info', 'Alias already defined: %s', $left_side); 267 $ret++; 268 } 269 } 270 } 271 272 return $ret; 273} 274 2751; 276__END__ 277 278=encoding utf-8 279 280=head1 NAME 281 282Sympa::Aliases::Template - 283Alias management: Aliases file based on template 284 285=head1 SYNOPSIS 286 287 use Sympa::Aliases; 288 my $aliases = Sympa::Aliases->new('Template', 289 [ file => '/path/to/file' ] ); 290 $aliases->check('listname', 'domain'); 291 $aliases->add($list); 292 $aliases->del($list); 293 294=head1 DESCRIPTION 295 296L<Sympa::Aliases::Template> manages list aliases based on template 297F<list_aliases.tt2>. 298 299=head2 Methods 300 301=over 302 303=item check ( $listname, $domain ) 304 305See L<Sympa::Aliases::CheckSMTP>. 306 307=item add ( $list ) 308 309=item del ( $list ) 310 311Adds or removes aliases of list $list. 312 313If constructor was called with C<file> option, it will be used as aliases 314file and F<sympa_newaliases> utility will not be executed. 315Otherwise, value of C<sendmail_aliases> parameter will be used as aliases 316file and F<sympa_newaliases> utility will be executed to update 317alias database. 318If C<sendmail_aliases> parameter is set to C<none>, aliases will never be 319updated. 320 321=back 322 323=head2 Configuration parameters 324 325=over 326 327=item return_path_suffix 328 329Suffix of list return address. 330 331=item sendmail_aliases 332 333Path of the file that contains all list related aliases. 334 335=item tmpdir 336 337A directory temporary files are placed. 338 339=back 340 341=head1 FILES 342 343=over 344 345=item F<$SYSCONFDIR/I<domain name>/list_aliases.tt2> 346 347=item F<$SYSCONFDIR/list_aliases.tt2> 348 349=item F<$DEFAULTDIR/list_aliases.tt2> 350 351Template of aliases: Specific to a domain, global context and the default. 352 353=item F<$SENDMAIL_ALIASES> 354 355Default location of aliases file. 356 357=item F<$SBINDIR/sympa_newaliases> 358 359Auxiliary program to update alias database. 360 361=back 362 363=head1 SEE ALSO 364 365L<Sympa::Aliases>, 366L<Sympa::Aliases::CheckSMTP>, 367L<sympa_newaliases(1)>. 368 369=head1 HISTORY 370 371F<alias_manager.pl> to manage aliases using template appeared on 372Sympa 3.1b.13. 373 374L<Sympa::Aliases::Template> module appeared on Sympa 6.2.23b, 375and it obsoleted F<alias_manager(8)>. 376 377=cut 378