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 # - corrected a MAJOR logic error in the remove
18 # function. The comparison was being done across the
19 # entire line of the agent keys file, so both IPs
20 # and the SSH keys at the end could be matched against
21 # the 'agent ID' wanting to be removed. Changed the
22 # match to only compare the first column of the file
23 # - added an error output message to the remove
24 # function if it's fed an 'agent ID' that doesn't
26 # - the script now also removes the corresponding
27 # associated agent rid files after a successful remove
28 # operation, or gives an error on failure
30 ##########################################################
31 # Modified by Tim Meader (Timothy.A.Meader@nasa.gov)
34 # - fixed two errors that were popping up during add or
35 # remove operations due to the code not taking into
36 # account the old key entries that have the "#*#*#*"
37 # pattern after the ID number. Simple fix was to do
38 # a "if (defined(xxx))" on the vars
39 # - fixed the "list" operation to only show valid key
41 # - changed the extract operation to store options
42 # in an array, and subsequently rewrote the
43 # "extract_key" (now called "extract_keys") func
44 # to accept this new behavior
45 # - modified "extract_keys" func to accept either ID,
46 # name, or IP address as the argument after the
47 # "-e" operator. Output of key extraction now
48 # include the name and IP address by default in the
49 # format: "name,IP extracted_key"
51 #########################################################
56 # - Add check for ossec 1.4 and support longer agent names
57 # - Add in eval so that older version of perl without
58 # Time::HiRes still can use this script.
62 require 5.8.2; # Time::HiRes is standard from this version forth
65 use Digest::MD5 qw(md5_hex);
67 use Regexp::Common::net;
69 use constant AUTH_KEY_FILE => "/var/ossec/etc/client.keys";
70 use constant RIDS_PATH => "/var/ossec/queue/rids/";
72 my ($key, $add, $remove, @extracts, $import, $listagents);
73 my ($agentid, $agentname, $ipaddress);
76 'k|key=s' => \$key, # Unencoded ssh key
77 'a|add' => \$add, # Add a new agent
78 'r|remove=s' => \$remove, # Remove an agent
79 'e|extract=s' => \@extracts, # Extract a key
80 'm|import' => \$import, # Import a key
81 'l|list' => \$listagents, # List all agents
82 'i|id=s' => \$agentid, # Unique agent id
83 'n|name=s' => \$agentname, # Agent name. 32 char max
84 'p|ip=s' => \$ipaddress # IP Address in "dotted quad" notation
87 # Spit out a list of available agents, their names, and ip information
91 # Decode and extract the key for $agentid
94 extract_keys(@extracts);
102 if ($agentname && $ipaddress &&
104 $ipaddress =~ m/$RE{net}{IPv4}/
106 $ipaddress =~ m/$RE{net}{IPv6}/
110 # ossec doesn't like agent names > 32 characters.
111 length($agentname) <= 32) {
113 # Autogenerate an id incremented 1 from the last in a sorted list of
114 # all current ones if it isn't specified from the command line.
117 # Make a list of all of the used agentids and then sort it.
118 if (-r AUTH_KEY_FILE) {
119 my @used_agent_ids = ();
120 open (FH, "<", AUTH_KEY_FILE);
122 my ($id, $name, $ip, $key) = split;
123 push(@used_agent_ids, $id);
127 if (@used_agent_ids) {
128 @used_agent_ids = sort {$a <=> $b} @used_agent_ids;
129 $agentid = sprintf("%03d", $used_agent_ids[-1] + 1);
132 # If the client.keys is empty or doesn't exist set the id to 001
133 $agentid = sprintf("%03d", 001) if (!$agentid);
136 # Autogenerate a key unless one was specified on the command line
138 use Time::HiRes; # Standard with perl >= 5.8.2
140 my $rand_str1 = time() . $agentname . rand(10000);
141 my $rand_str2 = Time::HiRes::time . $ipaddress . $agentid . rand(10000);
142 $key = md5_hex($rand_str1) . md5_hex($rand_str2);
145 add_agent($agentid, $agentname, $ipaddress, $key);
148 warn "Error: adding agents requires: --name and --ip options.\n";
154 remove_agent($agentid);
157 remove_agent($remove)
161 # Every option needs to be specified and NOT autogenerated because what
162 # is autogenerated on the server and the agent will likely be different
163 if (!$agentid || !$agentname || !$ipaddress || !$key) {
164 warn "Error: importing requires: --id, --name, --ip, and --key\n";
168 # The key extracted from the server needs to be decoded before being put
169 # into the client.keys
170 $key = MIME::Base64::decode($key);
172 add_agent($agentid, $agentname, $ipaddress, $key);
176 warn "Error: no options specified!\n";
181 warn "Usage: $0 [OPERATION] [OPTIONS]\n";
182 warn " [operations]\n";
183 warn " -a or --add = Add a new agent\n";
184 warn " -r or --remove [id] = Remove agent\n";
185 warn " -e or --extract [id|name|ip] = Extract key\n";
186 warn " -m or --import [keydata] = Import key\n";
187 warn " -l or --list = List available agents\n";
189 warn " -k or --key [keydata] = Key data\n";
190 warn " -n or --name [name] = Agent name (32 character max)\n";
191 warn " -i or --id [id] = Agent identification (integer)\n";
192 warn " -p or --ip [ip] = IP address\n\n";
197 if (-r AUTH_KEY_FILE) {
198 open (FH, "<", AUTH_KEY_FILE);
201 die "Error reading ".AUTH_KEY_FILE.": $!\n";
203 print "Available Agents:\n";
204 print "ID", " " x (25 - length('ID')),
205 "NAME", " " x (25 - length('NAME')),
206 "IP", " " x (25 - length('IP'));
210 my ($id, $name, $ip, $key) = split;
212 print "$id", " " x (25 - length($id)),
213 "$name", " " x (25 - length($name)),
214 "$ip", " " x (25 - length($ip)) . "\n";
222 if (-r AUTH_KEY_FILE) {
223 open (FH, "<", AUTH_KEY_FILE);
226 die "No ".AUTH_KEY_FILE."!\n";
229 foreach my $extract (@_) {
230 my ($encoded, $decoded);
235 my ($id, $name, $ip, $key) = split;
236 # Check to make sure it's a valid entry
238 if (($extract =~ /^\d+$/) && ($id == $extract)) {
241 elsif ($name eq $extract) {
244 elsif ($ip eq $extract) {
250 # Newlines are valid base64 characters so use '' instead for \n
251 $decoded = MIME::Base64::encode($_, '');
252 print "$name,$ip $decoded\n";
257 warn "Error: Agent $extract doesn't exist!\n";
267 my $agentkey = shift;
269 if ($name && $ip && $agentkey) {
271 # 5a832efb8f93660857ce2acf8eec66a19fd9d4fa58e3221bbd2927ca8a0b40c3
272 if ($agentkey !~ m/[a-z0-9]{64}/) {
273 warn "Error: invalid keydata! Let this script autogenerate it.\n";
277 my @newagent = ($id, $name, $ip, $agentkey);
278 my $exists = check_if_exists(\@newagent);
281 # Append if client.keys exists and create it if it doesn't
282 if (-e AUTH_KEY_FILE) {
283 open(FH, ">>", AUTH_KEY_FILE) or die AUTH_KEY_FILE." error: $!\n";
286 open(FH, ">", AUTH_KEY_FILE) or die AUTH_KEY_FILE." error: $!\n";
288 print FH join(' ', @newagent), "\n";
291 elsif ($exists == 1) {
292 warn "ID: $id already in ".AUTH_KEY_FILE."!\n";
294 elsif ($exists == 2) {
295 warn "Agent: $name already in ".AUTH_KEY_FILE."!\n";
297 elsif ($exists == 3) {
298 warn "IP: $ip already in ".AUTH_KEY_FILE."!\n";
302 warn "Missing options to --add or problem with ".AUTH_KEY_FILE.": $!\n";
308 my $removeid = shift;
311 if (-r AUTH_KEY_FILE) {
312 open (FH, "<", AUTH_KEY_FILE);
315 die "Error: with ".AUTH_KEY_FILE.": $!\n";
318 push(@agent_array, $_);
322 if (-w AUTH_KEY_FILE) {
323 open (FHRW, ">", AUTH_KEY_FILE);
326 die "Error writing ".AUTH_KEY_FILE.": $!\n";
331 foreach my $line (@agent_array) {
332 my @split_line = split(/\s/,$line);
334 if ($split_line[0] ne $removeid) {
338 my $rids_file = RIDS_PATH.$removeid;
340 unlink $rids_file or warn "Could not remove rids file for Agent ID \'".$removeid."\'!\n";
346 die "Agent ID \'".$removeid."\' not found! Nothing removed.\n";
351 sub check_if_exists {
352 my $agentlist_ref = shift;
353 my ($newid, $newname, $newip);
356 $newid = $agentlist_ref->[0];
357 $newname = $agentlist_ref->[1];
358 $newip = $agentlist_ref->[2];
360 # If the file isn't readable, the id probably isn't already in it
361 if (-r AUTH_KEY_FILE) {
362 open (FH, "<", AUTH_KEY_FILE);
365 my ($id, $name, $ip, $key) = split;
367 $rval = 1 if ($id == $newid && $rval == 0);
368 $rval = 2 if ($name eq $newname && $rval == 0);
369 $rval = 3 if ($ip ne 'any' && $ip eq $newip && $rval == 0);