Code

Applied patch
[gosa.git] / gosa-si / gosa-si-server
1 #!/usr/bin/perl
2 #*********************************************************************
3 #
4 # gosa-si-client -- client for the gosa-si-server
5 #
6 # (c) 2007-2009 by Andreas Rettenberger <rettenberger@gonicus.de>
7 # (c) 2008-2010 by Cajus Pollmeier <pollmeier@gonicus.de>
8 # (c) 2008-2009 by Jan Wenzel <wenzel@gonicus.de>
9 # (c) 2010 by Benoit Mortier <benoit.mortier@opensides.be>
10 #
11 #*********************************************************************
14 =head1 NAME
16 gosa-si-server -Support infrastructure for GOsa
18 =head1 SYNOPSIS
20 gosa-si-server [-hvf] [-c config] [-x dump ]
22 =head1 OPTIONS
24 B<-h>, B<--help>
25     print out this help message
27 B<-v>, B<--verbose>
28     be verbose (multiple v's will increase verbosity) 
29     -v          ERROR level
30     -vvv        WARNING  + ERROR level
31     -vvvvv      INFO + WARNING level
32     -vvvvvvv    DEBUG + INFO level
33     -vvvvvvvvv  in and out going xml messages will be displayed
35 B<-f>, B<--foreground> 
36     foregroud, process will not be forked to background
38 B<-c> I<file>, B<--config=>I<file>
39     configuration file, default F</etc/gosa-si/server.conf>
41 B<--no-arp>
42     starts script without connection to arp module
44 B<-d> <int> 
45     if verbose level is higher than 7 'v' specified parts can be debugged
47       1 : receiving messages
48       2 : sending messages
49       4 : encrypting/decrypting messages
50       8 : verification if a message complies gosa-si requirements
51      16 : message processing
52      32 : ldap connectivity
53      64 : database status and connectivity
54     128 : main process
55     256 : creation of packages_list_db
56     512 : ARP debug information
58 B<-x> <dump>
59      dump configuration to stdout
60      ( 1 = current, 2 = default )
62 =head1 DESCRIPTION
64 gosa-si-server  belongs  to  the  support infrastructure of GOsa.  Several gosa-si-clients can connect to one gosa-si-server.  The server take care of the message forwarding from GOsa to si-clients.  At the client site each message is related to a working instruction which will be executed there.  Depending of the message an answer from the client to GOsa via the server is possible.  Additional to answers clients reporting events  or  information  to the server.  The server registers himself at other servers in network and shares his knowledge with them.  So messages to clients which are no registrated locally will be forward to the client corresponding server. The communication within the complete SI nework is realised by XML messages.
67 =head1 BUGS 
69 Please report any bugs, or post any suggestions, to the GOsa mailing list <gosa-devel@oss.gonicus.de> or to <https://oss.gonicus.de/labs/gosa>
72 =head1 LICENCE AND COPYRIGHT
74 This code is part of GOsa (L<http://www.gosa-project.org>)
76 Copyright (C) 2003-2010 GONICUS GmbH
78 This program is distributed in the hope that it will be useful,
79 but WITHOUT ANY WARRANTY; without even the implied warranty of
80 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
81 GNU General Public License for more details.
83 =cut
85 use strict;
86 use warnings;
88 use Getopt::Long;
89 use Config::IniFiles;
90 use IO::Socket::INET;
91 use IO::Handle;
92 use IO::Select;
93 use Crypt::Rijndael;
94 use MIME::Base64;
95 use Digest::MD5  qw(md5 md5_hex md5_base64);
96 use XML::Simple;
97 use Data::Dumper;
98 use Sys::Syslog qw( :DEFAULT setlogsock);
99 use Time::HiRes qw( usleep clock_gettime );
100 use File::Spec;
101 use File::Basename;
102 use File::Find;
103 use File::Copy;
104 use File::Path;
105 use Net::LDAP;
106 use Net::LDAP::Util qw(:escape);
107 use File::Pid;
108 use GOsaSI::GosaSupportDaemon;
110 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
111 use Symbol qw(qualify_to_ref);
112 use Fcntl qw/:flock/;
113 use POSIX;
115 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev$';
117 # revision number of server and program name
118 my $server_headURL;
119 my $server_revision;
120 my $server_status;
122 my $db_module = "DBsqlite";
124 no strict "refs";
125 require ("GOsaSI/".$db_module.".pm");
126 ("GOsaSI/".$db_module)->import;
129 my $modules_path = "/usr/lib/gosa-si/modules";
130 use lib "/usr/lib/gosa-si/modules";
132 my ($foreground, $ping_timeout);
133 my ($server);
134 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
135 my ($messaging_db_loop_delay);
136 my $procid;
137 my $arp_fifo;
138 my $debug_parts = 0;
139 my $debug_parts_bitstring;
140 my ($xml);
141 my $sources_list;
142 my $max_clients;
143 my %repo_files=();
144 my $repo_path;
145 my %repo_dirs=();
147 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
149 # Variables declared in config file are always set to 'our'
150 our (%cfg_defaults, $log_file, $pid_file, $pid,
151     $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
152     $arp_activ, $gosa_unit_tag,
153     $GosaPackages_key, $gosa_timeout,
154     $serverPackages_enabled, $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
155     $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
156     $arp_enabled, $arp_interface,
157     $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
158     $new_systems_ou,
159     $arch,
160 );
162 # additional variable which should be globaly accessable
163 our $server_address;
164 our $server_mac_address;
165 our $gosa_address;
166 our $no_arp;
167 our $forground;
168 our $cfg_file;
169 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn, $ldap_version, $ldap_retry_sec);
170 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
171 our $known_modules;
172 our $known_functions;
173 our $root_uid;
174 our $adm_gid;
175 our $verbose= 0;
176 our $global_kernel;
178 # where is the config stored by default and his name
179 our $config = '/etc/gosa-si/server.conf';
181 # by default dumping of config is undefined
182 our $dump_config = undef;
184 # if foreground is not null, script will be not forked to background
185 $foreground = 0 ;
187 # specifies the timeout seconds while checking the online status of a registrating client
188 $ping_timeout = 5;
190 $no_arp = 0;
191 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
192 my @packages_list_statements;
193 my $watch_for_new_jobs_in_progress = 0;
195 # holds all incoming decrypted messages
196 our $incoming_db;
197 our $incoming_tn = 'incoming';
198 my $incoming_file_name;
199 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
200         "timestamp VARCHAR(14) DEFAULT 'none'", 
201         "headertag VARCHAR(255) DEFAULT 'none'",
202         "targettag VARCHAR(255) DEFAULT 'none'",
203         "xmlmessage TEXT",
204         "module VARCHAR(255) DEFAULT 'none'",
205         "sessionid VARCHAR(255) DEFAULT '0'",
206 );
208 # holds all gosa jobs
209 our $job_db;
210 our $job_queue_tn = 'jobs';
211 my $job_queue_file_name;
212 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
213         "timestamp VARCHAR(14) DEFAULT 'none'", 
214         "status VARCHAR(255) DEFAULT 'none'", 
215         "result TEXT",
216         "progress VARCHAR(255) DEFAULT 'none'",
217         "headertag VARCHAR(255) DEFAULT 'none'",
218         "targettag VARCHAR(255) DEFAULT 'none'", 
219         "xmlmessage TEXT", 
220         "macaddress VARCHAR(17) DEFAULT 'none'",
221         "plainname VARCHAR(255) DEFAULT 'none'",
222         "siserver VARCHAR(255) DEFAULT 'none'",
223         "modified INTEGER DEFAULT '0'",
224         "periodic VARCHAR(6) DEFAULT 'none'",
225 );
227 # holds all other gosa-si-server
228 our $known_server_db;
229 our $known_server_tn = "known_server";
230 my $known_server_file_name;
231 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)", "update_time VARCHAR(14)");
233 # holds all registrated clients
234 our $known_clients_db;
235 our $known_clients_tn = "known_clients";
236 my $known_clients_file_name;
237 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)");
239 # holds all registered clients at a foreign server
240 our $foreign_clients_db;
241 our $foreign_clients_tn = "foreign_clients"; 
242 my $foreign_clients_file_name;
243 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
245 # holds all logged in user at each client 
246 our $login_users_db;
247 our $login_users_tn = "login_users";
248 my $login_users_file_name;
249 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
251 # holds all fai server, the debian release and tag
252 our $fai_server_db;
253 our $fai_server_tn = "fai_server"; 
254 my $fai_server_file_name;
255 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)"); 
257 our $fai_release_db;
258 our $fai_release_tn = "fai_release"; 
259 my $fai_release_file_name;
260 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)"); 
262 # holds all packages available from different repositories
263 our $packages_list_db;
264 our $packages_list_tn = "packages_list";
265 my $packages_list_file_name;
266 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
267 my $outdir = "/tmp/packages_list_db";
269 # holds all messages which should be delivered to a user
270 our $messaging_db;
271 our $messaging_tn = "messaging"; 
272 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)", 
273         "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
274 my $messaging_file_name;
276 # path to directory to store client install log files
277 our $client_fai_log_dir = "/var/log/fai"; 
279 # queue which stores taskes until one of the $max_children children are ready to process the task
280 my @msgs_to_decrypt = qw();
281 my $max_children = 2;
284 # loop delay for job queue to look for opsi jobs
285 my $job_queue_opsi_delay = 10;
286 our $opsi_client;
287 our $opsi_url;
288  
289 # Lifetime of logged in user information. If no update information comes after n seconds, 
290 # the user is expeceted to be no longer logged in or the host is no longer running. Because
291 # of this, the user is deleted from login_users_db
292 our $logged_in_user_date_of_expiry = 600;
294 # List of month names, used in function daemon_log
295 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
297 # List of accepted periodical xml tags related to cpan modul DateTime
298 our $check_periodic = {"months"=>'', "weeks"=>'', "days"=>'', "hours"=>'', "minutes"=>''};
301 %cfg_defaults = (
302 "General" => {
303     "log-file" => [\$log_file, "/var/log/gosa-si/gosa-si-server.log"],
304     "pid-file" => [\$pid_file, "/var/run/gosa-si/gosa-si-server.pid"],
305     },
306 "Server" => {
307     "ip"                    => [\$server_ip, "0.0.0.0"],
308     "port"                  => [\$server_port, "20081"],
309     "known-clients"         => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
310     "known-servers"         => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
311     "incoming"              => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
312     "login-users"           => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
313     "fai-server"            => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
314     "fai-release"           => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
315     "packages-list"         => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
316     "messaging"             => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
317     "foreign-clients"       => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
318     "source-list"           => [\$sources_list, '/etc/apt/sources.list'],
319     "repo-path"             => [\$repo_path, '/srv/www/debian'],
320     "debian-arch"           => [\$arch, 'i386'],     
321     "ldap-uri"              => [\$ldap_uri, ""],
322     "ldap-base"             => [\$ldap_base, ""],
323     "ldap-admin-dn"         => [\$ldap_admin_dn, ""],
324     "ldap-admin-password"   => [\$ldap_admin_password, ""],
325     "ldap-version"          => [\$ldap_version, 3],
326     "ldap-retry-sec"        => [\$ldap_retry_sec, 10],
327     "gosa-unit-tag"         => [\$gosa_unit_tag, ""],
328     "max-clients"           => [\$max_clients, 10],
329     "wol-password"          => [\$wake_on_lan_passwd, ""],
330     "mysql-username"        => [\$mysql_username, "gosa_si"],
331     "mysql-password"        => [\$mysql_password, ""],
332     "mysql-database"        => [\$mysql_database, "gosa_si"],
333     "mysql-host"            => [\$mysql_host, "127.0.0.1"],
334     },
335 "GOsaPackages" => {
336     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
337     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
338     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
339     "key" => [\$GosaPackages_key, "none"],
340     "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
341     },
342 "ClientPackages" => {
343     "key" => [\$ClientPackages_key, "none"],
344     "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
345     },
346 "ServerPackages"=> {
347     "enabled"      => [\$serverPackages_enabled, "true"],
348     "address"      => [\$foreign_server_string, ""],
349     "dns-lookup"   => [\$dns_lookup, "true"],
350     "domain"       => [\$server_domain, ""],
351     "key"          => [\$ServerPackages_key, "none"],
352     "key-lifetime" => [\$foreign_servers_register_delay, 120],
353     "job-synchronization-enabled" => [\$job_synchronization, "true"],
354     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
355     },
356 "ArpHandler" => {
357     "enabled"   => [\$arp_enabled, "false"],
358     "interface" => [\$arp_interface, "all"],
359         },
360 "Opsi" => {
361     "enabled"  => [\$opsi_enabled, "false"], 
362     "server"   => [\$opsi_server, "localhost"],
363     "admin"    => [\$opsi_admin, "opsi-admin"],
364     "password" => [\$opsi_password, "secret"],
365    },
367 );
369 #############################
371 # @brief Display error message and/or help text.
373 # In correspondence to previous GetOptions
375 # @param $text - string to print as error message
376 # @param $help - set true, if you want to show usage help
378 sub usage
380   my( $text, $help ) = @_;
382   $text = undef if( 'h' eq $text );
383   (defined $text) && print STDERR "\n$text\n";
385   if( (defined $help && $help)
386       || (!defined $help && !defined $text) )
388     print STDERR << "EOF";
390   usage: $0 [-hvf] [-c config] [-d number]
392    -h        : this (help) message
393    -c <file> : config file (default: ${config})
394    -x <cfg>  : dump configuration to stdout
395              ( 1 = current, 2 = default )
396    -f        : foreground (don't fork)
397            -v        : be verbose (multiple to increase verbosity)
398                               'v': error logs
399                             'vvv': warning plus error logs                                              
400                           'vvvvv': info plus warning logs
401                         'vvvvvvv': debug plus info logs
402            -no-arp   : starts gosa-si-server without connection to arp module
403            -d <int>  : if verbose level is higher than 7x 'v' specified parts can be debugged
404                            1 : report incoming messages
405                            2 : report unencrypted outgoing messages 
406                            4 : report encrypting key for messages
407                            8 : report decrypted incoming message and verification if the message complies gosa-si requirements
408                           16 : message processing
409                           32 : ldap connectivity
410                           64 : database status and connectivity
411                          128 : main process 
412                          256 : creation of packages_list_db
413                          512 : ARP debug information
414 EOF
416   print( "\n" );
418   exit( -1 );
421 #############################
423 # @brief Manage gosa-si-client configuration.
425 # Will exit after successfull dump to stdout (type = 1 | 2)
427 # Dump type can be:
428 #   1: Current gosa-si-client configuration in config file (exit)
429 #   2: Default gosa-si-client configuration (exit)
430 #   3: Dump to logfile (no exit)
432 # @param int config type
434 sub dump_configuration {
436   my( $cfg_type ) = @_;
438   return if( ! defined $cfg_type );
440   if(1==$cfg_type ) {
441     print( "# Current gosa-si-server configuration\n" );
442         } elsif (2==$cfg_type) {
443     print( "# Default gosa-si-server configuration\n" );
444         } elsif (3==$cfg_type) {
445     daemon_log( "Dumping gosa-si-server configuration\n", 2 );
446         } else {
447     return;
448         }
450   foreach my $section (keys %cfg_defaults) {
451     if( 3 != $cfg_type ) { 
452       print( "\n[${section}]\n" ); 
453         } else {
454       daemon_log( "\n  [${section}]\n", 3 ); 
455         }
457                 foreach my $param (sort( keys %{$cfg_defaults{ $section }})) {
458       my $pinfo = $cfg_defaults{ $section }{ $param };
459       my $value;
460       if (1==$cfg_type) {
461         if( defined( ${@$pinfo[ 0 ]} ) ) {
462           $value = ${@$pinfo[ 0 ]};
463           print( "$param=$value\n" );
464                                 } else {
465           print( "#${param}=\n" ); 
466                                 }
467                         } elsif (2==$cfg_type) {
468         $value = @{$pinfo}[ 1 ];
469         if( defined( @$pinfo[ 1 ] ) ) {
470           $value = @{$pinfo}[ 1 ];
471           print( "$param=$value\n" );
472                                 } else {
473           print( "#${param}=\n" ); 
474                                 }
475                         } elsif (3==$cfg_type) {
476         if( defined(  ${@$pinfo[ 0 ]} ) ) {
477           $value = ${@$pinfo[ 0 ]};
478           daemon_log( "  $param=$value\n", 3 )
479                                 }
480                         }
481                 }
482         }
485 # We just exit at stdout dump
486   if( 3 == $cfg_type ) { 
487     daemon_log( "\n", 3 );
488         } else {
489     exit( 0 );
490         }
494 #===  FUNCTION  ================================================================
495 #         NAME:  logging
496 #   PARAMETERS:  level - string - default 'info'
497 #                msg - string -
498 #                facility - string - default 'LOG_DAEMON'
499 #      RETURNS:  nothing
500 #  DESCRIPTION:  function for logging
501 #===============================================================================
502 sub daemon_log {
503     my( $msg, $level ) = @_;
504     if (not defined $msg) { return }
505     if (not defined $level) { $level = 1 }
506         my $to_be_logged = 0;
508         # Write log line if line level is lower than verbosity given in commandline
509         if ($level <= $verbose) 
510         { 
511                 $to_be_logged = 1 ;
512         }
514         # Write if debug flag is set and bitstring matches
515         if ($debug_parts > 0)
516         {
517                 my $tmp_level = ($level - 10 >= 0) ? $level - 10 : 0 ;
518                 my $tmp_level_bitstring = unpack("B32", pack("N", $tmp_level));
519                 if (int($debug_parts_bitstring & $tmp_level_bitstring)) 
520                 {
521                         $to_be_logged = 1;
522                 }
523         }
525         if ($to_be_logged) 
526         {
527                 if(defined $log_file){
528                         my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
529                         if(not $open_log_fh) {
530                                 print STDERR "cannot open $log_file: $!";
531                                 return;
532                         }
533                         # Check owner and group of log_file and update settings if necessary
534                         my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
535                         if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
536                                 chown($root_uid, $adm_gid, $log_file);
537                         }
539                         # Prepare time string for log message
540                         my ($seconds,$minutes,$hours,$monthday,$month,$year,$weekday,$yearday,$sommertime) = localtime(time);
541                         $hours = $hours < 10 ? $hours = "0".$hours : $hours;
542                         $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
543                         $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
544                         $month = $monthnames[$month];
545                         $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
546                         $year+=1900;
548                         # Microseconds since epoch
549                         my $microSeconds = sprintf("%.2f", &Time::HiRes::clock_gettime());
550                         $microSeconds =~ s/^\d*(.\d\d)$/$1/;
552                         # Build log message and write it to log file and commandline
553                         chomp($msg);
554                         my $log_msg = "$month $monthday $hours:$minutes:$seconds$microSeconds $0 $msg\n";
555                         flock(LOG_HANDLE, LOCK_EX);
556                         seek(LOG_HANDLE, 0, 2);
557                         print LOG_HANDLE $log_msg;
558                         flock(LOG_HANDLE, LOCK_UN);
559                         if( $foreground ) 
560                         { 
561                                 print STDERR $log_msg;
562                         }
563                         close( LOG_HANDLE );
564                 }
565         }
568 #===  FUNCTION  ================================================================
569 #         NAME:  import_modules
570 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
571 #                are stored
572 #      RETURNS:  nothing
573 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
574 #                state is on is imported by "require 'file';"
575 #===============================================================================
576 sub import_modules {
577     if (not -e $modules_path) {
578         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
579     }
581     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
583     while (defined (my $file = readdir (DIR))) {
584         if (not $file =~ /(\S*?).pm$/) {
585             next;
586         }
587                 my $mod_name = $1;
589         # ArpHandler switch
590         if( $file =~ /ArpHandler.pm/ ) {
591             if( $arp_enabled eq "false" ) { next; }
592         }
594                 # ServerPackages switch
595                 if ($file eq "ServerPackages.pm" && $serverPackages_enabled eq "false") 
596                 {
597                         $dns_lookup = "false";
598                         next; 
599                 }
600         
601         eval { require $file; };
602         if ($@) {
603             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
604             daemon_log("$@", 1);
605             exit;
606                 } else {
607                         my $info = eval($mod_name.'::get_module_info()');
608                         # Only load module if get_module_info() returns a non-null object
609                         if( $info ) {
610                                 my ($input_address, $input_key, $event_hash) = @{$info};
611                                 $known_modules->{$mod_name} = $info;
612                                 daemon_log("0 INFO: module $mod_name loaded", 5);
613                         }
614                 }
615     }   
616     close (DIR);
619 #===  FUNCTION  ================================================================
620 #         NAME:  password_check
621 #   PARAMETERS:  nothing
622 #      RETURNS:  nothing
623 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
624 #                the same password
625 #===============================================================================
626 sub password_check {
627     my $passwd_hash = {};
628     while (my ($mod_name, $mod_info) = each %$known_modules) {
629         my $mod_passwd = @$mod_info[1];
630         if (not defined $mod_passwd) { next; }
631         if (not exists $passwd_hash->{$mod_passwd}) {
632             $passwd_hash->{$mod_passwd} = $mod_name;
634         # escalates critical error
635         } else {
636             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
637             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
638             exit( -1 );
639         }
640     }
645 #===  FUNCTION  ================================================================
646 #         NAME:  sig_int_handler
647 #   PARAMETERS:  signal - string - signal came from system
648 #      RETURNS:  nothing
649 #  DESCRIPTION:  handle tasks to be done before signal becomes active
650 #===============================================================================
651 sub sig_int_handler {
652         my ($signal) = @_;
654 #       if (defined($ldap_handle)) {
655 #               $ldap_handle->disconnect;
656 #       }
657 # TODO all ldap connections shoudl be closed
658     
659         daemon_log("shutting down gosa-si-server", 1);
661         # asking the poe kernel to shutdown the server
662         $global_kernel->yield(TCP_SERVER => 'shutdown');
664         # removing the pidfile
665         $pid->remove or warn "Could not remove $pid_file\n";
667         exit(0);
669 $SIG{INT} = \&sig_int_handler;
672 sub check_key_and_xml_validity {
673     my ($crypted_msg, $module_key, $session_id) = @_;
674     my $msg;
675     my $msg_hash;
676     my $error_string;
677     eval{
678         $msg = &decrypt_msg($crypted_msg, $module_key);
680         if ($msg =~ /<xml>/i){
681             $msg =~ s/\s+/ /g;  # just for better daemon_log
682             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 18);
683             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
685             ##############
686             # check header
687             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
688             my $header_l = $msg_hash->{'header'};
689             if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
690             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
691             my $header = @{$header_l}[0];
692             if( 0 == length $header) { die 'empty string in header tag'; }
694             ##############
695             # check source
696             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
697             my $source_l = $msg_hash->{'source'};
698             if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
699             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
700             my $source = @{$source_l}[0];
701             if( 0 == length $source) { die 'source error'; }
703             ##############
704             # check target
705             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
706             my $target_l = $msg_hash->{'target'};
707             if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
708         }
709     };
710     if($@) {
711         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
712         $msg = undef;
713         $msg_hash = undef;
714     }
716     return ($msg, $msg_hash);
720 sub check_outgoing_xml_validity {
721     my ($msg, $session_id) = @_;
723     my $msg_hash;
724     eval{
725         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
727         ##############
728         # check header
729         my $header_l = $msg_hash->{'header'};
730         if( 1 != @{$header_l} ) {
731             die 'no or more than one headers specified';
732         }
733         my $header = @{$header_l}[0];
734         if( 0 == length $header) {
735             die 'header has length 0';
736         }
738         ##############
739         # check source
740         my $source_l = $msg_hash->{'source'};
741         if( 1 != @{$source_l} ) {
742             die 'no or more than 1 sources specified';
743         }
744         my $source = @{$source_l}[0];
745         if( 0 == length $source) {
746             die 'source has length 0';
747         }
749                 # Check if source contains hostname instead of ip address
750                 if($source =~ /^[a-z][\w\-\.]+:\d+$/i) {
751                         my ($hostname,$port) = split(/:/, $source);
752                         my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
753                         if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
754                                 # Write ip address to $source variable
755                                 $source = "$ip_address:$port";
756                                 $msg_hash->{source}[0] = $source ;
757                                 $msg =~ s/<source>.*<\/source>/<source>$source<\/source>/; 
758                         }
759                 }
760         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
761                 $source =~ /^GOSA$/i) {
762             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
763         }
764         
765         ##############
766         # check target  
767         my $target_l = $msg_hash->{'target'};
768         if( 0 == @{$target_l} ) {
769             die "no targets specified";
770         }
771         foreach my $target (@$target_l) {
772             if( 0 == length $target) {
773                 die "target has length 0";
774             }
775             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
776                     $target =~ /^GOSA$/i ||
777                     $target =~ /^\*$/ ||
778                     $target =~ /KNOWN_SERVER/i ||
779                     $target =~ /JOBDB/i ||
780                     $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 ){
781                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
782             }
783         }
784     };
785     if($@) {
786         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
787         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
788         $msg_hash = undef;
789     }
791     return ($msg, $msg_hash);
795 sub input_from_known_server {
796     my ($input, $remote_ip, $session_id) = @_ ;  
797     my ($msg, $msg_hash, $module);
799     my $sql_statement= "SELECT * FROM known_server";
800     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
802     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
803         my $host_name = $hit->{hostname};
804         if( not $host_name =~ "^$remote_ip") {
805             next;
806         }
807         my $host_key = $hit->{hostkey};
808         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 14);
809         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 14);
811         # check if module can open msg envelope with module key
812         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
813         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
814             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 14);
815             daemon_log("$@", 14);
816             next;
817         }
818         else {
819             $msg = $tmp_msg;
820             $msg_hash = $tmp_msg_hash;
821             $module = "ServerPackages";
822             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
823             last;
824         }
825     }
827     if( (!$msg) || (!$msg_hash) || (!$module) ) {
828         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 14);
829     }
830   
831     return ($msg, $msg_hash, $module);
835 sub input_from_known_client {
836     my ($input, $remote_ip, $session_id) = @_ ;  
837     my ($msg, $msg_hash, $module);
839     my $sql_statement= "SELECT * FROM known_clients";
840     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
841     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
842         my $host_name = $hit->{hostname};
843         if( not $host_name =~ /^$remote_ip/) {
844                 next;
845                 }
846         my $host_key = $hit->{hostkey};
847         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 14);
848         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 14);
850         # check if module can open msg envelope with module key
851         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
853         if( (!$msg) || (!$msg_hash) ) {
854             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 14);
855             next;
856         }
857         else {
858             $module = "ClientPackages";
859             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
860             last;
861         }
862     }
864     if( (!$msg) || (!$msg_hash) || (!$module) ) {
865         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 14);
866     }
868     return ($msg, $msg_hash, $module);
872 sub input_from_unknown_host {
873         no strict "refs";
874         my ($input, $session_id) = @_ ;
875         my ($msg, $msg_hash, $module);
876         my $error_string;
878         my %act_modules = %$known_modules;
880         while( my ($mod, $info) = each(%act_modules)) {
882                 # check a key exists for this module
883                 my $module_key = ${$mod."_key"};
884                 if( not defined $module_key ) {
885                         if( $mod eq 'ArpHandler' ) {
886                                 next;
887                         }
888                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
889                         next;
890                 }
891                 daemon_log("$session_id DEBUG: $mod: $module_key", 14);
893                 # check if module can open msg envelope with module key
894                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
895                 if( (not defined $msg) || (not defined $msg_hash) ) {
896                         next;
897                 } else {
898                         $module = $mod;
899             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 18);
900                         last;
901                 }
902         }
904         if( (!$msg) || (!$msg_hash) || (!$module)) {
905                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 14);
906         }
908         return ($msg, $msg_hash, $module);
912 sub create_ciphering {
913     my ($passwd) = @_;
914         if((!defined($passwd)) || length($passwd)==0) {
915                 $passwd = "";
916         }
917     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
918     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
919     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
920     $my_cipher->set_iv($iv);
921     return $my_cipher;
925 sub encrypt_msg {
926     my ($msg, $key) = @_;
927     my $my_cipher = &create_ciphering($key);
928     my $len;
929     {
930             use bytes;
931             $len= 16-length($msg)%16;
932     }
933     $msg = "\0"x($len).$msg;
934     $msg = $my_cipher->encrypt($msg);
935     chomp($msg = &encode_base64($msg));
936     # there are no newlines allowed inside msg
937     $msg=~ s/\n//g;
938     return $msg;
942 sub decrypt_msg {
944     my ($msg, $key) = @_ ;
945     $msg = &decode_base64($msg);
946     my $my_cipher = &create_ciphering($key);
947     $msg = $my_cipher->decrypt($msg); 
948     $msg =~ s/\0*//g;
949     return $msg;
953 sub get_encrypt_key {
954     my ($target) = @_ ;
955     my $encrypt_key;
956     my $error = 0;
958     # target can be in known_server
959     if( not defined $encrypt_key ) {
960         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
961         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
962         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
963             my $host_name = $hit->{hostname};
964             if( $host_name ne $target ) {
965                 next;
966             }
967             $encrypt_key = $hit->{hostkey};
968             last;
969         }
970     }
972     # target can be in known_client
973     if( not defined $encrypt_key ) {
974         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
975         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
976         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
977             my $host_name = $hit->{hostname};
978             if( $host_name ne $target ) {
979                 next;
980             }
981             $encrypt_key = $hit->{hostkey};
982             last;
983         }
984     }
986     return $encrypt_key;
990 #===  FUNCTION  ================================================================
991 #         NAME:  open_socket
992 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
993 #                [PeerPort] string necessary if port not appended by PeerAddr
994 #      RETURNS:  socket IO::Socket::INET
995 #  DESCRIPTION:  open a socket to PeerAddr
996 #===============================================================================
997 sub open_socket {
998     my ($PeerAddr, $PeerPort) = @_ ;
999     if(defined($PeerPort)){
1000         $PeerAddr = $PeerAddr.":".$PeerPort;
1001     }
1002     my $socket;
1003     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
1004             Porto => "tcp",
1005             Type => SOCK_STREAM,
1006             Timeout => 5,
1007             );
1008     if(not defined $socket) {
1009         return;
1010     }
1011 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
1012     return $socket;
1016 sub send_msg_to_target {
1017     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
1018     my $error = 0;
1019     my $header;
1020     my $timestamp = &get_time();
1021     my $new_status;
1022     my $act_status;
1023     my ($sql_statement, $res);
1024   
1025     if( $msg_header ) {
1026         $header = "'$msg_header'-";
1027     } else {
1028         $header = "";
1029     }
1031         # Memorize own source address
1032         my $own_source_address = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
1033         $own_source_address .= ":".$server_port;
1035         # Patch 0.0.0.0 source to real address
1036         $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$own_source_address<\/source>/s;
1037         # Patch GOSA source to real address and add forward_to_gosa tag
1038         $msg =~ s/<source>GOSA<\/source>/<source>$own_source_address<\/source> <forward_to_gosa>$own_source_address,$session_id<\/forward_to_gosa>/ ;
1040     # encrypt xml msg
1041     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
1043     # opensocket
1044     my $socket = &open_socket($address);
1045     if( !$socket ) {
1046         daemon_log("$session_id ERROR: Cannot open socket to host '$address'. Message processing aborted!", 1);
1047         $error++;
1048     }
1049     
1050     if( $error == 0 ) {
1051         # send xml msg
1052         print $socket $crypted_msg.";$own_source_address\n";
1053         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
1054         daemon_log("$session_id DEBUG: message:\n$msg", 12);
1055         
1056     }
1058     # close socket in any case
1059     if( $socket ) {
1060         close $socket;
1061     }
1063     if( $error > 0 ) { $new_status = "down"; }
1064     else { $new_status = $msg_header; }
1067     # known_clients
1068     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
1069     $res = $known_clients_db->select_dbentry($sql_statement);
1070     if( keys(%$res) == 1) {
1071         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1072         if ($act_status eq "down" && $new_status eq "down") {
1073             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
1074             $res = $known_clients_db->del_dbentry($sql_statement);
1075             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
1076         } else { 
1077             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1078             $res = $known_clients_db->update_dbentry($sql_statement);
1079             if($new_status eq "down"){
1080                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1081             } else {
1082                 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1083             }
1084         }
1085     }
1087     # known_server
1088     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
1089     $res = $known_server_db->select_dbentry($sql_statement);
1090     if( keys(%$res) == 1) {
1091         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1092         if ($act_status eq "down" && $new_status eq "down") {
1093             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
1094             $res = $known_server_db->del_dbentry($sql_statement);
1095             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
1096         } 
1097         else { 
1098             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1099             $res = $known_server_db->update_dbentry($sql_statement);
1100             if($new_status eq "down"){
1101                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1102             } else {
1103                 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1104             }
1105         }
1106     }
1107     return $error; 
1111 sub update_jobdb_status_for_send_msgs {
1112     my ($session_id, $answer, $error) = @_;
1113     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1114                 &daemon_log("$session_id DEBUG: try to update job status", 138); 
1115         my $jobdb_id = $1;
1116     
1117         $answer =~ /<header>(.*)<\/header>/;
1118         my $job_header = $1;
1120         $answer =~ /<target>(.*)<\/target>/;
1121         my $job_target = $1;
1122             
1123         # Sending msg failed
1124         if( $error ) {
1126             # Set jobs to done, jobs do not need to deliver their message in any case
1127             if (($job_header eq "trigger_action_localboot")
1128                     ||($job_header eq "trigger_action_lock")
1129                     ||($job_header eq "trigger_action_halt") 
1130                     ) {
1131                 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1132                 my $res = $job_db->update_dbentry($sql_statement);
1133                 
1134             # Reactivate jobs, jobs need to deliver their message
1135             } elsif (($job_header eq "trigger_action_activate")
1136                     ||($job_header eq "trigger_action_update")
1137                     ||($job_header eq "trigger_action_reinstall") 
1138                     ||($job_header eq "trigger_activate_new")
1139                     ) {
1140                                                 if ($job_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) {
1141                                                         &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1142                                                 } else {
1143                                                         # If we don't have the mac adress at this time, we use the plainname
1144                                                         my $plainname_result = $job_db->select_dbentry("SELECT plainname from jobs where id=$jobdb_id");
1145                                                         my $plainname = $job_target;
1146                                                         if ((keys(%$plainname_result) > 0) ) {
1147                                                                 $plainname = $plainname_result->{1}->{$job_target};
1148                                                         }
1149                                                         &reactivate_job_with_delay($session_id, $plainname, $job_header, 30 );
1150                                                 }
1151             # For all other messages
1152             } else {
1153                 my $sql_statement = "UPDATE $job_queue_tn ".
1154                     "SET status='error', result='can not deliver msg, please consult log file' ".
1155                     "WHERE id=$jobdb_id";
1156                 my $res = $job_db->update_dbentry($sql_statement);
1157             }
1159         # Sending msg was successful
1160         } else {
1161             # Set jobs localboot, lock, activate, halt, reboot and wake to done
1162             # jobs reinstall, update, inst_update do themself setting to done
1163             if (($job_header eq "trigger_action_localboot")
1164                     ||($job_header eq "trigger_action_lock")
1165                     ||($job_header eq "trigger_action_activate")
1166                     ||($job_header eq "trigger_action_halt") 
1167                     ||($job_header eq "trigger_action_reboot")
1168                     ||($job_header eq "trigger_action_wake")
1169                     ||($job_header eq "trigger_wake")
1170                     ) {
1172                 my $sql_statement = "UPDATE $job_queue_tn ".
1173                     "SET status='done' ".
1174                     "WHERE id=$jobdb_id AND status='processed'";
1175                 my $res = $job_db->update_dbentry($sql_statement);
1176             } else { 
1177                 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 138); 
1178             } 
1179         } 
1180     } else { 
1181         &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag.", 138); 
1182     }
1185 sub reactivate_job_with_delay {
1186     my ($session_id, $target, $header, $delay) = @_ ;
1187     # 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
1188     
1189     if (not defined $delay) { $delay = 30 } ;
1190     my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1192     my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress LIKE '$target' OR plainname LIKE '$target') AND headertag='$header'"; 
1193     my $res = $job_db->update_dbentry($sql);
1194     daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1195             "cause client '$target' is currently not available", 5);
1196     return;
1200 sub sig_handler {
1201         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1202         daemon_log("0 INFO got signal '$signal'", 1); 
1203         $kernel->sig_handled();
1204         return;
1208 sub msg_to_decrypt {
1209         my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1210         my $session_id = $session->ID;
1211         my ($msg, $msg_hash, $module);
1212         my $error = 0;
1214         # fetch new msg out of @msgs_to_decrypt
1215         my $tmp_next_msg = shift @msgs_to_decrypt;
1216     my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1218         # msg is from a new client or gosa
1219         ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1221         # msg is from a gosa-si-server
1222         if(((!$msg) || (!$msg_hash) || (!$module)) && ($serverPackages_enabled eq "true")){
1223                 if (not defined $msg_source) 
1224                 {
1225                         # Only needed, to be compatible with older gosa-si-server versions
1226                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1227                 }
1228                 else
1229                 {
1230                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $msg_source, $session_id);
1231                 }
1232         }
1233         # msg is from a gosa-si-client
1234         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1235                 if (not defined $msg_source) 
1236                 {
1237                         # Only needed, to be compatible with older gosa-si-server versions
1238                         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1239                 }
1240                 else
1241                 {
1242                         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $msg_source, $session_id);
1243                 }
1244         }
1245         # an error occurred
1246         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1247                 # If an incoming msg could not be decrypted (maybe a wrong key), decide if msg comes from a client 
1248                 # or a server.  In case of a client, send a ping. If the client could not understand a msg from its 
1249                 # server the client cause a re-registering process. In case of a server, decrease update_time in kown_server_db
1250                 # and trigger a re-registering process for servers
1251                 if (defined $msg_source && $msg_source =~ /:$server_port$/ && $serverPackages_enabled eq "true")
1252                 {
1253                         daemon_log("$session_id WARNING: Cannot understand incoming msg from server '$msg_source'. Cause re-registration process for servers.", 3);
1254                         my $update_statement = "UPDATE $known_server_tn SET update_time='19700101000000' WHERE hostname='$msg_source'"; 
1255                         daemon_log("$session_id DEBUG: $update_statement", 7);
1256                         my $upadte_res = $known_server_db->exec_statement($update_statement);
1257                         $kernel->yield("register_at_foreign_servers");
1258                 }
1259                 elsif ((defined $msg_source) && (not $msg_source =~ /:$server_port$/))
1260                 {
1261                         daemon_log("$session_id WARNING: Cannot understand incoming msg from client '$msg_source'. Send ping-msg to cause a re-registering of the client if necessary", 3);
1262                         #my $remote_ip = $heap->{'remote_ip'};
1263                         #my $remote_port = $heap->{'remote_port'};
1264                         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1265                         my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1266                         daemon_log("$session_id WARNING: Sending msg to cause re-registering: $ping_msg", 3);
1267                 }
1268                 else
1269                 {
1270                         my $foreign_host = defined $msg_source ? $msg_source : $heap->{'remote_ip'};
1271                         daemon_log("$session_id ERROR: Incoming message from host '$foreign_host' cannot be understood. Processing aborted!", 1);
1272                         daemon_log("$session_id DEBUG: Aborted message: $tmp_next_msg", 11);
1273                 }
1275                 $error++
1276         }
1279         my $header;
1280         my $target;
1281         my $source;
1282         my $done = 0;
1283         my $sql;
1284         my $res;
1286         # check whether this message should be processed here
1287         if ($error == 0) {
1288                 $header = @{$msg_hash->{'header'}}[0];
1289                 $target = @{$msg_hash->{'target'}}[0];
1290                 $source = @{$msg_hash->{'source'}}[0];
1291                 my $not_found_in_known_clients_db = 0;
1292                 my $not_found_in_known_server_db = 0;
1293                 my $not_found_in_foreign_clients_db = 0;
1294                 my $local_address;
1295                 my $local_mac;
1296                 my ($target_ip, $target_port) = split(':', $target);
1298                 # Determine the local ip address if target is an ip address
1299                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1300                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1301                 } else {
1302                         $local_address = $server_address;
1303                 }
1305                 # Determine the local mac address if target is a mac address
1306                 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) {
1307                         my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1308                         my $network_interface= &get_interface_for_ip($loc_ip);
1309                         $local_mac = &get_mac_for_interface($network_interface);
1310                 } else {
1311                         $local_mac = $server_mac_address;
1312                 }
1314                 # target and source is equal to GOSA -> process here
1315                 if (not $done) {
1316                         if ($target eq "GOSA" && $source eq "GOSA") {
1317                                 $done = 1;                    
1318                                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process '$header' here", 11);
1319                         }
1320                 }
1322                 # target is own address without forward_to_gosa-tag -> process here
1323                 if (not $done) {
1324                         #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1325                         if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1326                                 $done = 1;
1327                                 if ($source eq "GOSA") {
1328                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1329                                 }
1330                                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process '$header' here", 11);
1331                         }
1332                 }
1334                 # target is own address with forward_to_gosa-tag not pointing to myself -> process here
1335                 if (not $done) {
1336                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1337                         my $gosa_at;
1338                         my $gosa_session_id;
1339                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1340                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1341                                 if ($gosa_at ne $local_address) {
1342                                         $done = 1;
1343                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process '$header' here", 11); 
1344                                 }
1345                         }
1346                 }
1348                 # Target is a client address and there is a processing function within a plugin -> process loaclly
1349                 if (not $done)
1350                 {
1351                         # Check if target is a client address
1352                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1353                         $res = $known_clients_db->select_dbentry($sql);
1354                         if ((keys(%$res) > 0) ) 
1355                         {
1356                                 my $hostname = $res->{1}->{'hostname'};
1357                                 my $reduced_header = $header;
1358                                 $reduced_header =~ s/gosa_//;
1359                                 # Check if there is a processing function within a plugin
1360                                 if (exists $known_functions->{$reduced_header}) 
1361                                 {
1362                                         $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1363                                         $done = 1;
1364                                         &daemon_log("$session_id DEBUG: Target is client address with processing function within a plugin -> process '$header' here", 11);
1365                                 }
1366                         }
1367                 }
1369                 # If header has a 'job_' prefix, do always process message locally
1370                 # which means put it into job queue
1371                 if ((not $done) && ($header =~ /job_/))
1372                 {
1373                         $done = 1;
1374                         &daemon_log("$session_id DEBUG: Header has a 'job_' prefix. Put it into job queue. -> process '$header' here", 11);
1375                 }
1377                 # if message should be processed here -> add message to incoming_db
1378                 if ($done) {
1379                         # if a 'job_' or a 'gosa_' message comes from a foreign server, fake module from
1380                         # ServerPackages to GosaPackages so gosa-si-server knows how to process this kind of messages
1381                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1382                                 $module = "GosaPackages";
1383                         }
1385                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1386                                         primkey=>[],
1387                                         headertag=>$header,
1388                                         targettag=>$target,
1389                                         xmlmessage=>&encode_base64($msg),
1390                                         timestamp=>&get_time,
1391                                         module=>$module,
1392                                         sessionid=>$session_id,
1393                                 } );
1394                         $kernel->yield('watch_for_next_tasks');
1395                 }
1397                 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1398                 if (not $done) {
1399                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1400                         my $gosa_at;
1401                         my $gosa_session_id;
1402                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1403                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1404                                 if ($gosa_at eq $local_address) {
1405                                         my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1406                                         if( defined $session_reference ) {
1407                                                 $heap = $session_reference->get_heap();
1408                                         }
1409                                         if(exists $heap->{'client'}) {
1410                                                 $msg = &encrypt_msg($msg, $GosaPackages_key);
1411                                                 $heap->{'client'}->put($msg);
1412                                                 &daemon_log("$session_id DEBUG: incoming '$header' message forwarded to GOsa", 11); 
1413                                         }
1414                                         $done = 1;
1415                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward '$header' to gosa", 11);
1416                                 }
1417                         }
1419                 }
1421                 # target is a client address in known_clients -> forward to client
1422                 if (not $done) {
1423                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1424                         $res = $known_clients_db->select_dbentry($sql);
1425                         if (keys(%$res) > 0) 
1426                         {
1427                                 $done = 1; 
1428                                 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> forward '$header' to client", 11);
1429                                 my $hostkey = $res->{1}->{'hostkey'};
1430                                 my $hostname = $res->{1}->{'hostname'};
1431                                 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1432                                 $msg =~ s/<header>gosa_/<header>/;
1433                                 my $error= &send_msg_to_target($msg, $hostname, $hostkey, $header, $session_id);
1434                                 if ($error) {
1435                                         &daemon_log("$session_id ERROR: Some problems occurred while trying to send msg to client '$hostname': $msg", 1);
1436                                 }
1437                         } 
1438                         else 
1439                         {
1440                                 $not_found_in_known_clients_db = 1;
1441                         }
1442                 }
1444                 # target is a client address in foreign_clients -> forward to registration server
1445                 if (not $done) {
1446                         $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1447                         $res = $foreign_clients_db->select_dbentry($sql);
1448                         if (keys(%$res) > 0) {
1449                                 my $hostname = $res->{1}->{'hostname'};
1450                                 my ($host_ip, $host_port) = split(/:/, $hostname);
1451                                 my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1452                                 my $regserver = $res->{1}->{'regserver'};
1453                                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1454                                 my $res = $known_server_db->select_dbentry($sql);
1455                                 if (keys(%$res) > 0) {
1456                                         my $regserver_key = $res->{1}->{'hostkey'};
1457                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1458                                         $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1459                                         if ($source eq "GOSA") {
1460                                                 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1461                                         }
1462                                         my $error= &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1463                                         if ($error) {
1464                                                 &daemon_log("$session_id ERROR: some problems occurred while trying to send msg to registration server: $msg", 1); 
1465                                         }
1466                                 }
1467                                 $done = 1;
1468                                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward '$header' to registration server", 11);
1469                         } else {
1470                                 $not_found_in_foreign_clients_db = 1;
1471                         }
1472                 }
1474                 # target is a server address -> forward to server
1475                 if (not $done) {
1476                         $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1477                         $res = $known_server_db->select_dbentry($sql);
1478                         if (keys(%$res) > 0) {
1479                                 my $hostkey = $res->{1}->{'hostkey'};
1481                                 if ($source eq "GOSA") {
1482                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1483                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1485                                 }
1487                                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1488                                 $done = 1;
1489                                 &daemon_log("$session_id DEBUG: target is a server address -> forward '$header' to server", 11);
1490                         } else {
1491                                 $not_found_in_known_server_db = 1;
1492                         }
1493                 }
1496                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1497                 if ( $not_found_in_foreign_clients_db 
1498                         && $not_found_in_known_server_db
1499                         && $not_found_in_known_clients_db) {
1500                         &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 '$header' here", 11);
1501             if ($header =~ /^gosa_/ || $header =~ /^job_/) { 
1502                 $module = "GosaPackages"; 
1503             }
1504                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1505                                         primkey=>[],
1506                                         headertag=>$header,
1507                                         targettag=>$target,
1508                                         xmlmessage=>&encode_base64($msg),
1509                                         timestamp=>&get_time,
1510                                         module=>$module,
1511                                         sessionid=>$session_id,
1512                                 } );
1513                         $done = 1;
1514                         $kernel->yield('watch_for_next_tasks');
1515                 }
1518                 if (not $done) {
1519                         daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1520                         if ($source eq "GOSA") {
1521                                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1522                                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1524                                 my $session_reference = $kernel->ID_id_to_session($session_id);
1525                                 if( defined $session_reference ) {
1526                                         $heap = $session_reference->get_heap();
1527                                 }
1528                                 if(exists $heap->{'client'}) {
1529                                         $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1530                                         $heap->{'client'}->put($error_msg);
1531                                 }
1532                         }
1533                 }
1535         }
1537         return;
1541 sub next_task {
1542     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0, ARG1];
1543     my $running_task = POE::Wheel::Run->new(
1544             Program => sub { process_task($session, $heap, $task) },
1545             StdioFilter => POE::Filter::Reference->new(),
1546             StdoutEvent  => "task_result",
1547             StderrEvent  => "task_debug",
1548             CloseEvent   => "task_done",
1549             );
1550     $heap->{task}->{ $running_task->ID } = $running_task;
1553 sub handle_task_result {
1554     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1555     my $client_answer = $result->{'answer'};
1556     if( $client_answer =~ s/session_id=(\d+)$// ) {
1557         my $session_id = $1;
1558         if( defined $session_id ) {
1559             my $session_reference = $kernel->ID_id_to_session($session_id);
1560             if( defined $session_reference ) {
1561                 $heap = $session_reference->get_heap();
1562             }
1563         }
1565         if(exists $heap->{'client'}) {
1566             $heap->{'client'}->put($client_answer);
1567         }
1568     }
1569     $kernel->sig(CHLD => "child_reap");
1572 sub handle_task_debug {
1573     my $result = $_[ARG0];
1574     print STDERR "$result\n";
1577 sub handle_task_done {
1578     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1579     delete $heap->{task}->{$task_id};
1580         if (exists $heap->{ldap_handle}->{$task_id}) {
1581                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
1582         }
1585 sub process_task {
1586     no strict "refs";
1587     #CHECK: Not @_[...]?
1588     my ($session, $heap, $task) = @_;
1589     my $error = 0;
1590     my $answer_l;
1591     my ($answer_header, @answer_target_l, $answer_source);
1592     my $client_answer = "";
1594     # prepare all variables needed to process message
1595     #my $msg = $task->{'xmlmessage'};
1596     my $msg = &decode_base64($task->{'xmlmessage'});
1597     my $incoming_id = $task->{'id'};
1598     my $module = $task->{'module'};
1599     my $header =  $task->{'headertag'};
1600     my $session_id = $task->{'sessionid'};
1601                 my $msg_hash;
1602                 eval {
1603         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1604                 }; 
1605                 daemon_log("ERROR: XML failure '$@'") if ($@);
1606     my $source = @{$msg_hash->{'source'}}[0];
1607     
1608     # set timestamp of incoming client uptodate, so client will not 
1609     # be deleted from known_clients because of expiration
1610     my $cur_time = &get_time();
1611     my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'"; 
1612     my $res = $known_clients_db->exec_statement($sql);
1614     ######################
1615     # process incoming msg
1616     if( $error == 0) {
1617         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1618         daemon_log("$session_id DEBUG: Processing module ".$module, 26);
1619         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1621         if ( 0 < @{$answer_l} ) {
1622             my $answer_str = join("\n", @{$answer_l});
1623                         my @headers; 
1624             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1625                                 push(@headers, $1);
1626             }
1627                         daemon_log("$session_id DEBUG: got answer message(s) with header: '".join("', '", @headers)."'", 26);
1628         } else {
1629             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,26);
1630         }
1632     }
1633     if( !$answer_l ) { $error++ };
1635     ########
1636     # answer
1637     if( $error == 0 ) {
1639         foreach my $answer ( @{$answer_l} ) {
1640             # check outgoing msg to xml validity
1641             my ($answer, $answer_hash) = &check_outgoing_xml_validity($answer, $session_id);
1642             if( not defined $answer_hash ) { next; }
1643             
1644             $answer_header = @{$answer_hash->{'header'}}[0];
1645             @answer_target_l = @{$answer_hash->{'target'}};
1646             $answer_source = @{$answer_hash->{'source'}}[0];
1648             # deliver msg to all targets 
1649             foreach my $answer_target ( @answer_target_l ) {
1651                 # targets of msg are all gosa-si-clients in known_clients_db
1652                 if( $answer_target eq "*" ) {
1653                     # answer is for all clients
1654                     my $sql_statement= "SELECT * FROM known_clients";
1655                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1656                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1657                         my $host_name = $hit->{hostname};
1658                         my $host_key = $hit->{hostkey};
1659                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1660                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1661                     }
1662                 }
1664                 # targets of msg are all gosa-si-server in known_server_db
1665                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1666                     # answer is for all server in known_server
1667                     my $sql_statement= "SELECT * FROM $known_server_tn";
1668                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1669                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1670                         my $host_name = $hit->{hostname};
1671                         my $host_key = $hit->{hostkey};
1672                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1673                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1674                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1675                     }
1676                 }
1678                 # target of msg is GOsa
1679                                 elsif( $answer_target eq "GOSA" ) {
1680                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1681                                         my $add_on = "";
1682                     if( defined $session_id ) {
1683                         $add_on = ".session_id=$session_id";
1684                     }
1685                                         my $header = ($1) if $answer =~ /<header>(\S*)<\/header>/;
1686                                         daemon_log("$session_id INFO: send ".$header." message to GOsa", 5);
1687                                         daemon_log("$session_id DEBUG: message:\n$answer", 12);
1688                     # answer is for GOSA and has to returned to connected client
1689                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1690                     $client_answer = $gosa_answer.$add_on;
1691                 }
1693                 # target of msg is job queue at this host
1694                 elsif( $answer_target eq "JOBDB") {
1695                     $answer =~ /<header>(\S+)<\/header>/;   
1696                     my $header;
1697                     if( defined $1 ) { $header = $1; }
1698                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1699                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1700                 }
1702                 # Target of msg is a mac address
1703                 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 ) {
1704                     daemon_log("$session_id DEBUG: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 138);
1706                     # Looking for macaddress in known_clients
1707                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1708                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1709                     my $found_ip_flag = 0;
1710                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1711                         my $host_name = $hit->{hostname};
1712                         my $host_key = $hit->{hostkey};
1713                         $answer =~ s/$answer_target/$host_name/g;
1714                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1715                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1716                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1717                         $found_ip_flag++ ;
1718                     }   
1720                     # Looking for macaddress in foreign_clients
1721                     if ($found_ip_flag == 0) {
1722                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1723                         my $res = $foreign_clients_db->select_dbentry($sql);
1724                         while( my ($hit_num, $hit) = each %{ $res } ) {
1725                             my $host_name = $hit->{hostname};
1726                             my $reg_server = $hit->{regserver};
1727                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1728                             
1729                             # Fetch key for reg_server
1730                             my $reg_server_key;
1731                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1732                             my $res = $known_server_db->select_dbentry($sql);
1733                             if (exists $res->{1}) {
1734                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1735                             } else {
1736                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1737                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1738                                 $reg_server_key = undef;
1739                             }
1741                             # Send answer to server where client is registered
1742                             if (defined $reg_server_key) {
1743                                 $answer =~ s/$answer_target/$host_name/g;
1744                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1745                                 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1746                                 $found_ip_flag++ ;
1747                             }
1748                         }
1749                     }
1751                     # No mac to ip matching found
1752                     if( $found_ip_flag == 0) {
1753                         daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1754                         &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1755                     }
1757                 # Answer is for one specific host   
1758                 } else {
1759                     # get encrypt_key
1760                     my $encrypt_key = &get_encrypt_key($answer_target);
1761                     if( not defined $encrypt_key ) {
1762                         # unknown target
1763                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1764                         next;
1765                     }
1766                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1767                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1768                 }
1769             }
1770         }
1771     }
1773     my $filter = POE::Filter::Reference->new();
1774     my %result = ( 
1775             status => "seems ok to me",
1776             answer => $client_answer,
1777             );
1779     my $output = $filter->put( [ \%result ] );
1780     print @$output;
1785 sub session_start {
1786         my ($kernel) = $_[KERNEL];
1787         $global_kernel = $kernel;
1788         $kernel->yield('register_at_foreign_servers');
1789         $kernel->yield('create_fai_server_db', $fai_server_tn );
1790         $kernel->yield('create_fai_release_db', $fai_release_tn );
1791         $kernel->sig(USR1 => "sig_handler");
1792         $kernel->sig(USR2 => "recreate_packages_db");
1793         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1794         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1795         $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1796         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1797         $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1798         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1799         $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1801         # Start opsi check
1802         if ($opsi_enabled eq "true") {
1803                 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1804         }
1809 sub watch_for_done_jobs {
1810         my $kernel = $_[KERNEL];
1812         my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1813         my $res = $job_db->select_dbentry( $sql_statement );
1815         while( my ($number, $hit) = each %{$res} ) 
1816         {
1817                 # Non periodical jobs can be deleted.
1818                 if ($hit->{periodic} eq "none")
1819                 {
1820                         my $jobdb_id = $hit->{id};
1821                         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1822                         my $res = $job_db->del_dbentry($sql_statement); 
1823                 }
1825                 # Periodical jobs should not be deleted but reactivated with new timestamp instead.
1826                 else
1827                 {
1828                         my ($p_time, $periodic) = split("_", $hit->{periodic});
1829                         my $reactivated_ts = $hit->{timestamp};
1830                         my $act_ts = int(&get_time());
1831                         while ($act_ts > int($reactivated_ts))   # Redo calculation to avoid multiple jobs in the past
1832                         {
1833                                 $reactivated_ts = &calc_timestamp($reactivated_ts, "plus", $p_time, $periodic);
1834                         }
1835                         my $sql = "UPDATE $job_queue_tn SET status='waiting', timestamp='$reactivated_ts' WHERE id='".$hit->{id}."'"; 
1836                         my $res = $job_db->exec_statement($sql);
1837                         &daemon_log("J INFO: Update periodical job '".$hit->{headertag}."' for client '".$hit->{targettag}."'. New execution time '$reactivated_ts'.", 5);
1838                 }
1839         }
1841         $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1845 sub watch_for_opsi_jobs {
1846     my ($kernel) = $_[KERNEL];
1847     # 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 
1848     # opsi install job is to parse the xml message. There is still the correct header.
1849     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1850         my $res = $job_db->select_dbentry( $sql_statement );
1852     # Ask OPSI for an update of the running jobs
1853     while (my ($id, $hit) = each %$res ) {
1854         # Determine current parameters of the job
1855         my $hostId = $hit->{'plainname'};
1856         my $macaddress = $hit->{'macaddress'};
1857         my $progress = $hit->{'progress'};
1859         my $result= {};
1860         
1861         # For hosts, only return the products that are or get installed
1862         my $callobj;
1863         $callobj = {
1864             method  => 'getProductStates_hash',
1865             params  => [ $hostId ],
1866             id  => 1,
1867         };
1868         
1869         my $hres = $opsi_client->call($opsi_url, $callobj);
1870                 # TODO : move all opsi relevant statments to opsi plugin
1871                 # The following 3 lines must be tested befor they can replace the rubbish above and below !!!
1872                 #my ($res, $err) = &opsi_com::_getProductStates_hash(hostId=>$hostId)
1873                 #if (not $err) {
1874                 #       my $htmp = $res->{$hostId};
1875                 # 
1876         if (not &check_opsi_res($hres)) {
1877             my $htmp= $hres->result->{$hostId};
1878             # Check state != not_installed or action == setup -> load and add
1879             my $products= 0;
1880             my $installed= 0;
1881             my $installing = 0;
1882             my $error= 0;  
1883             my @installed_list;
1884             my @error_list;
1885             my $act_status = "none";
1886             foreach my $product (@{$htmp}){
1888                 if ($product->{'installationStatus'} ne "not_installed" or
1889                         $product->{'actionRequest'} eq "setup"){
1891                     # Increase number of products for this host
1892                     $products++;
1893         
1894                     if ($product->{'installationStatus'} eq "failed"){
1895                         $result->{$product->{'productId'}}= "error";
1896                         unshift(@error_list, $product->{'productId'});
1897                         $error++;
1898                     }
1899                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1900                         $result->{$product->{'productId'}}= "installed";
1901                         unshift(@installed_list, $product->{'productId'});
1902                         $installed++;
1903                     }
1904                     if ($product->{'installationStatus'} eq "installing"){
1905                         $result->{$product->{'productId'}}= "installing";
1906                         $installing++;
1907                         $act_status = "installing - ".$product->{'productId'};
1908                     }
1909                 }
1910             }
1911         
1912             # Estimate "rough" progress, avoid division by zero
1913             if ($products == 0) {
1914                 $result->{'progress'}= 0;
1915             } else {
1916                 $result->{'progress'}= int($installed * 100 / $products);
1917             }
1919             # Set updates in job queue
1920             if ((not $error) && (not $installing) && ($installed)) {
1921                 $act_status = "installed - ".join(", ", @installed_list);
1922             }
1923             if ($error) {
1924                 $act_status = "error - ".join(", ", @error_list);
1925             }
1926             if ($progress ne $result->{'progress'} ) {
1927                 # Updating progress and result 
1928                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1929                 my $update_res = $job_db->update_dbentry($update_statement);
1930             }
1931             if ($progress eq 100) { 
1932                 # Updateing status
1933                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1934                 if ($error) {
1935                     $done_statement .= "status='error'";
1936                 } else {
1937                     $done_statement .= "status='done'";
1938                 }
1939                 $done_statement .= " WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1940                 my $done_res = $job_db->update_dbentry($done_statement);
1941             }
1944         }
1945     }
1947     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1951 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1952 sub watch_for_modified_jobs {
1953     my ($kernel,$heap) = @_[KERNEL, HEAP];
1955     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')"; 
1956     my $res = $job_db->select_dbentry( $sql_statement );
1957     
1958     # if db contains no jobs which should be update, do nothing
1959     if (keys %$res != 0) {
1961         if ($job_synchronization  eq "true") {
1962             # make out of the db result a gosa-si message   
1963             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1964  
1965             # update all other SI-server
1966             &inform_all_other_si_server($update_msg);
1967         }
1969         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1970         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1971         $res = $job_db->update_dbentry($sql_statement);
1972     }
1974     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1978 sub watch_for_new_jobs {
1979         if($watch_for_new_jobs_in_progress == 0) {
1980                 $watch_for_new_jobs_in_progress = 1;
1981                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1983                 # check gosa job queue for jobs with executable timestamp
1984                 my $timestamp = &get_time();
1985                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE siserver='localhost' AND status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1986                 my $res = $job_db->exec_statement( $sql_statement );
1988                 # Merge all new jobs that would do the same actions
1989                 my @drops;
1990                 my $hits;
1991                 foreach my $hit (reverse @{$res} ) {
1992                         my $macaddress= lc @{$hit}[8];
1993                         my $headertag= @{$hit}[5];
1994                         if(
1995                                 defined($hits->{$macaddress}) &&
1996                                 defined($hits->{$macaddress}->{$headertag}) &&
1997                                 defined($hits->{$macaddress}->{$headertag}[0])
1998                         ) {
1999                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
2000                         }
2001                         $hits->{$macaddress}->{$headertag}= $hit;
2002                 }
2004                 # Delete new jobs with a matching job in state 'processing'
2005                 foreach my $macaddress (keys %{$hits}) {
2006                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
2007                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
2008                                 if(defined($jobdb_id)) {
2009                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
2010                                         my $res = $job_db->exec_statement( $sql_statement );
2011                                         foreach my $hit (@{$res}) {
2012                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
2013                                         }
2014                                 } else {
2015                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
2016                                 }
2017                         }
2018                 }
2020                 # Commit deletion
2021                 $job_db->exec_statementlist(\@drops);
2023                 # Look for new jobs that could be executed
2024                 foreach my $macaddress (keys %{$hits}) {
2026                         # Look if there is an executing job
2027                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
2028                         my $res = $job_db->exec_statement( $sql_statement );
2030                         # Skip new jobs for host if there is a processing job
2031                         if(defined($res) and defined @{$res}[0]) {
2032                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
2033                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
2034                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
2035                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
2036                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
2037                                         if(defined($res_2) and defined @{$res_2}[0]) {
2038                                                 # Set status from goto-activation to 'waiting' and update timestamp
2039                                                 $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'");
2040                                         }
2041                                 }
2042                                 next;
2043                         }
2045                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
2046                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
2047                                 if(defined($jobdb_id)) {
2048                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
2050                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
2051                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
2052                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
2054                                         # expect macaddress is unique!!!!!!
2055                                         my $target = $res_hash->{1}->{hostname};
2057                                         # change header
2058                                         $job_msg =~ s/<header>job_/<header>gosa_/;
2060                                         # add sqlite_id
2061                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
2063                                         $job_msg =~ /<header>(\S+)<\/header>/;
2064                                         my $header = $1 ;
2065                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");                    
2067                                         # update status in job queue to ...
2068                     # ... 'processing', for jobs: 'reinstall', 'update', activate_new
2069                     if (($header =~ /gosa_trigger_action_reinstall/) 
2070                             || ($header =~ /gosa_trigger_activate_new/)
2071                             || ($header =~ /gosa_trigger_action_update/)) {
2072                         my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
2073                         my $dbres = $job_db->update_dbentry($sql_statement);
2074                     }
2076                     # ... 'done', for all other jobs, they are no longer needed in the jobqueue
2077                     else {
2078                         my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
2079                         my $dbres = $job_db->exec_statement($sql_statement);
2080                     }
2081                 
2083                                         # We don't want parallel processing
2084                                         last;
2085                                 }
2086                         }
2087                 }
2089                 $watch_for_new_jobs_in_progress = 0;
2090                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
2091         }
2095 sub watch_for_new_messages {
2096     my ($kernel,$heap) = @_[KERNEL, HEAP];
2097     my @coll_user_msg;   # collection list of outgoing messages
2098     
2099     # check messaging_db for new incoming messages with executable timestamp
2100     my $timestamp = &get_time();
2101     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
2102     my $res = $messaging_db->exec_statement( $sql_statement );
2103         foreach my $hit (@{$res}) {
2105         # create outgoing messages
2106         my $message_to = @{$hit}[3];
2107         # translate message_to to plain login name
2108         my @message_to_l = split(/,/, $message_to);  
2109                 my %receiver_h; 
2110                 foreach my $receiver (@message_to_l) {
2111                         if ($receiver =~ /^u_([\s\S]*)$/) {
2112                                 $receiver_h{$1} = 0;
2113                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
2114                                 my $group_name = $1;
2115                                 # fetch all group members from ldap and add them to receiver hash
2116                                 my $ldap_handle = &get_ldap_handle();
2117                                 if (defined $ldap_handle) {
2118                                                 my $mesg = $ldap_handle->search(
2119                                                                                 base => $ldap_base,
2120                                                                                 scope => 'sub',
2121                                                                                 attrs => ['memberUid'],
2122                                                                                 filter => "cn=$group_name",
2123                                                                                 );
2124                                                 if ($mesg->count) {
2125                                                                 my @entries = $mesg->entries;
2126                                                                 foreach my $entry (@entries) {
2127                                                                                 my @receivers= $entry->get_value("memberUid");
2128                                                                                 foreach my $receiver (@receivers) { 
2129                                                                                                 $receiver_h{$receiver} = 0;
2130                                                                                 }
2131                                                                 }
2132                                                 } 
2133                                                 # translating errors ?
2134                                                 if ($mesg->code) {
2135                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
2136                                                 }
2137                                                 &release_ldap_handle($ldap_handle);
2138                                 # ldap handle error ?           
2139                                 } else {
2140                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
2141                                 }
2142                         } else {
2143                                 my $sbjct = &encode_base64(@{$hit}[1]);
2144                                 my $msg = &encode_base64(@{$hit}[7]);
2145                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
2146                         }
2147                 }
2148                 my @receiver_l = keys(%receiver_h);
2150         my $message_id = @{$hit}[0];
2152         #add each outgoing msg to messaging_db
2153         my $receiver;
2154         foreach $receiver (@receiver_l) {
2155             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
2156                 "VALUES ('".
2157                 $message_id."', '".    # id
2158                 @{$hit}[1]."', '".     # subject
2159                 @{$hit}[2]."', '".     # message_from
2160                 $receiver."', '".      # message_to
2161                 "none"."', '".         # flag
2162                 "out"."', '".          # direction
2163                 @{$hit}[6]."', '".     # delivery_time
2164                 @{$hit}[7]."', '".     # message
2165                 $timestamp."'".     # timestamp
2166                 ")";
2167             &daemon_log("M DEBUG: $sql_statement", 1);
2168             my $res = $messaging_db->exec_statement($sql_statement);
2169             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
2170         }
2172         # set incoming message to flag d=deliverd
2173         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
2174         &daemon_log("M DEBUG: $sql_statement", 7);
2175         $res = $messaging_db->update_dbentry($sql_statement);
2176         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
2177     }
2179     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
2180     return;
2183 sub watch_for_delivery_messages {
2184     my ($kernel, $heap) = @_[KERNEL, HEAP];
2186     # select outgoing messages
2187     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
2188     my $res = $messaging_db->exec_statement( $sql_statement );
2189     
2190     # build out msg for each    usr
2191     foreach my $hit (@{$res}) {
2192         my $receiver = @{$hit}[3];
2193         my $msg_id = @{$hit}[0];
2194         my $subject = @{$hit}[1];
2195         my $message = @{$hit}[7];
2197         # resolve usr -> host where usr is logged in
2198         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
2199         my $res = $login_users_db->exec_statement($sql);
2201         # receiver is logged in nowhere
2202         if (not ref(@$res[0]) eq "ARRAY") { next; }    
2204         # receiver ist logged in at a client registered at local server
2205                 my $send_succeed = 0;
2206                 foreach my $hit (@$res) {
2207                                 my $receiver_host = @$hit[0];
2208                 my $delivered2host = 0;
2209                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
2211                                 # Looking for host in know_clients_db 
2212                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
2213                                 my $res = $known_clients_db->exec_statement($sql);
2215                 # Host is known in known_clients_db
2216                 if (ref(@$res[0]) eq "ARRAY") {
2217                     my $receiver_key = @{@{$res}[0]}[2];
2218                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2219                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2220                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
2221                     if ($error == 0 ) {
2222                         $send_succeed++ ;
2223                         $delivered2host++ ;
2224                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
2225                     } else {
2226                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
2227                     }
2228                 }
2229                 
2230                 # Message already send, do not need to do anything more, otherwise ...
2231                 if ($delivered2host) { next;}
2232     
2233                 # ...looking for host in foreign_clients_db
2234                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2235                 $res = $foreign_clients_db->exec_statement($sql);
2236   
2237                                 # Host is known in foreign_clients_db 
2238                                 if (ref(@$res[0]) eq "ARRAY") { 
2239                     my $registration_server = @{@{$res}[0]}[2];
2240                     
2241                     # Fetch encryption key for registration server
2242                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2243                     my $res = $known_server_db->exec_statement($sql);
2244                     if (ref(@$res[0]) eq "ARRAY") { 
2245                         my $registration_server_key = @{@{$res}[0]}[3];
2246                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2247                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2248                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
2249                         if ($error == 0 ) {
2250                             $send_succeed++ ;
2251                             $delivered2host++ ;
2252                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
2253                         } else {
2254                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
2255                         }
2257                     } else {
2258                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2259                                 "registrated at server '$registration_server', ".
2260                                 "but no data available in known_server_db ", 1); 
2261                     }
2262                 }
2263                 
2264                 if (not $delivered2host) {
2265                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2266                 }
2267                 }
2269                 if ($send_succeed) {
2270                                 # set outgoing msg at db to deliverd
2271                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
2272                                 my $res = $messaging_db->exec_statement($sql); 
2273                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2274                 } else {
2275             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
2276         }
2277         }
2279     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
2280     return;
2284 sub watch_for_done_messages {
2285     my ($kernel,$heap) = @_[KERNEL, HEAP];
2287     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
2288     my $res = $messaging_db->exec_statement($sql); 
2290     foreach my $hit (@{$res}) {
2291         my $msg_id = @{$hit}[0];
2293         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
2294         my $res = $messaging_db->exec_statement($sql);
2296         # not all usr msgs have been seen till now
2297         if ( ref(@$res[0]) eq "ARRAY") { next; }
2298         
2299         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
2300         $res = $messaging_db->exec_statement($sql);
2301     
2302     }
2304     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2305     return;
2309 sub watch_for_old_known_clients {
2310     my ($kernel,$heap) = @_[KERNEL, HEAP];
2312     my $sql_statement = "SELECT * FROM $known_clients_tn";
2313     my $res = $known_clients_db->select_dbentry( $sql_statement );
2315     my $cur_time = int(&get_time());
2317     while ( my ($hit_num, $hit) = each %$res) {
2318         my $expired_timestamp = int($hit->{'timestamp'});
2319         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2320         my $dt = DateTime->new( year   => $1,
2321                 month  => $2,
2322                 day    => $3,
2323                 hour   => $4,
2324                 minute => $5,
2325                 second => $6,
2326                 );
2328         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2329         $expired_timestamp = $dt->ymd('').$dt->hms('');
2330         if ($cur_time > $expired_timestamp) {
2331             my $hostname = $hit->{'hostname'};
2332             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2333             my $del_res = $known_clients_db->exec_statement($del_sql);
2335             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2336         }
2338     }
2340     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2344 sub watch_for_next_tasks {
2345     my ($kernel,$heap) = @_[KERNEL, HEAP];
2347     my $sql = "SELECT * FROM $incoming_tn";
2348     my $res = $incoming_db->select_dbentry($sql);
2349     
2350     while ( my ($hit_num, $hit) = each %$res) {
2351         my $headertag = $hit->{'headertag'};
2352         if ($headertag =~ /^answer_(\d+)/) {
2353             # do not start processing, this message is for a still running POE::Wheel
2354             next;
2355         }
2356         my $message_id = $hit->{'id'};
2357         my $session_id = $hit->{'sessionid'};
2358         &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 11);
2360         $kernel->yield('next_task', $hit);
2362         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2363         my $res = $incoming_db->exec_statement($sql);
2364     }
2368 sub get_ldap_handle {
2369         my ($session_id) = @_;
2370         my $heap;
2372         if (not defined $session_id ) { $session_id = 0 };
2373         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2375         my ($package, $file, $row, $subroutine, $hasArgs, $wantArray, $evalText, $isRequire) = caller(1);
2376         my $caller_text = "subroutine $subroutine";
2377         if ($subroutine eq "(eval)") {
2378                 $caller_text = "eval block within file '$file' for '$evalText'"; 
2379         }
2380         daemon_log("$session_id DEBUG: new ldap handle for '$caller_text' required!", 42);
2382 get_handle:
2383         my $ldap_handle = Net::LDAP->new( $ldap_uri );
2384         if (not ref $ldap_handle) {
2385                 daemon_log("$session_id ERROR: Connection to LDAP URI '$ldap_uri' failed! Retrying in $ldap_retry_sec seconds.", 1); 
2386                 sleep($ldap_retry_sec);
2387                 goto get_handle;
2388         } else {
2389                 daemon_log("$session_id DEBUG: Connection to LDAP URI '$ldap_uri' established.", 42);
2390         }
2392         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or &daemon_log("$session_id ERROR: Could not bind as '$ldap_admin_dn' to LDAP URI '$ldap_uri'!", 1);
2393         return $ldap_handle;
2397 sub release_ldap_handle {
2398         my ($ldap_handle, $session_id) = @_ ;
2399         if (not defined $session_id ) { $session_id = 0 };
2401         if(ref $ldap_handle) {
2402           $ldap_handle->disconnect();
2403   }
2404         &main::daemon_log("$session_id DEBUG: Released a ldap handle!", 42);
2405         return;
2409 sub change_fai_state {
2410         my ($st, $targets, $session_id) = @_;
2411         $session_id = 0 if not defined $session_id;
2412         # Set FAI state to localboot
2413         my %mapActions= (
2414                 reboot    => '',
2415                 update    => 'softupdate',
2416                 localboot => 'localboot',
2417                 reinstall => 'install',
2418                 rescan    => '',
2419                 wake      => '',
2420                 memcheck  => 'memcheck',
2421                 sysinfo   => 'sysinfo',
2422                 install   => 'install',
2423         );
2425         # Return if this is unknown
2426         if (!exists $mapActions{ $st }){
2427                 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2428                 return;
2429         }
2431         my $state= $mapActions{ $st };
2433         # Build search filter for hosts
2434         my $search= "(&(objectClass=GOhard)";
2435         foreach (@{$targets}){
2436                 $search.= "(macAddress=$_)";
2437         }
2438         $search.= ")";
2440         # If there's any host inside of the search string, procress them
2441         if (!($search =~ /macAddress/)){
2442                 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2443                 return;
2444         }
2446         my $ldap_handle = &get_ldap_handle($session_id);
2447         # Perform search for Unit Tag
2448         my $mesg = $ldap_handle->search(
2449                 base   => $ldap_base,
2450                 scope  => 'sub',
2451                 attrs  => ['dn', 'FAIstate', 'objectClass'],
2452                 filter => "$search"
2453         );
2455         if ($mesg->count) {
2456                 my @entries = $mesg->entries;
2457                 if (0 == @entries) {
2458                         daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2459                 }
2461                 foreach my $entry (@entries) {
2462                         # Only modify entry if it is not set to '$state'
2463                         if ($entry->get_value("FAIstate") ne "$state"){
2464                                 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2465                                 my $result;
2466                                 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2467                                 if (exists $tmp{'FAIobject'}){
2468                                         if ($state eq ''){
2469                                                 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2470                                         } else {
2471                                                 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2472                                         }
2473                                 } elsif ($state ne ''){
2474                                         $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2475                                 }
2477                                 # Errors?
2478                                 if ($result->code){
2479                                         daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2480                                 }
2481                         } else {
2482                                 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 42); 
2483                         }  
2484                 }
2485         } else {
2486                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2487         }
2488         &release_ldap_handle($ldap_handle, $session_id);                  
2490         return;
2494 sub change_goto_state {
2495     my ($st, $targets, $session_id) = @_;
2496     $session_id = 0  if not defined $session_id;
2498     # Switch on or off?
2499     my $state= $st eq 'active' ? 'active': 'locked';
2501     my $ldap_handle = &get_ldap_handle($session_id);
2502     if( defined($ldap_handle) ) {
2504       # Build search filter for hosts
2505       my $search= "(&(objectClass=GOhard)";
2506       foreach (@{$targets}){
2507         $search.= "(macAddress=$_)";
2508       }
2509       $search.= ")";
2511       # If there's any host inside of the search string, procress them
2512       if (!($search =~ /macAddress/)){
2513               &release_ldap_handle($ldap_handle);
2514         return;
2515       }
2517       # Perform search for Unit Tag
2518       my $mesg = $ldap_handle->search(
2519           base   => $ldap_base,
2520           scope  => 'sub',
2521           attrs  => ['dn', 'gotoMode'],
2522           filter => "$search"
2523           );
2525       if ($mesg->count) {
2526         my @entries = $mesg->entries;
2527         foreach my $entry (@entries) {
2529           # Only modify entry if it is not set to '$state'
2530           if ($entry->get_value("gotoMode") ne $state){
2532             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2533             my $result;
2534             $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2536             # Errors?
2537             if ($result->code){
2538               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2539             }
2541           }
2542         }
2543       } else {
2544                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2545           }
2547     }
2548         &release_ldap_handle($ldap_handle, $session_id);
2549         return;
2553 sub run_recreate_packages_db {
2554     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2555     my $session_id = $session->ID;
2556         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2557         $kernel->yield('create_fai_release_db', $fai_release_tn);
2558         $kernel->yield('create_fai_server_db', $fai_server_tn);
2559         return;
2563 sub run_create_fai_server_db {
2564     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2565     my $session_id = $session->ID;
2566     my $task = POE::Wheel::Run->new(
2567             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2568             StdoutEvent  => "session_run_result",
2569             StderrEvent  => "session_run_debug",
2570             CloseEvent   => "session_run_done",
2571             );
2573     $heap->{task}->{ $task->ID } = $task;
2574     return;
2578 sub create_fai_server_db {
2579         my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2580         my $result;
2582         if (not defined $session_id) { $session_id = 0; }
2583         my $ldap_handle = &get_ldap_handle($session_id);
2584         if(defined($ldap_handle)) {
2585                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2586                 my $mesg= $ldap_handle->search(
2587                         base   => $ldap_base,
2588                         scope  => 'sub',
2589                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2590                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2591                 );
2592                 if($mesg->{'resultCode'} == 0 &&
2593                         $mesg->count != 0) {
2594                         foreach my $entry (@{$mesg->{entries}}) {
2595                                 if($entry->exists('FAIrepository')) {
2596                                         # Add an entry for each Repository configured for server
2597                                         foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2598                                                 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2599                                                 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2600                                                 $result= $fai_server_db->add_dbentry( { 
2601                                                                 table => $table_name,
2602                                                                 primkey => ['server', 'fai_release', 'tag'],
2603                                                                 server => $tmp_url,
2604                                                                 fai_release => $tmp_release,
2605                                                                 sections => $tmp_sections,
2606                                                                 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2607                                                         } );
2608                                         }
2609                                 }
2610                         }
2611                 }
2612                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2613                 &release_ldap_handle($ldap_handle);
2615                 # TODO: Find a way to post the 'create_packages_list_db' event
2616                 if(not defined($dont_create_packages_list)) {
2617                         &create_packages_list_db(undef, $session_id);
2618                 }
2619         }       
2621         return $result;
2625 sub run_create_fai_release_db {
2626         my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2627         my $session_id = $session->ID;
2628         my $task = POE::Wheel::Run->new(
2629                 Program => sub { &create_fai_release_db($table_name, $session_id) },
2630                 StdoutEvent  => "session_run_result",
2631                 StderrEvent  => "session_run_debug",
2632                 CloseEvent   => "session_run_done",
2633         );
2635         $heap->{task}->{ $task->ID } = $task;
2636         return;
2640 sub create_fai_release_db {
2641         my ($table_name, $session_id) = @_;
2642         my $result;
2644         # used for logging
2645         if (not defined $session_id) { $session_id = 0; }
2647         my $ldap_handle = &get_ldap_handle($session_id);
2648         if(defined($ldap_handle)) {
2649                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2650                 my $mesg= $ldap_handle->search(
2651                         base   => $ldap_base,
2652                         scope  => 'sub',
2653                         attrs  => [],
2654                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2655                 );
2656                 if(($mesg->code == 0) && ($mesg->count != 0))
2657                 {
2658                         daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,138);
2660                         # Walk through all possible FAI container ou's
2661                         my @sql_list;
2662                         my $timestamp= &get_time();
2663                         foreach my $ou (@{$mesg->{entries}}) {
2664                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2665                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2666                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2667                                         if(@tmp_array) {
2668                                                 foreach my $entry (@tmp_array) {
2669                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2670                                                                 my $sql= 
2671                                                                 "INSERT INTO $table_name "
2672                                                                 ."(timestamp, fai_release, class, type, state) VALUES ("
2673                                                                 .$timestamp.","
2674                                                                 ."'".$entry->{'release'}."',"
2675                                                                 ."'".$entry->{'class'}."',"
2676                                                                 ."'".$entry->{'type'}."',"
2677                                                                 ."'".$entry->{'state'}."')";
2678                                                                 push @sql_list, $sql;
2679                                                         }
2680                                                 }
2681                                         }
2682                                 }
2683                         }
2685                         daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",138);
2686             &release_ldap_handle($ldap_handle);
2687                         if(@sql_list) {
2688                                 unshift @sql_list, "VACUUM";
2689                                 unshift @sql_list, "DELETE FROM $table_name";
2690                                 $fai_release_db->exec_statementlist(\@sql_list);
2691                         }
2692                         daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",138);
2693                 } else {
2694                         daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2695                 }
2696                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2697         }
2698         return $result;
2701 sub get_fai_types {
2702         my $tmp_classes = shift || return undef;
2703         my @result;
2705         foreach my $type(keys %{$tmp_classes}) {
2706                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2707                         my $entry = {
2708                                 type => $type,
2709                                 state => $tmp_classes->{$type}[0],
2710                         };
2711                         push @result, $entry;
2712                 }
2713         }
2715         return @result;
2718 sub get_fai_state {
2719         my $result = "";
2720         my $tmp_classes = shift || return $result;
2722         foreach my $type(keys %{$tmp_classes}) {
2723                 if(defined($tmp_classes->{$type}[0])) {
2724                         $result = $tmp_classes->{$type}[0];
2725                         
2726                 # State is equal for all types in class
2727                         last;
2728                 }
2729         }
2731         return $result;
2734 sub resolve_fai_classes {
2735         my ($fai_base, $ldap_handle, $session_id) = @_;
2736         if (not defined $session_id) { $session_id = 0; }
2737         my $result;
2738         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2739         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2740         my $fai_classes;
2742         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base", 138);
2743         my $mesg= $ldap_handle->search(
2744                 base   => $fai_base,
2745                 scope  => 'sub',
2746                 attrs  => ['cn','objectClass','FAIstate'],
2747                 filter => $fai_filter,
2748         );
2749         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries", 138);
2751         if($mesg->{'resultCode'} == 0 &&
2752                 $mesg->count != 0) {
2753                 foreach my $entry (@{$mesg->{entries}}) {
2754                         if($entry->exists('cn')) {
2755                                 my $tmp_dn= $entry->dn();
2756                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2757                                         - length($fai_base) - 1 );
2759                                 # Skip classname and ou dn parts for class
2760                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2762                                 # Skip classes without releases
2763                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2764                                         next;
2765                                 }
2767                                 my $tmp_cn= $entry->get_value('cn');
2768                                 my $tmp_state= $entry->get_value('FAIstate');
2770                                 my $tmp_type;
2771                                 # Get FAI type
2772                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2773                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2774                                                 $tmp_type= $oclass;
2775                                                 last;
2776                                         }
2777                                 }
2779                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2780                                         # A Subrelease
2781                                         my @sub_releases = split(/,/, $tmp_release);
2783                                         # Walk through subreleases and build hash tree
2784                                         my $hash;
2785                                         while(my $tmp_sub_release = pop @sub_releases) {
2786                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2787                                         }
2788                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2789                                 } else {
2790                                         # A branch, no subrelease
2791                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2792                                 }
2793                         } elsif (!$entry->exists('cn')) {
2794                                 my $tmp_dn= $entry->dn();
2795                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2796                                         - length($fai_base) - 1 );
2797                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2799                                 # Skip classes without releases
2800                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2801                                         next;
2802                                 }
2804                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2805                                         # A Subrelease
2806                                         my @sub_releases= split(/,/, $tmp_release);
2808                                         # Walk through subreleases and build hash tree
2809                                         my $hash;
2810                                         while(my $tmp_sub_release = pop @sub_releases) {
2811                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2812                                         }
2813                                         # Remove the last two characters
2814                                         chop($hash);
2815                                         chop($hash);
2817                                         eval('$fai_classes->'.$hash.'= {}');
2818                                 } else {
2819                                         # A branch, no subrelease
2820                                         if(!exists($fai_classes->{$tmp_release})) {
2821                                                 $fai_classes->{$tmp_release} = {};
2822                                         }
2823                                 }
2824                         }
2825                 }
2827                 # The hash is complete, now we can honor the copy-on-write based missing entries
2828                 foreach my $release (keys %$fai_classes) {
2829                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2830                 }
2831         }
2832         return $result;
2835 sub apply_fai_inheritance {
2836        my $fai_classes = shift || return {};
2837        my $tmp_classes;
2839        # Get the classes from the branch
2840        foreach my $class (keys %{$fai_classes}) {
2841                # Skip subreleases
2842                if($class =~ /^ou=.*$/) {
2843                        next;
2844                } else {
2845                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2846                }
2847        }
2849        # Apply to each subrelease
2850        foreach my $subrelease (keys %{$fai_classes}) {
2851                if($subrelease =~ /ou=/) {
2852                        foreach my $tmp_class (keys %{$tmp_classes}) {
2853                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2854                                        $fai_classes->{$subrelease}->{$tmp_class} =
2855                                        deep_copy($tmp_classes->{$tmp_class});
2856                                } else {
2857                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2858                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2859                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2860                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2861                                                }
2862                                        }
2863                                }
2864                        }
2865                }
2866        }
2868        # Find subreleases in deeper levels
2869        foreach my $subrelease (keys %{$fai_classes}) {
2870                if($subrelease =~ /ou=/) {
2871                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2872                                if($subsubrelease =~ /ou=/) {
2873                                        apply_fai_inheritance($fai_classes->{$subrelease});
2874                                }
2875                        }
2876                }
2877        }
2879        return $fai_classes;
2882 sub get_fai_release_entries {
2883         my $tmp_classes = shift || return;
2884         my $parent = shift || "";
2885         my @result = shift || ();
2887         foreach my $entry (keys %{$tmp_classes}) {
2888                 if(defined($entry)) {
2889                         if($entry =~ /^ou=.*$/) {
2890                                 my $release_name = $entry;
2891                                 $release_name =~ s/ou=//g;
2892                                 if(length($parent)>0) {
2893                                         $release_name = $parent."/".$release_name;
2894                                 }
2895                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2896                                 foreach my $bufentry(@bufentries) {
2897                                         push @result, $bufentry;
2898                                 }
2899                         } else {
2900                                 my @types = get_fai_types($tmp_classes->{$entry});
2901                                 foreach my $type (@types) {
2902                                         push @result, 
2903                                         {
2904                                                 'class' => $entry,
2905                                                 'type' => $type->{'type'},
2906                                                 'release' => $parent,
2907                                                 'state' => $type->{'state'},
2908                                         };
2909                                 }
2910                         }
2911                 }
2912         }
2914         return @result;
2917 sub deep_copy {
2918         my $this = shift;
2919         if (not ref $this) {
2920                 $this;
2921         } elsif (ref $this eq "ARRAY") {
2922                 [map deep_copy($_), @$this];
2923         } elsif (ref $this eq "HASH") {
2924                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2925         } else { die "what type is $_?" }
2929 sub session_run_result {
2930     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2931     $kernel->sig(CHLD => "child_reap");
2934 sub session_run_debug {
2935     my $result = $_[ARG0];
2936     print STDERR "$result\n";
2939 sub session_run_done {
2940     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2941     delete $heap->{task}->{$task_id};
2942         if (exists $heap->{ldap_handle}->{$task_id}) {
2943                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
2944         }
2945         delete $heap->{ldap_handle}->{$task_id};
2949 sub create_sources_list {
2950         my $session_id = shift || 0;
2951         my $result="/tmp/gosa_si_tmp_sources_list";
2953         # Remove old file
2954         if(stat($result)) {
2955                 unlink($result);
2956                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2957         }
2959         open(my $fh, ">", "$result");
2960         if (not defined $fh) {
2961                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2962                 return undef;
2963         }
2964         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2965                 my $ldap_handle = &get_ldap_handle($session_id);
2966                 my $mesg=$ldap_handle->search(
2967                         base    => $main::ldap_server_dn,
2968                         scope   => 'base',
2969                         attrs   => 'FAIrepository',
2970                         filter  => 'objectClass=FAIrepositoryServer'
2971                 );
2972                 if($mesg->count) {
2973                         foreach my $entry(@{$mesg->{'entries'}}) {
2974                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2975                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2976                                         my $line = "deb $server $release";
2977                                         $sections =~ s/,/ /g;
2978                                         $line.= " $sections";
2979                                         print $fh $line."\n";
2980                                 }
2981                         }
2982                 }
2983                 &release_ldap_handle($ldap_handle);
2984         } else {
2985                 if (defined $main::ldap_server_dn){
2986                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2987                 } else {
2988                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2989                 }
2990         }
2991         close($fh);
2993         return $result;
2997 sub run_create_packages_list_db {
2998     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2999         my $session_id = $session->ID;
3000         my $task = POE::Wheel::Run->new(
3001                                         Priority => +20,
3002                                         Program => sub {&create_packages_list_db(undef, $session_id)},
3003                                         StdoutEvent  => "session_run_result",
3004                                         StderrEvent  => "session_run_debug",
3005                                         CloseEvent   => "session_run_done",
3006                                         );
3007         $heap->{task}->{ $task->ID } = $task;
3011 sub create_packages_list_db {
3012         my ($sources_file, $session_id) = @_;
3013         
3014         # it should not be possible to trigger a recreation of packages_list_db
3015         # while packages_list_db is under construction, so set flag packages_list_under_construction
3016         # which is tested befor recreation can be started
3017         if (-r $packages_list_under_construction) {
3018                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
3019                 return;
3020         } else {
3021                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
3022                 # set packages_list_under_construction to true
3023                 system("touch $packages_list_under_construction");
3024                 @packages_list_statements=();
3025         }
3027         if (not defined $session_id) { $session_id = 0; }
3029         if (not defined $sources_file) { 
3030                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
3031                 $sources_file = &create_sources_list($session_id);
3032         }
3034         if (not defined $sources_file) {
3035                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
3036                 unlink($packages_list_under_construction);
3037                 return;
3038         }
3040         my $line;
3042         open(my $CONFIG, "<", "$sources_file") or do {
3043                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
3044                 unlink($packages_list_under_construction);
3045                 return;
3046         };
3048         # Read lines
3049         while ($line = <$CONFIG>){
3050                 # Unify
3051                 chop($line);
3052                 $line =~ s/^\s+//;
3053                 $line =~ s/^\s+/ /;
3055                 # Strip comments
3056                 $line =~ s/#.*$//g;
3058                 # Skip empty lines
3059                 if ($line =~ /^\s*$/){
3060                         next;
3061                 }
3063                 # Interpret deb line
3064                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
3065                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
3066                         my $section;
3067                         foreach $section (split(' ', $sections)){
3068                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
3069                         }
3070                 }
3071     else {
3072         daemon_log("$session_id ERROR: cannot parse line '$line'", 1);
3073     }
3074         }
3076         close ($CONFIG);
3078         if(keys(%repo_dirs)) {
3079                 find(\&cleanup_and_extract, keys( %repo_dirs ));
3080                 &main::strip_packages_list_statements();
3081                 $packages_list_db->exec_statementlist(\@packages_list_statements);
3082         }
3083         unlink($packages_list_under_construction);
3084         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
3085         return;
3088 # This function should do some intensive task to minimize the db-traffic
3089 sub strip_packages_list_statements {
3090         my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
3091         my @new_statement_list=();
3092         my $hash;
3093         my $insert_hash;
3094         my $update_hash;
3095         my $delete_hash;
3096         my $known_packages_hash;
3097         my $local_timestamp=get_time();
3099         foreach my $existing_entry (@existing_entries) {
3100                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
3101         }
3103         foreach my $statement (@packages_list_statements) {
3104                 if($statement =~ /^INSERT/i) {
3105                         # Assign the values from the insert statement
3106                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
3107                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
3108                         if(exists($hash->{$distribution}->{$package}->{$version})) {
3109                                 # If section or description has changed, update the DB
3110                                 if( 
3111                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
3112                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
3113                                 ) {
3114                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
3115                                 } else {
3116                                         # package is already present in database. cache this knowledge for later use
3117                                         @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3118                                 }
3119                         } else {
3120                                 # Insert a non-existing entry to db
3121                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3122                         }
3123                 } elsif ($statement =~ /^UPDATE/i) {
3124                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
3125                         /^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;
3126                         foreach my $distribution (keys %{$hash}) {
3127                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
3128                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
3129                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
3130                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
3131                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
3132                                                 my $section;
3133                                                 my $description;
3134                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
3135                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
3136                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
3137                                                 }
3138                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3139                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
3140                                                 }
3141                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3142                                         }
3143                                 }
3144                         }
3145                 }
3146         }
3148         # Check for orphaned entries
3149         foreach my $existing_entry (@existing_entries) {
3150                 my $distribution= @{$existing_entry}[0];
3151                 my $package= @{$existing_entry}[1];
3152                 my $version= @{$existing_entry}[2];
3153                 my $section= @{$existing_entry}[3];
3155                 if(
3156                         exists($insert_hash->{$distribution}->{$package}->{$version}) ||
3157                         exists($update_hash->{$distribution}->{$package}->{$version}) ||
3158                         exists($known_packages_hash->{$distribution}->{$package}->{$version})
3159                 ) {
3160                         next;
3161                 } else {
3162                         # Insert entry to delete hash
3163                         @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
3164                 }
3165         }
3167         # unroll the insert hash
3168         foreach my $distribution (keys %{$insert_hash}) {
3169                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
3170                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
3171                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
3172                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
3173                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
3174                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
3175                                 ."'$local_timestamp')";
3176                         }
3177                 }
3178         }
3180         # unroll the update hash
3181         foreach my $distribution (keys %{$update_hash}) {
3182                 foreach my $package (keys %{$update_hash->{$distribution}}) {
3183                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
3184                                 my $set = "";
3185                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
3186                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
3187                                 }
3188                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3189                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
3190                                 }
3191                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
3192                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
3193                                 }
3194                                 if(defined($set) and length($set) > 0) {
3195                                         $set .= "timestamp = '$local_timestamp'";
3196                                 } else {
3197                                         next;
3198                                 }
3199                                 push @new_statement_list, 
3200                                 "UPDATE $main::packages_list_tn SET $set WHERE"
3201                                 ." distribution = '$distribution'"
3202                                 ." AND package = '$package'"
3203                                 ." AND version = '$version'";
3204                         }
3205                 }
3206         }
3207         
3208         # unroll the delete hash
3209         foreach my $distribution (keys %{$delete_hash}) {
3210                 foreach my $package (keys %{$delete_hash->{$distribution}}) {
3211                         foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
3212                                 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
3213                                 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
3214                         }
3215                 }
3216         }
3218         unshift(@new_statement_list, "VACUUM");
3220         @packages_list_statements = @new_statement_list;
3224 sub parse_package_info {
3225     my ($baseurl, $dist, $section, $session_id)= @_;
3226     my ($package);
3227     if (not defined $session_id) { $session_id = 0; }
3228     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3229     $repo_dirs{ "${repo_path}/pool" } = 1;
3231     foreach $package ("Packages.gz"){
3232         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 266);
3233         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3234         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3235     }
3236     
3240 sub get_package {
3241     my ($url, $dest, $session_id)= @_;
3242     if (not defined $session_id) { $session_id = 0; }
3244     my $tpath = dirname($dest);
3245     -d "$tpath" || mkpath "$tpath";
3247     # This is ugly, but I've no time to take a look at "how it works in perl"
3248     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3249         system("gunzip -cd '$dest' > '$dest.in'");
3250         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 266);
3251         unlink($dest);
3252         daemon_log("$session_id DEBUG: delete file '$dest'", 266); 
3253     } else {
3254         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3255     }
3256     return 0;
3260 sub parse_package {
3261     my ($path, $dist, $srv_path, $session_id)= @_;
3262     if (not defined $session_id) { $session_id = 0;}
3263     my ($package, $version, $section, $description);
3264     my $timestamp = &get_time();
3266     if(not stat("$path.in")) {
3267         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3268         return;
3269     }
3271     open(my $PACKAGES, "<", "$path.in");
3272     if(not defined($PACKAGES)) {
3273         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
3274         return;
3275     }
3277     # Read lines
3278     while (<$PACKAGES>){
3279         my $line = $_;
3280         # Unify
3281         chop($line);
3283         # Use empty lines as a trigger
3284         if ($line =~ /^\s*$/){
3285             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3286             push(@packages_list_statements, $sql);
3287             $package = "none";
3288             $version = "none";
3289             $section = "none";
3290             $description = "none"; 
3291             next;
3292         }
3294         # Trigger for package name
3295         if ($line =~ /^Package:\s/){
3296             ($package)= ($line =~ /^Package: (.*)$/);
3297             next;
3298         }
3300         # Trigger for version
3301         if ($line =~ /^Version:\s/){
3302             ($version)= ($line =~ /^Version: (.*)$/);
3303             next;
3304         }
3306         # Trigger for description
3307         if ($line =~ /^Description:\s/){
3308             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3309             next;
3310         }
3312         # Trigger for section
3313         if ($line =~ /^Section:\s/){
3314             ($section)= ($line =~ /^Section: (.*)$/);
3315             next;
3316         }
3318         # Trigger for filename
3319         if ($line =~ /^Filename:\s/){
3320             my ($filename) = ($line =~ /^Filename: (.*)$/);
3321             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3322             next;
3323         }
3324     }
3326     close( $PACKAGES );
3327     unlink( "$path.in" );
3331 sub store_fileinfo {
3332     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3334     my %fileinfo = (
3335         'package' => $package,
3336         'dist' => $dist,
3337         'version' => $vers,
3338     );
3340     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3344 sub cleanup_and_extract {
3345         my $fileinfo = $repo_files{ $File::Find::name };
3347         if( defined $fileinfo ) {
3348                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3349                 my $sql;
3350                 my $package = $fileinfo->{ 'package' };
3351                 my $newver = $fileinfo->{ 'version' };
3353                 mkpath($dir);
3354                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3356                 if( -f "$dir/DEBIAN/templates" ) {
3358                         daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 266);
3360                         my $tmpl= ""; {
3361                                 local $/=undef;
3362                                 open(my $FILE, "$dir/DEBIAN/templates");
3363                                 $tmpl = &encode_base64(<$FILE>);
3364                                 close($FILE);
3365                         }
3366                         rmtree("$dir/DEBIAN/templates");
3368                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3369                         push @packages_list_statements, $sql;
3370                 }
3371         }
3373         return;
3377 sub prepare_server_registration 
3379         # Add foreign server from cfg file
3380         my @foreign_server_list;
3381         if ($foreign_server_string ne "") {
3382             my @cfg_foreign_server_list = split(",", $foreign_server_string);
3383             foreach my $foreign_server (@cfg_foreign_server_list) {
3384                 push(@foreign_server_list, $foreign_server);
3385             }
3386         
3387             daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3388         }
3389         
3390         # Perform a DNS lookup for server registration if flag is true
3391         if ($dns_lookup eq "true") {
3392             # Add foreign server from dns
3393             my @tmp_servers;
3394             if (not $server_domain) {
3395                 # Try our DNS Searchlist
3396                 for my $domain(get_dns_domains()) {
3397                     chomp($domain);
3398                     my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3399                     if(@$tmp_domains) {
3400                         for my $tmp_server(@$tmp_domains) {
3401                             push @tmp_servers, $tmp_server;
3402                         }
3403                     }
3404                 }
3405                 if(@tmp_servers && length(@tmp_servers)==0) {
3406                     daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3407                 }
3408             } else {
3409                 @tmp_servers = &get_server_addresses($server_domain);
3410                 if( 0 == @tmp_servers ) {
3411                     daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3412                 }
3413             }
3414         
3415             daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3416         
3417             foreach my $server (@tmp_servers) { 
3418                 unshift(@foreign_server_list, $server); 
3419             }
3420         } else {
3421             daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3422         }
3423         
3424         # eliminate duplicate entries
3425         @foreign_server_list = &del_doubles(@foreign_server_list);
3426         my $all_foreign_server = join(", ", @foreign_server_list);
3427         daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3428         
3429         # add all found foreign servers to known_server
3430         my $cur_timestamp = &get_time();
3431         foreach my $foreign_server (@foreign_server_list) {
3432         
3433                 # do not add myself to known_server_db
3434                 if (&is_local($foreign_server)) { next; }
3435                 ######################################
3436         
3437             my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3438                     primkey=>['hostname'],
3439                     hostname=>$foreign_server,
3440                     macaddress=>"",
3441                     status=>'not_yet_registered',
3442                     hostkey=>"none",
3443                     loaded_modules => "none", 
3444                     timestamp=>$cur_timestamp,
3445                                 update_time=>'19700101000000',
3446                     } );
3447         }
3450 sub register_at_foreign_servers {   
3451     my ($kernel) = $_[KERNEL];
3453         # Update status and update-time of all si-server with expired update_time and 
3454         # block them for race conditional registration processes of other si-servers.
3455         my $act_time = &get_time();
3456         my $block_statement = "UPDATE $known_server_tn SET status='new_server',update_time='19700101000000' WHERE (CAST(update_time AS UNSIGNED))<$act_time ";
3457         my $block_res = $known_server_db->exec_statement($block_statement);
3459         # Fetch all si-server from db where update_time is younger than act_time
3460         my $fetch_statement = "SELECT * FROM $known_server_tn WHERE update_time='19700101000000'"; 
3461         my $fetch_res = $known_server_db->exec_statement($fetch_statement);
3463     # Detect already connected clients. Will be added to registration msg later. 
3464     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3465     my $client_res = $known_clients_db->exec_statement($client_sql);
3467         # Send registration messag to all fetched si-server
3468     foreach my $hit (@$fetch_res) {
3469         my $hostname = @$hit[0];
3470         my $hostkey = &create_passwd;
3472         # Add already connected clients to registration message 
3473         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3474         &add_content2xml_hash($myhash, 'key', $hostkey);
3475         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3477         # Add locally loaded gosa-si modules to registration message
3478         my $loaded_modules = {};
3479         while (my ($package, $pck_info) = each %$known_modules) {
3480                         next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3481                         foreach my $act_module (keys(%{@$pck_info[2]})) {
3482                                 $loaded_modules->{$act_module} = ""; 
3483                         }
3484                 }
3485         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3487         # Add macaddress to registration message
3488         my ($host_ip, $host_port) = split(/:/, $hostname);
3489         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3490         my $network_interface= &get_interface_for_ip($local_ip);
3491         my $host_mac = &get_mac_for_interface($network_interface);
3492         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3493         
3494         # Build registration message and send it
3495         my $foreign_server_msg = &create_xml_string($myhash);
3496         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3497     }
3500         # After n sec perform a check of all server registration processes
3501     $kernel->delay_set("control_server_registration", 2); 
3503         return;
3507 sub control_server_registration {
3508         my ($kernel) = $_[KERNEL];
3509         
3510         # Check if all registration processes succeed or not
3511         my $select_statement = "SELECT * FROM $known_server_tn WHERE status='new_server'"; 
3512         my $select_res = $known_server_db->exec_statement($select_statement);
3514         # If at least one registration process failed, maybe in case of a race condition
3515         # with a foreign registration process
3516         if (@$select_res > 0) 
3517         {
3518                 # Release block statement 'new_server' to make the server accessible
3519                 # for foreign registration processes
3520                 my $update_statement = "UPDATE $known_server_tn SET status='waiting' WHERE status='new_server'";        
3521                 my $update_res = $known_server_db->exec_statement($update_statement);
3523                 # Set a random delay to avoid the registration race condition
3524                 my $new_foreign_servers_register_delay = int(rand(4))+1;
3525                 $kernel->delay_set("register_at_foreign_servers", $new_foreign_servers_register_delay);
3526         }
3527         # If all registration processes succeed
3528         else
3529         {
3530                 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3531         }
3533         return;
3536 sub start_daemon {
3538         #to prevent zombie child
3539   $SIG{CHLD} = 'IGNORE';
3541         if( ! $foreground ) { 
3542                 chdir '/'                 or die "Can't chdir to /: $!";
3543                 umask 0;
3544                 open STDIN, '+>/dev/null'   or die "Can't read /dev/null: $!";
3545                 open STDOUT, '+>&STDIN' or die "Can't write to /dev/null: $!";
3546                 open STDERR, '+>&STDIN' or die "Can't write to /dev/null: $!";
3547                 defined($pid = fork)   or die "Can't fork: $!";
3548                 exit if $pid;
3549                 setsid                    or die "Can't start a new session: $!";
3550         }
3551         return;
3554 sub put_version {
3556         # parse head url and revision from svn
3557         $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3558         $server_headURL = defined $1 ? $1 : 'unknown' ;
3559         $server_revision = defined $2 ? $2 : 'unknown' ;
3560         if ($server_headURL =~ /\/tag\// ||
3561         $server_headURL =~ /\/branches\// ) {
3562     $server_status = "stable";
3563         } else {
3564     $server_status = "developmental" ;
3565         }
3566         return;
3569 sub get_perms_admin {
3570 # Determine root uid and adm gid, used for creating log files
3571         $root_uid = getpwnam('root');
3572         $adm_gid = getgrnam('adm');
3573         if(not defined $adm_gid){
3574                 $adm_gid = getgrnam('root');
3575         }
3576         return;
3579 sub open_log {
3580         # Prepare log file and set permissions
3581         open(my $log, ">>", "$log_file");
3582         close($log);
3583         chmod(0440, $log_file);
3584         chown($root_uid, $adm_gid, $log_file);
3586         return;
3589 sub create_pid {
3590         # Create the PID object
3591         $pid = File::Pid->new({ file  => $pid_file });
3593         # Write the PID file
3594         $pid->write;
3596         return;
3600 #==== MAIN = main ==============================================================
3602 # Parse options and allow '-vvv'
3603 Getopt::Long::Configure( 'bundling' );
3604 GetOptions( 'v|verbose+' => \$verbose,
3605             'h|help' => \&usage,
3606             'c|config=s' => \$config,
3607             'x|dump-config=i' => \$dump_config,
3608             'f|foreground' => \$foreground,
3609                                                 'd=s' => \$debug_parts)
3610   or usage( '', 1 );
3612 # We may want to dump the default configuration
3613 if( defined $dump_config ) {
3614   if($dump_config==1) {
3615         } elsif ($dump_config==2) {
3616     dump_configuration( $dump_config ); 
3617         } else {
3618     usage( "Dump configuration value has to be 1 or 2" );
3619         }
3622 #  read and set config parameters
3623 &read_configfile($config, %cfg_defaults);
3625 # daemonize the program
3626 &start_daemon($pid, $foreground);
3628 # create pid file
3629 &create_pid($pid, $pid_file);
3631 # Determine root uid and adm gid, used for creating log files
3632 &get_perms_admin($root_uid, $adm_gid);
3634 # put version
3635 &put_version($server_status_hash, $server_version, $server_headURL, $server_revision, $server_status);
3637 #open log file
3638 &open_log($root_uid, $adm_gid, $log_file);
3640 # prepare directory for databases
3641 mkpath('/var/lib/gosa-si', 0, {owner=>'root', group=>'root', mode=> '0755'});
3643 # remove leftover files in tmp for packaged.db populate
3644 rmtree( '/tmp/packages_list_db',0,1);
3646 # remove list of sources from apt.sources.list
3647 unlink '/tmp/gosa_si_tmp_sources_list';
3649 # remove marker that the list creation is in progress
3650 unlink '/tmp/packages_list_creation_in_progress';
3652 daemon_log(" ", 1);
3653 daemon_log("$0 started!", 1);
3654 daemon_log("status: $server_status", 1);
3655 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3657 # Buildup data bases
3659     no strict "refs";
3661                 daemon_log("0 INFO: importing database module '$db_module'", 1);
3663     if ($db_module eq "DBmysql") {
3664     
3665         # connect to incoming_db
3666         $incoming_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3668         # connect to gosa-si job queue
3669         $job_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3671         # connect to known_clients_db
3672         $known_clients_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3674         # connect to foreign_clients_db
3675         $foreign_clients_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3677         # connect to known_server_db
3678         $known_server_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3680         # connect to login_usr_db
3681         $login_users_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3683         # connect to fai_server_db 
3684         $fai_server_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3686         # connect to fai_release_db
3687         $fai_release_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3689         # connect to packages_list_db
3690         $packages_list_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3692         # connect to messaging_db
3693         $messaging_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3695     } elsif ($db_module eq "DBsqlite") {
3697         daemon_log("0 INFO: Removing SQLlite lock files", 1);
3699                                 # delete old DBsqlite lock files to be replace by rmtree !!
3700                                 system('rm -f /var/lib/gosa-si/*.si.lock*');
3701                                 #rmtree( '/var/lib/gosa-si/',0,1);
3703         # connect to incoming_db
3704         unlink($incoming_file_name);
3705         $incoming_db = ("GOsaSI::".$db_module)->new($incoming_file_name);
3707         # connect to gosa-si job queue
3708         $job_db = ("GOsaSI::".$db_module)->new($job_queue_file_name);
3709         
3710         # connect to known_clients_db
3711         $known_clients_db = ("GOsaSI::".$db_module)->new($known_clients_file_name);
3712         
3713         # connect to foreign_clients_db
3714         $foreign_clients_db = ("GOsaSI::".$db_module)->new($foreign_clients_file_name);
3715         
3716         # connect to known_server_db
3717         $known_server_db = ("GOsaSI::".$db_module)->new($known_server_file_name);
3718         
3719         # connect to login_usr_db
3720         $login_users_db = ("GOsaSI::".$db_module)->new($login_users_file_name);
3721         
3722         # connect to fai_server_db
3723         $fai_server_db = ("GOsaSI::".$db_module)->new($fai_server_file_name);
3724         
3725         # connect to fai_release_db
3726         $fai_release_db = ("GOsaSI::".$db_module)->new($fai_release_file_name);
3727         
3728         # connect to packages_list_db
3729         $packages_list_db = ("GOsaSI::".$db_module)->new($packages_list_file_name);
3730         
3731         # connect to messaging_db
3732         $messaging_db = ("GOsaSI::".$db_module)->new($messaging_file_name);
3733     }
3736 # Creating tables
3738 daemon_log("0 INFO: creating tables in database with '$db_module'", 1);
3740 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3741 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3742 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3743 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3744 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3745 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3746 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3747 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3748 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3749 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3751 # create xml object used for en/decrypting
3752 $xml = new XML::Simple();
3754 # Import all modules
3755 &import_modules;
3757 # Check wether all modules are gosa-si valid passwd check
3758 &password_check;
3760 # Check DNS and config file for server registration
3761 if ($serverPackages_enabled eq "true") { &prepare_server_registration; }
3763 # Create functions hash
3764 while (my ($module, @mod_info) = each %$known_modules) 
3766         while (my ($plugin, $functions) = each %{$mod_info[0][2]})
3767         {
3768                 while (my ($function, $nothing) = each %$functions )
3769                 {
3770                         $known_functions->{$function} = $nothing;
3771                 }
3772         }
3775 # Prepare for using Opsi 
3776 if ($opsi_enabled eq "true") {
3777     use JSON::RPC::Client;
3778     use XML::Quote qw(:all);
3779     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3780     $opsi_client = new JSON::RPC::Client;
3783 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3785 POE::Component::Server::TCP->new(
3786         Alias => "TCP_SERVER",
3787         Port => $server_port,
3788         ClientInput => sub {
3789                 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3790         my $session_id = $session->ID;
3791                 if ($input =~ /;([\d\.]+):([\d]+)$/) 
3792                 {
3793                         # Messages from other servers should be blocked if config option is set
3794                         if (($2 eq $server_port) && ($serverPackages_enabled eq "false"))
3795                         {
3796                                 return;
3797                         }
3798                         &daemon_log("$session_id DEBUG: incoming message from '$1:$2'", 11);
3799                 }
3800                 else
3801                 {
3802                         my $remote_ip = $heap->{'remote_ip'};
3803                         &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 11);
3804                 }
3805                 push(@msgs_to_decrypt, $input);
3806                 $kernel->yield("msg_to_decrypt");
3807         },
3808         InlineStates => {
3809                 msg_to_decrypt => \&msg_to_decrypt,
3810                 watch_for_next_tasks => \&watch_for_next_tasks,
3811                 next_task => \&next_task,
3812                 task_result => \&handle_task_result,
3813                 task_done   => \&handle_task_done,
3814                 task_debug  => \&handle_task_debug,
3815                 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3816         }
3817 );
3819 # create session for repeatedly checking the job queue for jobs
3820 POE::Session->create(
3821         inline_states => {
3822                                 _start => \&session_start,
3823         register_at_foreign_servers => \&register_at_foreign_servers,
3824                                 control_server_registration => \&control_server_registration,
3825         sig_handler => \&sig_handler,
3826         next_task => \&next_task,
3827         task_result => \&handle_task_result,
3828         task_done   => \&handle_task_done,
3829         task_debug  => \&handle_task_debug,
3830         watch_for_new_messages => \&watch_for_new_messages,
3831         watch_for_delivery_messages => \&watch_for_delivery_messages,
3832         watch_for_done_messages => \&watch_for_done_messages,
3833                                 watch_for_new_jobs => \&watch_for_new_jobs,
3834         watch_for_modified_jobs => \&watch_for_modified_jobs,
3835         watch_for_done_jobs => \&watch_for_done_jobs,
3836         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3837         watch_for_old_known_clients => \&watch_for_old_known_clients,
3838         create_packages_list_db => \&run_create_packages_list_db,
3839         create_fai_server_db => \&run_create_fai_server_db,
3840         create_fai_release_db => \&run_create_fai_release_db,
3841                                 recreate_packages_db => \&run_recreate_packages_db,
3842         session_run_result => \&session_run_result,
3843         session_run_debug => \&session_run_debug,
3844         session_run_done => \&session_run_done,
3845         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3846         }
3847 );
3850 POE::Kernel->run();
3851 exit;