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 # libipc-shareable-perl libdata-dumper-simple-perl
13 # libdbd-sqlite3-perl libnet-ldap-perl
14 # BUGS: ---
15 # NOTES:
16 # AUTHOR: (Andreas Rettenberger), <rettenberger@gonicus.de>
17 # COMPANY:
18 # VERSION: 1.0
19 # CREATED: 12.09.2007 08:54:41 CEST
20 # REVISION: ---
21 #===============================================================================
24 use strict;
25 use warnings;
26 use Getopt::Long;
27 use Config::IniFiles;
28 use POSIX;
29 use Time::HiRes qw( gettimeofday );
31 use Fcntl;
32 use IO::Socket::INET;
33 use Crypt::Rijndael;
34 use MIME::Base64;
35 use Digest::MD5 qw(md5 md5_hex md5_base64);
36 use XML::Simple;
37 use Data::Dumper;
38 use Sys::Syslog qw( :DEFAULT setlogsock);
39 use Cwd;
40 use File::Spec;
41 use IPC::Shareable qw( :lock);
42 IPC::Shareable->clean_up_all;
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, ""],
117 "bus_port" => [\$bus_port, "20080"],
118 },
119 "server" =>
120 {"server_activ" => [\$server_activ, "on"],
121 "server_ip" => [\$server_ip, ""],
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, ""],
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!";
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 print LOG_HANDLE "$level $msg\n";
208 if($foreground) { print $msg."\n" }
209 }
210 }
211 # close( LOG_HANDLE );
212 #log into syslog
213 # my ($msg, $level, $facility) = @_;
214 # if(not defined $msg) {return}
215 # if(not defined $level) {$level = "info"}
216 # if(not defined $facility) {$facility = "LOG_DAEMON"}
217 # openlog($0, "pid,cons,", $facility);
218 # syslog($level, $msg);
219 # closelog;
220 # return;
221 }
224 #=== FUNCTION ================================================================
225 # NAME: check_cmdline_param
226 # PARAMETERS: nothing
227 # RETURNS: nothing
228 # DESCRIPTION: validates commandline parameter
229 #===============================================================================
230 sub check_cmdline_param () {
231 my $err_config;
232 my $err_counter = 0;
233 if( not defined( $cfg_file)) {
234 #$err_config = "please specify a config file";
235 #$err_counter += 1;
236 my $cwd = getcwd;
237 my $name = "/etc/gosa-si/server.conf";
238 $cfg_file = File::Spec->catfile( $cwd, $name );
239 }
240 if( $err_counter > 0 ) {
241 &usage( "", 1 );
242 if( defined( $err_config)) { print STDERR "$err_config\n"}
243 print STDERR "\n";
244 exit( -1 );
245 }
246 }
249 #=== FUNCTION ================================================================
250 # NAME: check_pid
251 # PARAMETERS: nothing
252 # RETURNS: nothing
253 # DESCRIPTION: handels pid processing
254 #===============================================================================
255 sub check_pid {
256 $pid = -1;
257 # Check, if we are already running
258 if( open(LOCK_FILE, "<$pid_file") ) {
259 $pid = <LOCK_FILE>;
260 if( defined $pid ) {
261 chomp( $pid );
262 if( -f "/proc/$pid/stat" ) {
263 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
264 if( $0 eq $stat ) {
265 close( LOCK_FILE );
266 exit -1;
267 }
268 }
269 }
270 close( LOCK_FILE );
271 unlink( $pid_file );
272 }
274 # create a syslog msg if it is not to possible to open PID file
275 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
276 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
277 if (open(LOCK_FILE, '<', $pid_file)
278 && ($pid = <LOCK_FILE>))
279 {
280 chomp($pid);
281 $msg .= "(PID $pid)\n";
282 } else {
283 $msg .= "(unable to read PID)\n";
284 }
285 if( ! ($foreground) ) {
286 openlog( $0, "cons,pid", "daemon" );
287 syslog( "warning", $msg );
288 closelog();
289 }
290 else {
291 print( STDERR " $msg " );
292 }
293 exit( -1 );
294 }
295 }
298 #=== FUNCTION ================================================================
299 # NAME: get_ip_and_mac
300 # PARAMETERS: nothing
301 # RETURNS: (ip, mac)
302 # DESCRIPTION: executes /sbin/ifconfig and parses the output, the first occurence
303 # of a inet address is returned as well as the mac address in the line
304 # above the inet address
305 #===============================================================================
306 sub get_ip_and_mac {
307 my $ip = "0.0.0.0.0"; # Defualt-IP
308 my $mac = "00:00:00:00:00:00"; # Default-MAC
309 my @ifconfig = qx(/sbin/ifconfig);
310 foreach(@ifconfig) {
311 if (/Hardware Adresse (\S{2}):(\S{2}):(\S{2}):(\S{2}):(\S{2}):(\S{2})/) {
312 $mac = "$1:$2:$3:$4:$5:$6";
313 next;
314 }
315 if (/inet Adresse:(\d+).(\d+).(\d+).(\d+)/) {
316 $ip = "$1.$2.$3.$4";
317 last;
318 }
319 }
320 return ($ip, $mac);
321 }
325 #=== FUNCTION ================================================================
326 # NAME: import_modules
327 # PARAMETERS: module_path - string - abs. path to the directory the modules
328 # are stored
329 # RETURNS: nothing
330 # DESCRIPTION: each file in module_path which ends with '.pm' is imported by
331 # "require 'file';"
332 #===============================================================================
333 sub import_modules {
334 daemon_log(" ", 1);
336 if (not -e $modules_path) {
337 daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);
338 }
340 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
341 while (defined (my $file = readdir (DIR))) {
342 if (not $file =~ /(\S*?).pm$/) {
343 next;
344 }
345 eval { require $file; };
346 if ($@) {
347 daemon_log("ERROR: gosa-sd could not load module $file", 1);
348 daemon_log("$@", 5);
349 next;
350 }
351 my $mod_name = $1;
352 #my $module_tag_hash = eval( $mod_name.'::get_module_tags()' );
354 my $info = eval($mod_name.'::get_module_info()');
355 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
356 $known_modules->{$mod_name} = $info;
358 daemon_log("module $mod_name loaded", 1);
359 }
361 # for debugging
362 #while ( my ($module, $tag_hash) = each(%$known_modules)) {
363 # print "\tmodule: $module"."\n";
364 # print "\ttags: ".join(", ", keys(%$tag_hash))."\n";
365 #}
366 close (DIR);
367 }
370 #=== FUNCTION ================================================================
371 # NAME: sig_int_handler
372 # PARAMETERS: signal - string - signal arose from system
373 # RETURNS: noting
374 # DESCRIPTION: handels tasks to be done befor signal becomes active
375 #===============================================================================
376 sub sig_int_handler {
377 my ($signal) = @_;
378 if($server){
379 close($server);
380 daemon_log("daemon server closed", 1);
381 }
382 if( -p $arp_fifo_path ) {
383 close $arp_fifo ;
384 unlink($arp_fifo_path) ;
385 daemon_log("ARP_FIFO closed", 1) ;
386 }
388 if($gosa_server){
389 close($gosa_server);
390 daemon_log("gosa server closed", 1);
391 }
393 print STDERR "$signal\n";
395 exit(1);
396 }
397 $SIG{INT} = \&sig_int_handler;
400 #=== FUNCTION ================================================================
401 # NAME: activating_child
402 # PARAMETERS: msg - string - incoming message
403 # host - string - host from which the incomming message comes
404 # RETURNS: nothing
405 # DESCRIPTION: handels the distribution of incoming messages to working childs
406 #===============================================================================
407 sub activating_child {
408 my ($msg, $host, $client) = @_;
409 my $child = &get_processing_child();
410 my $pipe_wr = $$child{'pipe_wr'};
411 my $pipe_rd = $$child{'pipe_rd'};
412 $$child{client_ref} = $client;
414 daemon_log("activating: childpid:$$child{'pid'}", 5);
416 print $pipe_wr $msg.".".$host."\n";
418 return;
419 }
422 #=== FUNCTION ================================================================
423 # NAME: get_processing_child
424 # PARAMETERS: nothing
425 # RETURNS: child - hash - holding the process id and the references to the pipe
426 # handles pipe_wr and pipe_rd
427 # DESCRIPTION: handels the forking, reactivating and keeping alive tasks
428 #===============================================================================
429 sub get_processing_child {
430 my $child;
432 while(my ($key, $val) = each(%free_child)) {
433 my $exitus_pid = waitpid($key, WNOHANG);
434 if($exitus_pid != 0) {
435 delete $free_child{$key};
436 }
437 daemon_log("free child:$key", 5);
438 }
439 # check @free_child and @busy_child
440 my $free_len = scalar(keys(%free_child));
441 my $busy_len = scalar(keys(%busy_child));
442 daemon_log("free children $free_len, busy children $busy_len", 5);
444 # if there is a free child, let the child work
445 if($free_len > 0){
446 my @keys = keys(%free_child);
447 $child = $free_child{$keys[0]};
448 if(defined $child) {
449 $busy_child{$$child{'pid'}} = $child ;
450 delete $free_child{$$child{'pid'}};
451 }
452 return $child;
453 }
455 # no free child, try to fork another one
456 if($free_len + $busy_len < $child_max) {
458 daemon_log("not enough children, create a new one", 5);
460 # New pipes for communication
461 my( $PARENT_wr, $PARENT_rd );
462 my( $CHILD_wr, $CHILD_rd );
463 pipe( $CHILD_rd, $PARENT_wr );
464 pipe( $PARENT_rd, $CHILD_wr );
465 $PARENT_wr->autoflush(1);
466 $CHILD_wr->autoflush(1);
468 ############
469 # fork child
470 ############
471 my $child_pid = fork();
473 #CHILD
474 if($child_pid == 0) {
475 # Close unused pipes
476 close( $CHILD_rd );
477 close( $CHILD_wr );
478 while( 1 ) {
479 my $rbits = "";
480 vec( $rbits, fileno $PARENT_rd , 1 ) = 1;
481 my $nf = select($rbits, undef, undef, $child_timeout);
482 if($nf < 0 ) {
483 die "select(): $!\n";
484 } elsif (! $nf) {
485 # if already child_min childs are alive, then leave loop
486 $free_len = scalar(keys(%free_child));
487 $busy_len = scalar(keys(%busy_child));
488 if($free_len + $busy_len >= $child_min) {
489 last;
490 } else {
491 redo;
492 }
493 }
495 # a job for a child arise
496 if ( vec $rbits, fileno $PARENT_rd, 1 ) {
497 # read everything from pipe
498 my $msg = "";
499 $PARENT_rd->blocking(0);
500 while(1) {
501 my $read = <$PARENT_rd>;
502 if(not defined $read) { last}
503 $msg .= $read;
504 }
506 ######################################
507 # forward msg to all imported modules
508 no strict "refs";
509 my $answer;
510 my %act_modules = %$known_modules;
511 while( my ($module, $info) = each(%act_modules)) {
512 &daemon_log("##########", 5);
513 my $tmp = &{ $module."::process_incoming_msg" }($msg);
514 if (defined $tmp) {
515 $answer = $tmp;
516 }
517 &daemon_log("##########", 5);
518 }
520 #&print_known_daemons();
521 #&print_known_clients();
523 daemon_log("processing of msg finished", 5);
525 if (defined $answer) {
526 print $PARENT_wr $answer."\n";
527 daemon_log("with answer: $answer", 5);
528 daemon_log(" ", 5);
529 } else {
530 print $PARENT_wr "done"."\n";
531 daemon_log(" ", 5);
532 }
533 redo;
534 }
535 }
536 # childs leaving the loop are allowed to die
537 exit(0);
540 #PARENT
541 } else {
542 # Close unused pipes
543 close( $PARENT_rd );
544 close( $PARENT_wr );
546 # add child to child alive hash
547 my %child_hash = (
548 'pid' => $child_pid,
549 'pipe_wr' => $CHILD_wr,
550 'pipe_rd' => $CHILD_rd,
551 'client_ref' => "",
552 );
554 $child = \%child_hash;
555 $busy_child{$$child{'pid'}} = $child;
556 return $child;
557 }
558 }
559 }
562 #=== FUNCTION ================================================================
563 # NAME: read_from_socket
564 # PARAMETERS: socket fh -
565 # RETURNS: result string - readed characters from socket
566 # DESCRIPTION: reads data from socket in 16 byte steps
567 #===============================================================================
568 sub read_from_socket {
569 my ($socket) = @_;
570 my $result = "";
572 $socket->blocking(1);
573 $result = <$socket>;
575 $socket->blocking(0);
576 while ( my $char = <$socket> ) {
577 if (not defined $char) { last }
578 $result .= $char;
579 }
581 return $result;
582 }
585 #=== FUNCTION ================================================================
586 # NAME: print_known_daemons
587 # PARAMETERS: nothing
588 # RETURNS: nothing
589 # DESCRIPTION: nomen est omen
590 #===============================================================================
591 #sub print_known_daemons {
592 # my ($tmp) = @_ ;
593 # print "####################################\n";
594 # print "# status of known_daemons\n";
595 # $shmda->shlock(LOCK_EX);
596 # my @hosts = keys %$known_daemons;
597 # foreach my $host (@hosts) {
598 # my $status = $known_daemons->{$host}->{status} ;
599 # my $passwd = $known_daemons->{$host}->{passwd};
600 # my $timestamp = $known_daemons->{$host}->{timestamp};
601 # print "$host\n";
602 # print "\tstatus: $status\n";
603 # print "\tpasswd: $passwd\n";
604 # print "\ttimestamp: $timestamp\n";
605 # }
606 # $shmda->shunlock(LOCK_EX);
607 # print "####################################\n";
608 # return;
609 #}
612 #=== FUNCTION ================================================================
613 # NAME: create_known_daemon
614 # PARAMETERS: hostname - string - key for the hash known_daemons
615 # RETURNS: nothing
616 # DESCRIPTION: creates a dummy entry for hostname in known_daemons
617 #===============================================================================
618 #sub create_known_daemon {
619 # my ($hostname) = @_;
620 # $shmda->shlock(LOCK_EX);
621 # $known_daemons->{$hostname} = {};
622 # $known_daemons->{$hostname}->{status} = "none";
623 # $known_daemons->{$hostname}->{passwd} = "none";
624 # $known_daemons->{$hostname}->{timestamp} = "none";
625 # $shmda->shunlock(LOCK_EX);
626 # return;
627 #}
630 #=== FUNCTION ================================================================
631 # NAME: add_content2known_daemons
632 # PARAMETERS: hostname - string - ip address and port of host (required)
633 # status - string - (optional)
634 # passwd - string - (optional)
635 # mac_address - string - mac address of host (optional)
636 # RETURNS: nothing
637 # DESCRIPTION: nome est omen and updates each time the timestamp of hostname
638 #===============================================================================
639 #sub add_content2known_daemons {
640 # my $arg = {
641 # hostname => undef, status => undef, passwd => undef,
642 # mac_address => undef, events => undef,
643 # @_ };
644 # my $hostname = $arg->{hostname};
645 # my $status = $arg->{status};
646 # my $passwd = $arg->{passwd};
647 # my $mac_address = $arg->{mac_address};
648 # my $events = $arg->{events};
649 #
650 # if (not defined $hostname) {
651 # daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1);
652 # return;
653 # }
654 #
655 # my ($seconds, $minutes, $hours, $monthday, $month,
656 # $year, $weekday, $yearday, $sommertime) = localtime(time);
657 # $hours = $hours < 10 ? $hours = "0".$hours : $hours;
658 # $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
659 # $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
660 # $month+=1;
661 # $month = $month < 10 ? $month = "0".$month : $month;
662 # $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
663 # $year+=1900;
664 # my $t = "$year$month$monthday$hours$minutes$seconds";
665 #
666 # $shmda->shlock(LOCK_EX);
667 # if (defined $status) {
668 # $known_daemons->{$hostname}->{status} = $status;
669 # }
670 # if (defined $passwd) {
671 # $known_daemons->{$hostname}->{passwd} = $passwd;
672 # }
673 # if (defined $mac_address) {
674 # $known_daemons->{$hostname}->{mac_address} = $mac_address;
675 # }
676 # if (defined $events) {
677 # $known_daemons->{$hostname}->{events} = $events;
678 # }
679 # $known_daemons->{$hostname}->{timestamp} = $t;
680 # $shmda->shlock(LOCK_EX);
681 # return;
682 #}
685 #=== FUNCTION ================================================================
686 # NAME: update_known_daemons
687 # PARAMETERS: hostname - string - ip address and port of host (required)
688 # status - string - (optional)
689 # passwd - string - (optional)
690 # client - string - ip address and port of client (optional)
691 # RETURNS: nothing
692 # DESCRIPTION: nome est omen and updates each time the timestamp of hostname
693 #===============================================================================
694 #sub update_known_daemons {
695 # my $arg = {
696 # hostname => undef, status => undef, passwd => undef,
697 # @_ };
698 # my $hostname = $arg->{hostname};
699 # my $status = $arg->{status};
700 # my $passwd = $arg->{passwd};
701 #
702 # if (not defined $hostname) {
703 # daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1);
704 # return;
705 # }
706 #
707 # my ($seconds, $minutes, $hours, $monthday, $month,
708 # $year, $weekday, $yearday, $sommertime) = localtime(time);
709 # $hours = $hours < 10 ? $hours = "0".$hours : $hours;
710 # $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
711 # $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
712 # $month+=1;
713 # $month = $month < 10 ? $month = "0".$month : $month;
714 # $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
715 # $year+=1900;
716 # my $t = "$year$month$monthday$hours$minutes$seconds";
717 #
718 # $shmda->shlock(LOCK_EX);
719 # if (defined $status) {
720 # $known_daemons->{$hostname}->{status} = $status;
721 # }
722 # if (defined $passwd) {
723 # $known_daemons->{$hostname}->{passwd} = $passwd;
724 # }
725 # $known_daemons->{$hostname}->{timestamp} = $t;
726 # $shmda->shunlock(LOCK_EX);
727 # return;
728 #}
731 #=== FUNCTION ================================================================
732 # NAME: print_known_clients
733 # PARAMETERS: nothing
734 # RETURNS: nothing
735 # DESCRIPTION: nomen est omen
736 #===============================================================================
737 #sub print_known_clients {
738 #
739 # print "####################################\n";
740 # print "# status of known_clients\n";
741 # $shmcl->shlock(LOCK_EX);
742 # my @hosts = keys %$known_clients;
743 # if (@hosts) {
744 # foreach my $host (@hosts) {
745 # my $status = $known_clients->{$host}->{status} ;
746 # my $passwd = $known_clients->{$host}->{passwd};
747 # my $timestamp = $known_clients->{$host}->{timestamp};
748 # my $mac_address = $known_clients->{$host}->{mac_address};
749 # my $events = $known_clients->{$host}->{events};
750 # print "$host\n";
751 # print "\tstatus: $status\n";
752 # print "\tpasswd: $passwd\n";
753 # print "\ttimestamp: $timestamp\n";
754 # print "\tmac_address: $mac_address\n";
755 # print "\tevents: $events\n";
756 # }
757 # }
758 # $shmcl->shunlock(LOCK_EX);
759 # print "####################################\n";
760 # return;
761 #}
764 #=== FUNCTION ================================================================
765 # NAME: create_known_client
766 # PARAMETERS: hostname - string - key for the hash known_clients
767 # RETURNS: nothing
768 # DESCRIPTION: creates a dummy entry for hostname in known_clients
769 #===============================================================================
770 sub create_known_client {
771 my ($hostname) = @_;
773 my $entry = { table=>'known_clients',
774 hostname=>$hostname,
775 status=>'none',
776 hostkey=>'none',
777 timestamp=>'none',
778 macaddress=>'none',
779 events=>'none',
780 };
781 my $res = $known_clients_db->add_dbentry($entry);
782 if ($res > 0) {
783 daemon_log("ERROR: cannot add entry to known_clients.db: $res", 1);
784 }
786 return;
787 }
790 #=== FUNCTION ================================================================
791 # NAME: add_content2known_clients
792 # PARAMETERS: hostname - string - ip address and port of host (required)
793 # status - string - (optional)
794 # passwd - string - (optional)
795 # mac_address - string - (optional)
796 # events - string - event of client, executable skripts
797 # under /etc/gosac/events
798 # RETURNS: nothing
799 # DESCRIPTION: nome est omen and updates each time the timestamp of hostname
800 #===============================================================================
801 #sub update_known_clients {
802 # my $arg = {
803 # hostname => undef, status => undef, hostkey => undef,
804 # macaddress => undef, events => undef, timestamp=>undef,
805 # @_ };
806 # my $hostname = $arg->{hostname};
807 # my $status = $arg->{status};
808 # my $hostkey = $arg->{hostkey};
809 # my $macaddress = $arg->{macaddress};
810 # my $events = $arg->{events};
811 # my $timestamp = $arg->{timestamp};
812 #
813 # if (not defined $hostname) {
814 # daemon_log("ERROR: function add_content2known_clients is not invoked with requiered parameter 'hostname'", 1);
815 # return;
816 # }
817 #
818 # my $change_entry = { table=>'known_clients',
819 # where=>'hostname',
820 # timestamp=>&get_time,
821 # };
822 #
823 #
824 # if (defined $status) {
825 # $change_entry->{status} = $status;
826 # }
827 # if (defined $hostkey) {
828 # $change_entry->{hostkey} = $hostkey;
829 # }
830 # if (defined $macaddress) {
831 # $change_entry->{macaddress} = $macaddress;
832 # }
833 # if (defined $events) {
834 # $change_entry->{events} = $events;
835 # }
836 #
837 # $known_clients->change_dbentry($change_entry);
838 # return;
839 #}
842 #=== FUNCTION ================================================================
843 # NAME:
844 # PARAMETERS:
845 # RETURNS:
846 # DESCRIPTION:
847 #===============================================================================
848 #sub clean_up_known_clients {
849 # my ($address) = @_ ;
850 #
851 # if (not exists $known_clients->{$address}) {
852 # daemon_log("cannot prune known_clients from $address, client not known", 5);
853 # return;
854 # }
855 #
856 # delete $known_clients->{$address};
857 #
858 # # send bus a msg that address was deleted from known_clients
859 # my $out_hash = &create_xml_hash('delete_client', $server_address, $bus_address, $address);
860 # &send_msg_hash2bus($out_hash);
861 #
862 # daemon_log("client $address deleted from known_clients because of multiple down time", 3);
863 # return;
864 #}
867 #=== FUNCTION ================================================================
868 # NAME: update_known_clients
869 # PARAMETERS: hostname - string - ip address and port of host (required)
870 # status - string - (optional)
871 # passwd - string - (optional)
872 # client - string - ip address and port of client (optional)
873 # RETURNS: nothing
874 # DESCRIPTION: nome est omen and updates each time the timestamp of hostname
875 #===============================================================================
876 #sub update_known_clients {
877 # my $arg = {
878 # hostname => undef, status => undef, passwd => undef,
879 # mac_address => undef, events => undef,
880 # @_ };
881 # my $hostname = $arg->{hostname};
882 # my $status = $arg->{status};
883 # my $passwd = $arg->{passwd};
884 # my $mac_address = $arg->{mac_address};
885 # my $events = $arg->{events};
886 #
887 # if (not defined $hostname) {
888 # daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1);
889 # return;
890 # }
891 #
892 # my ($seconds, $minutes, $hours, $monthday, $month,
893 # $year, $weekday, $yearday, $sommertime) = localtime(time);
894 # $hours = $hours < 10 ? $hours = "0".$hours : $hours;
895 # $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
896 # $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
897 # $month+=1;
898 # $month = $month < 10 ? $month = "0".$month : $month;
899 # $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
900 # $year+=1900;
901 # my $t = "$year$month$monthday$hours$minutes$seconds";
902 #
903 # if (defined $status) {
904 # $known_clients->{$hostname}->{status} = $status;
905 # }
906 # if (defined $passwd) {
907 # $known_clients->{$hostname}->{passwd} = $passwd;
908 # }
909 # if (defined $mac_address) {
910 # $known_clients->{$hostname}->{mac_address} = $mac_address;
911 # }
912 # if (defined $events) {
913 # $known_clients->{$hostname}->{events} = $events;
914 # }
915 # $known_clients_db->{$hostname}->{timestamp} = $t;
916 # return;
917 #}
925 #==== MAIN = main ==============================================================
927 # parse commandline options
928 Getopt::Long::Configure( "bundling" );
929 GetOptions("h|help" => \&usage,
930 "c|config=s" => \$cfg_file,
931 "f|foreground" => \$foreground,
932 "v|verbose+" => \$verbose,
933 "no-bus+" => \$no_bus,
934 "no-arp+" => \$no_arp,
935 );
937 # read and set config parameters
938 &check_cmdline_param ;
939 &read_configfile;
940 &check_pid;
942 $SIG{CHLD} = 'IGNORE';
944 # restart daemon log file
945 daemon_log(" ", 1);
946 daemon_log("$0 started!", 1);
948 # Just fork, if we"re not in foreground mode
949 if( ! $foreground ) {
950 $pid = fork();
951 } else {
952 $pid = $$;
953 }
955 # Do something useful - put our PID into the pid_file
956 if( 0 != $pid ) {
957 open( LOCK_FILE, ">$pid_file" );
958 print LOCK_FILE "$pid\n";
959 close( LOCK_FILE );
960 if( !$foreground ) {
961 exit( 0 )
962 };
963 }
965 # connect to gosa-si job queue
966 my @job_col_names = ("timestamp", "status", "result", "headertag", "targettag", "xmlmessage", "macaddress");
967 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
968 $job_db->create_table('jobs', \@job_col_names);
970 # connect to known_clients_db
971 my @clients_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'macaddress', 'events');
972 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
973 $known_clients_db->create_table('known_clients', \@clients_col_names);
975 # connect to known_server_db
976 my @server_col_names = ('hostname', 'status', 'hostkey', 'timestamp');
977 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
978 $known_server_db->create_table('known_server', \@server_col_names);
980 # import all modules
981 &import_modules;
983 # check wether all modules are gosa-si valid passwd check
985 # create reading and writing vectors
986 my $rbits = my $wbits = my $ebits = "";
988 # add all module inputs to listening vector
989 while( my ($mod_name, $info) = each %$known_modules ) {
990 my ($input_address, $input_key, $input, $input_activ, $input_type) = @{$info};
991 vec($rbits, fileno $input, 1) = 1;
993 }
996 ## start arp fifo
997 #if ($no_arp > 0) {
998 # $arp_activ = "off";
999 #}
1000 #my $my_fifo;
1001 #if($arp_activ eq "on") {
1002 # daemon_log(" ", 1);
1003 # $my_fifo = &open_fifo($arp_fifo_path);
1004 # if($my_fifo == 0) { die "fifo file disappeared\n" }
1005 # sysopen($arp_fifo, $arp_fifo_path, O_RDWR) or die "can't read from $arp_fifo: $!" ;
1006 #
1007 # vec($rbits, fileno $arp_fifo, 1) = 1;
1008 #}
1009 #
1012 ##################################
1013 #everything ready, okay, lets start
1014 ##################################
1015 while(1) {
1017 # add all handles from the childs
1018 while ( my ($pid, $child_hash) = each %busy_child ) {
1020 # check whether process still exists
1021 my $exitus_pid = waitpid($pid, WNOHANG);
1022 if($exitus_pid != 0) {
1023 delete $busy_child{$pid};
1024 next;
1025 }
1027 # add child fhd to the listener
1028 my $fhd = $$child_hash{'pipe_rd'};
1029 vec($rbits, fileno $fhd, 1) = 1;
1030 }
1032 my ($rout, $wout);
1033 my $nf = select($rout=$rbits, $wout=$wbits, undef, $job_queue_timeout);
1035 # error handling
1036 if($nf < 0 ) {
1037 }
1040 # if($arp_activ eq "on" && vec($rout, fileno $arp_fifo, 1)) {
1041 # my $in_msg = <$arp_fifo>;
1042 # chomp($in_msg);
1043 # print "arp_activ: msg: $in_msg\n";
1044 # my $act_passwd = $known_daemons->{$bus_address}->{passwd};
1045 # print "arp_activ: arp_passwd: $act_passwd\n";
1046 #
1047 # my $in_msg_hash = $xml->XMLin($in_msg, ForceArray=>1);
1048 #
1049 # my $target = &get_content_from_xml_hash($in_msg_hash, 'target');
1050 #
1051 # if ($target eq $server_address) {
1052 # print "arp_activ: forward to server\n";
1053 # my $arp_cipher = &create_ciphering($act_passwd);
1054 # my $crypted_msg = &encrypt_msg($in_msg, $arp_cipher);
1055 # &activating_child($crypted_msg, $server_ip);
1056 # } else {
1057 # print "arp_activ: send to bus\n";
1058 # &send_msg_hash2address($in_msg_hash, $bus_address);
1059 # }
1060 # print "\n";
1061 # }
1064 # check input fhd of all modules
1065 while ( my ($mod_name, $info) = each %$known_modules) {
1066 my $input_fhd = @{$info}[2];
1067 my $input_activ = @{$info}[3];
1068 if (vec($rout, fileno $input_fhd, 1) && $input_activ eq 'on') {
1069 daemon_log(" ", 1);
1070 my $client = $input_fhd->accept();
1071 my $other_end = getpeername($client);
1072 if(not defined $other_end) {
1073 daemon_log("client cannot be identified: $!");
1074 } else {
1075 my ($port, $iaddr) = unpack_sockaddr_in($other_end);
1076 my $actual_ip = inet_ntoa($iaddr);
1077 daemon_log("accept client at daemon socket from $actual_ip", 5);
1078 my $in_msg = &read_from_socket($client);
1079 if(defined $in_msg){
1080 chomp($in_msg);
1081 &activating_child($in_msg, $actual_ip, $client);
1082 } else {
1083 daemon_log("cannot read from $actual_ip", 5);
1084 }
1085 }
1086 }
1087 }
1089 # check all processing childs whether they are finished ('done') or
1090 while ( my ($pid, $child_hash) = each %busy_child ) {
1091 my $fhd = $$child_hash{'pipe_rd'};
1093 if (vec($rout, fileno $fhd, 1) ) {
1094 daemon_log("process child $pid is ready to read", 5);
1096 $fhd->blocking(1);
1097 my $in_msg = <$fhd>;
1098 $fhd->blocking(0);
1099 my $part_in_msg;
1100 while ($part_in_msg = <$fhd>) {
1101 if (not defined $part_in_msg) {
1102 last;
1103 }
1104 $in_msg .= $part_in_msg;
1105 }
1106 chomp($in_msg);
1108 daemon_log("process child read: $in_msg", 5);
1109 if (not defined $in_msg) {
1110 next;
1111 } elsif ($in_msg =~ "done") {
1112 delete $busy_child{$pid};
1113 $free_child{$pid} = $child_hash;
1115 } else {
1116 # send computed answer back to connected client
1117 my $act_client = $busy_child{$pid}{client_ref};
1118 print $act_client $in_msg."\n";
1120 #my $act_pipe = $busy_child{$pid}{pipe_rd};
1121 delete $busy_child{$pid};
1122 $free_child{$pid} = $child_hash;
1124 # give the client a chance to read
1125 sleep(2);
1126 close ($act_client);
1127 }
1128 }
1129 }
1131 # check gosa job queue for jobs with executable timestamp
1132 my ($seconds, $minutes, $hours, $monthday, $month,
1133 $year, $weekday, $yearday, $sommertime) = localtime(time);
1134 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
1135 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
1136 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
1137 $month+=1;
1138 $month = $month < 10 ? $month = "0".$month : $month;
1139 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
1140 $year+=1900;
1141 my $timestamp = "$year$month$monthday$hours$minutes$seconds";
1144 my $res = $job_db->select_dbentry( { table=>$job_queue_table_name, status=>'waiting', timestamp=>$timestamp } );
1146 while( my ($id, $hit) = each %{$res} ) {
1148 my $jobdb_id = $hit->{ROWID};
1149 my $macaddress = $hit->{macaddress};
1150 my $job_msg_hash = &transform_msg2hash($hit->{xmlmessage});
1151 my $out_msg_hash = $job_msg_hash;
1152 my $res_hash = $known_clients_db->select_dbentry( {table=>'known_clients', macaddress=>$macaddress} );
1153 # expect macaddress is unique!!!!!!
1154 my $target = $res_hash->{1}->{hostname};
1156 if (not defined $target) {
1157 &daemon_log("ERROR: no host found for mac address: $job_msg_hash->{mac}[0]", 1);
1158 &daemon_log("xml message: $hit->{xmlmessage}", 5);
1159 my $update_hash = { table=>$job_queue_table_name,
1160 update=> [ { status=>['error'], result=>["no host found for mac address"] } ],
1161 where=> [ { ROWID=>[$jobdb_id] } ],
1162 };
1163 my $res = $job_db->update_dbentry($update_hash);
1165 next;
1166 }
1168 # add target
1169 &add_content2xml_hash($out_msg_hash, "target", $target);
1171 # add new header
1172 my $out_header = $job_msg_hash->{header}[0];
1173 $out_header =~ s/job_/gosa_/;
1174 delete $out_msg_hash->{header};
1175 &add_content2xml_hash($out_msg_hash, "header", $out_header);
1177 # add sqlite_id
1178 &add_content2xml_hash($out_msg_hash, "jobdb_id", $jobdb_id);
1180 my $out_msg = &create_xml_string($out_msg_hash);
1182 # encrypt msg as a GosaPackage module
1183 my $cipher = &create_ciphering($gosa_passwd);
1184 my $crypted_out_msg = &encrypt_msg($out_msg, $cipher);
1186 my $error = &send_msg_hash2address($out_msg_hash, "$gosa_ip:$gosa_port", $gosa_passwd);
1188 #######################
1189 # TODO exchange ROWID with jobid, insert column jobid in table jobs befor
1191 if ($error == 0) {
1192 my $sql = "UPDATE '$job_queue_table_name' SET status='processing', targettag='$target' WHERE ROWID='$jobdb_id'";
1193 my $res = $job_db->exec_statement($sql);
1194 } else {
1195 my $update_hash = { table=>$job_queue_table_name,
1196 update=> [ { status=>'error' } ],
1197 where=> [ { ROWID=>$jobdb_id } ],
1198 };
1199 my $res = $job_db->update_dbentry($update_hash);
1200 }
1202 }
1205 }