File Coverage

lib/Haineko/Sendmail.pm
Criterion Covered Total %
statement 225 515 43.6
branch 53 190 27.8
condition 44 154 28.5
subroutine 17 26 65.3
pod 0 1 0.0
total 339 886 38.2


line stmt bran cond sub pod time code
1             package Haineko::Sendmail;
2 1     1   854 use strict;
  1         2  
  1         36  
3 1     1   5 use warnings;
  1         2  
  1         28  
4 1     1   1062 use Encode;
  1         12578  
  1         101  
5 1     1   10 use Try::Tiny;
  1         2  
  1         55  
6 1     1   15469 use Time::Piece;
  1         36787  
  1         7  
7 1     1   104 use Scalar::Util;
  1         2  
  1         68  
8 1     1   1105 use Haineko::Log;
  1         4  
  1         37  
9 1     1   7 use Haineko::JSON;
  1         2  
  1         23  
10 1     1   7 use Haineko::Default;
  1         1  
  1         21  
11 1     1   7 use Haineko::SMTPD::Milter;
  1         2  
  1         10  
12 1     1   1186 use Haineko::SMTPD::Session;
  1         4  
  1         32  
13 1     1   8 use Haineko::SMTPD::Response;
  1         2  
  1         8589  
14              
15             sub submit {
16 12     12 0 26 my $class = shift;
17 12         28 my $httpd = shift; # (Haineko::HTTPD)
18              
19 12         36 my $serverconf = $httpd->{'conf'}->{'smtpd'};
20 12         52 my $defaultset = Haineko::Default->conf;
21 12         35 my $responsecn = 'Haineko::SMTPD::Response';
22 12         29 my $responsejk = 'response'; # (String) Response json key name
23 12         20 my $exceptions = 0; # (Integer) Flag, be set in try {...} catch { ... }
24 12         30 my $tmpsession = undef; # (Haineko::SMTPD::Session) Temporary session object
25              
26             # Create a queue id (session id)
27 12         111 my $queueident = Haineko::SMTPD::Session->make_queueid;
28              
29             # Variables related user information such as hostname or port number.
30 12   50     55 my $xforwarded = [ split( ',', $httpd->req->header('X-Forwarded-For') || q() ) ];
31 12   33     2868 my $remoteaddr = pop @$xforwarded || $httpd->req->address // undef;
      50        
32 12   50     217 my $remoteport = $httpd->req->env->{'REMOTE_PORT'} // undef;
33 12   50     125 my $remotehost = $httpd->req->env->{'REMOTE_HOST'} // undef;
34 12   50     119 my $remoteuser = $httpd->req->env->{'REMOTE_USER'} // undef;
35 12   50     131 my $useragent1 = $httpd->req->user_agent // undef;
36              
37             # Syslog object
38 12         523 my $syslogargv = {
39             'queueid' => $queueident,
40             'facility' => $serverconf->{'syslog'}->{'facility'},
41             'disabled' => $serverconf->{'syslog'}->{'disabled'},
42             'useragent' => $useragent1,
43             'remoteaddr' => $remoteaddr,
44             'remoteport' => $remoteport,
45             };
46 12         31 for my $e ( 'facility', 'disabled' ) {
47             # Fallback to the default value when these values are not defined in
48             # etc/haineko.cf
49 24   33     87 $syslogargv->{ $e } //= $defaultset->{'smtpd'}->{'syslog'}->{ $e };
50             }
51 12         136 my $nekosyslog = Haineko::Log->new( %$syslogargv );
52 12         29 my $esresponse = undef;
53              
54             # Create a new SMTP Session
55 12   50     48 $tmpsession = Haineko::SMTPD::Session->new(
56             'queueid' => $queueident,
57             'referer' => $httpd->req->referer // q(),
58             'useragent' => $useragent1,
59             'remoteaddr' => $remoteaddr,
60             'remoteport' => $remoteport );
61              
62              
63 12 100 66     64 if( $httpd->debug == 0 && $httpd->req->method eq 'GET' ) {
64             # GET method is not permitted in production mode.
65             # Use ``POST'' method instead.
66 1         28 $esresponse = $responsecn->r( 'http', 'method-not-supported' );
67 1         6 $tmpsession->add_response( $esresponse );
68 1         4 $nekosyslog->w( 'err', $esresponse->damn );
69              
70 1         7 return $httpd->res->json( 405, $tmpsession->damn );
71             }
72              
73             CONN: {
74             # ____ ___ _ _ _ _
75             # / ___/ _ \| \ | | \ | |
76             # | | | | | | \| | \| |
77             # | |__| |_| | |\ | |\ |
78             # \____\___/|_| \_|_| \_|
79             #
80             # Check the remote address
81 11         181 my $relayhosts = undef;
  11         25  
82 11         24 my $ip4network = undef;
83              
84             try {
85             # Check etc/relayhosts file. The remote host or the network should
86             # be listed in the file.
87 11     11   350 $exceptions = 0;
88 11         3602 require Net::CIDR::Lite;
89 11         7604 $relayhosts = Haineko::JSON->loadfile( $serverconf->{'access'}->{'conn'} );
90 11         26 $ip4network = Net::CIDR::Lite->new( @{ $relayhosts->{'relayhosts'} } );
  11         1507  
91              
92             } catch {
93 0     0   0 $exceptions = 1;
94 11         126 };
95              
96             # If etc/relayhosts file does not exist or failed to load,
97             # only 127.0.0.1 is permitted to relay.
98 11   33     311 $ip4network //= Net::CIDR::Lite->new( '127.0.0.1/32' );
99 11 50       50 $ip4network->add( '127.0.0.1/32' ) unless $ip4network->list;
100              
101             # Haineko relays an email from any to any when the remote user successfully
102             # authenticated by Haineko with etc/password file.
103 11 50       1525 $relayhosts->{'open-relay'} = 1 if $remoteuser;
104              
105 11 50       41 if( not $relayhosts->{'open-relay'} ) {
106             # When the value of ``openrelay'' is defined as ``0'' in etc/relayhosts,
107             # Only permitted host can send an email.
108 11 50       53 if( not $ip4network->find( $remoteaddr ) ) {
109             # The remote address or the remote network is not listed in
110             # etc/relayhosts.
111 0         0 $esresponse = $responsecn->r( 'auth', 'access-denied' );
112 0         0 $tmpsession->add_response( $esresponse );
113 0         0 $nekosyslog->w( 'err', $esresponse->damn );
114              
115 0         0 return $httpd->res->json( 403, $tmpsession->damn );
116             }
117             }
118              
119             XXFI_CONNECT: {
120             # Act like xxfi_connect() function
121 11   50     960 my $milterlibs = $serverconf->{'milter'}->{'conn'} || [];
  11         76  
122 11         22 my $mfresponse = undef;
123              
124 11         20 for my $e ( @{ Haineko::SMTPD::Milter->import( $milterlibs ) } ) {
  11         61  
125             # Check the remote address with conn() method of each milter
126 0         0 $mfresponse = $responsecn->new( 'code' => 421, 'command' => 'CONN' );
127 0 0       0 last if not $e->conn( $mfresponse, $remotehost, $remoteaddr );
128             }
129 11 50       126 last XXFI_CONNECT unless defined $mfresponse;
130 0 0       0 last XXFI_CONNECT unless $mfresponse->error;
131              
132             # Reject connection
133 0         0 $esresponse = $mfresponse;
134 0         0 $tmpsession->add_response( $esresponse );
135 0         0 $nekosyslog->w( 'err', $esresponse->damn );
136              
137 0         0 return $httpd->res->json( 400, $tmpsession->damn );
138             } # End of ``XXFI_CONNECT''
139              
140             } # End of ``CONN''
141              
142 11         40 my $headerlist = [ 'from', 'replyto', 'subject' ];
143 11         15 my $emencoding = q(); # Character set such as iSO-2022-JP, UTF-8, or ISO-8859-1.
144 11         22 my $recipients = []; # Recipient addresses specified in JSON
145 11         16 my $cannotsend = []; # Invalid recipient addresses checked by the following codes
146 11         22 my ( $ehlo, $mail, $rcpt, $head, $body, $json ) = undef;
147              
148             try {
149             # Load email data as a JSON
150 11     11   301 $exceptions = 0;
151 11         36 $json = Haineko::JSON->loadjson( $httpd->req->content );
152 10   33     64 $ehlo = $json->{'ehlo'} // $json->{'helo'} // q();
      50        
153 10   66     77 $mail = $json->{'mail'} // $json->{'send'} // $json->{'from'} // q();
      66        
      50        
154 10   66     87 $rcpt = $json->{'rcpt'} // $json->{'recv'} // $json->{'to'} // [];
      66        
      50        
155 10   100     67 $body = $json->{'body'} // q();
156 10         23 $head = {};
157              
158 10         25 for my $e ( @$headerlist ) {
159             # Load each email header
160 14 100       52 last unless ref $json->{'header'} eq 'HASH';
161 6 100       21 next unless defined $json->{'header'}->{ $e };
162              
163 2         6 $head->{ $e } = $json->{'header'}->{ $e };
164 2 50       19 utf8::decode $head->{ $e } unless utf8::is_utf8 $head->{ $e };
165             }
166              
167 10   33     144 $emencoding = $head->{'charset'} // $head->{'Charset'} // 'UTF-8';
      50        
168 10   100     70 $head->{'subject'} //= q();
169 10 50       53 utf8::decode $body unless utf8::is_utf8 $body;
170 10         39 $recipients = $rcpt;
171              
172             } catch {
173             # Failed to load the email body or email headers
174 1     1   12 $exceptions = 1;
175 1         10 $esresponse = $responsecn->r( 'http', 'malformed-json' );
176 1 50       5 $esresponse = $esresponse->mesg( $_ ) if $httpd->debug;
177 1         11 $tmpsession->add_response( $esresponse );
178 1         5 $nekosyslog->w( 'err', $esresponse->damn );
179 11         148 };
180 11 100       301 return $httpd->res->json( 400, $tmpsession->damn ) if $exceptions;
181              
182 10   50     68 DETECT_LOOP_DURING_HAINEKO_SERVERS: {
183             # _ ___
184             # | | ___ ___ _ __ |__ \
185             # | | / _ \ / _ \| '_ \ / /
186             # | |__| (_) | (_) | |_) | |_|
187             # |_____\___/ \___/| .__/ (_)
188             # |_|
189             # Check ``X-Haineko-Loop'' header
190 10         20 my $v = $head->{'x-haineko-loop'} || [];
191 10 50       35 if( ref $v eq 'ARRAY' ) {
192             # The value of X-Haineko-Loop is an array reference
193 10 50       27 if( scalar @$v ) {
194             # ``X-Haineko-Loop'' exists in received JSON
195             # "header": { ..., "x-haineko-loop": [] },
196 0 0       0 if( grep { $serverconf->{'servername'} eq $_ } @$v ) {
  0         0  
197             # DETECTED LOOP:
198             # THE MESSAGE HAS ALREADY PASSED THIS Haineko
199 0         0 $esresponse = $responsecn->r( 'conn', 'detect-loop' );
200 0         0 $tmpsession->add_response( $esresponse );
201 0         0 $nekosyslog->w( 'err', $esresponse->damn );
202              
203 0         0 return $httpd->res->json( 400, $tmpsession->damn );
204             }
205             } else {
206             # The header is empty or other data structure.
207 10         21 push @{ $head->{'x-haineko-loop'} }, $serverconf->{'servername'};
  10         53  
208             }
209             } else {
210             # The value of the header is not an array reference, set this hostname
211             # into X-Haineko-Loop header.
212 0         0 $head->{'x-haineko-loop'} = [ $serverconf->{'servername'} ];
213             }
214             }
215              
216             EHLO: {
217             # _____ _ _ _ ___
218             # | ____| | | | | / _ \
219             # | _| | |_| | | | | | |
220             # | |___| _ | |__| |_| |
221             # |_____|_| |_|_____\___/
222             #
223             # Check the value of ``ehlo'' field
224 10         17 require Haineko::SMTPD::RFC5321;
  10         2980  
225 10         59 require Haineko::SMTPD::RFC5322;
226              
227 10 100       105 if( not length $ehlo ) {
    100          
228             # The value is empty: { "ehlo": '', ... }
229 1         10 $esresponse = $responsecn->r( 'ehlo', 'require-domain' );
230 1         7 $tmpsession->add_response( $esresponse );
231 1         5 $nekosyslog->w( 'err', $esresponse->damn );
232              
233 1         7 return $httpd->res->json( 400, $tmpsession->damn );
234              
235             } elsif( not Haineko::SMTPD::RFC5321->check_ehlo( $ehlo ) ) {
236             # The value is invalid: { "ehlo": 1, ... }
237 1         11 $esresponse = $responsecn->r( 'ehlo', 'invalid-domain' );
238 1         7 $tmpsession->add_response( $esresponse );
239 1         5 $nekosyslog->w( 'err', $esresponse->damn );
240              
241 1         6 return $httpd->res->json( 400, $tmpsession->damn );
242             }
243              
244             XXFI_HELO: {
245             # Act like xxfi_helo() function
246 8   50     14 my $milterlibs = $serverconf->{'milter'}->{'ehlo'} || [];
  8         59  
247 8         15 my $mfresponse = undef;
248              
249 8         17 for my $e ( @{ Haineko::SMTPD::Milter->import( $milterlibs ) } ) {
  8         52  
250             # Check the EHLO value with ehlo() method of each milter
251 0         0 $mfresponse = $responsecn->new( 'code' => 521, 'command' => 'EHLO' );
252 0 0       0 last if not $e->ehlo( $mfresponse, $remotehost, $remoteaddr );
253             }
254              
255 8 50 33     34 if( defined $mfresponse && $mfresponse->error ){
256             # The value of EHLO is rejected
257 0         0 $esresponse = $mfresponse->damn;
258 0         0 $tmpsession->add_response( $mfresponse );
259 0         0 $nekosyslog->w( 'err', $esresponse );
260              
261 0         0 return $httpd->res->json( 400, $tmpsession->damn );
262             }
263             } # End of ``XXFI_HELO''
264 8         52 $tmpsession->ehlo(1);
265              
266             } # End of ``EHLO''
267              
268             MAIL_FROM: {
269             # __ __ _ ___ _ _____ ____ ___ __ __
270             # | \/ | / \ |_ _| | | ___| _ \ / _ \| \/ |
271             # | |\/| | / _ \ | || | | |_ | |_) | | | | |\/| |
272             # | | | |/ ___ \ | || |___ | _| | _ <| |_| | | | |
273             # |_| |_/_/ \_\___|_____| |_| |_| \_\\___/|_| |_|
274             #
275             # Check the envelope sender address
276 8 100       10 if( not length $mail ) {
  8 100       44  
    50          
277             # The envelope sender address is empty: { "mail": '', ... }
278 1         10 $esresponse = $responsecn->r( 'mail', 'syntax-error' );
279 1         5 $tmpsession->add_response( $esresponse );
280 1         6 $nekosyslog->w( 'err', $esresponse->damn );
281              
282 1         8 return $httpd->res->json( 400, $tmpsession->damn );
283              
284             } elsif( not Haineko::SMTPD::RFC5322->is_emailaddress( $mail ) ) {
285             # The envelope sender address is not valid: { "mail": 'neko', ... }
286 1         10 $esresponse = $responsecn->r( 'mail', 'domain-required' );
287 1         6 $tmpsession->add_response( $esresponse );
288 1         5 $nekosyslog->w( 'err', $esresponse->damn );
289              
290 1         7 return $httpd->res->json( 400, $tmpsession->damn );
291              
292             } elsif( Haineko::SMTPD::RFC5321->is8bit( \$mail ) ) {
293             # The envelope sender address includes multi-byte character
294 0         0 $esresponse = $responsecn->r( 'mail', 'non-ascii' );
295 0         0 $tmpsession->add_response( $esresponse );
296 0         0 $nekosyslog->w( 'err', $esresponse->damn );
297              
298 0         0 return $httpd->res->json( 400, $tmpsession->damn );
299             }
300              
301             XXFI_ENVFROM: {
302             # Act like xxfi_envfrom() function
303 6   50     10 my $milterlibs = $serverconf->{'milter'}->{'mail'} || [];
  6         48  
304 6         17 my $mfresponse = undef;
305              
306 6         12 for my $e ( @{ Haineko::SMTPD::Milter->import( $milterlibs ) } ) {
  6         18  
307             # Check the envelope sender address with mail() method of each milter
308 0         0 $mfresponse = $responsecn->new( 'code' => 501, 'dsn' => '5.1.8', 'command' => 'MAIL' );
309 0 0       0 last if not $e->mail( $mfresponse, $mail );
310             }
311              
312 6 50 33     24 if( defined $mfresponse && $mfresponse->error ){
313             # The envelope sender address rejected
314 0         0 $esresponse = $mfresponse->damn;
315 0         0 $tmpsession->add_response( $mfresponse );
316 0         0 $nekosyslog->w( 'err', $esresponse );
317              
318 0         0 return $httpd->res->json( 400, $tmpsession->damn );
319             }
320             } # End of ``XXFI_ENVFROM''
321 6         30 $tmpsession->mail(1);
322              
323             } # End of ``MAIL_FROM''
324              
325             RCPT_TO: {
326             # ____ ____ ____ _____ _____ ___
327             # | _ \ / ___| _ \_ _| |_ _/ _ \
328             # | |_) | | | |_) || | | || | | |
329             # | _ <| |___| __/ | | | || |_| |
330             # |_| \_\\____|_| |_| |_| \___/
331             #
332             # Check envelope recipient addresses
333 6         7 my $accessconf = undef;
  6         13  
334 6   33     20 my $xrecipient = $serverconf->{'max_rcpts_per_message'} // $defaultset->{'smtpd'}->{'max_rcpts_per_message'};
335              
336 6 100       20 if( not scalar @$recipients ) {
337             # No envelope recipient address: { "rcpt": [], ... }
338 1         14 $esresponse = $responsecn->r( 'rcpt', 'address-required' );
339 1         7 $tmpsession->add_response( $esresponse );
340 1         7 $nekosyslog->w( 'err', $esresponse->damn );
341              
342 1         9 return $httpd->res->json( 400, $tmpsession->damn );
343             }
344              
345 5 50       27 if( Scalar::Util::looks_like_number $xrecipient ) {
346              
347 5 50 33     29 if( $xrecipient && $xrecipient > 0 ) {
348              
349 5 100       13 if( scalar @$recipients > $xrecipient ) {
350             # The number of recipients exceeded the value of ``max_rcpts_per_message''
351             # defined in etc/haineko.cf
352 1         13 $esresponse = $responsecn->r( 'rcpt', 'too-many-recipients' );
353 1         46 $tmpsession->add_response( $esresponse );
354 1         6 $nekosyslog->w( 'err', $esresponse->damn );
355              
356 1         9 return $httpd->res->json( 403, $tmpsession->damn );
357             }
358             }
359             } else {
360             # The value of max_rcpts_per_message does not look like number, such
361             # as "max_rcpts_per_message": "neko"
362 0         0 $esresponse = $responsecn->r( 'conf', 'not-looks-like-number' );
363 0         0 $esresponse->mesg( sprintf( "Wrong value of max_rcpts_per_message: '%s'", $xrecipient ) );
364 0         0 $tmpsession->add_response( $esresponse );
365 0         0 $nekosyslog->w( 'err', $esresponse->damn );
366              
367 0         0 return $httpd->res->json( 500, $tmpsession->damn );
368             }
369              
370             VALID_EMAIL_ADDRESS_OR_NOT: {
371             # When there is any invalid email address in the value of "rcpt",
372             # Haineko rejects current session and returns an error message as
373             # a JSON with HTTP status code 400.
374 4         6 for my $e ( @$recipients ) {
  4         13  
375             # Check the all envelope recipient addresses
376 4 100       15 if( Haineko::SMTPD::RFC5322->is_emailaddress( $e ) ) {
377             # Check the envelope recipient address includes multi-byte
378             # character or not.
379 2 50       8 next unless Haineko::SMTPD::RFC5321->is8bit( \$e );
380              
381             # The envelope recipient address includes multi-byte character
382 0         0 $esresponse = $responsecn->r( 'mail', 'non-ascii' );
383              
384             } else {
385             # The envelope recipient address is not valid email address
386 2         24 $esresponse = $responsecn->r( 'rcpt', 'is-not-emailaddress' );
387             }
388              
389 2         16 $esresponse->rcpt( $e );
390 2         24 $nekosyslog->w( 'err', $esresponse->damn );
391 2         12 $tmpsession->add_response( $esresponse );
392 2         6 push @$cannotsend, $e;
393             }
394             }
395              
396             ALLOWED_RECIPIENT: {
397             # Check etc/recipients file. The envelope recipient address or the
398             # domain part of the recipient address should be listed in the file.
399 4         10 try {
400 4     4   1232 $exceptions = 0;
401 4         36 $accessconf = Haineko::JSON->loadfile( $serverconf->{'access'}->{'rcpt'} );
402              
403             } catch {
404 0     0   0 $exceptions = 1;
405 4         64 };
406              
407 4 50       63 if( not defined $accessconf ) {
408             # If the file does not exist or failed to load the file, only
409             # $serverconf->{'hostname'} or $ENV{'HOSTNAME'} or $ENV{'SERVER_NAME'}
410             # or `hostname` allowed as a domain part of the recipient address.
411 4   50     55 $accessconf //= {
412             'open-relay' => 0,
413             'domainpart' => [ $serverconf->{'hostname'} ],
414             'recipients' => [],
415             };
416             }
417              
418 4 50       15 if( ref $accessconf eq 'HASH' ) {
419             # etc/recipients file has loaded successfully
420 4         5 if( 0 ) {
421             # DISABLED FOR DUE TO SECURITY REASON.
422             if( $remoteaddr eq '127.0.0.1' && $remoteaddr eq $httpd->host ) {
423             # Allow relaying when the value of REMOTE_ADDR is equal to
424             # the value value SERVER_NAME and the value is 127.0.0.1
425             $accessconf->{'open-relay'} = 1;
426              
427             } elsif( $remoteuser ) {
428             # Turn on open-relay if REMOTE_USER environment variable exists.
429             $accessconf->{'open-relay'} = 1;
430             }
431             }
432              
433 4 50       12 if( not $accessconf->{'open-relay'} ) {
434             # When the value of ``open-relay'' is 0, check the all recipient
435             # addresses with entries defined in etc/recipients.
436 4   50     13 my $r = $accessconf->{'recipients'} || [];
437 4   50     10 my $d = $accessconf->{'domainpart'} || [];
438              
439 4         9 for my $e ( @$recipients ) {
440             # The envelope recipient address is defined in etc/recipients
441 4 50       11 next if grep { $e eq $_ } @$r;
  0         0  
442              
443             # The domain part of the envelope recipient address is
444             # defined in etc/recipients
445 4         46 my $x = [ split( '@', $e ) ]->[-1];
446 4 100       19 next if grep { $x eq $_ } @$d;
  4         17  
447              
448             # Neither the envelope recipient address nor the domain
449             # part of the address are not allowed at etc/recipients
450             # file.
451 2         9 $esresponse = $responsecn->r( 'rcpt', 'rejected' );
452 2         10 $esresponse->rcpt( $e );
453 2         17 $tmpsession->add_response( $esresponse );
454 2         8 $nekosyslog->w( 'err', $esresponse->damn );
455              
456 2         11 push @$cannotsend, $e;
457             }
458             }
459             }
460             } # End of ``ALLOWED_RECIPIENT'' block
461              
462             XXFI_ENVRCPT: {
463             # Act like xxfi_envrcpt() function
464 4   50     9 my $milterlibs = $serverconf->{'milter'}->{'rcpt'} || [];
  4         29  
465 4         8 my $mfresponse = undef;
466              
467 4         7 for my $e ( @{ Haineko::SMTPD::Milter->import( $milterlibs ) } ) {
  4         14  
468             # Check the envelope recipient address with rcpt() method of each milter
469 0         0 for my $r ( @$recipients ) {
470 0         0 my $v = { 'code' => 553, 'dsn' => '5.7.1', 'command' => 'RCPT' };
471 0         0 $mfresponse = $responsecn->new( %$v );
472              
473 0 0       0 next if $e->rcpt( $mfresponse, $r );
474 0 0 0     0 if( defined $mfresponse && $mfresponse->error ) {
475             # One or more envelope recipient address will be rejected
476 0         0 $esresponse = $mfresponse->damn;
477 0         0 $mfresponse->rcpt( $r );
478 0         0 $tmpsession->add_response( $mfresponse );
479 0         0 $nekosyslog->w( 'err', $esresponse->damn );
480              
481 0         0 push @$cannotsend, $r;
482             }
483             }
484             }
485             } # End of ``XXFI_ENVRCPT''
486              
487             CHECK_RCPT: {
488             # Check recipient addresses. If there is no envelope recipient address
489             # Haineko can send. The following code returns error.
490 4 100       9 if( scalar @$cannotsend ) {
  4         19  
491             # Cannot send to one or more envelope recipient address
492 2         3 my $v = [];
493              
494 2         5 for my $e ( @$recipients ) {
495             # Verify each envelope recipient address with addresses in
496             # variable ``@$cannotsend''
497 2 50       5 next if grep { $e eq $_ } @$cannotsend;
  4         12  
498 0         0 push @$v, $e;
499             }
500              
501 2 50       6 if( scalar @$v ) {
502             # Update the variable for holding recipient addresses without
503             # invalid addresses checked above.
504 0         0 $recipients = $v;
505              
506             } else {
507             # There is no valid envelope recipient address.
508 2         11 return $httpd->res->json( 400, $tmpsession->damn );
509             }
510             }
511             }
512 2         10 $tmpsession->rcpt(1);
513              
514             } # End of ``RCPT_TO''
515              
516             DATA: {
517             # ____ _ _____ _
518             # | _ \ / \|_ _|/ \
519             # | | | |/ _ \ | | / _ \
520             # | |_| / ___ \| |/ ___ \
521             # |____/_/ \_\_/_/ \_\
522             #
523             # Check email body and subject header
524 2 100       5 if( not length $body ) {
  2         8  
525             # Empty message is not allowed on Haineko
526 1         10 $esresponse = $responsecn->r( 'data', 'empty-body' );
527 1         6 $tmpsession->add_response( $esresponse );
528 1         6 $nekosyslog->w( 'err', $esresponse->damn );
529              
530 1         6 return $httpd->res->json( 400, $tmpsession->damn );
531              
532             } else {
533             # Check message body size
534 1   33     5 my $xmesgsize = $serverconf->{'max_message_size'} // $defaultset->{'smtpd'}->{'max_message_size'};
535              
536 1 50       4 if( Scalar::Util::looks_like_number $xmesgsize ) {
537              
538 1 50 33     12 if( $xmesgsize > 0 && length( $body ) > $xmesgsize ) {
539             # Message body size exceeds the limit defined in etc/haineko.cf
540             # or Haineko::Default module.
541 0         0 $esresponse = $responsecn->r( 'data', 'mesg-too-big' );
542 0         0 $tmpsession->add_response( $esresponse );
543 0         0 $nekosyslog->w( 'err', $esresponse->damn );
544              
545 0         0 return $httpd->res->json( 400, $tmpsession->damn );
546             }
547              
548             } else {
549             # The value of max_message_size does not look like number, such
550             # as "max_message_size": "neko"
551 0         0 $esresponse = $responsecn->r( 'conf', 'not-looks-like-number' );
552 0         0 $esresponse->mesg( sprintf( "Wrong value of max_message_size: '%s'", $xmesgsize ) );
553 0         0 $tmpsession->add_response( $esresponse );
554 0         0 $nekosyslog->w( 'err', $esresponse->damn );
555              
556 0         0 return $httpd->res->json( 500, $tmpsession->damn );
557             }
558             }
559              
560 1 50       8 if( not length $head->{'subject'} ) {
561             # Empty subject is not allowed on Haineko
562 1         11 $esresponse = $responsecn->r( 'data', 'empty-subject' );
563 1         5 $tmpsession->add_response( $esresponse );
564 1         6 $nekosyslog->w( 'err', $esresponse->damn );
565              
566 1         6 return $httpd->res->json( 400, $tmpsession->damn );
567             }
568 0           $tmpsession->data(1);
569              
570             } # End of ``DATA''
571              
572              
573             # Create new Haineko::SMTPD::Session object from temporary session object
574 0           my $submission = Haineko::SMTPD::Session->new(
575             'addresser' => $mail,
576             'recipient' => $recipients,
577 0           %{ $tmpsession->damn },
578             );
579              
580 0           my $timestamp1 = localtime Time::Piece->new;
581 0           my $attributes = { 'content_type' => 'text/plain' };
582 0           my $mailheader = {
583             'Date' => $timestamp1->strftime,
584             'Received' => $head->{'received'} || [],
585             'Message-Id' => sprintf( "%s.%d.%d.%03d@%s",
586             $submission->queueid, $$, $submission->started->epoch,
587             int(rand(100)), $serverconf->{'hostname'}
588             ),
589             'MIME-Version' => '1.0',
590             'X-Mailer' => $submission->useragent // q(),
591             'X-SMTP-Engine' => sprintf( "%s %s", $serverconf->{'system'}, $serverconf->{'version'} ),
592             'X-HTTP-Referer' => $submission->referer // q(),
593 0   0       'X-Haineko-Loop' => join( ',', @{ $head->{'x-haineko-loop'} } ),
      0        
      0        
594             'X-Originating-IP' => $remoteaddr,
595             };
596 0           push @{ $mailheader->{'Received'} }, sprintf( "from %s ([%s]) by %s with HTTP id %s; %s",
  0            
597             $ehlo, $remoteaddr, $serverconf->{'hostname'},
598             $submission->queueid, $timestamp1->strftime );
599              
600 0           MIME_ENCODING: {
601             # __ __ ___ __ __ _____
602             # | \/ |_ _| \/ | ____|
603             # | |\/| || || |\/| | _|
604             # | | | || || | | | |___
605             # |_| |_|___|_| |_|_____|
606             #
607             # detect encodings
608 0           my $encodelist = [ 'US-ASCII', 'ISO-2022-JP', 'ISO-8859-1' ];
609 0           my $ctencindex = {
610             'US-ASCII' => '7bit',
611             'ISO-8859-1' => 'quoted-printable',
612             'ISO-2022-JP' => '7bit',
613             };
614              
615 0 0         my $ctencoding = Haineko::SMTPD::RFC5321->is8bit( \$body ) ? '8bit' : '7bit';
616 0           my $headencode = 'MIME-Header';
617 0           my $thisencode = uc $emencoding; # The value of ``charset'' in received JSON
618              
619 0 0         if( grep { $thisencode eq $_ } @$encodelist ) {
  0            
620             # Received supported encodings except UTF-8
621 0 0         if( $ctencoding eq '8bit' ) {
622             # The message body includes multi-byte character
623 0           $ctencoding = $ctencindex->{ $thisencode };
624              
625 0 0         if( $thisencode eq 'ISO-2022-JP' ) {
626             # ISO-2022-JP is 7bit encoding
627 0           $thisencode =~ y/-/_/;
628 0           $headencode = sprintf( "MIME-Header-%s", $thisencode );
629             }
630             }
631              
632             } else {
633             # Force UTF-8 except available encodings
634 0           $emencoding = 'UTF-8';
635             }
636 0           $attributes->{'charset'} = $emencoding;
637 0           $attributes->{'encoding'} = $ctencoding;
638              
639 0           for my $e ( keys %$head ) {
640             # Prepare email headers. Email headers in received JSON except supported
641             # headers in Haineko are not converted (will be ignored).
642 0 0         next unless grep { $e eq $_ } @$headerlist;
  0            
643 0 0         next unless defined $head->{ $e };
644              
645 0           my $fieldvalue = $head->{ $e };
646 0           my $headername = ucfirst $e;
647 0 0         $headername = 'Reply-To' if $headername eq 'Replyto';
648              
649 0 0         if( Haineko::SMTPD::RFC5321->is8bit( \$fieldvalue ) ) {
650             # MIME encode if the value of the header contains any multi-byte
651             # character.
652 0           $fieldvalue = Encode::encode( $headencode, $fieldvalue );
653             }
654              
655 0 0         if( exists $mailheader->{ $headername } ) {
656             # There is the header which has the same header name, such as
657             # ``Received:'' header in generic email message.
658 0 0         if( ref $mailheader->{ $headername } eq 'ARRAY' ) {
659             # The header already exists, Add the header into array.
660 0           push @{ $mailheader->{ $headername } }, $fieldvalue;
  0            
661              
662             } else {
663             # The first header, Add the header as the first element
664 0           $mailheader->{ $headername } = [ $mailheader->{ $headername }, $fieldvalue ];
665             }
666              
667             } else {
668 0           $mailheader->{ $headername } = $fieldvalue;
669             }
670             } # End of for()
671              
672             } # End of MIME_ENCODING
673              
674             SENDER_HEADER: {
675             # Add the envelope sender address into ``Sender:'' header.
676 0           my $fromheader = Haineko::SMTPD::Address->canonify( $head->{'from'} );
  0            
677 0           my $envelopemf = $submission->addresser->address;
678 0 0         $mailheader->{'Sender'} = $envelopemf if $fromheader eq $envelopemf;
679             }
680              
681             XXFI_HEADER: {
682             # Act like xxfi_header() function
683 0   0       my $milterlibs = $serverconf->{'milter'}->{'head'} || [];
  0            
684 0           my $mfresponse = undef;
685              
686 0           for my $e ( @{ Haineko::SMTPD::Milter->import( $milterlibs ) } ) {
  0            
687             # Check email headers with head() method of each milter
688 0           $mfresponse = $responsecn->new( 'code' => 554, 'dsn' => '5.7.1', 'command' => 'DATA' );
689 0 0         last if not $e->head( $mfresponse, $mailheader );
690             }
691              
692 0 0 0       if( defined $mfresponse && $mfresponse->error ){
693             # One or more email header will be rejected
694 0           $esresponse = $mfresponse->damn;
695 0           $nekosyslog->w( 'err', $esresponse );
696 0           $submission->add_response( $mfresponse );
697              
698 0           return $httpd->res->json( 400, $submission->damn );
699             }
700             } # End of ``XXFI_HEADER''
701              
702             XXFI_BODY: {
703             # Act like xxfi_body() function
704 0   0       my $milterlibs = $serverconf->{'milter'}->{'body'} || [];
  0            
705 0           my $mfresponse = undef;
706              
707 0           for my $e ( @{ Haineko::SMTPD::Milter->import( $milterlibs ) } ) {
  0            
708             # Check the email body with body() method of each milter
709 0           $mfresponse = $responsecn->new( 'code' => 554, 'dsn' => '5.6.0', 'command' => 'DATA' );
710 0 0         last if not $e->body( $mfresponse, \$body );
711             }
712              
713 0 0 0       if( defined $mfresponse && $mfresponse->error ){
714             # The email body will be rejected
715 0           $esresponse = $mfresponse->damn;
716 0           $submission->add_response( $mfresponse );
717 0           $nekosyslog->w( 'err', $esresponse );
718              
719 0           return $httpd->res->json( 400, $submission->damn );
720             }
721              
722             } # End of ``XXFI_BODY''
723              
724              
725             # mailertable
726 0           my $mailerconf = { 'mail' => {}, 'rcpt' => {} };
727 0           my $defaulthub = undef; # Relays based on the domain part of the recipient address
728 0           my $sendershub = undef; # Relays based on the domain part of the sender address
729              
730 0           MAILERTABLE: {
731             # Load etc/mailertable, etc/sendermt
732 0           require Haineko::SMTPD::Relay;
733              
734 0           for my $e ( 'mail', 'rcpt' ) {
735             # Check the contents of the following mailer table files:
736             # - etc/mailertable
737             # - etc/sendermt
738             try {
739 0     0     $exceptions = 0;
740 0           $mailerconf->{ $e } = Haineko::JSON->loadfile( $serverconf->{'mailer'}->{ $e } );
741              
742             } catch {
743             # Failed to load etc/mailertable or etc/sendermt. Maybe the file
744             # format is wrong or is not JSON or YAML.
745 0     0     $exceptions = 1;
746 0           };
747              
748             # Load ``default:'' section in etc/mailertable
749 0   0       $defaulthub //= $mailerconf->{'rcpt'}->{'default'};
750              
751 0 0         last if $e eq 'rcpt';
752              
753             # If the value of ``disabled'' is 1, the mailer table based on the
754             # domain part of the envelope sender address will not be used.
755 0 0         next unless exists $mailerconf->{'mail'}->{ $submission->addresser->host };
756 0 0         next if $mailerconf->{'mail'}->{ $submission->addresser->host }->{'disabled'};
757              
758 0           $sendershub = $mailerconf->{'mail'}->{ $submission->addresser->host };
759             }
760              
761             # ``default:'' section was not defined in etc/mailertable. Use system
762             # configuration as a default hub for relaying.
763 0   0       $defaulthub //= Haineko::SMTPD::Relay->defaulthub;
764             }
765              
766 0           my $autheninfo = undef;
767             AUTHINFO: {
768             # Load etc/authinfo file. Entries defined in etc/authinfo are used at
769             # relaying to an external SMTP server or sending message to an email
770             # clouds.
771 0           try {
772 0     0     $exceptions = 0;
773 0           $mailerconf->{'auth'} = Haineko::JSON->loadfile( $serverconf->{'mailer'}->{'auth'} );
774             } catch {
775             # Failed to load etc/authinfo file.
776 0     0     $exceptions = 1;
777 0           };
778 0   0       $autheninfo = $mailerconf->{'auth'} // {};
779             }
780              
781             SENDMIAL: {
782             # ____ _____ _ _ ____ __ __ _ ___ _
783             # / ___|| ____| \ | | _ \| \/ | / \ |_ _| |
784             # \___ \| _| | \| | | | | |\/| | / _ \ | || |
785             # ___) | |___| |\ | |_| | | | |/ ___ \ | || |___
786             # |____/|_____|_| \_|____/|_| |_/_/ \_\___|_____|
787             #
788 0           require Module::Load;
  0            
789              
790 0           my $maxworkers = scalar @$recipients;
791 0           my $preforkset = undef; # (Parallel::Prefork) object
792 0           my $preforkarg = undef; # (Ref->Hash) arguments for Parallel::Prefork
793 0           my $preforkipc = []; # (Ref->Array) IO::Pipe objects
794 0           my $useprefork = 0; # (Integer) use fork() or not
795 0           my $procnumber = 0; # (Integer) Job ID of each child process
796 0           my $trappedsig = 0; # (Integer) The number of received USR2 signal
797              
798 0 0         if( $maxworkers > 1 ) {
799             # Adjust the number of max worker processes.
800 0   0       my $xprocesses = $serverconf->{'max_workers'} // $defaultset->{'smtpd'}->{'max_workers'};
801              
802 0 0         if( Scalar::Util::looks_like_number $xprocesses ) {
803             # Limit the value of max_workers to the value defined in
804             # etc/haineko.cf or Haineko::Default.
805 0 0         $maxworkers = $xprocesses if $maxworkers > $xprocesses;
806              
807             } else {
808             # The value of max_workers does not look like number, such as
809             # "max_workers": "neko"
810 0           $esresponse = $responsecn->r( 'conf', 'not-looks-like-number' );
811 0           $esresponse->mesg( sprintf( "Wrong value of max_workers: '%s'", $xprocesses ) );
812 0           $tmpsession->add_response( $esresponse );
813 0           $nekosyslog->w( 'err', $esresponse->damn );
814              
815 0           return $httpd->res->json( 500, $tmpsession->damn );
816             }
817 0 0         $useprefork = 1 if $maxworkers > 1;;
818             }
819              
820 0 0         if( $useprefork ) {
821             # If the number of recipients or the value of `maxworkers` is greater
822             # than 1, fork and send emails by each child process.
823 0           require IO::Pipe;
824 0           require Parallel::Prefork;
825              
826             $preforkarg = {
827             'max_workers' => $maxworkers,
828             'err_respawn_interval' => 2,
829             'spawn_interval' => 0,
830             'trap_signals' => { 'HUP' => 'TERM', 'TERM' => 'TERM' },
831             'before_fork' => sub {
832 0     0     my $k = shift;
833 0           $k->{'procnumber'} = $procnumber;
834              
835 0 0         if( $procnumber < $maxworkers ) {
836 0           $preforkipc->[ $procnumber ] = IO::Pipe->new;;
837 0           $procnumber++;
838             }
839             }
840 0           };
841 0           $preforkset = Parallel::Prefork->new( $preforkarg );
842              
843             $SIG{'USR2'} = sub {
844             # Trap signal from each child process
845 0     0     $trappedsig++;
846 0 0         kill( 'TERM', $$ ) if $trappedsig >= $maxworkers;
847 0           };
848             }
849              
850             my $sendmailto = sub {
851             # Code reference for sending an email to each recipient which called
852             # from Parallel::Prefork->start().
853 0     0     my $thisworker = undef;
854              
855 0           local $SIG{'TERM'} = 'IGNORE';
856              
857 0 0         if( $useprefork ) {
858             # fork and send each email
859 0           kill( 'USR2', $preforkset->manager_pid );
860 0           $thisworker = $preforkset->{'procnumber'};
861              
862             # The number of worker processes has exceeded the limit
863 0 0         return -1 if $thisworker >= $maxworkers;
864              
865             } else {
866             # send each email in order
867 0           $thisworker = 0;
868             }
869              
870 0           ONE_TO_ONE: for( my $i = $thisworker; $i < $maxworkers; $i += $maxworkers ) {
871             # Skip if the recipient address is in @$cannotsend
872 0           my $e = $recipients->[ $i ];
873 0 0         next if grep { $e eq $_ } @$cannotsend;
  0            
874              
875             # Create email address objects from each envelope recipient address
876 0           my $r = Haineko::SMTPD::Address->new( 'address' => $e );
877              
878 0           my $relayclass = q(); # (String) Class name of $smtpmailer
879 0           my $smtpmailer = undef; # (Haineko::SMTPD::Relay::*) Mailer object
880 0           my $relayingto = undef; # (Ref->Hash) Mailertable
881 0           my $credential = undef; # (Ref->Hash) Username and password for SMTP-AUTH or API
882              
883 0   0       $relayingto = $mailerconf->{'rcpt'}->{ $r->host } // $sendershub;
884 0 0         $relayingto = $sendershub if $relayingto->{'disabled'};
885              
886 0 0         $relayingto = $defaulthub unless keys %$relayingto;
887 0 0         $relayingto = $defaulthub if $relayingto->{'disabled'};
888              
889 0   0       $relayingto->{'port'} //= 25;
890 0   0       $relayingto->{'host'} //= '127.0.0.1';
891 0   0       $relayingto->{'mailer'} //= 'ESMTP';
892              
893 0 0         if( $relayingto->{'auth'} ) {
894 0   0       $credential = $autheninfo->{ $relayingto->{'auth'} } // {};
895             }
896 0 0         $relayingto->{'auth'} = q() unless keys %$credential;
897              
898 0 0 0       if( $relayingto->{'mailer'} =~ m/\A(?:ESMTP|Haineko|MX)\z/ ) {
    0          
    0          
899             # Use Haineko::SMTPD::Relay::ESMTP or Haineko::SMTPD::Relay::Haineko
900             # ::MX = Directly connect to the host listed in MX Resource record
901             # ::ESMTP = Generic SMTP connection to an external server
902             # ::Haineko = Relay an message to Haineko running on other host
903 0   0       my $methodargv = {
      0        
      0        
      0        
904             'ehlo' => $serverconf->{'hostname'},
905             'mail' => $submission->addresser->address,
906             'rcpt' => $r->address,
907             'head' => $mailheader,
908             'body' => \$body,
909             'attr' => $attributes,
910             'host' => $relayingto->{'host'} // '127.0.0.1',
911             'retry' => $relayingto->{'retry'} // 0,
912             'sleep' => $relayingto->{'sleep'} // 5,
913             'timeout' => $relayingto->{'timeout'} // 59,
914             'starttls' => $relayingto->{'starttls'},
915             };
916              
917 0 0         if( $relayingto->{'mailer'} eq 'ESMTP' ) {
    0          
    0          
918             # use well-known port for SMTP
919 0   0       $methodargv->{'port'} = $relayingto->{'port'} // 25;
920 0   0       $methodargv->{'debug'} = $relayingto->{'debug'} // 0;
921              
922             } elsif( $relayingto->{'mailer'} eq 'Haineko' ) {
923             # Haineko uses 2794 by default
924 0   0       $methodargv->{'port'} = $relayingto->{'port'} // 2794;
925              
926             } elsif( $relayingto->{'mailer'} eq 'MX' ) {
927             # Mail Exchanger is waiting on *:25
928 0           $methodargv->{'port'} = 25;
929 0   0       $methodargv->{'debug'} = $relayingto->{'debug'} // 0;
930             }
931              
932 0           $relayclass = sprintf( "Haineko::SMTPD::Relay::%s", $relayingto->{'mailer'} );
933 0           Module::Load::load( $relayclass );
934 0           $smtpmailer = $relayclass->new( %$methodargv );
935              
936 0 0         if( $relayingto->{'auth'} ) {
937             # Load credentials for SMTP-AUTH
938 0           $smtpmailer->auth( 1 );
939 0           $smtpmailer->username( $credential->{'username'} );
940 0           $smtpmailer->password( $credential->{'password'} );
941             }
942              
943 0           $smtpmailer->sendmail();
944              
945             } elsif( $relayingto->{'mailer'} =~ m/(?:Discard|Screen)/ ) {
946             # These mailer does not open new connection to any host.
947             # Haineko::SMTPD::Relay::
948             # - Discard: email blackhole. It will discard all messages
949             # - Screen: print the email message to STDERR
950 0           $relayclass = sprintf( "Haineko::SMTPD::Relay::%s", $relayingto->{'mailer'} );
951 0           Module::Load::load( $relayclass );
952              
953 0           my $methodargv = {
954             'ehlo' => $serverconf->{'hostname'},
955             'mail' => $submission->addresser->address,
956             'rcpt' => $r->address,
957             'head' => $mailheader,
958             'body' => \$body,
959             'attr' => $attributes,
960             };
961 0           $smtpmailer = $relayclass->new( %$methodargv );
962 0           $smtpmailer->sendmail();
963              
964             } elsif( $relayingto->{'mailer'} =~ m|\A/| or $relayingto->{'mailer'} eq 'File' ) {
965             # Haineko::SMTPD::Relay::File mailer
966 0           require Haineko::SMTPD::Relay::File;
967 0 0         my $mailfolder = $relayingto->{'mailer'} eq 'File' ? '/tmp' : $relayingto->{'mailer'};
968 0           my $methodargv = {
969             'ehlo' => $serverconf->{'hostname'},
970             'host' => $mailfolder,
971             'mail' => $submission->addresser->address,
972             'rcpt' => $r->address,
973             'head' => $mailheader,
974             'body' => \$body,
975             'attr' => $attributes,
976             };
977 0           $smtpmailer = Haineko::SMTPD::Relay::File->new( %$methodargv );
978 0           $smtpmailer->sendmail();
979              
980             } else {
981 0           $mailheader->{'To'} = $r->address;
982 0   0       my $methodargv = {
      0        
983             'ehlo' => $serverconf->{'hostname'},
984             'mail' => $submission->addresser->address,
985             'rcpt' => $r->address,
986             'head' => $mailheader,
987             'body' => \$body,
988             'attr' => $attributes,
989             'retry' => $relayingto->{'retry'} // 0,
990             'timeout' => $relayingto->{'timeout'} // 60,
991             };
992              
993 0 0         if( length $relayingto->{'mailer'} ) {
994             # Use Haineko::SMTPD::Relay::* except H::S::R::ESMTP,
995             # H::S::R::Haineko and H::S::R::Discard.
996             try {
997 0           $relayclass = sprintf( "Haineko::SMTPD::Relay::%s", $relayingto->{'mailer'} );
998 0           Module::Load::load( $relayclass );
999 0           $smtpmailer = $relayclass->new( %$methodargv );
1000              
1001 0 0         if( $relayingto->{'auth'} ) {
1002             # Load credentials for SMTP-AUTH
1003 0           $smtpmailer->auth( 1 );
1004 0           $smtpmailer->username( $credential->{'username'} );
1005 0           $smtpmailer->password( $credential->{'password'} );
1006             }
1007              
1008 0           $smtpmailer->sendmail();
1009              
1010 0 0         if( not $smtpmailer->response->dsn ) {
1011             # D.S.N. is empty or undefined.
1012 0 0         if( $smtpmailer->response->error ) {
1013             # Error but no D.S.N.
1014 0           $smtpmailer->response->dsn( '5.0.0' );
1015             } else {
1016             # Successfully sent but no D.S.N.
1017 0           $smtpmailer->response->dsn( '2.0.0' );
1018             }
1019             }
1020              
1021             } catch {
1022 0           require Haineko::E;
1023 0           my $v = [ split( "\n", $_ ) ]->[0];
1024 0           my $E = Haineko::E->new( $v );
1025 0           my $R = {
1026             'code' => 500,
1027             'error' => 1,
1028             'message' => [ $E->mesg->[0] ],
1029             };
1030              
1031 0           $smtpmailer = Haineko::SMTPD::Relay->new( %$methodargv );
1032 0           $smtpmailer->response( Haineko::SMTPD::Response->new( %$R ) );
1033 0           };
1034              
1035             } else {
1036             # The value of "mailer" is empty
1037 0           $smtpmailer = Haineko::SMTPD::Relay->new( %$methodargv );
1038 0           my $R = {
1039             'code' => 500,
1040             'error' => 1,
1041             'message' => [ 'The value of "mailer" is empty' ],
1042             };
1043 0           $smtpmailer->response( Haineko::SMTPD::Response->new( %$R ) );
1044             }
1045             }
1046              
1047 0 0         if( $maxworkers > 1 ) {
1048             # Send the received response as a JSON from child process
1049             # to parent process via pipe.
1050 0           my $p = $preforkipc->[ $thisworker ];
1051 0           $p->writer;
1052 0           print( { $p } Haineko::JSON->dumpjson( $smtpmailer->response->damn ) );
  0            
1053 0           close $p;
1054              
1055             } else {
1056             # Add the received response as a Haineko::SMTPD::Response object
1057             # into Haineko::SMTPD::Session object.
1058 0           $submission->add_response( $smtpmailer->response );
1059             }
1060              
1061             } # End of for(ONE_TO_ONE)
1062              
1063 0           return 0;
1064              
1065 0           }; # End of `sub sendmailto`
1066              
1067 0 0         if( $useprefork ) {
1068             # Call sendmailto->() and wait all children
1069 0           while(1) {
1070 0 0         last if $preforkset->signal_received eq 'TERM';
1071 0 0         last if $preforkset->signal_received eq 'INT';
1072 0           $preforkset->start( $sendmailto );
1073             }
1074 0           $preforkset->wait_all_children;
1075              
1076 0           for my $v ( @$preforkipc ) {
1077             # Receive the response as a JSON from each child
1078 0           my $j = q();
1079 0           my $p = undef;
1080              
1081 0           $v->reader;
1082 0           while( <$v> ) {
1083 0           $j .= $_;
1084             }
1085 0           $p = Haineko::JSON->loadjson( $j );
1086 0           $submission->add_response( Haineko::SMTPD::Response->new( %$p ) );
1087             }
1088              
1089             } else {
1090             # Haineko does not fork when the number of recipients is ``1''
1091 0           $sendmailto->();
1092             }
1093              
1094             } # End of SENDMAIL
1095              
1096             # Respond to the client
1097 0           $nekosyslog->w( 'notice', $submission->damn );
1098 0           return $httpd->res->json( 200, $submission->damn );
1099              
1100             }
1101              
1102             1;
1103             __END__