File Coverage

blib/lib/PGP/Mail.pm
Criterion Covered Total %
statement 133 136 97.7
branch 33 38 86.8
condition 15 20 75.0
subroutine 14 14 100.0
pod 3 8 37.5
total 198 216 91.6


line stmt bran cond sub pod time code
1             package PGP::Mail;
2              
3 3     3   1644 use strict;
  3         5  
  3         107  
4              
5 3     3   14 use Fcntl;
  3         7  
  3         967  
6 3     3   3278 use IO::Handle;
  3         30775  
  3         157  
7 3     3   3780 use GnuPG::Interface;
  3         1120765  
  3         108  
8 3     3   3515 use MIME::Parser;
  3         461078  
  3         134  
9              
10             =head1 NAME
11              
12             PGP::Mail - Signature checking for PGP-signed mail messages
13              
14             =head1 SYNOPSIS
15              
16             use PGP::Mail;
17             my $pgpmail=new PGP::Mail($mail, {default-keyring=>"kr.gpg"});
18             $status=$pgpmail->status();
19             $keyid=$pgpmail->keyid();
20             $data=$pgpmail->data();
21              
22             =head1 DESCRIPTION
23              
24             This module operates on PGP-signed mail messages. It checks the signature of
25             either a standard clearsigned, a signed message or a PGP/MIME style message.
26              
27             It returns an object which can be used to check what the signed data was,
28             whether the signature verification succeeded, and what keyid did the
29             signature.
30              
31             =cut
32              
33 3     3   35 use vars qw($VERSION);
  3         7  
  3         4630  
34              
35             $VERSION=('$Revision: 1.7 $'=~/(\d+\.\d+)/)[0];
36              
37             =head2 my I<$pgpmail>=B PGP::Mail(I<$mesg>, I<$args>);
38              
39             Creates a new PGP::Mail object using the RFC2822 message specified in
40             I<$mesg>. It will do the signature verification itself. I<$args> is a
41             hashref which gets passed to GnuPG::Interface's options. It is particularly
42             worth looking at L for this.
43              
44             =cut
45              
46             sub new {
47 5     5 0 297 my $class=shift;
48 5   33     54 $class = ref($class) || $class;
49              
50 5         23 my $self=bless{},$class;
51 5         32 $self->init(@_);
52 5         137 $self;
53             }
54              
55             =head2 I<$pgpmail>->B();
56              
57             Returns the status of the signature verification (currently C, C
58             or C).
59              
60             =cut
61              
62             sub status {
63 5     5 1 701 my $self=shift;
64              
65 5         28 return $self->{'status'}
66             }
67              
68             =head2 I<$pgpmail>->B();
69              
70             Returns the keyid of this signature, in the format "0xI<64-bit_key_id>".
71              
72             =cut
73              
74             sub keyid {
75 3     3 1 31 my $self=shift;
76              
77 3         16 return $self->{'keyid'}
78             }
79              
80             =head2 I<$pgpmail>->B();
81              
82             Returns the signed data, run through MIME::Parser if necessary.
83              
84             =cut
85              
86             sub data {
87 3     3 1 41 my $self=shift;
88              
89 3         18 return $self->{'data'}
90             }
91              
92             sub init {
93 5     5 0 11 my $self = shift;
94 5         10 my $data = shift;
95 5   50     19 my $args = shift || {};
96              
97 5         157 my @lines=map {$_."\n"} split /\r?\n/, $data;
  119         231  
98              
99 5         28 my @header=();
100 5         11 my $finished=0;
101 5         24 while(!$finished) {
102 28         40 my $line=shift(@lines);
103              
104 28 50 66     205 if(!defined $line) {
    100          
    100          
105 0         0 $finished=1;
106             }
107             elsif(defined $line && $line=~/^$/) {
108 5         16 $finished=1;
109             }
110             elsif($line=~/^[ \t]+/) {
111 2         13 $header[-1].=$line;
112             }
113             else {
114 21         58 push(@header,$line);
115             }
116             }
117              
118             # we should now have the header in @header and the body
119             # in @lines
120              
121 5         20 for my $header (@header) {
122 21 100       85 if($header=~/^content-type:\s+(\S+\/\S+)(;.*)?$/si) {
123 2 50       14 if(lc $1 eq "multipart/signed") {
124 2 50       14 if($header=~/protocol="?application\/pgp/i) {
125 2         13 $self->{PGPMIME}=1;
126 2         25 $self->{PGPMIMEBOUND} =
127             ($header=~/boundary=\"([^\"]+)\"(;.*)?$/i)[0];
128             }
129             }
130             }
131             }
132              
133 5 100       33 if(!$self->{PGPMIME}) {
134 3         6 for my $line (@lines) {
135 3 100       21 if($line=~/^-----BEGIN PGP SIGNED MESSAGE-----\s*$/) {
136 2         7 $self->{PGPTEXT}=1;
137 2         4 last;
138             }
139             }
140             }
141              
142 5         13 $self->{status}="unverified";
143 5         20 $self->{keyid}="0x0000000000000000";
144 5         49 $self->{data}=join("",@lines);
145              
146 5 100 66     34 if(!$self->{PGPTEXT} && !$self->{PGPMIME}) {
147 1         4 return 0;
148             }
149              
150 4         148 $self->{gpg}=new GnuPG::Interface;
151 4         11074 $self->{gpg}->options->hash_init( %$args );
152 4         36720 $self->{gpg}->options->meta_interactive( 0 );
153              
154 4 100       205 if($self->{PGPTEXT}) {
155 2         13 $self->textpgp(\@lines);
156             }
157             else {
158 2         15 $self->mimepgp(\@lines, $self->{PGPMIMEBOUND});
159             }
160 4         227 return 1;
161             }
162              
163             sub textpgp {
164 2     2 0 5 my $self=shift;
165 2         2 my $data=shift;
166              
167 2         28 my $input=new IO::Handle;
168 2         80 my $output=new IO::Handle;
169 2         44 my $error=new IO::Handle;
170 2         35 my $status=new IO::Handle;
171 2         38 my $pp=new IO::Handle;
172 2         66 my $handles=GnuPG::Handles->new(
173             stdin=>$input,
174             stdout=>$output,
175             stderr=>$error,
176             status=>$status,
177             passphrase=>$pp
178             );
179 2         10189 my $pid=$self->{gpg}->decrypt(handles=>$handles);
180 2         13822 close $pp;
181              
182 2         96 print $input join "",@$data;
183 2         13 close $input;
184              
185 2         188290 $self->{data}=join "",<$output>;
186 2         62 close $output;
187              
188 2         51 $self->get_status($status);
189 2         726 waitpid $pid, 0;
190             }
191              
192             sub get_status {
193 4     4 0 26 my $self=shift;
194 4         14 my $statusfh=shift;
195              
196 4         150236 for my $line (<$statusfh>) {
197 10 100       228 if($line =~ /^\[GNUPG:\] GOODSIG (\w+) /) {
    100          
198 2         18 $self->{status}="good";
199 2         27 $self->{keyid}="0x$1";
200             }
201             elsif($line =~ /^\[GNUPG:\] BADSIG (\w+) /) {
202 2         60 $self->{status}="bad";
203 2         46 $self->{keyid}="0x$1";
204             }
205             }
206             }
207              
208             sub mimepgp {
209 2     2 0 4 my $self=shift;
210 2         5 my $data=shift;
211 2         6 my $bound=shift;
212              
213 2         5 my $state="before";
214 2         3 my $sigdata="";
215 2         30 my $signature="";
216 2         7 for my $line (@$data) {
217 56 100 100     436 if($state eq "before" &&
    100 100        
    100 100        
    100          
    100          
218             $line eq "--$bound\n") {
219 2         6 $state="data";
220 2         6 next;
221             }
222             elsif($state eq "data" &&
223             $line eq "--$bound\n") {
224 2         4 $state="sig";
225 2         5 next;
226             }
227             elsif($state eq "sig" &&
228             $line eq "--$bound--\n") {
229 2         4 $state="finished";
230 2         5 next;
231             }
232             elsif($state eq "data") {
233 24         31 my $l=$line;
234 24         29 chomp $l;
235 24         50 $sigdata.=$l."\r\n";
236             }
237             elsif($state eq "sig") {
238 22         38 $signature.=$line;
239             }
240             }
241 2         5 chomp $signature;
242 2         12 $sigdata=~s/\r\n$//;
243              
244 2         23 my $parser=new MIME::Parser;
245 2         358 $parser->output_to_core(1);
246 2         28 $signature=$parser->parse_data($signature)->bodyhandle->as_string;
247              
248 2         9162 my $fn="";
249 2         9 for my $i (0..3) {
250 2 50       361 if(sysopen(SIGNATURE,
251             $fn="/tmp/file-$$-$i-" . time() . ".dat",
252             O_EXCL | O_RDWR | O_CREAT, 0666)) {
253 2         5 last;
254             }
255             else {
256 0         0 $fn="";
257             }
258             }
259              
260 2 50       13 if(!length $fn) {
261 0         0 return 0;
262             }
263              
264 2         15 print SIGNATURE $sigdata;
265 2         119 close SIGNATURE;
266              
267 2         30 my $input=new IO::Handle;
268 2         41 my $output=new IO::Handle;
269 2         33 my $error=new IO::Handle;
270 2         34 my $status=new IO::Handle;
271 2         77 my $handles=GnuPG::Handles->new(
272             stdin=>$input,
273             stdout=>$output,
274             stderr=>$error,
275             status=>$status
276             );
277 2         9981 my $pid=$self->{gpg}->verify(handles=>$handles, command_args=>["-",$fn]);
278              
279 2         15322 print $input $signature;
280 2         52 close $input;
281              
282 2         166 $parser=new MIME::Parser;
283 2         2172 $parser->output_to_core(1);
284 2         71 $self->{data}=$parser->parse_data($sigdata)->bodyhandle->as_string;
285              
286 2         10463 $self->get_status($status);
287              
288 2         73 waitpid $pid, 0;
289 2         1118 unlink $fn;
290             }
291              
292             =head1 BUGS
293              
294             The style of this module leaves quite a bit to be desired, and it only
295             supports verifying signatures at the moment, rather than the full encryption,
296             decryptions, and creating the messages.
297              
298             =head1 AUTHOR
299              
300             Matthew Byng-Maddick Embm@colondot.netE
301              
302             =head1 SEE ALSO
303              
304             L, L, L, L.
305              
306             =cut
307              
308             1;