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 |
|