1 |
slords |
1.1 |
From 7bfad42ac9c07c2981e44a7ad891015a5bf75757 Mon Sep 17 00:00:00 2001 |
2 |
|
|
From: Hanno Hecker <vetinari@ankh-morp.org> |
3 |
|
|
Date: Fri, 6 Mar 2009 00:27:10 +0800 |
4 |
|
|
Subject: new plugin rcpt_map |
5 |
|
|
|
6 |
|
|
Check recipients from a postfix style map. The valid return codes are of course |
7 |
|
|
qpsmtpd constants. By storing the addresses in a %hash, this is much faster |
8 |
|
|
for fixed addresses than using the rcpt_regexp plugin just with fixed strings. |
9 |
|
|
This plugin handles only one domain per plugin instance. Use the :N suffix for |
10 |
|
|
the plugin if you need several domains mapped. |
11 |
|
|
--- |
12 |
|
|
plugins/rcpt_map | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
13 |
|
|
1 files changed, 187 insertions(+), 0 deletions(-) |
14 |
|
|
create mode 100644 plugins/rcpt_map |
15 |
|
|
|
16 |
|
|
diff --git a/plugins/rcpt_map b/plugins/rcpt_map |
17 |
|
|
new file mode 100644 |
18 |
|
|
index 0000000..727ae7d |
19 |
|
|
--- /dev/null |
20 |
|
|
+++ b/plugins/rcpt_map |
21 |
|
|
@@ -0,0 +1,187 @@ |
22 |
|
|
+ |
23 |
|
|
+=head1 NAME |
24 |
|
|
+ |
25 |
|
|
+rcpt_map - check recipients against recipient map |
26 |
|
|
+ |
27 |
|
|
+=head1 DESCRIPTION |
28 |
|
|
+ |
29 |
|
|
+B<rcpt_map> reads a list of adresses, return codes and comments |
30 |
|
|
+from the supplied config file. Adresses are compared with I<eq lc($rcpt)>. |
31 |
|
|
+The recipient addresses are checked against this list, and if the first |
32 |
|
|
+matches, the return code from that line and the comment are returned to |
33 |
|
|
+qpsmtpd. Return code can be any valid plugin return code from |
34 |
|
|
+L<Qpsmtpd::Constants>. Matching is always done case insenstive. |
35 |
|
|
+ |
36 |
|
|
+=head1 ARGUMENTS |
37 |
|
|
+ |
38 |
|
|
+The C<file MAP> and C<domain NAME> arguments are required. The default value of |
39 |
|
|
+the C<default> argument is C<DENY=No_such_user.> (see below why C<_>). |
40 |
|
|
+ |
41 |
|
|
+=over 4 |
42 |
|
|
+ |
43 |
|
|
+=item domain NAME |
44 |
|
|
+ |
45 |
|
|
+If the recipient address does not match this domain name NAME, this plugin will |
46 |
|
|
+return C<DECLINED> |
47 |
|
|
+ |
48 |
|
|
+=item file MAP |
49 |
|
|
+ |
50 |
|
|
+Use the config file as map file, format as explained below |
51 |
|
|
+ |
52 |
|
|
+=item default CODE[=MSG] |
53 |
|
|
+ |
54 |
|
|
+Use CODE as default return code (and return MSG as message) if a recipient |
55 |
|
|
+was B<not> found in the map. Since we can't use spaces in MSG, every C<_> |
56 |
|
|
+is replaced by a space, i.e. use C<DENY=User_not_found> if you want a deny |
57 |
|
|
+message C<User not found>. |
58 |
|
|
+ |
59 |
|
|
+=back |
60 |
|
|
+ |
61 |
|
|
+=head1 CONFIG FILE |
62 |
|
|
+ |
63 |
|
|
+The config file contains lines with an address, a return code and a comment, |
64 |
|
|
+which will be returned to the sender, if the code is not OK or DECLINED. |
65 |
|
|
+Example: |
66 |
|
|
+ |
67 |
|
|
+ # example_org_map - config for rcpt_map plugin |
68 |
|
|
+ me@example.org OK |
69 |
|
|
+ you@example.org OK |
70 |
|
|
+ info@example.org DENY User not found. |
71 |
|
|
+ |
72 |
|
|
+=head1 NOTES |
73 |
|
|
+ |
74 |
|
|
+We're currently running this plugin like shown in the following example. |
75 |
|
|
+ |
76 |
|
|
+Excerpt from the C<plugins> config file: |
77 |
|
|
+ |
78 |
|
|
+ ## list of valid users, config in /srv/qpsmtpd/config/rcpt_regexp |
79 |
|
|
+ ## ... except for "*@example.org": |
80 |
|
|
+ rcpt_regexp |
81 |
|
|
+ ## only for "@example.org": |
82 |
|
|
+ rcpt_map domain example.org file /srv/qpsmtpd/config/map_example_org |
83 |
|
|
+ |
84 |
|
|
+And the C<rcpt_regexp> config file: |
85 |
|
|
+ |
86 |
|
|
+ ### "example.org" addresses are checked later by the rcpt_map |
87 |
|
|
+ ### plugin, return DECLINED here: |
88 |
|
|
+ /^.*\@example\.org$/ DECLINED |
89 |
|
|
+ ### all other domains just check for valid users, the validity |
90 |
|
|
+ ### of the domain is checked by the rcpt_ok plugin => never use |
91 |
|
|
+ ### something else than "DENY" or "DECLINED" here! |
92 |
|
|
+ /^(abuse|postmaster)\@/ DECLINED |
93 |
|
|
+ /^(me|you)\@/ DECLINED |
94 |
|
|
+ /^.*$/ DENY No such user. |
95 |
|
|
+ |
96 |
|
|
+=head1 COPYRIGHT AND LICENSE |
97 |
|
|
+ |
98 |
|
|
+Copyright (c) 2009 Hanno Hecker |
99 |
|
|
+ |
100 |
|
|
+This plugin is licensed under the same terms as the qpsmtpd package itself. |
101 |
|
|
+Please see the LICENSE file included with qpsmtpd for details. |
102 |
|
|
+ |
103 |
|
|
+=cut |
104 |
|
|
+ |
105 |
|
|
+use Qpsmtpd::Constants; |
106 |
|
|
+ |
107 |
|
|
+our %map; |
108 |
|
|
+ |
109 |
|
|
+sub register { |
110 |
|
|
+ my ($self, $qp, %args) = @_; |
111 |
|
|
+ foreach my $arg (qw(domain file default)) { |
112 |
|
|
+ next unless exists $args{$arg}; |
113 |
|
|
+ if ($arg eq "default") { |
114 |
|
|
+ my ($code, $msg) = split /=/, $args{$arg}; |
115 |
|
|
+ $code = Qpsmtpd::Constants::return_code($code); |
116 |
|
|
+ unless (defined $code) { |
117 |
|
|
+ $self->log(LOGERROR, "Not a valid constant for 'default' arg"); |
118 |
|
|
+ die "Not a valid constant for 'default' arg"; |
119 |
|
|
+ } |
120 |
|
|
+ |
121 |
|
|
+ if ($msg) { |
122 |
|
|
+ $msg =~ s/_/ /g; |
123 |
|
|
+ } |
124 |
|
|
+ else { |
125 |
|
|
+ $msg = "No such user."; |
126 |
|
|
+ } |
127 |
|
|
+ |
128 |
|
|
+ $self->{_default} = [$code, $msg]; |
129 |
|
|
+ } |
130 |
|
|
+ else { |
131 |
|
|
+ $self->{"_$arg"} = $args{$arg}; |
132 |
|
|
+ } |
133 |
|
|
+ } |
134 |
|
|
+ |
135 |
|
|
+ $self->{_default} |
136 |
|
|
+ or $self->{_default} = [DENY, "No such user"]; |
137 |
|
|
+ |
138 |
|
|
+ $self->{_file} |
139 |
|
|
+ or die "No map file given..."; |
140 |
|
|
+ |
141 |
|
|
+ $self->log(LOGDEBUG, "Using file ".$self->{_file}); |
142 |
|
|
+ %map = $self->read_map(1); |
143 |
|
|
+ die "Empty map file" |
144 |
|
|
+ unless keys %map; |
145 |
|
|
+} |
146 |
|
|
+ |
147 |
|
|
+sub hook_pre_connection { |
148 |
|
|
+ my $self = shift; |
149 |
|
|
+ my ($time) = (stat($self->{_file}))[9] || 0; |
150 |
|
|
+ if ($time > $self->{_time}) { |
151 |
|
|
+ my %temp = $self->read_map(); |
152 |
|
|
+ keys %temp |
153 |
|
|
+ or return DECLINED; |
154 |
|
|
+ %map = %temp; |
155 |
|
|
+ } |
156 |
|
|
+ return DECLINED; |
157 |
|
|
+} |
158 |
|
|
+ |
159 |
|
|
+sub read_map { |
160 |
|
|
+ my $self = shift; |
161 |
|
|
+ my %hash = (); |
162 |
|
|
+ open F, $self->{_file} |
163 |
|
|
+ or do { $_[0] ? die "ERROR opening: $!" : return (); }; |
164 |
|
|
+ |
165 |
|
|
+ ($self->{_time}) = (stat(F))[9] || 0; |
166 |
|
|
+ |
167 |
|
|
+ my $line = 0; |
168 |
|
|
+ while (<F>) { |
169 |
|
|
+ ++$line; |
170 |
|
|
+ s/^\s*//; |
171 |
|
|
+ next if /^#/; |
172 |
|
|
+ next unless $_; |
173 |
|
|
+ my ($addr, $code, $msg) = split ' ', $_, 3; |
174 |
|
|
+ next unless $addr; |
175 |
|
|
+ |
176 |
|
|
+ unless ($code) { |
177 |
|
|
+ $self->log(LOGERROR, |
178 |
|
|
+ "No constant in line $line in ".$self->{_file}); |
179 |
|
|
+ next; |
180 |
|
|
+ } |
181 |
|
|
+ $code = Qpsmtpd::Constants::return_code($code); |
182 |
|
|
+ unless (defined $code) { |
183 |
|
|
+ $self->log(LOGERROR, |
184 |
|
|
+ "Not a valid constant in line $line in ".$self->{_file}); |
185 |
|
|
+ next; |
186 |
|
|
+ } |
187 |
|
|
+ $msg or $msg = "No such user."; |
188 |
|
|
+ $hash{$addr} = [$code, $msg]; |
189 |
|
|
+ } |
190 |
|
|
+ return %hash; |
191 |
|
|
+} |
192 |
|
|
+ |
193 |
|
|
+sub hook_rcpt { |
194 |
|
|
+ my ($self, $transaction, $recipient) = @_; |
195 |
|
|
+ return (DECLINED) |
196 |
|
|
+ unless $recipient->host && $recipient->user; |
197 |
|
|
+ |
198 |
|
|
+ return (DECLINED) |
199 |
|
|
+ unless lc($recipient->host) eq $self->{_domain}; |
200 |
|
|
+ |
201 |
|
|
+ my $rcpt = lc $recipient->user . '@' . lc $recipient->host; |
202 |
|
|
+ return (@{$self->{_default}}) |
203 |
|
|
+ unless exists $map{$rcpt}; |
204 |
|
|
+ |
205 |
|
|
+ return @{$map{$rcpt}}; |
206 |
|
|
+} |
207 |
|
|
+ |
208 |
|
|
+# vim: ts=4 sw=4 expandtab syn=perl |
209 |
|
|
-- |
210 |
|
|
1.7.2.2 |
211 |
|
|
|