897aef8b6a23665f8f6ab0924b4af229b96d76f4
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
12 # libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 # BUGS: ---
14 # NOTES:
15 # AUTHOR: (Andreas Rettenberger), <rettenberger@gonicus.de>
16 # COMPANY:
17 # VERSION: 1.0
18 # CREATED: 12.09.2007 08:54:41 CEST
19 # REVISION: ---
20 #===============================================================================
23 use strict;
24 use warnings;
25 use Getopt::Long;
26 use Config::IniFiles;
27 use POSIX;
28 use Time::HiRes qw( gettimeofday );
30 use Fcntl;
31 use IO::Socket::INET;
32 use IO::Handle;
33 use IO::Select;
34 use Symbol qw(qualify_to_ref);
35 use Crypt::Rijndael;
36 use MIME::Base64;
37 use Digest::MD5 qw(md5 md5_hex md5_base64);
38 use XML::Simple;
39 use Data::Dumper;
40 use Sys::Syslog qw( :DEFAULT setlogsock);
41 use Cwd;
42 use File::Spec;
43 use GOSA::GosaSupportDaemon;
44 use GOSA::DBsqlite;
46 my $modules_path = "/usr/lib/gosa-si/modules";
47 use lib "/usr/lib/gosa-si/modules";
49 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
50 my ($bus, $msg_to_bus, $bus_cipher);
51 my ($server, $server_mac_address, $server_events);
52 my ($gosa_server, $job_queue_timeout, $job_queue_table_name, $job_queue_file_name);
53 my ($known_modules, $known_clients_file_name, $known_server_file_name);
54 my ($max_clients);
55 my ($pid_file, $procid, $pid, $log_file);
56 my (%free_child, %busy_child, $child_max, $child_min, %child_alive_time, $child_timeout);
57 my ($arp_activ, $arp_fifo, $arp_fifo_path);
59 # variables declared in config file are always set to 'our'
60 our (%cfg_defaults, $log_file, $pid_file,
61 $bus_activ, $bus_passwd, $bus_ip, $bus_port,
62 $server_activ, $server_ip, $server_port, $server_passwd, $max_clients,
63 $arp_activ, $arp_fifo_path,
64 $gosa_activ, $gosa_passwd, $gosa_ip, $gosa_port, $gosa_timeout,
65 );
67 # additional variable which should be globaly accessable
68 our $xml;
69 our $server_address;
70 our $bus_address;
71 our $gosa_address;
72 our $no_bus;
73 our $no_arp;
74 our $verbose;
75 our $forground;
76 our $cfg_file;
78 # specifies the verbosity of the daemon_log
79 $verbose = 0 ;
81 # if foreground is not null, script will be not forked to background
82 $foreground = 0 ;
84 # specifies the timeout seconds while checking the online status of a registrating client
85 $ping_timeout = 5;
87 $no_bus = 0;
89 $no_arp = 0;
91 # name of table for storing gosa jobs
92 our $job_queue_table_name = 'jobs';
93 our $job_db;
95 # holds all other gosa-sd as well as the gosa-sd-bus
96 our $known_server_db;
98 # holds all registrated clients
99 our $known_clients_db;
101 %cfg_defaults =
102 ("general" =>
103 {"log_file" => [\$log_file, "/var/run/".$0.".log"],
104 "pid_file" => [\$pid_file, "/var/run/".$0.".pid"],
105 "child_max" => [\$child_max, 10],
106 "child_min" => [\$child_min, 3],
107 "child_timeout" => [\$child_timeout, 180],
108 "job_queue_timeout" => [\$job_queue_timeout, undef],
109 "job_queue_file_name" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
110 "known_clients_file_name" => [\$known_clients_file_name, '/var/lib/gosa-si/known_clients.db' ],
111 "known_server_file_name" => [\$known_server_file_name, '/var/lib/gosa-si/known_server.db'],
112 },
113 "bus" =>
114 {"bus_activ" => [\$bus_activ, "on"],
115 "bus_passwd" => [\$bus_passwd, ""],
116 "bus_ip" => [\$bus_ip, "0.0.0.0"],
117 "bus_port" => [\$bus_port, "20080"],
118 },
119 "server" =>
120 {"server_activ" => [\$server_activ, "on"],
121 "server_ip" => [\$server_ip, "0.0.0.0"],
122 "server_port" => [\$server_port, "20081"],
123 "server_passwd" => [\$server_passwd, ""],
124 "max_clients" => [\$max_clients, 100],
125 },
126 "arp" =>
127 {"arp_activ" => [\$arp_activ, "on"],
128 "arp_fifo_path" => [\$arp_fifo_path, "/var/run/gosa-si/arp-notify"],
129 },
130 "gosa" =>
131 {"gosa_activ" => [\$gosa_activ, "on"],
132 "gosa_ip" => [\$gosa_ip, "0.0.0.0"],
133 "gosa_port" => [\$gosa_port, "20082"],
134 "gosa_passwd" => [\$gosa_passwd, "none"],
135 },
136 );
139 #=== FUNCTION ================================================================
140 # NAME: usage
141 # PARAMETERS: nothing
142 # RETURNS: nothing
143 # DESCRIPTION: print out usage text to STDERR
144 #===============================================================================
145 sub usage {
146 print STDERR << "EOF" ;
147 usage: $0 [-hvf] [-c config]
149 -h : this (help) message
150 -c <file> : config file
151 -f : foreground, process will not be forked to background
152 -v : be verbose (multiple to increase verbosity)
153 -no-bus : starts $0 without connection to bus
154 -no-arp : starts $0 without connection to arp module
156 EOF
157 print "\n" ;
158 }
161 #=== FUNCTION ================================================================
162 # NAME: read_configfile
163 # PARAMETERS: cfg_file - string -
164 # RETURNS: nothing
165 # DESCRIPTION: read cfg_file and set variables
166 #===============================================================================
167 sub read_configfile {
168 my $cfg;
169 if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
170 if( -r $cfg_file ) {
171 $cfg = Config::IniFiles->new( -file => $cfg_file );
172 } else {
173 print STDERR "Couldn't read config file!\n";
174 }
175 } else {
176 $cfg = Config::IniFiles->new() ;
177 }
178 foreach my $section (keys %cfg_defaults) {
179 foreach my $param (keys %{$cfg_defaults{ $section }}) {
180 my $pinfo = $cfg_defaults{ $section }{ $param };
181 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
182 }
183 }
184 }
187 #=== FUNCTION ================================================================
188 # NAME: logging
189 # PARAMETERS: level - string - default 'info'
190 # msg - string -
191 # facility - string - default 'LOG_DAEMON'
192 # RETURNS: nothing
193 # DESCRIPTION: function for logging
194 #===============================================================================
195 sub daemon_log {
196 # log into log_file
197 my( $msg, $level ) = @_;
198 if(not defined $msg) { return }
199 if(not defined $level) { $level = 1 }
200 if(defined $log_file){
201 open(LOG_HANDLE, ">>$log_file");
202 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
203 print STDERR "cannot open $log_file: $!";
204 return }
205 chomp($msg);
206 if($level <= $verbose){
207 my ($seconds, $minutes, $hours, $monthday, $month,
208 $year, $weekday, $yearday, $sommertime) = localtime(time);
209 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
210 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
211 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
212 my @monthnames = ("Jan", "Feb", "Mar", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
213 $month = $monthnames[$month];
214 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
215 $year+=1900;
216 my $name = $0;
217 $name =~ s/\.\///;
219 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
220 print LOG_HANDLE $log_msg;
221 if( $foreground ) {
222 print STDERR $log_msg;
223 }
224 }
225 close( LOG_HANDLE );
226 }
227 #log into syslog
228 # my ($msg, $level, $facility) = @_;
229 # if(not defined $msg) {return}
230 # if(not defined $level) {$level = "info"}
231 # if(not defined $facility) {$facility = "LOG_DAEMON"}
232 # openlog($0, "pid,cons,", $facility);
233 # syslog($level, $msg);
234 # closelog;
235 # return;
236 }
239 #=== FUNCTION ================================================================
240 # NAME: check_cmdline_param
241 # PARAMETERS: nothing
242 # RETURNS: nothing
243 # DESCRIPTION: validates commandline parameter
244 #===============================================================================
245 sub check_cmdline_param () {
246 my $err_config;
247 my $err_counter = 0;
248 if( not defined( $cfg_file)) {
249 #$err_config = "please specify a config file";
250 #$err_counter += 1;
251 my $cwd = getcwd;
252 my $name = "/etc/gosa-si/server.conf";
253 $cfg_file = File::Spec->catfile( $cwd, $name );
254 }
255 if( $err_counter > 0 ) {
256 &usage( "", 1 );
257 if( defined( $err_config)) { print STDERR "$err_config\n"}
258 print STDERR "\n";
259 exit( -1 );
260 }
261 }
264 #=== FUNCTION ================================================================
265 # NAME: check_pid
266 # PARAMETERS: nothing
267 # RETURNS: nothing
268 # DESCRIPTION: handels pid processing
269 #===============================================================================
270 sub check_pid {
271 $pid = -1;
272 # Check, if we are already running
273 if( open(LOCK_FILE, "<$pid_file") ) {
274 $pid = <LOCK_FILE>;
275 if( defined $pid ) {
276 chomp( $pid );
277 if( -f "/proc/$pid/stat" ) {
278 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
279 if( $0 eq $stat ) {
280 close( LOCK_FILE );
281 exit -1;
282 }
283 }
284 }
285 close( LOCK_FILE );
286 unlink( $pid_file );
287 }
289 # create a syslog msg if it is not to possible to open PID file
290 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
291 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
292 if (open(LOCK_FILE, '<', $pid_file)
293 && ($pid = <LOCK_FILE>))
294 {
295 chomp($pid);
296 $msg .= "(PID $pid)\n";
297 } else {
298 $msg .= "(unable to read PID)\n";
299 }
300 if( ! ($foreground) ) {
301 openlog( $0, "cons,pid", "daemon" );
302 syslog( "warning", $msg );
303 closelog();
304 }
305 else {
306 print( STDERR " $msg " );
307 }
308 exit( -1 );
309 }
310 }
312 #=== FUNCTION ================================================================
313 # NAME: import_modules
314 # PARAMETERS: module_path - string - abs. path to the directory the modules
315 # are stored
316 # RETURNS: nothing
317 # DESCRIPTION: each file in module_path which ends with '.pm' is imported by
318 # "require 'file';"
319 #===============================================================================
320 sub import_modules {
321 daemon_log(" ", 1);
323 if (not -e $modules_path) {
324 daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);
325 }
327 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
328 while (defined (my $file = readdir (DIR))) {
329 if (not $file =~ /(\S*?).pm$/) {
330 next;
331 }
332 eval { require $file; };
333 if ($@) {
334 daemon_log("ERROR: gosa-si-server could not load module $file", 1);
335 daemon_log("$@", 5);
336 } else {
337 my $mod_name = $1;
338 my $info = eval($mod_name.'::get_module_info()');
339 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
340 $known_modules->{$mod_name} = $info;
342 daemon_log("module $mod_name loaded", 1);
343 }
344 }
346 # for debugging
347 #while ( my ($module, $tag_hash) = each(%$known_modules)) {
348 # print "\tmodule: $module"."\n";
349 # print "\ttags: ".join(", ", keys(%$tag_hash))."\n";
350 #}
351 close (DIR);
352 }
355 #=== FUNCTION ================================================================
356 # NAME: sig_int_handler
357 # PARAMETERS: signal - string - signal arose from system
358 # RETURNS: noting
359 # DESCRIPTION: handels tasks to be done befor signal becomes active
360 #===============================================================================
361 sub sig_int_handler {
362 my ($signal) = @_;
364 daemon_log("shutting down gosa-si-server", 1);
365 exit(1);
366 }
367 $SIG{INT} = \&sig_int_handler;
370 #=== FUNCTION ================================================================
371 # NAME: activating_child
372 # PARAMETERS: msg - string - incoming message
373 # host - string - host from which the incomming message comes
374 # RETURNS: nothing
375 # DESCRIPTION: handels the distribution of incoming messages to working childs
376 #===============================================================================
377 sub activating_child {
378 my ($msg, $host, $client) = @_;
379 my $child = &get_processing_child();
380 my $pipe_wr = $$child{'pipe_wr'};
381 my $pipe_rd = $$child{'pipe_rd'};
382 $$child{client_ref} = $client;
384 daemon_log("activating: childpid:$$child{'pid'}", 5);
386 print $pipe_wr $msg.".".$host."\n";
388 return;
389 }
392 #=== FUNCTION ================================================================
393 # NAME: get_processing_child
394 # PARAMETERS: nothing
395 # RETURNS: child - hash - holding the process id and the references to the pipe
396 # handles pipe_wr and pipe_rd
397 # DESCRIPTION: handels the forking, reactivating and keeping alive tasks
398 #===============================================================================
399 sub get_processing_child {
400 my $child;
402 while(my ($key, $val) = each(%free_child)) {
403 my $exitus_pid = waitpid($key, WNOHANG);
404 if($exitus_pid != 0) {
405 delete $free_child{$key};
406 }
407 daemon_log("free child:$key", 5);
408 }
409 # check @free_child and @busy_child
410 my $free_len = scalar(keys(%free_child));
411 my $busy_len = scalar(keys(%busy_child));
412 daemon_log("free children $free_len, busy children $busy_len", 5);
414 # if there is a free child, let the child work
415 if($free_len > 0){
416 my @keys = keys(%free_child);
417 $child = $free_child{$keys[0]};
418 if(defined $child) {
419 $busy_child{$$child{'pid'}} = $child ;
420 delete $free_child{$$child{'pid'}};
421 }
422 return $child;
423 }
425 # no free child, try to fork another one
426 if($free_len + $busy_len < $child_max) {
428 daemon_log("not enough children, create a new one", 5);
430 # New pipes for communication
431 my( $PARENT_wr, $PARENT_rd );
432 my( $CHILD_wr, $CHILD_rd );
433 pipe( $CHILD_rd, $PARENT_wr );
434 pipe( $PARENT_rd, $CHILD_wr );
435 $PARENT_wr->autoflush(1);
436 $CHILD_wr->autoflush(1);
438 ############
439 # fork child
440 ############
441 my $child_pid = fork();
443 #CHILD
444 if($child_pid == 0) {
445 # Close unused pipes
446 close( $CHILD_rd );
447 close( $CHILD_wr );
448 while( 1 ) {
449 my $rbits = "";
450 vec( $rbits, fileno $PARENT_rd , 1 ) = 1;
451 my $nf = select($rbits, undef, undef, $child_timeout);
452 if($nf < 0 ) {
453 die "select(): $!\n";
454 } elsif (! $nf) {
455 # if already child_min childs are alive, then leave loop
456 $free_len = scalar(keys(%free_child));
457 $busy_len = scalar(keys(%busy_child));
458 if($free_len + $busy_len >= $child_min) {
459 last;
460 } else {
461 redo;
462 }
463 }
465 # a job for a child arise
466 if ( vec $rbits, fileno $PARENT_rd, 1 ) {
467 # read everything from pipe
468 my $msg = "";
469 $PARENT_rd->blocking(0);
470 while(1) {
471 my $read = <$PARENT_rd>;
472 if(not defined $read) { last}
473 $msg .= $read;
474 }
476 ######################################
477 # forward msg to all imported modules
478 no strict "refs";
479 my $answer;
480 my %act_modules = %$known_modules;
481 while( my ($module, $info) = each(%act_modules)) {
482 my $tmp = &{ $module."::process_incoming_msg" }($msg);
483 if (defined $tmp) {
484 $answer = $tmp;
485 }
486 }
488 #&print_known_daemons();
489 #&print_known_clients();
491 daemon_log("processing of msg finished", 5);
493 if (defined $answer) {
494 print $PARENT_wr $answer."\n";
495 print $PARENT_wr "ENDMESSAGE\n";
496 my $len_answer = length $answer;
497 daemon_log("with answer: length of answer: $len_answer", 7);
498 daemon_log("\n$answer", 7);
499 } else {
500 print $PARENT_wr "done"."\n";
501 daemon_log(" ", 7);
502 }
503 redo;
504 }
505 }
506 # childs leaving the loop are allowed to die
507 exit(0);
510 #PARENT
511 } else {
512 # Close unused pipes
513 close( $PARENT_rd );
514 close( $PARENT_wr );
516 # add child to child alive hash
517 my %child_hash = (
518 'pid' => $child_pid,
519 'pipe_wr' => $CHILD_wr,
520 'pipe_rd' => $CHILD_rd,
521 'client_ref' => "",
522 );
524 $child = \%child_hash;
525 $busy_child{$$child{'pid'}} = $child;
526 return $child;
527 }
528 }
529 }
532 #=== FUNCTION ================================================================
533 # NAME: read_from_socket
534 # PARAMETERS: socket fh -
535 # RETURNS: result string - readed characters from socket
536 # DESCRIPTION: reads data from socket in 16 byte steps
537 #===============================================================================
538 sub read_from_socket {
539 my ($socket) = @_;
540 my $result = "";
542 $socket->blocking(1);
543 $result = <$socket>;
545 $socket->blocking(0);
546 while ( my $char = <$socket> ) {
547 if (not defined $char) { last }
548 $result .= $char;
549 }
551 return $result;
552 }
555 #=== FUNCTION ================================================================
556 # NAME: print_known_daemons
557 # PARAMETERS: nothing
558 # RETURNS: nothing
559 # DESCRIPTION: nomen est omen
560 #===============================================================================
561 #sub print_known_daemons {
562 # my ($tmp) = @_ ;
563 # print "####################################\n";
564 # print "# status of known_daemons\n";
565 # $shmda->shlock(LOCK_EX);
566 # my @hosts = keys %$known_daemons;
567 # foreach my $host (@hosts) {
568 # my $status = $known_daemons->{$host}->{status} ;
569 # my $passwd = $known_daemons->{$host}->{passwd};
570 # my $timestamp = $known_daemons->{$host}->{timestamp};
571 # print "$host\n";
572 # print "\tstatus: $status\n";
573 # print "\tpasswd: $passwd\n";
574 # print "\ttimestamp: $timestamp\n";
575 # }
576 # $shmda->shunlock(LOCK_EX);
577 # print "####################################\n";
578 # return;
579 #}
582 #=== FUNCTION ================================================================
583 # NAME: create_known_daemon
584 # PARAMETERS: hostname - string - key for the hash known_daemons
585 # RETURNS: nothing
586 # DESCRIPTION: creates a dummy entry for hostname in known_daemons
587 #===============================================================================
588 #sub create_known_daemon {
589 # my ($hostname) = @_;
590 # $shmda->shlock(LOCK_EX);
591 # $known_daemons->{$hostname} = {};
592 # $known_daemons->{$hostname}->{status} = "none";
593 # $known_daemons->{$hostname}->{passwd} = "none";
594 # $known_daemons->{$hostname}->{timestamp} = "none";
595 # $shmda->shunlock(LOCK_EX);
596 # return;
597 #}
600 #=== FUNCTION ================================================================
601 # NAME: add_content2known_daemons
602 # PARAMETERS: hostname - string - ip address and port of host (required)
603 # status - string - (optional)
604 # passwd - string - (optional)
605 # mac_address - string - mac address of host (optional)
606 # RETURNS: nothing
607 # DESCRIPTION: nome est omen and updates each time the timestamp of hostname
608 #===============================================================================
609 #sub add_content2known_daemons {
610 # my $arg = {
611 # hostname => undef, status => undef, passwd => undef,
612 # mac_address => undef, events => undef,
613 # @_ };
614 # my $hostname = $arg->{hostname};
615 # my $status = $arg->{status};
616 # my $passwd = $arg->{passwd};
617 # my $mac_address = $arg->{mac_address};
618 # my $events = $arg->{events};
619 #
620 # if (not defined $hostname) {
621 # daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1);
622 # return;
623 # }
624 #
625 # my ($seconds, $minutes, $hours, $monthday, $month,
626 # $year, $weekday, $yearday, $sommertime) = localtime(time);
627 # $hours = $hours < 10 ? $hours = "0".$hours : $hours;
628 # $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
629 # $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
630 # $month+=1;
631 # $month = $month < 10 ? $month = "0".$month : $month;
632 # $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
633 # $year+=1900;
634 # my $t = "$year$month$monthday$hours$minutes$seconds";
635 #
636 # $shmda->shlock(LOCK_EX);
637 # if (defined $status) {
638 # $known_daemons->{$hostname}->{status} = $status;
639 # }
640 # if (defined $passwd) {
641 # $known_daemons->{$hostname}->{passwd} = $passwd;
642 # }
643 # if (defined $mac_address) {
644 # $known_daemons->{$hostname}->{mac_address} = $mac_address;
645 # }
646 # if (defined $events) {
647 # $known_daemons->{$hostname}->{events} = $events;
648 # }
649 # $known_daemons->{$hostname}->{timestamp} = $t;
650 # $shmda->shlock(LOCK_EX);
651 # return;
652 #}
655 #=== FUNCTION ================================================================
656 # NAME: update_known_daemons
657 # PARAMETERS: hostname - string - ip address and port of host (required)
658 # status - string - (optional)
659 # passwd - string - (optional)
660 # client - string - ip address and port of client (optional)
661 # RETURNS: nothing
662 # DESCRIPTION: nome est omen and updates each time the timestamp of hostname
663 #===============================================================================
664 #sub update_known_daemons {
665 # my $arg = {
666 # hostname => undef, status => undef, passwd => undef,
667 # @_ };
668 # my $hostname = $arg->{hostname};
669 # my $status = $arg->{status};
670 # my $passwd = $arg->{passwd};
671 #
672 # if (not defined $hostname) {
673 # daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1);
674 # return;
675 # }
676 #
677 # my ($seconds, $minutes, $hours, $monthday, $month,
678 # $year, $weekday, $yearday, $sommertime) = localtime(time);
679 # $hours = $hours < 10 ? $hours = "0".$hours : $hours;
680 # $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
681 # $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
682 # $month+=1;
683 # $month = $month < 10 ? $month = "0".$month : $month;
684 # $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
685 # $year+=1900;
686 # my $t = "$year$month$monthday$hours$minutes$seconds";
687 #
688 # $shmda->shlock(LOCK_EX);
689 # if (defined $status) {
690 # $known_daemons->{$hostname}->{status} = $status;
691 # }
692 # if (defined $passwd) {
693 # $known_daemons->{$hostname}->{passwd} = $passwd;
694 # }
695 # $known_daemons->{$hostname}->{timestamp} = $t;
696 # $shmda->shunlock(LOCK_EX);
697 # return;
698 #}
701 #=== FUNCTION ================================================================
702 # NAME: print_known_clients
703 # PARAMETERS: nothing
704 # RETURNS: nothing
705 # DESCRIPTION: nomen est omen
706 #===============================================================================
707 #sub print_known_clients {
708 #
709 # print "####################################\n";
710 # print "# status of known_clients\n";
711 # $shmcl->shlock(LOCK_EX);
712 # my @hosts = keys %$known_clients;
713 # if (@hosts) {
714 # foreach my $host (@hosts) {
715 # my $status = $known_clients->{$host}->{status} ;
716 # my $passwd = $known_clients->{$host}->{passwd};
717 # my $timestamp = $known_clients->{$host}->{timestamp};
718 # my $mac_address = $known_clients->{$host}->{mac_address};
719 # my $events = $known_clients->{$host}->{events};
720 # print "$host\n";
721 # print "\tstatus: $status\n";
722 # print "\tpasswd: $passwd\n";
723 # print "\ttimestamp: $timestamp\n";
724 # print "\tmac_address: $mac_address\n";
725 # print "\tevents: $events\n";
726 # }
727 # }
728 # $shmcl->shunlock(LOCK_EX);
729 # print "####################################\n";
730 # return;
731 #}
734 #=== FUNCTION ================================================================
735 # NAME: create_known_client
736 # PARAMETERS: hostname - string - key for the hash known_clients
737 # RETURNS: nothing
738 # DESCRIPTION: creates a dummy entry for hostname in known_clients
739 #===============================================================================
740 sub create_known_client {
741 my ($hostname) = @_;
743 my $entry = { table=>'known_clients',
744 hostname=>$hostname,
745 status=>'none',
746 hostkey=>'none',
747 timestamp=>'none',
748 macaddress=>'none',
749 events=>'none',
750 };
751 my $res = $known_clients_db->add_dbentry($entry);
752 if ($res > 0) {
753 daemon_log("ERROR: cannot add entry to known_clients.db: $res", 1);
754 }
756 return;
757 }
759 sub sysreadline(*;$) {
760 my ($hd, $timeout) = @_;
762 $hd = qualify_to_ref($hd, caller());
763 my $infinitely_patient = (@_ == 1 || $timeout < 0);
764 my $start_time = time();
765 my $selector = IO::Select->new();
766 $selector->add($hd);
767 my $line = "";
769 SLEEP:
770 until( at_eol($line)) {
771 if (not $infinitely_patient) {
772 return $line if time() > ($start_time + $timeout);
773 }
774 next SLEEP unless $selector->can_read(1.0);
775 INPUT_READY:
776 while( $selector->can_read(0.0)) {
777 my $was_blocking = $hd->blocking(0);
778 CHAR: while (sysread($hd, my $nextbyte, 1)) {
779 $line .= $nextbyte;
780 last CHAR if $nextbyte eq "\n";
782 }
783 $hd->blocking($was_blocking);
784 next SLEEP unless at_eol($line);
785 last INPUT_READY;
786 }
787 }
788 return $line;
789 }
791 sub at_eol($) {
792 $_[0] =~ /\n\z/ ;
793 }
795 #==== MAIN = main ==============================================================
797 # parse commandline options
798 Getopt::Long::Configure( "bundling" );
799 GetOptions("h|help" => \&usage,
800 "c|config=s" => \$cfg_file,
801 "f|foreground" => \$foreground,
802 "v|verbose+" => \$verbose,
803 "no-bus+" => \$no_bus,
804 "no-arp+" => \$no_arp,
805 );
807 # read and set config parameters
808 &check_cmdline_param ;
809 &read_configfile;
810 &check_pid;
812 $SIG{CHLD} = 'IGNORE';
814 # forward error messages to logfile
815 if( ! $foreground ) {
816 open(STDERR, '>>', $log_file);
817 open(STDOUT, '>>', $log_file);
818 }
820 # Just fork, if we are not in foreground mode
821 if( ! $foreground ) {
822 chdir '/' or die "Can't chdir to /: $!";
823 $pid = fork;
824 setsid or die "Can't start a new session: $!";
825 umask 0;
826 } else {
827 $pid = $$;
828 }
830 # Do something useful - put our PID into the pid_file
831 if( 0 != $pid ) {
832 open( LOCK_FILE, ">$pid_file" );
833 print LOCK_FILE "$pid\n";
834 close( LOCK_FILE );
835 if( !$foreground ) {
836 exit( 0 )
837 };
838 }
840 daemon_log(" ", 1);
841 daemon_log("$0 started!", 1);
844 # connect to gosa-si job queue
845 my @job_col_names = ("id", "timestamp", "status", "result", "headertag", "targettag", "xmlmessage", "macaddress");
846 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
847 $job_db->create_table('jobs', \@job_col_names);
849 # connect to known_clients_db
850 my @clients_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'macaddress', 'events');
851 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
852 $known_clients_db->create_table('known_clients', \@clients_col_names);
854 # connect to known_server_db
855 my @server_col_names = ('hostname', 'status', 'hostkey', 'timestamp');
856 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
857 $known_server_db->create_table('known_server', \@server_col_names);
859 # import all modules
860 &import_modules;
862 # check wether all modules are gosa-si valid passwd check
864 # create reading and writing vectors
865 my $rbits = my $wbits = my $ebits = "";
867 # add all module inputs to listening vector
868 while( my ($mod_name, $info) = each %$known_modules ) {
869 my ($input_address, $input_key, $input, $input_activ, $input_type) = @{$info};
870 vec($rbits, fileno $input, 1) = 1;
872 }
875 ## start arp fifo
876 #if ($no_arp > 0) {
877 # $arp_activ = "off";
878 #}
879 #my $my_fifo;
880 #if($arp_activ eq "on") {
881 # daemon_log(" ", 1);
882 # $my_fifo = &open_fifo($arp_fifo_path);
883 # if($my_fifo == 0) { die "fifo file disappeared\n" }
884 # sysopen($arp_fifo, $arp_fifo_path, O_RDWR) or die "can't read from $arp_fifo: $!" ;
885 #
886 # vec($rbits, fileno $arp_fifo, 1) = 1;
887 #}
888 #
890 ##################################
891 #everything ready, okay, lets start
892 ##################################
893 while(1) {
895 # add all handles from the childs
896 while ( my ($pid, $child_hash) = each %busy_child ) {
898 # check whether process still exists
899 my $exitus_pid = waitpid($pid, WNOHANG);
900 if($exitus_pid != 0) {
901 delete $busy_child{$pid};
902 next;
903 }
905 # add child fhd to the listener
906 my $fhd = $$child_hash{'pipe_rd'};
907 vec($rbits, fileno $fhd, 1) = 1;
908 }
910 my ($rout, $wout);
911 my $nf = select($rout=$rbits, $wout=$wbits, undef, $job_queue_timeout);
913 # error handling
914 if($nf < 0 ) {
915 }
918 # if($arp_activ eq "on" && vec($rout, fileno $arp_fifo, 1)) {
919 # my $in_msg = <$arp_fifo>;
920 # chomp($in_msg);
921 # print "arp_activ: msg: $in_msg\n";
922 # my $act_passwd = $known_daemons->{$bus_address}->{passwd};
923 # print "arp_activ: arp_passwd: $act_passwd\n";
924 #
925 # my $in_msg_hash = $xml->XMLin($in_msg, ForceArray=>1);
926 #
927 # my $target = &get_content_from_xml_hash($in_msg_hash, 'target');
928 #
929 # if ($target eq $server_address) {
930 # print "arp_activ: forward to server\n";
931 # my $arp_cipher = &create_ciphering($act_passwd);
932 # my $crypted_msg = &encrypt_msg($in_msg, $arp_cipher);
933 # &activating_child($crypted_msg, $server_ip);
934 # } else {
935 # print "arp_activ: send to bus\n";
936 # &send_msg_hash2address($in_msg_hash, $bus_address);
937 # }
938 # print "\n";
939 # }
942 # check input fhd of all modules
943 while ( my ($mod_name, $info) = each %$known_modules) {
944 my $input_fhd = @{$info}[2];
945 my $input_activ = @{$info}[3];
946 if (vec($rout, fileno $input_fhd, 1) && $input_activ eq 'on') {
947 daemon_log(" ", 1);
948 my $client = $input_fhd->accept();
949 my $other_end = getpeername($client);
950 if(not defined $other_end) {
951 daemon_log("client cannot be identified: $!");
952 } else {
953 my ($port, $iaddr) = unpack_sockaddr_in($other_end);
954 my $actual_ip = inet_ntoa($iaddr);
955 daemon_log("accept client at daemon socket from $actual_ip", 5);
956 my $in_msg = &read_from_socket($client);
957 if(defined $in_msg){
958 chomp($in_msg);
959 &activating_child($in_msg, $actual_ip, $client);
960 } else {
961 daemon_log("cannot read from $actual_ip", 5);
962 }
963 }
964 }
965 }
967 # check all processing childs whether they are finished ('done') or
968 while ( my ($pid, $child_hash) = each %busy_child ) {
969 my $fhd = $$child_hash{'pipe_rd'};
971 if (vec($rout, fileno $fhd, 1) ) {
972 daemon_log("process child $pid is ready to read", 5);
974 my $in_msg;
975 while (1) {
976 my $part_in_msg = <$fhd>;
977 if( $part_in_msg eq "ENDMESSAGE\n") {
978 last;
979 }
980 $in_msg .= $part_in_msg;
981 }
982 chomp($in_msg);
984 daemon_log("process child read:", 7);
985 daemon_log("\n$in_msg", 7);
986 if (not defined $in_msg) {
987 next;
988 } elsif ($in_msg =~ "done") {
989 delete $busy_child{$pid};
990 $free_child{$pid} = $child_hash;
992 } else {
993 print ">>>>>>>>>>>1\n";
994 # send computed answer back to connected client
995 my $act_client = $busy_child{$pid}{client_ref};
996 print $act_client $in_msg."\n";
997 print ">>>>>>>>>>>2\n";
999 #my $act_pipe = $busy_child{$pid}{pipe_rd};
1000 delete $busy_child{$pid};
1001 $free_child{$pid} = $child_hash;
1002 print ">>>>>>>>>>>3\n";
1004 # give the client a chance to read
1005 sleep(5);
1006 close ($act_client);
1007 print ">>>>>>>>>>>4\n";
1009 }
1010 }
1011 }
1013 # check gosa job queue for jobs with executable timestamp
1014 my ($seconds, $minutes, $hours, $monthday, $month,
1015 $year, $weekday, $yearday, $sommertime) = localtime(time);
1016 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
1017 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
1018 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
1019 $month+=1;
1020 $month = $month < 10 ? $month = "0".$month : $month;
1021 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
1022 $year+=1900;
1023 my $timestamp = "$year$month$monthday$hours$minutes$seconds";
1026 my $res = $job_db->select_dbentry( { table=>$job_queue_table_name, status=>'waiting', timestamp=>$timestamp } );
1028 while( my ($id, $hit) = each %{$res} ) {
1030 my $jobdb_id = $hit->{ROWID};
1031 my $macaddress = $hit->{macaddress};
1032 my $job_msg_hash = &transform_msg2hash($hit->{xmlmessage});
1033 my $out_msg_hash = $job_msg_hash;
1034 my $res_hash = $known_clients_db->select_dbentry( {table=>'known_clients', macaddress=>$macaddress} );
1035 # expect macaddress is unique!!!!!!
1036 my $target = $res_hash->{1}->{hostname};
1038 if (not defined $target) {
1039 &daemon_log("ERROR: no host found for mac address: $job_msg_hash->{mac}[0]", 1);
1040 &daemon_log("xml message: $hit->{xmlmessage}", 5);
1041 my $update_hash = { table=>$job_queue_table_name,
1042 update=> [ { status=>['error'], result=>["no host found for mac address"] } ],
1043 where=> [ { ROWID=>[$jobdb_id] } ],
1044 };
1045 my $res = $job_db->update_dbentry($update_hash);
1047 next;
1048 }
1050 # add target
1051 &add_content2xml_hash($out_msg_hash, "target", $target);
1053 # add new header
1054 my $out_header = $job_msg_hash->{header}[0];
1055 $out_header =~ s/job_/gosa_/;
1056 delete $out_msg_hash->{header};
1057 &add_content2xml_hash($out_msg_hash, "header", $out_header);
1059 # add sqlite_id
1060 &add_content2xml_hash($out_msg_hash, "jobdb_id", $jobdb_id);
1062 my $out_msg = &create_xml_string($out_msg_hash);
1064 # encrypt msg as a GosaPackage module
1065 my $cipher = &create_ciphering($gosa_passwd);
1066 my $crypted_out_msg = &encrypt_msg($out_msg, $cipher);
1068 my $error = &send_msg_hash2address($out_msg_hash, "$gosa_ip:$gosa_port", $gosa_passwd);
1070 #######################
1071 # TODO exchange ROWID with jobid, insert column jobid in table jobs befor
1073 if ($error == 0) {
1074 my $sql = "UPDATE '$job_queue_table_name' SET status='processing', targettag='$target' WHERE ROWID='$jobdb_id'";
1075 my $res = $job_db->exec_statement($sql);
1076 } else {
1077 my $update_hash = { table=>$job_queue_table_name,
1078 update=> [ { status=>'error' } ],
1079 where=> [ { ROWID=>$jobdb_id } ],
1080 };
1081 my $res = $job_db->update_dbentry($update_hash);
1082 }
1084 }
1087 }