Code

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