6564298915d13b3069510969dd36ae113f390dd1
[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
15 #$Id$
16 # TODO:
17 #       - Add check for ossec 1.4 and support longer agent names
18 #       - Add in eval so that older version of perl without
19 #         Time::HiRes still can use this script.
20
21 use strict;
22 use warnings;
23 require 5.8.2; # Time::HiRes is standard from this version forth
24 #use diagnostics;
25 use MIME::Base64;
26 use Digest::MD5 qw(md5_hex);
27 use Getopt::Long;
28
29 use constant AUTH_KEY_FILE => "/var/ossec/etc/client.keys";
30
31 my ($key, $add, $remove, $extract, $import, $listagents);
32 my ($agentid, $agentname, $ipaddress);
33
34 GetOptions(
35   'k|key=s'     => \$key,         # Unencoded ssh key
36   'a|add'       => \$add,         # Add a new agent
37   'r|remove=s'  => \$remove,      # Remove an agent
38   'e|extract=s' => \$extract,     # Extract a key
39   'm|import'    => \$import,      # Import a key
40   'l|list'      => \$listagents,  # List all agents
41   'i|id=s'      => \$agentid,     # Unique agent id
42   'n|name=s'    => \$agentname,   # Agent name. 32 char max
43   'p|ip=s'      => \$ipaddress    # IP Address in "dotted quad" notation
44 );
45
46 # Spit out a list of available agents, their names, and ip information
47 if ($listagents) {
48   list_agents();
49 }
50 # Decode and extract the key for $agentid
51 elsif ($extract) {
52   $agentid = $extract;
53   if ($agentid) {
54     extract_key($agentid);
55   }
56   else {
57     usage();
58   }
59 }
60 # Adding a new agent
61 elsif ($add) {
62   if ($agentname && $ipaddress && $ipaddress =~
63       m/(1?\d\d?|2[0-4]\d|25[0-5])(\.(1?\d\d?|2[0-4]\d|25[0-5])){3}/ &&
64       # ossec doesn't like agent names > 32 characters.
65       length($agentname) <= 32) {
66
67       # Autogenerate an id incremented 1 from the last in a sorted list of
68       # all current ones if it isn't specified from the command line.
69       if (!$agentid) {
70
71         # Make a list of all of the used agentids and then sort it.
72         if (-r AUTH_KEY_FILE) {
73           my @used_agent_ids = ();
74           open (FH, "<", AUTH_KEY_FILE);
75           while (<FH>) {
76             my ($id, $name, $ip, $key) = split;
77             push(@used_agent_ids, $id);
78           }
79           close(FH);
80
81           if (@used_agent_ids) {
82             @used_agent_ids = sort(@used_agent_ids);
83             $agentid = sprintf("%03d", $used_agent_ids[-1] + 1);
84           }
85         }
86         # If the client.keys is empty or doesn't exist set the id to 001
87         $agentid = sprintf("%03d", 001) if (!$agentid);
88         }
89
90     # Autogenerate a key unless one was specified on the command line
91     if (!$key) {
92       use Time::HiRes; # Standard with perl >= 5.8.2
93
94       my $rand_str1 = time() . $agentname . rand(10000);
95       my $rand_str2 = Time::HiRes::time . $ipaddress . $agentid . rand(10000);
96       $key = md5_hex($rand_str1) . md5_hex($rand_str2);
97     }
98       
99     add_agent($agentid, $agentname, $ipaddress, $key);
100   }
101   else {
102     warn "Error: adding agents requires: --name and --ip options.\n";
103     usage();
104   }
105 }
106 elsif ($remove) {
107   if ($agentid) {
108     remove_agent($agentid);
109   }
110   else {
111     remove_agent($remove)
112   }
113 }
114 elsif ($import) {
115   # Every option needs to be specified and NOT autogenerated because what
116   # is autogenerated on the server and the agent will likely be different
117   if (!$agentid || !$agentname || !$ipaddress || !$key) {
118     warn "Error: importing requires: --id, --name, --ip, and --key\n";
119     usage();
120   }
121   else {
122     # The key extracted from the server needs to be decoded before being put
123     # into the client.keys 
124     $key = MIME::Base64::decode($key);
125
126     add_agent($agentid, $agentname, $ipaddress, $key);
127   }
128 }
129 else {
130   warn "Error: no options specified!\n";
131   usage();
132 }
133
134 sub usage {
135   warn "Usage: $0 [OPERATION] [OPTIONS]\n";
136   warn "  [operations]\n";
137   warn "    -a or --add               = Add a new agent\n";
138   warn "    -r or --remove  [id]      = Remove agent\n";
139   warn "    -e or --extract [id]      = Extract key\n";
140   warn "    -m or --import  [keydata] = Import key\n";
141   warn "    -l or --list              = List available agents\n";
142   warn "  [options]\n";
143   warn "    -k or --key     [keydata] = Key data\n";
144   warn "    -n or --name    [name]    = Agent name (32 character max)\n";
145   warn "    -i or --id      [id]      = Agent identification (integer)\n";
146   warn "    -p or --ip      [ip]      = IP address\n\n";
147   exit 1;
148 }
149
150 sub list_agents {
151   if (-r AUTH_KEY_FILE) {
152     open (FH, "<", AUTH_KEY_FILE);
153   }
154   else {
155     die "Error reading ".AUTH_KEY_FILE.": $!\n";
156   }
157   print "Available Agents:\n";
158   print "ID",     " " x (25 - length('ID')),
159         "NAME",   " " x (25 - length('NAME')),
160         "IP",     " " x (25 - length('IP'));
161   print "\n";
162   while (<FH>) {
163     chomp;
164     my ($id, $name, $ip, $key) = split;
165     print "$id",    " " x (25 - length($id)),
166           "$name",  " " x (25 - length($name)),
167           "$ip",    " " x (25 - length($ip)) . "\n";
168   }
169   close(FH);
170   exit 0;
171 }
172
173 sub extract_key {
174   my $extractid = shift;
175   my ($encoded, $decoded);
176
177   if (-r AUTH_KEY_FILE) {
178     open (FH, "<", AUTH_KEY_FILE);
179   }
180   else {
181     die "No ".AUTH_KEY_FILE."!\n";
182   }
183   while (<FH>) {
184     chomp;
185     my ($id, $name, $ip, $key) = split;
186     if ($id == $extractid) {
187       # Newlines are valid base64 characters so use '' instead for \n
188       $decoded = MIME::Base64::encode($_, '');
189       print "$decoded\n";
190       exit 0;
191     }
192   }
193   warn "Error: Agent ID $extractid doesn't exist!\n";
194 }
195
196 sub add_agent {
197   my $id = shift;
198   my $name = shift;
199   my $ip = shift;
200   my $agentkey = shift;
201
202   if ($name && $ip && $agentkey) {
203     # Valid example key:
204     # 5a832efb8f93660857ce2acf8eec66a19fd9d4fa58e3221bbd2927ca8a0b40c3
205     if ($agentkey !~ m/[a-z0-9]{64}/) { 
206       warn "Error: invalid keydata! Let this script autogenerate it.\n";
207       usage();
208     }
209
210     my @newagent = ($id, $name, $ip, $agentkey);
211     my $exists = check_if_exists(\@newagent);
212
213     if ($exists == 0) {
214       # Append if client.keys exists and create it if it doesn't
215       if (-e AUTH_KEY_FILE) {
216         open(FH, ">>", AUTH_KEY_FILE) or die AUTH_KEY_FILE." error: $!\n";
217       }
218       else {
219         open(FH, ">", AUTH_KEY_FILE) or die AUTH_KEY_FILE." error: $!\n";
220       }
221       print FH join(' ', @newagent), "\n";
222       close(FH);
223     }
224     elsif ($exists == 1) {
225       warn "ID: $id already in ".AUTH_KEY_FILE."!\n";
226     }
227     elsif ($exists == 2) {
228       warn "Agent: $name already in ".AUTH_KEY_FILE."!\n";
229     }
230     elsif ($exists == 3) {
231       warn "IP: $ip already in ".AUTH_KEY_FILE."!\n";
232     }
233   }
234   else {
235     warn "Missing options to --add or problem with ".AUTH_KEY_FILE.": $!\n";
236     usage();
237   }
238 }
239
240 sub remove_agent {
241   my $removeid = shift;
242   my @agent_array;
243
244   if (-r AUTH_KEY_FILE) {
245     open (FH, "<", AUTH_KEY_FILE);
246   }
247   else {
248     die "Error: with ".AUTH_KEY_FILE.": $!\n";
249   }
250   while (<FH>) {
251     push(@agent_array, $_);
252   }
253   close(FH);
254
255   if (-w AUTH_KEY_FILE) {
256     open (FHRW, ">", AUTH_KEY_FILE);
257   }
258   else {
259     die "Error writing ".AUTH_KEY_FILE.": $!\n";
260   }
261   foreach my $line (@agent_array) {
262     if ($line !~ $removeid) {
263       print FHRW "$line";
264     }
265   }
266   close(FHRW);
267   exit 0;
268 }
269
270 sub check_if_exists {
271   my $agentlist_ref = shift;
272   my ($newid, $newname, $newip);
273   my $rval = 0;
274
275   $newid = $agentlist_ref->[0];
276   $newname = $agentlist_ref->[1];
277   $newip = $agentlist_ref->[2];
278
279   # If the file isn't readable, the id probably isn't already in it
280   if (-r AUTH_KEY_FILE) {
281     open (FH, "<", AUTH_KEY_FILE);
282     while (<FH>) {
283       chomp;
284       my ($id, $name, $ip, $key) = split;
285       $rval = 1 if ($id == $newid && $rval == 0);
286       $rval = 2 if ($name eq $newname && $rval == 0); 
287       $rval = 3 if ($ip eq $newip && $rval == 0);
288     }
289     close(FH);
290   }
291   return $rval;
292 }