Conf.pm 79.2 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, 2020, 2021 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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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' => ','
136
137
138
139
140
141
142
143
144
145
146
            },
            'set_variables' => {
                'format'     => '\S+=.*',
                'occurrence' => '0-n',
                'split_char' => ',',
            },
            'allow_commands' => {
                'format'     => '\S+',
                'occurrence' => '0-n',
                'split_char' => ',',
            },
147
148
        }
    }
149
);
IKEDA Soji's avatar
IKEDA Soji committed
150
#XXXmy $binary_file_extension = ".bin";
151

152
our $wwsconf;
olivier.salaun's avatar
olivier.salaun committed
153
our %Conf = ();
root's avatar
root committed
154

155
156
157
158
159
160
161
162
163
164
165
=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;

166
167
## we known that's dirty, this proc should be rewritten without this global
## var %Conf
168
169
170
171
172

=back

=cut

root's avatar
root committed
173
sub load {
174
175
    my $config_file   = shift || get_sympa_conf();
    my $no_db         = shift;
176
    my $return_result = shift;
177
    my $force_reload;
178

root's avatar
root committed
179
    my $config_err = 0;
180
181
    my %line_numbered_config;

IKEDA Soji's avatar
IKEDA Soji committed
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
    $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,
206
        }
IKEDA Soji's avatar
IKEDA Soji committed
207
    );
208

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

IKEDA Soji's avatar
IKEDA Soji committed
211
212
213
214
    ## 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});
215

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

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

IKEDA Soji's avatar
IKEDA Soji committed
221
222
223
224
225
    if ($config_err) {
        $log->syslog('err', 'Errors while parsing main config file %s',
            $config_file);
        return undef;
    }
226

IKEDA Soji's avatar
IKEDA Soji committed
227
228
229
    _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
230

231
232
    if (my $missing_modules_count =
        _check_cpan_modules_required_by_config({'config_hash' => \%Conf,})) {
233
        $log->syslog('err', 'Warning: %d required modules are missing',
sikeda's avatar
sikeda committed
234
            $missing_modules_count);
235
236
    }

237
238
    _replace_file_value_by_db_value({'config_hash' => \%Conf})
        unless ($no_db);
sikeda's avatar
sikeda committed
239
240
    _load_server_specific_secondary_config_files({'config_hash' => \%Conf,});
    _load_robot_secondary_config_files({'config_hash' => \%Conf});
241

242
    ## Load robot.conf files
243
244
245
246
247
248
249
    unless (
        load_robots(
            {   'config_hash'  => \%Conf,
                'no_db'        => $no_db,
                'force_reload' => $force_reload
            }
        )
Luc Didry's avatar
Luc Didry committed
250
    ) {
251
252
        return undef;
    }
sikeda's avatar
sikeda committed
253
    ##_create_robot_like_config_for_main_robot();
root's avatar
root committed
254
    return 1;
255
}
256

salaun's avatar
salaun committed
257
258
## load each virtual robots configuration files
sub load_robots {
259
    my $param = shift;
260
261
    my @robots;

sikeda's avatar
sikeda committed
262
    my $robots_list_ref = get_robots_list();
263
    unless (defined $robots_list_ref) {
264
        $log->syslog('err', 'Robots config loading failed');
265
        return undef;
266
    } else {
267
268
269
270
271
        @robots = @{$robots_list_ref};
    }
    unless ($#robots > -1) {
        return 1;
    }
272
    my $exiting = 0;
273
    foreach my $robot (@robots) {
274
        my $robot_config_file = "$Conf{'etc'}/$robot/robot.conf";
275
276
277
278
279
280
281
282
        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
283
        ) {
284
            $log->syslog(
sikeda's avatar
sikeda committed
285
286
287
288
                'err',
                'The config for robot %s contain errors: it could not be correctly loaded',
                $robot
            );
289
            $exiting = 1;
290
        } else {
291
292
            $param->{'config_hash'}{'robots'}{$robot} = $robot_conf;
        }
293
294
        #_check_double_url_usage(
        #    {'config_hash' => $param->{'config_hash'}{'robots'}{$robot}});
295
    }
296
    return undef if ($exiting);
297
    return 1;
298
299
300
301
302
303
}

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

304
305
    $param = $Sympa::Config::Schema::obsolete_robot_params{$param} // $param;

306
    if (defined $robot && $robot ne '*') {
307
308
        if (   defined $Conf{'robots'}{$robot}
            && defined $Conf{'robots'}{$robot}{$param}) {
309
310
            return $Conf{'robots'}{$robot}{$param};
        }
311
    }
312
    ## default
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
    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

343
344
our $sympa_config;

345
sub get_sympa_conf {
346
    return $sympa_config || $ENV{'SYMPA_CONFIG'} || Sympa::Constants::CONFIG;
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
}

=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
374
375
}

376
# deletes all the *.conf.bin files.
IKEDA Soji's avatar
IKEDA Soji committed
377
378
# No longer used.
#sub delete_binaries;
379

380
381
# Return a reference to an array containing the names of the robots on the
# server.
382
sub get_robots_list {
383
    $log->syslog('debug2', "Retrieving the list of robots on the server");
384
    my @robots_list;
385
    unless (opendir DIR, $Conf{'etc'}) {
386
        $log->syslog('err',
sikeda's avatar
sikeda committed
387
388
            'Unable to open directory %s for virtual robots config',
            $Conf{'etc'});
389
390
391
392
393
394
395
396
397
398
399
400
        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;
}

401
## Returns a hash containing the values of all the parameters of the group
402
403
## (as defined in Sympa::ConfDef) whose name is given as argument, in the
## context of the robot given as argument.
404
405
sub get_parameters_group {
    my ($robot, $group) = @_;
406
    $log->syslog('debug3', 'Getting parameters for group "%s"', $group);
407
408
    my $param_hash;
    foreach my $param_name (keys %{$params_by_categories->{$group}}) {
409
        $param_hash->{$param_name} = get_robot_conf($robot, $param_name);
410
411
412
    }
    return $param_hash;
}
413
414

## fetch the value from parameter $label of robot $robot from conf_table
415
sub get_db_conf {
416
417
418
    my $robot = shift;
    my $label = shift;

419
420
421
422
423
    # 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 = '*' }

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

443
    my $value = $sth->fetchrow;
444

445
    $sth->finish();
446
    return $value;
447
448
449
}

## store the value from parameter $label of robot $robot from conf_table
450
sub set_robot_conf {
451
452
453
454
    my $robot = shift;
    my $label = shift;
    my $value = shift;

455
    $log->syslog('info', 'Set config for robot %s, %s="%s"',
456
        $robot, $label, $value);
457

458
459
460
461
462
463
464
465
    # 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 = '*';
    }

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

485
486
    my $count = $sth->fetchrow;
    $sth->finish();
487

488
    if ($count == 0) {
489
        unless (
490
491
492
493
494
            $sth = $sdm->do_prepared_query(
                q{INSERT INTO conf_table
                  (robot_conf, label_conf, value_conf)
                  VALUES (?, ?, ?)},
                $robot, $label, $value
495
            )
Luc Didry's avatar
Luc Didry committed
496
        ) {
497
            $log->syslog(
498
499
500
501
502
503
                'err',
                'Unable add value %s for parameter %s in the robot %s DB conf',
                $value,
                $label,
                $robot
            );
504
505
            return undef;
        }
506
507
    } else {
        unless (
508
509
510
511
512
513
            $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
514
            )
Luc Didry's avatar
Luc Didry committed
515
        ) {
516
            $log->syslog(
517
518
519
520
521
522
                'err',
                'Unable set parameter %s value to %s in the robot %s DB conf',
                $label,
                $value,
                $robot
            );
523
            return undef;
524
        }
525
526
527
    }
}

528
529
# Store configs to database
sub conf_2_db {
530
    $log->syslog('debug2', '(%s)', @_);
531

532
    my @conf_parameters = @Sympa::ConfDef::params;
533
534

    # store in database robots parameters.
535
536
537
    # load only parameters that are in a robot.conf file (do not apply
    # defaults).
    my $robots_conf = load_robots();
538

539
    unless (opendir DIR, $Conf{'etc'}) {
540
        $log->syslog('err',
sikeda's avatar
sikeda committed
541
542
            'Unable to open directory %s for virtual robots config',
            $Conf{'etc'});
543
544
545
546
547
548
        return undef;
    }

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

550
        my $config;
551
        if (my $result_of_config_loading = _load_config_file_to_hash(
552
                {         'path_to_config_file' => $Conf{'etc'} . '/'
553
554
555
556
                        . $robot
                        . '/robot.conf'
                }
            )
Luc Didry's avatar
Luc Didry committed
557
        ) {
558
559
            $config = $result_of_config_loading->{'config'};
        }
sikeda's avatar
sikeda committed
560
        _remove_unvalid_robot_entry($config);
561
562
563
564
565
566
567

        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
568
                ) {
569
570
571
572
573
574
                    Conf::set_robot_conf(
                        $robot,
                        $conf_parameters[$i]->{'name'},
                        $config->{$conf_parameters[$i]->{'name'}}
                    );
                }
575
576
577
            }
        }
    }
578
    closedir(DIR);
579
580

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

582
583
    ## Load configuration file. Ignoring database config and get result
    my $global_conf;
584
585
    unless ($global_conf =
        Conf::load(Conf::get_sympa_conf(), 1, 'return_result')) {
586
        $log->syslog('err', 'Configuration file %s has errors',
587
588
            Conf::get_sympa_conf());
        return undef;
589
    }
590
591
592
593
594
595
596
597
598
599

    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]
            );
        }
600
601
602
    }
}

603
604
605
## Check required files and create them if required
sub checkfiles_as_root {

606
    my $config_err = 0;
sympa-authors's avatar
   
sympa-authors committed
607

608
    ## Check aliases file
609
610
611
    unless (-f $Conf{'sendmail_aliases'}
        || ($Conf{'sendmail_aliases'} =~ /^none$/i)) {
        unless (open ALIASES, ">$Conf{'sendmail_aliases'}") {
612
            $log->syslog(
613
614
615
616
617
618
                'err',
                "Failed to create aliases file %s",
                $Conf{'sendmail_aliases'}
            );
            return undef;
        }
619

620
621
622
623
624
        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;
625
        $log->syslog(
626
627
628
629
630
            'notice',
            "Created missing file %s",
            $Conf{'sendmail_aliases'}
        );
        unless (
631
            Sympa::Tools::File::set_file_rights(
632
633
634
635
636
                file  => $Conf{'sendmail_aliases'},
                user  => Sympa::Constants::USER,
                group => Sympa::Constants::GROUP,
                mode  => 0644,
            )
Luc Didry's avatar
Luc Didry committed
637
        ) {
638
            $log->syslog('err', 'Unable to set rights on %s',
639
640
641
                $Conf{'db_name'});
            return undef;
        }
sympa-authors's avatar
   
sympa-authors committed
642
643
    }

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

646
647
648
649
        # create static content directory
        my $dir = get_robot_conf($robot, 'static_content_path');
        if ($dir ne '' && !-d $dir) {
            unless (mkdir($dir, 0775)) {
650
651
                $log->syslog('err', 'Unable to create directory %s: %m',
                    $dir);
652
653
                $config_err++;
            }
654

655
            unless (
656
                Sympa::Tools::File::set_file_rights(
657
658
659
660
                    file  => $dir,
                    user  => Sympa::Constants::USER,
                    group => Sympa::Constants::GROUP,
                )
Luc Didry's avatar
Luc Didry committed
661
            ) {
662
                $log->syslog('err', 'Unable to set rights on %s',
663
664
665
                    $Conf{'db_name'});
                return undef;
            }
666
        }
667
668
    }

669
    return 1;
670
671
}

sikeda's avatar
sikeda committed
672
673
674
675
676
677
678
679
680
681
## 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) {
682
            $log->syslog('err', 'Unable to open %s: %m', $version_file);
sikeda's avatar
sikeda committed
683
684
685
686
687
688
689
690
691
692
693
694
695
696
            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) {
697
        $log->syslog('err',
sikeda's avatar
sikeda committed
698
699
700
701
702
703
704
705
            "Data structure (%s) is not uptodate for current release (%s)",
            $data_structure_version, Sympa::Constants::VERSION);
        return 0;
    }

    return 1;
}

706
707
# Check if cookie parameter was changed.
# Old name: tools::cookie_changed().
708
709
# Deprecated: No longer used.
#sub cookie_changed;
710

root's avatar
root committed
711
712
713
## Check a few files
sub checkfiles {
    my $config_err = 0;
714

715
    foreach my $p (qw(sendmail antivirus_path)) {
716
717
718
        next unless $Conf{$p};

        unless (-x $Conf{$p}) {
719
            $log->syslog('err', "File %s does not exist or is not executable",
720
721
                $Conf{$p});
            $config_err++;
722
        }
723
724
    }

725
    foreach my $qdir (qw(spool queuetask tmpdir)) {
726
        unless (-d $Conf{$qdir}) {
727
            $log->syslog('info', 'Creating spool %s', $Conf{$qdir});
728
            unless (mkdir($Conf{$qdir}, 0775)) {
729
730
                $log->syslog('err', 'Unable to create spool %s',
                    $Conf{$qdir});
731
732
733
                $config_err++;
            }
            unless (
734
                Sympa::Tools::File::set_file_rights(
735
736
737
                    file  => $Conf{$qdir},
                    user  => Sympa::Constants::USER,
                    group => Sympa::Constants::GROUP,
738
                )
Luc Didry's avatar
Luc Didry committed
739
            ) {
740
                $log->syslog('err', 'Unable to set rights on %s',
741
742
                    $Conf{$qdir});
                $config_err++;
743
            }
744
        }
root's avatar
root committed
745
    }
746

747
748
749
750
751
752
753
    # 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';
754
    my %dirs = (Sympa::Constants::PIDDIR() => 'PID directory');
755

756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
    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;
        }
772
773
    }

774
775
776
777
778
779
780
781
    # 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
782
            chmod 0775, $pictures_dir;    # set masked bits.
783

784
785
786
787
788
789
790
791
792
793
            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;
794
795
796
            }
        }
    }
797

798
799
    #update_css();

root's avatar
root committed
800
801
802
803
    return undef if ($config_err);
    return 1;
}

804
## return 1 if the parameter is a known robot
805
## Valid options :
806
##    'just_try' : prevent error logs if robot is not valid
807
sub valid_robot {
808
    my $robot   = shift;
809
    my $options = shift;
810

811
812
    ## Main host
    return 1 if ($robot eq $Conf{'domain'});
813

814
    ## Missing etc directory
815
    unless (-d $Conf{'etc'} . '/' . $robot) {
816
        $log->syslog(
817
            'err',  'Robot %s undefined; no %s directory',
818
819
820
            $robot, $Conf{'etc'} . '/' . $robot
        ) unless ($options->{'just_try'});
        return undef;
821
    }
822

823
    ## Missing expl directory
824
    unless (-d $Conf{'home'} . '/' . $robot) {
825
        $log->syslog(
826
            'err',  'Robot %s undefined; no %s directory',
827
828
829
            $robot, $Conf{'home'} . '/' . $robot
        ) unless ($options->{'just_try'});
        return undef;
830
    }
831

832
833
    ## Robot not loaded
    unless (defined $Conf{'robots'}{$robot}) {
834
        $log->syslog('err', 'Robot %s was not loaded by this Sympa process',
835
836
837
            $robot)
            unless ($options->{'just_try'});
        return undef;
838
    }
839

840
841
842
843
844
845
846
847
848
    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'}) {
849
        return undef;
850
851
852
    }

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

856
        return $sso;
857
    }
858

859
860
861
862
863
    return undef;
}

##########################################
## Low level subs. Not supposed to be called from other modules.
864
865
866
##########################################

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

870
871
872
    my $config_file = Sympa::search_fullpath($that, 'auth.conf');
    die sprintf 'No auth.conf for %s', $that
        unless $config_file and -r $config_file;
873

874
    my $robot      = ($that and $that ne '*') ? $that : $Conf{'domain'};
875
    my $line_num   = 0;
876
877
    my $config_err = 0;
    my @paragraphs;
878
    my %result;
879
880
881
882
883
884
885
886
887
888
889
    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
890
891
892
893
894
895
896
897
898
            '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
899
            'ssl_version' => 'sslv2/3|sslv2|sslv3|tlsv1|tlsv1_[123]',
900
901
902
903
904
905
            'ssl_ciphers' => '[\w:]+',
            'ssl_cert'    => '.+',
            'ssl_key'     => '.+',
            'ca_verify'   => '\w+',
            'ca_path'     => '.+',
            'ca_file'     => '.+',
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
        },

        '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
924
925
926
927
928
929
            'host'          => '[\w\.\-]+(:\d+)?(\s*,\s*[\w\.\-]+(:\d+)?)*',
            'bind_dn'       => '.+',
            'bind_password' => '.+',
            'timeout'       => '\d+',
            'suffix'        => '.+',
            'scope'         => 'base|one|sub',
930
            'get_email_by_uid_filter' => '.+',
931
            'email_attribute'         => Sympa::Regexps::ldap_attrdesc(),
sikeda's avatar
sikeda committed
932
            'use_tls'                 => 'starttls|ldaps|none',
IKEDA Soji's avatar
IKEDA Soji committed
933
934
            'use_ssl'       => '1',    # Obsoleted
            'use_start_tls' => '1',    # Obsoleted
IKEDA Soji's avatar
IKEDA Soji committed
935
            'ssl_version' => 'sslv2/3|sslv2|sslv3|tlsv1|tlsv1_[123]',
936
            'ssl_ciphers' => '[\w:]+',
sikeda's avatar
sikeda committed
937
938
939
940
941
            'ssl_cert'    => '.+',
            'ssl_key'     => '.+',
            'ca_verify'   => '\w+',
            'ca_path'     => '.+',
            'ca_file'     => '.+',
942
943
944
945
946
947
948
949
950
        },
        '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
951
952
953
954
955
956
            'host'          => '[\w\.\-]+(:\d+)?(\s*,\s*[\w\.\-]+(:\d+)?)*',
            'bind_dn'       => '.+',
            'bind_password' => '.+',
            'timeout'       => '\d+',
            'suffix'        => '.+',
            'scope'         => 'base|one|sub',
957
            'get_email_by_uid_filter' => '.+',
958
            'email_attribute'         => Sympa::Regexps::ldap_attrdesc(),
sikeda's avatar
sikeda committed
959
            'use_tls'                 => 'starttls|ldaps|none',
IKEDA Soji's avatar
IKEDA Soji committed
960
961
            'use_ssl'       => '1',    # Obsoleted
            'use_start_tls' => '1',    # Obsoleted
IKEDA Soji's avatar
IKEDA Soji committed
962
963
964
965
966
967
968
969
            'ssl_version'        => 'sslv2/3|sslv2|sslv3|tlsv1|tlsv1_[123]',
            'ssl_ciphers'        => '[\w:]+',
            'ssl_cert'           => '.+',
            'ssl_key'            => '.+',
            'ca_verify'          => '\w+',
            'ca_path'            => '.+',
            'ca_file'            => '.+',
            'force_email_verify' => '1',
970
971
            'internal_email_by_netid' => '1',
            'netid_http_header'       => '[\w\-\.]+',
972
973
974
        },
        'authentication_info_url' => 'http(s)?:/.*'
    );
975

976
    ## Open the configuration file or return and read the lines.
977
    unless (open(IN, $config_file)) {
978
        $log->syslog('notice', 'Unable to open %s: %m', $config_file);
979
        return undef;
980
    }
981

982
    $Conf{'cas_number'}{$robot}         = 0;
983
    $Conf{'generic_sso_number'}{$robot} = 0;
984
985
986
    $Conf{'ldap_number'}{$robot}        = 0;
    $Conf{'use_passwd'}{$robot}         = 0;

987
988
    ## Parsing  auth.conf
    while (<IN>) {
989

990
991
        $line_num++;
        next if (/^\s*[\#\;]/o);
992

993
994
        if (/^\s*authentication_info_url\s+(.*\S)\s*$/o) {
            $Conf{'authentication_info_url'}{$robot} = $1;
995
            next;
996
997
998
999
        } elsif (/^\s*(ldap|cas|user_table|generic_sso)\s*$/io) {
            $current_paragraph->{'auth_type'} = lc($1);
        } elsif (/^\s*(\S+)\s+(.*\S)\s*$/o) {
            my ($keyword, $value) = ($1, $2);
1000
1001
1002
1003
1004
1005

            # Workaround: Some parameters required by cas and generic_sso auth
            # types may be prefixed by "ldap_", but LDAP database driver
            # requires those not prefixed.
            $keyword =~ s/\Aldap_//;

1006
1007
1008
            unless (
                defined $valid_keywords{$current_paragraph->{'auth_type'}}
                {$keyword}) {
1009
                $log->syslog('err', 'Unknown keyword "%s" in %s line %d',
1010
1011
1012
1013
1014
                    $keyword, $config_file, $line_num);
                next;
            }
            unless ($value =~
                /^$valid_keywords{$current_paragraph->{'auth_type'}}{$keyword}$/
Luc Didry's avatar
Luc Didry committed
1015
            ) {
1016
                $log->syslog('err',
1017
1018
                    'Unknown format "%s" for keyword "%s" in %s line %d',
                    $value, $keyword, $config_file, $line_num);
1019
                next;
1020
1021
            }

1022
1023
1024
            ## Allow white spaces between hosts
            if ($keyword =~ /host$/) {
                $value =~ s/\s//g;
1025
            }
1026

1027
            $current_paragraph->{$keyword} = $value;