EVOLUTION-MANAGER
Edit File: Agent.pm
#!/usr/bin/perl -sw ## ## Razor2::Client::Agent -- UI routines for razor agents. ## ## Copyright (c) 2002, 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: Agent.pm,v 1.98 2006/10/18 06:15:08 rsoderberg Exp $ package Razor2::Client::Agent; use lib qw(lib); use strict; use Getopt::Long; use IO::File; use Razor2::String qw(fisher_yates_shuffle); use base qw(Razor2::Client::Core); use base qw(Razor2::Client::Config); use base qw(Razor2::Logger); use base qw(Razor2::String); use Razor2::Preproc::Manager; use Data::Dumper; use vars qw( $VERSION $PROTOCOL ); $PROTOCOL = $Razor2::Client::Version::PROTOCOL; $VERSION = $Razor2::Client::Version::VERSION; sub new { my ($class, $breed) = @_; # For Taint Friendliness delete $ENV{PATH}; delete $ENV{BASH_ENV}; my @valid_program_names = qw( razor-check razor-report razor-revoke razor-admin ); my $ok = 0; foreach (@valid_program_names) { $breed =~ /$_$/ and $ok = $_; } unless ($ok) { if ($breed =~ /razor-client$/) { # We no longer create symlinks, but for backwards compatibility # return success. exit 0; } die "Invalid program name, must be one of: @valid_program_names\n"; } $ok =~ /razor-(.*)$/; my %me = ( name_version => "Razor-Agents v$VERSION", # used in register breed => $1, preproc => new Razor2::Preproc::Manager (no_deHTMLcomment => 1), preproc_vr8 => new Razor2::Preproc::Manager (no_deHTML => 1), global_razorhome => '/etc/razor', ); return bless \%me, $class; } sub do_conf { my $self = shift; # parse config-related cmd-line args # # identity is parsed later after razorhome is fully resolved if ($self->{opt}->{config}) { if ($self->{opt}->{create_conf}) { $self->{razorconf} = $self->{opt}->{config}; } elsif (-r $self->{opt}->{config}) { $self->{razorconf} = $self->{opt}->{config}; } else { return $self->error("Can't read conf file: $self->{opt}->{config}") } } if ($self->{opt}->{razorhome}) { if (-d $self->{opt}->{razorhome}) { $self->{razorhome} = $self->{opt}->{razorhome}; } else { return $self->error("Can't read: $self->{opt}->{razorhome}") unless $self->{opt}->{create_conf}; } # once razorhome is successfully overridden, override the global razorhome as well. $self->{global_razorhome} = $self->{razorhome}; } return unless $self->read_conf(); if ($self->{opt}->{create_conf}) { $self->{force_discovery} = 1; $self->{force_bootstrap_discovery} = 1; $self->log(8," -create will force complete discovery"); } if ($self->{opt}->{force_discovery}) { $self->{force_discovery} = 1; $self->{force_bootstrap_discovery} = 1; $self->log(8," -discover will force complete discovery"); } if ($self->{opt}->{debug} && !$self->{opt}->{debuglevel}) { $self->{conf}->{debuglevel} ||= 9; $self->{conf}->{debuglevel} = 9 if $self->{conf}->{debuglevel} < 9; } # # Note: we start logging before we process '-create' , # so logfile will not go into a newly created razorhome # #my $logto = $self->{opt}->{debug} ? "stdout" : "file:$self->{conf}->{logfile}"; my $logto; if ($self->{opt}->{debug}) { $logto = 'stdout'; } elsif ($self->{conf}->{logfile} eq 'syslog') { $logto = 'syslog'; } elsif ($self->{conf}->{logfile} eq 'sys-syslog') { $logto = 'sys-syslog'; } else { $logto = "file:$self->{conf}->{logfile}"; } if (exists $self->{conf}->{logfile}) { my $debuglevel = exists $self->{conf}->{debuglevel} ? $self->{conf}->{debuglevel} : 9; my $logger = new Razor2::Logger ( LogDebugLevel => $debuglevel, LogTo => $logto, LogPrefix => $self->{breed}, LogTimestamp => 1, DontDie => 1, Log2FileDir => defined($self->{conf}->{tmp_dir}) ? $self->{conf}->{tmp_dir} : "/tmp", ); $self->{logref} = ref($logger) ? $logger : 0; # log error strings at loglevel 11. Pick a high number 'cuz # if its really an error, it will be in errstr for caller $self->{logerrors} = 11; } $self->logobj(15,"cmd-line options", $self->{opt}); $self->{preproc}->{rm}->{log} = $self->{logref}; # creates razorhome, and sets $self->{razorhome} if successful return $self->errprefix("Could not create 'razorhome'") unless $self->create_home_conf(); $self->compute_identity; $self->log(5,"computed razorhome=$self->{razorhome}, conf=$self->{razorconf}, ident=$self->{identity}"); return 1; } # if a debug log statement requires extra work, check this call before doing it. sub logll { my ($self, $loglevel) = @_; return unless $self->{logref}; return 1 if ($self->{logref}->{LogDebugLevel} >= $loglevel); return; } sub create_home_conf { my $self = shift; unless ($self->{opt}->{create_conf}) { # # if the global razorhome exists, don't create anything # without '-create' option # return 1 if (-d $self->{global_razorhome}); # # if there is not global razorhome, # try to create razorhome one anyway. # if it fails, thats ok. # $self->create_home($self->{razorhome_computed}); $self->errstrrst; # nuke error string return 1; } # # user passed in 'create' option, so create. # my $rhome = $self->{opt}->{razorhome} ? $self->{opt}->{razorhome} : $self->{razorhome_computed}; if ($rhome) { if (-d $rhome) { $self->log(6,"Not creating razorhome $rhome, already exists"); } else { return unless $self->create_home($rhome); } } if ($self->{opt}->{config}) { # if create and conf specified, exit if write is not successful # $self->{razorconf} = $self->{opt}->{config}; return $self->write_conf(); } else { # else just try and create, if fail ok. # $self->compute_razorconf(); $self->{razorconf} ||= $self->{computed_razorconf}; $self->write_conf(); $self->errstrrst; # nuke error string } return 1; } # wrapper for log sub log { my $self = shift; my $level = shift; my $msg = shift; if ($self->{logref}) { return $self->{logref}->log($level, $msg); } elsif ($self->{opt}->{debug}) { print " Razor-Log: $msg\n" if $self->{opt}->{debug}; } } sub log2file { my $self = shift; return unless $self->{logref}; return $self->{logref}->log2file(@_); } sub doit { my $self = shift; my $args = shift; my $r; $self->log(2," $self->{name_version} starting razor-$self->{breed} $self->{args}"); # $self->log(9,"uname -a: ". `uname -a`) if $self->logll(9); $r = $self->checkit($args) if $self->{breed} eq 'check'; $r = $self->adminit($args) if $self->{breed} eq 'admin'; $r = $self->reportit($args) if $self->{breed} eq 'report'; $r = $self->reportit($args) if $self->{breed} eq 'revoke'; # return exit code # 0, 1 => ok # > 1 => error (caller should prolly print $self->errstr) # if ($r > 1) { my $msg = $self->errstr; $self->log(1,"razor-$self->{breed} error: ". $msg); } else { $self->log(8,"razor-$self->{breed} finished successfully."); } return $r; } sub _help { my ($self,$breed) = @_; chomp(my $all = <<EOFALL); -h Print this usage message. -v Print version number and exit -d Turn on debugging. Logs to stdout. -s Simulate Only. Does not connect to server. -conf=file Use this config file instead of <razorhome>/razor.conf -home=dir Use this as razorhome -ident=file Use this identity file instead of <razorhome>/identity -rs Use this razor server instead of reading .lst EOFALL chomp(my $sigs = <<EOFSIGS); -H Compute and print signature. -S | --sig Accept a signatures to check on the command line -e eng Engine used to compute sig, integer -ep4 val String value required when engine == 4 EOFSIGS chomp(my $mbox = <<EOFMBOX); -M | --mbox Accept a mailbox name on the command line (default) If no filename, mbox, or signatures, input read from stdin. EOFMBOX my %b; $b{check} = <<EOFCHECK; razor-check [options] [ filename | -M mbox | -S signatures | < filename ] $all $sigs $mbox See razor-check(1) manpage for details. EOFCHECK $b{report} = <<EOFREPORT; razor-report [options] [ filename | -M mbox | -S signatures -e engine] $all $sigs $mbox -i file Use identity from this file -f Stay in foreground. -a Authenticate only. Exit 0 if authenticated, 1 if not Stays in foreground. See razor-report(1) manpage for details. EOFREPORT $b{admin} = <<EOFREGISTER; razor-admin [options] [ -register | -create | -discover ] $all -create Create razorhome, does discover, does not register -discover Discover Razor servers: write .lst files -register Register a new identity -user name Request 'name' when registering (requires -register) -pass pass Request 'password' when registering (requires -register) -l Make new identity the the default identity. Used only when registering. See razor-admin(1) manpage for details. EOFREGISTER $b{revoke} = <<EOFREVOKE; razor-revoke [options] filename $all $mbox -i file Use identity from this file -f Stay in foreground. -a Authenticate only. exit 0 if authenticated, 1 if not Stays in foreground. See razor-revoke(1) manpage for details. EOFREVOKE my $future = <<EOFFUTURE; EOFFUTURE return $b{$self->{breed}}; } # maybe this should be in Client::Config # sub read_options { my ($self, $agent) = @_; $self->{args} = join ' ', @ARGV; Getopt::Long::Configure ("no_ignore_case"); my %opt; # # These options override what is loaded in config file # the names on the right should match keys in config file # my $ret = GetOptions( 's' => \$opt{simulate}, 'd' => \$opt{debug}, 'verbose' => \$opt{debug}, 'v' => \$opt{version}, 'h' => \$opt{usage}, 'help' => \$opt{usage}, 'H' => \$opt{printhash}, 'C=s' => \$opt{printcleaned}, 'sig=s' => \$opt{sig}, 'S=s' => \$opt{sig}, 'e=s' => \$opt{sigengine}, 'ep4=s' => \$opt{sigep4}, 'mbox' => \$opt{mbox}, 'M' => \$opt{mbox}, 'n' => \$opt{negative}, 'conf=s' => \$opt{config}, 'config=s' => \$opt{config}, 'home=s' => \$opt{razorhome}, 'f' => \$opt{foreground}, 'noml' => \$opt{noml}, 'user=s' => \$opt{user}, 'u=s' => \$opt{user}, 'pass=s' => \$opt{pass}, 'a' => \$opt{authen_only}, 'rs=s' => \$opt{server}, 'server=s' => \$opt{server}, 'r' => \$opt{register}, 'register' => \$opt{register}, 'l' => \$opt{symlink}, 'i=s' => \$opt{identity}, 'ident=s' => \$opt{identity}, 'create' => \$opt{create_conf}, 'logfile=s' => \$opt{logfile}, 'discover' => \$opt{force_discovery}, 'dl=s' => \$opt{debuglevel}, 'debuglevel=s' => \$opt{debuglevel}, 'whitelist=s' => \$opt{whitelist}, 'lm=s' => \$opt{logic_method}, 'le=s' => \$opt{logic_engines}, ); if ($ret == 0) { $self->error("failed to parse command line options.\n"); return; } # remove elements not set in the cmd-line foreach (keys %opt) { delete $opt{$_} unless defined $opt{$_}; } if ($opt{usage}) { $self->error($self->_help); return; } elsif ($opt{mbox} && $opt{sig}) { $self->error("--mbox and --sig are mutually exclusive.\n"); return; } elsif ($opt{sig} && !$opt{sigengine}) { $self->error("--sig requires -e (engine used to generate sig)\n"); return; # # fixme - require ep4 if -e 4 is used ? # } elsif ($opt{version}) { $self->error("Razor Agents $VERSION, protocol version $PROTOCOL"); return; } $self->{opt} = \%opt; return 1; } # returns 0 if match (spam) # returns 1 if no match (legit) # returns 2 if error sub checkit { my $self = shift; my $args = shift; # check for spam. # input can be one of # file - single mail # mbox - many mail # sig - 1 or more sigs # or a filehandle provided via args my $objects; if ($self->{conf}->{sig}) { my @sigs; # # cmd-line sigs # # prepare 1 mail object per sig # foreach my $sig (split ',', $self->{conf}->{sig}) { $sig =~ s/^\s*//; $sig =~ s/\s*$//; my $hr = { eng => $self->{conf}->{sigengine}, sig => $sig, }; $hr->{ep4} = "7542-10"; $hr->{ep4} = $self->{conf}->{sigep4} if $self->{conf}->{sigep4}; push @sigs, $hr; } $self->log (5,"received ". (scalar @sigs) ." valid cmd-line sigs."); $objects = $self->prepare_objects(\@sigs) or return 2; } else { my $mails = $self->parse_mbox($args) or return 2; $objects = $self->prepare_objects($mails) or return 2; # # if mail is whitelisted, its not spam. # flag it so it we don't check it against server # foreach my $obj (@$objects) { if ($self->local_check($obj)) { $obj->{skipme} = 1; $obj->{spam} = 0; } else { next; } } } # compute_sigs needs server info like ep4, so get_server_info first $self->get_server_info() or return 2; my $printable_sigs = $self->compute_sigs($objects) or return 2; if ($self->{opt}->{printhash}) { my $i = 0; foreach (@$printable_sigs) { if ($self->{opt}->{sigengine}) { next unless (/ e$self->{opt}->{sigengine}: /); } print "$_\n"; $i++; } $self->log (4, "Done. Printed $i sig(s) for ". scalar(@$objects) ." mail(s)"); } if ($self->{opt}->{printcleaned}) { my $totalp = 0; my $totalc = 0; foreach my $obj (@$objects) { my $n = 0; mkdir("$self->{opt}->{printcleaned}/cleaned"); foreach ($obj->{headers}, @{$obj->{bodyparts_cleaned}}) { my $fn = "$self->{opt}->{printcleaned}/cleaned/mail$obj->{id}.". $n++; $self->write_file($fn, $_); $totalc++; } $n = 0; mkdir("$self->{opt}->{printcleaned}/uncleaned"); foreach ($obj->{headers}, @{$obj->{bodyparts}}) { my $fn = "$self->{opt}->{printcleaned}/uncleaned/mail$obj->{id}.". $n++; $self->write_file($fn, $_); $totalp++; } } $self->log (4, "Done. $totalp uncleaned, $totalc cleaned mails saved in $self->{opt}->{printcleaned}"); print "Done. $totalp uncleaned, $totalc cleaned mails saved in $self->{opt}->{printcleaned}\n"; return 1; } return 1 if $self->{opt}->{printhash}; # only check good objects my @goodones; # this should be optimized! foreach my $obj (@$objects) { next if $obj->{skipme}; push @goodones, $obj; } unless (scalar @goodones) { $self->log (4,"Done. No valid mail or signatures to check."); return 1; } if ($self->{conf}->{simulate}) { $self->log (4, "Done. (simulate only)"); return 1; } # # Connect to catalogue server # $self->{s}->{list} = $self->{s}->{catalogue}; $self->nextserver(); $self->connect() or return 2; # # Check against server # $self->check (\@goodones) or return 2; $self->disconnect() or return 2; # # print out responses and exit # my $only1check = (scalar(@$objects) == 1) ? 1 : 0; my $has_spam = 0; foreach my $obj (@$objects) { $obj->{spam} = 0 if $obj->{skipme}; $obj->{spam} = 0 unless defined $obj->{spam}; if ($obj->{spam} > 0) { return 0 if $only1check; $has_spam = 1; print $obj->{id} ."\n"; next; } elsif ($obj->{spam} == 0) { return 1 if $only1check; print "-". $obj->{id} ."\n" if $self->{conf}->{negative}; next; } else { # error # $self->logobj(1,"bad 'spam' in checkit", $obj); return 2 if $only1check; print "-". $obj->{id} ."\n" if $self->{conf}->{negative}; next; } } return 0 if $has_spam; return 1; } # returns 0 if success # returns 2 if error sub adminit { my $self = shift; my $done_something = 0; if ($self->{opt}->{create_conf}) { $done_something++; # $self->create_home_conf() is always checked } if ( $self->{opt}->{force_discovery} || $self->{opt}->{create_conf}) { $done_something++; # get_server_info() calls nextserver() which calls discovery() $self->get_server_info() or return 2; } if ($self->{opt}->{register}) { $done_something++; my $r = $self->registerit(); return $r if $r; } unless ($done_something) { $self->error("An option needs to be specified, -h for help."); return 2; } return 0; } # returns 0 if success # returns 2 if error sub registerit { my($self, $auto) = @_; unless ($self->{razorhome} || $self->{opt}->{identity}) { $self->errprefix("Unable to register without a valid razorhome or identity"); return 2; } my $ident; if (exists $self->{opt}->{user} && ($ident = $self->get_ident) && $ident->{user} eq $self->{opt}->{user} ) { $self->error("You are already registered as user=$ident->{user} in $self->{razorhome}"); return 2; } if ($self->{conf}->{simulate}) { $self->log(5,"Done - simulate only."); return 0; } if ($self->{opt}->{create_conf}) { $self->log(3, "Register create successful."); return 0; } if ($auto) { $self->log(3, "Write test underway"); my($ident) = { user => 'writetest', pass => 'writetest', }; my($fn); unless ($fn = $self->save_ident($ident)) { $self->log(3, "Unable to write identity to home"); return 2; } unlink($fn) or return 2; $self->log(3, "Write test completed"); } $self->get_server_info() or return 2; $self->connect() or return 2; $self->log(3, "Attempting to register."); # attempt to register the user/pass $ident = $self->register_identity($self->{opt}->{user}, $self->{opt}->{pass}); $self->disconnect() or return 2; unless (ref $ident) { $self->log(3, "Failed to register identity."); return 2; } if (my $fn = $self->save_ident($ident)) { my $msg = "Register successful. Identity stored in $fn"; $self->log(3, $msg); print "$msg\n"; return 0; } else { $self->log(3, "Register failed."); return 2; } } # # handles report and revoke # # returns 0 if success # returns 2 if error sub reportit { my ($self, $args) = @_; my $ident = $self->get_ident; unless ($ident) { $self->log(3, "Razor2 identity not found. Attempting to register automatically."); if ($self->registerit("auto")) { $self->log(3, "Automatic registration failed."); $self->errprefix("Bootstrap Error: Your Razor2 identity was not found.\n " . " If you haven't registered, please do so:\n" . " \"razor-admin -register -user=[name/email_address] -pass=[password]\".\n". " (Further information can be found in the razor-admin(1) manpage)\n" . " If you did register, please ensure your identity symlink (or file) is in order.\n"); return 2; } $ident = $self->get_ident; unless ($ident) { $self->log(3, "Unable to load automatically registered identity."); $self->errprefix("Bootstrap Error: Your Razor2 identity was not found.\n " . " If you haven't registered, please do so:\n" . " \"razor-admin -register -user=[name/email_address] -pass=[password]\".\n". " (Further information can be found in the razor-admin(1) manpage)\n" . " If you did register, please ensure your identity symlink (or file) is in order.\n"); return 2; } } if (!$self->{opt}{foreground} && (@ARGV < 1 || $ARGV[0] eq "-" || $ARGV[0] eq "")) { if (-t STDIN) { $self->error("Unable to read from a TTY using STDIN while forked. \n" . "Doing so leads to undefined behaviour in certain shells."); return 2; } } # background myself unless ($self->{opt}->{foreground}) { chdir '/'; fork && return 0; POSIX::setsid; # close 0, 1, 2; } if ($self->{opt}->{authen_only}) { $self->authenticate($ident) or return; $self->log(5,"Done - authenticate only."); return 0 if $self->{authenticated}; return 2; } my $mails = $self->parse_mbox($args) or return 2; my $objects = $self->prepare_objects($mails) or return 2; # compute_sigs needs server info like ep4, so get_server_info first $self->get_server_info() or return 2; my $printable_sigs = $self->compute_sigs($objects) or return 2; if ($self->{opt}->{printhash}) { foreach (@$printable_sigs) { if ($self->{opt}->{sigengine}) { next unless (/ e$self->{opt}->{sigengine}: /); } print "$_\n"; } exit 0; } if ( $self->{conf}->{simulate}) { $self->log (4, "Done. (simulate only)"); exit 0; } unless (scalar @$objects) { $self->log (4,"Done. No valid mail or signatures to check."); exit 1; } $self->{s}->{list} = $self->{s}->{nomination}; $self->nextserver(); $self->connect() or return 2; $self->authenticate($ident) or return 2; $self->report($objects) or return 2; $self->disconnect() or return 2; if ($self->{opt}->{foreground}) { foreach my $obj (@$objects) { # my $line = debugobj($obj->{r}); # $line =~ /(\S+=\S+)/s; # could be res=0|1, err=xxx # print "$obj->{id}: $1\n"; #print "$obj->{id}\n" if $obj->{r}->{res} == '1'; } } return 0; } sub parse_mbox { my ($self, $args) = @_; my @mails; my @message; my $passed_fh = 0; my $aref; # There are different kinds of mbox formats, we just split on simplest case. # djb defines mbox, mboxrd, mboxcl, mboxcl2 # http://www.qmail.org/qmail-manual-html/man5/mbox.html # # non-mbox support added, thanx to Aaron Hopkins <aaron@die.net> if (exists $$args{"fh"}) { @ARGV = (); push @ARGV, $$args{'fh'}; $passed_fh = 1; } elsif (exists $$args{"aref"}) { $aref = $$args{"aref"}; } elsif (!scalar @ARGV) { push @ARGV, "-" } if ($$args{'aref'}) { my @foo = (\join'', @{$$args{'aref'}}); return \@foo; } foreach my $file (@ARGV) { my $fh = new IO::File; my @message = (); if (ref $file) { $fh = $file } else { open $fh, "<$file" or return $self->error("Can't open $file: $!"); } my $line = <$fh>; next unless $line; if ($line =~ /^From /) { $self->log(8,"reading mbox formatted mail from ". ($file eq '-' ? "<stdin>" : $file)); while (1) { push @message, $line; $line = <$fh>; if (!defined($line) || $line =~ /^From /) { push @mails, \join ('', @message); @message = (); last unless defined $line; } } } else { $self->log(8,"reading straight RFC822 mail from ". ($file eq '-' ? "<stdin>" : $file)); push @mails, \join ('', map {s/^(>*From )/>$1/; $_} $line, <$fh>); } close $fh unless $passed_fh; } my $cnt = scalar @mails; $self->log (6, "read $cnt mail". ($cnt>1 ? 's' : '') ); return \@mails; } sub raise_error { my ($self, $errstr) = @_;; my $str; if (ref $self) { $str = $self->errstr; } $str = $errstr if $errstr; my ($code) = $str =~ /Razor Error (\d+):/; $code = 255 unless $code; print "FATAL: $str"; exit $code; } # returns 1 if mail should be skipped # sub local_check { my ($self, $obj) = @_; my ($headers, $body) = split /\n\r*\n/, ${$obj->{orig_mail}}, 2; $headers =~ s/\n\s+//sg; # merge multi-line headers if ($self->{conf}->{ignorelist}) { if ($headers =~ /\n((X-)?List-Id[^\n]+)/i) { my $listid = $1; my ($line1) = substr(${$obj->{orig_mail}}, 0, 50) =~ /^([^\n]+)/; $self->log (5,"Mailing List post; mail ". $obj->{id} ." not spam."); #$self->log (5,"Mailing List post; mail ". $obj->{id} ." not spam.\n $line1\n $listid"); return 1; } } return 0 if $self->{no_whitelist}; if (-s $self->{conf}->{whitelist}) { $self->read_whitelist; foreach my $sh (keys %{$self->{whitelist}}) { if ($sh ne 'sha1') { while ($headers =~ /^$sh:\s+(.*)$/img) { last unless $1; my $fc = $1; $self->log (13,"whitelist checking headers for match $sh: $fc"); foreach my $address (@{$self->{whitelist}->{$sh}}) { if ($fc =~ /$address/i) { $self->log (3,"ignoring mail $obj->{id}, whitelisted by rule: $sh: $address"); return 1; } } } } } $self->log (12,"Whitelist rules did not match mail $obj->{id}"); } elsif ($self->{conf}->{whitelist}) { $self->log (6,"skipping whitelist file (empty?): $self->{conf}->{whitelist}"); $self->{no_whitelist} = 1; } return 0; } sub read_whitelist { my ($self) = @_; return if $self->{whitelist}; my %whitelist; my $lines = $self->read_file($self->{conf}->{whitelist},0,1); for (@$lines) { s/^\s*//; next if /^#/; chomp; my ($type, $value) = split /\s+/, $_, 2; $type =~ y/A-Z/a-z/ if $type; push @{$whitelist{$type}}, $value if ($type && $value); } $self->{whitelist} = \%whitelist; $self->log (8,"loaded ". scalar(keys %whitelist) ." different types of whitelist"); #$self->logobj (15,"loaded whitelist:", \%whitelist); return 1; } sub logerr { my ($self,$msg) = @_; $msg = $self->errstr unless $msg; $self->log(1,"$self->{breed} error: ". $msg); return; } # see nextserver() for explanation of how data is stored # sub get_server_info { my $self = shift; unless (exists $self->{s}) { $self->{s} = {}; } if ($self->{opt}->{server}) { # cmd-line $self->{s}->{list} = [$self->{opt}->{server}]; $self->log(8,"Using cmd-line server ($self->{opt}->{server}), skipping .lst files"); } else { $self->readservers; } $self->loadservercache; #$self->logobj(6,"find_closest_server server info (before nextserver)", $self->{s}); $self->{loaded_servers} = 1; return $self->nextserver; # this will connect and get state info if not cached } # see nextserver() for explanation of how data is stored # sub readservers { my $self = shift; unless (exists $self->{s}) { $self->{s} = {}; } # read .lst files foreach my $lf (qw(discovery nomination catalogue)) { my $h = $self->read_file($self->{conf}->{"listfile_$lf"},0,1) or next; $self->{s}->{$lf} = []; foreach (@$h) { push @{$self->{s}->{$lf}}, $1 if /^(([^\.\s]+\.)+[^\.\s]+(:\S+)?)/; } if (defined($self->{s}->{$lf}) && ref($self->{s}->{$lf})) { $self->log(11,"Read ". scalar(@{$self->{s}->{$lf}}) ." from server listfile: ". $self->{conf}->{"listfile_$lf"}); } } foreach my $lf (qw(discovery nomination catalogue)) { next unless defined($self->{s}->{$lf}); next unless ref($self->{s}->{$lf}); next unless @{$self->{s}->{$lf}} > 1; fisher_yates_shuffle($self->{s}->{$lf}); } 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() } } sub loadservercache { my $self = shift; # # Read in server-specific config, using defaults for stuff not found # # NOTE: this reads all server.*.conf files in razor home, not just those in .lst # # load defaults for .lst servers foreach (qw(nomination catalogue)) { next unless $self->{s}->{$_}; foreach my $server (@{$self->{s}->{$_}}) { next if $self->{s}->{allconfs}->{$server}; # avoid repeats $self->{s}->{allconfs}->{$server} = $self->default_server_conf(); $self->log(9,"Assigning defaults to $server"); } } my @fns; my $sep = '\.'; $sep = '_' if $^O eq 'VMS'; if (opendir D,$self->{razorhome}) { @fns = map {s/_/./g; "$self->{razorhome}/$_";} grep /^server$sep[\S]+\.conf$/, readdir D; @fns = map { /^(\S+)$/, $1 } @fns; # untaint closedir D; } foreach (@fns) { /server\.(.+)\.conf$/ and my $sn = $1; next unless $sn; $self->{s}->{allconfs}->{$sn} = $self->read_file($_, $self->{s}->{allconfs}->{$sn} ); if ($self->{s}->{allconfs}->{$sn}) { #$self->log(8,"Loaded server specific conf info for $sn"); } else { $self->log(5,"loadservercache skipping $_"); } } return $self; } sub writeservers { my $self = shift; unless ($self->{razorhome}) { $self->log(5,"no razorhome, not caching server info to disk"); return; } foreach (@{$self->{s}->{modified_lst}}) { my $fn = $self->{conf}->{"listfile_$_"}; $self->write_file($fn, $self->{s}->{$_}, 0, 0, 1) || $self->log(5,"writeservers skipping .lst file: $fn"); } $self->log(11,"No bootstrap_discovery (DNS) recently, not recording .lst files") unless scalar (@{$self->{s}->{modified_lst}}); $self->{s}->{modified_lst} = []; foreach (@{$self->{s}->{modified}}) { my $fn = "$self->{razorhome}/server.$_.conf"; my $header = "#\n# Autogenerated by $self->{name_version}, ". localtime() ."\n"; $self->write_file($fn, $self->{s}->{allconfs}->{$_}, 0, $header) || $self->debug("writeservers skipping $fn"); } $self->{s}->{modified} = []; $self->errstrrst; # nuke error string if write errors return $self; } 1;