Conf.pm 79.7 KB
Newer Older
1
2
3
4
# -*- indent-tabs-mode: nil; -*-
# vim:ft=perl:et:sw=4
# $Id$

5
# Sympa - SYsteme de Multi-Postage Automatique
6
7
8
9
#
# 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
10
# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER
11
12
# Copyright 2017, 2018, 2019 The Sympa Community. See the AUTHORS.md file at
# the top-level directory of this distribution and at
13
# <https://github.com/sympa-community/sympa.git>.
14
15
16
17
18
19
20
21
22
23
24
25
#
# 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
26
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
27

root's avatar
root committed
28
29
30
31
## This module handles the configuration file for Sympa.

package Conf;

32
33
use strict;
use warnings;
34
use English qw(-no_match_vars);
35

36
use Sympa;
37
use Sympa::ConfDef;
38
use Sympa::Constants;
39
use Sympa::DatabaseManager;
40
use Sympa::Language;
41
use Sympa::Log;
42
use Sympa::Regexps;
43
44
use Sympa::Tools::Data;
use Sympa::Tools::File;
45
use Sympa::Tools::Text;
root's avatar
root committed
46

47
48
my $log = Sympa::Log->instance;

49
=encoding utf-8
50

51
52
53
#=head1 NAME
#
#Conf - Sympa configuration
54
55
56
57
58
59
60

=head1 DESCRIPTION

=head2 CONSTANTS AND EXPORTED VARIABLES

=cut

61
## Database and SQL statement handlers
62
my $sth;
63
# parameters hash, keyed by parameter name
64
our %params =
65
    map { $_->{name} => $_ }
66
    grep { $_->{name} } @Sympa::ConfDef::params;
67
68
69

# valid virtual host parameters, keyed by parameter name
my %valid_robot_key_words;
70
my %db_storable_parameters;
71
my %optional_key_words;
72
foreach my $hash (@Sympa::ConfDef::params) {
73
74
75
76
    $valid_robot_key_words{$hash->{'name'}} = 1 if ($hash->{'vhost'});
    $db_storable_parameters{$hash->{'name'}} = 1
        if (defined($hash->{'db'}) and $hash->{'db'} ne 'none');
    $optional_key_words{$hash->{'name'}} = 1 if ($hash->{'optional'});
77
78
}

sikeda's avatar
sikeda committed
79
our $params_by_categories = _get_parameters_names_by_category();
80

81
my %old_params = (
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
    trusted_ca_options               => 'capath,cafile',
    'msgcat'                         => '',
    queueexpire                      => '',
    clean_delay_queueother           => '',
    web_recode_to                    => 'filesystem_encoding', # ??? - 5.2
    'localedir'                      => '',
    'ldap_export_connection_timeout' => '',                    # 3.3b3 - 4.1?
    'ldap_export_dnmanager'          => '',                    # ,,
    'ldap_export_host'               => '',                    # ,,
    'ldap_export_name'               => '',                    # ,,
    'ldap_export_password'           => '',                    # ,,
    'ldap_export_suffix'             => '',                    # ,,
    'tri'                            => 'sort',                # ??? - 1.3.4-1
    'sort'                           => '',                    # 1.4.0 - ???
    'pidfile'                        => '',                    # ??? - 6.1.17
    'pidfile_distribute'             => '',                    # ,,
    'pidfile_creation'               => '',                    # ,,
    'pidfile_bulk'                   => '',                    # ,,
    'archived_pidfile'               => '',                    # ,,
    'bounced_pidfile'                => '',                    # ,,
    'task_manager_pidfile'           => '',                    # ,,
103
104
105
106
107
108
    'email_gecos'       => 'gecos',              # 6.2a.?? - 6.2a.33
    'lock_method'       => '',                   # 5.3b.3 - 6.2a.33
    'html_editor_file'  => 'html_editor_url',    # 6.2a
    'openssl'           => '',                   # ?? - 6.2a.40
    'distribution_mode' => '',                   # 5.0a.1 - 6.2a.40
    'queuedistribute'   => '',                   # ,,
109
110
111
112

    # These are not yet implemented
    'crl_dir'          => '',
    'dkim_header_list' => '',
root's avatar
root committed
113
);
114

115
116
## These parameters now have a hard-coded value
## Customized value can be accessed though as %Ignored_Conf
olivier.salaun's avatar
olivier.salaun committed
117
my %Ignored_Conf;
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
my %hardcoded_params = (filesystem_encoding => 'utf8');

my %trusted_applications = (
    'trusted_application' => {
        'occurrence' => '0-n',
        'format'     => {
            'name' => {
                'format'     => '\S*',
                'occurrence' => '1',
                'case'       => 'insensitive',
            },
            'ip' => {
                'format'     => '\d+\.\d+\.\d+\.\d+',
                'occurrence' => '0-1'
            },
            'md5password' => {
                'format'     => '.*',
                'occurrence' => '0-1'
            },
            'proxy_for_variables' => {
                'format'     => '.*',
                'occurrence' => '0-n',
                'split_char' => ','
141
142
143
144
145
146
147
148
149
150
151
            },
            'set_variables' => {
                'format'     => '\S+=.*',
                'occurrence' => '0-n',
                'split_char' => ',',
            },
            'allow_commands' => {
                'format'     => '\S+',
                'occurrence' => '0-n',
                'split_char' => ',',
            },
152
153
        }
    }
154
);
IKEDA Soji's avatar
IKEDA Soji committed
155
#XXXmy $binary_file_extension = ".bin";
156

157
our $wwsconf;
olivier.salaun's avatar
olivier.salaun committed
158
our %Conf = ();
root's avatar
root committed
159

160
161
162
163
164
165
166
167
168
169
170
=head2 FUNCTIONS

=over 4

=item load ( [ CONFIG_FILE ], [ NO_DB ], [ RETURN_RESULT ] )

Loads and parses the configuration file.  Reports errors if any.

do not try to load database values if NO_DB is set;
do not change gloval hash %Conf if RETURN_RESULT is set;

171
172
## we known that's dirty, this proc should be rewritten without this global
## var %Conf
173
174
175
176
177

=back

=cut

root's avatar
root committed
178
sub load {
179
180
    my $config_file   = shift || get_sympa_conf();
    my $no_db         = shift;
181
    my $return_result = shift;
182
    my $force_reload;
183

root's avatar
root committed
184
    my $config_err = 0;
185
186
    my %line_numbered_config;

IKEDA Soji's avatar
IKEDA Soji committed
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
    $log->syslog('debug3',
        'File %s has changed since the last cache. Loading file',
        $config_file);
    # Will force the robot.conf reloading, as sympa.conf is the default.
    $force_reload = 1;
    ## Loading the Sympa main config file.
    if (my $config_loading_result =
        _load_config_file_to_hash({'path_to_config_file' => $config_file})) {
        %line_numbered_config =
            %{$config_loading_result->{'numbered_config'}};
        %Conf       = %{$config_loading_result->{'config'}};
        $config_err = $config_loading_result->{'errors'};
    } else {
        return undef;
    }
    # Returning the config file content if this is what has been asked.
    return (\%line_numbered_config) if ($return_result);

    # Users may define parameters with a typo or other errors. Check that
    # the parameters
    # we found in the config file are all well defined Sympa parameters.
    $config_err += _detect_unknown_parameters_in_config(
        {   'config_hash'                          => \%Conf,
            'config_file_line_numbering_reference' => \%line_numbered_config,
211
        }
IKEDA Soji's avatar
IKEDA Soji committed
212
    );
213

IKEDA Soji's avatar
IKEDA Soji committed
214
215
216
217
    # Some parameter values are hardcoded. In that case, ignore what was
    #  set in the config file and simply use the hardcoded value.
    %Ignored_Conf =
        %{_set_hardcoded_parameter_values({'config_hash' => \%Conf,})};
root's avatar
root committed
218

IKEDA Soji's avatar
IKEDA Soji committed
219
    _set_listmasters_entry({'config_hash' => \%Conf, 'main_config' => 1});
220

IKEDA Soji's avatar
IKEDA Soji committed
221
222
223
224
    ## Some parameters must have a value specifically defined in the
    ## config. If not, it is an error.
    $config_err += _detect_missing_mandatory_parameters(
        {'config_hash' => \%Conf, 'file_to_check' => $config_file});
225

IKEDA Soji's avatar
IKEDA Soji committed
226
227
    # Some parameters need special treatments to get their final values.
    _infer_server_specific_parameter_values({'config_hash' => \%Conf,});
228

IKEDA Soji's avatar
IKEDA Soji committed
229
    _infer_robot_parameter_values({'config_hash' => \%Conf});
root's avatar
root committed
230

IKEDA Soji's avatar
IKEDA Soji committed
231
232
233
234
235
    if ($config_err) {
        $log->syslog('err', 'Errors while parsing main config file %s',
            $config_file);
        return undef;
    }
236

IKEDA Soji's avatar
IKEDA Soji committed
237
238
239
    _store_source_file_name(
        {'config_hash' => \%Conf, 'config_file' => $config_file});
    #XXX_save_config_hash_to_binary({'config_hash' => \%Conf,});
root's avatar
root committed
240

241
242
    if (my $missing_modules_count =
        _check_cpan_modules_required_by_config({'config_hash' => \%Conf,})) {
243
        $log->syslog('err', 'Warning: %d required modules are missing',
sikeda's avatar
sikeda committed
244
            $missing_modules_count);
245
246
    }

247
248
    _replace_file_value_by_db_value({'config_hash' => \%Conf})
        unless ($no_db);
sikeda's avatar
sikeda committed
249
250
    _load_server_specific_secondary_config_files({'config_hash' => \%Conf,});
    _load_robot_secondary_config_files({'config_hash' => \%Conf});
251

252
    ## Load robot.conf files
253
254
255
256
257
258
259
    unless (
        load_robots(
            {   'config_hash'  => \%Conf,
                'no_db'        => $no_db,
                'force_reload' => $force_reload
            }
        )
Luc Didry's avatar
Luc Didry committed
260
    ) {
261
262
        return undef;
    }
sikeda's avatar
sikeda committed
263
    ##_create_robot_like_config_for_main_robot();
root's avatar
root committed
264
    return 1;
265
}
266

salaun's avatar
salaun committed
267
268
## load each virtual robots configuration files
sub load_robots {
269
    my $param = shift;
270
271
    my @robots;

sikeda's avatar
sikeda committed
272
    my $robots_list_ref = get_robots_list();
273
    unless (defined $robots_list_ref) {
274
        $log->syslog('err', 'Robots config loading failed');
275
        return undef;
276
    } else {
277
278
279
280
281
        @robots = @{$robots_list_ref};
    }
    unless ($#robots > -1) {
        return 1;
    }
282
    my $exiting = 0;
283
    foreach my $robot (@robots) {
284
        my $robot_config_file = "$Conf{'etc'}/$robot/robot.conf";
285
286
287
288
289
290
291
292
        my $robot_conf        = undef;
        unless (
            $robot_conf = _load_single_robot_config(
                {   'robot'        => $robot,
                    'no_db'        => $param->{'no_db'},
                    'force_reload' => $param->{'force_reload'}
                }
            )
Luc Didry's avatar
Luc Didry committed
293
        ) {
294
            $log->syslog(
sikeda's avatar
sikeda committed
295
296
297
298
                'err',
                'The config for robot %s contain errors: it could not be correctly loaded',
                $robot
            );
299
            $exiting = 1;
300
        } else {
301
302
            $param->{'config_hash'}{'robots'}{$robot} = $robot_conf;
        }
303
304
        #_check_double_url_usage(
        #    {'config_hash' => $param->{'config_hash'}{'robots'}{$robot}});
305
    }
306
    return undef if ($exiting);
307
    return 1;
308
309
310
311
312
313
314
}

## returns a robot conf parameter
sub get_robot_conf {
    my ($robot, $param) = @_;

    if (defined $robot && $robot ne '*') {
315
316
        if (   defined $Conf{'robots'}{$robot}
            && defined $Conf{'robots'}{$robot}{$param}) {
317
318
            return $Conf{'robots'}{$robot}{$param};
        }
319
    }
320
    ## default
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
    return $Conf{$param};
}

=over 4

=item get_sympa_conf

Gets path name of main config file.
Path name is taken from:

=over 4

=item 1

C<--config> command line option

=item 2

C<SYMPA_CONFIG> environment variable

=item 3

built-in default

=back

=back

=cut

351
352
our $sympa_config;

353
sub get_sympa_conf {
354
    return $sympa_config || $ENV{'SYMPA_CONFIG'} || Sympa::Constants::CONFIG;
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
}

=over 4

=item get_wwsympa_conf

Gets path name of wwsympa.conf file.
Path name is taken from:

=over 4

=item 1

C<SYMPA_WWSCONFIG> environment variable

=item 2

built-in default

=back

=back

=cut

sub get_wwsympa_conf {
    return $ENV{'SYMPA_WWSCONFIG'} || Sympa::Constants::WWSCONFIG;
salaun's avatar
salaun committed
382
383
}

384
# deletes all the *.conf.bin files.
IKEDA Soji's avatar
IKEDA Soji committed
385
386
# No longer used.
#sub delete_binaries;
387

388
389
# Return a reference to an array containing the names of the robots on the
# server.
390
sub get_robots_list {
391
    $log->syslog('debug2', "Retrieving the list of robots on the server");
392
    my @robots_list;
393
    unless (opendir DIR, $Conf{'etc'}) {
394
        $log->syslog('err',
sikeda's avatar
sikeda committed
395
396
            'Unable to open directory %s for virtual robots config',
            $Conf{'etc'});
397
398
399
400
401
402
403
404
405
406
407
408
        return undef;
    }
    foreach my $robot (readdir DIR) {
        my $robot_config_file = "$Conf{'etc'}/$robot/robot.conf";
        next unless (-d "$Conf{'etc'}/$robot");
        next unless (-f $robot_config_file);
        push @robots_list, $robot;
    }
    closedir(DIR);
    return \@robots_list;
}

409
## Returns a hash containing the values of all the parameters of the group
410
411
## (as defined in Sympa::ConfDef) whose name is given as argument, in the
## context of the robot given as argument.
412
413
sub get_parameters_group {
    my ($robot, $group) = @_;
414
    $log->syslog('debug3', 'Getting parameters for group "%s"', $group);
415
416
    my $param_hash;
    foreach my $param_name (keys %{$params_by_categories->{$group}}) {
417
        $param_hash->{$param_name} = get_robot_conf($robot, $param_name);
418
419
420
    }
    return $param_hash;
}
421
422

## fetch the value from parameter $label of robot $robot from conf_table
423
sub get_db_conf {
424
425
426
    my $robot = shift;
    my $label = shift;

427
428
429
430
431
    # if the value is related to a robot that is not explicitly defined, apply
    # it to the default robot.
    $robot = '*' unless (-f $Conf{'etc'} . '/' . $robot . '/robot.conf');
    unless ($robot) { $robot = '*' }

432
    my $sdm = Sympa::DatabaseManager->instance;
433
    unless (
434
435
436
437
438
439
        $sdm
        and $sth = $sdm->do_prepared_query(
            q{SELECT value_conf AS value
              FROM conf_table
              WHERE robot_conf = ? AND label_conf = ?},
            $robot, $label
440
        )
Luc Didry's avatar
Luc Didry committed
441
    ) {
442
        $log->syslog(
443
444
445
446
447
            'err',
            'Unable retrieve value of parameter %s for robot %s from the database',
            $label,
            $robot
        );
448
        return undef;
449
    }
450

451
    my $value = $sth->fetchrow;
452

453
    $sth->finish();
454
    return $value;
455
456
457
}

## store the value from parameter $label of robot $robot from conf_table
458
sub set_robot_conf {
459
460
461
462
    my $robot = shift;
    my $label = shift;
    my $value = shift;

463
    $log->syslog('info', 'Set config for robot %s, %s="%s"',
464
        $robot, $label, $value);
465

466
467
468
469
470
471
472
473
    # set the current config before to update database.
    if (-f "$Conf{'etc'}/$robot/robot.conf") {
        $Conf{'robots'}{$robot}{$label} = $value;
    } else {
        $Conf{$label} = $value;
        $robot = '*';
    }

474
    my $sdm = Sympa::DatabaseManager->instance;
475
    unless (
476
477
478
479
480
481
        $sdm
        and $sth = $sdm->do_prepared_query(
            q{SELECT COUNT(*)
              FROM conf_table
              WHERE robot_conf = ? AND label_conf = ?},
            $robot, $label
482
        )
Luc Didry's avatar
Luc Didry committed
483
    ) {
484
        $log->syslog(
485
486
487
488
489
            'err',
            'Unable to check presence of parameter %s for robot %s in database',
            $label,
            $robot
        );
490
        return undef;
491
    }
492

493
494
    my $count = $sth->fetchrow;
    $sth->finish();
495

496
    if ($count == 0) {
497
        unless (
498
499
500
501
502
            $sth = $sdm->do_prepared_query(
                q{INSERT INTO conf_table
                  (robot_conf, label_conf, value_conf)
                  VALUES (?, ?, ?)},
                $robot, $label, $value
503
            )
Luc Didry's avatar
Luc Didry committed
504
        ) {
505
            $log->syslog(
506
507
508
509
510
511
                'err',
                'Unable add value %s for parameter %s in the robot %s DB conf',
                $value,
                $label,
                $robot
            );
512
513
            return undef;
        }
514
515
    } else {
        unless (
516
517
518
519
520
521
            $sth = $sdm->do_prepared_query(
                q{UPDATE conf_table
                  SET robot_conf = ?, label_conf = ?, value_conf = ?
                  WHERE robot_conf = ? AND label_conf = ?},
                $robot, $label, $value,
                $robot, $label
522
            )
Luc Didry's avatar
Luc Didry committed
523
        ) {
524
            $log->syslog(
525
526
527
528
529
530
                'err',
                'Unable set parameter %s value to %s in the robot %s DB conf',
                $label,
                $value,
                $robot
            );
531
            return undef;
532
        }
533
534
535
    }
}

536
537
# Store configs to database
sub conf_2_db {
538
    $log->syslog('debug2', '(%s)', @_);
539

540
    my @conf_parameters = @Sympa::ConfDef::params;
541
542

    # store in database robots parameters.
543
544
545
    # load only parameters that are in a robot.conf file (do not apply
    # defaults).
    my $robots_conf = load_robots();
546

547
    unless (opendir DIR, $Conf{'etc'}) {
548
        $log->syslog('err',
sikeda's avatar
sikeda committed
549
550
            'Unable to open directory %s for virtual robots config',
            $Conf{'etc'});
551
552
553
554
555
556
        return undef;
    }

    foreach my $robot (readdir(DIR)) {
        next unless (-d "$Conf{'etc'}/$robot");
        next unless (-f "$Conf{'etc'}/$robot/robot.conf");
557

558
        my $config;
559
        if (my $result_of_config_loading = _load_config_file_to_hash(
560
                {         'path_to_config_file' => $Conf{'etc'} . '/'
561
562
563
564
                        . $robot
                        . '/robot.conf'
                }
            )
Luc Didry's avatar
Luc Didry committed
565
        ) {
566
567
            $config = $result_of_config_loading->{'config'};
        }
sikeda's avatar
sikeda committed
568
        _remove_unvalid_robot_entry($config);
569
570
571
572
573
574
575

        for my $i (0 .. $#conf_parameters) {
            if ($conf_parameters[$i]->{'name'}) {
                # skip separators in conf_parameters structure
                if (($conf_parameters[$i]->{'vhost'} eq '1')
                    && #skip parameters that can't be define by robot so not to be loaded in db at that stage
                    ($config->{$conf_parameters[$i]->{'name'}})
Luc Didry's avatar
Luc Didry committed
576
                ) {
577
578
579
580
581
582
                    Conf::set_robot_conf(
                        $robot,
                        $conf_parameters[$i]->{'name'},
                        $config->{$conf_parameters[$i]->{'name'}}
                    );
                }
583
584
585
            }
        }
    }
586
    closedir(DIR);
587
588

    # store in database sympa;conf and wwsympa.conf
589

590
591
    ## Load configuration file. Ignoring database config and get result
    my $global_conf;
592
593
    unless ($global_conf =
        Conf::load(Conf::get_sympa_conf(), 1, 'return_result')) {
594
        $log->syslog('err', 'Configuration file %s has errors',
595
596
            Conf::get_sympa_conf());
        return undef;
597
    }
598
599
600
601
602
603
604
605
606
607

    for my $i (0 .. $#conf_parameters) {
        if (($conf_parameters[$i]->{'edit'} eq '1')
            && $global_conf->{$conf_parameters[$i]->{'name'}}) {
            Conf::set_robot_conf(
                "*",
                $conf_parameters[$i]->{'name'},
                $global_conf->{$conf_parameters[$i]->{'name'}}[0]
            );
        }
608
609
610
    }
}

611
612
613
## Check required files and create them if required
sub checkfiles_as_root {

614
    my $config_err = 0;
sympa-authors's avatar
   
sympa-authors committed
615

616
    ## Check aliases file
617
618
619
    unless (-f $Conf{'sendmail_aliases'}
        || ($Conf{'sendmail_aliases'} =~ /^none$/i)) {
        unless (open ALIASES, ">$Conf{'sendmail_aliases'}") {
620
            $log->syslog(
621
622
623
624
625
626
                'err',
                "Failed to create aliases file %s",
                $Conf{'sendmail_aliases'}
            );
            return undef;
        }
627

628
629
630
631
632
        print ALIASES
            "## This aliases file is dedicated to Sympa Mailing List Manager\n";
        print ALIASES
            "## You should edit your sendmail.mc or sendmail.cf file to declare it\n";
        close ALIASES;
633
        $log->syslog(
634
635
636
637
638
            'notice',
            "Created missing file %s",
            $Conf{'sendmail_aliases'}
        );
        unless (
639
            Sympa::Tools::File::set_file_rights(
640
641
642
643
644
                file  => $Conf{'sendmail_aliases'},
                user  => Sympa::Constants::USER,
                group => Sympa::Constants::GROUP,
                mode  => 0644,
            )
Luc Didry's avatar
Luc Didry committed
645
        ) {
646
            $log->syslog('err', 'Unable to set rights on %s',
647
648
649
                $Conf{'db_name'});
            return undef;
        }
sympa-authors's avatar
   
sympa-authors committed
650
651
    }

652
    foreach my $robot (keys %{$Conf{'robots'}}) {
sympa-authors's avatar
sympa-authors committed
653

654
655
656
657
        # create static content directory
        my $dir = get_robot_conf($robot, 'static_content_path');
        if ($dir ne '' && !-d $dir) {
            unless (mkdir($dir, 0775)) {
658
659
                $log->syslog('err', 'Unable to create directory %s: %m',
                    $dir);
660
661
                $config_err++;
            }
662

663
            unless (
664
                Sympa::Tools::File::set_file_rights(
665
666
667
668
                    file  => $dir,
                    user  => Sympa::Constants::USER,
                    group => Sympa::Constants::GROUP,
                )
Luc Didry's avatar
Luc Didry committed
669
            ) {
670
                $log->syslog('err', 'Unable to set rights on %s',
671
672
673
                    $Conf{'db_name'});
                return undef;
            }
674
        }
675
676
    }

677
    return 1;
678
679
}

sikeda's avatar
sikeda committed
680
681
682
683
684
685
686
687
688
689
## Check if data structures are uptodate
## If not, no operation should be performed before the upgrade process is run
sub data_structure_uptodate {
    my $version_file =
        Conf::get_robot_conf('*', 'etc') . '/data_structure.version';
    my $data_structure_version;

    if (-f $version_file) {
        my $fh;
        unless (open $fh, '<', $version_file) {
690
            $log->syslog('err', 'Unable to open %s: %m', $version_file);
sikeda's avatar
sikeda committed
691
692
693
694
695
696
697
698
699
700
701
702
703
704
            return undef;
        }
        while (<$fh>) {
            next if /^\s*$/;
            next if /^\s*\#/;
            chomp;
            $data_structure_version = $_;
            last;
        }
        close $fh;
    }

    if (defined $data_structure_version
        and $data_structure_version ne Sympa::Constants::VERSION) {
705
        $log->syslog('err',
sikeda's avatar
sikeda committed
706
707
708
709
710
711
712
713
            "Data structure (%s) is not uptodate for current release (%s)",
            $data_structure_version, Sympa::Constants::VERSION);
        return 0;
    }

    return 1;
}

714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
# Check if cookie parameter was changed.
# Old name: tools::cookie_changed().
sub cookie_changed {
    my $current = $Conf::Conf{'cookie'};
    $current = '' unless defined $current;

    my $changed = 1;
    if (-f "$Conf::Conf{'etc'}/cookies.history") {
        my $fh;
        unless (open $fh, "$Conf::Conf{'etc'}/cookies.history") {
            $log->syslog('err', 'Unable to read %s/cookies.history',
                $Conf::Conf{'etc'});
            return undef;
        }
        my $oldcook = <$fh>;
        close $fh;
        ($oldcook) = reverse split /\s+/, $oldcook;
        $oldcook = '' unless defined $oldcook;

        if ($oldcook eq $current) {
            $log->syslog('debug2', 'Cookie is stable');
            $changed = 0;
        }
        return $changed;
    } else {
        my $umask = umask 037;
        unless (open COOK, ">$Conf::Conf{'etc'}/cookies.history") {
            umask $umask;
            $log->syslog('err', 'Unable to create %s/cookies.history',
                $Conf::Conf{'etc'});
            return undef;
        }
        umask $umask;
        chown [getpwnam(Sympa::Constants::USER)]->[2],
            [getgrnam(Sympa::Constants::GROUP)]->[2],
            "$Conf::Conf{'etc'}/cookies.history";
        print COOK "$current ";
        close COOK;
        return (0);
    }
}

root's avatar
root committed
756
757
758
## Check a few files
sub checkfiles {
    my $config_err = 0;
759

760
    foreach my $p (qw(sendmail antivirus_path)) {
761
762
763
        next unless $Conf{$p};

        unless (-x $Conf{$p}) {
764
            $log->syslog('err', "File %s does not exist or is not executable",
765
766
                $Conf{$p});
            $config_err++;
767
        }
768
769
    }

770
    foreach my $qdir (qw(spool queuetask tmpdir)) {
771
        unless (-d $Conf{$qdir}) {
772
            $log->syslog('info', 'Creating spool %s', $Conf{$qdir});
773
            unless (mkdir($Conf{$qdir}, 0775)) {
774
775
                $log->syslog('err', 'Unable to create spool %s',
                    $Conf{$qdir});
776
777
778
                $config_err++;
            }
            unless (
779
                Sympa::Tools::File::set_file_rights(
780
781
782
                    file  => $Conf{$qdir},
                    user  => Sympa::Constants::USER,
                    group => Sympa::Constants::GROUP,
783
                )
Luc Didry's avatar
Luc Didry committed
784
            ) {
785
                $log->syslog('err', 'Unable to set rights on %s',
786
787
                    $Conf{$qdir});
                $config_err++;
788
            }
789
        }
root's avatar
root committed
790
    }
791

792
793
794
795
796
797
798
    # Check if directory parameters point to the same directory.
    my @keys = qw(bounce_path etc home
        queue queueauth queuebounce queuebulk queuedigest
        queuemod queueoutgoing queuesubscribe queuetask
        queuetopic spool tmpdir viewmail_dir);
    push @keys, 'queueautomatic'
        if $Conf::Conf{'automatic_list_feature'} eq 'on';
799
    my %dirs = (Sympa::Constants::PIDDIR() => 'PID directory');
800

801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
    foreach my $key (@keys) {
        my $val = $Conf::Conf{$key};
        next unless $val;

        if ($dirs{$val}) {
            $log->syslog(
                'err',
                'Error in config: %s and %s parameters pointing to the same directory (%s)',
                $dirs{$val},
                $key,
                $val
            );
            $config_err++;
        } else {
            $dirs{$val} = $key;
        }
817
818
    }

819
820
821
822
823
824
825
826
    # Create pictures directory. FIXME: Would be created on demand.
    my $pictures_dir = $Conf::Conf{'pictures_path'};
    unless (-d $pictures_dir) {
        unless (mkdir $pictures_dir, 0775) {
            $log->syslog('err', 'Unable to create directory %s',
                $pictures_dir);
            $config_err++;
        } else {
IKEDA Soji's avatar
IKEDA Soji committed
827
            chmod 0775, $pictures_dir;    # set masked bits.
828

829
830
831
832
833
834
835
836
837
838
            my $index_path = $pictures_dir . '/index.html';
            my $fh;
            unless (open $fh, '>', $index_path) {
                $log->syslog(
                    'err',
                    'Unable to create %s as an empty file to protect directory',
                    $index_path
                );
            } else {
                close $fh;
839
840
841
            }
        }
    }
842

843
844
    #update_css();

root's avatar
root committed
845
846
847
848
    return undef if ($config_err);
    return 1;
}

849
## return 1 if the parameter is a known robot
850
## Valid options :
851
##    'just_try' : prevent error logs if robot is not valid
852
sub valid_robot {
853
    my $robot   = shift;
854
    my $options = shift;
855

856
857
    ## Main host
    return 1 if ($robot eq $Conf{'domain'});
858

859
    ## Missing etc directory
860
    unless (-d $Conf{'etc'} . '/' . $robot) {
861
        $log->syslog(
862
            'err',  'Robot %s undefined; no %s directory',
863
864
865
            $robot, $Conf{'etc'} . '/' . $robot
        ) unless ($options->{'just_try'});
        return undef;
866
    }
867

868
    ## Missing expl directory
869
    unless (-d $Conf{'home'} . '/' . $robot) {
870
        $log->syslog(
871
            'err',  'Robot %s undefined; no %s directory',
872
873
874
            $robot, $Conf{'home'} . '/' . $robot
        ) unless ($options->{'just_try'});
        return undef;
875
    }
876

877
878
    ## Robot not loaded
    unless (defined $Conf{'robots'}{$robot}) {
879
        $log->syslog('err', 'Robot %s was not loaded by this Sympa process',
880
881
882
            $robot)
            unless ($options->{'just_try'});
        return undef;
883
    }
884

885
886
887
888
889
890
891
892
893
    return 1;
}

## Returns the SSO record correponding to the provided sso_id
## return undef if none was found
sub get_sso_by_id {
    my %param = @_;

    unless (defined $param{'service_id'} && defined $param{'robot'}) {
894
        return undef;
895
896
897
    }

    foreach my $sso (@{$Conf{'auth_services'}{$param{'robot'}}}) {
898
        $log->syslog('notice', 'SSO: %s', $sso->{'service_id'});
899
        next unless ($sso->{'service_id'} eq $param{'service_id'});
900

901
        return $sso;
902
    }
903

904
905
906
907
908
    return undef;
}

##########################################
## Low level subs. Not supposed to be called from other modules.
909
910
911
##########################################

sub _load_auth {
912
913
    $log->syslog('debug3', '(%s, %s)', @_);
    my $that = shift || '*';
914

915
916
917
    my $config_file = Sympa::search_fullpath($that, 'auth.conf');
    die sprintf 'No auth.conf for %s', $that
        unless $config_file and -r $config_file;
918

919
    my $robot      = ($that and $that ne '*') ? $that : $Conf{'domain'};
920
    my $line_num   = 0;
921
922
    my $config_err = 0;
    my @paragraphs;
923
    my %result;
924
925
926
927
928
929
930
931
932
933
934
    my $current_paragraph;

    my %valid_keywords = (
        'ldap' => {
            'regexp'          => '.*',
            'negative_regexp' => '.*',
            'host'            => '[\w\.\-]+(:\d+)?(\s*,\s*[\w\.\-]+(:\d+)?)*',
            'timeout'         => '\d+',
            'suffix'          => '.+',
            'bind_dn'         => '.+',
            'bind_password'   => '.+',
IKEDA Soji's avatar
IKEDA Soji committed
935
936
937
938
939
940
941
942
943
            'get_dn_by_uid_filter'   => '.+',
            'get_dn_by_email_filter' => '.+',
            'email_attribute'        => Sympa::Regexps::ldap_attrdesc(),
            'alternative_email_attribute' => '.*',                 # Obsoleted
            'scope'                       => 'base|one|sub',
            'authentication_info_url'     => 'http(s)?:/.*',
            'use_tls'                     => 'starttls|ldaps|none',
            'use_ssl'                     => '1',                  # Obsoleted
            'use_start_tls'               => '1',                  # Obsoleted
IKEDA Soji's avatar
IKEDA Soji committed
944
            'ssl_version' => 'sslv2/3|sslv2|sslv3|tlsv1|tlsv1_[123]',
945
946
947
948
949
950
            'ssl_ciphers' => '[\w:]+',
            'ssl_cert'    => '.+',
            'ssl_key'     => '.+',
            'ca_verify'   => '\w+',
            'ca_path'     => '.+',
            'ca_file'     => '.+',
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
        },

        'user_table' => {
            'regexp'          => '.*',
            'negative_regexp' => '.*'
        },

        'cas' => {
            'base_url'                   => 'http(s)?:/.*',
            'non_blocking_redirection'   => 'on|off',
            'login_path'                 => '.*',
            'logout_path'                => '.*',
            'service_validate_path'      => '.*',
            'proxy_path'                 => '.*',
            'proxy_validate_path'        => '.*',
            'auth_service_name'          => '[\w\-\.]+',
            'auth_service_friendly_name' => '.*',
            'authentication_info_url'    => 'http(s)?:/.*',
sikeda's avatar
sikeda committed
969
970
971
972
973
974
            'host'          => '[\w\.\-]+(:\d+)?(\s*,\s*[\w\.\-]+(:\d+)?)*',
            'bind_dn'       => '.+',
            'bind_password' => '.+',
            'timeout'       => '\d+',
            'suffix'        => '.+',
            'scope'         => 'base|one|sub',
975
            'get_email_by_uid_filter' => '.+',
976
            'email_attribute'         => Sympa::Regexps::ldap_attrdesc(),
sikeda's avatar
sikeda committed
977
            'use_tls'                 => 'starttls|ldaps|none',
IKEDA Soji's avatar
IKEDA Soji committed
978
979
            'use_ssl'       => '1',    # Obsoleted
            'use_start_tls' => '1',    # Obsoleted
IKEDA Soji's avatar
IKEDA Soji committed
980
            'ssl_version' => 'sslv2/3|sslv2|sslv3|tlsv1|tlsv1_[123]',
981
            'ssl_ciphers' => '[\w:]+',
sikeda's avatar
sikeda committed
982
983
984
985
986
            'ssl_cert'    => '.+',
            'ssl_key'     => '.+',
            'ca_verify'   => '\w+',
            'ca_path'     => '.+',
            'ca_file'     => '.+',
987
988
989
990
991
992
993
994
995
        },
        'generic_sso' => {
            'service_name'                => '.+',
            'service_id'                  => '\S+',
            'http_header_prefix'          => '\w+',
            'http_header_list'            => '[\w\.\-\,]+',
            'email_http_header'           => '\w+',
            'http_header_value_separator' => '.+',
            'logout_url'                  => '.+',
sikeda's avatar
sikeda committed
996
997
998
999
1000
            'host'          => '[\w\.\-]+(:\d+)?(\s*,\s*[\w\.\-]+(:\d+)?)*',
            'bind_dn'       => '.+',
            'bind_password' => '.+',
            'timeout'       => '\d+',
            'suffix'        => '.+',
For faster browsing, not all history is shown. View entire blame