Code

Reactivate job with delay sometimes got a mac and sometimes a socket address.
[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                          256 : creation of packages_list_db
329                          512 : ARP debug information
330 EOF
331         exit(0);
335 #===  FUNCTION  ================================================================
336 #         NAME:  logging
337 #   PARAMETERS:  level - string - default 'info'
338 #                msg - string -
339 #                facility - string - default 'LOG_DAEMON'
340 #      RETURNS:  nothing
341 #  DESCRIPTION:  function for logging
342 #===============================================================================
343 sub daemon_log {
344     my( $msg, $level ) = @_;
345     if (not defined $msg) { return }
346     if (not defined $level) { $level = 1 }
347         my $to_be_logged = 0;
349         # Write log line if line level is lower than verbosity given in commandline
350         if ($level <= $verbose) 
351         { 
352                 $to_be_logged = 1 ;
353         }
355         # Write if debug flag is set and bitstring matches
356         if ($debug_parts > 0)
357         {
358                 my $tmp_level = ($level - 10 >= 0) ? $level - 10 : 0 ;
359                 my $tmp_level_bitstring = unpack("B32", pack("N", $tmp_level));
360                 if (int($debug_parts_bitstring & $tmp_level_bitstring)) 
361                 {
362                         $to_be_logged = 1;
363                 }
364         }
366         if ($to_be_logged) 
367         {
368                 if(defined $log_file){
369                         my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
370                         if(not $open_log_fh) {
371                                 print STDERR "cannot open $log_file: $!";
372                                 return;
373                         }
374                         # Check owner and group of log_file and update settings if necessary
375                         my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
376                         if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
377                                 chown($root_uid, $adm_gid, $log_file);
378                         }
380                         # Prepare time string for log message
381                         my ($seconds,$minutes,$hours,$monthday,$month,$year,$weekday,$yearday,$sommertime) = localtime(time);
382                         $hours = $hours < 10 ? $hours = "0".$hours : $hours;
383                         $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
384                         $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
385                         $month = $monthnames[$month];
386                         $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
387                         $year+=1900;
389                         
390                         # Build log message and write it to log file and commandline
391                         chomp($msg);
392                         my $log_msg = "$month $monthday $hours:$minutes:$seconds $prg $msg\n";
393                         flock(LOG_HANDLE, LOCK_EX);
394                         seek(LOG_HANDLE, 0, 2);
395                         print LOG_HANDLE $log_msg;
396                         flock(LOG_HANDLE, LOCK_UN);
397                         if( $foreground ) 
398                         { 
399                                 print STDERR $log_msg;
400                         }
401                         close( LOG_HANDLE );
402                 }
403         }
407 #===  FUNCTION  ================================================================
408 #         NAME:  check_cmdline_param
409 #   PARAMETERS:  nothing
410 #      RETURNS:  nothing
411 #  DESCRIPTION:  validates commandline parameter
412 #===============================================================================
413 sub check_cmdline_param () {
414     my $err_counter = 0;
416         # Check configuration file
417         if(not defined($cfg_file)) {
418                 $cfg_file = "/etc/gosa-si/server.conf";
419                 if(! -r $cfg_file) {
420                         print STDERR "Please specify a config file.\n";
421                         $err_counter++;
422                 }
423     }
425         # Prepare identification which gosa-si parts should be debugged and which not
426         if (defined $debug_parts) 
427         {
428                 if ($debug_parts =~ /^\d+$/)
429                 {
430                         $debug_parts_bitstring = unpack("B32", pack("N", $debug_parts));
431                 }
432                 else
433                 {
434                         print STDERR "Value '$debug_parts' invalid for option d (number expected)\n";
435                         $err_counter++;
436                 }
437         }
439         # Exit if an error occour
440     if( $err_counter > 0 ) { &usage( "", 1 ); }
444 #===  FUNCTION  ================================================================
445 #         NAME:  check_pid
446 #   PARAMETERS:  nothing
447 #      RETURNS:  nothing
448 #  DESCRIPTION:  handels pid processing
449 #===============================================================================
450 sub check_pid {
451     $pid = -1;
452     # Check, if we are already running
453     if( open(LOCK_FILE, "<$pid_file") ) {
454         $pid = <LOCK_FILE>;
455         if( defined $pid ) {
456             chomp( $pid );
457             if( -f "/proc/$pid/stat" ) {
458                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
459                 if( $stat ) {
460                                         print STDERR "\nERROR: Already running!\n";
461                     close( LOCK_FILE );
462                     exit -1;
463                 }
464             }
465         }
466         close( LOCK_FILE );
467         unlink( $pid_file );
468     }
470     # create a syslog msg if it is not to possible to open PID file
471     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
472         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
473         if (open(LOCK_FILE, '<', $pid_file)
474                 && ($pid = <LOCK_FILE>))
475         {
476             chomp($pid);
477             $msg .= "(PID $pid)\n";
478         } else {
479             $msg .= "(unable to read PID)\n";
480         }
481         if( ! ($foreground) ) {
482             openlog( $0, "cons,pid", "daemon" );
483             syslog( "warning", $msg );
484             closelog();
485         }
486         else {
487             print( STDERR " $msg " );
488         }
489         exit( -1 );
490     }
493 #===  FUNCTION  ================================================================
494 #         NAME:  import_modules
495 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
496 #                are stored
497 #      RETURNS:  nothing
498 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
499 #                state is on is imported by "require 'file';"
500 #===============================================================================
501 sub import_modules {
502     if (not -e $modules_path) {
503         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
504     }
506     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
508     while (defined (my $file = readdir (DIR))) {
509         if (not $file =~ /(\S*?).pm$/) {
510             next;
511         }
512                 my $mod_name = $1;
514         # ArpHandler switch
515         if( $file =~ /ArpHandler.pm/ ) {
516             if( $arp_enabled eq "false" ) { next; }
517         }
519                 # ServerPackages switch
520                 if ($file eq "ServerPackages.pm" && $serverPackages_enabled eq "false") 
521                 {
522                         $dns_lookup = "false";
523                         next; 
524                 }
525         
526         eval { require $file; };
527         if ($@) {
528             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
529             daemon_log("$@", 1);
530             exit;
531                 } else {
532                         my $info = eval($mod_name.'::get_module_info()');
533                         # Only load module if get_module_info() returns a non-null object
534                         if( $info ) {
535                                 my ($input_address, $input_key, $event_hash) = @{$info};
536                                 $known_modules->{$mod_name} = $info;
537                                 daemon_log("0 INFO: module $mod_name loaded", 5);
538                         }
539                 }
540     }   
541     close (DIR);
544 #===  FUNCTION  ================================================================
545 #         NAME:  password_check
546 #   PARAMETERS:  nothing
547 #      RETURNS:  nothing
548 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
549 #                the same password
550 #===============================================================================
551 sub password_check {
552     my $passwd_hash = {};
553     while (my ($mod_name, $mod_info) = each %$known_modules) {
554         my $mod_passwd = @$mod_info[1];
555         if (not defined $mod_passwd) { next; }
556         if (not exists $passwd_hash->{$mod_passwd}) {
557             $passwd_hash->{$mod_passwd} = $mod_name;
559         # escalates critical error
560         } else {
561             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
562             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
563             exit( -1 );
564         }
565     }
570 #===  FUNCTION  ================================================================
571 #         NAME:  sig_int_handler
572 #   PARAMETERS:  signal - string - signal arose from system
573 #      RETURNS:  nothing
574 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
575 #===============================================================================
576 sub sig_int_handler {
577     my ($signal) = @_;
579 #       if (defined($ldap_handle)) {
580 #               $ldap_handle->disconnect;
581 #       }
582     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
583     
585     daemon_log("shutting down gosa-si-server", 1);
586     system("kill `ps -C gosa-si-server -o pid=`");
588 $SIG{INT} = \&sig_int_handler;
591 sub check_key_and_xml_validity {
592     my ($crypted_msg, $module_key, $session_id) = @_;
593     my $msg;
594     my $msg_hash;
595     my $error_string;
596     eval{
597         $msg = &decrypt_msg($crypted_msg, $module_key);
599         if ($msg =~ /<xml>/i){
600             $msg =~ s/\s+/ /g;  # just for better daemon_log
601             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 18);
602             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
604             ##############
605             # check header
606             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
607             my $header_l = $msg_hash->{'header'};
608             if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
609             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
610             my $header = @{$header_l}[0];
611             if( 0 == length $header) { die 'empty string in header tag'; }
613             ##############
614             # check source
615             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
616             my $source_l = $msg_hash->{'source'};
617             if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
618             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
619             my $source = @{$source_l}[0];
620             if( 0 == length $source) { die 'source error'; }
622             ##############
623             # check target
624             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
625             my $target_l = $msg_hash->{'target'};
626             if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
627         }
628     };
629     if($@) {
630         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
631         $msg = undef;
632         $msg_hash = undef;
633     }
635     return ($msg, $msg_hash);
639 sub check_outgoing_xml_validity {
640     my ($msg, $session_id) = @_;
642     my $msg_hash;
643     eval{
644         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
646         ##############
647         # check header
648         my $header_l = $msg_hash->{'header'};
649         if( 1 != @{$header_l} ) {
650             die 'no or more than one headers specified';
651         }
652         my $header = @{$header_l}[0];
653         if( 0 == length $header) {
654             die 'header has length 0';
655         }
657         ##############
658         # check source
659         my $source_l = $msg_hash->{'source'};
660         if( 1 != @{$source_l} ) {
661             die 'no or more than 1 sources specified';
662         }
663         my $source = @{$source_l}[0];
664         if( 0 == length $source) {
665             die 'source has length 0';
666         }
668                 # Check if source contains hostname instead of ip address
669                 if($source =~ /^[a-z][\w\-\.]+:\d+$/i) {
670                         my ($hostname,$port) = split(/:/, $source);
671                         my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
672                         if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
673                                 # Write ip address to $source variable
674                                 $source = "$ip_address:$port";
675                                 $msg_hash->{source}[0] = $source ;
676                                 $msg =~ s/<source>.*<\/source>/<source>$source<\/source>/; 
677                         }
678                 }
679         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
680                 $source =~ /^GOSA$/i) {
681             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
682         }
683         
684         ##############
685         # check target  
686         my $target_l = $msg_hash->{'target'};
687         if( 0 == @{$target_l} ) {
688             die "no targets specified";
689         }
690         foreach my $target (@$target_l) {
691             if( 0 == length $target) {
692                 die "target has length 0";
693             }
694             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
695                     $target =~ /^GOSA$/i ||
696                     $target =~ /^\*$/ ||
697                     $target =~ /KNOWN_SERVER/i ||
698                     $target =~ /JOBDB/i ||
699                     $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 ){
700                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
701             }
702         }
703     };
704     if($@) {
705         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
706         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
707         $msg_hash = undef;
708     }
710     return ($msg, $msg_hash);
714 sub input_from_known_server {
715     my ($input, $remote_ip, $session_id) = @_ ;  
716     my ($msg, $msg_hash, $module);
718     my $sql_statement= "SELECT * FROM known_server";
719     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
721     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
722         my $host_name = $hit->{hostname};
723         if( not $host_name =~ "^$remote_ip") {
724             next;
725         }
726         my $host_key = $hit->{hostkey};
727         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 14);
728         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 14);
730         # check if module can open msg envelope with module key
731         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
732         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
733             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 14);
734             daemon_log("$@", 14);
735             next;
736         }
737         else {
738             $msg = $tmp_msg;
739             $msg_hash = $tmp_msg_hash;
740             $module = "ServerPackages";
741             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
742             last;
743         }
744     }
746     if( (!$msg) || (!$msg_hash) || (!$module) ) {
747         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 14);
748     }
749   
750     return ($msg, $msg_hash, $module);
754 sub input_from_known_client {
755     my ($input, $remote_ip, $session_id) = @_ ;  
756     my ($msg, $msg_hash, $module);
758     my $sql_statement= "SELECT * FROM known_clients";
759     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
760     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
761         my $host_name = $hit->{hostname};
762         if( not $host_name =~ /^$remote_ip/) {
763                 next;
764                 }
765         my $host_key = $hit->{hostkey};
766         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 14);
767         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 14);
769         # check if module can open msg envelope with module key
770         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
772         if( (!$msg) || (!$msg_hash) ) {
773             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 14);
774             next;
775         }
776         else {
777             $module = "ClientPackages";
778             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
779             last;
780         }
781     }
783     if( (!$msg) || (!$msg_hash) || (!$module) ) {
784         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 14);
785     }
787     return ($msg, $msg_hash, $module);
791 sub input_from_unknown_host {
792         no strict "refs";
793         my ($input, $session_id) = @_ ;
794         my ($msg, $msg_hash, $module);
795         my $error_string;
797         my %act_modules = %$known_modules;
799         while( my ($mod, $info) = each(%act_modules)) {
801                 # check a key exists for this module
802                 my $module_key = ${$mod."_key"};
803                 if( not defined $module_key ) {
804                         if( $mod eq 'ArpHandler' ) {
805                                 next;
806                         }
807                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
808                         next;
809                 }
810                 daemon_log("$session_id DEBUG: $mod: $module_key", 14);
812                 # check if module can open msg envelope with module key
813                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
814                 if( (not defined $msg) || (not defined $msg_hash) ) {
815                         next;
816                 } else {
817                         $module = $mod;
818             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 18);
819                         last;
820                 }
821         }
823         if( (!$msg) || (!$msg_hash) || (!$module)) {
824                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 14);
825         }
827         return ($msg, $msg_hash, $module);
831 sub create_ciphering {
832     my ($passwd) = @_;
833         if((!defined($passwd)) || length($passwd)==0) {
834                 $passwd = "";
835         }
836     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
837     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
838     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
839     $my_cipher->set_iv($iv);
840     return $my_cipher;
844 sub encrypt_msg {
845     my ($msg, $key) = @_;
846     my $my_cipher = &create_ciphering($key);
847     my $len;
848     {
849             use bytes;
850             $len= 16-length($msg)%16;
851     }
852     $msg = "\0"x($len).$msg;
853     $msg = $my_cipher->encrypt($msg);
854     chomp($msg = &encode_base64($msg));
855     # there are no newlines allowed inside msg
856     $msg=~ s/\n//g;
857     return $msg;
861 sub decrypt_msg {
863     my ($msg, $key) = @_ ;
864     $msg = &decode_base64($msg);
865     my $my_cipher = &create_ciphering($key);
866     $msg = $my_cipher->decrypt($msg); 
867     $msg =~ s/\0*//g;
868     return $msg;
872 sub get_encrypt_key {
873     my ($target) = @_ ;
874     my $encrypt_key;
875     my $error = 0;
877     # target can be in known_server
878     if( not defined $encrypt_key ) {
879         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
880         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
881         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
882             my $host_name = $hit->{hostname};
883             if( $host_name ne $target ) {
884                 next;
885             }
886             $encrypt_key = $hit->{hostkey};
887             last;
888         }
889     }
891     # target can be in known_client
892     if( not defined $encrypt_key ) {
893         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
894         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
895         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
896             my $host_name = $hit->{hostname};
897             if( $host_name ne $target ) {
898                 next;
899             }
900             $encrypt_key = $hit->{hostkey};
901             last;
902         }
903     }
905     return $encrypt_key;
909 #===  FUNCTION  ================================================================
910 #         NAME:  open_socket
911 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
912 #                [PeerPort] string necessary if port not appended by PeerAddr
913 #      RETURNS:  socket IO::Socket::INET
914 #  DESCRIPTION:  open a socket to PeerAddr
915 #===============================================================================
916 sub open_socket {
917     my ($PeerAddr, $PeerPort) = @_ ;
918     if(defined($PeerPort)){
919         $PeerAddr = $PeerAddr.":".$PeerPort;
920     }
921     my $socket;
922     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
923             Porto => "tcp",
924             Type => SOCK_STREAM,
925             Timeout => 5,
926             );
927     if(not defined $socket) {
928         return;
929     }
930 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
931     return $socket;
935 sub send_msg_to_target {
936     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
937     my $error = 0;
938     my $header;
939     my $timestamp = &get_time();
940     my $new_status;
941     my $act_status;
942     my ($sql_statement, $res);
943   
944     if( $msg_header ) {
945         $header = "'$msg_header'-";
946     } else {
947         $header = "";
948     }
950         # Memorize own source address
951         my $own_source_address = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
952         $own_source_address .= ":".$server_port;
954         # Patch 0.0.0.0 source to real address
955         $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$own_source_address<\/source>/s;
956         # Patch GOSA source to real address and add forward_to_gosa tag
957         $msg =~ s/<source>GOSA<\/source>/<source>$own_source_address<\/source> <forward_to_gosa>$own_source_address,$session_id<\/forward_to_gosa>/ ;
959     # encrypt xml msg
960     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
962     # opensocket
963     my $socket = &open_socket($address);
964     if( !$socket ) {
965         daemon_log("$session_id ERROR: Cannot open socket to host '$address'. Message processing aborted!", 1);
966         $error++;
967     }
968     
969     if( $error == 0 ) {
970         # send xml msg
971         print $socket $crypted_msg.";$own_source_address\n";
972         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
973         daemon_log("$session_id DEBUG: message:\n$msg", 12);
974         
975     }
977     # close socket in any case
978     if( $socket ) {
979         close $socket;
980     }
982     if( $error > 0 ) { $new_status = "down"; }
983     else { $new_status = $msg_header; }
986     # known_clients
987     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
988     $res = $known_clients_db->select_dbentry($sql_statement);
989     if( keys(%$res) == 1) {
990         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
991         if ($act_status eq "down" && $new_status eq "down") {
992             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
993             $res = $known_clients_db->del_dbentry($sql_statement);
994             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
995         } else { 
996             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
997             $res = $known_clients_db->update_dbentry($sql_statement);
998             if($new_status eq "down"){
999                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1000             } else {
1001                 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1002             }
1003         }
1004     }
1006     # known_server
1007     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
1008     $res = $known_server_db->select_dbentry($sql_statement);
1009     if( keys(%$res) == 1) {
1010         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1011         if ($act_status eq "down" && $new_status eq "down") {
1012             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
1013             $res = $known_server_db->del_dbentry($sql_statement);
1014             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
1015         } 
1016         else { 
1017             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1018             $res = $known_server_db->update_dbentry($sql_statement);
1019             if($new_status eq "down"){
1020                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1021             } else {
1022                 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1023             }
1024         }
1025     }
1026     return $error; 
1030 sub update_jobdb_status_for_send_msgs {
1031     my ($session_id, $answer, $error) = @_;
1032     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1033                 &daemon_log("$session_id DEBUG: try to update job status", 138); 
1034         my $jobdb_id = $1;
1035     
1036         $answer =~ /<header>(.*)<\/header>/;
1037         my $job_header = $1;
1039         $answer =~ /<target>(.*)<\/target>/;
1040         my $job_target = $1;
1041             
1042         # Sending msg failed
1043         if( $error ) {
1045             # Set jobs to done, jobs do not need to deliver their message in any case
1046             if (($job_header eq "trigger_action_localboot")
1047                     ||($job_header eq "trigger_action_lock")
1048                     ||($job_header eq "trigger_action_halt") 
1049                     ) {
1050                 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1051                 my $res = $job_db->update_dbentry($sql_statement);
1052                 
1053             # Reactivate jobs, jobs need to deliver their message
1054             } elsif (($job_header eq "trigger_action_activate")
1055                     ||($job_header eq "trigger_action_update")
1056                     ||($job_header eq "trigger_action_reinstall") 
1057                     ||($job_header eq "trigger_activate_new")
1058                     ) {
1059                                                 if ($job_target =~ /^([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})$/i) {
1060                                                         &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1061                                                 } else {
1062                                                         # If we don't have the mac adress at this time, we use the plainname
1063                                                         my $plainname_result = $job_db->select_dbentry("SELECT plainname from jobs where id=$jobdb_id");
1064                                                         my $plainname = $job_target;
1065                                                         if ((keys(%$plainname_result) > 0) ) {
1066                                                                 $plainname = $plainname_result->{1}->{$job_target};
1067                                                         }
1068                                                         &reactivate_job_with_delay($session_id, $plainname, $job_header, 30 );
1069                                                 }
1070             # For all other messages
1071             } else {
1072                 my $sql_statement = "UPDATE $job_queue_tn ".
1073                     "SET status='error', result='can not deliver msg, please consult log file' ".
1074                     "WHERE id=$jobdb_id";
1075                 my $res = $job_db->update_dbentry($sql_statement);
1076             }
1078         # Sending msg was successful
1079         } else {
1080             # Set jobs localboot, lock, activate, halt, reboot and wake to done
1081             # jobs reinstall, update, inst_update do themself setting to done
1082             if (($job_header eq "trigger_action_localboot")
1083                     ||($job_header eq "trigger_action_lock")
1084                     ||($job_header eq "trigger_action_activate")
1085                     ||($job_header eq "trigger_action_halt") 
1086                     ||($job_header eq "trigger_action_reboot")
1087                     ||($job_header eq "trigger_action_wake")
1088                     ||($job_header eq "trigger_wake")
1089                     ) {
1091                 my $sql_statement = "UPDATE $job_queue_tn ".
1092                     "SET status='done' ".
1093                     "WHERE id=$jobdb_id AND status='processed'";
1094                 my $res = $job_db->update_dbentry($sql_statement);
1095             } else { 
1096                 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 138); 
1097             } 
1098         } 
1099     } else { 
1100         &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag.", 138); 
1101     }
1104 sub reactivate_job_with_delay {
1105     my ($session_id, $target, $header, $delay) = @_ ;
1106     # 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
1107     
1108     if (not defined $delay) { $delay = 30 } ;
1109     my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1111     my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress LIKE '$target' AND headertag='$header')"; 
1112     my $res = $job_db->update_dbentry($sql);
1113     daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1114             "cause client '$target' is currently not available", 5);
1115     return;
1119 sub sig_handler {
1120         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1121         daemon_log("0 INFO got signal '$signal'", 1); 
1122         $kernel->sig_handled();
1123         return;
1127 sub msg_to_decrypt {
1128         my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1129         my $session_id = $session->ID;
1130         my ($msg, $msg_hash, $module);
1131         my $error = 0;
1133         # fetch new msg out of @msgs_to_decrypt
1134         my $tmp_next_msg = shift @msgs_to_decrypt;
1135     my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1137         # msg is from a new client or gosa
1138         ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1140         # msg is from a gosa-si-server
1141         if(((!$msg) || (!$msg_hash) || (!$module)) && ($serverPackages_enabled eq "true")){
1142                 if (not defined $msg_source) 
1143                 {
1144                         # Only needed, to be compatible with older gosa-si-server versions
1145                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1146                 }
1147                 else
1148                 {
1149                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $msg_source, $session_id);
1150                 }
1151         }
1152         # msg is from a gosa-si-client
1153         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1154                 if (not defined $msg_source) 
1155                 {
1156                         # Only needed, to be compatible with older gosa-si-server versions
1157                         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1158                 }
1159                 else
1160                 {
1161                         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $msg_source, $session_id);
1162                 }
1163         }
1164         # an error occurred
1165         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1166                 # If an incoming msg could not be decrypted (maybe a wrong key), decide if msg comes from a client 
1167                 # or a server.  In case of a client, send a ping. If the client could not understand a msg from its 
1168                 # server the client cause a re-registering process. In case of a server, decrease update_time in kown_server_db
1169                 # and trigger a re-registering process for servers
1170                 if (defined $msg_source && $msg_source =~ /:$server_port$/ && $serverPackages_enabled eq "true")
1171                 {
1172                         daemon_log("$session_id WARNING: Cannot understand incoming msg from server '$msg_source'. Cause re-registration process for servers.", 3);
1173                         my $update_statement = "UPDATE $known_server_tn SET update_time='19700101000000' WHERE hostname='$msg_source'"; 
1174                         daemon_log("$session_id DEBUG: $update_statement", 7);
1175                         my $upadte_res = $known_server_db->exec_statement($update_statement);
1176                         $kernel->yield("register_at_foreign_servers");
1177                 }
1178                 elsif ((defined $msg_source) && (not $msg_source =~ /:$server_port$/))
1179                 {
1180                         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);
1181                         #my $remote_ip = $heap->{'remote_ip'};
1182                         #my $remote_port = $heap->{'remote_port'};
1183                         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1184                         my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1185                         daemon_log("$session_id WARNING: Sending msg to cause re-registering: $ping_msg", 3);
1186                 }
1187                 else
1188                 {
1189                         my $foreign_host = defined $msg_source ? $msg_source : $heap->{'remote_ip'};
1190                         daemon_log("$session_id ERROR: Incoming message from host '$foreign_host' cannot be understood. Processing aborted!", 1);
1191                         daemon_log("$session_id DEBUG: Aborted message: $tmp_next_msg", 11);
1192                 }
1194                 $error++
1195         }
1198         my $header;
1199         my $target;
1200         my $source;
1201         my $done = 0;
1202         my $sql;
1203         my $res;
1205         # check whether this message should be processed here
1206         if ($error == 0) {
1207                 $header = @{$msg_hash->{'header'}}[0];
1208                 $target = @{$msg_hash->{'target'}}[0];
1209                 $source = @{$msg_hash->{'source'}}[0];
1210                 my $not_found_in_known_clients_db = 0;
1211                 my $not_found_in_known_server_db = 0;
1212                 my $not_found_in_foreign_clients_db = 0;
1213                 my $local_address;
1214                 my $local_mac;
1215                 my ($target_ip, $target_port) = split(':', $target);
1217                 # Determine the local ip address if target is an ip address
1218                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1219                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1220                 } else {
1221                         $local_address = $server_address;
1222                 }
1224                 # Determine the local mac address if target is a mac address
1225                 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) {
1226                         my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1227                         my $network_interface= &get_interface_for_ip($loc_ip);
1228                         $local_mac = &get_mac_for_interface($network_interface);
1229                 } else {
1230                         $local_mac = $server_mac_address;
1231                 }
1233                 # target and source is equal to GOSA -> process here
1234                 if (not $done) {
1235                         if ($target eq "GOSA" && $source eq "GOSA") {
1236                                 $done = 1;                    
1237                                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process '$header' here", 11);
1238                         }
1239                 }
1241                 # target is own address without forward_to_gosa-tag -> process here
1242                 if (not $done) {
1243                         #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1244                         if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1245                                 $done = 1;
1246                                 if ($source eq "GOSA") {
1247                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1248                                 }
1249                                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process '$header' here", 11);
1250                         }
1251                 }
1253                 # target is own address with forward_to_gosa-tag not pointing to myself -> process here
1254                 if (not $done) {
1255                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1256                         my $gosa_at;
1257                         my $gosa_session_id;
1258                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1259                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1260                                 if ($gosa_at ne $local_address) {
1261                                         $done = 1;
1262                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process '$header' here", 11); 
1263                                 }
1264                         }
1265                 }
1267                 # Target is a client address and there is a processing function within a plugin -> process loaclly
1268                 if (not $done)
1269                 {
1270                         # Check if target is a client address
1271                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1272                         $res = $known_clients_db->select_dbentry($sql);
1273                         if ((keys(%$res) > 0) ) 
1274                         {
1275                                 my $hostname = $res->{1}->{'hostname'};
1276                                 my $reduced_header = $header;
1277                                 $reduced_header =~ s/gosa_//;
1278                                 # Check if there is a processing function within a plugin
1279                                 if (exists $known_functions->{$reduced_header}) 
1280                                 {
1281                                         $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1282                                         $done = 1;
1283                                         &daemon_log("$session_id DEBUG: Target is client address with processing function within a plugin -> process '$header' here", 11);
1284                                 }
1285                         }
1286                 }
1288                 # If header has a 'job_' prefix, do always process message locally
1289                 # which means put it into job queue
1290                 if ((not $done) && ($header =~ /job_/))
1291                 {
1292                         $done = 1;
1293                         &daemon_log("$session_id DEBUG: Header has a 'job_' prefix. Put it into job queue. -> process '$header' here", 11);
1294                 }
1296                 # if message should be processed here -> add message to incoming_db
1297                 if ($done) {
1298                         # if a 'job_' or a 'gosa_' message comes from a foreign server, fake module from
1299                         # ServerPackages to GosaPackages so gosa-si-server knows how to process this kind of messages
1300                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1301                                 $module = "GosaPackages";
1302                         }
1304                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1305                                         primkey=>[],
1306                                         headertag=>$header,
1307                                         targettag=>$target,
1308                                         xmlmessage=>&encode_base64($msg),
1309                                         timestamp=>&get_time,
1310                                         module=>$module,
1311                                         sessionid=>$session_id,
1312                                 } );
1314                 }
1316                 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1317                 if (not $done) {
1318                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1319                         my $gosa_at;
1320                         my $gosa_session_id;
1321                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1322                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1323                                 if ($gosa_at eq $local_address) {
1324                                         my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1325                                         if( defined $session_reference ) {
1326                                                 $heap = $session_reference->get_heap();
1327                                         }
1328                                         if(exists $heap->{'client'}) {
1329                                                 $msg = &encrypt_msg($msg, $GosaPackages_key);
1330                                                 $heap->{'client'}->put($msg);
1331                                                 &daemon_log("$session_id DEBUG: incoming '$header' message forwarded to GOsa", 11); 
1332                                         }
1333                                         $done = 1;
1334                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward '$header' to gosa", 11);
1335                                 }
1336                         }
1338                 }
1340                 # target is a client address in known_clients -> forward to client
1341                 if (not $done) {
1342                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1343                         $res = $known_clients_db->select_dbentry($sql);
1344                         if (keys(%$res) > 0) 
1345                         {
1346                                 $done = 1; 
1347                                 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> forward '$header' to client", 11);
1348                                 my $hostkey = $res->{1}->{'hostkey'};
1349                                 my $hostname = $res->{1}->{'hostname'};
1350                                 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1351                                 $msg =~ s/<header>gosa_/<header>/;
1352                                 my $error= &send_msg_to_target($msg, $hostname, $hostkey, $header, $session_id);
1353                                 if ($error) {
1354                                         &daemon_log("$session_id ERROR: Some problems occurred while trying to send msg to client '$hostname': $msg", 1);
1355                                 }
1356                         } 
1357                         else 
1358                         {
1359                                 $not_found_in_known_clients_db = 1;
1360                         }
1361                 }
1363                 # target is a client address in foreign_clients -> forward to registration server
1364                 if (not $done) {
1365                         $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1366                         $res = $foreign_clients_db->select_dbentry($sql);
1367                         if (keys(%$res) > 0) {
1368                                 my $hostname = $res->{1}->{'hostname'};
1369                                 my ($host_ip, $host_port) = split(/:/, $hostname);
1370                                 my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1371                                 my $regserver = $res->{1}->{'regserver'};
1372                                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1373                                 my $res = $known_server_db->select_dbentry($sql);
1374                                 if (keys(%$res) > 0) {
1375                                         my $regserver_key = $res->{1}->{'hostkey'};
1376                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1377                                         $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1378                                         if ($source eq "GOSA") {
1379                                                 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1380                                         }
1381                                         my $error= &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1382                                         if ($error) {
1383                                                 &daemon_log("$session_id ERROR: some problems occurred while trying to send msg to registration server: $msg", 1); 
1384                                         }
1385                                 }
1386                                 $done = 1;
1387                                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward '$header' to registration server", 11);
1388                         } else {
1389                                 $not_found_in_foreign_clients_db = 1;
1390                         }
1391                 }
1393                 # target is a server address -> forward to server
1394                 if (not $done) {
1395                         $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1396                         $res = $known_server_db->select_dbentry($sql);
1397                         if (keys(%$res) > 0) {
1398                                 my $hostkey = $res->{1}->{'hostkey'};
1400                                 if ($source eq "GOSA") {
1401                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1402                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1404                                 }
1406                                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1407                                 $done = 1;
1408                                 &daemon_log("$session_id DEBUG: target is a server address -> forward '$header' to server", 11);
1409                         } else {
1410                                 $not_found_in_known_server_db = 1;
1411                         }
1412                 }
1415                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1416                 if ( $not_found_in_foreign_clients_db 
1417                         && $not_found_in_known_server_db
1418                         && $not_found_in_known_clients_db) {
1419                         &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);
1420             if ($header =~ /^gosa_/ || $header =~ /^job_/) { 
1421                 $module = "GosaPackages"; 
1422             }
1423                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1424                                         primkey=>[],
1425                                         headertag=>$header,
1426                                         targettag=>$target,
1427                                         xmlmessage=>&encode_base64($msg),
1428                                         timestamp=>&get_time,
1429                                         module=>$module,
1430                                         sessionid=>$session_id,
1431                                 } );
1432                         $done = 1;
1433                 }
1436                 if (not $done) {
1437                         daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1438                         if ($source eq "GOSA") {
1439                                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1440                                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1442                                 my $session_reference = $kernel->ID_id_to_session($session_id);
1443                                 if( defined $session_reference ) {
1444                                         $heap = $session_reference->get_heap();
1445                                 }
1446                                 if(exists $heap->{'client'}) {
1447                                         $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1448                                         $heap->{'client'}->put($error_msg);
1449                                 }
1450                         }
1451                 }
1453         }
1455         return;
1459 sub next_task {
1460     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0, ARG1];
1461     my $running_task = POE::Wheel::Run->new(
1462             Program => sub { process_task($session, $heap, $task) },
1463             StdioFilter => POE::Filter::Reference->new(),
1464             StdoutEvent  => "task_result",
1465             StderrEvent  => "task_debug",
1466             CloseEvent   => "task_done",
1467             );
1468     $heap->{task}->{ $running_task->ID } = $running_task;
1471 sub handle_task_result {
1472     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1473     my $client_answer = $result->{'answer'};
1474     if( $client_answer =~ s/session_id=(\d+)$// ) {
1475         my $session_id = $1;
1476         if( defined $session_id ) {
1477             my $session_reference = $kernel->ID_id_to_session($session_id);
1478             if( defined $session_reference ) {
1479                 $heap = $session_reference->get_heap();
1480             }
1481         }
1483         if(exists $heap->{'client'}) {
1484             $heap->{'client'}->put($client_answer);
1485         }
1486     }
1487     $kernel->sig(CHLD => "child_reap");
1490 sub handle_task_debug {
1491     my $result = $_[ARG0];
1492     print STDERR "$result\n";
1495 sub handle_task_done {
1496     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1497     delete $heap->{task}->{$task_id};
1498         if (exists $heap->{ldap_handle}->{$task_id}) {
1499                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
1500         }
1503 sub process_task {
1504     no strict "refs";
1505     #CHECK: Not @_[...]?
1506     my ($session, $heap, $task) = @_;
1507     my $error = 0;
1508     my $answer_l;
1509     my ($answer_header, @answer_target_l, $answer_source);
1510     my $client_answer = "";
1512     # prepare all variables needed to process message
1513     #my $msg = $task->{'xmlmessage'};
1514     my $msg = &decode_base64($task->{'xmlmessage'});
1515     my $incoming_id = $task->{'id'};
1516     my $module = $task->{'module'};
1517     my $header =  $task->{'headertag'};
1518     my $session_id = $task->{'sessionid'};
1519                 my $msg_hash;
1520                 eval {
1521         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1522                 }; 
1523                 daemon_log("ERROR: XML failure '$@'") if ($@);
1524     my $source = @{$msg_hash->{'source'}}[0];
1525     
1526     # set timestamp of incoming client uptodate, so client will not 
1527     # be deleted from known_clients because of expiration
1528     my $cur_time = &get_time();
1529     my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'"; 
1530     my $res = $known_clients_db->exec_statement($sql);
1532     ######################
1533     # process incoming msg
1534     if( $error == 0) {
1535         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1536         daemon_log("$session_id DEBUG: Processing module ".$module, 26);
1537         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1539         if ( 0 < @{$answer_l} ) {
1540             my $answer_str = join("\n", @{$answer_l});
1541                         my @headers; 
1542             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1543                                 push(@headers, $1);
1544             }
1545                         daemon_log("$session_id INFO: got answer message(s) with header: '".join("', '", @headers)."'", 5);
1546             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,26);
1547         } else {
1548             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,26);
1549         }
1551     }
1552     if( !$answer_l ) { $error++ };
1554     ########
1555     # answer
1556     if( $error == 0 ) {
1558         foreach my $answer ( @{$answer_l} ) {
1559             # check outgoing msg to xml validity
1560             my ($answer, $answer_hash) = &check_outgoing_xml_validity($answer, $session_id);
1561             if( not defined $answer_hash ) { next; }
1562             
1563             $answer_header = @{$answer_hash->{'header'}}[0];
1564             @answer_target_l = @{$answer_hash->{'target'}};
1565             $answer_source = @{$answer_hash->{'source'}}[0];
1567             # deliver msg to all targets 
1568             foreach my $answer_target ( @answer_target_l ) {
1570                 # targets of msg are all gosa-si-clients in known_clients_db
1571                 if( $answer_target eq "*" ) {
1572                     # answer is for all clients
1573                     my $sql_statement= "SELECT * FROM known_clients";
1574                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1575                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1576                         my $host_name = $hit->{hostname};
1577                         my $host_key = $hit->{hostkey};
1578                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1579                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1580                     }
1581                 }
1583                 # targets of msg are all gosa-si-server in known_server_db
1584                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1585                     # answer is for all server in known_server
1586                     my $sql_statement= "SELECT * FROM $known_server_tn";
1587                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1588                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1589                         my $host_name = $hit->{hostname};
1590                         my $host_key = $hit->{hostkey};
1591                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1592                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1593                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1594                     }
1595                 }
1597                 # target of msg is GOsa
1598                                 elsif( $answer_target eq "GOSA" ) {
1599                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1600                                         my $add_on = "";
1601                     if( defined $session_id ) {
1602                         $add_on = ".session_id=$session_id";
1603                     }
1604                     # answer is for GOSA and has to returned to connected client
1605                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1606                     $client_answer = $gosa_answer.$add_on;
1607                 }
1609                 # target of msg is job queue at this host
1610                 elsif( $answer_target eq "JOBDB") {
1611                     $answer =~ /<header>(\S+)<\/header>/;   
1612                     my $header;
1613                     if( defined $1 ) { $header = $1; }
1614                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1615                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1616                 }
1618                 # Target of msg is a mac address
1619                 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 ) {
1620                     daemon_log("$session_id DEBUG: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 138);
1622                     # Looking for macaddress in known_clients
1623                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1624                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1625                     my $found_ip_flag = 0;
1626                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1627                         my $host_name = $hit->{hostname};
1628                         my $host_key = $hit->{hostkey};
1629                         $answer =~ s/$answer_target/$host_name/g;
1630                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1631                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1632                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1633                         $found_ip_flag++ ;
1634                     }   
1636                     # Looking for macaddress in foreign_clients
1637                     if ($found_ip_flag == 0) {
1638                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1639                         my $res = $foreign_clients_db->select_dbentry($sql);
1640                         while( my ($hit_num, $hit) = each %{ $res } ) {
1641                             my $host_name = $hit->{hostname};
1642                             my $reg_server = $hit->{regserver};
1643                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1644                             
1645                             # Fetch key for reg_server
1646                             my $reg_server_key;
1647                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1648                             my $res = $known_server_db->select_dbentry($sql);
1649                             if (exists $res->{1}) {
1650                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1651                             } else {
1652                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1653                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1654                                 $reg_server_key = undef;
1655                             }
1657                             # Send answer to server where client is registered
1658                             if (defined $reg_server_key) {
1659                                 $answer =~ s/$answer_target/$host_name/g;
1660                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1661                                 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1662                                 $found_ip_flag++ ;
1663                             }
1664                         }
1665                     }
1667                     # No mac to ip matching found
1668                     if( $found_ip_flag == 0) {
1669                         daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1670                         &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1671                     }
1673                 # Answer is for one specific host   
1674                 } else {
1675                     # get encrypt_key
1676                     my $encrypt_key = &get_encrypt_key($answer_target);
1677                     if( not defined $encrypt_key ) {
1678                         # unknown target
1679                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1680                         next;
1681                     }
1682                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1683                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1684                 }
1685             }
1686         }
1687     }
1689     my $filter = POE::Filter::Reference->new();
1690     my %result = ( 
1691             status => "seems ok to me",
1692             answer => $client_answer,
1693             );
1695     my $output = $filter->put( [ \%result ] );
1696     print @$output;
1701 sub session_start {
1702     my ($kernel) = $_[KERNEL];
1703     $global_kernel = $kernel;
1704     $kernel->yield('register_at_foreign_servers');
1705         $kernel->yield('create_fai_server_db', $fai_server_tn );
1706         $kernel->yield('create_fai_release_db', $fai_release_tn );
1707     $kernel->yield('watch_for_next_tasks');
1708         $kernel->sig(USR1 => "sig_handler");
1709         $kernel->sig(USR2 => "recreate_packages_db");
1710         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1711         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1712     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1713         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1714     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1715         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1716     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1718     # Start opsi check
1719     if ($opsi_enabled eq "true") {
1720         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1721     }
1726 sub watch_for_done_jobs {
1727         my $kernel = $_[KERNEL];
1729         my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1730         my $res = $job_db->select_dbentry( $sql_statement );
1732         while( my ($number, $hit) = each %{$res} ) 
1733         {
1734                 # Non periodical jobs can be deleted.
1735                 if ($hit->{periodic} eq "none")
1736                 {
1737                         my $jobdb_id = $hit->{id};
1738                         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1739                         my $res = $job_db->del_dbentry($sql_statement); 
1740                 }
1742                 # Periodical jobs should not be deleted but reactivated with new timestamp instead.
1743                 else
1744                 {
1745                         my ($p_time, $periodic) = split("_", $hit->{periodic});
1746                         my $reactivated_ts = $hit->{timestamp};
1747                         my $act_ts = int(&get_time());
1748                         while ($act_ts > int($reactivated_ts))   # Redo calculation to avoid multiple jobs in the past
1749                         {
1750                                 $reactivated_ts = &calc_timestamp($reactivated_ts, "plus", $p_time, $periodic);
1751                         }
1752                         my $sql = "UPDATE $job_queue_tn SET status='waiting', timestamp='$reactivated_ts' WHERE id='".$hit->{id}."'"; 
1753                         my $res = $job_db->exec_statement($sql);
1754                         &daemon_log("J INFO: Update periodical job '".$hit->{headertag}."' for client '".$hit->{targettag}."'. New execution time '$reactivated_ts'.", 5);
1755                 }
1756         }
1758         $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1762 sub watch_for_opsi_jobs {
1763     my ($kernel) = $_[KERNEL];
1765     # 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 
1766     # opsi install job is to parse the xml message. There is still the correct header.
1767     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1768         my $res = $job_db->select_dbentry( $sql_statement );
1770     # Ask OPSI for an update of the running jobs
1771     while (my ($id, $hit) = each %$res ) {
1772         # Determine current parameters of the job
1773         my $hostId = $hit->{'plainname'};
1774         my $macaddress = $hit->{'macaddress'};
1775         my $progress = $hit->{'progress'};
1777         my $result= {};
1778         
1779         # For hosts, only return the products that are or get installed
1780         my $callobj;
1781         $callobj = {
1782             method  => 'getProductStates_hash',
1783             params  => [ $hostId ],
1784             id  => 1,
1785         };
1786         
1787         my $hres = $opsi_client->call($opsi_url, $callobj);
1788         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1789         if (not &check_opsi_res($hres)) {
1790             my $htmp= $hres->result->{$hostId};
1791         
1792             # Check state != not_installed or action == setup -> load and add
1793             my $products= 0;
1794             my $installed= 0;
1795             my $installing = 0;
1796             my $error= 0;  
1797             my @installed_list;
1798             my @error_list;
1799             my $act_status = "none";
1800             foreach my $product (@{$htmp}){
1802                 if ($product->{'installationStatus'} ne "not_installed" or
1803                         $product->{'actionRequest'} eq "setup"){
1805                     # Increase number of products for this host
1806                     $products++;
1807         
1808                     if ($product->{'installationStatus'} eq "failed"){
1809                         $result->{$product->{'productId'}}= "error";
1810                         unshift(@error_list, $product->{'productId'});
1811                         $error++;
1812                     }
1813                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1814                         $result->{$product->{'productId'}}= "installed";
1815                         unshift(@installed_list, $product->{'productId'});
1816                         $installed++;
1817                     }
1818                     if ($product->{'installationStatus'} eq "installing"){
1819                         $result->{$product->{'productId'}}= "installing";
1820                         $installing++;
1821                         $act_status = "installing - ".$product->{'productId'};
1822                     }
1823                 }
1824             }
1825         
1826             # Estimate "rough" progress, avoid division by zero
1827             if ($products == 0) {
1828                 $result->{'progress'}= 0;
1829             } else {
1830                 $result->{'progress'}= int($installed * 100 / $products);
1831             }
1833             # Set updates in job queue
1834             if ((not $error) && (not $installing) && ($installed)) {
1835                 $act_status = "installed - ".join(", ", @installed_list);
1836             }
1837             if ($error) {
1838                 $act_status = "error - ".join(", ", @error_list);
1839             }
1840             if ($progress ne $result->{'progress'} ) {
1841                 # Updating progress and result 
1842                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1843                 my $update_res = $job_db->update_dbentry($update_statement);
1844             }
1845             if ($progress eq 100) { 
1846                 # Updateing status
1847                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1848                 if ($error) {
1849                     $done_statement .= "status='error'";
1850                 } else {
1851                     $done_statement .= "status='done'";
1852                 }
1853                 $done_statement .= " WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1854                 my $done_res = $job_db->update_dbentry($done_statement);
1855             }
1858         }
1859     }
1861     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1865 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1866 sub watch_for_modified_jobs {
1867     my ($kernel,$heap) = @_[KERNEL, HEAP];
1869     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')"; 
1870     my $res = $job_db->select_dbentry( $sql_statement );
1871     
1872     # if db contains no jobs which should be update, do nothing
1873     if (keys %$res != 0) {
1875         if ($job_synchronization  eq "true") {
1876             # make out of the db result a gosa-si message   
1877             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1878  
1879             # update all other SI-server
1880             &inform_all_other_si_server($update_msg);
1881         }
1883         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1884         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1885         $res = $job_db->update_dbentry($sql_statement);
1886     }
1888     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1892 sub watch_for_new_jobs {
1893         if($watch_for_new_jobs_in_progress == 0) {
1894                 $watch_for_new_jobs_in_progress = 1;
1895                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1897                 # check gosa job queue for jobs with executable timestamp
1898                 my $timestamp = &get_time();
1899                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE siserver='localhost' AND status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1900                 my $res = $job_db->exec_statement( $sql_statement );
1902                 # Merge all new jobs that would do the same actions
1903                 my @drops;
1904                 my $hits;
1905                 foreach my $hit (reverse @{$res} ) {
1906                         my $macaddress= lc @{$hit}[8];
1907                         my $headertag= @{$hit}[5];
1908                         if(
1909                                 defined($hits->{$macaddress}) &&
1910                                 defined($hits->{$macaddress}->{$headertag}) &&
1911                                 defined($hits->{$macaddress}->{$headertag}[0])
1912                         ) {
1913                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1914                         }
1915                         $hits->{$macaddress}->{$headertag}= $hit;
1916                 }
1918                 # Delete new jobs with a matching job in state 'processing'
1919                 foreach my $macaddress (keys %{$hits}) {
1920                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1921                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1922                                 if(defined($jobdb_id)) {
1923                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1924                                         my $res = $job_db->exec_statement( $sql_statement );
1925                                         foreach my $hit (@{$res}) {
1926                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1927                                         }
1928                                 } else {
1929                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1930                                 }
1931                         }
1932                 }
1934                 # Commit deletion
1935                 $job_db->exec_statementlist(\@drops);
1937                 # Look for new jobs that could be executed
1938                 foreach my $macaddress (keys %{$hits}) {
1940                         # Look if there is an executing job
1941                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1942                         my $res = $job_db->exec_statement( $sql_statement );
1944                         # Skip new jobs for host if there is a processing job
1945                         if(defined($res) and defined @{$res}[0]) {
1946                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1947                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1948                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1949                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1950                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1951                                         if(defined($res_2) and defined @{$res_2}[0]) {
1952                                                 # Set status from goto-activation to 'waiting' and update timestamp
1953                                                 $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'");
1954                                         }
1955                                 }
1956                                 next;
1957                         }
1959                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1960                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1961                                 if(defined($jobdb_id)) {
1962                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1964                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1965                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1966                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1968                                         # expect macaddress is unique!!!!!!
1969                                         my $target = $res_hash->{1}->{hostname};
1971                                         # change header
1972                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1974                                         # add sqlite_id
1975                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1977                                         $job_msg =~ /<header>(\S+)<\/header>/;
1978                                         my $header = $1 ;
1979                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");                    
1981                                         # update status in job queue to ...
1982                     # ... 'processing', for jobs: 'reinstall', 'update', activate_new
1983                     if (($header =~ /gosa_trigger_action_reinstall/) 
1984                             || ($header =~ /gosa_trigger_activate_new/)
1985                             || ($header =~ /gosa_trigger_action_update/)) {
1986                         my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1987                         my $dbres = $job_db->update_dbentry($sql_statement);
1988                     }
1990                     # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1991                     else {
1992                         my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1993                         my $dbres = $job_db->exec_statement($sql_statement);
1994                     }
1995                 
1997                                         # We don't want parallel processing
1998                                         last;
1999                                 }
2000                         }
2001                 }
2003                 $watch_for_new_jobs_in_progress = 0;
2004                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
2005         }
2009 sub watch_for_new_messages {
2010     my ($kernel,$heap) = @_[KERNEL, HEAP];
2011     my @coll_user_msg;   # collection list of outgoing messages
2012     
2013     # check messaging_db for new incoming messages with executable timestamp
2014     my $timestamp = &get_time();
2015     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
2016     my $res = $messaging_db->exec_statement( $sql_statement );
2017         foreach my $hit (@{$res}) {
2019         # create outgoing messages
2020         my $message_to = @{$hit}[3];
2021         # translate message_to to plain login name
2022         my @message_to_l = split(/,/, $message_to);  
2023                 my %receiver_h; 
2024                 foreach my $receiver (@message_to_l) {
2025                         if ($receiver =~ /^u_([\s\S]*)$/) {
2026                                 $receiver_h{$1} = 0;
2027                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
2028                                 my $group_name = $1;
2029                                 # fetch all group members from ldap and add them to receiver hash
2030                                 my $ldap_handle = &get_ldap_handle();
2031                                 if (defined $ldap_handle) {
2032                                                 my $mesg = $ldap_handle->search(
2033                                                                                 base => $ldap_base,
2034                                                                                 scope => 'sub',
2035                                                                                 attrs => ['memberUid'],
2036                                                                                 filter => "cn=$group_name",
2037                                                                                 );
2038                                                 if ($mesg->count) {
2039                                                                 my @entries = $mesg->entries;
2040                                                                 foreach my $entry (@entries) {
2041                                                                                 my @receivers= $entry->get_value("memberUid");
2042                                                                                 foreach my $receiver (@receivers) { 
2043                                                                                                 $receiver_h{$receiver} = 0;
2044                                                                                 }
2045                                                                 }
2046                                                 } 
2047                                                 # translating errors ?
2048                                                 if ($mesg->code) {
2049                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
2050                                                 }
2051                                                 &release_ldap_handle($ldap_handle);
2052                                 # ldap handle error ?           
2053                                 } else {
2054                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
2055                                 }
2056                         } else {
2057                                 my $sbjct = &encode_base64(@{$hit}[1]);
2058                                 my $msg = &encode_base64(@{$hit}[7]);
2059                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
2060                         }
2061                 }
2062                 my @receiver_l = keys(%receiver_h);
2064         my $message_id = @{$hit}[0];
2066         #add each outgoing msg to messaging_db
2067         my $receiver;
2068         foreach $receiver (@receiver_l) {
2069             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
2070                 "VALUES ('".
2071                 $message_id."', '".    # id
2072                 @{$hit}[1]."', '".     # subject
2073                 @{$hit}[2]."', '".     # message_from
2074                 $receiver."', '".      # message_to
2075                 "none"."', '".         # flag
2076                 "out"."', '".          # direction
2077                 @{$hit}[6]."', '".     # delivery_time
2078                 @{$hit}[7]."', '".     # message
2079                 $timestamp."'".     # timestamp
2080                 ")";
2081             &daemon_log("M DEBUG: $sql_statement", 1);
2082             my $res = $messaging_db->exec_statement($sql_statement);
2083             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
2084         }
2086         # set incoming message to flag d=deliverd
2087         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
2088         &daemon_log("M DEBUG: $sql_statement", 7);
2089         $res = $messaging_db->update_dbentry($sql_statement);
2090         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
2091     }
2093     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
2094     return;
2097 sub watch_for_delivery_messages {
2098     my ($kernel, $heap) = @_[KERNEL, HEAP];
2100     # select outgoing messages
2101     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
2102     my $res = $messaging_db->exec_statement( $sql_statement );
2103     
2104     # build out msg for each    usr
2105     foreach my $hit (@{$res}) {
2106         my $receiver = @{$hit}[3];
2107         my $msg_id = @{$hit}[0];
2108         my $subject = @{$hit}[1];
2109         my $message = @{$hit}[7];
2111         # resolve usr -> host where usr is logged in
2112         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
2113         my $res = $login_users_db->exec_statement($sql);
2115         # receiver is logged in nowhere
2116         if (not ref(@$res[0]) eq "ARRAY") { next; }    
2118         # receiver ist logged in at a client registered at local server
2119                 my $send_succeed = 0;
2120                 foreach my $hit (@$res) {
2121                                 my $receiver_host = @$hit[0];
2122                 my $delivered2host = 0;
2123                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
2125                                 # Looking for host in know_clients_db 
2126                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
2127                                 my $res = $known_clients_db->exec_statement($sql);
2129                 # Host is known in known_clients_db
2130                 if (ref(@$res[0]) eq "ARRAY") {
2131                     my $receiver_key = @{@{$res}[0]}[2];
2132                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2133                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2134                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
2135                     if ($error == 0 ) {
2136                         $send_succeed++ ;
2137                         $delivered2host++ ;
2138                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
2139                     } else {
2140                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
2141                     }
2142                 }
2143                 
2144                 # Message already send, do not need to do anything more, otherwise ...
2145                 if ($delivered2host) { next;}
2146     
2147                 # ...looking for host in foreign_clients_db
2148                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2149                 $res = $foreign_clients_db->exec_statement($sql);
2150   
2151                                 # Host is known in foreign_clients_db 
2152                                 if (ref(@$res[0]) eq "ARRAY") { 
2153                     my $registration_server = @{@{$res}[0]}[2];
2154                     
2155                     # Fetch encryption key for registration server
2156                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2157                     my $res = $known_server_db->exec_statement($sql);
2158                     if (ref(@$res[0]) eq "ARRAY") { 
2159                         my $registration_server_key = @{@{$res}[0]}[3];
2160                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2161                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2162                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
2163                         if ($error == 0 ) {
2164                             $send_succeed++ ;
2165                             $delivered2host++ ;
2166                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
2167                         } else {
2168                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
2169                         }
2171                     } else {
2172                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2173                                 "registrated at server '$registration_server', ".
2174                                 "but no data available in known_server_db ", 1); 
2175                     }
2176                 }
2177                 
2178                 if (not $delivered2host) {
2179                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2180                 }
2181                 }
2183                 if ($send_succeed) {
2184                                 # set outgoing msg at db to deliverd
2185                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
2186                                 my $res = $messaging_db->exec_statement($sql); 
2187                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2188                 } else {
2189             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
2190         }
2191         }
2193     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
2194     return;
2198 sub watch_for_done_messages {
2199     my ($kernel,$heap) = @_[KERNEL, HEAP];
2201     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
2202     my $res = $messaging_db->exec_statement($sql); 
2204     foreach my $hit (@{$res}) {
2205         my $msg_id = @{$hit}[0];
2207         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
2208         my $res = $messaging_db->exec_statement($sql);
2210         # not all usr msgs have been seen till now
2211         if ( ref(@$res[0]) eq "ARRAY") { next; }
2212         
2213         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
2214         $res = $messaging_db->exec_statement($sql);
2215     
2216     }
2218     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2219     return;
2223 sub watch_for_old_known_clients {
2224     my ($kernel,$heap) = @_[KERNEL, HEAP];
2226     my $sql_statement = "SELECT * FROM $known_clients_tn";
2227     my $res = $known_clients_db->select_dbentry( $sql_statement );
2229     my $cur_time = int(&get_time());
2231     while ( my ($hit_num, $hit) = each %$res) {
2232         my $expired_timestamp = int($hit->{'timestamp'});
2233         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2234         my $dt = DateTime->new( year   => $1,
2235                 month  => $2,
2236                 day    => $3,
2237                 hour   => $4,
2238                 minute => $5,
2239                 second => $6,
2240                 );
2242         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2243         $expired_timestamp = $dt->ymd('').$dt->hms('');
2244         if ($cur_time > $expired_timestamp) {
2245             my $hostname = $hit->{'hostname'};
2246             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2247             my $del_res = $known_clients_db->exec_statement($del_sql);
2249             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2250         }
2252     }
2254     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2258 sub watch_for_next_tasks {
2259     my ($kernel,$heap) = @_[KERNEL, HEAP];
2261     my $sql = "SELECT * FROM $incoming_tn";
2262     my $res = $incoming_db->select_dbentry($sql);
2263     
2264     while ( my ($hit_num, $hit) = each %$res) {
2265         my $headertag = $hit->{'headertag'};
2266         if ($headertag =~ /^answer_(\d+)/) {
2267             # do not start processing, this message is for a still running POE::Wheel
2268             next;
2269         }
2270         my $message_id = $hit->{'id'};
2271         my $session_id = $hit->{'sessionid'};
2272         &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 11);
2274         $kernel->yield('next_task', $hit);
2276         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2277         my $res = $incoming_db->exec_statement($sql);
2278     }
2280     $kernel->delay_set('watch_for_next_tasks', 1); 
2284 sub get_ldap_handle {
2285         my ($session_id) = @_;
2286         my $heap;
2288         if (not defined $session_id ) { $session_id = 0 };
2289         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2291         my ($package, $file, $row, $subroutine, $hasArgs, $wantArray, $evalText, $isRequire) = caller(1);
2292         my $caller_text = "subroutine $subroutine";
2293         if ($subroutine eq "(eval)") {
2294                 $caller_text = "eval block within file '$file' for '$evalText'"; 
2295         }
2296         daemon_log("$session_id DEBUG: new ldap handle for '$caller_text' required!", 42);
2298 get_handle:
2299         my $ldap_handle = Net::LDAP->new( $ldap_uri );
2300         if (not ref $ldap_handle) {
2301                 daemon_log("$session_id ERROR: Connection to LDAP URI '$ldap_uri' failed! Retrying!", 1);
2302                 usleep(100000);
2303                 goto get_handle;
2304         } else {
2305                 daemon_log("$session_id DEBUG: Connection to LDAP URI '$ldap_uri' established.", 42);
2306         }
2308         $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);
2309         return $ldap_handle;
2313 sub release_ldap_handle {
2314         my ($ldap_handle, $session_id) = @_ ;
2315         if (not defined $session_id ) { $session_id = 0 };
2317         if(ref $ldap_handle) {
2318           $ldap_handle->disconnect();
2319   }
2320         &main::daemon_log("$session_id DEBUG: Released a ldap handle!", 42);
2321         return;
2325 sub change_fai_state {
2326         my ($st, $targets, $session_id) = @_;
2327         $session_id = 0 if not defined $session_id;
2328         # Set FAI state to localboot
2329         my %mapActions= (
2330                 reboot    => '',
2331                 update    => 'softupdate',
2332                 localboot => 'localboot',
2333                 reinstall => 'install',
2334                 rescan    => '',
2335                 wake      => '',
2336                 memcheck  => 'memcheck',
2337                 sysinfo   => 'sysinfo',
2338                 install   => 'install',
2339         );
2341         # Return if this is unknown
2342         if (!exists $mapActions{ $st }){
2343                 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2344                 return;
2345         }
2347         my $state= $mapActions{ $st };
2349         # Build search filter for hosts
2350         my $search= "(&(objectClass=GOhard)";
2351         foreach (@{$targets}){
2352                 $search.= "(macAddress=$_)";
2353         }
2354         $search.= ")";
2356         # If there's any host inside of the search string, procress them
2357         if (!($search =~ /macAddress/)){
2358                 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2359                 return;
2360         }
2362         my $ldap_handle = &get_ldap_handle($session_id);
2363         # Perform search for Unit Tag
2364         my $mesg = $ldap_handle->search(
2365                 base   => $ldap_base,
2366                 scope  => 'sub',
2367                 attrs  => ['dn', 'FAIstate', 'objectClass'],
2368                 filter => "$search"
2369         );
2371         if ($mesg->count) {
2372                 my @entries = $mesg->entries;
2373                 if (0 == @entries) {
2374                         daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2375                 }
2377                 foreach my $entry (@entries) {
2378                         # Only modify entry if it is not set to '$state'
2379                         if ($entry->get_value("FAIstate") ne "$state"){
2380                                 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2381                                 my $result;
2382                                 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2383                                 if (exists $tmp{'FAIobject'}){
2384                                         if ($state eq ''){
2385                                                 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2386                                         } else {
2387                                                 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2388                                         }
2389                                 } elsif ($state ne ''){
2390                                         $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2391                                 }
2393                                 # Errors?
2394                                 if ($result->code){
2395                                         daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2396                                 }
2397                         } else {
2398                                 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 42); 
2399                         }  
2400                 }
2401         } else {
2402                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2403         }
2404         &release_ldap_handle($ldap_handle, $session_id);                  
2406         return;
2410 sub change_goto_state {
2411     my ($st, $targets, $session_id) = @_;
2412     $session_id = 0  if not defined $session_id;
2414     # Switch on or off?
2415     my $state= $st eq 'active' ? 'active': 'locked';
2417     my $ldap_handle = &get_ldap_handle($session_id);
2418     if( defined($ldap_handle) ) {
2420       # Build search filter for hosts
2421       my $search= "(&(objectClass=GOhard)";
2422       foreach (@{$targets}){
2423         $search.= "(macAddress=$_)";
2424       }
2425       $search.= ")";
2427       # If there's any host inside of the search string, procress them
2428       if (!($search =~ /macAddress/)){
2429               &release_ldap_handle($ldap_handle);
2430         return;
2431       }
2433       # Perform search for Unit Tag
2434       my $mesg = $ldap_handle->search(
2435           base   => $ldap_base,
2436           scope  => 'sub',
2437           attrs  => ['dn', 'gotoMode'],
2438           filter => "$search"
2439           );
2441       if ($mesg->count) {
2442         my @entries = $mesg->entries;
2443         foreach my $entry (@entries) {
2445           # Only modify entry if it is not set to '$state'
2446           if ($entry->get_value("gotoMode") ne $state){
2448             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2449             my $result;
2450             $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2452             # Errors?
2453             if ($result->code){
2454               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2455             }
2457           }
2458         }
2459       } else {
2460                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2461           }
2463     }
2464         &release_ldap_handle($ldap_handle, $session_id);
2465         return;
2469 sub run_recreate_packages_db {
2470     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2471     my $session_id = $session->ID;
2472         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2473         $kernel->yield('create_fai_release_db', $fai_release_tn);
2474         $kernel->yield('create_fai_server_db', $fai_server_tn);
2475         return;
2479 sub run_create_fai_server_db {
2480     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2481     my $session_id = $session->ID;
2482     my $task = POE::Wheel::Run->new(
2483             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2484             StdoutEvent  => "session_run_result",
2485             StderrEvent  => "session_run_debug",
2486             CloseEvent   => "session_run_done",
2487             );
2489     $heap->{task}->{ $task->ID } = $task;
2490     return;
2494 sub create_fai_server_db {
2495         my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2496         my $result;
2498         if (not defined $session_id) { $session_id = 0; }
2499         my $ldap_handle = &get_ldap_handle($session_id);
2500         if(defined($ldap_handle)) {
2501                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2502                 my $mesg= $ldap_handle->search(
2503                         base   => $ldap_base,
2504                         scope  => 'sub',
2505                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2506                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2507                 );
2508                 if($mesg->{'resultCode'} == 0 &&
2509                         $mesg->count != 0) {
2510                         foreach my $entry (@{$mesg->{entries}}) {
2511                                 if($entry->exists('FAIrepository')) {
2512                                         # Add an entry for each Repository configured for server
2513                                         foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2514                                                 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2515                                                 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2516                                                 $result= $fai_server_db->add_dbentry( { 
2517                                                                 table => $table_name,
2518                                                                 primkey => ['server', 'fai_release', 'tag'],
2519                                                                 server => $tmp_url,
2520                                                                 fai_release => $tmp_release,
2521                                                                 sections => $tmp_sections,
2522                                                                 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2523                                                         } );
2524                                         }
2525                                 }
2526                         }
2527                 }
2528                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2529                 &release_ldap_handle($ldap_handle);
2531                 # TODO: Find a way to post the 'create_packages_list_db' event
2532                 if(not defined($dont_create_packages_list)) {
2533                         &create_packages_list_db(undef, $session_id);
2534                 }
2535         }       
2537         return $result;
2541 sub run_create_fai_release_db {
2542         my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2543         my $session_id = $session->ID;
2544         my $task = POE::Wheel::Run->new(
2545                 Program => sub { &create_fai_release_db($table_name, $session_id) },
2546                 StdoutEvent  => "session_run_result",
2547                 StderrEvent  => "session_run_debug",
2548                 CloseEvent   => "session_run_done",
2549         );
2551         $heap->{task}->{ $task->ID } = $task;
2552         return;
2556 sub create_fai_release_db {
2557         my ($table_name, $session_id) = @_;
2558         my $result;
2560         # used for logging
2561         if (not defined $session_id) { $session_id = 0; }
2563         my $ldap_handle = &get_ldap_handle($session_id);
2564         if(defined($ldap_handle)) {
2565                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2566                 my $mesg= $ldap_handle->search(
2567                         base   => $ldap_base,
2568                         scope  => 'sub',
2569                         attrs  => [],
2570                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2571                 );
2572                 if(($mesg->code == 0) && ($mesg->count != 0))
2573                 {
2574                         daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,138);
2576                         # Walk through all possible FAI container ou's
2577                         my @sql_list;
2578                         my $timestamp= &get_time();
2579                         foreach my $ou (@{$mesg->{entries}}) {
2580                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2581                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2582                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2583                                         if(@tmp_array) {
2584                                                 foreach my $entry (@tmp_array) {
2585                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2586                                                                 my $sql= 
2587                                                                 "INSERT INTO $table_name "
2588                                                                 ."(timestamp, fai_release, class, type, state) VALUES ("
2589                                                                 .$timestamp.","
2590                                                                 ."'".$entry->{'release'}."',"
2591                                                                 ."'".$entry->{'class'}."',"
2592                                                                 ."'".$entry->{'type'}."',"
2593                                                                 ."'".$entry->{'state'}."')";
2594                                                                 push @sql_list, $sql;
2595                                                         }
2596                                                 }
2597                                         }
2598                                 }
2599                         }
2601                         daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",138);
2602             &release_ldap_handle($ldap_handle);
2603                         if(@sql_list) {
2604                                 unshift @sql_list, "VACUUM";
2605                                 unshift @sql_list, "DELETE FROM $table_name";
2606                                 $fai_release_db->exec_statementlist(\@sql_list);
2607                         }
2608                         daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",138);
2609                 } else {
2610                         daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2611                 }
2612                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2613         }
2614         return $result;
2617 sub get_fai_types {
2618         my $tmp_classes = shift || return undef;
2619         my @result;
2621         foreach my $type(keys %{$tmp_classes}) {
2622                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2623                         my $entry = {
2624                                 type => $type,
2625                                 state => $tmp_classes->{$type}[0],
2626                         };
2627                         push @result, $entry;
2628                 }
2629         }
2631         return @result;
2634 sub get_fai_state {
2635         my $result = "";
2636         my $tmp_classes = shift || return $result;
2638         foreach my $type(keys %{$tmp_classes}) {
2639                 if(defined($tmp_classes->{$type}[0])) {
2640                         $result = $tmp_classes->{$type}[0];
2641                         
2642                 # State is equal for all types in class
2643                         last;
2644                 }
2645         }
2647         return $result;
2650 sub resolve_fai_classes {
2651         my ($fai_base, $ldap_handle, $session_id) = @_;
2652         if (not defined $session_id) { $session_id = 0; }
2653         my $result;
2654         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2655         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2656         my $fai_classes;
2658         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base", 138);
2659         my $mesg= $ldap_handle->search(
2660                 base   => $fai_base,
2661                 scope  => 'sub',
2662                 attrs  => ['cn','objectClass','FAIstate'],
2663                 filter => $fai_filter,
2664         );
2665         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries", 138);
2667         if($mesg->{'resultCode'} == 0 &&
2668                 $mesg->count != 0) {
2669                 foreach my $entry (@{$mesg->{entries}}) {
2670                         if($entry->exists('cn')) {
2671                                 my $tmp_dn= $entry->dn();
2672                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2673                                         - length($fai_base) - 1 );
2675                                 # Skip classname and ou dn parts for class
2676                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2678                                 # Skip classes without releases
2679                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2680                                         next;
2681                                 }
2683                                 my $tmp_cn= $entry->get_value('cn');
2684                                 my $tmp_state= $entry->get_value('FAIstate');
2686                                 my $tmp_type;
2687                                 # Get FAI type
2688                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2689                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2690                                                 $tmp_type= $oclass;
2691                                                 last;
2692                                         }
2693                                 }
2695                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2696                                         # A Subrelease
2697                                         my @sub_releases = split(/,/, $tmp_release);
2699                                         # Walk through subreleases and build hash tree
2700                                         my $hash;
2701                                         while(my $tmp_sub_release = pop @sub_releases) {
2702                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2703                                         }
2704                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2705                                 } else {
2706                                         # A branch, no subrelease
2707                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2708                                 }
2709                         } elsif (!$entry->exists('cn')) {
2710                                 my $tmp_dn= $entry->dn();
2711                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2712                                         - length($fai_base) - 1 );
2713                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2715                                 # Skip classes without releases
2716                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2717                                         next;
2718                                 }
2720                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2721                                         # A Subrelease
2722                                         my @sub_releases= split(/,/, $tmp_release);
2724                                         # Walk through subreleases and build hash tree
2725                                         my $hash;
2726                                         while(my $tmp_sub_release = pop @sub_releases) {
2727                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2728                                         }
2729                                         # Remove the last two characters
2730                                         chop($hash);
2731                                         chop($hash);
2733                                         eval('$fai_classes->'.$hash.'= {}');
2734                                 } else {
2735                                         # A branch, no subrelease
2736                                         if(!exists($fai_classes->{$tmp_release})) {
2737                                                 $fai_classes->{$tmp_release} = {};
2738                                         }
2739                                 }
2740                         }
2741                 }
2743                 # The hash is complete, now we can honor the copy-on-write based missing entries
2744                 foreach my $release (keys %$fai_classes) {
2745                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2746                 }
2747         }
2748         return $result;
2751 sub apply_fai_inheritance {
2752        my $fai_classes = shift || return {};
2753        my $tmp_classes;
2755        # Get the classes from the branch
2756        foreach my $class (keys %{$fai_classes}) {
2757                # Skip subreleases
2758                if($class =~ /^ou=.*$/) {
2759                        next;
2760                } else {
2761                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2762                }
2763        }
2765        # Apply to each subrelease
2766        foreach my $subrelease (keys %{$fai_classes}) {
2767                if($subrelease =~ /ou=/) {
2768                        foreach my $tmp_class (keys %{$tmp_classes}) {
2769                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2770                                        $fai_classes->{$subrelease}->{$tmp_class} =
2771                                        deep_copy($tmp_classes->{$tmp_class});
2772                                } else {
2773                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2774                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2775                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2776                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2777                                                }
2778                                        }
2779                                }
2780                        }
2781                }
2782        }
2784        # Find subreleases in deeper levels
2785        foreach my $subrelease (keys %{$fai_classes}) {
2786                if($subrelease =~ /ou=/) {
2787                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2788                                if($subsubrelease =~ /ou=/) {
2789                                        apply_fai_inheritance($fai_classes->{$subrelease});
2790                                }
2791                        }
2792                }
2793        }
2795        return $fai_classes;
2798 sub get_fai_release_entries {
2799         my $tmp_classes = shift || return;
2800         my $parent = shift || "";
2801         my @result = shift || ();
2803         foreach my $entry (keys %{$tmp_classes}) {
2804                 if(defined($entry)) {
2805                         if($entry =~ /^ou=.*$/) {
2806                                 my $release_name = $entry;
2807                                 $release_name =~ s/ou=//g;
2808                                 if(length($parent)>0) {
2809                                         $release_name = $parent."/".$release_name;
2810                                 }
2811                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2812                                 foreach my $bufentry(@bufentries) {
2813                                         push @result, $bufentry;
2814                                 }
2815                         } else {
2816                                 my @types = get_fai_types($tmp_classes->{$entry});
2817                                 foreach my $type (@types) {
2818                                         push @result, 
2819                                         {
2820                                                 'class' => $entry,
2821                                                 'type' => $type->{'type'},
2822                                                 'release' => $parent,
2823                                                 'state' => $type->{'state'},
2824                                         };
2825                                 }
2826                         }
2827                 }
2828         }
2830         return @result;
2833 sub deep_copy {
2834         my $this = shift;
2835         if (not ref $this) {
2836                 $this;
2837         } elsif (ref $this eq "ARRAY") {
2838                 [map deep_copy($_), @$this];
2839         } elsif (ref $this eq "HASH") {
2840                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2841         } else { die "what type is $_?" }
2845 sub session_run_result {
2846     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2847     $kernel->sig(CHLD => "child_reap");
2850 sub session_run_debug {
2851     my $result = $_[ARG0];
2852     print STDERR "$result\n";
2855 sub session_run_done {
2856     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2857     delete $heap->{task}->{$task_id};
2858         if (exists $heap->{ldap_handle}->{$task_id}) {
2859                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
2860         }
2861         delete $heap->{ldap_handle}->{$task_id};
2865 sub create_sources_list {
2866         my $session_id = shift || 0;
2867         my $result="/tmp/gosa_si_tmp_sources_list";
2869         # Remove old file
2870         if(stat($result)) {
2871                 unlink($result);
2872                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2873         }
2875         my $fh;
2876         open($fh, ">$result");
2877         if (not defined $fh) {
2878                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2879                 return undef;
2880         }
2881         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2882                 my $ldap_handle = &get_ldap_handle($session_id);
2883                 my $mesg=$ldap_handle->search(
2884                         base    => $main::ldap_server_dn,
2885                         scope   => 'base',
2886                         attrs   => 'FAIrepository',
2887                         filter  => 'objectClass=FAIrepositoryServer'
2888                 );
2889                 if($mesg->count) {
2890                         foreach my $entry(@{$mesg->{'entries'}}) {
2891                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2892                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2893                                         my $line = "deb $server $release";
2894                                         $sections =~ s/,/ /g;
2895                                         $line.= " $sections";
2896                                         print $fh $line."\n";
2897                                 }
2898                         }
2899                 }
2900                 &release_ldap_handle($ldap_handle);
2901         } else {
2902                 if (defined $main::ldap_server_dn){
2903                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2904                 } else {
2905                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2906                 }
2907         }
2908         close($fh);
2910         return $result;
2914 sub run_create_packages_list_db {
2915     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2916         my $session_id = $session->ID;
2917         my $task = POE::Wheel::Run->new(
2918                                         Priority => +20,
2919                                         Program => sub {&create_packages_list_db(undef, $session_id)},
2920                                         StdoutEvent  => "session_run_result",
2921                                         StderrEvent  => "session_run_debug",
2922                                         CloseEvent   => "session_run_done",
2923                                         );
2924         $heap->{task}->{ $task->ID } = $task;
2928 sub create_packages_list_db {
2929         my ($sources_file, $session_id) = @_;
2930         
2931         # it should not be possible to trigger a recreation of packages_list_db
2932         # while packages_list_db is under construction, so set flag packages_list_under_construction
2933         # which is tested befor recreation can be started
2934         if (-r $packages_list_under_construction) {
2935                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2936                 return;
2937         } else {
2938                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2939                 # set packages_list_under_construction to true
2940                 system("touch $packages_list_under_construction");
2941                 @packages_list_statements=();
2942         }
2944         if (not defined $session_id) { $session_id = 0; }
2946         if (not defined $sources_file) { 
2947                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2948                 $sources_file = &create_sources_list($session_id);
2949         }
2951         if (not defined $sources_file) {
2952                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2953                 unlink($packages_list_under_construction);
2954                 return;
2955         }
2957         my $line;
2959         open(CONFIG, "<$sources_file") or do {
2960                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2961                 unlink($packages_list_under_construction);
2962                 return;
2963         };
2965         # Read lines
2966         while ($line = <CONFIG>){
2967                 # Unify
2968                 chop($line);
2969                 $line =~ s/^\s+//;
2970                 $line =~ s/^\s+/ /;
2972                 # Strip comments
2973                 $line =~ s/#.*$//g;
2975                 # Skip empty lines
2976                 if ($line =~ /^\s*$/){
2977                         next;
2978                 }
2980                 # Interpret deb line
2981                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2982                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2983                         my $section;
2984                         foreach $section (split(' ', $sections)){
2985                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2986                         }
2987                 }
2988         }
2990         close (CONFIG);
2992         if(keys(%repo_dirs)) {
2993                 find(\&cleanup_and_extract, keys( %repo_dirs ));
2994                 &main::strip_packages_list_statements();
2995                 $packages_list_db->exec_statementlist(\@packages_list_statements);
2996         }
2997         unlink($packages_list_under_construction);
2998         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2999         return;
3002 # This function should do some intensive task to minimize the db-traffic
3003 sub strip_packages_list_statements {
3004         my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
3005         my @new_statement_list=();
3006         my $hash;
3007         my $insert_hash;
3008         my $update_hash;
3009         my $delete_hash;
3010         my $known_packages_hash;
3011         my $local_timestamp=get_time();
3013         foreach my $existing_entry (@existing_entries) {
3014                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
3015         }
3017         foreach my $statement (@packages_list_statements) {
3018                 if($statement =~ /^INSERT/i) {
3019                         # Assign the values from the insert statement
3020                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
3021                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
3022                         if(exists($hash->{$distribution}->{$package}->{$version})) {
3023                                 # If section or description has changed, update the DB
3024                                 if( 
3025                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
3026                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
3027                                 ) {
3028                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
3029                                 } else {
3030                                         # package is already present in database. cache this knowledge for later use
3031                                         @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3032                                 }
3033                         } else {
3034                                 # Insert a non-existing entry to db
3035                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3036                         }
3037                 } elsif ($statement =~ /^UPDATE/i) {
3038                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
3039                         /^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;
3040                         foreach my $distribution (keys %{$hash}) {
3041                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
3042                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
3043                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
3044                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
3045                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
3046                                                 my $section;
3047                                                 my $description;
3048                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
3049                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
3050                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
3051                                                 }
3052                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3053                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
3054                                                 }
3055                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3056                                         }
3057                                 }
3058                         }
3059                 }
3060         }
3062         # Check for orphaned entries
3063         foreach my $existing_entry (@existing_entries) {
3064                 my $distribution= @{$existing_entry}[0];
3065                 my $package= @{$existing_entry}[1];
3066                 my $version= @{$existing_entry}[2];
3067                 my $section= @{$existing_entry}[3];
3069                 if(
3070                         exists($insert_hash->{$distribution}->{$package}->{$version}) ||
3071                         exists($update_hash->{$distribution}->{$package}->{$version}) ||
3072                         exists($known_packages_hash->{$distribution}->{$package}->{$version})
3073                 ) {
3074                         next;
3075                 } else {
3076                         # Insert entry to delete hash
3077                         @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
3078                 }
3079         }
3081         # unroll the insert hash
3082         foreach my $distribution (keys %{$insert_hash}) {
3083                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
3084                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
3085                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
3086                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
3087                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
3088                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
3089                                 ."'$local_timestamp')";
3090                         }
3091                 }
3092         }
3094         # unroll the update hash
3095         foreach my $distribution (keys %{$update_hash}) {
3096                 foreach my $package (keys %{$update_hash->{$distribution}}) {
3097                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
3098                                 my $set = "";
3099                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
3100                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
3101                                 }
3102                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3103                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
3104                                 }
3105                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
3106                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
3107                                 }
3108                                 if(defined($set) and length($set) > 0) {
3109                                         $set .= "timestamp = '$local_timestamp'";
3110                                 } else {
3111                                         next;
3112                                 }
3113                                 push @new_statement_list, 
3114                                 "UPDATE $main::packages_list_tn SET $set WHERE"
3115                                 ." distribution = '$distribution'"
3116                                 ." AND package = '$package'"
3117                                 ." AND version = '$version'";
3118                         }
3119                 }
3120         }
3121         
3122         # unroll the delete hash
3123         foreach my $distribution (keys %{$delete_hash}) {
3124                 foreach my $package (keys %{$delete_hash->{$distribution}}) {
3125                         foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
3126                                 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
3127                                 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
3128                         }
3129                 }
3130         }
3132         unshift(@new_statement_list, "VACUUM");
3134         @packages_list_statements = @new_statement_list;
3138 sub parse_package_info {
3139     my ($baseurl, $dist, $section, $session_id)= @_;
3140     my ($package);
3141     if (not defined $session_id) { $session_id = 0; }
3142     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3143     $repo_dirs{ "${repo_path}/pool" } = 1;
3145     foreach $package ("Packages.gz"){
3146         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 266);
3147         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3148         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3149     }
3150     
3154 sub get_package {
3155     my ($url, $dest, $session_id)= @_;
3156     if (not defined $session_id) { $session_id = 0; }
3158     my $tpath = dirname($dest);
3159     -d "$tpath" || mkpath "$tpath";
3161     # This is ugly, but I've no time to take a look at "how it works in perl"
3162     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3163         system("gunzip -cd '$dest' > '$dest.in'");
3164         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 266);
3165         unlink($dest);
3166         daemon_log("$session_id DEBUG: delete file '$dest'", 266); 
3167     } else {
3168         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3169     }
3170     return 0;
3174 sub parse_package {
3175     my ($path, $dist, $srv_path, $session_id)= @_;
3176     if (not defined $session_id) { $session_id = 0;}
3177     my ($package, $version, $section, $description);
3178     my $PACKAGES;
3179     my $timestamp = &get_time();
3181     if(not stat("$path.in")) {
3182         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3183         return;
3184     }
3186     open($PACKAGES, "<$path.in");
3187     if(not defined($PACKAGES)) {
3188         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
3189         return;
3190     }
3192     # Read lines
3193     while (<$PACKAGES>){
3194         my $line = $_;
3195         # Unify
3196         chop($line);
3198         # Use empty lines as a trigger
3199         if ($line =~ /^\s*$/){
3200             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3201             push(@packages_list_statements, $sql);
3202             $package = "none";
3203             $version = "none";
3204             $section = "none";
3205             $description = "none"; 
3206             next;
3207         }
3209         # Trigger for package name
3210         if ($line =~ /^Package:\s/){
3211             ($package)= ($line =~ /^Package: (.*)$/);
3212             next;
3213         }
3215         # Trigger for version
3216         if ($line =~ /^Version:\s/){
3217             ($version)= ($line =~ /^Version: (.*)$/);
3218             next;
3219         }
3221         # Trigger for description
3222         if ($line =~ /^Description:\s/){
3223             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3224             next;
3225         }
3227         # Trigger for section
3228         if ($line =~ /^Section:\s/){
3229             ($section)= ($line =~ /^Section: (.*)$/);
3230             next;
3231         }
3233         # Trigger for filename
3234         if ($line =~ /^Filename:\s/){
3235             my ($filename) = ($line =~ /^Filename: (.*)$/);
3236             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3237             next;
3238         }
3239     }
3241     close( $PACKAGES );
3242     unlink( "$path.in" );
3246 sub store_fileinfo {
3247     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3249     my %fileinfo = (
3250         'package' => $package,
3251         'dist' => $dist,
3252         'version' => $vers,
3253     );
3255     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3259 sub cleanup_and_extract {
3260         my $fileinfo = $repo_files{ $File::Find::name };
3262         if( defined $fileinfo ) {
3263                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3264                 my $sql;
3265                 my $package = $fileinfo->{ 'package' };
3266                 my $newver = $fileinfo->{ 'version' };
3268                 mkpath($dir);
3269                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3271                 if( -f "$dir/DEBIAN/templates" ) {
3273                         daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 266);
3275                         my $tmpl= ""; {
3276                                 local $/=undef;
3277                                 open FILE, "$dir/DEBIAN/templates";
3278                                 $tmpl = &encode_base64(<FILE>);
3279                                 close FILE;
3280                         }
3281                         rmtree("$dir/DEBIAN/templates");
3283                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3284                         push @packages_list_statements, $sql;
3285                 }
3286         }
3288         return;
3292 sub prepare_server_registration 
3294         # Add foreign server from cfg file
3295         my @foreign_server_list;
3296         if ($foreign_server_string ne "") {
3297             my @cfg_foreign_server_list = split(",", $foreign_server_string);
3298             foreach my $foreign_server (@cfg_foreign_server_list) {
3299                 push(@foreign_server_list, $foreign_server);
3300             }
3301         
3302             daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3303         }
3304         
3305         # Perform a DNS lookup for server registration if flag is true
3306         if ($dns_lookup eq "true") {
3307             # Add foreign server from dns
3308             my @tmp_servers;
3309             if (not $server_domain) {
3310                 # Try our DNS Searchlist
3311                 for my $domain(get_dns_domains()) {
3312                     chomp($domain);
3313                     my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3314                     if(@$tmp_domains) {
3315                         for my $tmp_server(@$tmp_domains) {
3316                             push @tmp_servers, $tmp_server;
3317                         }
3318                     }
3319                 }
3320                 if(@tmp_servers && length(@tmp_servers)==0) {
3321                     daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3322                 }
3323             } else {
3324                 @tmp_servers = &get_server_addresses($server_domain);
3325                 if( 0 == @tmp_servers ) {
3326                     daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3327                 }
3328             }
3329         
3330             daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3331         
3332             foreach my $server (@tmp_servers) { 
3333                 unshift(@foreign_server_list, $server); 
3334             }
3335         } else {
3336             daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3337         }
3338         
3339         # eliminate duplicate entries
3340         @foreign_server_list = &del_doubles(@foreign_server_list);
3341         my $all_foreign_server = join(", ", @foreign_server_list);
3342         daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3343         
3344         # add all found foreign servers to known_server
3345         my $cur_timestamp = &get_time();
3346         foreach my $foreign_server (@foreign_server_list) {
3347         
3348                 # do not add myself to known_server_db
3349                 if (&is_local($foreign_server)) { next; }
3350                 ######################################
3351         
3352             my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3353                     primkey=>['hostname'],
3354                     hostname=>$foreign_server,
3355                     macaddress=>"",
3356                     status=>'not_yet_registered',
3357                     hostkey=>"none",
3358                     loaded_modules => "none", 
3359                     timestamp=>$cur_timestamp,
3360                                 update_time=>'19700101000000',
3361                     } );
3362         }
3365 sub register_at_foreign_servers {   
3366     my ($kernel) = $_[KERNEL];
3368         # Update status and update-time of all si-server with expired update_time and 
3369         # block them for race conditional registration processes of other si-servers.
3370         my $act_time = &get_time();
3371         my $block_statement = "UPDATE $known_server_tn SET status='new_server',update_time='19700101000000' WHERE (CAST(update_time AS UNSIGNED))<$act_time ";
3372         my $block_res = $known_server_db->exec_statement($block_statement);
3374         # Fetch all si-server from db where update_time is younger than act_time
3375         my $fetch_statement = "SELECT * FROM $known_server_tn WHERE update_time='19700101000000'"; 
3376         my $fetch_res = $known_server_db->exec_statement($fetch_statement);
3378     # Detect already connected clients. Will be added to registration msg later. 
3379     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3380     my $client_res = $known_clients_db->exec_statement($client_sql);
3382         # Send registration messag to all fetched si-server
3383     foreach my $hit (@$fetch_res) {
3384         my $hostname = @$hit[0];
3385         my $hostkey = &create_passwd;
3387         # Add already connected clients to registration message 
3388         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3389         &add_content2xml_hash($myhash, 'key', $hostkey);
3390         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3392         # Add locally loaded gosa-si modules to registration message
3393         my $loaded_modules = {};
3394         while (my ($package, $pck_info) = each %$known_modules) {
3395                         next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3396                         foreach my $act_module (keys(%{@$pck_info[2]})) {
3397                                 $loaded_modules->{$act_module} = ""; 
3398                         }
3399                 }
3400         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3402         # Add macaddress to registration message
3403         my ($host_ip, $host_port) = split(/:/, $hostname);
3404         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3405         my $network_interface= &get_interface_for_ip($local_ip);
3406         my $host_mac = &get_mac_for_interface($network_interface);
3407         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3408         
3409         # Build registration message and send it
3410         my $foreign_server_msg = &create_xml_string($myhash);
3411         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3412     }
3415         # After n sec perform a check of all server registration processes
3416     $kernel->delay_set("control_server_registration", 2); 
3418         return;
3422 sub control_server_registration {
3423         my ($kernel) = $_[KERNEL];
3424         
3425         # Check if all registration processes succeed or not
3426         my $select_statement = "SELECT * FROM $known_server_tn WHERE status='new_server'"; 
3427         my $select_res = $known_server_db->exec_statement($select_statement);
3429         # If at least one registration process failed, maybe in case of a race condition
3430         # with a foreign registration process
3431         if (@$select_res > 0) 
3432         {
3433                 # Release block statement 'new_server' to make the server accessible
3434                 # for foreign registration processes
3435                 my $update_statement = "UPDATE $known_server_tn SET status='waiting' WHERE status='new_server'";        
3436                 my $update_res = $known_server_db->exec_statement($update_statement);
3438                 # Set a random delay to avoid the registration race condition
3439                 my $new_foreign_servers_register_delay = int(rand(4))+1;
3440                 $kernel->delay_set("register_at_foreign_servers", $new_foreign_servers_register_delay);
3441         }
3442         # If all registration processes succeed
3443         else
3444         {
3445                 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3446         }
3448         return;
3452 #==== MAIN = main ==============================================================
3453 #  parse commandline options
3454 Getopt::Long::Configure( "bundling" );
3455 GetOptions("h|help" => \&usage,
3456         "c|config=s" => \$cfg_file,
3457         "f|foreground" => \$foreground,
3458         "v|verbose+" => \$verbose,
3459         "no-arp+" => \$no_arp,
3460                 "d=s" => \$debug_parts,
3461            ) or &usage("", 1); 
3463 #  read and set config parameters
3464 &check_cmdline_param ;
3465 &read_configfile($cfg_file, %cfg_defaults);
3466 &check_pid;
3468 $SIG{CHLD} = 'IGNORE';
3470 # forward error messages to logfile
3471 if( ! $foreground ) {
3472   open( STDIN,  '+>/dev/null' );
3473   open( STDOUT, '+>&STDIN'    );
3474   open( STDERR, '+>&STDIN'    );
3477 # Just fork, if we are not in foreground mode
3478 if( ! $foreground ) { 
3479     chdir '/'                 or die "Can't chdir to /: $!";
3480     $pid = fork;
3481     setsid                    or die "Can't start a new session: $!";
3482     umask 0;
3483 } else { 
3484     $pid = $$; 
3487 # Do something useful - put our PID into the pid_file
3488 if( 0 != $pid ) {
3489     open( LOCK_FILE, ">$pid_file" );
3490     print LOCK_FILE "$pid\n";
3491     close( LOCK_FILE );
3492     if( !$foreground ) { 
3493         exit( 0 ) 
3494     };
3497 # parse head url and revision from svn
3498 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3499 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3500 $server_headURL = defined $1 ? $1 : 'unknown' ;
3501 $server_revision = defined $2 ? $2 : 'unknown' ;
3502 if ($server_headURL =~ /\/tag\// || 
3503         $server_headURL =~ /\/branches\// ) {
3504     $server_status = "stable"; 
3505 } else {
3506     $server_status = "developmental" ;
3508 # Prepare log file and set permissions
3509 $root_uid = getpwnam('root');
3510 $adm_gid = getgrnam('adm');
3511 open(FH, ">>$log_file");
3512 close FH;
3513 chmod(0440, $log_file);
3514 chown($root_uid, $adm_gid, $log_file);
3515 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3517 daemon_log(" ", 1);
3518 daemon_log("$0 started!", 1);
3519 daemon_log("status: $server_status", 1);
3520 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3522 # Buildup data bases
3524     no strict "refs";
3526     if ($db_module eq "DBmysql") {
3527         # connect to incoming_db
3528         $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3530         # connect to gosa-si job queue
3531         $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3533         # connect to known_clients_db
3534         $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3536         # connect to foreign_clients_db
3537         $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3539         # connect to known_server_db
3540         $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3542         # connect to login_usr_db
3543         $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3545         # connect to fai_server_db 
3546         $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3548         # connect to fai_release_db
3549         $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3551         # connect to packages_list_db
3552         $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3554         # connect to messaging_db
3555         $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3557     } elsif ($db_module eq "DBsqlite") {
3558         # connect to incoming_db
3559         unlink($incoming_file_name);
3560         $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3561         chmod(0640, $incoming_file_name);
3562         chown($root_uid, $adm_gid, $incoming_file_name);
3563         
3564         # connect to gosa-si job queue
3565         $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3566         chmod(0640, $job_queue_file_name);
3567         chown($root_uid, $adm_gid, $job_queue_file_name);
3568         
3569         # connect to known_clients_db
3570         #unlink($known_clients_file_name);
3571         $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3572         chmod(0640, $known_clients_file_name);
3573         chown($root_uid, $adm_gid, $known_clients_file_name);
3574         
3575         # connect to foreign_clients_db
3576         #unlink($foreign_clients_file_name);
3577         $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3578         chmod(0640, $foreign_clients_file_name);
3579         chown($root_uid, $adm_gid, $foreign_clients_file_name);
3580         
3581         # connect to known_server_db
3582         unlink($known_server_file_name);   # do not delete, gosa-si-server should be forced to check config file and dns at each start
3583         $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3584         chmod(0640, $known_server_file_name);
3585         chown($root_uid, $adm_gid, $known_server_file_name);
3586         
3587         # connect to login_usr_db
3588         #unlink($login_users_file_name);
3589         $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3590         chmod(0640, $login_users_file_name);
3591         chown($root_uid, $adm_gid, $login_users_file_name);
3592         
3593         # connect to fai_server_db
3594         unlink($fai_server_file_name);
3595         $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3596         chmod(0640, $fai_server_file_name);
3597         chown($root_uid, $adm_gid, $fai_server_file_name);
3598         
3599         # connect to fai_release_db
3600         unlink($fai_release_file_name);
3601         $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3602         chmod(0640, $fai_release_file_name);
3603         chown($root_uid, $adm_gid, $fai_release_file_name);
3604         
3605         # connect to packages_list_db
3606         unlink($packages_list_under_construction);
3607         $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3608         chmod(0640, $packages_list_file_name);
3609         chown($root_uid, $adm_gid, $packages_list_file_name);
3610         
3611         # connect to messaging_db
3612         #unlink($messaging_file_name);
3613         $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3614         chmod(0640, $messaging_file_name);
3615         chown($root_uid, $adm_gid, $messaging_file_name);
3616     }
3619 # Creating tables
3620 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3621 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3622 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3623 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3624 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3625 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3626 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3627 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3628 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3629 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3631 # create xml object used for en/decrypting
3632 $xml = new XML::Simple();
3634 # Import all modules
3635 &import_modules;
3637 # Check wether all modules are gosa-si valid passwd check
3638 &password_check;
3640 # Check DNS and config file for server registration
3641 if ($serverPackages_enabled eq "true") { &prepare_server_registration; }
3643 # Create functions hash
3644 while (my ($module, @mod_info) = each %$known_modules) 
3646         while (my ($plugin, $functions) = each %{$mod_info[0][2]})
3647         {
3648                 while (my ($function, $nothing) = each %$functions )
3649                 {
3650                         $known_functions->{$function} = $nothing;
3651                 }
3652         }
3655 # Prepare for using Opsi 
3656 if ($opsi_enabled eq "true") {
3657     use JSON::RPC::Client;
3658     use XML::Quote qw(:all);
3659     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3660     $opsi_client = new JSON::RPC::Client;
3664 POE::Component::Server::TCP->new(
3665         Alias => "TCP_SERVER",
3666         Port => $server_port,
3667         ClientInput => sub {
3668                 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3669         my $session_id = $session->ID;
3670                 if ($input =~ /;([\d\.]+):([\d]+)$/) 
3671                 {
3672                         # Messages from other servers should be blocked if config option is set
3673                         if (($2 eq $server_port) && ($serverPackages_enabled eq "false"))
3674                         {
3675                                 return;
3676                         }
3677                         &daemon_log("$session_id DEBUG: incoming message from '$1:$2'", 11);
3678                 }
3679                 else
3680                 {
3681                         my $remote_ip = $heap->{'remote_ip'};
3682                         &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 11);
3683                 }
3684                 push(@msgs_to_decrypt, $input);
3685                 $kernel->yield("msg_to_decrypt");
3686         },
3687         InlineStates => {
3688                 msg_to_decrypt => \&msg_to_decrypt,
3689                 next_task => \&next_task,
3690                 task_result => \&handle_task_result,
3691                 task_done   => \&handle_task_done,
3692                 task_debug  => \&handle_task_debug,
3693                 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3694         }
3695 );
3697 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3699 # create session for repeatedly checking the job queue for jobs
3700 POE::Session->create(
3701         inline_states => {
3702                 _start => \&session_start,
3703         register_at_foreign_servers => \&register_at_foreign_servers,
3704                 control_server_registration => \&control_server_registration,
3705         sig_handler => \&sig_handler,
3706         next_task => \&next_task,
3707         task_result => \&handle_task_result,
3708         task_done   => \&handle_task_done,
3709         task_debug  => \&handle_task_debug,
3710         watch_for_next_tasks => \&watch_for_next_tasks,
3711         watch_for_new_messages => \&watch_for_new_messages,
3712         watch_for_delivery_messages => \&watch_for_delivery_messages,
3713         watch_for_done_messages => \&watch_for_done_messages,
3714                 watch_for_new_jobs => \&watch_for_new_jobs,
3715         watch_for_modified_jobs => \&watch_for_modified_jobs,
3716         watch_for_done_jobs => \&watch_for_done_jobs,
3717         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3718         watch_for_old_known_clients => \&watch_for_old_known_clients,
3719         create_packages_list_db => \&run_create_packages_list_db,
3720         create_fai_server_db => \&run_create_fai_server_db,
3721         create_fai_release_db => \&run_create_fai_release_db,
3722                 recreate_packages_db => \&run_recreate_packages_db,
3723         session_run_result => \&session_run_result,
3724         session_run_debug => \&session_run_debug,
3725         session_run_done => \&session_run_done,
3726         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3727         }
3728 );
3731 POE::Kernel->run();
3732 exit;