#!/usr/bin/perl # vim:shiftwidth=2:tabstop=2:expandtab:textwidth=80:softtabstop=2:ai: ######################################################### # Written Aug 4, 2007 and released under the GNU/GPLv2 ## # by Jeff Schroeder (jeffschroeder@computer.org) # # ######################################################### # # # # # ossec-batch-manager.pl - Add and extract agents from # # # the ossec client.keys file non-interactively. This # # # started as a hack to properly script manage_agents. # # # # # ########################################################## #$Id: ossec-batch-manager.pl,v 1.3 2007/08/08 22:48:33 jeff Exp $ use strict; use warnings; require 5.8.2; # Time::HiRes is standard from this version forth #use diagnostics; use MIME::Base64; use Digest::MD5 qw(md5_hex); use Getopt::Long; use constant AUTH_KEY_FILE => "/var/ossec/etc/client.keys"; my ($key, $add, $remove, $extract, $import, $listagents); my ($agentid, $agentname, $ipaddress); GetOptions( 'k|key=s' => \$key, # Unencoded ssh key 'a|add' => \$add, # Add a new agent 'r|remove=s' => \$remove, # Remove an agent 'e|extract=s' => \$extract, # Extract a key 'm|import' => \$import, # Import a key 'l|list' => \$listagents, # List all agents 'i|id=s' => \$agentid, # Unique agent id 'n|name=s' => \$agentname, # Agent name. 32 char max 'p|ip=s' => \$ipaddress # IP Address in "dotted quad" notation ); # Spit out a list of available agents, their names, and ip information if ($listagents) { list_agents(); } # Decode and extract the key for $agentid elsif ($extract) { $agentid = $extract; if ($agentid) { extract_key($agentid); } else { usage(); } } # Adding a new agent elsif ($add) { if ($agentname && $ipaddress && $ipaddress =~ m/(1?\d\d?|2[0-4]\d|25[0-5])(\.(1?\d\d?|2[0-4]\d|25[0-5])){3}/ && # ossec doesn't like agent names > 32 characters. length($agentname) <= 32) { # Autogenerate an id incremented 1 from the last in a sorted list of # all current ones if it isn't specified from the command line. if (!$agentid) { # Make a list of all of the used agentids and then sort it. if (-r AUTH_KEY_FILE) { my @used_agent_ids = (); open (FH, "<", AUTH_KEY_FILE); while () { my ($id, $name, $ip, $key) = split; push(@used_agent_ids, $id); } close(FH); if (@used_agent_ids) { @used_agent_ids = sort(@used_agent_ids); $agentid = sprintf("%03d", $used_agent_ids[-1] + 1); } } # If the client.keys is empty or doesn't exist set the id to 001 $agentid = sprintf("%03d", 001) if (!$agentid); } # Autogenerate a key unless one was specified on the command line if (!$key) { use Time::HiRes; # Standard with perl >= 5.8.2 my $rand_str1 = time() . $agentname . rand(10000); my $rand_str2 = Time::HiRes::time . $ipaddress . $agentid . rand(10000); $key = md5_hex($rand_str1) . md5_hex($rand_str2); } add_agent($agentid, $agentname, $ipaddress, $key); } else { warn "Error: adding agents requires: --name and --ip options.\n"; usage(); } } elsif ($remove) { if ($agentid) { remove_agent($agentid); } else { remove_agent($remove) } } elsif ($import) { # Every option needs to be specified and NOT autogenerated because what # is autogenerated on the server and the agent will likely be different if (!$agentid || !$agentname || !$ipaddress || !$key) { warn "Error: importing requires: --id, --name, --ip, and --key\n"; usage(); } else { # The key extracted from the server needs to be decoded before being put # into the client.keys $key = MIME::Base64::decode($key); add_agent($agentid, $agentname, $ipaddress, $key); } } else { warn "Error: no options specified!\n"; usage(); } sub usage { warn "Usage: $0 [OPERATION] [OPTIONS]\n"; warn " [operations]\n"; warn " -a or --add = Add a new agent\n"; warn " -r or --remove [id] = Remove agent\n"; warn " -e or --extract [id] = Extract key\n"; warn " -m or --import [keydata] = Import key\n"; warn " -l or --list = List available agents\n"; warn " [options]\n"; warn " -k or --key [keydata] = Key data\n"; warn " -n or --name [name] = Agent name (32 character max)\n"; warn " -i or --id [id] = Agent identification (integer)\n"; warn " -p or --ip [ip] = IP address\n\n"; exit 1; } sub list_agents { if (-r AUTH_KEY_FILE) { open (FH, "<", AUTH_KEY_FILE); } else { die "Error reading ".AUTH_KEY_FILE.": $!\n"; } print "Available Agents:\n"; print "ID", " " x (25 - length('ID')), "NAME", " " x (25 - length('NAME')), "IP", " " x (25 - length('IP')); print "\n"; while () { chomp; my ($id, $name, $ip, $key) = split; print "$id", " " x (25 - length($id)), "$name", " " x (25 - length($name)), "$ip", " " x (25 - length($ip)) . "\n"; } close(FH); exit 0; } sub extract_key { my $extractid = shift; my ($encoded, $decoded); if (-r AUTH_KEY_FILE) { open (FH, "<", AUTH_KEY_FILE); } else { die "No ".AUTH_KEY_FILE."!\n"; } while () { chomp; my ($id, $name, $ip, $key) = split; if ($id == $extractid) { # Newlines are valid base64 characters so use '' instead for \n $decoded = MIME::Base64::encode($key, ''); print "$decoded\n"; exit 0; } } warn "Error: Agent ID $extractid doesn't exist!\n"; } sub add_agent { my $id = shift; my $name = shift; my $ip = shift; my $agentkey = shift; if ($name && $ip && $agentkey) { # Valid example key: # 5a832efb8f93660857ce2acf8eec66a19fd9d4fa58e3221bbd2927ca8a0b40c3 if ($agentkey !~ m/[a-z0-9]{64}/) { warn "Error: invalid keydata! Let this script autogenerate it.\n"; usage(); } my @newagent = ($id, $name, $ip, $agentkey); my $exists = check_if_exists(\@newagent); if ($exists == 0) { # Append if client.keys exists and create it if it doesn't if (-e AUTH_KEY_FILE) { open(FH, ">>", AUTH_KEY_FILE) or die AUTH_KEY_FILE." error: $!\n"; } else { open(FH, ">", AUTH_KEY_FILE) or die AUTH_KEY_FILE." error: $!\n"; } print FH join(' ', @newagent), "\n"; close(FH); } elsif ($exists == 1) { warn "ID: $id already in ".AUTH_KEY_FILE."!\n"; } elsif ($exists == 2) { warn "Agent: $name already in ".AUTH_KEY_FILE."!\n"; } elsif ($exists == 3) { warn "IP: $ip already in ".AUTH_KEY_FILE."!\n"; } } else { warn "Missing options to --add or problem with ".AUTH_KEY_FILE.": $!\n"; usage(); } } sub remove_agent { my $removeid = shift; my @agent_array; if (-r AUTH_KEY_FILE) { open (FH, "<", AUTH_KEY_FILE); } else { die "Error: with ".AUTH_KEY_FILE.": $!\n"; } while () { push(@agent_array, $_); } close(FH); if (-w AUTH_KEY_FILE) { open (FHRW, ">", AUTH_KEY_FILE); } else { die "Error writing ".AUTH_KEY_FILE.": $!\n"; } foreach my $line (@agent_array) { if ($line !~ $removeid) { print FHRW "$line"; } } close(FHRW); exit 0; } sub check_if_exists { my $agentlist_ref = shift; my ($newid, $newname, $newip); my $rval = 0; $newid = $agentlist_ref->[0]; $newname = $agentlist_ref->[1]; $newip = $agentlist_ref->[2]; # If the file isn't readable, the id probably isn't already in it if (-r AUTH_KEY_FILE) { open (FH, "<", AUTH_KEY_FILE); while () { chomp; my ($id, $name, $ip, $key) = split; $rval = 1 if ($id == $newid && $rval == 0); $rval = 2 if ($name eq $newname && $rval == 0); $rval = 3 if ($ip eq $newip && $rval == 0); } close(FH); } return $rval; }