sympa.pl.in 65.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, 2019, 2020, 2021 The Sympa Community. See the
# AUTHORS.md file at the top-level directory of this distribution and at
14
# <https://github.com/sympa-community/sympa.git>.
15
16
17
18
19
20
21
22
23
24
25
26
#
# 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;
43
use Sympa::Aliases;
root's avatar
root committed
44
use Conf;
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::Message;
53
use Sympa::Spindle::ProcessDigest;
54
use Sympa::Spindle::ProcessRequest;
55
use Sympa::Spool::Archive;
IKEDA Soji's avatar
IKEDA Soji committed
56
use Sympa::Template;
57
use Sympa::Tools::Data;
sikeda's avatar
sikeda committed
58
use Sympa::Upgrade;
59

60
## Init random engine
61
srand(time());
62

63
# Check options.
64
my %options;
65
66
unless (
    GetOptions(
Luc Didry's avatar
Luc Didry committed
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
        \%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',
92
        'open_list=s',                  'show_pending_lists=s',
93
        'notify',                       'rebuildarc=s'
94
    )
Luc Didry's avatar
Luc Didry committed
95
) {
96
    pod2usage(-exitval => 1, -output => \*STDERR);
97
}
98
99
100
101
102
103
if ($main::options{'help'}) {
    pod2usage(0);
} elsif ($main::options{'version'}) {
    printf "Sympa %s\n", Sympa::Constants::VERSION;
    exit 0;
}
104
$Conf::sympa_config = $main::options{config};
105

106
if ($main::options{'debug'}) {
107
    $main::options{'log_level'} = 2 unless $main::options{'log_level'};
108
}
109

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

117
if ($main::options{'upgrade_config_location'}) {
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
    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,
135
136
                owner => Sympa::Constants::USER(),
                group => Sympa::Constants::GROUP(),
137
138
139
140
141
142
143
144
145
146
147
148
149
                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];
150
151
152
153
    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;
154
155
156
    }

    # Check permissions on config folder
157
    if (($stat[2] & Fcntl::S_IRWXU()) != Fcntl::S_IRWXU()) {
158
159
160
161
162
163
164
165
166
167
168
        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")) {
169
170
            die sprintf 'Could not move %s/%s to %s/%s: %s', $old_dir, $file,
                $dir, $file, $ERRNO;
171
172
173
        }
    }

174
    printf "Sympa configuration moved to %s\n", $dir;
175
    exit 0;
176
177
178
179
180
181
182
} 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
183
        die sprintf
184
185
186
187
188
            "Configuration file %s has errors.\n",
            Conf::get_sympa_conf();
    }

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

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

212
213
214
215
216
217
218
    ## 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'};
    }

219
220
221
222
223
224
225
    ## 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'};
    }

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

    ## Now trying to load full config (including database)
    unless (Conf::load()) {    #FIXME: load Site, then robot cache
235
        die sprintf
236
237
238
239
240
241
            "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'})) {
242
        printf STDERR "Can't chdir to %s: %s\n", $Conf::Conf{'home'}, $ERRNO;
243
244
245
246
247
248
249
250
251
252
        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
253
    unless (Conf::data_structure_uptodate()) {
254
        printf STDOUT
255
256
257
            "Data structure was not updated; you should run sympa.pl --upgrade to run the upgrade process.\n";
    }

258
259
260
    exit 0;
}

261
my $default_lang;
serge.aumont's avatar
   
serge.aumont committed
262

263
my $language = Sympa::Language->instance;
264
my $mailer   = Sympa::Mailer->instance;
265

sikeda's avatar
sikeda committed
266
267
_load();

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

# 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])) {
282
283
    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
284
}
285

sikeda's avatar
sikeda committed
286
287
# Sets the UMASK
umask(oct($Conf::Conf{'umask'}));
288

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

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

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

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

    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;
        }
324

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

331
332
333
334
335
336
337
338
339
340
    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;
        }
    }
341

342
343
    foreach my $list (@$all_lists) {
        foreach my $role (@roles) {
344
            unless ($list->dump_users($role)) {
345
346
347
348
349
350
                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;
            }
351
        }
352
    }
353

354
    exit 0;
355
} elsif ($main::options{'restore_users'}) {
356
357
358
359
360
361
362
363
364
365
366
367
368
369
    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);
370
        unless (defined $list) {
371
372
373
374
375
376
377
378
379
380
381
382
383
            $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;
    }
384

385
386
387
388
389
390
391
392
    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;
393
        }
394
    }
395

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

408
409
410
411
412
413
414
    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
415
        for my $name (split /[\s,]+/, $robots) {
416
417
418
419
420
421
422
            next unless length($name);
            if (Conf::valid_robot($name)) {
                push @robots, $name;
            } else {
                printf STDERR "Invalid robot %s\n", $name;
            }
        }
423
424
425
426
427
428
429
430
431
432
    }
    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');
433
434
        next if $file eq 'none';

435
436
437
438
439
440
441
442
443
444
445
446
        $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;
    }
447

448
449
450
451
452
453
    # 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;
454
        }
455
456
        close $fh;
    }
457

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

463
464
465
466
467
        my $aliases =
            Sympa::Aliases->new($alias_manager, file => $sympa_aliases);
        next
            unless $aliases and $aliases->isa('Sympa::Aliases::Template');

468
469
470
471
        my $fh;
        unless (open $fh, '>>', $sympa_aliases) {    # append
            printf STDERR "Unable to create %s: %s\n", $sympa_aliases, $ERRNO;
            exit 1;
472
        }
473
474
475
        printf $fh "#\n#\tAliases for all Sympa lists open on %s\n#\n",
            $robot;
        close $fh;
476
477

        my $all_lists = Sympa::List::get_lists($robot);
478
479
        foreach my $list (@{$all_lists || []}) {
            next unless $list->{'admin'}{'status'} eq 'open';
480

481
            $aliases->add($list);
482
        }
483
    }
salaun's avatar
salaun committed
484

485
486
487
488
489
490
491
492
493
494
495
496
    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'});
497
    printf "md5 digest : %s \n", $md5;
498

499
500
    exit 0;
} elsif ($main::options{'import'}) {
501
    #FIXME The parameter should be a list address.
502
    unless ($main::options{'import'} =~ /\@/) {
503
504
        printf STDERR "Incorrect list address %s\n", $main::options{'import'};
        exit 1;
505
    }
506
    my $list;
507
    unless ($list = Sympa::List->new($main::options{'import'})) {
508
        printf STDERR "Unknown list name %s\n", $main::options{'import'};
509
510
        exit 1;
    }
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},
520
        quiet            => $main::options{quiet},
521
522
523
524
    );
    unless ($spindle and $spindle->spin) {
        printf STDERR "Failed to add email addresses to %s\n", $list;
        exit 1;
525
    }
526
527
528
    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
529
            @{$spindle->{stash} || []});
530
    exit($status ? 0 : 1);
serge.aumont's avatar
   
serge.aumont committed
531

532
} elsif ($main::options{'md5_encode_password'}) {
533
    print STDERR "Obsoleted.  Use upgrade_sympa_password.pl.\n";
534

535
536
537
    exit 0;
} elsif ($main::options{'lowercase'}) {
    print STDERR "Working on user_table...\n";
538
    my $total = _lowercase_field('user_table', 'email_user');
539

540
541
    if (defined $total) {
        print STDERR "Working on subscriber_table...\n";
542
543
        my $total_sub =
            _lowercase_field('subscriber_table', 'user_subscriber');
544
545
        if (defined $total_sub) {
            $total += $total_sub;
546
        }
547
    }
548

549
550
551
552
    unless (defined $total) {
        print STDERR "Could not work on dabatase.\n";
        exit 1;
    }
553

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

556
557
    exit 0;
} elsif ($main::options{'close_list'}) {
558
559
560
561
562
    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'};
563
564
        exit 1;
    }
sympa-authors's avatar
sympa-authors committed
565

566
567
568
569
570
571
572
573
574
575
    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;
576
577
    }
    exit 0;
578

579
} elsif ($main::options{'change_user_email'}) {
IKEDA Soji's avatar
IKEDA Soji committed
580
    unless ($main::options{'current_email'} and $main::options{'new_email'}) {
581
582
583
        print STDERR "Missing current_email or new_email parameter\n";
        exit 1;
    }
584

IKEDA Soji's avatar
IKEDA Soji committed
585
586
587
588
589
590
591
592
593
594
595
596
    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;
597
598
    }
    exit 0;
IKEDA Soji's avatar
IKEDA Soji committed
599

600
} elsif ($main::options{'purge_list'}) {
601
602
603
    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
604
605
        printf STDERR "Incorrect list name %s\n",
            $main::options{'purge_list'};
606
607
        exit 1;
    }
608

609
610
611
612
613
614
615
616
617
    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;
618
        exit 1;
619
620
    }
    exit 0;
621

622
} elsif ($main::options{'rename_list'}) {
623
624
625
626
627
628
    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'};
629
        exit 1;
630
    }
631

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

638
639
640
641
642
    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)) {
643
            printf STDERR "Unknown robot \"%s\"\n", $robot_id;
644
645
            exit 1;
        }
646
    }
647

648
649
650
651
652
653
654
    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},
655
    );
656
    unless ($spindle and $spindle->spin and _report($spindle)) {
657
        printf STDERR "Could not rename list %s to %s\@%s\n",
658
            $current_list->get_id, $listname, $robot_id;
659
660
661
        exit 1;
    }
    exit 0;
Luc Didry's avatar
Luc Didry committed
662

663
664
665
666
667
} 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
668
        printf STDERR "Incorrect list name %s\n", $main::options{'copy_list'};
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
        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
693
        mode             => 'copy',
694
695
696
697
698
699
700
701
702
        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;
703

704
} elsif ($main::options{'test_database_message_buffer'}) {
705
    print
706
707
708
709
710
711
712
713
714
715
716
717
718
        "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;
719

720
} elsif ($main::options{'create_list'}) {
721
    my $robot = $main::options{'robot'} || $Conf::Conf{'domain'};
722

723
724
725
726
    unless ($main::options{'input_file'}) {
        print STDERR "Error : missing 'input_file' parameter\n";
        exit 1;
    }
727

728
    my $spindle = Sympa::Spindle::ProcessRequest->new(
729
730
731
732
        context          => $robot,
        action           => 'create_list',
        parameters       => {file => $main::options{'input_file'}},
        sender           => Sympa::get_address($robot, 'listmaster'),
733
734
735
        scenario_context => {skip => 1}
    );
    unless ($spindle and $spindle->spin and _report($spindle)) {
736
        print STDERR "Could not create list\n";
737
738
        exit 1;
    }
739
    exit 0;
740

741
} elsif ($main::options{'instantiate_family'}) {
742
    my $robot = $main::options{'robot'} || $Conf::Conf{'domain'};
743

744
745
746
747
748
749
750
    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)) {
751
752
753
        printf STDERR
            "The family %s does not exist, impossible instantiation\n",
            $family_name;
754
755
        exit 1;
    }
756

757
758
759
760
    unless ($main::options{'input_file'}) {
        print STDERR "Error : missing input_file parameter\n";
        exit 1;
    }
761

762
    unless (-r $main::options{'input_file'}) {
Luc Didry's avatar
Luc Didry committed
763
764
        printf STDERR "Unable to read %s file\n",
            $main::options{'input_file'};
765
766
        exit 1;
    }
767

768
    unless (
769
770
        instantiate(
            $family,
771
            $main::options{'input_file'},
772
773
            close_unknown => $main::options{'close_unknown'},
            quiet         => $main::options{quiet},
774
        )
Luc Didry's avatar
Luc Didry committed
775
    ) {
776
777
778
        print STDERR "\nImpossible family instantiation : action stopped \n";
        exit 1;
    }
779

780
    my %result;
781
    my $err = get_instantiation_results($family, \%result);
782

783
784
785
786
    unless ($main::options{'quiet'}) {
        print STDOUT "@{$result{'info'}}";
        print STDOUT "@{$result{'warn'}}";
    }
787
    if ($err >= 0) {
788
        print STDERR "@{$result{'errors'}}";
IKEDA Soji's avatar
tidyall    
IKEDA Soji committed
789
        exit 1;
790
    }
791

792
793
    exit 0;
} elsif ($main::options{'add_list'}) {
794
    my $robot = $main::options{'robot'} || $Conf::Conf{'domain'};
795

796
797
798
799
800
    my $family_name;
    unless ($family_name = $main::options{'add_list'}) {
        print STDERR "Error : missing family parameter\n";
        exit 1;
    }
801

802
803
    my $family;
    unless ($family = Sympa::Family->new($family_name, $robot)) {
804
        printf STDERR
805
806
            "The family %s does not exist, impossible to add a list\n",
            $family_name;
807
808
        exit 1;
    }
809

810
811
812
813
    unless ($main::options{'input_file'}) {
        print STDERR "Error : missing 'input_file' parameter\n";
        exit 1;
    }
814

815
    my $spindle = Sympa::Spindle::ProcessRequest->new(
Luc Didry's avatar
Luc Didry committed
816
817
        context          => $family,
        action           => 'create_automatic_list',
818
        parameters       => {file => $main::options{'input_file'}},
Luc Didry's avatar
Luc Didry committed
819
        sender           => Sympa::get_address($family, 'listmaster'),
820
821
822
        scenario_context => {skip => 1},
    );
    unless ($spindle and $spindle->spin and _report($spindle)) {
823
824
        printf STDERR "Impossible to add a list to the family %s\n",
            $family_name;
825
826
        exit 1;
    }
827

828
    exit 0;
829

830
831
} elsif ($main::options{'sync_include'}) {
    my $list = Sympa::List->new($main::options{'sync_include'});
IKEDA Soji's avatar
IKEDA Soji committed
832
    my $role = $main::options{'role'} || 'member';    # Compat. <= 6.2.54
833

834
    unless (defined $list) {
Luc Didry's avatar
Luc Didry committed
835
836
        printf STDERR "Incorrect list name %s\n",
            $main::options{'sync_include'};
837
838
        exit 1;
    }
IKEDA Soji's avatar
IKEDA Soji committed
839
    unless (grep { $role eq $_ } qw(member owner editor)) {
840
841
842
        printf STDERR "Unknown role %s\n", $role;
        exit 1;
    }
843

IKEDA Soji's avatar
IKEDA Soji committed
844
845
846
847
848
849
850
851
852
853
    my $spindle = Sympa::Spindle::ProcessRequest->new(
        context          => $list,
        action           => 'include',
        role             => $role,
        sender           => Sympa::get_address($list, 'listmaster'),
        scenario_context => {skip => 1},
    );
    unless ($spindle and $spindle->spin and _report($spindle)) {
        printf STDERR "Could not sync role %s of list %s with data sources\n",
            $role, $list->get_id;
854
855
856
857
858
        exit 1;
    }
    exit 0;
## Migration from one version to another
} elsif ($main::options{'upgrade'}) {
859

860
    $log->syslog('notice', "Upgrade process...");
861

862
863
    $main::options{'from'} ||= Sympa::Upgrade::get_previous_version();
    $main::options{'to'}   ||= Sympa::Constants::VERSION;
864

865
    if ($main::options{'from'} eq $main::options{'to'}) {
866
        $log->syslog('notice', 'Current version: %s; no upgrade is required',
867
868
869
            $main::options{'to'});
        exit 0;
    } else {
870
        $log->syslog('notice', "Upgrading from %s to %s...",
871
872
            $main::options{'from'}, $main::options{'to'});
    }
873

874
875
876
    unless (
        Sympa::Upgrade::upgrade($main::options{'from'}, $main::options{'to'}))
    {
877
        $log->syslog('err', "Migration from %s to %s failed",
878
879
880
            $main::options{'from'}, $main::options{'to'});
        exit 1;
    }
881

882
    $log->syslog('notice', 'Upgrade process finished');
883
    Sympa::Upgrade::update_version();
884

885
    exit 0;
886

887
} elsif ($main::options{'upgrade_shared'}) {
888
    print STDERR "Obsoleted.  Use upgrade_shared_repository.pl.\n";
889

890
891
892
    exit 0;
} elsif ($main::options{'reload_list_config'}) {
    if ($main::options{'list'}) {
893
        $log->syslog('notice', 'Loading list %s...', $main::options{'list'});
894
895
        my $list =
            Sympa::List->new($main::options{'list'}, '',
896
            {reload_config => 1});
897
        unless (defined $list) {
Luc Didry's avatar
Luc Didry committed
898
899
            printf STDERR "Error : incorrect list name '%s'\n",
                $main::options{'list'};
900
901
902
            exit 1;
        }
    } else {
903
        $log->syslog('notice', "Loading ALL lists...");
904
        my $all_lists = Sympa::List::get_lists('*', reload_config => 1);
905
    }
906
    $log->syslog('notice', '...Done.');
907

908
909
    exit 0;
}
910

911
912
##########################################
elsif ($main::options{'modify_list'}) {
913
    my $robot = $main::options{'robot'} || $Conf::Conf{'domain'};
914

915
916
    my $family;
    unless ($main::options{'modify_list'}) {
917
918
919
        print STDERR "Error : missing family parameter\n";
        exit 1;
    }
920
921
    unless ($family =
        Sympa::Family->new($main::options{'modify_list'}, $robot)) {
922
923
        printf STDERR
            "The family %s does not exist, impossible to modify the list.\n",
924
            $main::options{'modify_list'};
925
926
927
928
929
930
        exit 1;
    }
    unless ($main::options{'input_file'}) {
        print STDERR "Error : missing input_file parameter\n";
        exit 1;
    }
931

932
933
934
935
936
937
938
939
940
941
    # list config family updating
    my $spindle = Sympa::Spindle::ProcessRequest->new(
        context          => $family,
        action           => 'update_automatic_list',
        parameters       => {file => $main::options{'input_file'}},
        sender           => Sympa::get_address($family, 'listmaster'),
        scenario_context => {skip => 1},
    );
    unless ($spindle and $spindle->spin and _report($spindle)) {
        print STDERR "No object list resulting from updating\n";
942
943
        exit 1;
    }
944

945
946
    exit 0;
}
947

948
949
##########################################
elsif ($main::options{'close_family'}) {
950
    my $robot = $main::options{'robot'} || $Conf::Conf{'domain'};
951

952
953
954
955
956
957
    my $family_name;
    unless ($family_name = $main::options{'close_family'}) {
        pod2usage(-exitval => 1, -output => \*STDERR);
    }
    my $family;
    unless ($family = Sympa::Family->new($family_name, $robot)) {
958
959
960
        printf STDERR
            "The family %s does not exist, impossible family closure\n",
            $family_name;
961
962
        exit 1;
    }
963

964
965
966
967
968
969
970
971
    my $lists = Sympa::List::get_lists($family);
    my @impossible_close;
    my @close_ok;

    foreach my $list (@{$lists || []}) {
        my $listname = $list->{'name'};

        my $spindle = Sympa::Spindle::ProcessRequest->new(
972
            context          => $family->{'domain'},
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
            action           => 'close_list',
            current_list     => $list,
            sender           => Sympa::get_address($family, 'listmaster'),
            scenario_context => {skip => 1},
        );
        unless ($spindle and $spindle->spin and _report($spindle)) {
            push @impossible_