Code

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