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 (c) 1997, 1998, 1999 Institut Pasteur & Christophe Wolfhugel 9# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 10# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites 11# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER 12# Copyright 2017, 2019 The Sympa Community. See the AUTHORS.md file at 13# the top-level directory of this distribution and at 14# <https://github.com/sympa-community/sympa.git>. 15# 16# This program is free software; you can redistribute it and/or modify 17# it under the terms of the GNU General Public License as published by 18# the Free Software Foundation; either version 2 of the License, or 19# (at your option) any later version. 20# 21# This program is distributed in the hope that it will be useful, 22# but WITHOUT ANY WARRANTY; without even the implied warranty of 23# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24# GNU General Public License for more details. 25# 26# You should have received a copy of the GNU General Public License 27# along with this program. If not, see <http://www.gnu.org/licenses/>. 28 29use lib split(/:/, $ENV{SYMPALIB} || ''), '--modulesdir--'; 30use strict; 31use warnings; 32use English qw(-no_match_vars); 33use Getopt::Long; 34use Pod::Usage; 35use POSIX qw(); 36 37use Conf; 38use Sympa::Constants; 39use Sympa::Crash; # Show traceback. 40use Sympa::DatabaseManager; 41use Sympa::Language; 42use Sympa::Log; 43use Sympa::Mailer; 44use Sympa::Process; 45use Sympa::Spindle::ProcessDigest; 46use Sympa::Spindle::ProcessIncoming; 47use Sympa::Spool::Listmaster; 48use Sympa::Tools::Data; 49 50my $process = Sympa::Process->instance; 51$process->init(pidname => 'sympa_msg', name => 'sympa/msg'); 52 53## Internal tuning 54# delay between each read of the digestqueue 55my $digestsleep = 5; 56 57## Init random engine 58srand(time()); 59 60# Check options. 61my %options; 62unless ( 63 GetOptions( 64 \%main::options, 'debug|d', 'log_level=s', 'foreground', 65 'config|f=s', 'lang|l=s', 'mail|m', 'keepcopy|k=s', 66 'help|h', 'version|v', 67 ) 68) { 69 pod2usage(-exitval => 1, -output => \*STDERR); 70} 71if ($main::options{'help'}) { 72 pod2usage(0); 73} elsif ($main::options{'version'}) { 74 printf "Sympa %s\n", Sympa::Constants::VERSION; 75 exit 0; 76} 77$Conf::sympa_config = $main::options{config}; 78 79if ($main::options{'debug'}) { 80 $main::options{'log_level'} = 2 unless $main::options{'log_level'}; 81 $main::options{'foreground'} = 1; 82} 83 84my $log = Sympa::Log->instance; 85$log->{log_to_stderr} = 'all' if $main::options{'foreground'}; 86 87my $language = Sympa::Language->instance; 88my $mailer = Sympa::Mailer->instance; 89 90_load(); 91 92# Put ourselves in background if we're not in debug mode. 93unless ($main::options{'foreground'}) { 94 $process->daemonize; 95 96 # Fork a new process dedicated to automatic list creation, if required. 97 if ($Conf::Conf{'automatic_list_feature'} eq 'on') { 98 my $child_pid = fork; 99 if ($child_pid) { 100 waitpid $child_pid, 0; 101 $CHILD_ERROR and die; 102 } elsif (not defined $child_pid) { 103 die sprintf 'Cannot fork: %s', $ERRNO; 104 } else { 105 # We're in the specialized child process: 106 # automatic lists creation. 107 exec q{--sbindir--/sympa_automatic.pl}, map { 108 defined $main::options{$_} 109 ? ("--$_", $main::options{$_}) 110 : () 111 } qw(config log_level mail); 112 die sprintf 'Cannot exec: %s', $ERRNO; 113 } 114 } 115} 116 117$log->openlog($Conf::Conf{'syslog'}, $Conf::Conf{'log_socket_type'}); 118 119# Create and write the PID file. 120$process->write_pid(initial => 1); 121# If process is running in foreground, don't write STDERR to a dedicated file. 122unless ($main::options{foreground}) { 123 $process->direct_stderr_to_file; 124} 125 126# Start multiple processes if required. 127unless ($main::options{'foreground'}) { 128 if (0 == $process->{generation} 129 and ($Conf::Conf{'incoming_max_count'} || 0) > 1) { 130 # Disconnect from database before fork to prevent DB handles 131 # to be shared by different processes. Sharing database 132 # handles may crash sympa_msg.pl. 133 Sympa::DatabaseManager->disconnect; 134 135 for my $process_count (2 .. $Conf::Conf{'incoming_max_count'}) { 136 my $child_pid = $process->fork; 137 if ($child_pid) { 138 $log->syslog('info', 'Starting child daemon, PID %s', 139 $child_pid); 140 # Saves the PID number 141 $process->write_pid(pid => $child_pid); 142 #$created_children{$child_pid} = 1; 143 sleep 1; 144 } elsif (not defined $child_pid) { 145 $log->syslog('err', 'Cannot fork: %m'); 146 last; 147 } else { 148 # We're in a child process 149 close STDERR; 150 $process->direct_stderr_to_file; 151 $log->openlog($Conf::Conf{'syslog'}, 152 $Conf::Conf{'log_socket_type'}); 153 $log->syslog('info', 'Slave daemon started with PID %s', 154 $PID); 155 last; 156 } 157 } 158 159 # Restore persistent connection. 160 Sympa::DatabaseManager->instance 161 or die 'Reconnecting database failed'; 162 } 163} 164 165# Set the User ID & Group ID for the process 166$GID = $EGID = (getgrnam(Sympa::Constants::GROUP))[2]; 167$UID = $EUID = (getpwnam(Sympa::Constants::USER))[2]; 168 169## Required on FreeBSD to change ALL IDs 170## (effective UID + real UID + saved UID) 171POSIX::setuid((getpwnam(Sympa::Constants::USER))[2]); 172POSIX::setgid((getgrnam(Sympa::Constants::GROUP))[2]); 173 174## Check if the UID has correctly been set (useful on OS X) 175unless (($GID == (getgrnam(Sympa::Constants::GROUP))[2]) 176 && ($UID == (getpwnam(Sympa::Constants::USER))[2])) { 177 die 178 "Failed to change process user ID and group ID. Note that on some OS Perl scripts can't change their real UID. In such circumstances Sympa should be run via sudo.\n"; 179} 180 181# Sets the UMASK 182umask(oct($Conf::Conf{'umask'})); 183 184## Most initializations have now been done. 185$log->syslog('notice', 'Sympa/msg %s Started', Sympa::Constants::VERSION()); 186 187# Check for several files. 188# Prevent that 2 processes perform checks at the same time... 189#FIXME: This would be done in --health_check mode. 190unless (Conf::checkfiles()) { 191 die "Missing files.\n"; 192 ## No return. 193} 194 195## Do we have right access in the directory 196if ($main::options{'keepcopy'}) { 197 if (!-d $main::options{'keepcopy'}) { 198 $log->syslog( 199 'notice', 200 'Cannot keep a copy of incoming messages: %s is not a directory', 201 $main::options{'keepcopy'} 202 ); 203 delete $main::options{'keepcopy'}; 204 } elsif (!-w $main::options{'keepcopy'}) { 205 $log->syslog( 206 'notice', 207 'Cannot keep a copy of incoming messages: no write access to %s', 208 $main::options{'keepcopy'} 209 ); 210 delete $main::options{'keepcopy'}; 211 } 212} 213 214my $spindle = Sympa::Spindle::ProcessIncoming->new( 215 keepcopy => $main::options{keepcopy}, 216 lang => $main::options{lang}, 217 log_level => $main::options{log_level}, 218 log_smtp => $main::options{mail}, 219 #FIXME: Is it required? 220 debug_virus_check => $main::options{debug}, 221); 222 223# Catch signals, in order to exit cleanly, whenever possible. 224$SIG{'TERM'} = 'sigterm'; 225$SIG{'INT'} = 'sigterm'; # Interrupt from terminal. 226$SIG{'HUP'} = 'sighup'; 227$SIG{'PIPE'} = 'IGNORE'; # Ignore SIGPIPE ; prevents process from dying 228 229# Main loop. 230# This loop is run foreach HUP signal received. 231 232my $index_queuedigest = 0; # verify the digest queue 233 234while (not $spindle->{finish} or $spindle->{finish} ne 'term') { 235 # Process digest only in master process ({generation} is 0). 236 # Scan queuedigest. 237 if (0 == $process->{generation} 238 and $index_queuedigest++ >= $digestsleep) { 239 $index_queuedigest = 0; 240 Sympa::Spindle::ProcessDigest->new->spin; 241 } 242 243 $spindle->spin; 244 245 if ($spindle->{finish} and $spindle->{finish} eq 'hup') { 246 # Disconnect from Database 247 Sympa::DatabaseManager->disconnect; 248 249 $log->syslog('notice', 'Sympa %s reload config', 250 Sympa::Constants::VERSION); 251 _load(); 252 $spindle = Sympa::Spindle::ProcessIncoming->new( 253 keepcopy => $main::options{keepcopy}, 254 lang => $main::options{lang}, 255 log_level => $main::options{log_level}, 256 log_smtp => $main::options{mail}, 257 #FIXME: Is it required? 258 debug_virus_check => $main::options{debug}, 259 ); 260 next; 261 } elsif ($spindle->{finish}) { 262 last; 263 } 264 265 # Sleep for a while if spool is empty. 266 sleep $Conf::Conf{'sleep'}; 267} 268 269# Purge grouped notifications 270Sympa::Spool::Listmaster->instance->flush(purge => 1); 271 272$log->syslog('notice', 'Sympa/msg exited normally due to signal'); 273$process->remove_pid; 274 275exit(0); 276 277# Load configuration. 278sub _load { 279 ## Load sympa.conf. 280 unless (Conf::load(Conf::get_sympa_conf(), 'no_db')) { #Site and Robot 281 die sprintf 282 "Unable to load sympa configuration, file %s or one of the vhost robot.conf files contain errors. Exiting.\n", 283 Conf::get_sympa_conf(); 284 } 285 286 ## Open the syslog and say we're read out stuff. 287 $log->openlog($Conf::Conf{'syslog'}, $Conf::Conf{'log_socket_type'}); 288 289 # Enable SMTP logging if required 290 $mailer->{log_smtp} = $main::options{'mail'} 291 || Sympa::Tools::Data::smart_eq($Conf::Conf{'log_smtp'}, 'on'); 292 293 # setting log_level using conf unless it is set by calling option 294 if (defined $main::options{'log_level'}) { 295 $log->{level} = $main::options{'log_level'}; 296 $log->syslog( 297 'info', 298 'Configuration file read, log level set using options: %s', 299 $main::options{'log_level'} 300 ); 301 } else { 302 $log->{level} = $Conf::Conf{'log_level'}; 303 $log->syslog( 304 'info', 305 'Configuration file read, default log level %s', 306 $Conf::Conf{'log_level'} 307 ); 308 } 309 310 # Check database connectivity. 311 unless (Sympa::DatabaseManager->instance) { 312 die sprintf 313 "Database %s defined in sympa.conf is unreachable. verify db_xxx parameters in sympa.conf\n", 314 $Conf::Conf{'db_name'}; 315 } 316 317 # Now trying to load full config (including database) 318 unless (Conf::load()) { #FIXME: load Site, then robot cache 319 die sprintf 320 "Unable to load Sympa configuration, file %s or any of the virtual host robot.conf files contain errors. Exiting.\n", 321 Conf::get_sympa_conf(); 322 } 323 324 ## Set locale configuration 325 ## Compatibility with version < 2.3.3 326 $main::options{'lang'} =~ s/\.cat$// 327 if defined $main::options{'lang'}; 328 $language->set_lang($main::options{'lang'}, $Conf::Conf{'lang'}, 'en'); 329 330 ## Main program 331 if (!chdir($Conf::Conf{'home'})) { 332 die sprintf 'Can\'t chdir to %s: %s', $Conf::Conf{'home'}, $ERRNO; 333 ## Function never returns. 334 } 335 336 ## Check for several files. 337 unless (Conf::checkfiles_as_root()) { 338 die "Missing files\n"; 339 } 340} 341 342############################################################ 343# sigterm 344############################################################ 345# When we catch signal, just changes the value of the $signal 346# loop variable. 347# 348# IN : - 349# 350# OUT : - 351# 352############################################################ 353sub sigterm { 354 my ($sig) = @_; 355 $log->syslog('notice', 356 'Signal %s received, still processing current task', $sig); 357 $spindle->{finish} = 'term'; 358} 359 360############################################################ 361# sighup 362############################################################ 363# When we catch SIGHUP, changes the value of the $signal 364# loop variable and puts the "-mail" logging option 365# 366# IN : - 367# 368# OUT : - 369# 370########################################################### 371sub sighup { 372 if ($mailer->{log_smtp}) { 373 $log->syslog('notice', 374 'signal HUP received, switch of the "-mail" logging option and continue current task' 375 ); 376 $mailer->{log_smtp} = undef; 377 } else { 378 $log->syslog('notice', 379 'signal HUP received, switch on the "-mail" logging option and continue current task' 380 ); 381 $mailer->{log_smtp} = 1; 382 } 383 $spindle->{finish} = 'hup'; 384} 385 386# Moved to Sympa::Spindle::ProcessIncoming::_twist(). 387#sub process_message; 388 389#sub DoSendMessage($message); 390#DEPRECATED: Run upgrade_send_spool.pl to migrate message with old format. 391 392# Moved to Sympa::Spindle::DoForward::_twist(). 393#sub DoForward; 394 395# Moved (divided) to Sympa::Spindle::DoMessage::_twist() & 396# Sympa::Spindle::AuthorizeMessage::_twist(). 397#sub DoMessage; 398 399# Old name: tools::checkcommand(). 400# Moved to Sympa::Spindle::DoMessage::_check_command(). 401#sub _check_command; 402 403# Moved to Sympa::Spindle::DoCommand::_twist(). 404#sub DoCommand; 405 406# DEPRECATED. Use Sympa::Spindle::ProcessDigest class. 407#sub SendDigest; 408 409# Moved to Sympa::Spindle::ProcessIncoming::_clean_msgid_table(). 410#sub clean_msgid_table; 411 412__END__ 413 414=encoding utf-8 415 416=head1 NAME 417 418sympa_msg, sympa_msg.pl - Daemon to handle incoming messages 419 420=head1 SYNOPSIS 421 422C<sympa_msg.pl> S<[ C<-d>, C<--debug> ]> 423S<[ C<-f>, C<--file>=I<another.sympa.conf> ]> 424S<[ C<-k>, C<--keepcopy>=I<directory> ]> 425S<[ C<-l>, C<--lang>=I<lang> ]> S<[ C<-m>, C<--mail> ]> 426S<[ C<-h>, C<--help> ]> S<[ C<-v>, C<--version> ]> 427 428=head1 DESCRIPTION 429 430Sympa_msg.pl is a program which scans permanently the incoming message spool 431and processes each message. 432 433Messages bound for the lists and authorized sending are modified as necessity 434and at last stored into digest spool, archive spool and outgoing spool. 435Those bound for command addresses are interpreted and appropriate actions are 436taken. 437Those bound for listmasters or list admins are forwarded to them. 438 439=head1 OPTIONS 440 441Sympa_msg.pl follows the usual GNU command line syntax, 442with long options starting with two dashes (C<-->). A summary of 443options is included below. 444 445=over 4 446 447=item C<-d>, C<--debug> 448 449Enable debug mode. 450 451=item C<-f>, C<--config=>I<file> 452 453Force Sympa to use an alternative configuration file instead 454of F<--CONFIG-->. 455 456=item C<-l>, C<--lang=>I<lang> 457 458Set this option to use a language for Sympa. The corresponding 459gettext catalog file must be located in F<$LOCALEDIR> 460directory. 461 462=item C<--log_level=>I<level> 463 464Sets Sympa log level. 465 466=back 467 468F<sympa_msg.pl> may run in daemon mode with following options. 469 470=over 4 471 472=item C<--foreground> 473 474The process remains attached to the TTY. 475 476=item C<-k>, C<--keepcopy=>F<directory> 477 478This option tells Sympa to keep a copy of every incoming message, 479instead of deleting them. `directory' is the directory to 480store messages. 481 482=item C<-m>, C<--mail> 483 484Sympa will log calls to sendmail, including recipients. This option is 485useful for keeping track of each mail sent (log files may grow faster 486though). 487 488=item C<--service=>I<service> 489 490B<Note>: 491This option was deprecated. 492 493Process is dedicated to messages distribution (C<process_message>), 494commands (C<process_command>) or to automatic lists 495creation (C<process_creation>, default three of them). 496 497=back 498 499With following options F<sympa_msg.pl> will print some information and exit. 500 501=over 4 502 503=item C<-h>, C<--help> 504 505Print this help message. 506 507=item C<-v>, C<--version> 508 509Print the version number. 510 511=back 512 513=head1 FILES 514 515F<--CONFIG--> main configuration file. 516 517F<$PIDDIR/sympa_msg.pid> this file contains the process ID 518of F<sympa_msg.pl>. 519 520=head1 SEE ALSO 521 522L<sympa.conf(5)>, L<sympa(1)>. 523 524L<archived(8)>, L<bulk(8)>, L<bounced(8)>, L<sympa_automatic(8)>, 525L<task_manager(8)>. 526 527L<Sympa::Spindle::ProcessDigest>, 528L<Sympa::Spindle::ProcessIncoming>. 529 530=head1 HISTORY 531 532F<sympa.pl> was originally written by: 533 534=over 4 535 536=item Serge Aumont 537 538ComitE<233> RE<233>seau des UniversitE<233>s 539 540=item Olivier SalaE<252>n 541 542ComitE<233> RE<233>seau des UniversitE<233>s 543 544=back 545 546As of Sympa 6.2b.4, it was split into three programs: 547F<sympa.pl> command line utility, F<sympa_automatic.pl> daemon and 548F<sympa_msg.pl> daemon. 549 550=cut 551