sympa.pl.in 48.5 KB
Newer Older
1
#!--PERL--
2
3
4
5
# -*- indent-tabs-mode: nil; -*-
# vim:ft=perl:et:sw=4
# $Id$

6
# Sympa - SYsteme de Multi-Postage Automatique
7
8
9
10
#
# Copyright (c) 1997, 1998, 1999 Institut Pasteur & Christophe Wolfhugel
# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites
11
# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER
12
13
# Copyright 2017, 2018 The Sympa Community. See the AUTHORS.md file at the
# top-level directory of this distribution and at
14
# <https://github.com/sympa-community/sympa.git>.
15
16
17
18
19
20
21
22
23
24
25
26
#
# 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
27
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
28

29
use lib split(/:/, $ENV{SYMPALIB} || ''), '--modulesdir--';
30
31
use strict;
use warnings;
32
use Digest::MD5;
33
use English qw(-no_match_vars);
34
35
use Fcntl qw();
use File::Basename qw();
sikeda's avatar
sikeda committed
36
use File::Copy qw();
37
use File::Path qw();
38
use Getopt::Long;
39
use Pod::Usage;
sikeda's avatar
sikeda committed
40
use POSIX qw();
root's avatar
root committed
41

IKEDA Soji's avatar
IKEDA Soji committed
42
use Sympa;
root's avatar
root committed
43
use Conf;
44
use Sympa::Config_XML;
45
use Sympa::Constants;
sikeda's avatar
sikeda committed
46
use Sympa::DatabaseManager;
47
use Sympa::Family;
48
use Sympa::Language;
49
use Sympa::List;
50
use Sympa::Log;
51
use Sympa::Mailer;
52
use Sympa::Spindle::ProcessDigest;
53
use Sympa::Spindle::ProcessRequest;
IKEDA Soji's avatar
IKEDA Soji committed
54
use Sympa::Template;
55
use Sympa::Tools::Data;
sikeda's avatar
sikeda committed
56
use Sympa::Upgrade;
57

58
## Init random engine
59
srand(time());
60

61
# Check options.
62
my %options;
63
64
unless (
    GetOptions(
Luc Didry's avatar
Luc Didry committed
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
        \%main::options,                'dump=s',
        'debug|d',                      'log_level=s',
        'config|f=s',                   'lang|l=s',
        'mail|m',                       'help|h',
        'version|v',                    'import=s',
        'make_alias_file',              'lowercase',
        'sync_list_db',                 'md5_encode_password',
        'close_list=s',                 'rename_list=s',
        'copy_list=s',                  'new_listname=s',
        'new_listrobot=s',              'purge_list=s',
        'create_list',                  'instantiate_family=s',
        'robot=s',                      'add_list=s',
        'modify_list=s',                'close_family=s',
        'md5_digest=s',                 'change_user_email',
        'current_email=s',              'new_email=s',
        'input_file=s',                 'sync_include=s',
        'upgrade',                      'upgrade_shared',
        'from=s',                       'to=s',
        'reload_list_config',           'list=s',
        'quiet',                        'close_unknown',
        'test_database_message_buffer', 'conf_2_db',
        'export_list',                  'health_check',
        'send_digest',                  'keep_digest',
        'upgrade_config_location',      'role=s',
        'dump_users',                   'restore_users',
90
        'open_list=s',                  'show_pending_lists=s'
91
    )
Luc Didry's avatar
Luc Didry committed
92
) {
93
    pod2usage(-exitval => 1, -output => \*STDERR);
94
}
95
96
97
98
99
100
if ($main::options{'help'}) {
    pod2usage(0);
} elsif ($main::options{'version'}) {
    printf "Sympa %s\n", Sympa::Constants::VERSION;
    exit 0;
}
101
$Conf::sympa_config = $main::options{config};
102

103
if ($main::options{'debug'}) {
104
    $main::options{'log_level'} = 2 unless $main::options{'log_level'};
105
}
106

107
108
109
my $log = Sympa::Log->instance;
$log->{log_to_stderr} = 'notice,err'
    if $main::options{'upgrade'}
110
111
112
    || $main::options{'reload_list_config'}
    || $main::options{'test_database_message_buffer'}
    || $main::options{'conf_2_db'};
113

114
if ($main::options{'upgrade_config_location'}) {
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
    my $config_file = Conf::get_sympa_conf();

    if (-f $config_file) {
        printf "Sympa configuration already located at %s\n", $config_file;
        exit 0;
    }

    my ($file, $dir, $suffix) = File::Basename::fileparse($config_file);
    my $old_dir = $dir;
    $old_dir =~ s/sympa\///;

    # Try to create config path if it does not exist
    unless (-d $dir) {
        my $error;
        File::Path::make_path(
            $dir,
            {   mode  => 0755,
132
133
                owner => Sympa::Constants::USER(),
                group => Sympa::Constants::GROUP(),
134
135
136
137
138
139
140
141
142
143
144
145
146
                error => \$error
            }
        );
        if (@$error) {
            my $diag = pop @$error;
            my ($target, $error) = %$diag;
            die "Unable to create $target: $error";
        }
    }

    # Check ownership of config folder
    my @stat = stat($dir);
    my $user = (getpwuid $stat[4])[0];
147
148
149
150
    if ($user ne Sympa::Constants::USER()) {
        die sprintf
            "Config dir %s exists but is not owned by %s (owned by %s).\n",
            $dir, Sympa::Constants::USER(), $user;
151
152
153
    }

    # Check permissions on config folder
154
    if (($stat[2] & Fcntl::S_IRWXU()) != Fcntl::S_IRWXU()) {
155
156
157
158
159
160
161
162
163
164
165
        die
            "Config dir $dir exists, but sympa does not have rwx permissions on it";
    }

    # Move files from old location to new one
    opendir(my $dh, $old_dir) or die("Could not open $dir for reading");
    my @files = grep(/^(ww)?sympa\.conf.*$/, readdir($dh));
    closedir($dh);

    foreach my $file (@files) {
        unless (File::Copy::move("$old_dir/$file", "$dir/$file")) {
166
167
            die sprintf 'Could not move %s/%s to %s/%s: %s', $old_dir, $file,
                $dir, $file, $ERRNO;
168
169
170
        }
    }

171
    printf "Sympa configuration moved to %s\n", $dir;
172
    exit 0;
173
174
175
176
177
178
179
} elsif ($main::options{'health_check'}) {
    ## Health check

    ## Load configuration file. Ignoring database config for now: it avoids
    ## trying to load a database that could not exist yet.
    unless (Conf::load(Conf::get_sympa_conf(), 'no_db')) {
        #FIXME: force reload
180
        die sprintf
181
182
183
184
185
            "Configuration file %s has errors.\n",
            Conf::get_sympa_conf();
    }

    ## Open the syslog and say we're read out stuff.
186
    $log->openlog(
187
188
189
190
        $Conf::Conf{'syslog'},
        $Conf::Conf{'log_socket_type'},
        service => 'sympa/health_check'
    );
191
192
193

    ## Setting log_level using conf unless it is set by calling option
    if ($main::options{'log_level'}) {
194
195
        $log->{level} = $main::options{'log_level'};
        $log->syslog(
196
197
198
199
200
            'info',
            'Configuration file read, log level set using options: %s',
            $main::options{'log_level'}
        );
    } else {
201
202
        $log->{level} = $Conf::Conf{'log_level'};
        $log->syslog(
203
204
205
206
207
208
            'info',
            'Configuration file read, default log level %s',
            $Conf::Conf{'log_level'}
        );
    }

209
    if (Conf::cookie_changed()) {
210
211
212
213
214
        die sprintf
            'sympa.conf/cookie parameter has changed. You may have severe inconsitencies into password storage. Restore previous cookie or write some tool to re-encrypt password in database and check spools contents (look at %s/cookies.history file).',
            $Conf::Conf{'etc'};
    }

215
216
217
218
219
220
221
    ## Check if db_type is not the boilerplate one
    if ($Conf::Conf{'db_type'} eq '(You must define this parameter)') {
        die sprintf
            "Database type \"%s\" defined in sympa.conf is the boilerplate one and obviously incorrect. Verify db_xxx parameters in sympa.conf\n",
            $Conf::Conf{'db_type'};
    }

222
223
224
225
226
227
228
    ## Preliminary check of db_type
    unless ($Conf::Conf{'db_type'} and $Conf::Conf{'db_type'} =~ /\A\w+\z/) {
        die sprintf
            "Database type \"%s\" defined in sympa.conf seems incorrect. Verify db_xxx parameters in sympa.conf\n",
            $Conf::Conf{'db_type'};
    }

229
    ## Check database connectivity and probe database
sikeda's avatar
sikeda committed
230
    unless (Sympa::DatabaseManager::probe_db()) {
231
        die sprintf
232
            "Database %s defined in sympa.conf has not the right structure or is unreachable. Verify db_xxx parameters in sympa.conf\n",
233
234
235
236
237
            $Conf::Conf{'db_name'};
    }

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

    ## Change working directory.
    if (!chdir($Conf::Conf{'home'})) {
245
        printf STDERR "Can't chdir to %s: %s\n", $Conf::Conf{'home'}, $ERRNO;
246
247
248
249
250
251
252
253
254
255
        exit 1;
    }

    ## Check for several files.
    unless (Conf::checkfiles_as_root()) {
        printf STDERR "Missing files.\n";
        exit 1;
    }

    ## Check that the data structure is uptodate
sikeda's avatar
sikeda committed
256
    unless (Conf::data_structure_uptodate()) {
257
        printf STDOUT
258
259
260
            "Data structure was not updated; you should run sympa.pl --upgrade to run the upgrade process.\n";
    }

261
262
263
    exit 0;
}

264
my $default_lang;
serge.aumont's avatar
   
serge.aumont committed
265

266
my $language = Sympa::Language->instance;
267
my $mailer   = Sympa::Mailer->instance;
268

sikeda's avatar
sikeda committed
269
270
_load();

271
$log->openlog($Conf::Conf{'syslog'}, $Conf::Conf{'log_socket_type'});
sikeda's avatar
sikeda committed
272
273
274
275
276
277
278
279
280
281
282
283
284

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

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

## Check if the UID has correctly been set (useful on OS X)
unless (($GID == (getgrnam(Sympa::Constants::GROUP))[2])
    && ($UID == (getpwnam(Sympa::Constants::USER))[2])) {
285
286
    die
        "Failed to change process user ID and group ID. Note that on some OS Perl scripts can't change their real UID. In such circumstances Sympa should be run via sudo.\n";
sikeda's avatar
sikeda committed
287
}
288

sikeda's avatar
sikeda committed
289
290
# Sets the UMASK
umask(oct($Conf::Conf{'umask'}));
291

sikeda's avatar
sikeda committed
292
## Most initializations have now been done.
293
$log->syslog('notice', 'Sympa %s Started', Sympa::Constants::VERSION());
root's avatar
root committed
294

sikeda's avatar
sikeda committed
295
296
# Check for several files.
#FIXME: This would be done in --health_check mode.
297
298
299
unless (Conf::checkfiles()) {
    die "Missing files.\n";
    ## No return.
sikeda's avatar
sikeda committed
300
}
root's avatar
root committed
301

302
# Daemon called for dumping subscribers list
303
if ($main::options{'dump'} or $main::options{'dump_users'}) {
304
305
306
    my $all_lists;

    # Compat. for old style "--dump=LIST".
307
    my $list_id = $main::options{'dump'} || $main::options{'list'};
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326

    if (defined $list_id and $list_id eq 'ALL') {
        $all_lists =
            Sympa::List::get_lists('*', filter => [status => 'open']);
    } elsif (defined $list_id and length $list_id) {
        # The parameter is list ID and list have to be open.
        unless (0 < index $list_id, '@') {
            $log->syslog('err', 'Incorrect list address %s', $list_id);
            exit 1;
        }
        my $list = Sympa::List->new($list_id);
        unless (defined $list) {
            $log->syslog('err', 'Unknown list %s', $list_id);
            exit 1;
        }
        unless ($list->{'admin'}{'status'} eq 'open') {
            $log->syslog('err', 'List is not open: %s', $list);
            exit 1;
        }
327

328
        $all_lists = [$list];
329
    } else {
330
331
332
        $log->syslog('err', 'No lists specified');
        exit 1;
    }
333

334
335
336
337
338
339
340
341
342
343
    my @roles = qw(member);
    if ($main::options{'role'}) {
        my %roles = map { ($_ => 1) }
            ($main::options{'role'} =~ /\b(member|owner|editor)\b/g);
        @roles = sort keys %roles;
        unless (@roles) {
            $log->syslog('err', 'Unknown role %s', $main::options{'role'});
            exit 1;
        }
    }
344

345
346
    foreach my $list (@$all_lists) {
        foreach my $role (@roles) {
347
            unless ($list->dump_users($role)) {
348
349
350
351
352
353
                printf STDERR "%s: Could not dump list users (%s)\n",
                    $list->get_id, $role;
            } else {
                printf STDERR "%s: Dumped list users (%s)\n",
                    $list->get_id, $role;
            }
354
        }
355
    }
356

357
    exit 0;
358
} elsif ($main::options{'restore_users'}) {
359
360
361
362
363
364
365
366
367
368
369
370
371
372
    my $all_lists;

    my $list_id = $main::options{'list'};

    if (defined $list_id and $list_id eq 'ALL') {
        $all_lists =
            Sympa::List::get_lists('*', filter => [status => 'open']);
    } elsif (defined $list_id and length $list_id) {
        # The parameter is list ID and list have to be open.
        unless (0 < index $list_id, '@') {
            $log->syslog('err', 'Incorrect list address %s', $list_id);
            exit 1;
        }
        my $list = Sympa::List->new($list_id);
373
        unless (defined $list) {
374
375
376
377
378
379
380
381
382
383
384
385
386
            $log->syslog('err', 'Unknown list %s', $list_id);
            exit 1;
        }
        unless ($list->{'admin'}{'status'} eq 'open') {
            $log->syslog('err', 'List is not open: %s', $list);
            exit 1;
        }

        $all_lists = [$list];
    } else {
        $log->syslog('err', 'No lists specified');
        exit 1;
    }
387

388
389
390
391
392
393
394
395
    my @roles = qw(member);
    if ($main::options{'role'}) {
        my %roles = map { ($_ => 1) }
            ($main::options{'role'} =~ /\b(member|owner|editor)\b/g);
        @roles = sort keys %roles;
        unless (@roles) {
            $log->syslog('err', 'Unknown role %s', $main::options{'role'});
            exit 1;
396
        }
397
    }
398

399
    foreach my $list (@$all_lists) {
400
        foreach my $role (@roles) {
401
402
            unless ($list->restore_users($role)) {
                printf STDERR "%s: Could not restore list users (%s)\n",
403
404
                    $list->get_id, $role;
            } else {
405
                printf STDERR "%s: Restored list users (%s)\n",
406
407
                    $list->get_id, $role;
            }
408
        }
409
    }
410

411
412
413
414
415
416
417
    exit 0;
} elsif ($main::options{'make_alias_file'}) {
    my $robots = $main::options{'robot'} || '*';
    my @robots;
    if ($robots eq '*') {
        @robots = Sympa::List::get_robots();
    } else {
Sympa authors's avatar
Sympa authors committed
418
        for my $name (split /[\s,]+/, $robots) {
419
420
421
422
423
424
425
            next unless length($name);
            if (Conf::valid_robot($name)) {
                push @robots, $name;
            } else {
                printf STDERR "Invalid robot %s\n", $name;
            }
        }
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
    }
    exit 0 unless @robots;

    # There may be multiple aliases files.  Give each of them suffixed
    # name.
    my ($basename, %robots_of, %sympa_aliases);
    $basename = sprintf '%s/sympa_aliases.%s', $Conf::Conf{'tmpdir'}, $PID;

    foreach my $robot (@robots) {
        my $file = Conf::get_robot_conf($robot, 'sendmail_aliases');
        $robots_of{$file} ||= [];
        push @{$robots_of{$file}}, $robot;
    }
    if (1 < scalar(keys %robots_of)) {
        my $i = 0;
        %sympa_aliases = map {
            $i++;
            map { $_ => sprintf('%s.%03d', $basename, $i) } @{$robots_of{$_}}
        } sort keys %robots_of;
    } else {
        %sympa_aliases = map { $_ => $basename } @robots;
    }
448

449
450
451
452
453
454
    # Create files.
    foreach my $sympa_aliases (values %sympa_aliases) {
        my $fh;
        unless (open $fh, '>', $sympa_aliases) {    # truncate if exists
            printf STDERR "Unable to create %s: %s\n", $sympa_aliases, $ERRNO;
            exit 1;
455
        }
456
457
        close $fh;
    }
458

459
460
461
462
463
    # Write files.
    foreach my $robot (sort @robots) {
        my $all_lists     = Sympa::List::get_lists($robot);
        my $alias_manager = Conf::get_robot_conf($robot, 'alias_manager');
        my $sympa_aliases = $sympa_aliases{$robot};
464

465
466
467
468
        my $fh;
        unless (open $fh, '>>', $sympa_aliases) {    # append
            printf STDERR "Unable to create %s: %s\n", $sympa_aliases, $ERRNO;
            exit 1;
469
        }
470
471
472
473
474
        printf $fh "#\n#\tAliases for all Sympa lists open on %s\n#\n",
            $robot;
        close $fh;
        foreach my $list (@{$all_lists || []}) {
            next unless $list->{'admin'}{'status'} eq 'open';
475

476
477
            system($alias_manager, 'add', $list->{'name'}, $list->{'domain'},
                $sympa_aliases);
478
        }
479
    }
salaun's avatar
salaun committed
480

481
482
483
484
485
486
487
488
489
490
491
492
    if (1 < scalar(keys %robots_of)) {
        printf
            "Sympa aliases files %s.??? were made.  You probably need to install them in your SMTP engine.\n",
            $basename;
    } else {
        printf
            "Sympa aliases file %s was made.  You probably need to install it in your SMTP engine.\n",
            $basename;
    }
    exit 0;
} elsif ($main::options{'md5_digest'}) {
    my $md5 = Digest::MD5::md5_hex($main::options{'md5_digest'});
493
    printf "md5 digest : %s \n", $md5;
494

495
496
    exit 0;
} elsif ($main::options{'import'}) {
497
    #FIXME The parameter should be a list address.
498
    unless ($main::options{'import'} =~ /\@/) {
499
500
        printf STDERR "Incorrect list address %s\n", $main::options{'import'};
        exit 1;
501
    }
502
    my $list;
503
    unless ($list = Sympa::List->new($main::options{'import'})) {
504
        printf STDERR "Unknown list name %s\n", $main::options{'import'};
505
506
        exit 1;
    }
507
508
509
510
511
512
513
514
515
516
517
518
519
    my $dump = do { local $RS; <STDIN> };

    my $spindle = Sympa::Spindle::ProcessRequest->new(
        context          => $list,
        action           => 'import',
        dump             => $dump,
        force            => 1,
        sender           => Sympa::get_address($list, 'listmaster'),
        scenario_context => {skip => 1},
    );
    unless ($spindle and $spindle->spin) {
        printf STDERR "Failed to add email addresses to %s\n", $list;
        exit 1;
520
    }
521
522
523
    my $status = _report($spindle);
    printf STDERR "Total imported subscribers: %d\n",
        scalar(grep { $_->[1] eq 'notice' and $_->[2] eq 'now_subscriber' }
Luc Didry's avatar
Luc Didry committed
524
            @{$spindle->{stash} || []});
525
    exit($status ? 0 : 1);
serge.aumont's avatar
   
serge.aumont committed
526

527
} elsif ($main::options{'md5_encode_password'}) {
528
    print STDERR "Obsoleted.  Use upgrade_sympa_password.pl.\n";
529

530
531
532
    exit 0;
} elsif ($main::options{'lowercase'}) {
    print STDERR "Working on user_table...\n";
533
    my $total = _lowercase_field('user_table', 'email_user');
534

535
536
    if (defined $total) {
        print STDERR "Working on subscriber_table...\n";
537
538
        my $total_sub =
            _lowercase_field('subscriber_table', 'user_subscriber');
539
540
        if (defined $total_sub) {
            $total += $total_sub;
541
        }
542
    }
543

544
545
546
547
    unless (defined $total) {
        print STDERR "Could not work on dabatase.\n";
        exit 1;
    }
548

549
    printf STDERR "Total lowercased rows: %d\n", $total;
sympa-authors's avatar
sympa-authors committed
550

551
552
    exit 0;
} elsif ($main::options{'close_list'}) {
553
554
555
556
557
    my ($listname, $robot_id) = split /\@/, $main::options{'close_list'}, 2;
    my $current_list = Sympa::List->new($listname, $robot_id);
    unless ($current_list) {
        printf STDERR "Incorrect list name %s.\n",
            $main::options{'close_list'};
558
559
        exit 1;
    }
sympa-authors's avatar
sympa-authors committed
560

561
562
563
564
565
566
567
568
569
570
    my $spindle = Sympa::Spindle::ProcessRequest->new(
        context          => $robot_id,
        action           => 'close_list',
        current_list     => $current_list,
        sender           => Sympa::get_address($robot_id, 'listmaster'),
        scenario_context => {skip => 1},
    );
    unless ($spindle and $spindle->spin and _report($spindle)) {
        printf STDERR "Could not close list %s\n", $current_list->get_id;
        exit 1;
571
572
    }
    exit 0;
573

574
} elsif ($main::options{'change_user_email'}) {
IKEDA Soji's avatar
IKEDA Soji committed
575
    unless ($main::options{'current_email'} and $main::options{'new_email'}) {
576
577
578
        print STDERR "Missing current_email or new_email parameter\n";
        exit 1;
    }
579

IKEDA Soji's avatar
IKEDA Soji committed
580
581
582
583
584
585
586
587
588
589
590
591
    my $spindle = Sympa::Spindle::ProcessRequest->new(
        context          => [Sympa::List::get_robots()],
        action           => 'move_user',
        current_email    => $main::options{'current_email'},
        email            => $main::options{'new_email'},
        sender           => Sympa::get_address('*', 'listmaster'),
        scenario_context => {skip => 1},
    );
    unless ($spindle and $spindle->spin and _report($spindle)) {
        printf STDERR "Failed to change user email address %s to %s\n",
            $main::options{'current_email'}, $main::options{'new_email'};
        exit 1;
592
593
    }
    exit 0;
IKEDA Soji's avatar
IKEDA Soji committed
594

595
} elsif ($main::options{'purge_list'}) {
596
597
598
    my ($listname, $robot_id) = split /\@/, $main::options{'purge_list'}, 2;
    my $current_list = Sympa::List->new($listname, $robot_id);
    unless ($current_list) {
Luc Didry's avatar
Luc Didry committed
599
600
        printf STDERR "Incorrect list name %s\n",
            $main::options{'purge_list'};
601
602
        exit 1;
    }
603

604
605
606
607
608
609
610
611
612
    my $spindle = Sympa::Spindle::ProcessRequest->new(
        context          => $robot_id,
        action           => 'close_list',
        current_list     => $current_list,
        mode             => 'purge',
        scenario_context => {skip => 1},
    );
    unless ($spindle and $spindle->spin and _report($spindle)) {
        printf STDERR "Could not purge list %s\n", $current_list->get_id;
613
        exit 1;
614
615
    }
    exit 0;
616

617
} elsif ($main::options{'rename_list'}) {
618
619
620
621
622
623
    my $current_list =
        Sympa::List->new(split(/\@/, $main::options{'rename_list'}, 2),
        {just_try => 1});
    unless ($current_list) {
        printf STDERR "Incorrect list name %s\n",
            $main::options{'rename_list'};
624
        exit 1;
625
    }
626

627
628
    my $listname = $main::options{'new_listname'};
    unless (defined $listname and length $listname) {
629
        print STDERR "Missing parameter new_listname\n";
630
        exit 1;
631
    }
632

633
634
635
636
637
    my $robot_id = $main::options{'new_listrobot'};
    unless (defined $robot_id) {
        $robot_id = $current_list->{'domain'};
    } else {
        unless (length $robot_id and Conf::valid_robot($robot_id)) {
638
            printf STDERR "Unknown robot \"%s\"\n", $robot_id;
639
640
            exit 1;
        }
641
    }
642

643
644
645
646
647
648
649
    my $spindle = Sympa::Spindle::ProcessRequest->new(
        context          => $robot_id,
        action           => 'move_list',
        current_list     => $current_list,
        listname         => $listname,
        sender           => Sympa::get_address($robot_id, 'listmaster'),
        scenario_context => {skip => 1},
650
    );
651
    unless ($spindle and $spindle->spin and _report($spindle)) {
652
        printf STDERR "Could not rename list %s to %s\@%s\n",
653
            $current_list->get_id, $listname, $robot_id;
654
655
656
        exit 1;
    }
    exit 0;
Luc Didry's avatar
Luc Didry committed
657

658
659
660
661
662
} elsif ($main::options{'copy_list'}) {
    my $current_list =
        Sympa::List->new(split(/\@/, $main::options{'copy_list'}, 2),
        {just_try => 1});
    unless ($current_list) {
Luc Didry's avatar
Luc Didry committed
663
        printf STDERR "Incorrect list name %s\n", $main::options{'copy_list'};
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
        exit 1;
    }

    my $listname = $main::options{'new_listname'};
    unless (defined $listname and length $listname) {
        print STDERR "Missing parameter new_listname\n";
        exit 1;
    }

    my $robot_id = $main::options{'new_listrobot'};
    unless (defined $robot_id) {
        $robot_id = $current_list->{'domain'};
    } else {
        unless (length $robot_id and Conf::valid_robot($robot_id)) {
            printf STDERR "Unknown robot \"%s\"\n", $robot_id;
            exit 1;
        }
    }

    my $spindle = Sympa::Spindle::ProcessRequest->new(
        context          => $robot_id,
        action           => 'move_list',
        current_list     => $current_list,
        listname         => $listname,
Luc Didry's avatar
Luc Didry committed
688
        mode             => 'copy',
689
690
691
692
693
694
695
696
697
        sender           => Sympa::get_address($robot_id, 'listmaster'),
        scenario_context => {skip => 1},
    );
    unless ($spindle and $spindle->spin and _report($spindle)) {
        printf STDERR "Could not copy list %s to %s\@%s\n",
            $current_list->get_id, $listname, $robot_id;
        exit 1;
    }
    exit 0;
698

699
} elsif ($main::options{'test_database_message_buffer'}) {
700
    print
701
702
703
704
705
706
707
708
709
710
711
712
713
        "Deprecated.  Size of messages no longer limited by database packet size.\n";
    exit 1;
} elsif ($main::options{'conf_2_db'}) {

    printf
        "Sympa is going to store %s in database conf_table. This operation do NOT remove original files\n",
        Conf::get_sympa_conf();
    if (Conf::conf_2_db()) {
        printf "Done";
    } else {
        printf "an error occur";
    }
    exit 1;
714

715
} elsif ($main::options{'create_list'}) {
716
    my $robot = $main::options{'robot'} || $Conf::Conf{'domain'};
717

718
719
720
721
    unless ($main::options{'input_file'}) {
        print STDERR "Error : missing 'input_file' parameter\n";
        exit 1;
    }
722
723
    my $fh;
    unless (open $fh, '<', $main::options{'input_file'}) {
724
        printf STDERR "Unable to open %s: %s\n", $main::options{'input_file'},
725
            $ERRNO;
726
727
        exit 1;
    }
728
    my $config = Sympa::Config_XML->new($fh);
729
    close $fh;
730
    unless (defined $config->createHash()) {
731
        print STDERR "Error in representation data with these XML data\n";
732
733
734
        exit 1;
    }
    my $hash = $config->getHash();
735
736
    # Compatibility: single topic on 6.2.24 or earlier.
    $hash->{config}{topics} ||= $hash->{config}{topic};
737
738
739
740
    # In old documentation "moderator" was single or multiple editors.
    my $mod = $hash->{config}{moderator};
    $hash->{config}{editor} ||=
        (ref $mod eq 'ARRAY') ? $mod : (ref $mod eq 'HASH') ? [$mod] : [];
741

742
743
744
745
746
747
748
749
750
751
    my $spindle = Sympa::Spindle::ProcessRequest->new(
        context    => $robot,
        action     => 'create_list',
        listname   => $hash->{config}{listname},
        parameters => {%{$hash->{config} || {}}, template => $hash->{type}},
        sender     => Sympa::get_address($robot, 'listmaster'),
        scenario_context => {skip => 1}
    );
    unless ($spindle and $spindle->spin and _report($spindle)) {
        printf STDERR "Could not create list %s\n", $hash->{config}{listname};
752
753
        exit 1;
    }
754
    exit 0;
755

756
} elsif ($main::options{'instantiate_family'}) {
757
    my $robot = $main::options{'robot'} || $Conf::Conf{'domain'};
758

759
760
761
762
763
764
765
    my $family_name;
    unless ($family_name = $main::options{'instantiate_family'}) {
        print STDERR "Error : missing family parameter\n";
        exit 1;
    }
    my $family;
    unless ($family = Sympa::Family->new($family_name, $robot)) {
766
767
768
        printf STDERR
            "The family %s does not exist, impossible instantiation\n",
            $family_name;
769
770
        exit 1;
    }
771

772
773
774
775
    unless ($main::options{'input_file'}) {
        print STDERR "Error : missing input_file parameter\n";
        exit 1;
    }
776

777
    unless (-r $main::options{'input_file'}) {
Luc Didry's avatar
Luc Didry committed
778
779
        printf STDERR "Unable to read %s file\n",
            $main::options{'input_file'};
780
781
        exit 1;
    }
782

783
784
785
    unless (
        $family->instantiate(
            $main::options{'input_file'},
786
787
            close_unknown => $main::options{'close_unknown'},
            quiet         => $main::options{quiet},
788
        )
Luc Didry's avatar
Luc Didry committed
789
    ) {
790
791
792
        print STDERR "\nImpossible family instantiation : action stopped \n";
        exit 1;
    }
793

794
795
796
    my %result;
    my $err = $family->get_instantiation_results(\%result);
    close INFILE;
797

798
799
800
801
802
803
804
    unless ($main::options{'quiet'}) {
        print STDOUT "@{$result{'info'}}";
        print STDOUT "@{$result{'warn'}}";
    }
    if ($err) {
        print STDERR "@{$result{'errors'}}";
    }
805

806
807
    exit 0;
} elsif ($main::options{'add_list'}) {
808
    my $robot = $main::options{'robot'} || $Conf::Conf{'domain'};
809

810
811
812
813
814
    my $family_name;
    unless ($family_name = $main::options{'add_list'}) {
        print STDERR "Error : missing family parameter\n";
        exit 1;
    }
815

816
817
    my $family;
    unless ($family = Sympa::Family->new($family_name, $robot)) {
818
        printf STDERR
819
820
            "The family %s does not exist, impossible to add a list\n",
            $family_name;
821
822
        exit 1;
    }
823

824
825
826
827
    unless ($main::options{'input_file'}) {
        print STDERR "Error : missing 'input_file' parameter\n";
        exit 1;
    }
828
829
830
    my $fh;
    unless (open $fh, '<', $main::options{'input_file'}) {
        printf STDERR "\n Impossible to open input file  : %s\n", $ERRNO;
831
832
        exit 1;
    }
833
834
835
836
837
    # get list data
    my $config = Sympa::Config_XML->new($fh);
    close $fh;
    unless (defined $config->createHash()) {
        print STDERR "Error in representation data with these XML data\n";
838
839
        exit 1;
    }
840
    my $hash = $config->getHash();
841
842
    # Compatibility: single topic on 6.2.24 or earlier.
    $hash->{config}{topics} ||= $hash->{config}{topic};
843
844
845
846
    # In old documentation "moderator" was single or multiple editors.
    my $mod = $hash->{config}{moderator};
    $hash->{config}{editor} ||=
        (ref $mod eq 'ARRAY') ? $mod : (ref $mod eq 'HASH') ? [$mod] : [];
847

848
    my $spindle = Sympa::Spindle::ProcessRequest->new(
Luc Didry's avatar
Luc Didry committed
849
850
851
852
853
        context          => $family,
        action           => 'create_automatic_list',
        listname         => $hash->{config}{listname},
        parameters       => $hash->{config},
        sender           => Sympa::get_address($family, 'listmaster'),
854
855
856
        scenario_context => {skip => 1},
    );
    unless ($spindle and $spindle->spin and _report($spindle)) {
857
        printf STDERR "Impossible to add a list %s to the family %s\n",
858
            $hash->{config}{listname}, $family_name;
859
860
        exit 1;
    }
861

Luc Didry's avatar
Luc Didry committed
862
863
864
865
866
867
868
869
    my $list =
        Sympa::List->new($hash->{config}{listname}, $family->{'robot'});
    unless (
        File::Copy::copy(
            $main::options{'input_file'},
            $list->{'dir'} . '/instance.xml'
        )
    ) {
870
        $list->set_status_error_config('error_copy_file', $family->{'name'});
Luc Didry's avatar
Luc Didry committed
871
872
        print STDERR
            "Impossible to copy the XML file in the list directory, the list is set in status error_config.\n";
873
    }
874

875
    exit 0;
876

877
} elsif ($main::options{'sync_include'}) {
878

879
    my $list = Sympa::List->new($main::options{'sync_include'});
880

881
    unless (defined $list) {
Luc Didry's avatar
Luc Didry committed
882
883
        printf STDERR "Incorrect list name %s\n",
            $main::options{'sync_include'};
884
885
        exit 1;
    }
886

887
    unless ($list->has_include_data_sources) {
888
889
890
891
892
        printf STDERR "No data sources defined for inclusion into list %s.\n",
            $list->get_id;
        exit 1;
    }

893
894
895
896
    unless (defined $list->sync_include()) {
        print STDERR "Failed to synchronize list members\n";
        exit 1;
    }
897

898
    printf "Members of list %s have been successfully updated.\n",
899
        $list->get_id;
900
901
902
    exit 0;
## Migration from one version to another
} elsif ($main::options{'upgrade'}) {
903

904
    $log->syslog('notice', "Upgrade process...");
905

906
907
    $main::options{'from'} ||= Sympa::Upgrade::get_previous_version();
    $main::options{'to'}   ||= Sympa::Constants::VERSION;
908

909
    if ($main::options{'from'} eq $main::options{'to'}) {
910
        $log->syslog('notice', 'Current version: %s; no upgrade is required',
911
912
913
            $main::options{'to'});
        exit 0;
    } else {
914
        $log->syslog('notice', "Upgrading from %s to %s...",
915
916
            $main::options{'from'}, $main::options{'to'});
    }
917

918
919
920
    unless (
        Sympa::Upgrade::upgrade($main::options{'from'}, $main::options{'to'}))
    {
921
        $log->syslog('err', "Migration from %s to %s failed",
922
923
924
            $main::options{'from'}, $main::options{'to'});
        exit 1;
    }
925

926
    $log->syslog('notice', 'Upgrade process finished');
927
    Sympa::Upgrade::update_version();
928

929
    exit 0;
930

931
} elsif ($main::options{'upgrade_shared'}) {
932
    print STDERR "Obsoleted.  Use upgrade_shared_repository.pl.\n";
933

934
935
936
    exit 0;
} elsif ($main::options{'reload_list_config'}) {
    if ($main::options{'list'}) {
937
        $log->syslog('notice', 'Loading list %s...', $main::options{'list'});
938
939
        my $list =
            Sympa::List->new($main::options{'list'}, '',
940
            {'reload_config' => 1, 'force_sync_admin' => 1});
941
        unless (defined $list) {
Luc Didry's avatar
Luc Didry committed
942
943
            printf STDERR "Error : incorrect list name '%s'\n",
                $main::options{'list'};
944
945
946
            exit 1;
        }
    } else {
947
        $log->syslog('notice', "Loading ALL lists...");
Luc Didry's avatar
Luc Didry committed
948
949
950
951
952
        my $all_lists = Sympa::List::get_lists(
            '*',
            'reload_config'    => 1,
            'force_sync_admin' => 1
        );
953
    }
954
    $log->syslog('notice', '...Done.');
955

956
957
    exit 0;
}
958

959
960
##########################################
elsif ($main::options{'modify_list'}) {
961

962
    my $robot = $main::options{'robot'} || $Conf::Conf{'domain'};
963

964
965
966
967
968
    my $family_name;
    unless ($family_name = $main::options{'modify_list'}) {
        print STDERR "Error : missing family parameter\n";
        exit 1;
    }
969

970
971
    print STDOUT
        "\n************************************************************\n";
972

973
974
    my $family;
    unless ($family = Sympa::Family->new($family_name, $robot)) {
975
976
977
        printf STDERR
            "The family %s does not exist, impossible to modify the list.\n",
            $family_name;
978
979
        exit 1;
    }
980

981
982
983
984
    unless ($main::options{'input_file'}) {
        print STDERR "Error : missing input_file parameter\n";
        exit 1;
    }
985

986
    unless (open INFILE, $main::options{'input_file'}) {
Luc Didry's avatar
Luc Didry committed
987
988
        printf STDERR "Unable to open %s file\n",
            $main::options{'input_file'};
989
990
        exit 1;
    }
991