6f54d2d0688a69924b25486506862c47388e871b
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_daemons, $shmda, $known_clients, $shmcl, $known_modules, $known_clients_file_name, $known_server_file_name, $known_clients_db, $known_server_db);
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;
97 #our $known_daemons = {};
98 #our $shmda = tie($known_daemons, 'IPC::Shareable', undef, {create => 1,
99 # exclusive => 1,
100 # mode => 0666,
101 # destroy => 1,
102 # });
104 # holds all registrated clients
105 our $known_clients_db;
107 %cfg_defaults =
108 ("general" =>
109 {"log_file" => [\$log_file, "/var/run/".$0.".log"],
110 "pid_file" => [\$pid_file, "/var/run/".$0.".pid"],
111 "child_max" => [\$child_max, 10],
112 "child_min" => [\$child_min, 3],
113 "child_timeout" => [\$child_timeout, 180],
114 "job_queue_timeout" => [\$job_queue_timeout, undef],
115 "job_queue_file_name" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
116 "known_clients_file_name" => [\$known_clients_file_name, '/var/lib/gosa-si/known_clients.db' ],
117 "known_server_file_name" => [\$known_server_file_name, '/var/lib/gosa-si/known_server.db'],
118 },
119 "bus" =>
120 {"bus_activ" => [\$bus_activ, "on"],
121 "bus_passwd" => [\$bus_passwd, ""],
122 "bus_ip" => [\$bus_ip, ""],
123 "bus_port" => [\$bus_port, "20080"],
124 },
125 "server" =>
126 {"server_activ" => [\$server_activ, "on"],
127 "server_ip" => [\$server_ip, ""],
128 "server_port" => [\$server_port, "20081"],
129 "server_passwd" => [\$server_passwd, ""],
130 "max_clients" => [\$max_clients, 100],
131 },
132 "arp" =>
133 {"arp_activ" => [\$arp_activ, "on"],
134 "arp_fifo_path" => [\$arp_fifo_path, "/var/run/gosa-si/arp-notify"],
135 },
136 "gosa" =>
137 {"gosa_activ" => [\$gosa_activ, "on"],
138 "gosa_ip" => [\$gosa_ip, ""],
139 "gosa_port" => [\$gosa_port, "20082"],
140 "gosa_passwd" => [\$gosa_passwd, "none"],
141 },
142 );
145 #=== FUNCTION ================================================================
146 # NAME: usage
147 # PARAMETERS: nothing
148 # RETURNS: nothing
149 # DESCRIPTION: print out usage text to STDERR
150 #===============================================================================
151 sub usage {
152 print STDERR << "EOF" ;
153 usage: $0 [-hvf] [-c config]
155 -h : this (help) message
156 -c <file> : config file
157 -f : foreground, process will not be forked to background
158 -v : be verbose (multiple to increase verbosity)
159 -no-bus : starts $0 without connection to bus
160 -no-arp : starts $0 without connection to arp module
162 EOF
163 print "\n" ;
164 }
167 #=== FUNCTION ================================================================
168 # NAME: read_configfile
169 # PARAMETERS: cfg_file - string -
170 # RETURNS: nothing
171 # DESCRIPTION: read cfg_file and set variables
172 #===============================================================================
173 sub read_configfile {
174 my $cfg;
175 if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
176 if( -r $cfg_file ) {
177 $cfg = Config::IniFiles->new( -file => $cfg_file );
178 } else {
179 print STDERR "Couldn't read config file!";
180 }
181 } else {
182 $cfg = Config::IniFiles->new() ;
183 }
184 foreach my $section (keys %cfg_defaults) {
185 foreach my $param (keys %{$cfg_defaults{ $section }}) {
186 my $pinfo = $cfg_defaults{ $section }{ $param };
187 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
188 }
189 }
190 }
193 #=== FUNCTION ================================================================
194 # NAME: logging
195 # PARAMETERS: level - string - default 'info'
196 # msg - string -
197 # facility - string - default 'LOG_DAEMON'
198 # RETURNS: nothing
199 # DESCRIPTION: function for logging
200 #===============================================================================
201 sub daemon_log {
202 # log into log_file
203 my( $msg, $level ) = @_;
204 if(not defined $msg) { return }
205 if(not defined $level) { $level = 1 }
206 if(defined $log_file){
207 open(LOG_HANDLE, ">>$log_file");
208 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
209 print STDERR "cannot open $log_file: $!";
210 return }
211 chomp($msg);
212 if($level <= $verbose){
213 print LOG_HANDLE "$level $msg\n";
214 if($foreground) { print $msg."\n" }
215 }
216 }
217 # close( LOG_HANDLE );
218 #log into syslog
219 # my ($msg, $level, $facility) = @_;
220 # if(not defined $msg) {return}
221 # if(not defined $level) {$level = "info"}
222 # if(not defined $facility) {$facility = "LOG_DAEMON"}
223 # openlog($0, "pid,cons,", $facility);
224 # syslog($level, $msg);
225 # closelog;
226 # return;
227 }
230 #=== FUNCTION ================================================================
231 # NAME: check_cmdline_param
232 # PARAMETERS: nothing
233 # RETURNS: nothing
234 # DESCRIPTION: validates commandline parameter
235 #===============================================================================
236 sub check_cmdline_param () {
237 my $err_config;
238 my $err_counter = 0;
239 if( not defined( $cfg_file)) {
240 #$err_config = "please specify a config file";
241 #$err_counter += 1;
242 my $cwd = getcwd;
243 my $name = "/etc/gosa-si/server.conf";
244 $cfg_file = File::Spec->catfile( $cwd, $name );
245 }
246 if( $err_counter > 0 ) {
247 &usage( "", 1 );
248 if( defined( $err_config)) { print STDERR "$err_config\n"}
249 print STDERR "\n";
250 exit( -1 );
251 }
252 }
255 #=== FUNCTION ================================================================
256 # NAME: check_pid
257 # PARAMETERS: nothing
258 # RETURNS: nothing
259 # DESCRIPTION: handels pid processing
260 #===============================================================================
261 sub check_pid {
262 $pid = -1;
263 # Check, if we are already running
264 if( open(LOCK_FILE, "<$pid_file") ) {
265 $pid = <LOCK_FILE>;
266 if( defined $pid ) {
267 chomp( $pid );
268 if( -f "/proc/$pid/stat" ) {
269 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
270 if( $0 eq $stat ) {
271 close( LOCK_FILE );
272 exit -1;
273 }
274 }
275 }
276 close( LOCK_FILE );
277 unlink( $pid_file );
278 }
280 # create a syslog msg if it is not to possible to open PID file
281 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
282 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
283 if (open(LOCK_FILE, '<', $pid_file)
284 && ($pid = <LOCK_FILE>))
285 {
286 chomp($pid);
287 $msg .= "(PID $pid)\n";
288 } else {
289 $msg .= "(unable to read PID)\n";
290 }
291 if( ! ($foreground) ) {
292 openlog( $0, "cons,pid", "daemon" );
293 syslog( "warning", $msg );
294 closelog();
295 }
296 else {
297 print( STDERR " $msg " );
298 }
299 exit( -1 );
300 }
301 }
304 #=== FUNCTION ================================================================
305 # NAME: get_ip_and_mac
306 # PARAMETERS: nothing
307 # RETURNS: (ip, mac)
308 # DESCRIPTION: executes /sbin/ifconfig and parses the output, the first occurence
309 # of a inet address is returned as well as the mac address in the line
310 # above the inet address
311 #===============================================================================
312 sub get_ip_and_mac {
313 my $ip = "0.0.0.0.0"; # Defualt-IP
314 my $mac = "00:00:00:00:00:00"; # Default-MAC
315 my @ifconfig = qx(/sbin/ifconfig);
316 foreach(@ifconfig) {
317 if (/Hardware Adresse (\S{2}):(\S{2}):(\S{2}):(\S{2}):(\S{2}):(\S{2})/) {
318 $mac = "$1:$2:$3:$4:$5:$6";
319 next;
320 }
321 if (/inet Adresse:(\d+).(\d+).(\d+).(\d+)/) {
322 $ip = "$1.$2.$3.$4";
323 last;
324 }
325 }
326 return ($ip, $mac);
327 }
331 #=== FUNCTION ================================================================
332 # NAME: import_modules
333 # PARAMETERS: module_path - string - abs. path to the directory the modules
334 # are stored
335 # RETURNS: nothing
336 # DESCRIPTION: each file in module_path which ends with '.pm' is imported by
337 # "require 'file';"
338 #===============================================================================
339 sub import_modules {
340 daemon_log(" ", 1);
342 if (not -e $modules_path) {
343 daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);
344 }
346 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
347 while (defined (my $file = readdir (DIR))) {
348 if (not $file =~ /(\S*?).pm$/) {
349 next;
350 }
351 eval { require $file; };
352 if ($@) {
353 daemon_log("ERROR: gosa-sd could not load module $file", 1);
354 daemon_log("$@", 5);
355 next;
356 }
357 my $mod_name = $1;
358 #my $module_tag_hash = eval( $mod_name.'::get_module_tags()' );
360 my $info = eval($mod_name.'::get_module_info()');
361 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
362 $known_modules->{$mod_name} = $info;
364 daemon_log("module $mod_name loaded", 1);
365 }
367 # for debugging
368 #while ( my ($module, $tag_hash) = each(%$known_modules)) {
369 # print "\tmodule: $module"."\n";
370 # print "\ttags: ".join(", ", keys(%$tag_hash))."\n";
371 #}
372 close (DIR);
373 }
376 #=== FUNCTION ================================================================
377 # NAME: sig_int_handler
378 # PARAMETERS: signal - string - signal arose from system
379 # RETURNS: noting
380 # DESCRIPTION: handels tasks to be done befor signal becomes active
381 #===============================================================================
382 sub sig_int_handler {
383 my ($signal) = @_;
384 if($server){
385 close($server);
386 daemon_log("daemon server closed", 1);
387 }
388 if( -p $arp_fifo_path ) {
389 close $arp_fifo ;
390 unlink($arp_fifo_path) ;
391 daemon_log("ARP_FIFO closed", 1) ;
392 }
394 if($gosa_server){
395 close($gosa_server);
396 daemon_log("gosa server closed", 1);
397 }
399 print STDERR "$signal\n";
401 exit(1);
402 }
403 $SIG{INT} = \&sig_int_handler;
406 #=== FUNCTION ================================================================
407 # NAME: activating_child
408 # PARAMETERS: msg - string - incoming message
409 # host - string - host from which the incomming message comes
410 # RETURNS: nothing
411 # DESCRIPTION: handels the distribution of incoming messages to working childs
412 #===============================================================================
413 sub activating_child {
414 my ($msg, $host, $client) = @_;
415 my $child = &get_processing_child();
416 my $pipe_wr = $$child{'pipe_wr'};
417 my $pipe_rd = $$child{'pipe_rd'};
418 $$child{client_ref} = $client;
420 daemon_log("activating: childpid:$$child{'pid'}", 5);
422 print $pipe_wr $msg.".".$host."\n";
424 return;
425 }
428 #=== FUNCTION ================================================================
429 # NAME: get_processing_child
430 # PARAMETERS: nothing
431 # RETURNS: child - hash - holding the process id and the references to the pipe
432 # handles pipe_wr and pipe_rd
433 # DESCRIPTION: handels the forking, reactivating and keeping alive tasks
434 #===============================================================================
435 sub get_processing_child {
436 my $child;
438 while(my ($key, $val) = each(%free_child)) {
439 my $exitus_pid = waitpid($key, WNOHANG);
440 if($exitus_pid != 0) {
441 delete $free_child{$key};
442 }
443 daemon_log("free child:$key", 5);
444 }
445 # check @free_child and @busy_child
446 my $free_len = scalar(keys(%free_child));
447 my $busy_len = scalar(keys(%busy_child));
448 daemon_log("free children $free_len, busy children $busy_len", 5);
450 # if there is a free child, let the child work
451 if($free_len > 0){
452 my @keys = keys(%free_child);
453 $child = $free_child{$keys[0]};
454 if(defined $child) {
455 $busy_child{$$child{'pid'}} = $child ;
456 delete $free_child{$$child{'pid'}};
457 }
458 return $child;
459 }
461 # no free child, try to fork another one
462 if($free_len + $busy_len < $child_max) {
464 daemon_log("not enough children, create a new one", 5);
466 # New pipes for communication
467 my( $PARENT_wr, $PARENT_rd );
468 my( $CHILD_wr, $CHILD_rd );
469 pipe( $CHILD_rd, $PARENT_wr );
470 pipe( $PARENT_rd, $CHILD_wr );
471 $PARENT_wr->autoflush(1);
472 $CHILD_wr->autoflush(1);
474 ############
475 # fork child
476 ############
477 my $child_pid = fork();
479 #CHILD
480 if($child_pid == 0) {
481 # Close unused pipes
482 close( $CHILD_rd );
483 close( $CHILD_wr );
484 while( 1 ) {
485 my $rbits = "";
486 vec( $rbits, fileno $PARENT_rd , 1 ) = 1;
487 my $nf = select($rbits, undef, undef, $child_timeout);
488 if($nf < 0 ) {
489 die "select(): $!\n";
490 } elsif (! $nf) {
491 # if already child_min childs are alive, then leave loop
492 $free_len = scalar(keys(%free_child));
493 $busy_len = scalar(keys(%busy_child));
494 if($free_len + $busy_len >= $child_min) {
495 last;
496 } else {
497 redo;
498 }
499 }
501 # a job for a child arise
502 if ( vec $rbits, fileno $PARENT_rd, 1 ) {
503 # read everything from pipe
504 my $msg = "";
505 $PARENT_rd->blocking(0);
506 while(1) {
507 my $read = <$PARENT_rd>;
508 if(not defined $read) { last}
509 $msg .= $read;
510 }
512 ######################################
513 # forward msg to all imported modules
514 no strict "refs";
515 my $answer;
516 my %act_modules = %$known_modules;
517 while( my ($module, $info) = each(%act_modules)) {
518 &daemon_log("##########", 5);
519 my $tmp = &{ $module."::process_incoming_msg" }($msg);
520 if (defined $tmp) {
521 $answer = $tmp;
522 }
523 &daemon_log("##########", 5);
524 }
526 #&print_known_daemons();
527 #&print_known_clients();
529 daemon_log("processing of msg finished", 5);
531 if (defined $answer) {
532 print $PARENT_wr $answer."\n";
533 daemon_log("with answer: $answer", 5);
534 daemon_log(" ", 5);
535 } else {
536 print $PARENT_wr "done"."\n";
537 daemon_log(" ", 5);
538 }
539 redo;
540 }
541 }
542 # childs leaving the loop are allowed to die
543 exit(0);
546 #PARENT
547 } else {
548 # Close unused pipes
549 close( $PARENT_rd );
550 close( $PARENT_wr );
552 # add child to child alive hash
553 my %child_hash = (
554 'pid' => $child_pid,
555 'pipe_wr' => $CHILD_wr,
556 'pipe_rd' => $CHILD_rd,
557 'client_ref' => "",
558 );
560 $child = \%child_hash;
561 $busy_child{$$child{'pid'}} = $child;
562 return $child;
563 }
564 }
565 }
568 #=== FUNCTION ================================================================
569 # NAME: read_from_socket
570 # PARAMETERS: socket fh -
571 # RETURNS: result string - readed characters from socket
572 # DESCRIPTION: reads data from socket in 16 byte steps
573 #===============================================================================
574 sub read_from_socket {
575 my ($socket) = @_;
576 my $result = "";
578 $socket->blocking(1);
579 $result = <$socket>;
581 $socket->blocking(0);
582 while ( my $char = <$socket> ) {
583 if (not defined $char) { last }
584 $result .= $char;
585 }
587 return $result;
588 }
591 #=== FUNCTION ================================================================
592 # NAME: print_known_daemons
593 # PARAMETERS: nothing
594 # RETURNS: nothing
595 # DESCRIPTION: nomen est omen
596 #===============================================================================
597 #sub print_known_daemons {
598 # my ($tmp) = @_ ;
599 # print "####################################\n";
600 # print "# status of known_daemons\n";
601 # $shmda->shlock(LOCK_EX);
602 # my @hosts = keys %$known_daemons;
603 # foreach my $host (@hosts) {
604 # my $status = $known_daemons->{$host}->{status} ;
605 # my $passwd = $known_daemons->{$host}->{passwd};
606 # my $timestamp = $known_daemons->{$host}->{timestamp};
607 # print "$host\n";
608 # print "\tstatus: $status\n";
609 # print "\tpasswd: $passwd\n";
610 # print "\ttimestamp: $timestamp\n";
611 # }
612 # $shmda->shunlock(LOCK_EX);
613 # print "####################################\n";
614 # return;
615 #}
618 #=== FUNCTION ================================================================
619 # NAME: create_known_daemon
620 # PARAMETERS: hostname - string - key for the hash known_daemons
621 # RETURNS: nothing
622 # DESCRIPTION: creates a dummy entry for hostname in known_daemons
623 #===============================================================================
624 #sub create_known_daemon {
625 # my ($hostname) = @_;
626 # $shmda->shlock(LOCK_EX);
627 # $known_daemons->{$hostname} = {};
628 # $known_daemons->{$hostname}->{status} = "none";
629 # $known_daemons->{$hostname}->{passwd} = "none";
630 # $known_daemons->{$hostname}->{timestamp} = "none";
631 # $shmda->shunlock(LOCK_EX);
632 # return;
633 #}
636 #=== FUNCTION ================================================================
637 # NAME: add_content2known_daemons
638 # PARAMETERS: hostname - string - ip address and port of host (required)
639 # status - string - (optional)
640 # passwd - string - (optional)
641 # mac_address - string - mac address of host (optional)
642 # RETURNS: nothing
643 # DESCRIPTION: nome est omen and updates each time the timestamp of hostname
644 #===============================================================================
645 sub add_content2known_daemons {
646 my $arg = {
647 hostname => undef, status => undef, passwd => undef,
648 mac_address => undef, events => undef,
649 @_ };
650 my $hostname = $arg->{hostname};
651 my $status = $arg->{status};
652 my $passwd = $arg->{passwd};
653 my $mac_address = $arg->{mac_address};
654 my $events = $arg->{events};
656 if (not defined $hostname) {
657 daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1);
658 return;
659 }
661 my ($seconds, $minutes, $hours, $monthday, $month,
662 $year, $weekday, $yearday, $sommertime) = localtime(time);
663 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
664 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
665 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
666 $month+=1;
667 $month = $month < 10 ? $month = "0".$month : $month;
668 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
669 $year+=1900;
670 my $t = "$year$month$monthday$hours$minutes$seconds";
672 $shmda->shlock(LOCK_EX);
673 if (defined $status) {
674 $known_daemons->{$hostname}->{status} = $status;
675 }
676 if (defined $passwd) {
677 $known_daemons->{$hostname}->{passwd} = $passwd;
678 }
679 if (defined $mac_address) {
680 $known_daemons->{$hostname}->{mac_address} = $mac_address;
681 }
682 if (defined $events) {
683 $known_daemons->{$hostname}->{events} = $events;
684 }
685 $known_daemons->{$hostname}->{timestamp} = $t;
686 $shmda->shlock(LOCK_EX);
687 return;
688 }
691 #=== FUNCTION ================================================================
692 # NAME: update_known_daemons
693 # PARAMETERS: hostname - string - ip address and port of host (required)
694 # status - string - (optional)
695 # passwd - string - (optional)
696 # client - string - ip address and port of client (optional)
697 # RETURNS: nothing
698 # DESCRIPTION: nome est omen and updates each time the timestamp of hostname
699 #===============================================================================
700 sub update_known_daemons {
701 my $arg = {
702 hostname => undef, status => undef, passwd => undef,
703 @_ };
704 my $hostname = $arg->{hostname};
705 my $status = $arg->{status};
706 my $passwd = $arg->{passwd};
708 if (not defined $hostname) {
709 daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1);
710 return;
711 }
713 my ($seconds, $minutes, $hours, $monthday, $month,
714 $year, $weekday, $yearday, $sommertime) = localtime(time);
715 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
716 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
717 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
718 $month+=1;
719 $month = $month < 10 ? $month = "0".$month : $month;
720 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
721 $year+=1900;
722 my $t = "$year$month$monthday$hours$minutes$seconds";
724 $shmda->shlock(LOCK_EX);
725 if (defined $status) {
726 $known_daemons->{$hostname}->{status} = $status;
727 }
728 if (defined $passwd) {
729 $known_daemons->{$hostname}->{passwd} = $passwd;
730 }
731 $known_daemons->{$hostname}->{timestamp} = $t;
732 $shmda->shunlock(LOCK_EX);
733 return;
734 }
737 #=== FUNCTION ================================================================
738 # NAME: print_known_clients
739 # PARAMETERS: nothing
740 # RETURNS: nothing
741 # DESCRIPTION: nomen est omen
742 #===============================================================================
743 #sub print_known_clients {
744 #
745 # print "####################################\n";
746 # print "# status of known_clients\n";
747 # $shmcl->shlock(LOCK_EX);
748 # my @hosts = keys %$known_clients;
749 # if (@hosts) {
750 # foreach my $host (@hosts) {
751 # my $status = $known_clients->{$host}->{status} ;
752 # my $passwd = $known_clients->{$host}->{passwd};
753 # my $timestamp = $known_clients->{$host}->{timestamp};
754 # my $mac_address = $known_clients->{$host}->{mac_address};
755 # my $events = $known_clients->{$host}->{events};
756 # print "$host\n";
757 # print "\tstatus: $status\n";
758 # print "\tpasswd: $passwd\n";
759 # print "\ttimestamp: $timestamp\n";
760 # print "\tmac_address: $mac_address\n";
761 # print "\tevents: $events\n";
762 # }
763 # }
764 # $shmcl->shunlock(LOCK_EX);
765 # print "####################################\n";
766 # return;
767 #}
770 #=== FUNCTION ================================================================
771 # NAME: create_known_client
772 # PARAMETERS: hostname - string - key for the hash known_clients
773 # RETURNS: nothing
774 # DESCRIPTION: creates a dummy entry for hostname in known_clients
775 #===============================================================================
776 sub create_known_client {
777 my ($hostname) = @_;
779 my $entry = { table=>'known_clients',
780 hostname=>$hostname,
781 status=>'none',
782 hostkey=>'none',
783 timestamp=>'none',
784 macaddress=>'none',
785 events=>'none',
786 };
787 my $res = $known_clients_db->add_dbentry($entry);
788 if ($res > 0) {
789 daemon_log("ERROR: cannot add entry to known_clients.db: $res", 1);
790 }
792 # $shmcl->shlock(LOCK_EX);
793 # $known_clients->{$hostname} = {};
794 # $known_clients->{$hostname}->{status} = "none";
795 # $known_clients->{$hostname}->{passwd} = "none";
796 # $known_clients->{$hostname}->{timestamp} = "none";
797 # $known_clients->{$hostname}->{mac_address} = "none";
798 # $known_clients->{$hostname}->{events} = "none";
799 # $shmcl->shunlock(LOCK_EX);
800 return;
801 }
804 #=== FUNCTION ================================================================
805 # NAME: add_content2known_clients
806 # PARAMETERS: hostname - string - ip address and port of host (required)
807 # status - string - (optional)
808 # passwd - string - (optional)
809 # mac_address - string - (optional)
810 # events - string - event of client, executable skripts
811 # under /etc/gosac/events
812 # RETURNS: nothing
813 # DESCRIPTION: nome est omen and updates each time the timestamp of hostname
814 #===============================================================================
815 sub update_known_clients {
816 my $arg = {
817 hostname => undef, status => undef, hostkey => undef,
818 macaddress => undef, events => undef, timestamp=>undef,
819 @_ };
820 my $hostname = $arg->{hostname};
821 my $status = $arg->{status};
822 my $hostkey = $arg->{hostkey};
823 my $macaddress = $arg->{macaddress};
824 my $events = $arg->{events};
825 my $timestamp = $arg->{timestamp};
827 if (not defined $hostname) {
828 daemon_log("ERROR: function add_content2known_clients is not invoked with requiered parameter 'hostname'", 1);
829 return;
830 }
832 my ($seconds, $minutes, $hours, $monthday, $month,
833 $year, $weekday, $yearday, $sommertime) = localtime(time);
834 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
835 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
836 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
837 $month+=1;
838 $month = $month < 10 ? $month = "0".$month : $month;
839 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
840 $year+=1900;
841 my $t = "$year$month$monthday$hours$minutes$seconds";
843 my $change_entry = { table=>'known_clients',
844 where=>'hostname',
845 timestamp=>$t,
846 };
849 if (defined $status) {
850 $change_entry->{status} = $status;
851 }
852 if (defined $hostkey) {
853 $change_entry->{hostkey} = $hostkey;
854 }
855 if (defined $macaddress) {
856 $change_entry->{macaddress} = $macaddress;
857 }
858 if (defined $events) {
859 $change_entry->{events} = $events;
860 }
862 $known_clients->change_dbentry($change_entry);
863 return;
864 }
867 #=== FUNCTION ================================================================
868 # NAME:
869 # PARAMETERS:
870 # RETURNS:
871 # DESCRIPTION:
872 #===============================================================================
873 sub clean_up_known_clients {
874 my ($address) = @_ ;
876 if (not exists $known_clients->{$address}) {
877 daemon_log("cannot prune known_clients from $address, client not known", 5);
878 return;
879 }
881 delete $known_clients->{$address};
883 # send bus a msg that address was deleted from known_clients
884 my $out_hash = &create_xml_hash('delete_client', $server_address, $bus_address, $address);
885 &send_msg_hash2bus($out_hash);
887 daemon_log("client $address deleted from known_clients because of multiple down time", 3);
888 return;
889 }
892 #=== FUNCTION ================================================================
893 # NAME: update_known_clients
894 # PARAMETERS: hostname - string - ip address and port of host (required)
895 # status - string - (optional)
896 # passwd - string - (optional)
897 # client - string - ip address and port of client (optional)
898 # RETURNS: nothing
899 # DESCRIPTION: nome est omen and updates each time the timestamp of hostname
900 #===============================================================================
901 #sub update_known_clients {
902 # my $arg = {
903 # hostname => undef, status => undef, passwd => undef,
904 # mac_address => undef, events => undef,
905 # @_ };
906 # my $hostname = $arg->{hostname};
907 # my $status = $arg->{status};
908 # my $passwd = $arg->{passwd};
909 # my $mac_address = $arg->{mac_address};
910 # my $events = $arg->{events};
911 #
912 # if (not defined $hostname) {
913 # daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1);
914 # return;
915 # }
916 #
917 # my ($seconds, $minutes, $hours, $monthday, $month,
918 # $year, $weekday, $yearday, $sommertime) = localtime(time);
919 # $hours = $hours < 10 ? $hours = "0".$hours : $hours;
920 # $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
921 # $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
922 # $month+=1;
923 # $month = $month < 10 ? $month = "0".$month : $month;
924 # $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
925 # $year+=1900;
926 # my $t = "$year$month$monthday$hours$minutes$seconds";
927 #
928 # if (defined $status) {
929 # $known_clients->{$hostname}->{status} = $status;
930 # }
931 # if (defined $passwd) {
932 # $known_clients->{$hostname}->{passwd} = $passwd;
933 # }
934 # if (defined $mac_address) {
935 # $known_clients->{$hostname}->{mac_address} = $mac_address;
936 # }
937 # if (defined $events) {
938 # $known_clients->{$hostname}->{events} = $events;
939 # }
940 # $known_clients_db->{$hostname}->{timestamp} = $t;
941 # return;
942 #}
950 #==== MAIN = main ==============================================================
952 # parse commandline options
953 Getopt::Long::Configure( "bundling" );
954 GetOptions("h|help" => \&usage,
955 "c|config=s" => \$cfg_file,
956 "f|foreground" => \$foreground,
957 "v|verbose+" => \$verbose,
958 "no-bus+" => \$no_bus,
959 "no-arp+" => \$no_arp,
960 );
962 # read and set config parameters
963 &check_cmdline_param ;
964 &read_configfile;
965 &check_pid;
967 $SIG{CHLD} = 'IGNORE';
969 # restart daemon log file
970 if(-e $log_file ) { unlink $log_file }
971 daemon_log(" ", 1);
972 daemon_log("$0 started!", 1);
974 # Just fork, if we"re not in foreground mode
975 if( ! $foreground ) {
976 $pid = fork();
977 } else {
978 $pid = $$;
979 }
981 # Do something useful - put our PID into the pid_file
982 if( 0 != $pid ) {
983 open( LOCK_FILE, ">$pid_file" );
984 print LOCK_FILE "$pid\n";
985 close( LOCK_FILE );
986 if( !$foreground ) {
987 exit( 0 )
988 };
989 }
991 # connect to gosa-si job queue
992 my @job_col_names = ("timestamp", "status", "result", "headertag", "targettag", "xmlmessage", "macaddress");
993 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
994 $job_db->create_table('jobs', \@job_col_names);
996 # connect to known_clients_db
997 my @clients_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'macaddress', 'events');
998 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
999 $known_clients_db->create_table('known_clients', \@clients_col_names);
1001 # connect to known_server_db
1002 my @server_col_names = ('hostname', 'status', 'hostkey', 'timestamp');
1003 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
1004 $known_server_db->create_table('known_server', \@server_col_names);
1006 # import all modules
1007 &import_modules;
1009 # check wether all modules are gosa-si valid passwd check
1011 # create reading and writing vectors
1012 my $rbits = my $wbits = my $ebits = "";
1014 # add all module inputs to listening vector
1015 while( my ($mod_name, $info) = each %$known_modules ) {
1016 my ($input_address, $input_key, $input, $input_activ, $input_type) = @{$info};
1017 vec($rbits, fileno $input, 1) = 1;
1019 }
1022 ## start arp fifo
1023 #if ($no_arp > 0) {
1024 # $arp_activ = "off";
1025 #}
1026 #my $my_fifo;
1027 #if($arp_activ eq "on") {
1028 # daemon_log(" ", 1);
1029 # $my_fifo = &open_fifo($arp_fifo_path);
1030 # if($my_fifo == 0) { die "fifo file disappeared\n" }
1031 # sysopen($arp_fifo, $arp_fifo_path, O_RDWR) or die "can't read from $arp_fifo: $!" ;
1032 #
1033 # vec($rbits, fileno $arp_fifo, 1) = 1;
1034 #}
1035 #
1038 ##################################
1039 #everything ready, okay, lets start
1040 ##################################
1041 while(1) {
1043 # add all handles from the childs
1044 while ( my ($pid, $child_hash) = each %busy_child ) {
1046 # check whether process still exists
1047 my $exitus_pid = waitpid($pid, WNOHANG);
1048 if($exitus_pid != 0) {
1049 delete $busy_child{$pid};
1050 next;
1051 }
1053 # add child fhd to the listener
1054 my $fhd = $$child_hash{'pipe_rd'};
1055 vec($rbits, fileno $fhd, 1) = 1;
1056 }
1058 my ($rout, $wout);
1059 my $nf = select($rout=$rbits, $wout=$wbits, undef, $job_queue_timeout);
1061 # error handling
1062 if($nf < 0 ) {
1063 }
1066 # if($arp_activ eq "on" && vec($rout, fileno $arp_fifo, 1)) {
1067 # my $in_msg = <$arp_fifo>;
1068 # chomp($in_msg);
1069 # print "arp_activ: msg: $in_msg\n";
1070 # my $act_passwd = $known_daemons->{$bus_address}->{passwd};
1071 # print "arp_activ: arp_passwd: $act_passwd\n";
1072 #
1073 # my $in_msg_hash = $xml->XMLin($in_msg, ForceArray=>1);
1074 #
1075 # my $target = &get_content_from_xml_hash($in_msg_hash, 'target');
1076 #
1077 # if ($target eq $server_address) {
1078 # print "arp_activ: forward to server\n";
1079 # my $arp_cipher = &create_ciphering($act_passwd);
1080 # my $crypted_msg = &encrypt_msg($in_msg, $arp_cipher);
1081 # &activating_child($crypted_msg, $server_ip);
1082 # } else {
1083 # print "arp_activ: send to bus\n";
1084 # &send_msg_hash2address($in_msg_hash, $bus_address);
1085 # }
1086 # print "\n";
1087 # }
1090 # check input fhd of all modules
1091 while ( my ($mod_name, $info) = each %$known_modules) {
1092 my $input_fhd = @{$info}[2];
1093 my $input_activ = @{$info}[3];
1094 if (vec($rout, fileno $input_fhd, 1) && $input_activ eq 'on') {
1095 daemon_log(" ", 1);
1096 my $client = $input_fhd->accept();
1097 my $other_end = getpeername($client);
1098 if(not defined $other_end) {
1099 daemon_log("client cannot be identified: $!");
1100 } else {
1101 my ($port, $iaddr) = unpack_sockaddr_in($other_end);
1102 my $actual_ip = inet_ntoa($iaddr);
1103 daemon_log("accept client at daemon socket from $actual_ip", 5);
1104 my $in_msg = &read_from_socket($client);
1105 if(defined $in_msg){
1106 chomp($in_msg);
1107 &activating_child($in_msg, $actual_ip, $client);
1108 } else {
1109 daemon_log("cannot read from $actual_ip", 5);
1110 }
1111 }
1112 }
1113 }
1115 # check all processing childs whether they are finished ('done') or
1116 while ( my ($pid, $child_hash) = each %busy_child ) {
1117 my $fhd = $$child_hash{'pipe_rd'};
1119 if (vec($rout, fileno $fhd, 1) ) {
1120 daemon_log("process child $pid is ready to read", 5);
1122 $fhd->blocking(1);
1123 my $in_msg = <$fhd>;
1124 $fhd->blocking(0);
1125 my $part_in_msg;
1126 while ($part_in_msg = <$fhd>) {
1127 if (not defined $part_in_msg) {
1128 last;
1129 }
1130 $in_msg .= $part_in_msg;
1131 }
1132 chomp($in_msg);
1134 daemon_log("process child read: $in_msg", 5);
1135 if (not defined $in_msg) {
1136 next;
1137 } elsif ($in_msg =~ "done") {
1138 delete $busy_child{$pid};
1139 $free_child{$pid} = $child_hash;
1141 } else {
1142 # send computed answer back to connected client
1143 my $act_client = $busy_child{$pid}{client_ref};
1144 print $act_client $in_msg."\n";
1146 #my $act_pipe = $busy_child{$pid}{pipe_rd};
1147 delete $busy_child{$pid};
1148 $free_child{$pid} = $child_hash;
1150 # give the client a chance to read
1151 sleep(2);
1152 close ($act_client);
1153 }
1154 }
1155 }
1157 # check gosa job queue for jobs with executable timestamp
1158 my ($seconds, $minutes, $hours, $monthday, $month,
1159 $year, $weekday, $yearday, $sommertime) = localtime(time);
1160 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
1161 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
1162 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
1163 $month+=1;
1164 $month = $month < 10 ? $month = "0".$month : $month;
1165 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
1166 $year+=1900;
1167 my $timestamp = "$year$month$monthday$hours$minutes$seconds";
1170 my $res = $job_db->select_dbentry( { table=>$job_queue_table_name, status=>'waiting', timestamp=>$timestamp } );
1172 while( my ($id, $hit) = each %{$res} ) {
1174 my $jobdb_id = $hit->{ROWID};
1175 my $macaddress = $hit->{macaddress};
1176 my $job_msg_hash = &transform_msg2hash($hit->{xmlmessage});
1177 my $out_msg_hash = $job_msg_hash;
1178 my $res_hash = $known_clients_db->select_dbentry( {table=>'known_clients', macaddress=>$macaddress} );
1179 # expect macaddress is unique!!!!!!
1180 my $target = $res_hash->{1}->{hostname};
1182 if (not defined $target) {
1183 &daemon_log("ERROR: no host found for mac address: $job_msg_hash->{mac}[0]", 1);
1184 &daemon_log("xml message: $hit->{xmlmessage}", 5);
1185 my $update_hash = { table=>$job_queue_table_name,
1186 update=> [ { status=>['error'], result=>["no host found for mac address"] } ],
1187 where=> [ { ROWID=>[$jobdb_id] } ],
1188 };
1189 my $res = $job_db->update_dbentry($update_hash);
1191 next;
1192 }
1194 # add target
1195 &add_content2xml_hash($out_msg_hash, "target", $target);
1197 # add new header
1198 my $out_header = $job_msg_hash->{header}[0];
1199 $out_header =~ s/job_/gosa_/;
1200 delete $out_msg_hash->{header};
1201 &add_content2xml_hash($out_msg_hash, "header", $out_header);
1203 # add sqlite_id
1204 &add_content2xml_hash($out_msg_hash, "jobdb_id", $jobdb_id);
1206 my $out_msg = &create_xml_string($out_msg_hash);
1208 # encrypt msg as a GosaPackage module
1209 my $cipher = &create_ciphering($gosa_passwd);
1210 my $crypted_out_msg = &encrypt_msg($out_msg, $cipher);
1212 my $error = &send_msg_hash2address($out_msg_hash, "$gosa_ip:$gosa_port", $gosa_passwd);
1214 #######################
1215 # TODO exchange ROWID with jobid, insert column jobid in table jobs befor
1217 if ($error == 0) {
1218 my $sql = "UPDATE '$job_queue_table_name' SET status='processing', targettag='$target' WHERE ROWID='$jobdb_id'";
1219 my $res = $job_db->exec_statement($sql);
1220 } else {
1221 my $update_hash = { table=>$job_queue_table_name,
1222 update=> [ { status=>'error' } ],
1223 where=> [ { ROWID=>$jobdb_id } ],
1224 };
1225 my $res = $job_db->update_dbentry($update_hash);
1226 }
1228 }
1231 }