edce8575144c1d006d3064a1fba942ba11c9d1ff
1 package ArpHandler;
3 use Exporter;
4 @ISA = ("Exporter");
6 use strict;
7 use warnings;
8 use GOSA::GosaSupportDaemon;
9 use POSIX;
10 use Fcntl;
11 use Net::LDAP;
12 use Net::LDAP::LDIF;
13 use Net::LDAP::Entry;
14 use Net::DNS;
15 use Switch;
16 use Data::Dumper;
18 # Don't start if some of the modules are missing
19 my $start_service=1;
20 my $lookup_vendor=1;
21 BEGIN{
22 unless(eval('use Socket qw(PF_INET SOCK_DGRAM inet_ntoa sockaddr_in)')) {
23 $start_service=0;
24 }
25 unless(eval('use POE qw(Component::Pcap Component::ArpWatch)')) {
26 $start_service=0;
27 }
28 unless(eval('use Net::MAC::Vendor')) {
29 $lookup_vendor=0;
30 }
31 }
33 END{}
35 my ($timeout, $mailto, $mailfrom, $user, $group);
36 my ($arp_activ, $arp_interface, $ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password);
37 my $hosts_database={};
38 my $resolver=Net::DNS::Resolver->new;
39 my $ldap;
40 if($lookup_vendor) {
41 eval("Net::MAC::Vendor::load_cache('file:///usr/lib/gosa-si/modules/oui.txt')");
42 if($@) {
43 &main::daemon_log("Loading OUI cache file failed! MAC Vendor lookup disabled", 1);
44 $lookup_vendor=0;
45 } else {
46 &main::daemon_log("Loading OUI cache file suceeded!", 6);
47 }
48 }
50 my %cfg_defaults =
51 (
52 "arp" => {
53 "arp_activ" => [\$arp_activ, "on"],
54 "arp_interface" => [\$arp_interface, "all"],
55 },
56 "server" => {
57 "ldap_uri" => [\$ldap_uri, ""],
58 "ldap_base" => [\$ldap_base, ""],
59 "ldap_admin_dn" => [\$ldap_admin_dn, ""],
60 "ldap_admin_password" => [\$ldap_admin_password, ""],
61 },
62 );
64 #=== FUNCTION ================================================================
65 # NAME: read_configfile
66 # PARAMETERS: cfg_file - string -
67 # RETURNS: nothing
68 # DESCRIPTION: read cfg_file and set variables
69 #===============================================================================
70 sub read_configfile {
71 my $cfg;
72 if( defined( $main::cfg_file) && ( length($main::cfg_file) > 0 )) {
73 if( -r $main::cfg_file ) {
74 $cfg = Config::IniFiles->new( -file => $main::cfg_file );
75 } else {
76 print STDERR "Couldn't read config file!";
77 }
78 } else {
79 $cfg = Config::IniFiles->new() ;
80 }
81 foreach my $section (keys %cfg_defaults) {
82 foreach my $param (keys %{$cfg_defaults{ $section }}) {
83 my $pinfo = $cfg_defaults{ $section }{ $param };
84 ${@$pinfo[0]} = $cfg->val( $section, $param, @$pinfo[1] );
85 }
86 }
87 }
89 sub get_module_info {
90 my @info = (undef,
91 undef,
92 );
94 &read_configfile();
95 # Don't start if some of the modules are missing
96 if(($arp_activ eq 'on') && $start_service) {
97 $ldap = Net::LDAP->new($ldap_uri);
98 if (!$ldap) {
99 &main::daemon_log("Could not connect to LDAP Server at $ldap_uri!\n$@", 1);
100 } else {
101 $ldap->bind($ldap_admin_dn, password => $ldap_admin_password);
102 }
104 # When interface is not configured (or 'all'), start arpwatch on all possible interfaces
105 if ((!defined($arp_interface)) || $arp_interface eq 'all') {
106 foreach my $device(&get_interfaces) {
107 # TODO: Need a better workaround for IPv4-to-IPv6 bridges
108 if($device =~ m/^sit\d+$/) {
109 next;
110 }
112 # If device has a valid mac address
113 # TODO: Check if this should be the right way
114 if(not(&get_mac($device) eq "00:00:00:00:00:00")) {
115 &main::daemon_log("Starting ArpWatch on $device", 1);
116 POE::Session->create(
117 inline_states => {
118 _start => sub {
119 &start(@_,$device);
120 },
121 _stop => sub {
122 $ldap->unbind if (defined($ldap));
123 $ldap->disconnect if (defined($ldap));
124 $_[KERNEL]->post( sprintf("arp_watch_$device") => 'shutdown' )
125 },
126 got_packet => \&got_packet,
127 },
128 );
129 }
130 }
131 } else {
132 foreach my $device(split(/[\s,]+/, $arp_interface)) {
133 &main::daemon_log("Starting ArpWatch on $device", 1);
134 POE::Session->create(
135 inline_states => {
136 _start => sub {
137 &start(@_,$device);
138 },
139 _stop => sub {
140 $ldap->unbind if (defined($ldap));
141 $ldap->disconnect if (defined($ldap));
142 $_[KERNEL]->post( sprintf("arp_watch_$device") => 'shutdown' )
143 },
144 got_packet => \&got_packet,
145 },
146 );
147 }
148 }
149 } else {
150 &main::daemon_log("ArpHandler disabled. Not starting any capture processes");
151 }
152 return \@info;
153 }
155 sub process_incoming_msg {
156 return 1;
157 }
159 sub start {
160 my $device = (exists($_[ARG0])?$_[ARG0]:'eth0');
161 POE::Component::ArpWatch->spawn(
162 Alias => sprintf("arp_watch_$device"),
163 Device => $device,
164 Dispatch => 'got_packet',
165 Session => $_[SESSION],
166 );
168 $_[KERNEL]->post( sprintf("arp_watch_$device") => 'run' );
169 }
171 sub got_packet {
172 my ($kernel, $heap, $sender, $packet) = @_[KERNEL, HEAP, SENDER, ARG0];
174 if( $packet->{source_haddr} eq "00:00:00:00:00:00" ||
175 $packet->{source_haddr} eq "ff:ff:ff:ff:ff:ff" ||
176 $packet->{source_ipaddr} eq "0.0.0.0") {
177 return;
178 }
180 my $capture_device = sprintf "%s", $kernel->alias_list($sender) =~ /^arp_watch_(.*)$/;
182 if(!exists($hosts_database->{$packet->{source_haddr}})) {
183 my $dnsresult= $resolver->search($packet->{source_ipaddr});
184 my $dnsname= (defined($dnsresult))?$dnsresult->{answer}[0]->{ptrdname}:$packet->{source_ipaddr};
185 my $ldap_result=&get_host_from_ldap($packet->{source_haddr});
186 if(exists($ldap_result->{dn})) {
187 $hosts_database->{$packet->{source_haddr}}=$ldap_result;
188 $hosts_database->{$packet->{source_haddr}}->{dnsname}= $dnsname;
189 if(!exists($ldap_result->{ipHostNumber})) {
190 $hosts_database->{$packet->{source_haddr}}->{ipHostNumber}=$packet->{source_ipaddr};
191 } else {
192 if(!($ldap_result->{ipHostNumber} eq $packet->{source_ipaddr})) {
193 &main::daemon_log(
194 "Current IP Address ".$packet->{source_ipaddr}.
195 " of host ".$hosts_database->{$packet->{source_haddr}}->{dnsname}.
196 " differs from LDAP (".$ldap_result->{ipHostNumber}.")", 4);
197 }
198 }
199 $hosts_database->{$packet->{source_haddr}}->{dnsname}=$dnsname;
200 &main::daemon_log("Host was found in LDAP as ".$ldap_result->{dn}, 8);
201 } else {
202 $hosts_database->{$packet->{source_haddr}}={
203 macAddress => $packet->{source_haddr},
204 ipHostNumber => $packet->{source_ipaddr},
205 dnsname => $dnsname,
206 cn => (($dnsname =~ /^(\d){1,3}\.(\d){1,3}\.(\d){1,3}\.(\d){1,3}/) ? $dnsname : sprintf "%s", $dnsname =~ /([^\.]+)\./),
207 macVendor => (($lookup_vendor) ? &get_vendor_for_mac($packet->{source_haddr}) : "Unknown Vendor"),
208 };
209 &main::daemon_log("Host was not found in LDAP (".($hosts_database->{$packet->{source_haddr}}->{dnsname}).")",6);
210 &main::daemon_log(
211 "New Host ".($hosts_database->{$packet->{source_haddr}}->{dnsname}).
212 ": ".$hosts_database->{$packet->{source_haddr}}->{ipHostNumber}.
213 "/".$hosts_database->{$packet->{source_haddr}}->{macAddress},4);
214 &add_ldap_entry(
215 $ldap,
216 $ldap_base,
217 $hosts_database->{$packet->{source_haddr}}->{macAddress},
218 'new-system',
219 $hosts_database->{$packet->{source_haddr}}->{ipHostNumber},
220 'interface',
221 $hosts_database->{$packet->{source_haddr}}->{macVendor});
222 }
223 $hosts_database->{$packet->{source_haddr}}->{device}= $capture_device;
224 } else {
225 if(!($hosts_database->{$packet->{source_haddr}}->{ipHostNumber} eq $packet->{source_ipaddr})) {
226 &main::daemon_log(
227 "IP Address change of MAC ".$packet->{source_haddr}.
228 ": ".$hosts_database->{$packet->{source_haddr}}->{ipHostNumber}.
229 "->".$packet->{source_ipaddr}, 4);
230 $hosts_database->{$packet->{source_haddr}}->{ipHostNumber}= $packet->{source_ipaddr};
231 &change_ldap_entry(
232 $ldap,
233 $ldap_base,
234 $hosts_database->{$packet->{source_haddr}}->{macAddress},
235 'ip-changed',
236 $hosts_database->{$packet->{source_haddr}}->{ipHostNumber},
237 );
239 }
240 &main::daemon_log("Host already in cache (".($hosts_database->{$packet->{source_haddr}}->{device})."->".($hosts_database->{$packet->{source_haddr}}->{dnsname}).")",8);
241 }
242 }
244 sub get_host_from_ldap {
245 my $mac=shift;
246 my $result={};
248 my $ldap_result= &search_ldap_entry(
249 $ldap,
250 $ldap_base,
251 "(|(macAddress=$mac)(dhcpHWAddress=ethernet $mac))"
252 );
254 if(defined($ldap_result) && $ldap_result->count==1) {
255 if(exists($ldap_result->{entries}[0]) &&
256 exists($ldap_result->{entries}[0]->{asn}->{objectName}) &&
257 exists($ldap_result->{entries}[0]->{asn}->{attributes})) {
259 for my $attribute(@{$ldap_result->{entries}[0]->{asn}->{attributes}}) {
260 if($attribute->{type} eq 'cn') {
261 $result->{cn} = $attribute->{vals}[0];
262 }
263 if($attribute->{type} eq 'macAddress') {
264 $result->{macAddress} = $attribute->{vals}[0];
265 }
266 if($attribute->{type} eq 'dhcpHWAddress') {
267 $result->{dhcpHWAddress} = $attribute->{vals}[0];
268 }
269 if($attribute->{type} eq 'ipHostNumber') {
270 $result->{ipHostNumber} = $attribute->{vals}[0];
271 }
272 }
273 }
274 $result->{dn} = $ldap_result->{entries}[0]->{asn}->{objectName};
275 }
277 return $result;
278 }
280 #=== FUNCTION ================================================================
281 # NAME: get_interfaces
282 # PARAMETERS: none
283 # RETURNS: (list of interfaces)
284 # DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces.
285 #===============================================================================
286 sub get_interfaces {
287 my @result;
288 my $PROC_NET_DEV= ('/proc/net/dev');
290 open(PROC_NET_DEV, "<$PROC_NET_DEV")
291 or die "Could not open $PROC_NET_DEV";
293 my @ifs = <PROC_NET_DEV>;
295 close(PROC_NET_DEV);
297 # Eat first two line
298 shift @ifs;
299 shift @ifs;
301 chomp @ifs;
302 foreach my $line(@ifs) {
303 my $if= (split /:/, $line)[0];
304 $if =~ s/^\s+//;
305 push @result, $if;
306 }
308 return @result;
309 }
311 #=== FUNCTION ================================================================
312 # NAME: get_mac
313 # PARAMETERS: interface name (i.e. eth0)
314 # RETURNS: (mac address)
315 # DESCRIPTION: Uses ioctl to get mac address directly from system.
316 #===============================================================================
317 sub get_mac {
318 my $ifreq= shift;
319 my $result;
320 if ($ifreq && length($ifreq) > 0) {
321 if($ifreq eq "all") {
322 $result = "00:00:00:00:00:00";
323 } else {
324 my $SIOCGIFHWADDR= 0x8927; # man 2 ioctl_list
326 socket SOCKET, PF_INET, SOCK_DGRAM, getprotobyname('ip')
327 or die "socket: $!";
329 if(ioctl SOCKET, $SIOCGIFHWADDR, $ifreq) {
330 my ($if, $mac)= unpack 'h36 H12', $ifreq;
332 if (length($mac) > 0) {
333 $mac=~ m/^([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$/;
334 $mac= sprintf("%s:%s:%s:%s:%s:%s", $1, $2, $3, $4, $5, $6);
335 $result = $mac;
336 }
337 }
338 }
339 }
340 return $result;
341 }
343 sub get_vendor_for_mac {
344 my $mac=shift;
345 my $result="Unknown Vendor";
347 if(defined($mac)) {
348 my $vendor= Net::MAC::Vendor::fetch_oui_from_cache(Net::MAC::Vendor::normalize_mac($mac));
349 if(length($vendor) > 0) {
350 $result= @{$vendor}[0];
351 }
352 &main::daemon_log("Looking up Vendor for MAC ".$mac.": $result", 4);
353 }
355 return $result;
356 }
358 #=== FUNCTION ================================================================
359 # NAME: add_ldap_entry
360 # PURPOSE: adds an element to ldap-tree
361 # PARAMETERS:
362 # RETURNS: none
363 # DESCRIPTION: ????
364 # THROWS: no exceptions
365 # COMMENTS: none
366 # SEE ALSO: n/a/bin
367 #===============================================================================
368 sub add_ldap_entry {
369 my ($ldap_tree, $ldap_base, $mac, $gotoSysStatus, $ip, $interface, $desc) = @_;
370 my $dn = "cn=".$hosts_database->{$mac}->{cn}.",ou=incoming,$ldap_base";
371 my $s_res = &search_ldap_entry($ldap_tree, $ldap_base, "(|(macAddress=$mac)(dhcpHWAddress=ethernet $mac))");
372 my $c_res = (defined($s_res))?$s_res->count:0;
373 if($c_res == 1) {
374 &main::daemon_log("WARNING: macAddress $mac already in LDAP", 1);
375 return;
376 } elsif($c_res > 0) {
377 &main::daemon_log("ERROR: macAddress $mac exists $c_res times in LDAP", 1);
378 return;
379 }
381 # create LDAP entry
382 my $entry = Net::LDAP::Entry->new( $dn );
383 $entry->dn($dn);
384 $entry->add("objectClass" => "goHard");
385 $entry->add("cn" => $hosts_database->{$mac}->{cn});
386 $entry->add("macAddress" => $mac);
387 if(defined $gotoSysStatus) {$entry->add("gotoSysStatus" => $gotoSysStatus)}
388 if(defined $ip) {$entry->add("ipHostNumber" => $ip) }
389 #if(defined $interface) { }
390 if(defined $desc) {$entry->add("description" => $desc) }
392 # submit entry to LDAP
393 my $result = $entry->update ($ldap_tree);
395 # for $result->code constants please look at Net::LDAP::Constant
396 if($result->code == 68) { # entry already exists
397 &main::daemon_log("WARNING: $dn ".$result->error, 3);
398 } elsif($result->code == 0) { # everything went fine
399 &main::daemon_log("add entry $dn to ldap", 1);
400 } else { # if any other error occur
401 &main::daemon_log("ERROR: $dn, ".$result->code.", ".$result->error, 1);
402 }
403 return;
404 }
407 #=== FUNCTION ================================================================
408 # NAME: change_ldap_entry
409 # PURPOSE: ????
410 # PARAMETERS: ????
411 # RETURNS: ????
412 # DESCRIPTION: ????
413 # THROWS: no exceptions
414 # COMMENTS: none
415 # SEE ALSO: n/a
416 #===============================================================================
417 sub change_ldap_entry {
418 my ($ldap_tree, $ldap_base, $mac, $gotoSysStatus, $ip) = @_;
420 # check if ldap_entry exists or not
421 my $s_res = &search_ldap_entry($ldap_tree, $ldap_base, "(|(macAddress=$mac)(dhcpHWAddress=ethernet $mac))");
422 my $c_res = (defined $s_res)?$s_res->count:0;
423 if($c_res == 0) {
424 &main::daemon_log("WARNING: macAddress $mac not in LDAP", 1);
425 return;
426 } elsif($c_res > 1) {
427 &main::daemon_log("ERROR: macAddress $mac exists $c_res times in LDAP", 1);
428 return;
429 }
431 my $s_res_entry = $s_res->pop_entry();
432 my $dn = $s_res_entry->dn();
433 my $replace = {
434 'gotoSysStatus' => $gotoSysStatus,
435 };
436 if (defined($ip)) {
437 $replace->{'ipHostNumber'} = $ip;
438 }
439 my $result = $ldap->modify( $dn, replace => $replace );
441 # for $result->code constants please look at Net::LDAP::Constant
442 if($result->code == 32) { # entry doesnt exists
443 &add_ldap_entry($mac, $gotoSysStatus);
444 } elsif($result->code == 0) { # everything went fine
445 &main::daemon_log("entry $dn changed successful", 1);
446 } else { # if any other error occur
447 &main::daemon_log("ERROR: $dn, ".$result->code.", ".$result->error, 1);
448 }
450 return;
451 }
453 #=== FUNCTION ================================================================
454 # NAME: search_ldap_entry
455 # PURPOSE: ????
456 # PARAMETERS: [Net::LDAP] $ldap_tree - object of an ldap-tree
457 # string $sub_tree - dn of the subtree the search is performed
458 # string $search_string - either a string or a Net::LDAP::Filter object
459 # RETURNS: [Net::LDAP::Search] $msg - result object of the performed search
460 # DESCRIPTION: ????
461 # THROWS: no exceptions
462 # COMMENTS: none
463 # SEE ALSO: n/a
464 #===============================================================================
465 sub search_ldap_entry {
466 my ($ldap_tree, $sub_tree, $search_string) = @_;
467 my $msg;
468 if(defined($ldap_tree)) {
469 $msg = $ldap_tree->search( # perform a search
470 base => $sub_tree,
471 filter => $search_string,
472 ) or &main::daemon_log("cannot perform search at ldap: $@", 1);
473 #if(defined $msg) {
474 # print $sub_tree."\t".$search_string."\t";
475 # print $msg->count."\n";
476 # foreach my $entry ($msg->entries) { $entry->dump; };
477 #}
478 }
479 return $msg;
480 }
481 # $ldap = Net::LDAP->new( "localhost" ) or die "$@";
482 # $ldap->bind($bind_phrase,
483 # password => $password,
484 # ) ;
485 #
486 # switch($arp_sig) {
487 # case 0 {&change_ldap_entry($ldap, $ldap_base,
488 # $mac, "ip-changed",
489 # )}
490 # case 1 {&change_ldap_entry($ldap, $ldap_base,
491 # $mac, "mac-not-whitelisted",
492 # )}
493 # case 2 {&change_ldap_entry($ldap, $ldap_base,
494 # $mac, "mac-in-blacklist",
495 # )}
496 # case 3 {&add_ldap_entry($ldap, $ldap_base,
497 # $mac, "new-mac-address", $ip,
498 # $interface, $desc,
499 # )}
500 # case 4 {&change_ldap_entry($ldap, $ldap_base,
501 # $mac, "unauthorized-arp-request",
502 # )}
503 # case 5 {&change_ldap_entry($ldap, $ldap_base,
504 # $mac, "abusive-number-of-arp-requests",
505 # )}
506 # case 6 {&change_ldap_entry($ldap, $ldap_base,
507 # $mac, "ether-and-arp-mac-differs",
508 # )}
509 # case 7 {&change_ldap_entry($ldap, $ldap_base,
510 # $mac, "flood-detected",
511 # )}
512 # case 8 {&add_ldap_entry($ldap, $ldap_base,
513 # $mac, $ip, "new-system",
514 # )}
515 # case 9 {&change_ldap_entry($ldap, $ldap_base,
516 # $mac, "mac-changed",
517 # )}
518 # }
519 #
520 #
521 # ldap search
522 # my $base_phrase = "dc=gonicus,dc=de";
523 # my $filter_phrase = "cn=keinesorge";
524 # my $attrs_phrase = "cn macAdress";
525 # my $msg_search = $ldap->search( base => $base_phrase,
526 # filter => $filter_phrase,
527 # attrs => $attrs_phrase,
528 # );
529 # $msg_search->code && die $msg_search->error;
530 #
531 # my @entries = $msg_search->entries;
532 # my $max = $msg_search->count;
533 # print "anzahl der entries: $max\n";
534 # my $i;
535 # for ( $i = 0 ; $i < $max ; $i++ ) {
536 # my $entry = $msg_search->entry ( $i );
537 # foreach my $attr ( $entry->attributes ) {
538 # if( not $attr eq "cn") {
539 # next;
540 # }
541 # print join( "\n ", $attr, $entry->get_value( $attr ) ), "\n\n";
542 # }
543 # }
544 #
545 # # ldap add
546 #
547 #
548 # $ldap->unbind;
549 # exit;
551 1;