File Coverage

lib/Net/Sieve.pm
Criterion Covered Total %
statement 35 413 8.4
branch 0 206 0.0
condition 0 69 0.0
subroutine 11 33 33.3
pod 15 15 100.0
total 61 736 8.2


line stmt bran cond sub pod time code
1             package Net::Sieve;
2 1     1   49927 use strict;
  1         2  
  1         23  
3 1     1   4 use warnings;
  1         1  
  1         32  
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   230 use Authen::SASL 2.11 qw(Perl);
  1         784  
  1         4  
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   4293 use Authen::SASL::Perl::EXTERNAL; # We munge inside its private stuff.
  1         286  
  1         37  
65 1     1   383 use IO::Socket::INET6;
  1         22417  
  1         5  
66 1     1   917 use IO::Socket::SSL 0.97; # SSL_ca_path bogus before 0.97
  1         47313  
  1         7  
67 1     1   457 use MIME::Base64;
  1         481  
  1         49  
68              
69             BEGIN {
70 1     1   6 use Exporter ();
  1         2  
  1         19  
71 1     1   3 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         2  
  1         70  
72 1     1   3 $VERSION = '0.12';
73 1         8 @ISA = qw(Exporter);
74             #Give a hoot don't pollute, do not export more than needed by default
75 1         1 @EXPORT = qw();
76 1         9 @EXPORT_OK = qw();
77 1         2700 %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              
237 0           my $tls_bitlength = -1;
238              
239 0 0         if (exists $capa{STARTTLS}) {
    0          
240 0           $self->ssend("STARTTLS");
241 0           $self->sget();
242 0 0         die "STARTTLS request rejected: $_\n" unless /^OK\b/;
243 0 0         IO::Socket::SSL->start_SSL($sock, %ssl_options) or do {
244 0           my $e = IO::Socket::SSL::errstr();
245 0           die "STARTTLS promotion failed: $e\n";
246             };
247 0 0 0       if (exists $main::{"Net::"} and exists $main::{"Net::"}{"SSLeay::"}) {
248 0           my $t = Net::SSLeay::get_cipher_bits($sock->_get_ssl_object(), 0);
249 0 0 0       $tls_bitlength = $t if defined $t and $t;
250             }
251 0           _debug("--- TLS activated here [$tls_bitlength bits]");
252 0 0         if ($dump_tls_information) {
253 0           print $sock->dump_peer_certificate();
254 0 0 0       if ($DEBUGGING and
      0        
255             exists $main::{"Net::"} and exists $main::{"Net::"}{"SSLeay::"}) {
256             # IO::Socket::SSL depends upon Net::SSLeay
257             # so this should be fairly safe, albeit messing
258             # around behind IO::Socket::SSL's back.
259 0           print STDERR Net::SSLeay::PEM_get_string_X509(
260             $sock->peer_certificate());
261             }
262             }
263 0           $forbid_clearauth = 0;
264             # Cyrus sieve might send CAPABILITY after STARTTLS without being
265             # prompted for it. This breaks the command-response model.
266             # We can't just check to see if there's data to read or not, since
267             # that will break if the next data is delayed (race condition).
268             # There is no protocol-compliant method to determine this, short
269             # of "wait a while, see if anything comes along; if not, send
270             # CAPABILITY ourselves". So, I broke protocol by sending the
271             # non-existent command NOOP, then scan for the resulting NO.
272             # This at least is stably deterministic. However, from draft 10
273             # onwards, NOOP is a registered available extension which returns
274             # OK.
275             # New problem: again, Cyrus timsieved. As of 2.3.13, it drops the
276             # connection for an unknown command instead of returning NO. And
277             # logs "Lost connection to client -- exiting" which is an interesting
278             # way of saying "we dropped the connection". At this point, I give up
279             # on protocol-deterministic checks and fall back to version checking.
280             # Alas, Cyrus 2.2.x is still widely deployed because 2.3.x is the
281             # development series and 2.2.x is officially the stable series.
282             # This means that if they don't support NOOP by 2.3.14, I have to
283             # figure out how to decide what is safe and backtrack which version
284             # precisely was the first to send the capability response correctly.
285 0           my $use_noop = 1;
286 0 0 0       if (exists $capa{"IMPLEMENTATION"} and
      0        
287             $capa{"IMPLEMENTATION"} =~ /^Cyrus timsieved v2\.3\.(\d+)/ and
288             $1 >= 13) {
289 0           _debug("--- Cyrus drops connection with dubious log msg if send NOOP, skip that");
290 0           $use_noop = 0;
291             }
292              
293 0 0         if ($use_noop) {
294 0           my $noop_tag = "STARTTLS-RESYNC-CAPA";
295 0           $self->ssend(qq{NOOP "$noop_tag"});
296             #if ($capa{IMPLEMENTATION} =~ /dovecot/i) {
297 0           $self->_parse_capabilities(
298             sent_a_noop => $noop_tag,
299             # until_see_no => 0,
300             external_first => $prioritise_auth_external);
301             }
302             else {
303 0           $self->_parse_capabilities(
304             # until_see_no => 1,
305             external_first => $prioritise_auth_external);
306             }
307 0 0         unless (scalar keys %capa) {
308 0           $self->ssend("CAPABILITY");
309 0           $self->_parse_capabilities(
310             external_first => $prioritise_auth_external);
311             }
312             } elsif ($forbid_clearchan) {
313 0           die "TLS not offered, SASL confidentiality not supported in client.\n";
314             }
315              
316 0           my %authen_sasl_params;
317 0           $authen_sasl_params{callback}{user} = $user;
318 0 0         if (defined $authzid) {
319 0           $authen_sasl_params{callback}{authname} = $authzid;
320             }
321 0 0         if (defined $realm) {
322             # for compatibility, we set it as a callback AND as a property (below)
323 0           $authen_sasl_params{callback}{realm} = $realm;
324             }
325              
326              
327 0           $authen_sasl_params{callback}{pass} = $password;
328              
329              
330             $self->closedie("Do not have an authentication mechanism list\n")
331 0 0         unless ref($capa{SASL}) eq 'ARRAY';
332 0 0         if (defined $authmech) {
333 0           $authmech = uc $authmech;
334 0 0         if (grep {$_ eq $authmech} map {uc $_} @{$capa{SASL}}) {
  0            
  0            
  0            
335 0           _debug("auth: will try requested SASL mechanism $authmech");
336             } else {
337 0           $self->closedie("Server does not offer SASL mechanism $authmech\n");
338             }
339 0           $authen_sasl_params{mechanism} = $authmech;
340             } else {
341 0           $authen_sasl_params{mechanism} = $raw_capabilities{SASL};
342             }
343              
344 0           my $sasl = Authen::SASL->new(%authen_sasl_params);
345 0 0         die "SASL object init failed (local problem): $!\n"
346             unless defined $sasl;
347              
348 0           my $secflags = 'noanonymous';
349 0 0         $secflags .= ' noplaintext' if $forbid_clearauth;
350 0 0         my $authconversation = $sasl->client_new('sieve', $server, $secflags)
351             or die "SASL conversation init failed (local problem): $!\n";
352 0 0         if ($tls_bitlength > 0) {
353 0           $authconversation->property(externalssf => $tls_bitlength);
354             }
355 0 0         if (defined $realm) {
356 0           $authconversation->property(realm => $realm);
357             }
358             {
359 0 0         my $sasl_m = $authconversation->mechanism()
  0            
360             or die "Oh why can't I decide which auth mech to send?\n";
361 0 0         if ($sasl_m eq 'GSSAPI') {
362 0           _debug("-A- GSSAPI sasl_m ");
363             # gross hack, but it was bad of us to assume anything.
364             # It also means that we ignore anything specified by the
365             # user, which is good since it's Kerberos anyway.
366             # (Major Assumption Alert!)
367 0           $authconversation->callback(
368             user => undef,
369             pass => undef,
370             );
371             }
372              
373 0           my $sasl_tosend = $authconversation->client_start();
374 0 0         if ($authconversation->code()) {
375 0           my $emsg = $authconversation->error();
376 0           $self->closedie("SASL Error: $emsg\n");
377             }
378              
379 0 0 0       if (defined $sasl_tosend and length $sasl_tosend) {
380 0           my $mimedata = encode_base64($sasl_tosend, '');
381 0           my $mlen = length($mimedata);
382 0           $self->ssend ( qq!AUTHENTICATE "$sasl_m" {${mlen}+}! );
383 0           $self->ssend ( $mimedata );
384             } else {
385 0           $self->ssend ( qq{AUTHENTICATE "$sasl_m"} );
386             }
387 0           $self->sget();
388              
389 0           while ($_ !~ /^(OK|NO)(?:\s.*)?$/m) {
390 0           my $challenge;
391 0 0         if (/^"(.*)"\r?\n?$/) {
392 0           $challenge = $1;
393             } else {
394 0 0         unless (/^\{(\d+)\+?}\r?$/m) {
395 0           $self->sfinish ( "*" );
396 0           $self->closedie ("Failure to parse server SASL response.\n");
397             }
398 0           ($challenge = $_) =~ s/^{\d+\+?}\r?\n?//;
399             }
400 0           $challenge = decode_base64($challenge);
401              
402 0           my $response = $authconversation->client_step($challenge);
403 0 0         if ($authconversation->code()) {
404 0           my $emsg = $authconversation->error();
405 0           $self->closedie("SASL Error: $emsg\n");
406             }
407 0 0         $response = '' unless defined $response; # sigh
408 0           my $senddata = encode_base64($response, '');
409 0           my $sendlen = length $senddata;
410 0           $self->ssend ( "{$sendlen+}" );
411             # okay, we send a blank line here even for 0 length data
412 0           $self->ssend ( $senddata );
413 0           $self->sget();
414             }
415              
416 0 0         if (/^NO((?:\s.*)?)$/) {
417 0           $self->closedie_NOmsg("Authentication refused by server");
418             }
419 0 0         if (/^OK\s+\(SASL\s+\"([^"]+)\"\)$/) {
420             # This _should_ be pre_sent with server-verification steps which
421             # in other profiles expect an empty response. But Authen::SASL
422             # doesn't let us confirm that we've finished authentication!
423             # The assumption seems to be that the server only verifies us
424             # so if it says "okay", we don't keep trying.
425 0           my $final_auth = decode_base64($1);
426 0           my $valid = $authconversation->client_step($final_auth);
427             # With Authen::SASL before 2.11 (..::Perl 1.06),
428             # Authen::SASL::Perl::DIGEST-MD5 module will complain at this
429             # final step:
430             # Server did not provide required field(s): algorithm nonce
431             # which is bogus -- it's not required or expected.
432             # Authen::SASL 2.11 fixes this, with ..::Perl 1.06
433             # We explicitly permit silent failure with the security
434             # implications because we require a new enough version of
435             # Authen::SASL at import time above and if someone removes
436             # that check, then on their head be it.
437 0 0         if ($authconversation->code()) {
438 0           my $emsg = $authconversation->error();
439 0 0         if ($Authen::SASL::Perl::VERSION >= 1.06) {
440 0           $self->closedie("SASL Error: $emsg\n");
441             }
442             }
443 0 0 0       if (defined $valid and length $valid) {
444 0           $self->closedie("Server failed final verification [$valid]");
445             }
446             }
447              
448             }
449              
450 0           return $self;
451             };
452              
453             # destructor
454             sub DESTROY {
455 0     0     my $self = shift;
456              
457 0 0         $self->sfinish() if $self->{_sock};
458             }
459              
460             #############
461             # public methods
462              
463             =head1 METHODS
464              
465             =head2 sock
466              
467             Usage : my $sock = $ServerSieve->sock();
468             Return : open socket
469             Argument : nothing
470             Purpose : access to socket
471              
472             =cut
473              
474             sub sock
475             {
476 0     0 1   my $self = shift;
477 0           return $self->{_sock};
478             }
479              
480             =head2 capabilities
481              
482             Usage : my $script_capa = $ServerSieve->capabilities();
483             Return : string with white space separator
484             Argument : nothing
485             Purpose : retrieve sieve script capabilities
486              
487             =cut
488              
489             sub capabilities
490             {
491 0     0 1   my $self = shift;
492 0           return $self->{_capa};
493             }
494              
495             =head2 list
496              
497             Usage :
498             foreach my $script ( $ServerSieve->list() ) {
499             print $script->{name}." ".$script->{status}."\n";
500             };
501             Return : array of hash with names and status scripts for current user
502             Argument : nothing
503             Purpose : list available scripts on server
504              
505             =cut
506              
507             sub list
508             {
509 0     0 1   my $self = shift;
510 0           my @list_scripts;
511 0           my $sock = $self->{_sock};
512 0           $self->ssend("LISTSCRIPTS");
513 0           $self->sget();
514 0           while (/^\"/) {
515 0           my $line = $_;
516 0 0         my $name = $1 if ($line =~ m/\"(.*?)\"/);
517 0 0         my $status = ($line =~ m/ACTIVE/) ? 1 : 0;
518 0           my %script = (name => $name, status => $status);
519 0           push @list_scripts,\%script;
520 0           $self->sget();
521             }
522              
523 0           return @list_scripts;
524             }
525              
526             =head2 put
527              
528             Usage : $ServerSieve->put($name,$script);
529             Return : 1 on success, 0 on missing name or script
530             Argument : name, script
531             Purpose : put script on server
532              
533             =cut
534              
535             sub put
536             {
537 0     0 1   my $self = shift;
538 0           my $name = shift;
539 0           my $script = shift;
540              
541 0           my $sock = $self->{_sock};
542              
543 0           my $size = length($script);
544 0 0 0       return 0 if (!$size || !$name);
545              
546 0           $self->ssend('PUTSCRIPT "'.$name.'" {'.$size.'+}');
547 0           $self->ssend('-noeol', $script);
548 0           $self->ssend('');
549 0           $self->sget();
550              
551 0 0         unless (/^OK((?:\s.*)?)$/) {
552 0           warn "PUTSCRIPT(".$name.") failed: $_\n";
553             }
554              
555 0           return 1;
556             }
557              
558             =head2 get
559              
560             Usage : my $script = $ServerSieve->get($name);
561             Return : 0 on missing name, string with script on success
562             Argument : name
563             Purpose : put script on server
564              
565             =cut
566              
567             sub get
568             {
569 0     0 1   my $self = shift;
570 0           my $name = shift;
571            
572 0 0         return 0 if (!$name);
573              
574 0           $self->ssend("GETSCRIPT \"$name\"");
575 0           $self->sget();
576 0 0         if (/^NO((?:\s.*)?)$/) {
577 0           die_NOmsg($1, qq{Script "$name" not returned by server});
578             }
579 0 0         if (/^OK((?:\s.*)?)$/) {
580 0           warn qq{Empty script "$name"? Not saved.\n};
581 0           return 0;
582             }
583 0 0         unless (/^\{(\d+)\+?}\r?$/m) {
584 0           die "QUIT:Failed to parse server response to GETSCRIPT";
585             }
586 0           my $contentdata = $_;
587 0           $self->sget();
588 0           while (/^$/) { $self->sget(); } # extra newline but only for GETSCRIPT?
  0            
589 0 0         unless (/^OK((?:\s.*)?)$/) {
590 0           die_NOmsg($_, "Script retrieval not successful, not saving");
591             }
592 0           $contentdata =~ s/^\{\d+\+?}\r?\n?//m;
593            
594 0           return $contentdata;
595             }
596              
597             =head2 activate
598              
599             Usage : $ServerSieve->activate($name);
600             Return : 0 on pb, 1 on success
601             Argument : name
602             Purpose : set named script active and switch other scripts to unactive
603              
604             =cut
605              
606             sub activate {
607 0     0 1   my $self = shift;
608 0           my $name = shift;
609              
610 0           $self->ssend("SETACTIVE \"$name\"");
611 0           $self->sget();
612 0 0         unless (/^OK((?:\s.*)?)$/) {
613 0           warn "SETACTIVE($name) failed: $_\n";
614 0           return 0;
615             }
616              
617 0           return 1;
618             }
619              
620             =head2 deactivate
621              
622             Usage : $ServerSieve->deactivate();
623             Return : activate response
624             Argument : nothing
625             Purpose : stop sieve processing, deactivate all scripts
626              
627             =cut
628              
629             sub deactivate {
630 0     0 1   my $self = shift;
631            
632 0           return $self->activate("");
633             }
634              
635             =head2 delete
636              
637             Usage : $ServerSieve->delete($name);
638             Return : 0 on missing name, 1 on success
639             Argument : name
640             Purpose : delete script on server
641              
642             =cut
643              
644             sub delete {
645 0     0 1   my $self = shift;
646 0           my $name = shift;
647            
648 0 0         return 0 if (!$name);
649              
650 0           $self->ssend("DELETESCRIPT \"$name\"");
651 0           $self->sget();
652 0 0         unless (/^OK((?:\s.*)?)$/) {
653 0           warn "DELETESCRIPT($name) failed: $_\n";
654 0           return 0;
655             }
656              
657 0           return 1;
658             }
659              
660             ###################
661             # private methods
662             #functions
663              
664             sub _parse_capabilities
665             {
666 0     0     my $self = shift;
667 0           my $sock = $self->{_sock};
668 0           local %_ = @_;
669 0           my $external_first = 0;
670 0 0         $external_first = $_{external_first} if exists $_{external_first};
671              
672 0           my @double_checks;
673 0           %raw_capabilities = ();
674 0           %capa = ();
675 0           while (<$sock>) {
676 0           chomp; s/\s*$//;
  0            
677 0 0         _received() unless /^OK\b/;
678 0 0         if (/^OK\b/) {
    0          
    0          
    0          
    0          
679 0           $self->sget('-firstline', $_);
680 0 0         last unless exists $_{sent_a_noop};
681             # See large comment below in STARTTLS explaining the
682             # resync problem to understand why this is here.
683 0           my $end_tag = $_{sent_a_noop};
684 0 0 0       unless (defined $end_tag and length $end_tag) {
685             # Default tag in absense of client-specified
686             # tag MUST be NOOP (2.11.2. NOOP Command)
687 0           $self->closedie("Internal error: sent_a_noop without tag\n");
688             }
689             # Play crude, just look for the tag anywhere in the
690             # response, honouring only word boundaries. It's our
691             # responsibility to make the tag long enough that this
692             # works without tokenising.
693 0 0         if ($_ =~ m/\b\Q${end_tag}\E\b/) {
694 0           return;
695             }
696             # Okay, that's the "server understands NOOP" case, for
697             # which the server should have advertised the
698             # capability prior to TLS (and so subject to
699             # tampering); we play fast and loose, so have to cover
700             # the NO case below too.
701             } elsif (/^\"([^"]+)\"\s+\"(.*)\"$/) {
702 0           my ($k, $v) = (uc($1), $2);
703 0 0         unless (length $v) {
704 0 0         unless (exists $capa_permit_empty{$k}) {
705 0           warn "Empty \"$k\" capability spec not permitted: $_\n";
706             # Don't keep the advertised capability unless
707             # it has some value which is needed. Eg,
708             # NOTIFY must list a mechanism to be useful.
709 0           next;
710             }
711 0 0         if (defined $capa_permit_empty{$k}) {
712 0           push @double_checks, $capa_permit_empty{$k};
713             }
714             }
715 0 0         if (exists $capa{$k}) {
716             # won't catch if the first instance was ignored for an
717             # impermissably empty value; by this point though we
718             # would already have issued a warning and the server
719             # is so fubar that it's not worth worrying about.
720 0           warn "Protocol violation. Already seen capability \"$k\".\n" .
721             "Ignoring second instance and continuing.\n";
722 0           next;
723             }
724 0           $raw_capabilities{$k} = $v;
725 0           $capa{$k} = $v;
726 0 0         if (exists $capa_dosplit{$k}) {
727 0           $capa{$k} = [ split /\s+/, $v ];
728             }
729             } elsif (/^\"([^"]+)\"$/) {
730 0           $raw_capabilities{$1} = '';
731 0           $capa{$1} = 1;
732             } elsif (/^NO\b/) {
733             #return if exists $_{until_see_no};
734 0 0         return if exists $_{sent_a_noop};
735 0           warn "Unhandled server line: $_\n"
736             } elsif (/^BYE\b(.*)/) {
737             #closedie_NOmsg( $1,
738 0           die (
739             "Server said BYE when we expected capabilities.\n");
740             } else {
741 0           warn "Unhandled server line: $_\n"
742             }
743             };
744              
745             die ( "Server does not return SIEVE capability, unable to continue.\n" )
746 0 0         unless exists $capa{SIEVE};
747             warn "Server does not return IMPLEMENTATION capability.\n"
748 0 0         unless exists $capa{IMPLEMENTATION};
749              
750 0           foreach my $check_sub (@double_checks) {
751 0           $check_sub->($sock, \%capa, \%raw_capabilities);
752             }
753              
754 0 0         if (grep {lc($_) eq 'enotify'} @{$capa{SIEVE}}) {
  0            
  0            
755 0 0         unless (exists $capa{NOTIFY}) {
756 0           warn "enotify extension present, NOTIFY capability missing\n" .
757             "This violates MANAGESIEVE specification.\n" .
758             "Continuing anyway.\n";
759             }
760             }
761              
762 0 0 0       if (exists $capa{SASL} and $external_first
      0        
763 0           and grep {uc($_) eq 'EXTERNAL'} @{$capa{SASL}}) {
  0            
764             # We do two things. We shift the EXTERNAL to the head of the
765             # list, suggesting that it's the server's preferred choice.
766             # We then mess around inside the Authen::SASL::Perl::EXTERNAL
767             # private stuff (name starts with an underscore) to bump up
768             # its priority -- for some reason, the method which is not
769             # interactive and says "use information already available"
770             # is less favoured than some others.
771 0           _debug("auth: shifting EXTERNAL to start of mechanism list");
772 0           my @sasl = ('EXTERNAL');
773 0           foreach (@{$capa{SASL}}) {
  0            
774 0 0         push @sasl, $_ unless uc($_) eq 'EXTERNAL';
775             }
776 0           $capa{SASL} = \@sasl;
777 0           $raw_capabilities{SASL} = join(' ', @sasl);
778 1     1   7 no warnings 'redefine';
  1         2  
  1         829  
779 0     0     $Authen::SASL::Perl::EXTERNAL::{_order} = sub { 10 };
  0            
780             }
781             }
782              
783             sub _debug
784             {
785 0 0   0     return unless $DEBUGGING;
786 0           print STDERR "$_[0]\n";
787             }
788              
789             sub _diag {
790 0     0     my ($prefix, $data) = @_;
791 0           $data =~ s/\r/\\r/g; $data =~ s/\n/\\n/g; $data =~ s/\t/\\t/g;
  0            
  0            
792 0           $data =~ s/([^[:graph:] ])/sprintf("%%%02X", ord $1)/eg;
  0            
793 0           _debug "$prefix $data";
794             }
795 0 0   0     sub _sent { my $t = defined $_[0] ? $_[0] : $_; _diag('>>>', $t) }
  0            
796 0 0   0     sub _received { my $t = defined $_[0] ? $_[0] : $_; _diag('<<<', $t) }
  0            
797              
798             #sub _sent { $_[0] = $_ unless defined $_[0]; _debug ">>> $_[0]"; }
799             #sub _received { $_[0] = $_ unless defined $_[0]; _debug "<<< $_[0]"; }
800              
801             # ######################################################################
802             # minor public routines
803              
804             =head1 Minor public methods
805              
806             =head2 ssend
807              
808             Usage : $self->ssend("GETSCRIPT \"$name\"");
809              
810             =cut
811              
812             sub ssend
813             {
814 0     0 1   my $self = shift;
815 0           my $sock = $self->{_sock};
816            
817 0           my $eol = "\r\n";
818 0 0 0       if (defined $_[0] and $_[0] eq '-noeol') {
819 0           shift;
820 0           $eol = '';
821             }
822 0           foreach my $l (@_) {
823 0           $sock->print("$l$eol");
824             # yes, the _debug output can have extra blank lines if supplied -noeol because
825             # they're already pre_sent. Rather than mess around to tidy it up, I'm leaving
826             # it because it's _debug output, not UI or protocol text.
827 0           _sent ( "$l$eol" );
828             }
829             }
830              
831             =head2 sget
832              
833             Usage:
834             $self->sget();
835             unless (/^OK((?:\s.*)?)$/) {
836             warn "SETACTIVE($name) failed: $_\n";
837             return 0;
838             }
839              
840             =cut
841              
842             sub sget
843             {
844 0     0 1   my $self = shift;
845 0           my $l = undef;
846 0           my $sock = $self->{_sock};
847 0           my $dochomp = 1;
848 0           while (@_) {
849 0           my $t = shift;
850 0 0         next unless defined $t;
851 0 0         if ($t eq '-nochomp') { $dochomp = 0; next; }
  0            
  0            
852 0 0         if ($t eq '-firstline') {
853 0 0         die "Missing sget -firstline parameter"
854             unless defined $_[0];
855 0           $l = $_[0];
856 0           shift;
857 0           next;
858             }
859 0           die "Unknown sget parameter [$t]";
860             }
861 0 0         $l = $sock->getline() unless defined $l;
862 0 0         unless (defined $l) {
863 0           _debug "... no line read, connection dropped?";
864 0           die "Connection dropped unexpectedly when trying to read.\n";
865             }
866 0 0         if ($l =~ /{(\d+)\+?}\s*\n?\z/) {
867 0           _debug("... literal string response, length $1");
868 0           my $len = $1;
869 0 0         if ($len == 0) {
870 0           my $discard = $sock->getline();
871             } else {
872 0           while ($len > 0) {
873 0           my $extra = $sock->getline();
874 0           $len -= length($extra);
875 0           $l .= $extra;
876             }
877             }
878 0           $dochomp = 0;
879             }
880 0 0         if ($dochomp) {
881 0           chomp $l; $l =~ s/\s*$//;
  0            
882             }
883 0           _received($l);
884 0 0         if (defined wantarray) {
885 0           return $l;
886             } else {
887 0           $_ = $l;
888             }
889             }
890              
891             =head2 sfinish
892              
893             send LOGOUT
894              
895             =cut
896              
897             sub sfinish
898             {
899 0     0 1   my $self = shift;
900 0           my $sock = $self->{_sock};
901 0 0         if (defined $_[0]) {
902 0           $self->ssend($_[0]);
903 0           $self->sget();
904             }
905 0           $self->ssend("LOGOUT");
906 0           $self->sget();
907 0 0         if (/^OK/) {
908 0           undef $self->{_sock};
909 0           return 1;
910             };
911             }
912              
913             =head2 closedie
914              
915             send LOGOUT and die
916              
917             =cut
918              
919             sub closedie
920             {
921 0     0 1   my $self = shift;
922 0           my $sock = $self->{_sock};
923 0           my $e = $!;
924 0           $self->sfinish();
925 0           $! = $e;
926 0           die @_;
927             }
928              
929             =head2 closedie_NOmsg
930              
931             closedie whitout message
932              
933             =cut
934              
935             sub closedie_NOmsg
936             {
937 0     0 1   my $self = shift;
938 0           my $sock = $self->{_sock};
939 0           my $suffix = shift;
940 0           $self->sfinish();
941 0           die $suffix;
942             }
943              
944             =head2 die_NOmsg
945              
946             die
947              
948             =cut
949              
950             sub die_NOmsg
951             {
952 0     0 1   my $suffix = shift;
953 0           my $msg = shift;
954 0 0         if (length $suffix) {
955 0           $msg .= ':' . $suffix . "\n";
956             } else {
957 0           $msg .= ".\n";
958             }
959 0           die $msg;
960             }
961              
962              
963             =head1 BUGS
964              
965             I don't try plain text or client certificate authentication.
966              
967             You can debug TLS connexion with openssl :
968              
969             openssl s_client -connect your.server.org:2000 -tls1 -CApath /etc/apache/ssl.crt/somecrt.crt -starttls imap
970              
971             See response in C
972              
973             Or with gnutls-cli
974              
975             gnutls-cli -s -p 4190 --crlf --insecure your.server.org
976              
977             Use Ctrl+D after STARTTLS to begin TLS negotiation
978              
979             =head1 SUPPORT
980              
981             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.
982              
983             =head1 AUTHOR
984              
985             Yves Agostini
986              
987             =head1 COPYRIGHT
988              
989             Copyright 2008-2017 Yves Agostini -
990              
991             This program is free software; you can redistribute
992             it and/or modify it under the same terms as Perl itself.
993              
994             The full text of the license can be found in the
995             LICENSE file included with this module.
996              
997             B source code is under a BSD-style license and re-licensed for Net::Sieve with permission of the author.
998              
999             =head1 SEE ALSO
1000              
1001             L
1002              
1003             =cut
1004              
1005             #################### main pod documentation end ###################
1006              
1007              
1008             1;
1009             # The preceding line will help the module return a true value
1010