File Coverage

blib/lib/Mail/Salsa/Action/Post.pm
Criterion Covered Total %
statement 24 251 9.5
branch 0 174 0.0
condition 0 68 0.0
subroutine 8 21 38.1
pod 0 12 0.0
total 32 526 6.0


line stmt bran cond sub pod time code
1             #
2             # Mail/Salsa/Action/Post.pm
3             # Last Modification: Wed Mar 17 19:11:38 WET 2010
4             #
5             # Copyright (c) 2010 Henrique Dias .
6             # All rights reserved.
7             # This module is free software; you can redistribute it and/or modify
8             # it under the same terms as Perl itself.
9             #
10             package Mail::Salsa::Action::Post;
11              
12 2     2   21255 use 5.008000;
  2         7  
  2         78  
13 2     2   12 use strict;
  2         3  
  2         66  
14 2     2   9 use warnings;
  2         9  
  2         75  
15              
16             require Exporter;
17             #use AutoLoader qw(AUTOLOAD);
18 2     2   2115 use SelfLoader;
  2         13016  
  2         277  
19 2     2   521 use Mail::Salsa::Utils qw(file_path create_file generate_id);
  2         5  
  2         246  
20 2     2   11 use Mail::Salsa::Logs qw(logs debug);
  2         4  
  2         95  
21 2     2   556 use Mail::Salsa::Archive qw(archive_msg);
  2         7  
  2         116  
22 2     2   2320 use MIME::QuotedPrint qw();
  2         4968  
  2         20696  
23              
24             # Items to export into callers namespace by default. Note: do not export
25             # names by default without a very good reason. Use EXPORT_OK instead.
26             # Do not simply export all your public functions/methods/constants.
27              
28             # This allows declaration use Mail::Salsa ':all';
29             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
30             # will save memory.
31              
32             our %EXPORT_TAGS = ( 'all' => [ qw() ] );
33             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
34             our @EXPORT = qw();
35             our $VERSION = '0.05';
36              
37             SelfLoader->load_stubs();
38              
39             sub new {
40 0     0 0   my $proto = shift;
41 0   0       my $class = ref($proto) || $proto;
42 0           my $self = {@_};
43 0           bless ($self, $class);
44 0           $self->process_msg();
45 0           return($self);
46             }
47              
48             sub process_msg {
49 0     0 0   my $self = shift;
50              
51 0           my ($name, $domain) = split(/\@/, $self->{'list'});
52 0 0         unless($self->check_restrict('restrict.txt', $self->{'headers'}->{'0.0'}->{'received'}, [])) {
53 0           Mail::Salsa::Utils::tplsendmail(
54             smtp_server => $self->{'smtp_server'},
55             timeout => $self->{'timeout'},
56             label => "PERMISSION_DENY",
57             lang => $self->{'config'}->{'language'},
58             vars => {
59             from => "$name\-owner\@$domain",
60             to => $self->{'from'},
61             list => $self->{'list'},
62             }
63             );
64 0           $self->logs(join("", "[permission deny] from: ", $self->{'from'}), "list");
65 0           return();
66             }
67 0 0 0       if($self->{'config'}->{'max_message_size'} && (((-s $self->{'message'})/1024) > $self->{'config'}->{'max_message_size'})) {
68 0           Mail::Salsa::Utils::tplsendmail(
69             smtp_server => $self->{'smtp_server'},
70             timeout => $self->{'timeout'},
71             label => "MAX_MESSAGE_SIZE",
72             lang => $self->{'config'}->{'language'},
73             vars => {
74             from => "$name\-owner\@$domain",
75             to => $self->{'from'},
76             list => $self->{'list'},
77             size => $self->{'config'}->{'max_message_size'},
78             }
79             );
80 0           $self->logs(join("", "[max message size exceed] from: ", $self->{'from'}), "list");
81 0           return();
82             }
83 0 0         if(scalar(keys(%{$self->{'headers'}})) > 1) {
  0            
84 0           my $attachfile = file_path($self->{'list'}, $self->{'list_dir'}, "attachments.txt");
85 0           my $listfile = file_path($self->{'list'}, $self->{'list_dir'}, "list.txt");
86 0           my ($code, $access, $mime_type) = ("", "allow", "");
87 0 0         if(my $error = &generate_code($listfile, $attachfile, $self->{'from'}, $code)) {
88 0           $self->logs("[file] $error", "errors");
89 0           return();
90             }
91 0           eval($code);
92 0 0         if($@) {
93 0           $self->logs("[eval] $@", "errors");
94 0           return();
95             }
96 0 0         if($access eq "deny") {
97 0           Mail::Salsa::Utils::tplsendmail(
98             smtp_server => $self->{'smtp_server'},
99             timeout => $self->{'timeout'},
100             label => "NO_ATTACHMENTS",
101             lang => $self->{'config'}->{'language'},
102             vars => {
103             from => "$name\-owner\@$domain",
104             to => $self->{'from'},
105             list => $self->{'list'},
106             mime_type => $mime_type,
107             }
108             );
109 0           $self->logs(join("", "[deny attachment] mime-type: $mime_type from: ", $self->{'from'}), "list");
110 0           return();
111             }
112             }
113              
114 0 0         $self->setup_stamp() if($self->{'config'}->{'stamp'} eq "y");
115 0 0         my $reply = ($self->{'headers'}->{'0.0'}->{'subject'}->{'value'} =~ /^Re: /i) ? 1 : 0;
116 0           my $bounce = $self->check4bounces();
117 0           my $human = $self->setup_msg();
118 0 0         $human = 0 unless($self->{'stamp'});
119              
120 0 0 0       if($bounce == 2) {
    0 0        
    0          
121             # debug and test
122 0           $self->logs(join("", "[been-there] from: ", $self->{'from'}), "list");
123             } elsif($bounce && $self->{'config'}->{'accept_bounces'} eq "n") {
124 0           Mail::Salsa::Utils::tplsendmail(
125             smtp_server => $self->{'smtp_server'},
126             timeout => $self->{'timeout'},
127             label => "DONT_BOUNCE",
128             lang => $self->{'config'}->{'language'},
129             vars => {
130             from => "$name\-owner\@$domain",
131             to => $self->{'from'},
132             list => $self->{'list'},
133             }
134             );
135 0           $self->logs(join("", "[bounce] from: ", $self->{'from'}), "list");
136             } elsif($self->{'stamp'} && !$human) {
137 0           Mail::Salsa::Utils::tplsendmail(
138             smtp_server => $self->{'smtp_server'},
139             timeout => $self->{'timeout'},
140             label => "MAILSTAMP",
141             lang => $self->{'config'}->{'language'},
142             vars => {
143             from => "$name\-owner\@$domain",
144             to => $self->{'from'},
145             list => $self->{'list'},
146             stamp => $self->{'stamp'}
147             }
148             );
149 0           $self->logs(join("", "[sent stamp] to: ", $self->{'from'}), "list");
150             } else {
151 0           $self->sendmail4all();
152 0           $self->logs(join("", "[post message] from: ", $self->{'from'}), "list");
153 0 0         $self->archive_msg() if($self->{'config'}->{'archive'} eq "y");
154             }
155 0           unlink($self->{'message'});
156 0           return();
157             }
158              
159             sub check4bounces {
160 0     0 0   my $self = shift;
161              
162 0           my $bounce = 1;
163 0           my $list = $self->{'list'};
164 0 0         $bounce = 0 if(lc($self->{'headers'}->{'0.0'}->{'to'}) =~ /\b$list\b/);
165 0 0 0       $bounce = 0 if(exists($self->{'headers'}->{'0.0'}->{'cc'}) && lc($self->{'headers'}->{'0.0'}->{'cc'}) =~ /\b$list\b/);
166 0 0 0       $bounce = 2 if(exists($self->{'headers'}->{'0.0'}->{'x-been-there'}) && $self->{'headers'}->{'0.0'}->{'x-been-there'} eq $self->{'list'});
167              
168 0           return($bounce);
169             }
170              
171             sub setup_stamp {
172 0     0 0   my $self = shift;
173              
174 0           my $sfile = file_path($self->{'list'}, $self->{'list_dir'}, 'stamp.txt');
175 0           my ($number, $letter) = ($self->{'config'}->{'stamp_life'} =~ /^(\d+)([dwmy])$/);
176              
177 0           my $days = int(-M $sfile);
178 0           my $newstamp = 0;
179              
180 0 0         if($letter eq "d") {
    0          
    0          
    0          
181 0 0         $newstamp = 1 if($days > $number);
182             } elsif($letter eq "w") {
183 0 0         $newstamp = 1 if(int($days/7) > $number);
184             } elsif($letter eq "m") {
185 0 0         $newstamp = 1 if(int($days/30) > $number);
186             } elsif($letter eq "y") {
187 0 0         $newstamp = 1 if(int($days/365) > $number);
188             }
189 0 0         if($newstamp) {
190 0           $self->{'stamp'} = uc(generate_id(32));
191 0           create_file($sfile, join("", $self->{'stamp'}, "\n"), 0600);
192 0           } else { $self->{'stamp'} = Mail::Salsa::Utils::get_key($sfile); }
193              
194 0           return();
195             }
196              
197             sub exist_subscriber {
198 0     0 0   my $file = shift;
199 0   0       my $string = shift || return(0);
200              
201 0           my $exist = 0;
202 0 0         open(MLIST, "<", $file) or return("$!");
203 0           while() {
204 0 0         next if(/^[\#\x0d\x0a]/);
205 0 0         if(/\b$string\b/) { $exist = 1; last; }
  0            
  0            
206             }
207 0           close(MLIST);
208 0           return($exist);
209             }
210              
211             sub generate_code {
212 0     0 0   my $listfile = shift;
213 0           my $attachfile = shift;
214 0           my $from_addr = shift;
215              
216 0           $_[0] .= <
217             for my \$part (keys(\%{\$self->{'headers'}})) {
218             \$mime_type = \$self->{'headers'}->{\$part}->{'content-type'}->{'value'};
219             local \$_ = \$mime_type;
220             next if(index(\$_, \"multipart\/\") > -1);
221             ENDCODE
222 0           my $subscriber = 0;
223 0 0         open(ATTACHMENT, "<", $attachfile) or return("$!");
224 0           while() {
225 0 0         next if(/^[\#\x0d\x0a]/);
226 0           my ($policy, $mime, $addr) = /^(\w+) +(\w+\/[\w\.\-\+]+) +from +(\S+)[\x0d\x0a]+/;
227 0 0         if($addr eq "subscribers") {
228 0 0         unless($subscriber) {
229 0           $subscriber = &exist_subscriber($listfile, $from_addr);
230 0 0         $subscriber = -1 unless($subscriber);
231             }
232 0 0         if($subscriber > 0) { $addr = $from_addr; }
  0            
233 0           else { next; }
234             }
235 0           $mime =~ s{any(?=\/)}{\[\\w\\+\\-\\.\]\+}g;
236 0           $mime =~ s{(?<=\/)any}{\[\\w\\+\\-\\.\]\+}g;
237 0           $mime =~ s{\/}{\\\/};
238 0           $addr =~ s/\@/\\\@/g;
239 0           $addr =~ s/\./\\\./g;
240 0 0         my $part = ($addr eq "any") ? "" : " (\$self-\>{\'from\'} =~ /$addr\$/) and";
241 0 0         my $keyword = $policy eq "deny" ? "last" : "next";
242 0           $_[0] .= "\t/\^$mime\$/ and$part \$access = \"$policy\", $keyword;\n";
243             }
244 0           close(ATTACHMENT);
245 0           $_[0] .= "}\n";
246 0           return();
247             }
248              
249             sub sendmail4all {
250 0     0 0   my $self = shift;
251              
252 0           my ($name, $domain) = split(/\@/, $self->{'list'});
253 0           my $listfile = file_path($self->{'list'}, $self->{'list_dir'}, "list.txt");
254 0           (my $outfile = $self->{'message'}) =~ s/\.msg$/\.out/;
255              
256             my $refsub = sub {
257 0     0     my $handle = shift;
258 0 0         open(FILE, "<", $outfile) or die("$!");
259 0           while() {
260 0           s{^\.}{\.\.};
261 0           print $handle $_;
262             }
263 0           close(FILE);
264 0           };
265 0           my $sm = Mail::Salsa::Sendmail->new(
266             smtp_server => $self->{'smtp_server'},
267             timeout => $self->{'timeout'},
268             smtp_port => 25,
269             );
270 0           $sm->everything(
271             mail_from => "$name\-return\@$domain",
272             list_file => $listfile,
273             data => $refsub
274             );
275 0           unlink($outfile);
276              
277 0           return();
278             }
279              
280             sub set_headers {
281 0     0 0   my $list = shift;
282              
283 0           my ($name, $domain) = split(/\@/, $list);
284 0           my $headers = <<"EOF";
285             X-Salsa-Version: 0.01
286             X-Been-There: $name\@$domain
287             Precedence: bulk
288             List-Help:
289             List-Unsubscribe:
290             List-Subscribe:
291             List-Admin:
292             List-Post: \
293             EOF
294 0           return($headers);
295             }
296              
297             sub insert_text {
298 0     0 0   my $handle = shift;
299 0           my $file = shift;
300 0           my $encoding = shift;
301              
302 0 0         open(BANNER, "<", $file) or die("$!");
303 0           while(local $_ = ) {
304 0 0         my $line = ($encoding eq "quoted-printable") ? MIME::QuotedPrint::encode($_) : $_;
305 0           print $handle $line;
306             }
307 0           close(BANNER);
308 0           print $handle "\n";
309              
310 0           return();
311             }
312              
313             sub check_encoding {
314 0   0 0 0   my $encoding = shift || return(1);
315              
316 0 0         return(length($encoding) == 0 ? 1 :
    0          
    0          
    0          
317             $encoding eq "quoted-printable" ? 1 :
318             $encoding eq "7bit" ? 1 :
319             $encoding eq "8bit" ? 1 : 0);
320             }
321              
322             sub setup_msg {
323 0     0 0   my $self = shift;
324            
325 0           my ($name, $domain) = split(/\@/, $self->{'list'});
326 0           (my $outfile = $self->{'message'}) =~ s/\.msg$/\.out/;
327              
328 0           my ($headerfile, $footerfile, $encoding) = ("", "", "");
329 0 0         if($self->{'config'}->{'header'} eq "y") {
330 0           $headerfile = file_path($self->{'list'}, $self->{'list_dir'}, "header.txt");
331 0 0 0       (-e $headerfile && -s $headerfile) or $headerfile = "";
332             }
333 0 0         if($self->{'config'}->{'footer'} eq "y") {
334 0           $footerfile = file_path($self->{'list'}, $self->{'list_dir'}, "footer.txt");
335 0 0 0       (-e $footerfile && -s $footerfile) or $footerfile = "";
336             }
337 0 0         my $boundary = exists($self->{'headers'}->{'0.0'}->{'content-type'}->{'boundary'}) ? $self->{'headers'}->{'0.0'}->{'content-type'}->{'boundary'} : "";
338 0 0         my ($tree, $count) = $boundary ? ("0.0.0", 0) : ("0.0", 1);
339              
340 0 0 0       if($headerfile || $footerfile) {
341 0 0 0       if(exists($self->{'headers'}->{$tree}->{'content-type'}->{'value'}) &&
342             $self->{'headers'}->{$tree}->{'content-type'}->{'value'} eq "text/plain") {
343 0 0         if(exists($self->{'headers'}->{$tree}->{'content-transfer-encoding'}->{'value'})) {
344 0           $encoding = $self->{'headers'}->{$tree}->{'content-transfer-encoding'}->{'value'};
345 0 0         $encoding = $footerfile = $headerfile = "" unless(&check_encoding($encoding));
346             }
347 0           } else { $footerfile = $headerfile = ""; }
348             }
349              
350 0   0       my $stamp = $self->{'stamp'} || "";
351 0           my $prefix = $self->{'config'}->{'prefix'};
352 0           my ($exist, $received, $headers, $topheaders) = (0, 1, 1, 1);
353 0 0         open(INFILE, "<", $self->{'message'}) or die("$!");
354 0 0         open(OUTFILE, ">", $outfile) or die("$!");
355 0           select(OUTFILE);
356 0           while() {
357 0 0         if($headers) {
358 0 0         if($topheaders) {
359 0 0         next if(&check_headers($_));
360 0 0 0       s/^Subject: (.+)/Subject: $prefix $1/o if($prefix && index($_, $prefix, 0) == -1);
361 0 0 0       $received = 0 if($received == 1 && !(/^(X-)?Received: / || /^[ \t]+/));
      0        
362 0 0         if($received == 0) {
363 0           print OUTFILE &set_headers($self->{'list'});
364 0           $received = -1;
365             }
366             }
367 0 0         $headers = 0 if(/^[\n\r]$/o);
368             } else {
369 0           $topheaders = 0;
370 0 0         $exist = 1 if(s/\b$stamp\b//og);
371 0 0 0       if($headerfile || $footerfile) {
372 0 0 0       if($headerfile && $count == 1) {
373 0           &insert_text(\*OUTFILE, $headerfile, $encoding);
374 0           $headerfile = "";
375             }
376 0 0 0       if($boundary && /^--$boundary/) {
377 0           $headers = 1;
378 0           $count++;
379             }
380 0 0 0       if($footerfile && $count == 2) {
381 0           &insert_text(\*OUTFILE, $footerfile, $encoding);
382 0           $footerfile = "";
383             }
384             }
385             }
386 0           print OUTFILE $_;
387             }
388 0 0         &insert_text(\*OUTFILE, $footerfile, $encoding) if($footerfile);
389 0           close(OUTFILE);
390 0           close(INFILE);
391              
392 0           select(STDOUT);
393 0           return($exist);
394             }
395              
396             sub check_restrict {
397 0     0 0   my $self = shift;
398 0           my $thisfile = shift;
399 0           my $receiveds = shift;
400 0   0       my $array = shift || [];
401              
402 0           my $file = file_path($self->{'list'}, $self->{'list_dir'}, $thisfile);
403 0 0         (-s $file) or return(1);
404 0           my $count = scalar(@{$array});
  0            
405             ### ["policy", "address", "action", "network", "stamp"] ###
406 0 0         $array = ["", "", "", "", ""] unless($count);
407 0           my $netok = 0;
408              
409 0 0         open(FILE, "<", $file) or return(1);
410 0           while() {
411 0 0         next if(/^[\#\n\r]+/);
412 0           chomp;
413 0 0         if($count) {
414 0           my ($addr) = (/\]+\@[^\@\<\>]+)\>?/);
415 0           $array->[1] = $addr;
416             } else {
417 0           (@{$array}) = (/^(allow|deny) +(\S+) +to +(post|bounce|proceed) +from +(localnet|anywhere)( +with(out)? +stamp)?/);
  0            
418             }
419 0 0         if($array->[1] eq "subscribers") {
420 0           $array->[1] = "";
421 0           $netok = $self->check_restrict('list.txt', $receiveds, $array);
422             } else {
423 0           $self->{'config'}->{'accept_bounces'} = ($self->{'config'}->{'stamp'} = "n");
424 0           my $address = $array->[1];
425 0 0         if($address eq "any") {
426 0 0 0       last if($array->[0] eq "deny" and $array->[3] eq "anywhere");
427 0           $address = "\.+";
428             }
429 0 0         if($self->{'from'} =~ /$address$/) {
430 0 0         $netok = ($array->[3] eq "anywhere") ? 1 :
    0          
431             ($array->[3] eq "localnet") ? &check_network($receiveds, $self->{'config'}->{'localnet'}) : 0;
432             }
433 0 0 0       if(defined($array->[4]) && $array->[4]) {
434 0 0         $self->{'config'}->{'stamp'} = ($array->[0] eq "allow") ?
    0          
    0          
435             (($array->[4] =~ /^ +without +stamp/) ? "n" : "y") :
436             (($array->[4] =~ /^ +without +stamp/) ? "y" : "n");
437             }
438 0 0 0       if($array->[2] eq "bounce" or $array->[2] eq "proceed") {
439 0 0         $self->{'config'}->{'accept_bounces'} = ($array->[0] eq "allow") ? "y" : ($self->{'config'}->{'stamp'} eq "y") ? "y" : "n";
    0          
440             }
441             }
442 0 0         last if($netok);
443             }
444 0           close(FILE);
445 0           return($netok);
446             }
447              
448             # Autoload methods go after =cut, and are processed by the autosplit program.
449              
450             1;
451             __DATA__