Commands.pm 114 KB
Newer Older
1
2
3
# -*- indent-tabs-mode: nil; -*-
# vim:ft=perl:et:sw=4
# $Id$
root's avatar
root committed
4

5
# Sympa - SYsteme de Multi-Postage Automatique
6
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
# Copyright (c) 2011, 2012, 2013, 2014 GIP RENATER
11
12
13
14
15
16
17
18
19
20
21
22
#
# 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
23
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
root's avatar
root committed
24

25
package Sympa::Commands;
root's avatar
root committed
26

27
28
use strict;
use warnings;
29
use Mail::Address;
30
use MIME::Parser;
31

32
use Sympa::Archive;
root's avatar
root committed
33
use Conf;
34
use Sympa::Language;
35
use Sympa::List;
36
use Log;
37
use Sympa::Message;
38
use Sympa::Regexps;
39
40
41
use Sympa::Report;
use Sympa::Robot;
use Sympa::Scenario;
42
use tools;
43
44
use Sympa::Tools::Data;
use Sympa::Tools::File;
45
use Sympa::Tools::Password;
46
use Sympa::User;
root's avatar
root committed
47

48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
my %comms = (
    'add'                               => 'add',
    'con|confirm'                       => 'confirm',
    'del|delete'                        => 'del',
    'dis|distribute'                    => 'distribute',
    'get'                               => 'getfile',
    'hel|help|sos'                      => 'help',
    'inf|info'                          => 'info',
    'inv|invite'                        => 'invite',
    'ind|index'                         => 'index',
    'las|last'                          => 'last',
    'lis|lists?'                        => 'lists',
    'mod|modindex|modind'               => 'modindex',
    'qui|quit|end|stop|-'               => 'finished',
    'rej|reject'                        => 'reject',
    'rem|remind'                        => 'remind',
    'rev|review|who'                    => 'review',
    'set'                               => 'set',
    'sub|subscribe'                     => 'subscribe',
    'sig|signoff|uns|unsub|unsubscribe' => 'signoff',
    'sta|stats'                         => 'stats',
    'ver|verify'                        => 'verify',
    'whi|which|status'                  => 'which'
);
72
73
74

my $language = Sympa::Language->instance;

75
# command sender
root's avatar
root committed
76
my $sender = '';
77
# time of the process command
root's avatar
root committed
78
my $time_command;
79
80
## my $msg_file;
# command line to process
81
my $cmd_line;
82
83
84
85
86
87
88
89
# key authentification if 'auth' is present in the command line
my $auth;
# boolean says if quiet is in the cmd line
my $quiet;

##############################################
#  parse
##############################################
90
91
92
# Parses the command and calls the adequate
# subroutine with the arguments to the command.
#
93
94
95
# IN :-$sender (+): the command sender
#     -$robot (+): robot
#     -$i (+): command line
96
#     -$sign_mod : 'smime'| 'dkim' -
97
98
#
# OUT : $status |'unknown_cmd'
99
#
100
##############################################
root's avatar
root committed
101
sub parse {
102
    Log::do_log('debug2', '(%s, %s, %s, %s, %s)', @_);
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
    $sender = lc(shift);    #FIXME: eliminate module-global variable.
    my $robot    = shift;
    my $i        = shift;
    my $sign_mod = shift;
    my $message  = shift;

    my $j;
    $cmd_line = '';

    Log::do_log('notice', "Parsing: %s", $i);

    ## allow reply usage for auth process based on user mail replies
    if ($i =~ /auth\s+(\S+)\s+(.+)$/io) {
        $auth = $1;
        $i    = $2;
    } else {
        $auth = '';
    }

    if ($i =~ /^quiet\s+(.+)$/i) {
        $i     = $1;
        $quiet = 1;
    } else {
        $quiet = 0;
    }

    foreach $j (keys %comms) {
        if ($i =~ /^($j)(\s+(.+))?\s*$/i) {
            no strict 'refs';

            $time_command = time;
            my $args = $3;
            if ($args and length $args) {
                $args =~ s/^\s*//;
                $args =~ s/\s*$//;
            }

            my $status;
            $cmd_line = $i;
            $status = &{$comms{$j}}($args, $robot, $sign_mod, $message);

            return $status;
        }
    }

    ## Unknown command
    return 'unknown_cmd';
root's avatar
root committed
150
151
}

152
153
154
155
##############################################
#  finished
##############################################
#  Do not process what is after this line
156
#
157
158
# IN : -
#
159
160
# OUT : 1
#
161
################################################
root's avatar
root committed
162
sub finished {
163
    Log::do_log('debug2', '');
root's avatar
root committed
164

165
    Sympa::Report::notice_report_cmd('finished', {}, $cmd_line);
root's avatar
root committed
166
167
168
    return 1;
}

169
170
171
172
##############################################
#  help
##############################################
#  Sends the help file for the software
173
174
175
#
# IN : - ?
#      -$robot (+): robot
176
177
#
# OUT : 1 | undef
178
#
179
##############################################
root's avatar
root committed
180
sub help {
sikeda's avatar
sikeda committed
181
    Log::do_log('debug2', '(%s, %s)', @_);
salaun's avatar
salaun committed
182
    shift;
183
    my $robot = shift;
salaun's avatar
salaun committed
184

sikeda's avatar
sikeda committed
185
    my $data = {};
root's avatar
root committed
186

187
188
    my @owner  = Sympa::List::get_which($sender, $robot, 'owner');
    my @editor = Sympa::List::get_which($sender, $robot, 'editor');
189

sikeda's avatar
sikeda committed
190
191
192
    $data->{'is_owner'}  = 1 if @owner;
    $data->{'is_editor'} = 1 if @editor;
    $data->{'user'}      = Sympa::User->new($sender);
193
    $language->set_lang($data->{'user'}->lang)
194
        if $data->{'user'}->lang;
195
    $data->{'subject'}        = $language->gettext("User guide");
sikeda's avatar
sikeda committed
196
197
    $data->{'auto_submitted'} = 'auto-replied';

198
    unless (tools::send_file($robot, "helpfile", $sender, $data)) {
199
200
        Log::do_log('notice', 'Unable to send template "helpfile" to %s',
            $sender);
sikeda's avatar
sikeda committed
201
202
        Sympa::Report::reject_report_cmd('intern_quiet', '', {}, $cmd_line,
            $sender, $robot);
root's avatar
root committed
203
204
    }

205
206
207
    Log::do_log('info', 'HELP from %s accepted (%d seconds)',
        $sender, time - $time_command);

root's avatar
root committed
208
209
210
    return 1;
}

211
212
213
214
#####################################################
#  lists
#####################################################
#  Sends back the list of public lists on this node.
215
216
217
#
# IN : - ?
#      -$robot (+): robot
218
219
#
# OUT : 1  | undef
220
221
#
#######################################################
root's avatar
root committed
222
sub lists {
223
224
    shift;
    my $robot    = shift;
225
    my $sign_mod = shift;
226
    my $message  = shift;
salaun's avatar
salaun committed
227

sikeda's avatar
sikeda committed
228
    my $sympa = Conf::get_robot_conf($robot, 'sympa');
229
    my $host  = Conf::get_robot_conf($robot, 'host');
230

231
    Log::do_log('debug', 'For robot %s, sign_mod %, message %s',
232
        $robot, $sign_mod, $message);
root's avatar
root committed
233

234
    my $data  = {};
root's avatar
root committed
235
236
    my $lists = {};

237
    my $all_lists = Sympa::List::get_lists($robot);
238
239
240
241

    foreach my $list (@$all_lists) {
        my $l = $list->{'name'};

242
        my $result = Sympa::Scenario::request_action(
243
            $list, 'visibility', 'smtp',    # 'smtp' isn't it a bug ?
244
245
246
247
248
249
250
251
252
253
254
            {   'sender'  => $sender,
                'message' => $message,
            }
        );

        my $action;
        $action = $result->{'action'} if (ref($result) eq 'HASH');

        unless (defined $action) {
            my $error =
                "Unable to evaluate scenario 'visibility' for list $l";
255
256
            tools::send_notify_to_listmaster(
                $list,
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
                'intern_error',
                {   'error'          => $error,
                    'who'            => $sender,
                    'cmd'            => $cmd_line,
                    'action'         => 'Command process',
                    'auto_submitted' => 'auto-replied'
                }
            );
            next;
        }

        if ($action eq 'do_it') {
            $lists->{$l}{'subject'} = $list->{'admin'}{'subject'};
            $lists->{$l}{'host'}    = $list->{'admin'}{'host'};
        }
    }

    $data->{'lists'}          = $lists;
275
    $data->{'auto_submitted'} = 'auto-replied';
276

277
    unless (tools::send_file($robot, 'lists', $sender, $data)) {
278
279
        Log::do_log('notice', 'Unable to send template "lists" to %s',
            $sender);
sikeda's avatar
sikeda committed
280
281
        Sympa::Report::reject_report_cmd('intern_quiet', '', {}, $cmd_line,
            $sender, $robot);
282
    }
root's avatar
root committed
283

284
285
    Log::do_log('info', 'LISTS from %s accepted (%d seconds)',
        $sender, time - $time_command);
root's avatar
root committed
286
287
288
289

    return 1;
}

290
291
292
293
294
#####################################################
#  stats
#####################################################
#  Sends the statistics about a list using template
#  'stats_report'
295
#
296
# IN : -$listname (+): list name
297
#      -$robot (+): robot
298
#      -$sign_mod : 'smime' | 'dkim'|  -
299
300
#
# OUT : 'unknown_list'|'not_allowed'|1  | undef
301
302
#
#######################################################
root's avatar
root committed
303
sub stats {
304
    my $listname = shift;
305
306
307
    my $robot    = shift;
    my $sign_mod = shift;
    my $message  = shift;
root's avatar
root committed
308

309
    Log::do_log('debug', '(%s, %s, %s, %s)',
310
        $listname, $robot, $sign_mod, $message);
root's avatar
root committed
311

312
    my $list = Sympa::List->new($listname, $robot);
313
    unless ($list) {
314
        Sympa::Report::reject_report_cmd('user', 'no_existing_list',
315
316
317
318
319
            {'listname' => $listname}, $cmd_line);
        Log::do_log('info',
            'STATS %s from %s refused, unknown list for robot %s',
            $listname, $sender, $robot);
        return 'unknown_list';
root's avatar
root committed
320
    }
321

322
323
324
325
326
327
328
329
330
    my $auth_method = get_auth_method(
        'stats', $sender,
        {   'type' => 'auth_failed',
            'data' => {},
            'msg'  => "STATS $listname from $sender"
        },
        $sign_mod,
        $list
    );
331
    return 'wrong_auth'
332
        unless (defined $auth_method);
333

334
    my $result = Sympa::Scenario::request_action(
335
        $list, 'review',
336
337
338
339
340
        $auth_method,
        {   'sender'  => $sender,
            'message' => $message,
        }
    );
341
342
    my $action;
    $action = $result->{'action'} if (ref($result) eq 'HASH');
343

344
    unless (defined $action) {
345
        my $error = "Unable to evaluate scenario 'review' for list $listname";
346
        Sympa::Report::reject_report_cmd('intern', $error,
347
348
349
            {'listname' => $listname, 'list' => $list},
            $cmd_line, $sender, $robot);
        return undef;
350
    }
351

352
    if ($action =~ /reject/i) {
353
354
        if (defined $result->{'tt2'}) {
            unless (
355
356
357
                tools::send_file(
                    $list, $result->{'tt2'},
                    $sender, {'auto_submitted' => 'auto-replied'}
358
359
                )
                ) {
360
                Log::do_log('notice', 'Unable to send template "%s" to %s',
361
                    $result->{'tt2'}, $sender);
sikeda's avatar
sikeda committed
362
363
                Sympa::Report::reject_report_cmd('auth', $result->{'reason'},
                    {}, $cmd_line);
364
365
            }
        } else {
366
            Sympa::Report::reject_report_cmd('auth', $result->{'reason'}, {},
367
368
                $cmd_line);
        }
369
        Log::do_log('info', 'Stats %s from %s refused (not allowed)',
370
371
372
373
374
375
376
377
378
379
380
381
382
            $listname, $sender);
        return 'not_allowed';
    } else {
        my %stats = (
            'msg_rcv'  => $list->{'stats'}[0],
            'msg_sent' => $list->{'stats'}[1],
            'byte_rcv' =>
                sprintf('%9.2f', ($list->{'stats'}[2] / 1024 / 1024)),
            'byte_sent' =>
                sprintf('%9.2f', ($list->{'stats'}[3] / 1024 / 1024))
        );

        unless (
383
384
            tools::send_file(
                $list,
385
                'stats_report',
386
                $sender,
387
388
389
390
391
392
393
                {   'stats'   => \%stats,
                    'subject' => "STATS $list->{'name'}",  # compat <= 6.1.17.
                    'auto_submitted' => 'auto-replied'
                }
            )
            ) {
            Log::do_log('notice',
394
                'Unable to send template "stats_reports" to %s', $sender);
395
            Sympa::Report::reject_report_cmd('intern_quiet', '',
396
397
398
399
400
401
                {'listname' => $listname, 'list' => $list},
                $cmd_line, $sender, $robot);
        }

        Log::do_log('info', 'STATS %s from %s accepted (%d seconds)',
            $listname, $sender, time - $time_command);
402
    }
root's avatar
root committed
403
404
405
406

    return 1;
}

407
408
409
410
###############################################
#  getfile
##############################################
# Sends back the requested archive file
411
#
412
# IN : -$which (+): command parameters : listname filename
413
#      -$robot (+): robot
414
#
415
416
417
# OUT : 'unknownlist'|'no_archive'|'not_allowed'|1
#
###############################################
root's avatar
root committed
418
sub getfile {
419
420
    my ($which, $file) = split(/\s+/, shift);
    my $robot = shift;
salaun's avatar
salaun committed
421

422
    Log::do_log('debug', '(%s, %s, %s)', $which, $file, $robot);
root's avatar
root committed
423

424
    my $list = Sympa::List->new($which, $robot);
425
    unless ($list) {
426
        Sympa::Report::reject_report_cmd('user', 'no_existing_list',
427
428
429
430
431
            {'listname' => $which}, $cmd_line);
        Log::do_log('info',
            'GET %s %s from %s refused, list unknown for robot %s',
            $which, $file, $sender, $robot);
        return 'unknownlist';
root's avatar
root committed
432
433
    }

434
    $language->set_lang($list->{'admin'}{'lang'});
root's avatar
root committed
435
436

    unless ($list->is_archived()) {
sikeda's avatar
sikeda committed
437
438
        Sympa::Report::reject_report_cmd('user', 'empty_archives', {},
            $cmd_line);
439
440
441
442
        Log::do_log('info',
            'GET %s %s from %s refused, no archive for list %s',
            $which, $file, $sender, $which);
        return 'no_archive';
root's avatar
root committed
443
444
445
    }
    ## Check file syntax
    if ($file =~ /(\.\.|\/)/) {
sikeda's avatar
sikeda committed
446
447
        Sympa::Report::reject_report_cmd('user', 'no_required_file', {},
            $cmd_line);
448
449
450
        Log::do_log('info', 'GET %s %s from %s, incorrect filename',
            $which, $file, $sender);
        return 'no_archive';
root's avatar
root committed
451
452
    }
    unless ($list->may_do('get', $sender)) {
sikeda's avatar
sikeda committed
453
454
        Sympa::Report::reject_report_cmd('auth', 'list_private_no_archive',
            {}, $cmd_line);
455
456
457
        Log::do_log('info', 'GET %s %s from %s refused, review not allowed',
            $which, $file, $sender);
        return 'not_allowed';
root's avatar
root committed
458
    }
sympa-authors's avatar
   
sympa-authors committed
459
#    unless ($list->archive_exist($file)) {
460
461
#	Sympa::Report::reject_report_cmd('user', 'no_required_file', {},
#	    $cmd_line);
462
# 	Log::do_log('info',
463
464
# 	'GET %s %s from %s refused, archive not found for list %s', $which,
# 	    $file, $sender, $which);
sympa-authors's avatar
   
sympa-authors committed
465
466
467
#	return 'no_archive';
#    }

468
    unless ($list->archive_send($sender, $file)) {
469
        Sympa::Report::reject_report_cmd(
470
471
472
473
474
475
            'intern',
            "Unable to send archive to $sender",
            {'listname' => $which},
            $cmd_line, $sender, $robot
        );
        return 'no_archive';
476
    }
477
478
479

    Log::do_log('info', 'GET %s %s from %s accepted (%d seconds)',
        $which, $file, $sender, time - $time_command);
root's avatar
root committed
480
481
482
483

    return 1;
}

484
485
486
487
###############################################
#  last
##############################################
# Sends back the last archive file
488
489
490
491
#
#
# IN : -$which (+): listname
#      -$robot (+): robot
492
493
#
# OUT : 'unknownlist'|'no_archive'|'not_allowed'|1
494
495
#
###############################################
root's avatar
root committed
496
497
sub last {
    my $which = shift;
salaun's avatar
salaun committed
498
499
    my $robot = shift;

sikeda's avatar
sikeda committed
500
    my $sympa = Conf::get_robot_conf($robot, 'sympa');
501

502
    Log::do_log('debug', '(%s, %s)', $which, $robot);
root's avatar
root committed
503

504
    my $list = Sympa::List->new($which, $robot);
505
    unless ($list) {
506
        Sympa::Report::reject_report_cmd('user', 'no_existing_list',
507
508
509
510
511
            {'listname' => $which}, $cmd_line);
        Log::do_log('info',
            'LAST %s from %s refused, list unknown for robot %s',
            $which, $sender, $robot);
        return 'unknownlist';
root's avatar
root committed
512
513
    }

514
    $language->set_lang($list->{'admin'}{'lang'});
root's avatar
root committed
515
516

    unless ($list->is_archived()) {
sikeda's avatar
sikeda committed
517
518
        Sympa::Report::reject_report_cmd('user', 'empty_archives', {},
            $cmd_line);
519
520
521
        Log::do_log('info', 'LAST %s from %s refused, list not archived',
            $which, $sender);
        return 'no_archive';
root's avatar
root committed
522
    }
523
    my $file;
524
    unless ($file = Sympa::Archive::last_path($list)) {
sikeda's avatar
sikeda committed
525
526
        Sympa::Report::reject_report_cmd('user', 'no_required_file', {},
            $cmd_line);
527
528
529
530
        Log::do_log('info',
            'LAST %s from %s refused, archive file %s not found',
            $which, $sender, $file);
        return 'no_archive';
root's avatar
root committed
531
532
    }
    unless ($list->may_do('get', $sender)) {
sikeda's avatar
sikeda committed
533
534
        Sympa::Report::reject_report_cmd('auth', 'list_private_no_archive',
            {}, $cmd_line);
535
536
537
538
        Log::do_log('info',
            'LAST %s from %s refused, archive access not allowed',
            $which, $sender);
        return 'not_allowed';
root's avatar
root committed
539
    }
540

sympa-authors's avatar
sympa-authors committed
541
    unless ($list->archive_send_last($sender)) {
542
        Sympa::Report::reject_report_cmd(
543
544
545
546
547
548
            'intern',
            "Unable to send archive to $sender",
            {'listname' => $which},
            $cmd_line, $sender, $robot
        );
        return 'no_archive';
root's avatar
root committed
549
550
    }

551
552
    Log::do_log('info', 'LAST %s from %s accepted (%d seconds)',
        $which, $sender, time - $time_command);
root's avatar
root committed
553
554
555
556

    return 1;
}

557
558
559
560
561
562
############################################################
#  index
############################################################
#  Sends the list of archived files of a list
#
# IN : -$which (+): list name
563
#      -$robot (+): robot
564
565
566
567
#
# OUT : 'unknown_list'|'not_allowed'|'no_archive'|1
#
#############################################################
root's avatar
root committed
568
569
sub index {
    my $which = shift;
salaun's avatar
salaun committed
570
571
    my $robot = shift;

572
    Log::do_log('debug', '(%s) Robot (%s)', $which, $robot);
root's avatar
root committed
573

574
    my $list = Sympa::List->new($which, $robot);
575
    unless ($list) {
576
        Sympa::Report::reject_report_cmd('user', 'no_existing_list',
577
578
579
580
581
            {'listname' => $which}, $cmd_line);
        Log::do_log('info',
            'INDEX %s from %s refused, list unknown for robot %s',
            $which, $sender, $robot);
        return 'unknown_list';
root's avatar
root committed
582
583
    }

584
    $language->set_lang($list->{'admin'}{'lang'});
585

root's avatar
root committed
586
587
588
589
    ## Now check if we may send the list of users to the requestor.
    ## Check all this depending on the values of the Review field in
    ## the control file.
    unless ($list->may_do('index', $sender)) {
590
        Sympa::Report::reject_report_cmd('auth', 'list_private_no_browse', {},
591
592
593
594
            $cmd_line);
        Log::do_log('info', 'INDEX %s from %s refused, not allowed',
            $which, $sender);
        return 'not_allowed';
root's avatar
root committed
595
596
    }
    unless ($list->is_archived()) {
sikeda's avatar
sikeda committed
597
598
        Sympa::Report::reject_report_cmd('user', 'empty_archives', {},
            $cmd_line);
599
600
601
        Log::do_log('info', 'INDEX %s from %s refused, list not archived',
            $which, $sender);
        return 'no_archive';
root's avatar
root committed
602
    }
603

root's avatar
root committed
604
    my @l = $list->archive_ls();
605
    unless (
606
607
        tools::send_file(
            $list, 'index_archive', $sender,
608
609
610
            {'archives' => \@l, 'auto_submitted' => 'auto-replied'}
        )
        ) {
611
612
        Log::do_log('notice', 'Unable to send template "index_archive" to %s',
            $sender);
613
        Sympa::Report::reject_report_cmd('intern_quiet', '',
614
615
616
617
618
619
            {'listname' => $list->{'name'}},
            $cmd_line, $sender, $robot);
    }

    Log::do_log('info', 'INDEX %s from %s accepted (%d seconds)',
        $which, $sender, time - $time_command);
root's avatar
root committed
620
621
622
623

    return 1;
}

624
625
626
627
628
629
############################################################
#  review
############################################################
#  Sends the list of subscribers to the requester.
#
# IN : -$listname (+): list name
630
#      -$robot (+): robot
631
632
633
634
635
#      -$sign_mod : 'smime'| -
#
# OUT : 'unknown_list'|'wrong_auth'|'not_allowed'
#       |'no_subscribers'|1 | undef
#
636
################################################################
root's avatar
root committed
637
sub review {
638
639
640
641
    my $listname = shift;
    my $robot    = shift;
    my $sign_mod = shift;
    my $message  = shift;
salaun's avatar
salaun committed
642

643
    Log::do_log('debug', '(%s, %s, %s)', $listname, $robot, $sign_mod);
sikeda's avatar
sikeda committed
644
    my $sympa = Conf::get_robot_conf($robot, 'sympa');
645

root's avatar
root committed
646
    my $user;
647
    my $list = Sympa::List->new($listname, $robot);
salaun's avatar
salaun committed
648

649
    unless ($list) {
650
        Sympa::Report::reject_report_cmd('user', 'no_existing_list',
651
652
653
654
655
            {'listname' => $listname}, $cmd_line);
        Log::do_log('info',
            'REVIEW %s from %s refused, list unknown to robot %s',
            $listname, $sender, $robot);
        return 'unknown_list';
root's avatar
root committed
656
    }
salaun's avatar
salaun committed
657

658
    $language->set_lang($list->{'admin'}{'lang'});
root's avatar
root committed
659

660
661
    $list->on_the_fly_sync_include('use_ttl' => 1);

662
663
664
665
666
667
668
669
670
    my $auth_method = get_auth_method(
        'review', '',
        {   'type' => 'auth_failed',
            'data' => {},
            'msg'  => "REVIEW $listname from $sender"
        },
        $sign_mod,
        $list
    );
671
    return 'wrong_auth'
672
        unless (defined $auth_method);
root's avatar
root committed
673

674
    my $result = Sympa::Scenario::request_action(
675
        $list, 'review',
676
677
678
679
680
        $auth_method,
        {   'sender'  => $sender,
            'message' => $message
        }
    );
681
682
    my $action;
    $action = $result->{'action'} if (ref($result) eq 'HASH');
683

684
685
    unless (defined $action) {
        my $error = "Unable to evaluate scenario 'review' for list $listname";
sikeda's avatar
sikeda committed
686
687
        Sympa::Report::reject_report_cmd('intern', $error,
            {'listname' => $listname},
688
689
            $cmd_line, $sender, $robot);
        return undef;
690
    }
salaun's avatar
salaun committed
691

root's avatar
root committed
692
    if ($action =~ /request_auth/i) {
693
        Log::do_log('debug2', 'Auth requested from %s', $sender);
694
695
696
        unless ($list->request_auth($sender, 'review', $robot)) {
            my $error =
                "Unable to request authentification for command 'review'";
697
            Sympa::Report::reject_report_cmd('intern', $error,
698
699
700
701
702
703
704
                {'listname' => $listname},
                $cmd_line, $sender, $robot);
            return undef;
        }
        Log::do_log('info', 'REVIEW %s from %s, auth requested (%d seconds)',
            $listname, $sender, time - $time_command);
        return 1;
root's avatar
root committed
705
    }
706
    if ($action =~ /reject/i) {
707
708
        if (defined $result->{'tt2'}) {
            unless (
709
710
711
                tools::send_file(
                    $list, $result->{'tt2'},
                    $sender, {'auto_submitted' => 'auto-replied'}
712
713
                )
                ) {
714
                Log::do_log('notice', 'Unable to send template "%s" to %s',
715
                    $result->{'tt2'}, $sender);
sikeda's avatar
sikeda committed
716
717
                Sympa::Report::reject_report_cmd('auth', $result->{'reason'},
                    {}, $cmd_line);
718
719
            }
        } else {
720
            Sympa::Report::reject_report_cmd('auth', $result->{'reason'}, {},
721
722
                $cmd_line);
        }
723
        Log::do_log('info', 'Review %s from %s refused (not allowed)',
724
725
            $listname, $sender);
        return 'not_allowed';
root's avatar
root committed
726
727
728
729
730
    }

    my @users;

    if ($action =~ /do_it/i) {
731
732
        my $is_owner = $list->am_i('owner', $sender);
        unless ($user = $list->get_first_list_member({'sortby' => 'email'})) {
733
            Sympa::Report::reject_report_cmd('user', 'no_subscriber',
734
                {'listname' => $listname}, $cmd_line);
735
            Log::do_log('err', 'No subscribers in list "%s"',
736
737
738
739
740
741
742
743
744
745
746
747
748
749
                $list->{'name'});
            return 'no_subscribers';
        }
        do {
            ## Owners bypass the visibility option
            unless (($user->{'visibility'} eq 'conceal')
                and (!$is_owner)) {

                ## Lower case email address
                $user->{'email'} =~ y/A-Z/a-z/;
                push @users, $user;
            }
        } while ($user = $list->get_next_list_member());
        unless (
750
751
            tools::send_file(
                $list, 'review', $sender,
752
753
754
755
756
757
758
                {   'users'   => \@users,
                    'total'   => $list->get_total(),
                    'subject' => "REVIEW $listname",    # Compat <= 6.1.17.
                    'auto_submitted' => 'auto-replied'
                }
            )
            ) {
759
760
            Log::do_log('notice', 'Unable to send template "review" to %s',
                $sender);
761
            Sympa::Report::reject_report_cmd('intern_quiet', '',
762
763
764
765
766
767
768
769
770
771
772
                {'listname' => $listname},
                $cmd_line, $sender, $robot);
        }

        Log::do_log('info', 'REVIEW %s from %s accepted (%d seconds)',
            $listname, $sender, time - $time_command);
        return 1;
    }
    Log::do_log('info',
        'REVIEW %s from %s aborted, unknown requested action in scenario',
        $listname, $sender);
773
    my $error = "Unknown requested action in scenario: $action.";
sikeda's avatar
sikeda committed
774
775
    Sympa::Report::reject_report_cmd('intern', $error,
        {'listname' => $listname},
776
        $cmd_line, $sender, $robot);
root's avatar
root committed
777
    return undef;
778
779
}

780
781
782
############################################################
#  verify
############################################################
783
#  Verify an S/MIME signature
784
785
#
# IN : -$listname (+): list name
786
#      -$robot (+): robot
787
#      -$sign_mod : 'smime'| 'dkim' | -
788
789
790
791
#
# OUT : 1
#
#############################################################
792
sub verify {
793
    Log::do_log('debug2', '(%s, %s)', @_);
794
795
796
    my $listname = shift;
    my $robot    = shift;
    my $sign_mod = shift;
797

798
    my $list = Sympa::List->new($listname, $robot);
799
    unless ($list) {
800
        Sympa::Report::reject_report_cmd('user', 'no_existing_list',
801
            {'listname' => $listname}, $cmd_line);
802
        Log::do_log('info',
803
804
805
806
807
            'VERIFY from %s refused, unknown list for robot %s',
            $sender, $robot);
        return 'unknown_list';
    }

808
    my $user;
809

810
    $language->set_lang($list->{'admin'}{'lang'});
811
812
813
814
815
816
817
818

    if ($sign_mod) {
        Log::do_log(
            'info',  'VERIFY successfull from %s',
            $sender, time - $time_command
        );
        if ($sign_mod eq 'smime') {
            ##$auth_method='smime';
819
            Sympa::Report::notice_report_cmd('smime', {}, $cmd_line);
820
821
        } elsif ($sign_mod eq 'dkim') {
            ##$auth_method='dkim';
822
            Sympa::Report::notice_report_cmd('dkim', {}, $cmd_line);
823
824
825
        }
    } else {
        Log::do_log('info',
826
            'VERIFY from %s: could not find correct S/MIME signature',
827
            $sender, time - $time_command);
sikeda's avatar
sikeda committed
828
829
        Sympa::Report::reject_report_cmd('user', 'no_verify_sign', {},
            $cmd_line);
830
831
    }
    return 1;
root's avatar
root committed
832
833
}

834
835
836
837
##############################################################
#  subscribe
##############################################################
#  Subscribes a user to a list. The user sent a subscribe
838
#  command. Format was : sub list optionnal comment. User can
839
#  be informed by template 'welcome'
840
#
841
# IN : -$what (+): command parameters : listname(+), comment
842
#      -$robot (+): robot
843
844
845
846
847
#      -$sign_mod : 'smime'| -
#
# OUT : 'unknown_list'|'wrong_auth'|'not_allowed'| 1 | undef
#
################################################################
root's avatar
root committed
848
sub subscribe {
849
850
    my $what     = shift;
    my $robot    = shift;
851
    my $sign_mod = shift;
852
    my $message  = shift;
salaun's avatar
salaun committed
853

854
    Log::do_log('debug', '(%s, %s, %s, %s)',
855
        $what, $robot, $sign_mod, $message);
root's avatar
root committed
856
857

    $what =~ /^(\S+)(\s+(.+))?\s*$/;
858
859
    my ($which, $comment) = ($1, $3);

root's avatar
root committed
860
861
    ## Load the list if not already done, and reject the
    ## subscription if this list is unknown to us.
862
    my $list = Sympa::List->new($which, $robot);
863
    unless ($list) {
864
        Sympa::Report::reject_report_cmd('user', 'no_existing_list',
865
866
867
868
869
            {'listname' => $which}, $cmd_line);
        Log::do_log('info',
            'SUB %s from %s refused, unknown list for robot %s',
            $which, $sender, $robot);
        return 'unknown_list';
root's avatar
root committed
870
871
    }

872
    $language->set_lang($list->{'admin'}{'lang'});
root's avatar
root committed
873
874
875

    ## This is a really minimalistic handling of the comments,
    ## it is far away from RFC-822 completeness.
876
877
878
879
880
881
    if (defined $comment and $comment =~ /\S/) {
        $comment =~ s/"/\\"/g;
        $comment = "\"$comment\"" if ($comment =~ /[<>\(\)]/);
    } else {
        undef $comment;
    }
882

root's avatar
root committed
883
    ## Now check if the user may subscribe to the list
884
885
886
887
888
889
890
891
892
893
894

    my $auth_method = get_auth_method(
        'subscribe',
        $sender,
        {   'type' => 'wrong_email_confirm',
            'data' => {'command' => 'subscription'},
            'msg'  => "SUB $which from $sender"
        },
        $sign_mod,
        $list
    );
895
    return 'wrong_auth'
896
        unless (defined $auth_method);
897

root's avatar
root committed
898
    ## query what to do with this subscribtion request
899

900
    my $result = Sympa::Scenario::request_action(
901
        $list,
902
903
904
905
906
907
        'subscribe',
        $auth_method,
        {   'sender'  => $sender,
            'message' => $message,
        }
    );
908
909
    my $action;
    $action = $result->{'action'} if (ref($result) eq 'HASH');
910
911
912

    unless (defined $action) {
        my $error = "Unable to evaluate scenario 'subscribe' for list $which";
sikeda's avatar
sikeda committed
913
914
        Sympa::Report::reject_report_cmd('intern', $error,
            {'listname' => $which},
915
916
            $cmd_line, $sender, $robot);
        return undef;
917
    }
salaun's avatar
salaun committed
918

919
    Log::do_log('debug2', 'Action: %s', $action);
920

921
    if ($action =~ /reject/i) {
922
923
        if (defined $result->{'tt2'}) {
            unless (
924
925
926
                tools::send_file(
                    $list, $result->{'tt2'},
                    $sender, {'auto_submitted' => 'auto-replied'}
927
928
                )
                ) {
929
                Log::do_log('notice', 'Unable to send template "%s" to %s',
930
                    $result->{'tt2'}, $sender);
sikeda's avatar
sikeda committed
931
932
                Sympa::Report::reject_report_cmd('auth', $result->{'reason'},
                    {}, $cmd_line);
933
934
            }
        } else {
935
            Sympa::Report::reject_report_cmd('auth', $result->{'reason'}, {},
936
937
938
939
940
941
942
943
944
945
946
                $cmd_line);
        }
        Log::do_log('info', 'SUB %s from %s refused (not allowed)',
            $which, $sender);
        return 'not_allowed';
    }

    ## Unless rejected by scenario, don't go further if the user is subscribed
    ## already.
    my $user_entry = $list->get_list_member($sender);
    if (defined($user_entry)) {
947
        Sympa::Report::reject_report_cmd('user', 'already_subscriber',
948
949
950
            {'email' => $sender, 'listname' => $list->{'name'}}, $cmd_line);
        Log::do_log(
            'err',
951
            'User %s is subscribed to %s already. Ignoring subscription request',
952
953
954
955
            $sender,
            $list->{'name'}
        );
        return undef;
956
957
958
    }

    ## Continue checking scenario.
root's avatar
root committed
959
    if ($action =~ /owner/i) {
960
        Sympa::Report::notice_report_cmd('req_forward', {}, $cmd_line);
961
962
963
964
965
966
967
968
969
970
971
        ## Send a notice to the owners.
        unless (
            $list->send_notify_to_owner(
                'subrequest',
                {   'who'     => $sender,
                    'keyauth' => $list->compute_auth($sender, 'add'),
                    'replyto' => Conf::get_robot_conf($robot, 'sympa'),
                    'gecos'   => $comment
                }
            )
            ) {
972
            #FIXME: Why is error reported only in this case?
973
            Log::do_log('info',
sikeda's avatar
sikeda committed
974
                'Unable to send notify "subrequest" to %s list owner', $list);
975
            Sympa::Report::reject_report_cmd(
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
                'intern',
                "Unable to send subrequest to $list->{'name'} list owner",
                {'listname' => $list->{'name'}},
                $cmd_line,
                $sender,
                $robot
            );
        }
        if ($list->store_subscription_request($sender, $comment)) {
            Log::do_log(
                'info',
                'SUB %s from %s forwarded to the owners of the list (%d seconds)',
                $which,
                $sender,
                tim