File Coverage

blib/lib/EMDIS/ECS/FileBackedMessage.pm
Criterion Covered Total %
statement 161 326 49.3
branch 72 234 30.7
condition 5 33 15.1
subroutine 23 25 92.0
pod 0 17 0.0
total 261 635 41.1


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             #
3             # Copyright (C) 2010-2016 National Marrow Donor Program. All rights reserved.
4             #
5             # For a description of this module, please refer to the POD documentation
6             # embedded at the bottom of the file (e.g. perldoc EMDIS::ECS::FileBackedMessage).
7              
8             package EMDIS::ECS::FileBackedMessage;
9              
10 1         200 use EMDIS::ECS qw($ECS_CFG $ECS_NODE_TBL $FILEMODE $VERSION ecs_is_configured
11             format_datetime format_msg_filename
12             log_debug log_info log_warn log_error log_fatal
13 1     1   11597 send_encrypted_email send_email dequote trim);
  1         3  
14 1     1   7 use Fcntl qw(:DEFAULT :flock);
  1         4  
  1         335  
15 1     1   7 use File::Basename;
  1         27  
  1         84  
16 1     1   7 use File::Spec::Functions qw(catdir catfile);
  1         1  
  1         58  
17 1     1   7 use File::Temp qw(tempfile);
  1         2  
  1         40  
18 1     1   7 use IO::File;
  1         17  
  1         217  
19 1     1   6 use strict;
  1         1  
  1         3605  
20              
21             # ----------------------------------------------------------------------
22             # Constructor.
23             # If error encountered, returns error message instead of object reference.
24             sub new
25             {
26 26     26 0 1369 my $arg1 = shift;
27 26         38 my $this;
28 26 50       59 if(ref $arg1)
29             {
30             # invoked as instance method
31 0         0 $this = $arg1;
32             }
33             else
34             {
35             # invoked as class method
36 26         50 $this = {};
37 26         51 bless $this, $arg1;
38             }
39              
40 26         46 my $err = '';
41 26         37 my ($sender_node_id, $seq_num, $filename);
42 26         47 my $argc = scalar(@_);
43 26 100       55 if($argc <= 1)
    50          
44             {
45 23         38 $filename = shift;
46             }
47             elsif($argc <= 3)
48             {
49 3         8 ($sender_node_id, $seq_num, $filename) = @_;
50             }
51             else
52             {
53 0         0 return "Illegal usage -- expected 0-3 parameters: " .
54             "[sender_node_id, seq_num,] [filename]";
55             }
56              
57 26         74 $this->{temp_files} = [];
58 26         53 $this->{is_closed} = 0;
59              
60             # if $filename not specified, read input from stdin
61 26 50       56 if(not $filename)
62             {
63             # read from stdin, create temp file
64 0         0 my $template = sprintf('%s_XXXX', format_datetime(time,
65             '%04d%02d%02d_%02d%02d%02d'));
66 0 0       0 return "Unable to create temp file from stdin: ECS is not configured!"
67             unless ecs_is_configured();
68 0         0 my $fh;
69 0         0 ($fh, $filename) = tempfile($template,
70             DIR => catdir($ECS_CFG->ECS_TMP_DIR),
71             SUFFIX => '.msg');
72 0         0 binmode(STDIN);
73 0         0 binmode($fh);
74 0         0 while(1)
75             {
76 0         0 my $buffer;
77              
78 0         0 my $readlen = read STDIN, $buffer, 65536;
79 0 0       0 if(not defined $readlen)
80             {
81 0         0 $err = "Unexpected problem reading STDIN: $!";
82 0         0 last;
83             }
84              
85 0 0       0 last if $readlen == 0;
86              
87 0 0       0 if(not print $fh $buffer)
88             {
89 0         0 $err = "Unexpected problem writing file $filename: $!";
90 0         0 last;
91             }
92             }
93 0         0 close $fh;
94 0 0       0 if($err)
95             {
96 0         0 unlink $filename;
97 0         0 return $err;
98             }
99 0         0 push @{$this->{temp_files}}, $filename;
  0         0  
100             }
101              
102 26         48 $this->{filename} = $filename;
103 26         39 my $file_handle;
104 26 50       982 return "Unable to open input file $filename: $!"
105             unless open $file_handle, "+< $filename";
106 26         102 $this->{file_handle} = $file_handle;
107 26         60 binmode $file_handle;
108              
109             # get exclusive lock (with retry loop)
110             # protects against reading a file while another process is writing it
111 26         37 my $locked = '';
112 26         68 for my $retry (1..5)
113             {
114 26         237 $locked = flock $file_handle, LOCK_EX | LOCK_NB;
115 26 50       85 last if $locked;
116             }
117 26 50       50 if(!$locked)
118             {
119 0         0 $err = "Unable to lock input file $filename: $!";
120 0         0 close $file_handle;
121 0         0 return $err;
122             }
123              
124 26         46 my $email_headers = '';
125 26         34 my $data_offset = 0;
126              
127             # attempt to read email headers only if $sender_node_id not specified
128 26 100       51 if(not $sender_node_id)
129             {
130             # attempt to read email headers from file, determine data offset
131 24         30 my $buf;
132 24         31 while(1)
133             {
134 5332         7933 my $bytecount = read $file_handle, $buf, 1;
135              
136 5332 50       8603 if(not defined $bytecount)
137             {
138 0         0 $err = "Unexpected problem reading from file $filename: $!";
139 0         0 last;
140             }
141              
142 5332 100 33     7659 if($bytecount > 0)
    50          
143             {
144 5328         6782 $email_headers .= $buf;
145 5328         6610 $data_offset++;
146              
147             # first empty line ends potential email header
148 5328 100       9276 last if $email_headers =~ /\r?\n\r?\n$/so;
149             }
150             elsif($bytecount == 0 or $data_offset >= 1048576)
151             {
152             # assume file does not contain email header
153             # if EOF encountered or no empty line found in first X bytes
154 4         6 $data_offset = 0;
155 4         7 last;
156             }
157             }
158 24 50       50 if($err)
159             {
160 0         0 close $file_handle;
161 0         0 return $err;
162             }
163             }
164              
165 26 100       55 if($data_offset > 0)
166             {
167             # convert headers to more easily parseable format, store in this obj
168 20         188 $email_headers =~ s/\r?\n/\n/go;
169              
170             # look for "Subject" line
171 20 100       125 if($email_headers =~ /^Subject:\s*(.+?)$/imo)
172             {
173 19         74 $this->{subject} = $1;
174 19         41 $this->{email_headers} = $email_headers;
175 19         29 $this->{data_offset} = $data_offset;
176             }
177             }
178              
179             # absence of "Subject" line indicates file contains data only
180 26 100       57 if(not exists $this->{subject})
181             {
182 7 100       17 $this->{sender_node_id} = $sender_node_id if $sender_node_id;
183 7 100       13 $this->{seq_num} = $seq_num if $seq_num;
184 7         13 $this->{data_offset} = 0;
185 7         21 $err = inspect_fml $this;
186 7 50       15 return $err if $err;
187 7         44 return $this;
188             }
189              
190             # parse "Subject" into MAIL_MRK:sender_node_id[:seqnum]
191 19         30 my $mail_mrk = 'EMDIS';
192 19 50       64 if(ecs_is_configured())
193             {
194 0         0 $mail_mrk = $ECS_CFG->MAIL_MRK;
195             }
196             else
197             {
198 19         625 warn "ECS not configured, using MAIL_MRK = '$mail_mrk'\n";
199             }
200 19 100       238 if($this->{subject} =~ /$mail_mrk:(\S+?):(\d+)(:(\d+)\/(\d+))?\s*$/i)
    100          
201             {
202             # regular message
203 10         34 $this->{is_ecs_message} = 1;
204 10         21 $this->{is_meta_message} = '';
205 10         29 $this->{sender_node_id} = $1;
206 10         21 $this->{seq_num} = $2;
207 10 100       32 $this->{part_num} = $4 if defined $4;
208 10 100       29 $this->{num_parts} = $5 if defined $5;
209 10 100 66     50 if(exists $this->{part_num} and exists $this->{num_parts}
      66        
210             and $this->{part_num} > $this->{num_parts})
211             {
212 1         16 close $file_handle;
213 1         10 return "part_num is greater than num_parts: " . $this->{subject};
214             }
215             }
216             elsif($this->{subject} =~ /$mail_mrk:(\S+)\s*$/i)
217             {
218             # meta-message
219 4         13 $this->{is_ecs_message} = 1;
220 4         8 $this->{is_meta_message} = 1;
221 4         11 $this->{sender_node_id} = $1;
222             }
223             else
224             {
225             # not an ECS message
226 5         17 $this->{is_ecs_message} = '';
227 5         10 $this->{is_meta_message} = '';
228             }
229              
230 18 50       38 return $err if $err;
231 18         54 $err = inspect_fml $this;
232 18 50       40 return $err if $err;
233              
234 18         75 return $this;
235             }
236              
237             # ----------------------------------------------------------------------
238             # prepare for object destruction: close $this->{file_handle}, delete
239             # temp files
240             sub cleanup
241             {
242 26     26 0 40 my $this = shift;
243 26 50       74 die "cleanum() must only be called as an instance method!"
244             unless ref $this;
245             close $this->{file_handle}
246 26 50       285 if exists $this->{file_handle};
247 26         59 foreach my $temp_file (@{$this->{temp_files}})
  26         77  
248             {
249 0         0 unlink $temp_file;
250             }
251 26         98 $this->{is_closed} = 1;
252             }
253              
254             # ----------------------------------------------------------------------
255             # Accessor method (read-only).
256             sub data_offset
257             {
258 4     4 0 7 my $this = shift;
259 4 50       11 die "data_offset() must only be called as an instance method!"
260             unless ref $this;
261 4         15 return $this->{data_offset};
262             }
263              
264             # ----------------------------------------------------------------------
265             # Accessor method (read-only).
266             sub email_headers
267             {
268 4     4 0 188 my $this = shift;
269 4 50       11 die "email_headers() must only be called as an instance method!"
270             unless ref $this;
271 4         20 return $this->{email_headers};
272             }
273              
274             # ----------------------------------------------------------------------
275             # Accessor method (read-only).
276             sub filename
277             {
278 1     1 0 3 my $this = shift;
279 1 50       5 die "filename() must only be called as an instance method!"
280             unless ref $this;
281 1         3 return $this->{filename};
282             }
283              
284             # ----------------------------------------------------------------------
285             # Accessor method (read-only).
286             sub hub_rcv
287             {
288 2     2 0 4 my $this = shift;
289 2 50       6 die "hub_rcv() must only be called as an instance method!"
290             unless ref $this;
291 2         7 return $this->{hub_rcv};
292             }
293              
294             # ----------------------------------------------------------------------
295             # Accessor method (read-only).
296             sub hub_snd
297             {
298 2     2 0 6 my $this = shift;
299 2 50       8 die "hub_snd() must only be called as an instance method!"
300             unless ref $this;
301 2         9 return $this->{hub_snd};
302             }
303              
304             # ----------------------------------------------------------------------
305             # Accessor method (read-only).
306             sub is_ecs_message
307             {
308 20     20 0 471 my $this = shift;
309 20 50       52 die "is_ecs_message() must only be called as an instance method!"
310             unless ref $this;
311 20         100 return $this->{is_ecs_message};
312             }
313              
314             # ----------------------------------------------------------------------
315             # Accessor method (read-only).
316             sub is_meta_message
317             {
318 16     16 0 33 my $this = shift;
319 16 50       49 die "is_meta_message() must only be called as an instance method!"
320             unless ref $this;
321 16         74 return $this->{is_meta_message};
322             }
323              
324             # ----------------------------------------------------------------------
325             # Accessor method (read-only).
326             sub num_parts
327             {
328 3     3 0 6 my $this = shift;
329 3 50       8 die "num_parts() must only be called as an instance method!"
330             unless ref $this;
331 3         13 return $this->{num_parts};
332             }
333              
334             # ----------------------------------------------------------------------
335             # Accessor method (read-only).
336             sub part_num
337             {
338 3     3 0 6 my $this = shift;
339 3 50       9 die "part_num() must only be called as an instance method!"
340             unless ref $this;
341 3         10 return $this->{part_num};
342             }
343              
344             # ----------------------------------------------------------------------
345             # Accessor method (read-only).
346             sub sender_node_id
347             {
348 14     14 0 26 my $this = shift;
349 14 50       32 die "sender_node_id() must only be called as an instance method!"
350             unless ref $this;
351 14         60 return $this->{sender_node_id};
352             }
353              
354             # ----------------------------------------------------------------------
355             # Accessor method (read-only).
356             sub seq_num
357             {
358 10     10 0 20 my $this = shift;
359 10 50       24 die "seq_num() must only be called as an instance method!"
360             unless ref $this;
361 10         42 return $this->{seq_num};
362             }
363              
364             # ----------------------------------------------------------------------
365             # Accessor method (read-only).
366             sub subject
367             {
368 8     8 0 343 my $this = shift;
369 8 50       21 die "subject() must only be called as an instance method!"
370             unless ref $this;
371 8         51 return $this->{subject};
372             }
373              
374             # ----------------------------------------------------------------------
375             # Accessor method (read only)
376             sub temp_files
377             {
378 0     0 0 0 my $this = shift;
379 0 0       0 die "temp_files() must only be called as an instance method!"
380             unless ref $this;
381 0         0 return @{$this->{temp_files}};
  0         0  
382             }
383              
384             # ----------------------------------------------------------------------
385             # object destructor, called by perl garbage collector
386             sub DESTROY
387             {
388 50     50   116 my $this = shift;
389 50 50       118 die "DESTROY() must only be called as an instance method!"
390             unless ref $this;
391 50 100       301 $this->cleanup unless $this->{is_closed};
392             }
393              
394             # ----------------------------------------------------------------------
395             # read first portion of message, attempt to extract HUB_SND and HUB_RCV
396             sub inspect_fml
397             {
398 25     25 0 45 my $this = shift;
399 25 50       56 return "compute_hub_snd_rcv() must only be called as an instance method!"
400             unless ref $this;
401             return "compute_hub_snd_rcv(): this FileBackedMessage object is closed!"
402 25 50       53 if $this->{is_closed};
403              
404             # read first part of FML payload, look for HUB_SND, HUB_RCV
405              
406             return "Unable to position file pointer for file $this->{filename}" .
407             " to position $this->{data_offset}: $!"
408 25 50       300 unless seek $this->{file_handle}, $this->{data_offset}, 0;
409 25         56 my $fml;
410 25         329 my $bytecount = read $this->{file_handle}, $fml, 65536;
411 25 50       79 return "Unable to read from file " . $this->{filename} . ": $!"
412             unless defined $bytecount;
413              
414 25 100       50 if(not exists $this->{is_ecs_message})
415             {
416             # if not already set, do cursory check of payload to
417             # compute is_ecs_message and is_meta_message
418 7 100       54 if($fml =~ /^\s*(BLOCK_BEGIN\s+\w+\s*;\s*)?\w+\s*:/iso)
    100          
419             {
420 5         14 $this->{is_ecs_message} = 1;
421 5         10 $this->{is_meta_message} = '';
422             }
423             elsif($fml =~ /^\s*msg_type\s*=\s*\S+/isom)
424             {
425 1         6 $this->{is_ecs_message} = 1;
426 1         2 $this->{is_meta_message} = 1;
427 1         4 return '';
428             }
429             else
430             {
431 1         6 $this->{is_ecs_message} = '';
432 1         3 $this->{is_meta_message} = '';
433 1         3 return '';
434             }
435             }
436              
437             # Note: this code only understands the simple forms of FML assignments
438             # (not the extended /FIELDS form)
439              
440             # look for HUB_RCV
441 23 100       83 if($fml =~ /HUB_RCV\s*=\s*([^,;]+)/iso) # presumes [^,;] in HUB_RCV
442             {
443 5         20 $this->{hub_rcv} = dequote(trim($1));
444             }
445              
446             # look for HUB_SND
447 23 100       73 if($fml =~ /HUB_SND\s*=\s*([^,;]+)/iso) # presumes [^,;] in HUB_SND
448             {
449 5         13 $this->{hub_snd} = dequote(trim($1));
450             }
451              
452 23         61 return '';
453             }
454              
455             # ----------------------------------------------------------------------
456             sub send_via_email
457             {
458 0     0 0   my $this = shift;
459 0 0         return "send_via_email() must only be called as an instance method!"
460             unless ref $this;
461             return "send_via_email(): this FileBackedMessage object is closed!"
462 0 0         if $this->{is_closed};
463             return "send_via_email(): this FileBackedMessage object represents " .
464             "only a partial message!"
465 0 0 0       if defined $this->{num_parts} and $this->{num_parts} > 1;
466              
467             # initialize
468 0           my $rcv_node_id = shift;
469 0           my $is_re_send = shift;
470 0           my $part_num = shift;
471 0 0         return "send_via_email(): ECS has not been configured."
472             unless ecs_is_configured();
473 0           my $cfg = $ECS_CFG;
474 0           my $node_tbl = $ECS_NODE_TBL;
475 0           my $err = '';
476              
477 0 0 0       return "send_via_email(): Missing \$rcv_node_id!"
478             unless defined $rcv_node_id and $rcv_node_id;
479              
480             # lock node_tbl, look up $rcv_node_id
481 0           my $was_locked = $node_tbl->LOCK;
482 0 0         if(not $was_locked)
483             {
484 0 0         $node_tbl->lock() # lock node_tbl
485             or return "send_via_email(): unable to lock node_tbl: " .
486             $node_tbl->ERROR;
487             }
488 0           my $node = $node_tbl->read($rcv_node_id);
489 0 0         if(not $node)
490             {
491 0 0         $node_tbl->unlock() unless $was_locked; # unlock node_tbl if needed
492 0           return "send_via_email(): node not found: $rcv_node_id";
493             }
494 0 0         if(not $node->{addr})
495             {
496 0 0         $node_tbl->unlock() unless $was_locked; # unlock node_tbl if needed
497 0           return "send_via_email(): addr not defined for node: $rcv_node_id";
498             }
499              
500             # compute or assign message seq_num
501 0           my $seq_num = '';
502 0 0         if($is_re_send)
    0          
503             {
504             # sanity checks
505 0 0         if(not defined $this->{seq_num})
506             {
507 0 0         $node_tbl->unlock() unless $was_locked; # unlock node_tbl if needed
508 0           return "send_via_email(): seq_num not defined for RE_SEND";
509             }
510 0 0         if($this->{seq_num} > $node->{out_seq})
511             {
512 0 0         $node_tbl->unlock() unless $was_locked; # unlock node_tbl if needed
513             return "send_via_email(): seq_num for RE_SEND (" .
514             $this->{seq_num} . ") is greater than out_seq for node " .
515 0           "$rcv_node_id (" . $node->{out_seq} . ")!";
516             }
517 0           $seq_num = $this->{seq_num};
518             }
519             elsif(not $this->{is_meta_message})
520             {
521             # only allow $part_num to be specified if this is a RE_SEND request
522 0 0         if($part_num)
523             {
524 0 0         $node_tbl->unlock() unless $was_locked; # unlock node_tbl if needed
525 0           return "send_via_email(): part_num specified ($part_num), for " .
526             "non- RE_SEND request!";
527             }
528             # automatically get next sequence number
529 0           $node->{out_seq}++;
530 0           $seq_num = $node->{out_seq};
531             }
532              
533             # compute message part size
534 0           my $msg_part_size = $cfg->MSG_PART_SIZE_DFLT;
535 0 0 0       if(defined $node->{msg_part_size} and $node->{msg_part_size} > 0)
536             {
537 0           $msg_part_size = $node->{msg_part_size};
538             }
539              
540             # compute data size
541 0           my $file_size = (stat $this->{file_handle})[7];
542 0           my $data_size = $file_size - $this->{data_offset};
543 0 0         if($data_size <= 0)
544             {
545 0 0         $node_tbl->unlock() unless $was_locked; # unlock node_tbl if needed
546 0           return "send_via_email(): data_size is <= 0 ($data_size)!";
547             }
548              
549             # compute num_parts
550 0           my $num_parts = int($data_size / $msg_part_size);
551 0 0         $num_parts++ if ($data_size % $msg_part_size) > 0;
552              
553             # num_parts should be 1 for meta message
554 0 0 0       if($this->{is_meta_message} and $num_parts > 1)
555             {
556 0 0         $node_tbl->unlock() unless $was_locked; # unlock node_tbl if needed
557 0           return "send_via_email(): num_parts cannot be > 1 for meta message!";
558             }
559             # $part_num cannot be greater than $num_parts
560 0 0 0       if(defined $part_num and $part_num and $part_num > $num_parts)
      0        
561             {
562 0 0         $node_tbl->unlock() unless $was_locked; # unlock node_tbl if needed
563 0           return "send_via_email(): part_num ($part_num) cannot be greater " .
564             "than num_parts ($num_parts)!";
565             }
566              
567             # compute base subject
568 0           my $subject = $cfg->MAIL_MRK . ':' . $cfg->THIS_NODE;
569 0 0         $subject .= ":$seq_num" if $seq_num;
570              
571 0 0         if($is_re_send)
572             {
573             # to save disk space, don't copy message to file for RE_SEND
574 0 0         log_info("send_via_email(): transmitting $rcv_node_id RE_SEND " .
575             "message $seq_num" . ($part_num ? ":$part_num" : '') . "\n");
576             }
577             else
578             {
579             # copy message to file (for non- RE_SEND)
580              
581 0           my $filename;
582              
583 0 0         if($this->{is_meta_message})
584             {
585             # copy meta message to mboxes/out subdirectory
586 0           $filename = sprintf("%s_%s_%s.msg",
587             $cfg->THIS_NODE, $rcv_node_id, "META");
588 0           my $dirname = $cfg->ECS_MBX_OUT_DIR;
589             # create directory if it doesn't already exist
590 0 0         mkdir $dirname unless -e $dirname;
591 0           $filename = catfile($dirname, $filename);
592             }
593             else
594             {
595             # copy regular message to mboxes/out_NODE subdirectory
596 0           $filename = format_msg_filename($rcv_node_id, $seq_num);
597             # create directory if it doesn't already exist
598 0           my $dirname = dirname($filename);
599 0 0         mkdir $dirname unless -e $dirname;
600             }
601              
602             # don't overwrite $filename file if it already exists
603 0           my $fh;
604 0 0         if(-e $filename)
605             {
606 0           my $template = $filename . "_XXXXXX";
607 0           ($fh, $filename) = tempfile($template);
608 0 0         return "send_via_email(): unable to open _XXXX file: " .
609             "$filename"
610             unless $fh;
611             }
612             else
613             {
614 0           $fh = new IO::File;
615 0 0         return "send_via_email(): unable to open file: " .
616             "$filename"
617             unless $fh->open("> $filename");
618             }
619              
620 0           print $fh "Subject: $subject\n";
621 0           print $fh "To: $node->{addr}\n";
622 0           print $fh "From: " . $cfg->SMTP_FROM . "\n\n";
623             # copy data to $fh
624             $err = "Unable to position file pointer for file $this->{filename}" .
625             " to position $this->{data_offset}: $!"
626 0 0         unless seek $this->{file_handle}, $this->{data_offset}, 0;
627 0           my $buffer;
628 0           while(1)
629             {
630 0 0         if($err)
631             {
632 0 0         $node_tbl->unlock() unless $was_locked; # unlock if needed
633 0           close $fh;
634 0           unlink $filename;
635 0           return $err;
636             }
637              
638 0           my $bytecount = read $this->{file_handle}, $buffer, 65536;
639 0 0         if(not defined $bytecount)
    0          
640             {
641 0           $err = "send_via_email(): Problem reading input file " .
642             "$this->{filename}: $!";
643             }
644             elsif($bytecount == 0)
645             {
646 0           last; # EOF
647             }
648             else
649             {
650 0 0         print $fh $buffer
651             or $err = "send_via_email(): Problem writing output " .
652             "file $filename: $!";
653             }
654             }
655 0           close $fh;
656 0           chmod $FILEMODE, $filename;
657             }
658              
659 0 0         if($num_parts == 1)
660             {
661             # read all data, send single email message
662             $err = "Unable to position file pointer for file $this->{filename}" .
663             " to position $this->{data_offset}: $!"
664 0 0         unless seek $this->{file_handle}, $this->{data_offset}, 0;
665              
666 0 0         if(not $err)
667             {
668 0           my $all_data;
669 0           my $bytecount = read $this->{file_handle}, $all_data, $data_size;
670              
671 0 0 0       if(not defined $bytecount)
    0          
    0          
672             {
673 0           $err = "send_via_email(): Problem reading input file " .
674             "$this->{filename}: $!";
675             }
676             elsif($bytecount != $data_size)
677             {
678 0           $err = "send_via_email(): Problem reading from input file " .
679             "$this->{filename}: expected $msg_part_size bytes, " .
680             "found $bytecount bytes.";
681             }
682             elsif($this->{is_meta_message}
683             and ($node->{encr_meta} !~ /true/io))
684             {
685             # don't encrypt meta-message
686 0           $err = send_email($node->{addr}, $subject, $all_data);
687             }
688             else
689             {
690             # send encrypted message
691             $err = send_encrypted_email(
692             $node->{encr_typ},
693             $node->{addr_r},
694             $node->{addr},
695             $node->{encr_out_keyid},
696             $node->{encr_out_passphrase},
697 0           $subject,
698             $all_data);
699             }
700             }
701             }
702             else
703             {
704             # process message parts ...
705              
706 0           my $min_part_num = 1;
707 0           my $max_part_num = $num_parts;
708 0 0         if($part_num)
709             {
710             # if $part_num specified, limit to that $part_num
711 0           $min_part_num = $part_num;
712 0           $max_part_num = $part_num;
713             }
714              
715             # iterate through message part(s), send email message(s)
716 0           my $parts_sent = 0;
717 0           for($part_num = $min_part_num; $part_num <= $max_part_num; $part_num++)
718             {
719             my $part_offset = $this->{data_offset} +
720 0           ($part_num -1) * $msg_part_size;
721             $err = "Unable to position file pointer for file " .
722             "$this->{filename} to position $this->{data_offset}: $!"
723 0 0         unless seek $this->{file_handle}, $part_offset, 0;
724              
725 0 0         if(not $err)
726             {
727 0           my $part_data;
728 0           my $bytecount = read $this->{file_handle}, $part_data,
729             $msg_part_size;
730              
731 0 0 0       if(not defined $bytecount)
    0          
    0          
732             {
733 0           $err = "send_via_email(): Problem reading input file " .
734             "$this->{filename}: $!";
735             }
736             elsif($part_num < $num_parts and $bytecount != $msg_part_size)
737             {
738 0           $err = "send_via_email(): Problem reading $rcv_node_id " .
739             "message part $part_num/$num_parts from input file " .
740             "$this->{filename}: expected $msg_part_size bytes, " .
741             "found $bytecount bytes.";
742             }
743             elsif($bytecount <= 0)
744             {
745 0           $err = "send_via_email(): Problem reading $rcv_node_id " .
746             "message part $part_num/$num_parts from input file " .
747             "$this->{filename}: found $bytecount bytes.";
748             }
749             else
750             {
751             # send encrypted email message
752             $err = send_encrypted_email(
753             $node->{encr_typ},
754             $node->{addr_r},
755             $node->{addr},
756             $node->{encr_out_keyid},
757             $node->{encr_out_passphrase},
758 0           "$subject:$part_num/$num_parts",
759             $part_data);
760             }
761             }
762              
763 0 0         if($err)
764             {
765 0 0         if($parts_sent == 0)
766             {
767             # nothing sent yet, so quit now (possible smtp problem?)
768 0           last;
769             }
770             else
771             {
772             # part of message was sent, so log error and continue
773 0           log_error($err);
774 0           $err = '';
775             }
776             }
777             else
778             {
779 0           $parts_sent++;
780             }
781             }
782             }
783              
784 0 0         if(not $err)
785             {
786             # update node last_out, possibly out_seq
787 0           $node->{last_out} = time();
788 0 0         $err = $node_tbl->ERROR
789             unless $node_tbl->write($rcv_node_id, $node);
790             }
791             $node_tbl->unlock() # unlock node_tbl if needed
792 0 0         unless $was_locked;
793              
794 0           return $err;
795             }
796              
797             1;
798              
799             __DATA__