File Coverage

blib/lib/EMDIS/ECS/Message.pm
Criterion Covered Total %
statement 115 162 70.9
branch 33 70 47.1
condition n/a
subroutine 24 25 96.0
pod 0 19 0.0
total 172 276 62.3


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             #
3             # Copyright (C) 2002-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::Message).
7              
8             package EMDIS::ECS::Message;
9              
10 1         146 use EMDIS::ECS qw($ECS_CFG $VERSION ecs_is_configured
11 1     1   12749 pgp2_decrypt openpgp_decrypt);
  1         3  
12 1     1   8 use IO::File;
  1         2  
  1         172  
13 1     1   8 use MIME::QuotedPrint qw( decode_qp );
  1         12  
  1         52  
14 1     1   7 use strict;
  1         2  
  1         25  
15 1     1   4 use vars qw($EOL_PATTERN);
  1         2  
  1         72  
16              
17             BEGIN {
18 1     1   1628 $EOL_PATTERN = "\r?\n";
19             }
20              
21             # ----------------------------------------------------------------------
22             # Constructor.
23             # If error encountered, returns error message instead of object reference.
24             sub new {
25 19     19 0 1351 my $arg1 = shift;
26 19         29 my $this;
27 19 50       37 if(ref $arg1) {
28             # invoked as instance method
29 0         0 $this = $arg1;
30             }
31             else {
32             # invoked as class method
33 19         30 $this = {};
34 19         34 bless $this, $arg1;
35             }
36              
37             # remember raw text
38 19         61 my $raw_text = shift;
39 19         47 $this->{raw_text} = $raw_text;
40              
41             # parse raw email message text
42 19         287 $raw_text =~ s/$EOL_PATTERN/\n/g; # convert to more easily parseable format
43 19 100       93 if($raw_text =~ /(.*?\n)\n(.*)/s) {
44 18         71 $this->{headers} = $1;
45 18         47 $this->{body} = $2;
46 18         32 $this->{cleartext} = '';
47             } else {
48 1         6 return "unable to parse message raw text.";
49             }
50              
51             # get "Subject" (required)
52 18 100       90 if($this->{headers} =~ /^Subject:\s*(.+?)$/im) {
53 17         41 $this->{subject} = $1;
54             } else {
55 1         10 return "message subject not found.";
56             }
57              
58             # attempt to parse "Subject" into MAIL_MRK:sender[:seqnum]
59 17         22 my $mail_mrk = 'EMDIS';
60 17 50       44 if(ecs_is_configured()) {
61 0         0 $mail_mrk = $ECS_CFG->MAIL_MRK;
62             }
63             else {
64 17         160 warn "ECS not configured, using MAIL_MRK = '$mail_mrk'\n";
65             }
66 17         52 my ($mark, $sender, $seq_num);
67 17 100       141 if($this->{subject} =~ /$mail_mrk:(\S+?):(\d+)(:(\d+)\/(\d+))?\s*$/i) {
    100          
68             # regular message
69 9         19 $this->{is_ecs_message} = 1;
70 9         14 $this->{is_meta_message} = '';
71 9         28 $this->{sender} = $1;
72 9         17 $this->{seq_num} = $2;
73 9 100       33 $this->{part_num} = defined $4 ? $4 : 1;
74 9 100       19 $this->{num_parts} = defined $5 ? $5 : 1;
75 9 100       24 if($this->{part_num} > $this->{num_parts}) {
76 1         8 return "part_num is greater than num_parts: " . $this->{subject};
77             }
78             }
79             elsif($this->{subject} =~ /$mail_mrk:(\S+)\s*$/i) {
80             # meta-message
81 3         6 $this->{is_ecs_message} = 1;
82 3         6 $this->{is_meta_message} = 1;
83 3         9 $this->{sender} = $1;
84             }
85             else {
86             # not an ECS message
87 5         10 $this->{is_ecs_message} = '';
88 5         17 $this->{is_meta_message} = '';
89             }
90              
91             # get "Content-type" (optional)
92 16 100       51 if($this->{headers} =~ /^Content-type:\s*(.+?)$/im) {
93 1         3 $this->{content_type} = $1;
94             }
95              
96             # get "From" (optional)
97 16 100       76 if($this->{headers} =~ /^From:\s*(.+?)$/im) {
98 14         40 $this->{from} = $1;
99             }
100              
101             # get "To" (optional)
102 16 100       69 if($this->{headers} =~ /^To:\s*(.+?)$/im) {
103 14         60 $this->{to} = $1;
104             }
105              
106             # decode quoted printable e-mails if necessary
107 16 100       60 if($this->{headers} =~ /^Content-Transfer-Encoding\s*:\s*quoted-printable$/im) {
108             $this->{headers} =~
109 1         12 s/^(Content-Transfer-Encoding)\s*:\s*quoted-printable$/$1: 8bit/im;
110 1         7 $this->{body} = decode_qp($this->{body});
111             }
112              
113 16         65 return $this;
114             }
115              
116             # ----------------------------------------------------------------------
117             # Accessor method (read-only).
118             sub content_type {
119 4     4 0 8 my $this = shift;
120 4         14 return $this->{content_type};
121             }
122              
123             # ----------------------------------------------------------------------
124             # Accessor method (read-only).
125             sub cleartext {
126 5     5 0 176 my $this = shift;
127 5         25 return $this->{cleartext};
128             }
129              
130             # ----------------------------------------------------------------------
131             # Accessor method (read-only).
132             sub body {
133 5     5 0 60 my $this = shift;
134 5         19 return $this->{body};
135             }
136              
137             # ----------------------------------------------------------------------
138             # Accessor method (read-only).
139             sub from {
140 6     6 0 12 my $this = shift;
141 6         31 return $this->{from};
142             }
143              
144             # ----------------------------------------------------------------------
145             # Accessor method (read-only).
146             sub headers {
147 4     4 0 6 my $this = shift;
148 4         37 return $this->{headers};
149             }
150              
151             # ----------------------------------------------------------------------
152             # Accessor method (read-only).
153             sub is_ecs_message {
154 13     13 0 473 my $this = shift;
155 13         45 return $this->{is_ecs_message};
156             }
157              
158             # ----------------------------------------------------------------------
159             # Accessor method (read-only).
160             sub is_meta_message {
161 10     10 0 23 my $this = shift;
162 10         40 return $this->{is_meta_message};
163             }
164              
165             # ----------------------------------------------------------------------
166             # Accessor method (read-only).
167             sub num_parts {
168 2     2 0 5 my $this = shift;
169 2         8 return $this->{num_parts};
170             }
171              
172             # ----------------------------------------------------------------------
173             # Accessor method (read-only).
174             sub part_num {
175 2     2 0 4 my $this = shift;
176 2         8 return $this->{part_num};
177             }
178              
179             # ----------------------------------------------------------------------
180             # Accessor method (read-only).
181             sub raw_text {
182 4     4 0 59 my $this = shift;
183 4         14 return $this->{raw_text};
184             }
185              
186             # ----------------------------------------------------------------------
187             # Accessor method (read-only).
188             sub sender {
189 9     9 0 18 my $this = shift;
190 9         29 return $this->{sender};
191             }
192              
193             # ----------------------------------------------------------------------
194             # Accessor method (read-only).
195             sub seq_num {
196 7     7 0 12 my $this = shift;
197 7         26 return $this->{seq_num};
198             }
199              
200             # ----------------------------------------------------------------------
201             # Accessor method (read-only).
202             sub subject {
203 5     5 0 62 my $this = shift;
204 5         19 return $this->{subject};
205             }
206              
207             # ----------------------------------------------------------------------
208             # Accessor method (read-only).
209             sub to {
210 6     6 0 11 my $this = shift;
211 6         22 return $this->{to};
212             }
213              
214             # ----------------------------------------------------------------------
215             # Accessor method (read-only).
216             sub full_msg {
217 2     2 0 4 my $this = shift;
218 2         28 return $this->{headers} . "\n" . $this->{body};
219             }
220              
221             # ----------------------------------------------------------------------
222             # save raw message to file
223             # returns empty string if successful, otherwise returns error message
224             sub save_to_file
225             {
226 1     1 0 64 my $err = '';
227 1         2 my $arg1 = shift;
228 1         2 my ($filename,$msg);
229 1 50       4 if(ref $arg1) {
230             # invoked as instance method
231 1         2 $msg = $arg1;
232 1         13 $filename = shift;
233             }
234             else {
235             # invoked as class method
236 0         0 $filename = $arg1;
237 0         0 my $raw_text = shift;
238 0         0 $msg = new EMDIS::ECS::Message($raw_text);
239             }
240 1 50       76 open MSGFILE, ">$filename"
241             or return "Unable to create file $filename: $!";
242 1 50       7 print MSGFILE $msg->full_msg()
243             or $err = "Unable to write file $filename: $!";
244 1         40 close MSGFILE;
245 1         26 chmod $EMDIS::ECS::FILEMODE, $filename;
246 1         7 return $err; # return error message (if any)
247             }
248              
249             # ----------------------------------------------------------------------
250             # read message from file
251             # returns object reference if successful, otherwise returns error message
252             sub read_from_file
253             {
254 1     1 0 2 my $err = '';
255 1         4 my $arg1 = shift;
256 1         2 my ($filename,$raw_text,$this);
257 1 50       3 if(ref $arg1) {
258             # invoked as instance method
259 0         0 $this = $arg1;
260 0         0 $filename = shift;
261             }
262             else {
263             # invoked as class method
264 1         2 $filename = $arg1;
265             }
266              
267             # read file
268 1 50       40 open(MSGFILE, "< $filename")
269             or return "Unable to open file $filename: $!";
270 1 50       41 $raw_text = join('', )
271             or $err = "Unable to read file $filename: $!";
272 1         11 close MSGFILE;
273 1 50       7 return $err if $err; # return error message (if any)
274              
275             # attempt to construct object
276 1         2 my $newmsg;
277 1 50       3 if(ref $arg1) {
278 0         0 $newmsg = $this->new($raw_text); # re-define this object
279             }
280             else {
281 1         4 $newmsg = new EMDIS::ECS::Message($raw_text);
282             }
283              
284             # set 'cleartext' attribute of message object
285             $newmsg->{cleartext} = $newmsg->{body}
286 1 50       7 if ref $newmsg;
287              
288 1         5 return $newmsg;
289             }
290              
291             # ----------------------------------------------------------------------
292             # read and decrypt message from encrypted file
293             # returns object reference if successful, otherwise returns error message
294             sub read_from_encrypted_file
295             {
296 0     0 0   my $err = '';
297 0           my $arg1 = shift;
298 0           my ($filename,$raw_text,$this);
299 0 0         if(ref $arg1) {
300             # invoked as instance method
301 0           $this = $arg1;
302 0           $filename = shift;
303             }
304             else {
305             # invoked as class method
306 0           $filename = $arg1;
307             }
308              
309             # read encrypted file
310 0           my $newmsg = read_from_file($filename);
311 0 0         return $newmsg unless ref $newmsg; # check for error
312 0 0         return "not an ECS message" unless $newmsg->is_ecs_message;
313              
314             # read relevant node info from node_tbl
315 0           my $node_tbl = $main::ECS_NODE_TBL;
316 0           my $was_locked = $node_tbl->LOCK;
317 0 0         if(not $was_locked) {
318 0 0         $node_tbl->lock() # lock node_tbl
319             or return "unable to lock node_tbl: " .
320             $node_tbl->ERROR;
321             }
322 0           my $node = $node_tbl->read($newmsg->sender);
323 0 0         if(not $was_locked) {
324 0           $node_tbl->unlock(); # unlock node_tbl
325             }
326 0 0         if(not $node) {
327 0           return "node not found: " . $newmsg->sender;
328             }
329              
330             # decrypt message into temp file
331 0           my $decr_filename = "$filename.asc";
332 0           for ($node->{encr_typ}) {
333 0 0         /PGP2/i and do {
334             $err = pgp2_decrypt(
335             $filename,
336             $decr_filename,
337             (/verify/i ? $node->{encr_sig} : undef),
338 0 0         $node->{encr_out_passphrase});
339 0           last;
340             };
341 0 0         /OpenPGP/i and do {
342             $err = openpgp_decrypt(
343             $filename,
344             $decr_filename,
345             (/verify/i ? $node->{encr_sig} : undef),
346 0 0         $node->{encr_out_passphrase});
347 0           last;
348             };
349 0           $err = "unrecognized encr_typ: $node->{encr_typ}\n";
350             }
351 0 0         if($err) {
352 0           unlink $decr_filename;
353 0           chomp($err);
354 0           return $err;
355             }
356              
357             # read message cleartext from temp file
358 0           my $fh = new IO::File;
359 0 0         return "unable to open file: $decr_filename"
360             unless $fh->open("< $decr_filename");
361 0           my @cleartext = $fh->getlines();
362 0           close $fh;
363              
364             # remove temp file
365 0           unlink $decr_filename;
366              
367             # set 'cleartext' message attribute
368 0           $newmsg->{cleartext} = join('', @cleartext);
369              
370 0           return $newmsg;
371             }
372              
373              
374             1;
375              
376             __DATA__