Code

36d3a3a786ddb8d132dcef02aca80ca0f89fc8ae
[gosa.git] / contrib / daemon / gosa-sd.pl
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  # :TODO:31.08.2007:: basename beim erzeugen von neuen entries noch 
22  # base64-codieren, bei add_ldap_entry und change_ldap_entry, klären mit cajus
23  # ist das überhaupt notwendig???
25  # :TODO:03.09.2007:: bei SIGHUP muss das logfile neu angelegt werden, nicht
26  # einfach in dem alten weiterschreiben
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
33 use Fcntl;
34 use Net::LDAP;
35 use Net::LDAP::LDIF;
36 use Net::LDAP::Entry;
37 use Switch;
38 use MIME::Base64;
42 my ($verbose, $cfg_file, $log_file, $pid_file, $foreground); 
43 my ($timeout, $mailto, $mailfrom, $user, $group);
44 my ($procid, $pid, $loglevel);
45 my ($fifo_path, $max_process_timeout, $max_process );
46 my %daemon_children;
47 my ($ldap, $bind_phrase, $password, $ldap_base) ;
49 $procid = -1 ;
50 $foreground = 0 ;
51 $verbose = 0 ;
52 $max_process = 1 ;
53 $max_process_timeout = 1 ;
54 $ldap_base = "dc=gonicus,dc=de" ;
55 #$ldap_path = "/var/run/gosa-support-daemon.socket";
56 #$log_path = "/var/log/gosa-support-daemon.log";
57 #$pid_path = "/var/run/gosa-support-daemon/gosa-support-daemon.pid";
59 #---------------------------------------------------------------------------
60 #  parse commandline options
61 #---------------------------------------------------------------------------
62 Getopt::Long::Configure( "bundling" );
63 GetOptions( "v|verbose+" => \$verbose,
64         "c|config=s" => \$cfg_file,
65         "h|help" => \&usage,
66         "l|logfile=s" => \$log_file,
67         "p|pid=s" => \$pid_file,
68         "f|foreground" => \$foreground);
70 #---------------------------------------------------------------------------
71 #  read and set config parameters
72 #---------------------------------------------------------------------------
73 my %cfg_defaults =
74 ("Allgemein" =>
75  {"timeout"  => [ \$timeout, 1000 ],
76  "mailto"   => [ \$mailto, 'root@localhost' ],
77  "mailfrom" => [ \$mailfrom, 'sps-daemon@localhost' ],
78  "user"     => [ \$user, "nobody" ],
79  "group"    => [ \$group, "nogroup" ],
80  "fifo_path" => [ \$fifo_path, "/home/rettenbe/gonicus/gosa-support/tmp/fifo" ],
81  "log_file"  => [ \$log_file, "/home/rettenbe/gonicus/gosa-support/tmp/gosa-support.log" ],
82  "pid_file"  => [ \$pid_file, "/home/rettenbe/gonicus/gosa-support/tmp/gosa-support.pid" ],
83  "loglevel"     => [ \$loglevel, 1]
84  },
85 "LDAP"  =>
86     {"bind" => [ \$bind_phrase, "cn=ldapadmin,dc=gonicus,dc=de" ],
87      "password" => [ \$password, "tester" ],
88     }
89  );
90 &read_configfile;
93 #===  FUNCTION  ================================================================
94 #         NAME:  check_cmdline_param
95 #      PURPOSE:  checks all commandline parameters to validity
96 #   PARAMETERS:  none
97 #      RETURNS:  none
98 #  DESCRIPTION:  ????
99 #       THROWS:  no exceptions
100 #     COMMENTS:  none
101 #     SEE ALSO:  n/a
102 #===============================================================================
103 sub check_cmdline_param () {
104     my $err_config;
105     my $err_log;
106     my $err_pid;
107     my $err_counter = 0;
108     if( not defined( $cfg_file)) {
109         $err_config = "please specify a config file";
110         $err_counter += 1;
111     }
112     if( not defined( $log_file)) {
113         $err_log = "please specify a log file";
114         $err_counter += 1;
115     }
116     if( not defined( $pid_file)) {
117         $err_pid = "please specify a pid file";
118         $err_counter += 1;
119     }
120     if( $err_counter > 0 ) {
121         &usage( "", 1 );
122         if( defined( $err_config)) { print STDERR "$err_config\n"}
123         if( defined( $err_log)) { print STDERR "$err_log\n" }
124         if( defined( $err_pid)) { print STDERR "$err_pid\n"}
125         print STDERR "\n";
126         exit( -1 );
127     }
130 #===  FUNCTION  ================================================================
131 #         NAME:  check_pid
132 #      PURPOSE:  
133 #   PARAMETERS:  none
134 #      RETURNS:  none
135 #  DESCRIPTION:  ????
136 #       THROWS:  no exceptions
137 #     COMMENTS:  none
138 #     SEE ALSO:  n/a
139 #===============================================================================
140 sub check_pid { 
141     if( open( LOCK_FILE, "<$pid_file") ) {
142         $procid = <LOCK_FILE>;
143         if( defined $procid ) {
144             chomp( $procid );
145             if( -f "/proc/$procid/stat" ) {
146                 my($stat) = `cat /proc/$procid/stat` =~ m/$procid \((.+)\).*/;
147                 print "\t".$stat."\n";
148                 if( "sps-daemon.pl" eq $stat ) {
149                     close( LOCK_FILE );
150                     exit -1;
151                 }
152             }
153         }
154         close( LOCK_FILE );
155         unlink( $pid_file );
156     }
158     # Try to open PID file
159     if (!sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
160         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
161         if (open(LOCK_FILE, "<", $pid_file) && ($pid = <LOCK_FILE>)) {
162             chomp($pid);
163             $msg .= "(PID $pid)\n";
164         } else {
165             $msg .= "(unable to read PID)\n";
166         }
167         if ( ! $foreground ) {
168             daemon_log( $msg."\n");
169         } else {
170             print( STDERR " $msg " );
171         }
172         exit( -1 );
173     }
176 #===  FUNCTION  ================================================================
177 #         NAME:  read_configfile
178 #      PURPOSE:  read the configuration file and provide the programm with 
179 #                parameters
180 #   PARAMETERS:  none
181 #      RETURNS:  none
182 #  DESCRIPTION:  ????
183 #       THROWS:  no exceptions
184 #     COMMENTS:  none
185 #     SEE ALSO:  n/a
186 #===============================================================================
187 sub read_configfile {
188     my $log_time = localtime(time);
189     my $cfg;
190     if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
191         if( -r $cfg_file ) { 
192             $cfg = Config::IniFiles->new( -file => $cfg_file ); 
193         } else { usage( "Couldn't read config file: $cfg_file \n" ); }
194     } else { $cfg = Config::IniFiles->new() ; }
196     foreach my $section (keys %cfg_defaults) {      # "Parse" config into values
197         foreach my $param (keys %{$cfg_defaults{ $section }}) {
198             my $pinfo = $cfg_defaults{ $section }{ $param };
199             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
200         }
201     }
203     if(-e $log_file ) { unlink $log_file }
204     daemon_log("$log_time: config file read\n");
207 #===  FUNCTION  ================================================================
208 #         NAME:  daemon_log
209 #      PURPOSE:  log messages to specified logfile
210 #   PARAMETERS:  $msg, $level
211 #      RETURNS:  ????
212 #  DESCRIPTION:  Takes a message ($msg) and append it to the logfile. The 
213 #                standard log-level ($level) is 1. Messages whith higher level
214 #                than the verbosity-level (defined by commandline) are printed 
215 #                out to commandline. Messages with log-level lower than 2 are 
216 #                not logged to logfile!
217 #       THROWS:  no exceptions
218 #     COMMENTS:  none
219 #     SEE ALSO:  n/a
220 #===============================================================================
221 sub daemon_log {
222     my( $msg, $level ) = @_;
223     if(not defined $msg) { return } 
224     if(not defined $level) { $level = 1 }
225     open(LOG_HANDLE, ">>$log_file");
226     if(not defined open( LOG_HANDLE, ">>$log_file" ) ) { return }
227     chomp($msg);
228     if( $verbose >= $level ) { print "$msg"."\n" }
229     if( $level <= 1 ) { print LOG_HANDLE $msg."\n"  }
230     close( LOG_HANDLE );
231     }
233 #===  FUNCTION  ================================================================
234 #         NAME:  signal handler
235 #      PURPOSE:  catches signals from the programm and do diffrent things 
236 #                than default
237 #   PARAMETERS:  none
238 #      RETURNS:  none
239 #  DESCRIPTION:  sighandler
240 #       THROWS:  no exceptions
241 #     COMMENTS:  none
242 #     SEE ALSO:  n/a
243 #===============================================================================
244 sub sigINT {
245     my $log_time = localtime(time);
246     print "INT\n";
247     if( -p $fifo_path ) {
248         close FIFO  ;
249         unlink($fifo_path) ;
250         daemon_log( "$log_time: FIFO closed after signal INT!\n") ;
251         }
252     if(defined($ldap)) {
253         $ldap->unbind;
254     }
255     $SIG{INT} = "DEFAULT" ;
256     kill INT => $$ ;
258 $SIG{INT} = \&sigINT ;
260 #===  FUNCTION  ================================================================
261 #         NAME:  usage
262 #      PURPOSE:  
263 #   PARAMETERS:  none
264 #      RETURNS:  none
265 #  DESCRIPTION:  print out the usage of the program
266 #       THROWS:  no exceptions
267 #     COMMENTS:  none
268 #     SEE ALSO:  n/a
269 #===============================================================================
270 sub usage {
271         my( $text, $help ) = @_;
272         $text = undef if( "h" eq $text );
273         (defined $text) && print STDERR "\n$text\n";
274         if( (defined $help && $help) || (!defined $help && !defined $text) ) {
275                 print STDERR << "EOF" ;
276 usage: $0 [-hvf] [-c config, -l logfile, -p pidfile]
278     -h        : this (help) message
279     -c <file> : config file
280     -l <file> : log file (example: /var/log/sps/sps.log)
281     -p <file> : pid file (example: /var/run/sps/sps.pid)
282     -f        : foreground (don"t fork)
283     -v        : be verbose (multiple to increase verbosity)
284 EOF
285         }
286         print "\n" ;
290 #===  FUNCTION  ================================================================
291 #         NAME:  open_fifo
292 #      PURPOSE:  
293 #   PARAMETERS:  $fifo_path
294 #      RETURNS:  0: FIFO couldn"t be setup, 1: FIFO setup correctly
295 #  DESCRIPTION:  creates a FIFO at $fifo_path
296 #       THROWS:  no exceptions
297 #     COMMENTS:  none
298 #     SEE ALSO:  n/a
299 #===============================================================================
300 sub open_fifo {
301     my ($fifo_path) = @_ ;
302     my $log_time = localtime( time );
303     if( -p $fifo_path ) {
304         daemon_log("$log_time: FIFO at $fifo_path already exists\n");
305         return 0;
306         } 
307     POSIX::mkfifo($fifo_path, 0666) or die "can't mkfifo $fifo_path: $!";
308     daemon_log( "$log_time: FIFO started at $fifo_path\n" ) ;
309     return 1;
310     }
313 #===  FUNCTION  ================================================================
314 #         NAME:  add_ldap_entry
315 #      PURPOSE:  adds an element to ldap-tree
316 #   PARAMETERS:  
317 #      RETURNS:  none
318 #  DESCRIPTION:  ????
319 #       THROWS:  no exceptions
320 #     COMMENTS:  none
321 #     SEE ALSO:  n/a
322 #===============================================================================
323 sub add_ldap_entry {
324     my( $mac, $gotoSysStatus, $ip, $interface, $desc ) = @_;
325     #my $dn = encode_base64("cn=$mac,ou=incoming,$ldap_base", "");
326     my $dn = "cn=$mac,ou=incoming,$ldap_base";
328     # create LDAP entry 
329     my $entry = Net::LDAP::Entry->new( $dn );
330     $entry->dn($dn);
331     $entry->add("objectClass" => "goHard");
332     $entry->add("cn" => $mac);
333     if(defined $gotoSysStatus) {$entry->add("gotoSysStatus" => $gotoSysStatus)}
334     if(defined $ip) {$entry->add("ipHostNumber" => $ip) }
335     #if(defined $interface) { }
336     if(defined $desc) {$entry->add("description" => $desc) }
337     
338     # submit entry to LDAP
339     my $result = $entry->update ( $ldap ); 
340         
341     # for $result->code constants please look at Net::LDAP::Constant
342     my $log_time = localtime( time );
343     if($result->code == 68) {   # entry already exists 
344         daemon_log("WARNING: $log_time: $dn ".$result->error, 3);
345     } elsif($result->code == 0) {   # everything went fine
346         daemon_log("$log_time: add entry $dn to ldap", 1);
347     } else {  # if any other error occur
348         daemon_log("ERROR: $log_time: $dn, ".$result->code.", ".$result->error, 1);
349     }
350     return;
354 #===  FUNCTION  ================================================================
355 #         NAME:  change_ldap_entry
356 #      PURPOSE:  ????
357 #   PARAMETERS:  ????
358 #      RETURNS:  ????
359 #  DESCRIPTION:  ????
360 #       THROWS:  no exceptions
361 #     COMMENTS:  none
362 #     SEE ALSO:  n/a
363 #===============================================================================
364 sub change_ldap_entry {
365     my ($mac, $gotoSysStatus ) = @_;
366     #my $dn = encode_base64("cn=$mac,ou=incoming,$ldap_base", "");
367     my $dn = "cn=$mac,ou=incoming,$ldap_base";
368     my $result = $ldap->modify( $dn, replace => {'gotoSysStatus' => $gotoSysStatus } );
370     # for $result->code constants please look at Net::LDAP::Constant
371     my $log_time = localtime( time );
372     if($result->code == 32) {   # entry doesnt exists 
373         &add_ldap_entry($mac, $gotoSysStatus);
374     } elsif($result->code == 0) {   # everything went fine
375         daemon_log("$log_time: entry $dn changed successful", 1);
376     } else {  # if any other error occur
377         daemon_log("ERROR: $log_time: $dn, ".$result->code.", ".$result->error, 1);
378     }
380     return;
382 # check wether entry exists
383     # if multiple time
384     #   raise error
385     # if true
386     #   change attr of entry
387     # if false
388     #   add entry to ldap (invoke add_ldap_entry)
389     #
392 #========= MAIN = main ========================================================
393 daemon_log( "####### START DAEMON ######\n", 1 );
394 #&check_cmdline_param ;
395 &check_pid;
396 &open_fifo($fifo_path);
398 # Just fork, if we"re not in foreground mode
399 if( ! $foreground ) { $pid = fork(); }
400 else { $pid = $$; }
402 # Do something useful - put our PID into the pid_file
403 if( 0 != $pid ) {
404     open( LOCK_FILE, ">$pid_file" );
405     print LOCK_FILE "$pid\n";
406     close( LOCK_FILE );
407     if( !$foreground ) { exit( 0 ) };
411 if( not -p $fifo_path ) { die "fifo file disappeared\n" }
412 sysopen(FIFO, $fifo_path, O_RDONLY) or die "can't read from $fifo_path: $!" ;
414 while( 1 ) {
415     # wenn FIFO nicht offen, dann öffne ihn
417     # checke alle prozesse im hash daemon_children ob sie noch aktiv sind, wenn
418     # nicht, dann entferne prozess aus hash
419     while( (my $key, my $val) = each( %daemon_children) ) {
420         my $status = waitpid( $key, &WNOHANG) ;
421         if( $status == -1 ) { 
422             delete $daemon_children{$key} ; 
423             daemon_log("childprocess finished: $key", 3) ;
424         }
425     }
427     # ist die max_process anzahl von prozesskindern erreicht, dann warte und 
428     # prüfe erneut, ob in der zwischenzeit prozesse fertig geworden sind
429     if( keys( %daemon_children ) >= $max_process ) { 
430         sleep($max_process_timeout) ;
431         next ;
432     }
434     my $msg = <FIFO>;
435     if( not defined( $msg )) { next ; }
436     
437     chomp( $msg );
438     if( length( $msg ) == 0 ) { next ; }
440     my $forked_pid = fork();
441 #=== PARENT = parent ==========================================================
442     if ( $forked_pid != 0 ) { 
443         daemon_log("childprocess forked: $forked_pid", 3) ;
444         $daemon_children{$forked_pid} = 0 ;
445     }
446 #=== CHILD = child ============================================================
447     else {
448         # parse the incoming message from arp, split the message and return 
449         # the values in an array. not defined values are set to "none" 
450         #my ($mac, $ip, $interface, $arp_sig, $desc) = &parse_input( $msg ) ;
451         daemon_log( "childprocess read from arp: $fifo_path\nline: $msg", 3);
452         my ($mac, $ip, $interface, $arp_sig, $desc) = split('\s', $msg, 5);
454         # create connection to LDAP
455         $ldap = Net::LDAP->new( "localhost" ) or die "$@";
456         $ldap->bind($bind_phrase,
457                     password => $password,
458                     ) ;
459         
460         switch($arp_sig) {
461             case 0 {&change_ldap_entry($mac, "ip-changed")} 
462             case 1 {&change_ldap_entry($mac, "mac-not-whitelisted")}
463             case 2 {&change_ldap_entry($mac, "mac-in-blacklist")}
464             case 3 {&add_ldap_entry($mac, "new-mac-address", $ip, $interface,
465                                     $desc )}
466             case 4 {&change_ldap_entry($mac, "unauthorized-arp-request")}
467             case 5 {&change_ldap_entry($mac, "abusive-number-of-arp-requests")}
468             case 6 {&change_ldap_entry($mac, "ether-and-arp-mac-differs")}
469             case 7 {&change_ldap_entry($mac, "flood-detected")}
470             case 8 {&add_ldap_entry($mac, $ip, "new-system")}
471             case 9 {&change_ldap_entry($mac, "mac-changed")}
472         }
475         # ldap search
476 #        my $base_phrase = "dc=gonicus,dc=de";
477 #        my $filter_phrase = "cn=keinesorge";
478 #        my $attrs_phrase = "cn macAdress";
479 #        my $msg_search = $ldap->search( base   => $base_phrase,
480 #                                        filter => $filter_phrase,
481 #                                        attrs => $attrs_phrase,
482 #                                        );
483 #        $msg_search->code && die $msg_search->error;
484 #        
485 #        my @entries = $msg_search->entries;
486 #        my $max = $msg_search->count;
487 #        print "anzahl der entries: $max\n";
488 #        my $i;
489 #        for ( $i = 0 ; $i < $max ; $i++ ) {
490 #            my $entry = $msg_search->entry ( $i );
491 #            foreach my $attr ( $entry->attributes ) {
492 #                if( not $attr eq "cn") {
493 #                    next;
494 #                }
495 #                print join( "\n ", $attr, $entry->get_value( $attr ) ), "\n\n";
496 #            }
497 #        }
499         # ldap add
500        
501         
502         sleep( 1 ) ;
503         $ldap->unbind;
504         exit;
505     }