Code

d4361aad2513db83618d8b2de66c320cf4a9f9b8
[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 # revision number of server and program name
58 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
59 my $server_headURL;
60 my $server_revision;
61 my $server_status;
62 our $prg= basename($0);
64 my $db_module = "DBsqlite";
65 {
66 no strict "refs";
67 require ("GOSA/".$db_module.".pm");
68 ("GOSA/".$db_module)->import;
69 daemon_log("0 INFO: importing database module '$db_module'", 1);
70 }
72 my $modules_path = "/usr/lib/gosa-si/modules";
73 use lib "/usr/lib/gosa-si/modules";
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)", "regserver VARCHAR(255) DEFAULT 'localhost'");
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 send_msg_to_target {
872     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
873     my $error = 0;
874     my $header;
875     my $timestamp = &get_time();
876     my $new_status;
877     my $act_status;
878     my ($sql_statement, $res);
879   
880     if( $msg_header ) {
881         $header = "'$msg_header'-";
882     } else {
883         $header = "";
884     }
886         # Patch the source ip
887         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
888                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
889                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
890         }
892     # encrypt xml msg
893     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
895     # opensocket
896     my $socket = &open_socket($address);
897     if( !$socket ) {
898         daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
899         $error++;
900     }
901     
902     if( $error == 0 ) {
903         # send xml msg
904         print $socket $crypted_msg."\n";
906         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
907         daemon_log("$session_id DEBUG: message:\n$msg", 9);
908         
909     }
911     # close socket in any case
912     if( $socket ) {
913         close $socket;
914     }
916     if( $error > 0 ) { $new_status = "down"; }
917     else { $new_status = $msg_header; }
920     # known_clients
921     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
922     $res = $known_clients_db->select_dbentry($sql_statement);
923     if( keys(%$res) == 1) {
924         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
925         if ($act_status eq "down" && $new_status eq "down") {
926             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
927             $res = $known_clients_db->del_dbentry($sql_statement);
928             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
929         } else { 
930             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
931             $res = $known_clients_db->update_dbentry($sql_statement);
932             if($new_status eq "down"){
933                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
934             } else {
935                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
936             }
937         }
938     }
940     # known_server
941     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
942     $res = $known_server_db->select_dbentry($sql_statement);
943     if( keys(%$res) == 1) {
944         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
945         if ($act_status eq "down" && $new_status eq "down") {
946             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
947             $res = $known_server_db->del_dbentry($sql_statement);
948             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
949         } 
950         else { 
951             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
952             $res = $known_server_db->update_dbentry($sql_statement);
953             if($new_status eq "down"){
954                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
955             } else {
956                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
957             }
958         }
959     }
960     return $error; 
964 sub update_jobdb_status_for_send_msgs {
965     my ($session_id, $answer, $error) = @_;
966     &daemon_log("$session_id DEBUG: try to update job status", 7); 
967     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
968         my $jobdb_id = $1;
969     
970         $answer =~ /<header>(.*)<\/header>/;
971         my $job_header = $1;
973         $answer =~ /<target>(.*)<\/target>/;
974         my $job_target = $1;
975             
976         # Sending msg failed
977         if( $error ) {
979             # Set jobs to done, jobs do not need to deliver their message in any case
980             if (($job_header eq "trigger_action_localboot")
981                     ||($job_header eq "trigger_action_lock")
982                     ||($job_header eq "trigger_action_halt") 
983                     ) {
984                 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
985                 &daemon_log("$session_id DEBUG: $sql_statement", 7); 
986                 my $res = $job_db->update_dbentry($sql_statement);
987                 
988             # Reactivate jobs, jobs need to deliver their message
989             } elsif (($job_header eq "trigger_action_activate")
990                     ||($job_header eq "trigger_action_update")
991                     ||($job_header eq "trigger_action_reinstall") 
992                     ||($job_header eq "trigger_activate_new")
993                     ) {
994                 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
996             # For all other messages
997             } else {
998                 my $sql_statement = "UPDATE $job_queue_tn ".
999                     "SET status='error', result='can not deliver msg, please consult log file' ".
1000                     "WHERE id=$jobdb_id";
1001                 &daemon_log("$session_id DEBUG: $sql_statement", 7); 
1002                 my $res = $job_db->update_dbentry($sql_statement);
1003             }
1005         # Sending msg was successful
1006         } else {
1007             # Set jobs localboot, lock, activate, halt, reboot and wake to done
1008             # jobs reinstall, update, inst_update do themself setting to done
1009             if (($job_header eq "trigger_action_localboot")
1010                     ||($job_header eq "trigger_action_lock")
1011                     ||($job_header eq "trigger_action_activate")
1012                     ||($job_header eq "trigger_action_halt") 
1013                     ||($job_header eq "trigger_action_reboot")
1014                     ||($job_header eq "trigger_action_wake")
1015                     ||($job_header eq "trigger_wake")
1016                     ) {
1018                 my $sql_statement = "UPDATE $job_queue_tn ".
1019                     "SET status='done' ".
1020                     "WHERE id=$jobdb_id AND status='processed'";
1021                 &daemon_log("$session_id DEBUG: $sql_statement", 7); 
1022                 my $res = $job_db->update_dbentry($sql_statement);
1023             } else { 
1024                 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 7); 
1025             } 
1026         } 
1027     } else { 
1028         &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag: $answer", 7); 
1029     }
1032 sub reactivate_job_with_delay {
1033     my ($session_id, $target, $header, $delay) = @_ ;
1034     # Sometimes the client is still booting or does not wake up, in this case reactivate the job (if it exists) with a delay of n sec
1035     
1036     if (not defined $delay) { $delay = 30 } ;
1037     my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1039     my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$target' AND headertag='$header')"; 
1040     my $res = $job_db->update_dbentry($sql);
1041     daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1042             "cause client '$target' is currently not available", 5);
1043     daemon_log("$session_id $sql", 7);                             
1044     return;
1048 sub sig_handler {
1049         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1050         daemon_log("0 INFO got signal '$signal'", 1); 
1051         $kernel->sig_handled();
1052         return;
1056 sub msg_to_decrypt {
1057         my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1058         my $session_id = $session->ID;
1059         my ($msg, $msg_hash, $module);
1060         my $error = 0;
1062         # fetch new msg out of @msgs_to_decrypt
1063         my $tmp_next_msg = shift @msgs_to_decrypt;
1064     my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1066         # msg is from a new client or gosa
1067         ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1069         # msg is from a gosa-si-server
1070         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1071                 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1072         }
1073         # msg is from a gosa-si-client
1074         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1075                 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1076         }
1077         # an error occurred
1078         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1079                 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1080                 # could not understand a msg from its server the client cause a re-registering process
1081         my $remote_ip = $heap->{'remote_ip'};
1082         my $remote_port = $heap->{'remote_port'};
1083         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1084         my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1086                 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1087                         "' to cause a re-registering of the client if necessary", 3);
1088                 $error++;
1089         }
1092         my $header;
1093         my $target;
1094         my $source;
1095         my $done = 0;
1096         my $sql;
1097         my $res;
1099         # check whether this message should be processed here
1100         if ($error == 0) {
1101                 $header = @{$msg_hash->{'header'}}[0];
1102                 $target = @{$msg_hash->{'target'}}[0];
1103                 $source = @{$msg_hash->{'source'}}[0];
1104                 my $not_found_in_known_clients_db = 0;
1105                 my $not_found_in_known_server_db = 0;
1106                 my $not_found_in_foreign_clients_db = 0;
1107                 my $local_address;
1108                 my $local_mac;
1109                 my ($target_ip, $target_port) = split(':', $target);
1111                 # Determine the local ip address if target is an ip address
1112                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1113                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1114                 } else {
1115                         $local_address = $server_address;
1116                 }
1118                 # Determine the local mac address if target is a mac address
1119                 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) {
1120                         my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1121                         my $network_interface= &get_interface_for_ip($loc_ip);
1122                         $local_mac = &get_mac_for_interface($network_interface);
1123                 } else {
1124                         $local_mac = $server_mac_address;
1125                 }
1127                 # target and source is equal to GOSA -> process here
1128                 if (not $done) {
1129                         if ($target eq "GOSA" && $source eq "GOSA") {
1130                                 $done = 1;                    
1131                                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1132                         }
1133                 }
1135                 # target is own address without forward_to_gosa-tag -> process here
1136                 if (not $done) {
1137                         #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1138                         if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1139                                 $done = 1;
1140                                 if ($source eq "GOSA") {
1141                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1142                                 }
1143                                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1144                         }
1145                 }
1147                 # target is a client address in known_clients -> process here
1148                 if (not $done) {
1149                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1150                         $res = $known_clients_db->select_dbentry($sql);
1151                         if (keys(%$res) > 0) {
1152                                 $done = 1; 
1153                                 my $hostname = $res->{1}->{'hostname'};
1154                                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1155                                 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1156                                 if ($source eq "GOSA") {
1157                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1158                                 }
1159                                 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1161                         } else {
1162                                 $not_found_in_known_clients_db = 1;
1163                         }
1164                 }
1166                 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1167                 if (not $done) {
1168                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1169                         my $gosa_at;
1170                         my $gosa_session_id;
1171                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1172                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1173                                 if ($gosa_at ne $local_address) {
1174                                         $done = 1;
1175                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7); 
1176                                 }
1177                         }
1178                 }
1180                 # if message should be processed here -> add message to incoming_db
1181                 if ($done) {
1182                         # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1183                         # so gosa-si-server knows how to process this kind of messages
1184                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1185                                 $module = "GosaPackages";
1186                         }
1188                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1189                                         primkey=>[],
1190                                         headertag=>$header,
1191                                         targettag=>$target,
1192                                         xmlmessage=>&encode_base64($msg),
1193                                         timestamp=>&get_time,
1194                                         module=>$module,
1195                                         sessionid=>$session_id,
1196                                 } );
1198                 }
1200                 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1201                 if (not $done) {
1202                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1203                         my $gosa_at;
1204                         my $gosa_session_id;
1205                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1206                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1207                                 if ($gosa_at eq $local_address) {
1208                                         my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1209                                         if( defined $session_reference ) {
1210                                                 $heap = $session_reference->get_heap();
1211                                         }
1212                                         if(exists $heap->{'client'}) {
1213                                                 $msg = &encrypt_msg($msg, $GosaPackages_key);
1214                                                 $heap->{'client'}->put($msg);
1215                                                 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5); 
1216                                         }
1217                                         $done = 1;
1218                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1219                                 }
1220                         }
1222                 }
1224                 # target is a client address in foreign_clients -> forward to registration server
1225                 if (not $done) {
1226                         $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1227                         $res = $foreign_clients_db->select_dbentry($sql);
1228                         if (keys(%$res) > 0) {
1229                                 my $hostname = $res->{1}->{'hostname'};
1230                                 my ($host_ip, $host_port) = split(/:/, $hostname);
1231                                 my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1232                                 my $regserver = $res->{1}->{'regserver'};
1233                                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1234                                 my $res = $known_server_db->select_dbentry($sql);
1235                                 if (keys(%$res) > 0) {
1236                                         my $regserver_key = $res->{1}->{'hostkey'};
1237                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1238                                         $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1239                                         if ($source eq "GOSA") {
1240                                                 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1241                                         }
1242                                         &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1243                                 }
1244                                 $done = 1;
1245                                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1246                         } else {
1247                                 $not_found_in_foreign_clients_db = 1;
1248                         }
1249                 }
1251                 # target is a server address -> forward to server
1252                 if (not $done) {
1253                         $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1254                         $res = $known_server_db->select_dbentry($sql);
1255                         if (keys(%$res) > 0) {
1256                                 my $hostkey = $res->{1}->{'hostkey'};
1258                                 if ($source eq "GOSA") {
1259                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1260                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1262                                 }
1264                                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1265                                 $done = 1;
1266                                 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1267                         } else {
1268                                 $not_found_in_known_server_db = 1;
1269                         }
1270                 }
1273                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1274                 if ( $not_found_in_foreign_clients_db 
1275                         && $not_found_in_known_server_db
1276                         && $not_found_in_known_clients_db) {
1277                         &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);
1278             if ($header =~ /^gosa_/ || $header =~ /^job_/) { 
1279                 $module = "GosaPackages"; 
1280             }
1281                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1282                                         primkey=>[],
1283                                         headertag=>$header,
1284                                         targettag=>$target,
1285                                         xmlmessage=>&encode_base64($msg),
1286                                         timestamp=>&get_time,
1287                                         module=>$module,
1288                                         sessionid=>$session_id,
1289                                 } );
1290                         $done = 1;
1291                 }
1294                 if (not $done) {
1295                         daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1296                         if ($source eq "GOSA") {
1297                                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1298                                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1300                                 my $session_reference = $kernel->ID_id_to_session($session_id);
1301                                 if( defined $session_reference ) {
1302                                         $heap = $session_reference->get_heap();
1303                                 }
1304                                 if(exists $heap->{'client'}) {
1305                                         $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1306                                         $heap->{'client'}->put($error_msg);
1307                                 }
1308                         }
1309                 }
1311         }
1313         return;
1317 sub next_task {
1318     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1319     my $running_task = POE::Wheel::Run->new(
1320             Program => sub { process_task($session, $heap, $task) },
1321             StdioFilter => POE::Filter::Reference->new(),
1322             StdoutEvent  => "task_result",
1323             StderrEvent  => "task_debug",
1324             CloseEvent   => "task_done",
1325             );
1326     $heap->{task}->{ $running_task->ID } = $running_task;
1329 sub handle_task_result {
1330     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1331     my $client_answer = $result->{'answer'};
1332     if( $client_answer =~ s/session_id=(\d+)$// ) {
1333         my $session_id = $1;
1334         if( defined $session_id ) {
1335             my $session_reference = $kernel->ID_id_to_session($session_id);
1336             if( defined $session_reference ) {
1337                 $heap = $session_reference->get_heap();
1338             }
1339         }
1341         if(exists $heap->{'client'}) {
1342             $heap->{'client'}->put($client_answer);
1343         }
1344     }
1345     $kernel->sig(CHLD => "child_reap");
1348 sub handle_task_debug {
1349     my $result = $_[ARG0];
1350     print STDERR "$result\n";
1353 sub handle_task_done {
1354     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1355     delete $heap->{task}->{$task_id};
1358 sub process_task {
1359     no strict "refs";
1360     #CHECK: Not @_[...]?
1361     my ($session, $heap, $task) = @_;
1362     my $error = 0;
1363     my $answer_l;
1364     my ($answer_header, @answer_target_l, $answer_source);
1365     my $client_answer = "";
1367     # prepare all variables needed to process message
1368     #my $msg = $task->{'xmlmessage'};
1369     my $msg = &decode_base64($task->{'xmlmessage'});
1370     my $incoming_id = $task->{'id'};
1371     my $module = $task->{'module'};
1372     my $header =  $task->{'headertag'};
1373     my $session_id = $task->{'sessionid'};
1374                 my $msg_hash;
1375                 eval {
1376         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1377                 }; 
1378                 daemon_log("ERROR: XML failure '$@'") if ($@);
1379     my $source = @{$msg_hash->{'source'}}[0];
1380     
1381     # set timestamp of incoming client uptodate, so client will not 
1382     # be deleted from known_clients because of expiration
1383     my $act_time = &get_time();
1384     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1385     my $res = $known_clients_db->exec_statement($sql);
1387     ######################
1388     # process incoming msg
1389     if( $error == 0) {
1390         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1391         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1392         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1394         if ( 0 < @{$answer_l} ) {
1395             my $answer_str = join("\n", @{$answer_l});
1396             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1397                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1398             }
1399             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1400         } else {
1401             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1402         }
1404     }
1405     if( !$answer_l ) { $error++ };
1407     ########
1408     # answer
1409     if( $error == 0 ) {
1411         foreach my $answer ( @{$answer_l} ) {
1412             # check outgoing msg to xml validity
1413             my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1414             if( not defined $answer_hash ) { next; }
1415             
1416             $answer_header = @{$answer_hash->{'header'}}[0];
1417             @answer_target_l = @{$answer_hash->{'target'}};
1418             $answer_source = @{$answer_hash->{'source'}}[0];
1420             # deliver msg to all targets 
1421             foreach my $answer_target ( @answer_target_l ) {
1423                 # targets of msg are all gosa-si-clients in known_clients_db
1424                 if( $answer_target eq "*" ) {
1425                     # answer is for all clients
1426                     my $sql_statement= "SELECT * FROM known_clients";
1427                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1428                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1429                         my $host_name = $hit->{hostname};
1430                         my $host_key = $hit->{hostkey};
1431                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1432                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1433                     }
1434                 }
1436                 # targets of msg are all gosa-si-server in known_server_db
1437                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1438                     # answer is for all server in known_server
1439                     my $sql_statement= "SELECT * FROM $known_server_tn";
1440                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1441                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1442                         my $host_name = $hit->{hostname};
1443                         my $host_key = $hit->{hostkey};
1444                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1445                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1446                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1447                     }
1448                 }
1450                 # target of msg is GOsa
1451                                 elsif( $answer_target eq "GOSA" ) {
1452                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1453                                         my $add_on = "";
1454                     if( defined $session_id ) {
1455                         $add_on = ".session_id=$session_id";
1456                     }
1457                     # answer is for GOSA and has to returned to connected client
1458                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1459                     $client_answer = $gosa_answer.$add_on;
1460                 }
1462                 # target of msg is job queue at this host
1463                 elsif( $answer_target eq "JOBDB") {
1464                     $answer =~ /<header>(\S+)<\/header>/;   
1465                     my $header;
1466                     if( defined $1 ) { $header = $1; }
1467                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1468                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1469                 }
1471                 # Target of msg is a mac address
1472                 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 ) {
1473                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1475                     # Looking for macaddress in known_clients
1476                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1477                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1478                     my $found_ip_flag = 0;
1479                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1480                         my $host_name = $hit->{hostname};
1481                         my $host_key = $hit->{hostkey};
1482                         $answer =~ s/$answer_target/$host_name/g;
1483                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1484                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1485                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1486                         $found_ip_flag++ ;
1487                     }   
1489                     # Looking for macaddress in foreign_clients
1490                     if ($found_ip_flag == 0) {
1491                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1492                         my $res = $foreign_clients_db->select_dbentry($sql);
1493                         while( my ($hit_num, $hit) = each %{ $res } ) {
1494                             my $host_name = $hit->{hostname};
1495                             my $reg_server = $hit->{regserver};
1496                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1497                             
1498                             # Fetch key for reg_server
1499                             my $reg_server_key;
1500                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1501                             my $res = $known_server_db->select_dbentry($sql);
1502                             if (exists $res->{1}) {
1503                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1504                             } else {
1505                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1506                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1507                                 $reg_server_key = undef;
1508                             }
1510                             # Send answer to server where client is registered
1511                             if (defined $reg_server_key) {
1512                                 $answer =~ s/$answer_target/$host_name/g;
1513                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1514                                 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1515                                 $found_ip_flag++ ;
1516                             }
1517                         }
1518                     }
1520                     # No mac to ip matching found
1521                     if( $found_ip_flag == 0) {
1522                         daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1523                         &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1524                     }
1526                 # Answer is for one specific host   
1527                 } else {
1528                     # get encrypt_key
1529                     my $encrypt_key = &get_encrypt_key($answer_target);
1530                     if( not defined $encrypt_key ) {
1531                         # unknown target
1532                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1533                         next;
1534                     }
1535                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1536                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1537                 }
1538             }
1539         }
1540     }
1542     my $filter = POE::Filter::Reference->new();
1543     my %result = ( 
1544             status => "seems ok to me",
1545             answer => $client_answer,
1546             );
1548     my $output = $filter->put( [ \%result ] );
1549     print @$output;
1554 sub session_start {
1555     my ($kernel) = $_[KERNEL];
1556     $global_kernel = $kernel;
1557     $kernel->yield('register_at_foreign_servers');
1558         $kernel->yield('create_fai_server_db', $fai_server_tn );
1559         $kernel->yield('create_fai_release_db', $fai_release_tn );
1560     $kernel->yield('watch_for_next_tasks');
1561         $kernel->sig(USR1 => "sig_handler");
1562         $kernel->sig(USR2 => "recreate_packages_db");
1563         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1564         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1565     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1566         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1567     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1568         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1569     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1571     # Start opsi check
1572     if ($opsi_enabled eq "true") {
1573         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1574     }
1579 sub watch_for_done_jobs {
1580     #CHECK: $heap for what?
1581     my ($kernel,$heap) = @_[KERNEL, HEAP];
1583     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1584         my $res = $job_db->select_dbentry( $sql_statement );
1586     while( my ($id, $hit) = each %{$res} ) {
1587         my $jobdb_id = $hit->{id};
1588         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1589         my $res = $job_db->del_dbentry($sql_statement); 
1590     }
1592     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1596 sub watch_for_opsi_jobs {
1597     my ($kernel) = $_[KERNEL];
1599     # 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 
1600     # opsi install job is to parse the xml message. There is still the correct header.
1601     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1602         my $res = $job_db->select_dbentry( $sql_statement );
1604     # Ask OPSI for an update of the running jobs
1605     while (my ($id, $hit) = each %$res ) {
1606         # Determine current parameters of the job
1607         my $hostId = $hit->{'plainname'};
1608         my $macaddress = $hit->{'macaddress'};
1609         my $progress = $hit->{'progress'};
1611         my $result= {};
1612         
1613         # For hosts, only return the products that are or get installed
1614         my $callobj;
1615         $callobj = {
1616             method  => 'getProductStates_hash',
1617             params  => [ $hostId ],
1618             id  => 1,
1619         };
1620         
1621         my $hres = $opsi_client->call($opsi_url, $callobj);
1622         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1623         if (not &check_opsi_res($hres)) {
1624             my $htmp= $hres->result->{$hostId};
1625         
1626             # Check state != not_installed or action == setup -> load and add
1627             my $products= 0;
1628             my $installed= 0;
1629             my $installing = 0;
1630             my $error= 0;  
1631             my @installed_list;
1632             my @error_list;
1633             my $act_status = "none";
1634             foreach my $product (@{$htmp}){
1636                 if ($product->{'installationStatus'} ne "not_installed" or
1637                         $product->{'actionRequest'} eq "setup"){
1639                     # Increase number of products for this host
1640                     $products++;
1641         
1642                     if ($product->{'installationStatus'} eq "failed"){
1643                         $result->{$product->{'productId'}}= "error";
1644                         unshift(@error_list, $product->{'productId'});
1645                         $error++;
1646                     }
1647                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1648                         $result->{$product->{'productId'}}= "installed";
1649                         unshift(@installed_list, $product->{'productId'});
1650                         $installed++;
1651                     }
1652                     if ($product->{'installationStatus'} eq "installing"){
1653                         $result->{$product->{'productId'}}= "installing";
1654                         $installing++;
1655                         $act_status = "installing - ".$product->{'productId'};
1656                     }
1657                 }
1658             }
1659         
1660             # Estimate "rough" progress, avoid division by zero
1661             if ($products == 0) {
1662                 $result->{'progress'}= 0;
1663             } else {
1664                 $result->{'progress'}= int($installed * 100 / $products);
1665             }
1667             # Set updates in job queue
1668             if ((not $error) && (not $installing) && ($installed)) {
1669                 $act_status = "installed - ".join(", ", @installed_list);
1670             }
1671             if ($error) {
1672                 $act_status = "error - ".join(", ", @error_list);
1673             }
1674             if ($progress ne $result->{'progress'} ) {
1675                 # Updating progress and result 
1676                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1677                 my $update_res = $job_db->update_dbentry($update_statement);
1678             }
1679             if ($progress eq 100) { 
1680                 # Updateing status
1681                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1682                 if ($error) {
1683                     $done_statement .= "status='error'";
1684                 } else {
1685                     $done_statement .= "status='done'";
1686                 }
1687                 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1688                 my $done_res = $job_db->update_dbentry($done_statement);
1689             }
1692         }
1693     }
1695     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1699 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1700 sub watch_for_modified_jobs {
1701     my ($kernel,$heap) = @_[KERNEL, HEAP];
1703     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')"; 
1704     my $res = $job_db->select_dbentry( $sql_statement );
1705     
1706     # if db contains no jobs which should be update, do nothing
1707     if (keys %$res != 0) {
1709         if ($job_synchronization  eq "true") {
1710             # make out of the db result a gosa-si message   
1711             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1712  
1713             # update all other SI-server
1714             &inform_all_other_si_server($update_msg);
1715         }
1717         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1718         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1719         $res = $job_db->update_dbentry($sql_statement);
1720     }
1722     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1726 sub watch_for_new_jobs {
1727         if($watch_for_new_jobs_in_progress == 0) {
1728                 $watch_for_new_jobs_in_progress = 1;
1729                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1731                 # check gosa job quaeue for jobs with executable timestamp
1732                 my $timestamp = &get_time();
1733                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1734                 my $res = $job_db->exec_statement( $sql_statement );
1736                 # Merge all new jobs that would do the same actions
1737                 my @drops;
1738                 my $hits;
1739                 foreach my $hit (reverse @{$res} ) {
1740                         my $macaddress= lc @{$hit}[8];
1741                         my $headertag= @{$hit}[5];
1742                         if(
1743                                 defined($hits->{$macaddress}) &&
1744                                 defined($hits->{$macaddress}->{$headertag}) &&
1745                                 defined($hits->{$macaddress}->{$headertag}[0])
1746                         ) {
1747                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1748                         }
1749                         $hits->{$macaddress}->{$headertag}= $hit;
1750                 }
1752                 # Delete new jobs with a matching job in state 'processing'
1753                 foreach my $macaddress (keys %{$hits}) {
1754                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1755                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1756                                 if(defined($jobdb_id)) {
1757                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1758                                         my $res = $job_db->exec_statement( $sql_statement );
1759                                         foreach my $hit (@{$res}) {
1760                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1761                                         }
1762                                 } else {
1763                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1764                                 }
1765                         }
1766                 }
1768                 # Commit deletion
1769                 $job_db->exec_statementlist(\@drops);
1771                 # Look for new jobs that could be executed
1772                 foreach my $macaddress (keys %{$hits}) {
1774                         # Look if there is an executing job
1775                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1776                         my $res = $job_db->exec_statement( $sql_statement );
1778                         # Skip new jobs for host if there is a processing job
1779                         if(defined($res) and defined @{$res}[0]) {
1780                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1781                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1782                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1783                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1784                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1785                                         if(defined($res_2) and defined @{$res_2}[0]) {
1786                                                 # Set status from goto-activation to 'waiting' and update timestamp
1787                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1788                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&calc_timestamp(&get_time(), 'plus', 30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1789                                         }
1790                                 }
1791                                 next;
1792                         }
1794                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1795                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1796                                 if(defined($jobdb_id)) {
1797                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1799                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1800                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1801                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1803                                         # expect macaddress is unique!!!!!!
1804                                         my $target = $res_hash->{1}->{hostname};
1806                                         # change header
1807                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1809                                         # add sqlite_id
1810                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1812                                         $job_msg =~ /<header>(\S+)<\/header>/;
1813                                         my $header = $1 ;
1814                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");                    
1816                                         # update status in job queue to ...
1817                     # ... 'processing', for jobs: 'reinstall', 'update'
1818                     if (($header =~ /gosa_trigger_action_reinstall/) 
1819                             || ($header =~ /gosa_trigger_activate_new/)
1820                             || ($header =~ /gosa_trigger_action_update/)) {
1821                         my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1822                         my $dbres = $job_db->update_dbentry($sql_statement);
1823                     }
1825                     # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1826                     else {
1827                         my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1828                         my $dbres = $job_db->update_dbentry($sql_statement);
1829                     }
1830                 
1832                                         # We don't want parallel processing
1833                                         last;
1834                                 }
1835                         }
1836                 }
1838                 $watch_for_new_jobs_in_progress = 0;
1839                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1840         }
1844 sub watch_for_new_messages {
1845     my ($kernel,$heap) = @_[KERNEL, HEAP];
1846     my @coll_user_msg;   # collection list of outgoing messages
1847     
1848     # check messaging_db for new incoming messages with executable timestamp
1849     my $timestamp = &get_time();
1850     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1851     my $res = $messaging_db->exec_statement( $sql_statement );
1852         foreach my $hit (@{$res}) {
1854         # create outgoing messages
1855         my $message_to = @{$hit}[3];
1856         # translate message_to to plain login name
1857         my @message_to_l = split(/,/, $message_to);  
1858                 my %receiver_h; 
1859                 foreach my $receiver (@message_to_l) {
1860                         if ($receiver =~ /^u_([\s\S]*)$/) {
1861                                 $receiver_h{$1} = 0;
1862                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1863                                 my $group_name = $1;
1864                                 # fetch all group members from ldap and add them to receiver hash
1865                                 my $ldap_handle = &get_ldap_handle();
1866                                 if (defined $ldap_handle) {
1867                                                 my $mesg = $ldap_handle->search(
1868                                                                                 base => $ldap_base,
1869                                                                                 scope => 'sub',
1870                                                                                 attrs => ['memberUid'],
1871                                                                                 filter => "cn=$group_name",
1872                                                                                 );
1873                                                 if ($mesg->count) {
1874                                                                 my @entries = $mesg->entries;
1875                                                                 foreach my $entry (@entries) {
1876                                                                                 my @receivers= $entry->get_value("memberUid");
1877                                                                                 foreach my $receiver (@receivers) { 
1878                                                                                                 $receiver_h{$receiver} = 0;
1879                                                                                 }
1880                                                                 }
1881                                                 } 
1882                                                 # translating errors ?
1883                                                 if ($mesg->code) {
1884                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1885                                                 }
1886                                 # ldap handle error ?           
1887                                 } else {
1888                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1889                                 }
1890                         } else {
1891                                 my $sbjct = &encode_base64(@{$hit}[1]);
1892                                 my $msg = &encode_base64(@{$hit}[7]);
1893                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1894                         }
1895                 }
1896                 my @receiver_l = keys(%receiver_h);
1898         my $message_id = @{$hit}[0];
1900         #add each outgoing msg to messaging_db
1901         my $receiver;
1902         foreach $receiver (@receiver_l) {
1903             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1904                 "VALUES ('".
1905                 $message_id."', '".    # id
1906                 @{$hit}[1]."', '".     # subject
1907                 @{$hit}[2]."', '".     # message_from
1908                 $receiver."', '".      # message_to
1909                 "none"."', '".         # flag
1910                 "out"."', '".          # direction
1911                 @{$hit}[6]."', '".     # delivery_time
1912                 @{$hit}[7]."', '".     # message
1913                 $timestamp."'".     # timestamp
1914                 ")";
1915             &daemon_log("M DEBUG: $sql_statement", 1);
1916             my $res = $messaging_db->exec_statement($sql_statement);
1917             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1918         }
1920         # set incoming message to flag d=deliverd
1921         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1922         &daemon_log("M DEBUG: $sql_statement", 7);
1923         $res = $messaging_db->update_dbentry($sql_statement);
1924         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1925     }
1927     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1928     return;
1931 sub watch_for_delivery_messages {
1932     my ($kernel, $heap) = @_[KERNEL, HEAP];
1934     # select outgoing messages
1935     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1936     #&daemon_log("0 DEBUG: $sql", 7);
1937     my $res = $messaging_db->exec_statement( $sql_statement );
1938     
1939     # build out msg for each    usr
1940     foreach my $hit (@{$res}) {
1941         my $receiver = @{$hit}[3];
1942         my $msg_id = @{$hit}[0];
1943         my $subject = @{$hit}[1];
1944         my $message = @{$hit}[7];
1946         # resolve usr -> host where usr is logged in
1947         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1948         #&daemon_log("0 DEBUG: $sql", 7);
1949         my $res = $login_users_db->exec_statement($sql);
1951         # receiver is logged in nowhere
1952         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1954         # receiver ist logged in at a client registered at local server
1955                 my $send_succeed = 0;
1956                 foreach my $hit (@$res) {
1957                                 my $receiver_host = @$hit[0];
1958                 my $delivered2host = 0;
1959                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1961                                 # Looking for host in know_clients_db 
1962                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1963                                 my $res = $known_clients_db->exec_statement($sql);
1965                 # Host is known in known_clients_db
1966                 if (ref(@$res[0]) eq "ARRAY") {
1967                     my $receiver_key = @{@{$res}[0]}[2];
1968                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1969                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1970                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1971                     if ($error == 0 ) {
1972                         $send_succeed++ ;
1973                         $delivered2host++ ;
1974                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
1975                     } else {
1976                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
1977                     }
1978                 }
1979                 
1980                 # Message already send, do not need to do anything more, otherwise ...
1981                 if ($delivered2host) { next;}
1982     
1983                 # ...looking for host in foreign_clients_db
1984                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1985                 $res = $foreign_clients_db->exec_statement($sql);
1986   
1987                                 # Host is known in foreign_clients_db 
1988                                 if (ref(@$res[0]) eq "ARRAY") { 
1989                     my $registration_server = @{@{$res}[0]}[2];
1990                     
1991                     # Fetch encryption key for registration server
1992                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1993                     my $res = $known_server_db->exec_statement($sql);
1994                     if (ref(@$res[0]) eq "ARRAY") { 
1995                         my $registration_server_key = @{@{$res}[0]}[3];
1996                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1997                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1998                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
1999                         if ($error == 0 ) {
2000                             $send_succeed++ ;
2001                             $delivered2host++ ;
2002                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
2003                         } else {
2004                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
2005                         }
2007                     } else {
2008                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2009                                 "registrated at server '$registration_server', ".
2010                                 "but no data available in known_server_db ", 1); 
2011                     }
2012                 }
2013                 
2014                 if (not $delivered2host) {
2015                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2016                 }
2017                 }
2019                 if ($send_succeed) {
2020                                 # set outgoing msg at db to deliverd
2021                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
2022                                 my $res = $messaging_db->exec_statement($sql); 
2023                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2024                 } else {
2025             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
2026         }
2027         }
2029     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
2030     return;
2034 sub watch_for_done_messages {
2035     my ($kernel,$heap) = @_[KERNEL, HEAP];
2037     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
2038     #&daemon_log("0 DEBUG: $sql", 7);
2039     my $res = $messaging_db->exec_statement($sql); 
2041     foreach my $hit (@{$res}) {
2042         my $msg_id = @{$hit}[0];
2044         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
2045         #&daemon_log("0 DEBUG: $sql", 7); 
2046         my $res = $messaging_db->exec_statement($sql);
2048         # not all usr msgs have been seen till now
2049         if ( ref(@$res[0]) eq "ARRAY") { next; }
2050         
2051         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
2052         #&daemon_log("0 DEBUG: $sql", 7);
2053         $res = $messaging_db->exec_statement($sql);
2054     
2055     }
2057     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2058     return;
2062 sub watch_for_old_known_clients {
2063     my ($kernel,$heap) = @_[KERNEL, HEAP];
2065     my $sql_statement = "SELECT * FROM $known_clients_tn";
2066     my $res = $known_clients_db->select_dbentry( $sql_statement );
2068     my $act_time = int(&get_time());
2070     while ( my ($hit_num, $hit) = each %$res) {
2071         my $expired_timestamp = int($hit->{'timestamp'});
2072         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2073         my $dt = DateTime->new( year   => $1,
2074                 month  => $2,
2075                 day    => $3,
2076                 hour   => $4,
2077                 minute => $5,
2078                 second => $6,
2079                 );
2081         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2082         $expired_timestamp = $dt->ymd('').$dt->hms('');
2083         if ($act_time > $expired_timestamp) {
2084             my $hostname = $hit->{'hostname'};
2085             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2086             my $del_res = $known_clients_db->exec_statement($del_sql);
2088             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2089         }
2091     }
2093     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2097 sub watch_for_next_tasks {
2098     my ($kernel,$heap) = @_[KERNEL, HEAP];
2100     my $sql = "SELECT * FROM $incoming_tn";
2101     my $res = $incoming_db->select_dbentry($sql);
2102     
2103     while ( my ($hit_num, $hit) = each %$res) {
2104         my $headertag = $hit->{'headertag'};
2105         if ($headertag =~ /^answer_(\d+)/) {
2106             # do not start processing, this message is for a still running POE::Wheel
2107             next;
2108         }
2109         my $message_id = $hit->{'id'};
2110         my $session_id = $hit->{'sessionid'};
2111         &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2112         $kernel->yield('next_task', $hit);
2114         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2115         my $res = $incoming_db->exec_statement($sql);
2116     }
2118     $kernel->delay_set('watch_for_next_tasks', 1); 
2122 sub get_ldap_handle {
2123         my ($session_id) = @_;
2124         my $heap;
2125         my $ldap_handle;
2127         if (not defined $session_id ) { $session_id = 0 };
2128         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2130         if ($session_id == 0) {
2131                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
2132                 $ldap_handle = Net::LDAP->new( $ldap_uri );
2133                 if (defined $ldap_handle) {
2134                         $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!"); 
2135                 } else {
2136                         daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2137                 }
2139         } else {
2140                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2141                 if( defined $session_reference ) {
2142                         $heap = $session_reference->get_heap();
2143                 }
2145                 if (not defined $heap) {
2146                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
2147                         return;
2148                 }
2150                 # TODO: This "if" is nonsense, because it doesn't prove that the
2151                 #       used handle is still valid - or if we've to reconnect...
2152                 #if (not exists $heap->{ldap_handle}) {
2153                         $ldap_handle = Net::LDAP->new( $ldap_uri );
2154                         $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!"); 
2155                         $heap->{ldap_handle} = $ldap_handle;
2156                 #}
2157         }
2158         return $ldap_handle;
2162 sub change_fai_state {
2163     my ($st, $targets, $session_id) = @_;
2164     $session_id = 0 if not defined $session_id;
2165     # Set FAI state to localboot
2166     my %mapActions= (
2167         reboot    => '',
2168         update    => 'softupdate',
2169         localboot => 'localboot',
2170         reinstall => 'install',
2171         rescan    => '',
2172         wake      => '',
2173         memcheck  => 'memcheck',
2174         sysinfo   => 'sysinfo',
2175         install   => 'install',
2176     );
2178     # Return if this is unknown
2179     if (!exists $mapActions{ $st }){
2180         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2181       return;
2182     }
2184     my $state= $mapActions{ $st };
2186     my $ldap_handle = &get_ldap_handle($session_id);
2187     if( defined($ldap_handle) ) {
2189       # Build search filter for hosts
2190         my $search= "(&(objectClass=GOhard)";
2191         foreach (@{$targets}){
2192             $search.= "(macAddress=$_)";
2193         }
2194         $search.= ")";
2196       # If there's any host inside of the search string, procress them
2197         if (!($search =~ /macAddress/)){
2198             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2199             return;
2200         }
2202       # Perform search for Unit Tag
2203       my $mesg = $ldap_handle->search(
2204           base   => $ldap_base,
2205           scope  => 'sub',
2206           attrs  => ['dn', 'FAIstate', 'objectClass'],
2207           filter => "$search"
2208           );
2210           if ($mesg->count) {
2211                   my @entries = $mesg->entries;
2212                   if (0 == @entries) {
2213                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2214                   }
2216                   foreach my $entry (@entries) {
2217                           # Only modify entry if it is not set to '$state'
2218                           if ($entry->get_value("FAIstate") ne "$state"){
2219                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2220                                   my $result;
2221                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2222                                   if (exists $tmp{'FAIobject'}){
2223                                           if ($state eq ''){
2224                                                   $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2225                                           } else {
2226                                                   $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2227                                           }
2228                                   } elsif ($state ne ''){
2229                                           $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2230                                   }
2232                                   # Errors?
2233                                   if ($result->code){
2234                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2235                                   }
2236                           } else {
2237                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
2238                           }  
2239                   }
2240           } else {
2241                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2242           }
2244     # if no ldap handle defined
2245     } else {
2246         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
2247     }
2249         return;
2253 sub change_goto_state {
2254     my ($st, $targets, $session_id) = @_;
2255     $session_id = 0  if not defined $session_id;
2257     # Switch on or off?
2258     my $state= $st eq 'active' ? 'active': 'locked';
2260     my $ldap_handle = &get_ldap_handle($session_id);
2261     if( defined($ldap_handle) ) {
2263       # Build search filter for hosts
2264       my $search= "(&(objectClass=GOhard)";
2265       foreach (@{$targets}){
2266         $search.= "(macAddress=$_)";
2267       }
2268       $search.= ")";
2270       # If there's any host inside of the search string, procress them
2271       if (!($search =~ /macAddress/)){
2272         return;
2273       }
2275       # Perform search for Unit Tag
2276       my $mesg = $ldap_handle->search(
2277           base   => $ldap_base,
2278           scope  => 'sub',
2279           attrs  => ['dn', 'gotoMode'],
2280           filter => "$search"
2281           );
2283       if ($mesg->count) {
2284         my @entries = $mesg->entries;
2285         foreach my $entry (@entries) {
2287           # Only modify entry if it is not set to '$state'
2288           if ($entry->get_value("gotoMode") ne $state){
2290             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2291             my $result;
2292             $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2294             # Errors?
2295             if ($result->code){
2296               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2297             }
2299           }
2300         }
2301       } else {
2302                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2303           }
2305     }
2309 sub run_recreate_packages_db {
2310     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2311     my $session_id = $session->ID;
2312         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2313         $kernel->yield('create_fai_release_db', $fai_release_tn);
2314         $kernel->yield('create_fai_server_db', $fai_server_tn);
2315         return;
2319 sub run_create_fai_server_db {
2320     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2321     my $session_id = $session->ID;
2322     my $task = POE::Wheel::Run->new(
2323             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2324             StdoutEvent  => "session_run_result",
2325             StderrEvent  => "session_run_debug",
2326             CloseEvent   => "session_run_done",
2327             );
2329     $heap->{task}->{ $task->ID } = $task;
2330     return;
2334 sub create_fai_server_db {
2335         my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2336         my $result;
2338         if (not defined $session_id) { $session_id = 0; }
2339         my $ldap_handle = &get_ldap_handle();
2340         if(defined($ldap_handle)) {
2341                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2342                 my $mesg= $ldap_handle->search(
2343                         base   => $ldap_base,
2344                         scope  => 'sub',
2345                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2346                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2347                 );
2348                 if($mesg->{'resultCode'} == 0 &&
2349                         $mesg->count != 0) {
2350                         foreach my $entry (@{$mesg->{entries}}) {
2351                                 if($entry->exists('FAIrepository')) {
2352                                         # Add an entry for each Repository configured for server
2353                                         foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2354                                                 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2355                                                 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2356                                                 $result= $fai_server_db->add_dbentry( { 
2357                                                                 table => $table_name,
2358                                                                 primkey => ['server', 'fai_release', 'tag'],
2359                                                                 server => $tmp_url,
2360                                                                 fai_release => $tmp_release,
2361                                                                 sections => $tmp_sections,
2362                                                                 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2363                                                         } );
2364                                         }
2365                                 }
2366                         }
2367                 }
2368                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2370                 # TODO: Find a way to post the 'create_packages_list_db' event
2371                 if(not defined($dont_create_packages_list)) {
2372                         &create_packages_list_db(undef, undef, $session_id);
2373                 }
2374         }       
2376         $ldap_handle->disconnect;
2377         return $result;
2381 sub run_create_fai_release_db {
2382         my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2383         my $session_id = $session->ID;
2384         my $task = POE::Wheel::Run->new(
2385                 Program => sub { &create_fai_release_db($table_name, $session_id) },
2386                 StdoutEvent  => "session_run_result",
2387                 StderrEvent  => "session_run_debug",
2388                 CloseEvent   => "session_run_done",
2389         );
2391         $heap->{task}->{ $task->ID } = $task;
2392         return;
2396 sub create_fai_release_db {
2397         my ($table_name, $session_id) = @_;
2398         my $result;
2400         # used for logging
2401         if (not defined $session_id) { $session_id = 0; }
2403         my $ldap_handle = &get_ldap_handle();
2404         if(defined($ldap_handle)) {
2405                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2406                 my $mesg= $ldap_handle->search(
2407                         base   => $ldap_base,
2408                         scope  => 'sub',
2409                         attrs  => [],
2410                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2411                 );
2412                 if($mesg->{'resultCode'} == 0 &&
2413                         $mesg->count != 0) {
2414                         # Walk through all possible FAI container ou's
2415                         my @sql_list;
2416                         my $timestamp= &get_time();
2417                         foreach my $ou (@{$mesg->{entries}}) {
2418                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2419                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2420                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2421                                         if(@tmp_array) {
2422                                                 foreach my $entry (@tmp_array) {
2423                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2424                                                                 my $sql= 
2425                                                                 "INSERT INTO $table_name "
2426                                                                 ."(timestamp, fai_release, class, type, state) VALUES ("
2427                                                                 .$timestamp.","
2428                                                                 ."'".$entry->{'release'}."',"
2429                                                                 ."'".$entry->{'class'}."',"
2430                                                                 ."'".$entry->{'type'}."',"
2431                                                                 ."'".$entry->{'state'}."')";
2432                                                                 push @sql_list, $sql;
2433                                                         }
2434                                                 }
2435                                         }
2436                                 }
2437                         }
2439                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2440                         if(@sql_list) {
2441                                 unshift @sql_list, "DELETE FROM $table_name";
2442                                 $fai_release_db->exec_statementlist(\@sql_list);
2443                         }
2444                         daemon_log("$session_id DEBUG: Done with inserting",7);
2445                 }
2446                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2447         }
2448         $ldap_handle->disconnect;
2449         return $result;
2452 sub get_fai_types {
2453         my $tmp_classes = shift || return undef;
2454         my @result;
2456         foreach my $type(keys %{$tmp_classes}) {
2457                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2458                         my $entry = {
2459                                 type => $type,
2460                                 state => $tmp_classes->{$type}[0],
2461                         };
2462                         push @result, $entry;
2463                 }
2464         }
2466         return @result;
2469 sub get_fai_state {
2470         my $result = "";
2471         my $tmp_classes = shift || return $result;
2473         foreach my $type(keys %{$tmp_classes}) {
2474                 if(defined($tmp_classes->{$type}[0])) {
2475                         $result = $tmp_classes->{$type}[0];
2476                         
2477                 # State is equal for all types in class
2478                         last;
2479                 }
2480         }
2482         return $result;
2485 sub resolve_fai_classes {
2486         my ($fai_base, $ldap_handle, $session_id) = @_;
2487         if (not defined $session_id) { $session_id = 0; }
2488         my $result;
2489         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2490         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2491         my $fai_classes;
2493         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2494         my $mesg= $ldap_handle->search(
2495                 base   => $fai_base,
2496                 scope  => 'sub',
2497                 attrs  => ['cn','objectClass','FAIstate'],
2498                 filter => $fai_filter,
2499         );
2500         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2502         if($mesg->{'resultCode'} == 0 &&
2503                 $mesg->count != 0) {
2504                 foreach my $entry (@{$mesg->{entries}}) {
2505                         if($entry->exists('cn')) {
2506                                 my $tmp_dn= $entry->dn();
2508                                 # Skip classname and ou dn parts for class
2509                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2511                                 # Skip classes without releases
2512                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2513                                         next;
2514                                 }
2516                                 my $tmp_cn= $entry->get_value('cn');
2517                                 my $tmp_state= $entry->get_value('FAIstate');
2519                                 my $tmp_type;
2520                                 # Get FAI type
2521                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2522                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2523                                                 $tmp_type= $oclass;
2524                                                 last;
2525                                         }
2526                                 }
2528                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2529                                         # A Subrelease
2530                                         my @sub_releases = split(/,/, $tmp_release);
2532                                         # Walk through subreleases and build hash tree
2533                                         my $hash;
2534                                         while(my $tmp_sub_release = pop @sub_releases) {
2535                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2536                                         }
2537                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2538                                 } else {
2539                                         # A branch, no subrelease
2540                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2541                                 }
2542                         } elsif (!$entry->exists('cn')) {
2543                                 my $tmp_dn= $entry->dn();
2544                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2546                                 # Skip classes without releases
2547                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2548                                         next;
2549                                 }
2551                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2552                                         # A Subrelease
2553                                         my @sub_releases= split(/,/, $tmp_release);
2555                                         # Walk through subreleases and build hash tree
2556                                         my $hash;
2557                                         while(my $tmp_sub_release = pop @sub_releases) {
2558                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2559                                         }
2560                                         # Remove the last two characters
2561                                         chop($hash);
2562                                         chop($hash);
2564                                         eval('$fai_classes->'.$hash.'= {}');
2565                                 } else {
2566                                         # A branch, no subrelease
2567                                         if(!exists($fai_classes->{$tmp_release})) {
2568                                                 $fai_classes->{$tmp_release} = {};
2569                                         }
2570                                 }
2571                         }
2572                 }
2574                 # The hash is complete, now we can honor the copy-on-write based missing entries
2575                 foreach my $release (keys %$fai_classes) {
2576                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2577                 }
2578         }
2579         return $result;
2582 sub apply_fai_inheritance {
2583        my $fai_classes = shift || return {};
2584        my $tmp_classes;
2586        # Get the classes from the branch
2587        foreach my $class (keys %{$fai_classes}) {
2588                # Skip subreleases
2589                if($class =~ /^ou=.*$/) {
2590                        next;
2591                } else {
2592                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2593                }
2594        }
2596        # Apply to each subrelease
2597        foreach my $subrelease (keys %{$fai_classes}) {
2598                if($subrelease =~ /ou=/) {
2599                        foreach my $tmp_class (keys %{$tmp_classes}) {
2600                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2601                                        $fai_classes->{$subrelease}->{$tmp_class} =
2602                                        deep_copy($tmp_classes->{$tmp_class});
2603                                } else {
2604                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2605                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2606                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2607                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2608                                                }
2609                                        }
2610                                }
2611                        }
2612                }
2613        }
2615        # Find subreleases in deeper levels
2616        foreach my $subrelease (keys %{$fai_classes}) {
2617                if($subrelease =~ /ou=/) {
2618                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2619                                if($subsubrelease =~ /ou=/) {
2620                                        apply_fai_inheritance($fai_classes->{$subrelease});
2621                                }
2622                        }
2623                }
2624        }
2626        return $fai_classes;
2629 sub get_fai_release_entries {
2630         my $tmp_classes = shift || return;
2631         my $parent = shift || "";
2632         my @result = shift || ();
2634         foreach my $entry (keys %{$tmp_classes}) {
2635                 if(defined($entry)) {
2636                         if($entry =~ /^ou=.*$/) {
2637                                 my $release_name = $entry;
2638                                 $release_name =~ s/ou=//g;
2639                                 if(length($parent)>0) {
2640                                         $release_name = $parent."/".$release_name;
2641                                 }
2642                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2643                                 foreach my $bufentry(@bufentries) {
2644                                         push @result, $bufentry;
2645                                 }
2646                         } else {
2647                                 my @types = get_fai_types($tmp_classes->{$entry});
2648                                 foreach my $type (@types) {
2649                                         push @result, 
2650                                         {
2651                                                 'class' => $entry,
2652                                                 'type' => $type->{'type'},
2653                                                 'release' => $parent,
2654                                                 'state' => $type->{'state'},
2655                                         };
2656                                 }
2657                         }
2658                 }
2659         }
2661         return @result;
2664 sub deep_copy {
2665         my $this = shift;
2666         if (not ref $this) {
2667                 $this;
2668         } elsif (ref $this eq "ARRAY") {
2669                 [map deep_copy($_), @$this];
2670         } elsif (ref $this eq "HASH") {
2671                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2672         } else { die "what type is $_?" }
2676 sub session_run_result {
2677     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2678     $kernel->sig(CHLD => "child_reap");
2681 sub session_run_debug {
2682     my $result = $_[ARG0];
2683     print STDERR "$result\n";
2686 sub session_run_done {
2687     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2688     delete $heap->{task}->{$task_id};
2692 sub create_sources_list {
2693         my $session_id = shift;
2694         my $ldap_handle = &main::get_ldap_handle;
2695         my $result="/tmp/gosa_si_tmp_sources_list";
2697         # Remove old file
2698         if(stat($result)) {
2699                 unlink($result);
2700                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2701         }
2703         my $fh;
2704         open($fh, ">$result");
2705         if (not defined $fh) {
2706                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2707                 return undef;
2708         }
2709         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2710                 my $mesg=$ldap_handle->search(
2711                         base    => $main::ldap_server_dn,
2712                         scope   => 'base',
2713                         attrs   => 'FAIrepository',
2714                         filter  => 'objectClass=FAIrepositoryServer'
2715                 );
2716                 if($mesg->count) {
2717                         foreach my $entry(@{$mesg->{'entries'}}) {
2718                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2719                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2720                                         my $line = "deb $server $release";
2721                                         $sections =~ s/,/ /g;
2722                                         $line.= " $sections";
2723                                         print $fh $line."\n";
2724                                 }
2725                         }
2726                 }
2727         } else {
2728                 if (defined $main::ldap_server_dn){
2729                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2730                 } else {
2731                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2732                 }
2733         }
2734         close($fh);
2736         return $result;
2740 sub run_create_packages_list_db {
2741     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2742         my $session_id = $session->ID;
2744         my $task = POE::Wheel::Run->new(
2745                                         Priority => +20,
2746                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2747                                         StdoutEvent  => "session_run_result",
2748                                         StderrEvent  => "session_run_debug",
2749                                         CloseEvent   => "session_run_done",
2750                                         );
2751         $heap->{task}->{ $task->ID } = $task;
2755 sub create_packages_list_db {
2756         my ($ldap_handle, $sources_file, $session_id) = @_;
2757         
2758         # it should not be possible to trigger a recreation of packages_list_db
2759         # while packages_list_db is under construction, so set flag packages_list_under_construction
2760         # which is tested befor recreation can be started
2761         if (-r $packages_list_under_construction) {
2762                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2763                 return;
2764         } else {
2765                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2766                 # set packages_list_under_construction to true
2767                 system("touch $packages_list_under_construction");
2768                 @packages_list_statements=();
2769         }
2771         if (not defined $session_id) { $session_id = 0; }
2772         if (not defined $ldap_handle) { 
2773                 $ldap_handle= &get_ldap_handle();
2775                 if (not defined $ldap_handle) {
2776                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2777                         unlink($packages_list_under_construction);
2778                         return;
2779                 }
2780         }
2781         if (not defined $sources_file) { 
2782                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2783                 $sources_file = &create_sources_list($session_id);
2784         }
2786         if (not defined $sources_file) {
2787                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2788                 unlink($packages_list_under_construction);
2789                 return;
2790         }
2792         my $line;
2794         open(CONFIG, "<$sources_file") or do {
2795                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2796                 unlink($packages_list_under_construction);
2797                 return;
2798         };
2800         # Read lines
2801         while ($line = <CONFIG>){
2802                 # Unify
2803                 chop($line);
2804                 $line =~ s/^\s+//;
2805                 $line =~ s/^\s+/ /;
2807                 # Strip comments
2808                 $line =~ s/#.*$//g;
2810                 # Skip empty lines
2811                 if ($line =~ /^\s*$/){
2812                         next;
2813                 }
2815                 # Interpret deb line
2816                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2817                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2818                         my $section;
2819                         foreach $section (split(' ', $sections)){
2820                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2821                         }
2822                 }
2823         }
2825         close (CONFIG);
2827         if(keys(%repo_dirs)) {
2828                 find(\&cleanup_and_extract, keys( %repo_dirs ));
2829                 &main::strip_packages_list_statements();
2830                 $packages_list_db->exec_statementlist(\@packages_list_statements);
2831         }
2832         unlink($packages_list_under_construction);
2833         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2834         return;
2837 # This function should do some intensive task to minimize the db-traffic
2838 sub strip_packages_list_statements {
2839         my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2840         my @new_statement_list=();
2841         my $hash;
2842         my $insert_hash;
2843         my $update_hash;
2844         my $delete_hash;
2845         my $known_packages_hash;
2846         my $local_timestamp=get_time();
2848         foreach my $existing_entry (@existing_entries) {
2849                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2850         }
2852         foreach my $statement (@packages_list_statements) {
2853                 if($statement =~ /^INSERT/i) {
2854                         # Assign the values from the insert statement
2855                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2856                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2857                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2858                                 # If section or description has changed, update the DB
2859                                 if( 
2860                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2861                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2862                                 ) {
2863                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2864                                 } else {
2865                                         # package is already present in database. cache this knowledge for later use
2866                                         @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2867                                 }
2868                         } else {
2869                                 # Insert a non-existing entry to db
2870                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2871                         }
2872                 } elsif ($statement =~ /^UPDATE/i) {
2873                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2874                         /^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;
2875                         foreach my $distribution (keys %{$hash}) {
2876                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2877                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2878                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2879                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2880                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2881                                                 my $section;
2882                                                 my $description;
2883                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2884                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2885                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2886                                                 }
2887                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2888                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2889                                                 }
2890                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2891                                         }
2892                                 }
2893                         }
2894                 }
2895         }
2897         # Check for orphaned entries
2898         foreach my $existing_entry (@existing_entries) {
2899                 my $distribution= @{$existing_entry}[0];
2900                 my $package= @{$existing_entry}[1];
2901                 my $version= @{$existing_entry}[2];
2902                 my $section= @{$existing_entry}[3];
2904                 if(
2905                         exists($insert_hash->{$distribution}->{$package}->{$version}) ||
2906                         exists($update_hash->{$distribution}->{$package}->{$version}) ||
2907                         exists($known_packages_hash->{$distribution}->{$package}->{$version})
2908                 ) {
2909                         next;
2910                 } else {
2911                         # Insert entry to delete hash
2912                         @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
2913                 }
2914         }
2916         # unroll the insert hash
2917         foreach my $distribution (keys %{$insert_hash}) {
2918                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2919                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2920                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2921                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2922                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2923                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2924                                 ."'$local_timestamp')";
2925                         }
2926                 }
2927         }
2929         # unroll the update hash
2930         foreach my $distribution (keys %{$update_hash}) {
2931                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2932                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2933                                 my $set = "";
2934                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2935                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2936                                 }
2937                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2938                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2939                                 }
2940                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2941                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2942                                 }
2943                                 if(defined($set) and length($set) > 0) {
2944                                         $set .= "timestamp = '$local_timestamp'";
2945                                 } else {
2946                                         next;
2947                                 }
2948                                 push @new_statement_list, 
2949                                 "UPDATE $main::packages_list_tn SET $set WHERE"
2950                                 ." distribution = '$distribution'"
2951                                 ." AND package = '$package'"
2952                                 ." AND version = '$version'";
2953                         }
2954                 }
2955         }
2956         
2957         # unroll the delete hash
2958         foreach my $distribution (keys %{$delete_hash}) {
2959                 foreach my $package (keys %{$delete_hash->{$distribution}}) {
2960                         foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
2961                                 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
2962                                 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
2963                         }
2964                 }
2965         }
2967         @packages_list_statements = @new_statement_list;
2971 sub parse_package_info {
2972     my ($baseurl, $dist, $section, $session_id)= @_;
2973     my ($package);
2974     if (not defined $session_id) { $session_id = 0; }
2975     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2976     $repo_dirs{ "${repo_path}/pool" } = 1;
2978     foreach $package ("Packages.gz"){
2979         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2980         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2981         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2982     }
2983     
2987 sub get_package {
2988     my ($url, $dest, $session_id)= @_;
2989     if (not defined $session_id) { $session_id = 0; }
2991     my $tpath = dirname($dest);
2992     -d "$tpath" || mkpath "$tpath";
2994     # This is ugly, but I've no time to take a look at "how it works in perl"
2995     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2996         system("gunzip -cd '$dest' > '$dest.in'");
2997         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2998         unlink($dest);
2999         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
3000     } else {
3001         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
3002     }
3003     return 0;
3007 sub parse_package {
3008     my ($path, $dist, $srv_path, $session_id)= @_;
3009     if (not defined $session_id) { $session_id = 0;}
3010     my ($package, $version, $section, $description);
3011     my $PACKAGES;
3012     my $timestamp = &get_time();
3014     if(not stat("$path.in")) {
3015         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3016         return;
3017     }
3019     open($PACKAGES, "<$path.in");
3020     if(not defined($PACKAGES)) {
3021         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
3022         return;
3023     }
3025     # Read lines
3026     while (<$PACKAGES>){
3027         my $line = $_;
3028         # Unify
3029         chop($line);
3031         # Use empty lines as a trigger
3032         if ($line =~ /^\s*$/){
3033             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3034             push(@packages_list_statements, $sql);
3035             $package = "none";
3036             $version = "none";
3037             $section = "none";
3038             $description = "none"; 
3039             next;
3040         }
3042         # Trigger for package name
3043         if ($line =~ /^Package:\s/){
3044             ($package)= ($line =~ /^Package: (.*)$/);
3045             next;
3046         }
3048         # Trigger for version
3049         if ($line =~ /^Version:\s/){
3050             ($version)= ($line =~ /^Version: (.*)$/);
3051             next;
3052         }
3054         # Trigger for description
3055         if ($line =~ /^Description:\s/){
3056             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3057             next;
3058         }
3060         # Trigger for section
3061         if ($line =~ /^Section:\s/){
3062             ($section)= ($line =~ /^Section: (.*)$/);
3063             next;
3064         }
3066         # Trigger for filename
3067         if ($line =~ /^Filename:\s/){
3068             my ($filename) = ($line =~ /^Filename: (.*)$/);
3069             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3070             next;
3071         }
3072     }
3074     close( $PACKAGES );
3075     unlink( "$path.in" );
3079 sub store_fileinfo {
3080     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3082     my %fileinfo = (
3083         'package' => $package,
3084         'dist' => $dist,
3085         'version' => $vers,
3086     );
3088     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3092 sub cleanup_and_extract {
3093         my $fileinfo = $repo_files{ $File::Find::name };
3095         if( defined $fileinfo ) {
3096                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3097                 my $sql;
3098                 my $package = $fileinfo->{ 'package' };
3099                 my $newver = $fileinfo->{ 'version' };
3101                 mkpath($dir);
3102                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3104                 if( -f "$dir/DEBIAN/templates" ) {
3106                         daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3108                         my $tmpl= ""; {
3109                                 local $/=undef;
3110                                 open FILE, "$dir/DEBIAN/templates";
3111                                 $tmpl = &encode_base64(<FILE>);
3112                                 close FILE;
3113                         }
3114                         rmtree("$dir/DEBIAN/templates");
3116                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3117                         push @packages_list_statements, $sql;
3118                 }
3119         }
3121         return;
3125 sub register_at_foreign_servers {   
3126     my ($kernel) = $_[KERNEL];
3128     # hole alle bekannten server aus known_server_db
3129     my $server_sql = "SELECT * FROM $known_server_tn";
3130     my $server_res = $known_server_db->exec_statement($server_sql);
3132     # no entries in known_server_db
3133     if (not ref(@$server_res[0]) eq "ARRAY") { 
3134         # TODO
3135     }
3137     # detect already connected clients
3138     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3139     my $client_res = $known_clients_db->exec_statement($client_sql);
3141     # send my server details to all other gosa-si-server within the network
3142     foreach my $hit (@$server_res) {
3143         my $hostname = @$hit[0];
3144         my $hostkey = &create_passwd;
3146         # add already connected clients to registration message 
3147         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3148         &add_content2xml_hash($myhash, 'key', $hostkey);
3149         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3151         # add locally loaded gosa-si modules to registration message
3152         my $loaded_modules = {};
3153         while (my ($package, $pck_info) = each %$known_modules) {
3154                                                 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3155                                                 foreach my $act_module (keys(%{@$pck_info[2]})) {
3156                                                         $loaded_modules->{$act_module} = ""; 
3157                                                 }
3158         }
3160         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3162         # add macaddress to registration message
3163         my ($host_ip, $host_port) = split(/:/, $hostname);
3164         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3165         my $network_interface= &get_interface_for_ip($local_ip);
3166         my $host_mac = &get_mac_for_interface($network_interface);
3167         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3168         
3169         # build registration message and send it
3170         my $foreign_server_msg = &create_xml_string($myhash);
3171         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3172     }
3173     
3174     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
3175     return;
3179 #==== MAIN = main ==============================================================
3180 #  parse commandline options
3181 Getopt::Long::Configure( "bundling" );
3182 GetOptions("h|help" => \&usage,
3183         "c|config=s" => \$cfg_file,
3184         "f|foreground" => \$foreground,
3185         "v|verbose+" => \$verbose,
3186         "no-arp+" => \$no_arp,
3187            );
3189 #  read and set config parameters
3190 &check_cmdline_param ;
3191 &read_configfile($cfg_file, %cfg_defaults);
3192 &check_pid;
3194 $SIG{CHLD} = 'IGNORE';
3196 # forward error messages to logfile
3197 if( ! $foreground ) {
3198   open( STDIN,  '+>/dev/null' );
3199   open( STDOUT, '+>&STDIN'    );
3200   open( STDERR, '+>&STDIN'    );
3203 # Just fork, if we are not in foreground mode
3204 if( ! $foreground ) { 
3205     chdir '/'                 or die "Can't chdir to /: $!";
3206     $pid = fork;
3207     setsid                    or die "Can't start a new session: $!";
3208     umask 0;
3209 } else { 
3210     $pid = $$; 
3213 # Do something useful - put our PID into the pid_file
3214 if( 0 != $pid ) {
3215     open( LOCK_FILE, ">$pid_file" );
3216     print LOCK_FILE "$pid\n";
3217     close( LOCK_FILE );
3218     if( !$foreground ) { 
3219         exit( 0 ) 
3220     };
3223 # parse head url and revision from svn
3224 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3225 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3226 $server_headURL = defined $1 ? $1 : 'unknown' ;
3227 $server_revision = defined $2 ? $2 : 'unknown' ;
3228 if ($server_headURL =~ /\/tag\// || 
3229         $server_headURL =~ /\/branches\// ) {
3230     $server_status = "stable"; 
3231 } else {
3232     $server_status = "developmental" ;
3235 # Prepare log file
3236 $root_uid = getpwnam('root');
3237 $adm_gid = getgrnam('adm');
3238 chmod(0640, $log_file);
3239 chown($root_uid, $adm_gid, $log_file);
3240 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3242 daemon_log(" ", 1);
3243 daemon_log("$0 started!", 1);
3244 daemon_log("status: $server_status", 1);
3245 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3248     no strict "refs";
3250     if ($db_module eq "DBmysql") {
3251         # connect to incoming_db
3252         $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3254         # connect to gosa-si job queue
3255         $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3257         # connect to known_clients_db
3258         $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3260         # connect to foreign_clients_db
3261         $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3263         # connect to known_server_db
3264         $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3266         # connect to login_usr_db
3267         $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3269         # connect to fai_server_db 
3270         $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3272         # connect to fai_release_db
3273         $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3275         # connect to packages_list_db
3276         $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3278         # connect to messaging_db
3279         $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3281     } elsif ($db_module eq "DBsqlite") {
3282         # connect to incoming_db
3283         unlink($incoming_file_name);
3284         $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3285         
3286         # connect to gosa-si job queue
3287         unlink($job_queue_file_name);  ## just for debugging
3288         $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3289         chmod(0660, $job_queue_file_name);
3290         chown($root_uid, $adm_gid, $job_queue_file_name);
3291         
3292         # connect to known_clients_db
3293         unlink($known_clients_file_name);   ## just for debugging
3294         $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3295         chmod(0660, $known_clients_file_name);
3296         chown($root_uid, $adm_gid, $known_clients_file_name);
3297         
3298         # connect to foreign_clients_db
3299         unlink($foreign_clients_file_name);
3300         $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3301         chmod(0660, $foreign_clients_file_name);
3302         chown($root_uid, $adm_gid, $foreign_clients_file_name);
3303         
3304         # connect to known_server_db
3305         unlink($known_server_file_name);
3306         $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3307         chmod(0660, $known_server_file_name);
3308         chown($root_uid, $adm_gid, $known_server_file_name);
3309         
3310         # connect to login_usr_db
3311         unlink($login_users_file_name);
3312         $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3313         chmod(0660, $login_users_file_name);
3314         chown($root_uid, $adm_gid, $login_users_file_name);
3315         
3316         # connect to fai_server_db
3317         unlink($fai_server_file_name);
3318         $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3319         chmod(0660, $fai_server_file_name);
3320         chown($root_uid, $adm_gid, $fai_server_file_name);
3321         
3322         # connect to fai_release_db
3323         unlink($fai_release_file_name);
3324         $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3325         chmod(0660, $fai_release_file_name);
3326         chown($root_uid, $adm_gid, $fai_release_file_name);
3327         
3328         # connect to packages_list_db
3329         #unlink($packages_list_file_name);
3330         unlink($packages_list_under_construction);
3331         $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3332         chmod(0660, $packages_list_file_name);
3333         chown($root_uid, $adm_gid, $packages_list_file_name);
3334         
3335         # connect to messaging_db
3336         unlink($messaging_file_name);
3337         $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3338         chmod(0660, $messaging_file_name);
3339         chown($root_uid, $adm_gid, $messaging_file_name);
3340     }
3343 # Creating tables
3344 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3345 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3346 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3347 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3348 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3349 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3350 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3351 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3352 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3353 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3356 # create xml object used for en/decrypting
3357 $xml = new XML::Simple();
3360 # foreign servers 
3361 my @foreign_server_list;
3363 # add foreign server from cfg file
3364 if ($foreign_server_string ne "") {
3365     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3366     foreach my $foreign_server (@cfg_foreign_server_list) {
3367         push(@foreign_server_list, $foreign_server);
3368     }
3370     daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3373 # Perform a DNS lookup for server registration if flag is true
3374 if ($dns_lookup eq "true") {
3375     # Add foreign server from dns
3376     my @tmp_servers;
3377     if (not $server_domain) {
3378         # Try our DNS Searchlist
3379         for my $domain(get_dns_domains()) {
3380             chomp($domain);
3381             my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3382             if(@$tmp_domains) {
3383                 for my $tmp_server(@$tmp_domains) {
3384                     push @tmp_servers, $tmp_server;
3385                 }
3386             }
3387         }
3388         if(@tmp_servers && length(@tmp_servers)==0) {
3389             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3390         }
3391     } else {
3392         @tmp_servers = &get_server_addresses($server_domain);
3393         if( 0 == @tmp_servers ) {
3394             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3395         }
3396     }
3398     daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3400     foreach my $server (@tmp_servers) { 
3401         unshift(@foreign_server_list, $server); 
3402     }
3403 } else {
3404     daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3408 # eliminate duplicate entries
3409 @foreign_server_list = &del_doubles(@foreign_server_list);
3410 my $all_foreign_server = join(", ", @foreign_server_list);
3411 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3413 # add all found foreign servers to known_server
3414 my $act_timestamp = &get_time();
3415 foreach my $foreign_server (@foreign_server_list) {
3417         # do not add myself to known_server_db
3418         if (&is_local($foreign_server)) { next; }
3419         ######################################
3421     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3422             primkey=>['hostname'],
3423             hostname=>$foreign_server,
3424             macaddress=>"",
3425             status=>'not_jet_registered',
3426             hostkey=>"none",
3427             loaded_modules => "none", 
3428             timestamp=>$act_timestamp,
3429             } );
3433 # Import all modules
3434 &import_modules;
3436 # Check wether all modules are gosa-si valid passwd check
3437 &password_check;
3439 # Prepare for using Opsi 
3440 if ($opsi_enabled eq "true") {
3441     use JSON::RPC::Client;
3442     use XML::Quote qw(:all);
3443     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3444     $opsi_client = new JSON::RPC::Client;
3448 POE::Component::Server::TCP->new(
3449         Alias => "TCP_SERVER",
3450         Port => $server_port,
3451         ClientInput => sub {
3452                 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3453         my $session_id = $session->ID;
3454         my $remote_ip = $heap->{'remote_ip'};
3455                 push(@msgs_to_decrypt, $input);
3456         &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3457                 $kernel->yield("msg_to_decrypt");
3458         },
3459         InlineStates => {
3460                 msg_to_decrypt => \&msg_to_decrypt,
3461                 next_task => \&next_task,
3462                 task_result => \&handle_task_result,
3463                 task_done   => \&handle_task_done,
3464                 task_debug  => \&handle_task_debug,
3465                 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3466         }
3467 );
3469 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3471 # create session for repeatedly checking the job queue for jobs
3472 POE::Session->create(
3473         inline_states => {
3474                 _start => \&session_start,
3475         register_at_foreign_servers => \&register_at_foreign_servers,
3476         sig_handler => \&sig_handler,
3477         next_task => \&next_task,
3478         task_result => \&handle_task_result,
3479         task_done   => \&handle_task_done,
3480         task_debug  => \&handle_task_debug,
3481         watch_for_next_tasks => \&watch_for_next_tasks,
3482         watch_for_new_messages => \&watch_for_new_messages,
3483         watch_for_delivery_messages => \&watch_for_delivery_messages,
3484         watch_for_done_messages => \&watch_for_done_messages,
3485                 watch_for_new_jobs => \&watch_for_new_jobs,
3486         watch_for_modified_jobs => \&watch_for_modified_jobs,
3487         watch_for_done_jobs => \&watch_for_done_jobs,
3488         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3489         watch_for_old_known_clients => \&watch_for_old_known_clients,
3490         create_packages_list_db => \&run_create_packages_list_db,
3491         create_fai_server_db => \&run_create_fai_server_db,
3492         create_fai_release_db => \&run_create_fai_release_db,
3493                 recreate_packages_db => \&run_recreate_packages_db,
3494         session_run_result => \&session_run_result,
3495         session_run_debug => \&session_run_debug,
3496         session_run_done => \&session_run_done,
3497         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3498         }
3499 );
3502 POE::Kernel->run();
3503 exit;