e273e9e0c04ef50dc78e664131fba949c20a25f2
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 # libpoe-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 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5 qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
57 use DateTime;
59 my $modules_path = "/usr/lib/gosa-si/modules";
60 use lib "/usr/lib/gosa-si/modules";
62 # revision number of server and program name
63 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
64 my $server_headURL;
65 my $server_revision;
66 my $server_status;
67 our $prg= basename($0);
69 our $global_kernel;
70 my ($foreground, $ping_timeout);
71 my ($server);
72 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
73 my ($messaging_db_loop_delay);
74 my ($known_modules);
75 my ($procid, $pid);
76 my ($arp_fifo);
77 my ($xml);
78 my $sources_list;
79 my $max_clients;
80 my %repo_files=();
81 my $repo_path;
82 my %repo_dirs=();
83 # variables declared in config file are always set to 'our'
84 our (%cfg_defaults, $log_file, $pid_file,
85 $server_ip, $server_port, $ClientPackages_key,
86 $arp_activ, $gosa_unit_tag,
87 $GosaPackages_key, $gosa_timeout,
88 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
89 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
90 $arp_enabled, $arp_interface,
91 );
93 # additional variable which should be globaly accessable
94 our $server_address;
95 our $server_mac_address;
96 our $gosa_address;
97 our $no_arp;
98 our $verbose;
99 our $forground;
100 our $cfg_file;
101 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
103 # specifies the verbosity of the daemon_log
104 $verbose = 0 ;
106 # if foreground is not null, script will be not forked to background
107 $foreground = 0 ;
109 # specifies the timeout seconds while checking the online status of a registrating client
110 $ping_timeout = 5;
112 $no_arp = 0;
113 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
114 my @packages_list_statements;
115 my $watch_for_new_jobs_in_progress = 0;
117 # holds all incoming decrypted messages
118 our $incoming_db;
119 our $incoming_tn = 'incoming';
120 my $incoming_file_name;
121 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
122 "timestamp DEFAULT 'none'",
123 "headertag DEFAULT 'none'",
124 "targettag DEFAULT 'none'",
125 "xmlmessage DEFAULT 'none'",
126 "module DEFAULT 'none'",
127 "sessionid DEFAULT '0'",
128 );
130 # holds all gosa jobs
131 our $job_db;
132 our $job_queue_tn = 'jobs';
133 my $job_queue_file_name;
134 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
135 "timestamp DEFAULT 'none'",
136 "status DEFAULT 'none'",
137 "result DEFAULT 'none'",
138 "progress DEFAULT 'none'",
139 "headertag DEFAULT 'none'",
140 "targettag DEFAULT 'none'",
141 "xmlmessage DEFAULT 'none'",
142 "macaddress DEFAULT 'none'",
143 "plainname DEFAULT 'none'",
144 "siserver DEFAULT 'none'",
145 "modified DEFAULT '0'",
146 );
148 # holds all other gosa-si-server
149 our $known_server_db;
150 our $known_server_tn = "known_server";
151 my $known_server_file_name;
152 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
154 # holds all registrated clients
155 our $known_clients_db;
156 our $known_clients_tn = "known_clients";
157 my $known_clients_file_name;
158 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
160 # holds all registered clients at a foreign server
161 our $foreign_clients_db;
162 our $foreign_clients_tn = "foreign_clients";
163 my $foreign_clients_file_name;
164 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
166 # holds all logged in user at each client
167 our $login_users_db;
168 our $login_users_tn = "login_users";
169 my $login_users_file_name;
170 my @login_users_col_names = ("client", "user", "timestamp");
172 # holds all fai server, the debian release and tag
173 our $fai_server_db;
174 our $fai_server_tn = "fai_server";
175 my $fai_server_file_name;
176 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
178 our $fai_release_db;
179 our $fai_release_tn = "fai_release";
180 my $fai_release_file_name;
181 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
183 # holds all packages available from different repositories
184 our $packages_list_db;
185 our $packages_list_tn = "packages_list";
186 my $packages_list_file_name;
187 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
188 my $outdir = "/tmp/packages_list_db";
189 my $arch = "i386";
191 # holds all messages which should be delivered to a user
192 our $messaging_db;
193 our $messaging_tn = "messaging";
194 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
195 "flag", "direction", "delivery_time", "message", "timestamp" );
196 my $messaging_file_name;
198 # path to directory to store client install log files
199 our $client_fai_log_dir = "/var/log/fai";
201 # queue which stores taskes until one of the $max_children children are ready to process the task
202 my @tasks = qw();
203 my @msgs_to_decrypt = qw();
204 my $max_children = 2;
207 # loop delay for job queue to look for opsi jobs
208 my $job_queue_opsi_delay = 2;
211 %cfg_defaults = (
212 "general" => {
213 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
214 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
215 },
216 "server" => {
217 "ip" => [\$server_ip, "0.0.0.0"],
218 "port" => [\$server_port, "20081"],
219 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
220 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
221 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
222 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
223 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
224 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
225 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
226 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
227 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
228 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
229 "repo-path" => [\$repo_path, '/srv/www/repository'],
230 "ldap-uri" => [\$ldap_uri, ""],
231 "ldap-base" => [\$ldap_base, ""],
232 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
233 "ldap-admin-password" => [\$ldap_admin_password, ""],
234 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
235 "max-clients" => [\$max_clients, 10],
236 "wol-password" => [\$wake_on_lan_passwd, ""],
237 },
238 "GOsaPackages" => {
239 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
240 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
241 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
242 "key" => [\$GosaPackages_key, "none"],
243 },
244 "ClientPackages" => {
245 "key" => [\$ClientPackages_key, "none"],
246 },
247 "ServerPackages"=> {
248 "address" => [\$foreign_server_string, ""],
249 "domain" => [\$server_domain, ""],
250 "key" => [\$ServerPackages_key, "none"],
251 "key-lifetime" => [\$foreign_servers_register_delay, 120],
252 "job-synchronization-enabled" => [\$job_synchronization, "true"],
253 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
254 },
255 "ArpHandler" => {
256 "enabled" => [\$arp_enabled, "true"],
257 "interface" => [\$arp_interface, "all"],
258 },
260 );
263 #=== FUNCTION ================================================================
264 # NAME: usage
265 # PARAMETERS: nothing
266 # RETURNS: nothing
267 # DESCRIPTION: print out usage text to STDERR
268 #===============================================================================
269 sub usage {
270 print STDERR << "EOF" ;
271 usage: $prg [-hvf] [-c config]
273 -h : this (help) message
274 -c <file> : config file
275 -f : foreground, process will not be forked to background
276 -v : be verbose (multiple to increase verbosity)
277 -no-arp : starts $prg without connection to arp module
279 EOF
280 print "\n" ;
281 }
284 #=== FUNCTION ================================================================
285 # NAME: logging
286 # PARAMETERS: level - string - default 'info'
287 # msg - string -
288 # facility - string - default 'LOG_DAEMON'
289 # RETURNS: nothing
290 # DESCRIPTION: function for logging
291 #===============================================================================
292 sub daemon_log {
293 # log into log_file
294 my( $msg, $level ) = @_;
295 if(not defined $msg) { return }
296 if(not defined $level) { $level = 1 }
297 if(defined $log_file){
298 open(LOG_HANDLE, ">>$log_file");
299 chmod 0600, $log_file;
300 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
301 print STDERR "cannot open $log_file: $!";
302 return
303 }
304 chomp($msg);
305 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
306 if($level <= $verbose){
307 my ($seconds, $minutes, $hours, $monthday, $month,
308 $year, $weekday, $yearday, $sommertime) = localtime(time);
309 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
310 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
311 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
312 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
313 $month = $monthnames[$month];
314 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
315 $year+=1900;
316 my $name = $prg;
318 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
319 print LOG_HANDLE $log_msg;
320 if( $foreground ) {
321 print STDERR $log_msg;
322 }
323 }
324 close( LOG_HANDLE );
325 }
326 }
329 #=== FUNCTION ================================================================
330 # NAME: check_cmdline_param
331 # PARAMETERS: nothing
332 # RETURNS: nothing
333 # DESCRIPTION: validates commandline parameter
334 #===============================================================================
335 sub check_cmdline_param () {
336 my $err_config;
337 my $err_counter = 0;
338 if(not defined($cfg_file)) {
339 $cfg_file = "/etc/gosa-si/server.conf";
340 if(! -r $cfg_file) {
341 $err_config = "please specify a config file";
342 $err_counter += 1;
343 }
344 }
345 if( $err_counter > 0 ) {
346 &usage( "", 1 );
347 if( defined( $err_config)) { print STDERR "$err_config\n"}
348 print STDERR "\n";
349 exit( -1 );
350 }
351 }
354 #=== FUNCTION ================================================================
355 # NAME: check_pid
356 # PARAMETERS: nothing
357 # RETURNS: nothing
358 # DESCRIPTION: handels pid processing
359 #===============================================================================
360 sub check_pid {
361 $pid = -1;
362 # Check, if we are already running
363 if( open(LOCK_FILE, "<$pid_file") ) {
364 $pid = <LOCK_FILE>;
365 if( defined $pid ) {
366 chomp( $pid );
367 if( -f "/proc/$pid/stat" ) {
368 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
369 if( $stat ) {
370 daemon_log("ERROR: Already running",1);
371 close( LOCK_FILE );
372 exit -1;
373 }
374 }
375 }
376 close( LOCK_FILE );
377 unlink( $pid_file );
378 }
380 # create a syslog msg if it is not to possible to open PID file
381 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
382 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
383 if (open(LOCK_FILE, '<', $pid_file)
384 && ($pid = <LOCK_FILE>))
385 {
386 chomp($pid);
387 $msg .= "(PID $pid)\n";
388 } else {
389 $msg .= "(unable to read PID)\n";
390 }
391 if( ! ($foreground) ) {
392 openlog( $0, "cons,pid", "daemon" );
393 syslog( "warning", $msg );
394 closelog();
395 }
396 else {
397 print( STDERR " $msg " );
398 }
399 exit( -1 );
400 }
401 }
403 #=== FUNCTION ================================================================
404 # NAME: import_modules
405 # PARAMETERS: module_path - string - abs. path to the directory the modules
406 # are stored
407 # RETURNS: nothing
408 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
409 # state is on is imported by "require 'file';"
410 #===============================================================================
411 sub import_modules {
412 daemon_log(" ", 1);
414 if (not -e $modules_path) {
415 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
416 }
418 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
419 while (defined (my $file = readdir (DIR))) {
420 if (not $file =~ /(\S*?).pm$/) {
421 next;
422 }
423 my $mod_name = $1;
425 # ArpHandler switch
426 if( $file =~ /ArpHandler.pm/ ) {
427 if( $arp_enabled eq "false" ) { next; }
428 }
430 eval { require $file; };
431 if ($@) {
432 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
433 daemon_log("$@", 5);
434 } else {
435 my $info = eval($mod_name.'::get_module_info()');
436 # Only load module if get_module_info() returns a non-null object
437 if( $info ) {
438 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
439 $known_modules->{$mod_name} = $info;
440 daemon_log("0 INFO: module $mod_name loaded", 5);
441 }
442 }
443 }
444 close (DIR);
445 }
447 #=== FUNCTION ================================================================
448 # NAME: password_check
449 # PARAMETERS: nothing
450 # RETURNS: nothing
451 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
452 # the same password
453 #===============================================================================
454 sub password_check {
455 my $passwd_hash = {};
456 while (my ($mod_name, $mod_info) = each %$known_modules) {
457 my $mod_passwd = @$mod_info[1];
458 if (not defined $mod_passwd) { next; }
459 if (not exists $passwd_hash->{$mod_passwd}) {
460 $passwd_hash->{$mod_passwd} = $mod_name;
462 # escalates critical error
463 } else {
464 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
465 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
466 exit( -1 );
467 }
468 }
470 }
473 #=== FUNCTION ================================================================
474 # NAME: sig_int_handler
475 # PARAMETERS: signal - string - signal arose from system
476 # RETURNS: nothing
477 # DESCRIPTION: handels tasks to be done befor signal becomes active
478 #===============================================================================
479 sub sig_int_handler {
480 my ($signal) = @_;
482 # if (defined($ldap_handle)) {
483 # $ldap_handle->disconnect;
484 # }
485 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
488 daemon_log("shutting down gosa-si-server", 1);
489 system("kill `ps -C gosa-si-server -o pid=`");
490 }
491 $SIG{INT} = \&sig_int_handler;
494 sub check_key_and_xml_validity {
495 my ($crypted_msg, $module_key, $session_id) = @_;
496 my $msg;
497 my $msg_hash;
498 my $error_string;
499 eval{
500 $msg = &decrypt_msg($crypted_msg, $module_key);
502 if ($msg =~ /<xml>/i){
503 $msg =~ s/\s+/ /g; # just for better daemon_log
504 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
505 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
507 ##############
508 # check header
509 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
510 my $header_l = $msg_hash->{'header'};
511 if( 1 > @{$header_l} ) { die 'empty header tag'; }
512 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
513 my $header = @{$header_l}[0];
514 if( 0 == length $header) { die 'empty string in header tag'; }
516 ##############
517 # check source
518 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
519 my $source_l = $msg_hash->{'source'};
520 if( 1 > @{$source_l} ) { die 'empty source tag'; }
521 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
522 my $source = @{$source_l}[0];
523 if( 0 == length $source) { die 'source error'; }
525 ##############
526 # check target
527 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
528 my $target_l = $msg_hash->{'target'};
529 if( 1 > @{$target_l} ) { die 'empty target tag'; }
530 }
531 };
532 if($@) {
533 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
534 $msg = undef;
535 $msg_hash = undef;
536 }
538 return ($msg, $msg_hash);
539 }
542 sub check_outgoing_xml_validity {
543 my ($msg, $session_id) = @_;
545 my $msg_hash;
546 eval{
547 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
549 ##############
550 # check header
551 my $header_l = $msg_hash->{'header'};
552 if( 1 != @{$header_l} ) {
553 die 'no or more than one headers specified';
554 }
555 my $header = @{$header_l}[0];
556 if( 0 == length $header) {
557 die 'header has length 0';
558 }
560 ##############
561 # check source
562 my $source_l = $msg_hash->{'source'};
563 if( 1 != @{$source_l} ) {
564 die 'no or more than 1 sources specified';
565 }
566 my $source = @{$source_l}[0];
567 if( 0 == length $source) {
568 die 'source has length 0';
569 }
570 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
571 $source =~ /^GOSA$/i ) {
572 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
573 }
575 ##############
576 # check target
577 my $target_l = $msg_hash->{'target'};
578 if( 0 == @{$target_l} ) {
579 die "no targets specified";
580 }
581 foreach my $target (@$target_l) {
582 if( 0 == length $target) {
583 die "target has length 0";
584 }
585 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
586 $target =~ /^GOSA$/i ||
587 $target =~ /^\*$/ ||
588 $target =~ /KNOWN_SERVER/i ||
589 $target =~ /JOBDB/i ||
590 $target =~ /^([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})$/i ){
591 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
592 }
593 }
594 };
595 if($@) {
596 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
597 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
598 $msg_hash = undef;
599 }
601 return ($msg_hash);
602 }
605 sub input_from_known_server {
606 my ($input, $remote_ip, $session_id) = @_ ;
607 my ($msg, $msg_hash, $module);
609 my $sql_statement= "SELECT * FROM known_server";
610 my $query_res = $known_server_db->select_dbentry( $sql_statement );
612 while( my ($hit_num, $hit) = each %{ $query_res } ) {
613 my $host_name = $hit->{hostname};
614 if( not $host_name =~ "^$remote_ip") {
615 next;
616 }
617 my $host_key = $hit->{hostkey};
618 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
619 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
621 # check if module can open msg envelope with module key
622 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
623 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
624 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
625 daemon_log("$@", 8);
626 next;
627 }
628 else {
629 $msg = $tmp_msg;
630 $msg_hash = $tmp_msg_hash;
631 $module = "ServerPackages";
632 last;
633 }
634 }
636 if( (!$msg) || (!$msg_hash) || (!$module) ) {
637 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
638 }
640 return ($msg, $msg_hash, $module);
641 }
644 sub input_from_known_client {
645 my ($input, $remote_ip, $session_id) = @_ ;
646 my ($msg, $msg_hash, $module);
648 my $sql_statement= "SELECT * FROM known_clients";
649 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
650 while( my ($hit_num, $hit) = each %{ $query_res } ) {
651 my $host_name = $hit->{hostname};
652 if( not $host_name =~ /^$remote_ip:\d*$/) {
653 next;
654 }
655 my $host_key = $hit->{hostkey};
656 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
657 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
659 # check if module can open msg envelope with module key
660 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
662 if( (!$msg) || (!$msg_hash) ) {
663 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
664 &daemon_log("$@", 8);
665 next;
666 }
667 else {
668 $module = "ClientPackages";
669 last;
670 }
671 }
673 if( (!$msg) || (!$msg_hash) || (!$module) ) {
674 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
675 }
677 return ($msg, $msg_hash, $module);
678 }
681 sub input_from_unknown_host {
682 no strict "refs";
683 my ($input, $session_id) = @_ ;
684 my ($msg, $msg_hash, $module);
685 my $error_string;
687 my %act_modules = %$known_modules;
689 while( my ($mod, $info) = each(%act_modules)) {
691 # check a key exists for this module
692 my $module_key = ${$mod."_key"};
693 if( not defined $module_key ) {
694 if( $mod eq 'ArpHandler' ) {
695 next;
696 }
697 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
698 next;
699 }
700 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
702 # check if module can open msg envelope with module key
703 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
704 if( (not defined $msg) || (not defined $msg_hash) ) {
705 next;
706 }
707 else {
708 $module = $mod;
709 last;
710 }
711 }
713 if( (!$msg) || (!$msg_hash) || (!$module)) {
714 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
715 }
717 return ($msg, $msg_hash, $module);
718 }
721 sub create_ciphering {
722 my ($passwd) = @_;
723 if((!defined($passwd)) || length($passwd)==0) {
724 $passwd = "";
725 }
726 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
727 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
728 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
729 $my_cipher->set_iv($iv);
730 return $my_cipher;
731 }
734 sub encrypt_msg {
735 my ($msg, $key) = @_;
736 my $my_cipher = &create_ciphering($key);
737 my $len;
738 {
739 use bytes;
740 $len= 16-length($msg)%16;
741 }
742 $msg = "\0"x($len).$msg;
743 $msg = $my_cipher->encrypt($msg);
744 chomp($msg = &encode_base64($msg));
745 # there are no newlines allowed inside msg
746 $msg=~ s/\n//g;
747 return $msg;
748 }
751 sub decrypt_msg {
753 my ($msg, $key) = @_ ;
754 $msg = &decode_base64($msg);
755 my $my_cipher = &create_ciphering($key);
756 $msg = $my_cipher->decrypt($msg);
757 $msg =~ s/\0*//g;
758 return $msg;
759 }
762 sub get_encrypt_key {
763 my ($target) = @_ ;
764 my $encrypt_key;
765 my $error = 0;
767 # target can be in known_server
768 if( not defined $encrypt_key ) {
769 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
770 my $query_res = $known_server_db->select_dbentry( $sql_statement );
771 while( my ($hit_num, $hit) = each %{ $query_res } ) {
772 my $host_name = $hit->{hostname};
773 if( $host_name ne $target ) {
774 next;
775 }
776 $encrypt_key = $hit->{hostkey};
777 last;
778 }
779 }
781 # target can be in known_client
782 if( not defined $encrypt_key ) {
783 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
784 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
785 while( my ($hit_num, $hit) = each %{ $query_res } ) {
786 my $host_name = $hit->{hostname};
787 if( $host_name ne $target ) {
788 next;
789 }
790 $encrypt_key = $hit->{hostkey};
791 last;
792 }
793 }
795 return $encrypt_key;
796 }
799 #=== FUNCTION ================================================================
800 # NAME: open_socket
801 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
802 # [PeerPort] string necessary if port not appended by PeerAddr
803 # RETURNS: socket IO::Socket::INET
804 # DESCRIPTION: open a socket to PeerAddr
805 #===============================================================================
806 sub open_socket {
807 my ($PeerAddr, $PeerPort) = @_ ;
808 if(defined($PeerPort)){
809 $PeerAddr = $PeerAddr.":".$PeerPort;
810 }
811 my $socket;
812 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
813 Porto => "tcp",
814 Type => SOCK_STREAM,
815 Timeout => 5,
816 );
817 if(not defined $socket) {
818 return;
819 }
820 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
821 return $socket;
822 }
825 sub get_local_ip_for_remote_ip {
826 my $remote_ip= shift;
827 my $result="0.0.0.0";
829 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
830 if($remote_ip eq "127.0.0.1") {
831 $result = "127.0.0.1";
832 } else {
833 my $PROC_NET_ROUTE= ('/proc/net/route');
835 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
836 or die "Could not open $PROC_NET_ROUTE";
838 my @ifs = <PROC_NET_ROUTE>;
840 close(PROC_NET_ROUTE);
842 # Eat header line
843 shift @ifs;
844 chomp @ifs;
845 foreach my $line(@ifs) {
846 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
847 my $destination;
848 my $mask;
849 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
850 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
851 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
852 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
853 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
854 # destination matches route, save mac and exit
855 $result= &get_ip($Iface);
856 last;
857 }
858 }
859 }
860 } else {
861 daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
862 }
863 return $result;
864 }
867 sub send_msg_to_target {
868 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
869 my $error = 0;
870 my $header;
871 my $timestamp = &get_time();
872 my $new_status;
873 my $act_status;
874 my ($sql_statement, $res);
876 if( $msg_header ) {
877 $header = "'$msg_header'-";
878 } else {
879 $header = "";
880 }
882 # Patch the source ip
883 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
884 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
885 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
886 }
888 # encrypt xml msg
889 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
891 # opensocket
892 my $socket = &open_socket($address);
893 if( !$socket ) {
894 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
895 $error++;
896 }
898 if( $error == 0 ) {
899 # send xml msg
900 print $socket $crypted_msg."\n";
902 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
903 daemon_log("$session_id DEBUG: message:\n$msg", 9);
905 }
907 # close socket in any case
908 if( $socket ) {
909 close $socket;
910 }
912 if( $error > 0 ) { $new_status = "down"; }
913 else { $new_status = $msg_header; }
916 # known_clients
917 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
918 $res = $known_clients_db->select_dbentry($sql_statement);
919 if( keys(%$res) == 1) {
920 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
921 if ($act_status eq "down" && $new_status eq "down") {
922 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
923 $res = $known_clients_db->del_dbentry($sql_statement);
924 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
925 } else {
926 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
927 $res = $known_clients_db->update_dbentry($sql_statement);
928 if($new_status eq "down"){
929 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
930 } else {
931 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
932 }
933 }
934 }
936 # known_server
937 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
938 $res = $known_server_db->select_dbentry($sql_statement);
939 if( keys(%$res) == 1) {
940 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
941 if ($act_status eq "down" && $new_status eq "down") {
942 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
943 $res = $known_server_db->del_dbentry($sql_statement);
944 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
945 }
946 else {
947 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
948 $res = $known_server_db->update_dbentry($sql_statement);
949 if($new_status eq "down"){
950 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
951 } else {
952 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
953 }
954 }
955 }
956 return $error;
957 }
960 sub update_jobdb_status_for_send_msgs {
961 my ($answer, $error) = @_;
962 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
963 my $jobdb_id = $1;
965 # sending msg faild
966 if( $error ) {
967 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
968 my $sql_statement = "UPDATE $job_queue_tn ".
969 "SET status='error', result='can not deliver msg, please consult log file' ".
970 "WHERE id=$jobdb_id";
971 my $res = $job_db->update_dbentry($sql_statement);
972 }
974 # sending msg was successful
975 } else {
976 my $sql_statement = "UPDATE $job_queue_tn ".
977 "SET status='done' ".
978 "WHERE id=$jobdb_id AND status='processed'";
979 my $res = $job_db->update_dbentry($sql_statement);
980 }
981 }
982 }
985 sub sig_handler {
986 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
987 daemon_log("0 INFO got signal '$signal'", 1);
988 $kernel->sig_handled();
989 return;
990 }
993 sub msg_to_decrypt {
994 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
995 my $session_id = $session->ID;
996 my ($msg, $msg_hash, $module);
997 my $error = 0;
999 # hole neue msg aus @msgs_to_decrypt
1000 my $next_msg = shift @msgs_to_decrypt;
1002 # entschlüssle sie
1004 # msg is from a new client or gosa
1005 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1006 # msg is from a gosa-si-server
1007 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1008 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1009 }
1010 # msg is from a gosa-si-client
1011 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1012 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1013 }
1014 # an error occurred
1015 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1016 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1017 # could not understand a msg from its server the client cause a re-registering process
1018 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1019 "' to cause a re-registering of the client if necessary", 3);
1020 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1021 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1022 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1023 my $host_name = $hit->{'hostname'};
1024 my $host_key = $hit->{'hostkey'};
1025 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1026 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1027 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1028 }
1029 $error++;
1030 }
1033 my $header;
1034 my $target;
1035 my $source;
1036 my $done = 0;
1037 my $sql;
1038 my $res;
1040 # check whether this message should be processed here
1041 if ($error == 0) {
1042 $header = @{$msg_hash->{'header'}}[0];
1043 $target = @{$msg_hash->{'target'}}[0];
1044 $source = @{$msg_hash->{'source'}}[0];
1045 my $not_found_in_known_clients_db = 0;
1046 my $not_found_in_known_server_db = 0;
1047 my $not_found_in_foreign_clients_db = 0;
1048 my $local_address;
1049 my ($target_ip, $target_port) = split(':', $target);
1050 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1051 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1052 } else {
1053 $local_address = $server_address;
1054 }
1056 # target and source is equal to GOSA -> process here
1057 if (not $done) {
1058 if ($target eq "GOSA" && $source eq "GOSA") {
1059 $done = 1;
1060 }
1061 }
1063 # target is own address without forward_to_gosa-tag -> process here
1064 if (not $done) {
1065 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1066 $done = 1;
1067 if ($source eq "GOSA") {
1068 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1069 }
1070 #print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1071 }
1072 }
1074 # target is a client address in known_clients -> process here
1075 if (not $done) {
1076 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1077 $res = $known_clients_db->select_dbentry($sql);
1078 if (keys(%$res) > 0) {
1079 $done = 1;
1080 my $hostname = $res->{1}->{'hostname'};
1081 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1082 #print STDERR "target is a client address in known_clients -> process here\n";
1083 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1084 if ($source eq "GOSA") {
1085 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1086 }
1088 } else {
1089 $not_found_in_known_clients_db = 1;
1090 }
1091 }
1093 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1094 if (not $done) {
1095 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1096 my $gosa_at;
1097 my $gosa_session_id;
1098 if (($target eq $local_address) && (defined $forward_to_gosa)){
1099 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1100 if ($gosa_at ne $local_address) {
1101 $done = 1;
1102 #print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n";
1103 }
1104 }
1105 }
1107 # if message should be processed here -> add message to incoming_db
1108 if ($done) {
1109 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1110 # so gosa-si-server knows how to process this kind of messages
1111 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1112 $module = "GosaPackages";
1113 }
1115 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1116 primkey=>[],
1117 headertag=>$header,
1118 targettag=>$target,
1119 xmlmessage=>&encode_base64($msg),
1120 timestamp=>&get_time,
1121 module=>$module,
1122 sessionid=>$session_id,
1123 } );
1124 }
1126 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1127 if (not $done) {
1128 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1129 my $gosa_at;
1130 my $gosa_session_id;
1131 if (($target eq $local_address) && (defined $forward_to_gosa)){
1132 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1133 if ($gosa_at eq $local_address) {
1134 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1135 if( defined $session_reference ) {
1136 $heap = $session_reference->get_heap();
1137 }
1138 if(exists $heap->{'client'}) {
1139 $msg = &encrypt_msg($msg, $GosaPackages_key);
1140 $heap->{'client'}->put($msg);
1141 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1142 }
1143 $done = 1;
1144 #print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1145 }
1146 }
1148 }
1150 # target is a client address in foreign_clients -> forward to registration server
1151 if (not $done) {
1152 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1153 $res = $foreign_clients_db->select_dbentry($sql);
1154 if (keys(%$res) > 0) {
1155 my $hostname = $res->{1}->{'hostname'};
1156 my ($host_ip, $host_port) = split(/:/, $hostname);
1157 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1158 my $regserver = $res->{1}->{'regserver'};
1159 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1160 my $res = $known_server_db->select_dbentry($sql);
1161 if (keys(%$res) > 0) {
1162 my $regserver_key = $res->{1}->{'hostkey'};
1163 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1164 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1165 if ($source eq "GOSA") {
1166 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1167 }
1168 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1169 }
1170 $done = 1;
1171 #print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1172 } else {
1173 $not_found_in_foreign_clients_db = 1;
1174 }
1175 }
1177 # target is a server address -> forward to server
1178 if (not $done) {
1179 $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1180 $res = $known_server_db->select_dbentry($sql);
1181 if (keys(%$res) > 0) {
1182 my $hostkey = $res->{1}->{'hostkey'};
1184 if ($source eq "GOSA") {
1185 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1186 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1188 }
1190 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1191 $done = 1;
1192 #print STDERR "target is a server address -> forward to server\n";
1193 } else {
1194 $not_found_in_known_server_db = 1;
1195 }
1196 }
1199 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1200 if ( $not_found_in_foreign_clients_db
1201 && $not_found_in_known_server_db
1202 && $not_found_in_known_clients_db) {
1203 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1204 primkey=>[],
1205 headertag=>$header,
1206 targettag=>$target,
1207 xmlmessage=>&encode_base64($msg),
1208 timestamp=>&get_time,
1209 module=>$module,
1210 sessionid=>$session_id,
1211 } );
1212 $done = 1;
1213 }
1216 if (not $done) {
1217 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1218 if ($source eq "GOSA") {
1219 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1220 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1222 my $session_reference = $kernel->ID_id_to_session($session_id);
1223 if( defined $session_reference ) {
1224 $heap = $session_reference->get_heap();
1225 }
1226 if(exists $heap->{'client'}) {
1227 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1228 $heap->{'client'}->put($error_msg);
1229 }
1230 }
1231 }
1233 }
1235 return;
1236 }
1239 sub next_task {
1240 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1241 my $running_task = POE::Wheel::Run->new(
1242 Program => sub { process_task($session, $heap, $task) },
1243 StdioFilter => POE::Filter::Reference->new(),
1244 StdoutEvent => "task_result",
1245 StderrEvent => "task_debug",
1246 CloseEvent => "task_done",
1247 );
1248 $heap->{task}->{ $running_task->ID } = $running_task;
1249 }
1251 sub handle_task_result {
1252 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1253 my $client_answer = $result->{'answer'};
1254 if( $client_answer =~ s/session_id=(\d+)$// ) {
1255 my $session_id = $1;
1256 if( defined $session_id ) {
1257 my $session_reference = $kernel->ID_id_to_session($session_id);
1258 if( defined $session_reference ) {
1259 $heap = $session_reference->get_heap();
1260 }
1261 }
1263 if(exists $heap->{'client'}) {
1264 $heap->{'client'}->put($client_answer);
1265 }
1266 }
1267 $kernel->sig(CHLD => "child_reap");
1268 }
1270 sub handle_task_debug {
1271 my $result = $_[ARG0];
1272 print STDERR "$result\n";
1273 }
1275 sub handle_task_done {
1276 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1277 delete $heap->{task}->{$task_id};
1278 }
1280 sub process_task {
1281 no strict "refs";
1282 #CHECK: Not @_[...]?
1283 my ($session, $heap, $task) = @_;
1284 my $error = 0;
1285 my $answer_l;
1286 my ($answer_header, @answer_target_l, $answer_source);
1287 my $client_answer = "";
1289 # prepare all variables needed to process message
1290 #my $msg = $task->{'xmlmessage'};
1291 my $msg = &decode_base64($task->{'xmlmessage'});
1292 my $incoming_id = $task->{'id'};
1293 my $module = $task->{'module'};
1294 my $header = $task->{'headertag'};
1295 my $session_id = $task->{'sessionid'};
1296 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1297 my $source = @{$msg_hash->{'source'}}[0];
1299 # set timestamp of incoming client uptodate, so client will not
1300 # be deleted from known_clients because of expiration
1301 my $act_time = &get_time();
1302 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1303 my $res = $known_clients_db->exec_statement($sql);
1305 ######################
1306 # process incoming msg
1307 if( $error == 0) {
1308 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1309 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1310 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1312 if ( 0 < @{$answer_l} ) {
1313 my $answer_str = join("\n", @{$answer_l});
1314 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1315 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1316 }
1317 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1318 } else {
1319 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1320 }
1322 }
1323 if( !$answer_l ) { $error++ };
1325 ########
1326 # answer
1327 if( $error == 0 ) {
1329 foreach my $answer ( @{$answer_l} ) {
1330 # check outgoing msg to xml validity
1331 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1332 if( not defined $answer_hash ) { next; }
1334 $answer_header = @{$answer_hash->{'header'}}[0];
1335 @answer_target_l = @{$answer_hash->{'target'}};
1336 $answer_source = @{$answer_hash->{'source'}}[0];
1338 # deliver msg to all targets
1339 foreach my $answer_target ( @answer_target_l ) {
1341 # targets of msg are all gosa-si-clients in known_clients_db
1342 if( $answer_target eq "*" ) {
1343 # answer is for all clients
1344 my $sql_statement= "SELECT * FROM known_clients";
1345 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1346 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1347 my $host_name = $hit->{hostname};
1348 my $host_key = $hit->{hostkey};
1349 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1350 &update_jobdb_status_for_send_msgs($answer, $error);
1351 }
1352 }
1354 # targets of msg are all gosa-si-server in known_server_db
1355 elsif( $answer_target eq "KNOWN_SERVER" ) {
1356 # answer is for all server in known_server
1357 my $sql_statement= "SELECT * FROM $known_server_tn";
1358 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1359 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1360 my $host_name = $hit->{hostname};
1361 my $host_key = $hit->{hostkey};
1362 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1363 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1364 &update_jobdb_status_for_send_msgs($answer, $error);
1365 }
1366 }
1368 # target of msg is GOsa
1369 elsif( $answer_target eq "GOSA" ) {
1370 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1371 my $add_on = "";
1372 if( defined $session_id ) {
1373 $add_on = ".session_id=$session_id";
1374 }
1375 # answer is for GOSA and has to returned to connected client
1376 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1377 $client_answer = $gosa_answer.$add_on;
1378 }
1380 # target of msg is job queue at this host
1381 elsif( $answer_target eq "JOBDB") {
1382 $answer =~ /<header>(\S+)<\/header>/;
1383 my $header;
1384 if( defined $1 ) { $header = $1; }
1385 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1386 &update_jobdb_status_for_send_msgs($answer, $error);
1387 }
1389 # target of msg is a mac address
1390 elsif( $answer_target =~ /^([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})$/i ) {
1391 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1392 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1393 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1394 my $found_ip_flag = 0;
1395 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1396 my $host_name = $hit->{hostname};
1397 my $host_key = $hit->{hostkey};
1398 $answer =~ s/$answer_target/$host_name/g;
1399 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1400 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1401 &update_jobdb_status_for_send_msgs($answer, $error);
1402 $found_ip_flag++ ;
1403 }
1404 if( $found_ip_flag == 0) {
1405 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1406 }
1408 # answer is for one specific host
1409 } else {
1410 # get encrypt_key
1411 my $encrypt_key = &get_encrypt_key($answer_target);
1412 if( not defined $encrypt_key ) {
1413 # unknown target
1414 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1415 next;
1416 }
1417 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1418 &update_jobdb_status_for_send_msgs($answer, $error);
1419 }
1420 }
1421 }
1422 }
1424 my $filter = POE::Filter::Reference->new();
1425 my %result = (
1426 status => "seems ok to me",
1427 answer => $client_answer,
1428 );
1430 my $output = $filter->put( [ \%result ] );
1431 print @$output;
1434 }
1436 sub session_start {
1437 my ($kernel) = $_[KERNEL];
1438 $global_kernel = $kernel;
1439 $kernel->yield('register_at_foreign_servers');
1440 $kernel->yield('create_fai_server_db', $fai_server_tn );
1441 $kernel->yield('create_fai_release_db', $fai_release_tn );
1442 $kernel->yield('watch_for_next_tasks');
1443 $kernel->sig(USR1 => "sig_handler");
1444 $kernel->sig(USR2 => "recreate_packages_db");
1445 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1446 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1447 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1448 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1449 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1450 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1451 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1452 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1455 }
1458 sub watch_for_done_jobs {
1459 #CHECK: $heap for what?
1460 my ($kernel,$heap) = @_[KERNEL, HEAP];
1462 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1463 my $res = $job_db->select_dbentry( $sql_statement );
1465 while( my ($id, $hit) = each %{$res} ) {
1466 my $jobdb_id = $hit->{id};
1467 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1468 my $res = $job_db->del_dbentry($sql_statement);
1469 }
1471 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1472 }
1475 sub watch_for_opsi_jobs {
1476 my ($kernel) = @_[KERNEL];
1478 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((headertag='opsi_install_client') AND (status='processing'))";
1479 my $res = $job_db->select_dbentry( $sql_statement );
1481 while (my ($id, $hit) = each %$res ) {
1483 # Ask OPSI for an update of the running jobs
1484 my $hostId = $hit->{'plainname'};
1485 print STDERR "\n\n$hostId\n";
1486 # my $result= {};
1487 #
1488 # # For hosts, only return the products that are or get installed
1489 # my $callobj;
1490 # $callobj = {
1491 # method => 'getProductStates_hash',
1492 # params => [ $hostId ],
1493 # id => 1,
1494 # };
1495 #
1496 # my $hres = $client->call($opsi_url, $callobj);
1497 # if (check_res($hres)){
1498 # my $htmp= $hres->result->{$hostId};
1499 #
1500 # # check state != not_installed or action == setup -> load and add
1501 # my $products= 0;
1502 # my $installed= 0;
1503 # my $error= 0;
1504 # foreach my $product (@{$htmp}){
1505 #
1506 # if ($product->{'installationStatus'} ne "not_installed" or
1507 # $product->{'actionRequest'} eq "setup"){
1508 #
1509 # # Increase number of products for this host
1510 # $products++;
1511 #
1512 # if ($product->{'installationStatus'} eq "failed"){
1513 # $result->{$product->{'productId'}}= "error";
1514 # $error++;
1515 # }
1516 # if ($product->{'installationStatus'} eq "installed"){
1517 # $result->{$product->{'productId'}}= "installed";
1518 # $installed++;
1519 # }
1520 # if ($product->{'installationStatus'} eq "installing"){
1521 # $result->{$product->{'productId'}}= "installing";
1522 # }
1523 # }
1524 # }
1525 #
1526 # # Estimate "rough" progress
1527 # $result->{'progress'}= int($installed * 100 / $products);
1528 #}
1529 #
1530 #return $result;
1531 #
1532 }
1534 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1535 }
1538 # if a job got an update or was modified anyway, send to all other si-server an update message
1539 # of this jobs
1540 sub watch_for_modified_jobs {
1541 my ($kernel,$heap) = @_[KERNEL, HEAP];
1543 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))";
1544 my $res = $job_db->select_dbentry( $sql_statement );
1546 # if db contains no jobs which should be update, do nothing
1547 if (keys %$res != 0) {
1549 if ($job_synchronization eq "true") {
1550 # make out of the db result a gosa-si message
1551 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1553 # update all other SI-server
1554 &inform_all_other_si_server($update_msg);
1555 }
1557 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1558 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1559 $res = $job_db->update_dbentry($sql_statement);
1560 }
1562 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1563 }
1566 sub watch_for_new_jobs {
1567 if($watch_for_new_jobs_in_progress == 0) {
1568 $watch_for_new_jobs_in_progress = 1;
1569 my ($kernel,$heap) = @_[KERNEL, HEAP];
1571 # check gosa job quaeue for jobs with executable timestamp
1572 my $timestamp = &get_time();
1573 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1574 my $res = $job_db->exec_statement( $sql_statement );
1576 # Merge all new jobs that would do the same actions
1577 my @drops;
1578 my $hits;
1579 foreach my $hit (reverse @{$res} ) {
1580 my $macaddress= lc @{$hit}[8];
1581 my $headertag= @{$hit}[5];
1582 if(
1583 defined($hits->{$macaddress}) &&
1584 defined($hits->{$macaddress}->{$headertag}) &&
1585 defined($hits->{$macaddress}->{$headertag}[0])
1586 ) {
1587 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1588 }
1589 $hits->{$macaddress}->{$headertag}= $hit;
1590 }
1592 # Delete new jobs with a matching job in state 'processing'
1593 foreach my $macaddress (keys %{$hits}) {
1594 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1595 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1596 if(defined($jobdb_id)) {
1597 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1598 my $res = $job_db->exec_statement( $sql_statement );
1599 foreach my $hit (@{$res}) {
1600 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1601 }
1602 } else {
1603 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1604 }
1605 }
1606 }
1608 # Commit deletion
1609 $job_db->exec_statementlist(\@drops);
1611 # Look for new jobs that could be executed
1612 foreach my $macaddress (keys %{$hits}) {
1614 # Look if there is an executing job
1615 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1616 my $res = $job_db->exec_statement( $sql_statement );
1618 # Skip new jobs for host if there is a processing job
1619 if(defined($res) and defined @{$res}[0]) {
1620 next;
1621 }
1623 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1624 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1625 if(defined($jobdb_id)) {
1626 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1628 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1629 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1630 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1632 # expect macaddress is unique!!!!!!
1633 my $target = $res_hash->{1}->{hostname};
1635 # change header
1636 $job_msg =~ s/<header>job_/<header>gosa_/;
1638 # add sqlite_id
1639 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1641 $job_msg =~ /<header>(\S+)<\/header>/;
1642 my $header = $1 ;
1643 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1645 # update status in job queue to 'processing'
1646 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1647 my $res = $job_db->update_dbentry($sql_statement);
1648 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1650 # We don't want parallel processing
1651 last;
1652 }
1653 }
1654 }
1656 $watch_for_new_jobs_in_progress = 0;
1657 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1658 }
1659 }
1662 sub watch_for_new_messages {
1663 my ($kernel,$heap) = @_[KERNEL, HEAP];
1664 my @coll_user_msg; # collection list of outgoing messages
1666 # check messaging_db for new incoming messages with executable timestamp
1667 my $timestamp = &get_time();
1668 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1669 my $res = $messaging_db->exec_statement( $sql_statement );
1670 foreach my $hit (@{$res}) {
1672 # create outgoing messages
1673 my $message_to = @{$hit}[3];
1674 # translate message_to to plain login name
1675 my @message_to_l = split(/,/, $message_to);
1676 my %receiver_h;
1677 foreach my $receiver (@message_to_l) {
1678 if ($receiver =~ /^u_([\s\S]*)$/) {
1679 $receiver_h{$1} = 0;
1680 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1681 my $group_name = $1;
1682 # fetch all group members from ldap and add them to receiver hash
1683 my $ldap_handle = &get_ldap_handle();
1684 if (defined $ldap_handle) {
1685 my $mesg = $ldap_handle->search(
1686 base => $ldap_base,
1687 scope => 'sub',
1688 attrs => ['memberUid'],
1689 filter => "cn=$group_name",
1690 );
1691 if ($mesg->count) {
1692 my @entries = $mesg->entries;
1693 foreach my $entry (@entries) {
1694 my @receivers= $entry->get_value("memberUid");
1695 foreach my $receiver (@receivers) {
1696 $receiver_h{$1} = 0;
1697 }
1698 }
1699 }
1700 # translating errors ?
1701 if ($mesg->code) {
1702 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1703 }
1704 # ldap handle error ?
1705 } else {
1706 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1707 }
1708 } else {
1709 my $sbjct = &encode_base64(@{$hit}[1]);
1710 my $msg = &encode_base64(@{$hit}[7]);
1711 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1712 }
1713 }
1714 my @receiver_l = keys(%receiver_h);
1716 my $message_id = @{$hit}[0];
1718 #add each outgoing msg to messaging_db
1719 my $receiver;
1720 foreach $receiver (@receiver_l) {
1721 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1722 "VALUES ('".
1723 $message_id."', '". # id
1724 @{$hit}[1]."', '". # subject
1725 @{$hit}[2]."', '". # message_from
1726 $receiver."', '". # message_to
1727 "none"."', '". # flag
1728 "out"."', '". # direction
1729 @{$hit}[6]."', '". # delivery_time
1730 @{$hit}[7]."', '". # message
1731 $timestamp."'". # timestamp
1732 ")";
1733 &daemon_log("M DEBUG: $sql_statement", 1);
1734 my $res = $messaging_db->exec_statement($sql_statement);
1735 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1736 }
1738 # set incoming message to flag d=deliverd
1739 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1740 &daemon_log("M DEBUG: $sql_statement", 7);
1741 $res = $messaging_db->update_dbentry($sql_statement);
1742 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1743 }
1745 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1746 return;
1747 }
1749 sub watch_for_delivery_messages {
1750 my ($kernel, $heap) = @_[KERNEL, HEAP];
1752 # select outgoing messages
1753 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1754 #&daemon_log("0 DEBUG: $sql", 7);
1755 my $res = $messaging_db->exec_statement( $sql_statement );
1757 # build out msg for each usr
1758 foreach my $hit (@{$res}) {
1759 my $receiver = @{$hit}[3];
1760 my $msg_id = @{$hit}[0];
1761 my $subject = @{$hit}[1];
1762 my $message = @{$hit}[7];
1764 # resolve usr -> host where usr is logged in
1765 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1766 #&daemon_log("0 DEBUG: $sql", 7);
1767 my $res = $login_users_db->exec_statement($sql);
1769 # reciver is logged in nowhere
1770 if (not ref(@$res[0]) eq "ARRAY") { next; }
1772 my $send_succeed = 0;
1773 foreach my $hit (@$res) {
1774 my $receiver_host = @$hit[0];
1775 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1777 # fetch key to encrypt msg propperly for usr/host
1778 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1779 &daemon_log("0 DEBUG: $sql", 7);
1780 my $res = $known_clients_db->exec_statement($sql);
1782 # host is already down
1783 if (not ref(@$res[0]) eq "ARRAY") { next; }
1785 # host is on
1786 my $receiver_key = @{@{$res}[0]}[2];
1787 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1788 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1789 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1790 if ($error == 0 ) {
1791 $send_succeed++ ;
1792 }
1793 }
1795 if ($send_succeed) {
1796 # set outgoing msg at db to deliverd
1797 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1798 &daemon_log("0 DEBUG: $sql", 7);
1799 my $res = $messaging_db->exec_statement($sql);
1800 }
1801 }
1803 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1804 return;
1805 }
1808 sub watch_for_done_messages {
1809 my ($kernel,$heap) = @_[KERNEL, HEAP];
1811 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1812 #&daemon_log("0 DEBUG: $sql", 7);
1813 my $res = $messaging_db->exec_statement($sql);
1815 foreach my $hit (@{$res}) {
1816 my $msg_id = @{$hit}[0];
1818 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1819 #&daemon_log("0 DEBUG: $sql", 7);
1820 my $res = $messaging_db->exec_statement($sql);
1822 # not all usr msgs have been seen till now
1823 if ( ref(@$res[0]) eq "ARRAY") { next; }
1825 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1826 #&daemon_log("0 DEBUG: $sql", 7);
1827 $res = $messaging_db->exec_statement($sql);
1829 }
1831 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1832 return;
1833 }
1836 sub watch_for_old_known_clients {
1837 my ($kernel,$heap) = @_[KERNEL, HEAP];
1839 my $sql_statement = "SELECT * FROM $known_clients_tn";
1840 my $res = $known_clients_db->select_dbentry( $sql_statement );
1842 my $act_time = int(&get_time());
1844 while ( my ($hit_num, $hit) = each %$res) {
1845 my $expired_timestamp = int($hit->{'timestamp'});
1846 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1847 my $dt = DateTime->new( year => $1,
1848 month => $2,
1849 day => $3,
1850 hour => $4,
1851 minute => $5,
1852 second => $6,
1853 );
1855 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1856 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1857 if ($act_time > $expired_timestamp) {
1858 my $hostname = $hit->{'hostname'};
1859 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1860 my $del_res = $known_clients_db->exec_statement($del_sql);
1862 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1863 }
1865 }
1867 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1868 }
1871 sub watch_for_next_tasks {
1872 my ($kernel,$heap) = @_[KERNEL, HEAP];
1874 my $sql = "SELECT * FROM $incoming_tn";
1875 my $res = $incoming_db->select_dbentry($sql);
1877 while ( my ($hit_num, $hit) = each %$res) {
1878 my $headertag = $hit->{'headertag'};
1879 if ($headertag =~ /^answer_(\d+)/) {
1880 # do not start processing, this message is for a still running POE::Wheel
1881 next;
1882 }
1883 my $message_id = $hit->{'id'};
1884 $kernel->yield('next_task', $hit);
1886 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1887 my $res = $incoming_db->exec_statement($sql);
1888 }
1890 $kernel->delay_set('watch_for_next_tasks', 0.1);
1891 }
1894 sub get_ldap_handle {
1895 my ($session_id) = @_;
1896 my $heap;
1897 my $ldap_handle;
1899 if (not defined $session_id ) { $session_id = 0 };
1900 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1902 if ($session_id == 0) {
1903 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1904 $ldap_handle = Net::LDAP->new( $ldap_uri );
1905 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
1907 } else {
1908 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1909 if( defined $session_reference ) {
1910 $heap = $session_reference->get_heap();
1911 }
1913 if (not defined $heap) {
1914 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1915 return;
1916 }
1918 # TODO: This "if" is nonsense, because it doesn't prove that the
1919 # used handle is still valid - or if we've to reconnect...
1920 #if (not exists $heap->{ldap_handle}) {
1921 $ldap_handle = Net::LDAP->new( $ldap_uri );
1922 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
1923 $heap->{ldap_handle} = $ldap_handle;
1924 #}
1925 }
1926 return $ldap_handle;
1927 }
1930 sub change_fai_state {
1931 my ($st, $targets, $session_id) = @_;
1932 $session_id = 0 if not defined $session_id;
1933 # Set FAI state to localboot
1934 my %mapActions= (
1935 reboot => '',
1936 update => 'softupdate',
1937 localboot => 'localboot',
1938 reinstall => 'install',
1939 rescan => '',
1940 wake => '',
1941 memcheck => 'memcheck',
1942 sysinfo => 'sysinfo',
1943 install => 'install',
1944 );
1946 # Return if this is unknown
1947 if (!exists $mapActions{ $st }){
1948 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1949 return;
1950 }
1952 my $state= $mapActions{ $st };
1954 my $ldap_handle = &get_ldap_handle($session_id);
1955 if( defined($ldap_handle) ) {
1957 # Build search filter for hosts
1958 my $search= "(&(objectClass=GOhard)";
1959 foreach (@{$targets}){
1960 $search.= "(macAddress=$_)";
1961 }
1962 $search.= ")";
1964 # If there's any host inside of the search string, procress them
1965 if (!($search =~ /macAddress/)){
1966 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1967 return;
1968 }
1970 # Perform search for Unit Tag
1971 my $mesg = $ldap_handle->search(
1972 base => $ldap_base,
1973 scope => 'sub',
1974 attrs => ['dn', 'FAIstate', 'objectClass'],
1975 filter => "$search"
1976 );
1978 if ($mesg->count) {
1979 my @entries = $mesg->entries;
1980 if (0 == @entries) {
1981 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
1982 }
1984 foreach my $entry (@entries) {
1985 # Only modify entry if it is not set to '$state'
1986 if ($entry->get_value("FAIstate") ne "$state"){
1987 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1988 my $result;
1989 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1990 if (exists $tmp{'FAIobject'}){
1991 if ($state eq ''){
1992 $result= $ldap_handle->modify($entry->dn, changes => [
1993 delete => [ FAIstate => [] ] ]);
1994 } else {
1995 $result= $ldap_handle->modify($entry->dn, changes => [
1996 replace => [ FAIstate => $state ] ]);
1997 }
1998 } elsif ($state ne ''){
1999 $result= $ldap_handle->modify($entry->dn, changes => [
2000 add => [ objectClass => 'FAIobject' ],
2001 add => [ FAIstate => $state ] ]);
2002 }
2004 # Errors?
2005 if ($result->code){
2006 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2007 }
2008 } else {
2009 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2010 }
2011 }
2012 } else {
2013 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2014 }
2016 # if no ldap handle defined
2017 } else {
2018 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2019 }
2021 return;
2022 }
2025 sub change_goto_state {
2026 my ($st, $targets, $session_id) = @_;
2027 $session_id = 0 if not defined $session_id;
2029 # Switch on or off?
2030 my $state= $st eq 'active' ? 'active': 'locked';
2032 my $ldap_handle = &get_ldap_handle($session_id);
2033 if( defined($ldap_handle) ) {
2035 # Build search filter for hosts
2036 my $search= "(&(objectClass=GOhard)";
2037 foreach (@{$targets}){
2038 $search.= "(macAddress=$_)";
2039 }
2040 $search.= ")";
2042 # If there's any host inside of the search string, procress them
2043 if (!($search =~ /macAddress/)){
2044 return;
2045 }
2047 # Perform search for Unit Tag
2048 my $mesg = $ldap_handle->search(
2049 base => $ldap_base,
2050 scope => 'sub',
2051 attrs => ['dn', 'gotoMode'],
2052 filter => "$search"
2053 );
2055 if ($mesg->count) {
2056 my @entries = $mesg->entries;
2057 foreach my $entry (@entries) {
2059 # Only modify entry if it is not set to '$state'
2060 if ($entry->get_value("gotoMode") ne $state){
2062 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2063 my $result;
2064 $result= $ldap_handle->modify($entry->dn, changes => [
2065 replace => [ gotoMode => $state ] ]);
2067 # Errors?
2068 if ($result->code){
2069 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2070 }
2072 }
2073 }
2074 } else {
2075 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2076 }
2078 }
2079 }
2082 sub run_recreate_packages_db {
2083 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2084 my $session_id = $session->ID;
2085 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2086 $kernel->yield('create_fai_release_db', $fai_release_tn);
2087 $kernel->yield('create_fai_server_db', $fai_server_tn);
2088 return;
2089 }
2092 sub run_create_fai_server_db {
2093 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2094 my $session_id = $session->ID;
2095 my $task = POE::Wheel::Run->new(
2096 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2097 StdoutEvent => "session_run_result",
2098 StderrEvent => "session_run_debug",
2099 CloseEvent => "session_run_done",
2100 );
2102 $heap->{task}->{ $task->ID } = $task;
2103 return;
2104 }
2107 sub create_fai_server_db {
2108 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2109 my $result;
2111 if (not defined $session_id) { $session_id = 0; }
2112 my $ldap_handle = &get_ldap_handle();
2113 if(defined($ldap_handle)) {
2114 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2115 my $mesg= $ldap_handle->search(
2116 base => $ldap_base,
2117 scope => 'sub',
2118 attrs => ['FAIrepository', 'gosaUnitTag'],
2119 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2120 );
2121 if($mesg->{'resultCode'} == 0 &&
2122 $mesg->count != 0) {
2123 foreach my $entry (@{$mesg->{entries}}) {
2124 if($entry->exists('FAIrepository')) {
2125 # Add an entry for each Repository configured for server
2126 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2127 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2128 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2129 $result= $fai_server_db->add_dbentry( {
2130 table => $table_name,
2131 primkey => ['server', 'release', 'tag'],
2132 server => $tmp_url,
2133 release => $tmp_release,
2134 sections => $tmp_sections,
2135 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2136 } );
2137 }
2138 }
2139 }
2140 }
2141 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2143 # TODO: Find a way to post the 'create_packages_list_db' event
2144 if(not defined($dont_create_packages_list)) {
2145 &create_packages_list_db(undef, undef, $session_id);
2146 }
2147 }
2149 $ldap_handle->disconnect;
2150 return $result;
2151 }
2154 sub run_create_fai_release_db {
2155 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2156 my $session_id = $session->ID;
2157 my $task = POE::Wheel::Run->new(
2158 Program => sub { &create_fai_release_db($table_name, $session_id) },
2159 StdoutEvent => "session_run_result",
2160 StderrEvent => "session_run_debug",
2161 CloseEvent => "session_run_done",
2162 );
2164 $heap->{task}->{ $task->ID } = $task;
2165 return;
2166 }
2169 sub create_fai_release_db {
2170 my ($table_name, $session_id) = @_;
2171 my $result;
2173 # used for logging
2174 if (not defined $session_id) { $session_id = 0; }
2176 my $ldap_handle = &get_ldap_handle();
2177 if(defined($ldap_handle)) {
2178 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2179 my $mesg= $ldap_handle->search(
2180 base => $ldap_base,
2181 scope => 'sub',
2182 attrs => [],
2183 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2184 );
2185 if($mesg->{'resultCode'} == 0 &&
2186 $mesg->count != 0) {
2187 # Walk through all possible FAI container ou's
2188 my @sql_list;
2189 my $timestamp= &get_time();
2190 foreach my $ou (@{$mesg->{entries}}) {
2191 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2192 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2193 my @tmp_array=get_fai_release_entries($tmp_classes);
2194 if(@tmp_array) {
2195 foreach my $entry (@tmp_array) {
2196 if(defined($entry) && ref($entry) eq 'HASH') {
2197 my $sql=
2198 "INSERT INTO $table_name "
2199 ."(timestamp, release, class, type, state) VALUES ("
2200 .$timestamp.","
2201 ."'".$entry->{'release'}."',"
2202 ."'".$entry->{'class'}."',"
2203 ."'".$entry->{'type'}."',"
2204 ."'".$entry->{'state'}."')";
2205 push @sql_list, $sql;
2206 }
2207 }
2208 }
2209 }
2210 }
2212 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2213 if(@sql_list) {
2214 unshift @sql_list, "VACUUM";
2215 unshift @sql_list, "DELETE FROM $table_name";
2216 $fai_release_db->exec_statementlist(\@sql_list);
2217 }
2218 daemon_log("$session_id DEBUG: Done with inserting",7);
2219 }
2220 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2221 }
2222 $ldap_handle->disconnect;
2223 return $result;
2224 }
2226 sub get_fai_types {
2227 my $tmp_classes = shift || return undef;
2228 my @result;
2230 foreach my $type(keys %{$tmp_classes}) {
2231 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2232 my $entry = {
2233 type => $type,
2234 state => $tmp_classes->{$type}[0],
2235 };
2236 push @result, $entry;
2237 }
2238 }
2240 return @result;
2241 }
2243 sub get_fai_state {
2244 my $result = "";
2245 my $tmp_classes = shift || return $result;
2247 foreach my $type(keys %{$tmp_classes}) {
2248 if(defined($tmp_classes->{$type}[0])) {
2249 $result = $tmp_classes->{$type}[0];
2251 # State is equal for all types in class
2252 last;
2253 }
2254 }
2256 return $result;
2257 }
2259 sub resolve_fai_classes {
2260 my ($fai_base, $ldap_handle, $session_id) = @_;
2261 if (not defined $session_id) { $session_id = 0; }
2262 my $result;
2263 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2264 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2265 my $fai_classes;
2267 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2268 my $mesg= $ldap_handle->search(
2269 base => $fai_base,
2270 scope => 'sub',
2271 attrs => ['cn','objectClass','FAIstate'],
2272 filter => $fai_filter,
2273 );
2274 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2276 if($mesg->{'resultCode'} == 0 &&
2277 $mesg->count != 0) {
2278 foreach my $entry (@{$mesg->{entries}}) {
2279 if($entry->exists('cn')) {
2280 my $tmp_dn= $entry->dn();
2282 # Skip classname and ou dn parts for class
2283 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2285 # Skip classes without releases
2286 if((!defined($tmp_release)) || length($tmp_release)==0) {
2287 next;
2288 }
2290 my $tmp_cn= $entry->get_value('cn');
2291 my $tmp_state= $entry->get_value('FAIstate');
2293 my $tmp_type;
2294 # Get FAI type
2295 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2296 if(grep $_ eq $oclass, @possible_fai_classes) {
2297 $tmp_type= $oclass;
2298 last;
2299 }
2300 }
2302 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2303 # A Subrelease
2304 my @sub_releases = split(/,/, $tmp_release);
2306 # Walk through subreleases and build hash tree
2307 my $hash;
2308 while(my $tmp_sub_release = pop @sub_releases) {
2309 $hash .= "\{'$tmp_sub_release'\}->";
2310 }
2311 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2312 } else {
2313 # A branch, no subrelease
2314 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2315 }
2316 } elsif (!$entry->exists('cn')) {
2317 my $tmp_dn= $entry->dn();
2318 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2320 # Skip classes without releases
2321 if((!defined($tmp_release)) || length($tmp_release)==0) {
2322 next;
2323 }
2325 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2326 # A Subrelease
2327 my @sub_releases= split(/,/, $tmp_release);
2329 # Walk through subreleases and build hash tree
2330 my $hash;
2331 while(my $tmp_sub_release = pop @sub_releases) {
2332 $hash .= "\{'$tmp_sub_release'\}->";
2333 }
2334 # Remove the last two characters
2335 chop($hash);
2336 chop($hash);
2338 eval('$fai_classes->'.$hash.'= {}');
2339 } else {
2340 # A branch, no subrelease
2341 if(!exists($fai_classes->{$tmp_release})) {
2342 $fai_classes->{$tmp_release} = {};
2343 }
2344 }
2345 }
2346 }
2348 # The hash is complete, now we can honor the copy-on-write based missing entries
2349 foreach my $release (keys %$fai_classes) {
2350 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2351 }
2352 }
2353 return $result;
2354 }
2356 sub apply_fai_inheritance {
2357 my $fai_classes = shift || return {};
2358 my $tmp_classes;
2360 # Get the classes from the branch
2361 foreach my $class (keys %{$fai_classes}) {
2362 # Skip subreleases
2363 if($class =~ /^ou=.*$/) {
2364 next;
2365 } else {
2366 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2367 }
2368 }
2370 # Apply to each subrelease
2371 foreach my $subrelease (keys %{$fai_classes}) {
2372 if($subrelease =~ /ou=/) {
2373 foreach my $tmp_class (keys %{$tmp_classes}) {
2374 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2375 $fai_classes->{$subrelease}->{$tmp_class} =
2376 deep_copy($tmp_classes->{$tmp_class});
2377 } else {
2378 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2379 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2380 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2381 deep_copy($tmp_classes->{$tmp_class}->{$type});
2382 }
2383 }
2384 }
2385 }
2386 }
2387 }
2389 # Find subreleases in deeper levels
2390 foreach my $subrelease (keys %{$fai_classes}) {
2391 if($subrelease =~ /ou=/) {
2392 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2393 if($subsubrelease =~ /ou=/) {
2394 apply_fai_inheritance($fai_classes->{$subrelease});
2395 }
2396 }
2397 }
2398 }
2400 return $fai_classes;
2401 }
2403 sub get_fai_release_entries {
2404 my $tmp_classes = shift || return;
2405 my $parent = shift || "";
2406 my @result = shift || ();
2408 foreach my $entry (keys %{$tmp_classes}) {
2409 if(defined($entry)) {
2410 if($entry =~ /^ou=.*$/) {
2411 my $release_name = $entry;
2412 $release_name =~ s/ou=//g;
2413 if(length($parent)>0) {
2414 $release_name = $parent."/".$release_name;
2415 }
2416 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2417 foreach my $bufentry(@bufentries) {
2418 push @result, $bufentry;
2419 }
2420 } else {
2421 my @types = get_fai_types($tmp_classes->{$entry});
2422 foreach my $type (@types) {
2423 push @result,
2424 {
2425 'class' => $entry,
2426 'type' => $type->{'type'},
2427 'release' => $parent,
2428 'state' => $type->{'state'},
2429 };
2430 }
2431 }
2432 }
2433 }
2435 return @result;
2436 }
2438 sub deep_copy {
2439 my $this = shift;
2440 if (not ref $this) {
2441 $this;
2442 } elsif (ref $this eq "ARRAY") {
2443 [map deep_copy($_), @$this];
2444 } elsif (ref $this eq "HASH") {
2445 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2446 } else { die "what type is $_?" }
2447 }
2450 sub session_run_result {
2451 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2452 $kernel->sig(CHLD => "child_reap");
2453 }
2455 sub session_run_debug {
2456 my $result = $_[ARG0];
2457 print STDERR "$result\n";
2458 }
2460 sub session_run_done {
2461 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2462 delete $heap->{task}->{$task_id};
2463 }
2466 sub create_sources_list {
2467 my $session_id = shift;
2468 my $ldap_handle = &main::get_ldap_handle;
2469 my $result="/tmp/gosa_si_tmp_sources_list";
2471 # Remove old file
2472 if(stat($result)) {
2473 unlink($result);
2474 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2475 }
2477 my $fh;
2478 open($fh, ">$result");
2479 if (not defined $fh) {
2480 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2481 return undef;
2482 }
2483 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2484 my $mesg=$ldap_handle->search(
2485 base => $main::ldap_server_dn,
2486 scope => 'base',
2487 attrs => 'FAIrepository',
2488 filter => 'objectClass=FAIrepositoryServer'
2489 );
2490 if($mesg->count) {
2491 foreach my $entry(@{$mesg->{'entries'}}) {
2492 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2493 my ($server, $tag, $release, $sections)= split /\|/, $value;
2494 my $line = "deb $server $release";
2495 $sections =~ s/,/ /g;
2496 $line.= " $sections";
2497 print $fh $line."\n";
2498 }
2499 }
2500 }
2501 } else {
2502 if (defined $main::ldap_server_dn){
2503 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2504 } else {
2505 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2506 }
2507 }
2508 close($fh);
2510 return $result;
2511 }
2514 sub run_create_packages_list_db {
2515 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2516 my $session_id = $session->ID;
2518 my $task = POE::Wheel::Run->new(
2519 Priority => +20,
2520 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2521 StdoutEvent => "session_run_result",
2522 StderrEvent => "session_run_debug",
2523 CloseEvent => "session_run_done",
2524 );
2525 $heap->{task}->{ $task->ID } = $task;
2526 }
2529 sub create_packages_list_db {
2530 my ($ldap_handle, $sources_file, $session_id) = @_;
2532 # it should not be possible to trigger a recreation of packages_list_db
2533 # while packages_list_db is under construction, so set flag packages_list_under_construction
2534 # which is tested befor recreation can be started
2535 if (-r $packages_list_under_construction) {
2536 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2537 return;
2538 } else {
2539 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2540 # set packages_list_under_construction to true
2541 system("touch $packages_list_under_construction");
2542 @packages_list_statements=();
2543 }
2545 if (not defined $session_id) { $session_id = 0; }
2546 if (not defined $ldap_handle) {
2547 $ldap_handle= &get_ldap_handle();
2549 if (not defined $ldap_handle) {
2550 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2551 unlink($packages_list_under_construction);
2552 return;
2553 }
2554 }
2555 if (not defined $sources_file) {
2556 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2557 $sources_file = &create_sources_list($session_id);
2558 }
2560 if (not defined $sources_file) {
2561 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2562 unlink($packages_list_under_construction);
2563 return;
2564 }
2566 my $line;
2568 open(CONFIG, "<$sources_file") or do {
2569 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2570 unlink($packages_list_under_construction);
2571 return;
2572 };
2574 # Read lines
2575 while ($line = <CONFIG>){
2576 # Unify
2577 chop($line);
2578 $line =~ s/^\s+//;
2579 $line =~ s/^\s+/ /;
2581 # Strip comments
2582 $line =~ s/#.*$//g;
2584 # Skip empty lines
2585 if ($line =~ /^\s*$/){
2586 next;
2587 }
2589 # Interpret deb line
2590 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2591 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2592 my $section;
2593 foreach $section (split(' ', $sections)){
2594 &parse_package_info( $baseurl, $dist, $section, $session_id );
2595 }
2596 }
2597 }
2599 close (CONFIG);
2602 find(\&cleanup_and_extract, keys( %repo_dirs ));
2603 &main::strip_packages_list_statements();
2604 unshift @packages_list_statements, "VACUUM";
2605 $packages_list_db->exec_statementlist(\@packages_list_statements);
2606 unlink($packages_list_under_construction);
2607 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2608 return;
2609 }
2611 # This function should do some intensive task to minimize the db-traffic
2612 sub strip_packages_list_statements {
2613 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2614 my @new_statement_list=();
2615 my $hash;
2616 my $insert_hash;
2617 my $update_hash;
2618 my $delete_hash;
2619 my $local_timestamp=get_time();
2621 foreach my $existing_entry (@existing_entries) {
2622 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2623 }
2625 foreach my $statement (@packages_list_statements) {
2626 if($statement =~ /^INSERT/i) {
2627 # Assign the values from the insert statement
2628 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2629 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2630 if(exists($hash->{$distribution}->{$package}->{$version})) {
2631 # If section or description has changed, update the DB
2632 if(
2633 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2634 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2635 ) {
2636 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2637 }
2638 } else {
2639 # Insert a non-existing entry to db
2640 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2641 }
2642 } elsif ($statement =~ /^UPDATE/i) {
2643 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2644 /^update\s+?$main::packages_list_tn\s+?set\s+?template\s*?=\s*?'(.*?)'\s+?where\s+?package\s*?=\s*?'(.*?)'\s+?and\s+?version\s*?=\s*?'(.*?)'\s*?;$/si;
2645 foreach my $distribution (keys %{$hash}) {
2646 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2647 # update the insertion hash to execute only one query per package (insert instead insert+update)
2648 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2649 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2650 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2651 my $section;
2652 my $description;
2653 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2654 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2655 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2656 }
2657 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2658 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2659 }
2660 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2661 }
2662 }
2663 }
2664 }
2665 }
2667 # TODO: Check for orphaned entries
2669 # unroll the insert_hash
2670 foreach my $distribution (keys %{$insert_hash}) {
2671 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2672 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2673 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2674 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2675 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2676 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2677 ."'$local_timestamp')";
2678 }
2679 }
2680 }
2682 # unroll the update hash
2683 foreach my $distribution (keys %{$update_hash}) {
2684 foreach my $package (keys %{$update_hash->{$distribution}}) {
2685 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2686 my $set = "";
2687 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2688 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2689 }
2690 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2691 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2692 }
2693 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2694 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2695 }
2696 if(defined($set) and length($set) > 0) {
2697 $set .= "timestamp = '$local_timestamp'";
2698 } else {
2699 next;
2700 }
2701 push @new_statement_list,
2702 "UPDATE $main::packages_list_tn SET $set WHERE"
2703 ." distribution = '$distribution'"
2704 ." AND package = '$package'"
2705 ." AND version = '$version'";
2706 }
2707 }
2708 }
2710 @packages_list_statements = @new_statement_list;
2711 }
2714 sub parse_package_info {
2715 my ($baseurl, $dist, $section, $session_id)= @_;
2716 my ($package);
2717 if (not defined $session_id) { $session_id = 0; }
2718 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2719 $repo_dirs{ "${repo_path}/pool" } = 1;
2721 foreach $package ("Packages.gz"){
2722 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2723 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2724 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2725 }
2727 }
2730 sub get_package {
2731 my ($url, $dest, $session_id)= @_;
2732 if (not defined $session_id) { $session_id = 0; }
2734 my $tpath = dirname($dest);
2735 -d "$tpath" || mkpath "$tpath";
2737 # This is ugly, but I've no time to take a look at "how it works in perl"
2738 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2739 system("gunzip -cd '$dest' > '$dest.in'");
2740 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2741 unlink($dest);
2742 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2743 } else {
2744 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2745 }
2746 return 0;
2747 }
2750 sub parse_package {
2751 my ($path, $dist, $srv_path, $session_id)= @_;
2752 if (not defined $session_id) { $session_id = 0;}
2753 my ($package, $version, $section, $description);
2754 my $PACKAGES;
2755 my $timestamp = &get_time();
2757 if(not stat("$path.in")) {
2758 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2759 return;
2760 }
2762 open($PACKAGES, "<$path.in");
2763 if(not defined($PACKAGES)) {
2764 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2765 return;
2766 }
2768 # Read lines
2769 while (<$PACKAGES>){
2770 my $line = $_;
2771 # Unify
2772 chop($line);
2774 # Use empty lines as a trigger
2775 if ($line =~ /^\s*$/){
2776 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2777 push(@packages_list_statements, $sql);
2778 $package = "none";
2779 $version = "none";
2780 $section = "none";
2781 $description = "none";
2782 next;
2783 }
2785 # Trigger for package name
2786 if ($line =~ /^Package:\s/){
2787 ($package)= ($line =~ /^Package: (.*)$/);
2788 next;
2789 }
2791 # Trigger for version
2792 if ($line =~ /^Version:\s/){
2793 ($version)= ($line =~ /^Version: (.*)$/);
2794 next;
2795 }
2797 # Trigger for description
2798 if ($line =~ /^Description:\s/){
2799 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2800 next;
2801 }
2803 # Trigger for section
2804 if ($line =~ /^Section:\s/){
2805 ($section)= ($line =~ /^Section: (.*)$/);
2806 next;
2807 }
2809 # Trigger for filename
2810 if ($line =~ /^Filename:\s/){
2811 my ($filename) = ($line =~ /^Filename: (.*)$/);
2812 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2813 next;
2814 }
2815 }
2817 close( $PACKAGES );
2818 unlink( "$path.in" );
2819 }
2822 sub store_fileinfo {
2823 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2825 my %fileinfo = (
2826 'package' => $package,
2827 'dist' => $dist,
2828 'version' => $vers,
2829 );
2831 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2832 }
2835 sub cleanup_and_extract {
2836 my $fileinfo = $repo_files{ $File::Find::name };
2838 if( defined $fileinfo ) {
2840 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2841 my $sql;
2842 my $package = $fileinfo->{ 'package' };
2843 my $newver = $fileinfo->{ 'version' };
2845 mkpath($dir);
2846 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2848 if( -f "$dir/DEBIAN/templates" ) {
2850 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
2852 my $tmpl= "";
2853 {
2854 local $/=undef;
2855 open FILE, "$dir/DEBIAN/templates";
2856 $tmpl = &encode_base64(<FILE>);
2857 close FILE;
2858 }
2859 rmtree("$dir/DEBIAN/templates");
2861 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2862 push @packages_list_statements, $sql;
2863 }
2864 }
2866 return;
2867 }
2870 sub register_at_foreign_servers {
2871 my ($kernel) = $_[KERNEL];
2873 # hole alle bekannten server aus known_server_db
2874 my $server_sql = "SELECT * FROM $known_server_tn";
2875 my $server_res = $known_server_db->exec_statement($server_sql);
2877 # no entries in known_server_db
2878 if (not ref(@$server_res[0]) eq "ARRAY") {
2879 # TODO
2880 }
2882 # detect already connected clients
2883 my $client_sql = "SELECT * FROM $known_clients_tn";
2884 my $client_res = $known_clients_db->exec_statement($client_sql);
2886 # send my server details to all other gosa-si-server within the network
2887 foreach my $hit (@$server_res) {
2888 my $hostname = @$hit[0];
2889 my $hostkey = &create_passwd;
2891 # add already connected clients to registration message
2892 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2893 &add_content2xml_hash($myhash, 'key', $hostkey);
2894 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2896 # build registration message and send it
2897 my $foreign_server_msg = &create_xml_string($myhash);
2898 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2899 }
2901 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2902 return;
2903 }
2906 #==== MAIN = main ==============================================================
2907 # parse commandline options
2908 Getopt::Long::Configure( "bundling" );
2909 GetOptions("h|help" => \&usage,
2910 "c|config=s" => \$cfg_file,
2911 "f|foreground" => \$foreground,
2912 "v|verbose+" => \$verbose,
2913 "no-arp+" => \$no_arp,
2914 );
2916 # read and set config parameters
2917 &check_cmdline_param ;
2918 &read_configfile($cfg_file, %cfg_defaults);
2919 &check_pid;
2921 $SIG{CHLD} = 'IGNORE';
2923 # forward error messages to logfile
2924 if( ! $foreground ) {
2925 open( STDIN, '+>/dev/null' );
2926 open( STDOUT, '+>&STDIN' );
2927 open( STDERR, '+>&STDIN' );
2928 }
2930 # Just fork, if we are not in foreground mode
2931 if( ! $foreground ) {
2932 chdir '/' or die "Can't chdir to /: $!";
2933 $pid = fork;
2934 setsid or die "Can't start a new session: $!";
2935 umask 0;
2936 } else {
2937 $pid = $$;
2938 }
2940 # Do something useful - put our PID into the pid_file
2941 if( 0 != $pid ) {
2942 open( LOCK_FILE, ">$pid_file" );
2943 print LOCK_FILE "$pid\n";
2944 close( LOCK_FILE );
2945 if( !$foreground ) {
2946 exit( 0 )
2947 };
2948 }
2950 # parse head url and revision from svn
2951 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2952 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2953 $server_headURL = defined $1 ? $1 : 'unknown' ;
2954 $server_revision = defined $2 ? $2 : 'unknown' ;
2955 if ($server_headURL =~ /\/tag\// ||
2956 $server_headURL =~ /\/branches\// ) {
2957 $server_status = "stable";
2958 } else {
2959 $server_status = "developmental" ;
2960 }
2963 daemon_log(" ", 1);
2964 daemon_log("$0 started!", 1);
2965 daemon_log("status: $server_status", 1);
2966 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2968 # connect to incoming_db
2969 unlink($incoming_file_name);
2970 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2971 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2973 # connect to gosa-si job queue
2974 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2975 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2977 # connect to known_clients_db
2978 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2979 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2981 # connect to foreign_clients_db
2982 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2983 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2985 # connect to known_server_db
2986 unlink($known_server_file_name);
2987 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2988 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2990 # connect to login_usr_db
2991 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2992 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2994 # connect to fai_server_db and fai_release_db
2995 unlink($fai_server_file_name);
2996 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2997 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2999 unlink($fai_release_file_name);
3000 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3001 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3003 # connect to packages_list_db
3004 #unlink($packages_list_file_name);
3005 unlink($packages_list_under_construction);
3006 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3007 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3009 # connect to messaging_db
3010 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3011 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3014 # create xml object used for en/decrypting
3015 $xml = new XML::Simple();
3018 # foreign servers
3019 my @foreign_server_list;
3021 # add foreign server from cfg file
3022 if ($foreign_server_string ne "") {
3023 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3024 foreach my $foreign_server (@cfg_foreign_server_list) {
3025 push(@foreign_server_list, $foreign_server);
3026 }
3027 }
3029 # add foreign server from dns
3030 my @tmp_servers;
3031 if ( !$server_domain) {
3032 # Try our DNS Searchlist
3033 for my $domain(get_dns_domains()) {
3034 chomp($domain);
3035 my @tmp_domains= &get_server_addresses($domain);
3036 if(@tmp_domains) {
3037 for my $tmp_server(@tmp_domains) {
3038 push @tmp_servers, $tmp_server;
3039 }
3040 }
3041 }
3042 if(@tmp_servers && length(@tmp_servers)==0) {
3043 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3044 }
3045 } else {
3046 @tmp_servers = &get_server_addresses($server_domain);
3047 if( 0 == @tmp_servers ) {
3048 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3049 }
3050 }
3051 foreach my $server (@tmp_servers) {
3052 unshift(@foreign_server_list, $server);
3053 }
3054 # eliminate duplicate entries
3055 @foreign_server_list = &del_doubles(@foreign_server_list);
3056 my $all_foreign_server = join(", ", @foreign_server_list);
3057 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
3059 # add all found foreign servers to known_server
3060 my $act_timestamp = &get_time();
3061 foreach my $foreign_server (@foreign_server_list) {
3063 # do not add myself to known_server_db
3064 if (&is_local($foreign_server)) { next; }
3065 ######################################
3067 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3068 primkey=>['hostname'],
3069 hostname=>$foreign_server,
3070 status=>'not_jet_registered',
3071 hostkey=>"none",
3072 timestamp=>$act_timestamp,
3073 } );
3074 }
3077 # import all modules
3078 &import_modules;
3079 # check wether all modules are gosa-si valid passwd check
3080 &password_check;
3083 POE::Component::Server::TCP->new(
3084 Alias => "TCP_SERVER",
3085 Port => $server_port,
3086 ClientInput => sub {
3087 my ($kernel, $input) = @_[KERNEL, ARG0];
3088 push(@tasks, $input);
3089 push(@msgs_to_decrypt, $input);
3090 $kernel->yield("msg_to_decrypt");
3091 },
3092 InlineStates => {
3093 msg_to_decrypt => \&msg_to_decrypt,
3094 next_task => \&next_task,
3095 task_result => \&handle_task_result,
3096 task_done => \&handle_task_done,
3097 task_debug => \&handle_task_debug,
3098 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3099 }
3100 );
3102 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3104 # create session for repeatedly checking the job queue for jobs
3105 POE::Session->create(
3106 inline_states => {
3107 _start => \&session_start,
3108 register_at_foreign_servers => \®ister_at_foreign_servers,
3109 sig_handler => \&sig_handler,
3110 next_task => \&next_task,
3111 task_result => \&handle_task_result,
3112 task_done => \&handle_task_done,
3113 task_debug => \&handle_task_debug,
3114 watch_for_next_tasks => \&watch_for_next_tasks,
3115 watch_for_new_messages => \&watch_for_new_messages,
3116 watch_for_delivery_messages => \&watch_for_delivery_messages,
3117 watch_for_done_messages => \&watch_for_done_messages,
3118 watch_for_new_jobs => \&watch_for_new_jobs,
3119 watch_for_modified_jobs => \&watch_for_modified_jobs,
3120 watch_for_done_jobs => \&watch_for_done_jobs,
3121 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3122 watch_for_old_known_clients => \&watch_for_old_known_clients,
3123 create_packages_list_db => \&run_create_packages_list_db,
3124 create_fai_server_db => \&run_create_fai_server_db,
3125 create_fai_release_db => \&run_create_fai_release_db,
3126 recreate_packages_db => \&run_recreate_packages_db,
3127 session_run_result => \&session_run_result,
3128 session_run_debug => \&session_run_debug,
3129 session_run_done => \&session_run_done,
3130 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3131 }
3132 );
3135 POE::Kernel->run();
3136 exit;