Code

8e7f37918bb0331802da5a19d64f2114f7c1e6e5
[gosa.git] / gosa-si / gosa-si-server
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 #         FILE:  gosa-sd
5 #
6 #        USAGE:  ./gosa-sd
7 #
8 #  DESCRIPTION:
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl 
12 #                libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 #                libpoe-perl
14 #         BUGS:  ---
15 #        NOTES:
16 #       AUTHOR:   (Andreas Rettenberger), <rettenberger@gonicus.de>
17 #      COMPANY:
18 #      VERSION:  1.0
19 #      CREATED:  12.09.2007 08:54:41 CEST
20 #     REVISION:  ---
21 #===============================================================================
23 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev$';
25 use strict;
26 use warnings;
27 use Getopt::Long;
28 use Config::IniFiles;
29 use POSIX;
31 use Fcntl qw/:flock/;
32 use IO::Socket::INET;
33 use IO::Handle;
34 use IO::Select;
35 use Symbol qw(qualify_to_ref);
36 use Crypt::Rijndael;
37 use MIME::Base64;
38 use Digest::MD5  qw(md5 md5_hex md5_base64);
39 use XML::Simple;
40 use Data::Dumper;
41 use Sys::Syslog qw( :DEFAULT setlogsock);
42 use Time::HiRes qw( usleep);
43 use Cwd;
44 use File::Spec;
45 use File::Basename;
46 use File::Find;
47 use File::Copy;
48 use File::Path;
49 use GOSA::GosaSupportDaemon;
50 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
51 use Net::LDAP;
52 use Net::LDAP::Util qw(:escape);
54 # revision number of server and program name
55 my $server_headURL;
56 my $server_revision;
57 my $server_status;
58 our $prg= basename($0);
59 our $verbose= 0;
61 my $db_module = "DBsqlite";
62 {
63 no strict "refs";
64 require ("GOSA/".$db_module.".pm");
65 ("GOSA/".$db_module)->import;
66 #daemon_log("0 INFO: importing database module '$db_module'", 1);
67 }
69 my $modules_path = "/usr/lib/gosa-si/modules";
70 use lib "/usr/lib/gosa-si/modules";
72 our $global_kernel;
73 my ($foreground, $ping_timeout);
74 my ($server);
75 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
76 my ($messaging_db_loop_delay);
77 my ($procid, $pid);
78 my $arp_fifo;
79 my $debug_parts = 0;
80 my $debug_parts_bitstring;
81 my ($xml);
82 my $sources_list;
83 my $max_clients;
84 my %repo_files=();
85 my $repo_path;
86 my %repo_dirs=();
88 # Variables declared in config file are always set to 'our'
89 our (%cfg_defaults, $log_file, $pid_file, 
90     $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
91     $arp_activ, $gosa_unit_tag,
92     $GosaPackages_key, $gosa_timeout,
93     $serverPackages_enabled, $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
94     $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
95     $arp_enabled, $arp_interface,
96     $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
97                 $new_systems_ou,
98 );
100 # additional variable which should be globaly accessable
101 our $server_address;
102 our $server_mac_address;
103 our $gosa_address;
104 our $no_arp;
105 our $forground;
106 our $cfg_file;
107 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn, $ldap_version);
108 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
109 our $known_modules;
110 our $known_functions;
111 our $root_uid;
112 our $adm_gid;
114 # if foreground is not null, script will be not forked to background
115 $foreground = 0 ;
117 # specifies the timeout seconds while checking the online status of a registrating client
118 $ping_timeout = 5;
120 $no_arp = 0;
121 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
122 my @packages_list_statements;
123 my $watch_for_new_jobs_in_progress = 0;
125 # holds all incoming decrypted messages
126 our $incoming_db;
127 our $incoming_tn = 'incoming';
128 my $incoming_file_name;
129 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
130         "timestamp VARCHAR(14) DEFAULT 'none'", 
131         "headertag VARCHAR(255) DEFAULT 'none'",
132         "targettag VARCHAR(255) DEFAULT 'none'",
133         "xmlmessage TEXT",
134         "module VARCHAR(255) DEFAULT 'none'",
135         "sessionid VARCHAR(255) DEFAULT '0'",
136 );
138 # holds all gosa jobs
139 our $job_db;
140 our $job_queue_tn = 'jobs';
141 my $job_queue_file_name;
142 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
143         "timestamp VARCHAR(14) DEFAULT 'none'", 
144         "status VARCHAR(255) DEFAULT 'none'", 
145         "result TEXT",
146         "progress VARCHAR(255) DEFAULT 'none'",
147         "headertag VARCHAR(255) DEFAULT 'none'",
148         "targettag VARCHAR(255) DEFAULT 'none'", 
149         "xmlmessage TEXT", 
150         "macaddress VARCHAR(17) DEFAULT 'none'",
151         "plainname VARCHAR(255) DEFAULT 'none'",
152         "siserver VARCHAR(255) DEFAULT 'none'",
153         "modified INTEGER DEFAULT '0'",
154         "periodic VARCHAR(6) DEFAULT 'none'",
155 );
157 # holds all other gosa-si-server
158 our $known_server_db;
159 our $known_server_tn = "known_server";
160 my $known_server_file_name;
161 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)", "update_time VARCHAR(14)");
163 # holds all registrated clients
164 our $known_clients_db;
165 our $known_clients_tn = "known_clients";
166 my $known_clients_file_name;
167 my @known_clients_col_names = ("hostname VARCHAR(255)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "timestamp VARCHAR(14)", "macaddress VARCHAR(17)", "events TEXT", "keylifetime VARCHAR(255)");
169 # holds all registered clients at a foreign server
170 our $foreign_clients_db;
171 our $foreign_clients_tn = "foreign_clients"; 
172 my $foreign_clients_file_name;
173 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
175 # holds all logged in user at each client 
176 our $login_users_db;
177 our $login_users_tn = "login_users";
178 my $login_users_file_name;
179 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
181 # holds all fai server, the debian release and tag
182 our $fai_server_db;
183 our $fai_server_tn = "fai_server"; 
184 my $fai_server_file_name;
185 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)"); 
187 our $fai_release_db;
188 our $fai_release_tn = "fai_release"; 
189 my $fai_release_file_name;
190 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)"); 
192 # holds all packages available from different repositories
193 our $packages_list_db;
194 our $packages_list_tn = "packages_list";
195 my $packages_list_file_name;
196 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
197 my $outdir = "/tmp/packages_list_db";
198 my $arch = "i386"; 
200 # holds all messages which should be delivered to a user
201 our $messaging_db;
202 our $messaging_tn = "messaging"; 
203 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)", 
204         "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
205 my $messaging_file_name;
207 # path to directory to store client install log files
208 our $client_fai_log_dir = "/var/log/fai"; 
210 # queue which stores taskes until one of the $max_children children are ready to process the task
211 #my @tasks = qw();
212 my @msgs_to_decrypt = qw();
213 my $max_children = 2;
216 # loop delay for job queue to look for opsi jobs
217 my $job_queue_opsi_delay = 10;
218 our $opsi_client;
219 our $opsi_url;
220  
221 # Lifetime of logged in user information. If no update information comes after n seconds, 
222 # the user is expeceted to be no longer logged in or the host is no longer running. Because
223 # of this, the user is deleted from login_users_db
224 our $logged_in_user_date_of_expiry = 600;
226 # List of month names, used in function daemon_log
227 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
229 # List of accepted periodical xml tags related to cpan modul DateTime
230 our $check_periodic = {"months"=>'', "weeks"=>'', "days"=>'', "hours"=>'', "minutes"=>''};
233 %cfg_defaults = (
234 "general" => {
235     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
236     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
237     },
238 "server" => {
239     "ip"                    => [\$server_ip, "0.0.0.0"],
240     "port"                  => [\$server_port, "20081"],
241     "known-clients"         => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
242     "known-servers"         => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
243     "incoming"              => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
244     "login-users"           => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
245     "fai-server"            => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
246     "fai-release"           => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
247     "packages-list"         => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
248     "messaging"             => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
249     "foreign-clients"       => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
250     "source-list"           => [\$sources_list, '/etc/apt/sources.list'],
251     "repo-path"             => [\$repo_path, '/srv/www/repository'],
252     "ldap-uri"              => [\$ldap_uri, ""],
253     "ldap-base"             => [\$ldap_base, ""],
254     "ldap-admin-dn"         => [\$ldap_admin_dn, ""],
255     "ldap-admin-password"   => [\$ldap_admin_password, ""],
256     "ldap-version"          => [\$ldap_version, 3],
257     "gosa-unit-tag"         => [\$gosa_unit_tag, ""],
258     "max-clients"           => [\$max_clients, 10],
259     "wol-password"          => [\$wake_on_lan_passwd, ""],
260         "mysql-username"        => [\$mysql_username, "gosa_si"],
261         "mysql-password"        => [\$mysql_password, ""],
262         "mysql-database"        => [\$mysql_database, "gosa_si"],
263         "mysql-host"            => [\$mysql_host, "127.0.0.1"],
264     },
265 "GOsaPackages" => {
266     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
267     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
268     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
269     "key" => [\$GosaPackages_key, "none"],
270                 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
271     },
272 "ClientPackages" => {
273     "key" => [\$ClientPackages_key, "none"],
274     "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
275     },
276 "ServerPackages"=> {
277         "enabled" => [\$serverPackages_enabled, "true"],
278     "address"      => [\$foreign_server_string, ""],
279     "dns-lookup"            => [\$dns_lookup, "true"],
280     "domain"  => [\$server_domain, ""],
281     "key"     => [\$ServerPackages_key, "none"],
282     "key-lifetime" => [\$foreign_servers_register_delay, 120],
283     "job-synchronization-enabled" => [\$job_synchronization, "true"],
284     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
285     },
286 "ArpHandler" => {
287     "enabled"   => [\$arp_enabled, "true"],
288     "interface" => [\$arp_interface, "all"],
289         },
290 "Opsi" => {
291     "enabled"  => [\$opsi_enabled, "false"], 
292     "server"   => [\$opsi_server, "localhost"],
293     "admin"    => [\$opsi_admin, "opsi-admin"],
294     "password" => [\$opsi_password, "secret"],
295    },
297 );
300 #===  FUNCTION  ================================================================
301 #         NAME:  usage
302 #   PARAMETERS:  nothing
303 #      RETURNS:  nothing
304 #  DESCRIPTION:  print out usage text to STDERR
305 #===============================================================================
306 sub usage {
307     print STDERR << "EOF" ;
308 usage: $prg [-hvf] [-c config] [-d number]
310            -h        : this (help) message
311            -c <file> : config file
312            -f        : foreground, process will not be forked to background
313            -v        : be verbose (multiple to increase verbosity)
314                               'v': error logs
315                             'vvv': warning plus error logs                                              
316                           'vvvvv': info plus warning logs
317                         'vvvvvvv': debug plus info logs
318            -no-arp   : starts $prg without connection to arp module
319            -d <int>  : if verbose level is higher than 7x 'v' specified parts can be debugged
320                            1 : receiving messages
321                            2 : sending messages
322                            4 : encrypting/decrypting messages
323                            8 : verification if a message complies gosa-si requirements
324                           16 : message processing
325                           32 : ldap connectivity
326                           64 : database status and connectivity
327                          128 : main process 
328 EOF
329         exit(0);
333 #===  FUNCTION  ================================================================
334 #         NAME:  logging
335 #   PARAMETERS:  level - string - default 'info'
336 #                msg - string -
337 #                facility - string - default 'LOG_DAEMON'
338 #      RETURNS:  nothing
339 #  DESCRIPTION:  function for logging
340 #===============================================================================
341 sub daemon_log {
342     my( $msg, $level ) = @_;
343     if (not defined $msg) { return }
344     if (not defined $level) { $level = 1 }
345         my $to_be_logged = 0;
347         # Write log line if line level is lower than verbosity given in commandline
348         if ($level <= $verbose) 
349         { 
350                 $to_be_logged = 1 ;
351         }
353         # Write if debug flag is set and bitstring matches
354         if ($debug_parts > 0)
355         {
356                 my $tmp_level = ($level - 10 >= 0) ? $level - 10 : 0 ;
357                 my $tmp_level_bitstring = unpack("B32", pack("N", $tmp_level));
358                 if (int($debug_parts_bitstring & $tmp_level_bitstring)) 
359                 {
360                         $to_be_logged = 1;
361                 }
362         }
364         if ($to_be_logged) 
365         {
366                 if(defined $log_file){
367                         my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
368                         if(not $open_log_fh) {
369                                 print STDERR "cannot open $log_file: $!";
370                                 return;
371                         }
372                         # Check owner and group of log_file and update settings if necessary
373                         my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
374                         if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
375                                 chown($root_uid, $adm_gid, $log_file);
376                         }
378                         # Prepare time string for log message
379                         my ($seconds,$minutes,$hours,$monthday,$month,$year,$weekday,$yearday,$sommertime) = localtime(time);
380                         $hours = $hours < 10 ? $hours = "0".$hours : $hours;
381                         $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
382                         $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
383                         $month = $monthnames[$month];
384                         $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
385                         $year+=1900;
387                         
388                         # Build log message and write it to log file and commandline
389                         chomp($msg);
390                         my $log_msg = "$month $monthday $hours:$minutes:$seconds $prg $msg\n";
391                         flock(LOG_HANDLE, LOCK_EX);
392                         seek(LOG_HANDLE, 0, 2);
393                         print LOG_HANDLE $log_msg;
394                         flock(LOG_HANDLE, LOCK_UN);
395                         if( $foreground ) 
396                         { 
397                                 print STDERR $log_msg;
398                         }
399                         close( LOG_HANDLE );
400                 }
401         }
405 #===  FUNCTION  ================================================================
406 #         NAME:  check_cmdline_param
407 #   PARAMETERS:  nothing
408 #      RETURNS:  nothing
409 #  DESCRIPTION:  validates commandline parameter
410 #===============================================================================
411 sub check_cmdline_param () {
412     my $err_counter = 0;
414         # Check configuration file
415         if(not defined($cfg_file)) {
416                 $cfg_file = "/etc/gosa-si/server.conf";
417                 if(! -r $cfg_file) {
418                         print STDERR "Please specify a config file.\n";
419                         $err_counter++;
420                 }
421     }
423         # Prepare identification which gosa-si parts should be debugged and which not
424         if (defined $debug_parts) 
425         {
426                 if ($debug_parts =~ /^\d+$/)
427                 {
428                         $debug_parts_bitstring = unpack("B32", pack("N", $debug_parts));
429                 }
430                 else
431                 {
432                         print STDERR "Value '$debug_parts' invalid for option d (number expected)\n";
433                         $err_counter++;
434                 }
435         }
437         # Exit if an error occour
438     if( $err_counter > 0 ) { &usage( "", 1 ); }
442 #===  FUNCTION  ================================================================
443 #         NAME:  check_pid
444 #   PARAMETERS:  nothing
445 #      RETURNS:  nothing
446 #  DESCRIPTION:  handels pid processing
447 #===============================================================================
448 sub check_pid {
449     $pid = -1;
450     # Check, if we are already running
451     if( open(LOCK_FILE, "<$pid_file") ) {
452         $pid = <LOCK_FILE>;
453         if( defined $pid ) {
454             chomp( $pid );
455             if( -f "/proc/$pid/stat" ) {
456                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
457                 if( $stat ) {
458                                         print STDERR "\nERROR: Already running!\n";
459                     close( LOCK_FILE );
460                     exit -1;
461                 }
462             }
463         }
464         close( LOCK_FILE );
465         unlink( $pid_file );
466     }
468     # create a syslog msg if it is not to possible to open PID file
469     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
470         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
471         if (open(LOCK_FILE, '<', $pid_file)
472                 && ($pid = <LOCK_FILE>))
473         {
474             chomp($pid);
475             $msg .= "(PID $pid)\n";
476         } else {
477             $msg .= "(unable to read PID)\n";
478         }
479         if( ! ($foreground) ) {
480             openlog( $0, "cons,pid", "daemon" );
481             syslog( "warning", $msg );
482             closelog();
483         }
484         else {
485             print( STDERR " $msg " );
486         }
487         exit( -1 );
488     }
491 #===  FUNCTION  ================================================================
492 #         NAME:  import_modules
493 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
494 #                are stored
495 #      RETURNS:  nothing
496 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
497 #                state is on is imported by "require 'file';"
498 #===============================================================================
499 sub import_modules {
500     if (not -e $modules_path) {
501         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
502     }
504     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
506     while (defined (my $file = readdir (DIR))) {
507         if (not $file =~ /(\S*?).pm$/) {
508             next;
509         }
510                 my $mod_name = $1;
512         # ArpHandler switch
513         if( $file =~ /ArpHandler.pm/ ) {
514             if( $arp_enabled eq "false" ) { next; }
515         }
517                 # ServerPackages switch
518                 if ($file eq "ServerPackages.pm" && $serverPackages_enabled eq "false") 
519                 {
520                         $dns_lookup = "false";
521                         next; 
522                 }
523         
524         eval { require $file; };
525         if ($@) {
526             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
527             daemon_log("$@", 1);
528             exit;
529                 } else {
530                         my $info = eval($mod_name.'::get_module_info()');
531                         # Only load module if get_module_info() returns a non-null object
532                         if( $info ) {
533                                 my ($input_address, $input_key, $event_hash) = @{$info};
534                                 $known_modules->{$mod_name} = $info;
535                                 daemon_log("0 INFO: module $mod_name loaded", 5);
536                         }
537                 }
538     }   
539     close (DIR);
542 #===  FUNCTION  ================================================================
543 #         NAME:  password_check
544 #   PARAMETERS:  nothing
545 #      RETURNS:  nothing
546 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
547 #                the same password
548 #===============================================================================
549 sub password_check {
550     my $passwd_hash = {};
551     while (my ($mod_name, $mod_info) = each %$known_modules) {
552         my $mod_passwd = @$mod_info[1];
553         if (not defined $mod_passwd) { next; }
554         if (not exists $passwd_hash->{$mod_passwd}) {
555             $passwd_hash->{$mod_passwd} = $mod_name;
557         # escalates critical error
558         } else {
559             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
560             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
561             exit( -1 );
562         }
563     }
568 #===  FUNCTION  ================================================================
569 #         NAME:  sig_int_handler
570 #   PARAMETERS:  signal - string - signal arose from system
571 #      RETURNS:  nothing
572 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
573 #===============================================================================
574 sub sig_int_handler {
575     my ($signal) = @_;
577 #       if (defined($ldap_handle)) {
578 #               $ldap_handle->disconnect;
579 #       }
580     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
581     
583     daemon_log("shutting down gosa-si-server", 1);
584     system("kill `ps -C gosa-si-server -o pid=`");
586 $SIG{INT} = \&sig_int_handler;
589 sub check_key_and_xml_validity {
590     my ($crypted_msg, $module_key, $session_id) = @_;
591     my $msg;
592     my $msg_hash;
593     my $error_string;
594     eval{
595         $msg = &decrypt_msg($crypted_msg, $module_key);
597         if ($msg =~ /<xml>/i){
598             $msg =~ s/\s+/ /g;  # just for better daemon_log
599             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 18);
600             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
602             ##############
603             # check header
604             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
605             my $header_l = $msg_hash->{'header'};
606             if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
607             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
608             my $header = @{$header_l}[0];
609             if( 0 == length $header) { die 'empty string in header tag'; }
611             ##############
612             # check source
613             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
614             my $source_l = $msg_hash->{'source'};
615             if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
616             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
617             my $source = @{$source_l}[0];
618             if( 0 == length $source) { die 'source error'; }
620             ##############
621             # check target
622             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
623             my $target_l = $msg_hash->{'target'};
624             if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
625         }
626     };
627     if($@) {
628         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
629         $msg = undef;
630         $msg_hash = undef;
631     }
633     return ($msg, $msg_hash);
637 sub check_outgoing_xml_validity {
638     my ($msg, $session_id) = @_;
640     my $msg_hash;
641     eval{
642         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
644         ##############
645         # check header
646         my $header_l = $msg_hash->{'header'};
647         if( 1 != @{$header_l} ) {
648             die 'no or more than one headers specified';
649         }
650         my $header = @{$header_l}[0];
651         if( 0 == length $header) {
652             die 'header has length 0';
653         }
655         ##############
656         # check source
657         my $source_l = $msg_hash->{'source'};
658         if( 1 != @{$source_l} ) {
659             die 'no or more than 1 sources specified';
660         }
661         my $source = @{$source_l}[0];
662         if( 0 == length $source) {
663             die 'source has length 0';
664         }
666                 # Check if source contains hostname instead of ip address
667                 if($source =~ /^[a-z][\w\-\.]+:\d+$/i) {
668                         my ($hostname,$port) = split(/:/, $source);
669                         my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
670                         if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
671                                 # Write ip address to $source variable
672                                 $source = "$ip_address:$port";
673                                 $msg_hash->{source}[0] = $source ;
674                                 $msg =~ s/<source>.*<\/source>/<source>$source<\/source>/; 
675                         }
676                 }
677         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
678                 $source =~ /^GOSA$/i) {
679             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
680         }
681         
682         ##############
683         # check target  
684         my $target_l = $msg_hash->{'target'};
685         if( 0 == @{$target_l} ) {
686             die "no targets specified";
687         }
688         foreach my $target (@$target_l) {
689             if( 0 == length $target) {
690                 die "target has length 0";
691             }
692             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
693                     $target =~ /^GOSA$/i ||
694                     $target =~ /^\*$/ ||
695                     $target =~ /KNOWN_SERVER/i ||
696                     $target =~ /JOBDB/i ||
697                     $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 ){
698                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
699             }
700         }
701     };
702     if($@) {
703         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
704         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
705         $msg_hash = undef;
706     }
708     return ($msg, $msg_hash);
712 sub input_from_known_server {
713     my ($input, $remote_ip, $session_id) = @_ ;  
714     my ($msg, $msg_hash, $module);
716     my $sql_statement= "SELECT * FROM known_server";
717     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
719     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
720         my $host_name = $hit->{hostname};
721         if( not $host_name =~ "^$remote_ip") {
722             next;
723         }
724         my $host_key = $hit->{hostkey};
725         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 14);
726         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 14);
728         # check if module can open msg envelope with module key
729         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
730         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
731             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 14);
732             daemon_log("$@", 14);
733             next;
734         }
735         else {
736             $msg = $tmp_msg;
737             $msg_hash = $tmp_msg_hash;
738             $module = "ServerPackages";
739             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
740             last;
741         }
742     }
744     if( (!$msg) || (!$msg_hash) || (!$module) ) {
745         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 14);
746     }
747   
748     return ($msg, $msg_hash, $module);
752 sub input_from_known_client {
753     my ($input, $remote_ip, $session_id) = @_ ;  
754     my ($msg, $msg_hash, $module);
756     my $sql_statement= "SELECT * FROM known_clients";
757     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
758     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
759         my $host_name = $hit->{hostname};
760         if( not $host_name =~ /^$remote_ip/) {
761                 next;
762                 }
763         my $host_key = $hit->{hostkey};
764         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 14);
765         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 14);
767         # check if module can open msg envelope with module key
768         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
770         if( (!$msg) || (!$msg_hash) ) {
771             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 14);
772             next;
773         }
774         else {
775             $module = "ClientPackages";
776             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
777             last;
778         }
779     }
781     if( (!$msg) || (!$msg_hash) || (!$module) ) {
782         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 14);
783     }
785     return ($msg, $msg_hash, $module);
789 sub input_from_unknown_host {
790         no strict "refs";
791         my ($input, $session_id) = @_ ;
792         my ($msg, $msg_hash, $module);
793         my $error_string;
795         my %act_modules = %$known_modules;
797         while( my ($mod, $info) = each(%act_modules)) {
799                 # check a key exists for this module
800                 my $module_key = ${$mod."_key"};
801                 if( not defined $module_key ) {
802                         if( $mod eq 'ArpHandler' ) {
803                                 next;
804                         }
805                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
806                         next;
807                 }
808                 daemon_log("$session_id DEBUG: $mod: $module_key", 14);
810                 # check if module can open msg envelope with module key
811                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
812                 if( (not defined $msg) || (not defined $msg_hash) ) {
813                         next;
814                 } else {
815                         $module = $mod;
816             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 18);
817                         last;
818                 }
819         }
821         if( (!$msg) || (!$msg_hash) || (!$module)) {
822                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 14);
823         }
825         return ($msg, $msg_hash, $module);
829 sub create_ciphering {
830     my ($passwd) = @_;
831         if((!defined($passwd)) || length($passwd)==0) {
832                 $passwd = "";
833         }
834     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
835     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
836     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
837     $my_cipher->set_iv($iv);
838     return $my_cipher;
842 sub encrypt_msg {
843     my ($msg, $key) = @_;
844     my $my_cipher = &create_ciphering($key);
845     my $len;
846     {
847             use bytes;
848             $len= 16-length($msg)%16;
849     }
850     $msg = "\0"x($len).$msg;
851     $msg = $my_cipher->encrypt($msg);
852     chomp($msg = &encode_base64($msg));
853     # there are no newlines allowed inside msg
854     $msg=~ s/\n//g;
855     return $msg;
859 sub decrypt_msg {
861     my ($msg, $key) = @_ ;
862     $msg = &decode_base64($msg);
863     my $my_cipher = &create_ciphering($key);
864     $msg = $my_cipher->decrypt($msg); 
865     $msg =~ s/\0*//g;
866     return $msg;
870 sub get_encrypt_key {
871     my ($target) = @_ ;
872     my $encrypt_key;
873     my $error = 0;
875     # target can be in known_server
876     if( not defined $encrypt_key ) {
877         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
878         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
879         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
880             my $host_name = $hit->{hostname};
881             if( $host_name ne $target ) {
882                 next;
883             }
884             $encrypt_key = $hit->{hostkey};
885             last;
886         }
887     }
889     # target can be in known_client
890     if( not defined $encrypt_key ) {
891         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
892         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
893         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
894             my $host_name = $hit->{hostname};
895             if( $host_name ne $target ) {
896                 next;
897             }
898             $encrypt_key = $hit->{hostkey};
899             last;
900         }
901     }
903     return $encrypt_key;
907 #===  FUNCTION  ================================================================
908 #         NAME:  open_socket
909 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
910 #                [PeerPort] string necessary if port not appended by PeerAddr
911 #      RETURNS:  socket IO::Socket::INET
912 #  DESCRIPTION:  open a socket to PeerAddr
913 #===============================================================================
914 sub open_socket {
915     my ($PeerAddr, $PeerPort) = @_ ;
916     if(defined($PeerPort)){
917         $PeerAddr = $PeerAddr.":".$PeerPort;
918     }
919     my $socket;
920     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
921             Porto => "tcp",
922             Type => SOCK_STREAM,
923             Timeout => 5,
924             );
925     if(not defined $socket) {
926         return;
927     }
928 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
929     return $socket;
933 sub send_msg_to_target {
934     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
935     my $error = 0;
936     my $header;
937     my $timestamp = &get_time();
938     my $new_status;
939     my $act_status;
940     my ($sql_statement, $res);
941   
942     if( $msg_header ) {
943         $header = "'$msg_header'-";
944     } else {
945         $header = "";
946     }
948         # Memorize own source address
949         my $own_source_address = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
950         $own_source_address .= ":".$server_port;
952         # Patch 0.0.0.0 source to real address
953         $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$own_source_address<\/source>/s;
954         # Patch GOSA source to real address and add forward_to_gosa tag
955         $msg =~ s/<source>GOSA<\/source>/<source>$own_source_address<\/source> <forward_to_gosa>$own_source_address,$session_id<\/forward_to_gosa>/ ;
957     # encrypt xml msg
958     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
960     # opensocket
961     my $socket = &open_socket($address);
962     if( !$socket ) {
963         daemon_log("$session_id ERROR: Cannot open socket to host '$address'. Message processing aborted!", 1);
964         $error++;
965     }
966     
967     if( $error == 0 ) {
968         # send xml msg
969         print $socket $crypted_msg.";$own_source_address\n";
970         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
971         daemon_log("$session_id DEBUG: message:\n$msg", 12);
972         
973     }
975     # close socket in any case
976     if( $socket ) {
977         close $socket;
978     }
980     if( $error > 0 ) { $new_status = "down"; }
981     else { $new_status = $msg_header; }
984     # known_clients
985     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
986     $res = $known_clients_db->select_dbentry($sql_statement);
987     if( keys(%$res) == 1) {
988         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
989         if ($act_status eq "down" && $new_status eq "down") {
990             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
991             $res = $known_clients_db->del_dbentry($sql_statement);
992             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
993         } else { 
994             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
995             $res = $known_clients_db->update_dbentry($sql_statement);
996             if($new_status eq "down"){
997                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
998             } else {
999                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
1000             }
1001         }
1002     }
1004     # known_server
1005     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
1006     $res = $known_server_db->select_dbentry($sql_statement);
1007     if( keys(%$res) == 1) {
1008         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1009         if ($act_status eq "down" && $new_status eq "down") {
1010             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
1011             $res = $known_server_db->del_dbentry($sql_statement);
1012             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
1013         } 
1014         else { 
1015             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1016             $res = $known_server_db->update_dbentry($sql_statement);
1017             if($new_status eq "down"){
1018                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1019             } else {
1020                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
1021             }
1022         }
1023     }
1024     return $error; 
1028 sub update_jobdb_status_for_send_msgs {
1029     my ($session_id, $answer, $error) = @_;
1030     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1031                 &daemon_log("$session_id DEBUG: try to update job status", 138); 
1032         my $jobdb_id = $1;
1033     
1034         $answer =~ /<header>(.*)<\/header>/;
1035         my $job_header = $1;
1037         $answer =~ /<target>(.*)<\/target>/;
1038         my $job_target = $1;
1039             
1040         # Sending msg failed
1041         if( $error ) {
1043             # Set jobs to done, jobs do not need to deliver their message in any case
1044             if (($job_header eq "trigger_action_localboot")
1045                     ||($job_header eq "trigger_action_lock")
1046                     ||($job_header eq "trigger_action_halt") 
1047                     ) {
1048                 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1049                 my $res = $job_db->update_dbentry($sql_statement);
1050                 
1051             # Reactivate jobs, jobs need to deliver their message
1052             } elsif (($job_header eq "trigger_action_activate")
1053                     ||($job_header eq "trigger_action_update")
1054                     ||($job_header eq "trigger_action_reinstall") 
1055                     ||($job_header eq "trigger_activate_new")
1056                     ) {
1057                 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1059             # For all other messages
1060             } else {
1061                 my $sql_statement = "UPDATE $job_queue_tn ".
1062                     "SET status='error', result='can not deliver msg, please consult log file' ".
1063                     "WHERE id=$jobdb_id";
1064                 my $res = $job_db->update_dbentry($sql_statement);
1065             }
1067         # Sending msg was successful
1068         } else {
1069             # Set jobs localboot, lock, activate, halt, reboot and wake to done
1070             # jobs reinstall, update, inst_update do themself setting to done
1071             if (($job_header eq "trigger_action_localboot")
1072                     ||($job_header eq "trigger_action_lock")
1073                     ||($job_header eq "trigger_action_activate")
1074                     ||($job_header eq "trigger_action_halt") 
1075                     ||($job_header eq "trigger_action_reboot")
1076                     ||($job_header eq "trigger_action_wake")
1077                     ||($job_header eq "trigger_wake")
1078                     ) {
1080                 my $sql_statement = "UPDATE $job_queue_tn ".
1081                     "SET status='done' ".
1082                     "WHERE id=$jobdb_id AND status='processed'";
1083                 my $res = $job_db->update_dbentry($sql_statement);
1084             } else { 
1085                 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 138); 
1086             } 
1087         } 
1088     } else { 
1089         &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag.", 138); 
1090     }
1093 sub reactivate_job_with_delay {
1094     my ($session_id, $target, $header, $delay) = @_ ;
1095     # 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
1096     
1097     if (not defined $delay) { $delay = 30 } ;
1098     my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1100     my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress LIKE 'target' AND headertag='$header')"; 
1101     my $res = $job_db->update_dbentry($sql);
1102     daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1103             "cause client '$target' is currently not available", 5);
1104     return;
1108 sub sig_handler {
1109         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1110         daemon_log("0 INFO got signal '$signal'", 1); 
1111         $kernel->sig_handled();
1112         return;
1116 sub msg_to_decrypt {
1117         my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1118         my $session_id = $session->ID;
1119         my ($msg, $msg_hash, $module);
1120         my $error = 0;
1122         # fetch new msg out of @msgs_to_decrypt
1123         my $tmp_next_msg = shift @msgs_to_decrypt;
1124     my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1126         # msg is from a new client or gosa
1127         ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1129         # msg is from a gosa-si-server
1130         if(((!$msg) || (!$msg_hash) || (!$module)) && ($serverPackages_enabled eq "true")){
1131                 if (not defined $msg_source) 
1132                 {
1133                         # Only needed, to be compatible with older gosa-si-server versions
1134                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1135                 }
1136                 else
1137                 {
1138                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $msg_source, $session_id);
1139                 }
1140         }
1141         # msg is from a gosa-si-client
1142         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1143                 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $msg_source, $session_id);
1144         }
1145         # an error occurred
1146         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1147                 # If an incoming msg could not be decrypted (maybe a wrong key), decide if msg comes from a client 
1148                 # or a server.  In case of a client, send a ping. If the client could not understand a msg from its 
1149                 # server the client cause a re-registering process. In case of a server, decrease update_time in kown_server_db
1150                 # and trigger a re-registering process for servers
1151                 if (defined $msg_source && $msg_source =~ /:$server_port$/ && $serverPackages_enabled eq "true")
1152                 {
1153                         daemon_log("$session_id WARNING: Cannot understand incoming msg from server '$msg_source'. Cause re-registration process for servers.", 3);
1154                         my $update_statement = "UPDATE $known_server_tn SET update_time='19700101000000' WHERE hostname='$msg_source'"; 
1155                         daemon_log("$session_id DEBUG: $update_statement", 7);
1156                         my $upadte_res = $known_server_db->exec_statement($update_statement);
1157                         $kernel->yield("register_at_foreign_servers");
1158                 }
1159                 elsif ((defined $msg_source) && (not $msg_source =~ /:$server_port$/))
1160                 {
1161                         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);
1162                         #my $remote_ip = $heap->{'remote_ip'};
1163                         #my $remote_port = $heap->{'remote_port'};
1164                         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1165                         my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1166                         daemon_log("$session_id WARNING: Sending msg to cause re-registering: $ping_msg", 3);
1167                 }
1168                 else
1169                 {
1170                         my $foreign_host = defined $msg_source ? $msg_source : $heap->{'remote_ip'};
1171                         daemon_log("$session_id ERROR: Incoming message from host '$foreign_host' cannot be understood. Processing aborted!", 1);
1172                         daemon_log("$session_id DEBUG: Aborted message: $tmp_next_msg", 11);
1173                 }
1175                 $error++
1176         }
1179         my $header;
1180         my $target;
1181         my $source;
1182         my $done = 0;
1183         my $sql;
1184         my $res;
1186         # check whether this message should be processed here
1187         if ($error == 0) {
1188                 $header = @{$msg_hash->{'header'}}[0];
1189                 $target = @{$msg_hash->{'target'}}[0];
1190                 $source = @{$msg_hash->{'source'}}[0];
1191                 my $not_found_in_known_clients_db = 0;
1192                 my $not_found_in_known_server_db = 0;
1193                 my $not_found_in_foreign_clients_db = 0;
1194                 my $local_address;
1195                 my $local_mac;
1196                 my ($target_ip, $target_port) = split(':', $target);
1198                 # Determine the local ip address if target is an ip address
1199                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1200                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1201                 } else {
1202                         $local_address = $server_address;
1203                 }
1205                 # Determine the local mac address if target is a mac address
1206                 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) {
1207                         my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1208                         my $network_interface= &get_interface_for_ip($loc_ip);
1209                         $local_mac = &get_mac_for_interface($network_interface);
1210                 } else {
1211                         $local_mac = $server_mac_address;
1212                 }
1214                 # target and source is equal to GOSA -> process here
1215                 if (not $done) {
1216                         if ($target eq "GOSA" && $source eq "GOSA") {
1217                                 $done = 1;                    
1218                                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process '$header' here", 11);
1219                         }
1220                 }
1222                 # target is own address without forward_to_gosa-tag -> process here
1223                 if (not $done) {
1224                         #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1225                         if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1226                                 $done = 1;
1227                                 if ($source eq "GOSA") {
1228                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1229                                 }
1230                                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process '$header' here", 11);
1231                         }
1232                 }
1234                 # target is own address with forward_to_gosa-tag not pointing to myself -> process here
1235                 if (not $done) {
1236                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1237                         my $gosa_at;
1238                         my $gosa_session_id;
1239                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1240                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1241                                 if ($gosa_at ne $local_address) {
1242                                         $done = 1;
1243                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process '$header' here", 11); 
1244                                 }
1245                         }
1246                 }
1248                 # Target is a client address and there is a processing function within a plugin -> process loaclly
1249                 if (not $done)
1250                 {
1251                         # Check if target is a client address
1252                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1253                         $res = $known_clients_db->select_dbentry($sql);
1254                         if ((keys(%$res) > 0) ) 
1255                         {
1256                                 my $hostname = $res->{1}->{'hostname'};
1257                                 my $reduced_header = $header;
1258                                 $reduced_header =~ s/gosa_//;
1259                                 # Check if there is a processing function within a plugin
1260                                 if (exists $known_functions->{$reduced_header}) 
1261                                 {
1262                                         $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1263                                         $done = 1;
1264                                         &daemon_log("$session_id DEBUG: Target is client address with processing function within a plugin -> process '$header' here", 11);
1265                                 }
1266                         }
1267                 }
1269                 # If header has a 'job_' prefix, do always process message locally
1270                 # which means put it into job queue
1271                 if ((not $done) && ($header =~ /job_/))
1272                 {
1273                         $done = 1;
1274                         &daemon_log("$session_id DEBUG: Header has a 'job_' prefix. Put it into job queue. -> process '$header' here", 11);
1275                 }
1277                 # if message should be processed here -> add message to incoming_db
1278                 if ($done) {
1279                         # if a 'job_' or a 'gosa_' message comes from a foreign server, fake module from
1280                         # ServerPackages to GosaPackages so gosa-si-server knows how to process this kind of messages
1281                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1282                                 $module = "GosaPackages";
1283                         }
1285                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1286                                         primkey=>[],
1287                                         headertag=>$header,
1288                                         targettag=>$target,
1289                                         xmlmessage=>&encode_base64($msg),
1290                                         timestamp=>&get_time,
1291                                         module=>$module,
1292                                         sessionid=>$session_id,
1293                                 } );
1295                 }
1297                 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1298                 if (not $done) {
1299                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1300                         my $gosa_at;
1301                         my $gosa_session_id;
1302                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1303                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1304                                 if ($gosa_at eq $local_address) {
1305                                         my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1306                                         if( defined $session_reference ) {
1307                                                 $heap = $session_reference->get_heap();
1308                                         }
1309                                         if(exists $heap->{'client'}) {
1310                                                 $msg = &encrypt_msg($msg, $GosaPackages_key);
1311                                                 $heap->{'client'}->put($msg);
1312                                                 &daemon_log("$session_id DEBUG: incoming '$header' message forwarded to GOsa", 11); 
1313                                         }
1314                                         $done = 1;
1315                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward '$header' to gosa", 11);
1316                                 }
1317                         }
1319                 }
1321                 # target is a client address in known_clients -> forward to client
1322                 if (not $done) {
1323                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1324                         $res = $known_clients_db->select_dbentry($sql);
1325                         if (keys(%$res) > 0) 
1326                         {
1327                                 $done = 1; 
1328                                 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> forward '$header' to client", 11);
1329                                 my $hostkey = $res->{1}->{'hostkey'};
1330                                 my $hostname = $res->{1}->{'hostname'};
1331                                 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1332                                 $msg =~ s/<header>gosa_/<header>/;
1333                                 my $error= &send_msg_to_target($msg, $hostname, $hostkey, $header, $session_id);
1334                                 if ($error) {
1335                                         &daemon_log("$session_id ERROR: Some problems occurred while trying to send msg to client '$hostname': $msg", 1);
1336                                 }
1337                         } 
1338                         else 
1339                         {
1340                                 $not_found_in_known_clients_db = 1;
1341                         }
1342                 }
1344                 # target is a client address in foreign_clients -> forward to registration server
1345                 if (not $done) {
1346                         $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1347                         $res = $foreign_clients_db->select_dbentry($sql);
1348                         if (keys(%$res) > 0) {
1349                                 my $hostname = $res->{1}->{'hostname'};
1350                                 my ($host_ip, $host_port) = split(/:/, $hostname);
1351                                 my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1352                                 my $regserver = $res->{1}->{'regserver'};
1353                                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1354                                 my $res = $known_server_db->select_dbentry($sql);
1355                                 if (keys(%$res) > 0) {
1356                                         my $regserver_key = $res->{1}->{'hostkey'};
1357                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1358                                         $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1359                                         if ($source eq "GOSA") {
1360                                                 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1361                                         }
1362                                         my $error= &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1363                                         if ($error) {
1364                                                 &daemon_log("$session_id ERROR: some problems occurred while trying to send msg to registration server: $msg", 1); 
1365                                         }
1366                                 }
1367                                 $done = 1;
1368                                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward '$header' to registration server", 11);
1369                         } else {
1370                                 $not_found_in_foreign_clients_db = 1;
1371                         }
1372                 }
1374                 # target is a server address -> forward to server
1375                 if (not $done) {
1376                         $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1377                         $res = $known_server_db->select_dbentry($sql);
1378                         if (keys(%$res) > 0) {
1379                                 my $hostkey = $res->{1}->{'hostkey'};
1381                                 if ($source eq "GOSA") {
1382                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1383                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1385                                 }
1387                                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1388                                 $done = 1;
1389                                 &daemon_log("$session_id DEBUG: target is a server address -> forward '$header' to server", 11);
1390                         } else {
1391                                 $not_found_in_known_server_db = 1;
1392                         }
1393                 }
1396                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1397                 if ( $not_found_in_foreign_clients_db 
1398                         && $not_found_in_known_server_db
1399                         && $not_found_in_known_clients_db) {
1400                         &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);
1401             if ($header =~ /^gosa_/ || $header =~ /^job_/) { 
1402                 $module = "GosaPackages"; 
1403             }
1404                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1405                                         primkey=>[],
1406                                         headertag=>$header,
1407                                         targettag=>$target,
1408                                         xmlmessage=>&encode_base64($msg),
1409                                         timestamp=>&get_time,
1410                                         module=>$module,
1411                                         sessionid=>$session_id,
1412                                 } );
1413                         $done = 1;
1414                 }
1417                 if (not $done) {
1418                         daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1419                         if ($source eq "GOSA") {
1420                                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1421                                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1423                                 my $session_reference = $kernel->ID_id_to_session($session_id);
1424                                 if( defined $session_reference ) {
1425                                         $heap = $session_reference->get_heap();
1426                                 }
1427                                 if(exists $heap->{'client'}) {
1428                                         $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1429                                         $heap->{'client'}->put($error_msg);
1430                                 }
1431                         }
1432                 }
1434         }
1436         return;
1440 sub next_task {
1441     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0, ARG1];
1442     my $running_task = POE::Wheel::Run->new(
1443             Program => sub { process_task($session, $heap, $task) },
1444             StdioFilter => POE::Filter::Reference->new(),
1445             StdoutEvent  => "task_result",
1446             StderrEvent  => "task_debug",
1447             CloseEvent   => "task_done",
1448             );
1449     $heap->{task}->{ $running_task->ID } = $running_task;
1452 sub handle_task_result {
1453     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1454     my $client_answer = $result->{'answer'};
1455     if( $client_answer =~ s/session_id=(\d+)$// ) {
1456         my $session_id = $1;
1457         if( defined $session_id ) {
1458             my $session_reference = $kernel->ID_id_to_session($session_id);
1459             if( defined $session_reference ) {
1460                 $heap = $session_reference->get_heap();
1461             }
1462         }
1464         if(exists $heap->{'client'}) {
1465             $heap->{'client'}->put($client_answer);
1466         }
1467     }
1468     $kernel->sig(CHLD => "child_reap");
1471 sub handle_task_debug {
1472     my $result = $_[ARG0];
1473     print STDERR "$result\n";
1476 sub handle_task_done {
1477     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1478     delete $heap->{task}->{$task_id};
1479         if (exists $heap->{ldap_handle}->{$task_id}) {
1480                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
1481         }
1484 sub process_task {
1485     no strict "refs";
1486     #CHECK: Not @_[...]?
1487     my ($session, $heap, $task) = @_;
1488     my $error = 0;
1489     my $answer_l;
1490     my ($answer_header, @answer_target_l, $answer_source);
1491     my $client_answer = "";
1493     # prepare all variables needed to process message
1494     #my $msg = $task->{'xmlmessage'};
1495     my $msg = &decode_base64($task->{'xmlmessage'});
1496     my $incoming_id = $task->{'id'};
1497     my $module = $task->{'module'};
1498     my $header =  $task->{'headertag'};
1499     my $session_id = $task->{'sessionid'};
1500                 my $msg_hash;
1501                 eval {
1502         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1503                 }; 
1504                 daemon_log("ERROR: XML failure '$@'") if ($@);
1505     my $source = @{$msg_hash->{'source'}}[0];
1506     
1507     # set timestamp of incoming client uptodate, so client will not 
1508     # be deleted from known_clients because of expiration
1509     my $cur_time = &get_time();
1510     my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'"; 
1511     my $res = $known_clients_db->exec_statement($sql);
1513     ######################
1514     # process incoming msg
1515     if( $error == 0) {
1516         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1517         daemon_log("$session_id DEBUG: Processing module ".$module, 26);
1518         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1520         if ( 0 < @{$answer_l} ) {
1521             my $answer_str = join("\n", @{$answer_l});
1522                         my @headers; 
1523             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1524                                 push(@headers, $1);
1525             }
1526                         daemon_log("$session_id INFO: got answer message(s) with header: '".join("', '", @headers)."'", 5);
1527             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,26);
1528         } else {
1529             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,26);
1530         }
1532     }
1533     if( !$answer_l ) { $error++ };
1535     ########
1536     # answer
1537     if( $error == 0 ) {
1539         foreach my $answer ( @{$answer_l} ) {
1540             # check outgoing msg to xml validity
1541             my ($answer, $answer_hash) = &check_outgoing_xml_validity($answer, $session_id);
1542             if( not defined $answer_hash ) { next; }
1543             
1544             $answer_header = @{$answer_hash->{'header'}}[0];
1545             @answer_target_l = @{$answer_hash->{'target'}};
1546             $answer_source = @{$answer_hash->{'source'}}[0];
1548             # deliver msg to all targets 
1549             foreach my $answer_target ( @answer_target_l ) {
1551                 # targets of msg are all gosa-si-clients in known_clients_db
1552                 if( $answer_target eq "*" ) {
1553                     # answer is for all clients
1554                     my $sql_statement= "SELECT * FROM known_clients";
1555                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1556                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1557                         my $host_name = $hit->{hostname};
1558                         my $host_key = $hit->{hostkey};
1559                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1560                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1561                     }
1562                 }
1564                 # targets of msg are all gosa-si-server in known_server_db
1565                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1566                     # answer is for all server in known_server
1567                     my $sql_statement= "SELECT * FROM $known_server_tn";
1568                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1569                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1570                         my $host_name = $hit->{hostname};
1571                         my $host_key = $hit->{hostkey};
1572                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1573                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1574                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1575                     }
1576                 }
1578                 # target of msg is GOsa
1579                                 elsif( $answer_target eq "GOSA" ) {
1580                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1581                                         my $add_on = "";
1582                     if( defined $session_id ) {
1583                         $add_on = ".session_id=$session_id";
1584                     }
1585                     # answer is for GOSA and has to returned to connected client
1586                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1587                     $client_answer = $gosa_answer.$add_on;
1588                 }
1590                 # target of msg is job queue at this host
1591                 elsif( $answer_target eq "JOBDB") {
1592                     $answer =~ /<header>(\S+)<\/header>/;   
1593                     my $header;
1594                     if( defined $1 ) { $header = $1; }
1595                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1596                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1597                 }
1599                 # Target of msg is a mac address
1600                 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 ) {
1601                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1603                     # Looking for macaddress in known_clients
1604                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1605                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1606                     my $found_ip_flag = 0;
1607                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1608                         my $host_name = $hit->{hostname};
1609                         my $host_key = $hit->{hostkey};
1610                         $answer =~ s/$answer_target/$host_name/g;
1611                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1612                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1613                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1614                         $found_ip_flag++ ;
1615                     }   
1617                     # Looking for macaddress in foreign_clients
1618                     if ($found_ip_flag == 0) {
1619                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1620                         my $res = $foreign_clients_db->select_dbentry($sql);
1621                         while( my ($hit_num, $hit) = each %{ $res } ) {
1622                             my $host_name = $hit->{hostname};
1623                             my $reg_server = $hit->{regserver};
1624                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1625                             
1626                             # Fetch key for reg_server
1627                             my $reg_server_key;
1628                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1629                             my $res = $known_server_db->select_dbentry($sql);
1630                             if (exists $res->{1}) {
1631                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1632                             } else {
1633                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1634                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1635                                 $reg_server_key = undef;
1636                             }
1638                             # Send answer to server where client is registered
1639                             if (defined $reg_server_key) {
1640                                 $answer =~ s/$answer_target/$host_name/g;
1641                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1642                                 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1643                                 $found_ip_flag++ ;
1644                             }
1645                         }
1646                     }
1648                     # No mac to ip matching found
1649                     if( $found_ip_flag == 0) {
1650                         daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1651                         &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1652                     }
1654                 # Answer is for one specific host   
1655                 } else {
1656                     # get encrypt_key
1657                     my $encrypt_key = &get_encrypt_key($answer_target);
1658                     if( not defined $encrypt_key ) {
1659                         # unknown target
1660                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1661                         next;
1662                     }
1663                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1664                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1665                 }
1666             }
1667         }
1668     }
1670     my $filter = POE::Filter::Reference->new();
1671     my %result = ( 
1672             status => "seems ok to me",
1673             answer => $client_answer,
1674             );
1676     my $output = $filter->put( [ \%result ] );
1677     print @$output;
1682 sub session_start {
1683     my ($kernel) = $_[KERNEL];
1684     $global_kernel = $kernel;
1685     $kernel->yield('register_at_foreign_servers');
1686         $kernel->yield('create_fai_server_db', $fai_server_tn );
1687         $kernel->yield('create_fai_release_db', $fai_release_tn );
1688     $kernel->yield('watch_for_next_tasks');
1689         $kernel->sig(USR1 => "sig_handler");
1690         $kernel->sig(USR2 => "recreate_packages_db");
1691         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1692         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1693     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1694         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1695     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1696         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1697     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1699     # Start opsi check
1700     if ($opsi_enabled eq "true") {
1701         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1702     }
1707 sub watch_for_done_jobs {
1708         my $kernel = $_[KERNEL];
1710         my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1711         my $res = $job_db->select_dbentry( $sql_statement );
1713         while( my ($number, $hit) = each %{$res} ) 
1714         {
1715                 # Periodical jobs should not be deleted but reactivated with new timestamp instead.
1716                 if (exists $check_periodic->{$hit->{periodic}})
1717                 {
1718                         my $reactivated_ts = $hit->{timestamp};
1719                         my $act_ts = int(&get_time());
1720                         while ($act_ts > int($reactivated_ts))   # Redo calculation to avoid multiple jobs in the past
1721                         {
1722                                 $reactivated_ts = &calc_timestamp($reactivated_ts, "plus", 1, $hit->{periodic});
1723                         }
1724                         my $sql = "UPDATE $job_queue_tn SET status='waiting', timestamp='$reactivated_ts' WHERE id='".$hit->{id}."'"; 
1725                         my $res = $job_db->exec_statement($sql);
1726                         &daemon_log("J INFO: Update periodical job '".$hit->{headertag}."' for client '".$hit->{targettag}."'. New execution time '$reactivated_ts'.", 5);
1727                 }
1728                 # Non periodical jobs can be deleted.
1729                 else
1730                 {
1731                         my $jobdb_id = $hit->{id};
1732                         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1733                         my $res = $job_db->del_dbentry($sql_statement); 
1734                 }
1735         }
1737         $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1741 sub watch_for_opsi_jobs {
1742     my ($kernel) = $_[KERNEL];
1744     # 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 
1745     # opsi install job is to parse the xml message. There is still the correct header.
1746     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1747         my $res = $job_db->select_dbentry( $sql_statement );
1749     # Ask OPSI for an update of the running jobs
1750     while (my ($id, $hit) = each %$res ) {
1751         # Determine current parameters of the job
1752         my $hostId = $hit->{'plainname'};
1753         my $macaddress = $hit->{'macaddress'};
1754         my $progress = $hit->{'progress'};
1756         my $result= {};
1757         
1758         # For hosts, only return the products that are or get installed
1759         my $callobj;
1760         $callobj = {
1761             method  => 'getProductStates_hash',
1762             params  => [ $hostId ],
1763             id  => 1,
1764         };
1765         
1766         my $hres = $opsi_client->call($opsi_url, $callobj);
1767         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1768         if (not &check_opsi_res($hres)) {
1769             my $htmp= $hres->result->{$hostId};
1770         
1771             # Check state != not_installed or action == setup -> load and add
1772             my $products= 0;
1773             my $installed= 0;
1774             my $installing = 0;
1775             my $error= 0;  
1776             my @installed_list;
1777             my @error_list;
1778             my $act_status = "none";
1779             foreach my $product (@{$htmp}){
1781                 if ($product->{'installationStatus'} ne "not_installed" or
1782                         $product->{'actionRequest'} eq "setup"){
1784                     # Increase number of products for this host
1785                     $products++;
1786         
1787                     if ($product->{'installationStatus'} eq "failed"){
1788                         $result->{$product->{'productId'}}= "error";
1789                         unshift(@error_list, $product->{'productId'});
1790                         $error++;
1791                     }
1792                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1793                         $result->{$product->{'productId'}}= "installed";
1794                         unshift(@installed_list, $product->{'productId'});
1795                         $installed++;
1796                     }
1797                     if ($product->{'installationStatus'} eq "installing"){
1798                         $result->{$product->{'productId'}}= "installing";
1799                         $installing++;
1800                         $act_status = "installing - ".$product->{'productId'};
1801                     }
1802                 }
1803             }
1804         
1805             # Estimate "rough" progress, avoid division by zero
1806             if ($products == 0) {
1807                 $result->{'progress'}= 0;
1808             } else {
1809                 $result->{'progress'}= int($installed * 100 / $products);
1810             }
1812             # Set updates in job queue
1813             if ((not $error) && (not $installing) && ($installed)) {
1814                 $act_status = "installed - ".join(", ", @installed_list);
1815             }
1816             if ($error) {
1817                 $act_status = "error - ".join(", ", @error_list);
1818             }
1819             if ($progress ne $result->{'progress'} ) {
1820                 # Updating progress and result 
1821                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1822                 my $update_res = $job_db->update_dbentry($update_statement);
1823             }
1824             if ($progress eq 100) { 
1825                 # Updateing status
1826                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1827                 if ($error) {
1828                     $done_statement .= "status='error'";
1829                 } else {
1830                     $done_statement .= "status='done'";
1831                 }
1832                 $done_statement .= " WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1833                 my $done_res = $job_db->update_dbentry($done_statement);
1834             }
1837         }
1838     }
1840     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1844 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1845 sub watch_for_modified_jobs {
1846     my ($kernel,$heap) = @_[KERNEL, HEAP];
1848     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')"; 
1849     my $res = $job_db->select_dbentry( $sql_statement );
1850     
1851     # if db contains no jobs which should be update, do nothing
1852     if (keys %$res != 0) {
1854         if ($job_synchronization  eq "true") {
1855             # make out of the db result a gosa-si message   
1856             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1857  
1858             # update all other SI-server
1859             &inform_all_other_si_server($update_msg);
1860         }
1862         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1863         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1864         $res = $job_db->update_dbentry($sql_statement);
1865     }
1867     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1871 sub watch_for_new_jobs {
1872         if($watch_for_new_jobs_in_progress == 0) {
1873                 $watch_for_new_jobs_in_progress = 1;
1874                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1876                 # check gosa job queue for jobs with executable timestamp
1877                 my $timestamp = &get_time();
1878                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE siserver='localhost' AND status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1879                 my $res = $job_db->exec_statement( $sql_statement );
1881                 # Merge all new jobs that would do the same actions
1882                 my @drops;
1883                 my $hits;
1884                 foreach my $hit (reverse @{$res} ) {
1885                         my $macaddress= lc @{$hit}[8];
1886                         my $headertag= @{$hit}[5];
1887                         if(
1888                                 defined($hits->{$macaddress}) &&
1889                                 defined($hits->{$macaddress}->{$headertag}) &&
1890                                 defined($hits->{$macaddress}->{$headertag}[0])
1891                         ) {
1892                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1893                         }
1894                         $hits->{$macaddress}->{$headertag}= $hit;
1895                 }
1897                 # Delete new jobs with a matching job in state 'processing'
1898                 foreach my $macaddress (keys %{$hits}) {
1899                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1900                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1901                                 if(defined($jobdb_id)) {
1902                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1903                                         my $res = $job_db->exec_statement( $sql_statement );
1904                                         foreach my $hit (@{$res}) {
1905                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1906                                         }
1907                                 } else {
1908                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1909                                 }
1910                         }
1911                 }
1913                 # Commit deletion
1914                 $job_db->exec_statementlist(\@drops);
1916                 # Look for new jobs that could be executed
1917                 foreach my $macaddress (keys %{$hits}) {
1919                         # Look if there is an executing job
1920                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1921                         my $res = $job_db->exec_statement( $sql_statement );
1923                         # Skip new jobs for host if there is a processing job
1924                         if(defined($res) and defined @{$res}[0]) {
1925                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1926                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1927                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1928                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1929                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1930                                         if(defined($res_2) and defined @{$res_2}[0]) {
1931                                                 # Set status from goto-activation to 'waiting' and update timestamp
1932                                                 $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'");
1933                                         }
1934                                 }
1935                                 next;
1936                         }
1938                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1939                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1940                                 if(defined($jobdb_id)) {
1941                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1943                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1944                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1945                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1947                                         # expect macaddress is unique!!!!!!
1948                                         my $target = $res_hash->{1}->{hostname};
1950                                         # change header
1951                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1953                                         # add sqlite_id
1954                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1956                                         $job_msg =~ /<header>(\S+)<\/header>/;
1957                                         my $header = $1 ;
1958                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");                    
1960                                         # update status in job queue to ...
1961                     # ... 'processing', for jobs: 'reinstall', 'update', activate_new
1962                     if (($header =~ /gosa_trigger_action_reinstall/) 
1963                             || ($header =~ /gosa_trigger_activate_new/)
1964                             || ($header =~ /gosa_trigger_action_update/)) {
1965                         my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1966                         my $dbres = $job_db->update_dbentry($sql_statement);
1967                     }
1969                     # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1970                     else {
1971                         my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1972                         my $dbres = $job_db->exec_statement($sql_statement);
1973                     }
1974                 
1976                                         # We don't want parallel processing
1977                                         last;
1978                                 }
1979                         }
1980                 }
1982                 $watch_for_new_jobs_in_progress = 0;
1983                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1984         }
1988 sub watch_for_new_messages {
1989     my ($kernel,$heap) = @_[KERNEL, HEAP];
1990     my @coll_user_msg;   # collection list of outgoing messages
1991     
1992     # check messaging_db for new incoming messages with executable timestamp
1993     my $timestamp = &get_time();
1994     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1995     my $res = $messaging_db->exec_statement( $sql_statement );
1996         foreach my $hit (@{$res}) {
1998         # create outgoing messages
1999         my $message_to = @{$hit}[3];
2000         # translate message_to to plain login name
2001         my @message_to_l = split(/,/, $message_to);  
2002                 my %receiver_h; 
2003                 foreach my $receiver (@message_to_l) {
2004                         if ($receiver =~ /^u_([\s\S]*)$/) {
2005                                 $receiver_h{$1} = 0;
2006                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
2007                                 my $group_name = $1;
2008                                 # fetch all group members from ldap and add them to receiver hash
2009                                 my $ldap_handle = &get_ldap_handle();
2010                                 if (defined $ldap_handle) {
2011                                                 my $mesg = $ldap_handle->search(
2012                                                                                 base => $ldap_base,
2013                                                                                 scope => 'sub',
2014                                                                                 attrs => ['memberUid'],
2015                                                                                 filter => "cn=$group_name",
2016                                                                                 );
2017                                                 if ($mesg->count) {
2018                                                                 my @entries = $mesg->entries;
2019                                                                 foreach my $entry (@entries) {
2020                                                                                 my @receivers= $entry->get_value("memberUid");
2021                                                                                 foreach my $receiver (@receivers) { 
2022                                                                                                 $receiver_h{$receiver} = 0;
2023                                                                                 }
2024                                                                 }
2025                                                 } 
2026                                                 # translating errors ?
2027                                                 if ($mesg->code) {
2028                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
2029                                                 }
2030                                                 &release_ldap_handle($ldap_handle);
2031                                 # ldap handle error ?           
2032                                 } else {
2033                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
2034                                 }
2035                         } else {
2036                                 my $sbjct = &encode_base64(@{$hit}[1]);
2037                                 my $msg = &encode_base64(@{$hit}[7]);
2038                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
2039                         }
2040                 }
2041                 my @receiver_l = keys(%receiver_h);
2043         my $message_id = @{$hit}[0];
2045         #add each outgoing msg to messaging_db
2046         my $receiver;
2047         foreach $receiver (@receiver_l) {
2048             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
2049                 "VALUES ('".
2050                 $message_id."', '".    # id
2051                 @{$hit}[1]."', '".     # subject
2052                 @{$hit}[2]."', '".     # message_from
2053                 $receiver."', '".      # message_to
2054                 "none"."', '".         # flag
2055                 "out"."', '".          # direction
2056                 @{$hit}[6]."', '".     # delivery_time
2057                 @{$hit}[7]."', '".     # message
2058                 $timestamp."'".     # timestamp
2059                 ")";
2060             &daemon_log("M DEBUG: $sql_statement", 1);
2061             my $res = $messaging_db->exec_statement($sql_statement);
2062             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
2063         }
2065         # set incoming message to flag d=deliverd
2066         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
2067         &daemon_log("M DEBUG: $sql_statement", 7);
2068         $res = $messaging_db->update_dbentry($sql_statement);
2069         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
2070     }
2072     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
2073     return;
2076 sub watch_for_delivery_messages {
2077     my ($kernel, $heap) = @_[KERNEL, HEAP];
2079     # select outgoing messages
2080     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
2081     my $res = $messaging_db->exec_statement( $sql_statement );
2082     
2083     # build out msg for each    usr
2084     foreach my $hit (@{$res}) {
2085         my $receiver = @{$hit}[3];
2086         my $msg_id = @{$hit}[0];
2087         my $subject = @{$hit}[1];
2088         my $message = @{$hit}[7];
2090         # resolve usr -> host where usr is logged in
2091         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
2092         my $res = $login_users_db->exec_statement($sql);
2094         # receiver is logged in nowhere
2095         if (not ref(@$res[0]) eq "ARRAY") { next; }    
2097         # receiver ist logged in at a client registered at local server
2098                 my $send_succeed = 0;
2099                 foreach my $hit (@$res) {
2100                                 my $receiver_host = @$hit[0];
2101                 my $delivered2host = 0;
2102                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
2104                                 # Looking for host in know_clients_db 
2105                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
2106                                 my $res = $known_clients_db->exec_statement($sql);
2108                 # Host is known in known_clients_db
2109                 if (ref(@$res[0]) eq "ARRAY") {
2110                     my $receiver_key = @{@{$res}[0]}[2];
2111                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2112                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2113                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
2114                     if ($error == 0 ) {
2115                         $send_succeed++ ;
2116                         $delivered2host++ ;
2117                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
2118                     } else {
2119                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
2120                     }
2121                 }
2122                 
2123                 # Message already send, do not need to do anything more, otherwise ...
2124                 if ($delivered2host) { next;}
2125     
2126                 # ...looking for host in foreign_clients_db
2127                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2128                 $res = $foreign_clients_db->exec_statement($sql);
2129   
2130                                 # Host is known in foreign_clients_db 
2131                                 if (ref(@$res[0]) eq "ARRAY") { 
2132                     my $registration_server = @{@{$res}[0]}[2];
2133                     
2134                     # Fetch encryption key for registration server
2135                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2136                     my $res = $known_server_db->exec_statement($sql);
2137                     if (ref(@$res[0]) eq "ARRAY") { 
2138                         my $registration_server_key = @{@{$res}[0]}[3];
2139                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2140                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2141                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
2142                         if ($error == 0 ) {
2143                             $send_succeed++ ;
2144                             $delivered2host++ ;
2145                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
2146                         } else {
2147                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
2148                         }
2150                     } else {
2151                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2152                                 "registrated at server '$registration_server', ".
2153                                 "but no data available in known_server_db ", 1); 
2154                     }
2155                 }
2156                 
2157                 if (not $delivered2host) {
2158                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2159                 }
2160                 }
2162                 if ($send_succeed) {
2163                                 # set outgoing msg at db to deliverd
2164                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
2165                                 my $res = $messaging_db->exec_statement($sql); 
2166                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2167                 } else {
2168             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
2169         }
2170         }
2172     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
2173     return;
2177 sub watch_for_done_messages {
2178     my ($kernel,$heap) = @_[KERNEL, HEAP];
2180     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
2181     my $res = $messaging_db->exec_statement($sql); 
2183     foreach my $hit (@{$res}) {
2184         my $msg_id = @{$hit}[0];
2186         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
2187         my $res = $messaging_db->exec_statement($sql);
2189         # not all usr msgs have been seen till now
2190         if ( ref(@$res[0]) eq "ARRAY") { next; }
2191         
2192         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
2193         $res = $messaging_db->exec_statement($sql);
2194     
2195     }
2197     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2198     return;
2202 sub watch_for_old_known_clients {
2203     my ($kernel,$heap) = @_[KERNEL, HEAP];
2205     my $sql_statement = "SELECT * FROM $known_clients_tn";
2206     my $res = $known_clients_db->select_dbentry( $sql_statement );
2208     my $cur_time = int(&get_time());
2210     while ( my ($hit_num, $hit) = each %$res) {
2211         my $expired_timestamp = int($hit->{'timestamp'});
2212         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2213         my $dt = DateTime->new( year   => $1,
2214                 month  => $2,
2215                 day    => $3,
2216                 hour   => $4,
2217                 minute => $5,
2218                 second => $6,
2219                 );
2221         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2222         $expired_timestamp = $dt->ymd('').$dt->hms('');
2223         if ($cur_time > $expired_timestamp) {
2224             my $hostname = $hit->{'hostname'};
2225             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2226             my $del_res = $known_clients_db->exec_statement($del_sql);
2228             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2229         }
2231     }
2233     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2237 sub watch_for_next_tasks {
2238     my ($kernel,$heap) = @_[KERNEL, HEAP];
2240     my $sql = "SELECT * FROM $incoming_tn";
2241     my $res = $incoming_db->select_dbentry($sql);
2242     
2243     while ( my ($hit_num, $hit) = each %$res) {
2244         my $headertag = $hit->{'headertag'};
2245         if ($headertag =~ /^answer_(\d+)/) {
2246             # do not start processing, this message is for a still running POE::Wheel
2247             next;
2248         }
2249         my $message_id = $hit->{'id'};
2250         my $session_id = $hit->{'sessionid'};
2251         &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 11);
2253         $kernel->yield('next_task', $hit);
2255         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2256         my $res = $incoming_db->exec_statement($sql);
2257     }
2259     $kernel->delay_set('watch_for_next_tasks', 1); 
2263 sub get_ldap_handle {
2264         my ($session_id) = @_;
2265         my $heap;
2267         if (not defined $session_id ) { $session_id = 0 };
2268         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2270         my ($package, $file, $row, $subroutine, $hasArgs, $wantArray, $evalText, $isRequire) = caller(1);
2271         my $caller_text = "subroutine $subroutine";
2272         if ($subroutine eq "(eval)") {
2273                 $caller_text = "eval block within file '$file' for '$evalText'"; 
2274         }
2275         daemon_log("$session_id DEBUG: new ldap handle for '$caller_text' required!", 42);
2277 get_handle:
2278         my $ldap_handle = Net::LDAP->new( $ldap_uri );
2279         if (not ref $ldap_handle) {
2280                 daemon_log("$session_id ERROR: Connection to LDAP URI '$ldap_uri' failed! Retrying!", 1);
2281                 usleep(100000);
2282                 goto get_handle;
2283         } else {
2284                 daemon_log("$session_id DEBUG: Connection to LDAP URI '$ldap_uri' established.", 42);
2285         }
2287         $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);
2288         return $ldap_handle;
2292 sub release_ldap_handle {
2293         my ($ldap_handle, $session_id) = @_ ;
2294         if (not defined $session_id ) { $session_id = 0 };
2296         if(ref $ldap_handle) {
2297           $ldap_handle->disconnect();
2298   }
2299         &main::daemon_log("$session_id DEBUG: Released a ldap handle!", 42);
2300         return;
2304 sub change_fai_state {
2305         my ($st, $targets, $session_id) = @_;
2306         $session_id = 0 if not defined $session_id;
2307         # Set FAI state to localboot
2308         my %mapActions= (
2309                 reboot    => '',
2310                 update    => 'softupdate',
2311                 localboot => 'localboot',
2312                 reinstall => 'install',
2313                 rescan    => '',
2314                 wake      => '',
2315                 memcheck  => 'memcheck',
2316                 sysinfo   => 'sysinfo',
2317                 install   => 'install',
2318         );
2320         # Return if this is unknown
2321         if (!exists $mapActions{ $st }){
2322                 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2323                 return;
2324         }
2326         my $state= $mapActions{ $st };
2328         # Build search filter for hosts
2329         my $search= "(&(objectClass=GOhard)";
2330         foreach (@{$targets}){
2331                 $search.= "(macAddress=$_)";
2332         }
2333         $search.= ")";
2335         # If there's any host inside of the search string, procress them
2336         if (!($search =~ /macAddress/)){
2337                 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2338                 return;
2339         }
2341         my $ldap_handle = &get_ldap_handle($session_id);
2342         # Perform search for Unit Tag
2343         my $mesg = $ldap_handle->search(
2344                 base   => $ldap_base,
2345                 scope  => 'sub',
2346                 attrs  => ['dn', 'FAIstate', 'objectClass'],
2347                 filter => "$search"
2348         );
2350         if ($mesg->count) {
2351                 my @entries = $mesg->entries;
2352                 if (0 == @entries) {
2353                         daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2354                 }
2356                 foreach my $entry (@entries) {
2357                         # Only modify entry if it is not set to '$state'
2358                         if ($entry->get_value("FAIstate") ne "$state"){
2359                                 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2360                                 my $result;
2361                                 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2362                                 if (exists $tmp{'FAIobject'}){
2363                                         if ($state eq ''){
2364                                                 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2365                                         } else {
2366                                                 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2367                                         }
2368                                 } elsif ($state ne ''){
2369                                         $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2370                                 }
2372                                 # Errors?
2373                                 if ($result->code){
2374                                         daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2375                                 }
2376                         } else {
2377                                 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 42); 
2378                         }  
2379                 }
2380         } else {
2381                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2382         }
2383         &release_ldap_handle($ldap_handle, $session_id);                  
2385         return;
2389 sub change_goto_state {
2390     my ($st, $targets, $session_id) = @_;
2391     $session_id = 0  if not defined $session_id;
2393     # Switch on or off?
2394     my $state= $st eq 'active' ? 'active': 'locked';
2396     my $ldap_handle = &get_ldap_handle($session_id);
2397     if( defined($ldap_handle) ) {
2399       # Build search filter for hosts
2400       my $search= "(&(objectClass=GOhard)";
2401       foreach (@{$targets}){
2402         $search.= "(macAddress=$_)";
2403       }
2404       $search.= ")";
2406       # If there's any host inside of the search string, procress them
2407       if (!($search =~ /macAddress/)){
2408               &release_ldap_handle($ldap_handle);
2409         return;
2410       }
2412       # Perform search for Unit Tag
2413       my $mesg = $ldap_handle->search(
2414           base   => $ldap_base,
2415           scope  => 'sub',
2416           attrs  => ['dn', 'gotoMode'],
2417           filter => "$search"
2418           );
2420       if ($mesg->count) {
2421         my @entries = $mesg->entries;
2422         foreach my $entry (@entries) {
2424           # Only modify entry if it is not set to '$state'
2425           if ($entry->get_value("gotoMode") ne $state){
2427             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2428             my $result;
2429             $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2431             # Errors?
2432             if ($result->code){
2433               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2434             }
2436           }
2437         }
2438       } else {
2439                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2440           }
2442     }
2443         &release_ldap_handle($ldap_handle, $session_id);
2444         return;
2448 sub run_recreate_packages_db {
2449     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2450     my $session_id = $session->ID;
2451         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2452         $kernel->yield('create_fai_release_db', $fai_release_tn);
2453         $kernel->yield('create_fai_server_db', $fai_server_tn);
2454         return;
2458 sub run_create_fai_server_db {
2459     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2460     my $session_id = $session->ID;
2461     my $task = POE::Wheel::Run->new(
2462             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2463             StdoutEvent  => "session_run_result",
2464             StderrEvent  => "session_run_debug",
2465             CloseEvent   => "session_run_done",
2466             );
2468     $heap->{task}->{ $task->ID } = $task;
2469     return;
2473 sub create_fai_server_db {
2474         my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2475         my $result;
2477         if (not defined $session_id) { $session_id = 0; }
2478         my $ldap_handle = &get_ldap_handle($session_id);
2479         if(defined($ldap_handle)) {
2480                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2481                 my $mesg= $ldap_handle->search(
2482                         base   => $ldap_base,
2483                         scope  => 'sub',
2484                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2485                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2486                 );
2487                 if($mesg->{'resultCode'} == 0 &&
2488                         $mesg->count != 0) {
2489                         foreach my $entry (@{$mesg->{entries}}) {
2490                                 if($entry->exists('FAIrepository')) {
2491                                         # Add an entry for each Repository configured for server
2492                                         foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2493                                                 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2494                                                 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2495                                                 $result= $fai_server_db->add_dbentry( { 
2496                                                                 table => $table_name,
2497                                                                 primkey => ['server', 'fai_release', 'tag'],
2498                                                                 server => $tmp_url,
2499                                                                 fai_release => $tmp_release,
2500                                                                 sections => $tmp_sections,
2501                                                                 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2502                                                         } );
2503                                         }
2504                                 }
2505                         }
2506                 }
2507                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2508                 &release_ldap_handle($ldap_handle);
2510                 # TODO: Find a way to post the 'create_packages_list_db' event
2511                 if(not defined($dont_create_packages_list)) {
2512                         &create_packages_list_db(undef, $session_id);
2513                 }
2514         }       
2516         return $result;
2520 sub run_create_fai_release_db {
2521         my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2522         my $session_id = $session->ID;
2523         my $task = POE::Wheel::Run->new(
2524                 Program => sub { &create_fai_release_db($table_name, $session_id) },
2525                 StdoutEvent  => "session_run_result",
2526                 StderrEvent  => "session_run_debug",
2527                 CloseEvent   => "session_run_done",
2528         );
2530         $heap->{task}->{ $task->ID } = $task;
2531         return;
2535 sub create_fai_release_db {
2536         my ($table_name, $session_id) = @_;
2537         my $result;
2539         # used for logging
2540         if (not defined $session_id) { $session_id = 0; }
2542         my $ldap_handle = &get_ldap_handle($session_id);
2543         if(defined($ldap_handle)) {
2544                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2545                 my $mesg= $ldap_handle->search(
2546                         base   => $ldap_base,
2547                         scope  => 'sub',
2548                         attrs  => [],
2549                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2550                 );
2551                 if(($mesg->code == 0) && ($mesg->count != 0))
2552                 {
2553                         daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,138);
2555                         # Walk through all possible FAI container ou's
2556                         my @sql_list;
2557                         my $timestamp= &get_time();
2558                         foreach my $ou (@{$mesg->{entries}}) {
2559                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2560                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2561                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2562                                         if(@tmp_array) {
2563                                                 foreach my $entry (@tmp_array) {
2564                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2565                                                                 my $sql= 
2566                                                                 "INSERT INTO $table_name "
2567                                                                 ."(timestamp, fai_release, class, type, state) VALUES ("
2568                                                                 .$timestamp.","
2569                                                                 ."'".$entry->{'release'}."',"
2570                                                                 ."'".$entry->{'class'}."',"
2571                                                                 ."'".$entry->{'type'}."',"
2572                                                                 ."'".$entry->{'state'}."')";
2573                                                                 push @sql_list, $sql;
2574                                                         }
2575                                                 }
2576                                         }
2577                                 }
2578                         }
2580                         daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",138);
2581             &release_ldap_handle($ldap_handle);
2582                         if(@sql_list) {
2583                                 unshift @sql_list, "VACUUM";
2584                                 unshift @sql_list, "DELETE FROM $table_name";
2585                                 $fai_release_db->exec_statementlist(\@sql_list);
2586                         }
2587                         daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",138);
2588                 } else {
2589                         daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2590                 }
2591                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2592         }
2593         return $result;
2596 sub get_fai_types {
2597         my $tmp_classes = shift || return undef;
2598         my @result;
2600         foreach my $type(keys %{$tmp_classes}) {
2601                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2602                         my $entry = {
2603                                 type => $type,
2604                                 state => $tmp_classes->{$type}[0],
2605                         };
2606                         push @result, $entry;
2607                 }
2608         }
2610         return @result;
2613 sub get_fai_state {
2614         my $result = "";
2615         my $tmp_classes = shift || return $result;
2617         foreach my $type(keys %{$tmp_classes}) {
2618                 if(defined($tmp_classes->{$type}[0])) {
2619                         $result = $tmp_classes->{$type}[0];
2620                         
2621                 # State is equal for all types in class
2622                         last;
2623                 }
2624         }
2626         return $result;
2629 sub resolve_fai_classes {
2630         my ($fai_base, $ldap_handle, $session_id) = @_;
2631         if (not defined $session_id) { $session_id = 0; }
2632         my $result;
2633         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2634         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2635         my $fai_classes;
2637         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base", 138);
2638         my $mesg= $ldap_handle->search(
2639                 base   => $fai_base,
2640                 scope  => 'sub',
2641                 attrs  => ['cn','objectClass','FAIstate'],
2642                 filter => $fai_filter,
2643         );
2644         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries", 138);
2646         if($mesg->{'resultCode'} == 0 &&
2647                 $mesg->count != 0) {
2648                 foreach my $entry (@{$mesg->{entries}}) {
2649                         if($entry->exists('cn')) {
2650                                 my $tmp_dn= $entry->dn();
2651                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2652                                         - length($fai_base) - 1 );
2654                                 # Skip classname and ou dn parts for class
2655                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2657                                 # Skip classes without releases
2658                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2659                                         next;
2660                                 }
2662                                 my $tmp_cn= $entry->get_value('cn');
2663                                 my $tmp_state= $entry->get_value('FAIstate');
2665                                 my $tmp_type;
2666                                 # Get FAI type
2667                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2668                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2669                                                 $tmp_type= $oclass;
2670                                                 last;
2671                                         }
2672                                 }
2674                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2675                                         # A Subrelease
2676                                         my @sub_releases = split(/,/, $tmp_release);
2678                                         # Walk through subreleases and build hash tree
2679                                         my $hash;
2680                                         while(my $tmp_sub_release = pop @sub_releases) {
2681                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2682                                         }
2683                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2684                                 } else {
2685                                         # A branch, no subrelease
2686                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2687                                 }
2688                         } elsif (!$entry->exists('cn')) {
2689                                 my $tmp_dn= $entry->dn();
2690                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2691                                         - length($fai_base) - 1 );
2692                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2694                                 # Skip classes without releases
2695                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2696                                         next;
2697                                 }
2699                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2700                                         # A Subrelease
2701                                         my @sub_releases= split(/,/, $tmp_release);
2703                                         # Walk through subreleases and build hash tree
2704                                         my $hash;
2705                                         while(my $tmp_sub_release = pop @sub_releases) {
2706                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2707                                         }
2708                                         # Remove the last two characters
2709                                         chop($hash);
2710                                         chop($hash);
2712                                         eval('$fai_classes->'.$hash.'= {}');
2713                                 } else {
2714                                         # A branch, no subrelease
2715                                         if(!exists($fai_classes->{$tmp_release})) {
2716                                                 $fai_classes->{$tmp_release} = {};
2717                                         }
2718                                 }
2719                         }
2720                 }
2722                 # The hash is complete, now we can honor the copy-on-write based missing entries
2723                 foreach my $release (keys %$fai_classes) {
2724                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2725                 }
2726         }
2727         return $result;
2730 sub apply_fai_inheritance {
2731        my $fai_classes = shift || return {};
2732        my $tmp_classes;
2734        # Get the classes from the branch
2735        foreach my $class (keys %{$fai_classes}) {
2736                # Skip subreleases
2737                if($class =~ /^ou=.*$/) {
2738                        next;
2739                } else {
2740                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2741                }
2742        }
2744        # Apply to each subrelease
2745        foreach my $subrelease (keys %{$fai_classes}) {
2746                if($subrelease =~ /ou=/) {
2747                        foreach my $tmp_class (keys %{$tmp_classes}) {
2748                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2749                                        $fai_classes->{$subrelease}->{$tmp_class} =
2750                                        deep_copy($tmp_classes->{$tmp_class});
2751                                } else {
2752                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2753                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2754                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2755                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2756                                                }
2757                                        }
2758                                }
2759                        }
2760                }
2761        }
2763        # Find subreleases in deeper levels
2764        foreach my $subrelease (keys %{$fai_classes}) {
2765                if($subrelease =~ /ou=/) {
2766                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2767                                if($subsubrelease =~ /ou=/) {
2768                                        apply_fai_inheritance($fai_classes->{$subrelease});
2769                                }
2770                        }
2771                }
2772        }
2774        return $fai_classes;
2777 sub get_fai_release_entries {
2778         my $tmp_classes = shift || return;
2779         my $parent = shift || "";
2780         my @result = shift || ();
2782         foreach my $entry (keys %{$tmp_classes}) {
2783                 if(defined($entry)) {
2784                         if($entry =~ /^ou=.*$/) {
2785                                 my $release_name = $entry;
2786                                 $release_name =~ s/ou=//g;
2787                                 if(length($parent)>0) {
2788                                         $release_name = $parent."/".$release_name;
2789                                 }
2790                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2791                                 foreach my $bufentry(@bufentries) {
2792                                         push @result, $bufentry;
2793                                 }
2794                         } else {
2795                                 my @types = get_fai_types($tmp_classes->{$entry});
2796                                 foreach my $type (@types) {
2797                                         push @result, 
2798                                         {
2799                                                 'class' => $entry,
2800                                                 'type' => $type->{'type'},
2801                                                 'release' => $parent,
2802                                                 'state' => $type->{'state'},
2803                                         };
2804                                 }
2805                         }
2806                 }
2807         }
2809         return @result;
2812 sub deep_copy {
2813         my $this = shift;
2814         if (not ref $this) {
2815                 $this;
2816         } elsif (ref $this eq "ARRAY") {
2817                 [map deep_copy($_), @$this];
2818         } elsif (ref $this eq "HASH") {
2819                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2820         } else { die "what type is $_?" }
2824 sub session_run_result {
2825     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2826     $kernel->sig(CHLD => "child_reap");
2829 sub session_run_debug {
2830     my $result = $_[ARG0];
2831     print STDERR "$result\n";
2834 sub session_run_done {
2835     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2836     delete $heap->{task}->{$task_id};
2837         if (exists $heap->{ldap_handle}->{$task_id}) {
2838                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
2839         }
2840         delete $heap->{ldap_handle}->{$task_id};
2844 sub create_sources_list {
2845         my $session_id = shift || 0;
2846         my $result="/tmp/gosa_si_tmp_sources_list";
2848         # Remove old file
2849         if(stat($result)) {
2850                 unlink($result);
2851                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2852         }
2854         my $fh;
2855         open($fh, ">$result");
2856         if (not defined $fh) {
2857                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2858                 return undef;
2859         }
2860         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2861                 my $ldap_handle = &get_ldap_handle($session_id);
2862                 my $mesg=$ldap_handle->search(
2863                         base    => $main::ldap_server_dn,
2864                         scope   => 'base',
2865                         attrs   => 'FAIrepository',
2866                         filter  => 'objectClass=FAIrepositoryServer'
2867                 );
2868                 if($mesg->count) {
2869                         foreach my $entry(@{$mesg->{'entries'}}) {
2870                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2871                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2872                                         my $line = "deb $server $release";
2873                                         $sections =~ s/,/ /g;
2874                                         $line.= " $sections";
2875                                         print $fh $line."\n";
2876                                 }
2877                         }
2878                 }
2879                 &release_ldap_handle($ldap_handle);
2880         } else {
2881                 if (defined $main::ldap_server_dn){
2882                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2883                 } else {
2884                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2885                 }
2886         }
2887         close($fh);
2889         return $result;
2893 sub run_create_packages_list_db {
2894     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2895         my $session_id = $session->ID;
2896         my $task = POE::Wheel::Run->new(
2897                                         Priority => +20,
2898                                         Program => sub {&create_packages_list_db(undef, $session_id)},
2899                                         StdoutEvent  => "session_run_result",
2900                                         StderrEvent  => "session_run_debug",
2901                                         CloseEvent   => "session_run_done",
2902                                         );
2903         $heap->{task}->{ $task->ID } = $task;
2907 sub create_packages_list_db {
2908         my ($sources_file, $session_id) = @_;
2909         
2910         # it should not be possible to trigger a recreation of packages_list_db
2911         # while packages_list_db is under construction, so set flag packages_list_under_construction
2912         # which is tested befor recreation can be started
2913         if (-r $packages_list_under_construction) {
2914                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2915                 return;
2916         } else {
2917                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2918                 # set packages_list_under_construction to true
2919                 system("touch $packages_list_under_construction");
2920                 @packages_list_statements=();
2921         }
2923         if (not defined $session_id) { $session_id = 0; }
2925         if (not defined $sources_file) { 
2926                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2927                 $sources_file = &create_sources_list($session_id);
2928         }
2930         if (not defined $sources_file) {
2931                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2932                 unlink($packages_list_under_construction);
2933                 return;
2934         }
2936         my $line;
2938         open(CONFIG, "<$sources_file") or do {
2939                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2940                 unlink($packages_list_under_construction);
2941                 return;
2942         };
2944         # Read lines
2945         while ($line = <CONFIG>){
2946                 # Unify
2947                 chop($line);
2948                 $line =~ s/^\s+//;
2949                 $line =~ s/^\s+/ /;
2951                 # Strip comments
2952                 $line =~ s/#.*$//g;
2954                 # Skip empty lines
2955                 if ($line =~ /^\s*$/){
2956                         next;
2957                 }
2959                 # Interpret deb line
2960                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2961                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2962                         my $section;
2963                         foreach $section (split(' ', $sections)){
2964                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2965                         }
2966                 }
2967         }
2969         close (CONFIG);
2971         if(keys(%repo_dirs)) {
2972                 find(\&cleanup_and_extract, keys( %repo_dirs ));
2973                 &main::strip_packages_list_statements();
2974                 $packages_list_db->exec_statementlist(\@packages_list_statements);
2975         }
2976         unlink($packages_list_under_construction);
2977         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2978         return;
2981 # This function should do some intensive task to minimize the db-traffic
2982 sub strip_packages_list_statements {
2983         my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2984         my @new_statement_list=();
2985         my $hash;
2986         my $insert_hash;
2987         my $update_hash;
2988         my $delete_hash;
2989         my $known_packages_hash;
2990         my $local_timestamp=get_time();
2992         foreach my $existing_entry (@existing_entries) {
2993                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2994         }
2996         foreach my $statement (@packages_list_statements) {
2997                 if($statement =~ /^INSERT/i) {
2998                         # Assign the values from the insert statement
2999                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
3000                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
3001                         if(exists($hash->{$distribution}->{$package}->{$version})) {
3002                                 # If section or description has changed, update the DB
3003                                 if( 
3004                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
3005                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
3006                                 ) {
3007                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
3008                                 } else {
3009                                         # package is already present in database. cache this knowledge for later use
3010                                         @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3011                                 }
3012                         } else {
3013                                 # Insert a non-existing entry to db
3014                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3015                         }
3016                 } elsif ($statement =~ /^UPDATE/i) {
3017                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
3018                         /^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;
3019                         foreach my $distribution (keys %{$hash}) {
3020                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
3021                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
3022                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
3023                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
3024                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
3025                                                 my $section;
3026                                                 my $description;
3027                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
3028                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
3029                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
3030                                                 }
3031                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3032                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
3033                                                 }
3034                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3035                                         }
3036                                 }
3037                         }
3038                 }
3039         }
3041         # Check for orphaned entries
3042         foreach my $existing_entry (@existing_entries) {
3043                 my $distribution= @{$existing_entry}[0];
3044                 my $package= @{$existing_entry}[1];
3045                 my $version= @{$existing_entry}[2];
3046                 my $section= @{$existing_entry}[3];
3048                 if(
3049                         exists($insert_hash->{$distribution}->{$package}->{$version}) ||
3050                         exists($update_hash->{$distribution}->{$package}->{$version}) ||
3051                         exists($known_packages_hash->{$distribution}->{$package}->{$version})
3052                 ) {
3053                         next;
3054                 } else {
3055                         # Insert entry to delete hash
3056                         @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
3057                 }
3058         }
3060         # unroll the insert hash
3061         foreach my $distribution (keys %{$insert_hash}) {
3062                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
3063                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
3064                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
3065                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
3066                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
3067                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
3068                                 ."'$local_timestamp')";
3069                         }
3070                 }
3071         }
3073         # unroll the update hash
3074         foreach my $distribution (keys %{$update_hash}) {
3075                 foreach my $package (keys %{$update_hash->{$distribution}}) {
3076                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
3077                                 my $set = "";
3078                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
3079                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
3080                                 }
3081                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3082                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
3083                                 }
3084                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
3085                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
3086                                 }
3087                                 if(defined($set) and length($set) > 0) {
3088                                         $set .= "timestamp = '$local_timestamp'";
3089                                 } else {
3090                                         next;
3091                                 }
3092                                 push @new_statement_list, 
3093                                 "UPDATE $main::packages_list_tn SET $set WHERE"
3094                                 ." distribution = '$distribution'"
3095                                 ." AND package = '$package'"
3096                                 ." AND version = '$version'";
3097                         }
3098                 }
3099         }
3100         
3101         # unroll the delete hash
3102         foreach my $distribution (keys %{$delete_hash}) {
3103                 foreach my $package (keys %{$delete_hash->{$distribution}}) {
3104                         foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
3105                                 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
3106                                 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
3107                         }
3108                 }
3109         }
3111         unshift(@new_statement_list, "VACUUM");
3113         @packages_list_statements = @new_statement_list;
3117 sub parse_package_info {
3118     my ($baseurl, $dist, $section, $session_id)= @_;
3119     my ($package);
3120     if (not defined $session_id) { $session_id = 0; }
3121     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3122     $repo_dirs{ "${repo_path}/pool" } = 1;
3124     foreach $package ("Packages.gz"){
3125         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
3126         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3127         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3128     }
3129     
3133 sub get_package {
3134     my ($url, $dest, $session_id)= @_;
3135     if (not defined $session_id) { $session_id = 0; }
3137     my $tpath = dirname($dest);
3138     -d "$tpath" || mkpath "$tpath";
3140     # This is ugly, but I've no time to take a look at "how it works in perl"
3141     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3142         system("gunzip -cd '$dest' > '$dest.in'");
3143         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 7);
3144         unlink($dest);
3145         daemon_log("$session_id DEBUG: delete file '$dest'", 7); 
3146     } else {
3147         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3148     }
3149     return 0;
3153 sub parse_package {
3154     my ($path, $dist, $srv_path, $session_id)= @_;
3155     if (not defined $session_id) { $session_id = 0;}
3156     my ($package, $version, $section, $description);
3157     my $PACKAGES;
3158     my $timestamp = &get_time();
3160     if(not stat("$path.in")) {
3161         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3162         return;
3163     }
3165     open($PACKAGES, "<$path.in");
3166     if(not defined($PACKAGES)) {
3167         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
3168         return;
3169     }
3171     # Read lines
3172     while (<$PACKAGES>){
3173         my $line = $_;
3174         # Unify
3175         chop($line);
3177         # Use empty lines as a trigger
3178         if ($line =~ /^\s*$/){
3179             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3180             push(@packages_list_statements, $sql);
3181             $package = "none";
3182             $version = "none";
3183             $section = "none";
3184             $description = "none"; 
3185             next;
3186         }
3188         # Trigger for package name
3189         if ($line =~ /^Package:\s/){
3190             ($package)= ($line =~ /^Package: (.*)$/);
3191             next;
3192         }
3194         # Trigger for version
3195         if ($line =~ /^Version:\s/){
3196             ($version)= ($line =~ /^Version: (.*)$/);
3197             next;
3198         }
3200         # Trigger for description
3201         if ($line =~ /^Description:\s/){
3202             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3203             next;
3204         }
3206         # Trigger for section
3207         if ($line =~ /^Section:\s/){
3208             ($section)= ($line =~ /^Section: (.*)$/);
3209             next;
3210         }
3212         # Trigger for filename
3213         if ($line =~ /^Filename:\s/){
3214             my ($filename) = ($line =~ /^Filename: (.*)$/);
3215             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3216             next;
3217         }
3218     }
3220     close( $PACKAGES );
3221     unlink( "$path.in" );
3225 sub store_fileinfo {
3226     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3228     my %fileinfo = (
3229         'package' => $package,
3230         'dist' => $dist,
3231         'version' => $vers,
3232     );
3234     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3238 sub cleanup_and_extract {
3239         my $fileinfo = $repo_files{ $File::Find::name };
3241         if( defined $fileinfo ) {
3242                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3243                 my $sql;
3244                 my $package = $fileinfo->{ 'package' };
3245                 my $newver = $fileinfo->{ 'version' };
3247                 mkpath($dir);
3248                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3250                 if( -f "$dir/DEBIAN/templates" ) {
3252                         daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3254                         my $tmpl= ""; {
3255                                 local $/=undef;
3256                                 open FILE, "$dir/DEBIAN/templates";
3257                                 $tmpl = &encode_base64(<FILE>);
3258                                 close FILE;
3259                         }
3260                         rmtree("$dir/DEBIAN/templates");
3262                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3263                         push @packages_list_statements, $sql;
3264                 }
3265         }
3267         return;
3271 sub prepare_server_registration 
3273         # Add foreign server from cfg file
3274         my @foreign_server_list;
3275         if ($foreign_server_string ne "") {
3276             my @cfg_foreign_server_list = split(",", $foreign_server_string);
3277             foreach my $foreign_server (@cfg_foreign_server_list) {
3278                 push(@foreign_server_list, $foreign_server);
3279             }
3280         
3281             daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3282         }
3283         
3284         # Perform a DNS lookup for server registration if flag is true
3285         if ($dns_lookup eq "true") {
3286             # Add foreign server from dns
3287             my @tmp_servers;
3288             if (not $server_domain) {
3289                 # Try our DNS Searchlist
3290                 for my $domain(get_dns_domains()) {
3291                     chomp($domain);
3292                     my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3293                     if(@$tmp_domains) {
3294                         for my $tmp_server(@$tmp_domains) {
3295                             push @tmp_servers, $tmp_server;
3296                         }
3297                     }
3298                 }
3299                 if(@tmp_servers && length(@tmp_servers)==0) {
3300                     daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3301                 }
3302             } else {
3303                 @tmp_servers = &get_server_addresses($server_domain);
3304                 if( 0 == @tmp_servers ) {
3305                     daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3306                 }
3307             }
3308         
3309             daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3310         
3311             foreach my $server (@tmp_servers) { 
3312                 unshift(@foreign_server_list, $server); 
3313             }
3314         } else {
3315             daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3316         }
3317         
3318         # eliminate duplicate entries
3319         @foreign_server_list = &del_doubles(@foreign_server_list);
3320         my $all_foreign_server = join(", ", @foreign_server_list);
3321         daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3322         
3323         # add all found foreign servers to known_server
3324         my $cur_timestamp = &get_time();
3325         foreach my $foreign_server (@foreign_server_list) {
3326         
3327                 # do not add myself to known_server_db
3328                 if (&is_local($foreign_server)) { next; }
3329                 ######################################
3330         
3331             my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3332                     primkey=>['hostname'],
3333                     hostname=>$foreign_server,
3334                     macaddress=>"",
3335                     status=>'not_yet_registered',
3336                     hostkey=>"none",
3337                     loaded_modules => "none", 
3338                     timestamp=>$cur_timestamp,
3339                                 update_time=>'19700101000000',
3340                     } );
3341         }
3344 sub register_at_foreign_servers {   
3345     my ($kernel) = $_[KERNEL];
3347         # Update status and update-time of all si-server with expired update_time and 
3348         # block them for race conditional registration processes of other si-servers.
3349         my $act_time = &get_time();
3350         my $block_statement = "UPDATE $known_server_tn SET status='new_server',update_time='19700101000000' WHERE (CAST(update_time AS UNSIGNED))<$act_time ";
3351         my $block_res = $known_server_db->exec_statement($block_statement);
3353         # Fetch all si-server from db where update_time is younger than act_time
3354         my $fetch_statement = "SELECT * FROM $known_server_tn WHERE update_time='19700101000000'"; 
3355         my $fetch_res = $known_server_db->exec_statement($fetch_statement);
3357     # Detect already connected clients. Will be added to registration msg later. 
3358     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3359     my $client_res = $known_clients_db->exec_statement($client_sql);
3361         # Send registration messag to all fetched si-server
3362     foreach my $hit (@$fetch_res) {
3363         my $hostname = @$hit[0];
3364         my $hostkey = &create_passwd;
3366         # Add already connected clients to registration message 
3367         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3368         &add_content2xml_hash($myhash, 'key', $hostkey);
3369         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3371         # Add locally loaded gosa-si modules to registration message
3372         my $loaded_modules = {};
3373         while (my ($package, $pck_info) = each %$known_modules) {
3374                         next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3375                         foreach my $act_module (keys(%{@$pck_info[2]})) {
3376                                 $loaded_modules->{$act_module} = ""; 
3377                         }
3378                 }
3379         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3381         # Add macaddress to registration message
3382         my ($host_ip, $host_port) = split(/:/, $hostname);
3383         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3384         my $network_interface= &get_interface_for_ip($local_ip);
3385         my $host_mac = &get_mac_for_interface($network_interface);
3386         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3387         
3388         # Build registration message and send it
3389         my $foreign_server_msg = &create_xml_string($myhash);
3390         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3391     }
3394         # After n sec perform a check of all server registration processes
3395     $kernel->delay_set("control_server_registration", 2); 
3397         return;
3401 sub control_server_registration {
3402         my ($kernel) = $_[KERNEL];
3403         
3404         # Check if all registration processes succeed or not
3405         my $select_statement = "SELECT * FROM $known_server_tn WHERE status='new_server'"; 
3406         my $select_res = $known_server_db->exec_statement($select_statement);
3408         # If at least one registration process failed, maybe in case of a race condition
3409         # with a foreign registration process
3410         if (@$select_res > 0) 
3411         {
3412                 # Release block statement 'new_server' to make the server accessible
3413                 # for foreign registration processes
3414                 my $update_statement = "UPDATE $known_server_tn SET status='waiting' WHERE status='new_server'";        
3415                 my $update_res = $known_server_db->exec_statement($update_statement);
3417                 # Set a random delay to avoid the registration race condition
3418                 my $new_foreign_servers_register_delay = int(rand(4))+1;
3419                 $kernel->delay_set("register_at_foreign_servers", $new_foreign_servers_register_delay);
3420         }
3421         # If all registration processes succeed
3422         else
3423         {
3424                 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3425         }
3427         return;
3431 #==== MAIN = main ==============================================================
3432 #  parse commandline options
3433 Getopt::Long::Configure( "bundling" );
3434 GetOptions("h|help" => \&usage,
3435         "c|config=s" => \$cfg_file,
3436         "f|foreground" => \$foreground,
3437         "v|verbose+" => \$verbose,
3438         "no-arp+" => \$no_arp,
3439                 "d=s" => \$debug_parts,
3440            ) or &usage("", 1); 
3442 #  read and set config parameters
3443 &check_cmdline_param ;
3444 &read_configfile($cfg_file, %cfg_defaults);
3445 &check_pid;
3447 $SIG{CHLD} = 'IGNORE';
3449 # forward error messages to logfile
3450 if( ! $foreground ) {
3451   open( STDIN,  '+>/dev/null' );
3452   open( STDOUT, '+>&STDIN'    );
3453   open( STDERR, '+>&STDIN'    );
3456 # Just fork, if we are not in foreground mode
3457 if( ! $foreground ) { 
3458     chdir '/'                 or die "Can't chdir to /: $!";
3459     $pid = fork;
3460     setsid                    or die "Can't start a new session: $!";
3461     umask 0;
3462 } else { 
3463     $pid = $$; 
3466 # Do something useful - put our PID into the pid_file
3467 if( 0 != $pid ) {
3468     open( LOCK_FILE, ">$pid_file" );
3469     print LOCK_FILE "$pid\n";
3470     close( LOCK_FILE );
3471     if( !$foreground ) { 
3472         exit( 0 ) 
3473     };
3476 # parse head url and revision from svn
3477 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3478 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3479 $server_headURL = defined $1 ? $1 : 'unknown' ;
3480 $server_revision = defined $2 ? $2 : 'unknown' ;
3481 if ($server_headURL =~ /\/tag\// || 
3482         $server_headURL =~ /\/branches\// ) {
3483     $server_status = "stable"; 
3484 } else {
3485     $server_status = "developmental" ;
3487 # Prepare log file and set permissions
3488 $root_uid = getpwnam('root');
3489 $adm_gid = getgrnam('adm');
3490 open(FH, ">>$log_file");
3491 close FH;
3492 chmod(0440, $log_file);
3493 chown($root_uid, $adm_gid, $log_file);
3494 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3496 daemon_log(" ", 1);
3497 daemon_log("$0 started!", 1);
3498 daemon_log("status: $server_status", 1);
3499 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3501 # Buildup data bases
3503     no strict "refs";
3505     if ($db_module eq "DBmysql") {
3506         # connect to incoming_db
3507         $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3509         # connect to gosa-si job queue
3510         $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3512         # connect to known_clients_db
3513         $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3515         # connect to foreign_clients_db
3516         $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3518         # connect to known_server_db
3519         $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3521         # connect to login_usr_db
3522         $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3524         # connect to fai_server_db 
3525         $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3527         # connect to fai_release_db
3528         $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3530         # connect to packages_list_db
3531         $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3533         # connect to messaging_db
3534         $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3536     } elsif ($db_module eq "DBsqlite") {
3537         # connect to incoming_db
3538         unlink($incoming_file_name);
3539         $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3540         chmod(0640, $incoming_file_name);
3541         chown($root_uid, $adm_gid, $incoming_file_name);
3542         
3543         # connect to gosa-si job queue
3544         $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3545         chmod(0640, $job_queue_file_name);
3546         chown($root_uid, $adm_gid, $job_queue_file_name);
3547         
3548         # connect to known_clients_db
3549         #unlink($known_clients_file_name);
3550         $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3551         chmod(0640, $known_clients_file_name);
3552         chown($root_uid, $adm_gid, $known_clients_file_name);
3553         
3554         # connect to foreign_clients_db
3555         #unlink($foreign_clients_file_name);
3556         $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3557         chmod(0640, $foreign_clients_file_name);
3558         chown($root_uid, $adm_gid, $foreign_clients_file_name);
3559         
3560         # connect to known_server_db
3561         unlink($known_server_file_name);   # do not delete, gosa-si-server should be forced to check config file and dns at each start
3562         $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3563         chmod(0640, $known_server_file_name);
3564         chown($root_uid, $adm_gid, $known_server_file_name);
3565         
3566         # connect to login_usr_db
3567         #unlink($login_users_file_name);
3568         $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3569         chmod(0640, $login_users_file_name);
3570         chown($root_uid, $adm_gid, $login_users_file_name);
3571         
3572         # connect to fai_server_db
3573         unlink($fai_server_file_name);
3574         $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3575         chmod(0640, $fai_server_file_name);
3576         chown($root_uid, $adm_gid, $fai_server_file_name);
3577         
3578         # connect to fai_release_db
3579         unlink($fai_release_file_name);
3580         $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3581         chmod(0640, $fai_release_file_name);
3582         chown($root_uid, $adm_gid, $fai_release_file_name);
3583         
3584         # connect to packages_list_db
3585         unlink($packages_list_under_construction);
3586         $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3587         chmod(0640, $packages_list_file_name);
3588         chown($root_uid, $adm_gid, $packages_list_file_name);
3589         
3590         # connect to messaging_db
3591         #unlink($messaging_file_name);
3592         $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3593         chmod(0640, $messaging_file_name);
3594         chown($root_uid, $adm_gid, $messaging_file_name);
3595     }
3598 # Creating tables
3599 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3600 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3601 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3602 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3603 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3604 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3605 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3606 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3607 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3608 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3610 # create xml object used for en/decrypting
3611 $xml = new XML::Simple();
3613 # Import all modules
3614 &import_modules;
3616 # Check wether all modules are gosa-si valid passwd check
3617 &password_check;
3619 # Check DNS and config file for server registration
3620 if ($serverPackages_enabled eq "true") { &prepare_server_registration; }
3622 # Create functions hash
3623 while (my ($module, @mod_info) = each %$known_modules) 
3625         while (my ($plugin, $functions) = each %{$mod_info[0][2]})
3626         {
3627                 while (my ($function, $nothing) = each %$functions )
3628                 {
3629                         $known_functions->{$function} = $nothing;
3630                 }
3631         }
3634 # Prepare for using Opsi 
3635 if ($opsi_enabled eq "true") {
3636     use JSON::RPC::Client;
3637     use XML::Quote qw(:all);
3638     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3639     $opsi_client = new JSON::RPC::Client;
3643 POE::Component::Server::TCP->new(
3644         Alias => "TCP_SERVER",
3645         Port => $server_port,
3646         ClientInput => sub {
3647                 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3648         my $session_id = $session->ID;
3649                 if ($input =~ /;([\d\.]+):([\d]+)$/) 
3650                 {
3651                         # Messages from other servers should be blocked if config option is set
3652                         if (($2 eq $server_port) && ($serverPackages_enabled eq "false"))
3653                         {
3654                                 return;
3655                         }
3656                         &daemon_log("$session_id DEBUG: incoming message from '$1:$2'", 11);
3657                 }
3658                 else
3659                 {
3660                         my $remote_ip = $heap->{'remote_ip'};
3661                         &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 11);
3662                 }
3663                 push(@msgs_to_decrypt, $input);
3664                 $kernel->yield("msg_to_decrypt");
3665         },
3666         InlineStates => {
3667                 msg_to_decrypt => \&msg_to_decrypt,
3668                 next_task => \&next_task,
3669                 task_result => \&handle_task_result,
3670                 task_done   => \&handle_task_done,
3671                 task_debug  => \&handle_task_debug,
3672                 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3673         }
3674 );
3676 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3678 # create session for repeatedly checking the job queue for jobs
3679 POE::Session->create(
3680         inline_states => {
3681                 _start => \&session_start,
3682         register_at_foreign_servers => \&register_at_foreign_servers,
3683                 control_server_registration => \&control_server_registration,
3684         sig_handler => \&sig_handler,
3685         next_task => \&next_task,
3686         task_result => \&handle_task_result,
3687         task_done   => \&handle_task_done,
3688         task_debug  => \&handle_task_debug,
3689         watch_for_next_tasks => \&watch_for_next_tasks,
3690         watch_for_new_messages => \&watch_for_new_messages,
3691         watch_for_delivery_messages => \&watch_for_delivery_messages,
3692         watch_for_done_messages => \&watch_for_done_messages,
3693                 watch_for_new_jobs => \&watch_for_new_jobs,
3694         watch_for_modified_jobs => \&watch_for_modified_jobs,
3695         watch_for_done_jobs => \&watch_for_done_jobs,
3696         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3697         watch_for_old_known_clients => \&watch_for_old_known_clients,
3698         create_packages_list_db => \&run_create_packages_list_db,
3699         create_fai_server_db => \&run_create_fai_server_db,
3700         create_fai_release_db => \&run_create_fai_release_db,
3701                 recreate_packages_db => \&run_recreate_packages_db,
3702         session_run_result => \&session_run_result,
3703         session_run_debug => \&session_run_debug,
3704         session_run_done => \&session_run_done,
3705         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3706         }
3707 );
3710 POE::Kernel->run();
3711 exit;