root/bin/pl-incoming

Revision 326:6fe792e6c8f1, 6.7 KB (checked in by Anselm Lingnau <anselm@…>, 9 months ago)

Big get_config() cleanup to get rid of hard_coded defaults.
We're now getting parameter defaults from a central list instead of putting
them near the get_config() calls themselves, to avoid possible inconsistencies.
If for whatever reasons parameters end up with obviously invalid values, we
still try to do the Right Thing(TM).

  • Property exe set to *
Line 
1#!/usr/bin/perl -w
2
3=head1 NAME
4
5pl-incoming - process incoming messages on a Lancelot list
6
7=head1 SYNOPSIS
8
9pl-incoming [-dv?] [--user=USER] list@domain
10
11=head1 DESCRIPTION
12
13When a Lancelot list is installed, the local mail server must be directed
14to forward all messages that arrive on the list address to B<pl-incoming>,
15e.g., by setting up suitable aliases. The B<pl-incoming> program parses
16the incoming message and launches an appropriate workflow, for example
17to post a message to all subscribers, or to handle a subscription or
18unsubscription request.
19
20B<pl-incoming> reads a message from standard input and considers its
21"To:" address. For a list called I<list>@I<domain>, several
22possibilities exist:
23
24  I<list>@I<domain>
25  I<list>-help@I<domain>
26  I<list>-owner@I<domain>
27  I<list>-subscribe@I<domain>
28  I<list>-subscribe-I<address>@I<domain>
29  I<list>-unsubscribe@I<domain>
30  I<list>-unsubscribe-I<address>@I<domain>
31  I<list>-config@I<domain>
32  I<list>-confirm-I<cookie>@I<domain>
33  I<list>-bounce-I<address>-I<message>@I<domain>
34
35(not all of which are currently implemented). The first word after I<list>
36(or "submit" for I<list>@I<domain>) identifies a "workflow" to be used
37for processing the message. Workflows are defined by the list owner and
38(usually) stored in the list's configuration database, and consist of a
39number of successive steps to be taken with the message.
40
41Normally B<pl-incoming> searches for the list's configuration within the
42home directory of the user running it. The B<--user> option specifies
43a different user whose home directory is to be considered instead. Note that
44the directories and files in question must be searchable/readable by the
45user running B<pl-incoming>; the option does not arrange for any rights to
46be available to that user that they would not otherwise have. The idea behind
47this option is to be able to refer to individual users' mailing lists from
48system-wide aliases files.
49
50=head1 SEE ALSO
51
52pl-conf(1), pl-init(1)
53
54=head1 AUTHOR
55
56Anselm Lingnau <anselm@anselms.net>
57
58=head1 COPYRIGHT AND LICENSE
59
60Copyright 2004 by Anselm Lingnau. This program is free software; you
61may redistribute it and/or modify it under the terms of the GNU
62General Public License as published by the Free Software Foundation;
63either version 2 of the License, or (at your option) any later version.
64
65This program is distributed in the hope that it will be useful, but
66WITHOUT ANY WARRANTY; without even the implied warranty of
67MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
68General Public License for more details.
69
70You should have received a copy of the GNU General Public License
71along with this program; if not, refer to
72<URL:http://www.gnu.org/copyleft/gpl.html>. You may also obtain it by
73writing to the Free Software Foundation, Inc., 59 Temple Place - Suite
74330, Boston, MA 02111-1307, USA.
75
76=cut
77
78use strict;
79
80# Make Binary find it's libraries for non standard installations ...
81use Config;
82use File::Basename;
83use vars qw($g_base_dir);
84BEGIN {
85    use FindBin qw($Bin);
86    $g_base_dir = dirname ($Bin);
87}
88use lib "$g_base_dir/lib";
89use lib "$g_base_dir/lib/perl5/$Config{version}";
90use lib "$g_base_dir/lib/perl5/site_perl/$Config{version}";
91
92use Lancelot::GetOpt qw/GetOptions/;
93
94use Lancelot::Message;
95use Lancelot::DB;
96use Lancelot::Workflow;
97use Lancelot::Send;
98
99use Lancelot::Log qw/log log_and_die/;
100
101use File::Spec;
102use Email::Address;
103
104my ($EX_TEMPFAIL) = 75;
105
106my (%opts);
107
108GetOptions(\%opts, -argvmin => 1, "no list address specified",
109                   -argvmax => 1, "too many arguments",
110                   "user|u=");
111
112my ($listaddr) = shift;
113
114# Read the message from standard input, and quit if it is one that we
115# have processed before.
116
117my ($msg) = new Lancelot::Message join "", <STDIN>;
118my $loop;
119exit 0 if defined ($loop = $msg->header("X-Loop")) && $loop =~ /^$listaddr:/i;
120
121my $db = new Lancelot::DB $listaddr, { user => $opts{user}, nodefault => 1 }
122           or die "couldn't find mailing list $listaddr\n";
123
124if (-f File::Spec->join($db->get_listdir(), "no-forward")) {
125    return $EX_TEMPFAIL;
126}
127
128if (my $tracefile = $db->get_config("mail.trace")) {
129    my $f = IO::File->new(">> $tracefile");
130    print $f "-" x 72, "\n";
131    print $f "Time: ", scalar(localtime), "\n";
132    print $f "-" x 72, "\n";
133    print $f $msg->as_string;
134    $f->close;
135}
136
137# Find the workflow name and possible arguments (such as an auxiliary
138# address for a subscription or VERP parameters on a bounce)
139
140my ($wfname) = parse_to($listaddr, $msg);
141$wfname = "help" if !$wfname || $wfname !~ /^[A-Za-z]\w*$/;
142
143my ($wf) = new Lancelot::Workflow "mail.workflow", $wfname,
144                                  $db, $msg, \&bounce;
145my ($ret) = $wf->execute;
146$ret = $EX_TEMPFAIL if $ret == 1;
147
148exit $ret;
149
150# parse_to - Parse the message's (SMTP) »To« address to find the workflow
151#            and arguments
152
153sub parse_to {
154    my ($listaddr, $msg) = @_;
155    my ($l_local) = $listaddr =~ /^(.*)\@/;
156    my ($h_local) = (Email::Address->parse($msg->header("Delivered-To")))[0]->user;
157
158    log "info", "processing message to $listaddr ($h_local, $l_local)";
159
160    unless ($h_local =~ s/^$l_local//) {
161        # policy error -- actual header local part does not match list address
162        return undef;
163    }
164
165    if ($h_local eq "") {
166        $msg->set_flag("workflow", "submit");
167        $msg->set_flag("argv", []);
168        return ("submit");
169    }
170    my ($delim) = $db->get_config("mail.delimiter");
171    log_and_die "crit", "mail.delimiter not set" unless $delim;
172    log "info", "h_local=$h_local delim=$delim";
173    if (my ($name, $args) = $h_local =~ /^\Q$delim\E([^-]+)(?:-(.*))?$/) {
174        $msg->set_flag("workflow", $name);
175        $msg->set_flag("argv", [ split /-/, ($args || '') ]);
176        return $name;
177    }
178    return undef;
179}
180
181# bounce -- Returns an error message to the original sender
182#
183# This is used as a catch-all procedure whenever a processing module has
184# something to complain about. Processing modules are supposed to return
185# either DROP or the name of a "template" used for the error message.
186
187sub bounce {
188    my ($db, $msg, $template, $argsref) = @_;
189    if ($template eq "DROP") {
190        log "info", "dropping message";
191    } elsif ($template eq "TEMPFAIL") {
192        log "info", "returning EX_TEMPFAIL";
193        return $EX_TEMPFAIL;
194    } else {
195        log "info", "bouncing message back to %s: %s",
196            $msg->header("Return-Path"), $template;
197
198        my $rv = Lancelot::Send::sendtemplate($template, $db, $msg, $argsref);
199        log "err", "error bouncing message: $rv" unless $rv;
200    }
201    return 0;
202}
203
204# For debugging.
205
206sub dumpmsg {
207    my ($msg) = @_;
208
209    my ($sep) = "-" x 72 . "\n";
210    print STDERR $sep;
211    print STDERR "SMTP To: ", $msg->header("Delivered-To"), "\n";
212    print STDERR "SMTP From: ", $msg->header("Return-Path"), "\n";
213    print STDERR $sep;
214    print $msg->as_string;
215    print STDERR $sep;
216}
Note: See TracBrowser for help on using the browser.