Code

091402f963c0916a547b2f829aa5f39f9c54eb1d
[gosa.git] / trunk / 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 #===============================================================================
23 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev$';
25 use strict;
26 use warnings;
27 use Getopt::Long;
28 use Config::IniFiles;
29 use POSIX;
31 use Fcntl qw/:flock/;
32 use IO::Socket::INET;
33 use IO::Handle;
34 use IO::Select;
35 use Symbol qw(qualify_to_ref);
36 use Crypt::Rijndael;
37 use MIME::Base64;
38 use Digest::MD5  qw(md5 md5_hex md5_base64);
39 use XML::Simple;
40 use Data::Dumper;
41 use Sys::Syslog qw( :DEFAULT setlogsock);
42 use Cwd;
43 use File::Spec;
44 use File::Basename;
45 use File::Find;
46 use File::Copy;
47 use File::Path;
48 use GOSA::GosaSupportDaemon;
49 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
50 use Net::LDAP;
51 use Net::LDAP::Util qw(:escape);
52 use Time::HiRes qw( usleep);
54 # revision number of server and program name
55 my $server_headURL;
56 my $server_revision;
57 my $server_status;
58 our $prg= basename($0);
60 my $db_module = "DBsqlite";
61 {
62 no strict "refs";
63 require ("GOSA/".$db_module.".pm");
64 ("GOSA/".$db_module)->import;
65 daemon_log("0 INFO: importing database module '$db_module'", 1);
66 }
68 my $modules_path = "/usr/lib/gosa-si/modules";
69 use lib "/usr/lib/gosa-si/modules";
71 our $global_kernel;
72 my ($foreground, $ping_timeout);
73 my ($server);
74 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
75 my ($messaging_db_loop_delay);
76 my ($procid, $pid);
77 my ($arp_fifo);
78 my ($xml);
79 my $sources_list;
80 my $max_clients;
81 my %repo_files=();
82 my $repo_path;
83 my %repo_dirs=();
85 # Variables declared in config file are always set to 'our'
86 our (%cfg_defaults, $log_file, $pid_file, 
87     $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
88     $arp_activ, $gosa_unit_tag,
89     $GosaPackages_key, $gosa_timeout,
90     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
91     $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
92     $arp_enabled, $arp_interface,
93     $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
94                 $new_systems_ou,
95 );
97 # additional variable which should be globaly accessable
98 our $server_address;
99 our $server_mac_address;
100 our $gosa_address;
101 our $no_arp;
102 our $verbose;
103 our $forground;
104 our $cfg_file;
105 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
106 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
107 our $known_modules;
108 our $root_uid;
109 our $adm_gid;
112 # specifies the verbosity of the daemon_log
113 $verbose = 0 ;
115 # if foreground is not null, script will be not forked to background
116 $foreground = 0 ;
118 # specifies the timeout seconds while checking the online status of a registrating client
119 $ping_timeout = 5;
121 $no_arp = 0;
122 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
123 my @packages_list_statements;
124 my $watch_for_new_jobs_in_progress = 0;
126 # holds all incoming decrypted messages
127 our $incoming_db;
128 our $incoming_tn = 'incoming';
129 my $incoming_file_name;
130 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
131         "timestamp VARCHAR(14) DEFAULT 'none'", 
132         "headertag VARCHAR(255) DEFAULT 'none'",
133         "targettag VARCHAR(255) DEFAULT 'none'",
134         "xmlmessage TEXT",
135         "module VARCHAR(255) DEFAULT 'none'",
136         "sessionid VARCHAR(255) DEFAULT '0'",
137 );
139 # holds all gosa jobs
140 our $job_db;
141 our $job_queue_tn = 'jobs';
142 my $job_queue_file_name;
143 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
144         "timestamp VARCHAR(14) DEFAULT 'none'", 
145         "status VARCHAR(255) DEFAULT 'none'", 
146         "result TEXT",
147         "progress VARCHAR(255) DEFAULT 'none'",
148         "headertag VARCHAR(255) DEFAULT 'none'",
149         "targettag VARCHAR(255) DEFAULT 'none'", 
150         "xmlmessage TEXT", 
151         "macaddress VARCHAR(17) DEFAULT 'none'",
152         "plainname VARCHAR(255) DEFAULT 'none'",
153         "siserver VARCHAR(255) DEFAULT 'none'",
154         "modified INTEGER DEFAULT '0'",
155 );
157 # holds all other gosa-si-server
158 our $known_server_db;
159 our $known_server_tn = "known_server";
160 my $known_server_file_name;
161 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)");
163 # holds all registrated clients
164 our $known_clients_db;
165 our $known_clients_tn = "known_clients";
166 my $known_clients_file_name;
167 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)");
169 # holds all registered clients at a foreign server
170 our $foreign_clients_db;
171 our $foreign_clients_tn = "foreign_clients"; 
172 my $foreign_clients_file_name;
173 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
175 # holds all logged in user at each client 
176 our $login_users_db;
177 our $login_users_tn = "login_users";
178 my $login_users_file_name;
179 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
181 # holds all fai server, the debian release and tag
182 our $fai_server_db;
183 our $fai_server_tn = "fai_server"; 
184 my $fai_server_file_name;
185 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)"); 
187 our $fai_release_db;
188 our $fai_release_tn = "fai_release"; 
189 my $fai_release_file_name;
190 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)"); 
192 # holds all packages available from different repositories
193 our $packages_list_db;
194 our $packages_list_tn = "packages_list";
195 my $packages_list_file_name;
196 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
197 my $outdir = "/tmp/packages_list_db";
198 my $arch = "i386"; 
200 # holds all messages which should be delivered to a user
201 our $messaging_db;
202 our $messaging_tn = "messaging"; 
203 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)", 
204         "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
205 my $messaging_file_name;
207 # path to directory to store client install log files
208 our $client_fai_log_dir = "/var/log/fai"; 
210 # queue which stores taskes until one of the $max_children children are ready to process the task
211 #my @tasks = qw();
212 my @msgs_to_decrypt = qw();
213 my $max_children = 2;
215 # Allow 50 POE Childs
216 sub MAX_CONCURRENT_TASKS () { 50 }
218 # loop delay for job queue to look for opsi jobs
219 my $job_queue_opsi_delay = 10;
220 our $opsi_client;
221 our $opsi_url;
222  
223 # Lifetime of logged in user information. If no update information comes after n seconds, 
224 # the user is expeceted to be no longer logged in or the host is no longer running. Because
225 # of this, the user is deleted from login_users_db
226 our $logged_in_user_date_of_expiry = 600;
229 %cfg_defaults = (
230 "general" => {
231     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
232     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
233     },
234 "server" => {
235     "ip"                    => [\$server_ip, "0.0.0.0"],
236     "port"                  => [\$server_port, "20081"],
237     "known-clients"         => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
238     "known-servers"         => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
239     "incoming"              => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
240     "login-users"           => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
241     "fai-server"            => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
242     "fai-release"           => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
243     "packages-list"         => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
244     "messaging"             => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
245     "foreign-clients"       => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
246     "source-list"           => [\$sources_list, '/etc/apt/sources.list'],
247     "repo-path"             => [\$repo_path, '/srv/www/repository'],
248     "ldap-uri"              => [\$ldap_uri, ""],
249     "ldap-base"             => [\$ldap_base, ""],
250     "ldap-admin-dn"         => [\$ldap_admin_dn, ""],
251     "ldap-admin-password"   => [\$ldap_admin_password, ""],
252     "gosa-unit-tag"         => [\$gosa_unit_tag, ""],
253     "max-clients"           => [\$max_clients, 10],
254     "wol-password"          => [\$wake_on_lan_passwd, ""],
255                 "mysql-username"        => [\$mysql_username, "gosa_si"],
256                 "mysql-password"        => [\$mysql_password, ""],
257                 "mysql-database"        => [\$mysql_database, "gosa_si"],
258                 "mysql-host"            => [\$mysql_host, "127.0.0.1"],
259     },
260 "GOsaPackages" => {
261     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
262     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
263     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
264     "key" => [\$GosaPackages_key, "none"],
265                 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
266     },
267 "ClientPackages" => {
268     "key" => [\$ClientPackages_key, "none"],
269     "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
270     },
271 "ServerPackages"=> {
272     "address"      => [\$foreign_server_string, ""],
273     "dns-lookup"            => [\$dns_lookup, "true"],
274     "domain"  => [\$server_domain, ""],
275     "key"     => [\$ServerPackages_key, "none"],
276     "key-lifetime" => [\$foreign_servers_register_delay, 120],
277     "job-synchronization-enabled" => [\$job_synchronization, "true"],
278     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
279     },
280 "ArpHandler" => {
281     "enabled"   => [\$arp_enabled, "true"],
282     "interface" => [\$arp_interface, "all"],
283         },
284 "Opsi" => {
285     "enabled"  => [\$opsi_enabled, "false"], 
286     "server"   => [\$opsi_server, "localhost"],
287     "admin"    => [\$opsi_admin, "opsi-admin"],
288     "password" => [\$opsi_password, "secret"],
289    },
291 );
294 #===  FUNCTION  ================================================================
295 #         NAME:  usage
296 #   PARAMETERS:  nothing
297 #      RETURNS:  nothing
298 #  DESCRIPTION:  print out usage text to STDERR
299 #===============================================================================
300 sub usage {
301     print STDERR << "EOF" ;
302 usage: $prg [-hvf] [-c config]
304            -h        : this (help) message
305            -c <file> : config file
306            -f        : foreground, process will not be forked to background
307            -v        : be verbose (multiple to increase verbosity)
308            -no-arp   : starts $prg without connection to arp module
309  
310 EOF
311     print "\n" ;
315 #===  FUNCTION  ================================================================
316 #         NAME:  logging
317 #   PARAMETERS:  level - string - default 'info'
318 #                msg - string -
319 #                facility - string - default 'LOG_DAEMON'
320 #      RETURNS:  nothing
321 #  DESCRIPTION:  function for logging
322 #===============================================================================
323 sub daemon_log {
324     # log into log_file
325     my( $msg, $level ) = @_;
326     if(not defined $msg) { return }
327     if(not defined $level) { $level = 1 }
328     if(defined $log_file){
329         my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
330         if(not $open_log_fh) {
331             print STDERR "cannot open $log_file: $!";
332             return;
333         }
334         # check owner and group of log_file and update settings if necessary
335         my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
336         if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
337             chown($root_uid, $adm_gid, $log_file);
338         }
340         chomp($msg);
341         #$msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
342         if($level <= $verbose){
343             my ($seconds, $minutes, $hours, $monthday, $month,
344                     $year, $weekday, $yearday, $sommertime) = localtime(time);
345             $hours = $hours < 10 ? $hours = "0".$hours : $hours;
346             $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
347             $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
348             my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
349             $month = $monthnames[$month];
350             $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
351             $year+=1900;
352             my $name = $prg;
354             my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
355                         flock(LOG_HANDLE, LOCK_EX);
356                         seek(LOG_HANDLE, 0, 2);
357             print LOG_HANDLE $log_msg;
358                         flock(LOG_HANDLE, LOCK_UN);
359             if( $foreground ) { 
360                 print STDERR $log_msg;
361             }
362         }
363         close( LOG_HANDLE );
364     }
368 #===  FUNCTION  ================================================================
369 #         NAME:  check_cmdline_param
370 #   PARAMETERS:  nothing
371 #      RETURNS:  nothing
372 #  DESCRIPTION:  validates commandline parameter
373 #===============================================================================
374 sub check_cmdline_param () {
375     my $err_config;
376     my $err_counter = 0;
377         if(not defined($cfg_file)) {
378                 $cfg_file = "/etc/gosa-si/server.conf";
379                 if(! -r $cfg_file) {
380                         $err_config = "please specify a config file";
381                         $err_counter += 1;
382                 }
383     }
384     if( $err_counter > 0 ) {
385         &usage( "", 1 );
386         if( defined( $err_config)) { print STDERR "$err_config\n"}
387         print STDERR "\n";
388         exit( -1 );
389     }
393 #===  FUNCTION  ================================================================
394 #         NAME:  check_pid
395 #   PARAMETERS:  nothing
396 #      RETURNS:  nothing
397 #  DESCRIPTION:  handels pid processing
398 #===============================================================================
399 sub check_pid {
400     $pid = -1;
401     # Check, if we are already running
402     if( open(LOCK_FILE, "<$pid_file") ) {
403         $pid = <LOCK_FILE>;
404         if( defined $pid ) {
405             chomp( $pid );
406             if( -f "/proc/$pid/stat" ) {
407                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
408                 if( $stat ) {
409                                         daemon_log("ERROR: Already running",1);
410                     close( LOCK_FILE );
411                     exit -1;
412                 }
413             }
414         }
415         close( LOCK_FILE );
416         unlink( $pid_file );
417     }
419     # create a syslog msg if it is not to possible to open PID file
420     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
421         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
422         if (open(LOCK_FILE, '<', $pid_file)
423                 && ($pid = <LOCK_FILE>))
424         {
425             chomp($pid);
426             $msg .= "(PID $pid)\n";
427         } else {
428             $msg .= "(unable to read PID)\n";
429         }
430         if( ! ($foreground) ) {
431             openlog( $0, "cons,pid", "daemon" );
432             syslog( "warning", $msg );
433             closelog();
434         }
435         else {
436             print( STDERR " $msg " );
437         }
438         exit( -1 );
439     }
442 #===  FUNCTION  ================================================================
443 #         NAME:  import_modules
444 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
445 #                are stored
446 #      RETURNS:  nothing
447 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
448 #                state is on is imported by "require 'file';"
449 #===============================================================================
450 sub import_modules {
451     daemon_log(" ", 1);
453     if (not -e $modules_path) {
454         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
455     }
457     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
458     while (defined (my $file = readdir (DIR))) {
459         if (not $file =~ /(\S*?).pm$/) {
460             next;
461         }
462                 my $mod_name = $1;
464         # ArpHandler switch
465         if( $file =~ /ArpHandler.pm/ ) {
466             if( $arp_enabled eq "false" ) { next; }
467         }
468         
469         eval { require $file; };
470         if ($@) {
471             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
472             daemon_log("$@", 1);
473             exit;
474                 } else {
475                         my $info = eval($mod_name.'::get_module_info()');
476                         # Only load module if get_module_info() returns a non-null object
477                         if( $info ) {
478                                 my ($input_address, $input_key, $event_hash) = @{$info};
479                                 $known_modules->{$mod_name} = $info;
480                                 daemon_log("0 INFO: module $mod_name loaded", 5);
481                         }
482                 }
483     }   
485     close (DIR);
488 #===  FUNCTION  ================================================================
489 #         NAME:  password_check
490 #   PARAMETERS:  nothing
491 #      RETURNS:  nothing
492 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
493 #                the same password
494 #===============================================================================
495 sub password_check {
496     my $passwd_hash = {};
497     while (my ($mod_name, $mod_info) = each %$known_modules) {
498         my $mod_passwd = @$mod_info[1];
499         if (not defined $mod_passwd) { next; }
500         if (not exists $passwd_hash->{$mod_passwd}) {
501             $passwd_hash->{$mod_passwd} = $mod_name;
503         # escalates critical error
504         } else {
505             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
506             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
507             exit( -1 );
508         }
509     }
514 #===  FUNCTION  ================================================================
515 #         NAME:  sig_int_handler
516 #   PARAMETERS:  signal - string - signal arose from system
517 #      RETURNS:  nothing
518 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
519 #===============================================================================
520 sub sig_int_handler {
521     my ($signal) = @_;
523 #       if (defined($ldap_handle)) {
524 #               $ldap_handle->disconnect;
525 #       }
526     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
527     
529     daemon_log("shutting down gosa-si-server", 1);
530     system("kill `ps -C gosa-si-server -o pid=`");
532 $SIG{INT} = \&sig_int_handler;
535 sub check_key_and_xml_validity {
536     my ($crypted_msg, $module_key, $session_id) = @_;
537     my $msg;
538     my $msg_hash;
539     my $error_string;
540     eval{
541         $msg = &decrypt_msg($crypted_msg, $module_key);
543         if ($msg =~ /<xml>/i){
544             $msg =~ s/\s+/ /g;  # just for better daemon_log
545             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 9);
546             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
548             ##############
549             # check header
550             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
551             my $header_l = $msg_hash->{'header'};
552             if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
553             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
554             my $header = @{$header_l}[0];
555             if( 0 == length $header) { die 'empty string in header tag'; }
557             ##############
558             # check source
559             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
560             my $source_l = $msg_hash->{'source'};
561             if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
562             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
563             my $source = @{$source_l}[0];
564             if( 0 == length $source) { die 'source error'; }
566             ##############
567             # check target
568             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
569             my $target_l = $msg_hash->{'target'};
570             if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
571         }
572     };
573     if($@) {
574         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
575         $msg = undef;
576         $msg_hash = undef;
577     }
579     return ($msg, $msg_hash);
583 sub check_outgoing_xml_validity {
584     my ($msg, $session_id) = @_;
586     my $msg_hash;
587     eval{
588         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
590         ##############
591         # check header
592         my $header_l = $msg_hash->{'header'};
593         if( 1 != @{$header_l} ) {
594             die 'no or more than one headers specified';
595         }
596         my $header = @{$header_l}[0];
597         if( 0 == length $header) {
598             die 'header has length 0';
599         }
601         ##############
602         # check source
603         my $source_l = $msg_hash->{'source'};
604         if( 1 != @{$source_l} ) {
605             die 'no or more than 1 sources specified';
606         }
607         my $source = @{$source_l}[0];
608         if( 0 == length $source) {
609             die 'source has length 0';
610         }
612                                 # Check if source contains hostname instead of ip address
613                                 if($source =~ /^[a-z][a-z0-9\.]+:\d+$/i) {
614                                                 my ($hostname,$port) = split(/:/, $source);
615                                                 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
616                                                 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
617                                                         # Write ip address to $source variable
618                                                         $source = "$ip_address:$port";
619                                                 }
620                                 }
621         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
622                 $source =~ /^GOSA$/i) {
623             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
624         }
625         
626         ##############
627         # check target  
628         my $target_l = $msg_hash->{'target'};
629         if( 0 == @{$target_l} ) {
630             die "no targets specified";
631         }
632         foreach my $target (@$target_l) {
633             if( 0 == length $target) {
634                 die "target has length 0";
635             }
636             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
637                     $target =~ /^GOSA$/i ||
638                     $target =~ /^\*$/ ||
639                     $target =~ /KNOWN_SERVER/i ||
640                     $target =~ /JOBDB/i ||
641                     $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 ){
642                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
643             }
644         }
645     };
646     if($@) {
647         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
648         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
649         $msg_hash = undef;
650     }
652     return ($msg_hash);
656 sub input_from_known_server {
657     my ($input, $remote_ip, $session_id) = @_ ;  
658     my ($msg, $msg_hash, $module);
660     my $sql_statement= "SELECT * FROM known_server";
661     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
663     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
664         my $host_name = $hit->{hostname};
665         if( not $host_name =~ "^$remote_ip") {
666             next;
667         }
668         my $host_key = $hit->{hostkey};
669         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
670         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
672         # check if module can open msg envelope with module key
673         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
674         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
675             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
676             daemon_log("$@", 8);
677             next;
678         }
679         else {
680             $msg = $tmp_msg;
681             $msg_hash = $tmp_msg_hash;
682             $module = "ServerPackages";
683             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
684             last;
685         }
686     }
688     if( (!$msg) || (!$msg_hash) || (!$module) ) {
689         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
690     }
691   
692     return ($msg, $msg_hash, $module);
696 sub input_from_known_client {
697     my ($input, $remote_ip, $session_id) = @_ ;  
698     my ($msg, $msg_hash, $module);
700     my $sql_statement= "SELECT * FROM known_clients";
701     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
702     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
703         my $host_name = $hit->{hostname};
704         if( not $host_name =~ /^$remote_ip:\d*$/) {
705                 next;
706                 }
707         my $host_key = $hit->{hostkey};
708         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
709         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
711         # check if module can open msg envelope with module key
712         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
714         if( (!$msg) || (!$msg_hash) ) {
715             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
716             &daemon_log("$@", 8);
717             next;
718         }
719         else {
720             $module = "ClientPackages";
721             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
722             last;
723         }
724     }
726     if( (!$msg) || (!$msg_hash) || (!$module) ) {
727         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
728     }
730     return ($msg, $msg_hash, $module);
734 sub input_from_unknown_host {
735         no strict "refs";
736         my ($input, $session_id) = @_ ;
737         my ($msg, $msg_hash, $module);
738         my $error_string;
740         my %act_modules = %$known_modules;
742         while( my ($mod, $info) = each(%act_modules)) {
744                 # check a key exists for this module
745                 my $module_key = ${$mod."_key"};
746                 if( not defined $module_key ) {
747                         if( $mod eq 'ArpHandler' ) {
748                                 next;
749                         }
750                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
751                         next;
752                 }
753                 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
755                 # check if module can open msg envelope with module key
756                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
757                 if( (not defined $msg) || (not defined $msg_hash) ) {
758                         next;
759                 } else {
760                         $module = $mod;
761             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
762                         last;
763                 }
764         }
766         if( (!$msg) || (!$msg_hash) || (!$module)) {
767                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
768         }
770         return ($msg, $msg_hash, $module);
774 sub create_ciphering {
775     my ($passwd) = @_;
776         if((!defined($passwd)) || length($passwd)==0) {
777                 $passwd = "";
778         }
779     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
780     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
781     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
782     $my_cipher->set_iv($iv);
783     return $my_cipher;
787 sub encrypt_msg {
788     my ($msg, $key) = @_;
789     my $my_cipher = &create_ciphering($key);
790     my $len;
791     {
792             use bytes;
793             $len= 16-length($msg)%16;
794     }
795     $msg = "\0"x($len).$msg;
796     $msg = $my_cipher->encrypt($msg);
797     chomp($msg = &encode_base64($msg));
798     # there are no newlines allowed inside msg
799     $msg=~ s/\n//g;
800     return $msg;
804 sub decrypt_msg {
806     my ($msg, $key) = @_ ;
807     $msg = &decode_base64($msg);
808     my $my_cipher = &create_ciphering($key);
809     $msg = $my_cipher->decrypt($msg); 
810     $msg =~ s/\0*//g;
811     return $msg;
815 sub get_encrypt_key {
816     my ($target) = @_ ;
817     my $encrypt_key;
818     my $error = 0;
820     # target can be in known_server
821     if( not defined $encrypt_key ) {
822         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
823         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
824         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
825             my $host_name = $hit->{hostname};
826             if( $host_name ne $target ) {
827                 next;
828             }
829             $encrypt_key = $hit->{hostkey};
830             last;
831         }
832     }
834     # target can be in known_client
835     if( not defined $encrypt_key ) {
836         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
837         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
838         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
839             my $host_name = $hit->{hostname};
840             if( $host_name ne $target ) {
841                 next;
842             }
843             $encrypt_key = $hit->{hostkey};
844             last;
845         }
846     }
848     return $encrypt_key;
852 #===  FUNCTION  ================================================================
853 #         NAME:  open_socket
854 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
855 #                [PeerPort] string necessary if port not appended by PeerAddr
856 #      RETURNS:  socket IO::Socket::INET
857 #  DESCRIPTION:  open a socket to PeerAddr
858 #===============================================================================
859 sub open_socket {
860     my ($PeerAddr, $PeerPort) = @_ ;
861     if(defined($PeerPort)){
862         $PeerAddr = $PeerAddr.":".$PeerPort;
863     }
864     my $socket;
865     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
866             Porto => "tcp",
867             Type => SOCK_STREAM,
868             Timeout => 5,
869             );
870     if(not defined $socket) {
871         return;
872     }
873 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
874     return $socket;
878 sub send_msg_to_target {
879     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
880     my $error = 0;
881     my $header;
882     my $timestamp = &get_time();
883     my $new_status;
884     my $act_status;
885     my ($sql_statement, $res);
886   
887     if( $msg_header ) {
888         $header = "'$msg_header'-";
889     } else {
890         $header = "";
891     }
893         # Patch the source ip
894         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
895                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
896                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
897         }
899     # encrypt xml msg
900     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
902     # opensocket
903     my $socket = &open_socket($address);
904     if( !$socket ) {
905         daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
906         $error++;
907     }
908     
909     if( $error == 0 ) {
910         # send xml msg
911         print $socket $crypted_msg."\n";
913         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
914         daemon_log("$session_id DEBUG: message:\n$msg", 9);
915         
916     }
918     # close socket in any case
919     if( $socket ) {
920         close $socket;
921     }
923     if( $error > 0 ) { $new_status = "down"; }
924     else { $new_status = $msg_header; }
927     # known_clients
928     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
929     $res = $known_clients_db->select_dbentry($sql_statement);
930     if( keys(%$res) == 1) {
931         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
932         if ($act_status eq "down" && $new_status eq "down") {
933             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
934             $res = $known_clients_db->del_dbentry($sql_statement);
935             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
936         } else { 
937             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
938             $res = $known_clients_db->update_dbentry($sql_statement);
939             if($new_status eq "down"){
940                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
941             } else {
942                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
943             }
944         }
945     }
947     # known_server
948     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
949     $res = $known_server_db->select_dbentry($sql_statement);
950     if( keys(%$res) == 1) {
951         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
952         if ($act_status eq "down" && $new_status eq "down") {
953             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
954             $res = $known_server_db->del_dbentry($sql_statement);
955             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
956         } 
957         else { 
958             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
959             $res = $known_server_db->update_dbentry($sql_statement);
960             if($new_status eq "down"){
961                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
962             } else {
963                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
964             }
965         }
966     }
967     return $error; 
971 sub update_jobdb_status_for_send_msgs {
972     my ($session_id, $answer, $error) = @_;
973     &daemon_log("$session_id DEBUG: try to update job status", 7); 
974     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
975         my $jobdb_id = $1;
976     
977         $answer =~ /<header>(.*)<\/header>/;
978         my $job_header = $1;
980         $answer =~ /<target>(.*)<\/target>/;
981         my $job_target = $1;
982             
983         # Sending msg failed
984         if( $error ) {
986             # Set jobs to done, jobs do not need to deliver their message in any case
987             if (($job_header eq "trigger_action_localboot")
988                     ||($job_header eq "trigger_action_lock")
989                     ||($job_header eq "trigger_action_halt") 
990                     ) {
991                 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
992                 &daemon_log("$session_id DEBUG: $sql_statement", 7); 
993                 my $res = $job_db->update_dbentry($sql_statement);
994                 
995             # Reactivate jobs, jobs need to deliver their message
996             } elsif (($job_header eq "trigger_action_activate")
997                     ||($job_header eq "trigger_action_update")
998                     ||($job_header eq "trigger_action_reinstall") 
999                     ||($job_header eq "trigger_activate_new")
1000                     ) {
1001                 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1003             # For all other messages
1004             } else {
1005                 my $sql_statement = "UPDATE $job_queue_tn ".
1006                     "SET status='error', result='can not deliver msg, please consult log file' ".
1007                     "WHERE id=$jobdb_id";
1008                 &daemon_log("$session_id DEBUG: $sql_statement", 7); 
1009                 my $res = $job_db->update_dbentry($sql_statement);
1010             }
1012         # Sending msg was successful
1013         } else {
1014             # Set jobs localboot, lock, activate, halt, reboot and wake to done
1015             # jobs reinstall, update, inst_update do themself setting to done
1016             if (($job_header eq "trigger_action_localboot")
1017                     ||($job_header eq "trigger_action_lock")
1018                     ||($job_header eq "trigger_action_activate")
1019                     ||($job_header eq "trigger_action_halt") 
1020                     ||($job_header eq "trigger_action_reboot")
1021                     ||($job_header eq "trigger_action_wake")
1022                     ||($job_header eq "trigger_wake")
1023                     ) {
1025                 my $sql_statement = "UPDATE $job_queue_tn ".
1026                     "SET status='done' ".
1027                     "WHERE id=$jobdb_id AND status='processed'";
1028                 &daemon_log("$session_id DEBUG: $sql_statement", 7); 
1029                 my $res = $job_db->update_dbentry($sql_statement);
1030             } else { 
1031                 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 7); 
1032             } 
1033         } 
1034     } else { 
1035         &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag: $answer", 7); 
1036     }
1039 sub reactivate_job_with_delay {
1040     my ($session_id, $target, $header, $delay) = @_ ;
1041     # Sometimes the client is still booting or does not wake up, in this case reactivate the job (if it exists) with a delay of n sec
1042     
1043     if (not defined $delay) { $delay = 30 } ;
1044     my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1046     my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$target' AND headertag='$header')"; 
1047     my $res = $job_db->update_dbentry($sql);
1048     daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1049             "cause client '$target' is currently not available", 5);
1050     daemon_log("$session_id $sql", 7);                             
1051     return;
1055 sub sig_handler {
1056         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1057         daemon_log("0 INFO got signal '$signal'", 1); 
1058         $kernel->sig_handled();
1059         return;
1063 sub msg_to_decrypt {
1064         my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1065         my $session_id = $session->ID;
1066         my ($msg, $msg_hash, $module);
1067         my $error = 0;
1069         # fetch new msg out of @msgs_to_decrypt
1070         my $tmp_next_msg = shift @msgs_to_decrypt;
1071     my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1073         # msg is from a new client or gosa
1074         ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1076         # msg is from a gosa-si-server
1077         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1078                 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1079         }
1080         # msg is from a gosa-si-client
1081         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1082                 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1083         }
1084         # an error occurred
1085         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1086                 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1087                 # could not understand a msg from its server the client cause a re-registering process
1088         my $remote_ip = $heap->{'remote_ip'};
1089         my $remote_port = $heap->{'remote_port'};
1090         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1091         my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1093                 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1094                         "' to cause a re-registering of the client if necessary", 3);
1095                 $error++;
1096         }
1099         my $header;
1100         my $target;
1101         my $source;
1102         my $done = 0;
1103         my $sql;
1104         my $res;
1106         # check whether this message should be processed here
1107         if ($error == 0) {
1108                 $header = @{$msg_hash->{'header'}}[0];
1109                 $target = @{$msg_hash->{'target'}}[0];
1110                 $source = @{$msg_hash->{'source'}}[0];
1111                 my $not_found_in_known_clients_db = 0;
1112                 my $not_found_in_known_server_db = 0;
1113                 my $not_found_in_foreign_clients_db = 0;
1114                 my $local_address;
1115                 my $local_mac;
1116                 my ($target_ip, $target_port) = split(':', $target);
1118                 # Determine the local ip address if target is an ip address
1119                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1120                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1121                 } else {
1122                         $local_address = $server_address;
1123                 }
1125                 # Determine the local mac address if target is a mac address
1126                 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) {
1127                         my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1128                         my $network_interface= &get_interface_for_ip($loc_ip);
1129                         $local_mac = &get_mac_for_interface($network_interface);
1130                 } else {
1131                         $local_mac = $server_mac_address;
1132                 }
1134                 # target and source is equal to GOSA -> process here
1135                 if (not $done) {
1136                         if ($target eq "GOSA" && $source eq "GOSA") {
1137                                 $done = 1;                    
1138                                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1139                         }
1140                 }
1142                 # target is own address without forward_to_gosa-tag -> process here
1143                 if (not $done) {
1144                         #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1145                         if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1146                                 $done = 1;
1147                                 if ($source eq "GOSA") {
1148                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1149                                 }
1150                                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1151                         }
1152                 }
1154                 # target is a client address in known_clients -> process here
1155                 if (not $done) {
1156                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1157                         $res = $known_clients_db->select_dbentry($sql);
1158                         if (keys(%$res) > 0) {
1159                                 $done = 1; 
1160                                 my $hostname = $res->{1}->{'hostname'};
1161                                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1162                                 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1163                                 if ($source eq "GOSA") {
1164                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1165                                 }
1166                                 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1168                         } else {
1169                                 $not_found_in_known_clients_db = 1;
1170                         }
1171                 }
1173                 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1174                 if (not $done) {
1175                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1176                         my $gosa_at;
1177                         my $gosa_session_id;
1178                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1179                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1180                                 if ($gosa_at ne $local_address) {
1181                                         $done = 1;
1182                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7); 
1183                                 }
1184                         }
1185                 }
1187                 # if message should be processed here -> add message to incoming_db
1188                 if ($done) {
1189                         # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1190                         # so gosa-si-server knows how to process this kind of messages
1191                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1192                                 $module = "GosaPackages";
1193                         }
1195                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1196                                         primkey=>[],
1197                                         headertag=>$header,
1198                                         targettag=>$target,
1199                                         xmlmessage=>&encode_base64($msg),
1200                                         timestamp=>&get_time,
1201                                         module=>$module,
1202                                         sessionid=>$session_id,
1203                                 } );
1205                 }
1207                 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1208                 if (not $done) {
1209                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1210                         my $gosa_at;
1211                         my $gosa_session_id;
1212                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1213                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1214                                 if ($gosa_at eq $local_address) {
1215                                         my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1216                                         if( defined $session_reference ) {
1217                                                 $heap = $session_reference->get_heap();
1218                                         }
1219                                         if(exists $heap->{'client'}) {
1220                                                 $msg = &encrypt_msg($msg, $GosaPackages_key);
1221                                                 $heap->{'client'}->put($msg);
1222                                                 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5); 
1223                                         }
1224                                         $done = 1;
1225                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1226                                 }
1227                         }
1229                 }
1231                 # target is a client address in foreign_clients -> forward to registration server
1232                 if (not $done) {
1233                         $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1234                         $res = $foreign_clients_db->select_dbentry($sql);
1235                         if (keys(%$res) > 0) {
1236                                 my $hostname = $res->{1}->{'hostname'};
1237                                 my ($host_ip, $host_port) = split(/:/, $hostname);
1238                                 my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1239                                 my $regserver = $res->{1}->{'regserver'};
1240                                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1241                                 my $res = $known_server_db->select_dbentry($sql);
1242                                 if (keys(%$res) > 0) {
1243                                         my $regserver_key = $res->{1}->{'hostkey'};
1244                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1245                                         $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1246                                         if ($source eq "GOSA") {
1247                                                 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1248                                         }
1249                                         &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1250                                 }
1251                                 $done = 1;
1252                                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1253                         } else {
1254                                 $not_found_in_foreign_clients_db = 1;
1255                         }
1256                 }
1258                 # target is a server address -> forward to server
1259                 if (not $done) {
1260                         $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1261                         $res = $known_server_db->select_dbentry($sql);
1262                         if (keys(%$res) > 0) {
1263                                 my $hostkey = $res->{1}->{'hostkey'};
1265                                 if ($source eq "GOSA") {
1266                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1267                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1269                                 }
1271                                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1272                                 $done = 1;
1273                                 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1274                         } else {
1275                                 $not_found_in_known_server_db = 1;
1276                         }
1277                 }
1280                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1281                 if ( $not_found_in_foreign_clients_db 
1282                         && $not_found_in_known_server_db
1283                         && $not_found_in_known_clients_db) {
1284                         &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);
1285             if ($header =~ /^gosa_/ || $header =~ /^job_/) { 
1286                 $module = "GosaPackages"; 
1287             }
1288                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1289                                         primkey=>[],
1290                                         headertag=>$header,
1291                                         targettag=>$target,
1292                                         xmlmessage=>&encode_base64($msg),
1293                                         timestamp=>&get_time,
1294                                         module=>$module,
1295                                         sessionid=>$session_id,
1296                                 } );
1297                         $done = 1;
1298                 }
1301                 if (not $done) {
1302                         daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1303                         if ($source eq "GOSA") {
1304                                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1305                                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1307                                 my $session_reference = $kernel->ID_id_to_session($session_id);
1308                                 if( defined $session_reference ) {
1309                                         $heap = $session_reference->get_heap();
1310                                 }
1311                                 if(exists $heap->{'client'}) {
1312                                         $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1313                                         $heap->{'client'}->put($error_msg);
1314                                 }
1315                         }
1316                 }
1318         }
1320         return;
1324 sub next_task {
1325     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1326     my $running_task = POE::Wheel::Run->new(
1327             Program => sub { process_task($session, $heap, $task) },
1328             StdioFilter => POE::Filter::Reference->new(),
1329             StdoutEvent  => "task_result",
1330             StderrEvent  => "task_debug",
1331             CloseEvent   => "task_done",
1332             );
1333     $heap->{task}->{ $running_task->ID } = $running_task;
1336 sub handle_task_result {
1337     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1338     my $client_answer = $result->{'answer'};
1339     if( $client_answer =~ s/session_id=(\d+)$// ) {
1340         my $session_id = $1;
1341         if( defined $session_id ) {
1342             my $session_reference = $kernel->ID_id_to_session($session_id);
1343             if( defined $session_reference ) {
1344                 $heap = $session_reference->get_heap();
1345             }
1346         }
1348         if(exists $heap->{'client'}) {
1349             $heap->{'client'}->put($client_answer);
1350         }
1351     }
1352     $kernel->sig(CHLD => "child_reap");
1355 sub handle_task_debug {
1356     my $result = $_[ARG0];
1357     print STDERR "$result\n";
1360 sub handle_task_done {
1361     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1362     delete $heap->{task}->{$task_id};
1365 sub process_task {
1366     no strict "refs";
1367     #CHECK: Not @_[...]?
1368     my ($session, $heap, $task) = @_;
1369     my $error = 0;
1370     my $answer_l;
1371     my ($answer_header, @answer_target_l, $answer_source);
1372     my $client_answer = "";
1374     # prepare all variables needed to process message
1375     #my $msg = $task->{'xmlmessage'};
1376     my $msg = &decode_base64($task->{'xmlmessage'});
1377     my $incoming_id = $task->{'id'};
1378     my $module = $task->{'module'};
1379     my $header =  $task->{'headertag'};
1380     my $session_id = $task->{'sessionid'};
1381                 my $msg_hash;
1382                 eval {
1383         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1384                 }; 
1385                 daemon_log("ERROR: XML failure '$@'") if ($@);
1386     my $source = @{$msg_hash->{'source'}}[0];
1387     
1388     # set timestamp of incoming client uptodate, so client will not 
1389     # be deleted from known_clients because of expiration
1390     my $cur_time = &get_time();
1391     my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'"; 
1392     my $res = $known_clients_db->exec_statement($sql);
1394     ######################
1395     # process incoming msg
1396     if( $error == 0) {
1397         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1398         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1399         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1401         if ( 0 < @{$answer_l} ) {
1402             my $answer_str = join("\n", @{$answer_l});
1403             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1404                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1405             }
1406             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1407         } else {
1408             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1409         }
1411     }
1412     if( !$answer_l ) { $error++ };
1414     ########
1415     # answer
1416     if( $error == 0 ) {
1418         foreach my $answer ( @{$answer_l} ) {
1419             # check outgoing msg to xml validity
1420             my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1421             if( not defined $answer_hash ) { next; }
1422             
1423             $answer_header = @{$answer_hash->{'header'}}[0];
1424             @answer_target_l = @{$answer_hash->{'target'}};
1425             $answer_source = @{$answer_hash->{'source'}}[0];
1427             # deliver msg to all targets 
1428             foreach my $answer_target ( @answer_target_l ) {
1430                 # targets of msg are all gosa-si-clients in known_clients_db
1431                 if( $answer_target eq "*" ) {
1432                     # answer is for all clients
1433                     my $sql_statement= "SELECT * FROM known_clients";
1434                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1435                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1436                         my $host_name = $hit->{hostname};
1437                         my $host_key = $hit->{hostkey};
1438                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1439                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1440                     }
1441                 }
1443                 # targets of msg are all gosa-si-server in known_server_db
1444                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1445                     # answer is for all server in known_server
1446                     my $sql_statement= "SELECT * FROM $known_server_tn";
1447                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1448                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1449                         my $host_name = $hit->{hostname};
1450                         my $host_key = $hit->{hostkey};
1451                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1452                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1453                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1454                     }
1455                 }
1457                 # target of msg is GOsa
1458                                 elsif( $answer_target eq "GOSA" ) {
1459                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1460                                         my $add_on = "";
1461                     if( defined $session_id ) {
1462                         $add_on = ".session_id=$session_id";
1463                     }
1464                     # answer is for GOSA and has to returned to connected client
1465                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1466                     $client_answer = $gosa_answer.$add_on;
1467                 }
1469                 # target of msg is job queue at this host
1470                 elsif( $answer_target eq "JOBDB") {
1471                     $answer =~ /<header>(\S+)<\/header>/;   
1472                     my $header;
1473                     if( defined $1 ) { $header = $1; }
1474                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1475                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1476                 }
1478                 # Target of msg is a mac address
1479                 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 ) {
1480                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1482                     # Looking for macaddress in known_clients
1483                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1484                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1485                     my $found_ip_flag = 0;
1486                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1487                         my $host_name = $hit->{hostname};
1488                         my $host_key = $hit->{hostkey};
1489                         $answer =~ s/$answer_target/$host_name/g;
1490                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1491                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1492                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1493                         $found_ip_flag++ ;
1494                     }   
1496                     # Looking for macaddress in foreign_clients
1497                     if ($found_ip_flag == 0) {
1498                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1499                         my $res = $foreign_clients_db->select_dbentry($sql);
1500                         while( my ($hit_num, $hit) = each %{ $res } ) {
1501                             my $host_name = $hit->{hostname};
1502                             my $reg_server = $hit->{regserver};
1503                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1504                             
1505                             # Fetch key for reg_server
1506                             my $reg_server_key;
1507                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1508                             my $res = $known_server_db->select_dbentry($sql);
1509                             if (exists $res->{1}) {
1510                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1511                             } else {
1512                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1513                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1514                                 $reg_server_key = undef;
1515                             }
1517                             # Send answer to server where client is registered
1518                             if (defined $reg_server_key) {
1519                                 $answer =~ s/$answer_target/$host_name/g;
1520                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1521                                 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1522                                 $found_ip_flag++ ;
1523                             }
1524                         }
1525                     }
1527                     # No mac to ip matching found
1528                     if( $found_ip_flag == 0) {
1529                         daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1530                         &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1531                     }
1533                 # Answer is for one specific host   
1534                 } else {
1535                     # get encrypt_key
1536                     my $encrypt_key = &get_encrypt_key($answer_target);
1537                     if( not defined $encrypt_key ) {
1538                         # unknown target
1539                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1540                         next;
1541                     }
1542                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1543                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1544                 }
1545             }
1546         }
1547     }
1549     my $filter = POE::Filter::Reference->new();
1550     my %result = ( 
1551             status => "seems ok to me",
1552             answer => $client_answer,
1553             );
1555     my $output = $filter->put( [ \%result ] );
1556     print @$output;
1561 sub session_start {
1562     my ($kernel) = $_[KERNEL];
1563     $global_kernel = $kernel;
1564     $kernel->yield('register_at_foreign_servers');
1565         $kernel->yield('create_fai_server_db', $fai_server_tn );
1566         $kernel->yield('create_fai_release_db', $fai_release_tn );
1567     $kernel->yield('watch_for_next_tasks');
1568         $kernel->sig(USR1 => "sig_handler");
1569         $kernel->sig(USR2 => "recreate_packages_db");
1570         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1571         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1572     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1573         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1574     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1575         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1576     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1578     # Start opsi check
1579     if ($opsi_enabled eq "true") {
1580         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1581     }
1586 sub watch_for_done_jobs {
1587         #CHECK: $heap for what?
1588         my ($kernel,$heap) = @_[KERNEL, HEAP];
1590         my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1591         my $res = $job_db->select_dbentry( $sql_statement );
1593         while( my ($id, $hit) = each %{$res} ) {
1594                 my $jobdb_id = $hit->{id};
1595                 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1596                 my $res = $job_db->del_dbentry($sql_statement); 
1597         }
1599         $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1603 sub watch_for_opsi_jobs {
1604     my ($kernel) = $_[KERNEL];
1606     # 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 
1607     # opsi install job is to parse the xml message. There is still the correct header.
1608     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1609         my $res = $job_db->select_dbentry( $sql_statement );
1611     # Ask OPSI for an update of the running jobs
1612     while (my ($id, $hit) = each %$res ) {
1613         # Determine current parameters of the job
1614         my $hostId = $hit->{'plainname'};
1615         my $macaddress = $hit->{'macaddress'};
1616         my $progress = $hit->{'progress'};
1618         my $result= {};
1619         
1620         # For hosts, only return the products that are or get installed
1621         my $callobj;
1622         $callobj = {
1623             method  => 'getProductStates_hash',
1624             params  => [ $hostId ],
1625             id  => 1,
1626         };
1627         
1628         my $hres = $opsi_client->call($opsi_url, $callobj);
1629         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1630         if (not &check_opsi_res($hres)) {
1631             my $htmp= $hres->result->{$hostId};
1632         
1633             # Check state != not_installed or action == setup -> load and add
1634             my $products= 0;
1635             my $installed= 0;
1636             my $installing = 0;
1637             my $error= 0;  
1638             my @installed_list;
1639             my @error_list;
1640             my $act_status = "none";
1641             foreach my $product (@{$htmp}){
1643                 if ($product->{'installationStatus'} ne "not_installed" or
1644                         $product->{'actionRequest'} eq "setup"){
1646                     # Increase number of products for this host
1647                     $products++;
1648         
1649                     if ($product->{'installationStatus'} eq "failed"){
1650                         $result->{$product->{'productId'}}= "error";
1651                         unshift(@error_list, $product->{'productId'});
1652                         $error++;
1653                     }
1654                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1655                         $result->{$product->{'productId'}}= "installed";
1656                         unshift(@installed_list, $product->{'productId'});
1657                         $installed++;
1658                     }
1659                     if ($product->{'installationStatus'} eq "installing"){
1660                         $result->{$product->{'productId'}}= "installing";
1661                         $installing++;
1662                         $act_status = "installing - ".$product->{'productId'};
1663                     }
1664                 }
1665             }
1666         
1667             # Estimate "rough" progress, avoid division by zero
1668             if ($products == 0) {
1669                 $result->{'progress'}= 0;
1670             } else {
1671                 $result->{'progress'}= int($installed * 100 / $products);
1672             }
1674             # Set updates in job queue
1675             if ((not $error) && (not $installing) && ($installed)) {
1676                 $act_status = "installed - ".join(", ", @installed_list);
1677             }
1678             if ($error) {
1679                 $act_status = "error - ".join(", ", @error_list);
1680             }
1681             if ($progress ne $result->{'progress'} ) {
1682                 # Updating progress and result 
1683                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1684                 my $update_res = $job_db->update_dbentry($update_statement);
1685             }
1686             if ($progress eq 100) { 
1687                 # Updateing status
1688                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1689                 if ($error) {
1690                     $done_statement .= "status='error'";
1691                 } else {
1692                     $done_statement .= "status='done'";
1693                 }
1694                 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1695                 my $done_res = $job_db->update_dbentry($done_statement);
1696             }
1699         }
1700     }
1702     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1706 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1707 sub watch_for_modified_jobs {
1708     my ($kernel,$heap) = @_[KERNEL, HEAP];
1710     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')"; 
1711     my $res = $job_db->select_dbentry( $sql_statement );
1712     
1713     # if db contains no jobs which should be update, do nothing
1714     if (keys %$res != 0) {
1716         if ($job_synchronization  eq "true") {
1717             # make out of the db result a gosa-si message   
1718             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1719  
1720             # update all other SI-server
1721             &inform_all_other_si_server($update_msg);
1722         }
1724         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1725         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1726         $res = $job_db->update_dbentry($sql_statement);
1727     }
1729     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1733 sub watch_for_new_jobs {
1734         if($watch_for_new_jobs_in_progress == 0) {
1735                 $watch_for_new_jobs_in_progress = 1;
1736                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1738                 # check gosa job quaeue for jobs with executable timestamp
1739                 my $timestamp = &get_time();
1740                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1741                 my $res = $job_db->exec_statement( $sql_statement );
1743                 # Merge all new jobs that would do the same actions
1744                 my @drops;
1745                 my $hits;
1746                 foreach my $hit (reverse @{$res} ) {
1747                         my $macaddress= lc @{$hit}[8];
1748                         my $headertag= @{$hit}[5];
1749                         if(
1750                                 defined($hits->{$macaddress}) &&
1751                                 defined($hits->{$macaddress}->{$headertag}) &&
1752                                 defined($hits->{$macaddress}->{$headertag}[0])
1753                         ) {
1754                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1755                         }
1756                         $hits->{$macaddress}->{$headertag}= $hit;
1757                 }
1759                 # Delete new jobs with a matching job in state 'processing'
1760                 foreach my $macaddress (keys %{$hits}) {
1761                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1762                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1763                                 if(defined($jobdb_id)) {
1764                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1765                                         my $res = $job_db->exec_statement( $sql_statement );
1766                                         foreach my $hit (@{$res}) {
1767                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1768                                         }
1769                                 } else {
1770                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1771                                 }
1772                         }
1773                 }
1775                 # Commit deletion
1776                 $job_db->exec_statementlist(\@drops);
1778                 # Look for new jobs that could be executed
1779                 foreach my $macaddress (keys %{$hits}) {
1781                         # Look if there is an executing job
1782                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1783                         my $res = $job_db->exec_statement( $sql_statement );
1785                         # Skip new jobs for host if there is a processing job
1786                         if(defined($res) and defined @{$res}[0]) {
1787                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1788                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1789                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1790                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1791                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1792                                         if(defined($res_2) and defined @{$res_2}[0]) {
1793                                                 # Set status from goto-activation to 'waiting' and update timestamp
1794                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting', timestamp='".&calc_timestamp(&get_time(), 'plus', 30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1795                                         }
1796                                 }
1797                                 next;
1798                         }
1800                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1801                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1802                                 if(defined($jobdb_id)) {
1803                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1805                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1806                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1807                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1809                                         # expect macaddress is unique!!!!!!
1810                                         my $target = $res_hash->{1}->{hostname};
1812                                         # change header
1813                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1815                                         # add sqlite_id
1816                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1818                                         $job_msg =~ /<header>(\S+)<\/header>/;
1819                                         my $header = $1 ;
1820                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");                    
1822                                         # update status in job queue to ...
1823                     # ... 'processing', for jobs: 'reinstall', 'update'
1824                     if (($header =~ /gosa_trigger_action_reinstall/) 
1825                             || ($header =~ /gosa_trigger_activate_new/)
1826                             || ($header =~ /gosa_trigger_action_update/)) {
1827                         my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1828                         my $dbres = $job_db->update_dbentry($sql_statement);
1829                     }
1831                     # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1832                     else {
1833                         my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1834                         my $dbres = $job_db->update_dbentry($sql_statement);
1835                     }
1836                 
1838                                         # We don't want parallel processing
1839                                         last;
1840                                 }
1841                         }
1842                 }
1844                 $watch_for_new_jobs_in_progress = 0;
1845                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1846         }
1850 sub watch_for_new_messages {
1851     my ($kernel,$heap) = @_[KERNEL, HEAP];
1852     my @coll_user_msg;   # collection list of outgoing messages
1853     
1854     # check messaging_db for new incoming messages with executable timestamp
1855     my $timestamp = &get_time();
1856     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1857     my $res = $messaging_db->exec_statement( $sql_statement );
1858         foreach my $hit (@{$res}) {
1860         # create outgoing messages
1861         my $message_to = @{$hit}[3];
1862         # translate message_to to plain login name
1863         my @message_to_l = split(/,/, $message_to);  
1864                 my %receiver_h; 
1865                 foreach my $receiver (@message_to_l) {
1866                         if ($receiver =~ /^u_([\s\S]*)$/) {
1867                                 $receiver_h{$1} = 0;
1868                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1869                                 my $group_name = $1;
1870                                 # fetch all group members from ldap and add them to receiver hash
1871                                 my $ldap_handle = &get_ldap_handle();
1872                                 if (defined $ldap_handle) {
1873                                                 my $mesg = $ldap_handle->search(
1874                                                                                 base => $ldap_base,
1875                                                                                 scope => 'sub',
1876                                                                                 attrs => ['memberUid'],
1877                                                                                 filter => "cn=$group_name",
1878                                                                                 );
1879                                                 if ($mesg->count) {
1880                                                                 my @entries = $mesg->entries;
1881                                                                 foreach my $entry (@entries) {
1882                                                                                 my @receivers= $entry->get_value("memberUid");
1883                                                                                 foreach my $receiver (@receivers) { 
1884                                                                                                 $receiver_h{$receiver} = 0;
1885                                                                                 }
1886                                                                 }
1887                                                 } 
1888                                                 # translating errors ?
1889                                                 if ($mesg->code) {
1890                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1891                                                 }
1892                                 # ldap handle error ?           
1893                                 } else {
1894                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1895                                 }
1896                         } else {
1897                                 my $sbjct = &encode_base64(@{$hit}[1]);
1898                                 my $msg = &encode_base64(@{$hit}[7]);
1899                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1900                         }
1901                 }
1902                 my @receiver_l = keys(%receiver_h);
1904         my $message_id = @{$hit}[0];
1906         #add each outgoing msg to messaging_db
1907         my $receiver;
1908         foreach $receiver (@receiver_l) {
1909             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1910                 "VALUES ('".
1911                 $message_id."', '".    # id
1912                 @{$hit}[1]."', '".     # subject
1913                 @{$hit}[2]."', '".     # message_from
1914                 $receiver."', '".      # message_to
1915                 "none"."', '".         # flag
1916                 "out"."', '".          # direction
1917                 @{$hit}[6]."', '".     # delivery_time
1918                 @{$hit}[7]."', '".     # message
1919                 $timestamp."'".     # timestamp
1920                 ")";
1921             &daemon_log("M DEBUG: $sql_statement", 1);
1922             my $res = $messaging_db->exec_statement($sql_statement);
1923             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1924         }
1926         # set incoming message to flag d=deliverd
1927         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1928         &daemon_log("M DEBUG: $sql_statement", 7);
1929         $res = $messaging_db->update_dbentry($sql_statement);
1930         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1931     }
1933     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1934     return;
1937 sub watch_for_delivery_messages {
1938     my ($kernel, $heap) = @_[KERNEL, HEAP];
1940     # select outgoing messages
1941     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1942     #&daemon_log("0 DEBUG: $sql", 7);
1943     my $res = $messaging_db->exec_statement( $sql_statement );
1944     
1945     # build out msg for each    usr
1946     foreach my $hit (@{$res}) {
1947         my $receiver = @{$hit}[3];
1948         my $msg_id = @{$hit}[0];
1949         my $subject = @{$hit}[1];
1950         my $message = @{$hit}[7];
1952         # resolve usr -> host where usr is logged in
1953         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1954         #&daemon_log("0 DEBUG: $sql", 7);
1955         my $res = $login_users_db->exec_statement($sql);
1957         # receiver is logged in nowhere
1958         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1960         # receiver ist logged in at a client registered at local server
1961                 my $send_succeed = 0;
1962                 foreach my $hit (@$res) {
1963                                 my $receiver_host = @$hit[0];
1964                 my $delivered2host = 0;
1965                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1967                                 # Looking for host in know_clients_db 
1968                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1969                                 my $res = $known_clients_db->exec_statement($sql);
1971                 # Host is known in known_clients_db
1972                 if (ref(@$res[0]) eq "ARRAY") {
1973                     my $receiver_key = @{@{$res}[0]}[2];
1974                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1975                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1976                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1977                     if ($error == 0 ) {
1978                         $send_succeed++ ;
1979                         $delivered2host++ ;
1980                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
1981                     } else {
1982                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
1983                     }
1984                 }
1985                 
1986                 # Message already send, do not need to do anything more, otherwise ...
1987                 if ($delivered2host) { next;}
1988     
1989                 # ...looking for host in foreign_clients_db
1990                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1991                 $res = $foreign_clients_db->exec_statement($sql);
1992   
1993                                 # Host is known in foreign_clients_db 
1994                                 if (ref(@$res[0]) eq "ARRAY") { 
1995                     my $registration_server = @{@{$res}[0]}[2];
1996                     
1997                     # Fetch encryption key for registration server
1998                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1999                     my $res = $known_server_db->exec_statement($sql);
2000                     if (ref(@$res[0]) eq "ARRAY") { 
2001                         my $registration_server_key = @{@{$res}[0]}[3];
2002                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2003                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2004                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
2005                         if ($error == 0 ) {
2006                             $send_succeed++ ;
2007                             $delivered2host++ ;
2008                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
2009                         } else {
2010                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
2011                         }
2013                     } else {
2014                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2015                                 "registrated at server '$registration_server', ".
2016                                 "but no data available in known_server_db ", 1); 
2017                     }
2018                 }
2019                 
2020                 if (not $delivered2host) {
2021                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2022                 }
2023                 }
2025                 if ($send_succeed) {
2026                                 # set outgoing msg at db to deliverd
2027                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
2028                                 my $res = $messaging_db->exec_statement($sql); 
2029                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2030                 } else {
2031             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
2032         }
2033         }
2035     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
2036     return;
2040 sub watch_for_done_messages {
2041     my ($kernel,$heap) = @_[KERNEL, HEAP];
2043     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
2044     #&daemon_log("0 DEBUG: $sql", 7);
2045     my $res = $messaging_db->exec_statement($sql); 
2047     foreach my $hit (@{$res}) {
2048         my $msg_id = @{$hit}[0];
2050         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
2051         #&daemon_log("0 DEBUG: $sql", 7); 
2052         my $res = $messaging_db->exec_statement($sql);
2054         # not all usr msgs have been seen till now
2055         if ( ref(@$res[0]) eq "ARRAY") { next; }
2056         
2057         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
2058         #&daemon_log("0 DEBUG: $sql", 7);
2059         $res = $messaging_db->exec_statement($sql);
2060     
2061     }
2063     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2064     return;
2068 sub watch_for_old_known_clients {
2069     my ($kernel,$heap) = @_[KERNEL, HEAP];
2071     my $sql_statement = "SELECT * FROM $known_clients_tn";
2072     my $res = $known_clients_db->select_dbentry( $sql_statement );
2074     my $cur_time = int(&get_time());
2076     while ( my ($hit_num, $hit) = each %$res) {
2077         my $expired_timestamp = int($hit->{'timestamp'});
2078         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2079         my $dt = DateTime->new( year   => $1,
2080                 month  => $2,
2081                 day    => $3,
2082                 hour   => $4,
2083                 minute => $5,
2084                 second => $6,
2085                 );
2087         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2088         $expired_timestamp = $dt->ymd('').$dt->hms('');
2089         if ($cur_time > $expired_timestamp) {
2090             my $hostname = $hit->{'hostname'};
2091             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2092             my $del_res = $known_clients_db->exec_statement($del_sql);
2094             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2095         }
2097     }
2099     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2103 sub watch_for_next_tasks {
2104     my ($kernel,$heap) = @_[KERNEL, HEAP];
2106     my $sql = "SELECT * FROM $incoming_tn";
2107     my $res = $incoming_db->select_dbentry($sql);
2108     
2109     while ( my ($hit_num, $hit) = each %$res) {
2110         my $headertag = $hit->{'headertag'};
2111         if ($headertag =~ /^answer_(\d+)/) {
2112             # do not start processing, this message is for a still running POE::Wheel
2113             next;
2114         }
2115         my $message_id = $hit->{'id'};
2116         my $session_id = $hit->{'sessionid'};
2117         &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2118         $kernel->yield('next_task', $hit);
2120         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2121         my $res = $incoming_db->exec_statement($sql);
2122     }
2124     $kernel->delay_set('watch_for_next_tasks', 1); 
2128 sub get_ldap_handle {
2129         my ($session_id) = @_;
2130         my $heap;
2131         my $ldap_handle;
2133         if (not defined $session_id ) { $session_id = 0 };
2134         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2136         if ($session_id == 0) {
2137                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
2138                 $ldap_handle = Net::LDAP->new( $ldap_uri );
2139                 if (defined $ldap_handle) {
2140                         $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!"); 
2141                 } else {
2142                         daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2143                 }
2145         } else {
2146                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2147                 if( defined $session_reference ) {
2148                         $heap = $session_reference->get_heap();
2149                 }
2151                 if (not defined $heap) {
2152                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
2153                         return;
2154                 }
2156                 # TODO: This "if" is nonsense, because it doesn't prove that the
2157                 #       used handle is still valid - or if we've to reconnect...
2158                 #if (not exists $heap->{ldap_handle}) {
2159                         $ldap_handle = Net::LDAP->new( $ldap_uri );
2160                         $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!"); 
2161                         $heap->{ldap_handle} = $ldap_handle;
2162                 #}
2163         }
2164         return $ldap_handle;
2168 sub change_fai_state {
2169     my ($st, $targets, $session_id) = @_;
2170     $session_id = 0 if not defined $session_id;
2171     # Set FAI state to localboot
2172     my %mapActions= (
2173         reboot    => '',
2174         update    => 'softupdate',
2175         localboot => 'localboot',
2176         reinstall => 'install',
2177         rescan    => '',
2178         wake      => '',
2179         memcheck  => 'memcheck',
2180         sysinfo   => 'sysinfo',
2181         install   => 'install',
2182     );
2184     # Return if this is unknown
2185     if (!exists $mapActions{ $st }){
2186         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2187       return;
2188     }
2190     my $state= $mapActions{ $st };
2192     my $ldap_handle = &get_ldap_handle($session_id);
2193     if( defined($ldap_handle) ) {
2195       # Build search filter for hosts
2196         my $search= "(&(objectClass=GOhard)";
2197         foreach (@{$targets}){
2198             $search.= "(macAddress=$_)";
2199         }
2200         $search.= ")";
2202       # If there's any host inside of the search string, procress them
2203         if (!($search =~ /macAddress/)){
2204             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2205             return;
2206         }
2208       # Perform search for Unit Tag
2209       my $mesg = $ldap_handle->search(
2210           base   => $ldap_base,
2211           scope  => 'sub',
2212           attrs  => ['dn', 'FAIstate', 'objectClass'],
2213           filter => "$search"
2214           );
2216           if ($mesg->count) {
2217                   my @entries = $mesg->entries;
2218                   if (0 == @entries) {
2219                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2220                   }
2222                   foreach my $entry (@entries) {
2223                           # Only modify entry if it is not set to '$state'
2224                           if ($entry->get_value("FAIstate") ne "$state"){
2225                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2226                                   my $result;
2227                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2228                                   if (exists $tmp{'FAIobject'}){
2229                                           if ($state eq ''){
2230                                                   $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2231                                           } else {
2232                                                   $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2233                                           }
2234                                   } elsif ($state ne ''){
2235                                           $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2236                                   }
2238                                   # Errors?
2239                                   if ($result->code){
2240                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2241                                   }
2242                           } else {
2243                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
2244                           }  
2245                   }
2246           } else {
2247                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2248           }
2250     # if no ldap handle defined
2251     } else {
2252         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
2253     }
2255         return;
2259 sub change_goto_state {
2260     my ($st, $targets, $session_id) = @_;
2261     $session_id = 0  if not defined $session_id;
2263     # Switch on or off?
2264     my $state= $st eq 'active' ? 'active': 'locked';
2266     my $ldap_handle = &get_ldap_handle($session_id);
2267     if( defined($ldap_handle) ) {
2269       # Build search filter for hosts
2270       my $search= "(&(objectClass=GOhard)";
2271       foreach (@{$targets}){
2272         $search.= "(macAddress=$_)";
2273       }
2274       $search.= ")";
2276       # If there's any host inside of the search string, procress them
2277       if (!($search =~ /macAddress/)){
2278         return;
2279       }
2281       # Perform search for Unit Tag
2282       my $mesg = $ldap_handle->search(
2283           base   => $ldap_base,
2284           scope  => 'sub',
2285           attrs  => ['dn', 'gotoMode'],
2286           filter => "$search"
2287           );
2289       if ($mesg->count) {
2290         my @entries = $mesg->entries;
2291         foreach my $entry (@entries) {
2293           # Only modify entry if it is not set to '$state'
2294           if ($entry->get_value("gotoMode") ne $state){
2296             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2297             my $result;
2298             $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2300             # Errors?
2301             if ($result->code){
2302               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2303             }
2305           }
2306         }
2307       } else {
2308                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2309           }
2311     }
2315 sub run_recreate_packages_db {
2316     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2317     my $session_id = $session->ID;
2318         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2319         $kernel->yield('create_fai_release_db', $fai_release_tn);
2320         $kernel->yield('create_fai_server_db', $fai_server_tn);
2321         return;
2325 sub run_create_fai_server_db {
2326     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2327     my $session_id = $session->ID;
2328     my $task = POE::Wheel::Run->new(
2329             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2330             StdoutEvent  => "session_run_result",
2331             StderrEvent  => "session_run_debug",
2332             CloseEvent   => "session_run_done",
2333             );
2335     $heap->{task}->{ $task->ID } = $task;
2336     return;
2340 sub create_fai_server_db {
2341         my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2342         my $result;
2344         if (not defined $session_id) { $session_id = 0; }
2345         my $ldap_handle = &get_ldap_handle();
2346         if(defined($ldap_handle)) {
2347                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2348                 my $mesg= $ldap_handle->search(
2349                         base   => $ldap_base,
2350                         scope  => 'sub',
2351                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2352                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2353                 );
2354                 if($mesg->{'resultCode'} == 0 &&
2355                         $mesg->count != 0) {
2356                         foreach my $entry (@{$mesg->{entries}}) {
2357                                 if($entry->exists('FAIrepository')) {
2358                                         # Add an entry for each Repository configured for server
2359                                         foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2360                                                 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2361                                                 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2362                                                 $result= $fai_server_db->add_dbentry( { 
2363                                                                 table => $table_name,
2364                                                                 primkey => ['server', 'fai_release', 'tag'],
2365                                                                 server => $tmp_url,
2366                                                                 fai_release => $tmp_release,
2367                                                                 sections => $tmp_sections,
2368                                                                 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2369                                                         } );
2370                                         }
2371                                 }
2372                         }
2373                 }
2374                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2376                 # TODO: Find a way to post the 'create_packages_list_db' event
2377                 if(not defined($dont_create_packages_list)) {
2378                         &create_packages_list_db(undef, undef, $session_id);
2379                 }
2380         }       
2382         $ldap_handle->disconnect;
2383         return $result;
2387 sub run_create_fai_release_db {
2388         my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2389         my $session_id = $session->ID;
2390         my $task = POE::Wheel::Run->new(
2391                 Program => sub { &create_fai_release_db($table_name, $session_id) },
2392                 StdoutEvent  => "session_run_result",
2393                 StderrEvent  => "session_run_debug",
2394                 CloseEvent   => "session_run_done",
2395         );
2397         $heap->{task}->{ $task->ID } = $task;
2398         return;
2402 sub create_fai_release_db {
2403         my ($table_name, $session_id) = @_;
2404         my $result;
2406         # used for logging
2407         if (not defined $session_id) { $session_id = 0; }
2409         my $ldap_handle = &get_ldap_handle();
2410         if(defined($ldap_handle)) {
2411                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2412                 my $mesg= $ldap_handle->search(
2413                         base   => $ldap_base,
2414                         scope  => 'sub',
2415                         attrs  => [],
2416                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2417                 );
2418                 if(($mesg->code == 0) && ($mesg->count != 0))
2419                 {
2420                         daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,8);
2422                         # Walk through all possible FAI container ou's
2423                         my @sql_list;
2424                         my $timestamp= &get_time();
2425                         foreach my $ou (@{$mesg->{entries}}) {
2426                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2427                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2428                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2429                                         if(@tmp_array) {
2430                                                 foreach my $entry (@tmp_array) {
2431                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2432                                                                 my $sql= 
2433                                                                 "INSERT INTO $table_name "
2434                                                                 ."(timestamp, fai_release, class, type, state) VALUES ("
2435                                                                 .$timestamp.","
2436                                                                 ."'".$entry->{'release'}."',"
2437                                                                 ."'".$entry->{'class'}."',"
2438                                                                 ."'".$entry->{'type'}."',"
2439                                                                 ."'".$entry->{'state'}."')";
2440                                                                 push @sql_list, $sql;
2441                                                         }
2442                                                 }
2443                                         }
2444                                 }
2445                         }
2447                         daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",8);
2448                         if(@sql_list) {
2449                                 unshift @sql_list, "VACUUM";
2450                                 unshift @sql_list, "DELETE FROM $table_name";
2451                                 $fai_release_db->exec_statementlist(\@sql_list);
2452                         }
2453                         daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",7);
2454                 } else {
2455                         daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2456                 }
2457                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2458         }
2459         $ldap_handle->disconnect;
2460         return $result;
2463 sub get_fai_types {
2464         my $tmp_classes = shift || return undef;
2465         my @result;
2467         foreach my $type(keys %{$tmp_classes}) {
2468                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2469                         my $entry = {
2470                                 type => $type,
2471                                 state => $tmp_classes->{$type}[0],
2472                         };
2473                         push @result, $entry;
2474                 }
2475         }
2477         return @result;
2480 sub get_fai_state {
2481         my $result = "";
2482         my $tmp_classes = shift || return $result;
2484         foreach my $type(keys %{$tmp_classes}) {
2485                 if(defined($tmp_classes->{$type}[0])) {
2486                         $result = $tmp_classes->{$type}[0];
2487                         
2488                 # State is equal for all types in class
2489                         last;
2490                 }
2491         }
2493         return $result;
2496 sub resolve_fai_classes {
2497         my ($fai_base, $ldap_handle, $session_id) = @_;
2498         if (not defined $session_id) { $session_id = 0; }
2499         my $result;
2500         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2501         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2502         my $fai_classes;
2504         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2505         my $mesg= $ldap_handle->search(
2506                 base   => $fai_base,
2507                 scope  => 'sub',
2508                 attrs  => ['cn','objectClass','FAIstate'],
2509                 filter => $fai_filter,
2510         );
2511         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2513         if($mesg->{'resultCode'} == 0 &&
2514                 $mesg->count != 0) {
2515                 foreach my $entry (@{$mesg->{entries}}) {
2516                         if($entry->exists('cn')) {
2517                                 my $tmp_dn= $entry->dn();
2518                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2519                                         - length($fai_base) - 1 );
2521                                 # Skip classname and ou dn parts for class
2522                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2524                                 # Skip classes without releases
2525                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2526                                         next;
2527                                 }
2529                                 my $tmp_cn= $entry->get_value('cn');
2530                                 my $tmp_state= $entry->get_value('FAIstate');
2532                                 my $tmp_type;
2533                                 # Get FAI type
2534                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2535                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2536                                                 $tmp_type= $oclass;
2537                                                 last;
2538                                         }
2539                                 }
2541                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2542                                         # A Subrelease
2543                                         my @sub_releases = split(/,/, $tmp_release);
2545                                         # Walk through subreleases and build hash tree
2546                                         my $hash;
2547                                         while(my $tmp_sub_release = pop @sub_releases) {
2548                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2549                                         }
2550                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2551                                 } else {
2552                                         # A branch, no subrelease
2553                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2554                                 }
2555                         } elsif (!$entry->exists('cn')) {
2556                                 my $tmp_dn= $entry->dn();
2557                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2558                                         - length($fai_base) - 1 );
2559                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2561                                 # Skip classes without releases
2562                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2563                                         next;
2564                                 }
2566                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2567                                         # A Subrelease
2568                                         my @sub_releases= split(/,/, $tmp_release);
2570                                         # Walk through subreleases and build hash tree
2571                                         my $hash;
2572                                         while(my $tmp_sub_release = pop @sub_releases) {
2573                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2574                                         }
2575                                         # Remove the last two characters
2576                                         chop($hash);
2577                                         chop($hash);
2579                                         eval('$fai_classes->'.$hash.'= {}');
2580                                 } else {
2581                                         # A branch, no subrelease
2582                                         if(!exists($fai_classes->{$tmp_release})) {
2583                                                 $fai_classes->{$tmp_release} = {};
2584                                         }
2585                                 }
2586                         }
2587                 }
2589                 # The hash is complete, now we can honor the copy-on-write based missing entries
2590                 foreach my $release (keys %$fai_classes) {
2591                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2592                 }
2593         }
2594         return $result;
2597 sub apply_fai_inheritance {
2598        my $fai_classes = shift || return {};
2599        my $tmp_classes;
2601        # Get the classes from the branch
2602        foreach my $class (keys %{$fai_classes}) {
2603                # Skip subreleases
2604                if($class =~ /^ou=.*$/) {
2605                        next;
2606                } else {
2607                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2608                }
2609        }
2611        # Apply to each subrelease
2612        foreach my $subrelease (keys %{$fai_classes}) {
2613                if($subrelease =~ /ou=/) {
2614                        foreach my $tmp_class (keys %{$tmp_classes}) {
2615                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2616                                        $fai_classes->{$subrelease}->{$tmp_class} =
2617                                        deep_copy($tmp_classes->{$tmp_class});
2618                                } else {
2619                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2620                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2621                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2622                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2623                                                }
2624                                        }
2625                                }
2626                        }
2627                }
2628        }
2630        # Find subreleases in deeper levels
2631        foreach my $subrelease (keys %{$fai_classes}) {
2632                if($subrelease =~ /ou=/) {
2633                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2634                                if($subsubrelease =~ /ou=/) {
2635                                        apply_fai_inheritance($fai_classes->{$subrelease});
2636                                }
2637                        }
2638                }
2639        }
2641        return $fai_classes;
2644 sub get_fai_release_entries {
2645         my $tmp_classes = shift || return;
2646         my $parent = shift || "";
2647         my @result = shift || ();
2649         foreach my $entry (keys %{$tmp_classes}) {
2650                 if(defined($entry)) {
2651                         if($entry =~ /^ou=.*$/) {
2652                                 my $release_name = $entry;
2653                                 $release_name =~ s/ou=//g;
2654                                 if(length($parent)>0) {
2655                                         $release_name = $parent."/".$release_name;
2656                                 }
2657                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2658                                 foreach my $bufentry(@bufentries) {
2659                                         push @result, $bufentry;
2660                                 }
2661                         } else {
2662                                 my @types = get_fai_types($tmp_classes->{$entry});
2663                                 foreach my $type (@types) {
2664                                         push @result, 
2665                                         {
2666                                                 'class' => $entry,
2667                                                 'type' => $type->{'type'},
2668                                                 'release' => $parent,
2669                                                 'state' => $type->{'state'},
2670                                         };
2671                                 }
2672                         }
2673                 }
2674         }
2676         return @result;
2679 sub deep_copy {
2680         my $this = shift;
2681         if (not ref $this) {
2682                 $this;
2683         } elsif (ref $this eq "ARRAY") {
2684                 [map deep_copy($_), @$this];
2685         } elsif (ref $this eq "HASH") {
2686                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2687         } else { die "what type is $_?" }
2691 sub session_run_result {
2692     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2693     $kernel->sig(CHLD => "child_reap");
2696 sub session_run_debug {
2697     my $result = $_[ARG0];
2698     print STDERR "$result\n";
2701 sub session_run_done {
2702     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2703     delete $heap->{task}->{$task_id};
2707 sub create_sources_list {
2708         my $session_id = shift;
2709         my $ldap_handle = &main::get_ldap_handle;
2710         my $result="/tmp/gosa_si_tmp_sources_list";
2712         # Remove old file
2713         if(stat($result)) {
2714                 unlink($result);
2715                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2716         }
2718         my $fh;
2719         open($fh, ">$result");
2720         if (not defined $fh) {
2721                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2722                 return undef;
2723         }
2724         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2725                 my $mesg=$ldap_handle->search(
2726                         base    => $main::ldap_server_dn,
2727                         scope   => 'base',
2728                         attrs   => 'FAIrepository',
2729                         filter  => 'objectClass=FAIrepositoryServer'
2730                 );
2731                 if($mesg->count) {
2732                         foreach my $entry(@{$mesg->{'entries'}}) {
2733                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2734                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2735                                         my $line = "deb $server $release";
2736                                         $sections =~ s/,/ /g;
2737                                         $line.= " $sections";
2738                                         print $fh $line."\n";
2739                                 }
2740                         }
2741                 }
2742         } else {
2743                 if (defined $main::ldap_server_dn){
2744                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2745                 } else {
2746                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2747                 }
2748         }
2749         close($fh);
2751         return $result;
2755 sub run_create_packages_list_db {
2756     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2757         my $session_id = $session->ID;
2759         my $task = POE::Wheel::Run->new(
2760                                         Priority => +20,
2761                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2762                                         StdoutEvent  => "session_run_result",
2763                                         StderrEvent  => "session_run_debug",
2764                                         CloseEvent   => "session_run_done",
2765                                         );
2766         $heap->{task}->{ $task->ID } = $task;
2770 sub create_packages_list_db {
2771         my ($ldap_handle, $sources_file, $session_id) = @_;
2772         
2773         # it should not be possible to trigger a recreation of packages_list_db
2774         # while packages_list_db is under construction, so set flag packages_list_under_construction
2775         # which is tested befor recreation can be started
2776         if (-r $packages_list_under_construction) {
2777                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2778                 return;
2779         } else {
2780                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2781                 # set packages_list_under_construction to true
2782                 system("touch $packages_list_under_construction");
2783                 @packages_list_statements=();
2784         }
2786         if (not defined $session_id) { $session_id = 0; }
2787         if (not defined $ldap_handle) { 
2788                 $ldap_handle= &get_ldap_handle();
2790                 if (not defined $ldap_handle) {
2791                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2792                         unlink($packages_list_under_construction);
2793                         return;
2794                 }
2795         }
2796         if (not defined $sources_file) { 
2797                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2798                 $sources_file = &create_sources_list($session_id);
2799         }
2801         if (not defined $sources_file) {
2802                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2803                 unlink($packages_list_under_construction);
2804                 return;
2805         }
2807         my $line;
2809         open(CONFIG, "<$sources_file") or do {
2810                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2811                 unlink($packages_list_under_construction);
2812                 return;
2813         };
2815         # Read lines
2816         while ($line = <CONFIG>){
2817                 # Unify
2818                 chop($line);
2819                 $line =~ s/^\s+//;
2820                 $line =~ s/^\s+/ /;
2822                 # Strip comments
2823                 $line =~ s/#.*$//g;
2825                 # Skip empty lines
2826                 if ($line =~ /^\s*$/){
2827                         next;
2828                 }
2830                 # Interpret deb line
2831                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2832                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2833                         my $section;
2834                         foreach $section (split(' ', $sections)){
2835                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2836                         }
2837                 }
2838         }
2840         close (CONFIG);
2842         if(keys(%repo_dirs)) {
2843                 find(\&cleanup_and_extract, keys( %repo_dirs ));
2844                 &main::strip_packages_list_statements();
2845                 $packages_list_db->exec_statementlist(\@packages_list_statements);
2846         }
2847         unlink($packages_list_under_construction);
2848         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2849         return;
2852 # This function should do some intensive task to minimize the db-traffic
2853 sub strip_packages_list_statements {
2854         my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2855         my @new_statement_list=();
2856         my $hash;
2857         my $insert_hash;
2858         my $update_hash;
2859         my $delete_hash;
2860         my $known_packages_hash;
2861         my $local_timestamp=get_time();
2863         foreach my $existing_entry (@existing_entries) {
2864                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2865         }
2867         foreach my $statement (@packages_list_statements) {
2868                 if($statement =~ /^INSERT/i) {
2869                         # Assign the values from the insert statement
2870                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2871                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2872                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2873                                 # If section or description has changed, update the DB
2874                                 if( 
2875                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2876                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2877                                 ) {
2878                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2879                                 } else {
2880                                         # package is already present in database. cache this knowledge for later use
2881                                         @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2882                                 }
2883                         } else {
2884                                 # Insert a non-existing entry to db
2885                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2886                         }
2887                 } elsif ($statement =~ /^UPDATE/i) {
2888                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2889                         /^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;
2890                         foreach my $distribution (keys %{$hash}) {
2891                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2892                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2893                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2894                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2895                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2896                                                 my $section;
2897                                                 my $description;
2898                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2899                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2900                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2901                                                 }
2902                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2903                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2904                                                 }
2905                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2906                                         }
2907                                 }
2908                         }
2909                 }
2910         }
2912         # Check for orphaned entries
2913         foreach my $existing_entry (@existing_entries) {
2914                 my $distribution= @{$existing_entry}[0];
2915                 my $package= @{$existing_entry}[1];
2916                 my $version= @{$existing_entry}[2];
2917                 my $section= @{$existing_entry}[3];
2919                 if(
2920                         exists($insert_hash->{$distribution}->{$package}->{$version}) ||
2921                         exists($update_hash->{$distribution}->{$package}->{$version}) ||
2922                         exists($known_packages_hash->{$distribution}->{$package}->{$version})
2923                 ) {
2924                         next;
2925                 } else {
2926                         # Insert entry to delete hash
2927                         @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
2928                 }
2929         }
2931         # unroll the insert hash
2932         foreach my $distribution (keys %{$insert_hash}) {
2933                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2934                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2935                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2936                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2937                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2938                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2939                                 ."'$local_timestamp')";
2940                         }
2941                 }
2942         }
2944         # unroll the update hash
2945         foreach my $distribution (keys %{$update_hash}) {
2946                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2947                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2948                                 my $set = "";
2949                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2950                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2951                                 }
2952                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2953                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2954                                 }
2955                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2956                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2957                                 }
2958                                 if(defined($set) and length($set) > 0) {
2959                                         $set .= "timestamp = '$local_timestamp'";
2960                                 } else {
2961                                         next;
2962                                 }
2963                                 push @new_statement_list, 
2964                                 "UPDATE $main::packages_list_tn SET $set WHERE"
2965                                 ." distribution = '$distribution'"
2966                                 ." AND package = '$package'"
2967                                 ." AND version = '$version'";
2968                         }
2969                 }
2970         }
2971         
2972         # unroll the delete hash
2973         foreach my $distribution (keys %{$delete_hash}) {
2974                 foreach my $package (keys %{$delete_hash->{$distribution}}) {
2975                         foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
2976                                 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
2977                                 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
2978                         }
2979                 }
2980         }
2982         unshift(@new_statement_list, "VACUUM");
2984         @packages_list_statements = @new_statement_list;
2988 sub parse_package_info {
2989     my ($baseurl, $dist, $section, $session_id)= @_;
2990     my ($package);
2991     if (not defined $session_id) { $session_id = 0; }
2992     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2993     $repo_dirs{ "${repo_path}/pool" } = 1;
2995     foreach $package ("Packages.gz"){
2996         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2997         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2998         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2999     }
3000     
3004 sub get_package {
3005     my ($url, $dest, $session_id)= @_;
3006     if (not defined $session_id) { $session_id = 0; }
3008     my $tpath = dirname($dest);
3009     -d "$tpath" || mkpath "$tpath";
3011     # This is ugly, but I've no time to take a look at "how it works in perl"
3012     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3013         system("gunzip -cd '$dest' > '$dest.in'");
3014         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
3015         unlink($dest);
3016         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
3017     } else {
3018         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3019     }
3020     return 0;
3024 sub parse_package {
3025     my ($path, $dist, $srv_path, $session_id)= @_;
3026     if (not defined $session_id) { $session_id = 0;}
3027     my ($package, $version, $section, $description);
3028     my $PACKAGES;
3029     my $timestamp = &get_time();
3031     if(not stat("$path.in")) {
3032         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3033         return;
3034     }
3036     open($PACKAGES, "<$path.in");
3037     if(not defined($PACKAGES)) {
3038         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
3039         return;
3040     }
3042     # Read lines
3043     while (<$PACKAGES>){
3044         my $line = $_;
3045         # Unify
3046         chop($line);
3048         # Use empty lines as a trigger
3049         if ($line =~ /^\s*$/){
3050             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3051             push(@packages_list_statements, $sql);
3052             $package = "none";
3053             $version = "none";
3054             $section = "none";
3055             $description = "none"; 
3056             next;
3057         }
3059         # Trigger for package name
3060         if ($line =~ /^Package:\s/){
3061             ($package)= ($line =~ /^Package: (.*)$/);
3062             next;
3063         }
3065         # Trigger for version
3066         if ($line =~ /^Version:\s/){
3067             ($version)= ($line =~ /^Version: (.*)$/);
3068             next;
3069         }
3071         # Trigger for description
3072         if ($line =~ /^Description:\s/){
3073             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3074             next;
3075         }
3077         # Trigger for section
3078         if ($line =~ /^Section:\s/){
3079             ($section)= ($line =~ /^Section: (.*)$/);
3080             next;
3081         }
3083         # Trigger for filename
3084         if ($line =~ /^Filename:\s/){
3085             my ($filename) = ($line =~ /^Filename: (.*)$/);
3086             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3087             next;
3088         }
3089     }
3091     close( $PACKAGES );
3092     unlink( "$path.in" );
3096 sub store_fileinfo {
3097     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3099     my %fileinfo = (
3100         'package' => $package,
3101         'dist' => $dist,
3102         'version' => $vers,
3103     );
3105     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3109 sub cleanup_and_extract {
3110         my $fileinfo = $repo_files{ $File::Find::name };
3112         if( defined $fileinfo ) {
3113                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3114                 my $sql;
3115                 my $package = $fileinfo->{ 'package' };
3116                 my $newver = $fileinfo->{ 'version' };
3118                 mkpath($dir);
3119                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3121                 if( -f "$dir/DEBIAN/templates" ) {
3123                         daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3125                         my $tmpl= ""; {
3126                                 local $/=undef;
3127                                 open FILE, "$dir/DEBIAN/templates";
3128                                 $tmpl = &encode_base64(<FILE>);
3129                                 close FILE;
3130                         }
3131                         rmtree("$dir/DEBIAN/templates");
3133                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3134                         push @packages_list_statements, $sql;
3135                 }
3136         }
3138         return;
3142 sub register_at_foreign_servers {   
3143     my ($kernel) = $_[KERNEL];
3145     # hole alle bekannten server aus known_server_db
3146     my $server_sql = "SELECT * FROM $known_server_tn";
3147     my $server_res = $known_server_db->exec_statement($server_sql);
3149     # no entries in known_server_db
3150     if (not ref(@$server_res[0]) eq "ARRAY") { 
3151         # TODO
3152     }
3154     # detect already connected clients
3155     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3156     my $client_res = $known_clients_db->exec_statement($client_sql);
3158     # send my server details to all other gosa-si-server within the network
3159     foreach my $hit (@$server_res) {
3160         my $hostname = @$hit[0];
3161         my $hostkey = &create_passwd;
3163         # add already connected clients to registration message 
3164         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3165         &add_content2xml_hash($myhash, 'key', $hostkey);
3166         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3168         # add locally loaded gosa-si modules to registration message
3169         my $loaded_modules = {};
3170         while (my ($package, $pck_info) = each %$known_modules) {
3171                                                 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3172                                                 foreach my $act_module (keys(%{@$pck_info[2]})) {
3173                                                         $loaded_modules->{$act_module} = ""; 
3174                                                 }
3175         }
3177         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3179         # add macaddress to registration message
3180         my ($host_ip, $host_port) = split(/:/, $hostname);
3181         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3182         my $network_interface= &get_interface_for_ip($local_ip);
3183         my $host_mac = &get_mac_for_interface($network_interface);
3184         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3185         
3186         # build registration message and send it
3187         my $foreign_server_msg = &create_xml_string($myhash);
3188         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3189     }
3190     
3191     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
3192     return;
3196 #==== MAIN = main ==============================================================
3197 #  parse commandline options
3198 Getopt::Long::Configure( "bundling" );
3199 GetOptions("h|help" => \&usage,
3200         "c|config=s" => \$cfg_file,
3201         "f|foreground" => \$foreground,
3202         "v|verbose+" => \$verbose,
3203         "no-arp+" => \$no_arp,
3204            );
3206 # Prepare UID / GID as daemon_log may need it quite early
3207 $root_uid = getpwnam('root');
3208 $adm_gid = getgrnam('adm');
3210 #  read and set config parameters
3211 &check_cmdline_param ;
3212 &read_configfile($cfg_file, %cfg_defaults);
3213 &check_pid;
3215 $SIG{CHLD} = 'IGNORE';
3217 # forward error messages to logfile
3218 if( ! $foreground ) {
3219   open( STDIN,  '+>/dev/null' );
3220   open( STDOUT, '+>&STDIN'    );
3221   open( STDERR, '+>&STDIN'    );
3224 # Just fork, if we are not in foreground mode
3225 if( ! $foreground ) { 
3226     chdir '/'                 or die "Can't chdir to /: $!";
3227     $pid = fork;
3228     setsid                    or die "Can't start a new session: $!";
3229     umask 0;
3230 } else { 
3231     $pid = $$; 
3234 # Do something useful - put our PID into the pid_file
3235 if( 0 != $pid ) {
3236     open( LOCK_FILE, ">$pid_file" );
3237     print LOCK_FILE "$pid\n";
3238     close( LOCK_FILE );
3239     if( !$foreground ) { 
3240         exit( 0 ) 
3241     };
3244 # parse head url and revision from svn
3245 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3246 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3247 $server_headURL = defined $1 ? $1 : 'unknown' ;
3248 $server_revision = defined $2 ? $2 : 'unknown' ;
3249 if ($server_headURL =~ /\/tag\// || 
3250         $server_headURL =~ /\/branches\// ) {
3251     $server_status = "stable"; 
3252 } else {
3253     $server_status = "developmental" ;
3256 # Prepare log file and set permissons
3257 open(FH, ">>$log_file");
3258 close FH;
3259 chmod(0440, $log_file);
3260 chown($root_uid, $adm_gid, $log_file);
3261 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3263 daemon_log(" ", 1);
3264 daemon_log("$0 started!", 1);
3265 daemon_log("status: $server_status", 1);
3266 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3269     no strict "refs";
3271     if ($db_module eq "DBmysql") {
3272         # connect to incoming_db
3273         $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3275         # connect to gosa-si job queue
3276         $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3278         # connect to known_clients_db
3279         $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3281         # connect to foreign_clients_db
3282         $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3284         # connect to known_server_db
3285         $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3287         # connect to login_usr_db
3288         $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3290         # connect to fai_server_db 
3291         $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3293         # connect to fai_release_db
3294         $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3296         # connect to packages_list_db
3297         $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3299         # connect to messaging_db
3300         $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3302     } elsif ($db_module eq "DBsqlite") {
3303         # connect to incoming_db
3304         unlink($incoming_file_name);
3305         $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3306         
3307         # connect to gosa-si job queue
3308         unlink($job_queue_file_name);  ## just for debugging
3309         $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3310         chmod(0640, $job_queue_file_name);
3311         chown($root_uid, $adm_gid, $job_queue_file_name);
3312         
3313         # connect to known_clients_db
3314         unlink($known_clients_file_name);   ## just for debugging
3315         $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3316         chmod(0640, $known_clients_file_name);
3317         chown($root_uid, $adm_gid, $known_clients_file_name);
3318         
3319         # connect to foreign_clients_db
3320         unlink($foreign_clients_file_name);
3321         $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3322         chmod(0640, $foreign_clients_file_name);
3323         chown($root_uid, $adm_gid, $foreign_clients_file_name);
3324         
3325         # connect to known_server_db
3326         unlink($known_server_file_name);
3327         $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3328         chmod(0640, $known_server_file_name);
3329         chown($root_uid, $adm_gid, $known_server_file_name);
3330         
3331         # connect to login_usr_db
3332         unlink($login_users_file_name);
3333         $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3334         chmod(0640, $login_users_file_name);
3335         chown($root_uid, $adm_gid, $login_users_file_name);
3336         
3337         # connect to fai_server_db
3338         unlink($fai_server_file_name);
3339         $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3340         chmod(0640, $fai_server_file_name);
3341         chown($root_uid, $adm_gid, $fai_server_file_name);
3342         
3343         # connect to fai_release_db
3344         unlink($fai_release_file_name);
3345         $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3346         chmod(0640, $fai_release_file_name);
3347         chown($root_uid, $adm_gid, $fai_release_file_name);
3348         
3349         # connect to packages_list_db
3350         #unlink($packages_list_file_name);
3351         unlink($packages_list_under_construction);
3352         $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3353         chmod(0640, $packages_list_file_name);
3354         chown($root_uid, $adm_gid, $packages_list_file_name);
3355         
3356         # connect to messaging_db
3357         unlink($messaging_file_name);
3358         $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3359         chmod(0640, $messaging_file_name);
3360         chown($root_uid, $adm_gid, $messaging_file_name);
3361     }
3365 # Creating tables
3366 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3367 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3368 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3369 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3370 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3371 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3372 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3373 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3374 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3375 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3377 # create xml object used for en/decrypting
3378 $xml = new XML::Simple();
3381 # foreign servers 
3382 my @foreign_server_list;
3384 # add foreign server from cfg file
3385 if ($foreign_server_string ne "") {
3386     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3387     foreach my $foreign_server (@cfg_foreign_server_list) {
3388         push(@foreign_server_list, $foreign_server);
3389     }
3391     daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3394 # Perform a DNS lookup for server registration if flag is true
3395 if ($dns_lookup eq "true") {
3396     # Add foreign server from dns
3397     my @tmp_servers;
3398     if (not $server_domain) {
3399         # Try our DNS Searchlist
3400         for my $domain(get_dns_domains()) {
3401             chomp($domain);
3402             my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3403             if(@$tmp_domains) {
3404                 for my $tmp_server(@$tmp_domains) {
3405                     push @tmp_servers, $tmp_server;
3406                 }
3407             }
3408         }
3409         if(@tmp_servers && length(@tmp_servers)==0) {
3410             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3411         }
3412     } else {
3413         @tmp_servers = &get_server_addresses($server_domain);
3414         if( 0 == @tmp_servers ) {
3415             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3416         }
3417     }
3419     daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3421     foreach my $server (@tmp_servers) { 
3422         unshift(@foreign_server_list, $server); 
3423     }
3424 } else {
3425     daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3429 # eliminate duplicate entries
3430 @foreign_server_list = &del_doubles(@foreign_server_list);
3431 my $all_foreign_server = join(", ", @foreign_server_list);
3432 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3434 # add all found foreign servers to known_server
3435 my $cur_timestamp = &get_time();
3436 foreach my $foreign_server (@foreign_server_list) {
3438         # do not add myself to known_server_db
3439         if (&is_local($foreign_server)) { next; }
3440         ######################################
3442     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3443             primkey=>['hostname'],
3444             hostname=>$foreign_server,
3445             macaddress=>"",
3446             status=>'not_yet_registered',
3447             hostkey=>"none",
3448             loaded_modules => "none", 
3449             timestamp=>$cur_timestamp,
3450             } );
3454 # Import all modules
3455 &import_modules;
3457 # Check wether all modules are gosa-si valid passwd check
3458 &password_check;
3460 # Prepare for using Opsi 
3461 if ($opsi_enabled eq "true") {
3462     use JSON::RPC::Client;
3463     use XML::Quote qw(:all);
3464     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3465     $opsi_client = new JSON::RPC::Client;
3469 POE::Component::Server::TCP->new(
3470         Alias => "TCP_SERVER",
3471         Port => $server_port,
3472         ClientInput => sub {
3473                 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3474         my $session_id = $session->ID;
3475         my $remote_ip = $heap->{'remote_ip'};
3476                 push(@msgs_to_decrypt, $input);
3477         &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3478                 $kernel->yield("msg_to_decrypt");
3479         },
3480         InlineStates => {
3481                 msg_to_decrypt => \&msg_to_decrypt,
3482                 next_task => \&next_task,
3483                 task_result => \&handle_task_result,
3484                 task_done   => \&handle_task_done,
3485                 task_debug  => \&handle_task_debug,
3486                 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3487         }
3488 );
3490 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3492 # create session for repeatedly checking the job queue for jobs
3493 POE::Session->create(
3494         inline_states => {
3495                 _start => \&session_start,
3496         register_at_foreign_servers => \&register_at_foreign_servers,
3497         sig_handler => \&sig_handler,
3498         next_task => \&next_task,
3499         task_result => \&handle_task_result,
3500         task_done   => \&handle_task_done,
3501         task_debug  => \&handle_task_debug,
3502         watch_for_next_tasks => \&watch_for_next_tasks,
3503         watch_for_new_messages => \&watch_for_new_messages,
3504         watch_for_delivery_messages => \&watch_for_delivery_messages,
3505         watch_for_done_messages => \&watch_for_done_messages,
3506                 watch_for_new_jobs => \&watch_for_new_jobs,
3507         watch_for_modified_jobs => \&watch_for_modified_jobs,
3508         watch_for_done_jobs => \&watch_for_done_jobs,
3509         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3510         watch_for_old_known_clients => \&watch_for_old_known_clients,
3511         create_packages_list_db => \&run_create_packages_list_db,
3512         create_fai_server_db => \&run_create_fai_server_db,
3513         create_fai_release_db => \&run_create_fai_release_db,
3514                 recreate_packages_db => \&run_recreate_packages_db,
3515         session_run_result => \&session_run_result,
3516         session_run_debug => \&session_run_debug,
3517         session_run_done => \&session_run_done,
3518         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3519         }
3520 );
3523 POE::Kernel->run();
3524 exit;