Imported Upstream version 2.7
[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 2010/12/08
16 #            
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
23 #   entries
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"
33 #
34 #########################################################
35
36
37 #$Id$
38 # TODO:
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.
42
43 use strict;
44 use warnings;
45 require 5.8.2; # Time::HiRes is standard from this version forth
46 #use diagnostics;
47 use MIME::Base64;
48 use Digest::MD5 qw(md5_hex);
49 use Getopt::Long;
50
51 use constant AUTH_KEY_FILE => "/var/ossec/etc/client.keys";
52
53 my ($key, $add, $remove, @extracts, $import, $listagents);
54 my ($agentid, $agentname, $ipaddress);
55
56 GetOptions(
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
66 );
67
68 # Spit out a list of available agents, their names, and ip information
69 if ($listagents) {
70   list_agents();
71 }
72 # Decode and extract the key for $agentid
73 elsif (@extracts) {
74   if (@extracts) {
75     extract_keys(@extracts);
76   }
77   else {
78     usage();
79   }
80 }
81 # Adding a new agent
82 elsif ($add) {
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) {
87
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.
90       if (!$agentid) {
91
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);
96           while (<FH>) {
97             my ($id, $name, $ip, $key) = split;
98             push(@used_agent_ids, $id);
99           }
100           close(FH);
101
102           if (@used_agent_ids) {
103             @used_agent_ids = sort { $a <=> $b } @used_agent_ids;
104             $agentid = sprintf("%03d", $used_agent_ids[-1] + 1);
105           }
106         }
107         # If the client.keys is empty or doesn't exist set the id to 001
108         $agentid = sprintf("%03d", 001) if (!$agentid);
109         }
110
111     # Autogenerate a key unless one was specified on the command line
112     if (!$key) {
113       use Time::HiRes; # Standard with perl >= 5.8.2
114
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);
118     }
119       
120     add_agent($agentid, $agentname, $ipaddress, $key);
121   }
122   else {
123     warn "Error: adding agents requires: --name and --ip options.\n";
124     usage();
125   }
126 }
127 elsif ($remove) {
128   if ($agentid) {
129     remove_agent($agentid);
130   }
131   else {
132     remove_agent($remove)
133   }
134 }
135 elsif ($import) {
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";
140     usage();
141   }
142   else {
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);
146
147     add_agent($agentid, $agentname, $ipaddress, $key);
148   }
149 }
150 else {
151   warn "Error: no options specified!\n";
152   usage();
153 }
154
155 sub usage {
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";
163   warn "  [options]\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";
168   exit 1;
169 }
170
171 sub list_agents {
172   if (-r AUTH_KEY_FILE) {
173     open (FH, "<", AUTH_KEY_FILE);
174   }
175   else {
176     die "Error reading ".AUTH_KEY_FILE.": $!\n";
177   }
178   print "Available Agents:\n";
179   print "ID",     " " x (25 - length('ID')),
180         "NAME",   " " x (25 - length('NAME')),
181         "IP",     " " x (25 - length('IP'));
182   print "\n";
183   while (<FH>) {
184     chomp;
185     my ($id, $name, $ip, $key) = split;
186     if (defined($key)) {
187       print "$id",    " " x (25 - length($id)),
188             "$name",  " " x (25 - length($name)),
189             "$ip",    " " x (25 - length($ip)) . "\n";
190     }
191   }
192   close(FH);
193   exit 0;
194 }
195
196 sub extract_keys {
197   if (-r AUTH_KEY_FILE) {
198     open (FH, "<", AUTH_KEY_FILE);
199   }
200   else {
201     die "No ".AUTH_KEY_FILE."!\n";
202   }
203   
204   foreach my $extract (@_) {
205     my ($encoded, $decoded);
206     my $found = 0;
207
208     while (<FH>) {
209       chomp;
210       my ($id, $name, $ip, $key) = split;
211       # Check to make sure it's a valid entry
212       if (defined($key)) {
213         if (($extract =~ /^\d+$/) && ($id == $extract)) {
214           $found = 1;
215         }
216         elsif ($name eq $extract) {
217           $found = 1;
218         }
219         elsif ($ip eq $extract) {
220           $found = 1;
221         }
222         else {
223           next;
224         }
225         # Newlines are valid base64 characters so use '' instead for \n
226         $decoded = MIME::Base64::encode($_, '');
227         print "$name,$ip  $decoded\n";
228         next;
229       }
230     }
231     if (!$found) {
232       warn "Error: Agent $extract doesn't exist!\n";
233     }
234     seek FH,0,0;
235   }
236 }
237
238 sub add_agent {
239   my $id = shift;
240   my $name = shift;
241   my $ip = shift;
242   my $agentkey = shift;
243
244   if ($name && $ip && $agentkey) {
245     # Valid example key:
246     # 5a832efb8f93660857ce2acf8eec66a19fd9d4fa58e3221bbd2927ca8a0b40c3
247     if ($agentkey !~ m/[a-z0-9]{64}/) { 
248       warn "Error: invalid keydata! Let this script autogenerate it.\n";
249       usage();
250     }
251
252     my @newagent = ($id, $name, $ip, $agentkey);
253     my $exists = check_if_exists(\@newagent);
254
255     if ($exists == 0) {
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";
259       }
260       else {
261         open(FH, ">", AUTH_KEY_FILE) or die AUTH_KEY_FILE." error: $!\n";
262       }
263       print FH join(' ', @newagent), "\n";
264       close(FH);
265     }
266     elsif ($exists == 1) {
267       warn "ID: $id already in ".AUTH_KEY_FILE."!\n";
268     }
269     elsif ($exists == 2) {
270       warn "Agent: $name already in ".AUTH_KEY_FILE."!\n";
271     }
272     elsif ($exists == 3) {
273       warn "IP: $ip already in ".AUTH_KEY_FILE."!\n";
274     }
275   }
276   else {
277     warn "Missing options to --add or problem with ".AUTH_KEY_FILE.": $!\n";
278     usage();
279   }
280 }
281
282 sub remove_agent {
283   my $removeid = shift;
284   my @agent_array;
285
286   if (-r AUTH_KEY_FILE) {
287     open (FH, "<", AUTH_KEY_FILE);
288   }
289   else {
290     die "Error: with ".AUTH_KEY_FILE.": $!\n";
291   }
292   while (<FH>) {
293     push(@agent_array, $_);
294   }
295   close(FH);
296
297   if (-w AUTH_KEY_FILE) {
298     open (FHRW, ">", AUTH_KEY_FILE);
299   }
300   else {
301     die "Error writing ".AUTH_KEY_FILE.": $!\n";
302   }
303   foreach my $line (@agent_array) {
304     if ($line !~ $removeid) {
305       print FHRW "$line";
306     }
307   }
308   close(FHRW);
309   exit 0;
310 }
311
312 sub check_if_exists {
313   my $agentlist_ref = shift;
314   my ($newid, $newname, $newip);
315   my $rval = 0;
316
317   $newid = $agentlist_ref->[0];
318   $newname = $agentlist_ref->[1];
319   $newip = $agentlist_ref->[2];
320
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);
324     while (<FH>) {
325       chomp;
326       my ($id, $name, $ip, $key) = split;
327       if(defined($key)) {
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);
331       }
332     }
333     close(FH);
334   }
335   return $rval;
336 }