Code

Moving part III
[gosa.git] / contrib / daemon / arp-handler-d
1 #!/usr/bin/perl 
2 #===============================================================================
3 #
4 #         FILE:  gosa-support-daemon.pl
5 #
6 #        USAGE:  ./.gosa-support-daemon.pl
7 #
8 #  DESCRIPTION:  
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  ---
12 #         BUGS:  ---
13 #        NOTES:  ---
14 #       AUTHOR:   Andreas Rettenberger, <rettenberger@gonicus.de>
15 #      COMPANY:  Gonicus GmbH, Arnsberg
16 #      VERSION:  1.0
17 #      CREATED:  21.08.2007 15:13:51 CEST
18 #     REVISION:  ---
19 #===============================================================================
21 use strict;
22 use warnings;
23 use Getopt::Long;
24 use Config::IniFiles;
25 use POSIX;
26 use Fcntl;
27 use Net::LDAP;
28 use Net::LDAP::LDIF;
29 use Net::LDAP::Entry;
30 use Switch;
33 my ($verbose, $cfg_file, $log_file, $pid_file, $foreground); 
34 my ($timeout, $mailto, $mailfrom, $user, $group);
35 my ($procid, $pid, $loglevel);
36 my ($fifo_path, $max_process_timeout, $max_process );
37 my %daemon_children;
38 my ($ldap, $bind_phrase, $password, $ldap_base) ;
40 $procid = -1 ;
41 $foreground = 0 ;
42 $verbose = 0 ;
43 $max_process = 2 ;
44 $max_process_timeout = 1 ;
45 $ldap_base = "dc=gonicus,dc=de" ;
46 #$ldap_path = "/var/run/gosa-support-daemon.socket";
47 #$log_path = "/var/log/gosa-support-daemon.log";
48 #$pid_path = "/var/run/gosa-support-daemon/gosa-support-daemon.pid";
50 #---------------------------------------------------------------------------
51 #  parse commandline options
52 #---------------------------------------------------------------------------
53 Getopt::Long::Configure( "bundling" );
54 GetOptions( "v|verbose+" => \$verbose,
55         "c|config=s" => \$cfg_file,
56         "h|help" => \&usage,
57         "l|logfile=s" => \$log_file,
58         "p|pid=s" => \$pid_file,
59         "f|foreground" => \$foreground);
61 #---------------------------------------------------------------------------
62 #  read and set config parameters
63 #---------------------------------------------------------------------------
64 my %cfg_defaults =
65 ("Allgemein" =>
66  {"timeout"  => [ \$timeout, 1000 ],
67  "mailto"   => [ \$mailto, 'root@localhost' ],
68  "mailfrom" => [ \$mailfrom, 'sps-daemon@localhost' ],
69  "user"     => [ \$user, "nobody" ],
70  "group"    => [ \$group, "nogroup" ],
71  "fifo_path" => [ \$fifo_path, "/home/rettenbe/gonicus/gosa-support/tmp/fifo" ],
72  "log_file"  => [ \$log_file, "/home/rettenbe/gonicus/gosa-support/tmp/gosa-support.log" ],
73  "pid_file"  => [ \$pid_file, "/home/rettenbe/gonicus/gosa-support/tmp/gosa-support.pid" ],
74  "loglevel"     => [ \$loglevel, 1]
75  },
76 "LDAP"  =>
77     {"bind" => [ \$bind_phrase, "cn=ldapadmin,dc=gonicus,dc=de" ],
78      "password" => [ \$password, "tester" ],
79     }
80  );
81 &read_configfile;
84 #===  FUNCTION  ================================================================
85 #         NAME:  check_cmdline_param
86 #      PURPOSE:  checks all commandline parameters to validity
87 #   PARAMETERS:  none
88 #      RETURNS:  none
89 #  DESCRIPTION:  ????
90 #       THROWS:  no exceptions
91 #     COMMENTS:  none
92 #     SEE ALSO:  n/a
93 #===============================================================================
94 sub check_cmdline_param () {
95     my $err_config;
96     my $err_log;
97     my $err_pid;
98     my $err_counter = 0;
99     if( not defined( $cfg_file)) {
100         $err_config = "please specify a config file";
101         $err_counter += 1;
102     }
103     if( not defined( $log_file)) {
104         $err_log = "please specify a log file";
105         $err_counter += 1;
106     }
107     if( not defined( $pid_file)) {
108         $err_pid = "please specify a pid file";
109         $err_counter += 1;
110     }
111     if( $err_counter > 0 ) {
112         &usage( "", 1 );
113         if( defined( $err_config)) { print STDERR "$err_config\n"}
114         if( defined( $err_log)) { print STDERR "$err_log\n" }
115         if( defined( $err_pid)) { print STDERR "$err_pid\n"}
116         print STDERR "\n";
117         exit( -1 );
118     }
121 #===  FUNCTION  ================================================================
122 #         NAME:  check_pid
123 #      PURPOSE:  
124 #   PARAMETERS:  none
125 #      RETURNS:  none
126 #  DESCRIPTION:  ????
127 #       THROWS:  no exceptions
128 #     COMMENTS:  none
129 #     SEE ALSO:  n/a
130 #===============================================================================
131 sub check_pid { 
132     if( open( LOCK_FILE, "<$pid_file") ) {
133         $procid = <LOCK_FILE>;
134         if( defined $procid ) {
135             chomp( $procid );
136             if( -f "/proc/$procid/stat" ) {
137                 my($stat) = `cat /proc/$procid/stat` =~ m/$procid \((.+)\).*/;
138                 print "\t".$stat."\n";
139                 if( "sps-daemon.pl" eq $stat ) {
140                     close( LOCK_FILE );
141                     exit -1;
142                 }
143             }
144         }
145         close( LOCK_FILE );
146         unlink( $pid_file );
147     }
149     # Try to open PID file
150     if (!sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
151         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
152         if (open(LOCK_FILE, "<", $pid_file) && ($pid = <LOCK_FILE>)) {
153             chomp($pid);
154             $msg .= "(PID $pid)\n";
155         } else {
156             $msg .= "(unable to read PID)\n";
157         }
158         if ( ! $foreground ) {
159             daemon_log( $msg."\n");
160         } else {
161             print( STDERR " $msg " );
162         }
163         exit( -1 );
164     }
167 #===  FUNCTION  ================================================================
168 #         NAME:  read_configfile
169 #      PURPOSE:  read the configuration file and provide the programm with 
170 #                parameters
171 #   PARAMETERS:  none
172 #      RETURNS:  none
173 #  DESCRIPTION:  ????
174 #       THROWS:  no exceptions
175 #     COMMENTS:  none
176 #     SEE ALSO:  n/a
177 #===============================================================================
178 sub read_configfile {
179     my $log_time = localtime(time);
180     my $cfg;
181     if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
182         if( -r $cfg_file ) { 
183             $cfg = Config::IniFiles->new( -file => $cfg_file ); 
184         } else { 
185             usage( "Couldn't read config file: $cfg_file \n" ); 
186         }
187     } else { 
188         $cfg = Config::IniFiles->new() ; 
189     }
191     foreach my $section (keys %cfg_defaults) {      # "Parse" config into values
192         foreach my $param (keys %{$cfg_defaults{ $section }}) {
193             my $pinfo = $cfg_defaults{ $section }{ $param };
194             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
195         }
196     }
198     if(-e $log_file ) { unlink $log_file }
199     daemon_log("$log_time: config file read\n");
202 #===  FUNCTION  ================================================================
203 #         NAME:  daemon_log
204 #      PURPOSE:  log messages to specified logfile
205 #   PARAMETERS:  $msg, $level
206 #      RETURNS:  ????
207 #  DESCRIPTION:  Takes a message ($msg) and append it to the logfile. The 
208 #                standard log-level ($level) is 1. Messages whith higher level
209 #                than the verbosity-level (defined by commandline) are printed 
210 #                out to commandline. Messages with log-level lower than 2 are 
211 #                not logged to logfile!
212 #       THROWS:  no exceptions
213 #     COMMENTS:  none
214 #     SEE ALSO:  n/a
215 #===============================================================================
216 sub daemon_log {
217     my( $msg, $level ) = @_;
218     if(not defined $msg) { return } 
219     if(not defined $level) { $level = 1 }
220     open(LOG_HANDLE, ">>$log_file");
221     if(not defined open( LOG_HANDLE, ">>$log_file" ) ) { return }
222     chomp($msg);
223     #if( $verbose >= $level ) { print "$msg"."\n" }
224     if( $level <= 1 ) { print LOG_HANDLE $msg."\n"  }
225     if( $foreground ) { print $msg."\n" }
226     close( LOG_HANDLE );
227     }
229 #===  FUNCTION  ================================================================
230 #         NAME:  signal handler
231 #      PURPOSE:  catches signals from the programm and do diffrent things 
232 #                than default
233 #   PARAMETERS:  none
234 #      RETURNS:  none
235 #  DESCRIPTION:  sighandler
236 #       THROWS:  no exceptions
237 #     COMMENTS:  none
238 #     SEE ALSO:  n/a
239 #===============================================================================
240 sub sigINT {
241     my $log_time = localtime(time);
242     print "INT\n";
243     if( -p $fifo_path ) {
244         close FIFO  ;
245         unlink($fifo_path) ;
246         daemon_log( "$log_time: FIFO closed after signal INT!\n") ;
247         }
248     if(defined($ldap)) {
249         $ldap->unbind;
250     }
251     $SIG{INT} = "DEFAULT" ;
252     kill INT => $$ ;
254 $SIG{INT} = \&sigINT ;
256 #===  FUNCTION  ================================================================
257 #         NAME:  usage
258 #      PURPOSE:  
259 #   PARAMETERS:  none
260 #      RETURNS:  none
261 #  DESCRIPTION:  print out the usage of the program
262 #       THROWS:  no exceptions
263 #     COMMENTS:  none
264 #     SEE ALSO:  n/a
265 #===============================================================================
266 sub usage {
267         my( $text, $help ) = @_;
268         $text = undef if( "h" eq $text );
269         (defined $text) && print STDERR "\n$text\n";
270         if( (defined $help && $help) || (!defined $help && !defined $text) ) {
271                 print STDERR << "EOF" ;
272 usage: $0 [-hvf] [-c config, -l logfile, -p pidfile]
274     -h        : this (help) message
275     -c <file> : config file
276     -l <file> : log file (example: /var/log/sps/sps.log)
277     -p <file> : pid file (example: /var/run/sps/sps.pid)
278     -f        : foreground (don"t fork)
279     -v        : be verbose (multiple to increase verbosity)
280 EOF
281         }
282         print "\n" ;
286 #===  FUNCTION  ================================================================
287 #         NAME:  open_fifo
288 #      PURPOSE:  
289 #   PARAMETERS:  $fifo_path
290 #      RETURNS:  0: FIFO couldn"t be setup, 1: FIFO setup correctly
291 #  DESCRIPTION:  creates a FIFO at $fifo_path
292 #       THROWS:  no exceptions
293 #     COMMENTS:  none
294 #     SEE ALSO:  n/a
295 #===============================================================================
296 sub open_fifo {
297     my ($fifo_path) = @_ ;
298     my $log_time = localtime( time );
299     if( -p $fifo_path ) {
300         daemon_log("$log_time: FIFO at $fifo_path already exists\n");
301         return 0;
302         } 
303     POSIX::mkfifo($fifo_path, 0666) or die "can't mkfifo $fifo_path: $!";
304     daemon_log( "$log_time: FIFO started at $fifo_path\n" ) ;
305     return 1;
306     }
309 #===  FUNCTION  ================================================================
310 #         NAME:  add_ldap_entry
311 #      PURPOSE:  adds an element to ldap-tree
312 #   PARAMETERS:  
313 #      RETURNS:  none
314 #  DESCRIPTION:  ????
315 #       THROWS:  no exceptions
316 #     COMMENTS:  none
317 #     SEE ALSO:  n/a
318 #===============================================================================
319 sub add_ldap_entry {
320     my ($ldap_tree, $ldap_base, $mac, $gotoSysStatus, $ip, $interface, $desc) = @_;
321     my $dn = "cn=$mac,ou=incoming,$ldap_base";
322     my $s_res = &search_ldap_entry($ldap_tree, $ldap_base, "(|(macAddress=$mac)(dhcpHWAddress=ethernet $mac))");
323     my $c_res = $s_res->count;
324     if($c_res == 1) {
325         daemon_log("WARNING: macAddress $mac already in LDAP", 1);
326         return;
327     } elsif($c_res > 0) {
328         daemon_log("ERROR: macAddress $mac exists $c_res times in LDAP", 1);
329         return;
330     }
332     # create LDAP entry 
333     my $entry = Net::LDAP::Entry->new( $dn );
334     $entry->dn($dn);
335     $entry->add("objectClass" => "goHard");
336     $entry->add("cn" => $mac);
337     $entry->add("macAddress" => $mac);
338     if(defined $gotoSysStatus) {$entry->add("gotoSysStatus" => $gotoSysStatus)}
339     if(defined $ip) {$entry->add("ipHostNumber" => $ip) }
340     #if(defined $interface) { }
341     if(defined $desc) {$entry->add("description" => $desc) }
342     
343     # submit entry to LDAP
344     my $result = $entry->update ($ldap_tree); 
345         
346     # for $result->code constants please look at Net::LDAP::Constant
347     my $log_time = localtime( time );
348     if($result->code == 68) {   # entry already exists 
349         daemon_log("WARNING: $log_time: $dn ".$result->error, 3);
350     } elsif($result->code == 0) {   # everything went fine
351         daemon_log("$log_time: add entry $dn to ldap", 1);
352     } else {  # if any other error occur
353         daemon_log("ERROR: $log_time: $dn, ".$result->code.", ".$result->error, 1);
354     }
355     return;
359 #===  FUNCTION  ================================================================
360 #         NAME:  change_ldap_entry
361 #      PURPOSE:  ????
362 #   PARAMETERS:  ????
363 #      RETURNS:  ????
364 #  DESCRIPTION:  ????
365 #       THROWS:  no exceptions
366 #     COMMENTS:  none
367 #     SEE ALSO:  n/a
368 #===============================================================================
369 sub change_ldap_entry {
370     my ($ldap_tree, $ldap_base, $mac, $gotoSysStatus ) = @_;
371     
372     # check if ldap_entry exists or not
373     my $s_res = &search_ldap_entry($ldap_tree, $ldap_base, "(|(macAddress=$mac)(dhcpHWAddress=ethernet $mac))");
374     my $c_res = $s_res->count;
375     if($c_res == 0) {
376         daemon_log("WARNING: macAddress $mac not in LDAP", 1);
377         return;
378     } elsif($c_res > 1) {
379         daemon_log("ERROR: macAddress $mac exists $c_res times in LDAP", 1);
380         return;
381     }
383     my $s_res_entry = $s_res->pop_entry();
384     my $dn = $s_res_entry->dn();
385     my $result = $ldap->modify( $dn, replace => {'gotoSysStatus' => $gotoSysStatus } );
387     # for $result->code constants please look at Net::LDAP::Constant
388     my $log_time = localtime( time );
389     if($result->code == 32) {   # entry doesnt exists 
390         &add_ldap_entry($mac, $gotoSysStatus);
391     } elsif($result->code == 0) {   # everything went fine
392         daemon_log("$log_time: entry $dn changed successful", 1);
393     } else {  # if any other error occur
394         daemon_log("ERROR: $log_time: $dn, ".$result->code.", ".$result->error, 1);
395     }
397     return;
400 #===  FUNCTION  ================================================================
401 #         NAME:  search_ldap_entry
402 #      PURPOSE:  ????
403 #   PARAMETERS:  [Net::LDAP] $ldap_tree - object of an ldap-tree
404 #                string $sub_tree - dn of the subtree the search is performed
405 #                string $search_string - either a string or a Net::LDAP::Filter object
406 #      RETURNS:  [Net::LDAP::Search] $msg - result object of the performed search
407 #  DESCRIPTION:  ????
408 #       THROWS:  no exceptions
409 #     COMMENTS:  none
410 #     SEE ALSO:  n/a
411 #===============================================================================
412 sub search_ldap_entry {
413     my ($ldap_tree, $sub_tree, $search_string) = @_;
414     my $msg = $ldap_tree->search( # perform a search
415                         base   => $sub_tree,
416                         filter => $search_string,
417                       ) or daemon_log("cannot perform search at ldap: $@", 1);
418 #    if(defined $msg) {
419 #        print $sub_tree."\t".$search_string."\t";
420 #        print $msg->count."\n";
421 #        foreach my $entry ($msg->entries) { $entry->dump; };
422 #    }
424     return $msg;
429 #========= MAIN = main ========================================================
430 daemon_log( "####### START DAEMON ######\n", 1 );
431 &check_cmdline_param ;
432 &check_pid;
433 &open_fifo($fifo_path);
435 # Just fork, if we"re not in foreground mode
436 if( ! $foreground ) { $pid = fork(); }
437 else { $pid = $$; }
439 # Do something useful - put our PID into the pid_file
440 if( 0 != $pid ) {
441     open( LOCK_FILE, ">$pid_file" );
442     print LOCK_FILE "$pid\n";
443     close( LOCK_FILE );
444     if( !$foreground ) { exit( 0 ) };
448 if( not -p $fifo_path ) { die "fifo file disappeared\n" }
449 sysopen(FIFO, $fifo_path, O_RDONLY) or die "can't read from $fifo_path: $!" ;
451 while( 1 ) {
452     # checke alle prozesse im hash daemon_children ob sie noch aktiv sind, wenn
453     # nicht, dann entferne prozess aus hash
454     while( (my $key, my $val) = each( %daemon_children) ) {
455         my $status = waitpid( $key, &WNOHANG) ;
456         if( $status == -1 ) { 
457             delete $daemon_children{$key} ; 
458             daemon_log("childprocess finished: $key", 3) ;
459         }
460     }
462     # ist die max_process anzahl von prozesskindern erreicht, dann warte und 
463     # prüfe erneut, ob in der zwischenzeit prozesse fertig geworden sind
464     if( keys( %daemon_children ) >= $max_process ) { 
465         sleep($max_process_timeout) ;
466         next ;
467     }
469     my $msg = <FIFO>;
470     if( not defined( $msg )) { next ; }
471     
472     chomp( $msg );
473     if( length( $msg ) == 0 ) { next ; }
475     my $forked_pid = fork();
476 #=== PARENT = parent ==========================================================
477     if ( $forked_pid != 0 ) { 
478         daemon_log("childprocess forked: $forked_pid", 3) ;
479         $daemon_children{$forked_pid} = 0 ;
480     }
481 #=== CHILD = child ============================================================
482     else {
483         # parse the incoming message from arp, split the message and return 
484         # the values in an array. not defined values are set to "none" 
485         #my ($mac, $ip, $interface, $arp_sig, $desc) = &parse_input( $msg ) ;
486         daemon_log( "childprocess read from arp: $fifo_path\nline: $msg", 3);
487         my ($mac, $ip, $interface, $arp_sig, $desc) = split('\s', $msg, 5);
489         # create connection to LDAP
490         $ldap = Net::LDAP->new( "localhost" ) or die "$@";
491         $ldap->bind($bind_phrase,
492                     password => $password,
493                     ) ;
494         
495         switch($arp_sig) {
496             case 0 {&change_ldap_entry($ldap, $ldap_base, 
497                                       $mac, "ip-changed",
498                                       )} 
499             case 1 {&change_ldap_entry($ldap, $ldap_base, 
500                                       $mac, "mac-not-whitelisted",
501                                       )}
502             case 2 {&change_ldap_entry($ldap, $ldap_base, 
503                                       $mac, "mac-in-blacklist",
504                                       )}
505             case 3 {&add_ldap_entry($ldap, $ldap_base, 
506                                    $mac, "new-mac-address", $ip, 
507                                    $interface, $desc, 
508                                    )}
509             case 4 {&change_ldap_entry($ldap, $ldap_base, 
510                                       $mac, "unauthorized-arp-request",
511                                       )}
512             case 5 {&change_ldap_entry($ldap, $ldap_base, 
513                                       $mac, "abusive-number-of-arp-requests",
514                                       )}
515             case 6 {&change_ldap_entry($ldap, $ldap_base, 
516                                       $mac, "ether-and-arp-mac-differs",
517                                       )}
518             case 7 {&change_ldap_entry($ldap, $ldap_base, 
519                                       $mac, "flood-detected",
520                                       )}
521             case 8 {&add_ldap_entry($ldap, $ldap_base, 
522                                    $mac, $ip, "new-system",
523                                    )}
524             case 9 {&change_ldap_entry($ldap, $ldap_base, 
525                                       $mac, "mac-changed",
526                                       )}
527         }
530         # ldap search
531 #        my $base_phrase = "dc=gonicus,dc=de";
532 #        my $filter_phrase = "cn=keinesorge";
533 #        my $attrs_phrase = "cn macAdress";
534 #        my $msg_search = $ldap->search( base   => $base_phrase,
535 #                                        filter => $filter_phrase,
536 #                                        attrs => $attrs_phrase,
537 #                                        );
538 #        $msg_search->code && die $msg_search->error;
539 #        
540 #        my @entries = $msg_search->entries;
541 #        my $max = $msg_search->count;
542 #        print "anzahl der entries: $max\n";
543 #        my $i;
544 #        for ( $i = 0 ; $i < $max ; $i++ ) {
545 #            my $entry = $msg_search->entry ( $i );
546 #            foreach my $attr ( $entry->attributes ) {
547 #                if( not $attr eq "cn") {
548 #                    next;
549 #                }
550 #                print join( "\n ", $attr, $entry->get_value( $attr ) ), "\n\n";
551 #            }
552 #        }
554         # ldap add
555        
556         
557         $ldap->unbind;
558         exit;
559     }