izmjene licence
[ossec-hids.git] / contrib / ossec-batch-manager.pl
1 #!/usr/bin/perl
2 # vim:shiftwidth=2:tabstop=2:expandtab:textwidth=80:softtabstop=2:ai:
3
4    #########################################################
5   # Written Aug 4, 2007 and released under the GNU/GPLv2  ##
6  # by Jeff Schroeder (jeffschroeder@computer.org)        # #
7 #########################################################  #
8 #                                                       #  #
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.   #  #
12 #                                                       # #
13 ##########################################################
14 # Modified by Tim Meader (Timothy.A.Meader@nasa.gov)
15 # on 2013/07/01
16 #
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
25 #   exist
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
29 #            
30 ##########################################################
31 # Modified by Tim Meader (Timothy.A.Meader@nasa.gov)
32 # on 2010/12/08
33 #            
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
40 #   entries
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"
50 #
51 #########################################################
52
53
54 #$Id$
55 # TODO:
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.
59
60 use strict;
61 use warnings;
62 require 5.8.2; # Time::HiRes is standard from this version forth
63 #use diagnostics;
64 use MIME::Base64;
65 use Digest::MD5 qw(md5_hex);
66 use Getopt::Long;
67
68 use constant AUTH_KEY_FILE => "/var/ossec/etc/client.keys";
69 use constant RIDS_PATH => "/var/ossec/queue/rids/";
70
71 my ($key, $add, $remove, @extracts, $import, $listagents);
72 my ($agentid, $agentname, $ipaddress);
73
74 GetOptions(
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
84 );
85
86 # Spit out a list of available agents, their names, and ip information
87 if ($listagents) {
88   list_agents();
89 }
90 # Decode and extract the key for $agentid
91 elsif (@extracts) {
92   if (@extracts) {
93     extract_keys(@extracts);
94   }
95   else {
96     usage();
97   }
98 }
99 # Adding a new agent
100 elsif ($add) {
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) {
105
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.
108       if (!$agentid) {
109
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);
114           while (<FH>) {
115             my ($id, $name, $ip, $key) = split;
116             push(@used_agent_ids, $id);
117           }
118           close(FH);
119
120           if (@used_agent_ids) {
121             @used_agent_ids = sort(@used_agent_ids);
122             $agentid = sprintf("%03d", $used_agent_ids[-1] + 1);
123           }
124         }
125         # If the client.keys is empty or doesn't exist set the id to 001
126         $agentid = sprintf("%03d", 001) if (!$agentid);
127         }
128
129     # Autogenerate a key unless one was specified on the command line
130     if (!$key) {
131       use Time::HiRes; # Standard with perl >= 5.8.2
132
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);
136     }
137       
138     add_agent($agentid, $agentname, $ipaddress, $key);
139   }
140   else {
141     warn "Error: adding agents requires: --name and --ip options.\n";
142     usage();
143   }
144 }
145 elsif ($remove) {
146   if ($agentid) {
147     remove_agent($agentid);
148   }
149   else {
150     remove_agent($remove)
151   }
152 }
153 elsif ($import) {
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";
158     usage();
159   }
160   else {
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);
164
165     add_agent($agentid, $agentname, $ipaddress, $key);
166   }
167 }
168 else {
169   warn "Error: no options specified!\n";
170   usage();
171 }
172
173 sub usage {
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";
181   warn "  [options]\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";
186   exit 1;
187 }
188
189 sub list_agents {
190   if (-r AUTH_KEY_FILE) {
191     open (FH, "<", AUTH_KEY_FILE);
192   }
193   else {
194     die "Error reading ".AUTH_KEY_FILE.": $!\n";
195   }
196   print "Available Agents:\n";
197   print "ID",     " " x (25 - length('ID')),
198         "NAME",   " " x (25 - length('NAME')),
199         "IP",     " " x (25 - length('IP'));
200   print "\n";
201   while (<FH>) {
202     chomp;
203     my ($id, $name, $ip, $key) = split;
204     if (defined($key)) {
205       print "$id",    " " x (25 - length($id)),
206             "$name",  " " x (25 - length($name)),
207             "$ip",    " " x (25 - length($ip)) . "\n";
208     }
209   }
210   close(FH);
211   exit 0;
212 }
213
214 sub extract_keys {
215   if (-r AUTH_KEY_FILE) {
216     open (FH, "<", AUTH_KEY_FILE);
217   }
218   else {
219     die "No ".AUTH_KEY_FILE."!\n";
220   }
221   
222   foreach my $extract (@_) {
223     my ($encoded, $decoded);
224     my $found = 0;
225
226     while (<FH>) {
227       chomp;
228       my ($id, $name, $ip, $key) = split;
229       # Check to make sure it's a valid entry
230       if (defined($key)) {
231         if (($extract =~ /^\d+$/) && ($id == $extract)) {
232           $found = 1;
233         }
234         elsif ($name eq $extract) {
235           $found = 1;
236         }
237         elsif ($ip eq $extract) {
238           $found = 1;
239         }
240         else {
241           next;
242         }
243         # Newlines are valid base64 characters so use '' instead for \n
244         $decoded = MIME::Base64::encode($_, '');
245         print "$name,$ip  $decoded\n";
246         next;
247       }
248     }
249     if (!$found) {
250       warn "Error: Agent $extract doesn't exist!\n";
251     }
252     seek FH,0,0;
253   }
254 }
255
256 sub add_agent {
257   my $id = shift;
258   my $name = shift;
259   my $ip = shift;
260   my $agentkey = shift;
261
262   if ($name && $ip && $agentkey) {
263     # Valid example key:
264     # 5a832efb8f93660857ce2acf8eec66a19fd9d4fa58e3221bbd2927ca8a0b40c3
265     if ($agentkey !~ m/[a-z0-9]{64}/) { 
266       warn "Error: invalid keydata! Let this script autogenerate it.\n";
267       usage();
268     }
269
270     my @newagent = ($id, $name, $ip, $agentkey);
271     my $exists = check_if_exists(\@newagent);
272
273     if ($exists == 0) {
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";
277       }
278       else {
279         open(FH, ">", AUTH_KEY_FILE) or die AUTH_KEY_FILE." error: $!\n";
280       }
281       print FH join(' ', @newagent), "\n";
282       close(FH);
283     }
284     elsif ($exists == 1) {
285       warn "ID: $id already in ".AUTH_KEY_FILE."!\n";
286     }
287     elsif ($exists == 2) {
288       warn "Agent: $name already in ".AUTH_KEY_FILE."!\n";
289     }
290     elsif ($exists == 3) {
291       warn "IP: $ip already in ".AUTH_KEY_FILE."!\n";
292     }
293   }
294   else {
295     warn "Missing options to --add or problem with ".AUTH_KEY_FILE.": $!\n";
296     usage();
297   }
298 }
299
300 sub remove_agent {
301   my $removeid = shift;
302   my @agent_array;
303
304   if (-r AUTH_KEY_FILE) {
305     open (FH, "<", AUTH_KEY_FILE);
306   }
307   else {
308     die "Error: with ".AUTH_KEY_FILE.": $!\n";
309   }
310   while (<FH>) {
311     push(@agent_array, $_);
312   }
313   close(FH);
314
315   if (-w AUTH_KEY_FILE) {
316     open (FHRW, ">", AUTH_KEY_FILE);
317   }
318   else {
319     die "Error writing ".AUTH_KEY_FILE.": $!\n";
320   }
321
322   my $key_found = 0;
323
324   foreach my $line (@agent_array) {
325     my @split_line = split(/\s/,$line);
326
327     if ($split_line[0] ne $removeid) {
328       print FHRW "$line";
329     }
330     else {
331       my $rids_file = RIDS_PATH.$removeid;
332       $key_found = 1;
333       unlink $rids_file or warn "Could not remove rids file for Agent ID \'".$removeid."\'!\n";
334     }
335   }
336   close(FHRW);
337
338   if (!$key_found) {
339     die "Agent ID \'".$removeid."\' not found! Nothing removed.\n";
340   }
341   exit(0);
342 }
343
344 sub check_if_exists {
345   my $agentlist_ref = shift;
346   my ($newid, $newname, $newip);
347   my $rval = 0;
348
349   $newid = $agentlist_ref->[0];
350   $newname = $agentlist_ref->[1];
351   $newip = $agentlist_ref->[2];
352
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);
356     while (<FH>) {
357       chomp;
358       my ($id, $name, $ip, $key) = split;
359       if(defined($key)) {
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);
363       }
364     }
365     close(FH);
366   }
367   return $rval;
368 }