/
lib
/
sonarpush
/
File Upload :
llllll
Current File: //lib/sonarpush/SonarPush.pm
package SonarPush; use strict; use POSIX; use Proc::Daemon; use HTTP::Tiny; use JSON::Tiny qw(decode_json encode_json); use MIME::Base64; use SonarPush::Globals; use SonarPush::Providers::State; use SonarPush::Providers::Hardware; use SonarPush::Providers::Software; =head1 NAME SonarPush - Collects information/stats. =head1 DESCRIPTION Class for starting/running sonarpush daemon and collecting data about various machine attributes like software, hardware, states. Once data is collected from the machine the daemon is running on it is posted to a central server (currently Mr.Radar) where it is stored. =head2 new =over 4 =item Description: my $sonarpush = SonarPush->new(); Initializes sonarpush object and creates a local datastructure of status attributes used to keep track of success/fail attempts when posting the collected data to the collection server. Provider objects are also initialized and stored in the local $self->{providers} dataset which do the actual collection work. =item Parameters none =item Returns A blessed class object with status attributes, and provider classes. =back =cut sub new { my $class = shift; my $self = { status => { Updates => { successful => 0, failed => 0, total => 0, }, Checkins => { successful => 0, failed => 0, total => 0, }, }, providers => {}, }; bless $self, $class; map { $self->{providers}{$_} = "SonarPush::Providers::$_"->new($self) } qw(State Hardware Software); $self->{Started} = POSIX::strftime("%Y/%m/%d %H:%M:%S", localtime); return $self; } =head2 provider =over 4 =item Description: $self->provider('Software'); Creates or returns a previously created provider object. Each provider object contains methods used to collect data. $self->provider('Hardware')->cpu; =item Parameters $provider: Software, Hardware, or State. =item Returns the provider object specified as the parameter. =back =cut sub provider { my ($self, $provider) = @_; return $self->{providers}{$provider} ||= "SonarPush::Providers::$provider"->new($self); } =head2 userAgent =over 4 =item Description: $self->userAgent; Creates or returns a previously created HTTP::Tiny UserAgent object. =item Parameters none =item Returns HTTP::Tiny UserAgent object =back =cut sub userAgent { my $self = shift; my $version = $self->version; $self->{useragent} ||= do { my $ua = HTTP::Tiny->new( agent => $SonarPush::Globals::USERAGENT . $version, ssl_opts => { verify_hostname => 0, SSL_verify_mode => 0, }, timeout => $SonarPush::Globals::HTTP_TIMEOUT, ); $ua; }; return $self->{useragent}; } =head2 post =over 4 =item Description: $self->post($args); JSON encoded the passed parameters and submits a authenticated http post of the encoded data. =item Parameters url parameters to pass to webserver, see process and run method. =item Returns returns a JSON encoded data returned by the webserver, either consisting of actual data, or error response codes. =back =cut sub post { my $self = shift; my $args = shift; my $json = encode_json($args); my $ua = $self->userAgent; my $response = $ua->post_form($SonarPush::Globals::PUSH_URL, { request => $json }, { headers => { Authorization => $self->authHeader } }, ); if ($response->{status} == 599) { $response->{error} .= "Error processing https request timeout [ $@ ]\n"; } if (!($response->{success})) { $response->{error} .= "Error processing https request: " . $response->{reason} . "\n"; } else { $self->printlog("Good POST\n"); my $body = $response->{content}; eval { $response = decode_json($body); }; if ($@) { $response->{error} .= "Invalid JSON reponse received. $body\n"; } else { if ($response->{'RequestResult'} eq "OK") { $self->printlog("Result = OK\n"); } else { $response->{error} .= 'Error processing https request ' + $response->{'RequestResultMessage'} . "\n"; } } } return $response; } =head2 run =over 4 =item Description: $sonarpush->run() After $sonarpush->start has been called successfully run() puts the program into an endless timed loop and processes schedule info every X seconds. =item Parameters none =item Returns undef =back =cut sub run { my $self = shift; my $args = shift; if (($< == 0) or ($> == 0) or ($( == 0) or ($) == 0)) { $self->suicide("Root privileges were not dropped properly. Exiting!\n"); } $self->{failcount} = 0; $self->{loopint} = $SonarPush::Globals::DEFAULT_COLLECTION_LOOP; while (1) { $self->checkDebugFile; $0 = $self->name . " [getting schedule information]"; $self->printlog("Getting schedule information\n"); if ($self->{failcount} > 5) { $self->printlog("More than 5 Consecutive HTTP failures have taken place. Falling back to fail-safe LoopInterval.\n"); $self->{loopint} = $SonarPush::Globals::DEFAULT_COLLECTION_LOOP; } $self->scheduler({ Command => 'getScheduleInfo' }); $0 = $self->name . " [sleeping]"; $self->printlog("Sleeping for $self->{loopint} seconds\n"); sleep($self->{loopint}); } return; } =head2 scheduler =over 4 =item Description: $sonarpush->scheduler({ Command => 'getScheduleInfo' }) gets a list of tasks and passes those tasks to be processed. keeps track of failure/success counts when processing those requests. also keeps track of http post success and failures. =item Parameters { Command => 'getScheduleInfo' } =item Returns $self =back =cut sub scheduler { my $self = shift; my $request = shift; $self->{status}{Checkins}{total}++; my $response = $self->post($request); if (!$response->{error}) { $self->{status}{Checkins}{successful}++; $self->process($response); } else { $self->{status}{Checkins}{failed}++; $self->{failcount}++; $self->printlog($response->{error}, 1); } return $self; } =head2 process =over 4 =item Description: $self->process($response); this method determines if colletion providers need to be executed or not, =item Parameters The response to a successful $self->post({ Command => 'getScheduleInfo' }); =item Returns $self =back =cut sub process { my $self = shift; my $response = shift; if ($response->{'NextTask'} > $response->{'LoopInterval'}) { $self->printlog( "NextTask (" . $response->{'NextTask'} . ") is larger than our Sonar-specified LoopInterval (" . $response->{'LoopInterval'} . "). Setting LoopInterval to Sonar-specified LoopInterval.\n" ); $self->{loopint} = $response->{'LoopInterval'}; } elsif ($response->{'NextTask'} < 5) { $self->printlog("NextTask (" . $response->{'NextTask'} . ") is less than 5 seconds away. Setting LoopInterval to 5 seconds.\n"); $self->{loopint} = 5; } else { $self->printlog("Setting LoopInterval to make our next request coincide with NextTask.\n"); $self->{loopint} = ($response->{'NextTask'} + 1); } if ($response->{'TasksWaiting'} eq "yes") { $0 = $self->name . " [collecting data]"; $self->printlog("Performing collections for the following tasks: " . $response->{'TaskList'} . "\n"); my $buffer = $self->_generateTaskXML($response->{TaskList}); $0 = $self->name . " [submitting data]"; $self->printlog("Submitting data.\n"); $self->update({ Command => 'postcollecteddata', XMLdata => $buffer, WorkTicketID => $response->{'WorkTicketID'}, }); } return $self; } =head2 update =over 4 =item Description: $self->update({ Command => 'postcollecteddata', XMLdata => $buffer, WorkTicketID => $response->{'WorkTicketID'}, }); this method posts xml encoded provider task data to be posted to the collector server. also keeps track fo successful/fail updates posted to the collection server. =item Parameters Command => 'postcollecteddata XMLdata => valid xml string, WorkTicketID => derived from scheduleinfo. =item Returns $self =back =cut sub update { my $self = shift; my $request = shift; $self->{status}{Updates}{total}++; my $result = $self->post($request); if ($result->{error}) { $self->{failcount}++; $self->{status}{Updates}{failed}++; $self->printlog($result->{error}, 1); } else { $self->{failcount} = 0; $self->{status}{Updates}{successful}++; } return $self; } sub checkDebugFile { my ($self) = @_; if ((-f $SonarPush::Globals::DEBUGFLAGFILE) and !$self->{debug}) { $self->{debug} = 1; $self->printlog("DEBUGFLAGFILE found. Enabling debug mode.\n"); } elsif ((!-f $SonarPush::Globals::DEBUGFLAGFILE) and ($self->{debug})) { $self->printlog("DEBUGFLAGFILE is gone and DEBUG is currently set. Disabling debug mode.\n"); $self->{debug} = 0; } } =head2 start =over 4 =item Description: $sonarpush->start($args); this method starts the program as a process and forks, sets appropriate perms on the process pid file, =item Parameters any options that may be passed to Proc::Daemon for process initialzation. =item Returns nothing =back =cut sub start { my $self = shift; my $args = shift; if ($args->{debug}) { $self->{debug} = delete $args->{debug}; $self->printlog("Debug mode... staying in foreground.\n"); } if (($> != 0) or ($< != 0)) { $self->suicide("This program requires root privileges.\n"); } $0 = $self->name . " [starting up]"; $self->printlog("SonarPush " . $self->version . " starting\n"); $self->setProcessUser; my $daemon; if (!$self->{debug}) { $args->{pid_file} = $SonarPush::Globals::PIDFile; $args->{child_STDERR} = "+>>$SonarPush::Globals::LOGFILEERROR"; my $daemon = Proc::Daemon->new(); $daemon->Init($args); $SIG{'HUP'} = sub { $self->handleHUP(); }; chown $self->{processuser}{uid}, $self->{processuser}{gid}, $SonarPush::Globals::PIDFile or $self->printlog("Warning: Could not change ownership on PID File!\n", 1); chmod 0664, $SonarPush::Globals::PIDFile or $self->printlog("Warning: Could not change permissions on PID File!\n", 1); chown $self->{processuser}{uid}, $self->{processuser}{gid}, $self->getlogfilename() or $self->printlog("Warning: Could not set permissions on log file!\n"); } my $credentials = $self->_authCredentials; $credentials->{auth_user} || $self->suicide("Error loading UserName for push authentication. Exiting.\n"); if (!$credentials->{auth_pass}) { $self->register; } $self->dropPrivs({ uid => $self->{processuser}{uid}, gid => $self->{processuser}{gid} }); $self->run(); exit(1); } =head2 setProcessUser =over 4 =item Description: this method sets the UID of the running process to the user specified in Globals.pm, =item Parameters none =item Returns $self =back =cut sub setProcessUser { my $self = shift; $self->{processuser} ||= do { (my $login, my $pass, my $newUID, my $newGID) = getpwnam($SonarPush::Globals::NonPrivUser) or $self->suicide("$SonarPush::Globals::NonPrivUser not in passwd file"); ($login, $pass) = undef; { uid => $newUID, gid => $newGID }; }; if (!$self->{processuser}{uid} || !$self->{processuser}{gid}) { $self->suicide("Error obtaining non privledged user information!\n"); } return $self; } =head2 dropPrivs =over 4 =item Description: this program is started with root privs, and this method drops those privs to the UID that is derived in setProcessUser. =item Parameters { $uid => 112, gid => 115} =item Returns $self =back =cut sub dropPrivs { my $self = shift; my $args = shift; if ($args->{uid} == 0) { $self->suicide("New UID has root privileges. Exiting!\n"); } if ($args->{gid} == 0) { $self->suicide("New GID has root privileges. Exiting!\n"); } $self->printlog("UID (real/effective): $</$>\n"); $self->printlog("GID (real/effective): $(/$)\n"); $self->printlog("Dropping privileges...\n"); $( = $args->{gid}; $) = $args->{gid}; $< = $args->{uid}; $> = $args->{uid}; $self->printlog("UID (real/effective): $</$>\n"); $self->printlog("GID (real/effective): $(/$)\n"); return $self; } =head2 register =over 4 =item Description: before sonarpush can collect data it must register with the collection server, this method will continuously try to register with the UNIQ_ID of the subaccnt until a success is received. A sucessful registration means the app was able to successfully download a password file and store it. =item Parameters none =item Returns $self =back =cut sub register { my $self = shift; my $tryfail = 1; my $regtries = 0; my $maxregtries = 5; while (($regtries <= $maxregtries) and $tryfail) { $regtries++; $self->printlog("Attempting to perform registration with Sonar system (attempt $regtries of $maxregtries).\n"); my %options = ( headers => { Authorization => $self->authHeader($SonarPush::Globals::REGISTER_USER, $SonarPush::Globals::REGISTER_PASS), } ); my $ua = $self->userAgent; my $res = $ua->get("$SonarPush::Globals::REGISTER_URL?uid=$self->{auth_user}", \%options); if (!($res->{success})) { # GET failed $self->printlog("Request failed: [ $res->{status} $res->{reason} ].\n", 1); $tryfail = 1; } else { my $body = $res->{content}; # Creation: SUCCESS # Password: SUCCESS: tEXnZG4gKzZLpQYBl754JHnDYBHZa if ($body =~ /^Creation: SUCCESS$/m) { # Creation was good $self->printlog("Base registration successful.\n"); if ($body =~ /^Password: SUCCESS: ([A-Za-z0-9]{24,32})$/m) { $self->printlog("Password successfully retrieved.\n"); $tryfail = 0; my $success = $self->storeAuthPass($1); if (not $success) { $self->suicide("There was an error writing to the password file.\n"); } } else { # Password retrieval failed $self->suicide("Password retrieval failed. Password will need to be configured manually.\n"); } } else { # Server creation unsuccessful $self->printlog("Base registration unsuccessful.\n"); $tryfail++; } } if ($tryfail) { my $sleeptime = 600 * $regtries; $self->printlog("Sleeping for $sleeptime before retry...\n", 1); sleep($sleeptime); } } return $self; } =head2 _deriveTaskList =over 4 =item Description: this method orders tasks by the provider it belogs to derived from the string of tasks returned by the collection server =item Parameters a comma seperated string of tasks to be executed, this is returned after a successful getScheduleInfo post. =item Returns a hashref of key value pairs of provider => tasks: { software => [apache, exim], hardware => [cpu disk], state => [network], } =back =cut sub _deriveTasklist { my $self = shift; my $response = shift; my %tasks = map { $_ => 1 } split(/,/, $response); for my $alias (qw(EVERYTHING ALLSOFTWARE ALLSTATE ALLHARDWARE)) { if ($tasks{$alias}) { delete $tasks{$alias}; for my $subTask (@{ $SonarPush::Globals::TaskList{$alias} }) { $tasks{$subTask} = 1; } } } my %taskData; TASK: foreach my $task (sort keys %tasks) { foreach my $type (qw/software hardware state/) { for my $typeTask (@{ $SonarPush::Globals::TaskList{ 'ALL' . uc $type } }) { if ($typeTask eq $task) { $taskData{$type} ||= []; push(@{ $taskData{$type} }, $task); next TASK; } } } } return \%taskData; } =head2 _generateTaskXML =over 4 =item Description: executes the provider tasks, and creates a wellformed XML string. =item Parameters a comma seperated string of tasks to be executed, this is returned after a successful getScheduleInfo post. =item Returns an xml string =back =cut sub _generateTaskXML { my $self = shift; my $response = shift; my $providerAttrs = { providedby => 'NONE', datatype => 'None', }; # This particular line of raw XML isn't producible by the XML generator. # As such, we need to explicitly prepend it to the generated output. # This is the only case where we should need to write explicit XML my @buffer = ('<?xml version="1.0"?>'); my $taskhash = $self->_deriveTasklist($response); my $dataNode = { name => 'data', value => [], }; foreach my $provider (sort keys %$taskhash) { my $providerNode = { name => $provider, attributes => $providerAttrs, value => [], }; push(@{ $dataNode->{value} }, $providerNode); my $class = ucfirst($provider); foreach my $task (@{ $taskhash->{$provider} }) { $task =~ s/[^\w_]//g; push(@{ $providerNode->{value} }, $self->provider($class)->$task()); } } push(@buffer, SonarPush::Providers::encodeXML({ name => 'provider', value => [ { name => 'version', value => 'SonarPush ' . $self->version }, $dataNode ] })); return join("\n", @buffer); } sub _authCredentials { my $self = shift; return { auth_user => $self->authUsername, auth_pass => $self->authPassword, }; } sub storeAuthPass { my $self = shift; my $auth_pass = shift; my $success = 1; my $fh; open $fh, '>', $SonarPush::Globals::PASSWORD_FILE or $success = 0; if ($success) { print $fh $auth_pass; chown 0, 0, $SonarPush::Globals::PASSWORD_FILE; chmod 0600, $SonarPush::Globals::PASSWORD_FILE; $self->{auth_pass} = $auth_pass; } return $success; } sub authUsername { my $self = shift; my $username; $self->{auth_user} ||= do { my $flag = 1; my $input; { local $/; open(INPUT, "< $SonarPush::Globals::USERNAME_FILE") or $flag = 0; if ($flag) { $input = <INPUT>; } } if ($flag) { if ($input =~ /^([A-Za-z0-9]{1,32})\s+$/) { $username = $1; } } $username; }; return $self->{auth_user}; } sub name { my $self = shift; $self->{appname} ||= do { my $app = "SonarPush " . $self->version; $app; }; return $self->{appname}; } sub version { my $self = shift; my $version; $self->{version} ||= do { if (-e $SonarPush::Globals::VERSIONFILE) { if (-r $SonarPush::Globals::VERSIONFILE) { local $/; open(INPUT, "< $SonarPush::Globals::VERSIONFILE") or $self->suicide("Could not open version file: $!"); $version = <INPUT>; close(INPUT); } } chomp $version; $version; }; return $self->{version}; } sub authPassword { my $self = shift; my $password; $self->{auth_pass} ||= do { my $flag = 1; my $input; if (-e $SonarPush::Globals::PASSWORD_FILE) { if (-r $SonarPush::Globals::PASSWORD_FILE) { # Password file exists and is readable local $/; open(INPUT, "< $SonarPush::Globals::PASSWORD_FILE") or $flag = 0; if ($flag) { $input = <INPUT>; } } else { #password file exists, but is not readable $self->suicide("Password file exists, but is not readable. Exiting.\n"); } if ($flag and $input =~ /^([A-Za-z0-9]{1,32})\s*$/) { $password = $1; } } $password; }; return $self->{auth_pass}; } sub now { my $self = shift; my $now = POSIX::strftime("[%Y/%m/%d %H:%M:%S] ", localtime); return $now; } sub getlogfilename { my $self = shift; return $SonarPush::Globals::LOGFILEBASE . POSIX::strftime("%Y%m%d", localtime) . ".log"; } sub suicide { my $self = shift; my $message = shift; $self->printlog($message, 1); die $message; } sub printlog { my $self = shift; my $message = shift; my $error = shift; print $message; my $logfile = $error ? $SonarPush::Globals::LOGFILEERROR : $self->getlogfilename(); if ($error or $self->{debug}) { open(my $file, '>>', $logfile); print $file $self->now() . $message; } return; } sub handleHUP { my $self = shift; print "Caught HUP signal... \n"; print "Removing PID file $SonarPush::Globals::PIDFile... "; if (unlink $SonarPush::Globals::PIDFile) { print "success!\n"; } else { print "failed!\n"; } exit; } sub authHeader { my ($self, $authuser, $authpass) = @_; $authuser ||= $self->authUsername; $authpass ||= $self->authPassword; my $code = 'Basic ' . MIME::Base64::encode($authuser . ':' . $authpass); $code =~ s/\n$//; return $code; } 1;
Copyright ©2k19 -
Hexid
|
Tex7ure