sympa_msg.pl.in 15.9 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
#!--PERL--
# -*- indent-tabs-mode: nil; -*-
# vim:ft=perl:et:sw=4
# $Id$

# Sympa - SYsteme de Multi-Postage Automatique
#
# Copyright (c) 1997, 1998, 1999 Institut Pasteur & Christophe Wolfhugel
# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites
11
# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER
12
13
# Copyright 2017, 2019, 2021 The Sympa Community. See the
# AUTHORS.md file at the top-level directory of this distribution and at
14
# <https://github.com/sympa-community/sympa.git>.
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

use lib split(/:/, $ENV{SYMPALIB} || ''), '--modulesdir--';
use strict;
use warnings;
use English qw(-no_match_vars);
use Getopt::Long;
use Pod::Usage;
use POSIX qw();

use Conf;
use Sympa::Constants;
use Sympa::Crash;    # Show traceback.
use Sympa::DatabaseManager;
use Sympa::Language;
42
use Sympa::Log;
43
use Sympa::Mailer;
44
use Sympa::Process;
45
use Sympa::Spindle::ProcessDigest;
46
use Sympa::Spindle::ProcessIncoming;
47
use Sympa::Spool::Listmaster;
48
49
use Sympa::Tools::Data;

50
my $process = Sympa::Process->instance;
51
$process->init(pidname => 'sympa_msg', name => 'sympa/msg');
52

53
54
55
56
57
58
59
## Internal tuning
# delay between each read of the digestqueue
my $digestsleep = 5;

## Init random engine
srand(time());

60
# Check options.
61
62
63
64
65
66
67
my %options;
unless (
    GetOptions(
        \%main::options, 'debug|d',  'log_level=s', 'foreground',
        'config|f=s',    'lang|l=s', 'mail|m',      'keepcopy|k=s',
        'help|h',        'version|v',
    )
Luc Didry's avatar
Luc Didry committed
68
) {
69
70
71
72
73
74
75
76
    pod2usage(-exitval => 1, -output => \*STDERR);
}
if ($main::options{'help'}) {
    pod2usage(0);
} elsif ($main::options{'version'}) {
    printf "Sympa %s\n", Sympa::Constants::VERSION;
    exit 0;
}
77
$Conf::sympa_config = $main::options{config};
78

79
80
81
82
83
84
85
86
if ($main::options{'debug'}) {
    $main::options{'log_level'} = 2 unless $main::options{'log_level'};
    $main::options{'foreground'} = 1;
}

my $log = Sympa::Log->instance;
$log->{log_to_stderr} = 'all' if $main::options{'foreground'};

87
88
89
90
91
my $language = Sympa::Language->instance;
my $mailer   = Sympa::Mailer->instance;

_load();

92
# Put ourselves in background if we're not in debug mode.
93
unless ($main::options{'foreground'}) {
94
    $process->daemonize;
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

    # Fork a new process dedicated to automatic list creation, if required.
    if ($Conf::Conf{'automatic_list_feature'} eq 'on') {
        my $child_pid = fork;
        if ($child_pid) {
            waitpid $child_pid, 0;
            $CHILD_ERROR and die;
        } elsif (not defined $child_pid) {
            die sprintf 'Cannot fork: %s', $ERRNO;
        } else {
            # We're in the specialized child process:
            # automatic lists creation.
            exec q{--sbindir--/sympa_automatic.pl}, map {
                defined $main::options{$_}
                    ? ("--$_", $main::options{$_})
                    : ()
sikeda's avatar
sikeda committed
111
            } qw(config log_level mail);
112
113
114
115
116
            die sprintf 'Cannot exec: %s', $ERRNO;
        }
    }
}

117
$log->openlog($Conf::Conf{'syslog'}, $Conf::Conf{'log_socket_type'});
118

119
120
121
122
123
# Create and write the PID file.
$process->write_pid(initial => 1);
# If process is running in foreground, don't write STDERR to a dedicated file.
unless ($main::options{foreground}) {
    $process->direct_stderr_to_file;
124
125
126
127
}

# Start multiple processes if required.
unless ($main::options{'foreground'}) {
128
    if (0 == $process->{generation}
129
130
131
132
133
134
135
        and ($Conf::Conf{'incoming_max_count'} || 0) > 1) {
        # Disconnect from database before fork to prevent DB handles
        # to be shared by different processes.  Sharing database
        # handles may crash sympa_msg.pl.
        Sympa::DatabaseManager->disconnect;

        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
140
                    $child_pid);
                # Saves the PID number
141
                $process->write_pid(pid => $child_pid);
142
143
144
                #$created_children{$child_pid} = 1;
                sleep 1;
            } elsif (not defined $child_pid) {
145
                $log->syslog('err', 'Cannot fork: %m');
146
147
148
149
                last;
            } else {
                # We're in a child process
                close STDERR;
150
                $process->direct_stderr_to_file;
151
                $log->openlog($Conf::Conf{'syslog'},
152
                    $Conf::Conf{'log_socket_type'});
153
154
                $log->syslog('info', 'Slave daemon started with PID %s',
                    $PID);
155
156
157
158
159
160
161
162
                last;
            }
        }

        # Restore persistent connection.
        Sympa::DatabaseManager->instance
            or die 'Reconnecting database failed';
    }
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
}

# Set the User ID & Group ID for the process
$GID = $EGID = (getgrnam(Sympa::Constants::GROUP))[2];
$UID = $EUID = (getpwnam(Sympa::Constants::USER))[2];

## Required on FreeBSD to change ALL IDs
## (effective UID + real UID + saved UID)
POSIX::setuid((getpwnam(Sympa::Constants::USER))[2]);
POSIX::setgid((getgrnam(Sympa::Constants::GROUP))[2]);

## Check if the UID has correctly been set (useful on OS X)
unless (($GID == (getgrnam(Sympa::Constants::GROUP))[2])
    && ($UID == (getpwnam(Sympa::Constants::USER))[2])) {
    die
        "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";
}

# Sets the UMASK
umask(oct($Conf::Conf{'umask'}));

## Most initializations have now been done.
185
$log->syslog('notice', 'Sympa/msg %s Started', Sympa::Constants::VERSION());
186
187
188
189
190
191
192
193
194
195
196
197

# Check for several files.
# Prevent that 2 processes perform checks at the same time...
#FIXME: This would be done in --health_check mode.
unless (Conf::checkfiles()) {
    die "Missing files.\n";
    ## No return.
}

## Do we have right access in the directory
if ($main::options{'keepcopy'}) {
    if (!-d $main::options{'keepcopy'}) {
198
        $log->syslog(
199
200
201
202
203
204
            'notice',
            'Cannot keep a copy of incoming messages: %s is not a directory',
            $main::options{'keepcopy'}
        );
        delete $main::options{'keepcopy'};
    } elsif (!-w $main::options{'keepcopy'}) {
205
        $log->syslog(
206
207
208
209
210
211
212
213
            'notice',
            'Cannot keep a copy of incoming messages: no write access to %s',
            $main::options{'keepcopy'}
        );
        delete $main::options{'keepcopy'};
    }
}

214
215
216
217
218
my $spindle = Sympa::Spindle::ProcessIncoming->new(
    keepcopy  => $main::options{keepcopy},
    lang      => $main::options{lang},
    log_level => $main::options{log_level},
    log_smtp  => $main::options{mail},
219
220
    #FIXME: Is it required?
    debug_virus_check => $main::options{debug},
221
222
);

223
224
# Catch signals, in order to exit cleanly, whenever possible.
$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
230
231

# Main loop.
# This loop is run foreach HUP signal received.

232
my $index_queuedigest = 0;   # verify the digest queue
233

234
while (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
247
248
        # Disconnect from Database
        Sympa::DatabaseManager->disconnect;

249
        $log->syslog('notice', 'Sympa %s reload config',
250
251
            Sympa::Constants::VERSION);
        _load();
252
253
254
255
256
        $spindle = Sympa::Spindle::ProcessIncoming->new(
            keepcopy  => $main::options{keepcopy},
            lang      => $main::options{lang},
            log_level => $main::options{log_level},
            log_smtp  => $main::options{mail},
257
258
            #FIXME: Is it required?
            debug_virus_check => $main::options{debug},
259
260
261
262
        );
        next;
    } elsif ($spindle->{finish}) {
        last;
263
    }
264

265
266
267
    # Sleep for a while if spool is empty.
    sleep $Conf::Conf{'sleep'};
}
268
269

# Purge grouped notifications
270
Sympa::Spool::Listmaster->instance->flush(purge => 1);
271

272
$log->syslog('notice', 'Sympa/msg exited normally due to signal');
273
$process->remove_pid;
274
275
276
277
278
279
280
281
282
283
284
285
286

exit(0);

# Load configuration.
sub _load {
    ## Load sympa.conf.
    unless (Conf::load(Conf::get_sympa_conf(), 'no_db')) {    #Site and Robot
        die sprintf
            "Unable to load sympa configuration, file %s or one of the vhost robot.conf files contain errors. Exiting.\n",
            Conf::get_sympa_conf();
    }

    ## Open the syslog and say we're read out stuff.
287
    $log->openlog($Conf::Conf{'syslog'}, $Conf::Conf{'log_socket_type'});
288
289
290
291
292
293
294

    # Enable SMTP logging if required
    $mailer->{log_smtp} = $main::options{'mail'}
        || Sympa::Tools::Data::smart_eq($Conf::Conf{'log_smtp'}, 'on');

    # setting log_level using conf unless it is set by calling option
    if (defined $main::options{'log_level'}) {
295
296
        $log->{level} = $main::options{'log_level'};
        $log->syslog(
297
298
299
300
301
            'info',
            'Configuration file read, log level set using options: %s',
            $main::options{'log_level'}
        );
    } else {
302
303
        $log->{level} = $Conf::Conf{'log_level'};
        $log->syslog(
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
            'info',
            'Configuration file read, default log level %s',
            $Conf::Conf{'log_level'}
        );
    }

    # Check database connectivity.
    unless (Sympa::DatabaseManager->instance) {
        die sprintf
            "Database %s defined in sympa.conf is unreachable. verify db_xxx parameters in sympa.conf\n",
            $Conf::Conf{'db_name'};
    }

    # Now trying to load full config (including database)
    unless (Conf::load()) {    #FIXME: load Site, then robot cache
        die sprintf
            "Unable to load Sympa configuration, file %s or any of the virtual host robot.conf files contain errors. Exiting.\n",
            Conf::get_sympa_conf();
    }

    ## Set locale configuration
    ## Compatibility with version < 2.3.3
    $main::options{'lang'} =~ s/\.cat$//
        if defined $main::options{'lang'};
328
    $language->set_lang($main::options{'lang'}, $Conf::Conf{'lang'}, 'en');
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354

    ## Main program
    if (!chdir($Conf::Conf{'home'})) {
        die sprintf 'Can\'t chdir to %s: %s', $Conf::Conf{'home'}, $ERRNO;
        ## Function never returns.
    }

    ## Check for several files.
    unless (Conf::checkfiles_as_root()) {
        die "Missing files\n";
    }
}

############################################################
# sigterm
############################################################
#  When we catch signal, just changes the value of the $signal
#  loop variable.
#
# IN : -
#
# OUT : -
#
############################################################
sub sigterm {
    my ($sig) = @_;
355
356
    $log->syslog('notice',
        'Signal %s received, still processing current task', $sig);
357
    $spindle->{finish} = 'term';
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
}

############################################################
# sighup
############################################################
#  When we catch SIGHUP, changes the value of the $signal
#  loop variable and puts the "-mail" logging option
#
# IN : -
#
# OUT : -
#
###########################################################
sub sighup {
    if ($mailer->{log_smtp}) {
373
        $log->syslog('notice',
374
375
376
377
            'signal HUP received, switch of the "-mail" logging option and continue current task'
        );
        $mailer->{log_smtp} = undef;
    } else {
378
        $log->syslog('notice',
379
380
381
382
            'signal HUP received, switch on the "-mail" logging option and continue current task'
        );
        $mailer->{log_smtp} = 1;
    }
383
    $spindle->{finish} = 'hup';
384
385
}

386
387
# Moved to Sympa::Spindle::ProcessIncoming::_twist().
#sub process_message;
388
389
390
391

#sub DoSendMessage($message);
#DEPRECATED: Run upgrade_send_spool.pl to migrate message with old format.

392
# Moved to Sympa::Spindle::DoForward::_twist().
393
#sub DoForward;
394

395
396
# Moved (divided) to Sympa::Spindle::DoMessage::_twist() &
# 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
410
# Moved to Sympa::Spindle::ProcessIncoming::_clean_msgid_table().
#sub clean_msgid_table;
411
412
413
414
415
416
417
418
419
420
421

__END__

=encoding utf-8

=head1 NAME

sympa_msg, sympa_msg.pl - Daemon to handle incoming messages

=head1 SYNOPSIS

422
423
424
425
426
C<sympa_msg.pl> S<[ C<-d>, C<--debug> ]>
S<[ C<-f>, C<--file>=I<another.sympa.conf> ]>
S<[ C<-k>, C<--keepcopy>=I<directory> ]>
S<[ C<-l>, C<--lang>=I<lang> ]> S<[ C<-m>, C<--mail> ]>
S<[ C<-h>, C<--help> ]> S<[ C<-v>, C<--version> ]>
427
428
429

=head1 DESCRIPTION

sikeda's avatar
sikeda committed
430
431
432
Sympa_msg.pl is a program which scans permanently the incoming message spool
and processes each message.

433
Messages bound for the lists and authorized sending are modified as necessity
434
and at last stored into digest spool, archive spool and outgoing spool.
sikeda's avatar
sikeda committed
435
436
437
Those bound for command addresses are interpreted and appropriate actions are
taken.
Those bound for listmasters or list admins are forwarded to them.
438
439
440

=head1 OPTIONS

sikeda's avatar
sikeda committed
441
442
443
Sympa_msg.pl follows the usual GNU command line syntax,
with long options starting with two dashes (C<-->).  A summary of
options is included below.
444
445
446

=over 4

447
=item C<-d>, C<--debug>
448
449
450

Enable debug mode.

451
=item C<-f>, C<--config=>I<file>
452
453
454
455

Force Sympa to use an alternative configuration file instead
of F<--CONFIG-->.

456
=item C<-l>, C<--lang=>I<lang>
457
458

Set this option to use a language for Sympa. The corresponding
459
gettext catalog file must be located in F<$LOCALEDIR>
460
461
directory.

462
=item C<--log_level=>I<level>
463
464
465
466
467
468
469
470
471

Sets Sympa log level.

=back

F<sympa_msg.pl> may run in daemon mode with following options.

=over 4

472
=item C<--foreground>
473
474
475

The process remains attached to the TTY.

476
=item C<-k>, C<--keepcopy=>F<directory>
477
478
479
480
481

This option tells Sympa to keep a copy of every incoming message, 
instead of deleting them. `directory' is the directory to 
store messages.

482
=item C<-m>, C<--mail>
483
484
485
486
487

Sympa will log calls to sendmail, including recipients. This option is
useful for keeping track of each mail sent (log files may grow faster
though).

488
=item C<--service=>I<service>
489
490
491
492

B<Note>:
This option was deprecated.

493
494
495
Process is dedicated to messages distribution (C<process_message>),
commands (C<process_command>) or to automatic lists
creation (C<process_creation>, default three of them).
496
497
498
499
500
501
502

=back

With following options F<sympa_msg.pl> will print some information and exit.

=over 4

503
=item C<-h>, C<--help>
504
505
506

Print this help message.

507
=item C<-v>, C<--version>
508
509
510
511
512
513
514
515
516

Print the version number.

=back

=head1 FILES

F<--CONFIG--> main configuration file.

517
F<$PIDDIR/sympa_msg.pid> this file contains the process ID
518
519
of F<sympa_msg.pl>.

sikeda's avatar
sikeda committed
520
=head1 SEE ALSO
521

sikeda's avatar
sikeda committed
522
L<sympa.conf(5)>, L<sympa(1)>.
523

sikeda's avatar
sikeda committed
524
525
L<archived(8)>, L<bulk(8)>, L<bounced(8)>, L<sympa_automatic(8)>,
L<task_manager(8)>.
526

527
528
L<Sympa::Spindle::ProcessDigest>,
L<Sympa::Spindle::ProcessIncoming>.
529

530
531
=head1 HISTORY

sikeda's avatar
sikeda committed
532
F<sympa.pl> was originally written by:
533
534
535
536
537
538
539
540
541
542
543
544
545

=over 4

=item Serge Aumont

ComitE<233> RE<233>seau des UniversitE<233>s

=item Olivier SalaE<252>n

ComitE<233> RE<233>seau des UniversitE<233>s

=back

546
As of Sympa 6.2b.4, it was split into three programs:
sikeda's avatar
sikeda committed
547
548
F<sympa.pl> command line utility, F<sympa_automatic.pl> daemon and
F<sympa_msg.pl> daemon.
549
550

=cut