Code

Updated debian infrastructure
[gosa.git] / gosa-si / gosa-si-server
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 #         FILE:  gosa-sd
5 #
6 #        USAGE:  ./gosa-sd
7 #
8 #  DESCRIPTION:
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl libipc-shareable-perl libdata-dumper-simple-perl
12 #         BUGS:  ---
13 #        NOTES:
14 #       AUTHOR:   (Andreas Rettenberger), <rettenberger@gonicus.de>
15 #      COMPANY:
16 #      VERSION:  1.0
17 #      CREATED:  12.09.2007 08:54:41 CEST
18 #     REVISION:  ---
19 #===============================================================================
22 use strict;
23 use warnings;
24 use Getopt::Long;
25 use Config::IniFiles;
26 use POSIX;
27 use Time::HiRes qw( gettimeofday );
29 use Fcntl;
30 use IO::Socket::INET;
31 use Crypt::Rijndael;
32 use MIME::Base64;
33 use Digest::MD5  qw(md5 md5_hex md5_base64);
34 use XML::Simple;
35 use Data::Dumper;
36 use Sys::Syslog qw( :DEFAULT setlogsock);
37 use Cwd;
38 use File::Spec;
39 use IPC::Shareable qw( :lock);
40 IPC::Shareable->clean_up_all;
41 use GOSA::GosaSupportDaemon;
42 use GOSA::DBsqlite;
44 my $modules_path = "/usr/lib/gosa-si/modules";
45 use lib "/usr/lib/gosa-si/modules";
47 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
48 my ($bus, $msg_to_bus, $bus_cipher);
49 my ($server, $server_mac_address, $server_events);
50 my ($gosa_server, $job_queue_timeout, $job_queue_table_name, $job_queue_file_name);
51 my ($known_daemons, $shmda, $known_clients, $shmcl, $known_modules);
52 my ($max_clients);
53 my ($pid_file, $procid, $pid, $log_file);
54 my (%free_child, %busy_child, $child_max, $child_min, %child_alive_time, $child_timeout);
55 my ($arp_activ, $arp_fifo, $arp_fifo_path);
57 # variables declared in config file are always set to 'our'
58 our (%cfg_defaults, $log_file, $pid_file, 
59     $bus_activ, $bus_passwd, $bus_ip, $bus_port,
60     $server_activ, $server_ip, $server_port, $server_passwd, $max_clients,
61     $arp_activ, $arp_fifo_path,
62     $gosa_activ, $gosa_passwd, $gosa_ip, $gosa_port, $gosa_timeout,
63 );
65 # additional variable which should be globaly accessable
66 our $xml;
67 our $server_address;
68 our $bus_address;
69 our $gosa_address;
70 our $no_bus;
71 our $no_arp;
72 our $verbose;
73 our $forground;
74 our $cfg_file;
76 # specifies the verbosity of the daemon_log
77 $verbose = 0 ;
79 # if foreground is not null, script will be not forked to background
80 $foreground = 0 ;
82 # specifies the timeout seconds while checking the online status of a registrating client
83 $ping_timeout = 5;
85 $no_bus = 0;
87 $no_arp = 0;
89 # name of table for storing gosa jobs
90 $job_queue_table_name = 'jobs';
92 # holds all other gosa-sd as well as the gosa-sd-bus
93 our $known_daemons = {};
94 our $shmda = tie($known_daemons, 'IPC::Shareable', undef, {create => 1, 
95                                                             exclusive => 1, 
96                                                             mode => 0666, 
97                                                             destroy => 1,
98                                                             });
99 # holds all registrated clients
100 our $known_clients = {};
101 our $shmcl = tie($known_clients, 'IPC::Shareable', undef, {create => 1, 
102                                                             exclusive => 1, 
103                                                             mode => 0666, 
104                                                             destroy => 1,
105                                                             });
108 %cfg_defaults =
109 ("general" =>
110     {"log_file" => [\$log_file, "/var/run/".$0.".log"],
111     "pid_file" => [\$pid_file, "/var/run/".$0.".pid"],
112     "child_max" => [\$child_max, 10],
113     "child_min" => [\$child_min, 3],
114     "child_timeout" => [\$child_timeout, 180],
115     "job_queue_timeout" => [\$job_queue_timeout, undef],
116     "job_queue_file_name" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
117    },
118 "bus" =>
119     {"bus_activ" => [\$bus_activ, "on"],
120     "bus_passwd" => [\$bus_passwd, ""],
121     "bus_ip" => [\$bus_ip, ""],
122     "bus_port" => [\$bus_port, "20080"],
123     },
124 "server" =>
125     {"server_activ" => [\$server_activ, "on"],
126     "server_ip" => [\$server_ip, ""],
127     "server_port" => [\$server_port, "20081"],
128     "server_passwd" => [\$server_passwd, ""],
129     "max_clients" => [\$max_clients, 100],
130     },
131 "arp" =>
132     {"arp_activ" => [\$arp_activ, "on"],
133     "arp_fifo_path" => [\$arp_fifo_path, "/var/run/gosa-si/arp-notify"],
134     },
135 "gosa" =>
136     {"gosa_activ" => [\$gosa_activ, "on"],
137     "gosa_ip" => [\$gosa_ip, ""],
138     "gosa_port" => [\$gosa_port, "20082"],
139     "gosa_passwd" => [\$gosa_passwd, "none"],
140     },
141     );
144 #===  FUNCTION  ================================================================
145 #         NAME:  usage
146 #   PARAMETERS:  nothing
147 #      RETURNS:  nothing
148 #  DESCRIPTION:  print out usage text to STDERR
149 #===============================================================================
150 sub usage {
151     print STDERR << "EOF" ;
152 usage: $0 [-hvf] [-c config]
154            -h        : this (help) message
155            -c <file> : config file
156            -f        : foreground, process will not be forked to background
157            -v        : be verbose (multiple to increase verbosity)
158            -no-bus   : starts $0 without connection to bus
159            -no-arp   : starts $0 without connection to arp module
160  
161 EOF
162     print "\n" ;
166 #===  FUNCTION  ================================================================
167 #         NAME:  read_configfile
168 #   PARAMETERS:  cfg_file - string -
169 #      RETURNS:  nothing
170 #  DESCRIPTION:  read cfg_file and set variables
171 #===============================================================================
172 sub read_configfile {
173     my $cfg;
174     if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
175         if( -r $cfg_file ) {
176             $cfg = Config::IniFiles->new( -file => $cfg_file );
177         } else {
178             print STDERR "Couldn't read config file!";
179         }
180     } else {
181         $cfg = Config::IniFiles->new() ;
182     }
183     foreach my $section (keys %cfg_defaults) {
184         foreach my $param (keys %{$cfg_defaults{ $section }}) {
185             my $pinfo = $cfg_defaults{ $section }{ $param };
186             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
187         }
188     }
192 #===  FUNCTION  ================================================================
193 #         NAME:  logging
194 #   PARAMETERS:  level - string - default 'info'
195 #                msg - string -
196 #                facility - string - default 'LOG_DAEMON'
197 #      RETURNS:  nothing
198 #  DESCRIPTION:  function for logging
199 #===============================================================================
200 sub daemon_log {
201     # log into log_file
202     my( $msg, $level ) = @_;
203     if(not defined $msg) { return }
204     if(not defined $level) { $level = 1 }
205     if(defined $log_file){
206         open(LOG_HANDLE, ">>$log_file");
207         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
208             print STDERR "cannot open $log_file: $!";
209             return }
210             chomp($msg);
211             if($level <= $verbose){
212                 print LOG_HANDLE "$level $msg\n";
213                 if(defined $foreground) { print $msg."\n" }
214             }
215     }
216 #    close( LOG_HANDLE );
217 #log into syslog
218 #    my ($msg, $level, $facility) = @_;
219 #    if(not defined $msg) {return}
220 #    if(not defined $level) {$level = "info"}
221 #    if(not defined $facility) {$facility = "LOG_DAEMON"}
222 #    openlog($0, "pid,cons,", $facility);
223 #    syslog($level, $msg);
224 #    closelog;
225 #    return;
229 #===  FUNCTION  ================================================================
230 #         NAME:  check_cmdline_param
231 #   PARAMETERS:  nothing
232 #      RETURNS:  nothing
233 #  DESCRIPTION:  validates commandline parameter
234 #===============================================================================
235 sub check_cmdline_param () {
236     my $err_config;
237     my $err_counter = 0;
238     if( not defined( $cfg_file)) {
239         #$err_config = "please specify a config file";
240         #$err_counter += 1;
241         my $cwd = getcwd;
242         my $name = "/etc/gosa-si/server.conf";
243         $cfg_file = File::Spec->catfile( $cwd, $name );
244     }
245     if( $err_counter > 0 ) {
246         &usage( "", 1 );
247         if( defined( $err_config)) { print STDERR "$err_config\n"}
248         print STDERR "\n";
249         exit( -1 );
250     }
254 #===  FUNCTION  ================================================================
255 #         NAME:  check_pid
256 #   PARAMETERS:  nothing
257 #      RETURNS:  nothing
258 #  DESCRIPTION:  handels pid processing
259 #===============================================================================
260 sub check_pid {
261     $pid = -1;
262     # Check, if we are already running
263     if( open(LOCK_FILE, "<$pid_file") ) {
264         $pid = <LOCK_FILE>;
265         if( defined $pid ) {
266             chomp( $pid );
267             if( -f "/proc/$pid/stat" ) {
268                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
269                 if( $0 eq $stat ) {
270                     close( LOCK_FILE );
271                     exit -1;
272                 }
273             }
274         }
275         close( LOCK_FILE );
276         unlink( $pid_file );
277     }
279     # create a syslog msg if it is not to possible to open PID file
280     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
281         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
282         if (open(LOCK_FILE, '<', $pid_file)
283                 && ($pid = <LOCK_FILE>))
284         {
285             chomp($pid);
286             $msg .= "(PID $pid)\n";
287         } else {
288             $msg .= "(unable to read PID)\n";
289         }
290         if( ! ($foreground) ) {
291             openlog( $0, "cons,pid", "daemon" );
292             syslog( "warning", $msg );
293             closelog();
294         }
295         else {
296             print( STDERR " $msg " );
297         }
298         exit( -1 );
299     }
303 #===  FUNCTION  ================================================================
304 #         NAME:  get_ip_and_mac 
305 #   PARAMETERS:  nothing
306 #      RETURNS:  (ip, mac) 
307 #  DESCRIPTION:  executes /sbin/ifconfig and parses the output, the first occurence 
308 #                of a inet address is returned as well as the mac address in the line
309 #                above the inet address
310 #===============================================================================
311 sub get_ip_and_mac {
312     my $ip = "0.0.0.0.0"; # Defualt-IP
313     my $mac = "00:00:00:00:00:00";  # Default-MAC
314     my @ifconfig = qx(/sbin/ifconfig);
315     foreach(@ifconfig) {
316         if (/Hardware Adresse (\S{2}):(\S{2}):(\S{2}):(\S{2}):(\S{2}):(\S{2})/) {
317             $mac = "$1:$2:$3:$4:$5:$6";
318             next;
319         }
320         if (/inet Adresse:(\d+).(\d+).(\d+).(\d+)/) {
321             $ip = "$1.$2.$3.$4";
322             last;
323         }
324     }
325     return ($ip, $mac);
330 #===  FUNCTION  ================================================================
331 #         NAME:  import_modules
332 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
333 #                are stored
334 #      RETURNS:  nothing
335 #  DESCRIPTION:  each file in module_path which ends with '.pm' is imported by 
336 #                "require 'file';"
337 #===============================================================================
338 sub import_modules {
339     daemon_log(" ", 1);
341     if (not -e $modules_path) {
342         daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
343     }
345     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
346     while (defined (my $file = readdir (DIR))) {
347         if (not $file =~ /(\S*?).pm$/) {
348             next;
349         }
350         eval { require $file; };
351         if ($@) {
352             daemon_log("ERROR: gosa-sd could not load module $file", 1);
353             daemon_log("$@", 5);
354             next;
355         }
356         my $mod_name = $1;
357         #my $module_tag_hash = eval( $mod_name.'::get_module_tags()' );
359         my $info = eval($mod_name.'::get_module_info()');
360         my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
361         $known_modules->{$mod_name} = $info;
363         daemon_log("module $mod_name loaded", 1);
364     }   
366     # for debugging
367     #while ( my ($module, $tag_hash) = each(%$known_modules)) {
368     #    print "\tmodule: $module"."\n";   
369     #    print "\ttags: ".join(", ", keys(%$tag_hash))."\n";
370     #}
371     close (DIR);
375 #===  FUNCTION  ================================================================
376 #         NAME:  sig_int_handler
377 #   PARAMETERS:  signal - string - signal arose from system
378 #      RETURNS:  noting
379 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
380 #===============================================================================
381 sub sig_int_handler {
382     my ($signal) = @_;
383     if($server){
384         close($server);
385         daemon_log("daemon server closed", 1);
386     }
387     if( -p $arp_fifo_path ) {
388         close $arp_fifo  ;
389         unlink($arp_fifo_path) ;
390         daemon_log("ARP_FIFO closed", 1) ;
391     }
393     if($gosa_server){
394         close($gosa_server);
395         daemon_log("gosa server closed", 1);
396     }
398     print STDERR "$signal\n";
399     
400     exit(1);
402 $SIG{INT} = \&sig_int_handler;
405 #===  FUNCTION  ================================================================
406 #         NAME:  activating_child
407 #   PARAMETERS:  msg - string - incoming message
408 #                host - string - host from which the incomming message comes
409 #      RETURNS:  nothing
410 #  DESCRIPTION:  handels the distribution of incoming messages to working childs
411 #===============================================================================
412 sub activating_child {
413     my ($msg, $host, $client) = @_;
414     my $child = &get_processing_child();
415     my $pipe_wr = $$child{'pipe_wr'};
416     my $pipe_rd = $$child{'pipe_rd'};
417     $$child{client_ref} = $client;
418     
419     daemon_log("activating: childpid:$$child{'pid'}", 5);
421     print $pipe_wr $msg.".".$host."\n";
423     return;
427 #===  FUNCTION  ================================================================
428 #         NAME:  get_processing_child
429 #   PARAMETERS:  nothing
430 #      RETURNS:  child - hash - holding the process id and the references to the pipe
431 #                               handles pipe_wr and pipe_rd
432 #  DESCRIPTION:  handels the forking, reactivating and keeping alive tasks
433 #===============================================================================
434 sub get_processing_child {
435     my $child;
437     while(my ($key, $val) = each(%free_child)) {
438         my $exitus_pid = waitpid($key, WNOHANG);
439         if($exitus_pid != 0) {
440             delete $free_child{$key};
441         }
442         daemon_log("free child:$key", 5);
443     }
444     # check @free_child and @busy_child
445     my $free_len = scalar(keys(%free_child));
446     my $busy_len = scalar(keys(%busy_child));
447     daemon_log("free children $free_len, busy children $busy_len", 5);
449     # if there is a free child, let the child work
450     if($free_len > 0){
451         my @keys = keys(%free_child);
452         $child = $free_child{$keys[0]};
453         if(defined $child) {
454             $busy_child{$$child{'pid'}} = $child ;
455             delete $free_child{$$child{'pid'}};
456         }
457         return $child;
458     }
460     # no free child, try to fork another one
461     if($free_len + $busy_len < $child_max) {
463         daemon_log("not enough children, create a new one", 5);
465         # New pipes for communication
466         my( $PARENT_wr, $PARENT_rd );
467         my( $CHILD_wr, $CHILD_rd );
468         pipe( $CHILD_rd,  $PARENT_wr );
469         pipe( $PARENT_rd, $CHILD_wr  );
470         $PARENT_wr->autoflush(1);
471         $CHILD_wr->autoflush(1);
473         ############
474         # fork child
475         ############
476         my $child_pid = fork();
477         
478         #CHILD
479         if($child_pid == 0) {
480             # Close unused pipes
481             close( $CHILD_rd );
482             close( $CHILD_wr );
483             while( 1 ) {
484                 my $rbits = "";
485                 vec( $rbits, fileno $PARENT_rd , 1 ) = 1;
486                 my $nf = select($rbits, undef, undef, $child_timeout);
487                 if($nf < 0 ) {
488                     die "select(): $!\n";
489                 } elsif (! $nf) {
490                     # if already child_min childs are alive, then leave loop
491                     $free_len = scalar(keys(%free_child));
492                     $busy_len = scalar(keys(%busy_child));
493                     if($free_len + $busy_len >= $child_min) {
494                         last;
495                     } else {
496                         redo;
497                     }
498                 }
500                 # a job for a child arise
501                 if ( vec $rbits, fileno $PARENT_rd, 1 ) {
502                     # read everything from pipe
503                     my $msg = "";
504                     $PARENT_rd->blocking(0);
505                     while(1) {
506                         my $read = <$PARENT_rd>;
507                         if(not defined $read) { last}
508                         $msg .= $read;
509                     }
511                     ######################################
512                     # forward msg to all imported modules 
513                     no strict "refs";
514                     my $answer;
515                     my %act_modules = %$known_modules;
516                     while( my ($module, $info) = each(%act_modules)) {
517                             my $tmp = &{ $module."::process_incoming_msg" }($msg);
518                             if (defined $tmp) {
519                                 $answer = $tmp;
520                             }
521                     }        
523                     #&print_known_daemons();
524                     #&print_known_clients();
526                     daemon_log("processing of msg finished", 5);
527  
528                    if (defined $answer) {
529                         print $PARENT_wr $answer."\n";
530                         daemon_log("with answer: $answer", 5);
531                         daemon_log(" ", 5);
532                     } else {
533                         print $PARENT_wr "done"."\n";
534                         daemon_log(" ", 5);
535                     }
536                     redo;
537                 }
538             }
539             # childs leaving the loop are allowed to die
540             exit(0);
543         #PARENT
544         } else {
545             # Close unused pipes
546             close( $PARENT_rd );
547             close( $PARENT_wr );
549             # add child to child alive hash
550             my %child_hash = (
551                     'pid' => $child_pid,
552                     'pipe_wr' => $CHILD_wr,
553                     'pipe_rd' => $CHILD_rd,
554                     'client_ref' => "",
555                     );
557             $child = \%child_hash;
558             $busy_child{$$child{'pid'}} = $child;
559             return $child;
560         }
561     }
565 #===  FUNCTION  ================================================================
566 #         NAME:  read_from_socket
567 #   PARAMETERS:  socket fh - 
568 #      RETURNS:  result string - readed characters from socket
569 #  DESCRIPTION:  reads data from socket in 16 byte steps
570 #===============================================================================
571 sub read_from_socket {
572     my ($socket) = @_;
573     my $result = "";
575     $socket->blocking(1);
576     $result = <$socket>;
578     $socket->blocking(0);
579     while ( my $char = <$socket> ) {
580         if (not defined $char) { last }
581         $result .= $char;
582     }
584     return $result;
588 #===  FUNCTION  ================================================================
589 #         NAME:  print_known_daemons
590 #   PARAMETERS:  nothing
591 #      RETURNS:  nothing
592 #  DESCRIPTION:  nomen est omen
593 #===============================================================================
594 sub print_known_daemons {
595     my ($tmp) = @_ ;
596     print "####################################\n";
597     print "# status of known_daemons\n";
598     $shmda->shlock(LOCK_EX);
599     my @hosts = keys %$known_daemons;
600     foreach my $host (@hosts) {
601         my $status = $known_daemons->{$host}->{status} ;
602         my $passwd = $known_daemons->{$host}->{passwd};
603         my $timestamp = $known_daemons->{$host}->{timestamp};
604         print "$host\n";
605         print "\tstatus:    $status\n";
606         print "\tpasswd:    $passwd\n";
607         print "\ttimestamp: $timestamp\n";
608     }
609     $shmda->shunlock(LOCK_EX);
610     print "####################################\n";
611     return;
615 #===  FUNCTION  ================================================================
616 #         NAME:  create_known_daemon
617 #   PARAMETERS:  hostname - string - key for the hash known_daemons
618 #      RETURNS:  nothing
619 #  DESCRIPTION:  creates a dummy entry for hostname in known_daemons
620 #===============================================================================
621 sub create_known_daemon {
622     my ($hostname) = @_;
623     $shmda->shlock(LOCK_EX);
624     $known_daemons->{$hostname} = {};
625     $known_daemons->{$hostname}->{status} = "none";
626     $known_daemons->{$hostname}->{passwd} = "none";
627     $known_daemons->{$hostname}->{timestamp} = "none";
628     $shmda->shunlock(LOCK_EX); 
629     return;  
633 #===  FUNCTION  ================================================================
634 #         NAME:  add_content2known_daemons
635 #   PARAMETERS:  hostname - string - ip address and port of host (required)
636 #                status - string - (optional)
637 #                passwd - string - (optional)
638 #                mac_address - string - mac address of host (optional)
639 #      RETURNS:  nothing
640 #  DESCRIPTION:  nome est omen and updates each time the timestamp of hostname
641 #===============================================================================
642 sub add_content2known_daemons {
643     my $arg = {
644         hostname => undef, status => undef, passwd => undef,
645         mac_address => undef, events => undef, 
646         @_ };
647     my $hostname = $arg->{hostname};
648     my $status = $arg->{status};
649     my $passwd = $arg->{passwd};
650     my $mac_address = $arg->{mac_address};
651     my $events = $arg->{events};
653     if (not defined $hostname) {
654         daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1);
655         return;
656     }
658     my ($seconds, $minutes, $hours, $monthday, $month,
659     $year, $weekday, $yearday, $sommertime) = localtime(time);
660     $hours = $hours < 10 ? $hours = "0".$hours : $hours;
661     $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
662     $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
663     $month+=1;
664     $month = $month < 10 ? $month = "0".$month : $month;
665     $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
666     $year+=1900;
667     my $t = "$year$month$monthday$hours$minutes$seconds";
668     
669     $shmda->shlock(LOCK_EX);
670     if (defined $status) {
671         $known_daemons->{$hostname}->{status} = $status;
672     }
673     if (defined $passwd) {
674         $known_daemons->{$hostname}->{passwd} = $passwd;
675     }
676     if (defined $mac_address) {
677         $known_daemons->{$hostname}->{mac_address} = $mac_address;
678     }
679     if (defined $events) {
680         $known_daemons->{$hostname}->{events} = $events;
681     }
682     $known_daemons->{$hostname}->{timestamp} = $t;
683     $shmda->shlock(LOCK_EX);
684     return;
688 #===  FUNCTION  ================================================================
689 #         NAME:  update_known_daemons
690 #   PARAMETERS:  hostname - string - ip address and port of host (required)
691 #                status - string - (optional)
692 #                passwd - string - (optional)
693 #                client - string - ip address and port of client (optional)
694 #      RETURNS:  nothing
695 #  DESCRIPTION:  nome est omen and updates each time the timestamp of hostname
696 #===============================================================================
697 sub update_known_daemons {
698     my $arg = {
699         hostname => undef, status => undef, passwd => undef,
700         @_ };
701     my $hostname = $arg->{hostname};
702     my $status = $arg->{status};
703     my $passwd = $arg->{passwd};
705     if (not defined $hostname) {
706         daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1);
707         return;
708     }
710     my ($seconds, $minutes, $hours, $monthday, $month,
711     $year, $weekday, $yearday, $sommertime) = localtime(time);
712     $hours = $hours < 10 ? $hours = "0".$hours : $hours;
713     $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
714     $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
715     $month+=1;
716     $month = $month < 10 ? $month = "0".$month : $month;
717     $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
718     $year+=1900;
719     my $t = "$year$month$monthday$hours$minutes$seconds";
721     $shmda->shlock(LOCK_EX);
722     if (defined $status) {
723         $known_daemons->{$hostname}->{status} = $status;
724     }
725     if (defined $passwd) {
726         $known_daemons->{$hostname}->{passwd} = $passwd;
727     }
728     $known_daemons->{$hostname}->{timestamp} = $t;
729     $shmda->shunlock(LOCK_EX);
730     return;
734 #===  FUNCTION  ================================================================
735 #         NAME:  print_known_clients 
736 #   PARAMETERS:  nothing
737 #      RETURNS:  nothing
738 #  DESCRIPTION:  nomen est omen
739 #===============================================================================
740 sub print_known_clients {
742     print "####################################\n";
743     print "# status of known_clients\n";
744     $shmcl->shlock(LOCK_EX);
745     my @hosts = keys %$known_clients;
746     if (@hosts) {
747         foreach my $host (@hosts) {
748             my $status = $known_clients->{$host}->{status} ;
749             my $passwd = $known_clients->{$host}->{passwd};
750             my $timestamp = $known_clients->{$host}->{timestamp};
751             my $mac_address = $known_clients->{$host}->{mac_address};
752             my $events = $known_clients->{$host}->{events};
753             print "$host\n";
754             print "\tstatus:      $status\n";
755             print "\tpasswd:      $passwd\n";
756             print "\ttimestamp:   $timestamp\n";
757             print "\tmac_address: $mac_address\n";
758             print "\tevents:      $events\n";
759         }
760     }
761     $shmcl->shunlock(LOCK_EX);
762     print "####################################\n";
763     return;
767 #===  FUNCTION  ================================================================
768 #         NAME:  create_known_client
769 #   PARAMETERS:  hostname - string - key for the hash known_clients
770 #      RETURNS:  nothing
771 #  DESCRIPTION:  creates a dummy entry for hostname in known_clients
772 #===============================================================================
773 sub create_known_client {
774     my ($hostname) = @_;
775     $shmcl->shlock(LOCK_EX);
776     $known_clients->{$hostname} = {};
777     $known_clients->{$hostname}->{status} = "none";
778     $known_clients->{$hostname}->{passwd} = "none";
779     $known_clients->{$hostname}->{timestamp} = "none";
780     $known_clients->{$hostname}->{mac_address} = "none";
781     $known_clients->{$hostname}->{events} = "none";
782     $shmcl->shunlock(LOCK_EX); 
783     return;  
787 #===  FUNCTION  ================================================================
788 #         NAME:  add_content2known_clients
789 #   PARAMETERS:  hostname - string - ip address and port of host (required)
790 #                status - string - (optional)
791 #                passwd - string - (optional)
792 #                mac_address - string - (optional)
793 #                events - string - event of client, executable skripts 
794 #                under /etc/gosac/events
795 #      RETURNS:  nothing
796 #  DESCRIPTION:  nome est omen and updates each time the timestamp of hostname
797 #===============================================================================
798 sub add_content2known_clients {
799     my $arg = {
800         hostname => undef, status => undef, passwd => undef,
801         mac_address => undef, events => undef, 
802         @_ };
803     my $hostname = $arg->{hostname};
804     my $status = $arg->{status};
805     my $passwd = $arg->{passwd};
806     my $mac_address = $arg->{mac_address};
807     my $events = $arg->{events};
809     if (not defined $hostname) {
810         daemon_log("ERROR: function add_content2known_clients is not invoked with requiered parameter 'hostname'", 1);
811         return;
812     }
814     my ($seconds, $minutes, $hours, $monthday, $month,
815     $year, $weekday, $yearday, $sommertime) = localtime(time);
816     $hours = $hours < 10 ? $hours = "0".$hours : $hours;
817     $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
818     $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
819     $month+=1;
820     $month = $month < 10 ? $month = "0".$month : $month;
821     $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
822     $year+=1900;
823     my $t = "$year$month$monthday$hours$minutes$seconds";
824     
825     $shmcl->shlock(LOCK_EX);
826     if (defined $status) {
827         $known_clients->{$hostname}->{status} = $status;
828     }
829     if (defined $passwd) {
830         $known_clients->{$hostname}->{passwd} = $passwd;
831     }
832     if (defined $mac_address) {
833         $known_clients->{$hostname}->{mac_address} = $mac_address;
834     }
835     if (defined $events) {
836         $known_clients->{$hostname}->{events} = $events;
837     }
838     $known_clients->{$hostname}->{timestamp} = $t;
839     $shmcl->shlock(LOCK_EX);
840     return;
842  
844 #===  FUNCTION  ================================================================
845 #         NAME:  
846 #   PARAMETERS:  
847 #      RETURNS:  
848 #  DESCRIPTION:  
849 #===============================================================================    
850 sub clean_up_known_clients {
851     my ($address) = @_ ;
852     
853     if (not exists $known_clients->{$address}) {
854         daemon_log("cannot prune known_clients from $address, client not known", 5);
855         return;
856     }
858     delete $known_clients->{$address};
860     # send bus a msg that address was deleted from known_clients
861     my $out_hash = &create_xml_hash('delete_client', $server_address, $bus_address, $address);
862     &send_msg_hash2bus($out_hash);
864     daemon_log("client $address deleted from known_clients because of multiple down time", 3);
865     return;
869 #===  FUNCTION  ================================================================
870 #         NAME:  update_known_clients
871 #   PARAMETERS:  hostname - string - ip address and port of host (required)
872 #                status - string - (optional)
873 #                passwd - string - (optional)
874 #                client - string - ip address and port of client (optional)
875 #      RETURNS:  nothing
876 #  DESCRIPTION:  nome est omen and updates each time the timestamp of hostname
877 #===============================================================================
878 sub update_known_clients {
879     my $arg = {
880         hostname => undef, status => undef, passwd => undef,
881         mac_address => undef, events => undef,
882         @_ };
883     my $hostname = $arg->{hostname};
884     my $status = $arg->{status};
885     my $passwd = $arg->{passwd};
886     my $mac_address = $arg->{mac_address};
887     my $events = $arg->{events};
889     if (not defined $hostname) {
890         daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1);
891         return;
892     }
894     my ($seconds, $minutes, $hours, $monthday, $month,
895     $year, $weekday, $yearday, $sommertime) = localtime(time);
896     $hours = $hours < 10 ? $hours = "0".$hours : $hours;
897     $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
898     $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
899     $month+=1;
900     $month = $month < 10 ? $month = "0".$month : $month;
901     $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
902     $year+=1900;
903     my $t = "$year$month$monthday$hours$minutes$seconds";
905     $shmcl->shlock(LOCK_EX);
906     if (defined $status) {
907         $known_clients->{$hostname}->{status} = $status;
908     }
909     if (defined $passwd) {
910         $known_clients->{$hostname}->{passwd} = $passwd;
911     }
912     if (defined $mac_address) {
913         $known_clients->{$hostname}->{mac_address} = $mac_address; 
914     }
915     if (defined $events) {
916         $known_clients->{$hostname}->{events} = $events;
917     }
918     $known_clients->{$hostname}->{timestamp} = $t;
919     $shmcl->shunlock(LOCK_EX);
920     return;
929 #==== MAIN = main ==============================================================
931 #  parse commandline options
932 Getopt::Long::Configure( "bundling" );
933 GetOptions("h|help" => \&usage,
934         "c|config=s" => \$cfg_file,
935         "f|foreground" => \$foreground,
936         "v|verbose+" => \$verbose,
937         "no-bus+" => \$no_bus,
938         "no-arp+" => \$no_arp,
939            );
941 #  read and set config parameters
942 &check_cmdline_param ;
943 &read_configfile;
944 &check_pid;
946 $SIG{CHLD} = 'IGNORE';
948 # restart daemon log file
949 if(-e $log_file ) { unlink $log_file }
950 daemon_log(" ", 1);
951 daemon_log("$0 started!", 1);
953 # Just fork, if we"re not in foreground mode
954 if( ! $foreground ) { $pid = fork(); }
955 else { $pid = $$; }
957 # Do something useful - put our PID into the pid_file
958 if( 0 != $pid ) {
959     open( LOCK_FILE, ">$pid_file" );
960     print LOCK_FILE "$pid\n";
961 close( LOCK_FILE );
962     if( !$foreground ) { exit( 0 ) };
965 # import all modules
966 &import_modules;
968 # check wether all modules are gosa-si valid passwd check
970 # connect to gosa-si job queue
971 my $job_db = GOSA::DBsqlite->new($job_queue_file_name);
973 # create reading and writing vectors
974 my $rbits = my $wbits = my $ebits = "";
976 # add all module inputs to listening vector
977 while( my ($mod_name, $info) = each %$known_modules ) {
978     my ($input_address, $input_key, $input, $input_activ, $input_type) = @{$info};
979     vec($rbits, fileno $input, 1) = 1;   
984 ## start arp fifo
985 #if ($no_arp > 0) {
986 #    $arp_activ = "off";
987 #}
988 #my $my_fifo;
989 #if($arp_activ eq "on") {
990 #    daemon_log(" ", 1);
991 #    $my_fifo = &open_fifo($arp_fifo_path);
992 #    if($my_fifo == 0) { die "fifo file disappeared\n" }
993 #    sysopen($arp_fifo, $arp_fifo_path, O_RDWR) or die "can't read from $arp_fifo: $!" ;
994 #    
995 #    vec($rbits, fileno $arp_fifo, 1) = 1;
996 #}
1000 ##################################
1001 #everything ready, okay, lets start
1002 ##################################
1003 while(1) {
1005     # add all handles from the childs
1006     while ( my ($pid, $child_hash) = each %busy_child ) {
1007         
1008         # check whether process still exists
1009         my $exitus_pid = waitpid($pid, WNOHANG);
1010         if($exitus_pid != 0) {
1011             delete $busy_child{$pid};
1012             next;
1013         }
1014      
1015         # add child fhd to the listener    
1016         my $fhd = $$child_hash{'pipe_rd'};
1017         vec($rbits, fileno $fhd, 1) = 1;
1018     }
1020     my ($rout, $wout);
1021     my $nf = select($rout=$rbits, $wout=$wbits, undef, $job_queue_timeout);
1023     # error handling
1024     if($nf < 0 ) {
1025     }
1028     if($arp_activ eq "on" && vec($rout, fileno $arp_fifo, 1)) {
1029         my $in_msg = <$arp_fifo>;
1030         chomp($in_msg);
1031         print "arp_activ: msg: $in_msg\n";
1032         my $act_passwd = $known_daemons->{$bus_address}->{passwd};
1033         print "arp_activ: arp_passwd: $act_passwd\n";
1035         my $in_msg_hash = $xml->XMLin($in_msg, ForceArray=>1);
1037         my $target = &get_content_from_xml_hash($in_msg_hash, 'target');
1039         if ($target eq $server_address) { 
1040              print "arp_activ: forward to server\n";
1041             my $arp_cipher = &create_ciphering($act_passwd);
1042             my $crypted_msg = &encrypt_msg($in_msg, $arp_cipher);
1043             &activating_child($crypted_msg, $server_ip);
1044         } else {
1045             print "arp_activ: send to bus\n";
1046             &send_msg_hash2address($in_msg_hash, $bus_address);
1047         }
1048         print "\n";
1049     }
1052     # check input fhd of all modules 
1053     while ( my ($mod_name, $info) = each %$known_modules) {
1054         my $input_fhd = @{$info}[2];    
1055         my $input_activ = @{$info}[3];
1056         if (vec($rout, fileno $input_fhd, 1) && $input_activ eq 'on') {
1057             daemon_log(" ", 1);
1058             my $client = $input_fhd->accept();
1059             my $other_end = getpeername($client);
1060             if(not defined $other_end) {
1061                 daemon_log("client cannot be identified: $!");
1062             } else {
1063                 my ($port, $iaddr) = unpack_sockaddr_in($other_end);
1064                 my $actual_ip = inet_ntoa($iaddr);
1065                 daemon_log("accept client at daemon socket from $actual_ip", 5);
1066                 my $in_msg = &read_from_socket($client);
1067                 if(defined $in_msg){
1068                     chomp($in_msg);
1069                     &activating_child($in_msg, $actual_ip, $client);
1070                 } else {
1071                     daemon_log("cannot read from $actual_ip", 5);
1072                 }
1073             }
1074             #close($client);
1076         }
1077     }
1079     # check all processing childs whether they are finished ('done') or 
1080     while ( my ($pid, $child_hash) = each %busy_child ) {
1081         my $fhd = $$child_hash{'pipe_rd'};
1083         if (vec($rout, fileno $fhd, 1) ) {
1084             daemon_log("process child $pid is ready to read", 5);
1086             $fhd->blocking(1);
1087             my $in_msg = <$fhd>;
1088             $fhd->blocking(0);
1089             my $part_in_msg;
1090             while ($part_in_msg = <$fhd>) {
1091                 if (not defined $part_in_msg) {
1092                     last;
1093                 }
1094                 $in_msg .= $part_in_msg;
1095             }
1096             chomp($in_msg);
1098             daemon_log("process child read: $in_msg", 5);
1099             if (not defined $in_msg) { 
1100                 next; 
1101             } elsif ($in_msg =~ "done") {
1102                 delete $busy_child{$pid};
1103                 $free_child{$pid} = $child_hash;
1104  
1105             } else {
1106                 # send computed answer back to connected client
1107                 my $act_client = $busy_child{$pid}{client_ref};
1108                 print $act_client $in_msg."\n";
1110                 #my $act_pipe = $busy_child{$pid}{pipe_rd};
1111                 sleep(10);
1112                 close ($act_client);   
1113                 delete $busy_child{$pid};
1114                 $free_child{$pid} = $child_hash;
1116             }
1117         }
1118     }
1120     # check gosa job queue for jobs with executable timestamp
1121     my ($seconds, $minutes, $hours, $monthday, $month,
1122     $year, $weekday, $yearday, $sommertime) = localtime(time);
1123     $hours = $hours < 10 ? $hours = "0".$hours : $hours;
1124     $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
1125     $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
1126     $month+=1;
1127     $month = $month < 10 ? $month = "0".$month : $month;
1128     $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
1129     $year+=1900;
1130     my $timestamp = "$year$month$monthday$hours$minutes$seconds";
1131     
1132     
1133     my $sql = "SELECT * FROM '$job_queue_table_name' WHERE status='waiting' AND timestamp<'$timestamp'";
1134     my $res = $job_db->exec_statement($sql);
1135     foreach my $msg (@{$res}) {
1136         
1137         my $jobdb_id = @{$msg}[0];
1138         my $job_msg_hash = &transform_msg2hash(@{$msg}[6]);
1139         my $out_msg_hash = $job_msg_hash;
1140     
1141         # hole mac address und suche die entsprechende ip addresse
1142         my $target;
1143         my @hostnames = keys %{$known_clients};
1144         foreach my $hostname (@hostnames) {
1145             if ($known_clients->{$hostname}->{mac_address} eq $job_msg_hash->{mac}[0]) {
1146                 $target = $hostname;
1147                 last;
1148             }
1149         }
1150         
1151         if (not defined $target) {
1152             &daemon_log("ERROR: no host found for mac address: $job_msg_hash->{mac}[0]", 1);
1153             next;
1154         }
1156         # add target
1157         print "select: target: $target\n";
1158         &add_content2xml_hash($out_msg_hash, "target", $target);
1160         # add new header
1161         my $out_header = $job_msg_hash->{header}[0];
1162         $out_header =~ s/job_/gosa_/;
1163         print "select: header: $out_header\n";
1164         delete $out_msg_hash->{header};
1165         &add_content2xml_hash($out_msg_hash, "header", $out_header);
1166         
1167         # add sqlite_id 
1168         &add_content2xml_hash($out_msg_hash, "jobdb_id", $jobdb_id); 
1169     
1170 #        my $out_msg = &create_xml_string($out_msg_hash);
1172 #        # encrypt msg as a GosaPackage module
1173 #        my $cipher = &create_ciphering($gosa_passwd);
1174 #        my $crypted_out_msg = &encrypt_msg($out_msg, $cipher);
1176         my $error = &send_msg_hash2address($out_msg_hash, "$gosa_ip:$gosa_port", $gosa_passwd);
1178         if ($error == 0) {
1179             my $sql = "UPDATE '$job_queue_table_name' SET status='processing', target='$target' WHERE id='$jobdb_id'";
1180             my $res = $job_db->exec_statement($sql);
1181         }
1184     }