]> source.dussan.org Git - rspamd.git/commitdiff
* Make new Mail::Rspamd::Client working
authorcebka@lenovo-laptop <cebka@lenovo-laptop>
Thu, 4 Mar 2010 17:56:47 +0000 (20:56 +0300)
committercebka@lenovo-laptop <cebka@lenovo-laptop>
Thu, 4 Mar 2010 17:56:47 +0000 (20:56 +0300)
perl/lib/Mail/Rspamd/Client.pm
rspamc.pl.in

index d850a093a6a7f27b92be2b63769c3af8ea49c029..d87fe16ae28fb8c03b7bd83b02fcaa532dca2094 100644 (file)
@@ -130,476 +130,124 @@ sub make_ssl_socket {
 }
 
 
-# Currently just read stdin for user's message and pass it to rspamd
-sub _do_rspamc_command {
-       my ($self, $remote, $msg) = @_;
-
-       my %metrics;
-
-
-       my $msgsize = length($msg.$EOL);
 
-       local $SIG{PIPE} = 'IGNORE';
+=head2 process_item
 
-       if (!(syswrite($remote, "$self->{command} $PROTOVERSION$EOL"))) {
-               $self->_mark_dead($remote);
-               return 0;
-       }
-       syswrite $remote, "Content-length: $msgsize$EOL";
-       syswrite $remote, "User: $self->{username}$EOL" if ($self->{username});
-       syswrite $remote, "From: $self->{from}$EOL" if ($self->{from});
-       syswrite $remote, "IP: $self->{ip}$EOL" if ($self->{ip});
-       syswrite $remote, "Subject: $self->{subject}$EOL" if ($self->{subject});
-       if (ref $self->{rcpt} eq "ARRAY") {
-               foreach ($self->{rcpt}) {
-                       syswrite $remote, "Rcpt: $_ $EOL";
-               }
-       }
-       syswrite $remote, $EOL;
-       syswrite $remote, $msg;
-       syswrite $remote, $EOL;
-       
-       return undef unless $self->_get_io_readiness($remote, 0);
-                       
-       my ($in, $res);
-       my $offset = 0;
-       do {
-               $res = sysread($remote, $in, 512, $offset);
-               if ($res > 0 && $res < 512) {
-                       $self->_get_io_readiness($remote, 0);
-               }
-               $offset += $res;
-       } while ($res > 0);
+public instance (\%) process_item (String $item)
 
-       my ($version, $resp_code, $resp_msg) = $self->_parse_response_line($in);
+Description:
+Do specified command for a single file, path or IMAP folder
 
-       $self->{resp_code} = $resp_code;
-       $self->{resp_msg} = $resp_msg;
+The return value is a hash reference containing results of each command for each server from cluster
 
-       return undef unless (defined($resp_code) && $resp_code == 0);
+=cut
 
-       my $cur_metric;
-       my @lines = split (/^/, $in);
-       foreach my $line (@lines) {
-               if ($line =~ m!Metric: (\S+); (\S+); (\S+) / (\S+)!) {
-                       $metrics{$1} = {
-                               isspam => $2,
-                               score => $3 + 0,
-                               threshold => $4 + 0,
-                               symbols => [],
-                               urls => [],
-                               messages => [],
-                       };
-                       $cur_metric = $1;
-               }
-               elsif ($line =~ /^Symbol: (\S+)/ && $cur_metric) {
-                       my $symref = $metrics{$cur_metric}->{'symbols'};
-                       push(@$symref, $1);
+sub process_item {
+       my $self = shift;
+       my $item = shift;
+       
+       if (defined ($item)) {
+               if ($item =~ qr|^imap(s?):user:([^:]+):password:([^:]*):host:([^:]+):mbox:(.+)$|) {
+                       return $self->_process_imap ($1, $2, $3, $4, $5);
                }
-               elsif ($line =~ /^Urls: (\S+)/ && $cur_metric) {
-                       @{ $metrics{$cur_metric}->{'urls'} } = split /\s/, $1;
+               elsif (-f $item) {
+                       return $self->_process_file ($item);
                }
-               elsif ($line =~ /^Message: (\S+)/ && $cur_metric) {
-                       my $symref = $metrics{$cur_metric}->{'messages'};
-                       push(@$symref, $1);
+               elsif (-d $item) {
+                       return $self->_process_directory ($item);
                }
-               elsif ($line =~ /^${EOL}$/) {
-                       last;
+               else {
+                       warn "urecognized argument: $item";
                }
        }
+       undef;
+}
 
-       close $remote;
+=head2 process_path
 
-       return \%metrics;
+public instance (\%) process_path ()
+
+Description:
+Do specified command for each file in path or message in IMAP folder
+
+The return value is a hash reference containing results of each command for each server from cluster
+
+=cut
+sub process_path {
+       my $self = shift;
+       my %res;
+       foreach (@_) {
+               $res{$_} = $self->process_item($_);
+       }
 
+       return \%res;
 }
 
+=head2 do_all_cmd
 
-sub _do_control_command {
-       my ($self, $remote, $msg) = @_;
+public instance (\%) do_all_cmd (String $msg)
+
+Description:
+This method makes a call to the the whole rspamd cluster and call specified command
+(in $self->{command}).
+
+The return value is a hash reference containing results of each command for each server from cluster
+
+=cut
+
+sub do_all_cmd {
+       my ($self, $input) = @_;
 
-       local $SIG{PIPE} = 'IGNORE';
        my %res;
        
-       $res{error_code} = 0;
+       foreach my $hostdef (@{ $self->{'hosts'} }) {
+               $self->_clear_errors();
 
-    # Read greeting first
-    if (defined (my $greeting = <$remote>)) {
-        if ($greeting !~ /^Rspamd version/) {
-            $res{error} = "Not rspamd greeting line $greeting";
-                       $res{error_code} = 500;
-                       return \%res;
-        }
-    }
+               my $remote = $self->_create_connection($hostdef);
 
-    if ($self->{'command'} =~ /^learn$/i) {
-        if (!$self->{'statfile'}) {
-                       $res{error} = "Statfile is not specified to learn command";
-                       $res{error_code} = 500;
-                       return \%res;
-               }
-        
-        if ($self->_auth ($remote)) {
-            my $len = length ($msg);
-            syswrite $remote, "learn $self->{statfile} $len -w $self->{weight}" . $EOL;
-            syswrite $remote, $input . $EOL;
-                       return undef unless $self->_get_io_readiness($remote, 0);
-            if (defined (my $reply = <$remote>)) {
-                if ($reply =~ /^learn ok, sum weight: ([0-9.]+)/) {
-                    $res{error} = "Learn succeed. Sum weight: $1\n";
-                                       return \%res;
-                }
-                else {
-                                       $res{error_code} = 500;
-                    $res{error} = "Learn failed\n";
-                                       return \%res;
-                }
-            }
-        }
-        else {
-                       $res{error_code} = 403;
-            $res{error} = "Authentication failed\n";
-                       return \%res;
-        }
-    }
-    elsif ($self->{'command'} =~ /^weights$/i) {
-        if (!$self->{'statfile'}) {
-                       $res{error_code} = 500;
-                       $res{error} = "Statfile is not specified to weights command";
-                       return \%res;
+               if (! $remote) {
+                       $res{$hostdef}->{error_code} = 404;
+                       $res{$hostdef}->{error} = "Cannot connect to $hostdef";
                }
-        
-               my $len = length ($input);
-               $res{error} = "Sending $len bytes...\n";
-               syswrite $remote, "weights $self->{'statfile'} $len" . $EOL;
-               syswrite $remote, $input . $EOL;
-               return undef unless $self->_get_io_readiness($remote, 0);
-               while (defined (my $reply = <$remote>)) {
-                       last if $reply =~ /^END/;
-                       $res{error} .= $reply;
+               else {
+                       if ($self->{'control'}) {
+                               $res{$hostdef} = $self->_do_control_command ($remote, $input);
+                       }
+                       else {
+                               $res{$hostdef} = $self->_do_rspamc_command ($remote, $input);
+                       }
                }
-    }
-    elsif ($self->{'command'} =~ /(reload|shutdown)/i) {
-        if ($self->_auth ($remote)) {
-            syswrite $remote, $self->{'command'} . $EOL;
-            while (defined (my $line = <$remote>)) {
-                last if $line =~ /^END/;
-                $res{error} .= $line;
-            }
-        }
-        else {
-                       $res{error_code} = 403;
-            $res{error} = "Authentication failed\n";
-                       return \%res;
-        }
-    }
-    elsif ($self->{'command'} =~ /(fuzzy_add|fuzzy_del)/i) {
-        if ($self->_auth ($remote)) {
-            my $len = length ($input);
-            syswrite $remote, $self->{'command'} . " $len $self->{'weight'}" . $EOL;
-            syswrite $remote, $input . $EOL;
-                       return undef unless $self->_get_io_readiness($remote, 0);
-            if (defined (my $reply = <$remote>)) {
-                if ($reply =~ /^OK/) {
-                    $res{error} = $self->{'command'} . " succeed\n";
-                                       return \%res;
-                }
-                else {
-                                       $res{error_code} = 500;
-                    $res{error} = $self->{'command'} . " failed\n";
-                                       return \%res;
-                }
-            }
-        }
-        else {
-                       $res{error_code} = 403;
-            $res{error} = "Authentication failed\n";
-                       return \%res;
-        }
-    
-    }
-    else {
-        syswrite $remote, $self->{'command'} . $EOL;
-        while (defined (my $line = <$remote>)) {
-            last if $line =~ /^END/;
-            $res{error} .= $line;
-        }
-    }
+       }
 
        return \%res;
 }
 
-sub _process_file {
-       my $self = shift;
-       my $file = shift;
 
-       open(FILE, "< $file") or return;
-       
-       my $input;
-       while (defined (my $line = <FILE>)) {
-               $input .= $line;
-       }
-       
-       close FILE;
-       $self->do_all_cmd ($input);
-}
+=head2 check
 
-sub _process_directory {
-       my $self = shift;
-       my $dir = shift;
+public instance (\%) check (String $msg)
 
-       opendir (DIR, $dir) or return;
-
-       while (defined (my $file = readdir (DIR))) {
-               $file = "$dir/$file";
-               if (-f $file) {
-                       $self->_process_file ($file);
-               }       
-       }
-       closedir (DIR);
-}
-
-sub _check_imap_reply {
-       my $self = shift;
-       my $sock = shift;
-       my $seq = shift;
-
-       my $input;
-
-       while (defined ($input = <$sock>)) {
-               chomp $input;
-               if ($input =~ /BAD|NO (.+)$/) {
-                       $_[0] = $1;
-                       return 0;
-               }
-               next if ($input =~ /^\*/);
-               if ($input =~ /^$seq OK/) {
-                       return 1;
-               }
-
-               $_[0] = $input;
-               return 0;
-       }
-
-       $_[0] = "timeout";
-       
-       return 0;
-}
-
-sub _parse_imap_body {
-       my $self = shift;
-       my $sock = shift;
-       my $seq = shift;
-       my $input;
-       my $got_body = 0;
-
-       while (defined (my $line = <$sock>)) {
-               if (!$got_body && $line =~ /^\*/) {
-                       $got_body = 1;
-                       next;
-               }
-               if ($line =~ /^$seq OK/) {
-                       return $input;
-               }
-               elsif ($got_body) {
-                       $input .= $line;
-                       next;
-               }
-               
-               return undef;
-       }
-
-       return undef;
-
-}
-
-sub _parse_imap_sequences {
-       my $self = shift;
-       my $sock = shift;
-       my $seq = shift;
-       my $input;
-
-       while (defined ($input = <$sock>)) {
-               chomp $input;
-               if ($input =~ /^\* SEARCH (.+)$/) {
-                       @res = split (/\s/, $1);
-                       next;
-               }
-               elsif ($input =~ /^$seq OK/) {
-                       return \@res;
-               }
-               return undef;
-       }
-
-}
-
-sub process_imap {
-       my ($self, $ssl, $user, $password, $host, $mbox) = @_;
-       my $seq = 1;
-       my $sock;
-
-       if (!$password) {
-               eval {
-                       use Term::ReadKey;
-                       $self->{error} = "Enter IMAP password: ";
-                       ReadMode 'noecho';
-                       $password = ReadLine 0;
-                       chomp $password;
-                       ReadMode 'normal';
-                       $self->{error} = "\n";
-               } or die "cannot get password. Check that Term::ReadKey is installed";
-       }
-
-       # Stupid code that does not take care of timeouts etc, just trying to extract messages
-       if ($ssl) {
-               $sock = $self->_make_ssl_socket ($host, 'imaps');
-       }
-       else {
-               $sock = $self->_make_tcp_socket ($host, 143);
-       }
-       my $reply = <$sock>;
-       if (!defined ($reply) || $reply !~ /^\* OK/) {
-               $self->{error} = "Imap server is not ready";
-               return;
-       }
-       syswrite $sock, "$seq LOGIN $user $password$EOL";
-       if (!$self->_check_imap_reply ($sock, $seq, $reply)) {
-               $self->{error} = "Cannot login to imap server: $reply";
-               return;
-       }
-       $seq ++;
-       syswrite $sock, "$seq SELECT $mbox$EOL";
-       if (!$self->_check_imap_reply ($sock, $seq, $reply)) {
-               $self->{error} = "Cannot select mbox $mbox: $reply";
-               return;
-       }
-       $seq ++;
-       syswrite $sock, "$seq SEARCH $self->{imap_search}$EOL";
-       my $messages;
-       if (!defined ($messages = $self->_parse_imap_sequences ($sock, $seq))) {
-               $self->{error} = "Cannot make search";
-               return;
-       }
-       $seq ++;
-       foreach my $message (@{ $messages }){
-               syswrite $sock, "$seq FETCH $message body[]$EOL";
-               if (defined (my $input = $self->_parse_imap_body ($sock, $seq))) {
-                       $self->do_all_cmd ($input);
-               }
-               $seq ++;
-       } 
-       syswrite $sock, "$seq LOGOUT$EOL";
-       close $sock;
-}
-
-=head2 process_item
-
-public instance (\%) process_item (String $item)
-
-Description:
-Do specified command for a single file, path or IMAP folder
-
-The return value is a hash reference containing results of each command for each server from cluster
-
-=cut
-
-sub process_item {
-       my $item = shift;
-       
-       if (defined ($item)) {
-               if ($item =~ qr|^imap(s?):user:([^:]+):password:([^:]*):host:([^:]+):mbox:(.+)$|) {
-                       return $self->_process_imap ($1, $2, $3, $4, $5);
-               }
-               elsif (-f $item) {
-                       return $self->_process_file ($item);
-               }
-               elsif (-d $item) {
-                       return $self->_process_directory ($item);
-               }
-               else {
-                       warn "urecognized argument: $item";
-               }
-       }
-       undef;
-}
-
-=head2 process_path
-
-public instance (\%) process_path ()
-
-Description:
-Do specified command for each file in path or message in IMAP folder
-
-The return value is a hash reference containing results of each command for each server from cluster
-
-=cut
-sub process_path {
-       my %res;
-       foreach (@_) {
-               $res{$_} = $self->process_item($_);
-       }
-
-       return \%res;
-}
-
-=head2 do_all_cmd
-
-public instance (\%) do_all_cmd (String $msg)
-
-Description:
-This method makes a call to the the whole rspamd cluster and call specified command
-(in $self->{command}).
-
-The return value is a hash reference containing results of each command for each server from cluster
-
-=cut
-
-sub do_all_cmd {
-       my ($self, $input) = @_;
-
-       my %res;
-       
-       foreach my $hostdef (@{ $self->{'hosts'} }) {
-               $self->_clear_errors();
-
-               my $remote = $self->_create_connection($hostdef);
-
-               if (! $remote) {
-                       $res{$hostdef}->{error} = "Cannot connect to $hostdef";
-               }
-               else {
-                       if ($self->{'control'}) {
-                               $res{$hostdef} = $self->_do_control_command ($remote, $input);
-                       }
-                       else {
-                               $res{$hostdef} = $self->_do_rspamc_command ($remote, $input);
-                       }
-               }
-       }
-
-       return \%res;
-}
-
-
-=head2 check
-
-public instance (\%) check (String $msg)
-
-Description:
-This method makes a call to the spamd server and depending on the value of
-C<$is_check_p> either calls PROCESS or CHECK.
+Description:
+This method makes a call to the spamd server and depending on the value of
+C<$is_check_p> either calls PROCESS or CHECK.
 
 The return value is a hash reference containing metrics indexed by name. Each metric
 is hash that contains data:
-
+=over 
+=item *
 isspam
 
+=item *
 score
 
+=item *
 threshold
 
+=item *
 symbols - array of symbols
 
+=back
+
 =cut
 
 sub check {
@@ -619,15 +267,21 @@ This method makes a call to the spamd server
 
 The return value is a hash reference containing metrics indexed by name. Each metric
 is hash that contains data:
-
+=over
+=item *
 isspam
 
+=item *
 score
 
+=item *
 threshold
 
+=item *
 symbols - array of symbols
 
+=back
+
 =cut
 
 sub symbols {
@@ -648,14 +302,21 @@ This method makes a call to the spamd server
 The return value is a hash reference containing metrics indexed by name. Each metric
 is hash that contains data:
 
+=over
+=item *
 isspam
 
+=item *
 score
 
+=item *
 threshold
 
+=item *
 symbols - array of symbols
 
+=back
+
 =cut
 sub process {
        my ($self, $msg) = @_;
@@ -708,7 +369,7 @@ sub urls {
 
 =head2 learn
 
-public instance (\%) check (String $msg, String $statfile, Boolean in_class)
+public instance (\%) learn (String $msg)
 
 Description:
 This method makes a call to the spamd learning a statfile with message.
@@ -723,6 +384,14 @@ sub learn {
        return $self->_do_control_command ($self, $msg);
 }
 
+=head2 weights
+
+public instance (\%) weights (String $msg)
+
+Description:
+This method makes a call to the spamd showing weights of message by each statfile.
+
+=cut
 sub weights {
        my ($self, $msg) = @_;
        
@@ -731,6 +400,14 @@ sub weights {
        return $self->_do_control_command ($self, $msg);
 }
 
+=head2 fuzzy_add
+
+public instance (\%) fuzzy_add (String $msg)
+
+Description:
+This method makes a call to the spamd adding specified message to fuzzy storage.
+
+=cut
 sub fuzzy_add {
        my ($self, $msg) = @_;
        
@@ -738,6 +415,14 @@ sub fuzzy_add {
 
        return $self->_do_control_command ($self, $msg);
 }
+=head2 fuzzy_del
+
+public instance (\%) fuzzy_add (String $msg)
+
+Description:
+This method makes a call to the spamd removing specified message from fuzzy storage.
+
+=cut
 sub fuzzy_del {
        my ($self, $msg) = @_;
        
@@ -746,13 +431,29 @@ sub fuzzy_del {
        return $self->_do_control_command ($self, $msg);
 }
 
-sub stat {
-       my ($self) = @_;
-       
-       $self->{command} = 'STAT';
+=head2 stat
+
+public instance (\%) stat ()
+
+Description:
+This method makes a call to the spamd and get statistics.
+
+=cut
+sub stat {
+       my ($self) = @_;
+       
+       $self->{command} = 'STAT';
 
        return $self->_do_control_command ($self, undef);
 }
+=head2 uptime
+
+public instance (\%) uptime ()
+
+Description:
+This method makes a call to the spamd and get uptime.
+
+=cut
 sub uptime {
        my ($self) = @_;
        
@@ -760,6 +461,14 @@ sub uptime {
 
        return $self->_do_control_command ($self, undef);
 }
+=head2 counters
+
+public instance (\%) counters ()
+
+Description:
+This method makes a call to the spamd and get counters.
+
+=cut
 sub counters {
        my ($self) = @_;
        
@@ -864,8 +573,25 @@ sub _create_connection {
     }
     elsif ($hostdef =~ /^\s*(([^:]+):(\d+))\s*$/) {
                $remote = IO::Socket::INET->new( Proto     => "tcp",
-                                       PeerAddr  => $server->{host},
-                                       PeerPort  => $server->{port},
+                                       PeerAddr  => $2,
+                                       PeerPort  => $3,
+                                       Blocking  => 0,
+                               );
+               # Get write readiness
+               if (defined ($remote)) {
+                       if ($self->_get_io_readiness($remote, 1) != 0) {
+                               return $remote;
+                       }
+                       else {
+                               close ($remote);
+                               return undef;
+                       }
+               }
+    }
+    elsif ($hostdef =~ /^\s*([^:]+)\s*$/) {
+               $remote = IO::Socket::INET->new( Proto     => "tcp",
+                                       PeerAddr  => $1,
+                                       PeerPort  => $self->{control} ? 11334 : 11333,
                                        Blocking  => 0,
                                );
                # Get write readiness
@@ -1060,4 +786,400 @@ sub _clear_errors {
        $self->{error} = undef;
 }
 
+# Currently just read stdin for user's message and pass it to rspamd
+sub _do_rspamc_command {
+       my ($self, $remote, $msg) = @_;
+
+       my %metrics;
+
+
+       my $msgsize = length($msg.$EOL);
+
+       local $SIG{PIPE} = 'IGNORE';
+
+       if (!(syswrite($remote, "$self->{command} $PROTOVERSION$EOL"))) {
+               $self->_mark_dead($remote);
+               return 0;
+       }
+       syswrite $remote, "Content-length: $msgsize$EOL";
+       syswrite $remote, "User: $self->{username}$EOL" if ($self->{username});
+       syswrite $remote, "From: $self->{from}$EOL" if ($self->{from});
+       syswrite $remote, "IP: $self->{ip}$EOL" if ($self->{ip});
+       syswrite $remote, "Subject: $self->{subject}$EOL" if ($self->{subject});
+       if (ref $self->{rcpt} eq "ARRAY") {
+               foreach ($self->{rcpt}) {
+                       syswrite $remote, "Rcpt: $_ $EOL";
+               }
+       }
+       syswrite $remote, $EOL;
+       syswrite $remote, $msg;
+       syswrite $remote, $EOL;
+       
+       return undef unless $self->_get_io_readiness($remote, 0);
+                       
+       my ($in, $res);
+       my $offset = 0;
+       do {
+               $res = sysread($remote, $in, 512, $offset);
+               if ($res > 0 && $res < 512) {
+                       $self->_get_io_readiness($remote, 0);
+               }
+               $offset += $res;
+       } while ($res > 0);
+
+       my ($version, $resp_code, $resp_msg) = $self->_parse_response_line($in);
+
+       $self->{resp_code} = $resp_code;
+       $self->{resp_msg} = $resp_msg;
+
+       return undef unless (defined($resp_code) && $resp_code == 0);
+
+       my $cur_metric;
+       my @lines = split (/^/, $in);
+       foreach my $line (@lines) {
+               if ($line =~ m!Metric: (\S+); (\S+); (\S+) / (\S+)!) {
+                       $metrics{$1} = {
+                               isspam => $2,
+                               score => $3 + 0,
+                               threshold => $4 + 0,
+                               symbols => [],
+                               urls => [],
+                               messages => [],
+                       };
+                       $cur_metric = $1;
+               }
+               elsif ($line =~ /^Symbol: (\S+);\s*(.+)${EOL}$/ && $cur_metric) {
+                       # Line with parameters
+                       my $symref = $metrics{$cur_metric}->{'symbols'};
+                       push(@$symref, "$1($2)");
+               }
+               elsif ($line =~ /^Symbol: (\S+)/ && $cur_metric) {
+                       my $symref = $metrics{$cur_metric}->{'symbols'};
+                       push(@$symref, $1);
+               }
+               elsif ($line =~ /^Urls: (.+)$/ && $cur_metric) {
+                       @{ $metrics{$cur_metric}->{'urls'} } = split /,\s+/, $1;
+               }
+               elsif ($line =~ /^Message: (.+)/ && $cur_metric) {
+                       my $symref = $metrics{$cur_metric}->{'messages'};
+                       push(@$symref, $1);
+               }
+               elsif ($line =~ /^${EOL}$/) {
+                       last;
+               }
+       }
+
+       close $remote;
+
+       return \%metrics;
+
+}
+
+
+sub _do_control_command {
+       my ($self, $remote, $msg) = @_;
+
+       local $SIG{PIPE} = 'IGNORE';
+       my %res = (
+               error_code      => 0,
+               error           => '',
+       );
+
+       unless ($self->_get_io_readiness($remote, 0)) {
+               $res{error} = "Timeout while reading data from socket";
+               $res{error_code} = 501;
+               return \%res;
+       }
+
+    # Read greeting first
+    if (defined (my $greeting = <$remote>)) {
+        if ($greeting !~ /^Rspamd version/) {
+            $res{error} = "Not rspamd greeting line $greeting";
+                       $res{error_code} = 500;
+                       return \%res;
+        }
+    }
+
+    if ($self->{'command'} =~ /^learn$/i) {
+        if (!$self->{'statfile'}) {
+                       $res{error} = "Statfile is not specified to learn command";
+                       $res{error_code} = 500;
+                       return \%res;
+               }
+        
+        if ($self->_auth ($remote)) {
+            my $len = length ($msg);
+            syswrite $remote, "learn $self->{statfile} $len -w $self->{weight}" . $EOL;
+            syswrite $remote, $input . $EOL;
+                       unless ($self->_get_io_readiness($remote, 0)) {
+                               $res{error} = "Timeout while reading data from socket";
+                               $res{error_code} = 501;
+                               return \%res;
+                       }
+            if (defined (my $reply = <$remote>)) {
+                if ($reply =~ /^learn ok, sum weight: ([0-9.]+)/) {
+                    $res{error} = "Learn succeed. Sum weight: $1\n";
+                                       return \%res;
+                }
+                else {
+                                       $res{error_code} = 500;
+                    $res{error} = "Learn failed\n";
+                                       return \%res;
+                }
+            }
+        }
+        else {
+                       $res{error_code} = 403;
+            $res{error} = "Authentication failed\n";
+                       return \%res;
+        }
+    }
+    elsif ($self->{'command'} =~ /^weights$/i) {
+        if (!$self->{'statfile'}) {
+                       $res{error_code} = 500;
+                       $res{error} = "Statfile is not specified to weights command";
+                       return \%res;
+               }
+        
+               my $len = length ($input);
+               $res{error} = "Sending $len bytes...\n";
+               syswrite $remote, "weights $self->{'statfile'} $len" . $EOL;
+               syswrite $remote, $input . $EOL;
+               unless ($self->_get_io_readiness($remote, 0)) {
+                       $res{error} = "Timeout while reading data from socket";
+                       $res{error_code} = 501;
+                       return \%res;
+               }
+               while (defined (my $reply = <$remote>)) {
+                       last if $reply =~ /^END/;
+                       $res{error} .= $reply;
+               }
+    }
+    elsif ($self->{'command'} =~ /(reload|shutdown)/i) {
+        if ($self->_auth ($remote)) {
+            syswrite $remote, $self->{'command'} . $EOL;
+                       unless ($self->_get_io_readiness($remote, 0)) {
+                               $res{error} = "Timeout while reading data from socket";
+                               $res{error_code} = 501;
+                               return \%res;
+                       }
+            while (defined (my $line = <$remote>)) {
+                last if $line =~ /^END/;
+                $res{error} .= $line;
+            }
+        }
+        else {
+                       $res{error_code} = 403;
+            $res{error} = "Authentication failed\n";
+                       return \%res;
+        }
+    }
+    elsif ($self->{'command'} =~ /(fuzzy_add|fuzzy_del)/i) {
+        if ($self->_auth ($remote)) {
+            my $len = length ($input);
+            syswrite $remote, $self->{'command'} . " $len $self->{'weight'}" . $EOL;
+            syswrite $remote, $input . $EOL;
+                       unless ($self->_get_io_readiness($remote, 0)) {
+                               $res{error} = "Timeout while reading data from socket";
+                               $res{error_code} = 501;
+                               return \%res;
+                       }
+            if (defined (my $reply = <$remote>)) {
+                if ($reply =~ /^OK/) {
+                    $res{error} = $self->{'command'} . " succeed\n";
+                                       return \%res;
+                }
+                else {
+                                       $res{error_code} = 500;
+                    $res{error} = $self->{'command'} . " failed\n";
+                                       return \%res;
+                }
+            }
+        }
+        else {
+                       $res{error_code} = 403;
+            $res{error} = "Authentication failed\n";
+                       return \%res;
+        }
+    
+    }
+    else {
+        syswrite $remote, $self->{'command'} . $EOL;
+               unless ($self->_get_io_readiness($remote, 0)) {
+                       $res{error} = "Timeout while reading data from socket";
+                       $res{error_code} = 501;
+                       return \%res;
+               }
+        while (defined (my $line = <$remote>)) {
+            last if $line =~ /^END/;
+            $res{error} .= $line;
+        }
+    }
+
+       return \%res;
+}
+
+sub _process_file {
+       my $self = shift;
+       my $file = shift;
+
+       open(FILE, "< $file") or return;
+       
+       my $input;
+       while (defined (my $line = <FILE>)) {
+               $input .= $line;
+       }
+       
+       close FILE;
+       $self->do_all_cmd ($input);
+}
+
+sub _process_directory {
+       my $self = shift;
+       my $dir = shift;
+
+       opendir (DIR, $dir) or return;
+
+       while (defined (my $file = readdir (DIR))) {
+               $file = "$dir/$file";
+               if (-f $file) {
+                       $self->_process_file ($file);
+               }       
+       }
+       closedir (DIR);
+}
+
+sub _check_imap_reply {
+       my $self = shift;
+       my $sock = shift;
+       my $seq = shift;
+
+       my $input;
+
+       while (defined ($input = <$sock>)) {
+               chomp $input;
+               if ($input =~ /BAD|NO (.+)$/) {
+                       $_[0] = $1;
+                       return 0;
+               }
+               next if ($input =~ /^\*/);
+               if ($input =~ /^$seq OK/) {
+                       return 1;
+               }
+
+               $_[0] = $input;
+               return 0;
+       }
+
+       $_[0] = "timeout";
+       
+       return 0;
+}
+
+sub _parse_imap_body {
+       my $self = shift;
+       my $sock = shift;
+       my $seq = shift;
+       my $input;
+       my $got_body = 0;
+
+       while (defined (my $line = <$sock>)) {
+               if (!$got_body && $line =~ /^\*/) {
+                       $got_body = 1;
+                       next;
+               }
+               if ($line =~ /^$seq OK/) {
+                       return $input;
+               }
+               elsif ($got_body) {
+                       $input .= $line;
+                       next;
+               }
+               
+               return undef;
+       }
+
+       return undef;
+
+}
+
+sub _parse_imap_sequences {
+       my $self = shift;
+       my $sock = shift;
+       my $seq = shift;
+       my $input;
+
+       while (defined ($input = <$sock>)) {
+               chomp $input;
+               if ($input =~ /^\* SEARCH (.+)$/) {
+                       @res = split (/\s/, $1);
+                       next;
+               }
+               elsif ($input =~ /^$seq OK/) {
+                       return \@res;
+               }
+               return undef;
+       }
+
+}
+
+sub process_imap {
+       my ($self, $ssl, $user, $password, $host, $mbox) = @_;
+       my $seq = 1;
+       my $sock;
+
+       if (!$password) {
+               eval {
+                       use Term::ReadKey;
+                       $self->{error} = "Enter IMAP password: ";
+                       ReadMode 'noecho';
+                       $password = ReadLine 0;
+                       chomp $password;
+                       ReadMode 'normal';
+                       $self->{error} = "\n";
+               } or die "cannot get password. Check that Term::ReadKey is installed";
+       }
+
+       # Stupid code that does not take care of timeouts etc, just trying to extract messages
+       if ($ssl) {
+               $sock = $self->_make_ssl_socket ($host, 'imaps');
+       }
+       else {
+               $sock = $self->_make_tcp_socket ($host, 143);
+       }
+       my $reply = <$sock>;
+       if (!defined ($reply) || $reply !~ /^\* OK/) {
+               $self->{error} = "Imap server is not ready";
+               return;
+       }
+       syswrite $sock, "$seq LOGIN $user $password$EOL";
+       if (!$self->_check_imap_reply ($sock, $seq, $reply)) {
+               $self->{error} = "Cannot login to imap server: $reply";
+               return;
+       }
+       $seq ++;
+       syswrite $sock, "$seq SELECT $mbox$EOL";
+       if (!$self->_check_imap_reply ($sock, $seq, $reply)) {
+               $self->{error} = "Cannot select mbox $mbox: $reply";
+               return;
+       }
+       $seq ++;
+       syswrite $sock, "$seq SEARCH $self->{imap_search}$EOL";
+       my $messages;
+       if (!defined ($messages = $self->_parse_imap_sequences ($sock, $seq))) {
+               $self->{error} = "Cannot make search";
+               return;
+       }
+       $seq ++;
+       foreach my $message (@{ $messages }){
+               syswrite $sock, "$seq FETCH $message body[]$EOL";
+               if (defined (my $input = $self->_parse_imap_body ($sock, $seq))) {
+                       $self->do_all_cmd ($input);
+               }
+               $seq ++;
+       } 
+       syswrite $sock, "$seq LOGOUT$EOL";
+       close $sock;
+}
+
 1;
index a8f410ba829f38f685f9288a0e02fa48fdef82fc..ba00d5a5ca13b2ece42977a82aa9c4c3f7f459e9 100755 (executable)
@@ -9,7 +9,7 @@
 
 use Socket qw(:DEFAULT :crlf);
 use Getopt::Std;
-use Data::Dumper;
+use Term::Cap;
 use Mail::Rspamd::Client;
 
 my %cfg = (
@@ -25,6 +25,8 @@ my %cfg = (
        'imap_search' => 'ALL',
 );
 
+my $terminal;
+
 $main::VERSION = '@RSPAMD_VERSION@';
 
 sub HELP_MESSAGE {
@@ -133,6 +135,50 @@ sub parse_config {
 
 }
 
+sub print_control_result {
+       my ($host, $res) = @_;
+
+       $terminal->Tputs( 'md', 1, *STDOUT );
+       print "Results for host $host:\n\n";
+       $terminal->Tputs( 'me', 1, *STDOUT );
+       if ($res->{error_code} == 0) {
+               print "$res->{error}\n";
+       }
+       else {
+               print "Error occured: $res->{error_code}:\n$res->{error}\n";
+       }
+}
+
+sub print_rspamc_result {
+       my ($host, $res) = @_;
+
+       $terminal->Tputs( 'md', 1, *STDOUT );
+       print "Results for host $host:\n\n";
+       $terminal->Tputs( 'me', 1, *STDOUT );
+
+       if (defined($res->{error})) {
+               print "Error occured: $res->{error}\n\n";
+       }
+       else {
+               while (my ($metric, $result) = each (%{ $res })) {
+                       $terminal->Tputs( 'md', 1, *STDOUT );
+                       print "$metric: ";
+                       $terminal->Tputs( 'me', 1, *STDOUT );
+                       print "$result->{isspam}, [ $result->{score} / $result->{threshold} ]\n";
+
+                       $terminal->Tputs( 'md', 1, *STDOUT );
+                       print "Symbols: ";
+                       $terminal->Tputs( 'me', 1, *STDOUT );
+                       print join("; ", @{ $result->{symbols} }) . "\n";
+                       print "Urls: " . join(", ", @{ $result->{urls} }) . "\n";
+                       foreach my $msg (@{ $result->{messages} }) {
+                               print "Message: $msg\n";
+                       }
+                       print "\n\n";
+               }
+       }
+}
+
 ############################# Main part ###########################################
 my %args;
 
@@ -210,6 +256,8 @@ if (defined ($args{H})) {
 
 my $rspamd = Mail::Rspamd::Client->new(\%cfg);
 
+$terminal = Tgetent Term::Cap { TERM => undef, OSPEED => 9600 };
+
 if (!defined ($path[0]) || ! $cfg{'require_input'}) {
        my $input;
        if ($cfg{'require_input'}) {
@@ -219,9 +267,27 @@ if (!defined ($path[0]) || ! $cfg{'require_input'}) {
        }
        
        my $res = $rspamd->do_all_cmd ($input);
-       print Dumper($res);
+       while (my ($host, $result) =  each (%{ $res })) {
+               if ($cfg{control}) {
+                       print_control_result ($host, $result);
+               }
+               else {
+                       print_rspamc_result ($host, $result);
+               }
+       }
 }
 else {
        my $res = $rspamd->process_path (@path);
-       print Dumper($res);
+
+       while (my ($item, $result) =  each (%{ $res })) {
+               print "Results for item $item:\n";
+               while (my ($host, $r) =  each (%{ $result })) {
+                       if ($cfg{control}) {
+                               print_control_result ($host, $r);
+                       }
+                       else {
+                               print_rspamc_result ($host, $r);
+                       }
+               }
+       }
 }