Conf.pm 81.6 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 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;

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

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

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

sikeda's avatar
sikeda committed
222
        _set_listmasters_entry({'config_hash' => \%Conf, 'main_config' => 1});
223
224
225
226
227

        ## 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});
228

229
        # Some parameters need special treatments to get their final values.
sikeda's avatar
sikeda committed
230
        _infer_server_specific_parameter_values({'config_hash' => \%Conf,});
231

sikeda's avatar
sikeda committed
232
        _infer_robot_parameter_values({'config_hash' => \%Conf});
root's avatar
root committed
233

234
        if ($config_err) {
235
            $log->syslog('err', 'Errors while parsing main config file %s',
sikeda's avatar
sikeda committed
236
                $config_file);
237
238
            return undef;
        }
239

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

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

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

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

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

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

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

    if (defined $robot && $robot ne '*') {
318
319
        if (   defined $Conf{'robots'}{$robot}
            && defined $Conf{'robots'}{$robot}{$param}) {
320
321
            return $Conf{'robots'}{$robot}{$param};
        }
322
    }
323
    ## default
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
351
352
353
    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

354
355
our $sympa_config;

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

=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
385
386
}

387
# deletes all the *.conf.bin files.
IKEDA Soji's avatar
IKEDA Soji committed
388
389
# No longer used.
#sub delete_binaries;
390

391
392
# Return a reference to an array containing the names of the robots on the
# server.
393
sub get_robots_list {
394
    $log->syslog('debug2', "Retrieving the list of robots on the server");
395
    my @robots_list;
396
    unless (opendir DIR, $Conf{'etc'}) {
397
        $log->syslog('err',
sikeda's avatar
sikeda committed
398
399
            'Unable to open directory %s for virtual robots config',
            $Conf{'etc'});
400
401
402
403
404
405
406
407
408
409
410
411
        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;
}

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

## fetch the value from parameter $label of robot $robot from conf_table
426
sub get_db_conf {
427
428
429
    my $robot = shift;
    my $label = shift;

430
431
432
433
434
    # 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 = '*' }

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

454
    my $value = $sth->fetchrow;
455

456
    $sth->finish();
457
    return $value;
458
459
460
}

## store the value from parameter $label of robot $robot from conf_table
461
sub set_robot_conf {
462
463
464
465
    my $robot = shift;
    my $label = shift;
    my $value = shift;

466
    $log->syslog('info', 'Set config for robot %s, %s="%s"',
467
        $robot, $label, $value);
468

469
470
471
472
473
474
475
476
    # 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 = '*';
    }

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

496
497
    my $count = $sth->fetchrow;
    $sth->finish();
498

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

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

543
    my @conf_parameters = @Sympa::ConfDef::params;
544
545

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

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

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

561
        my $config;
562
        if (my $result_of_config_loading = _load_config_file_to_hash(
563
                {         'path_to_config_file' => $Conf{'etc'} . '/'
564
565
566
567
568
                        . $robot
                        . '/robot.conf'
                }
            )
            ) {
569
570
            $config = $result_of_config_loading->{'config'};
        }
sikeda's avatar
sikeda committed
571
        _remove_unvalid_robot_entry($config);
572
573
574
575
576
577
578
579
580
581
582
583
584
585

        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'}})
                    ) {
                    Conf::set_robot_conf(
                        $robot,
                        $conf_parameters[$i]->{'name'},
                        $config->{$conf_parameters[$i]->{'name'}}
                    );
                }
586
587
588
            }
        }
    }
589
    closedir(DIR);
590
591

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

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

    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]
            );
        }
611
612
613
    }
}

614
615
616
## Check required files and create them if required
sub checkfiles_as_root {

617
    my $config_err = 0;
sympa-authors's avatar
   
sympa-authors committed
618

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

631
632
633
634
635
        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;
636
        $log->syslog(
637
638
639
640
641
            'notice',
            "Created missing file %s",
            $Conf{'sendmail_aliases'}
        );
        unless (
642
            Sympa::Tools::File::set_file_rights(
643
644
645
646
647
648
                file  => $Conf{'sendmail_aliases'},
                user  => Sympa::Constants::USER,
                group => Sympa::Constants::GROUP,
                mode  => 0644,
            )
            ) {
649
            $log->syslog('err', 'Unable to set rights on %s',
650
651
652
                $Conf{'db_name'});
            return undef;
        }
sympa-authors's avatar
   
sympa-authors committed
653
654
    }

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

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

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

680
    return 1;
681
682
}

sikeda's avatar
sikeda committed
683
684
685
686
687
688
689
690
691
692
## 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) {
693
            $log->syslog('err', 'Unable to open %s: %m', $version_file);
sikeda's avatar
sikeda committed
694
695
696
697
698
699
700
701
702
703
704
705
706
707
            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) {
708
        $log->syslog('err',
sikeda's avatar
sikeda committed
709
710
711
712
713
714
715
716
            "Data structure (%s) is not uptodate for current release (%s)",
            $data_structure_version, Sympa::Constants::VERSION);
        return 0;
    }

    return 1;
}

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
756
757
758
# 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
759
760
761
## Check a few files
sub checkfiles {
    my $config_err = 0;
762

763
    foreach my $p (qw(sendmail antivirus_path)) {
764
765
766
        next unless $Conf{$p};

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

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

795
796
797
798
799
800
801
    # 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';
802
    my %dirs = (Sympa::Constants::PIDDIR() => 'PID directory');
803

804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
    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;
        }
820
821
    }

822
823
824
825
826
827
828
829
830
    # 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 {
            chmod 0775, $pictures_dir;  # set masked bits.
831

832
833
834
835
836
837
838
839
840
841
            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;
842
843
844
            }
        }
    }
845

846
847
    #update_css();

root's avatar
root committed
848
849
850
851
    return undef if ($config_err);
    return 1;
}

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

859
860
    ## Main host
    return 1 if ($robot eq $Conf{'domain'});
861

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

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

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

888
889
890
891
892
893
894
895
896
    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'}) {
897
        return undef;
898
899
900
    }

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

904
        return $sso;
905
    }
906

907
908
909
910
911
    return undef;
}

##########################################
## Low level subs. Not supposed to be called from other modules.
912
913
914
##########################################

sub _load_auth {
915
916

    my $robot         = shift;
917
918
    my $is_main_robot = shift;
    # find appropriate auth.conf file
919
920
    my $config_file =
        _get_config_file_name({'robot' => $robot, 'file' => "auth.conf"});
921
    $log->syslog('debug', '(%s)', $config_file);
922

923
    $robot ||= $Conf{'domain'};
924
    my $line_num   = 0;
925
926
    my $config_err = 0;
    my @paragraphs;
927
    my %result;
928
929
930
931
932
933
934
935
936
937
938
939
940
    my $current_paragraph;

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

        '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
975
976
977
978
979
980
            'host'          => '[\w\.\-]+(:\d+)?(\s*,\s*[\w\.\-]+(:\d+)?)*',
            'bind_dn'       => '.+',
            'bind_password' => '.+',
            'timeout'       => '\d+',
            'suffix'        => '.+',
            'scope'         => 'base|one|sub',
981
            'get_email_by_uid_filter' => '.+',
982
            'email_attribute'         => Sympa::Regexps::ldap_attrdesc(),
sikeda's avatar
sikeda committed
983
984
985
            'use_tls'                 => 'starttls|ldaps|none',
            'use_ssl'                 => '1',                     # Obsoleted
            'use_start_tls'           => '1',                     # Obsoleted
986
987
            'ssl_version' => 'sslv2/3|sslv2|sslv3|tlsv1|tlsv1_1|tlsv1_2',
            'ssl_ciphers' => '[\w:]+',
sikeda's avatar
sikeda committed
988
989
990
991
992
            'ssl_cert'    => '.+',
            'ssl_key'     => '.+',
            'ca_verify'   => '\w+',
            'ca_path'     => '.+',
            'ca_file'     => '.+',
993
994
995
996
997
998
999
1000
        },
        'generic_sso' => {
            'service_name'                => '.+',
            'service_id'                  => '\S+',
            'http_header_prefix'          => '\w+',
            'http_header_list'            => '[\w\.\-\,]+',
            'email_http_header'           => '\w+',
            'http_header_value_separator' => '.+',
For faster browsing, not all history is shown. View entire blame