File Coverage

lib/Net/Sieve.pm
Criterion Covered Total %
statement 35 407 8.6
branch 0 200 0.0
condition 0 63 0.0
subroutine 11 33 33.3
pod 15 15 100.0
total 61 718 8.5


line stmt bran cond sub pod time code
1             package Net::Sieve;
2 1     1   30611 use strict;
  1         3  
  1         38  
3 1     1   7 use warnings;
  1         2  
  1         57  
4              
5             =head1 NAME
6              
7             Net::Sieve - Implementation of managesieve protocol to manage sieve scripts
8              
9             =head1 SYNOPSIS
10              
11             use Net::Sieve;
12              
13             my $SieveServer = Net::Sieve->new (
14             server => 'imap.server.org',
15             user => 'user',
16             password => 'pass' ,
17             );
18              
19             foreach my $script ( $SieveServer->list() ) {
20             print $script->{name}." ".$script->{status}."\n";
21             };
22              
23             my $name_script = 'test';
24              
25             # read
26             print $SieveServer->get($name_script);
27              
28             # write
29             my $test_script='
30             require "fileinto";
31             ## Place all these in the "Test" folder
32             if header :contains "Subject" "[Test]" {
33             fileinto "Test";
34             }
35             ';
36              
37             # other
38             $SieveServer->put($name_script,$new_script);
39             $SieveServer->activate($name_script);
40             $SieveServer->deactivate();
41             $SieveServer->delete($name_script);
42              
43              
44             =head1 DESCRIPTION
45              
46             B is a package for clients for the "MANAGESIEVE" protocol, which is an Internet Draft protocol for manipulation of "Sieve" scripts in a repository. More simply, Net::Sieve lets you control your mail-filtering rule files on a mail server.
47              
48             B supports the use of "TLS" via the "STARTTLS" command. B open the connexion to the sieve server, methods allow to list all scripts, activate or deactivate scripts, read, delete or put scripts.
49              
50             Most of code come from the great Phil Pennock B command-line tool L.
51              
52             See L to manipulate Sieve scripts content.
53              
54             =cut
55              
56 1     1   974 use Authen::SASL 2.11 qw(Perl);
  1         2057  
  1         7  
57             # 2.11: first version with non-broken DIGEST-MD5
58             # Earlier versions don't allow server verification
59             # NB: code still explicitly checks for a new-enough version, so
60             # if you have an older version of Authen::SASL and know what you're
61             # doing then you can remove this version check here. I advise
62             # against it, though.
63             # Perl: Need a way to ask which mechanism to send
64 1     1   7818 use Authen::SASL::Perl::EXTERNAL; # We munge inside its private stuff.
  1         226  
  1         23  
65 1     1   776 use IO::Socket::INET6;
  1         41627  
  1         9  
66 1     1   2016 use IO::Socket::SSL 0.97; # SSL_ca_path bogus before 0.97
  1         83623  
  1         8  
67 1     1   998 use MIME::Base64;
  1         792  
  1         74  
68              
69             BEGIN {
70 1     1   8 use Exporter ();
  1         2  
  1         22  
71 1     1   4 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         2  
  1         100  
72 1     1   2 $VERSION = '0.11';
73 1         17 @ISA = qw(Exporter);
74             #Give a hoot don't pollute, do not export more than needed by default
75 1         2 @EXPORT = qw();
76 1         3 @EXPORT_OK = qw();
77 1         3840 %EXPORT_TAGS = ();
78             }
79              
80              
81             my %capa;
82             my %raw_capabilities;
83             my %capa_dosplit = map {$_ => 1} qw( SASL SIEVE );
84             # Key is permissably empty keyword, value if defined is closure to call with
85             # capabilities after receiving complete list, for verifying permissability.
86             # First param $sock, second \%capa, third \%raw_capabilities
87             my %capa_permit_empty = (
88             # draft 7 onwards clarify that empty SASL is permitted, but is error
89             # in absense of STARTTLS
90             SASL => sub {
91             return if exists $_[1]{STARTTLS};
92             # We die because there's no way to authenticate.
93             # Spec states that after STARTTLS SASL must be non-empty
94             warn "Empty SASL not permitted without STARTTLS\n";
95             },
96             SIEVE => undef,
97             );
98             my $DEBUGGING = 1;
99              
100             =head1 CONSTRUCTOR
101              
102             =head2 new
103              
104             Usage :
105             my $SieveServer = Net::Sieve->new (
106             server => 'imap.server.org',
107             user => 'user',
108             password => 'pass' );
109             Returns :
110             Net::Sieve object which contain current open socket
111             Argument :
112             server : default localhost
113             port : default 2000
114             user : default logname or $ENV{USERNAME} or $ENV{LOGNAME}
115             password :
116             net_domain :
117             sslkeyfile : default search in /etc/ssl/certs
118             sslcertfile : default search in /etc/ssl/certs
119             autmech : to force a particular authentication mechanism
120             authzid : request authorisation to act as the specified id
121             realm : pass realm information to the authentication mechanism
122             ssl_verif : default 0x01, set 0x00 to don't verify and allow self-signed cerificate
123             notssl_verif: default 0x00, set 0x01 to don't verify and allow self-signed cerificate
124             debug : default 0, set 1 to have transmission logs
125             dumptlsinfo : dump tls information
126              
127             =cut
128              
129             sub new
130             {
131 0     0 1   my ($class, %param) = @_;
132              
133 0   0       my $self = bless ({}, ref ($class) || $class);
134              
135 0   0       my $server = $param{server}||'localhost';
136 0   0       my $port = $param{port}||'2000';
137 0           my $user = $param{user};
138 0           my $password = $param{password};
139 0   0       my $net_domain = $param{net_domain}||AF_UNSPEC;
140 0           my $sslkeyfile = $param{sslkeyfile};
141 0           my $sslcertfile = $param{sslcertfile};
142 0           my $realm = $param{realm};
143 0           my $authmech = $param{autmech};
144 0           my $authzid = $param{authzid};
145 0           my $ssl_verify = 0x01;
146 0 0         $ssl_verify = 0x01 if $param{ssl_verify};
147 0 0         $ssl_verify = 0x00 if $param{ssl_verify} eq '0x00';
148 0 0         $ssl_verify = 0x00 if $param{notssl_verify};
149 0           my $dump_tls_information = $param{dumptlsinfo};
150 0           $DEBUGGING = $param{debug};
151              
152              
153              
154 0           my %ssl_options = (
155             SSL_version => 'SSLv23:!SSLv2:!SSLv3',
156             SSL_cipher_list => 'ALL:!aNULL:!NULL:!LOW:!EXP:!ADH:@STRENGTH',
157             SSL_verify_mode => $ssl_verify,
158             SSL_ca_path => '/etc/ssl/certs',
159             );
160              
161 0           my $prioritise_auth_external = 0;
162 0           my ($forbid_clearauth, $forbid_clearchan) = (0, 0);
163              
164 0 0         unless (defined $server) {
165 0           $server = 'localhost';
166 0 0 0       if (exists $ENV{'IMAP_SERVER'}
167             and $ENV{'IMAP_SERVER'} !~ m!^/!) {
168 0           $server = $ENV{'IMAP_SERVER'};
169             # deal with a port number.
170 0 0         unless ($server =~ /:.*:/) { # IPv6 address literal
171 0           $server =~ s/:\d+\z//;
172             }
173             }
174             }
175              
176 0 0         die "Bad server name\n"
177             unless $server =~ /^[A-Za-z0-9_.-]+\z/;
178 0 0         die "Bad port specification\n"
179             unless $port =~ /^[A-Za-z0-9_()-]+\z/;
180              
181 0 0         unless (defined $user) {
182 0 0         if ($^O eq "MSWin32") {
183             # perlvar documents always "MSWin32" on Windows ...
184             # what about 64bit windows?
185 0 0 0       if (exists $ENV{USERNAME} and length $ENV{USERNAME}) {
    0 0        
186 0           $user = $ENV{USERNAME};
187             } elsif (exists $ENV{LOGNAME} and length $ENV{LOGNAME}) {
188 0           $user = $ENV{LOGNAME};
189             } else {
190 0           die "Unable to figure out a default user, sorry.\n";
191             }
192             } else {
193 0           $user = getpwuid $>;
194             }
195             # this should handle the non-mswin32 case if 64bit _is_ different.
196 0 0         die "Unable to figure out a default user, sorry!\n"
197             unless defined $user;
198             }
199              
200 0 0 0       if ((defined $sslkeyfile and not defined $sslcertfile) or
      0        
      0        
201             (defined $sslcertfile and not defined $sslkeyfile)) {
202 0           die "Need both a client key and cert for SSL certificate auth.\n";
203             }
204 0 0         if (defined $sslkeyfile) {
205 0           $ssl_options{SSL_use_cert} = 1;
206 0           $ssl_options{SSL_key_file} = $sslkeyfile;
207 0           $ssl_options{SSL_cert_file} = $sslcertfile;
208 0           $prioritise_auth_external = 1;
209             }
210              
211              
212 0           my $sock = IO::Socket::INET6->new(
213             PeerHost => $server,
214             PeerPort => $port,
215             Proto => 'tcp',
216             Domain => $net_domain,
217             MultiHomed => 1, # try multiple IPs (IPv4 works, v6 doesn't?)
218             );
219 0 0         unless (defined $sock) {
220 0           my $extra = '';
221 0 0 0       if ($!{EINVAL} and $net_domain != AF_UNSPEC) {
222 0           $extra = " (Probably no host record for overriden IP version)\n";
223             }
224 0           die qq{Connection to "$server" [port $port] failed: $!\n$extra};
225             }
226              
227 0           $sock->autoflush(1);
228 0           _debug("connection: remote host address is @{[$sock->peerhost()]}");
  0            
229              
230 0           $self->{_sock} = $sock;
231              
232 0           $self->_parse_capabilities();
233              
234 0           $self->{_capa} = $raw_capabilities{SIEVE};
235              
236             # New problem: again, Cyrus timsieved. As of 2.3.13, it drops the
237             # connection for an unknown command instead of returning NO. And
238             # logs "Lost connection to client -- exiting" which is an interesting
239             # way of saying "we dropped the connection". At this point, I give up
240             # on protocol-deterministic checks and fall back to version checking.
241             # Alas, Cyrus 2.2.x is still widely deployed because 2.3.x is the
242             # development series and 2.2.x is officially the stable series.
243             # This means that if they don't support NOOP by 2.3.14, I have to
244             # figure out how to decide what is safe and backtrack which version
245             # precisely was the first to send the capability response correctly.
246 0           my $use_noop = 1;
247 0 0 0       if (exists $capa{"IMPLEMENTATION"} and
      0        
248             $capa{"IMPLEMENTATION"} =~ /^Cyrus timsieved v2\.3\.(\d+)-/ and
249             $1 >= 13) {
250 0           _debug("--- Cyrus drops connection with dubious log msg if send NOOP, skip that");
251 0           $use_noop = 0;
252             }
253              
254 0 0         if (exists $capa{STARTTLS}) {
    0          
255 0           $self->ssend("STARTTLS");
256 0           $self->sget();
257 0 0         die "STARTTLS request rejected: $_\n" unless /^OK\b/;
258 0 0         IO::Socket::SSL->start_SSL($sock, %ssl_options) or do {
259 0           my $e = IO::Socket::SSL::errstr();
260 0           die "STARTTLS promotion failed: $e\n";
261             };
262 0           _debug("--- TLS activated here");
263 0 0         if ($dump_tls_information) {
264 0           print $sock->dump_peer_certificate();
265 0 0 0       if ($DEBUGGING and
      0        
266             exists $main::{"Net::"} and exists $main::{"Net::"}{"SSLeay::"}) {
267             # IO::Socket::SSL depends upon Net::SSLeay
268             # so this should be fairly safe, albeit messing
269             # around behind IO::Socket::SSL's back.
270 0           print STDERR Net::SSLeay::PEM_get_string_X509(
271             $sock->peer_certificate());
272             }
273             }
274 0           $forbid_clearauth = 0;
275             # Cyrus sieve might send CAPABILITY after STARTTLS without being
276             # prompted for it. This breaks the command-response model.
277             # We can't just check to see if there's data to read or not, since
278             # that will break if the next data is delayed (race condition).
279             # There is no protocol-compliant method to determine this, short
280             # of "wait a while, see if anything comes along; if not, send
281             # CAPABILITY ourselves". So, I broke protocol by sending the
282             # non-existent command NOOP, then scan for the resulting NO.
283             # This at least is stably deterministic. However, from draft 10
284             # onwards, NOOP is a registered available extension which returns
285             # OK.
286              
287              
288 0 0         if ($use_noop) {
289 0           my $noop_tag = "STARTTLS-RESYNC-CAPA";
290 0           $self->ssend(qq{NOOP "$noop_tag"});
291             #if ($capa{IMPLEMENTATION} =~ /dovecot/i) {
292 0           $self->_parse_capabilities(
293             sent_a_noop => $noop_tag,
294             # until_see_no => 0,
295             external_first => $prioritise_auth_external);
296             }
297             else {
298 0           $self->_parse_capabilities(
299             # until_see_no => 1,
300             external_first => $prioritise_auth_external);
301             }
302 0 0         unless (scalar keys %capa) {
303 0           $self->ssend("CAPABILITY");
304 0           $self->_parse_capabilities(
305             external_first => $prioritise_auth_external);
306             }
307             } elsif ($forbid_clearchan) {
308 0           die "TLS not offered, SASL confidentiality not supported in client.\n";
309             }
310              
311 0           my %authen_sasl_params;
312 0           $authen_sasl_params{callback}{user} = $user;
313 0 0         if (defined $authzid) {
314 0           $authen_sasl_params{callback}{authname} = $authzid;
315             }
316 0 0         if (defined $realm) {
317             # for compatibility, we set it as a callback AND as a property (below)
318 0           $authen_sasl_params{callback}{realm} = $realm;
319             }
320              
321              
322 0           $authen_sasl_params{callback}{pass} = $password;
323              
324              
325 0 0         $self->closedie("Do not have an authentication mechanism list\n")
326             unless ref($capa{SASL}) eq 'ARRAY';
327 0 0         if (defined $authmech) {
328 0           $authmech = uc $authmech;
329 0 0         if (grep {$_ eq $authmech} map {uc $_} @{$capa{SASL}}) {
  0            
  0            
  0            
330 0           _debug("auth: will try requested SASL mechanism $authmech");
331             } else {
332 0           $self->closedie("Server does not offer SASL mechanism $authmech\n");
333             }
334 0           $authen_sasl_params{mechanism} = $authmech;
335             } else {
336 0           $authen_sasl_params{mechanism} = $raw_capabilities{SASL};
337             }
338              
339 0           my $sasl = Authen::SASL->new(%authen_sasl_params);
340 0 0         die "SASL object init failed (local problem): $!\n"
341             unless defined $sasl;
342              
343 0           my $secflags = 'noanonymous';
344 0 0         $secflags .= ' noplaintext' if $forbid_clearauth;
345 0 0         my $authconversation = $sasl->client_new('sieve', $server, $secflags)
346             or die "SASL conversation init failed (local problem): $!\n";
347 0 0         if (defined $realm) {
348 0           $authconversation->property(realm => $realm);
349             }
350             {
351 0 0         my $sasl_m = $authconversation->mechanism()
  0            
352             or die "Oh why can't I decide which auth mech to send?\n";
353 0 0         if ($sasl_m eq 'GSSAPI') {
354 0           _debug("-A- GSSAPI sasl_m ");
355             # gross hack, but it was bad of us to assume anything.
356             # It also means that we ignore anything specified by the
357             # user, which is good since it's Kerberos anyway.
358             # (Major Assumption Alert!)
359 0           $authconversation->callback(
360             user => undef,
361             pass => undef,
362             );
363             }
364              
365 0           my $sasl_tosend = $authconversation->client_start();
366 0 0         if ($authconversation->code()) {
367 0           my $emsg = $authconversation->error();
368 0           $self->closedie("SASL Error: $emsg\n");
369             }
370              
371 0 0 0       if (defined $sasl_tosend and length $sasl_tosend) {
372 0           my $mimedata = encode_base64($sasl_tosend, '');
373 0           my $mlen = length($mimedata);
374 0           $self->ssend ( qq!AUTHENTICATE "$sasl_m" {${mlen}+}! );
375 0           $self->ssend ( $mimedata );
376             } else {
377 0           $self->ssend ( qq{AUTHENTICATE "$sasl_m"} );
378             }
379 0           $self->sget();
380              
381 0           while ($_ !~ /^(OK|NO)(?:\s.*)?$/m) {
382 0           my $challenge;
383 0 0         if (/^"(.*)"\r?\n?$/) {
384 0           $challenge = $1;
385             } else {
386 0 0         unless (/^{(\d+)\+?}\r?$/m) {
387 0           $self->sfinish ( "*" );
388 0           $self->closedie ("Failure to parse server SASL response.\n");
389             }
390 0           ($challenge = $_) =~ s/^{\d+\+?}\r?\n?//;
391             }
392 0           $challenge = decode_base64($challenge);
393              
394 0           my $response = $authconversation->client_step($challenge);
395 0 0         if ($authconversation->code()) {
396 0           my $emsg = $authconversation->error();
397 0           $self->closedie("SASL Error: $emsg\n");
398             }
399 0 0         $response = '' unless defined $response; # sigh
400 0           my $senddata = encode_base64($response, '');
401 0           my $sendlen = length $senddata;
402 0           $self->ssend ( "{$sendlen+}" );
403             # okay, we send a blank line here even for 0 length data
404 0           $self->ssend ( $senddata );
405 0           $self->sget();
406             }
407              
408 0 0         if (/^NO((?:\s.*)?)$/) {
409 0           $self->closedie_NOmsg("Authentication refused by server");
410             }
411 0 0         if (/^OK\s+\(SASL\s+\"([^"]+)\"\)$/) {
412             # This _should_ be pre_sent with server-verification steps which
413             # in other profiles expect an empty response. But Authen::SASL
414             # doesn't let us confirm that we've finished authentication!
415             # The assumption seems to be that the server only verifies us
416             # so if it says "okay", we don't keep trying.
417 0           my $final_auth = decode_base64($1);
418 0           my $valid = $authconversation->client_step($final_auth);
419             # With Authen::SASL before 2.11 (..::Perl 1.06),
420             # Authen::SASL::Perl::DIGEST-MD5 module will complain at this
421             # final step:
422             # Server did not provide required field(s): algorithm nonce
423             # which is bogus -- it's not required or expected.
424             # Authen::SASL 2.11 fixes this, with ..::Perl 1.06
425             # We explicitly permit silent failure with the security
426             # implications because we require a new enough version of
427             # Authen::SASL at import time above and if someone removes
428             # that check, then on their head be it.
429 0 0         if ($authconversation->code()) {
430 0           my $emsg = $authconversation->error();
431 0 0         if ($Authen::SASL::Perl::VERSION >= 1.06) {
432 0           $self->closedie("SASL Error: $emsg\n");
433             }
434             }
435 0 0 0       if (defined $valid and length $valid) {
436 0           $self->closedie("Server failed final verification [$valid]");
437             }
438             }
439              
440             }
441              
442 0           return $self;
443             };
444              
445             # destructor
446             sub DESTROY {
447 0     0     my $self = shift;
448              
449 0 0         $self->sfinish() if $self->{_sock};
450             }
451              
452             #############
453             # public methods
454              
455             =head1 METHODS
456              
457             =head2 sock
458              
459             Usage : my $sock = $ServerSieve->sock();
460             Return : open socket
461             Argument : nothing
462             Purpose : access to socket
463              
464             =cut
465              
466             sub sock
467             {
468 0     0 1   my $self = shift;
469 0           return $self->{_sock};
470             }
471              
472             =head2 capabilities
473              
474             Usage : my $script_capa = $ServerSieve->capabilities();
475             Return : string with white space separator
476             Argument : nothing
477             Purpose : retrieve sieve script capabilities
478              
479             =cut
480              
481             sub capabilities
482             {
483 0     0 1   my $self = shift;
484 0           return $self->{_capa};
485             }
486              
487             =head2 list
488              
489             Usage :
490             foreach my $script ( $ServerSieve->list() ) {
491             print $script->{name}." ".$script->{status}."\n";
492             };
493             Return : array of hash with names and status scripts for current user
494             Argument : nothing
495             Purpose : list available scripts on server
496              
497             =cut
498              
499             sub list
500             {
501 0     0 1   my $self = shift;
502 0           my @list_scripts;
503 0           my $sock = $self->{_sock};
504 0           $self->ssend("LISTSCRIPTS");
505 0           $self->sget();
506 0           while (/^\"/) {
507 0           my $line = $_;
508 0 0         my $name = $1 if ($line =~ m/\"(.*?)\"/);
509 0 0         my $status = ($line =~ m/ACTIVE/) ? 1 : 0;
510 0           my %script = (name => $name, status => $status);
511 0           push @list_scripts,\%script;
512 0           $self->sget();
513             }
514              
515 0           return @list_scripts;
516             }
517              
518             =head2 put
519              
520             Usage : $ServerSieve->put($name,$script);
521             Return : 1 on success, 0 on missing name or script
522             Argument : name, script
523             Purpose : put script on server
524              
525             =cut
526              
527             sub put
528             {
529 0     0 1   my $self = shift;
530 0           my $name = shift;
531 0           my $script = shift;
532              
533 0           my $sock = $self->{_sock};
534              
535 0           my $size = length($script);
536 0 0 0       return 0 if (!$size || !$name);
537              
538 0           $self->ssend('PUTSCRIPT "'.$name.'" {'.$size.'+}');
539 0           $self->ssend('-noeol', $script);
540 0           $self->ssend('');
541 0           $self->sget();
542              
543 0 0         unless (/^OK((?:\s.*)?)$/) {
544 0           warn "PUTSCRIPT(".$name.") failed: $_\n";
545             }
546              
547 0           return 1;
548             }
549              
550             =head2 get
551              
552             Usage : my $script = $ServerSieve->get($name);
553             Return : 0 on missing name, string with script on success
554             Argument : name
555             Purpose : put script on server
556              
557             =cut
558              
559             sub get
560             {
561 0     0 1   my $self = shift;
562 0           my $name = shift;
563            
564 0 0         return 0 if (!$name);
565              
566 0           $self->ssend("GETSCRIPT \"$name\"");
567 0           $self->sget();
568 0 0         if (/^NO((?:\s.*)?)$/) {
569 0           die_NOmsg($1, qq{Script "$name" not returned by server});
570             }
571 0 0         if (/^OK((?:\s.*)?)$/) {
572 0           warn qq{Empty script "$name"? Not saved.\n};
573 0           return 0;
574             }
575 0 0         unless (/^{(\d+)\+?}\r?$/m) {
576 0           die "QUIT:Failed to parse server response to GETSCRIPT";
577             }
578 0           my $contentdata = $_;
579 0           $self->sget();
580 0           while (/^$/) { $self->sget(); } # extra newline but only for GETSCRIPT?
  0            
581 0 0         unless (/^OK((?:\s.*)?)$/) {
582 0           die_NOmsg($_, "Script retrieval not successful, not saving");
583             }
584 0           $contentdata =~ s/^{\d+\+?}\r?\n?//m;
585            
586 0           return $contentdata;
587             }
588              
589             =head2 activate
590              
591             Usage : $ServerSieve->activate($name);
592             Return : 0 on pb, 1 on success
593             Argument : name
594             Purpose : set named script active and switch other scripts to unactive
595              
596             =cut
597              
598             sub activate {
599 0     0 1   my $self = shift;
600 0           my $name = shift;
601              
602 0           $self->ssend("SETACTIVE \"$name\"");
603 0           $self->sget();
604 0 0         unless (/^OK((?:\s.*)?)$/) {
605 0           warn "SETACTIVE($name) failed: $_\n";
606 0           return 0;
607             }
608              
609 0           return 1;
610             }
611              
612             =head2 deactivate
613              
614             Usage : $ServerSieve->deactivate();
615             Return : activate response
616             Argument : nothing
617             Purpose : stop sieve processing, deactivate all scripts
618              
619             =cut
620              
621             sub deactivate {
622 0     0 1   my $self = shift;
623            
624 0           return $self->activate("");
625             }
626              
627             =head2 delete
628              
629             Usage : $ServerSieve->delete($name);
630             Return : 0 on missing name, 1 on success
631             Argument : name
632             Purpose : delete script on server
633              
634             =cut
635              
636             sub delete {
637 0     0 1   my $self = shift;
638 0           my $name = shift;
639            
640 0 0         return 0 if (!$name);
641              
642 0           $self->ssend("DELETESCRIPT \"$name\"");
643 0           $self->sget();
644 0 0         unless (/^OK((?:\s.*)?)$/) {
645 0           warn "DELETESCRIPT($name) failed: $_\n";
646 0           return 0;
647             }
648              
649 0           return 1;
650             }
651              
652             ###################
653             # private methods
654             #functions
655              
656             sub _parse_capabilities
657             {
658 0     0     my $self = shift;
659 0           my $sock = $self->{_sock};
660 0           local %_ = @_;
661 0           my $external_first = 0;
662 0 0         $external_first = $_{external_first} if exists $_{external_first};
663              
664 0           my @double_checks;
665 0           %raw_capabilities = ();
666 0           %capa = ();
667 0           while (<$sock>) {
668 0           chomp; s/\s*$//;
  0            
669 0 0         _received() unless /^OK\b/;
670 0 0         if (/^OK\b/) {
    0          
    0          
    0          
    0          
671 0           $self->sget('-firstline', $_);
672 0 0         last unless exists $_{sent_a_noop};
673             # See large comment below in STARTTLS explaining the
674             # resync problem to understand why this is here.
675 0           my $end_tag = $_{sent_a_noop};
676 0 0 0       unless (defined $end_tag and length $end_tag) {
677             # Default tag in absense of client-specified
678             # tag MUST be NOOP (2.11.2. NOOP Command)
679 0           $self->closedie("Internal error: sent_a_noop without tag\n");
680             }
681             # Play crude, just look for the tag anywhere in the
682             # response, honouring only word boundaries. It's our
683             # responsibility to make the tag long enough that this
684             # works without tokenising.
685 0 0         if ($_ =~ m/\b\Q${end_tag}\E\b/) {
686 0           return;
687             }
688             # Okay, that's the "server understands NOOP" case, for
689             # which the server should have advertised the
690             # capability prior to TLS (and so subject to
691             # tampering); we play fast and loose, so have to cover
692             # the NO case below too.
693             } elsif (/^\"([^"]+)\"\s+\"(.*)\"$/) {
694 0           my ($k, $v) = (uc($1), $2);
695 0 0         unless (length $v) {
696 0 0         unless (exists $capa_permit_empty{$k}) {
697 0           warn "Empty \"$k\" capability spec not permitted: $_\n";
698             # Don't keep the advertised capability unless
699             # it has some value which is needed. Eg,
700             # NOTIFY must list a mechanism to be useful.
701 0           next;
702             }
703 0 0         if (defined $capa_permit_empty{$k}) {
704 0           push @double_checks, $capa_permit_empty{$k};
705             }
706             }
707 0 0         if (exists $capa{$k}) {
708             # won't catch if the first instance was ignored for an
709             # impermissably empty value; by this point though we
710             # would already have issued a warning and the server
711             # is so fubar that it's not worth worrying about.
712 0           warn "Protocol violation. Already seen capability \"$k\".\n" .
713             "Ignoring second instance and continuing.\n";
714 0           next;
715             }
716 0           $raw_capabilities{$k} = $v;
717 0           $capa{$k} = $v;
718 0 0         if (exists $capa_dosplit{$k}) {
719 0           $capa{$k} = [ split /\s+/, $v ];
720             }
721             } elsif (/^\"([^"]+)\"$/) {
722 0           $raw_capabilities{$1} = '';
723 0           $capa{$1} = 1;
724             } elsif (/^NO\b/) {
725             #return if exists $_{until_see_no};
726 0 0         return if exists $_{sent_a_noop};
727 0           warn "Unhandled server line: $_\n"
728             } elsif (/^BYE\b(.*)/) {
729             #closedie_NOmsg( $1,
730 0           die (
731             "Server said BYE when we expected capabilities.\n");
732             } else {
733 0           warn "Unhandled server line: $_\n"
734             }
735             };
736              
737 0 0         die ( "Server does not return SIEVE capability, unable to continue.\n" )
738             unless exists $capa{SIEVE};
739 0 0         warn "Server does not return IMPLEMENTATION capability.\n"
740             unless exists $capa{IMPLEMENTATION};
741              
742 0           foreach my $check_sub (@double_checks) {
743 0           $check_sub->($sock, \%capa, \%raw_capabilities);
744             }
745              
746 0 0         if (grep {lc($_) eq 'enotify'} @{$capa{SIEVE}}) {
  0            
  0            
747 0 0         unless (exists $capa{NOTIFY}) {
748 0           warn "enotify extension present, NOTIFY capability missing\n" .
749             "This violates MANAGESIEVE specification.\n" .
750             "Continuing anyway.\n";
751             }
752             }
753              
754 0 0 0       if (exists $capa{SASL} and $external_first
  0   0        
755 0           and grep {uc($_) eq 'EXTERNAL'} @{$capa{SASL}}) {
756             # We do two things. We shift the EXTERNAL to the head of the
757             # list, suggesting that it's the server's preferred choice.
758             # We then mess around inside the Authen::SASL::Perl::EXTERNAL
759             # private stuff (name starts with an underscore) to bump up
760             # its priority -- for some reason, the method which is not
761             # interactive and says "use information already available"
762             # is less favoured than some others.
763 0           _debug("auth: shifting EXTERNAL to start of mechanism list");
764 0           my @sasl = ('EXTERNAL');
765 0           foreach (@{$capa{SASL}}) {
  0            
766 0 0         push @sasl, $_ unless uc($_) eq 'EXTERNAL';
767             }
768 0           $capa{SASL} = \@sasl;
769 0           $raw_capabilities{SASL} = join(' ', @sasl);
770 1     1   9 no warnings 'redefine';
  1         2  
  1         1103  
771 0     0     $Authen::SASL::Perl::EXTERNAL::{_order} = sub { 10 };
  0            
772             }
773             }
774              
775             sub _debug
776             {
777 0 0   0     return unless $DEBUGGING;
778 0           print STDERR "$_[0]\n";
779             }
780              
781             sub _diag {
782 0     0     my ($prefix, $data) = @_;
783 0           $data =~ s/\r/\\r/g; $data =~ s/\n/\\n/g; $data =~ s/\t/\\t/g;
  0            
  0            
784 0           $data =~ s/([^[:graph:] ])/sprintf("%%%02X", ord $1)/eg;
  0            
785 0           _debug "$prefix $data";
786             }
787 0 0   0     sub _sent { my $t = defined $_[0] ? $_[0] : $_; _diag('>>>', $t) }
  0            
788 0 0   0     sub _received { my $t = defined $_[0] ? $_[0] : $_; _diag('<<<', $t) }
  0            
789              
790             #sub _sent { $_[0] = $_ unless defined $_[0]; _debug ">>> $_[0]"; }
791             #sub _received { $_[0] = $_ unless defined $_[0]; _debug "<<< $_[0]"; }
792              
793             # ######################################################################
794             # minor public routines
795              
796             =head1 Minor public methods
797              
798             =head2 ssend
799              
800             Usage : $self->ssend("GETSCRIPT \"$name\"");
801              
802             =cut
803              
804             sub ssend
805             {
806 0     0 1   my $self = shift;
807 0           my $sock = $self->{_sock};
808            
809 0           my $eol = "\r\n";
810 0 0 0       if (defined $_[0] and $_[0] eq '-noeol') {
811 0           shift;
812 0           $eol = '';
813             }
814 0           foreach my $l (@_) {
815 0           $sock->print("$l$eol");
816             # yes, the _debug output can have extra blank lines if supplied -noeol because
817             # they're already pre_sent. Rather than mess around to tidy it up, I'm leaving
818             # it because it's _debug output, not UI or protocol text.
819 0           _sent ( "$l$eol" );
820             }
821             }
822              
823             =head2 sget
824              
825             Usage:
826             $self->sget();
827             unless (/^OK((?:\s.*)?)$/) {
828             warn "SETACTIVE($name) failed: $_\n";
829             return 0;
830             }
831              
832             =cut
833              
834             sub sget
835             {
836 0     0 1   my $self = shift;
837 0           my $l = undef;
838 0           my $sock = $self->{_sock};
839 0           my $dochomp = 1;
840 0           while (@_) {
841 0           my $t = shift;
842 0 0         next unless defined $t;
843 0 0         if ($t eq '-nochomp') { $dochomp = 0; next; }
  0            
  0            
844 0 0         if ($t eq '-firstline') {
845 0 0         die "Missing sget -firstline parameter"
846             unless defined $_[0];
847 0           $l = $_[0];
848 0           shift;
849 0           next;
850             }
851 0           die "Unknown sget parameter [$t]";
852             }
853 0 0         $l = $sock->getline() unless defined $l;
854 0 0         unless (defined $l) {
855 0           _debug "... no line read, connection dropped?";
856 0           die "Connection dropped unexpectedly when trying to read.\n";
857             }
858 0 0         if ($l =~ /{(\d+)\+?}\s*\n?\z/) {
859 0           _debug("... literal string response, length $1");
860 0           my $len = $1;
861 0 0         if ($len == 0) {
862 0           my $discard = $sock->getline();
863             } else {
864 0           while ($len > 0) {
865 0           my $extra = $sock->getline();
866 0           $len -= length($extra);
867 0           $l .= $extra;
868             }
869             }
870 0           $dochomp = 0;
871             }
872 0 0         if ($dochomp) {
873 0           chomp $l; $l =~ s/\s*$//;
  0            
874             }
875 0           _received($l);
876 0 0         if (defined wantarray) {
877 0           return $l;
878             } else {
879 0           $_ = $l;
880             }
881             }
882              
883             =head2 sfinish
884              
885             send LOGOUT
886              
887             =cut
888              
889             sub sfinish
890             {
891 0     0 1   my $self = shift;
892 0           my $sock = $self->{_sock};
893 0 0         if (defined $_[0]) {
894 0           $self->ssend($_[0]);
895 0           $self->sget();
896             }
897 0           $self->ssend("LOGOUT");
898 0           $self->sget();
899 0 0         if (/^OK/) {
900 0           undef $self->{_sock};
901 0           return 1;
902             };
903             }
904              
905             =head2 closedie
906              
907             send LOGOUT and die
908              
909             =cut
910              
911             sub closedie
912             {
913 0     0 1   my $self = shift;
914 0           my $sock = $self->{_sock};
915 0           my $e = $!;
916 0           $self->sfinish();
917 0           $! = $e;
918 0           die @_;
919             }
920              
921             =head2 closedie_NOmsg
922              
923             closedie whitout message
924              
925             =cut
926              
927             sub closedie_NOmsg
928             {
929 0     0 1   my $self = shift;
930 0           my $sock = $self->{_sock};
931 0           my $suffix = shift;
932 0           $self->sfinish();
933 0           die $suffix;
934             }
935              
936             =head2 die_NOmsg
937              
938             die
939              
940             =cut
941              
942             sub die_NOmsg
943             {
944 0     0 1   my $suffix = shift;
945 0           my $msg = shift;
946 0 0         if (length $suffix) {
947 0           $msg .= ':' . $suffix . "\n";
948             } else {
949 0           $msg .= ".\n";
950             }
951 0           die $msg;
952             }
953              
954              
955             =head1 BUGS
956              
957             I don't try plain text or client certificate authentication.
958              
959             You can debug TLS connexion with openssl :
960              
961             openssl s_client -connect your.server.org:2000 -tls1 -CApath /etc/apache/ssl.crt/somecrt.crt -starttls imap
962              
963             See response in C
964              
965             Or with gnutls-cli
966              
967             gnutls-cli -s -p 4190 --crlf --insecure your.server.org
968              
969             Use Ctrl+D after STARTTLS to begin TLS negotiation
970              
971             =head1 SUPPORT
972              
973             Please report any bugs or feature requests to "bug-net-sieve at rt.cpan.org", or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
974              
975             =head1 AUTHOR
976              
977             Yves Agostini
978              
979             =head1 COPYRIGHT
980              
981             Copyright 2008-2012 Yves Agostini -
982              
983             This program is free software; you can redistribute
984             it and/or modify it under the same terms as Perl itself.
985              
986             The full text of the license can be found in the
987             LICENSE file included with this module.
988              
989             B source code is under a BSD-style license and re-licensed for Net::Sieve with permission of the author.
990              
991             =head1 SEE ALSO
992              
993             L
994              
995             =cut
996              
997             #################### main pod documentation end ###################
998              
999              
1000             1;
1001             # The preceding line will help the module return a true value
1002