All pastes #818385 Raw Edit

Someone

public text v1 · immutable
·published 2007-12-16 07:55 UTC
rendered paste body
#!/usr/bin/perl

##Copyright (C) 2007 Fonality Inc. (fonality.com)
#
#This program is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public License
#as published by the Free Software Foundation; either version 2
#of the License, or (at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.
#


use strict;
use Crypt::CBC;
use LWP::UserAgent;
use LWP::Simple;
use HTTP::Request::Common;
use IPC::Open3 qw/open3/;
use MIME::Base64 qw/encode_base64/;
use DB_File;
use Digest::MD5 qw/md5_hex/;
use Getopt::Long qw/GetOptions/;
use File::Spec;

my $VERSION = 9; #Increment to force clients to attempt to update themselves.

my $INSTALL_PATH = "/var/adm/bin";
my $CACHE_DB = "$INSTALL_PATH/registry_cache.db";
INSTALL_CRON_JOB(); #If we aren't already in the cron, install ourselves.

# This file is design to be executed regularly by an external controller such as cron.
# It retrieves a list of commands to be executed from the specified URI and executes them, saving the output
# and returning it to the webserver as an encrypted string.
my $remote_host;
if (-e "/etc/asterisk/fonality/globals.conf")
{
	my $server_id = get_server_id();
	if ($server_id >= 100000)
	{
		$remote_host = "proregistry.trixbox.com";
	}
	else
	{
		$remote_host = "update.fonality.com";
	}
}
else
{
	$remote_host = "registry.trixbox.com";
}

my $server_uri = "http://$remote_host/server_monitor.cgi";
my $update_uri = "http://$remote_host/";
my $server_id = get_server_id();

warn "SENDING SERVER ID: '$server_id'\n";

###############################################################################
# Fetch commands to run.
my $ua = LWP::UserAgent->new;

my $resp = fetch_server_commands( $ua );

my $key = get_key();
chomp $key;

my $cipher = Crypt::CBC->new( -cipher => "Blowfish", -key => $key );
my @commands = split /\n/, $cipher->decrypt( $resp->content );


###############################################################################
# Check our version.
my $expected_version = shift @commands;
if( $expected_version > $VERSION ) 
{
	warn "Trying to update: $update_uri/recognition.pl\n";
	my $update_resp = $ua->get("$update_uri/recognition.pl");
	
	if( $update_resp->is_success )
	{
		open my $fh, "> $INSTALL_PATH/tmp_recognition.pl" or die $!;
		print $fh $update_resp->content;
		close $fh;
		rename "$INSTALL_PATH/tmp_recognition.pl", "$INSTALL_PATH/recognition.pl" or warn "ERROR couldn't replace old recognition with new one: $!\n";
	}
	else
	{
		warn "ERROr, couldn't fetch recognition.pl ", $update_resp->status_line;
	}
	
	$update_resp = $ua->get("$update_uri/registry.pl");

	if( $update_resp->is_success )
	{
		my ( $fname  ) = (File::Spec->splitpath($0))[-1];

		open my $fh, "> $INSTALL_PATH/tmp_$fname" or die "Failed to open $INSTALL_PATH/tmp_$fname: $!";
		print $fh $update_resp->content;
		close $fh;
		rename "$INSTALL_PATH/tmp_$fname", "$INSTALL_PATH/$fname";
		exec "perl", "$INSTALL_PATH/$fname";
		exit; #If the exec failed, cleanly die.
	}
	else
	{
		die "Error, version is out of date and I could not fetch an update.\n";
	}


}


###############################################################################
# Check if we've been assigned a new server-id (for CE servers only)
my $new_id = shift @commands;
if( length $new_id > 1 and $new_id ne $server_id )
{
	$server_id = $new_id;
	store_generated_id( $new_id );

	#SERVER_ID\nSERVER_KEY
	#The key is only there if our ID has changed..
	my $new_key = shift @commands;
	store_generated_key( $new_key );
	$cipher = Crypt::CBC->new( -cipher => "Blowfish", -key => $new_key );

	#Remove cache file, since we should think we're now running on a brand new server.
	unlink $CACHE_DB;
}

###############################################################################
# Execute commands.
my @command_outputs;
for( @commands ) 
{
	my $pid = open3 my $write_handle, my $read_handle, my $err_handle, $_;
	close $write_handle;

	chomp( my @output = <$read_handle> );
	chomp( my @errors = <$err_handle> );
	waitpid $pid, 0;
	my $child_return_value = $?;
	my $success = 1; #Default to succeeded.

	if( $output[0] =~ /open3:/ ) #This matches the exceptions thrown by open3. Example, open3: Failed to exec in child..
	{
		@errors = @output;
		@output = ();
		$success = 0;
	}

	print "Cmd: $_\n";
	print "Received ", scalar @output, " lines of output\n";
	print "\t-- '$output[0]'\n";
	print "Received ", scalar @errors, " lines of errors\n\n";


	push @command_outputs, {
		stdout => ( join "\n", @output ),
		stderr => ( join "\n", @errors ),
		return_value => $child_return_value,
		succeeded => $success,
		command => $_,
	};
}


###############################################################################
# Generate report structure.
my @xml_outputs;
tie my %cached_output, 'DB_File', $CACHE_DB or die $!;
for( @command_outputs )
{
#  my $stdout = encode_base64 join "\n", @{ $_->{stdout} };
#  my $stderr = encode_base64 join "\n", @{ $_->{stderr} };
#  my $command = encode_base64 $_->{command};
	for( values %$_ )
	{
		$_ = encode_base64( $_ ) ;
	}

	my $xml = 
		"<results>
			<command>$_->{command}</command>
			<stdout>$_->{stdout}</stdout>
			<stderr>$_->{stderr}</stderr>
			<return_value>$_->{return_value}</return_value>
			<succeeded>$_->{succeeded}</succeeded>
		</results>"
	;
	my $checksum = md5_hex( $xml );

	if( $cached_output{ $_->{command} } ne $checksum )
	{
		push @xml_outputs, { encrypted => $cipher->encrypt( $xml ), command => $_->{command}, xml => $xml };
	}
}


###############################################################################
# Upload report.
if( @xml_outputs )
{
	my $post = POST $server_uri, 
		Content_Type => 'form-data',
		Content => [
			m => 'receive_results',
			server_id => $server_id,
			map {; "command$_" => [ undef, "command$_", Content => $xml_outputs[$_]->{encrypted}  ] } #Note, the semicolon is in fact required.
				0 .. $#xml_outputs,
		]
	;

	#Actually send request.
	my $resp = $ua->request( $post );

	############################################################################
	# Successful upload, update our cachefile
	if( $resp->is_success )
	{
		for( @xml_outputs )
		{
			my $checksum = md5_hex( $_->{xml} );
			$cached_output{ $_->{command} } = $checksum;
		}
	}
}


################################################################################
# Begin subs.
################################################################################
sub fetch_server_commands
{
	my( $ua ) = @_;

	my $resp = $ua->get("$server_uri?m=fetch_commands&sid=$server_id");

	warn "$server_uri?m=fetch_commands&sid=$server_id\n";
	if( $resp->is_error )
	{
		die "The request to retrieve commands to execute has errored with the following status: ", $resp->code, " -- exiting.\n";
	}
	elsif( $resp->content =~ /ERROR:/ )
	{
		if( $resp->content =~ /invalid server id/ )
		{
			$server_id = server_generate_id();
			return fetch_server_commands( $ua );
		}
	}

	return $resp;
}


sub get_server_id
{
	my $id = get_globals_user_id();

	if( $id ) { return $id; }

	$id = get_generated_id();

	if( $id ) { return $id; }
	
	die "Error: Couldn't find or generate a server id\n";
}

sub get_globals_user_id
{
	open my $fh, "/etc/asterisk/fonality/globals.conf" or return;

	while( <$fh> )
	{
		if( /ACCOUNT=(\S+)/ )
		{
			return $1;
		}
	}
}

sub get_generated_id
{
	if( -e "/etc/trixbox/monitor_server_id" )
	{
		open my $fh, "/etc/trixbox/monitor_server_id" or die; #FIXME shouldn't die
		chomp( my $id = <$fh> );

		if( $id )
		{
			return $id;
		}
	}

	return server_generate_id();
}

sub server_generate_id
{
	my $key_id = get( "$server_uri?m=generate_id" );
warn "RETURN VALUE: '$key_id'\n";
	my( $id, $key ) = split /\n/, $key_id;

	store_generated_id( $id );
	store_generated_key( $key );

	return $id;
}

sub store_generated_id
{
	my( $id ) = @_;

warn "STORING ID -- $id\n";

	open my $fh, "> /etc/trixbox/monitor_server_id" or die "Error, couldn't open /etc/trixbox/monitor_server_id: $!\n";
	print $fh $id;
}

sub store_generated_key
{
	my( $key ) = @_;

	mkdir "/etc/trixbox";
warn "STORING KEY -- $key\n";
	
	open my $fh, "> /etc/trixbox/monitor_server_key" or die "Error, couldn't open /etc/trixbox/monitor_server_key: $!\n";
	print $fh $key;
}

sub get_key
{
	my $file;

	for( qw{ /etc/asterisk/key /etc/trixbox/monitor_server_key } )
	{
		if( -e $_ ) { $file = $_; last; }
	}

	open my $fh, "< $file" or die "Error couldn't open keyfile $file: $!\n";
	my $key = <$fh>;
	chomp $key;

	return $key;
}

sub INSTALL_CRON_JOB
{
	my $fn = "/tmp/newcron.$$";
	system "crontab -l > $fn";

	open my $fh, "< $fn" or die $!;
	while( <$fh> )
	{
		if( /registry.pl/ ) { unlink $fn; return; }
	}

	#We didn't exit early..
	close $fh;
	open $fh, ">> $fn" or die $!;
	my ($minute,$hour ) = (int(rand(60)), int(rand(24)));
	print $fh "$minute $hour * * * perl $INSTALL_PATH/registry.pl &> /dev/null \n";
	close $fh;

	system( "crontab $fn" );
}