Code

9c0c9776b202582cb7dcea01c9d78b809effef08
[gosa.git] / gosa-si / gosa-si-server
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 #         FILE:  gosa-sd
5 #
6 #        USAGE:  ./gosa-sd
7 #
8 #  DESCRIPTION:
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl 
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::GosaSupportDaemon;
52 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
53 use Net::LDAP;
54 use Net::LDAP::Util qw(:escape);
55 use Time::HiRes qw( usleep);
57 my $db_module = "DBsqlite";
58 {
59 no strict "refs";
60 require ("GOSA/".$db_module.".pm");
61 ("GOSA/".$db_module)->import;
62 daemon_log("0 INFO: importing database module '$db_module'", 1);
63 }
65 my $modules_path = "/usr/lib/gosa-si/modules";
66 use lib "/usr/lib/gosa-si/modules";
68 # revision number of server and program name
69 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
70 my $server_headURL;
71 my $server_revision;
72 my $server_status;
73 our $prg= basename($0);
75 our $global_kernel;
76 my ($foreground, $ping_timeout);
77 my ($server);
78 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
79 my ($messaging_db_loop_delay);
80 my ($procid, $pid);
81 my ($arp_fifo);
82 my ($xml);
83 my $sources_list;
84 my $max_clients;
85 my %repo_files=();
86 my $repo_path;
87 my %repo_dirs=();
89 # Variables declared in config file are always set to 'our'
90 our (%cfg_defaults, $log_file, $pid_file, 
91     $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
92     $arp_activ, $gosa_unit_tag,
93     $GosaPackages_key, $gosa_timeout,
94     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
95     $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
96     $arp_enabled, $arp_interface,
97     $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
98                 $new_systems_ou,
99 );
101 # additional variable which should be globaly accessable
102 our $server_address;
103 our $server_mac_address;
104 our $gosa_address;
105 our $no_arp;
106 our $verbose;
107 our $forground;
108 our $cfg_file;
109 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
110 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
111 our $known_modules;
112 our $root_uid;
113 our $adm_gid;
116 # specifies the verbosity of the daemon_log
117 $verbose = 0 ;
119 # if foreground is not null, script will be not forked to background
120 $foreground = 0 ;
122 # specifies the timeout seconds while checking the online status of a registrating client
123 $ping_timeout = 5;
125 $no_arp = 0;
126 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
127 my @packages_list_statements;
128 my $watch_for_new_jobs_in_progress = 0;
130 # holds all incoming decrypted messages
131 our $incoming_db;
132 our $incoming_tn = 'incoming';
133 my $incoming_file_name;
134 my @incoming_col_names = ("id INTEGER PRIMARY KEY auto_increment",
135         "timestamp VARCHAR(14) DEFAULT 'none'", 
136         "headertag VARCHAR(255) DEFAULT 'none'",
137         "targettag VARCHAR(255) DEFAULT 'none'",
138         "xmlmessage TEXT",
139         "module VARCHAR(255) DEFAULT 'none'",
140         "sessionid VARCHAR(255) DEFAULT '0'",
141 );
143 # holds all gosa jobs
144 our $job_db;
145 our $job_queue_tn = 'jobs';
146 my $job_queue_file_name;
147 my @job_queue_col_names = ("id INTEGER PRIMARY KEY auto_increment",
148         "timestamp VARCHAR(14) DEFAULT 'none'", 
149         "status VARCHAR(255) DEFAULT 'none'", 
150         "result TEXT",
151         "progress VARCHAR(255) DEFAULT 'none'",
152         "headertag VARCHAR(255) DEFAULT 'none'",
153         "targettag VARCHAR(255) DEFAULT 'none'", 
154         "xmlmessage TEXT", 
155         "macaddress VARCHAR(17) DEFAULT 'none'",
156         "plainname VARCHAR(255) DEFAULT 'none'",
157         "siserver VARCHAR(255) DEFAULT 'none'",
158         "modified INTEGER DEFAULT '0'",
159 );
161 # holds all other gosa-si-server
162 our $known_server_db;
163 our $known_server_tn = "known_server";
164 my $known_server_file_name;
165 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)");
167 # holds all registrated clients
168 our $known_clients_db;
169 our $known_clients_tn = "known_clients";
170 my $known_clients_file_name;
171 my @known_clients_col_names = ("hostname VARCHAR(255)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "timestamp VARCHAR(14)", "macaddress VARCHAR(17)", "events TEXT", "keylifetime VARCHAR(255)");
173 # holds all registered clients at a foreign server
174 our $foreign_clients_db;
175 our $foreign_clients_tn = "foreign_clients"; 
176 my $foreign_clients_file_name;
177 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
179 # holds all logged in user at each client 
180 our $login_users_db;
181 our $login_users_tn = "login_users";
182 my $login_users_file_name;
183 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)");
185 # holds all fai server, the debian release and tag
186 our $fai_server_db;
187 our $fai_server_tn = "fai_server"; 
188 my $fai_server_file_name;
189 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)"); 
191 our $fai_release_db;
192 our $fai_release_tn = "fai_release"; 
193 my $fai_release_file_name;
194 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)"); 
196 # holds all packages available from different repositories
197 our $packages_list_db;
198 our $packages_list_tn = "packages_list";
199 my $packages_list_file_name;
200 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
201 my $outdir = "/tmp/packages_list_db";
202 my $arch = "i386"; 
204 # holds all messages which should be delivered to a user
205 our $messaging_db;
206 our $messaging_tn = "messaging"; 
207 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)", 
208         "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
209 my $messaging_file_name;
211 # path to directory to store client install log files
212 our $client_fai_log_dir = "/var/log/fai"; 
214 # queue which stores taskes until one of the $max_children children are ready to process the task
215 #my @tasks = qw();
216 my @msgs_to_decrypt = qw();
217 my $max_children = 2;
220 # loop delay for job queue to look for opsi jobs
221 my $job_queue_opsi_delay = 10;
222 our $opsi_client;
223 our $opsi_url;
224  
225 # Lifetime of logged in user information. If no update information comes after n seconds, 
226 # the user is expeceted to be no longer logged in or the host is no longer running. Because
227 # of this, the user is deleted from login_users_db
228 our $logged_in_user_date_of_expiry = 600;
231 %cfg_defaults = (
232 "general" => {
233     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
234     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
235     },
236 "server" => {
237     "ip"                    => [\$server_ip, "0.0.0.0"],
238     "port"                  => [\$server_port, "20081"],
239     "known-clients"         => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
240     "known-servers"         => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
241     "incoming"              => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
242     "login-users"           => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
243     "fai-server"            => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
244     "fai-release"           => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
245     "packages-list"         => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
246     "messaging"             => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
247     "foreign-clients"       => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
248     "source-list"           => [\$sources_list, '/etc/apt/sources.list'],
249     "repo-path"             => [\$repo_path, '/srv/www/repository'],
250     "ldap-uri"              => [\$ldap_uri, ""],
251     "ldap-base"             => [\$ldap_base, ""],
252     "ldap-admin-dn"         => [\$ldap_admin_dn, ""],
253     "ldap-admin-password"   => [\$ldap_admin_password, ""],
254     "gosa-unit-tag"         => [\$gosa_unit_tag, ""],
255     "max-clients"           => [\$max_clients, 10],
256     "wol-password"          => [\$wake_on_lan_passwd, ""],
257                 "mysql-username"        => [\$mysql_username, "gosa_si"],
258                 "mysql-password"        => [\$mysql_password, ""],
259                 "mysql-database"        => [\$mysql_database, "gosa_si"],
260                 "mysql-host"            => [\$mysql_host, "127.0.0.1"],
261     },
262 "GOsaPackages" => {
263     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
264     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
265     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
266     "key" => [\$GosaPackages_key, "none"],
267                 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
268     },
269 "ClientPackages" => {
270     "key" => [\$ClientPackages_key, "none"],
271     "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
272     },
273 "ServerPackages"=> {
274     "address"      => [\$foreign_server_string, ""],
275     "dns-lookup"            => [\$dns_lookup, "true"],
276     "domain"  => [\$server_domain, ""],
277     "key"     => [\$ServerPackages_key, "none"],
278     "key-lifetime" => [\$foreign_servers_register_delay, 120],
279     "job-synchronization-enabled" => [\$job_synchronization, "true"],
280     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
281     },
282 "ArpHandler" => {
283     "enabled"   => [\$arp_enabled, "true"],
284     "interface" => [\$arp_interface, "all"],
285         },
286 "Opsi" => {
287     "enabled"  => [\$opsi_enabled, "false"], 
288     "server"   => [\$opsi_server, "localhost"],
289     "admin"    => [\$opsi_admin, "opsi-admin"],
290     "password" => [\$opsi_password, "secret"],
291    },
293 );
296 #===  FUNCTION  ================================================================
297 #         NAME:  usage
298 #   PARAMETERS:  nothing
299 #      RETURNS:  nothing
300 #  DESCRIPTION:  print out usage text to STDERR
301 #===============================================================================
302 sub usage {
303     print STDERR << "EOF" ;
304 usage: $prg [-hvf] [-c config]
306            -h        : this (help) message
307            -c <file> : config file
308            -f        : foreground, process will not be forked to background
309            -v        : be verbose (multiple to increase verbosity)
310            -no-arp   : starts $prg without connection to arp module
311  
312 EOF
313     print "\n" ;
317 #===  FUNCTION  ================================================================
318 #         NAME:  logging
319 #   PARAMETERS:  level - string - default 'info'
320 #                msg - string -
321 #                facility - string - default 'LOG_DAEMON'
322 #      RETURNS:  nothing
323 #  DESCRIPTION:  function for logging
324 #===============================================================================
325 sub daemon_log {
326     # log into log_file
327     my( $msg, $level ) = @_;
328     if(not defined $msg) { return }
329     if(not defined $level) { $level = 1 }
330     if(defined $log_file){
331         open(LOG_HANDLE, ">>$log_file");
332         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
333             print STDERR "cannot open $log_file: $!";
334             return 
335         }
336         chomp($msg);
337         #$msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
338         if($level <= $verbose){
339             my ($seconds, $minutes, $hours, $monthday, $month,
340                     $year, $weekday, $yearday, $sommertime) = localtime(time);
341             $hours = $hours < 10 ? $hours = "0".$hours : $hours;
342             $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
343             $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
344             my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
345             $month = $monthnames[$month];
346             $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
347             $year+=1900;
348             my $name = $prg;
350             my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
351             print LOG_HANDLE $log_msg;
352             if( $foreground ) { 
353                 print STDERR $log_msg;
354             }
355         }
356         close( LOG_HANDLE );
357     }
361 #===  FUNCTION  ================================================================
362 #         NAME:  check_cmdline_param
363 #   PARAMETERS:  nothing
364 #      RETURNS:  nothing
365 #  DESCRIPTION:  validates commandline parameter
366 #===============================================================================
367 sub check_cmdline_param () {
368     my $err_config;
369     my $err_counter = 0;
370         if(not defined($cfg_file)) {
371                 $cfg_file = "/etc/gosa-si/server.conf";
372                 if(! -r $cfg_file) {
373                         $err_config = "please specify a config file";
374                         $err_counter += 1;
375                 }
376     }
377     if( $err_counter > 0 ) {
378         &usage( "", 1 );
379         if( defined( $err_config)) { print STDERR "$err_config\n"}
380         print STDERR "\n";
381         exit( -1 );
382     }
386 #===  FUNCTION  ================================================================
387 #         NAME:  check_pid
388 #   PARAMETERS:  nothing
389 #      RETURNS:  nothing
390 #  DESCRIPTION:  handels pid processing
391 #===============================================================================
392 sub check_pid {
393     $pid = -1;
394     # Check, if we are already running
395     if( open(LOCK_FILE, "<$pid_file") ) {
396         $pid = <LOCK_FILE>;
397         if( defined $pid ) {
398             chomp( $pid );
399             if( -f "/proc/$pid/stat" ) {
400                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
401                 if( $stat ) {
402                                         daemon_log("ERROR: Already running",1);
403                     close( LOCK_FILE );
404                     exit -1;
405                 }
406             }
407         }
408         close( LOCK_FILE );
409         unlink( $pid_file );
410     }
412     # create a syslog msg if it is not to possible to open PID file
413     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
414         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
415         if (open(LOCK_FILE, '<', $pid_file)
416                 && ($pid = <LOCK_FILE>))
417         {
418             chomp($pid);
419             $msg .= "(PID $pid)\n";
420         } else {
421             $msg .= "(unable to read PID)\n";
422         }
423         if( ! ($foreground) ) {
424             openlog( $0, "cons,pid", "daemon" );
425             syslog( "warning", $msg );
426             closelog();
427         }
428         else {
429             print( STDERR " $msg " );
430         }
431         exit( -1 );
432     }
435 #===  FUNCTION  ================================================================
436 #         NAME:  import_modules
437 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
438 #                are stored
439 #      RETURNS:  nothing
440 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
441 #                state is on is imported by "require 'file';"
442 #===============================================================================
443 sub import_modules {
444     daemon_log(" ", 1);
446     if (not -e $modules_path) {
447         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
448     }
450     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
451     while (defined (my $file = readdir (DIR))) {
452         if (not $file =~ /(\S*?).pm$/) {
453             next;
454         }
455                 my $mod_name = $1;
457         # ArpHandler switch
458         if( $file =~ /ArpHandler.pm/ ) {
459             if( $arp_enabled eq "false" ) { next; }
460         }
461         
462         eval { require $file; };
463         if ($@) {
464             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
465             daemon_log("$@", 1);
466             exit;
467                 } else {
468                         my $info = eval($mod_name.'::get_module_info()');
469                         # Only load module if get_module_info() returns a non-null object
470                         if( $info ) {
471                                 my ($input_address, $input_key, $event_hash) = @{$info};
472                                 $known_modules->{$mod_name} = $info;
473                                 daemon_log("0 INFO: module $mod_name loaded", 5);
474                         }
475                 }
476     }   
478     close (DIR);
481 #===  FUNCTION  ================================================================
482 #         NAME:  password_check
483 #   PARAMETERS:  nothing
484 #      RETURNS:  nothing
485 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
486 #                the same password
487 #===============================================================================
488 sub password_check {
489     my $passwd_hash = {};
490     while (my ($mod_name, $mod_info) = each %$known_modules) {
491         my $mod_passwd = @$mod_info[1];
492         if (not defined $mod_passwd) { next; }
493         if (not exists $passwd_hash->{$mod_passwd}) {
494             $passwd_hash->{$mod_passwd} = $mod_name;
496         # escalates critical error
497         } else {
498             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
499             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
500             exit( -1 );
501         }
502     }
507 #===  FUNCTION  ================================================================
508 #         NAME:  sig_int_handler
509 #   PARAMETERS:  signal - string - signal arose from system
510 #      RETURNS:  nothing
511 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
512 #===============================================================================
513 sub sig_int_handler {
514     my ($signal) = @_;
516 #       if (defined($ldap_handle)) {
517 #               $ldap_handle->disconnect;
518 #       }
519     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
520     
522     daemon_log("shutting down gosa-si-server", 1);
523     system("kill `ps -C gosa-si-server -o pid=`");
525 $SIG{INT} = \&sig_int_handler;
528 sub check_key_and_xml_validity {
529     my ($crypted_msg, $module_key, $session_id) = @_;
530     my $msg;
531     my $msg_hash;
532     my $error_string;
533     eval{
534         $msg = &decrypt_msg($crypted_msg, $module_key);
536         if ($msg =~ /<xml>/i){
537             $msg =~ s/\s+/ /g;  # just for better daemon_log
538             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 9);
539             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
541             ##############
542             # check header
543             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
544             my $header_l = $msg_hash->{'header'};
545             if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
546             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
547             my $header = @{$header_l}[0];
548             if( 0 == length $header) { die 'empty string in header tag'; }
550             ##############
551             # check source
552             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
553             my $source_l = $msg_hash->{'source'};
554             if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
555             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
556             my $source = @{$source_l}[0];
557             if( 0 == length $source) { die 'source error'; }
559             ##############
560             # check target
561             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
562             my $target_l = $msg_hash->{'target'};
563             if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
564         }
565     };
566     if($@) {
567         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
568         $msg = undef;
569         $msg_hash = undef;
570     }
572     return ($msg, $msg_hash);
576 sub check_outgoing_xml_validity {
577     my ($msg, $session_id) = @_;
579     my $msg_hash;
580     eval{
581         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
583         ##############
584         # check header
585         my $header_l = $msg_hash->{'header'};
586         if( 1 != @{$header_l} ) {
587             die 'no or more than one headers specified';
588         }
589         my $header = @{$header_l}[0];
590         if( 0 == length $header) {
591             die 'header has length 0';
592         }
594         ##############
595         # check source
596         my $source_l = $msg_hash->{'source'};
597         if( 1 != @{$source_l} ) {
598             die 'no or more than 1 sources specified';
599         }
600         my $source = @{$source_l}[0];
601         if( 0 == length $source) {
602             die 'source has length 0';
603         }
605                                 # Check if source contains hostname instead of ip address
606                                 if(not $source =~ /^[a-z0-9\.]+:\d+$/i) {
607                                                 my ($hostname,$port) = split(/:/, $source);
608                                                 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
609                                                 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
610                                                         # Write ip address to $source variable
611                                                         $source = "$ip_address:$port";
612                                                 }
613                                 }
614         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
615                 $source =~ /^GOSA$/i) {
616             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
617         }
618         
619         ##############
620         # check target  
621         my $target_l = $msg_hash->{'target'};
622         if( 0 == @{$target_l} ) {
623             die "no targets specified";
624         }
625         foreach my $target (@$target_l) {
626             if( 0 == length $target) {
627                 die "target has length 0";
628             }
629             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
630                     $target =~ /^GOSA$/i ||
631                     $target =~ /^\*$/ ||
632                     $target =~ /KNOWN_SERVER/i ||
633                     $target =~ /JOBDB/i ||
634                     $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 ){
635                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
636             }
637         }
638     };
639     if($@) {
640         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
641         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
642         $msg_hash = undef;
643     }
645     return ($msg_hash);
649 sub input_from_known_server {
650     my ($input, $remote_ip, $session_id) = @_ ;  
651     my ($msg, $msg_hash, $module);
653     my $sql_statement= "SELECT * FROM known_server";
654     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
656     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
657         my $host_name = $hit->{hostname};
658         if( not $host_name =~ "^$remote_ip") {
659             next;
660         }
661         my $host_key = $hit->{hostkey};
662         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
663         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
665         # check if module can open msg envelope with module key
666         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
667         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
668             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
669             daemon_log("$@", 8);
670             next;
671         }
672         else {
673             $msg = $tmp_msg;
674             $msg_hash = $tmp_msg_hash;
675             $module = "ServerPackages";
676             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
677             last;
678         }
679     }
681     if( (!$msg) || (!$msg_hash) || (!$module) ) {
682         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
683     }
684   
685     return ($msg, $msg_hash, $module);
689 sub input_from_known_client {
690     my ($input, $remote_ip, $session_id) = @_ ;  
691     my ($msg, $msg_hash, $module);
693     my $sql_statement= "SELECT * FROM known_clients";
694     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
695     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
696         my $host_name = $hit->{hostname};
697         if( not $host_name =~ /^$remote_ip:\d*$/) {
698                 next;
699                 }
700         my $host_key = $hit->{hostkey};
701         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
702         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
704         # check if module can open msg envelope with module key
705         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
707         if( (!$msg) || (!$msg_hash) ) {
708             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
709             &daemon_log("$@", 8);
710             next;
711         }
712         else {
713             $module = "ClientPackages";
714             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
715             last;
716         }
717     }
719     if( (!$msg) || (!$msg_hash) || (!$module) ) {
720         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
721     }
723     return ($msg, $msg_hash, $module);
727 sub input_from_unknown_host {
728         no strict "refs";
729         my ($input, $session_id) = @_ ;
730         my ($msg, $msg_hash, $module);
731         my $error_string;
733         my %act_modules = %$known_modules;
735         while( my ($mod, $info) = each(%act_modules)) {
737                 # check a key exists for this module
738                 my $module_key = ${$mod."_key"};
739                 if( not defined $module_key ) {
740                         if( $mod eq 'ArpHandler' ) {
741                                 next;
742                         }
743                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
744                         next;
745                 }
746                 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
748                 # check if module can open msg envelope with module key
749                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
750                 if( (not defined $msg) || (not defined $msg_hash) ) {
751                         next;
752                 } else {
753                         $module = $mod;
754             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
755                         last;
756                 }
757         }
759         if( (!$msg) || (!$msg_hash) || (!$module)) {
760                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
761         }
763         return ($msg, $msg_hash, $module);
767 sub create_ciphering {
768     my ($passwd) = @_;
769         if((!defined($passwd)) || length($passwd)==0) {
770                 $passwd = "";
771         }
772     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
773     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
774     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
775     $my_cipher->set_iv($iv);
776     return $my_cipher;
780 sub encrypt_msg {
781     my ($msg, $key) = @_;
782     my $my_cipher = &create_ciphering($key);
783     my $len;
784     {
785             use bytes;
786             $len= 16-length($msg)%16;
787     }
788     $msg = "\0"x($len).$msg;
789     $msg = $my_cipher->encrypt($msg);
790     chomp($msg = &encode_base64($msg));
791     # there are no newlines allowed inside msg
792     $msg=~ s/\n//g;
793     return $msg;
797 sub decrypt_msg {
799     my ($msg, $key) = @_ ;
800     $msg = &decode_base64($msg);
801     my $my_cipher = &create_ciphering($key);
802     $msg = $my_cipher->decrypt($msg); 
803     $msg =~ s/\0*//g;
804     return $msg;
808 sub get_encrypt_key {
809     my ($target) = @_ ;
810     my $encrypt_key;
811     my $error = 0;
813     # target can be in known_server
814     if( not defined $encrypt_key ) {
815         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
816         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
817         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
818             my $host_name = $hit->{hostname};
819             if( $host_name ne $target ) {
820                 next;
821             }
822             $encrypt_key = $hit->{hostkey};
823             last;
824         }
825     }
827     # target can be in known_client
828     if( not defined $encrypt_key ) {
829         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
830         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
831         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
832             my $host_name = $hit->{hostname};
833             if( $host_name ne $target ) {
834                 next;
835             }
836             $encrypt_key = $hit->{hostkey};
837             last;
838         }
839     }
841     return $encrypt_key;
845 #===  FUNCTION  ================================================================
846 #         NAME:  open_socket
847 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
848 #                [PeerPort] string necessary if port not appended by PeerAddr
849 #      RETURNS:  socket IO::Socket::INET
850 #  DESCRIPTION:  open a socket to PeerAddr
851 #===============================================================================
852 sub open_socket {
853     my ($PeerAddr, $PeerPort) = @_ ;
854     if(defined($PeerPort)){
855         $PeerAddr = $PeerAddr.":".$PeerPort;
856     }
857     my $socket;
858     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
859             Porto => "tcp",
860             Type => SOCK_STREAM,
861             Timeout => 5,
862             );
863     if(not defined $socket) {
864         return;
865     }
866 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
867     return $socket;
871 #sub get_local_ip_for_remote_ip {
872 #       my $remote_ip= shift;
873 #       my $result="0.0.0.0";
875 #       if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
876 #               if($remote_ip eq "127.0.0.1") {
877 #                       $result = "127.0.0.1";
878 #               } else {
879 #                       my $PROC_NET_ROUTE= ('/proc/net/route');
881 #                       open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
882 #                               or die "Could not open $PROC_NET_ROUTE";
884 #                       my @ifs = <PROC_NET_ROUTE>;
886 #                       close(PROC_NET_ROUTE);
888 #                       # Eat header line
889 #                       shift @ifs;
890 #                       chomp @ifs;
891 #                       foreach my $line(@ifs) {
892 #                               my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
893 #                               my $destination;
894 #                               my $mask;
895 #                               my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
896 #                               $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
897 #                               ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
898 #                               $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
899 #                               if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
900 #                                       # destination matches route, save mac and exit
901 #                                       $result= &get_ip($Iface);
902 #                                       last;
903 #                               }
904 #                       }
905 #               }
906 #       } else {
907 #               daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
908 #       }
909 #       return $result;
910 #}
913 sub send_msg_to_target {
914     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
915     my $error = 0;
916     my $header;
917     my $timestamp = &get_time();
918     my $new_status;
919     my $act_status;
920     my ($sql_statement, $res);
921   
922     if( $msg_header ) {
923         $header = "'$msg_header'-";
924     } else {
925         $header = "";
926     }
928         # Patch the source ip
929         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
930                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
931                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
932         }
934     # encrypt xml msg
935     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
937     # opensocket
938     my $socket = &open_socket($address);
939     if( !$socket ) {
940         daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
941         $error++;
942     }
943     
944     if( $error == 0 ) {
945         # send xml msg
946         print $socket $crypted_msg."\n";
948         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
949         daemon_log("$session_id DEBUG: message:\n$msg", 9);
950         
951     }
953     # close socket in any case
954     if( $socket ) {
955         close $socket;
956     }
958     if( $error > 0 ) { $new_status = "down"; }
959     else { $new_status = $msg_header; }
962     # known_clients
963     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
964     $res = $known_clients_db->select_dbentry($sql_statement);
965     if( keys(%$res) == 1) {
966         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
967         if ($act_status eq "down" && $new_status eq "down") {
968             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
969             $res = $known_clients_db->del_dbentry($sql_statement);
970             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
971         } else { 
972             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
973             $res = $known_clients_db->update_dbentry($sql_statement);
974             if($new_status eq "down"){
975                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
976             } else {
977                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
978             }
979         }
980     }
982     # known_server
983     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
984     $res = $known_server_db->select_dbentry($sql_statement);
985     if( keys(%$res) == 1) {
986         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
987         if ($act_status eq "down" && $new_status eq "down") {
988             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
989             $res = $known_server_db->del_dbentry($sql_statement);
990             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
991         } 
992         else { 
993             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
994             $res = $known_server_db->update_dbentry($sql_statement);
995             if($new_status eq "down"){
996                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
997             } else {
998                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
999             }
1000         }
1001     }
1002     return $error; 
1006 sub update_jobdb_status_for_send_msgs {
1007     my ($answer, $error) = @_;
1008     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1009         my $jobdb_id = $1;
1010             
1011         # sending msg faild
1012         if( $error ) {
1013             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
1014                 my $sql_statement = "UPDATE $job_queue_tn ".
1015                     "SET status='error', result='can not deliver msg, please consult log file' ".
1016                     "WHERE id=$jobdb_id";
1017                 my $res = $job_db->update_dbentry($sql_statement);
1018             }
1020         # sending msg was successful
1021         } else {
1022             my $sql_statement = "UPDATE $job_queue_tn ".
1023                 "SET status='done' ".
1024                 "WHERE id=$jobdb_id AND status='processed'";
1025             my $res = $job_db->update_dbentry($sql_statement);
1026         }
1027     }
1031 sub sig_handler {
1032         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1033         daemon_log("0 INFO got signal '$signal'", 1); 
1034         $kernel->sig_handled();
1035         return;
1039 sub msg_to_decrypt {
1040         my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1041         my $session_id = $session->ID;
1042         my ($msg, $msg_hash, $module);
1043         my $error = 0;
1045         # hole neue msg aus @msgs_to_decrypt
1046         my $next_msg = shift @msgs_to_decrypt;
1048         # msg is from a new client or gosa
1049         ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1051         # msg is from a gosa-si-server
1052         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1053                 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1054         }
1055         # msg is from a gosa-si-client
1056         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1057                 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1058         }
1059         # an error occurred
1060         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1061                 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1062                 # could not understand a msg from its server the client cause a re-registering process
1063                 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1064                         "' to cause a re-registering of the client if necessary", 3);
1065                 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1066                 my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1067                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1068                         my $host_name = $hit->{'hostname'};
1069                         my $host_key = $hit->{'hostkey'};
1070                         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1071                         my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1072                         &update_jobdb_status_for_send_msgs($ping_msg, $error);
1073                 }
1074                 $error++;
1075         }
1078         my $header;
1079         my $target;
1080         my $source;
1081         my $done = 0;
1082         my $sql;
1083         my $res;
1085         # check whether this message should be processed here
1086         if ($error == 0) {
1087                 $header = @{$msg_hash->{'header'}}[0];
1088                 $target = @{$msg_hash->{'target'}}[0];
1089                 $source = @{$msg_hash->{'source'}}[0];
1090                 my $not_found_in_known_clients_db = 0;
1091                 my $not_found_in_known_server_db = 0;
1092                 my $not_found_in_foreign_clients_db = 0;
1093                 my $local_address;
1094                 my $local_mac;
1095                 my ($target_ip, $target_port) = split(':', $target);
1097                 # Determine the local ip address if target is an ip address
1098                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1099                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1100                 } else {
1101                         $local_address = $server_address;
1102                 }
1104                 # Determine the local mac address if target is a mac address
1105                 if ($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) {
1106                         my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1107                         my $network_interface= &get_interface_for_ip($loc_ip);
1108                         $local_mac = &get_mac_for_interface($network_interface);
1109                 } else {
1110                         $local_mac = $server_mac_address;
1111                 }
1113                 # target and source is equal to GOSA -> process here
1114                 if (not $done) {
1115                         if ($target eq "GOSA" && $source eq "GOSA") {
1116                                 $done = 1;                    
1117                                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1118                         }
1119                 }
1121                 # target is own address without forward_to_gosa-tag -> process here
1122                 if (not $done) {
1123                         #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1124                         if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1125                                 $done = 1;
1126                                 if ($source eq "GOSA") {
1127                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1128                                 }
1129                                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1130                         }
1131                 }
1133                 # target is a client address in known_clients -> process here
1134                 if (not $done) {
1135                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1136                         $res = $known_clients_db->select_dbentry($sql);
1137                         if (keys(%$res) > 0) {
1138                                 $done = 1; 
1139                                 my $hostname = $res->{1}->{'hostname'};
1140                                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1141                                 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1142                                 if ($source eq "GOSA") {
1143                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1144                                 }
1145                                 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1147                         } else {
1148                                 $not_found_in_known_clients_db = 1;
1149                         }
1150                 }
1152                 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1153                 if (not $done) {
1154                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1155                         my $gosa_at;
1156                         my $gosa_session_id;
1157                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1158                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1159                                 if ($gosa_at ne $local_address) {
1160                                         $done = 1;
1161                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7); 
1162                                 }
1163                         }
1164                 }
1166                 # if message should be processed here -> add message to incoming_db
1167                 if ($done) {
1168                         # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1169                         # so gosa-si-server knows how to process this kind of messages
1170                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1171                                 $module = "GosaPackages";
1172                         }
1174                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1175                                         primkey=>[],
1176                                         headertag=>$header,
1177                                         targettag=>$target,
1178                                         xmlmessage=>&encode_base64($msg),
1179                                         timestamp=>&get_time,
1180                                         module=>$module,
1181                                         sessionid=>$session_id,
1182                                 } );
1184                 }
1186                 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1187                 if (not $done) {
1188                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1189                         my $gosa_at;
1190                         my $gosa_session_id;
1191                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1192                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1193                                 if ($gosa_at eq $local_address) {
1194                                         my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1195                                         if( defined $session_reference ) {
1196                                                 $heap = $session_reference->get_heap();
1197                                         }
1198                                         if(exists $heap->{'client'}) {
1199                                                 $msg = &encrypt_msg($msg, $GosaPackages_key);
1200                                                 $heap->{'client'}->put($msg);
1201                                                 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5); 
1202                                         }
1203                                         $done = 1;
1204                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1205                                 }
1206                         }
1208                 }
1210                 # target is a client address in foreign_clients -> forward to registration server
1211                 if (not $done) {
1212                         $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1213                         $res = $foreign_clients_db->select_dbentry($sql);
1214                         if (keys(%$res) > 0) {
1215                                 my $hostname = $res->{1}->{'hostname'};
1216                                 my ($host_ip, $host_port) = split(/:/, $hostname);
1217                                 my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1218                                 my $regserver = $res->{1}->{'regserver'};
1219                                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1220                                 my $res = $known_server_db->select_dbentry($sql);
1221                                 if (keys(%$res) > 0) {
1222                                         my $regserver_key = $res->{1}->{'hostkey'};
1223                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1224                                         $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1225                                         if ($source eq "GOSA") {
1226                                                 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1227                                         }
1228                                         &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1229                                 }
1230                                 $done = 1;
1231                                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1232                         } else {
1233                                 $not_found_in_foreign_clients_db = 1;
1234                         }
1235                 }
1237                 # target is a server address -> forward to server
1238                 if (not $done) {
1239                         $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1240                         $res = $known_server_db->select_dbentry($sql);
1241                         if (keys(%$res) > 0) {
1242                                 my $hostkey = $res->{1}->{'hostkey'};
1244                                 if ($source eq "GOSA") {
1245                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1246                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1248                                 }
1250                                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1251                                 $done = 1;
1252                                 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1253                         } else {
1254                                 $not_found_in_known_server_db = 1;
1255                         }
1256                 }
1259                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1260                 if ( $not_found_in_foreign_clients_db 
1261                         && $not_found_in_known_server_db
1262                         && $not_found_in_known_clients_db) {
1263                         &daemon_log("$session_id DEBUG: target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here", 7);
1264             if ($header =~ /^gosa_/ || $header =~ /^job_/) { 
1265                 $module = "GosaPackages"; 
1266             }
1267                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1268                                         primkey=>[],
1269                                         headertag=>$header,
1270                                         targettag=>$target,
1271                                         xmlmessage=>&encode_base64($msg),
1272                                         timestamp=>&get_time,
1273                                         module=>$module,
1274                                         sessionid=>$session_id,
1275                                 } );
1276                         $done = 1;
1277                 }
1280                 if (not $done) {
1281                         daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1282                         if ($source eq "GOSA") {
1283                                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1284                                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1286                                 my $session_reference = $kernel->ID_id_to_session($session_id);
1287                                 if( defined $session_reference ) {
1288                                         $heap = $session_reference->get_heap();
1289                                 }
1290                                 if(exists $heap->{'client'}) {
1291                                         $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1292                                         $heap->{'client'}->put($error_msg);
1293                                 }
1294                         }
1295                 }
1297         }
1299         return;
1303 sub next_task {
1304     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1305     my $running_task = POE::Wheel::Run->new(
1306             Program => sub { process_task($session, $heap, $task) },
1307             StdioFilter => POE::Filter::Reference->new(),
1308             StdoutEvent  => "task_result",
1309             StderrEvent  => "task_debug",
1310             CloseEvent   => "task_done",
1311             );
1312     $heap->{task}->{ $running_task->ID } = $running_task;
1315 sub handle_task_result {
1316     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1317     my $client_answer = $result->{'answer'};
1318     if( $client_answer =~ s/session_id=(\d+)$// ) {
1319         my $session_id = $1;
1320         if( defined $session_id ) {
1321             my $session_reference = $kernel->ID_id_to_session($session_id);
1322             if( defined $session_reference ) {
1323                 $heap = $session_reference->get_heap();
1324             }
1325         }
1327         if(exists $heap->{'client'}) {
1328             $heap->{'client'}->put($client_answer);
1329         }
1330     }
1331     $kernel->sig(CHLD => "child_reap");
1334 sub handle_task_debug {
1335     my $result = $_[ARG0];
1336     print STDERR "$result\n";
1339 sub handle_task_done {
1340     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1341     delete $heap->{task}->{$task_id};
1344 sub process_task {
1345     no strict "refs";
1346     #CHECK: Not @_[...]?
1347     my ($session, $heap, $task) = @_;
1348     my $error = 0;
1349     my $answer_l;
1350     my ($answer_header, @answer_target_l, $answer_source);
1351     my $client_answer = "";
1353     # prepare all variables needed to process message
1354     #my $msg = $task->{'xmlmessage'};
1355     my $msg = &decode_base64($task->{'xmlmessage'});
1356     my $incoming_id = $task->{'id'};
1357     my $module = $task->{'module'};
1358     my $header =  $task->{'headertag'};
1359     my $session_id = $task->{'sessionid'};
1360                 my $msg_hash;
1361                 eval {
1362         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1363                 }; 
1364                 daemon_log("ERROR: XML failure '$@'") if ($@);
1365     my $source = @{$msg_hash->{'source'}}[0];
1366     
1367     # set timestamp of incoming client uptodate, so client will not 
1368     # be deleted from known_clients because of expiration
1369     my $act_time = &get_time();
1370     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1371     my $res = $known_clients_db->exec_statement($sql);
1373     ######################
1374     # process incoming msg
1375     if( $error == 0) {
1376         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1377         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1378         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1380         if ( 0 < @{$answer_l} ) {
1381             my $answer_str = join("\n", @{$answer_l});
1382             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1383                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1384             }
1385             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1386         } else {
1387             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1388         }
1390     }
1391     if( !$answer_l ) { $error++ };
1393     ########
1394     # answer
1395     if( $error == 0 ) {
1397         foreach my $answer ( @{$answer_l} ) {
1398             # check outgoing msg to xml validity
1399             my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1400             if( not defined $answer_hash ) { next; }
1401             
1402             $answer_header = @{$answer_hash->{'header'}}[0];
1403             @answer_target_l = @{$answer_hash->{'target'}};
1404             $answer_source = @{$answer_hash->{'source'}}[0];
1406             # deliver msg to all targets 
1407             foreach my $answer_target ( @answer_target_l ) {
1409                 # targets of msg are all gosa-si-clients in known_clients_db
1410                 if( $answer_target eq "*" ) {
1411                     # answer is for all clients
1412                     my $sql_statement= "SELECT * FROM known_clients";
1413                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1414                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1415                         my $host_name = $hit->{hostname};
1416                         my $host_key = $hit->{hostkey};
1417                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1418                         &update_jobdb_status_for_send_msgs($answer, $error);
1419                     }
1420                 }
1422                 # targets of msg are all gosa-si-server in known_server_db
1423                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1424                     # answer is for all server in known_server
1425                     my $sql_statement= "SELECT * FROM $known_server_tn";
1426                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1427                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1428                         my $host_name = $hit->{hostname};
1429                         my $host_key = $hit->{hostkey};
1430                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1431                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1432                         &update_jobdb_status_for_send_msgs($answer, $error);
1433                     }
1434                 }
1436                 # target of msg is GOsa
1437                                 elsif( $answer_target eq "GOSA" ) {
1438                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1439                                         my $add_on = "";
1440                     if( defined $session_id ) {
1441                         $add_on = ".session_id=$session_id";
1442                     }
1443                     # answer is for GOSA and has to returned to connected client
1444                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1445                     $client_answer = $gosa_answer.$add_on;
1446                 }
1448                 # target of msg is job queue at this host
1449                 elsif( $answer_target eq "JOBDB") {
1450                     $answer =~ /<header>(\S+)<\/header>/;   
1451                     my $header;
1452                     if( defined $1 ) { $header = $1; }
1453                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1454                     &update_jobdb_status_for_send_msgs($answer, $error);
1455                 }
1457                 # Target of msg is a mac address
1458                 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 ) {
1459                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1460                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1461                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1462                     my $found_ip_flag = 0;
1463                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1464                         my $host_name = $hit->{hostname};
1465                         my $host_key = $hit->{hostkey};
1466                         $answer =~ s/$answer_target/$host_name/g;
1467                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1468                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1469                         &update_jobdb_status_for_send_msgs($answer, $error);
1470                         $found_ip_flag++ ;
1471                     }   
1472                     if ($found_ip_flag == 0) {
1473                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1474                         my $res = $foreign_clients_db->select_dbentry($sql);
1475                         while( my ($hit_num, $hit) = each %{ $res } ) {
1476                             my $host_name = $hit->{hostname};
1477                             my $reg_server = $hit->{regserver};
1478                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1479                             
1480                             # Fetch key for reg_server
1481                             my $reg_server_key;
1482                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1483                             my $res = $known_server_db->select_dbentry($sql);
1484                             if (exists $res->{1}) {
1485                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1486                             } else {
1487                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1488                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1489                                 $reg_server_key = undef;
1490                             }
1492                             # Send answer to server where client is registered
1493                             if (defined $reg_server_key) {
1494                                 $answer =~ s/$answer_target/$host_name/g;
1495                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1496                                 &update_jobdb_status_for_send_msgs($answer, $error);
1497                                 $found_ip_flag++ ;
1498                             }
1499                         }
1500                     }
1501                     if( $found_ip_flag == 0) {
1502                         daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1504                         # Sometimes the client is still booting or does not wake up, in this case reactivate the job (if it exists) with a delay of 30 sec
1505                         my $delay_timestamp = &calc_timestamp(&get_time(), "plus", 30);
1506                         my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$answer_target' AND headertag='$answer_header')"; 
1507                         my $res = $job_db->update_dbentry($sql);
1508                         daemon_log("$session_id INFO: '$answer_header'-job will be reactivated at '$delay_timestamp' ".
1509                                 "cause client '$answer_target' is currently not available", 5);
1510                         daemon_log("$session_id $sql", 7);                                
1511                     }
1513                 # Answer is for one specific host   
1514                 } else {
1515                     # get encrypt_key
1516                     my $encrypt_key = &get_encrypt_key($answer_target);
1517                     if( not defined $encrypt_key ) {
1518                         # unknown target
1519                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1520                         next;
1521                     }
1522                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1523                     &update_jobdb_status_for_send_msgs($answer, $error);
1524                 }
1525             }
1526         }
1527     }
1529     my $filter = POE::Filter::Reference->new();
1530     my %result = ( 
1531             status => "seems ok to me",
1532             answer => $client_answer,
1533             );
1535     my $output = $filter->put( [ \%result ] );
1536     print @$output;
1541 sub session_start {
1542     my ($kernel) = $_[KERNEL];
1543     $global_kernel = $kernel;
1544     $kernel->yield('register_at_foreign_servers');
1545         $kernel->yield('create_fai_server_db', $fai_server_tn );
1546         $kernel->yield('create_fai_release_db', $fai_release_tn );
1547     $kernel->yield('watch_for_next_tasks');
1548         $kernel->sig(USR1 => "sig_handler");
1549         $kernel->sig(USR2 => "recreate_packages_db");
1550         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1551         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1552     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1553         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1554     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1555         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1556     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1558     # Start opsi check
1559     if ($opsi_enabled eq "true") {
1560         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1561     }
1566 sub watch_for_done_jobs {
1567     #CHECK: $heap for what?
1568     my ($kernel,$heap) = @_[KERNEL, HEAP];
1570     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1571         my $res = $job_db->select_dbentry( $sql_statement );
1573     while( my ($id, $hit) = each %{$res} ) {
1574         my $jobdb_id = $hit->{id};
1575         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1576         my $res = $job_db->del_dbentry($sql_statement); 
1577     }
1579     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1583 sub watch_for_opsi_jobs {
1584     my ($kernel) = $_[KERNEL];
1586     # This is not very nice to look for opsi install jobs, but headertag has to be trigger_action_reinstall. The only way to identify a 
1587     # opsi install job is to parse the xml message. There is still the correct header.
1588     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1589         my $res = $job_db->select_dbentry( $sql_statement );
1591     # Ask OPSI for an update of the running jobs
1592     while (my ($id, $hit) = each %$res ) {
1593         # Determine current parameters of the job
1594         my $hostId = $hit->{'plainname'};
1595         my $macaddress = $hit->{'macaddress'};
1596         my $progress = $hit->{'progress'};
1598         my $result= {};
1599         
1600         # For hosts, only return the products that are or get installed
1601         my $callobj;
1602         $callobj = {
1603             method  => 'getProductStates_hash',
1604             params  => [ $hostId ],
1605             id  => 1,
1606         };
1607         
1608         my $hres = $opsi_client->call($opsi_url, $callobj);
1609         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1610         if (not &check_opsi_res($hres)) {
1611             my $htmp= $hres->result->{$hostId};
1612         
1613             # Check state != not_installed or action == setup -> load and add
1614             my $products= 0;
1615             my $installed= 0;
1616             my $installing = 0;
1617             my $error= 0;  
1618             my @installed_list;
1619             my @error_list;
1620             my $act_status = "none";
1621             foreach my $product (@{$htmp}){
1623                 if ($product->{'installationStatus'} ne "not_installed" or
1624                         $product->{'actionRequest'} eq "setup"){
1626                     # Increase number of products for this host
1627                     $products++;
1628         
1629                     if ($product->{'installationStatus'} eq "failed"){
1630                         $result->{$product->{'productId'}}= "error";
1631                         unshift(@error_list, $product->{'productId'});
1632                         $error++;
1633                     }
1634                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1635                         $result->{$product->{'productId'}}= "installed";
1636                         unshift(@installed_list, $product->{'productId'});
1637                         $installed++;
1638                     }
1639                     if ($product->{'installationStatus'} eq "installing"){
1640                         $result->{$product->{'productId'}}= "installing";
1641                         $installing++;
1642                         $act_status = "installing - ".$product->{'productId'};
1643                     }
1644                 }
1645             }
1646         
1647             # Estimate "rough" progress, avoid division by zero
1648             if ($products == 0) {
1649                 $result->{'progress'}= 0;
1650             } else {
1651                 $result->{'progress'}= int($installed * 100 / $products);
1652             }
1654             # Set updates in job queue
1655             if ((not $error) && (not $installing) && ($installed)) {
1656                 $act_status = "installed - ".join(", ", @installed_list);
1657             }
1658             if ($error) {
1659                 $act_status = "error - ".join(", ", @error_list);
1660             }
1661             if ($progress ne $result->{'progress'} ) {
1662                 # Updating progress and result 
1663                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1664                 my $update_res = $job_db->update_dbentry($update_statement);
1665             }
1666             if ($progress eq 100) { 
1667                 # Updateing status
1668                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1669                 if ($error) {
1670                     $done_statement .= "status='error'";
1671                 } else {
1672                     $done_statement .= "status='done'";
1673                 }
1674                 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1675                 my $done_res = $job_db->update_dbentry($done_statement);
1676             }
1679         }
1680     }
1682     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1686 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1687 sub watch_for_modified_jobs {
1688     my ($kernel,$heap) = @_[KERNEL, HEAP];
1690     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')"; 
1691     my $res = $job_db->select_dbentry( $sql_statement );
1692     
1693     # if db contains no jobs which should be update, do nothing
1694     if (keys %$res != 0) {
1696         if ($job_synchronization  eq "true") {
1697             # make out of the db result a gosa-si message   
1698             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1699  
1700             # update all other SI-server
1701             &inform_all_other_si_server($update_msg);
1702         }
1704         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1705         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1706         $res = $job_db->update_dbentry($sql_statement);
1707     }
1709     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1713 sub watch_for_new_jobs {
1714         if($watch_for_new_jobs_in_progress == 0) {
1715                 $watch_for_new_jobs_in_progress = 1;
1716                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1718                 # check gosa job quaeue for jobs with executable timestamp
1719                 my $timestamp = &get_time();
1720                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1721                 my $res = $job_db->exec_statement( $sql_statement );
1723                 # Merge all new jobs that would do the same actions
1724                 my @drops;
1725                 my $hits;
1726                 foreach my $hit (reverse @{$res} ) {
1727                         my $macaddress= lc @{$hit}[8];
1728                         my $headertag= @{$hit}[5];
1729                         if(
1730                                 defined($hits->{$macaddress}) &&
1731                                 defined($hits->{$macaddress}->{$headertag}) &&
1732                                 defined($hits->{$macaddress}->{$headertag}[0])
1733                         ) {
1734                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1735                         }
1736                         $hits->{$macaddress}->{$headertag}= $hit;
1737                 }
1739                 # Delete new jobs with a matching job in state 'processing'
1740                 foreach my $macaddress (keys %{$hits}) {
1741                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1742                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1743                                 if(defined($jobdb_id)) {
1744                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1745                                         my $res = $job_db->exec_statement( $sql_statement );
1746                                         foreach my $hit (@{$res}) {
1747                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1748                                         }
1749                                 } else {
1750                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1751                                 }
1752                         }
1753                 }
1755                 # Commit deletion
1756                 $job_db->exec_statementlist(\@drops);
1758                 # Look for new jobs that could be executed
1759                 foreach my $macaddress (keys %{$hits}) {
1761                         # Look if there is an executing job
1762                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1763                         my $res = $job_db->exec_statement( $sql_statement );
1765                         # Skip new jobs for host if there is a processing job
1766                         if(defined($res) and defined @{$res}[0]) {
1767                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1768                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1769                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1770                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1771                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1772                                         if(defined($res_2) and defined @{$res_2}[0]) {
1773                                                 # Set status from goto-activation to 'waiting' and update timestamp
1774                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1775                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1776                                         }
1777                                 }
1778                                 next;
1779                         }
1781                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1782                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1783                                 if(defined($jobdb_id)) {
1784                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1786                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1787                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1788                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1790                                         # expect macaddress is unique!!!!!!
1791                                         my $target = $res_hash->{1}->{hostname};
1793                                         # change header
1794                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1796                                         # add sqlite_id
1797                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1799                                         $job_msg =~ /<header>(\S+)<\/header>/;
1800                                         my $header = $1 ;
1801                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");                    
1803                                         # update status in job queue to ...
1804                     # ... 'processing', for jobs: 'reinstall', 'update'
1805                     if (($header =~ /gosa_trigger_action_reinstall/) || ($header =~ /gosa_trigger_action_update/)) {
1806                         my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1807                         my $dbres = $job_db->update_dbentry($sql_statement);
1808                     }
1810                     # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1811                     else {
1812                         my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1813                         my $dbres = $job_db->update_dbentry($sql_statement);
1814                     }
1815                 
1817                                         # We don't want parallel processing
1818                                         last;
1819                                 }
1820                         }
1821                 }
1823                 $watch_for_new_jobs_in_progress = 0;
1824                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1825         }
1829 sub watch_for_new_messages {
1830     my ($kernel,$heap) = @_[KERNEL, HEAP];
1831     my @coll_user_msg;   # collection list of outgoing messages
1832     
1833     # check messaging_db for new incoming messages with executable timestamp
1834     my $timestamp = &get_time();
1835     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1836     my $res = $messaging_db->exec_statement( $sql_statement );
1837         foreach my $hit (@{$res}) {
1839         # create outgoing messages
1840         my $message_to = @{$hit}[3];
1841         # translate message_to to plain login name
1842         my @message_to_l = split(/,/, $message_to);  
1843                 my %receiver_h; 
1844                 foreach my $receiver (@message_to_l) {
1845                         if ($receiver =~ /^u_([\s\S]*)$/) {
1846                                 $receiver_h{$receiver} = 0;
1847                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1848                                 my $group_name = $1;
1849                                 # fetch all group members from ldap and add them to receiver hash
1850                                 my $ldap_handle = &get_ldap_handle();
1851                                 if (defined $ldap_handle) {
1852                                                 my $mesg = $ldap_handle->search(
1853                                                                                 base => $ldap_base,
1854                                                                                 scope => 'sub',
1855                                                                                 attrs => ['memberUid'],
1856                                                                                 filter => "cn=$group_name",
1857                                                                                 );
1858                                                 if ($mesg->count) {
1859                                                                 my @entries = $mesg->entries;
1860                                                                 foreach my $entry (@entries) {
1861                                                                                 my @receivers= $entry->get_value("memberUid");
1862                                                                                 foreach my $receiver (@receivers) { 
1863                                                                                                 $receiver_h{$receiver} = 0;
1864                                                                                 }
1865                                                                 }
1866                                                 } 
1867                                                 # translating errors ?
1868                                                 if ($mesg->code) {
1869                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1870                                                 }
1871                                 # ldap handle error ?           
1872                                 } else {
1873                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1874                                 }
1875                         } else {
1876                                 my $sbjct = &encode_base64(@{$hit}[1]);
1877                                 my $msg = &encode_base64(@{$hit}[7]);
1878                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1879                         }
1880                 }
1881                 my @receiver_l = keys(%receiver_h);
1883         my $message_id = @{$hit}[0];
1885         #add each outgoing msg to messaging_db
1886         my $receiver;
1887         foreach $receiver (@receiver_l) {
1888             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1889                 "VALUES ('".
1890                 $message_id."', '".    # id
1891                 @{$hit}[1]."', '".     # subject
1892                 @{$hit}[2]."', '".     # message_from
1893                 $receiver."', '".      # message_to
1894                 "none"."', '".         # flag
1895                 "out"."', '".          # direction
1896                 @{$hit}[6]."', '".     # delivery_time
1897                 @{$hit}[7]."', '".     # message
1898                 $timestamp."'".     # timestamp
1899                 ")";
1900             &daemon_log("M DEBUG: $sql_statement", 1);
1901             my $res = $messaging_db->exec_statement($sql_statement);
1902             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1903         }
1905         # set incoming message to flag d=deliverd
1906         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1907         &daemon_log("M DEBUG: $sql_statement", 7);
1908         $res = $messaging_db->update_dbentry($sql_statement);
1909         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1910     }
1912     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1913     return;
1916 sub watch_for_delivery_messages {
1917     my ($kernel, $heap) = @_[KERNEL, HEAP];
1919     # select outgoing messages
1920     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1921     #&daemon_log("0 DEBUG: $sql", 7);
1922     my $res = $messaging_db->exec_statement( $sql_statement );
1923     
1924     # build out msg for each    usr
1925     foreach my $hit (@{$res}) {
1926         my $receiver = @{$hit}[3];
1927         my $msg_id = @{$hit}[0];
1928         my $subject = @{$hit}[1];
1929         my $message = @{$hit}[7];
1931         # resolve usr -> host where usr is logged in
1932         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1933         #&daemon_log("0 DEBUG: $sql", 7);
1934         my $res = $login_users_db->exec_statement($sql);
1936         # receiver is logged in nowhere
1937         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1939         # receiver ist logged in at a client registered at local server
1940                 my $send_succeed = 0;
1941                 foreach my $hit (@$res) {
1942                                 my $receiver_host = @$hit[0];
1943                 my $delivered2host = 0;
1944                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1946                                 # Looking for host in know_clients_db 
1947                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1948                                 my $res = $known_clients_db->exec_statement($sql);
1950                 # Host is known in known_clients_db
1951                 if (ref(@$res[0]) eq "ARRAY") {
1952                     my $receiver_key = @{@{$res}[0]}[2];
1953                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1954                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1955                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1956                     if ($error == 0 ) {
1957                         $send_succeed++ ;
1958                         $delivered2host++ ;
1959                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
1960                     } else {
1961                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
1962                     }
1963                 }
1964                 
1965                 # Message already send, do not need to do anything more, otherwise ...
1966                 if ($delivered2host) { next;}
1967     
1968                 # ...looking for host in foreign_clients_db
1969                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1970                 $res = $foreign_clients_db->exec_statement($sql);
1971   
1972                                 # Host is known in foreign_clients_db 
1973                                 if (ref(@$res[0]) eq "ARRAY") { 
1974                     my $registration_server = @{@{$res}[0]}[2];
1975                     
1976                     # Fetch encryption key for registration server
1977                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1978                     my $res = $known_server_db->exec_statement($sql);
1979                     if (ref(@$res[0]) eq "ARRAY") { 
1980                         my $registration_server_key = @{@{$res}[0]}[3];
1981                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1982                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1983                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
1984                         if ($error == 0 ) {
1985                             $send_succeed++ ;
1986                             $delivered2host++ ;
1987                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
1988                         } else {
1989                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
1990                         }
1992                     } else {
1993                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1994                                 "registrated at server '$registration_server', ".
1995                                 "but no data available in known_server_db ", 1); 
1996                     }
1997                 }
1998                 
1999                 if (not $delivered2host) {
2000                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2001                 }
2002                 }
2004                 if ($send_succeed) {
2005                                 # set outgoing msg at db to deliverd
2006                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
2007                                 my $res = $messaging_db->exec_statement($sql); 
2008                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2009                 } else {
2010             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
2011         }
2012         }
2014     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
2015     return;
2019 sub watch_for_done_messages {
2020     my ($kernel,$heap) = @_[KERNEL, HEAP];
2022     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
2023     #&daemon_log("0 DEBUG: $sql", 7);
2024     my $res = $messaging_db->exec_statement($sql); 
2026     foreach my $hit (@{$res}) {
2027         my $msg_id = @{$hit}[0];
2029         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
2030         #&daemon_log("0 DEBUG: $sql", 7); 
2031         my $res = $messaging_db->exec_statement($sql);
2033         # not all usr msgs have been seen till now
2034         if ( ref(@$res[0]) eq "ARRAY") { next; }
2035         
2036         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
2037         #&daemon_log("0 DEBUG: $sql", 7);
2038         $res = $messaging_db->exec_statement($sql);
2039     
2040     }
2042     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2043     return;
2047 sub watch_for_old_known_clients {
2048     my ($kernel,$heap) = @_[KERNEL, HEAP];
2050     my $sql_statement = "SELECT * FROM $known_clients_tn";
2051     my $res = $known_clients_db->select_dbentry( $sql_statement );
2053     my $act_time = int(&get_time());
2055     while ( my ($hit_num, $hit) = each %$res) {
2056         my $expired_timestamp = int($hit->{'timestamp'});
2057         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2058         my $dt = DateTime->new( year   => $1,
2059                 month  => $2,
2060                 day    => $3,
2061                 hour   => $4,
2062                 minute => $5,
2063                 second => $6,
2064                 );
2066         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2067         $expired_timestamp = $dt->ymd('').$dt->hms('');
2068         if ($act_time > $expired_timestamp) {
2069             my $hostname = $hit->{'hostname'};
2070             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2071             my $del_res = $known_clients_db->exec_statement($del_sql);
2073             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2074         }
2076     }
2078     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2082 sub watch_for_next_tasks {
2083     my ($kernel,$heap) = @_[KERNEL, HEAP];
2085     my $sql = "SELECT * FROM $incoming_tn";
2086     my $res = $incoming_db->select_dbentry($sql);
2087     
2088     while ( my ($hit_num, $hit) = each %$res) {
2089         my $headertag = $hit->{'headertag'};
2090         if ($headertag =~ /^answer_(\d+)/) {
2091             # do not start processing, this message is for a still running POE::Wheel
2092             next;
2093         }
2094         my $message_id = $hit->{'id'};
2095         my $session_id = $hit->{'sessionid'};
2096         &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2097         $kernel->yield('next_task', $hit);
2099         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2100         my $res = $incoming_db->exec_statement($sql);
2101     }
2103     $kernel->delay_set('watch_for_next_tasks', 1); 
2107 sub get_ldap_handle {
2108         my ($session_id) = @_;
2109         my $heap;
2110         my $ldap_handle;
2112         if (not defined $session_id ) { $session_id = 0 };
2113         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2115         if ($session_id == 0) {
2116                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
2117                 $ldap_handle = Net::LDAP->new( $ldap_uri );
2118                 if (defined $ldap_handle) {
2119                         $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!"); 
2120                 } else {
2121                         daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2122                 }
2124         } else {
2125                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2126                 if( defined $session_reference ) {
2127                         $heap = $session_reference->get_heap();
2128                 }
2130                 if (not defined $heap) {
2131                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
2132                         return;
2133                 }
2135                 # TODO: This "if" is nonsense, because it doesn't prove that the
2136                 #       used handle is still valid - or if we've to reconnect...
2137                 #if (not exists $heap->{ldap_handle}) {
2138                         $ldap_handle = Net::LDAP->new( $ldap_uri );
2139                         $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!"); 
2140                         $heap->{ldap_handle} = $ldap_handle;
2141                 #}
2142         }
2143         return $ldap_handle;
2147 sub change_fai_state {
2148     my ($st, $targets, $session_id) = @_;
2149     $session_id = 0 if not defined $session_id;
2150     # Set FAI state to localboot
2151     my %mapActions= (
2152         reboot    => '',
2153         update    => 'softupdate',
2154         localboot => 'localboot',
2155         reinstall => 'install',
2156         rescan    => '',
2157         wake      => '',
2158         memcheck  => 'memcheck',
2159         sysinfo   => 'sysinfo',
2160         install   => 'install',
2161     );
2163     # Return if this is unknown
2164     if (!exists $mapActions{ $st }){
2165         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2166       return;
2167     }
2169     my $state= $mapActions{ $st };
2171     my $ldap_handle = &get_ldap_handle($session_id);
2172     if( defined($ldap_handle) ) {
2174       # Build search filter for hosts
2175         my $search= "(&(objectClass=GOhard)";
2176         foreach (@{$targets}){
2177             $search.= "(macAddress=$_)";
2178         }
2179         $search.= ")";
2181       # If there's any host inside of the search string, procress them
2182         if (!($search =~ /macAddress/)){
2183             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2184             return;
2185         }
2187       # Perform search for Unit Tag
2188       my $mesg = $ldap_handle->search(
2189           base   => $ldap_base,
2190           scope  => 'sub',
2191           attrs  => ['dn', 'FAIstate', 'objectClass'],
2192           filter => "$search"
2193           );
2195           if ($mesg->count) {
2196                   my @entries = $mesg->entries;
2197                   if (0 == @entries) {
2198                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2199                   }
2201                   foreach my $entry (@entries) {
2202                           # Only modify entry if it is not set to '$state'
2203                           if ($entry->get_value("FAIstate") ne "$state"){
2204                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2205                                   my $result;
2206                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2207                                   if (exists $tmp{'FAIobject'}){
2208                                           if ($state eq ''){
2209                                                   $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2210                                           } else {
2211                                                   $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2212                                           }
2213                                   } elsif ($state ne ''){
2214                                           $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2215                                   }
2217                                   # Errors?
2218                                   if ($result->code){
2219                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2220                                   }
2221                           } else {
2222                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
2223                           }  
2224                   }
2225           } else {
2226                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2227           }
2229     # if no ldap handle defined
2230     } else {
2231         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
2232     }
2234         return;
2238 sub change_goto_state {
2239     my ($st, $targets, $session_id) = @_;
2240     $session_id = 0  if not defined $session_id;
2242     # Switch on or off?
2243     my $state= $st eq 'active' ? 'active': 'locked';
2245     my $ldap_handle = &get_ldap_handle($session_id);
2246     if( defined($ldap_handle) ) {
2248       # Build search filter for hosts
2249       my $search= "(&(objectClass=GOhard)";
2250       foreach (@{$targets}){
2251         $search.= "(macAddress=$_)";
2252       }
2253       $search.= ")";
2255       # If there's any host inside of the search string, procress them
2256       if (!($search =~ /macAddress/)){
2257         return;
2258       }
2260       # Perform search for Unit Tag
2261       my $mesg = $ldap_handle->search(
2262           base   => $ldap_base,
2263           scope  => 'sub',
2264           attrs  => ['dn', 'gotoMode'],
2265           filter => "$search"
2266           );
2268       if ($mesg->count) {
2269         my @entries = $mesg->entries;
2270         foreach my $entry (@entries) {
2272           # Only modify entry if it is not set to '$state'
2273           if ($entry->get_value("gotoMode") ne $state){
2275             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2276             my $result;
2277             $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2279             # Errors?
2280             if ($result->code){
2281               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2282             }
2284           }
2285         }
2286       } else {
2287                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2288           }
2290     }
2294 sub run_recreate_packages_db {
2295     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2296     my $session_id = $session->ID;
2297         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2298         $kernel->yield('create_fai_release_db', $fai_release_tn);
2299         $kernel->yield('create_fai_server_db', $fai_server_tn);
2300         return;
2304 sub run_create_fai_server_db {
2305     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2306     my $session_id = $session->ID;
2307     my $task = POE::Wheel::Run->new(
2308             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2309             StdoutEvent  => "session_run_result",
2310             StderrEvent  => "session_run_debug",
2311             CloseEvent   => "session_run_done",
2312             );
2314     $heap->{task}->{ $task->ID } = $task;
2315     return;
2319 sub create_fai_server_db {
2320         my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2321         my $result;
2323         if (not defined $session_id) { $session_id = 0; }
2324         my $ldap_handle = &get_ldap_handle();
2325         if(defined($ldap_handle)) {
2326                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2327                 my $mesg= $ldap_handle->search(
2328                         base   => $ldap_base,
2329                         scope  => 'sub',
2330                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2331                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2332                 );
2333                 if($mesg->{'resultCode'} == 0 &&
2334                         $mesg->count != 0) {
2335                         foreach my $entry (@{$mesg->{entries}}) {
2336                                 if($entry->exists('FAIrepository')) {
2337                                         # Add an entry for each Repository configured for server
2338                                         foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2339                                                 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2340                                                 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2341                                                 $result= $fai_server_db->add_dbentry( { 
2342                                                                 table => $table_name,
2343                                                                 primkey => ['server', 'fai_release', 'tag'],
2344                                                                 server => $tmp_url,
2345                                                                 fai_release => $tmp_release,
2346                                                                 sections => $tmp_sections,
2347                                                                 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2348                                                         } );
2349                                         }
2350                                 }
2351                         }
2352                 }
2353                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2355                 # TODO: Find a way to post the 'create_packages_list_db' event
2356                 if(not defined($dont_create_packages_list)) {
2357                         &create_packages_list_db(undef, undef, $session_id);
2358                 }
2359         }       
2361         $ldap_handle->disconnect;
2362         return $result;
2366 sub run_create_fai_release_db {
2367         my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2368         my $session_id = $session->ID;
2369         my $task = POE::Wheel::Run->new(
2370                 Program => sub { &create_fai_release_db($table_name, $session_id) },
2371                 StdoutEvent  => "session_run_result",
2372                 StderrEvent  => "session_run_debug",
2373                 CloseEvent   => "session_run_done",
2374         );
2376         $heap->{task}->{ $task->ID } = $task;
2377         return;
2381 sub create_fai_release_db {
2382         my ($table_name, $session_id) = @_;
2383         my $result;
2385         # used for logging
2386         if (not defined $session_id) { $session_id = 0; }
2388         my $ldap_handle = &get_ldap_handle();
2389         if(defined($ldap_handle)) {
2390                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2391                 my $mesg= $ldap_handle->search(
2392                         base   => $ldap_base,
2393                         scope  => 'sub',
2394                         attrs  => [],
2395                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2396                 );
2397                 if($mesg->{'resultCode'} == 0 &&
2398                         $mesg->count != 0) {
2399                         # Walk through all possible FAI container ou's
2400                         my @sql_list;
2401                         my $timestamp= &get_time();
2402                         foreach my $ou (@{$mesg->{entries}}) {
2403                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2404                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2405                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2406                                         if(@tmp_array) {
2407                                                 foreach my $entry (@tmp_array) {
2408                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2409                                                                 my $sql= 
2410                                                                 "INSERT INTO $table_name "
2411                                                                 ."(timestamp, fai_release, class, type, state) VALUES ("
2412                                                                 .$timestamp.","
2413                                                                 ."'".$entry->{'release'}."',"
2414                                                                 ."'".$entry->{'class'}."',"
2415                                                                 ."'".$entry->{'type'}."',"
2416                                                                 ."'".$entry->{'state'}."')";
2417                                                                 push @sql_list, $sql;
2418                                                         }
2419                                                 }
2420                                         }
2421                                 }
2422                         }
2424                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2425                         if(@sql_list) {
2426                                 unshift @sql_list, "DELETE FROM $table_name";
2427                                 $fai_release_db->exec_statementlist(\@sql_list);
2428                         }
2429                         daemon_log("$session_id DEBUG: Done with inserting",7);
2430                 }
2431                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2432         }
2433         $ldap_handle->disconnect;
2434         return $result;
2437 sub get_fai_types {
2438         my $tmp_classes = shift || return undef;
2439         my @result;
2441         foreach my $type(keys %{$tmp_classes}) {
2442                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2443                         my $entry = {
2444                                 type => $type,
2445                                 state => $tmp_classes->{$type}[0],
2446                         };
2447                         push @result, $entry;
2448                 }
2449         }
2451         return @result;
2454 sub get_fai_state {
2455         my $result = "";
2456         my $tmp_classes = shift || return $result;
2458         foreach my $type(keys %{$tmp_classes}) {
2459                 if(defined($tmp_classes->{$type}[0])) {
2460                         $result = $tmp_classes->{$type}[0];
2461                         
2462                 # State is equal for all types in class
2463                         last;
2464                 }
2465         }
2467         return $result;
2470 sub resolve_fai_classes {
2471         my ($fai_base, $ldap_handle, $session_id) = @_;
2472         if (not defined $session_id) { $session_id = 0; }
2473         my $result;
2474         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2475         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2476         my $fai_classes;
2478         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2479         my $mesg= $ldap_handle->search(
2480                 base   => $fai_base,
2481                 scope  => 'sub',
2482                 attrs  => ['cn','objectClass','FAIstate'],
2483                 filter => $fai_filter,
2484         );
2485         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2487         if($mesg->{'resultCode'} == 0 &&
2488                 $mesg->count != 0) {
2489                 foreach my $entry (@{$mesg->{entries}}) {
2490                         if($entry->exists('cn')) {
2491                                 my $tmp_dn= $entry->dn();
2493                                 # Skip classname and ou dn parts for class
2494                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2496                                 # Skip classes without releases
2497                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2498                                         next;
2499                                 }
2501                                 my $tmp_cn= $entry->get_value('cn');
2502                                 my $tmp_state= $entry->get_value('FAIstate');
2504                                 my $tmp_type;
2505                                 # Get FAI type
2506                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2507                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2508                                                 $tmp_type= $oclass;
2509                                                 last;
2510                                         }
2511                                 }
2513                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2514                                         # A Subrelease
2515                                         my @sub_releases = split(/,/, $tmp_release);
2517                                         # Walk through subreleases and build hash tree
2518                                         my $hash;
2519                                         while(my $tmp_sub_release = pop @sub_releases) {
2520                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2521                                         }
2522                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2523                                 } else {
2524                                         # A branch, no subrelease
2525                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2526                                 }
2527                         } elsif (!$entry->exists('cn')) {
2528                                 my $tmp_dn= $entry->dn();
2529                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2531                                 # Skip classes without releases
2532                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2533                                         next;
2534                                 }
2536                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2537                                         # A Subrelease
2538                                         my @sub_releases= split(/,/, $tmp_release);
2540                                         # Walk through subreleases and build hash tree
2541                                         my $hash;
2542                                         while(my $tmp_sub_release = pop @sub_releases) {
2543                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2544                                         }
2545                                         # Remove the last two characters
2546                                         chop($hash);
2547                                         chop($hash);
2549                                         eval('$fai_classes->'.$hash.'= {}');
2550                                 } else {
2551                                         # A branch, no subrelease
2552                                         if(!exists($fai_classes->{$tmp_release})) {
2553                                                 $fai_classes->{$tmp_release} = {};
2554                                         }
2555                                 }
2556                         }
2557                 }
2559                 # The hash is complete, now we can honor the copy-on-write based missing entries
2560                 foreach my $release (keys %$fai_classes) {
2561                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2562                 }
2563         }
2564         return $result;
2567 sub apply_fai_inheritance {
2568        my $fai_classes = shift || return {};
2569        my $tmp_classes;
2571        # Get the classes from the branch
2572        foreach my $class (keys %{$fai_classes}) {
2573                # Skip subreleases
2574                if($class =~ /^ou=.*$/) {
2575                        next;
2576                } else {
2577                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2578                }
2579        }
2581        # Apply to each subrelease
2582        foreach my $subrelease (keys %{$fai_classes}) {
2583                if($subrelease =~ /ou=/) {
2584                        foreach my $tmp_class (keys %{$tmp_classes}) {
2585                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2586                                        $fai_classes->{$subrelease}->{$tmp_class} =
2587                                        deep_copy($tmp_classes->{$tmp_class});
2588                                } else {
2589                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2590                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2591                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2592                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2593                                                }
2594                                        }
2595                                }
2596                        }
2597                }
2598        }
2600        # Find subreleases in deeper levels
2601        foreach my $subrelease (keys %{$fai_classes}) {
2602                if($subrelease =~ /ou=/) {
2603                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2604                                if($subsubrelease =~ /ou=/) {
2605                                        apply_fai_inheritance($fai_classes->{$subrelease});
2606                                }
2607                        }
2608                }
2609        }
2611        return $fai_classes;
2614 sub get_fai_release_entries {
2615         my $tmp_classes = shift || return;
2616         my $parent = shift || "";
2617         my @result = shift || ();
2619         foreach my $entry (keys %{$tmp_classes}) {
2620                 if(defined($entry)) {
2621                         if($entry =~ /^ou=.*$/) {
2622                                 my $release_name = $entry;
2623                                 $release_name =~ s/ou=//g;
2624                                 if(length($parent)>0) {
2625                                         $release_name = $parent."/".$release_name;
2626                                 }
2627                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2628                                 foreach my $bufentry(@bufentries) {
2629                                         push @result, $bufentry;
2630                                 }
2631                         } else {
2632                                 my @types = get_fai_types($tmp_classes->{$entry});
2633                                 foreach my $type (@types) {
2634                                         push @result, 
2635                                         {
2636                                                 'class' => $entry,
2637                                                 'type' => $type->{'type'},
2638                                                 'release' => $parent,
2639                                                 'state' => $type->{'state'},
2640                                         };
2641                                 }
2642                         }
2643                 }
2644         }
2646         return @result;
2649 sub deep_copy {
2650         my $this = shift;
2651         if (not ref $this) {
2652                 $this;
2653         } elsif (ref $this eq "ARRAY") {
2654                 [map deep_copy($_), @$this];
2655         } elsif (ref $this eq "HASH") {
2656                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2657         } else { die "what type is $_?" }
2661 sub session_run_result {
2662     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2663     $kernel->sig(CHLD => "child_reap");
2666 sub session_run_debug {
2667     my $result = $_[ARG0];
2668     print STDERR "$result\n";
2671 sub session_run_done {
2672     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2673     delete $heap->{task}->{$task_id};
2677 sub create_sources_list {
2678         my $session_id = shift;
2679         my $ldap_handle = &main::get_ldap_handle;
2680         my $result="/tmp/gosa_si_tmp_sources_list";
2682         # Remove old file
2683         if(stat($result)) {
2684                 unlink($result);
2685                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2686         }
2688         my $fh;
2689         open($fh, ">$result");
2690         if (not defined $fh) {
2691                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2692                 return undef;
2693         }
2694         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2695                 my $mesg=$ldap_handle->search(
2696                         base    => $main::ldap_server_dn,
2697                         scope   => 'base',
2698                         attrs   => 'FAIrepository',
2699                         filter  => 'objectClass=FAIrepositoryServer'
2700                 );
2701                 if($mesg->count) {
2702                         foreach my $entry(@{$mesg->{'entries'}}) {
2703                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2704                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2705                                         my $line = "deb $server $release";
2706                                         $sections =~ s/,/ /g;
2707                                         $line.= " $sections";
2708                                         print $fh $line."\n";
2709                                 }
2710                         }
2711                 }
2712         } else {
2713                 if (defined $main::ldap_server_dn){
2714                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2715                 } else {
2716                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2717                 }
2718         }
2719         close($fh);
2721         return $result;
2725 sub run_create_packages_list_db {
2726     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2727         my $session_id = $session->ID;
2729         my $task = POE::Wheel::Run->new(
2730                                         Priority => +20,
2731                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2732                                         StdoutEvent  => "session_run_result",
2733                                         StderrEvent  => "session_run_debug",
2734                                         CloseEvent   => "session_run_done",
2735                                         );
2736         $heap->{task}->{ $task->ID } = $task;
2740 sub create_packages_list_db {
2741         my ($ldap_handle, $sources_file, $session_id) = @_;
2742         
2743         # it should not be possible to trigger a recreation of packages_list_db
2744         # while packages_list_db is under construction, so set flag packages_list_under_construction
2745         # which is tested befor recreation can be started
2746         if (-r $packages_list_under_construction) {
2747                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2748                 return;
2749         } else {
2750                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2751                 # set packages_list_under_construction to true
2752                 system("touch $packages_list_under_construction");
2753                 @packages_list_statements=();
2754         }
2756         if (not defined $session_id) { $session_id = 0; }
2757         if (not defined $ldap_handle) { 
2758                 $ldap_handle= &get_ldap_handle();
2760                 if (not defined $ldap_handle) {
2761                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2762                         unlink($packages_list_under_construction);
2763                         return;
2764                 }
2765         }
2766         if (not defined $sources_file) { 
2767                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2768                 $sources_file = &create_sources_list($session_id);
2769         }
2771         if (not defined $sources_file) {
2772                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2773                 unlink($packages_list_under_construction);
2774                 return;
2775         }
2777         my $line;
2779         open(CONFIG, "<$sources_file") or do {
2780                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2781                 unlink($packages_list_under_construction);
2782                 return;
2783         };
2785         # Read lines
2786         while ($line = <CONFIG>){
2787                 # Unify
2788                 chop($line);
2789                 $line =~ s/^\s+//;
2790                 $line =~ s/^\s+/ /;
2792                 # Strip comments
2793                 $line =~ s/#.*$//g;
2795                 # Skip empty lines
2796                 if ($line =~ /^\s*$/){
2797                         next;
2798                 }
2800                 # Interpret deb line
2801                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2802                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2803                         my $section;
2804                         foreach $section (split(' ', $sections)){
2805                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2806                         }
2807                 }
2808         }
2810         close (CONFIG);
2812         if(keys(%repo_dirs)) {
2813                 find(\&cleanup_and_extract, keys( %repo_dirs ));
2814                 &main::strip_packages_list_statements();
2815                 $packages_list_db->exec_statementlist(\@packages_list_statements);
2816         }
2817         unlink($packages_list_under_construction);
2818         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2819         return;
2822 # This function should do some intensive task to minimize the db-traffic
2823 sub strip_packages_list_statements {
2824         my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2825         my @new_statement_list=();
2826         my $hash;
2827         my $insert_hash;
2828         my $update_hash;
2829         my $delete_hash;
2830         my $known_packages_hash;
2831         my $local_timestamp=get_time();
2833         foreach my $existing_entry (@existing_entries) {
2834                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2835         }
2837         foreach my $statement (@packages_list_statements) {
2838                 if($statement =~ /^INSERT/i) {
2839                         # Assign the values from the insert statement
2840                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2841                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2842                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2843                                 # If section or description has changed, update the DB
2844                                 if( 
2845                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2846                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2847                                 ) {
2848                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2849                                 } else {
2850                                         # package is already present in database. cache this knowledge for later use
2851                                         @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2852                                 }
2853                         } else {
2854                                 # Insert a non-existing entry to db
2855                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2856                         }
2857                 } elsif ($statement =~ /^UPDATE/i) {
2858                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2859                         /^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;
2860                         foreach my $distribution (keys %{$hash}) {
2861                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2862                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2863                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2864                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2865                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2866                                                 my $section;
2867                                                 my $description;
2868                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2869                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2870                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2871                                                 }
2872                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2873                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2874                                                 }
2875                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2876                                         }
2877                                 }
2878                         }
2879                 }
2880         }
2882         # Check for orphaned entries
2883         foreach my $existing_entry (@existing_entries) {
2884                 my $distribution= @{$existing_entry}[0];
2885                 my $package= @{$existing_entry}[1];
2886                 my $version= @{$existing_entry}[2];
2887                 my $section= @{$existing_entry}[3];
2889                 if(
2890                         exists($insert_hash->{$distribution}->{$package}->{$version}) ||
2891                         exists($update_hash->{$distribution}->{$package}->{$version}) ||
2892                         exists($known_packages_hash->{$distribution}->{$package}->{$version})
2893                 ) {
2894                         next;
2895                 } else {
2896                         # Insert entry to delete hash
2897                         @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
2898                 }
2899         }
2901         # unroll the insert hash
2902         foreach my $distribution (keys %{$insert_hash}) {
2903                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2904                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2905                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2906                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2907                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2908                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2909                                 ."'$local_timestamp')";
2910                         }
2911                 }
2912         }
2914         # unroll the update hash
2915         foreach my $distribution (keys %{$update_hash}) {
2916                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2917                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2918                                 my $set = "";
2919                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2920                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2921                                 }
2922                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2923                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2924                                 }
2925                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2926                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2927                                 }
2928                                 if(defined($set) and length($set) > 0) {
2929                                         $set .= "timestamp = '$local_timestamp'";
2930                                 } else {
2931                                         next;
2932                                 }
2933                                 push @new_statement_list, 
2934                                 "UPDATE $main::packages_list_tn SET $set WHERE"
2935                                 ." distribution = '$distribution'"
2936                                 ." AND package = '$package'"
2937                                 ." AND version = '$version'";
2938                         }
2939                 }
2940         }
2941         
2942         # unroll the delete hash
2943         foreach my $distribution (keys %{$delete_hash}) {
2944                 foreach my $package (keys %{$delete_hash->{$distribution}}) {
2945                         foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
2946                                 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
2947                                 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
2948                         }
2949                 }
2950         }
2952         @packages_list_statements = @new_statement_list;
2956 sub parse_package_info {
2957     my ($baseurl, $dist, $section, $session_id)= @_;
2958     my ($package);
2959     if (not defined $session_id) { $session_id = 0; }
2960     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2961     $repo_dirs{ "${repo_path}/pool" } = 1;
2963     foreach $package ("Packages.gz"){
2964         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2965         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2966         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2967     }
2968     
2972 sub get_package {
2973     my ($url, $dest, $session_id)= @_;
2974     if (not defined $session_id) { $session_id = 0; }
2976     my $tpath = dirname($dest);
2977     -d "$tpath" || mkpath "$tpath";
2979     # This is ugly, but I've no time to take a look at "how it works in perl"
2980     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2981         system("gunzip -cd '$dest' > '$dest.in'");
2982         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2983         unlink($dest);
2984         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2985     } else {
2986         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2987     }
2988     return 0;
2992 sub parse_package {
2993     my ($path, $dist, $srv_path, $session_id)= @_;
2994     if (not defined $session_id) { $session_id = 0;}
2995     my ($package, $version, $section, $description);
2996     my $PACKAGES;
2997     my $timestamp = &get_time();
2999     if(not stat("$path.in")) {
3000         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3001         return;
3002     }
3004     open($PACKAGES, "<$path.in");
3005     if(not defined($PACKAGES)) {
3006         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
3007         return;
3008     }
3010     # Read lines
3011     while (<$PACKAGES>){
3012         my $line = $_;
3013         # Unify
3014         chop($line);
3016         # Use empty lines as a trigger
3017         if ($line =~ /^\s*$/){
3018             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3019             push(@packages_list_statements, $sql);
3020             $package = "none";
3021             $version = "none";
3022             $section = "none";
3023             $description = "none"; 
3024             next;
3025         }
3027         # Trigger for package name
3028         if ($line =~ /^Package:\s/){
3029             ($package)= ($line =~ /^Package: (.*)$/);
3030             next;
3031         }
3033         # Trigger for version
3034         if ($line =~ /^Version:\s/){
3035             ($version)= ($line =~ /^Version: (.*)$/);
3036             next;
3037         }
3039         # Trigger for description
3040         if ($line =~ /^Description:\s/){
3041             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3042             next;
3043         }
3045         # Trigger for section
3046         if ($line =~ /^Section:\s/){
3047             ($section)= ($line =~ /^Section: (.*)$/);
3048             next;
3049         }
3051         # Trigger for filename
3052         if ($line =~ /^Filename:\s/){
3053             my ($filename) = ($line =~ /^Filename: (.*)$/);
3054             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3055             next;
3056         }
3057     }
3059     close( $PACKAGES );
3060     unlink( "$path.in" );
3064 sub store_fileinfo {
3065     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3067     my %fileinfo = (
3068         'package' => $package,
3069         'dist' => $dist,
3070         'version' => $vers,
3071     );
3073     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3077 sub cleanup_and_extract {
3078         my $fileinfo = $repo_files{ $File::Find::name };
3080         if( defined $fileinfo ) {
3081                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3082                 my $sql;
3083                 my $package = $fileinfo->{ 'package' };
3084                 my $newver = $fileinfo->{ 'version' };
3086                 mkpath($dir);
3087                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3089                 if( -f "$dir/DEBIAN/templates" ) {
3091                         daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3093                         my $tmpl= ""; {
3094                                 local $/=undef;
3095                                 open FILE, "$dir/DEBIAN/templates";
3096                                 $tmpl = &encode_base64(<FILE>);
3097                                 close FILE;
3098                         }
3099                         rmtree("$dir/DEBIAN/templates");
3101                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3102                         push @packages_list_statements, $sql;
3103                 }
3104         }
3106         return;
3110 sub register_at_foreign_servers {   
3111     my ($kernel) = $_[KERNEL];
3113     # hole alle bekannten server aus known_server_db
3114     my $server_sql = "SELECT * FROM $known_server_tn";
3115     my $server_res = $known_server_db->exec_statement($server_sql);
3117     # no entries in known_server_db
3118     if (not ref(@$server_res[0]) eq "ARRAY") { 
3119         # TODO
3120     }
3122     # detect already connected clients
3123     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3124     my $client_res = $known_clients_db->exec_statement($client_sql);
3126     # send my server details to all other gosa-si-server within the network
3127     foreach my $hit (@$server_res) {
3128         my $hostname = @$hit[0];
3129         my $hostkey = &create_passwd;
3131         # add already connected clients to registration message 
3132         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3133         &add_content2xml_hash($myhash, 'key', $hostkey);
3134         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3136         # add locally loaded gosa-si modules to registration message
3137         my $loaded_modules = {};
3138         while (my ($package, $pck_info) = each %$known_modules) {
3139                                                 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3140                                                 foreach my $act_module (keys(%{@$pck_info[2]})) {
3141                                                         $loaded_modules->{$act_module} = ""; 
3142                                                 }
3143         }
3145         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3147         # add macaddress to registration message
3148         my ($host_ip, $host_port) = split(/:/, $hostname);
3149         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3150         my $network_interface= &get_interface_for_ip($local_ip);
3151         my $host_mac = &get_mac_for_interface($network_interface);
3152         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3153         
3154         # build registration message and send it
3155         my $foreign_server_msg = &create_xml_string($myhash);
3156         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3157     }
3158     
3159     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
3160     return;
3164 #==== MAIN = main ==============================================================
3165 #  parse commandline options
3166 Getopt::Long::Configure( "bundling" );
3167 GetOptions("h|help" => \&usage,
3168         "c|config=s" => \$cfg_file,
3169         "f|foreground" => \$foreground,
3170         "v|verbose+" => \$verbose,
3171         "no-arp+" => \$no_arp,
3172            );
3174 #  read and set config parameters
3175 &check_cmdline_param ;
3176 &read_configfile($cfg_file, %cfg_defaults);
3177 &check_pid;
3179 $SIG{CHLD} = 'IGNORE';
3181 # forward error messages to logfile
3182 if( ! $foreground ) {
3183   open( STDIN,  '+>/dev/null' );
3184   open( STDOUT, '+>&STDIN'    );
3185   open( STDERR, '+>&STDIN'    );
3188 # Just fork, if we are not in foreground mode
3189 if( ! $foreground ) { 
3190     chdir '/'                 or die "Can't chdir to /: $!";
3191     $pid = fork;
3192     setsid                    or die "Can't start a new session: $!";
3193     umask 0;
3194 } else { 
3195     $pid = $$; 
3198 # Do something useful - put our PID into the pid_file
3199 if( 0 != $pid ) {
3200     open( LOCK_FILE, ">$pid_file" );
3201     print LOCK_FILE "$pid\n";
3202     close( LOCK_FILE );
3203     if( !$foreground ) { 
3204         exit( 0 ) 
3205     };
3208 # parse head url and revision from svn
3209 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3210 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3211 $server_headURL = defined $1 ? $1 : 'unknown' ;
3212 $server_revision = defined $2 ? $2 : 'unknown' ;
3213 if ($server_headURL =~ /\/tag\// || 
3214         $server_headURL =~ /\/branches\// ) {
3215     $server_status = "stable"; 
3216 } else {
3217     $server_status = "developmental" ;
3220 # Prepare log file
3221 $root_uid = getpwnam('root');
3222 $adm_gid = getgrnam('adm');
3223 chmod(0640, $log_file);
3224 chown($root_uid, $adm_gid, $log_file);
3225 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3227 daemon_log(" ", 1);
3228 daemon_log("$0 started!", 1);
3229 daemon_log("status: $server_status", 1);
3230 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3233     no strict "refs";
3235     if ($db_module eq "DBmysql") {
3236         # connect to incoming_db
3237         $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3239         # connect to gosa-si job queue
3240         $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3242         # connect to known_clients_db
3243         $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3245         # connect to foreign_clients_db
3246         $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3248         # connect to known_server_db
3249         $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3251         # connect to login_usr_db
3252         $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3254         # connect to fai_server_db 
3255         $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3257         # connect to fai_release_db
3258         $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3260         # connect to packages_list_db
3261         $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3263         # connect to messaging_db
3264         $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3266     } elsif ($db_module eq "DBsqlite") {
3267         # connect to incoming_db
3268         unlink($incoming_file_name);
3269         $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3270         
3271         # connect to gosa-si job queue
3272         unlink($job_queue_file_name);  ## just for debugging
3273         $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3274         chmod(0660, $job_queue_file_name);
3275         chown($root_uid, $adm_gid, $job_queue_file_name);
3276         
3277         # connect to known_clients_db
3278         unlink($known_clients_file_name);   ## just for debugging
3279         $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3280         chmod(0660, $known_clients_file_name);
3281         chown($root_uid, $adm_gid, $known_clients_file_name);
3282         
3283         # connect to foreign_clients_db
3284         unlink($foreign_clients_file_name);
3285         $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3286         chmod(0660, $foreign_clients_file_name);
3287         chown($root_uid, $adm_gid, $foreign_clients_file_name);
3288         
3289         # connect to known_server_db
3290         unlink($known_server_file_name);
3291         $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3292         chmod(0660, $known_server_file_name);
3293         chown($root_uid, $adm_gid, $known_server_file_name);
3294         
3295         # connect to login_usr_db
3296         unlink($login_users_file_name);
3297         $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3298         chmod(0660, $login_users_file_name);
3299         chown($root_uid, $adm_gid, $login_users_file_name);
3300         
3301         # connect to fai_server_db
3302         unlink($fai_server_file_name);
3303         $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3304         chmod(0660, $fai_server_file_name);
3305         chown($root_uid, $adm_gid, $fai_server_file_name);
3306         
3307         # connect to fai_release_db
3308         unlink($fai_release_file_name);
3309         $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3310         chmod(0660, $fai_release_file_name);
3311         chown($root_uid, $adm_gid, $fai_release_file_name);
3312         
3313         # connect to packages_list_db
3314         #unlink($packages_list_file_name);
3315         unlink($packages_list_under_construction);
3316         $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3317         chmod(0660, $packages_list_file_name);
3318         chown($root_uid, $adm_gid, $packages_list_file_name);
3319         
3320         # connect to messaging_db
3321         unlink($messaging_file_name);
3322         $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3323         chmod(0660, $messaging_file_name);
3324         chown($root_uid, $adm_gid, $messaging_file_name);
3325     }
3328 # Creating tables
3329 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3330 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3331 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3332 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3333 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3334 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3335 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3336 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3337 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3338 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3341 # create xml object used for en/decrypting
3342 $xml = new XML::Simple();
3345 # foreign servers 
3346 my @foreign_server_list;
3348 # add foreign server from cfg file
3349 if ($foreign_server_string ne "") {
3350     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3351     foreach my $foreign_server (@cfg_foreign_server_list) {
3352         push(@foreign_server_list, $foreign_server);
3353     }
3355     daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3358 # Perform a DNS lookup for server registration if flag is true
3359 if ($dns_lookup eq "true") {
3360     # Add foreign server from dns
3361     my @tmp_servers;
3362     if (not $server_domain) {
3363         # Try our DNS Searchlist
3364         for my $domain(get_dns_domains()) {
3365             chomp($domain);
3366             my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3367             if(@$tmp_domains) {
3368                 for my $tmp_server(@$tmp_domains) {
3369                     push @tmp_servers, $tmp_server;
3370                 }
3371             }
3372         }
3373         if(@tmp_servers && length(@tmp_servers)==0) {
3374             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3375         }
3376     } else {
3377         @tmp_servers = &get_server_addresses($server_domain);
3378         if( 0 == @tmp_servers ) {
3379             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3380         }
3381     }
3383     daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3385     foreach my $server (@tmp_servers) { 
3386         unshift(@foreign_server_list, $server); 
3387     }
3388 } else {
3389     daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3393 # eliminate duplicate entries
3394 @foreign_server_list = &del_doubles(@foreign_server_list);
3395 my $all_foreign_server = join(", ", @foreign_server_list);
3396 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3398 # add all found foreign servers to known_server
3399 my $act_timestamp = &get_time();
3400 foreach my $foreign_server (@foreign_server_list) {
3402         # do not add myself to known_server_db
3403         if (&is_local($foreign_server)) { next; }
3404         ######################################
3406     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3407             primkey=>['hostname'],
3408             hostname=>$foreign_server,
3409             macaddress=>"",
3410             status=>'not_jet_registered',
3411             hostkey=>"none",
3412             loaded_modules => "none", 
3413             timestamp=>$act_timestamp,
3414             } );
3418 # Import all modules
3419 &import_modules;
3421 # Check wether all modules are gosa-si valid passwd check
3422 &password_check;
3424 # Prepare for using Opsi 
3425 if ($opsi_enabled eq "true") {
3426     use JSON::RPC::Client;
3427     use XML::Quote qw(:all);
3428     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3429     $opsi_client = new JSON::RPC::Client;
3433 POE::Component::Server::TCP->new(
3434         Alias => "TCP_SERVER",
3435         Port => $server_port,
3436         ClientInput => sub {
3437                 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3438         my $session_id = $session->ID;
3439         my $remote_ip = $heap->{'remote_ip'};
3440                 push(@msgs_to_decrypt, $input);
3441         &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3442                 $kernel->yield("msg_to_decrypt");
3443         },
3444         InlineStates => {
3445                 msg_to_decrypt => \&msg_to_decrypt,
3446                 next_task => \&next_task,
3447                 task_result => \&handle_task_result,
3448                 task_done   => \&handle_task_done,
3449                 task_debug  => \&handle_task_debug,
3450                 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3451         }
3452 );
3454 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3456 # create session for repeatedly checking the job queue for jobs
3457 POE::Session->create(
3458         inline_states => {
3459                 _start => \&session_start,
3460         register_at_foreign_servers => \&register_at_foreign_servers,
3461         sig_handler => \&sig_handler,
3462         next_task => \&next_task,
3463         task_result => \&handle_task_result,
3464         task_done   => \&handle_task_done,
3465         task_debug  => \&handle_task_debug,
3466         watch_for_next_tasks => \&watch_for_next_tasks,
3467         watch_for_new_messages => \&watch_for_new_messages,
3468         watch_for_delivery_messages => \&watch_for_delivery_messages,
3469         watch_for_done_messages => \&watch_for_done_messages,
3470                 watch_for_new_jobs => \&watch_for_new_jobs,
3471         watch_for_modified_jobs => \&watch_for_modified_jobs,
3472         watch_for_done_jobs => \&watch_for_done_jobs,
3473         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3474         watch_for_old_known_clients => \&watch_for_old_known_clients,
3475         create_packages_list_db => \&run_create_packages_list_db,
3476         create_fai_server_db => \&run_create_fai_server_db,
3477         create_fai_release_db => \&run_create_fai_release_db,
3478                 recreate_packages_db => \&run_recreate_packages_db,
3479         session_run_result => \&session_run_result,
3480         session_run_debug => \&session_run_debug,
3481         session_run_done => \&session_run_done,
3482         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3483         }
3484 );
3487 POE::Kernel->run();
3488 exit;