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);
68 use constant AUTH_KEY_FILE => "/var/ossec/etc/client.keys";
69 use constant RIDS_PATH => "/var/ossec/queue/rids/";
71 my ($key, $add, $remove, @extracts, $import, $listagents);
72 my ($agentid, $agentname, $ipaddress);
75 'k|key=s' => \$key, # Unencoded ssh key
76 'a|add' => \$add, # Add a new agent
77 'r|remove=s' => \$remove, # Remove an agent
78 'e|extract=s' => \@extracts, # Extract a key
79 'm|import' => \$import, # Import a key
80 'l|list' => \$listagents, # List all agents
81 'i|id=s' => \$agentid, # Unique agent id
82 'n|name=s' => \$agentname, # Agent name. 32 char max
83 'p|ip=s' => \$ipaddress # IP Address in "dotted quad" notation
86 # Spit out a list of available agents, their names, and ip information
90 # Decode and extract the key for $agentid
93 extract_keys(@extracts);
101 if ($agentname && $ipaddress && $ipaddress =~
102 m/(1?\d\d?|2[0-4]\d|25[0-5])(\.(1?\d\d?|2[0-4]\d|25[0-5])){3}/ &&
103 # ossec doesn't like agent names > 32 characters.
104 length($agentname) <= 32) {
106 # Autogenerate an id incremented 1 from the last in a sorted list of
107 # all current ones if it isn't specified from the command line.
110 # Make a list of all of the used agentids and then sort it.
111 if (-r AUTH_KEY_FILE) {
112 my @used_agent_ids = ();
113 open (FH, "<", AUTH_KEY_FILE);
115 my ($id, $name, $ip, $key) = split;
116 push(@used_agent_ids, $id);
120 if (@used_agent_ids) {
121 @used_agent_ids = sort(@used_agent_ids);
122 $agentid = sprintf("%03d", $used_agent_ids[-1] + 1);
125 # If the client.keys is empty or doesn't exist set the id to 001
126 $agentid = sprintf("%03d", 001) if (!$agentid);
129 # Autogenerate a key unless one was specified on the command line
131 use Time::HiRes; # Standard with perl >= 5.8.2
133 my $rand_str1 = time() . $agentname . rand(10000);
134 my $rand_str2 = Time::HiRes::time . $ipaddress . $agentid . rand(10000);
135 $key = md5_hex($rand_str1) . md5_hex($rand_str2);
138 add_agent($agentid, $agentname, $ipaddress, $key);
141 warn "Error: adding agents requires: --name and --ip options.\n";
147 remove_agent($agentid);
150 remove_agent($remove)
154 # Every option needs to be specified and NOT autogenerated because what
155 # is autogenerated on the server and the agent will likely be different
156 if (!$agentid || !$agentname || !$ipaddress || !$key) {
157 warn "Error: importing requires: --id, --name, --ip, and --key\n";
161 # The key extracted from the server needs to be decoded before being put
162 # into the client.keys
163 $key = MIME::Base64::decode($key);
165 add_agent($agentid, $agentname, $ipaddress, $key);
169 warn "Error: no options specified!\n";
174 warn "Usage: $0 [OPERATION] [OPTIONS]\n";
175 warn " [operations]\n";
176 warn " -a or --add = Add a new agent\n";
177 warn " -r or --remove [id] = Remove agent\n";
178 warn " -e or --extract [id|name|ip] = Extract key\n";
179 warn " -m or --import [keydata] = Import key\n";
180 warn " -l or --list = List available agents\n";
182 warn " -k or --key [keydata] = Key data\n";
183 warn " -n or --name [name] = Agent name (32 character max)\n";
184 warn " -i or --id [id] = Agent identification (integer)\n";
185 warn " -p or --ip [ip] = IP address\n\n";
190 if (-r AUTH_KEY_FILE) {
191 open (FH, "<", AUTH_KEY_FILE);
194 die "Error reading ".AUTH_KEY_FILE.": $!\n";
196 print "Available Agents:\n";
197 print "ID", " " x (25 - length('ID')),
198 "NAME", " " x (25 - length('NAME')),
199 "IP", " " x (25 - length('IP'));
203 my ($id, $name, $ip, $key) = split;
205 print "$id", " " x (25 - length($id)),
206 "$name", " " x (25 - length($name)),
207 "$ip", " " x (25 - length($ip)) . "\n";
215 if (-r AUTH_KEY_FILE) {
216 open (FH, "<", AUTH_KEY_FILE);
219 die "No ".AUTH_KEY_FILE."!\n";
222 foreach my $extract (@_) {
223 my ($encoded, $decoded);
228 my ($id, $name, $ip, $key) = split;
229 # Check to make sure it's a valid entry
231 if (($extract =~ /^\d+$/) && ($id == $extract)) {
234 elsif ($name eq $extract) {
237 elsif ($ip eq $extract) {
243 # Newlines are valid base64 characters so use '' instead for \n
244 $decoded = MIME::Base64::encode($_, '');
245 print "$name,$ip $decoded\n";
250 warn "Error: Agent $extract doesn't exist!\n";
260 my $agentkey = shift;
262 if ($name && $ip && $agentkey) {
264 # 5a832efb8f93660857ce2acf8eec66a19fd9d4fa58e3221bbd2927ca8a0b40c3
265 if ($agentkey !~ m/[a-z0-9]{64}/) {
266 warn "Error: invalid keydata! Let this script autogenerate it.\n";
270 my @newagent = ($id, $name, $ip, $agentkey);
271 my $exists = check_if_exists(\@newagent);
274 # Append if client.keys exists and create it if it doesn't
275 if (-e AUTH_KEY_FILE) {
276 open(FH, ">>", AUTH_KEY_FILE) or die AUTH_KEY_FILE." error: $!\n";
279 open(FH, ">", AUTH_KEY_FILE) or die AUTH_KEY_FILE." error: $!\n";
281 print FH join(' ', @newagent), "\n";
284 elsif ($exists == 1) {
285 warn "ID: $id already in ".AUTH_KEY_FILE."!\n";
287 elsif ($exists == 2) {
288 warn "Agent: $name already in ".AUTH_KEY_FILE."!\n";
290 elsif ($exists == 3) {
291 warn "IP: $ip already in ".AUTH_KEY_FILE."!\n";
295 warn "Missing options to --add or problem with ".AUTH_KEY_FILE.": $!\n";
301 my $removeid = shift;
304 if (-r AUTH_KEY_FILE) {
305 open (FH, "<", AUTH_KEY_FILE);
308 die "Error: with ".AUTH_KEY_FILE.": $!\n";
311 push(@agent_array, $_);
315 if (-w AUTH_KEY_FILE) {
316 open (FHRW, ">", AUTH_KEY_FILE);
319 die "Error writing ".AUTH_KEY_FILE.": $!\n";
324 foreach my $line (@agent_array) {
325 my @split_line = split(/\s/,$line);
327 if ($split_line[0] ne $removeid) {
331 my $rids_file = RIDS_PATH.$removeid;
333 unlink $rids_file or warn "Could not remove rids file for Agent ID \'".$removeid."\'!\n";
339 die "Agent ID \'".$removeid."\' not found! Nothing removed.\n";
344 sub check_if_exists {
345 my $agentlist_ref = shift;
346 my ($newid, $newname, $newip);
349 $newid = $agentlist_ref->[0];
350 $newname = $agentlist_ref->[1];
351 $newip = $agentlist_ref->[2];
353 # If the file isn't readable, the id probably isn't already in it
354 if (-r AUTH_KEY_FILE) {
355 open (FH, "<", AUTH_KEY_FILE);
358 my ($id, $name, $ip, $key) = split;
360 $rval = 1 if ($id == $newid && $rval == 0);
361 $rval = 2 if ($name eq $newname && $rval == 0);
362 $rval = 3 if ($ip eq $newip && $rval == 0);