Code

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