#!/usr/bin/perl ############################################################################################################### #Copyright 2004 by Christopher Andrew Hotchkiss # # # #This program is distributed without warrenty for anything # #under the GNU Public License v2.0. # # # #This program is designed to do bulk updates of the post227 # #workstations # ################################################################################################################ #Usage: # # # # netexec [add|clear|run|file] group="b2,b3,..." ["command"|file path] [destination file|path] # # # # group name can be specified in two ways # # direct hostname resolutions "home" "home.post227.org" # # or # # as a chain that needs to bounce from box to box "home.post227.org->b2->b3" # ################################################################################################################ use strict; use warnings; use Frontier::Daemon; use Frontier::Client; use XML::Dumper; use MIME::Base64; use IO::File; #Generic daemonization code use POSIX qw(WNOHANG setsid); use constant PID_FILE => '/var/tmp/server.pl.pid'; $SIG{CHLD} = sub { while ( waitpid(-1,WNOHANG)>0 ) { } }; $SIG{TERM} = $SIG{INT} = sub { my $quit++ }; my $fh = open_pid_file(PID_FILE); my $pid = become_daemon(); #Globals my $config_file = '/var/spool/netexec/netexec.xml'; my $config_dir = '/var/spool/netexec'; #Read in the xml queue if it exists, if it doesn't then make a skeleton one if(!( -d $config_dir)){ mkdir($config_dir); } if(!( -e $config_file)){ make_skelton_queue($config_file); } my $config = read_in_queue($config_file); #Final Server Start Code # Call me as http://localhost:8080/RPC2 my $methods = {'add' => \&add, 'clear' => \&clear, 'forward' => \&forward, 'run' => \&run, 'file' => \&file}; Frontier::Daemon->new(LocalPort => 8080, methods => $methods) or die "Couldn't start HTTP server: $!"; #Server callable functions #Add a command to the queue sub add { my ($cmd) = @_; my $key = $config->{next}; $config->{$key} = { type => 'cmd', cmd => $cmd }; $config->{next} = $key + 1; #Save the queue to disk my $dump = new XML::Dumper; $dump->pl2xml( $config, $config_file ); return {'good' => 'good'}; } #Clear the queue sub clear { $config->{notrun} = $config->{next} + 1; $config->{next} = $config->{notrun}; my $dump = new XML::Dumper; $dump->pl2xml( $config, $config_file ); return {'good' => 'good'}; } #Run all queued commands sub run { my $output; do { #Two possible types are file copies and cmds my $current = $config->{notrun}; if($config->{$current}->{type} eq 'cmd'){ my $cmd = $config->{$current}->{cmd}; my $info = `$cmd`; #Save output to the queue $config->{$current} = { 'output' => $info}; $config->{notrun} = $config->{notrun} + 1; $output .= $info; } elsif($config->{$config->{notrun}}->{type} eq 'file'){ my $file = $config->{$current}->{filename}; my $dest = $config->{$current}->{path}; my $info = `cp $file $dest`; #Save output to the queue $config->{$current} = { 'output' => $info}; $config->{notrun} = $config->{notrun} + 1; $output .= $info; } } while ($config->{notrun} != $config->{next}); my $dump = new XML::Dumper; $dump->pl2xml( $config, $config_file ); #Return the output up the chain return {'good' => $output}; } #Forward the command to another computer, make no record of it sub forward { my ($type, $path, $cmd, $dest) = @_; my $output; my $group = $path; $group =~ /group="(.*)"/; my @comps = split(',', $1); foreach my $comp (@comps){ if($comp =~ /^(.*?)->(.*)/){ $output .= &cforward( $1, $type, "group=\"$2\"", $cmd, $dest); } elsif($type eq "add"){ &cadd($1, $cmd); } elsif($type eq "clear"){ &cclear($1); } elsif($type eq "run"){ $output .= &crun($1); } elsif($type eq "file"){ $output .= &cfile($1, $cmd, $dest); } } #Return output back up the chain return {'good' => $output}; } #Add a file to the Queue sub file { my ($file, $destination) = @_; my $output = decode_base64($file); #Write it to the location as its number #It is written to a separate location to reduce memory usage my $foo = $config->{next}.".tmp"; open(FH, ">/var/spool/netexec/".$foo); print FH $output; my $key = $config->{next}; $config->{$key} = { type => 'file', filename => "/var/spool/netexec/".$foo, path => $destination }; $config->{next} = $key + 1; my $dump = new XML::Dumper; $dump->pl2xml( $config, $config_file ); return ( 'good' => "good"); } #client simulating functions, same thing as a client sub cadd { my ($host, $cmd) = @_; my $server = Frontier::Client->new(url => $config->{prefix}.$host.$config->{suffix}); my $result = $server->call('add', $cmd); } sub cclear { my ($host) = @_; my $server = Frontier::Client->new(url => $config->{prefix}.$host.$config->{suffix}); my $result = $server->call('clear'); } sub crun { my ($host) = @_; my $server = Frontier::Client->new(url => $config->{prefix}.$host.$config->{suffix}); my $result = $server->call('run'); return $result->{good}; } sub cforward { my ( $host, $type, $path, $cmd, $dest) = @_; my $server = Frontier::Client->new(url => $config->{prefix}.$host.$config->{suffix}); my $result = $server->call('forward', $type, $path, $cmd, $dest); return $result->{good}; } sub cfile { my ($host, $file, $destination) = @_; my $server = Frontier::Client->new(url => "http://".$host.":8080/RPC2"); my $result = $server->call('file', $file, $destination); } #General Subroutines sub make_skelton_queue{ my $config_file = shift; my $dump = new XML::Dumper; my $xml = { notrun => 1, next => 1, prefix => "http://", suffix => ":8080/RPC2" }; $dump->pl2xml( $xml, $config_file ); } sub read_in_queue { my $config_file = shift; my $dump = new XML::Dumper; my $ref = $dump->xml2pl( $config_file ); return $ref; } #Generic Daemonization code sub become_daemon { die "Can't fork" unless defined (my $child = fork); exit 0 if $child; # parent dies; setsid(); # become session leader open(STDIN, "/dev/null"); open(STDERR,">&STDOUT"); chdir '/'; # change working directory umask(0); # forget file mode creation mask $ENV{PATH} = '/bin:/sbin:/usr/bin:/usr/sbin'; return $$; } sub open_pid_file { my $file = shift; if (-e $file) { # oops. pid file already exists my $fh = IO::File->new($file) || return; my $pid = <$fh>; die "Server already running with PID $pid" if kill 0 => $pid; warn "Removing PID file for defunct server process $pid.\n"; die "Can't unlink PID file $file" unless -w $file && unlink $file; } return IO::File->new($file,O_WRONLY|O_CREAT|O_EXCL,0644) or die "Can't create $file: $!\n"; } END { unlink PID_FILE if $$ == $pid; }