Code

changing file permissions of /var/log/gosa-si-client.log, /etc/ldap/ldap-shell.conf...
[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;
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 PRIMARY KEY", "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             print LOG_HANDLE $log_msg;
356             if( $foreground ) { 
357                 print STDERR $log_msg;
358             }
359         }
360         close( LOG_HANDLE );
361     }
365 #===  FUNCTION  ================================================================
366 #         NAME:  check_cmdline_param
367 #   PARAMETERS:  nothing
368 #      RETURNS:  nothing
369 #  DESCRIPTION:  validates commandline parameter
370 #===============================================================================
371 sub check_cmdline_param () {
372     my $err_config;
373     my $err_counter = 0;
374         if(not defined($cfg_file)) {
375                 $cfg_file = "/etc/gosa-si/server.conf";
376                 if(! -r $cfg_file) {
377                         $err_config = "please specify a config file";
378                         $err_counter += 1;
379                 }
380     }
381     if( $err_counter > 0 ) {
382         &usage( "", 1 );
383         if( defined( $err_config)) { print STDERR "$err_config\n"}
384         print STDERR "\n";
385         exit( -1 );
386     }
390 #===  FUNCTION  ================================================================
391 #         NAME:  check_pid
392 #   PARAMETERS:  nothing
393 #      RETURNS:  nothing
394 #  DESCRIPTION:  handels pid processing
395 #===============================================================================
396 sub check_pid {
397     $pid = -1;
398     # Check, if we are already running
399     if( open(LOCK_FILE, "<$pid_file") ) {
400         $pid = <LOCK_FILE>;
401         if( defined $pid ) {
402             chomp( $pid );
403             if( -f "/proc/$pid/stat" ) {
404                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
405                 if( $stat ) {
406                                         daemon_log("ERROR: Already running",1);
407                     close( LOCK_FILE );
408                     exit -1;
409                 }
410             }
411         }
412         close( LOCK_FILE );
413         unlink( $pid_file );
414     }
416     # create a syslog msg if it is not to possible to open PID file
417     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
418         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
419         if (open(LOCK_FILE, '<', $pid_file)
420                 && ($pid = <LOCK_FILE>))
421         {
422             chomp($pid);
423             $msg .= "(PID $pid)\n";
424         } else {
425             $msg .= "(unable to read PID)\n";
426         }
427         if( ! ($foreground) ) {
428             openlog( $0, "cons,pid", "daemon" );
429             syslog( "warning", $msg );
430             closelog();
431         }
432         else {
433             print( STDERR " $msg " );
434         }
435         exit( -1 );
436     }
439 #===  FUNCTION  ================================================================
440 #         NAME:  import_modules
441 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
442 #                are stored
443 #      RETURNS:  nothing
444 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
445 #                state is on is imported by "require 'file';"
446 #===============================================================================
447 sub import_modules {
448     daemon_log(" ", 1);
450     if (not -e $modules_path) {
451         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
452     }
454     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
455     while (defined (my $file = readdir (DIR))) {
456         if (not $file =~ /(\S*?).pm$/) {
457             next;
458         }
459                 my $mod_name = $1;
461         # ArpHandler switch
462         if( $file =~ /ArpHandler.pm/ ) {
463             if( $arp_enabled eq "false" ) { next; }
464         }
465         
466         eval { require $file; };
467         if ($@) {
468             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
469             daemon_log("$@", 1);
470             exit;
471                 } else {
472                         my $info = eval($mod_name.'::get_module_info()');
473                         # Only load module if get_module_info() returns a non-null object
474                         if( $info ) {
475                                 my ($input_address, $input_key, $event_hash) = @{$info};
476                                 $known_modules->{$mod_name} = $info;
477                                 daemon_log("0 INFO: module $mod_name loaded", 5);
478                         }
479                 }
480     }   
482     close (DIR);
485 #===  FUNCTION  ================================================================
486 #         NAME:  password_check
487 #   PARAMETERS:  nothing
488 #      RETURNS:  nothing
489 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
490 #                the same password
491 #===============================================================================
492 sub password_check {
493     my $passwd_hash = {};
494     while (my ($mod_name, $mod_info) = each %$known_modules) {
495         my $mod_passwd = @$mod_info[1];
496         if (not defined $mod_passwd) { next; }
497         if (not exists $passwd_hash->{$mod_passwd}) {
498             $passwd_hash->{$mod_passwd} = $mod_name;
500         # escalates critical error
501         } else {
502             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
503             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
504             exit( -1 );
505         }
506     }
511 #===  FUNCTION  ================================================================
512 #         NAME:  sig_int_handler
513 #   PARAMETERS:  signal - string - signal arose from system
514 #      RETURNS:  nothing
515 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
516 #===============================================================================
517 sub sig_int_handler {
518     my ($signal) = @_;
520 #       if (defined($ldap_handle)) {
521 #               $ldap_handle->disconnect;
522 #       }
523     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
524     
526     daemon_log("shutting down gosa-si-server", 1);
527     system("kill `ps -C gosa-si-server -o pid=`");
529 $SIG{INT} = \&sig_int_handler;
532 sub check_key_and_xml_validity {
533     my ($crypted_msg, $module_key, $session_id) = @_;
534     my $msg;
535     my $msg_hash;
536     my $error_string;
537     eval{
538         $msg = &decrypt_msg($crypted_msg, $module_key);
540         if ($msg =~ /<xml>/i){
541             $msg =~ s/\s+/ /g;  # just for better daemon_log
542             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 9);
543             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
545             ##############
546             # check header
547             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
548             my $header_l = $msg_hash->{'header'};
549             if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
550             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
551             my $header = @{$header_l}[0];
552             if( 0 == length $header) { die 'empty string in header tag'; }
554             ##############
555             # check source
556             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
557             my $source_l = $msg_hash->{'source'};
558             if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
559             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
560             my $source = @{$source_l}[0];
561             if( 0 == length $source) { die 'source error'; }
563             ##############
564             # check target
565             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
566             my $target_l = $msg_hash->{'target'};
567             if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
568         }
569     };
570     if($@) {
571         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
572         $msg = undef;
573         $msg_hash = undef;
574     }
576     return ($msg, $msg_hash);
580 sub check_outgoing_xml_validity {
581     my ($msg, $session_id) = @_;
583     my $msg_hash;
584     eval{
585         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
587         ##############
588         # check header
589         my $header_l = $msg_hash->{'header'};
590         if( 1 != @{$header_l} ) {
591             die 'no or more than one headers specified';
592         }
593         my $header = @{$header_l}[0];
594         if( 0 == length $header) {
595             die 'header has length 0';
596         }
598         ##############
599         # check source
600         my $source_l = $msg_hash->{'source'};
601         if( 1 != @{$source_l} ) {
602             die 'no or more than 1 sources specified';
603         }
604         my $source = @{$source_l}[0];
605         if( 0 == length $source) {
606             die 'source has length 0';
607         }
609                                 # Check if source contains hostname instead of ip address
610                                 if(not $source =~ /^[a-z0-9\.]+:\d+$/i) {
611                                                 my ($hostname,$port) = split(/:/, $source);
612                                                 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
613                                                 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
614                                                         # Write ip address to $source variable
615                                                         $source = "$ip_address:$port";
616                                                 }
617                                 }
618         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
619                 $source =~ /^GOSA$/i) {
620             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
621         }
622         
623         ##############
624         # check target  
625         my $target_l = $msg_hash->{'target'};
626         if( 0 == @{$target_l} ) {
627             die "no targets specified";
628         }
629         foreach my $target (@$target_l) {
630             if( 0 == length $target) {
631                 die "target has length 0";
632             }
633             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
634                     $target =~ /^GOSA$/i ||
635                     $target =~ /^\*$/ ||
636                     $target =~ /KNOWN_SERVER/i ||
637                     $target =~ /JOBDB/i ||
638                     $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 ){
639                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
640             }
641         }
642     };
643     if($@) {
644         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
645         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
646         $msg_hash = undef;
647     }
649     return ($msg_hash);
653 sub input_from_known_server {
654     my ($input, $remote_ip, $session_id) = @_ ;  
655     my ($msg, $msg_hash, $module);
657     my $sql_statement= "SELECT * FROM known_server";
658     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
660     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
661         my $host_name = $hit->{hostname};
662         if( not $host_name =~ "^$remote_ip") {
663             next;
664         }
665         my $host_key = $hit->{hostkey};
666         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
667         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
669         # check if module can open msg envelope with module key
670         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
671         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
672             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
673             daemon_log("$@", 8);
674             next;
675         }
676         else {
677             $msg = $tmp_msg;
678             $msg_hash = $tmp_msg_hash;
679             $module = "ServerPackages";
680             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
681             last;
682         }
683     }
685     if( (!$msg) || (!$msg_hash) || (!$module) ) {
686         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
687     }
688   
689     return ($msg, $msg_hash, $module);
693 sub input_from_known_client {
694     my ($input, $remote_ip, $session_id) = @_ ;  
695     my ($msg, $msg_hash, $module);
697     my $sql_statement= "SELECT * FROM known_clients";
698     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
699     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
700         my $host_name = $hit->{hostname};
701         if( not $host_name =~ /^$remote_ip:\d*$/) {
702                 next;
703                 }
704         my $host_key = $hit->{hostkey};
705         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
706         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
708         # check if module can open msg envelope with module key
709         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
711         if( (!$msg) || (!$msg_hash) ) {
712             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
713             &daemon_log("$@", 8);
714             next;
715         }
716         else {
717             $module = "ClientPackages";
718             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
719             last;
720         }
721     }
723     if( (!$msg) || (!$msg_hash) || (!$module) ) {
724         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
725     }
727     return ($msg, $msg_hash, $module);
731 sub input_from_unknown_host {
732         no strict "refs";
733         my ($input, $session_id) = @_ ;
734         my ($msg, $msg_hash, $module);
735         my $error_string;
737         my %act_modules = %$known_modules;
739         while( my ($mod, $info) = each(%act_modules)) {
741                 # check a key exists for this module
742                 my $module_key = ${$mod."_key"};
743                 if( not defined $module_key ) {
744                         if( $mod eq 'ArpHandler' ) {
745                                 next;
746                         }
747                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
748                         next;
749                 }
750                 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
752                 # check if module can open msg envelope with module key
753                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
754                 if( (not defined $msg) || (not defined $msg_hash) ) {
755                         next;
756                 } else {
757                         $module = $mod;
758             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
759                         last;
760                 }
761         }
763         if( (!$msg) || (!$msg_hash) || (!$module)) {
764                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
765         }
767         return ($msg, $msg_hash, $module);
771 sub create_ciphering {
772     my ($passwd) = @_;
773         if((!defined($passwd)) || length($passwd)==0) {
774                 $passwd = "";
775         }
776     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
777     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
778     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
779     $my_cipher->set_iv($iv);
780     return $my_cipher;
784 sub encrypt_msg {
785     my ($msg, $key) = @_;
786     my $my_cipher = &create_ciphering($key);
787     my $len;
788     {
789             use bytes;
790             $len= 16-length($msg)%16;
791     }
792     $msg = "\0"x($len).$msg;
793     $msg = $my_cipher->encrypt($msg);
794     chomp($msg = &encode_base64($msg));
795     # there are no newlines allowed inside msg
796     $msg=~ s/\n//g;
797     return $msg;
801 sub decrypt_msg {
803     my ($msg, $key) = @_ ;
804     $msg = &decode_base64($msg);
805     my $my_cipher = &create_ciphering($key);
806     $msg = $my_cipher->decrypt($msg); 
807     $msg =~ s/\0*//g;
808     return $msg;
812 sub get_encrypt_key {
813     my ($target) = @_ ;
814     my $encrypt_key;
815     my $error = 0;
817     # target can be in known_server
818     if( not defined $encrypt_key ) {
819         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
820         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
821         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
822             my $host_name = $hit->{hostname};
823             if( $host_name ne $target ) {
824                 next;
825             }
826             $encrypt_key = $hit->{hostkey};
827             last;
828         }
829     }
831     # target can be in known_client
832     if( not defined $encrypt_key ) {
833         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
834         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
835         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
836             my $host_name = $hit->{hostname};
837             if( $host_name ne $target ) {
838                 next;
839             }
840             $encrypt_key = $hit->{hostkey};
841             last;
842         }
843     }
845     return $encrypt_key;
849 #===  FUNCTION  ================================================================
850 #         NAME:  open_socket
851 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
852 #                [PeerPort] string necessary if port not appended by PeerAddr
853 #      RETURNS:  socket IO::Socket::INET
854 #  DESCRIPTION:  open a socket to PeerAddr
855 #===============================================================================
856 sub open_socket {
857     my ($PeerAddr, $PeerPort) = @_ ;
858     if(defined($PeerPort)){
859         $PeerAddr = $PeerAddr.":".$PeerPort;
860     }
861     my $socket;
862     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
863             Porto => "tcp",
864             Type => SOCK_STREAM,
865             Timeout => 5,
866             );
867     if(not defined $socket) {
868         return;
869     }
870 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
871     return $socket;
875 sub send_msg_to_target {
876     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
877     my $error = 0;
878     my $header;
879     my $timestamp = &get_time();
880     my $new_status;
881     my $act_status;
882     my ($sql_statement, $res);
883   
884     if( $msg_header ) {
885         $header = "'$msg_header'-";
886     } else {
887         $header = "";
888     }
890         # Patch the source ip
891         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
892                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
893                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
894         }
896     # encrypt xml msg
897     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
899     # opensocket
900     my $socket = &open_socket($address);
901     if( !$socket ) {
902         daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
903         $error++;
904     }
905     
906     if( $error == 0 ) {
907         # send xml msg
908         print $socket $crypted_msg."\n";
910         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
911         daemon_log("$session_id DEBUG: message:\n$msg", 9);
912         
913     }
915     # close socket in any case
916     if( $socket ) {
917         close $socket;
918     }
920     if( $error > 0 ) { $new_status = "down"; }
921     else { $new_status = $msg_header; }
924     # known_clients
925     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
926     $res = $known_clients_db->select_dbentry($sql_statement);
927     if( keys(%$res) == 1) {
928         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
929         if ($act_status eq "down" && $new_status eq "down") {
930             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
931             $res = $known_clients_db->del_dbentry($sql_statement);
932             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
933         } else { 
934             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
935             $res = $known_clients_db->update_dbentry($sql_statement);
936             if($new_status eq "down"){
937                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
938             } else {
939                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
940             }
941         }
942     }
944     # known_server
945     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
946     $res = $known_server_db->select_dbentry($sql_statement);
947     if( keys(%$res) == 1) {
948         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
949         if ($act_status eq "down" && $new_status eq "down") {
950             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
951             $res = $known_server_db->del_dbentry($sql_statement);
952             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
953         } 
954         else { 
955             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
956             $res = $known_server_db->update_dbentry($sql_statement);
957             if($new_status eq "down"){
958                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
959             } else {
960                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
961             }
962         }
963     }
964     return $error; 
968 sub update_jobdb_status_for_send_msgs {
969     my ($session_id, $answer, $error) = @_;
970     &daemon_log("$session_id DEBUG: try to update job status", 7); 
971     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
972         my $jobdb_id = $1;
973     
974         $answer =~ /<header>(.*)<\/header>/;
975         my $job_header = $1;
977         $answer =~ /<target>(.*)<\/target>/;
978         my $job_target = $1;
979             
980         # Sending msg failed
981         if( $error ) {
983             # Set jobs to done, jobs do not need to deliver their message in any case
984             if (($job_header eq "trigger_action_localboot")
985                     ||($job_header eq "trigger_action_lock")
986                     ||($job_header eq "trigger_action_halt") 
987                     ) {
988                 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
989                 &daemon_log("$session_id DEBUG: $sql_statement", 7); 
990                 my $res = $job_db->update_dbentry($sql_statement);
991                 
992             # Reactivate jobs, jobs need to deliver their message
993             } elsif (($job_header eq "trigger_action_activate")
994                     ||($job_header eq "trigger_action_update")
995                     ||($job_header eq "trigger_action_reinstall") 
996                     ||($job_header eq "trigger_activate_new")
997                     ) {
998                 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1000             # For all other messages
1001             } else {
1002                 my $sql_statement = "UPDATE $job_queue_tn ".
1003                     "SET status='error', result='can not deliver msg, please consult log file' ".
1004                     "WHERE id=$jobdb_id";
1005                 &daemon_log("$session_id DEBUG: $sql_statement", 7); 
1006                 my $res = $job_db->update_dbentry($sql_statement);
1007             }
1009         # Sending msg was successful
1010         } else {
1011             # Set jobs localboot, lock, activate, halt, reboot and wake to done
1012             # jobs reinstall, update, inst_update do themself setting to done
1013             if (($job_header eq "trigger_action_localboot")
1014                     ||($job_header eq "trigger_action_lock")
1015                     ||($job_header eq "trigger_action_activate")
1016                     ||($job_header eq "trigger_action_halt") 
1017                     ||($job_header eq "trigger_action_reboot")
1018                     ||($job_header eq "trigger_action_wake")
1019                     ||($job_header eq "trigger_wake")
1020                     ) {
1022                 my $sql_statement = "UPDATE $job_queue_tn ".
1023                     "SET status='done' ".
1024                     "WHERE id=$jobdb_id AND status='processed'";
1025                 &daemon_log("$session_id DEBUG: $sql_statement", 7); 
1026                 my $res = $job_db->update_dbentry($sql_statement);
1027             } else { 
1028                 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 7); 
1029             } 
1030         } 
1031     } else { 
1032         &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag: $answer", 7); 
1033     }
1036 sub reactivate_job_with_delay {
1037     my ($session_id, $target, $header, $delay) = @_ ;
1038     # 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
1039     
1040     if (not defined $delay) { $delay = 30 } ;
1041     my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1043     my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$target' AND headertag='$header')"; 
1044     my $res = $job_db->update_dbentry($sql);
1045     daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1046             "cause client '$target' is currently not available", 5);
1047     daemon_log("$session_id $sql", 7);                             
1048     return;
1052 sub sig_handler {
1053         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1054         daemon_log("0 INFO got signal '$signal'", 1); 
1055         $kernel->sig_handled();
1056         return;
1060 sub msg_to_decrypt {
1061         my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1062         my $session_id = $session->ID;
1063         my ($msg, $msg_hash, $module);
1064         my $error = 0;
1066         # fetch new msg out of @msgs_to_decrypt
1067         my $tmp_next_msg = shift @msgs_to_decrypt;
1068     my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1070         # msg is from a new client or gosa
1071         ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1073         # msg is from a gosa-si-server
1074         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1075                 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1076         }
1077         # msg is from a gosa-si-client
1078         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1079                 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1080         }
1081         # an error occurred
1082         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1083                 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1084                 # could not understand a msg from its server the client cause a re-registering process
1085         my $remote_ip = $heap->{'remote_ip'};
1086         my $remote_port = $heap->{'remote_port'};
1087         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1088         my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1090                 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1091                         "' to cause a re-registering of the client if necessary", 3);
1092                 $error++;
1093         }
1096         my $header;
1097         my $target;
1098         my $source;
1099         my $done = 0;
1100         my $sql;
1101         my $res;
1103         # check whether this message should be processed here
1104         if ($error == 0) {
1105                 $header = @{$msg_hash->{'header'}}[0];
1106                 $target = @{$msg_hash->{'target'}}[0];
1107                 $source = @{$msg_hash->{'source'}}[0];
1108                 my $not_found_in_known_clients_db = 0;
1109                 my $not_found_in_known_server_db = 0;
1110                 my $not_found_in_foreign_clients_db = 0;
1111                 my $local_address;
1112                 my $local_mac;
1113                 my ($target_ip, $target_port) = split(':', $target);
1115                 # Determine the local ip address if target is an ip address
1116                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1117                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1118                 } else {
1119                         $local_address = $server_address;
1120                 }
1122                 # Determine the local mac address if target is a mac address
1123                 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) {
1124                         my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1125                         my $network_interface= &get_interface_for_ip($loc_ip);
1126                         $local_mac = &get_mac_for_interface($network_interface);
1127                 } else {
1128                         $local_mac = $server_mac_address;
1129                 }
1131                 # target and source is equal to GOSA -> process here
1132                 if (not $done) {
1133                         if ($target eq "GOSA" && $source eq "GOSA") {
1134                                 $done = 1;                    
1135                                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1136                         }
1137                 }
1139                 # target is own address without forward_to_gosa-tag -> process here
1140                 if (not $done) {
1141                         #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1142                         if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1143                                 $done = 1;
1144                                 if ($source eq "GOSA") {
1145                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1146                                 }
1147                                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1148                         }
1149                 }
1151                 # target is a client address in known_clients -> process here
1152                 if (not $done) {
1153                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1154                         $res = $known_clients_db->select_dbentry($sql);
1155                         if (keys(%$res) > 0) {
1156                                 $done = 1; 
1157                                 my $hostname = $res->{1}->{'hostname'};
1158                                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1159                                 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1160                                 if ($source eq "GOSA") {
1161                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1162                                 }
1163                                 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1165                         } else {
1166                                 $not_found_in_known_clients_db = 1;
1167                         }
1168                 }
1170                 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1171                 if (not $done) {
1172                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1173                         my $gosa_at;
1174                         my $gosa_session_id;
1175                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1176                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1177                                 if ($gosa_at ne $local_address) {
1178                                         $done = 1;
1179                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7); 
1180                                 }
1181                         }
1182                 }
1184                 # if message should be processed here -> add message to incoming_db
1185                 if ($done) {
1186                         # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1187                         # so gosa-si-server knows how to process this kind of messages
1188                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1189                                 $module = "GosaPackages";
1190                         }
1192                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1193                                         primkey=>[],
1194                                         headertag=>$header,
1195                                         targettag=>$target,
1196                                         xmlmessage=>&encode_base64($msg),
1197                                         timestamp=>&get_time,
1198                                         module=>$module,
1199                                         sessionid=>$session_id,
1200                                 } );
1202                 }
1204                 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1205                 if (not $done) {
1206                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1207                         my $gosa_at;
1208                         my $gosa_session_id;
1209                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1210                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1211                                 if ($gosa_at eq $local_address) {
1212                                         my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1213                                         if( defined $session_reference ) {
1214                                                 $heap = $session_reference->get_heap();
1215                                         }
1216                                         if(exists $heap->{'client'}) {
1217                                                 $msg = &encrypt_msg($msg, $GosaPackages_key);
1218                                                 $heap->{'client'}->put($msg);
1219                                                 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5); 
1220                                         }
1221                                         $done = 1;
1222                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1223                                 }
1224                         }
1226                 }
1228                 # target is a client address in foreign_clients -> forward to registration server
1229                 if (not $done) {
1230                         $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1231                         $res = $foreign_clients_db->select_dbentry($sql);
1232                         if (keys(%$res) > 0) {
1233                                 my $hostname = $res->{1}->{'hostname'};
1234                                 my ($host_ip, $host_port) = split(/:/, $hostname);
1235                                 my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1236                                 my $regserver = $res->{1}->{'regserver'};
1237                                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1238                                 my $res = $known_server_db->select_dbentry($sql);
1239                                 if (keys(%$res) > 0) {
1240                                         my $regserver_key = $res->{1}->{'hostkey'};
1241                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1242                                         $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1243                                         if ($source eq "GOSA") {
1244                                                 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1245                                         }
1246                                         &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1247                                 }
1248                                 $done = 1;
1249                                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1250                         } else {
1251                                 $not_found_in_foreign_clients_db = 1;
1252                         }
1253                 }
1255                 # target is a server address -> forward to server
1256                 if (not $done) {
1257                         $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1258                         $res = $known_server_db->select_dbentry($sql);
1259                         if (keys(%$res) > 0) {
1260                                 my $hostkey = $res->{1}->{'hostkey'};
1262                                 if ($source eq "GOSA") {
1263                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1264                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1266                                 }
1268                                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1269                                 $done = 1;
1270                                 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1271                         } else {
1272                                 $not_found_in_known_server_db = 1;
1273                         }
1274                 }
1277                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1278                 if ( $not_found_in_foreign_clients_db 
1279                         && $not_found_in_known_server_db
1280                         && $not_found_in_known_clients_db) {
1281                         &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);
1282             if ($header =~ /^gosa_/ || $header =~ /^job_/) { 
1283                 $module = "GosaPackages"; 
1284             }
1285                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1286                                         primkey=>[],
1287                                         headertag=>$header,
1288                                         targettag=>$target,
1289                                         xmlmessage=>&encode_base64($msg),
1290                                         timestamp=>&get_time,
1291                                         module=>$module,
1292                                         sessionid=>$session_id,
1293                                 } );
1294                         $done = 1;
1295                 }
1298                 if (not $done) {
1299                         daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1300                         if ($source eq "GOSA") {
1301                                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1302                                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1304                                 my $session_reference = $kernel->ID_id_to_session($session_id);
1305                                 if( defined $session_reference ) {
1306                                         $heap = $session_reference->get_heap();
1307                                 }
1308                                 if(exists $heap->{'client'}) {
1309                                         $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1310                                         $heap->{'client'}->put($error_msg);
1311                                 }
1312                         }
1313                 }
1315         }
1317         return;
1321 sub next_task {
1322     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1323     my $running_task = POE::Wheel::Run->new(
1324             Program => sub { process_task($session, $heap, $task) },
1325             StdioFilter => POE::Filter::Reference->new(),
1326             StdoutEvent  => "task_result",
1327             StderrEvent  => "task_debug",
1328             CloseEvent   => "task_done",
1329             );
1330     $heap->{task}->{ $running_task->ID } = $running_task;
1333 sub handle_task_result {
1334     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1335     my $client_answer = $result->{'answer'};
1336     if( $client_answer =~ s/session_id=(\d+)$// ) {
1337         my $session_id = $1;
1338         if( defined $session_id ) {
1339             my $session_reference = $kernel->ID_id_to_session($session_id);
1340             if( defined $session_reference ) {
1341                 $heap = $session_reference->get_heap();
1342             }
1343         }
1345         if(exists $heap->{'client'}) {
1346             $heap->{'client'}->put($client_answer);
1347         }
1348     }
1349     $kernel->sig(CHLD => "child_reap");
1352 sub handle_task_debug {
1353     my $result = $_[ARG0];
1354     print STDERR "$result\n";
1357 sub handle_task_done {
1358     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1359     delete $heap->{task}->{$task_id};
1362 sub process_task {
1363     no strict "refs";
1364     #CHECK: Not @_[...]?
1365     my ($session, $heap, $task) = @_;
1366     my $error = 0;
1367     my $answer_l;
1368     my ($answer_header, @answer_target_l, $answer_source);
1369     my $client_answer = "";
1371     # prepare all variables needed to process message
1372     #my $msg = $task->{'xmlmessage'};
1373     my $msg = &decode_base64($task->{'xmlmessage'});
1374     my $incoming_id = $task->{'id'};
1375     my $module = $task->{'module'};
1376     my $header =  $task->{'headertag'};
1377     my $session_id = $task->{'sessionid'};
1378                 my $msg_hash;
1379                 eval {
1380         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1381                 }; 
1382                 daemon_log("ERROR: XML failure '$@'") if ($@);
1383     my $source = @{$msg_hash->{'source'}}[0];
1384     
1385     # set timestamp of incoming client uptodate, so client will not 
1386     # be deleted from known_clients because of expiration
1387     my $cur_time = &get_time();
1388     my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'"; 
1389     my $res = $known_clients_db->exec_statement($sql);
1391     ######################
1392     # process incoming msg
1393     if( $error == 0) {
1394         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1395         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1396         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1398         if ( 0 < @{$answer_l} ) {
1399             my $answer_str = join("\n", @{$answer_l});
1400             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1401                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1402             }
1403             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1404         } else {
1405             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1406         }
1408     }
1409     if( !$answer_l ) { $error++ };
1411     ########
1412     # answer
1413     if( $error == 0 ) {
1415         foreach my $answer ( @{$answer_l} ) {
1416             # check outgoing msg to xml validity
1417             my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1418             if( not defined $answer_hash ) { next; }
1419             
1420             $answer_header = @{$answer_hash->{'header'}}[0];
1421             @answer_target_l = @{$answer_hash->{'target'}};
1422             $answer_source = @{$answer_hash->{'source'}}[0];
1424             # deliver msg to all targets 
1425             foreach my $answer_target ( @answer_target_l ) {
1427                 # targets of msg are all gosa-si-clients in known_clients_db
1428                 if( $answer_target eq "*" ) {
1429                     # answer is for all clients
1430                     my $sql_statement= "SELECT * FROM known_clients";
1431                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1432                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1433                         my $host_name = $hit->{hostname};
1434                         my $host_key = $hit->{hostkey};
1435                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1436                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1437                     }
1438                 }
1440                 # targets of msg are all gosa-si-server in known_server_db
1441                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1442                     # answer is for all server in known_server
1443                     my $sql_statement= "SELECT * FROM $known_server_tn";
1444                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1445                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1446                         my $host_name = $hit->{hostname};
1447                         my $host_key = $hit->{hostkey};
1448                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1449                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1450                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1451                     }
1452                 }
1454                 # target of msg is GOsa
1455                                 elsif( $answer_target eq "GOSA" ) {
1456                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1457                                         my $add_on = "";
1458                     if( defined $session_id ) {
1459                         $add_on = ".session_id=$session_id";
1460                     }
1461                     # answer is for GOSA and has to returned to connected client
1462                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1463                     $client_answer = $gosa_answer.$add_on;
1464                 }
1466                 # target of msg is job queue at this host
1467                 elsif( $answer_target eq "JOBDB") {
1468                     $answer =~ /<header>(\S+)<\/header>/;   
1469                     my $header;
1470                     if( defined $1 ) { $header = $1; }
1471                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1472                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1473                 }
1475                 # Target of msg is a mac address
1476                 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 ) {
1477                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1479                     # Looking for macaddress in known_clients
1480                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1481                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1482                     my $found_ip_flag = 0;
1483                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1484                         my $host_name = $hit->{hostname};
1485                         my $host_key = $hit->{hostkey};
1486                         $answer =~ s/$answer_target/$host_name/g;
1487                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1488                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1489                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1490                         $found_ip_flag++ ;
1491                     }   
1493                     # Looking for macaddress in foreign_clients
1494                     if ($found_ip_flag == 0) {
1495                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1496                         my $res = $foreign_clients_db->select_dbentry($sql);
1497                         while( my ($hit_num, $hit) = each %{ $res } ) {
1498                             my $host_name = $hit->{hostname};
1499                             my $reg_server = $hit->{regserver};
1500                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1501                             
1502                             # Fetch key for reg_server
1503                             my $reg_server_key;
1504                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1505                             my $res = $known_server_db->select_dbentry($sql);
1506                             if (exists $res->{1}) {
1507                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1508                             } else {
1509                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1510                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1511                                 $reg_server_key = undef;
1512                             }
1514                             # Send answer to server where client is registered
1515                             if (defined $reg_server_key) {
1516                                 $answer =~ s/$answer_target/$host_name/g;
1517                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1518                                 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1519                                 $found_ip_flag++ ;
1520                             }
1521                         }
1522                     }
1524                     # No mac to ip matching found
1525                     if( $found_ip_flag == 0) {
1526                         daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1527                         &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1528                     }
1530                 # Answer is for one specific host   
1531                 } else {
1532                     # get encrypt_key
1533                     my $encrypt_key = &get_encrypt_key($answer_target);
1534                     if( not defined $encrypt_key ) {
1535                         # unknown target
1536                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1537                         next;
1538                     }
1539                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1540                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1541                 }
1542             }
1543         }
1544     }
1546     my $filter = POE::Filter::Reference->new();
1547     my %result = ( 
1548             status => "seems ok to me",
1549             answer => $client_answer,
1550             );
1552     my $output = $filter->put( [ \%result ] );
1553     print @$output;
1558 sub session_start {
1559     my ($kernel) = $_[KERNEL];
1560     $global_kernel = $kernel;
1561     $kernel->yield('register_at_foreign_servers');
1562         $kernel->yield('create_fai_server_db', $fai_server_tn );
1563         $kernel->yield('create_fai_release_db', $fai_release_tn );
1564     $kernel->yield('watch_for_next_tasks');
1565         $kernel->sig(USR1 => "sig_handler");
1566         $kernel->sig(USR2 => "recreate_packages_db");
1567         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1568         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1569     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1570         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1571     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1572         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1573     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1575     # Start opsi check
1576     if ($opsi_enabled eq "true") {
1577         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1578     }
1583 sub watch_for_done_jobs {
1584         #CHECK: $heap for what?
1585         my ($kernel,$heap) = @_[KERNEL, HEAP];
1587         my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1588         my $res = $job_db->select_dbentry( $sql_statement );
1590         while( my ($id, $hit) = each %{$res} ) {
1591                 my $jobdb_id = $hit->{id};
1592                 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1593                 my $res = $job_db->del_dbentry($sql_statement); 
1594         }
1596         $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1600 sub watch_for_opsi_jobs {
1601     my ($kernel) = $_[KERNEL];
1603     # 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 
1604     # opsi install job is to parse the xml message. There is still the correct header.
1605     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1606         my $res = $job_db->select_dbentry( $sql_statement );
1608     # Ask OPSI for an update of the running jobs
1609     while (my ($id, $hit) = each %$res ) {
1610         # Determine current parameters of the job
1611         my $hostId = $hit->{'plainname'};
1612         my $macaddress = $hit->{'macaddress'};
1613         my $progress = $hit->{'progress'};
1615         my $result= {};
1616         
1617         # For hosts, only return the products that are or get installed
1618         my $callobj;
1619         $callobj = {
1620             method  => 'getProductStates_hash',
1621             params  => [ $hostId ],
1622             id  => 1,
1623         };
1624         
1625         my $hres = $opsi_client->call($opsi_url, $callobj);
1626         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1627         if (not &check_opsi_res($hres)) {
1628             my $htmp= $hres->result->{$hostId};
1629         
1630             # Check state != not_installed or action == setup -> load and add
1631             my $products= 0;
1632             my $installed= 0;
1633             my $installing = 0;
1634             my $error= 0;  
1635             my @installed_list;
1636             my @error_list;
1637             my $act_status = "none";
1638             foreach my $product (@{$htmp}){
1640                 if ($product->{'installationStatus'} ne "not_installed" or
1641                         $product->{'actionRequest'} eq "setup"){
1643                     # Increase number of products for this host
1644                     $products++;
1645         
1646                     if ($product->{'installationStatus'} eq "failed"){
1647                         $result->{$product->{'productId'}}= "error";
1648                         unshift(@error_list, $product->{'productId'});
1649                         $error++;
1650                     }
1651                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1652                         $result->{$product->{'productId'}}= "installed";
1653                         unshift(@installed_list, $product->{'productId'});
1654                         $installed++;
1655                     }
1656                     if ($product->{'installationStatus'} eq "installing"){
1657                         $result->{$product->{'productId'}}= "installing";
1658                         $installing++;
1659                         $act_status = "installing - ".$product->{'productId'};
1660                     }
1661                 }
1662             }
1663         
1664             # Estimate "rough" progress, avoid division by zero
1665             if ($products == 0) {
1666                 $result->{'progress'}= 0;
1667             } else {
1668                 $result->{'progress'}= int($installed * 100 / $products);
1669             }
1671             # Set updates in job queue
1672             if ((not $error) && (not $installing) && ($installed)) {
1673                 $act_status = "installed - ".join(", ", @installed_list);
1674             }
1675             if ($error) {
1676                 $act_status = "error - ".join(", ", @error_list);
1677             }
1678             if ($progress ne $result->{'progress'} ) {
1679                 # Updating progress and result 
1680                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1681                 my $update_res = $job_db->update_dbentry($update_statement);
1682             }
1683             if ($progress eq 100) { 
1684                 # Updateing status
1685                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1686                 if ($error) {
1687                     $done_statement .= "status='error'";
1688                 } else {
1689                     $done_statement .= "status='done'";
1690                 }
1691                 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1692                 my $done_res = $job_db->update_dbentry($done_statement);
1693             }
1696         }
1697     }
1699     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1703 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1704 sub watch_for_modified_jobs {
1705     my ($kernel,$heap) = @_[KERNEL, HEAP];
1707     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')"; 
1708     my $res = $job_db->select_dbentry( $sql_statement );
1709     
1710     # if db contains no jobs which should be update, do nothing
1711     if (keys %$res != 0) {
1713         if ($job_synchronization  eq "true") {
1714             # make out of the db result a gosa-si message   
1715             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1716  
1717             # update all other SI-server
1718             &inform_all_other_si_server($update_msg);
1719         }
1721         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1722         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1723         $res = $job_db->update_dbentry($sql_statement);
1724     }
1726     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1730 sub watch_for_new_jobs {
1731         if($watch_for_new_jobs_in_progress == 0) {
1732                 $watch_for_new_jobs_in_progress = 1;
1733                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1735                 # check gosa job quaeue for jobs with executable timestamp
1736                 my $timestamp = &get_time();
1737                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1738                 my $res = $job_db->exec_statement( $sql_statement );
1740                 # Merge all new jobs that would do the same actions
1741                 my @drops;
1742                 my $hits;
1743                 foreach my $hit (reverse @{$res} ) {
1744                         my $macaddress= lc @{$hit}[8];
1745                         my $headertag= @{$hit}[5];
1746                         if(
1747                                 defined($hits->{$macaddress}) &&
1748                                 defined($hits->{$macaddress}->{$headertag}) &&
1749                                 defined($hits->{$macaddress}->{$headertag}[0])
1750                         ) {
1751                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1752                         }
1753                         $hits->{$macaddress}->{$headertag}= $hit;
1754                 }
1756                 # Delete new jobs with a matching job in state 'processing'
1757                 foreach my $macaddress (keys %{$hits}) {
1758                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1759                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1760                                 if(defined($jobdb_id)) {
1761                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1762                                         my $res = $job_db->exec_statement( $sql_statement );
1763                                         foreach my $hit (@{$res}) {
1764                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1765                                         }
1766                                 } else {
1767                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1768                                 }
1769                         }
1770                 }
1772                 # Commit deletion
1773                 $job_db->exec_statementlist(\@drops);
1775                 # Look for new jobs that could be executed
1776                 foreach my $macaddress (keys %{$hits}) {
1778                         # Look if there is an executing job
1779                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1780                         my $res = $job_db->exec_statement( $sql_statement );
1782                         # Skip new jobs for host if there is a processing job
1783                         if(defined($res) and defined @{$res}[0]) {
1784                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1785                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1786                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1787                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1788                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1789                                         if(defined($res_2) and defined @{$res_2}[0]) {
1790                                                 # Set status from goto-activation to 'waiting' and update timestamp
1791                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1792                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&calc_timestamp(&get_time(), 'plus', 30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1793                                         }
1794                                 }
1795                                 next;
1796                         }
1798                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1799                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1800                                 if(defined($jobdb_id)) {
1801                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1803                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1804                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1805                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1807                                         # expect macaddress is unique!!!!!!
1808                                         my $target = $res_hash->{1}->{hostname};
1810                                         # change header
1811                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1813                                         # add sqlite_id
1814                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1816                                         $job_msg =~ /<header>(\S+)<\/header>/;
1817                                         my $header = $1 ;
1818                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");                    
1820                                         # update status in job queue to ...
1821                     # ... 'processing', for jobs: 'reinstall', 'update'
1822                     if (($header =~ /gosa_trigger_action_reinstall/) 
1823                             || ($header =~ /gosa_trigger_activate_new/)
1824                             || ($header =~ /gosa_trigger_action_update/)) {
1825                         my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1826                         my $dbres = $job_db->update_dbentry($sql_statement);
1827                     }
1829                     # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1830                     else {
1831                         my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1832                         my $dbres = $job_db->update_dbentry($sql_statement);
1833                     }
1834                 
1836                                         # We don't want parallel processing
1837                                         last;
1838                                 }
1839                         }
1840                 }
1842                 $watch_for_new_jobs_in_progress = 0;
1843                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1844         }
1848 sub watch_for_new_messages {
1849     my ($kernel,$heap) = @_[KERNEL, HEAP];
1850     my @coll_user_msg;   # collection list of outgoing messages
1851     
1852     # check messaging_db for new incoming messages with executable timestamp
1853     my $timestamp = &get_time();
1854     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1855     my $res = $messaging_db->exec_statement( $sql_statement );
1856         foreach my $hit (@{$res}) {
1858         # create outgoing messages
1859         my $message_to = @{$hit}[3];
1860         # translate message_to to plain login name
1861         my @message_to_l = split(/,/, $message_to);  
1862                 my %receiver_h; 
1863                 foreach my $receiver (@message_to_l) {
1864                         if ($receiver =~ /^u_([\s\S]*)$/) {
1865                                 $receiver_h{$1} = 0;
1866                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1867                                 my $group_name = $1;
1868                                 # fetch all group members from ldap and add them to receiver hash
1869                                 my $ldap_handle = &get_ldap_handle();
1870                                 if (defined $ldap_handle) {
1871                                                 my $mesg = $ldap_handle->search(
1872                                                                                 base => $ldap_base,
1873                                                                                 scope => 'sub',
1874                                                                                 attrs => ['memberUid'],
1875                                                                                 filter => "cn=$group_name",
1876                                                                                 );
1877                                                 if ($mesg->count) {
1878                                                                 my @entries = $mesg->entries;
1879                                                                 foreach my $entry (@entries) {
1880                                                                                 my @receivers= $entry->get_value("memberUid");
1881                                                                                 foreach my $receiver (@receivers) { 
1882                                                                                                 $receiver_h{$receiver} = 0;
1883                                                                                 }
1884                                                                 }
1885                                                 } 
1886                                                 # translating errors ?
1887                                                 if ($mesg->code) {
1888                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1889                                                 }
1890                                 # ldap handle error ?           
1891                                 } else {
1892                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1893                                 }
1894                         } else {
1895                                 my $sbjct = &encode_base64(@{$hit}[1]);
1896                                 my $msg = &encode_base64(@{$hit}[7]);
1897                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1898                         }
1899                 }
1900                 my @receiver_l = keys(%receiver_h);
1902         my $message_id = @{$hit}[0];
1904         #add each outgoing msg to messaging_db
1905         my $receiver;
1906         foreach $receiver (@receiver_l) {
1907             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1908                 "VALUES ('".
1909                 $message_id."', '".    # id
1910                 @{$hit}[1]."', '".     # subject
1911                 @{$hit}[2]."', '".     # message_from
1912                 $receiver."', '".      # message_to
1913                 "none"."', '".         # flag
1914                 "out"."', '".          # direction
1915                 @{$hit}[6]."', '".     # delivery_time
1916                 @{$hit}[7]."', '".     # message
1917                 $timestamp."'".     # timestamp
1918                 ")";
1919             &daemon_log("M DEBUG: $sql_statement", 1);
1920             my $res = $messaging_db->exec_statement($sql_statement);
1921             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1922         }
1924         # set incoming message to flag d=deliverd
1925         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1926         &daemon_log("M DEBUG: $sql_statement", 7);
1927         $res = $messaging_db->update_dbentry($sql_statement);
1928         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1929     }
1931     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1932     return;
1935 sub watch_for_delivery_messages {
1936     my ($kernel, $heap) = @_[KERNEL, HEAP];
1938     # select outgoing messages
1939     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1940     #&daemon_log("0 DEBUG: $sql", 7);
1941     my $res = $messaging_db->exec_statement( $sql_statement );
1942     
1943     # build out msg for each    usr
1944     foreach my $hit (@{$res}) {
1945         my $receiver = @{$hit}[3];
1946         my $msg_id = @{$hit}[0];
1947         my $subject = @{$hit}[1];
1948         my $message = @{$hit}[7];
1950         # resolve usr -> host where usr is logged in
1951         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1952         #&daemon_log("0 DEBUG: $sql", 7);
1953         my $res = $login_users_db->exec_statement($sql);
1955         # receiver is logged in nowhere
1956         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1958         # receiver ist logged in at a client registered at local server
1959                 my $send_succeed = 0;
1960                 foreach my $hit (@$res) {
1961                                 my $receiver_host = @$hit[0];
1962                 my $delivered2host = 0;
1963                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1965                                 # Looking for host in know_clients_db 
1966                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1967                                 my $res = $known_clients_db->exec_statement($sql);
1969                 # Host is known in known_clients_db
1970                 if (ref(@$res[0]) eq "ARRAY") {
1971                     my $receiver_key = @{@{$res}[0]}[2];
1972                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1973                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1974                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1975                     if ($error == 0 ) {
1976                         $send_succeed++ ;
1977                         $delivered2host++ ;
1978                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
1979                     } else {
1980                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
1981                     }
1982                 }
1983                 
1984                 # Message already send, do not need to do anything more, otherwise ...
1985                 if ($delivered2host) { next;}
1986     
1987                 # ...looking for host in foreign_clients_db
1988                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1989                 $res = $foreign_clients_db->exec_statement($sql);
1990   
1991                                 # Host is known in foreign_clients_db 
1992                                 if (ref(@$res[0]) eq "ARRAY") { 
1993                     my $registration_server = @{@{$res}[0]}[2];
1994                     
1995                     # Fetch encryption key for registration server
1996                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1997                     my $res = $known_server_db->exec_statement($sql);
1998                     if (ref(@$res[0]) eq "ARRAY") { 
1999                         my $registration_server_key = @{@{$res}[0]}[3];
2000                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2001                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2002                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
2003                         if ($error == 0 ) {
2004                             $send_succeed++ ;
2005                             $delivered2host++ ;
2006                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
2007                         } else {
2008                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
2009                         }
2011                     } else {
2012                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2013                                 "registrated at server '$registration_server', ".
2014                                 "but no data available in known_server_db ", 1); 
2015                     }
2016                 }
2017                 
2018                 if (not $delivered2host) {
2019                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2020                 }
2021                 }
2023                 if ($send_succeed) {
2024                                 # set outgoing msg at db to deliverd
2025                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
2026                                 my $res = $messaging_db->exec_statement($sql); 
2027                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2028                 } else {
2029             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
2030         }
2031         }
2033     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
2034     return;
2038 sub watch_for_done_messages {
2039     my ($kernel,$heap) = @_[KERNEL, HEAP];
2041     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
2042     #&daemon_log("0 DEBUG: $sql", 7);
2043     my $res = $messaging_db->exec_statement($sql); 
2045     foreach my $hit (@{$res}) {
2046         my $msg_id = @{$hit}[0];
2048         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
2049         #&daemon_log("0 DEBUG: $sql", 7); 
2050         my $res = $messaging_db->exec_statement($sql);
2052         # not all usr msgs have been seen till now
2053         if ( ref(@$res[0]) eq "ARRAY") { next; }
2054         
2055         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
2056         #&daemon_log("0 DEBUG: $sql", 7);
2057         $res = $messaging_db->exec_statement($sql);
2058     
2059     }
2061     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2062     return;
2066 sub watch_for_old_known_clients {
2067     my ($kernel,$heap) = @_[KERNEL, HEAP];
2069     my $sql_statement = "SELECT * FROM $known_clients_tn";
2070     my $res = $known_clients_db->select_dbentry( $sql_statement );
2072     my $cur_time = int(&get_time());
2074     while ( my ($hit_num, $hit) = each %$res) {
2075         my $expired_timestamp = int($hit->{'timestamp'});
2076         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2077         my $dt = DateTime->new( year   => $1,
2078                 month  => $2,
2079                 day    => $3,
2080                 hour   => $4,
2081                 minute => $5,
2082                 second => $6,
2083                 );
2085         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2086         $expired_timestamp = $dt->ymd('').$dt->hms('');
2087         if ($cur_time > $expired_timestamp) {
2088             my $hostname = $hit->{'hostname'};
2089             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2090             my $del_res = $known_clients_db->exec_statement($del_sql);
2092             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2093         }
2095     }
2097     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2101 sub watch_for_next_tasks {
2102     my ($kernel,$heap) = @_[KERNEL, HEAP];
2104     my $sql = "SELECT * FROM $incoming_tn";
2105     my $res = $incoming_db->select_dbentry($sql);
2106     
2107     while ( my ($hit_num, $hit) = each %$res) {
2108         my $headertag = $hit->{'headertag'};
2109         if ($headertag =~ /^answer_(\d+)/) {
2110             # do not start processing, this message is for a still running POE::Wheel
2111             next;
2112         }
2113         my $message_id = $hit->{'id'};
2114         my $session_id = $hit->{'sessionid'};
2115         &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2116         $kernel->yield('next_task', $hit);
2118         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2119         my $res = $incoming_db->exec_statement($sql);
2120     }
2122     $kernel->delay_set('watch_for_next_tasks', 1); 
2126 sub get_ldap_handle {
2127         my ($session_id) = @_;
2128         my $heap;
2129         my $ldap_handle;
2131         if (not defined $session_id ) { $session_id = 0 };
2132         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2134         if ($session_id == 0) {
2135                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
2136                 $ldap_handle = Net::LDAP->new( $ldap_uri );
2137                 if (defined $ldap_handle) {
2138                         $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!"); 
2139                 } else {
2140                         daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2141                 }
2143         } else {
2144                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2145                 if( defined $session_reference ) {
2146                         $heap = $session_reference->get_heap();
2147                 }
2149                 if (not defined $heap) {
2150                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
2151                         return;
2152                 }
2154                 # TODO: This "if" is nonsense, because it doesn't prove that the
2155                 #       used handle is still valid - or if we've to reconnect...
2156                 #if (not exists $heap->{ldap_handle}) {
2157                         $ldap_handle = Net::LDAP->new( $ldap_uri );
2158                         $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!"); 
2159                         $heap->{ldap_handle} = $ldap_handle;
2160                 #}
2161         }
2162         return $ldap_handle;
2166 sub change_fai_state {
2167     my ($st, $targets, $session_id) = @_;
2168     $session_id = 0 if not defined $session_id;
2169     # Set FAI state to localboot
2170     my %mapActions= (
2171         reboot    => '',
2172         update    => 'softupdate',
2173         localboot => 'localboot',
2174         reinstall => 'install',
2175         rescan    => '',
2176         wake      => '',
2177         memcheck  => 'memcheck',
2178         sysinfo   => 'sysinfo',
2179         install   => 'install',
2180     );
2182     # Return if this is unknown
2183     if (!exists $mapActions{ $st }){
2184         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2185       return;
2186     }
2188     my $state= $mapActions{ $st };
2190     my $ldap_handle = &get_ldap_handle($session_id);
2191     if( defined($ldap_handle) ) {
2193       # Build search filter for hosts
2194         my $search= "(&(objectClass=GOhard)";
2195         foreach (@{$targets}){
2196             $search.= "(macAddress=$_)";
2197         }
2198         $search.= ")";
2200       # If there's any host inside of the search string, procress them
2201         if (!($search =~ /macAddress/)){
2202             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2203             return;
2204         }
2206       # Perform search for Unit Tag
2207       my $mesg = $ldap_handle->search(
2208           base   => $ldap_base,
2209           scope  => 'sub',
2210           attrs  => ['dn', 'FAIstate', 'objectClass'],
2211           filter => "$search"
2212           );
2214           if ($mesg->count) {
2215                   my @entries = $mesg->entries;
2216                   if (0 == @entries) {
2217                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2218                   }
2220                   foreach my $entry (@entries) {
2221                           # Only modify entry if it is not set to '$state'
2222                           if ($entry->get_value("FAIstate") ne "$state"){
2223                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2224                                   my $result;
2225                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2226                                   if (exists $tmp{'FAIobject'}){
2227                                           if ($state eq ''){
2228                                                   $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2229                                           } else {
2230                                                   $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2231                                           }
2232                                   } elsif ($state ne ''){
2233                                           $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2234                                   }
2236                                   # Errors?
2237                                   if ($result->code){
2238                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2239                                   }
2240                           } else {
2241                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
2242                           }  
2243                   }
2244           } else {
2245                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2246           }
2248     # if no ldap handle defined
2249     } else {
2250         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
2251     }
2253         return;
2257 sub change_goto_state {
2258     my ($st, $targets, $session_id) = @_;
2259     $session_id = 0  if not defined $session_id;
2261     # Switch on or off?
2262     my $state= $st eq 'active' ? 'active': 'locked';
2264     my $ldap_handle = &get_ldap_handle($session_id);
2265     if( defined($ldap_handle) ) {
2267       # Build search filter for hosts
2268       my $search= "(&(objectClass=GOhard)";
2269       foreach (@{$targets}){
2270         $search.= "(macAddress=$_)";
2271       }
2272       $search.= ")";
2274       # If there's any host inside of the search string, procress them
2275       if (!($search =~ /macAddress/)){
2276         return;
2277       }
2279       # Perform search for Unit Tag
2280       my $mesg = $ldap_handle->search(
2281           base   => $ldap_base,
2282           scope  => 'sub',
2283           attrs  => ['dn', 'gotoMode'],
2284           filter => "$search"
2285           );
2287       if ($mesg->count) {
2288         my @entries = $mesg->entries;
2289         foreach my $entry (@entries) {
2291           # Only modify entry if it is not set to '$state'
2292           if ($entry->get_value("gotoMode") ne $state){
2294             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2295             my $result;
2296             $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2298             # Errors?
2299             if ($result->code){
2300               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2301             }
2303           }
2304         }
2305       } else {
2306                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2307           }
2309     }
2313 sub run_recreate_packages_db {
2314     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2315     my $session_id = $session->ID;
2316         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2317         $kernel->yield('create_fai_release_db', $fai_release_tn);
2318         $kernel->yield('create_fai_server_db', $fai_server_tn);
2319         return;
2323 sub run_create_fai_server_db {
2324     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2325     my $session_id = $session->ID;
2326     my $task = POE::Wheel::Run->new(
2327             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2328             StdoutEvent  => "session_run_result",
2329             StderrEvent  => "session_run_debug",
2330             CloseEvent   => "session_run_done",
2331             );
2333     $heap->{task}->{ $task->ID } = $task;
2334     return;
2338 sub create_fai_server_db {
2339         my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2340         my $result;
2342         if (not defined $session_id) { $session_id = 0; }
2343         my $ldap_handle = &get_ldap_handle();
2344         if(defined($ldap_handle)) {
2345                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2346                 my $mesg= $ldap_handle->search(
2347                         base   => $ldap_base,
2348                         scope  => 'sub',
2349                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2350                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2351                 );
2352                 if($mesg->{'resultCode'} == 0 &&
2353                         $mesg->count != 0) {
2354                         foreach my $entry (@{$mesg->{entries}}) {
2355                                 if($entry->exists('FAIrepository')) {
2356                                         # Add an entry for each Repository configured for server
2357                                         foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2358                                                 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2359                                                 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2360                                                 $result= $fai_server_db->add_dbentry( { 
2361                                                                 table => $table_name,
2362                                                                 primkey => ['server', 'fai_release', 'tag'],
2363                                                                 server => $tmp_url,
2364                                                                 fai_release => $tmp_release,
2365                                                                 sections => $tmp_sections,
2366                                                                 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2367                                                         } );
2368                                         }
2369                                 }
2370                         }
2371                 }
2372                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2374                 # TODO: Find a way to post the 'create_packages_list_db' event
2375                 if(not defined($dont_create_packages_list)) {
2376                         &create_packages_list_db(undef, undef, $session_id);
2377                 }
2378         }       
2380         $ldap_handle->disconnect;
2381         return $result;
2385 sub run_create_fai_release_db {
2386         my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2387         my $session_id = $session->ID;
2388         my $task = POE::Wheel::Run->new(
2389                 Program => sub { &create_fai_release_db($table_name, $session_id) },
2390                 StdoutEvent  => "session_run_result",
2391                 StderrEvent  => "session_run_debug",
2392                 CloseEvent   => "session_run_done",
2393         );
2395         $heap->{task}->{ $task->ID } = $task;
2396         return;
2400 sub create_fai_release_db {
2401         my ($table_name, $session_id) = @_;
2402         my $result;
2404         # used for logging
2405         if (not defined $session_id) { $session_id = 0; }
2407         my $ldap_handle = &get_ldap_handle();
2408         if(defined($ldap_handle)) {
2409                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2410                 my $mesg= $ldap_handle->search(
2411                         base   => $ldap_base,
2412                         scope  => 'sub',
2413                         attrs  => [],
2414                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2415                 );
2416                 if(($mesg->code == 0) && ($mesg->count != 0))
2417                 {
2418                         daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,8);
2420                         # Walk through all possible FAI container ou's
2421                         my @sql_list;
2422                         my $timestamp= &get_time();
2423                         foreach my $ou (@{$mesg->{entries}}) {
2424                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2425                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2426                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2427                                         if(@tmp_array) {
2428                                                 foreach my $entry (@tmp_array) {
2429                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2430                                                                 my $sql= 
2431                                                                 "INSERT INTO $table_name "
2432                                                                 ."(timestamp, fai_release, class, type, state) VALUES ("
2433                                                                 .$timestamp.","
2434                                                                 ."'".$entry->{'release'}."',"
2435                                                                 ."'".$entry->{'class'}."',"
2436                                                                 ."'".$entry->{'type'}."',"
2437                                                                 ."'".$entry->{'state'}."')";
2438                                                                 push @sql_list, $sql;
2439                                                         }
2440                                                 }
2441                                         }
2442                                 }
2443                         }
2445                         daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",8);
2446                         if(@sql_list) {
2447                                 unshift @sql_list, "VACUUM";
2448                                 unshift @sql_list, "DELETE FROM $table_name";
2449                                 $fai_release_db->exec_statementlist(\@sql_list);
2450                         }
2451                         daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",7);
2452                 } else {
2453                         daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2454                 }
2455                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2456         }
2457         $ldap_handle->disconnect;
2458         return $result;
2461 sub get_fai_types {
2462         my $tmp_classes = shift || return undef;
2463         my @result;
2465         foreach my $type(keys %{$tmp_classes}) {
2466                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2467                         my $entry = {
2468                                 type => $type,
2469                                 state => $tmp_classes->{$type}[0],
2470                         };
2471                         push @result, $entry;
2472                 }
2473         }
2475         return @result;
2478 sub get_fai_state {
2479         my $result = "";
2480         my $tmp_classes = shift || return $result;
2482         foreach my $type(keys %{$tmp_classes}) {
2483                 if(defined($tmp_classes->{$type}[0])) {
2484                         $result = $tmp_classes->{$type}[0];
2485                         
2486                 # State is equal for all types in class
2487                         last;
2488                 }
2489         }
2491         return $result;
2494 sub resolve_fai_classes {
2495         my ($fai_base, $ldap_handle, $session_id) = @_;
2496         if (not defined $session_id) { $session_id = 0; }
2497         my $result;
2498         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2499         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2500         my $fai_classes;
2502         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2503         my $mesg= $ldap_handle->search(
2504                 base   => $fai_base,
2505                 scope  => 'sub',
2506                 attrs  => ['cn','objectClass','FAIstate'],
2507                 filter => $fai_filter,
2508         );
2509         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2511         if($mesg->{'resultCode'} == 0 &&
2512                 $mesg->count != 0) {
2513                 foreach my $entry (@{$mesg->{entries}}) {
2514                         if($entry->exists('cn')) {
2515                                 my $tmp_dn= $entry->dn();
2516                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2517                                         - length($fai_base) - 1 );
2519                                 # Skip classname and ou dn parts for class
2520                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2522                                 # Skip classes without releases
2523                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2524                                         next;
2525                                 }
2527                                 my $tmp_cn= $entry->get_value('cn');
2528                                 my $tmp_state= $entry->get_value('FAIstate');
2530                                 my $tmp_type;
2531                                 # Get FAI type
2532                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2533                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2534                                                 $tmp_type= $oclass;
2535                                                 last;
2536                                         }
2537                                 }
2539                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2540                                         # A Subrelease
2541                                         my @sub_releases = split(/,/, $tmp_release);
2543                                         # Walk through subreleases and build hash tree
2544                                         my $hash;
2545                                         while(my $tmp_sub_release = pop @sub_releases) {
2546                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2547                                         }
2548                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2549                                 } else {
2550                                         # A branch, no subrelease
2551                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2552                                 }
2553                         } elsif (!$entry->exists('cn')) {
2554                                 my $tmp_dn= $entry->dn();
2555                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2556                                         - length($fai_base) - 1 );
2557                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2559                                 # Skip classes without releases
2560                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2561                                         next;
2562                                 }
2564                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2565                                         # A Subrelease
2566                                         my @sub_releases= split(/,/, $tmp_release);
2568                                         # Walk through subreleases and build hash tree
2569                                         my $hash;
2570                                         while(my $tmp_sub_release = pop @sub_releases) {
2571                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2572                                         }
2573                                         # Remove the last two characters
2574                                         chop($hash);
2575                                         chop($hash);
2577                                         eval('$fai_classes->'.$hash.'= {}');
2578                                 } else {
2579                                         # A branch, no subrelease
2580                                         if(!exists($fai_classes->{$tmp_release})) {
2581                                                 $fai_classes->{$tmp_release} = {};
2582                                         }
2583                                 }
2584                         }
2585                 }
2587                 # The hash is complete, now we can honor the copy-on-write based missing entries
2588                 foreach my $release (keys %$fai_classes) {
2589                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2590                 }
2591         }
2592         return $result;
2595 sub apply_fai_inheritance {
2596        my $fai_classes = shift || return {};
2597        my $tmp_classes;
2599        # Get the classes from the branch
2600        foreach my $class (keys %{$fai_classes}) {
2601                # Skip subreleases
2602                if($class =~ /^ou=.*$/) {
2603                        next;
2604                } else {
2605                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2606                }
2607        }
2609        # Apply to each subrelease
2610        foreach my $subrelease (keys %{$fai_classes}) {
2611                if($subrelease =~ /ou=/) {
2612                        foreach my $tmp_class (keys %{$tmp_classes}) {
2613                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2614                                        $fai_classes->{$subrelease}->{$tmp_class} =
2615                                        deep_copy($tmp_classes->{$tmp_class});
2616                                } else {
2617                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2618                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2619                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2620                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2621                                                }
2622                                        }
2623                                }
2624                        }
2625                }
2626        }
2628        # Find subreleases in deeper levels
2629        foreach my $subrelease (keys %{$fai_classes}) {
2630                if($subrelease =~ /ou=/) {
2631                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2632                                if($subsubrelease =~ /ou=/) {
2633                                        apply_fai_inheritance($fai_classes->{$subrelease});
2634                                }
2635                        }
2636                }
2637        }
2639        return $fai_classes;
2642 sub get_fai_release_entries {
2643         my $tmp_classes = shift || return;
2644         my $parent = shift || "";
2645         my @result = shift || ();
2647         foreach my $entry (keys %{$tmp_classes}) {
2648                 if(defined($entry)) {
2649                         if($entry =~ /^ou=.*$/) {
2650                                 my $release_name = $entry;
2651                                 $release_name =~ s/ou=//g;
2652                                 if(length($parent)>0) {
2653                                         $release_name = $parent."/".$release_name;
2654                                 }
2655                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2656                                 foreach my $bufentry(@bufentries) {
2657                                         push @result, $bufentry;
2658                                 }
2659                         } else {
2660                                 my @types = get_fai_types($tmp_classes->{$entry});
2661                                 foreach my $type (@types) {
2662                                         push @result, 
2663                                         {
2664                                                 'class' => $entry,
2665                                                 'type' => $type->{'type'},
2666                                                 'release' => $parent,
2667                                                 'state' => $type->{'state'},
2668                                         };
2669                                 }
2670                         }
2671                 }
2672         }
2674         return @result;
2677 sub deep_copy {
2678         my $this = shift;
2679         if (not ref $this) {
2680                 $this;
2681         } elsif (ref $this eq "ARRAY") {
2682                 [map deep_copy($_), @$this];
2683         } elsif (ref $this eq "HASH") {
2684                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2685         } else { die "what type is $_?" }
2689 sub session_run_result {
2690     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2691     $kernel->sig(CHLD => "child_reap");
2694 sub session_run_debug {
2695     my $result = $_[ARG0];
2696     print STDERR "$result\n";
2699 sub session_run_done {
2700     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2701     delete $heap->{task}->{$task_id};
2705 sub create_sources_list {
2706         my $session_id = shift;
2707         my $ldap_handle = &main::get_ldap_handle;
2708         my $result="/tmp/gosa_si_tmp_sources_list";
2710         # Remove old file
2711         if(stat($result)) {
2712                 unlink($result);
2713                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2714         }
2716         my $fh;
2717         open($fh, ">$result");
2718         if (not defined $fh) {
2719                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2720                 return undef;
2721         }
2722         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2723                 my $mesg=$ldap_handle->search(
2724                         base    => $main::ldap_server_dn,
2725                         scope   => 'base',
2726                         attrs   => 'FAIrepository',
2727                         filter  => 'objectClass=FAIrepositoryServer'
2728                 );
2729                 if($mesg->count) {
2730                         foreach my $entry(@{$mesg->{'entries'}}) {
2731                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2732                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2733                                         my $line = "deb $server $release";
2734                                         $sections =~ s/,/ /g;
2735                                         $line.= " $sections";
2736                                         print $fh $line."\n";
2737                                 }
2738                         }
2739                 }
2740         } else {
2741                 if (defined $main::ldap_server_dn){
2742                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2743                 } else {
2744                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2745                 }
2746         }
2747         close($fh);
2749         return $result;
2753 sub run_create_packages_list_db {
2754     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2755         my $session_id = $session->ID;
2757         my $task = POE::Wheel::Run->new(
2758                                         Priority => +20,
2759                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2760                                         StdoutEvent  => "session_run_result",
2761                                         StderrEvent  => "session_run_debug",
2762                                         CloseEvent   => "session_run_done",
2763                                         );
2764         $heap->{task}->{ $task->ID } = $task;
2768 sub create_packages_list_db {
2769         my ($ldap_handle, $sources_file, $session_id) = @_;
2770         
2771         # it should not be possible to trigger a recreation of packages_list_db
2772         # while packages_list_db is under construction, so set flag packages_list_under_construction
2773         # which is tested befor recreation can be started
2774         if (-r $packages_list_under_construction) {
2775                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2776                 return;
2777         } else {
2778                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2779                 # set packages_list_under_construction to true
2780                 system("touch $packages_list_under_construction");
2781                 @packages_list_statements=();
2782         }
2784         if (not defined $session_id) { $session_id = 0; }
2785         if (not defined $ldap_handle) { 
2786                 $ldap_handle= &get_ldap_handle();
2788                 if (not defined $ldap_handle) {
2789                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2790                         unlink($packages_list_under_construction);
2791                         return;
2792                 }
2793         }
2794         if (not defined $sources_file) { 
2795                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2796                 $sources_file = &create_sources_list($session_id);
2797         }
2799         if (not defined $sources_file) {
2800                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2801                 unlink($packages_list_under_construction);
2802                 return;
2803         }
2805         my $line;
2807         open(CONFIG, "<$sources_file") or do {
2808                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2809                 unlink($packages_list_under_construction);
2810                 return;
2811         };
2813         # Read lines
2814         while ($line = <CONFIG>){
2815                 # Unify
2816                 chop($line);
2817                 $line =~ s/^\s+//;
2818                 $line =~ s/^\s+/ /;
2820                 # Strip comments
2821                 $line =~ s/#.*$//g;
2823                 # Skip empty lines
2824                 if ($line =~ /^\s*$/){
2825                         next;
2826                 }
2828                 # Interpret deb line
2829                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2830                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2831                         my $section;
2832                         foreach $section (split(' ', $sections)){
2833                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2834                         }
2835                 }
2836         }
2838         close (CONFIG);
2840         if(keys(%repo_dirs)) {
2841                 find(\&cleanup_and_extract, keys( %repo_dirs ));
2842                 &main::strip_packages_list_statements();
2843                 $packages_list_db->exec_statementlist(\@packages_list_statements);
2844         }
2845         unlink($packages_list_under_construction);
2846         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2847         return;
2850 # This function should do some intensive task to minimize the db-traffic
2851 sub strip_packages_list_statements {
2852         my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2853         my @new_statement_list=();
2854         my $hash;
2855         my $insert_hash;
2856         my $update_hash;
2857         my $delete_hash;
2858         my $known_packages_hash;
2859         my $local_timestamp=get_time();
2861         foreach my $existing_entry (@existing_entries) {
2862                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2863         }
2865         foreach my $statement (@packages_list_statements) {
2866                 if($statement =~ /^INSERT/i) {
2867                         # Assign the values from the insert statement
2868                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2869                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2870                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2871                                 # If section or description has changed, update the DB
2872                                 if( 
2873                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2874                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2875                                 ) {
2876                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2877                                 } else {
2878                                         # package is already present in database. cache this knowledge for later use
2879                                         @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2880                                 }
2881                         } else {
2882                                 # Insert a non-existing entry to db
2883                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2884                         }
2885                 } elsif ($statement =~ /^UPDATE/i) {
2886                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2887                         /^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;
2888                         foreach my $distribution (keys %{$hash}) {
2889                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2890                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2891                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2892                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2893                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2894                                                 my $section;
2895                                                 my $description;
2896                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2897                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2898                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2899                                                 }
2900                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2901                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2902                                                 }
2903                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2904                                         }
2905                                 }
2906                         }
2907                 }
2908         }
2910         # Check for orphaned entries
2911         foreach my $existing_entry (@existing_entries) {
2912                 my $distribution= @{$existing_entry}[0];
2913                 my $package= @{$existing_entry}[1];
2914                 my $version= @{$existing_entry}[2];
2915                 my $section= @{$existing_entry}[3];
2917                 if(
2918                         exists($insert_hash->{$distribution}->{$package}->{$version}) ||
2919                         exists($update_hash->{$distribution}->{$package}->{$version}) ||
2920                         exists($known_packages_hash->{$distribution}->{$package}->{$version})
2921                 ) {
2922                         next;
2923                 } else {
2924                         # Insert entry to delete hash
2925                         @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
2926                 }
2927         }
2929         # unroll the insert hash
2930         foreach my $distribution (keys %{$insert_hash}) {
2931                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2932                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2933                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2934                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2935                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2936                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2937                                 ."'$local_timestamp')";
2938                         }
2939                 }
2940         }
2942         # unroll the update hash
2943         foreach my $distribution (keys %{$update_hash}) {
2944                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2945                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2946                                 my $set = "";
2947                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2948                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2949                                 }
2950                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2951                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2952                                 }
2953                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2954                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2955                                 }
2956                                 if(defined($set) and length($set) > 0) {
2957                                         $set .= "timestamp = '$local_timestamp'";
2958                                 } else {
2959                                         next;
2960                                 }
2961                                 push @new_statement_list, 
2962                                 "UPDATE $main::packages_list_tn SET $set WHERE"
2963                                 ." distribution = '$distribution'"
2964                                 ." AND package = '$package'"
2965                                 ." AND version = '$version'";
2966                         }
2967                 }
2968         }
2969         
2970         # unroll the delete hash
2971         foreach my $distribution (keys %{$delete_hash}) {
2972                 foreach my $package (keys %{$delete_hash->{$distribution}}) {
2973                         foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
2974                                 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
2975                                 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
2976                         }
2977                 }
2978         }
2980         unshift(@new_statement_list, "VACUUM");
2982         @packages_list_statements = @new_statement_list;
2986 sub parse_package_info {
2987     my ($baseurl, $dist, $section, $session_id)= @_;
2988     my ($package);
2989     if (not defined $session_id) { $session_id = 0; }
2990     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2991     $repo_dirs{ "${repo_path}/pool" } = 1;
2993     foreach $package ("Packages.gz"){
2994         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2995         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2996         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2997     }
2998     
3002 sub get_package {
3003     my ($url, $dest, $session_id)= @_;
3004     if (not defined $session_id) { $session_id = 0; }
3006     my $tpath = dirname($dest);
3007     -d "$tpath" || mkpath "$tpath";
3009     # This is ugly, but I've no time to take a look at "how it works in perl"
3010     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3011         system("gunzip -cd '$dest' > '$dest.in'");
3012         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
3013         unlink($dest);
3014         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
3015     } else {
3016         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3017     }
3018     return 0;
3022 sub parse_package {
3023     my ($path, $dist, $srv_path, $session_id)= @_;
3024     if (not defined $session_id) { $session_id = 0;}
3025     my ($package, $version, $section, $description);
3026     my $PACKAGES;
3027     my $timestamp = &get_time();
3029     if(not stat("$path.in")) {
3030         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3031         return;
3032     }
3034     open($PACKAGES, "<$path.in");
3035     if(not defined($PACKAGES)) {
3036         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
3037         return;
3038     }
3040     # Read lines
3041     while (<$PACKAGES>){
3042         my $line = $_;
3043         # Unify
3044         chop($line);
3046         # Use empty lines as a trigger
3047         if ($line =~ /^\s*$/){
3048             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3049             push(@packages_list_statements, $sql);
3050             $package = "none";
3051             $version = "none";
3052             $section = "none";
3053             $description = "none"; 
3054             next;
3055         }
3057         # Trigger for package name
3058         if ($line =~ /^Package:\s/){
3059             ($package)= ($line =~ /^Package: (.*)$/);
3060             next;
3061         }
3063         # Trigger for version
3064         if ($line =~ /^Version:\s/){
3065             ($version)= ($line =~ /^Version: (.*)$/);
3066             next;
3067         }
3069         # Trigger for description
3070         if ($line =~ /^Description:\s/){
3071             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3072             next;
3073         }
3075         # Trigger for section
3076         if ($line =~ /^Section:\s/){
3077             ($section)= ($line =~ /^Section: (.*)$/);
3078             next;
3079         }
3081         # Trigger for filename
3082         if ($line =~ /^Filename:\s/){
3083             my ($filename) = ($line =~ /^Filename: (.*)$/);
3084             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3085             next;
3086         }
3087     }
3089     close( $PACKAGES );
3090     unlink( "$path.in" );
3094 sub store_fileinfo {
3095     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3097     my %fileinfo = (
3098         'package' => $package,
3099         'dist' => $dist,
3100         'version' => $vers,
3101     );
3103     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3107 sub cleanup_and_extract {
3108         my $fileinfo = $repo_files{ $File::Find::name };
3110         if( defined $fileinfo ) {
3111                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3112                 my $sql;
3113                 my $package = $fileinfo->{ 'package' };
3114                 my $newver = $fileinfo->{ 'version' };
3116                 mkpath($dir);
3117                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3119                 if( -f "$dir/DEBIAN/templates" ) {
3121                         daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3123                         my $tmpl= ""; {
3124                                 local $/=undef;
3125                                 open FILE, "$dir/DEBIAN/templates";
3126                                 $tmpl = &encode_base64(<FILE>);
3127                                 close FILE;
3128                         }
3129                         rmtree("$dir/DEBIAN/templates");
3131                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3132                         push @packages_list_statements, $sql;
3133                 }
3134         }
3136         return;
3140 sub register_at_foreign_servers {   
3141     my ($kernel) = $_[KERNEL];
3143     # hole alle bekannten server aus known_server_db
3144     my $server_sql = "SELECT * FROM $known_server_tn";
3145     my $server_res = $known_server_db->exec_statement($server_sql);
3147     # no entries in known_server_db
3148     if (not ref(@$server_res[0]) eq "ARRAY") { 
3149         # TODO
3150     }
3152     # detect already connected clients
3153     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3154     my $client_res = $known_clients_db->exec_statement($client_sql);
3156     # send my server details to all other gosa-si-server within the network
3157     foreach my $hit (@$server_res) {
3158         my $hostname = @$hit[0];
3159         my $hostkey = &create_passwd;
3161         # add already connected clients to registration message 
3162         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3163         &add_content2xml_hash($myhash, 'key', $hostkey);
3164         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3166         # add locally loaded gosa-si modules to registration message
3167         my $loaded_modules = {};
3168         while (my ($package, $pck_info) = each %$known_modules) {
3169                                                 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3170                                                 foreach my $act_module (keys(%{@$pck_info[2]})) {
3171                                                         $loaded_modules->{$act_module} = ""; 
3172                                                 }
3173         }
3175         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3177         # add macaddress to registration message
3178         my ($host_ip, $host_port) = split(/:/, $hostname);
3179         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3180         my $network_interface= &get_interface_for_ip($local_ip);
3181         my $host_mac = &get_mac_for_interface($network_interface);
3182         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3183         
3184         # build registration message and send it
3185         my $foreign_server_msg = &create_xml_string($myhash);
3186         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3187     }
3188     
3189     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
3190     return;
3194 #==== MAIN = main ==============================================================
3195 #  parse commandline options
3196 Getopt::Long::Configure( "bundling" );
3197 GetOptions("h|help" => \&usage,
3198         "c|config=s" => \$cfg_file,
3199         "f|foreground" => \$foreground,
3200         "v|verbose+" => \$verbose,
3201         "no-arp+" => \$no_arp,
3202            );
3204 #  read and set config parameters
3205 &check_cmdline_param ;
3206 &read_configfile($cfg_file, %cfg_defaults);
3207 &check_pid;
3209 $SIG{CHLD} = 'IGNORE';
3211 # forward error messages to logfile
3212 if( ! $foreground ) {
3213   open( STDIN,  '+>/dev/null' );
3214   open( STDOUT, '+>&STDIN'    );
3215   open( STDERR, '+>&STDIN'    );
3218 # Just fork, if we are not in foreground mode
3219 if( ! $foreground ) { 
3220     chdir '/'                 or die "Can't chdir to /: $!";
3221     $pid = fork;
3222     setsid                    or die "Can't start a new session: $!";
3223     umask 0;
3224 } else { 
3225     $pid = $$; 
3228 # Do something useful - put our PID into the pid_file
3229 if( 0 != $pid ) {
3230     open( LOCK_FILE, ">$pid_file" );
3231     print LOCK_FILE "$pid\n";
3232     close( LOCK_FILE );
3233     if( !$foreground ) { 
3234         exit( 0 ) 
3235     };
3238 # parse head url and revision from svn
3239 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3240 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3241 $server_headURL = defined $1 ? $1 : 'unknown' ;
3242 $server_revision = defined $2 ? $2 : 'unknown' ;
3243 if ($server_headURL =~ /\/tag\// || 
3244         $server_headURL =~ /\/branches\// ) {
3245     $server_status = "stable"; 
3246 } else {
3247     $server_status = "developmental" ;
3250 # Prepare log file and set permissons
3251 $root_uid = getpwnam('root');
3252 $adm_gid = getgrnam('adm');
3253 open(FH, ">>$log_file");
3254 close FH;
3255 chmod(0440, $log_file);
3256 chown($root_uid, $adm_gid, $log_file);
3257 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3259 daemon_log(" ", 1);
3260 daemon_log("$0 started!", 1);
3261 daemon_log("status: $server_status", 1);
3262 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3265     no strict "refs";
3267     if ($db_module eq "DBmysql") {
3268         # connect to incoming_db
3269         $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3271         # connect to gosa-si job queue
3272         $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3274         # connect to known_clients_db
3275         $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3277         # connect to foreign_clients_db
3278         $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3280         # connect to known_server_db
3281         $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3283         # connect to login_usr_db
3284         $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3286         # connect to fai_server_db 
3287         $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3289         # connect to fai_release_db
3290         $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3292         # connect to packages_list_db
3293         $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3295         # connect to messaging_db
3296         $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3298     } elsif ($db_module eq "DBsqlite") {
3299         # connect to incoming_db
3300         unlink($incoming_file_name);
3301         $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3302         
3303         # connect to gosa-si job queue
3304         unlink($job_queue_file_name);  ## just for debugging
3305         $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3306         chmod(0640, $job_queue_file_name);
3307         chown($root_uid, $adm_gid, $job_queue_file_name);
3308         
3309         # connect to known_clients_db
3310         unlink($known_clients_file_name);   ## just for debugging
3311         $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3312         chmod(0640, $known_clients_file_name);
3313         chown($root_uid, $adm_gid, $known_clients_file_name);
3314         
3315         # connect to foreign_clients_db
3316         unlink($foreign_clients_file_name);
3317         $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3318         chmod(0640, $foreign_clients_file_name);
3319         chown($root_uid, $adm_gid, $foreign_clients_file_name);
3320         
3321         # connect to known_server_db
3322         unlink($known_server_file_name);
3323         $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3324         chmod(0640, $known_server_file_name);
3325         chown($root_uid, $adm_gid, $known_server_file_name);
3326         
3327         # connect to login_usr_db
3328         unlink($login_users_file_name);
3329         $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3330         chmod(0640, $login_users_file_name);
3331         chown($root_uid, $adm_gid, $login_users_file_name);
3332         
3333         # connect to fai_server_db
3334         unlink($fai_server_file_name);
3335         $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3336         chmod(0640, $fai_server_file_name);
3337         chown($root_uid, $adm_gid, $fai_server_file_name);
3338         
3339         # connect to fai_release_db
3340         unlink($fai_release_file_name);
3341         $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3342         chmod(0640, $fai_release_file_name);
3343         chown($root_uid, $adm_gid, $fai_release_file_name);
3344         
3345         # connect to packages_list_db
3346         #unlink($packages_list_file_name);
3347         unlink($packages_list_under_construction);
3348         $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3349         chmod(0640, $packages_list_file_name);
3350         chown($root_uid, $adm_gid, $packages_list_file_name);
3351         
3352         # connect to messaging_db
3353         unlink($messaging_file_name);
3354         $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3355         chmod(0640, $messaging_file_name);
3356         chown($root_uid, $adm_gid, $messaging_file_name);
3357     }
3361 # Creating tables
3362 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3363 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3364 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3365 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3366 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3367 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3368 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3369 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3370 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3371 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3373 # create xml object used for en/decrypting
3374 $xml = new XML::Simple();
3377 # foreign servers 
3378 my @foreign_server_list;
3380 # add foreign server from cfg file
3381 if ($foreign_server_string ne "") {
3382     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3383     foreach my $foreign_server (@cfg_foreign_server_list) {
3384         push(@foreign_server_list, $foreign_server);
3385     }
3387     daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3390 # Perform a DNS lookup for server registration if flag is true
3391 if ($dns_lookup eq "true") {
3392     # Add foreign server from dns
3393     my @tmp_servers;
3394     if (not $server_domain) {
3395         # Try our DNS Searchlist
3396         for my $domain(get_dns_domains()) {
3397             chomp($domain);
3398             my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3399             if(@$tmp_domains) {
3400                 for my $tmp_server(@$tmp_domains) {
3401                     push @tmp_servers, $tmp_server;
3402                 }
3403             }
3404         }
3405         if(@tmp_servers && length(@tmp_servers)==0) {
3406             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3407         }
3408     } else {
3409         @tmp_servers = &get_server_addresses($server_domain);
3410         if( 0 == @tmp_servers ) {
3411             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3412         }
3413     }
3415     daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3417     foreach my $server (@tmp_servers) { 
3418         unshift(@foreign_server_list, $server); 
3419     }
3420 } else {
3421     daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3425 # eliminate duplicate entries
3426 @foreign_server_list = &del_doubles(@foreign_server_list);
3427 my $all_foreign_server = join(", ", @foreign_server_list);
3428 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3430 # add all found foreign servers to known_server
3431 my $cur_timestamp = &get_time();
3432 foreach my $foreign_server (@foreign_server_list) {
3434         # do not add myself to known_server_db
3435         if (&is_local($foreign_server)) { next; }
3436         ######################################
3438     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3439             primkey=>['hostname'],
3440             hostname=>$foreign_server,
3441             macaddress=>"",
3442             status=>'not_yet_registered',
3443             hostkey=>"none",
3444             loaded_modules => "none", 
3445             timestamp=>$cur_timestamp,
3446             } );
3450 # Import all modules
3451 &import_modules;
3453 # Check wether all modules are gosa-si valid passwd check
3454 &password_check;
3456 # Prepare for using Opsi 
3457 if ($opsi_enabled eq "true") {
3458     use JSON::RPC::Client;
3459     use XML::Quote qw(:all);
3460     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3461     $opsi_client = new JSON::RPC::Client;
3465 POE::Component::Server::TCP->new(
3466         Alias => "TCP_SERVER",
3467         Port => $server_port,
3468         ClientInput => sub {
3469                 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3470         my $session_id = $session->ID;
3471         my $remote_ip = $heap->{'remote_ip'};
3472                 push(@msgs_to_decrypt, $input);
3473         &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3474                 $kernel->yield("msg_to_decrypt");
3475         },
3476         InlineStates => {
3477                 msg_to_decrypt => \&msg_to_decrypt,
3478                 next_task => \&next_task,
3479                 task_result => \&handle_task_result,
3480                 task_done   => \&handle_task_done,
3481                 task_debug  => \&handle_task_debug,
3482                 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3483         }
3484 );
3486 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3488 # create session for repeatedly checking the job queue for jobs
3489 POE::Session->create(
3490         inline_states => {
3491                 _start => \&session_start,
3492         register_at_foreign_servers => \&register_at_foreign_servers,
3493         sig_handler => \&sig_handler,
3494         next_task => \&next_task,
3495         task_result => \&handle_task_result,
3496         task_done   => \&handle_task_done,
3497         task_debug  => \&handle_task_debug,
3498         watch_for_next_tasks => \&watch_for_next_tasks,
3499         watch_for_new_messages => \&watch_for_new_messages,
3500         watch_for_delivery_messages => \&watch_for_delivery_messages,
3501         watch_for_done_messages => \&watch_for_done_messages,
3502                 watch_for_new_jobs => \&watch_for_new_jobs,
3503         watch_for_modified_jobs => \&watch_for_modified_jobs,
3504         watch_for_done_jobs => \&watch_for_done_jobs,
3505         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3506         watch_for_old_known_clients => \&watch_for_old_known_clients,
3507         create_packages_list_db => \&run_create_packages_list_db,
3508         create_fai_server_db => \&run_create_fai_server_db,
3509         create_fai_release_db => \&run_create_fai_release_db,
3510                 recreate_packages_db => \&run_recreate_packages_db,
3511         session_run_result => \&session_run_result,
3512         session_run_debug => \&session_run_debug,
3513         session_run_done => \&session_run_done,
3514         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3515         }
3516 );
3519 POE::Kernel->run();
3520 exit;