Code

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