2 # vim:shiftwidth=2:tabstop=2:expandtab:textwidth=80:softtabstop=2:ai:
4 #########################################################
5 # Written Aug 4, 2007 and released under the GNU/GPLv2 ##
6 # by Jeff Schroeder (jeffschroeder@computer.org) # #
7 ######################################################### #
9 # ossec-batch-manager.pl - Add and extract agents from # #
10 # the ossec client.keys file non-interactively. This # #
11 # started as a hack to properly script manage_agents. # #
13 ##########################################################
14 # Modified by Tim Meader (Timothy.A.Meader@nasa.gov)
17 # - fixed two errors that were popping up during add or
18 # remove operations due to the code not taking into
19 # account the old key entries that have the "#*#*#*"
20 # pattern after the ID number. Simple fix was to do
21 # a "if (defined(xxx))" on the vars
22 # - fixed the "list" operation to only show valid key
24 # - changed the extract operation to store options
25 # in an array, and subsequently rewrote the
26 # "extract_key" (now called "extract_keys") func
27 # to accept this new behavior
28 # - modified "extract_keys" func to accept either ID,
29 # name, or IP address as the argument after the
30 # "-e" operator. Output of key extraction now
31 # includes the name and IP address by default in the
32 # format: "name,IP extracted_key"
34 #########################################################
39 # - Add check for ossec 1.4 and support longer agent names
40 # - Add in eval so that older version of perl without
41 # Time::HiRes still can use this script.
45 require 5.8.2; # Time::HiRes is standard from this version forth
48 use Digest::MD5 qw(md5_hex);
51 use constant AUTH_KEY_FILE => "/var/ossec/etc/client.keys";
53 my ($key, $add, $remove, @extracts, $import, $listagents);
54 my ($agentid, $agentname, $ipaddress);
57 'k|key=s' => \$key, # Unencoded ssh key
58 'a|add' => \$add, # Add a new agent
59 'r|remove=s' => \$remove, # Remove an agent
60 'e|extract=s' => \@extracts, # Extract a key
61 'm|import' => \$import, # Import a key
62 'l|list' => \$listagents, # List all agents
63 'i|id=s' => \$agentid, # Unique agent id
64 'n|name=s' => \$agentname, # Agent name. 32 char max
65 'p|ip=s' => \$ipaddress # IP Address in "dotted quad" notation
68 # Spit out a list of available agents, their names, and ip information
72 # Decode and extract the key for $agentid
75 extract_keys(@extracts);
83 if ($agentname && $ipaddress && $ipaddress =~
84 m/(1?\d\d?|2[0-4]\d|25[0-5])(\.(1?\d\d?|2[0-4]\d|25[0-5])){3}/ &&
85 # ossec doesn't like agent names > 32 characters.
86 length($agentname) <= 32) {
88 # Autogenerate an id incremented 1 from the last in a sorted list of
89 # all current ones if it isn't specified from the command line.
92 # Make a list of all of the used agentids and then sort it.
93 if (-r AUTH_KEY_FILE) {
94 my @used_agent_ids = ();
95 open (FH, "<", AUTH_KEY_FILE);
97 my ($id, $name, $ip, $key) = split;
98 push(@used_agent_ids, $id);
102 if (@used_agent_ids) {
103 @used_agent_ids = sort { $a <=> $b } @used_agent_ids;
104 $agentid = sprintf("%03d", $used_agent_ids[-1] + 1);
107 # If the client.keys is empty or doesn't exist set the id to 001
108 $agentid = sprintf("%03d", 001) if (!$agentid);
111 # Autogenerate a key unless one was specified on the command line
113 use Time::HiRes; # Standard with perl >= 5.8.2
115 my $rand_str1 = time() . $agentname . rand(10000);
116 my $rand_str2 = Time::HiRes::time . $ipaddress . $agentid . rand(10000);
117 $key = md5_hex($rand_str1) . md5_hex($rand_str2);
120 add_agent($agentid, $agentname, $ipaddress, $key);
123 warn "Error: adding agents requires: --name and --ip options.\n";
129 remove_agent($agentid);
132 remove_agent($remove)
136 # Every option needs to be specified and NOT autogenerated because what
137 # is autogenerated on the server and the agent will likely be different
138 if (!$agentid || !$agentname || !$ipaddress || !$key) {
139 warn "Error: importing requires: --id, --name, --ip, and --key\n";
143 # The key extracted from the server needs to be decoded before being put
144 # into the client.keys
145 $key = MIME::Base64::decode($key);
147 add_agent($agentid, $agentname, $ipaddress, $key);
151 warn "Error: no options specified!\n";
156 warn "Usage: $0 [OPERATION] [OPTIONS]\n";
157 warn " [operations]\n";
158 warn " -a or --add = Add a new agent\n";
159 warn " -r or --remove [id] = Remove agent\n";
160 warn " -e or --extract [id|name|ip] = Extract key\n";
161 warn " -m or --import [keydata] = Import key\n";
162 warn " -l or --list = List available agents\n";
164 warn " -k or --key [keydata] = Key data\n";
165 warn " -n or --name [name] = Agent name (32 character max)\n";
166 warn " -i or --id [id] = Agent identification (integer)\n";
167 warn " -p or --ip [ip] = IP address\n\n";
172 if (-r AUTH_KEY_FILE) {
173 open (FH, "<", AUTH_KEY_FILE);
176 die "Error reading ".AUTH_KEY_FILE.": $!\n";
178 print "Available Agents:\n";
179 print "ID", " " x (25 - length('ID')),
180 "NAME", " " x (25 - length('NAME')),
181 "IP", " " x (25 - length('IP'));
185 my ($id, $name, $ip, $key) = split;
187 print "$id", " " x (25 - length($id)),
188 "$name", " " x (25 - length($name)),
189 "$ip", " " x (25 - length($ip)) . "\n";
197 if (-r AUTH_KEY_FILE) {
198 open (FH, "<", AUTH_KEY_FILE);
201 die "No ".AUTH_KEY_FILE."!\n";
204 foreach my $extract (@_) {
205 my ($encoded, $decoded);
210 my ($id, $name, $ip, $key) = split;
211 # Check to make sure it's a valid entry
213 if (($extract =~ /^\d+$/) && ($id == $extract)) {
216 elsif ($name eq $extract) {
219 elsif ($ip eq $extract) {
225 # Newlines are valid base64 characters so use '' instead for \n
226 $decoded = MIME::Base64::encode($_, '');
227 print "$name,$ip $decoded\n";
232 warn "Error: Agent $extract doesn't exist!\n";
242 my $agentkey = shift;
244 if ($name && $ip && $agentkey) {
246 # 5a832efb8f93660857ce2acf8eec66a19fd9d4fa58e3221bbd2927ca8a0b40c3
247 if ($agentkey !~ m/[a-z0-9]{64}/) {
248 warn "Error: invalid keydata! Let this script autogenerate it.\n";
252 my @newagent = ($id, $name, $ip, $agentkey);
253 my $exists = check_if_exists(\@newagent);
256 # Append if client.keys exists and create it if it doesn't
257 if (-e AUTH_KEY_FILE) {
258 open(FH, ">>", AUTH_KEY_FILE) or die AUTH_KEY_FILE." error: $!\n";
261 open(FH, ">", AUTH_KEY_FILE) or die AUTH_KEY_FILE." error: $!\n";
263 print FH join(' ', @newagent), "\n";
266 elsif ($exists == 1) {
267 warn "ID: $id already in ".AUTH_KEY_FILE."!\n";
269 elsif ($exists == 2) {
270 warn "Agent: $name already in ".AUTH_KEY_FILE."!\n";
272 elsif ($exists == 3) {
273 warn "IP: $ip already in ".AUTH_KEY_FILE."!\n";
277 warn "Missing options to --add or problem with ".AUTH_KEY_FILE.": $!\n";
283 my $removeid = shift;
286 if (-r AUTH_KEY_FILE) {
287 open (FH, "<", AUTH_KEY_FILE);
290 die "Error: with ".AUTH_KEY_FILE.": $!\n";
293 push(@agent_array, $_);
297 if (-w AUTH_KEY_FILE) {
298 open (FHRW, ">", AUTH_KEY_FILE);
301 die "Error writing ".AUTH_KEY_FILE.": $!\n";
303 foreach my $line (@agent_array) {
304 if ($line !~ $removeid) {
312 sub check_if_exists {
313 my $agentlist_ref = shift;
314 my ($newid, $newname, $newip);
317 $newid = $agentlist_ref->[0];
318 $newname = $agentlist_ref->[1];
319 $newip = $agentlist_ref->[2];
321 # If the file isn't readable, the id probably isn't already in it
322 if (-r AUTH_KEY_FILE) {
323 open (FH, "<", AUTH_KEY_FILE);
326 my ($id, $name, $ip, $key) = split;
328 $rval = 1 if ($id == $newid && $rval == 0);
329 $rval = 2 if ($name eq $newname && $rval == 0);
330 $rval = 3 if ($ip eq $newip && $rval == 0);