File Coverage

lib/Haineko/SMTPD/Response.pm
Criterion Covered Total %
statement 76 76 100.0
branch 24 34 70.5
condition 13 24 54.1
subroutine 9 9 100.0
pod 5 5 100.0
total 127 148 85.8


line stmt bran cond sub pod time code
1             package Haineko::SMTPD::Response;
2 9     9   5659 use feature ':5.10';
  9         19  
  9         1159  
3 9     9   65 use strict;
  9         21  
  9         267  
4 9     9   43 use warnings;
  9         13  
  9         242  
5 9     9   2466 use Class::Accessor::Lite;
  9         5503  
  9         67  
6              
7             my $rwaccessors = [
8             'dsn', # (String) Delivery Status Notifier
9             'code', # (Integer) SMTP reply code
10             'host', # (String) SMTP server name
11             'port', # (Integer) Port number of the server
12             'rcpt', # (String) Recipient address
13             'error', # (Integer) 0 = OK, 1 = NG
14             'mailer', # (String) Mailer name
15             'command', # (String) SMTP Command
16             'message', # (ArrayRef) Reply messages
17             'greeting', # (ArrayRef) EHLO Greeting response
18             ];
19             my $roaccessors = [];
20             my $woaccessors = [];
21             Class::Accessor::Lite->mk_accessors( @$rwaccessors );
22              
23             my $Replies = {
24             'conn' => {
25             'ok' => {
26             'dsn' => undef,
27             'code' => 220,
28             'message' => [ 'ESMTP Haineko' ],
29             },
30             'cannot-connect' => {
31             'dsn' => undef,
32             'code' => 421,
33             'message' => [ 'Cannot connect SMTP Server' ],
34             },
35             'detect-loop' => {
36             'dsn' => undef,
37             'code' => 421,
38             'message' => [ 'Detected message loop' ],
39             },
40             },
41             'http' => {
42             'method-not-supported' => {
43             'dsn' => undef,
44             'code' => 421,
45             'message' => [ 'GET method not supported' ],
46             },
47             'malformed-json' => {
48             'dsn' => undef,
49             'code' => 421,
50             'message' => [ 'Malformed JSON string' ],
51             },
52             'not-found' => {
53             'dsn' => undef,
54             'code' => 421,
55             'message' => [ 'Not found' ],
56             },
57             'forbidden' => {
58             'dsn' => undef,
59             'code' => 500,
60             'message' => [ 'Access denied' ],
61             },
62             'server-error' => {
63             'dsn' => undef,
64             'code' => 500,
65             'message' => [ 'Internal Server Error' ],
66             },
67             },
68             'conf' => {
69             'not-looks-like-number' => {
70             'dsn' => undef,
71             'code' => 500,
72             'message' => [ 'does not look like number' ],
73             },
74             },
75             'ehlo' => {
76             'invalid-domain' => { # 501 5.0.0 Invalid domain name
77             'dsn' => '5.0.0',
78             'code' => 501,
79             'message' => [ 'Invalid domain name' ],
80             },
81             'require-domain' => { # 501 5.0.0 EHLO requires domain address
82             'dsn' => '5.0.0',
83             'code' => 501,
84             'message' => [ 'EHLO requires domain address' ],
85             },
86             'helo-first' => { # 503 5.0.0 Polite people say HELO first
87             'dsn' => '5.0.0',
88             'code' => 503,
89             'message' => [ 'Polite people say HELO first' ],
90             },
91             },
92             'auth' => {
93             'no-checkrelay' => {
94             'dsn' => '5.7.4',
95             'code' => 500,
96             'message' => [ 'Security features not supported' ],
97             },
98             'auth-required' => {
99             'dsn' => '5.7.1',
100             'code' => 500,
101             'message' => [ 'Access denied. Proper authentication required.' ],
102             },
103             'access-denied' => {
104             'dsn' => '5.7.1',
105             'code' => 500,
106             'message' => [ 'Access denied' ],
107             },
108             'cannot-decode' => { # 501 5.5.4 cannot decode AUTH parameter
109             'dsn' => '5.5.4',
110             'code' => 501,
111             'message' => [ 'cannot decode AUTH parameter' ],
112             },
113             'auth-failed' => { # 535 5.7.0 authentication failed
114             'dsn' => '5.7.0',
115             'code' => 535,
116             'message' => [ 'authentication failed' ],
117             },
118             'unavailable-mech' => { # 504 5.3.3 AUTH mechanism * not available
119             'dsn' => '5.3.3',
120             'code' => 504,
121             'message' => [ 'Unavailable AUTH mechanism' ],
122             },
123             'no-auth-mech' => { # 501 5.5.2 AUTH mechanism must be specified
124             'dsn' => '5.5.2',
125             'code' => 501,
126             'message' => [ 'AUTH mechanism must be specified' ],
127             },
128             },
129             'mail' => {
130             'ok' => {
131             'dsn' => '2.1.0',
132             'code' => 250,
133             'message' => [ 'Sender ok' ],
134             },
135             'sender-specified' => { # 503 5.5.0 Sender already specified
136             'dsn' => '5.5.0',
137             'code' => 503,
138             'message' => [ 'Sender already specified' ],
139             },
140             'domain-required' => { # 553 5.5.4 <*>... Domain name required for sender address *
141             'dsn' => '5.5.4',
142             'code' => 553,
143             'message' => [ 'Domain name required for sender address' ],
144             },
145             'syntax-error' => { # 501 5.5.2 Syntax error in parameters scanning "FROM"
146             'dsn' => '5.5.2',
147             'code' => 501,
148             'message' => [ 'Syntax error in parameters scanning "FROM"' ],
149             },
150             'domain-does-not-exist' => { # 553 5.1.8 <*>... Domain of sender address * does not exist
151             'dsn' => '5.1.8',
152             'code' => 553,
153             'message' => [ 'Domain of sender address does not exist' ],
154             },
155             'need-mail' => { # 503 5.0.0 Need MAIL before RCPT
156             'dsn' => '5.0.0',
157             'code' => 503,
158             'message' => [ 'Need MAIL before RCPT' ],
159             },
160             'non-ascii' => { # non-ASCII addresses are not permitted
161             'dsn' => '5.6.7',
162             'code' => 553,
163             'message' => [ 'non-ASCII address is not permitted' ],
164             },
165             },
166             'rcpt' => {
167             'ok' => { # 250 2.1.5 <*@*>... Recipient ok
168             'dsn' => '2.1.5',
169             'code' => 250,
170             'message' => [ 'Recipient ok' ],
171             },
172             'syntax-error' => { # 501 5.5.2 Syntax error in parameters scanning "TO"
173             'dsn' => '5.5.2',
174             'code' => 501,
175             'message' => [ 'Syntax error in parameters scanning "TO"' ],
176             },
177             'address-required' => { # 553 5.0.0 <>... User address required
178             'dsn' => '5.0.0',
179             'code' => 553,
180             'message' => [ 'User address required' ],
181             },
182             'too-many-recipients' => { # 452 4.5.3 Too many recipients
183             'dsn' => '4.5.3',
184             'code' => 452,
185             'message' => [ 'Too many recipients' ],
186             },
187             'is-not-emailaddress' => {
188             'dsn' => '5.1.5',
189             'code' => 553,
190             'message' => [ 'Recipient address is invalid' ],
191             },
192             'need-rcpt' => { # 503 5.0.0 Need RCPT (recipient)
193             'dsn' => '5.0.0',
194             'code' => 503,
195             'message' => [ 'Need RCPT (recipient)' ],
196             },
197             'rejected' => {
198             'dsn' => '5.7.1',
199             'code' => 553,
200             'message' => [ 'Recipient address is not permitted' ],
201             },
202             },
203             'data' => {
204             'ok' => { # 250 2.0.0 r5H6WfHC023944 Message accepted for delivery
205             'dsn' => '2.0.0',
206             'code' => 250,
207             'message' => [ 'Message accepted for delivery' ],
208             },
209             'enter-mail' => { # 354 Enter mail, end with "." on a line by itself
210             'dsn' => undef,
211             'code' => 354,
212             'message' => [ 'Enter mail' ],
213             },
214             'mesg-too-big' => { # 552 5.2.3 Message size exceeds fixed maximum message size (10485760)
215             'dsn' => '5.2.3',
216             'code' => 552,
217             'message' => [ 'Message size exceeds fixed maximum message size' ],
218             },
219             'empty-body' => {
220             'dsn' => '5.6.0',
221             'code' => 500,
222             'message' => [ 'Message body is empty' ],
223             },
224             'empty-subject' => {
225             'dsn' => '5.6.0',
226             'code' => 500,
227             'message' => [ 'Subject header is empty' ],
228             },
229             'discard' => {
230             'dsn' => undef,
231             'code' => 200,
232             'message' => [ 'Discard' ],
233             },
234             },
235             'rset' => {
236             'ok' => { # 250 2.0.0 Reset state
237             'dsn' => '2.0.0',
238             'code' => 250,
239             'message' => [ 'Reset state' ],
240             },
241             },
242             'vrfy' => { # 252 2.5.2 Cannot VRFY user; try RCPT to attempt delivery (or try finger)
243             'cannot-vrfy' => {
244             'dsn' => '2.5.2',
245             'code' => 252,
246             'message' => [ 'Cannot VRFY user; try RCPT to attempt delivery (or try finger)' ],
247             },
248             },
249             'verb' => { # 502 5.7.0 Verbose unavailable
250             'verb-unavailable' => {
251             'dsn' => '5.7.0',
252             'code' => 502,
253             'message' => [ 'Verbose unavailable' ],
254             },
255             },
256             'noop' => { # 250 2.0.0 OK
257             'ok' => {
258             'dsn' => '2.0.0',
259             'code' => 250,
260             'message' => [ 'OK' ],
261             },
262             },
263             'quit' => {
264             'ok' => {
265             'dsn' => '2.0.0',
266             'code' => 221,
267             'message' => [ 'closing connection' ],
268             },
269             },
270              
271             };
272              
273             sub new {
274 75     75 1 4817 my $class = shift;
275 75         483 my $argvs = { @_ };
276              
277 75         118 while(1) {
278 75 100       242 last unless exists $argvs->{'message'};
279 66 100       324 last unless ref $argvs->{'message'} eq 'ARRAY';
280 65 100       76 last unless scalar @{ $argvs->{'message'} };
  65         193  
281              
282 63         92 for my $r ( @{ $argvs->{'message'} } ) {
  63         201  
283 64 50       142 next unless defined $r;
284 64         124 chomp $r;
285 64         112 $r =~ s|\r\n||g;
286 64         264 $r =~ s|\A *||;
287 64         522 $r =~ s| *\z||;
288             }
289 63         105 last;
290             }
291 75         538 return bless $argvs, __PACKAGE__;
292             }
293              
294             sub r {
295 64     64 1 53381 my $class = shift;
296 64   100     175 my $esmtp = shift || return undef; # (String) SMTP Command
297 63   100     178 my $rname = shift || return undef; # (String) Response name
298 61   50     283 my $mesgs = shift || []; # (String) Additional messages
299 61         204 my $argvs = {};
300              
301 61 100       270 return undef unless grep { $esmtp eq $_ } keys %$Replies;
  793         1121  
302 60 50       143 return undef unless grep { $rname eq $_ } keys %{ $Replies->{ $esmtp } };
  320         572  
  60         213  
303              
304 60         93 for my $e ( keys %{ $Replies->{ $esmtp }->{ $rname } } ) {
  60         249  
305             # Get the defined message and code
306 180         495 $argvs->{ $e } = $Replies->{ $esmtp }->{ $rname }->{ $e };
307             }
308              
309 60 50       169 $argvs->{'message'} = $mesgs if scalar @$mesgs;
310 60         153 $argvs->{'command'} = uc $esmtp;
311 60 100       367 $argvs->{'error'} = $argvs->{'code'} =~ m/\A[45]\d+/ ? 1 : 0;
312 60         277 return __PACKAGE__->new( %$argvs );
313             }
314              
315             sub p {
316 1     1 1 868 my $class = shift;
317 1         4 my $argvs = { @_ };
318 1         2 my $lines = [];
319 1   50     29 my $nekor = {
      50        
      50        
      50        
      50        
      50        
320             'dsn' => undef,
321             'code' => $argvs->{'code'} // undef,
322             'host' => $argvs->{'host'} // undef,
323             'port' => $argvs->{'port'} // undef,
324             'rcpt' => $argvs->{'rcpt'} // undef,
325             'error' => 0,
326             'mailer' => $argvs->{'mailer'} // undef,
327             'message' => [],
328             'command' => uc( $argvs->{'command'} // q() ),
329             };
330              
331 1 50       5 $lines = ref $argvs->{'message'} eq 'ARRAY' ? $argvs->{'message'} : [ $argvs->{'message'} ];
332 1         5 while( my $r = shift @$lines ) {
333             # Parse the response from external SMTP server
334 1         4 $r =~ s|\r\n||g;
335 1         8 $r =~ s|\A *||;
336 1         7 $r =~ s| *\z||;
337 1 50       24 $nekor->{'dsn'} = $1 if $r =~ /\b([2345][.]\d[.]\d+)\b/;
338 1 50       11 $nekor->{'code'} = $1 if $r =~ /\b([2345]\d\d)\b/;
339 1         2 push @{ $nekor->{'message'} }, $r;
  1         5  
340             }
341              
342 1 50 33     11 $nekor->{'error'} = 1 if( defined $nekor->{'dsn'} && $nekor->{'dsn'} =~ /\A[45]/ );
343 1 50 33     8 $nekor->{'error'} = 1 if( defined $nekor->{'code'} && $nekor->{'code'} =~ /\A[45]/ );
344 1         7 return __PACKAGE__->new( %$nekor );
345             }
346              
347             sub mesg {
348 2     2 1 3514 my $self = shift;
349 2         5 my $argv = shift; # (Ref->Array) New messages
350 2         3 my $mesg = undef;
351              
352 2 100       10 return $self unless $argv;
353              
354 1 50       5 $self->{'message'} = [] unless ref $self->{'message'} eq 'ARRAY';
355 1 50       5 $mesg = ref $argv eq 'ARRAY' ? $argv : [ $argv ];
356 1         2 push @{ $self->{'message'} }, @$mesg;
  1         2  
357              
358 1         4 return $self;
359             }
360              
361             sub damn {
362 31     31 1 2963 my $self = shift;
363 31         52 my $smtp = {};
364              
365 31         68 for my $e ( @$rwaccessors, @$roaccessors ) {
366 310 100       585 next if $e eq 'greeting';
367 279         598 $smtp->{ $e } = $self->{ $e };
368             }
369 31         164 return $smtp;
370             }
371              
372             1;
373             __END__