⚝
One Hat Cyber Team
⚝
Your IP:
216.73.216.19
Server IP:
178.33.27.10
Server:
Linux cpanel.dev-unit.com 3.10.0-1160.108.1.el7.x86_64 #1 SMP Thu Jan 25 16:17:31 UTC 2024 x86_64
Server Software:
Apache/2.4.57 (Unix) OpenSSL/1.0.2k-fips
PHP Version:
8.2.11
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
lib64
/
perl5
/
vendor_perl
/
Razor2
/
Client
/
View File Name :
Core.pm
#!/usr/bin/perl -sw ## ## Razor2::Client::Core - Vipul's Razor Client API ## ## Copyright (c) 2001, Vipul Ved Prakash. All rights reserved. ## This code is free software; you can redistribute it and/or modify ## it under the same terms as Perl itself. ## ## $Id: Core.pm,v 1.92 2006/05/27 00:00:53 rsoderberg Exp $ package Razor2::Client::Core; use strict; use IO::Socket; use IO::Select; use Errno qw(:POSIX); use Razor2::Client::Version; use Data::Dumper; use base qw(Razor2::String); use base qw(Razor2::Logger); use base qw(Razor2::Client::Engine); use base qw(Razor2::Errorhandler); use Razor2::Client::Version; use Razor2::String qw(hextobase64 makesis parsesis hmac_sha1 xor_key prep_mail debugobj to_batched_query from_batched_query hexbits2hash fisher_yates_shuffle); our ($VERSION) = do { my @r = ( q$Revision: 1.92 $ =~ /\d+/g ); sprintf "%d." . "%02d" x $#r, @r }; our $PROTOCOL = $Razor2::Client::Version::PROTOCOL; sub new { my ( $class, $conf, %params ) = @_; my $self = {}; bless $self, $class; $self->debug("Razor Agents $VERSION, protocol version $PROTOCOL."); return $self; } # # We store server-specific config info for each server we know about. # All info about razor servers is stored in $self->{s}. # # Basically we get the server name/ip from {list}, # load that server's specific info from {allconfs} into {conf}, # and do stuff. If server is no good, we get nextserver from {list} # # $self->{s}->{list} ptr to {nomination} if report,revoke; or {catalogue} if check # or the cmd-line server (-rs server) # $self->{s}->{new_list} set to 1 when discover gets new lists # $self->{s}->{catalogue} array ref containing catalogue servers # $self->{s}->{nomination}array ref containing nomination servers # $self->{s}->{discovery} array ref containing discovery servers # # $self->{s}->{modified} array ref containing servers whose .conf needs updating # $self->{s}->{modified_lst} array ref containing which .lst files need updating # # $self->{s}->{ip} string containing ip (or dns name) of current server from {list}) # $self->{s}->{port} string containing port, taken from server:port from {list} # $self->{s}->{engines} engines supported, derived from {conf}->{se} # $self->{s}->{conf} hash ref containing current server's config params # read from $razorhome/server.$ip.conf # # $self->{s}->{allconfs} hash ref of all servers' configs. key={ip}, val={conf} # as read from server.*.conf file # # $self->{s}->{listfile} string containing path/file of server.lst, either # nomination or catalogue depending $self->{breed} # $self->{conf}->{listfile_discovery} string containing path/file of discovery server # # NOTE: if we are razor-check, server is Catalogue Server # otherwise server is Nomination server. # # everytime we update our server list, $self->{s}->{list}; # we want to write that to disk - $self->{s}->{listfile} # sub nextserver { my ($self) = @_; $self->log( 16, "entered nextserver" ); # see if we need to discover (.lst files might be too old) $self->discover() or return $self->errprefix("nextserver"); # first time we don't remove from list # or if we've rediscovered. shift @{ $self->{s}->{list} } unless ( $self->{s}->{new_list} || !$self->{s}->{ip} ); $self->{s}->{new_list} = 0; my $next = ${ $self->{s}->{list} }[0]; # do we ever want to put current back on the end of list? # push @{$self->{s}->{list}}, $self->{s}->{ip}; if ($next) { ( $self->{s}->{port} ) = $next =~ /:(.*)$/; $next =~ s/:.*$//; # optional $self->{s}->{ip} = $next; # ip can be IP or DNS name $self->{s}->{port} ||= $self->{conf}->{port} || 2703; $self->{s}->{conf} = $self->{s}->{allconfs}->{$next}; my $svrport = "$self->{s}->{ip}:$self->{s}->{port}"; # get rid of server specific stuff delete $self->{s}->{greeting}; unless ( ref( $self->{s}->{conf} ) ) { # never used this server before, no cached info. go get it! $self->{s}->{conf} = {}; $self->connect; # calls parse_greeting which calls compute_server_conf } else { $self->compute_server_conf(1); # computes supported engines, logs info } $self->writeservers(); my $srl = defined( $self->{s}->{conf}->{srl} ) ? $self->{s}->{conf}->{srl} : "<unknown>"; $self->log( 8, "Using next closest server $svrport, cached info srl $srl" ); #$self->logobj(11, "Using next closest server $svrport, cached info", $self->{s}->{conf}); return 1; } else { return $self->error("Razor server $self->{opt}->{server} not available at this time") if $self->{opt}->{server}; $self->{force_discovery} = 1; if ( $self->{done_discovery} && !( $self->discover ) ) { return $self->errprefix("No Razor servers available at this time"); } return $self->nextserver; } } sub load_at_runtime { my ( $self, $class, $sub, $args ) = @_; $sub = 'new' unless defined $sub; $args = "" unless defined $args; eval "use $class"; if ($@) { $self->log( 2, "$class not found, please to fix." ); return $self->error("\n\n$@"); } my $evalstr; if ( $sub && $sub ne "new" ) { $evalstr = $class . "::$sub($args);"; } else { $evalstr = $class . "->new($args)"; } if ( my $dude = eval $evalstr ) { $self->log( 12, "Found and evaled $evalstr ==> $dude" ); return $dude; } else { $self->log( 5, "Found but problem (bad args?) with $evalstr" ); return $self->error("Problem with $evalstr"); } } # # uses DNS to find Discovery servers # puts discovery servers in $self->{s}->{discovery} # sub bootstrap_discovery { my ($self) = @_; $self->log( 16, "entered bootstrap_discovery" ); if ( $self->{conf}->{server} ) { $self->log( 8, "no bootstap_discovery when cmd-line server specified" ); return 1; } unless ( $self->{force_bootstrap_discovery} ) { if ( ref( $self->{s}->{discovery} ) && scalar( @{ $self->{s}->{discovery} } ) ) { $self->log( 8, "already have " . scalar( @{ $self->{s}->{discovery} } ) . " discovery servers" ); return 1; } elsif ( $self->{done_bootstrap} ) { # if we've done it before {s}->{discovery} should be set $self->log( 8, "already have done bootstrap_discovery" ); return 1; } } unless ( defined $self->{conf}->{listfile_discovery} ) { $self->log( 6, "discovery listfile not defined!" ); } elsif ( -s $self->{conf}->{listfile_discovery} ) { my $wait = $self->{conf}->{rediscovery_wait_dns} || 604800; # 604800 secs == 7 days my $randomize = int( rand( $wait / 7 ) ); my $timeleft = ( ( stat( $self->{conf}->{listfile_discovery} ) )[9] + $wait - $randomize ) - time; if ( $timeleft > 0 ) { $self->log( 7, "$timeleft seconds before soonest DNS discovery" ); return 1 unless $self->{force_bootstrap_discovery}; $self->log( 5, "forcing DNS discovery" ); } else { $self->log( 5, "DNS discovery overdue by " . ( 0 - $timeleft ) . " seconds" ); } } else { if ( -e $self->{conf}->{listfile_discovery} ) { $self->log( 6, "empty discovery listfile: $self->{conf}->{listfile_discovery}" ); } else { $self->log( 6, "no discovery listfile: $self->{conf}->{listfile_discovery}" ); } } $self->{s}->{discovery} = [ $self->{conf}->{razordiscovery} ]; push @{ $self->{s}->{modified_lst} }, "discovery"; return 1; } # # uses Discovery Servers to find closest Nomination/Catalogue Servers. # called every day or so of if .lst file is empty # # puts servers in $self->{s}->{list} # sub discover { my ($self) = @_; $self->log( 16, "entered discover" ); # # do we need to discover? # # no discover if cmd-line server return 1 if $self->{opt}->{server}; # # don't discover if conf says turn_off_discovery (unless force_discovery) # return 1 if $self->{conf}->{turn_off_discovery} && ( !( $self->{force_discovery} ) ); return $self->error("No Razor servers available at this time") if $self->{done_discovery}; # so if user has their own servers, and they are temporarily down, force_discovery. # good: shit will work # bad: it will erase their custom server*.lst file # unless ( defined $self->{s}->{listfile} ) { $self->debug("listfile not defined!"); } elsif ( -s $self->{s}->{listfile} ) { my $randomize = int( rand( $self->{conf}->{rediscovery_wait} / 7 ) ); my $timeleft = ( ( stat( $self->{s}->{listfile} ) )[9] + $self->{conf}->{rediscovery_wait} - $randomize ) - time; if ( $timeleft > 0 ) { $self->debug("$timeleft seconds before closest server discovery"); return 1 unless $self->{force_discovery}; $self->debug("forcing discovery"); } else { $self->debug( "server discovery overdue by " . ( 0 - $timeleft ) . " seconds" ); } } else { if ( -e $self->{s}->{listfile} ) { $self->debug("empty listfile: $self->{s}->{listfile}"); } else { $self->debug("no listfile: $self->{s}->{listfile}"); } } # # we need to discover. # return $self->errprefix("discover0") unless $self->bootstrap_discovery(); # # Go ahead and do discovery for both csl and nsl. # my %stype = ( csl => 'catalogue', nsl => 'nomination' ); my $srvs = { csl => {}, nsl => {} }; my $list_orig = $self->{s}->{list}; $self->{s}->{list} = $self->{s}->{discovery}; foreach ( @{ $self->{s}->{discovery} } ) { unless ( defined $_ ) { $self->log( 5, "Razor Discovery Server not defined!" ); next; } $self->log( 8, "Checking with Razor Discovery Server $_" ); unless ( $self->connect( server => $_, discovery_server => 1 ) ) { $self->log( 5, "Razor Discovery Server $_ is unreachable" ); next; } foreach my $querytype (qw(csl nsl)) { my $query = "a=g&pm=$querytype\r\n"; my $resp = $self->_send( [$query] ); unless ($resp) { $self->{s}->{list} = $list_orig; return $self->errprefix("discover1"); } # from_batched_query wants "-" in beginning, but not ".\r\n" at end $resp->[0] =~ s/\.\r\n$//sg; my $h = from_batched_query( $resp->[0], {} ); foreach my $href (@$h) { next unless $href->{$querytype}; $self->log( 8, "Discovery Server $_ replying with $querytype=$href->{$querytype}" ); $srvs->{$querytype}->{ $href->{$querytype} } = 1; } unless ( keys %{ $srvs->{$querytype} } ) { $self->log( 5, "Razor Discovery Server $_ had no valid $querytype servers" ); next; } } } $self->{s}->{list} = $list_orig; foreach my $querytype (qw(csl nsl)) { my @list = keys %{ $srvs->{$querytype} }; #return $self->error("Could not get valid info from Discovery Servers") # unless @list; unless (@list) { if ( $self->{force_bootstrap_discovery} ) { return $self->error("Bootstrap discovery failed. Giving up."); } $self->log( 5, "Couldn't talk to discovery servers. Will force a bootstrap..." ); $self->{force_bootstrap_discovery} = 1; return $self->error("Bootstrap discovery failed. Giving up.") unless $self->bootstrap_discovery(); return $self->discover(); } fisher_yates_shuffle( \@list ) if @list > 1; $self->{s}->{ $stype{$querytype} } = \@list; push @{ $self->{s}->{modified_lst} }, $stype{$querytype}; } $self->disconnect(); unless ( $self->{opt}->{server} ) { if ( $self->{breed} =~ /^check/ ) { $self->{s}->{list} = $self->{s}->{catalogue}; $self->{s}->{listfile} = $self->{conf}->{listfile_catalogue}; # for discovery() } else { $self->{s}->{list} = $self->{s}->{nomination}; $self->{s}->{listfile} = $self->{conf}->{listfile_nomination}; # for discovery() } } $self->{s}->{new_list} = 1; $self->{done_discovery} = 1; $self->writeservers(); return $self; } # only for debugging and errorchecking # sub logobj { my ( $self, $loglevel, $prefix, @objs ) = @_; return unless $self->logll($loglevel); foreach my $obj (@objs) { my $line = debugobj($obj); $self->log( $loglevel, "$prefix:\n $line" ); } } # # Mail Object # # Main data type used by check and report is the Mail Object. # an array of hash ref's, where array order matches mails in mbox (or stdin). # # key = value (not all defined) # # id = integer NOTE: only key guaranteed to exist # orig_mail = ref to string containing orig email (headers+body) # headers = headers of orig_email # spam = 0, not spam, >1 spam # skipme = 0|1 (not checked against server, usually whitelisted mail) # p = array ref to mimeparts. see below # e1 = similar to p, but special for engine 1 # # e1: each mail obj contains a special part for engine 1 # # skipme = 0|1 (ex: 1 if cleaned body goes to 0 len) # spam = 0, not spam, >1 spam # body = body of orig_mail # cleaned = body sent thru razor 1 preproc # e1 = hash using engine 1 # sent = hash ref sent to server # resp = hash ref of server response # # p: each mail obj contains 1 or more mimeparts, which can contain: # # id = string - mailid.part # skipme = 0|1 (ex: 1 if cleaned body goes to 0 len) # spam = 0, not spam, >1 spam # body = bodyparts (mimeparts) of orig_email, has X-Razor & Content-* headers # cleaned = body sent through preprocessors (deHtml, deQP, etc..), debugging use only # e2 = hash using engine 2 # e3 = hash using engine 2 # e4 = hash using engine 2 # sent = array ref of hash ref's sent to server # resp = array ref of hash ref's, where hash is parsed sis of server response # # sub prepare_objects { my ( $self, $objs ) = @_; my @objects; unless ( $self->{s}->{engines} || ( $self->{s}->{engines} = $self->compute_supported_engines() ) ) { $self->log( 1, "ALLBAD. supported engines not defined" ); } my $i = 1; if ( ref( $objs->[0] ) eq 'HASH' ) { # checking cmd-line signatures foreach my $o (@$objs) { my $obj = { id => $i++ }; $obj->{p}->[0]->{id} = "$obj->{id}.0"; $obj->{p}->[0]->{"e$o->{eng}"} = $o->{sig}; $obj->{ep4} = $o->{ep4} if $o->{ep4}; push @objects, $obj; } } elsif ( ref( $objs->[0] ) eq 'SCALAR' ) { # checking/reporting mail foreach my $o (@$objs) { my $obj = { id => $i++ }; $obj->{orig_mail} = $o; $self->log2file( 16, $o, "$obj->{id}.orig_mail" ); # includes headers and all push @objects, $obj; } $self->prepare_parts( \@objects ); } $self->logobj( 14, "prepared objs", \@objects ); return \@objects; } sub prepare_parts { my ( $self, $objs ) = @_; my $prep_mail_debug = 0; # debug print, 0=none, 1=split_mime stuff, 2=more verbose $prep_mail_debug++ if $self->{conf}->{debuglevel} > 15; $prep_mail_debug++ if $self->{conf}->{debuglevel} > 16; foreach my $obj (@$objs) { next if ( $obj->{skipme} || !$obj->{orig_mail} ); # # now split up mime parts from orig mail # my ( $headers, @bodyparts ) = prep_mail( $obj->{orig_mail}, $self->{conf}->{report_headers}, 4 * 1024, 60 * 1024, 15 * 1024, $self->{name_version}, $prep_mail_debug, # $debug, ); my $lines = " prep_mail done: mail $obj->{id} headers=" . length($$headers); foreach ( 0 .. $#bodyparts ) { $lines .= ", mime$_=" . length( ${ $bodyparts[$_] } ); } $self->log( 8, $lines ); unless (@bodyparts) { $self->log( 2, "empty body in mail $obj->{id}, skipping" ); next; } $$headers =~ s/\r\n/\n/gs; $obj->{headers} = $headers; # $obj->{e1} = { # id => "$obj->{id}.e1", # body => $obj->{orig_mail}, # }; $obj->{p} = []; foreach ( 0 .. $#bodyparts ) { $bodyparts[$_] =~ s/\r\n/\n/gs; $obj->{p}->[$_] = { id => "$obj->{id}.$_", body => $bodyparts[$_], }; } } return 1; } # given mail objects, fills out # # - e1 # # and for each body part of mail object, fills out # # - cleaned # - e2 # - e3 # - e4 # # also returns array ref of sigs suitable for printing # sub compute_sigs { my ( $self, $objects ) = @_; my @printable_sigs; foreach my $obj (@$objects) { next if ( $obj->{skipme} || !$obj->{orig_mail} ); if ( ${ $obj->{orig_mail} } =~ /\n(Subject: [^\n]+)\n/ ) { my $subj = substr $1, 0, 70; $self->log( 8, "mail " . $obj->{id} . " $subj" ); } else { $self->log( 8, "mail " . $obj->{id} . " has no subject" ); } # # clean each bodypart, removing if new length is 0 # next unless $obj->{p}; foreach my $objp ( @{ $obj->{p} } ) { next if $objp->{skipme}; my $olen = length( ${ $objp->{body} } ); my $clnpart = ${ $objp->{body} }; # We'll do a VR8 preproc to determine emptiness # of email, and store it so VR8 can use it. my $clnpart_vr8 = $clnpart; $self->{preproc_vr8}->preproc( \$clnpart_vr8 ); # in da future: $self->{s}->{conf}->{dre} $objp->{cleaned_vr8} = \$clnpart_vr8; # This for VR4 (the only other signature scheme # supported at this time. $self->{preproc}->preproc( \$clnpart ); $objp->{cleaned} = \$clnpart; my $clen = length($clnpart_vr8); $self->log2file( 15, $objp->{body}, "$objp->{id}.before_preproc.as_reported" ); $self->log2file( 15, $objp->{cleaned}, "$objp->{id}.after_preproc" ); if ( $clen eq 0 ) { $self->log( 6, "preproc: mail $objp->{id} went from $olen bytes to 0, erasing" ); $objp->{skipme} = 1; next; } elsif ( ( $clen < 128 ) and ( $clnpart =~ /^(Content\S*:[^\n]*\n\r?)+(Content\S*:[^\n]*)?\s*$/s ) ) { $self->log( 6, "preproc: mail $objp->{id} seems empty, erasing" ); $objp->{skipme} = 1; next; } elsif ( $clnpart_vr8 !~ /\S/ ) { $self->log( 6, "preproc: mail $objp->{id} went to all whitespace, erasing" ); $objp->{skipme} = 1; next; } elsif ( $clen eq $olen ) { $self->log( 6, "preproc: mail $objp->{id} unchanged, bytes=$olen" ); } else { $self->log( 6, "preproc: mail $objp->{id} went from $olen bytes to $clen " ); } } # # compute sig for bodyparts that are cleaned. # if ( $self->{s}->{conf}->{ep4} ) { $obj->{ep4} = $self->{s}->{conf}->{ep4}; } else { $obj->{ep4} = '7542-10'; $self->log( 8, "warning: no ep4 for server $self->{s}->{ip}, using $obj->{ep4}" ); } foreach my $objp ( @{ $obj->{p} } ) { next if $objp->{skipme}; $self->log( 15, "mail part is [${$objp->{cleaned}}]" ); if ( ${ $objp->{cleaned} } =~ /^\s+$/ ) { $self->log( 6, "mail $objp->{id} is whitespace only; skipping!" ); } $self->log( 6, "computing sigs for mail $objp->{id}, len " . length( ${ $objp->{cleaned} } ) ); foreach ( sort keys %{ $self->{s}->{engines} } ) { my $engine_no = $_; my $sig; if ( $engine_no == 4 ) { $sig = $self->compute_engine( $engine_no, $objp->{cleaned}, $obj->{ep4} ); } elsif ( $engine_no == 8 ) { $sig = $self->compute_engine( $engine_no, $objp->{cleaned_vr8} ); } else { # Unsupported signature type, don't calculate. next; # handled above } if ($sig) { $objp->{"e$engine_no"} = $sig; my @sigs; if ( ref $sig eq 'ARRAY' ) { @sigs = @$sig; } else { push @sigs, $sig; } for (@sigs) { my $line = "$objp->{id} e$engine_no: $_"; $line .= ", ep4: $obj->{ep4}" if ( $engine_no eq '4' ); push @printable_sigs, $line; } } else { $self->log( 6, "Engine ($engine_no) didn't produce a signature for mail $objp->{id}" ); } } } $self->logobj( 14, "computed sigs for obj", $obj ); } return \@printable_sigs; } # # this function is the only one that has to be aware # of razor protocol syntax. (not including random logging) # the hashes generated here are eventually sent to to_batched_query. # sub make_query { my ( $self, $params ) = @_; if ( $params->{action} =~ /^check/ ) { if ( ref $params->{sig} eq 'ARRAY' ) { # Multiple signature per part, VR8 my $sigs = $params->{sig}; my @queries; for (@$sigs) { my %query = ( a => 'c', e => $params->{eng}, s => $_ ); push @queries, \%query; } return \@queries; } else { my %query = ( a => 'c', e => $params->{eng}, s => $params->{sig} ); $query{ep4} = $params->{ep4} if $query{e} eq '4'; return \%query; } } elsif ( $params->{action} =~ /^rcheck/ ) { my %query = ( a => 'r', e => $params->{eng}, s => $params->{sig}, ); $query{ep4} = $params->{ep4} if $query{e} eq '4'; return \%query; } elsif ( $params->{action} =~ /(report)/ ) { # prep_mail already truncated headers and body parts > 64K my @dudes; my $n = 0; while ( $params->{obj}->{p}->[$n] ) { my $line = ${ $params->{obj}->{headers} }; while (1) { my $body = $params->{obj}->{p}->[$n]->{body}; last unless ( ( length($$body) + length($line) < $self->{s}->{conf}->{bqs} * 1024 ) ); $self->log( 11, "bqs=" . ( $self->{s}->{conf}->{bqs} * 1024 ) . " adding to line [len=" . length($line) . "] mail $params->{obj}->{p}->[$n]->{id}" . " [len=" . length($$body) . "], total len=" . ( length($$body) + length($line) ) ); $line .= "\r\n" . $$body; $n++; last unless $params->{obj}->{p}->[$n]; } push @dudes, $line; } my @queries; foreach (@dudes) { push @queries, { a => $params->{action} eq 'report' ? 'r' : 'revoke', message => $_, }; } return @queries; } elsif ( $params->{action} =~ /revoke/ ) { # Never send messages on revoke. Revoke all signature # that we were able to compute. my $n = 0; my @queries; while ( $params->{obj}->{p}->[$n] ) { for my $engine ( keys %{ $self->{s}->{engines} } ) { my $sigs; if ( $sigs = $params->{obj}->{p}->[$n]->{"e$engine"} ) { if ( ref $sigs eq 'ARRAY' ) { for my $sig (@$sigs) { push @queries, { a => 'revoke', e => $engine, s => $sig }; } } else { push @queries, { a => 'revoke', e => $engine, s => $sigs }; } } } $n++; } return @queries; } } # # prepare queries in correct syntax for sending over network # sub obj2queries { my ( $self, $objects, $action ) = @_; my @queries = (); foreach my $obj (@$objects) { next if $obj->{skipme}; push @queries, $obj->{e1}->{sent} if $obj->{e1}->{sent}; foreach my $objp ( @{ $obj->{p} } ) { next if $objp->{skipme}; #$self->log(8,"not skipping mail part $objp->{id}, sent: ". scalar(@{$objp->{sent}})); push @queries, @{ $objp->{sent} } if $objp->{sent}; } } if ( scalar(@queries) ) { $self->log( 8, "preparing " . scalar(@queries) . " queries" ); } else { $self->log( 8, "objects yielded no valid queries" ); return []; } my $qbatched = to_batched_query( \@queries, $self->{s}->{conf}->{bql}, $self->{s}->{conf}->{bqs}, 1 ); $self->log( 8, "sending " . scalar(@$qbatched) . " batches" ); return $qbatched; } # # Parse response syntax, add info to appropriate object # sub queries2obj { my ( $self, $objs, $responses, $action ) = @_; my @resp; foreach (@$responses) { # from_batched_query wants "-" in beginning, but not ".\r\n" at end s/\.\r\n$//sg; my $arrayref = from_batched_query($_); push @resp, @$arrayref; } $self->log( 12, "processing " . scalar(@resp) . " responses" ); $self->logobj( 14, "from_batched_query", \@resp ); my $j = 0; while (@resp) { my $obj = $objs->[ $j++ ]; return $self->error("more responses than mail objs!") unless $obj; next if $obj->{skipme}; if ( $obj->{e1}->{sent} && !$obj->{e1}->{skipme} ) { $obj->{e1}->{resp} = shift @resp; $self->log( 12, "adding a resp to mail $obj->{e1}->{id}" ); } foreach my $objp ( @{ $obj->{p} } ) { next unless $objp->{sent}; # for each part, shift out as many responses as there were queries foreach ( @{ $objp->{sent} } ) { push @{ $objp->{resp} }, shift @resp; $self->log( 12, "adding a resp to mail $objp->{id}" ); } } #$self->logobj(13,"end of queries2obj",$obj); } return 1; } sub check_resp { my ( $self, $me, $sent, $resp, $objp ) = @_; # default is no contention $objp->{ct} = 0; $objp->{ct} = $resp->{ct} if exists $resp->{ct}; if ( exists $resp->{err} ) { $self->logobj( 4, "$me: got err $resp->{err} for query", $sent ); return 0; } if ( $resp->{p} eq '1' ) { if ( exists $resp->{cf} ) { if ( $resp->{cf} < $self->{s}->{min_cf} ) { $self->log( 6, "$me: Not spam: cf $resp->{cf} < min_cf $self->{s}->{min_cf}" ); return 0; } else { $self->log( 6, "$me: Is spam: cf $resp->{cf} >= min_cf $self->{s}->{min_cf}" ); return 1; } } $self->log( 6, "$me: sig found, no cf, ok." ); return 1; } if ( $resp->{p} eq '0' ) { $self->log( 6, "$me: sig not found." ); return 0; } # should never get here $self->logobj( 2, "$me: got bad response from server - sent obj, resp obj", [ $sent, $resp ] ); return 0; } sub rcheck_resp { my ( $self, $me, $sent, $resp ) = @_; $self->log( 8, "$me: invalid $sent" ) unless ref($sent); $self->log( 8, "$me: invalid $resp" ) unless ref($resp); if ( exists $resp->{err} ) { if ( $resp->{err} eq '230' ) { $self->log( 8, "$me: err 230 - server wants mail" ); return 1; } $self->logobj( 4, "$me: got err $resp->{err} for query", $sent ); return 0; } if ( $resp->{res} eq '1' ) { $self->log( 5, "$me: Server accepted report." ); return 0; } if ( $resp->{res} eq '0' ) { $self->log( 1, "$me: Server did not accept report. Shame on the server." ); return 0; } # should never get here $self->logobj( 2, "$me: got bad response from server - sent obj, resp obj", [ $sent, $resp ] ); return 0; } sub check { my ( $self, $objects ) = @_; my $valid = 0; foreach my $obj (@$objects) { next if $obj->{skipme}; # # Logic used in ordering of check queries # # queries should go like this: (e=engine, p=part) # e1, p0e2, p0e3, p0e4, p1e2, p1e3, p1e4, etc.. # unless cmd-line sigs are passed. # # engine 1 is for entire mail, not parts if ( $obj->{e1} # cmd-line sig checks don't have this && $self->{s}->{engines}->{1} ) { $obj->{e1}->{sent} = $self->make_query( { action => 'check', sig => $obj->{e1}->{e1}, eng => 1 } ); } # rest of engines and mime parts foreach my $objp ( @{ $obj->{p} } ) { if ( $objp->{skipme} ) { $self->log( 8, "mail $objp->{id} skipped in check" ); next; } $objp->{sent} = []; foreach ( sort keys %{ $self->{s}->{engines} } ) { my $engine_save = $_; next if $_ eq 1; # engine 1 done above my $sig = $objp->{"e$_"}; unless ($sig) { $self->log( 5, "mail $objp->{id} e$_ got no sig" ); next; } unless ( $self->{s}->{engines}->{$_} ) { # warn if cmd-lig sig check is not supported $self->log( 5, "mail $objp->{id} engine $_ is not supported, sig check skipped" ) if ( $sig && !$obj->{orig_mail} ); next; } if ( ref $sig ) { for (@$sig) { $self->log( 8, "mail $objp->{id} e$engine_save sig: $_" ); } } else { $self->log( 8, "mail $objp->{id} e$engine_save sig: $sig" ); } my $query = $self->make_query( { action => 'check', sig => $sig, ep4 => $obj->{ep4}, eng => $_ } ); $valid++ if $query; if ( ref $query eq 'ARRAY' ) { push @{ $objp->{sent} }, @$query; } else { push @{ $objp->{sent} }, $query; } } } } unless ($valid) { $self->log( 5, "No queries, no spam" ); return 1; } $self->{s}->{list} = $self->{s}->{catalogue}; $self->connect; # Build query text strings # my $queries = $self->obj2queries( $objects, 'check' ) or return $self->errprefix("check 1"); # send to server and store answers in mail obj # my $response = $self->_send($queries) or return $self->errprefix("check 2"); $self->queries2obj( $objects, $response, 'check' ) or return $self->errprefix("check 3"); foreach my $obj (@$objects) { # check_logic will parse response for each object, decide if its spam # $self->check_logic($obj); $self->log( 3, "mail $obj->{id} is " . ( $obj->{spam} ? '' : 'not ' ) . "known spam." ); } return 1; } sub check_logic { my ( $self, $obj ) = @_; # default is not spam $obj->{spam} = 0; if ( $obj->{skipme} ) { next; } # # Logic for Spam # # my $logic_method = $self->{conf}->{logic_method} || 4; my $logic_engines = $self->{conf}->{logic_engines} || 'any'; # cmd-line sig checks default to logic_method 1 $logic_method = 1 unless $obj->{orig_mail}; my $leng; if ( $logic_engines eq 'any' ) { $leng = ""; # not a hash ref, implies 'any' logic_engine } elsif ( $logic_engines eq 'all' ) { $leng = $self->{s}->{engines}; } elsif ( $logic_engines =~ /^(\d\,)+$/ ) { $leng = {}; foreach ( split /,/, $logic_engines ) { unless ( $self->{s}->{engines}->{$_} ) { $self->log( 3, "logic_engine $_ not supported, skipping" ); next; } $leng->{$_} = 1; } } else { $self->log( 3, "invalid logic_engines: $logic_engines, defaulting to 'any'" ); $leng = ""; # not a hash ref, implies 'any' logic_engine } # iterate through sent queries and responses, # perform engine analysis (logic_engines). # # engine 1 case my $sent = $obj->{e1}->{sent}; my $resp = $obj->{e1}->{resp}; if ( $resp && $sent ) { # if skipme, there would be no resp my $logmsg = "mail $obj->{id} e=1 sig=$sent->{s}"; $obj->{e1}->{spam} = $self->check_resp( $logmsg, $sent, $resp, $obj->{e1} ); } # all other engines for all parts foreach my $objp ( @{ $obj->{p} } ) { $objp->{spam} = 0; if ( $objp->{skipme} ) { $self->log( 8, "doh. $objp->{id} is skipped, yet has sent" ) if $objp->{sent}; next; } next unless $objp->{sent}; my $not_spam = 0; foreach ( 0 .. ( scalar( @{ $objp->{sent} } ) - 1 ) ) { $sent = $objp->{sent}->[$_]; $resp = $objp->{resp}->[$_]; unless ($resp) { $self->log( 5, "doh. more sent queries than responses" ); next; } my $logmsg = "mail $objp->{id} e=$sent->{e} sig=$sent->{s}"; my $is_spam = $self->check_resp( $logmsg, $sent, $resp, $objp ); if ( ref($leng) ) { if ( $leng->{ $sent->{e} } && $is_spam ) { $self->log( 8, "logic_engines requires $sent->{e}, and it is. cool." ); $objp->{spam} = 1; } elsif ( $leng->{ $sent->{e} } && !$is_spam ) { $self->log( 8, "logic_engines requires $sent->{e}, and it is not, part not spam" ); $not_spam = 1; } else { $self->log( 8, "logic_engines doesn't care about $sent->{e}, skipping" ); } } else { # not a hash ref, implies 'any' logic_engine $objp->{spam} += $is_spam; } $objp->{spam} = 0 if $not_spam; } } # mime part analysis (logic_methods) # if ( $logic_method == 1 ) { $obj->{spam} = 0; if ( $obj->{e1} ) { $obj->{spam} += $obj->{e1}->{spam} if $obj->{e1}->{spam}; } foreach my $objp ( @{ $obj->{p} } ) { $obj->{spam} += $objp->{spam} if $objp->{spam}; } } elsif ( $logic_method =~ /^(2|3)$/ ) { # logic_methods > 1 foreach my $objp ( @{ $obj->{p} } ) { next if $objp->{skipme}; next unless $objp->{body}; my ( $hdrs, $body ) = split /\n\n/, ${ $objp->{body} }, 2; $hdrs .= "\n"; #$self->log(8,"$objp->{id} hdrs:\n$hdrs"); my $type = "<type unknown>"; $objp->{is_text} = 0; $objp->{is_inline} = 0; $objp->{is_inline} = 1 if $hdrs =~ /Content-Disposition: inline/i; #$type = $1 if $hdrs =~ /Content-Type:\s([^\;\n]+)/i; $type = $1 if $hdrs =~ /Content-Type:\s([^\n]+)/i; $objp->{is_text} = 1 if $type =~ /text\//i; $objp->{is_text} = 1 if $type =~ /type unknown/; # assume text ? $self->log( 8, "mail $objp->{id} Type $objp->{is_text},$objp->{is_inline} $type" ); } } if ( $logic_method == 2 ) { # in this method, only 1 dude decides if mail is spam. decider. # the first part is the default decider. can be overwritten, tho. my $decider = $obj->{p}->[0]; # basically the first inline text/* becomes the decider. # however, if no inline, the first text/* is used my $found = 0; foreach my $objp ( @{ $obj->{p} } ) { next if $objp->{skipme}; if ( $objp->{is_inline} && $objp->{is_text} ) { $decider = $objp; last; } if ( !$found && $objp->{is_text} ) { $decider = $objp; $found = 1; } } $self->log( 7, "method 2: $decider->{id} is the spam decider" ); $obj->{spam} = $decider->{spam}; } elsif ( $logic_method == 3 ) { # in this method, all text/* parts must be spam for obj to be spam # non-text parts are ignored my $found = 0; foreach my $objp ( @{ $obj->{p} } ) { next if $objp->{skipme}; next unless $objp->{is_text}; $found = 1; $obj->{spam} = $objp->{spam}; unless ( $objp->{spam} ) { $self->log( 7, "method 3: $objp->{id} is_text but not spam, mail not spam" ); last; } } $self->log( 7, "method 3: mail $obj->{id}: all is_text parts spam, mail spam" ) if $obj->{spam}; # if no parts where text, use the first part as spam indicator unless ($found) { $self->log( 6, "method 3: mail $obj->{id}: no is_text, using part 1" ); $obj->{spam} = 1 if $obj->{p}->[0]->{spam}; } } elsif ( $logic_method == 4 ) { # in this method, if any non-contention parts is spam, mail obj is spam # contention parts are ignored. $obj->{spam} = 0; foreach my $objp ( @{ $obj->{p} } ) { next if $objp->{skipme}; if ( $objp->{ct} ) { $self->log( 7, "method 4: mail $objp->{id}: contention part, skipping" ); } else { $self->log( 7, "method 4: mail $objp->{id}: no-contention part, spam=$objp->{spam}" ); $obj->{spam} = 1 if $objp->{spam}; } } if ( $obj->{spam} ) { $self->log( 7, "method 4: mail $obj->{id}: a non-contention part was spam, mail spam" ); } else { $self->log( 7, "method 4: mail $obj->{id}: all non-contention parts not spam, mail not spam" ); } } elsif ( $logic_method == 5 ) { # in this method, all non-contention parts must be spam for obj to be spam # contention parts are ignored. my $not_spam = 0; my $is_spam = 0; foreach my $objp ( @{ $obj->{p} } ) { next if $objp->{skipme}; if ( $objp->{ct} ) { $self->log( 7, "method 5: mail $objp->{id}: contention part, skipping" ); next; } else { $self->log( 7, "method 5: mail $objp->{id}: no-contention part, spam=$objp->{spam}" ); } if ( $objp->{spam} ) { $is_spam = 1; } else { $not_spam = 1; } } if ( $is_spam && !$not_spam ) { $obj->{spam} = 1; $self->log( 7, "method 5: mail $obj->{id}: all non-contention parts spam, mail spam" ); } else { $self->log( 7, "method 5: mail $obj->{id}: a non-contention part not spam, mail not spam" ); $obj->{spam} = 0; } } return 1; } # returns hash ref if successfully registered # returns 0 if not sub register { my ( $self, $p, ) = @_; my @queries; my $registrar = $self->{name_version}; my %qr = ( a => 'reg', registrar => $registrar ); $qr{user} = $p->{user} if $p->{user}; $qr{pass} = $p->{pass} if $p->{pass}; $queries[0] = makesis(%qr); $self->{s}->{list} = $self->{s}->{nomination}; $self->connect; my $response = $self->_send( \@queries ) or return $self->errprefix("register"); my %resp = parsesis( $$response[0] ); if ( $resp{err} && $resp{err} eq '210' ) { if ( $qr{user} && $qr{pass} ) { my ($creds) = { user => $qr{user}, pass => $qr{pass} }; if ( $self->authenticate($creds) ) { $self->log( 6, "Successfully registered provided credentials.\n" ); return $creds; } } return $self->error("Error $resp{err}: User exists. Try another name. aborting.\n"); } return $self->error("Error $resp{err} while performing register, aborting.\n") if ( $resp{err} ); return $self->error("No success (res=$resp{res}) while performing register, aborting.\n") if ( $resp{res} ne '1' ); $self->log( 6, "Successfully registered with $self->{s}->{ip} identity: $resp{user}" ); # otherwise return hash containing 'user' and 'pass' delete $resp{res}; return \%resp; } sub authenticate { my ( $self, $options ) = @_; my @queries; unless ( ( $options->{user} =~ /\S/ ) && ( $options->{pass} =~ /\S/ ) ) { return $self->error("authenticate did not get valid user + pass"); } my %qr = ( a => 'ai', user => $options->{user}, cn => 'razor-agents', cv => $Razor2::Client::Version::VERSION ); $queries[0] = makesis(%qr); $self->{s}->{list} = $self->{s}->{nomination}; $self->connect; my $response = $self->_send( \@queries ) or return $self->errprefix("authenticate 1"); my %resp = parsesis( $$response[0] ); if ( $resp{err} ) { if ( ( $resp{err} eq '213' ) && !defined( $self->{reregistered} ) ) { # 213 = unknown user. # Try to register with current user+pass and continue with authenticate $self->log( 8, "unknown user, attempting to re-register" ); my $id = $self->register($options); $self->{reregistered} = 1; if ( ( $id->{user} eq $options->{user} ) && ( $id->{pass} eq $options->{pass} ) ) { $self->log( 5, "re-registered user $id->{user} with $self->{s}->{ip}" ); return $self->authenticate($options); } else { return $self->error("Error 213 while authenticating, aborting.\n"); } } else { return $self->error("Error $resp{err} while authenticating, aborting.\n"); } } my ( $iv1, $iv2 ) = xor_key( $options->{pass} ); my ($my_digest) = hmac_sha1( $resp{achal}, $iv1, $iv2 ); %qr = ( a => 'auth', aresp => $my_digest ); $queries[0] = makesis(%qr); $response = $self->_send( \@queries ) or return $self->errprefix("authenticate 2"); %resp = parsesis( $$response[0] ); return $self->error("Error $resp{err} while authenticating, aborting.\n") if ( $resp{err} ); return $self->error("Authentication failed for user=$options->{user}") if ( $resp{res} ne '1' ); $self->log( 5, "Authenticated user=$options->{user}" ); $self->{authenticated} = 1; return 1; } # # handles report and revoke # sub report { my ( $self, $objs ) = @_; return $self->error("report: Not Authenticated") unless $self->{authenticated}; return $self->error("report/revoke for engine 1 not supported") if ( $self->{s}->{conf}->{dre} == 1 ); $self->{s}->{list} = $self->{s}->{nomination}; $self->connect; my @robjs; my $valid = 0; if ( $self->{breed} eq 'report' ) { # # Before reporting entire email, check to see if server already has it # unless ( $self->{s}->{conf}->{dre} ) { $self->logobj( 8, "server has no default dre, using 4", $self->{s}->{conf} ); $self->{s}->{conf}->{dre} = 4; } foreach my $obj (@$objs) { next if $obj->{skipme}; # handle special case for engine 1 # note: razor 1 does not store emails in its db, just sigs. # so we should never get a res=230 for e=1 a=r sig=xxx # #$obj->{e1}->{sent} = $self->make_query( { # action => 'rcheck', # sig => $obj->{e1}->{e1}, # eng => 1, } ); #$valid++ if $obj->{e1}->{sent}; # rest of engines and mime parts foreach my $objp ( @{ $obj->{p} } ) { if ( $objp->{skipme} ) { $self->log( 13, "mail $objp->{id} skipped in report" ); next; } my $q = $self->make_query( { action => 'rcheck', sig => $objp->{"e$self->{s}->{conf}->{dre}"}, ep4 => $obj->{ep4}, eng => $self->{s}->{conf}->{dre}, } ); $objp->{sent} = [$q]; $valid++; } } unless ($valid) { $self->log( 5, "No report check queries, no spam" ); return 1; } $valid = 0; # Build query text strings - signatures computed already (see reportit) my $queries = $self->obj2queries( $objs, 'rcheck' ) or return $self->errprefix("report1"); # send to server and store answers in mail obj my $response = $self->_send($queries) or return $self->errprefix("report2"); $self->queries2obj( $objs, $response, 'rcheck' ) or return $self->errprefix("report3"); # # If server wants email or certain body parts, # create new {sent} and add obj to @robjs # foreach my $obj (@$objs) { next if $obj->{skipme}; #$self->log(12,"mail $obj->{id} read ". scalar(@{$obj->{resp}}) ." queries"); # handle engine 1 special case #if ( !$obj->{e1}->{skipme} && $self->rcheck_resp( # "mail ". $obj->{id} .", orig_email, special case eng 1", # $obj->{e1}->{sent}, # $obj->{e1}->{resp} # ) ) { # $self->log(5,"doh. Server should not send res=230 for eng=1 report"); #} #delete $obj->{e1}->{sent}; my $wants_orig_mail = 0; foreach my $objp ( @{ $obj->{p} } ) { next if $objp->{skipme}; $self->logobj( 14, "checking response for $objp->{id}", $objp ); unless ( $self->rcheck_resp( "mail $objp->{id}, eng $self->{s}->{conf}->{dre}", $objp->{sent}->[0], $objp->{resp}->[0] ) ) { $objp->{skipme} = 1; } else { $wants_orig_mail++; } $objp->{resp} = []; # clear responses from rcheck $objp->{sent} = []; } if ($wants_orig_mail) { # reports are special, all parts need to be together, so use part 0's sent my $objp = $obj->{p}->[0]; $objp->{skipme} = 0 if $objp->{skipme}; push @{ $objp->{sent} }, $self->make_query( { action => 'report', obj => $obj, } ); push @robjs, $obj; } $valid += $wants_orig_mail; } } else { # revoke foreach my $obj (@$objs) { # don't revoke eng 1 # engines > 1 we send all the body parts, use part 0 to store sent my $objp = $obj->{p}->[0]; $objp->{sent} = []; push @{ $objp->{sent} }, $self->make_query( { action => 'revoke', obj => $obj, } ); $valid++ if scalar( @{ $objp->{sent} } ); $self->log( 9, "revoke sent:" . scalar( @{ $objp->{sent} } ) ); push @robjs, $obj; } } unless ( $valid && scalar(@robjs) ) { $self->log( 3, "Finished $self->{breed}." ); return 1; } #$self->logobj(14,"report objs", \@robjs); # # send server mails/body parts either # revoked, or requested if reporting # my $queries = $self->obj2queries( \@robjs, $self->{breed} ) or return $self->errprefix("report4"); my $response = $self->_send($queries) or return $self->errprefix("report5"); $self->queries2obj( \@robjs, $response, $self->{breed} ) or return $self->errprefix("report6"); # we just do this to log server's response # foreach my $obj (@robjs) { my $objp = $obj->{p}->[0]; my $cur = -1; while ( $objp->{sent}->[ ++$cur ] ) { $self->rcheck_resp( "$self->{breed}: mail $obj->{id}, $cur", $objp->{sent}->[$cur], $objp->{resp}->[$cur] ) unless ( $objp->{skipme} ); } } $self->logobj( 14, "report objs", \@robjs ); $self->log( 3, "Sent $self->{breed}." ); return 1; } sub _send { my ( $self, $msg, $closesock, $skipread ) = @_; $self->log( 16, "entered _send" ); unless ( $self->{connected_to} ) { $self->connect() or return $self->errprefix("_send"); } my @response; my $select = $self->{select}; my $sock = ( $select->handles )[0]; $self->{sent_cnt} = 0 unless $self->{sent_cnt}; foreach my $i ( 0 .. ( ( scalar @$msg ) - 1 ) ) { my @handles = $select->can_write(15); if ( $handles[0] ) { $self->log( 4, "$self->{connected_to} << " . length( $$msg[$i] ) ); if ( $$msg[$i] =~ /message/ ) { my $line = debugobj( $$msg[$i] ); $self->log( 6, $line ); $self->log2file( 16, \$$msg[$i], "sent_to." . $self->{sent_cnt} ); } else { $self->log( 6, $$msg[$i] ); } local $\; undef $\; print $sock $$msg[$i]; $self->{sent_cnt}++; } else { return $self->error("Timed out (15 sec) while writing to $self->{s}->{ip}"); } next if $skipread; @handles = $select->can_read(15); if ( $sock = $handles[0] ) { local $/; undef $/; $response[$i] = $self->_read($sock) or return $self->error("Error reading socket"); $self->log( 4, "$self->{connected_to} >> " . length( $response[$i] ) ); $self->log( 6, "response to sent.$self->{sent_cnt}\n" . $response[$i] ); } else { return $self->error("Timed out (15 sec) while reading from $self->{s}->{ip}"); } } if ($closesock) { $select->remove($sock); close $sock; } return \@response; } sub _read { my ( $self, $socket ) = @_; my ( $query, $read ); # fixme - need to trim this down (copied from server) # unless ( $read = sysread( $socket, $query, 1024 ) ) { # There was an error on sysread(), could be a real error or a # blocking error. if ( $! == EWOULDBLOCK ) { # write would block, so we try again later $self->debug("_read: EWOULDBLOCK"); return; } elsif ( $! == EINTR or $! == EIO ) { # sysread() got interupted by a signal. # we will process this socket on next wheelwalk. $self->debug("_read: EINTR"); return; } elsif ( $! == EPIPE or $! == EISDIR or $! == EBADF or $! == EINVAL or $! == EFAULT ) { $self->debug("_read: EPIPE"); return; } else { # This happens when client breaks the connection. # Find out why we don't get an EPIPE instead. FIX! $self->debug("_read: connection_closed"); return; } } if ( $read > 0 ) { # Now we are absolutely sure there is data on the socket. return $query; } else { # Otherwise we got an EOF, expire the socket $self->debug("_read: EOF, connection_closed"); return; } } sub connect { my ( $self, %params ) = @_; my $sock; $self->log( 16, "entered connect" ); if ( $self->{simulate} ) { return $self->error("Razor Error 4: This is a simulation. Won't connect to $self->{s}->{ip}."); } my $server = $params{server} || $self->{s}->{ip}; unless ( $self->{s}->{ip} ) { $self->{s}->{ip} = $server; } if ( $self->{sock} && $self->{connected_to} ) { unless ($server) { $self->log( 13, "no server specified, using already connected server $self->{connected_to}" ); return 1; } if ( $server eq $self->{connected_to} ) { $self->log( 15, "already connected to server $self->{connected_to}" ); return 1; } return 1 if $self->{disconnecting}; $self->log( 6, "losing old server connection, $self->{connected_to}, for new server, $server" ); $self->disconnect; } unless ($server) { $self->log( 6, "no server specified, not connecting" ); return; } my $port = $params{port} || $self->{s}->{port}; unless ( defined($port) && $port =~ /^\d+$/ ) { my $portlog = defined($port) ? " ($port)" : ""; $self->log( 6, "No port specified$portlog, using 2703" ); # bootstrap_discovery will come here $port = 2703; } $self->log( 5, "Connecting to $server ..." ); if ( my $proxy = $self->{conf}->{proxy} ) { # # Proxy stuff never been tested # $proxy =~ s!^http://!!; $proxy =~ s!:(\d+)/?$!!; my $pport = $1 || 80; $self->debug("HTTP tunneling through $proxy:$pport."); $sock = IO::Socket::INET->new( PeerAddr => $proxy, PeerPort => $pport, Proto => 'tcp', Timeout => 20, ); unless ($sock) { $self->debug("Unable to connect to proxy $proxy:$pport; Reason: $!."); } else { $sock->printf( "CONNECT %s:%d HTTP/1.0\r\n\r\n", $server, $port ); if ( $sock->getline =~ m!^HTTP/1\.\d+ 200 ! ) { # Skip through remaining part of MIME header. while ( $sock->getline !~ m!^\r! ) { ; } } else { $self->log( 4, "HTTP tunneling is disabled at $proxy." ); $sock = undef; } } } # if proxy, we already might have a $sock. # if proxy failed to connect, try without proxy. # if ( $self->{conf}->{socks_server} ) { my $socks_module = "Net::SOCKS"; eval "require $socks_module"; $self->log( 6, "Will try to connect through the SOCKS server on $$self{conf}{socks_server}..." ); my $socks_sock = Net::SOCKS->new( socks_addr => $$self{conf}{socks_server}, socks_port => 1080, protocol_version => 4 ); if ($socks_sock) { $sock = $socks_sock->connect( peer_addr => $server, peer_port => $port ); if ($sock) { $self->log( 6, "Connected to $server via SOCKS server $$self{conf}{socks_server}." ); } } } unless ($sock) { $sock = IO::Socket::INET->new( PeerAddr => $server, PeerPort => $port, Proto => 'tcp', Timeout => 20, ); unless ($sock) { $self->log( 3, "Unable to connect to $server:$port; Reason: $!." ); return if $params{discovery_server}; $self->nextserver or do { return $self->errprefix("connect1"); }; return $self->connect; } } my $select = new IO::Select($sock); my @handles = $select->can_read(15); if ( $handles[0] ) { $self->log( 8, "Connection established" ); my $greeting = <$sock>; # $sock->autoflush; # this is on by default as of IO::Socket 1.18 $self->{sock} = $sock; $self->{connected_to} = $server; $self->{select} = $select; $self->log( 4, "$server >> " . length($greeting) . " server greeting: $greeting" ); return 1 if $params{discovery_server}; unless ( $self->parse_greeting($greeting) ) { $self->nextserver or return $self->errprefix("connect2"); return $self->connect; } return 1; } else { $self->log( 3, "Timed out (15 sec) while reading from $self->{s}->{ip}." ); $select->remove($sock); $sock->close(); return $self->errprefix("connect3") if $params{skip_greeting}; $self->nextserver or return $self->errprefix("connect4"); return $self->connect; } } sub disconnect { my $self = shift; unless ( $self->{sock} ) { $self->log( 5, "already disconnected from server " . $self->{connected_to} ); return 1; } $self->log( 5, "disconnecting from server " . $self->{connected_to} ); $self->{disconnecting} = 1; $self->_send( ["a=q\r\n"], 0, 1 ); delete $self->{disconnecting}; delete $self->{sock}; # _send closes socket return 1; } sub parse_greeting { my ( $self, $greeting ) = @_; $self->log( 16, "entered parse_greeting($greeting)" ); my %server_greeting = parsesis($greeting); $self->{s}->{greeting} = \%server_greeting; unless ( $self->{s}->{greeting} && $self->{s}->{greeting}->{sn} ) { $self->log( 1, "Couldn't parse server greeting\n" ); return; } # server greeting must contain: sn, srl # server greeting may contain: ep4, redirect, a, # # fixme - add support for redirect, etc. # # # current server config info is stored in $self->{s}->{conf} # see nextserver for more info # # If server greeting says there are new values # (which we know if greeting's srl > conf's srl) # we ask server for new values, update conf, then # put that server on modified list so it gets recorded to disk # # fixme - in the future, we could have a key with no value # in .conf file - forcing client to ask server 'a=g&pm=key' # if ( $self->{s}->{greeting}->{a} eq 'cg' ) { my $version = $Razor2::Client::Version::VERSION; my @cg = ("cn=razor-agents&cv=$version\r\n"); $self->_send( \@cg, 0, 1 ); } if ( defined( $self->{s}->{greeting}->{srl} ) && defined( $self->{s}->{conf}->{srl} ) && $self->{s}->{greeting}->{srl} <= $self->{s}->{conf}->{srl} ) { $self->compute_server_conf; return 1; } # srl > our cached srl, request update (a=g&pm=state) # and rediscover # my @queries = ("a=g&pm=state\r\n"); my $response = $self->_send( \@queries ) or return $self->errprefix("parse_greeting"); # should be just one response # from_batched_query wants "-" in beginning, but not ".\r\n" at end $response->[0] =~ s/\.\r\n$//sg; my $h = from_batched_query( $response->[0], {} ); foreach my $href (@$h) { foreach ( sort keys %$href ) { $self->{s}->{conf}->{$_} = $href->{$_}; #$self->log(8,"updated: $_=$href->{$_}"); } } $self->log( 1, "Bad info while trying to get server state (a=g&pm=state)" ) unless scalar(@$h); $self->{s}->{conf}->{srl} = $self->{s}->{greeting}->{srl}; push @{ $self->{s}->{modified} }, $self->{s}->{ip}; $self->{s}->{allconfs}->{ $self->{s}->{ip} } = $self->{s}->{conf}; # in case new server # now we're up to date $self->log( 5, "Updated to new server state srl " . $self->{s}->{conf}->{srl} . " for server " . $self->{s}->{ip} ); $self->compute_server_conf(); $self->writeservers; # writes to disk servers listed in $self->{s}->{modified} $self->log( 5, "srl was updated, forcing discovery ..." ); $self->{done_discovery} = 0; $self->{force_discovery} = 1; $self->discover(); return 1; } # Returns engines supported # # can be called with no paramaters or # with hash of server supported engines sub compute_supported_engines { my ( $self, $orig ) = @_; my %all; my $se = $self->supported_engines(); # local supported engines foreach ( @{ $self->{conf}->{use_engines} } ) { if ($orig) { $all{$_} = 1 if ( exists $se->{$_} ) && ( exists $orig->{$_} ); } else { $all{$_} = 1 if exists $se->{$_}; } } if ($orig) { $self->log( 8, "Computed supported_engines: " . join( ' ', sort( keys %all ) ) ); } else { $self->log( 8, "Client supported_engines: " . join( ' ', sort( keys %all ) ) ); } return \%all; } # called when we need to parse server conf # - after initial parse_greeting # - if state (srl) changes # - when we switch to cached server conf info in nextserver # sub compute_server_conf { my ( $self, $cached ) = @_; # # compute a confindence (cf) from razor-agent.conf's 'min_cf' # and server's average confidence (ac) # # min_cf can be 'n', 'ac', 'ac + n', or 'ac - n' # where 'n' can be 1..100 # my $cf = $self->{s}->{conf}->{ac}; # default is server's ac my $min_cf = $self->{conf}->{min_cf}; $min_cf =~ s/\s//g; if ( $min_cf =~ /^ac\+(\d+)$/ ) { $cf = $self->{s}->{conf}->{ac} + $1; } elsif ( $min_cf =~ /^ac-(\d+)$/ ) { $cf = $self->{s}->{conf}->{ac} - $1; } elsif ( $min_cf =~ /^ac$/ ) { $cf = $self->{s}->{conf}->{ac}; } elsif ( $min_cf =~ /^(\d+)$/ ) { $cf = $min_cf; } else { $self->log( 5, "Invalid min_cf $self->{conf}->{min_cf}" ); } $cf = 100 if $cf > 100; $cf = 0 if $cf < 0; $self->{s}->{min_cf} = $cf; # # ep4 - special for vr4 # $self->{s}->{conf}->{ep4} = $self->{s}->{greeting}->{ep4} if $self->{s}->{greeting}->{ep4}; my $info = $cached ? $self->{s}->{conf} : $self->{s}->{greeting}; my $name = "Unknown-Type: "; if ( $info->{sn} ) { $name .= $info->{sn}; $name = "Nomination" if $info->{sn} =~ /N/; $name = "Catalogue" if $info->{sn} =~ /C/; $name = "Catalogue" if $info->{sn} =~ /S/; $name = "Discovery" if $info->{sn} =~ /D/; } $self->log( 6, $self->{s}->{ip} . " is a $name Server srl " . $self->{s}->{conf}->{srl} . "; computed min_cf=$cf, Server se: $self->{s}->{conf}->{se}" ); # # Supported Engines - greeting contains hex of bits # we turn into a hash so we can just quickly do # do_eng3_stuff if $self->{s}->{engines}->{3}; # # if we're just computing hashes locally, ignore what engines server currently supports # fixme - this prolly should be done somewhere else if ( $self->{opt}->{printhash} ) { $self->log( 6, "Ignore what engines server supports for -H" ); $self->{s}->{engines} = $self->compute_supported_engines(); } else { my $se = hexbits2hash( $self->{s}->{conf}->{se} ); $self->{s}->{engines} = $self->compute_supported_engines($se); } } # sub log2file moved to Agent.pm sub debug { my ( $self, $message ) = @_; $self->log( 5, $message ); } sub DESTROY { my $self = shift; #$self->debug ("Agent terminated"); } sub zonename { my ( $zone, $type ) = @_; my ( $sub, $dom ) = split /\./, $zone, 2; return "$sub-$type.$dom"; } 1;