Code

d391e2de71a3bd1a3f384d333094e4095ff5265a
[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     $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 );
156 # holds all other gosa-si-server
157 our $known_server_db;
158 our $known_server_tn = "known_server";
159 my $known_server_file_name;
160 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)");
162 # holds all registrated clients
163 our $known_clients_db;
164 our $known_clients_tn = "known_clients";
165 my $known_clients_file_name;
166 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)");
168 # holds all registered clients at a foreign server
169 our $foreign_clients_db;
170 our $foreign_clients_tn = "foreign_clients"; 
171 my $foreign_clients_file_name;
172 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
174 # holds all logged in user at each client 
175 our $login_users_db;
176 our $login_users_tn = "login_users";
177 my $login_users_file_name;
178 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
180 # holds all fai server, the debian release and tag
181 our $fai_server_db;
182 our $fai_server_tn = "fai_server"; 
183 my $fai_server_file_name;
184 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)"); 
186 our $fai_release_db;
187 our $fai_release_tn = "fai_release"; 
188 my $fai_release_file_name;
189 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)"); 
191 # holds all packages available from different repositories
192 our $packages_list_db;
193 our $packages_list_tn = "packages_list";
194 my $packages_list_file_name;
195 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
196 my $outdir = "/tmp/packages_list_db";
197 my $arch = "i386"; 
199 # holds all messages which should be delivered to a user
200 our $messaging_db;
201 our $messaging_tn = "messaging"; 
202 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)", 
203         "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
204 my $messaging_file_name;
206 # path to directory to store client install log files
207 our $client_fai_log_dir = "/var/log/fai"; 
209 # queue which stores taskes until one of the $max_children children are ready to process the task
210 #my @tasks = qw();
211 my @msgs_to_decrypt = qw();
212 my $max_children = 2;
215 # loop delay for job queue to look for opsi jobs
216 my $job_queue_opsi_delay = 10;
217 our $opsi_client;
218 our $opsi_url;
219  
220 # Lifetime of logged in user information. If no update information comes after n seconds, 
221 # the user is expeceted to be no longer logged in or the host is no longer running. Because
222 # of this, the user is deleted from login_users_db
223 our $logged_in_user_date_of_expiry = 600;
225 # List of month names, used in function daemon_log
226 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
228 %cfg_defaults = (
229 "general" => {
230     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
231     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
232     },
233 "server" => {
234     "ip"                    => [\$server_ip, "0.0.0.0"],
235     "port"                  => [\$server_port, "20081"],
236     "known-clients"         => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
237     "known-servers"         => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
238     "incoming"              => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
239     "login-users"           => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
240     "fai-server"            => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
241     "fai-release"           => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
242     "packages-list"         => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
243     "messaging"             => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
244     "foreign-clients"       => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
245     "source-list"           => [\$sources_list, '/etc/apt/sources.list'],
246     "repo-path"             => [\$repo_path, '/srv/www/repository'],
247     "ldap-uri"              => [\$ldap_uri, ""],
248     "ldap-base"             => [\$ldap_base, ""],
249     "ldap-admin-dn"         => [\$ldap_admin_dn, ""],
250     "ldap-admin-password"   => [\$ldap_admin_password, ""],
251     "ldap-version"          => [\$ldap_version, 3],
252     "gosa-unit-tag"         => [\$gosa_unit_tag, ""],
253     "max-clients"           => [\$max_clients, 10],
254     "wol-password"          => [\$wake_on_lan_passwd, ""],
255         "mysql-username"        => [\$mysql_username, "gosa_si"],
256         "mysql-password"        => [\$mysql_password, ""],
257         "mysql-database"        => [\$mysql_database, "gosa_si"],
258         "mysql-host"            => [\$mysql_host, "127.0.0.1"],
259     },
260 "GOsaPackages" => {
261     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
262     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
263     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
264     "key" => [\$GosaPackages_key, "none"],
265                 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
266     },
267 "ClientPackages" => {
268     "key" => [\$ClientPackages_key, "none"],
269     "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
270     },
271 "ServerPackages"=> {
272     "address"      => [\$foreign_server_string, ""],
273     "dns-lookup"            => [\$dns_lookup, "true"],
274     "domain"  => [\$server_domain, ""],
275     "key"     => [\$ServerPackages_key, "none"],
276     "key-lifetime" => [\$foreign_servers_register_delay, 120],
277     "job-synchronization-enabled" => [\$job_synchronization, "true"],
278     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
279     },
280 "ArpHandler" => {
281     "enabled"   => [\$arp_enabled, "true"],
282     "interface" => [\$arp_interface, "all"],
283         },
284 "Opsi" => {
285     "enabled"  => [\$opsi_enabled, "false"], 
286     "server"   => [\$opsi_server, "localhost"],
287     "admin"    => [\$opsi_admin, "opsi-admin"],
288     "password" => [\$opsi_password, "secret"],
289    },
291 );
294 #===  FUNCTION  ================================================================
295 #         NAME:  usage
296 #   PARAMETERS:  nothing
297 #      RETURNS:  nothing
298 #  DESCRIPTION:  print out usage text to STDERR
299 #===============================================================================
300 sub usage {
301     print STDERR << "EOF" ;
302 usage: $prg [-hvf] [-c config] [-d number]
304            -h        : this (help) message
305            -c <file> : config file
306            -f        : foreground, process will not be forked to background
307            -v        : be verbose (multiple to increase verbosity)
308                               'v': error logs
309                             'vvv': warning plus error logs                                              
310                           'vvvvv': info plus warning logs
311                         'vvvvvvv': debug plus info logs
312            -no-arp   : starts $prg without connection to arp module
313            -d <int>  : if verbose level is higher than 7x 'v' specified parts can be debugged
314                            1 : receiving messages
315                            2 : sending messages
316                            4 : encrypting/decrypting messages
317                            8 : verification if a message complies gosa-si requirements
318                           16 :
319  
320 EOF
321     print "\n" ;
325 #===  FUNCTION  ================================================================
326 #         NAME:  logging
327 #   PARAMETERS:  level - string - default 'info'
328 #                msg - string -
329 #                facility - string - default 'LOG_DAEMON'
330 #      RETURNS:  nothing
331 #  DESCRIPTION:  function for logging
332 #===============================================================================
333 sub daemon_log {
334     my( $msg, $level ) = @_;
335     if (not defined $msg) { return }
336     if (not defined $level) { $level = 1 }
337         my $to_be_logged = 0;
339         # Write log line if line level is lower than verbosity given in commandline
340         if ($level <= $verbose) 
341         { 
342                 $to_be_logged = 1 ;
343         }
345         # Write if debug flag is set and bitstring matches
346         if ($debug_parts > 0)
347         {
348                 my $tmp_level = ($level - 10 >= 0) ? $level - 10 : 0 ;
349                 my $tmp_level_bitstring = unpack("B32", pack("N", $tmp_level));
350                 if (int($debug_parts_bitstring & $tmp_level_bitstring)) 
351                 {
352                         $to_be_logged = 1;
353                 }
354         }
356         if ($to_be_logged) 
357         {
358                 if(defined $log_file){
359                         my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
360                         if(not $open_log_fh) {
361                                 print STDERR "cannot open $log_file: $!";
362                                 return;
363                         }
364                         # Check owner and group of log_file and update settings if necessary
365                         my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
366                         if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
367                                 chown($root_uid, $adm_gid, $log_file);
368                         }
370                         # Prepare time string for log message
371                         my ($seconds,$minutes,$hours,$monthday,$month,$year,$weekday,$yearday,$sommertime) = localtime(time);
372                         $hours = $hours < 10 ? $hours = "0".$hours : $hours;
373                         $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
374                         $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
375                         $month = $monthnames[$month];
376                         $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
377                         $year+=1900;
379                         
380                         # Build log message and write it to log file and commandline
381                         chomp($msg);
382                         my $log_msg = "$month $monthday $hours:$minutes:$seconds $prg $msg\n";
383                         flock(LOG_HANDLE, LOCK_EX);
384                         seek(LOG_HANDLE, 0, 2);
385                         print LOG_HANDLE $log_msg;
386                         flock(LOG_HANDLE, LOCK_UN);
387                         if( $foreground ) 
388                         { 
389                                 print STDERR $log_msg;
390                         }
391                         close( LOG_HANDLE );
392                 }
393         }
397 #===  FUNCTION  ================================================================
398 #         NAME:  check_cmdline_param
399 #   PARAMETERS:  nothing
400 #      RETURNS:  nothing
401 #  DESCRIPTION:  validates commandline parameter
402 #===============================================================================
403 sub check_cmdline_param () {
404     my $err_counter = 0;
406         # Check configuration file
407         if(not defined($cfg_file)) {
408                 $cfg_file = "/etc/gosa-si/server.conf";
409                 if(! -r $cfg_file) {
410                         print STDERR "Please specify a config file.\n";
411                         $err_counter++;
412                 }
413     }
415         # Prepare identification which gosa-si parts should be debugged and which not
416         if (defined $debug_parts) 
417         {
418                 if ($debug_parts =~ /^\d+$/)
419                 {
420                         $debug_parts_bitstring = unpack("B32", pack("N", $debug_parts));
421                 }
422                 else
423                 {
424                         print STDERR "Value '$debug_parts' invalid for option d (number expected)\n";
425                         $err_counter++;
426                 }
427         }
429         # Exit if an error occour
430     if( $err_counter > 0 ) 
431         {
432         &usage( "", 1 );
433         exit( -1 );
434     }
438 #===  FUNCTION  ================================================================
439 #         NAME:  check_pid
440 #   PARAMETERS:  nothing
441 #      RETURNS:  nothing
442 #  DESCRIPTION:  handels pid processing
443 #===============================================================================
444 sub check_pid {
445     $pid = -1;
446     # Check, if we are already running
447     if( open(LOCK_FILE, "<$pid_file") ) {
448         $pid = <LOCK_FILE>;
449         if( defined $pid ) {
450             chomp( $pid );
451             if( -f "/proc/$pid/stat" ) {
452                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
453                 if( $stat ) {
454                                         print STDERR "\nERROR: Already running!\n";
455                     close( LOCK_FILE );
456                     exit -1;
457                 }
458             }
459         }
460         close( LOCK_FILE );
461         unlink( $pid_file );
462     }
464     # create a syslog msg if it is not to possible to open PID file
465     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
466         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
467         if (open(LOCK_FILE, '<', $pid_file)
468                 && ($pid = <LOCK_FILE>))
469         {
470             chomp($pid);
471             $msg .= "(PID $pid)\n";
472         } else {
473             $msg .= "(unable to read PID)\n";
474         }
475         if( ! ($foreground) ) {
476             openlog( $0, "cons,pid", "daemon" );
477             syslog( "warning", $msg );
478             closelog();
479         }
480         else {
481             print( STDERR " $msg " );
482         }
483         exit( -1 );
484     }
487 #===  FUNCTION  ================================================================
488 #         NAME:  import_modules
489 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
490 #                are stored
491 #      RETURNS:  nothing
492 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
493 #                state is on is imported by "require 'file';"
494 #===============================================================================
495 sub import_modules {
496     if (not -e $modules_path) {
497         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
498     }
500     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
502     while (defined (my $file = readdir (DIR))) {
503         if (not $file =~ /(\S*?).pm$/) {
504             next;
505         }
506                 my $mod_name = $1;
508         # ArpHandler switch
509         if( $file =~ /ArpHandler.pm/ ) {
510             if( $arp_enabled eq "false" ) { next; }
511         }
512         
513         eval { require $file; };
514         if ($@) {
515             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
516             daemon_log("$@", 1);
517             exit;
518                 } else {
519                         my $info = eval($mod_name.'::get_module_info()');
520                         # Only load module if get_module_info() returns a non-null object
521                         if( $info ) {
522                                 my ($input_address, $input_key, $event_hash) = @{$info};
523                                 $known_modules->{$mod_name} = $info;
524                                 daemon_log("0 INFO: module $mod_name loaded", 5);
525                         }
526                 }
527     }   
528     close (DIR);
531 #===  FUNCTION  ================================================================
532 #         NAME:  password_check
533 #   PARAMETERS:  nothing
534 #      RETURNS:  nothing
535 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
536 #                the same password
537 #===============================================================================
538 sub password_check {
539     my $passwd_hash = {};
540     while (my ($mod_name, $mod_info) = each %$known_modules) {
541         my $mod_passwd = @$mod_info[1];
542         if (not defined $mod_passwd) { next; }
543         if (not exists $passwd_hash->{$mod_passwd}) {
544             $passwd_hash->{$mod_passwd} = $mod_name;
546         # escalates critical error
547         } else {
548             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
549             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
550             exit( -1 );
551         }
552     }
557 #===  FUNCTION  ================================================================
558 #         NAME:  sig_int_handler
559 #   PARAMETERS:  signal - string - signal arose from system
560 #      RETURNS:  nothing
561 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
562 #===============================================================================
563 sub sig_int_handler {
564     my ($signal) = @_;
566 #       if (defined($ldap_handle)) {
567 #               $ldap_handle->disconnect;
568 #       }
569     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
570     
572     daemon_log("shutting down gosa-si-server", 1);
573     system("kill `ps -C gosa-si-server -o pid=`");
575 $SIG{INT} = \&sig_int_handler;
578 sub check_key_and_xml_validity {
579     my ($crypted_msg, $module_key, $session_id) = @_;
580     my $msg;
581     my $msg_hash;
582     my $error_string;
583     eval{
584         $msg = &decrypt_msg($crypted_msg, $module_key);
586         if ($msg =~ /<xml>/i){
587             $msg =~ s/\s+/ /g;  # just for better daemon_log
588             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 18);
589             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
591             ##############
592             # check header
593             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
594             my $header_l = $msg_hash->{'header'};
595             if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
596             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
597             my $header = @{$header_l}[0];
598             if( 0 == length $header) { die 'empty string in header tag'; }
600             ##############
601             # check source
602             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
603             my $source_l = $msg_hash->{'source'};
604             if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
605             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
606             my $source = @{$source_l}[0];
607             if( 0 == length $source) { die 'source error'; }
609             ##############
610             # check target
611             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
612             my $target_l = $msg_hash->{'target'};
613             if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
614         }
615     };
616     if($@) {
617         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
618         $msg = undef;
619         $msg_hash = undef;
620     }
622     return ($msg, $msg_hash);
626 sub check_outgoing_xml_validity {
627     my ($msg, $session_id) = @_;
629     my $msg_hash;
630     eval{
631         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
633         ##############
634         # check header
635         my $header_l = $msg_hash->{'header'};
636         if( 1 != @{$header_l} ) {
637             die 'no or more than one headers specified';
638         }
639         my $header = @{$header_l}[0];
640         if( 0 == length $header) {
641             die 'header has length 0';
642         }
644         ##############
645         # check source
646         my $source_l = $msg_hash->{'source'};
647         if( 1 != @{$source_l} ) {
648             die 'no or more than 1 sources specified';
649         }
650         my $source = @{$source_l}[0];
651         if( 0 == length $source) {
652             die 'source has length 0';
653         }
655                                 # Check if source contains hostname instead of ip address
656                                 if($source =~ /^[a-z][a-z0-9\.]+:\d+$/i) {
657                                                 my ($hostname,$port) = split(/:/, $source);
658                                                 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
659                                                 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
660                                                         # Write ip address to $source variable
661                                                         $source = "$ip_address:$port";
662                                                 }
663                                 }
664         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
665                 $source =~ /^GOSA$/i) {
666             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
667         }
668         
669         ##############
670         # check target  
671         my $target_l = $msg_hash->{'target'};
672         if( 0 == @{$target_l} ) {
673             die "no targets specified";
674         }
675         foreach my $target (@$target_l) {
676             if( 0 == length $target) {
677                 die "target has length 0";
678             }
679             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
680                     $target =~ /^GOSA$/i ||
681                     $target =~ /^\*$/ ||
682                     $target =~ /KNOWN_SERVER/i ||
683                     $target =~ /JOBDB/i ||
684                     $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 ){
685                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
686             }
687         }
688     };
689     if($@) {
690         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
691         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
692         $msg_hash = undef;
693     }
695     return ($msg_hash);
699 sub input_from_known_server {
700     my ($input, $remote_ip, $session_id) = @_ ;  
701     my ($msg, $msg_hash, $module);
703     my $sql_statement= "SELECT * FROM known_server";
704     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
706     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
707         my $host_name = $hit->{hostname};
708         if( not $host_name =~ "^$remote_ip") {
709             next;
710         }
711         my $host_key = $hit->{hostkey};
712         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 14);
713         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 14);
715         # check if module can open msg envelope with module key
716         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
717         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
718             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 14);
719             daemon_log("$@", 14);
720             next;
721         }
722         else {
723             $msg = $tmp_msg;
724             $msg_hash = $tmp_msg_hash;
725             $module = "ServerPackages";
726             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
727             last;
728         }
729     }
731     if( (!$msg) || (!$msg_hash) || (!$module) ) {
732         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 14);
733     }
734   
735     return ($msg, $msg_hash, $module);
739 sub input_from_known_client {
740     my ($input, $remote_ip, $session_id) = @_ ;  
741     my ($msg, $msg_hash, $module);
743     my $sql_statement= "SELECT * FROM known_clients";
744     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
745     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
746         my $host_name = $hit->{hostname};
747         if( not $host_name =~ /^$remote_ip/) {
748                 next;
749                 }
750         my $host_key = $hit->{hostkey};
751         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 14);
752         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 14);
754         # check if module can open msg envelope with module key
755         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
757         if( (!$msg) || (!$msg_hash) ) {
758             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 14);
759             next;
760         }
761         else {
762             $module = "ClientPackages";
763             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
764             last;
765         }
766     }
768     if( (!$msg) || (!$msg_hash) || (!$module) ) {
769         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 14);
770     }
772     return ($msg, $msg_hash, $module);
776 sub input_from_unknown_host {
777         no strict "refs";
778         my ($input, $session_id) = @_ ;
779         my ($msg, $msg_hash, $module);
780         my $error_string;
782         my %act_modules = %$known_modules;
784         while( my ($mod, $info) = each(%act_modules)) {
786                 # check a key exists for this module
787                 my $module_key = ${$mod."_key"};
788                 if( not defined $module_key ) {
789                         if( $mod eq 'ArpHandler' ) {
790                                 next;
791                         }
792                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
793                         next;
794                 }
795                 daemon_log("$session_id DEBUG: $mod: $module_key", 14);
797                 # check if module can open msg envelope with module key
798                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
799                 if( (not defined $msg) || (not defined $msg_hash) ) {
800                         next;
801                 } else {
802                         $module = $mod;
803             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 18);
804                         last;
805                 }
806         }
808         if( (!$msg) || (!$msg_hash) || (!$module)) {
809                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 14);
810         }
812         return ($msg, $msg_hash, $module);
816 sub create_ciphering {
817     my ($passwd) = @_;
818         if((!defined($passwd)) || length($passwd)==0) {
819                 $passwd = "";
820         }
821     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
822     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
823     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
824     $my_cipher->set_iv($iv);
825     return $my_cipher;
829 sub encrypt_msg {
830     my ($msg, $key) = @_;
831     my $my_cipher = &create_ciphering($key);
832     my $len;
833     {
834             use bytes;
835             $len= 16-length($msg)%16;
836     }
837     $msg = "\0"x($len).$msg;
838     $msg = $my_cipher->encrypt($msg);
839     chomp($msg = &encode_base64($msg));
840     # there are no newlines allowed inside msg
841     $msg=~ s/\n//g;
842     return $msg;
846 sub decrypt_msg {
848     my ($msg, $key) = @_ ;
849     $msg = &decode_base64($msg);
850     my $my_cipher = &create_ciphering($key);
851     $msg = $my_cipher->decrypt($msg); 
852     $msg =~ s/\0*//g;
853     return $msg;
857 sub get_encrypt_key {
858     my ($target) = @_ ;
859     my $encrypt_key;
860     my $error = 0;
862     # target can be in known_server
863     if( not defined $encrypt_key ) {
864         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
865         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
866         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
867             my $host_name = $hit->{hostname};
868             if( $host_name ne $target ) {
869                 next;
870             }
871             $encrypt_key = $hit->{hostkey};
872             last;
873         }
874     }
876     # target can be in known_client
877     if( not defined $encrypt_key ) {
878         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
879         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
880         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
881             my $host_name = $hit->{hostname};
882             if( $host_name ne $target ) {
883                 next;
884             }
885             $encrypt_key = $hit->{hostkey};
886             last;
887         }
888     }
890     return $encrypt_key;
894 #===  FUNCTION  ================================================================
895 #         NAME:  open_socket
896 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
897 #                [PeerPort] string necessary if port not appended by PeerAddr
898 #      RETURNS:  socket IO::Socket::INET
899 #  DESCRIPTION:  open a socket to PeerAddr
900 #===============================================================================
901 sub open_socket {
902     my ($PeerAddr, $PeerPort) = @_ ;
903     if(defined($PeerPort)){
904         $PeerAddr = $PeerAddr.":".$PeerPort;
905     }
906     my $socket;
907     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
908             Porto => "tcp",
909             Type => SOCK_STREAM,
910             Timeout => 5,
911             );
912     if(not defined $socket) {
913         return;
914     }
915 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
916     return $socket;
920 sub send_msg_to_target {
921     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
922     my $error = 0;
923     my $header;
924     my $timestamp = &get_time();
925     my $new_status;
926     my $act_status;
927     my ($sql_statement, $res);
928   
929     if( $msg_header ) {
930         $header = "'$msg_header'-";
931     } else {
932         $header = "";
933     }
935         # Memorize own source address
936         my $own_source_address = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
937         $own_source_address .= ":".$server_port;
939         # Patch 0.0.0.0 source to real address
940         $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$own_source_address<\/source>/s;
941         # Patch GOSA source to real address and add forward_to_gosa tag
942         $msg =~ s/<source>GOSA<\/source>/<source>$own_source_address<\/source> <forward_to_gosa>$own_source_address,$session_id<\/forward_to_gosa>/ ;
944     # encrypt xml msg
945     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
947     # opensocket
948     my $socket = &open_socket($address);
949     if( !$socket ) {
950         daemon_log("$session_id ERROR: Cannot open socket to host '$address'. Message processing aborted!", 1);
951         $error++;
952     }
953     
954     if( $error == 0 ) {
955         # send xml msg
956         print $socket $crypted_msg.";$own_source_address\n";
957         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
958         daemon_log("$session_id DEBUG: message:\n$msg", 12);
959         
960     }
962     # close socket in any case
963     if( $socket ) {
964         close $socket;
965     }
967     if( $error > 0 ) { $new_status = "down"; }
968     else { $new_status = $msg_header; }
971     # known_clients
972     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
973     $res = $known_clients_db->select_dbentry($sql_statement);
974     if( keys(%$res) == 1) {
975         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
976         if ($act_status eq "down" && $new_status eq "down") {
977             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
978             $res = $known_clients_db->del_dbentry($sql_statement);
979             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
980         } else { 
981             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
982             $res = $known_clients_db->update_dbentry($sql_statement);
983             if($new_status eq "down"){
984                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
985             } else {
986                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
987             }
988         }
989     }
991     # known_server
992     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
993     $res = $known_server_db->select_dbentry($sql_statement);
994     if( keys(%$res) == 1) {
995         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
996         if ($act_status eq "down" && $new_status eq "down") {
997             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
998             $res = $known_server_db->del_dbentry($sql_statement);
999             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
1000         } 
1001         else { 
1002             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1003             $res = $known_server_db->update_dbentry($sql_statement);
1004             if($new_status eq "down"){
1005                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1006             } else {
1007                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
1008             }
1009         }
1010     }
1011     return $error; 
1015 sub update_jobdb_status_for_send_msgs {
1016     my ($session_id, $answer, $error) = @_;
1017     &daemon_log("$session_id DEBUG: try to update job status", 7); 
1018     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1019         my $jobdb_id = $1;
1020     
1021         $answer =~ /<header>(.*)<\/header>/;
1022         my $job_header = $1;
1024         $answer =~ /<target>(.*)<\/target>/;
1025         my $job_target = $1;
1026             
1027         # Sending msg failed
1028         if( $error ) {
1030             # Set jobs to done, jobs do not need to deliver their message in any case
1031             if (($job_header eq "trigger_action_localboot")
1032                     ||($job_header eq "trigger_action_lock")
1033                     ||($job_header eq "trigger_action_halt") 
1034                     ) {
1035                 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1036                 &daemon_log("$session_id DEBUG: $sql_statement", 7); 
1037                 my $res = $job_db->update_dbentry($sql_statement);
1038                 
1039             # Reactivate jobs, jobs need to deliver their message
1040             } elsif (($job_header eq "trigger_action_activate")
1041                     ||($job_header eq "trigger_action_update")
1042                     ||($job_header eq "trigger_action_reinstall") 
1043                     ||($job_header eq "trigger_activate_new")
1044                     ) {
1045                 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1047             # For all other messages
1048             } else {
1049                 my $sql_statement = "UPDATE $job_queue_tn ".
1050                     "SET status='error', result='can not deliver msg, please consult log file' ".
1051                     "WHERE id=$jobdb_id";
1052                 &daemon_log("$session_id DEBUG: $sql_statement", 7); 
1053                 my $res = $job_db->update_dbentry($sql_statement);
1054             }
1056         # Sending msg was successful
1057         } else {
1058             # Set jobs localboot, lock, activate, halt, reboot and wake to done
1059             # jobs reinstall, update, inst_update do themself setting to done
1060             if (($job_header eq "trigger_action_localboot")
1061                     ||($job_header eq "trigger_action_lock")
1062                     ||($job_header eq "trigger_action_activate")
1063                     ||($job_header eq "trigger_action_halt") 
1064                     ||($job_header eq "trigger_action_reboot")
1065                     ||($job_header eq "trigger_action_wake")
1066                     ||($job_header eq "trigger_wake")
1067                     ) {
1069                 my $sql_statement = "UPDATE $job_queue_tn ".
1070                     "SET status='done' ".
1071                     "WHERE id=$jobdb_id AND status='processed'";
1072                 &daemon_log("$session_id DEBUG: $sql_statement", 7); 
1073                 my $res = $job_db->update_dbentry($sql_statement);
1074             } else { 
1075                 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 7); 
1076             } 
1077         } 
1078     } else { 
1079         &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag.", 7); 
1080     }
1083 sub reactivate_job_with_delay {
1084     my ($session_id, $target, $header, $delay) = @_ ;
1085     # 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
1086     
1087     if (not defined $delay) { $delay = 30 } ;
1088     my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1090     my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress LIKE 'target' AND headertag='$header')"; 
1091     my $res = $job_db->update_dbentry($sql);
1092     daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1093             "cause client '$target' is currently not available", 5);
1094     daemon_log("$session_id $sql", 7);                             
1095     return;
1099 sub sig_handler {
1100         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1101         daemon_log("0 INFO got signal '$signal'", 1); 
1102         $kernel->sig_handled();
1103         return;
1107 sub msg_to_decrypt {
1108         my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1109         my $session_id = $session->ID;
1110         my ($msg, $msg_hash, $module);
1111         my $error = 0;
1113         # fetch new msg out of @msgs_to_decrypt
1114         my $tmp_next_msg = shift @msgs_to_decrypt;
1115     my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1117         # msg is from a new client or gosa
1118         ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1120         # msg is from a gosa-si-server
1121         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1122                 if (not defined $msg_source) 
1123                 {
1124                         # Only needed, to be compatible with older gosa-si-server versions
1125                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1126                 }
1127                 else
1128                 {
1129                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $msg_source, $session_id);
1130                 }
1131         }
1132         # msg is from a gosa-si-client
1133         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1134                 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $msg_source, $session_id);
1135         }
1136         # an error occurred
1137         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1138                 # If an incoming msg could not be decrypted (maybe a wrong key), decide if msg comes from a client 
1139                 # or a server.  In case of a client, send a ping. If the client could not understand a msg from its 
1140                 # server the client cause a re-registering process. In case of a server, decrease update_time in kown_server_db
1141                 # and trigger a re-registering process for servers
1142                 if (defined $msg_source && $msg_source =~ /:$server_port$/)
1143                 {
1144                         daemon_log("$session_id WARNING: Cannot understand incoming msg from server '$msg_source'. Cause re-registration process for servers.", 3);
1145                         my $update_statement = "UPDATE $known_server_tn SET update_time='19700101000000' WHERE hostname='$msg_source'"; 
1146                         daemon_log("$session_id DEBUG: $update_statement", 7);
1147                         my $upadte_res = $known_server_db->exec_statement($update_statement);
1148                         $kernel->yield("register_at_foreign_servers");
1149                 }
1150                 elsif (defined $msg_source)
1151                 {
1152                         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);
1153                         #my $remote_ip = $heap->{'remote_ip'};
1154                         #my $remote_port = $heap->{'remote_port'};
1155                         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1156                         my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1157                         daemon_log("$session_id WARNING: sending msg to cause re-registering: $ping_msg", 3);
1158                 }
1159                 else
1160                 {
1161                         my $foreign_host = defined $msg_source ? $msg_source : $heap->{'remote_ip'};
1162                         daemon_log("$session_id ERROR: incoming message from host '$foreign_host' cannot be understood. Processing aborted: $tmp_next_msg", 1);
1163                 }
1165                 $error++
1166         }
1169         my $header;
1170         my $target;
1171         my $source;
1172         my $done = 0;
1173         my $sql;
1174         my $res;
1176         # check whether this message should be processed here
1177         if ($error == 0) {
1178                 $header = @{$msg_hash->{'header'}}[0];
1179                 $target = @{$msg_hash->{'target'}}[0];
1180                 $source = @{$msg_hash->{'source'}}[0];
1181                 my $not_found_in_known_clients_db = 0;
1182                 my $not_found_in_known_server_db = 0;
1183                 my $not_found_in_foreign_clients_db = 0;
1184                 my $local_address;
1185                 my $local_mac;
1186                 my ($target_ip, $target_port) = split(':', $target);
1188                 # Determine the local ip address if target is an ip address
1189                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1190                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1191                 } else {
1192                         $local_address = $server_address;
1193                 }
1195                 # Determine the local mac address if target is a mac address
1196                 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) {
1197                         my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1198                         my $network_interface= &get_interface_for_ip($loc_ip);
1199                         $local_mac = &get_mac_for_interface($network_interface);
1200                 } else {
1201                         $local_mac = $server_mac_address;
1202                 }
1204                 # target and source is equal to GOSA -> process here
1205                 if (not $done) {
1206                         if ($target eq "GOSA" && $source eq "GOSA") {
1207                                 $done = 1;                    
1208                                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process '$header' here", 11);
1209                         }
1210                 }
1212                 # target is own address without forward_to_gosa-tag -> process here
1213                 if (not $done) {
1214                         #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1215                         if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1216                                 $done = 1;
1217                                 if ($source eq "GOSA") {
1218                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1219                                 }
1220                                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process '$header' here", 11);
1221                         }
1222                 }
1224                 # target is own address with forward_to_gosa-tag not pointing to myself -> process here
1225                 if (not $done) {
1226                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1227                         my $gosa_at;
1228                         my $gosa_session_id;
1229                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1230                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1231                                 if ($gosa_at ne $local_address) {
1232                                         $done = 1;
1233                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process '$header' here", 11); 
1234                                 }
1235                         }
1236                 }
1238                 # Target is a client address and there is a processing function within a plugin -> process loaclly
1239                 if (not $done)
1240                 {
1241                         # Check if target is a client address
1242                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1243                         $res = $known_clients_db->select_dbentry($sql);
1244                         if ((keys(%$res) > 0) ) 
1245                         {
1246                                 my $hostname = $res->{1}->{'hostname'};
1247                                 my $reduced_header = $header;
1248                                 $reduced_header =~ s/gosa_//;
1249                                 # Check if there is a processing function within a plugin
1250                                 if (exists $known_functions->{$reduced_header}) 
1251                                 {
1252                                         $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1253                                         $done = 1;
1254                                         &daemon_log("$session_id DEBUG: Target is client address with processing function within a plugin -> process '$header' here", 11);
1255                                 }
1256                         }
1257                 }
1259                 # If header has a 'job_' prefix, do always process message locally
1260                 # which means put it into job queue
1261                 if ((not $done) && ($header =~ /job_/))
1262                 {
1263                         $done = 1;
1264                         &daemon_log("$session_id DEBUG: Header has a 'job_' prefix. Put it into job queue. -> process '$header' here", 11);
1265                 }
1267                 # if message should be processed here -> add message to incoming_db
1268                 if ($done) {
1269                         # if a 'job_' or a 'gosa_' message comes from a foreign server, fake module from
1270                         # ServerPackages to GosaPackages so gosa-si-server knows how to process this kind of messages
1271                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1272                                 $module = "GosaPackages";
1273                         }
1275                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1276                                         primkey=>[],
1277                                         headertag=>$header,
1278                                         targettag=>$target,
1279                                         xmlmessage=>&encode_base64($msg),
1280                                         timestamp=>&get_time,
1281                                         module=>$module,
1282                                         sessionid=>$session_id,
1283                                 } );
1285                 }
1287                 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1288                 if (not $done) {
1289                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1290                         my $gosa_at;
1291                         my $gosa_session_id;
1292                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1293                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1294                                 if ($gosa_at eq $local_address) {
1295                                         my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1296                                         if( defined $session_reference ) {
1297                                                 $heap = $session_reference->get_heap();
1298                                         }
1299                                         if(exists $heap->{'client'}) {
1300                                                 $msg = &encrypt_msg($msg, $GosaPackages_key);
1301                                                 $heap->{'client'}->put($msg);
1302                                                 &daemon_log("$session_id DEBUG: incoming '$header' message forwarded to GOsa", 11); 
1303                                         }
1304                                         $done = 1;
1305                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward '$header' to gosa", 11);
1306                                 }
1307                         }
1309                 }
1311                 # target is a client address in known_clients -> forward to client
1312                 if (not $done) {
1313                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1314                         $res = $known_clients_db->select_dbentry($sql);
1315                         if (keys(%$res) > 0) 
1316                         {
1317                                 $done = 1; 
1318                                 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> forward '$header' to client", 11);
1319                                 my $hostkey = $res->{1}->{'hostkey'};
1320                                 my $hostname = $res->{1}->{'hostname'};
1321                                 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1322                                 $msg =~ s/<header>gosa_/<header>/;
1323                                 my $error= &send_msg_to_target($msg, $hostname, $hostkey, $header, $session_id);
1324                                 if ($error) {
1325                                         &daemon_log("$session_id ERROR: Some problems occurred while trying to send msg to client '$hostkey': $msg", 1);
1326                                 }
1327                         } 
1328                         else 
1329                         {
1330                                 $not_found_in_known_clients_db = 1;
1331                         }
1332                 }
1334                 # target is a client address in foreign_clients -> forward to registration server
1335                 if (not $done) {
1336                         $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1337                         $res = $foreign_clients_db->select_dbentry($sql);
1338                         if (keys(%$res) > 0) {
1339                                 my $hostname = $res->{1}->{'hostname'};
1340                                 my ($host_ip, $host_port) = split(/:/, $hostname);
1341                                 my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1342                                 my $regserver = $res->{1}->{'regserver'};
1343                                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1344                                 my $res = $known_server_db->select_dbentry($sql);
1345                                 if (keys(%$res) > 0) {
1346                                         my $regserver_key = $res->{1}->{'hostkey'};
1347                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1348                                         $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1349                                         if ($source eq "GOSA") {
1350                                                 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1351                                         }
1352                                         my $error= &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1353                                         if ($error) {
1354                                                 &daemon_log("$session_id ERROR: some problems occurred while trying to send msg to registration server: $msg", 1); 
1355                                         }
1356                                 }
1357                                 $done = 1;
1358                                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward '$header' to registration server", 11);
1359                         } else {
1360                                 $not_found_in_foreign_clients_db = 1;
1361                         }
1362                 }
1364                 # target is a server address -> forward to server
1365                 if (not $done) {
1366                         $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1367                         $res = $known_server_db->select_dbentry($sql);
1368                         if (keys(%$res) > 0) {
1369                                 my $hostkey = $res->{1}->{'hostkey'};
1371                                 if ($source eq "GOSA") {
1372                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1373                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1375                                 }
1377                                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1378                                 $done = 1;
1379                                 &daemon_log("$session_id DEBUG: target is a server address -> forward '$header' to server", 11);
1380                         } else {
1381                                 $not_found_in_known_server_db = 1;
1382                         }
1383                 }
1386                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1387                 if ( $not_found_in_foreign_clients_db 
1388                         && $not_found_in_known_server_db
1389                         && $not_found_in_known_clients_db) {
1390                         &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);
1391             if ($header =~ /^gosa_/ || $header =~ /^job_/) { 
1392                 $module = "GosaPackages"; 
1393             }
1394                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1395                                         primkey=>[],
1396                                         headertag=>$header,
1397                                         targettag=>$target,
1398                                         xmlmessage=>&encode_base64($msg),
1399                                         timestamp=>&get_time,
1400                                         module=>$module,
1401                                         sessionid=>$session_id,
1402                                 } );
1403                         $done = 1;
1404                 }
1407                 if (not $done) {
1408                         daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1409                         if ($source eq "GOSA") {
1410                                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1411                                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1413                                 my $session_reference = $kernel->ID_id_to_session($session_id);
1414                                 if( defined $session_reference ) {
1415                                         $heap = $session_reference->get_heap();
1416                                 }
1417                                 if(exists $heap->{'client'}) {
1418                                         $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1419                                         $heap->{'client'}->put($error_msg);
1420                                 }
1421                         }
1422                 }
1424         }
1426         return;
1430 sub next_task {
1431     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0, ARG1];
1432     my $running_task = POE::Wheel::Run->new(
1433             Program => sub { process_task($session, $heap, $task) },
1434             StdioFilter => POE::Filter::Reference->new(),
1435             StdoutEvent  => "task_result",
1436             StderrEvent  => "task_debug",
1437             CloseEvent   => "task_done",
1438             );
1439     $heap->{task}->{ $running_task->ID } = $running_task;
1442 sub handle_task_result {
1443     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1444     my $client_answer = $result->{'answer'};
1445     if( $client_answer =~ s/session_id=(\d+)$// ) {
1446         my $session_id = $1;
1447         if( defined $session_id ) {
1448             my $session_reference = $kernel->ID_id_to_session($session_id);
1449             if( defined $session_reference ) {
1450                 $heap = $session_reference->get_heap();
1451             }
1452         }
1454         if(exists $heap->{'client'}) {
1455             $heap->{'client'}->put($client_answer);
1456         }
1457     }
1458     $kernel->sig(CHLD => "child_reap");
1461 sub handle_task_debug {
1462     my $result = $_[ARG0];
1463     print STDERR "$result\n";
1466 sub handle_task_done {
1467     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1468     delete $heap->{task}->{$task_id};
1469         if (exists $heap->{ldap_handle}->{$task_id}) {
1470                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
1471         }
1474 sub process_task {
1475     no strict "refs";
1476     #CHECK: Not @_[...]?
1477     my ($session, $heap, $task) = @_;
1478     my $error = 0;
1479     my $answer_l;
1480     my ($answer_header, @answer_target_l, $answer_source);
1481     my $client_answer = "";
1483     # prepare all variables needed to process message
1484     #my $msg = $task->{'xmlmessage'};
1485     my $msg = &decode_base64($task->{'xmlmessage'});
1486     my $incoming_id = $task->{'id'};
1487     my $module = $task->{'module'};
1488     my $header =  $task->{'headertag'};
1489     my $session_id = $task->{'sessionid'};
1490                 my $msg_hash;
1491                 eval {
1492         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1493                 }; 
1494                 daemon_log("ERROR: XML failure '$@'") if ($@);
1495     my $source = @{$msg_hash->{'source'}}[0];
1496     
1497     # set timestamp of incoming client uptodate, so client will not 
1498     # be deleted from known_clients because of expiration
1499     my $cur_time = &get_time();
1500     my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'"; 
1501     my $res = $known_clients_db->exec_statement($sql);
1503     ######################
1504     # process incoming msg
1505     if( $error == 0) {
1506         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1507         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1508         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1510         if ( 0 < @{$answer_l} ) {
1511             my $answer_str = join("\n", @{$answer_l});
1512             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1513                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1514             }
1515             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1516         } else {
1517             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1518         }
1520     }
1521     if( !$answer_l ) { $error++ };
1523     ########
1524     # answer
1525     if( $error == 0 ) {
1527         foreach my $answer ( @{$answer_l} ) {
1528             # check outgoing msg to xml validity
1529             my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1530             if( not defined $answer_hash ) { next; }
1531             
1532             $answer_header = @{$answer_hash->{'header'}}[0];
1533             @answer_target_l = @{$answer_hash->{'target'}};
1534             $answer_source = @{$answer_hash->{'source'}}[0];
1536             # deliver msg to all targets 
1537             foreach my $answer_target ( @answer_target_l ) {
1539                 # targets of msg are all gosa-si-clients in known_clients_db
1540                 if( $answer_target eq "*" ) {
1541                     # answer is for all clients
1542                     my $sql_statement= "SELECT * FROM known_clients";
1543                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1544                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1545                         my $host_name = $hit->{hostname};
1546                         my $host_key = $hit->{hostkey};
1547                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1548                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1549                     }
1550                 }
1552                 # targets of msg are all gosa-si-server in known_server_db
1553                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1554                     # answer is for all server in known_server
1555                     my $sql_statement= "SELECT * FROM $known_server_tn";
1556                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1557                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1558                         my $host_name = $hit->{hostname};
1559                         my $host_key = $hit->{hostkey};
1560                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1561                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1562                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1563                     }
1564                 }
1566                 # target of msg is GOsa
1567                                 elsif( $answer_target eq "GOSA" ) {
1568                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1569                                         my $add_on = "";
1570                     if( defined $session_id ) {
1571                         $add_on = ".session_id=$session_id";
1572                     }
1573                     # answer is for GOSA and has to returned to connected client
1574                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1575                     $client_answer = $gosa_answer.$add_on;
1576                 }
1578                 # target of msg is job queue at this host
1579                 elsif( $answer_target eq "JOBDB") {
1580                     $answer =~ /<header>(\S+)<\/header>/;   
1581                     my $header;
1582                     if( defined $1 ) { $header = $1; }
1583                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1584                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1585                 }
1587                 # Target of msg is a mac address
1588                 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 ) {
1589                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1591                     # Looking for macaddress in known_clients
1592                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1593                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1594                     my $found_ip_flag = 0;
1595                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1596                         my $host_name = $hit->{hostname};
1597                         my $host_key = $hit->{hostkey};
1598                         $answer =~ s/$answer_target/$host_name/g;
1599                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1600                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1601                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1602                         $found_ip_flag++ ;
1603                     }   
1605                     # Looking for macaddress in foreign_clients
1606                     if ($found_ip_flag == 0) {
1607                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1608                         my $res = $foreign_clients_db->select_dbentry($sql);
1609                         while( my ($hit_num, $hit) = each %{ $res } ) {
1610                             my $host_name = $hit->{hostname};
1611                             my $reg_server = $hit->{regserver};
1612                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1613                             
1614                             # Fetch key for reg_server
1615                             my $reg_server_key;
1616                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1617                             my $res = $known_server_db->select_dbentry($sql);
1618                             if (exists $res->{1}) {
1619                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1620                             } else {
1621                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1622                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1623                                 $reg_server_key = undef;
1624                             }
1626                             # Send answer to server where client is registered
1627                             if (defined $reg_server_key) {
1628                                 $answer =~ s/$answer_target/$host_name/g;
1629                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1630                                 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1631                                 $found_ip_flag++ ;
1632                             }
1633                         }
1634                     }
1636                     # No mac to ip matching found
1637                     if( $found_ip_flag == 0) {
1638                         daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1639                         &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1640                     }
1642                 # Answer is for one specific host   
1643                 } else {
1644                     # get encrypt_key
1645                     my $encrypt_key = &get_encrypt_key($answer_target);
1646                     if( not defined $encrypt_key ) {
1647                         # unknown target
1648                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1649                         next;
1650                     }
1651                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1652                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1653                 }
1654             }
1655         }
1656     }
1658     my $filter = POE::Filter::Reference->new();
1659     my %result = ( 
1660             status => "seems ok to me",
1661             answer => $client_answer,
1662             );
1664     my $output = $filter->put( [ \%result ] );
1665     print @$output;
1670 sub session_start {
1671     my ($kernel) = $_[KERNEL];
1672     $global_kernel = $kernel;
1673     $kernel->yield('register_at_foreign_servers');
1674         $kernel->yield('create_fai_server_db', $fai_server_tn );
1675         $kernel->yield('create_fai_release_db', $fai_release_tn );
1676     $kernel->yield('watch_for_next_tasks');
1677         $kernel->sig(USR1 => "sig_handler");
1678         $kernel->sig(USR2 => "recreate_packages_db");
1679         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1680         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1681     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1682         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1683     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1684         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1685     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1687     # Start opsi check
1688     if ($opsi_enabled eq "true") {
1689         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1690     }
1695 sub watch_for_done_jobs {
1696         #CHECK: $heap for what?
1697         my ($kernel,$heap) = @_[KERNEL, HEAP];
1699         my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1700         my $res = $job_db->select_dbentry( $sql_statement );
1702         while( my ($id, $hit) = each %{$res} ) {
1703                 my $jobdb_id = $hit->{id};
1704                 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1705                 my $res = $job_db->del_dbentry($sql_statement); 
1706         }
1708         $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1712 sub watch_for_opsi_jobs {
1713     my ($kernel) = $_[KERNEL];
1715     # 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 
1716     # opsi install job is to parse the xml message. There is still the correct header.
1717     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1718         my $res = $job_db->select_dbentry( $sql_statement );
1720     # Ask OPSI for an update of the running jobs
1721     while (my ($id, $hit) = each %$res ) {
1722         # Determine current parameters of the job
1723         my $hostId = $hit->{'plainname'};
1724         my $macaddress = $hit->{'macaddress'};
1725         my $progress = $hit->{'progress'};
1727         my $result= {};
1728         
1729         # For hosts, only return the products that are or get installed
1730         my $callobj;
1731         $callobj = {
1732             method  => 'getProductStates_hash',
1733             params  => [ $hostId ],
1734             id  => 1,
1735         };
1736         
1737         my $hres = $opsi_client->call($opsi_url, $callobj);
1738         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1739         if (not &check_opsi_res($hres)) {
1740             my $htmp= $hres->result->{$hostId};
1741         
1742             # Check state != not_installed or action == setup -> load and add
1743             my $products= 0;
1744             my $installed= 0;
1745             my $installing = 0;
1746             my $error= 0;  
1747             my @installed_list;
1748             my @error_list;
1749             my $act_status = "none";
1750             foreach my $product (@{$htmp}){
1752                 if ($product->{'installationStatus'} ne "not_installed" or
1753                         $product->{'actionRequest'} eq "setup"){
1755                     # Increase number of products for this host
1756                     $products++;
1757         
1758                     if ($product->{'installationStatus'} eq "failed"){
1759                         $result->{$product->{'productId'}}= "error";
1760                         unshift(@error_list, $product->{'productId'});
1761                         $error++;
1762                     }
1763                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1764                         $result->{$product->{'productId'}}= "installed";
1765                         unshift(@installed_list, $product->{'productId'});
1766                         $installed++;
1767                     }
1768                     if ($product->{'installationStatus'} eq "installing"){
1769                         $result->{$product->{'productId'}}= "installing";
1770                         $installing++;
1771                         $act_status = "installing - ".$product->{'productId'};
1772                     }
1773                 }
1774             }
1775         
1776             # Estimate "rough" progress, avoid division by zero
1777             if ($products == 0) {
1778                 $result->{'progress'}= 0;
1779             } else {
1780                 $result->{'progress'}= int($installed * 100 / $products);
1781             }
1783             # Set updates in job queue
1784             if ((not $error) && (not $installing) && ($installed)) {
1785                 $act_status = "installed - ".join(", ", @installed_list);
1786             }
1787             if ($error) {
1788                 $act_status = "error - ".join(", ", @error_list);
1789             }
1790             if ($progress ne $result->{'progress'} ) {
1791                 # Updating progress and result 
1792                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1793                 my $update_res = $job_db->update_dbentry($update_statement);
1794             }
1795             if ($progress eq 100) { 
1796                 # Updateing status
1797                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1798                 if ($error) {
1799                     $done_statement .= "status='error'";
1800                 } else {
1801                     $done_statement .= "status='done'";
1802                 }
1803                 $done_statement .= " WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1804                 my $done_res = $job_db->update_dbentry($done_statement);
1805             }
1808         }
1809     }
1811     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1815 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1816 sub watch_for_modified_jobs {
1817     my ($kernel,$heap) = @_[KERNEL, HEAP];
1819     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')"; 
1820     my $res = $job_db->select_dbentry( $sql_statement );
1821     
1822     # if db contains no jobs which should be update, do nothing
1823     if (keys %$res != 0) {
1825         if ($job_synchronization  eq "true") {
1826             # make out of the db result a gosa-si message   
1827             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1828  
1829             # update all other SI-server
1830             &inform_all_other_si_server($update_msg);
1831         }
1833         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1834         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1835         $res = $job_db->update_dbentry($sql_statement);
1836     }
1838     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1842 sub watch_for_new_jobs {
1843         if($watch_for_new_jobs_in_progress == 0) {
1844                 $watch_for_new_jobs_in_progress = 1;
1845                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1847                 # check gosa job queue for jobs with executable timestamp
1848                 my $timestamp = &get_time();
1849                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE siserver='localhost' AND status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1850                 my $res = $job_db->exec_statement( $sql_statement );
1852                 # Merge all new jobs that would do the same actions
1853                 my @drops;
1854                 my $hits;
1855                 foreach my $hit (reverse @{$res} ) {
1856                         my $macaddress= lc @{$hit}[8];
1857                         my $headertag= @{$hit}[5];
1858                         if(
1859                                 defined($hits->{$macaddress}) &&
1860                                 defined($hits->{$macaddress}->{$headertag}) &&
1861                                 defined($hits->{$macaddress}->{$headertag}[0])
1862                         ) {
1863                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1864                         }
1865                         $hits->{$macaddress}->{$headertag}= $hit;
1866                 }
1868                 # Delete new jobs with a matching job in state 'processing'
1869                 foreach my $macaddress (keys %{$hits}) {
1870                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1871                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1872                                 if(defined($jobdb_id)) {
1873                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1874                                         my $res = $job_db->exec_statement( $sql_statement );
1875                                         foreach my $hit (@{$res}) {
1876                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1877                                         }
1878                                 } else {
1879                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1880                                 }
1881                         }
1882                 }
1884                 # Commit deletion
1885                 $job_db->exec_statementlist(\@drops);
1887                 # Look for new jobs that could be executed
1888                 foreach my $macaddress (keys %{$hits}) {
1890                         # Look if there is an executing job
1891                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1892                         my $res = $job_db->exec_statement( $sql_statement );
1894                         # Skip new jobs for host if there is a processing job
1895                         if(defined($res) and defined @{$res}[0]) {
1896                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1897                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1898                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1899                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1900                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1901                                         if(defined($res_2) and defined @{$res_2}[0]) {
1902                                                 # Set status from goto-activation to 'waiting' and update timestamp
1903                                                 $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'");
1904                                         }
1905                                 }
1906                                 next;
1907                         }
1909                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1910                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1911                                 if(defined($jobdb_id)) {
1912                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1914                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1915                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1916                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1918                                         # expect macaddress is unique!!!!!!
1919                                         my $target = $res_hash->{1}->{hostname};
1921                                         # change header
1922                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1924                                         # add sqlite_id
1925                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1927                                         $job_msg =~ /<header>(\S+)<\/header>/;
1928                                         my $header = $1 ;
1929                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");                    
1931                                         # update status in job queue to ...
1932                     # ... 'processing', for jobs: 'reinstall', 'update'
1933                     if (($header =~ /gosa_trigger_action_reinstall/) 
1934                             || ($header =~ /gosa_trigger_activate_new/)
1935                             || ($header =~ /gosa_trigger_action_update/)) {
1936                         my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1937                         my $dbres = $job_db->update_dbentry($sql_statement);
1938                     }
1940                     # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1941                     else {
1942                         my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1943                         my $dbres = $job_db->update_dbentry($sql_statement);
1944                     }
1945                 
1947                                         # We don't want parallel processing
1948                                         last;
1949                                 }
1950                         }
1951                 }
1953                 $watch_for_new_jobs_in_progress = 0;
1954                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1955         }
1959 sub watch_for_new_messages {
1960     my ($kernel,$heap) = @_[KERNEL, HEAP];
1961     my @coll_user_msg;   # collection list of outgoing messages
1962     
1963     # check messaging_db for new incoming messages with executable timestamp
1964     my $timestamp = &get_time();
1965     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1966     my $res = $messaging_db->exec_statement( $sql_statement );
1967         foreach my $hit (@{$res}) {
1969         # create outgoing messages
1970         my $message_to = @{$hit}[3];
1971         # translate message_to to plain login name
1972         my @message_to_l = split(/,/, $message_to);  
1973                 my %receiver_h; 
1974                 foreach my $receiver (@message_to_l) {
1975                         if ($receiver =~ /^u_([\s\S]*)$/) {
1976                                 $receiver_h{$1} = 0;
1977                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1978                                 my $group_name = $1;
1979                                 # fetch all group members from ldap and add them to receiver hash
1980                                 my $ldap_handle = &get_ldap_handle();
1981                                 if (defined $ldap_handle) {
1982                                                 my $mesg = $ldap_handle->search(
1983                                                                                 base => $ldap_base,
1984                                                                                 scope => 'sub',
1985                                                                                 attrs => ['memberUid'],
1986                                                                                 filter => "cn=$group_name",
1987                                                                                 );
1988                                                 if ($mesg->count) {
1989                                                                 my @entries = $mesg->entries;
1990                                                                 foreach my $entry (@entries) {
1991                                                                                 my @receivers= $entry->get_value("memberUid");
1992                                                                                 foreach my $receiver (@receivers) { 
1993                                                                                                 $receiver_h{$receiver} = 0;
1994                                                                                 }
1995                                                                 }
1996                                                 } 
1997                                                 # translating errors ?
1998                                                 if ($mesg->code) {
1999                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
2000                                                 }
2001                                                 &release_ldap_handle($ldap_handle);
2002                                 # ldap handle error ?           
2003                                 } else {
2004                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
2005                                 }
2006                         } else {
2007                                 my $sbjct = &encode_base64(@{$hit}[1]);
2008                                 my $msg = &encode_base64(@{$hit}[7]);
2009                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
2010                         }
2011                 }
2012                 my @receiver_l = keys(%receiver_h);
2014         my $message_id = @{$hit}[0];
2016         #add each outgoing msg to messaging_db
2017         my $receiver;
2018         foreach $receiver (@receiver_l) {
2019             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
2020                 "VALUES ('".
2021                 $message_id."', '".    # id
2022                 @{$hit}[1]."', '".     # subject
2023                 @{$hit}[2]."', '".     # message_from
2024                 $receiver."', '".      # message_to
2025                 "none"."', '".         # flag
2026                 "out"."', '".          # direction
2027                 @{$hit}[6]."', '".     # delivery_time
2028                 @{$hit}[7]."', '".     # message
2029                 $timestamp."'".     # timestamp
2030                 ")";
2031             &daemon_log("M DEBUG: $sql_statement", 1);
2032             my $res = $messaging_db->exec_statement($sql_statement);
2033             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
2034         }
2036         # set incoming message to flag d=deliverd
2037         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
2038         &daemon_log("M DEBUG: $sql_statement", 7);
2039         $res = $messaging_db->update_dbentry($sql_statement);
2040         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
2041     }
2043     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
2044     return;
2047 sub watch_for_delivery_messages {
2048     my ($kernel, $heap) = @_[KERNEL, HEAP];
2050     # select outgoing messages
2051     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
2052     #&daemon_log("0 DEBUG: $sql", 7);
2053     my $res = $messaging_db->exec_statement( $sql_statement );
2054     
2055     # build out msg for each    usr
2056     foreach my $hit (@{$res}) {
2057         my $receiver = @{$hit}[3];
2058         my $msg_id = @{$hit}[0];
2059         my $subject = @{$hit}[1];
2060         my $message = @{$hit}[7];
2062         # resolve usr -> host where usr is logged in
2063         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
2064         #&daemon_log("0 DEBUG: $sql", 7);
2065         my $res = $login_users_db->exec_statement($sql);
2067         # receiver is logged in nowhere
2068         if (not ref(@$res[0]) eq "ARRAY") { next; }    
2070         # receiver ist logged in at a client registered at local server
2071                 my $send_succeed = 0;
2072                 foreach my $hit (@$res) {
2073                                 my $receiver_host = @$hit[0];
2074                 my $delivered2host = 0;
2075                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
2077                                 # Looking for host in know_clients_db 
2078                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
2079                                 my $res = $known_clients_db->exec_statement($sql);
2081                 # Host is known in known_clients_db
2082                 if (ref(@$res[0]) eq "ARRAY") {
2083                     my $receiver_key = @{@{$res}[0]}[2];
2084                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2085                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2086                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
2087                     if ($error == 0 ) {
2088                         $send_succeed++ ;
2089                         $delivered2host++ ;
2090                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
2091                     } else {
2092                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
2093                     }
2094                 }
2095                 
2096                 # Message already send, do not need to do anything more, otherwise ...
2097                 if ($delivered2host) { next;}
2098     
2099                 # ...looking for host in foreign_clients_db
2100                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2101                 $res = $foreign_clients_db->exec_statement($sql);
2102   
2103                                 # Host is known in foreign_clients_db 
2104                                 if (ref(@$res[0]) eq "ARRAY") { 
2105                     my $registration_server = @{@{$res}[0]}[2];
2106                     
2107                     # Fetch encryption key for registration server
2108                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2109                     my $res = $known_server_db->exec_statement($sql);
2110                     if (ref(@$res[0]) eq "ARRAY") { 
2111                         my $registration_server_key = @{@{$res}[0]}[3];
2112                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2113                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2114                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
2115                         if ($error == 0 ) {
2116                             $send_succeed++ ;
2117                             $delivered2host++ ;
2118                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
2119                         } else {
2120                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
2121                         }
2123                     } else {
2124                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2125                                 "registrated at server '$registration_server', ".
2126                                 "but no data available in known_server_db ", 1); 
2127                     }
2128                 }
2129                 
2130                 if (not $delivered2host) {
2131                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2132                 }
2133                 }
2135                 if ($send_succeed) {
2136                                 # set outgoing msg at db to deliverd
2137                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
2138                                 my $res = $messaging_db->exec_statement($sql); 
2139                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2140                 } else {
2141             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
2142         }
2143         }
2145     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
2146     return;
2150 sub watch_for_done_messages {
2151     my ($kernel,$heap) = @_[KERNEL, HEAP];
2153     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
2154     #&daemon_log("0 DEBUG: $sql", 7);
2155     my $res = $messaging_db->exec_statement($sql); 
2157     foreach my $hit (@{$res}) {
2158         my $msg_id = @{$hit}[0];
2160         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
2161         #&daemon_log("0 DEBUG: $sql", 7); 
2162         my $res = $messaging_db->exec_statement($sql);
2164         # not all usr msgs have been seen till now
2165         if ( ref(@$res[0]) eq "ARRAY") { next; }
2166         
2167         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
2168         #&daemon_log("0 DEBUG: $sql", 7);
2169         $res = $messaging_db->exec_statement($sql);
2170     
2171     }
2173     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2174     return;
2178 sub watch_for_old_known_clients {
2179     my ($kernel,$heap) = @_[KERNEL, HEAP];
2181     my $sql_statement = "SELECT * FROM $known_clients_tn";
2182     my $res = $known_clients_db->select_dbentry( $sql_statement );
2184     my $cur_time = int(&get_time());
2186     while ( my ($hit_num, $hit) = each %$res) {
2187         my $expired_timestamp = int($hit->{'timestamp'});
2188         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2189         my $dt = DateTime->new( year   => $1,
2190                 month  => $2,
2191                 day    => $3,
2192                 hour   => $4,
2193                 minute => $5,
2194                 second => $6,
2195                 );
2197         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2198         $expired_timestamp = $dt->ymd('').$dt->hms('');
2199         if ($cur_time > $expired_timestamp) {
2200             my $hostname = $hit->{'hostname'};
2201             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2202             my $del_res = $known_clients_db->exec_statement($del_sql);
2204             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2205         }
2207     }
2209     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2213 sub watch_for_next_tasks {
2214     my ($kernel,$heap) = @_[KERNEL, HEAP];
2216     my $sql = "SELECT * FROM $incoming_tn";
2217     my $res = $incoming_db->select_dbentry($sql);
2218     
2219     while ( my ($hit_num, $hit) = each %$res) {
2220         my $headertag = $hit->{'headertag'};
2221         if ($headertag =~ /^answer_(\d+)/) {
2222             # do not start processing, this message is for a still running POE::Wheel
2223             next;
2224         }
2225         my $message_id = $hit->{'id'};
2226         my $session_id = $hit->{'sessionid'};
2227         &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2229         $kernel->yield('next_task', $hit);
2231         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2232         my $res = $incoming_db->exec_statement($sql);
2233     }
2235     $kernel->delay_set('watch_for_next_tasks', 1); 
2239 sub get_ldap_handle {
2240         my ($session_id) = @_;
2241         my $heap;
2243         if (not defined $session_id ) { $session_id = 0 };
2244         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2246         my ($package, $file, $row, $subroutine, $hasArgs, $wantArray, $evalText, $isRequire) = caller(1);
2247         my $caller_text = "subroutine $subroutine";
2248         if ($subroutine eq "(eval)") {
2249                 $caller_text = "eval block within file '$file' for '$evalText'"; 
2250         }
2251         daemon_log("$session_id INFO: new ldap handle for '$caller_text' required!", 7);
2253 get_handle:
2254         my $ldap_handle = Net::LDAP->new( $ldap_uri );
2255         if (not ref $ldap_handle) {
2256                 daemon_log("$session_id ERROR: Connection to LDAP URI '$ldap_uri' failed! Retrying!", 1);
2257                 usleep(100000);
2258                 goto get_handle;
2259         } else {
2260                 daemon_log("$session_id DEBUG: Connection to LDAP URI '$ldap_uri' established.", 6);
2261         }
2263         $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);
2264         return $ldap_handle;
2268 sub release_ldap_handle {
2269         my ($ldap_handle) = @_ ;
2270         if(ref $ldap_handle) {
2271           $ldap_handle->disconnect();
2272   }
2273         &main::daemon_log("0 DEBUG: Released a ldap handle!", 6);
2274         return;
2278 sub change_fai_state {
2279         my ($st, $targets, $session_id) = @_;
2280         $session_id = 0 if not defined $session_id;
2281         # Set FAI state to localboot
2282         my %mapActions= (
2283                 reboot    => '',
2284                 update    => 'softupdate',
2285                 localboot => 'localboot',
2286                 reinstall => 'install',
2287                 rescan    => '',
2288                 wake      => '',
2289                 memcheck  => 'memcheck',
2290                 sysinfo   => 'sysinfo',
2291                 install   => 'install',
2292         );
2294         # Return if this is unknown
2295         if (!exists $mapActions{ $st }){
2296                 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2297                 return;
2298         }
2300         my $state= $mapActions{ $st };
2302         # Build search filter for hosts
2303         my $search= "(&(objectClass=GOhard)";
2304         foreach (@{$targets}){
2305                 $search.= "(macAddress=$_)";
2306         }
2307         $search.= ")";
2309         # If there's any host inside of the search string, procress them
2310         if (!($search =~ /macAddress/)){
2311                 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2312                 return;
2313         }
2315         my $ldap_handle = &get_ldap_handle($session_id);
2316         # Perform search for Unit Tag
2317         my $mesg = $ldap_handle->search(
2318                 base   => $ldap_base,
2319                 scope  => 'sub',
2320                 attrs  => ['dn', 'FAIstate', 'objectClass'],
2321                 filter => "$search"
2322         );
2324         if ($mesg->count) {
2325                 my @entries = $mesg->entries;
2326                 if (0 == @entries) {
2327                         daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2328                 }
2330                 foreach my $entry (@entries) {
2331                         # Only modify entry if it is not set to '$state'
2332                         if ($entry->get_value("FAIstate") ne "$state"){
2333                                 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2334                                 my $result;
2335                                 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2336                                 if (exists $tmp{'FAIobject'}){
2337                                         if ($state eq ''){
2338                                                 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2339                                         } else {
2340                                                 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2341                                         }
2342                                 } elsif ($state ne ''){
2343                                         $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2344                                 }
2346                                 # Errors?
2347                                 if ($result->code){
2348                                         daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2349                                 }
2350                         } else {
2351                                 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
2352                         }  
2353                 }
2354         } else {
2355                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2356         }
2357         &release_ldap_handle($ldap_handle);               
2359         return;
2363 sub change_goto_state {
2364     my ($st, $targets, $session_id) = @_;
2365     $session_id = 0  if not defined $session_id;
2367     # Switch on or off?
2368     my $state= $st eq 'active' ? 'active': 'locked';
2370     my $ldap_handle = &get_ldap_handle($session_id);
2371     if( defined($ldap_handle) ) {
2373       # Build search filter for hosts
2374       my $search= "(&(objectClass=GOhard)";
2375       foreach (@{$targets}){
2376         $search.= "(macAddress=$_)";
2377       }
2378       $search.= ")";
2380       # If there's any host inside of the search string, procress them
2381       if (!($search =~ /macAddress/)){
2382               &release_ldap_handle($ldap_handle);
2383         return;
2384       }
2386       # Perform search for Unit Tag
2387       my $mesg = $ldap_handle->search(
2388           base   => $ldap_base,
2389           scope  => 'sub',
2390           attrs  => ['dn', 'gotoMode'],
2391           filter => "$search"
2392           );
2394       if ($mesg->count) {
2395         my @entries = $mesg->entries;
2396         foreach my $entry (@entries) {
2398           # Only modify entry if it is not set to '$state'
2399           if ($entry->get_value("gotoMode") ne $state){
2401             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2402             my $result;
2403             $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2405             # Errors?
2406             if ($result->code){
2407               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2408             }
2410           }
2411         }
2412       } else {
2413                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2414           }
2416     }
2417         &release_ldap_handle($ldap_handle);
2418         return;
2422 sub run_recreate_packages_db {
2423     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2424     my $session_id = $session->ID;
2425         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2426         $kernel->yield('create_fai_release_db', $fai_release_tn);
2427         $kernel->yield('create_fai_server_db', $fai_server_tn);
2428         return;
2432 sub run_create_fai_server_db {
2433     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2434     my $session_id = $session->ID;
2435     my $task = POE::Wheel::Run->new(
2436             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2437             StdoutEvent  => "session_run_result",
2438             StderrEvent  => "session_run_debug",
2439             CloseEvent   => "session_run_done",
2440             );
2442     $heap->{task}->{ $task->ID } = $task;
2443     return;
2447 sub create_fai_server_db {
2448         my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2449         my $result;
2451         if (not defined $session_id) { $session_id = 0; }
2452         my $ldap_handle = &get_ldap_handle($session_id);
2453         if(defined($ldap_handle)) {
2454                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2455                 my $mesg= $ldap_handle->search(
2456                         base   => $ldap_base,
2457                         scope  => 'sub',
2458                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2459                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2460                 );
2461                 if($mesg->{'resultCode'} == 0 &&
2462                         $mesg->count != 0) {
2463                         foreach my $entry (@{$mesg->{entries}}) {
2464                                 if($entry->exists('FAIrepository')) {
2465                                         # Add an entry for each Repository configured for server
2466                                         foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2467                                                 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2468                                                 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2469                                                 $result= $fai_server_db->add_dbentry( { 
2470                                                                 table => $table_name,
2471                                                                 primkey => ['server', 'fai_release', 'tag'],
2472                                                                 server => $tmp_url,
2473                                                                 fai_release => $tmp_release,
2474                                                                 sections => $tmp_sections,
2475                                                                 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2476                                                         } );
2477                                         }
2478                                 }
2479                         }
2480                 }
2481                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2482                 &release_ldap_handle($ldap_handle);
2484                 # TODO: Find a way to post the 'create_packages_list_db' event
2485                 if(not defined($dont_create_packages_list)) {
2486                         &create_packages_list_db(undef, $session_id);
2487                 }
2488         }       
2490         return $result;
2494 sub run_create_fai_release_db {
2495         my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2496         my $session_id = $session->ID;
2497         my $task = POE::Wheel::Run->new(
2498                 Program => sub { &create_fai_release_db($table_name, $session_id) },
2499                 StdoutEvent  => "session_run_result",
2500                 StderrEvent  => "session_run_debug",
2501                 CloseEvent   => "session_run_done",
2502         );
2504         $heap->{task}->{ $task->ID } = $task;
2505         return;
2509 sub create_fai_release_db {
2510         my ($table_name, $session_id) = @_;
2511         my $result;
2513         # used for logging
2514         if (not defined $session_id) { $session_id = 0; }
2516         my $ldap_handle = &get_ldap_handle($session_id);
2517         if(defined($ldap_handle)) {
2518                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2519                 my $mesg= $ldap_handle->search(
2520                         base   => $ldap_base,
2521                         scope  => 'sub',
2522                         attrs  => [],
2523                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2524                 );
2525                 if(($mesg->code == 0) && ($mesg->count != 0))
2526                 {
2527                         daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,8);
2529                         # Walk through all possible FAI container ou's
2530                         my @sql_list;
2531                         my $timestamp= &get_time();
2532                         foreach my $ou (@{$mesg->{entries}}) {
2533                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2534                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2535                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2536                                         if(@tmp_array) {
2537                                                 foreach my $entry (@tmp_array) {
2538                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2539                                                                 my $sql= 
2540                                                                 "INSERT INTO $table_name "
2541                                                                 ."(timestamp, fai_release, class, type, state) VALUES ("
2542                                                                 .$timestamp.","
2543                                                                 ."'".$entry->{'release'}."',"
2544                                                                 ."'".$entry->{'class'}."',"
2545                                                                 ."'".$entry->{'type'}."',"
2546                                                                 ."'".$entry->{'state'}."')";
2547                                                                 push @sql_list, $sql;
2548                                                         }
2549                                                 }
2550                                         }
2551                                 }
2552                         }
2554                         daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",8);
2555             &release_ldap_handle($ldap_handle);
2556                         if(@sql_list) {
2557                                 unshift @sql_list, "VACUUM";
2558                                 unshift @sql_list, "DELETE FROM $table_name";
2559                                 $fai_release_db->exec_statementlist(\@sql_list);
2560                         }
2561                         daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",7);
2562                 } else {
2563                         daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2564                 }
2565                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2566         }
2567         return $result;
2570 sub get_fai_types {
2571         my $tmp_classes = shift || return undef;
2572         my @result;
2574         foreach my $type(keys %{$tmp_classes}) {
2575                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2576                         my $entry = {
2577                                 type => $type,
2578                                 state => $tmp_classes->{$type}[0],
2579                         };
2580                         push @result, $entry;
2581                 }
2582         }
2584         return @result;
2587 sub get_fai_state {
2588         my $result = "";
2589         my $tmp_classes = shift || return $result;
2591         foreach my $type(keys %{$tmp_classes}) {
2592                 if(defined($tmp_classes->{$type}[0])) {
2593                         $result = $tmp_classes->{$type}[0];
2594                         
2595                 # State is equal for all types in class
2596                         last;
2597                 }
2598         }
2600         return $result;
2603 sub resolve_fai_classes {
2604         my ($fai_base, $ldap_handle, $session_id) = @_;
2605         if (not defined $session_id) { $session_id = 0; }
2606         my $result;
2607         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2608         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2609         my $fai_classes;
2611         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2612         my $mesg= $ldap_handle->search(
2613                 base   => $fai_base,
2614                 scope  => 'sub',
2615                 attrs  => ['cn','objectClass','FAIstate'],
2616                 filter => $fai_filter,
2617         );
2618         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2620         if($mesg->{'resultCode'} == 0 &&
2621                 $mesg->count != 0) {
2622                 foreach my $entry (@{$mesg->{entries}}) {
2623                         if($entry->exists('cn')) {
2624                                 my $tmp_dn= $entry->dn();
2625                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2626                                         - length($fai_base) - 1 );
2628                                 # Skip classname and ou dn parts for class
2629                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2631                                 # Skip classes without releases
2632                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2633                                         next;
2634                                 }
2636                                 my $tmp_cn= $entry->get_value('cn');
2637                                 my $tmp_state= $entry->get_value('FAIstate');
2639                                 my $tmp_type;
2640                                 # Get FAI type
2641                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2642                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2643                                                 $tmp_type= $oclass;
2644                                                 last;
2645                                         }
2646                                 }
2648                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2649                                         # A Subrelease
2650                                         my @sub_releases = split(/,/, $tmp_release);
2652                                         # Walk through subreleases and build hash tree
2653                                         my $hash;
2654                                         while(my $tmp_sub_release = pop @sub_releases) {
2655                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2656                                         }
2657                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2658                                 } else {
2659                                         # A branch, no subrelease
2660                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2661                                 }
2662                         } elsif (!$entry->exists('cn')) {
2663                                 my $tmp_dn= $entry->dn();
2664                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2665                                         - length($fai_base) - 1 );
2666                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2668                                 # Skip classes without releases
2669                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2670                                         next;
2671                                 }
2673                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2674                                         # A Subrelease
2675                                         my @sub_releases= split(/,/, $tmp_release);
2677                                         # Walk through subreleases and build hash tree
2678                                         my $hash;
2679                                         while(my $tmp_sub_release = pop @sub_releases) {
2680                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2681                                         }
2682                                         # Remove the last two characters
2683                                         chop($hash);
2684                                         chop($hash);
2686                                         eval('$fai_classes->'.$hash.'= {}');
2687                                 } else {
2688                                         # A branch, no subrelease
2689                                         if(!exists($fai_classes->{$tmp_release})) {
2690                                                 $fai_classes->{$tmp_release} = {};
2691                                         }
2692                                 }
2693                         }
2694                 }
2696                 # The hash is complete, now we can honor the copy-on-write based missing entries
2697                 foreach my $release (keys %$fai_classes) {
2698                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2699                 }
2700         }
2701         return $result;
2704 sub apply_fai_inheritance {
2705        my $fai_classes = shift || return {};
2706        my $tmp_classes;
2708        # Get the classes from the branch
2709        foreach my $class (keys %{$fai_classes}) {
2710                # Skip subreleases
2711                if($class =~ /^ou=.*$/) {
2712                        next;
2713                } else {
2714                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2715                }
2716        }
2718        # Apply to each subrelease
2719        foreach my $subrelease (keys %{$fai_classes}) {
2720                if($subrelease =~ /ou=/) {
2721                        foreach my $tmp_class (keys %{$tmp_classes}) {
2722                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2723                                        $fai_classes->{$subrelease}->{$tmp_class} =
2724                                        deep_copy($tmp_classes->{$tmp_class});
2725                                } else {
2726                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2727                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2728                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2729                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2730                                                }
2731                                        }
2732                                }
2733                        }
2734                }
2735        }
2737        # Find subreleases in deeper levels
2738        foreach my $subrelease (keys %{$fai_classes}) {
2739                if($subrelease =~ /ou=/) {
2740                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2741                                if($subsubrelease =~ /ou=/) {
2742                                        apply_fai_inheritance($fai_classes->{$subrelease});
2743                                }
2744                        }
2745                }
2746        }
2748        return $fai_classes;
2751 sub get_fai_release_entries {
2752         my $tmp_classes = shift || return;
2753         my $parent = shift || "";
2754         my @result = shift || ();
2756         foreach my $entry (keys %{$tmp_classes}) {
2757                 if(defined($entry)) {
2758                         if($entry =~ /^ou=.*$/) {
2759                                 my $release_name = $entry;
2760                                 $release_name =~ s/ou=//g;
2761                                 if(length($parent)>0) {
2762                                         $release_name = $parent."/".$release_name;
2763                                 }
2764                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2765                                 foreach my $bufentry(@bufentries) {
2766                                         push @result, $bufentry;
2767                                 }
2768                         } else {
2769                                 my @types = get_fai_types($tmp_classes->{$entry});
2770                                 foreach my $type (@types) {
2771                                         push @result, 
2772                                         {
2773                                                 'class' => $entry,
2774                                                 'type' => $type->{'type'},
2775                                                 'release' => $parent,
2776                                                 'state' => $type->{'state'},
2777                                         };
2778                                 }
2779                         }
2780                 }
2781         }
2783         return @result;
2786 sub deep_copy {
2787         my $this = shift;
2788         if (not ref $this) {
2789                 $this;
2790         } elsif (ref $this eq "ARRAY") {
2791                 [map deep_copy($_), @$this];
2792         } elsif (ref $this eq "HASH") {
2793                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2794         } else { die "what type is $_?" }
2798 sub session_run_result {
2799     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2800     $kernel->sig(CHLD => "child_reap");
2803 sub session_run_debug {
2804     my $result = $_[ARG0];
2805     print STDERR "$result\n";
2808 sub session_run_done {
2809     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2810     delete $heap->{task}->{$task_id};
2811         if (exists $heap->{ldap_handle}->{$task_id}) {
2812                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
2813         }
2814         delete $heap->{ldap_handle}->{$task_id};
2818 sub create_sources_list {
2819         my $session_id = shift || 0;
2820         my $result="/tmp/gosa_si_tmp_sources_list";
2822         # Remove old file
2823         if(stat($result)) {
2824                 unlink($result);
2825                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2826         }
2828         my $fh;
2829         open($fh, ">$result");
2830         if (not defined $fh) {
2831                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2832                 return undef;
2833         }
2834         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2835                 my $ldap_handle = &get_ldap_handle($session_id);
2836                 my $mesg=$ldap_handle->search(
2837                         base    => $main::ldap_server_dn,
2838                         scope   => 'base',
2839                         attrs   => 'FAIrepository',
2840                         filter  => 'objectClass=FAIrepositoryServer'
2841                 );
2842                 if($mesg->count) {
2843                         foreach my $entry(@{$mesg->{'entries'}}) {
2844                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2845                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2846                                         my $line = "deb $server $release";
2847                                         $sections =~ s/,/ /g;
2848                                         $line.= " $sections";
2849                                         print $fh $line."\n";
2850                                 }
2851                         }
2852                 }
2853                 &release_ldap_handle($ldap_handle);
2854         } else {
2855                 if (defined $main::ldap_server_dn){
2856                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2857                 } else {
2858                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2859                 }
2860         }
2861         close($fh);
2863         return $result;
2867 sub run_create_packages_list_db {
2868     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2869         my $session_id = $session->ID;
2870         my $task = POE::Wheel::Run->new(
2871                                         Priority => +20,
2872                                         Program => sub {&create_packages_list_db(undef, $session_id)},
2873                                         StdoutEvent  => "session_run_result",
2874                                         StderrEvent  => "session_run_debug",
2875                                         CloseEvent   => "session_run_done",
2876                                         );
2877         $heap->{task}->{ $task->ID } = $task;
2881 sub create_packages_list_db {
2882         my ($sources_file, $session_id) = @_;
2883         
2884         # it should not be possible to trigger a recreation of packages_list_db
2885         # while packages_list_db is under construction, so set flag packages_list_under_construction
2886         # which is tested befor recreation can be started
2887         if (-r $packages_list_under_construction) {
2888                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2889                 return;
2890         } else {
2891                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2892                 # set packages_list_under_construction to true
2893                 system("touch $packages_list_under_construction");
2894                 @packages_list_statements=();
2895         }
2897         if (not defined $session_id) { $session_id = 0; }
2899         if (not defined $sources_file) { 
2900                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2901                 $sources_file = &create_sources_list($session_id);
2902         }
2904         if (not defined $sources_file) {
2905                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2906                 unlink($packages_list_under_construction);
2907                 return;
2908         }
2910         my $line;
2912         open(CONFIG, "<$sources_file") or do {
2913                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2914                 unlink($packages_list_under_construction);
2915                 return;
2916         };
2918         # Read lines
2919         while ($line = <CONFIG>){
2920                 # Unify
2921                 chop($line);
2922                 $line =~ s/^\s+//;
2923                 $line =~ s/^\s+/ /;
2925                 # Strip comments
2926                 $line =~ s/#.*$//g;
2928                 # Skip empty lines
2929                 if ($line =~ /^\s*$/){
2930                         next;
2931                 }
2933                 # Interpret deb line
2934                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2935                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2936                         my $section;
2937                         foreach $section (split(' ', $sections)){
2938                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2939                         }
2940                 }
2941         }
2943         close (CONFIG);
2945         if(keys(%repo_dirs)) {
2946                 find(\&cleanup_and_extract, keys( %repo_dirs ));
2947                 &main::strip_packages_list_statements();
2948                 $packages_list_db->exec_statementlist(\@packages_list_statements);
2949         }
2950         unlink($packages_list_under_construction);
2951         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2952         return;
2955 # This function should do some intensive task to minimize the db-traffic
2956 sub strip_packages_list_statements {
2957         my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2958         my @new_statement_list=();
2959         my $hash;
2960         my $insert_hash;
2961         my $update_hash;
2962         my $delete_hash;
2963         my $known_packages_hash;
2964         my $local_timestamp=get_time();
2966         foreach my $existing_entry (@existing_entries) {
2967                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2968         }
2970         foreach my $statement (@packages_list_statements) {
2971                 if($statement =~ /^INSERT/i) {
2972                         # Assign the values from the insert statement
2973                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2974                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2975                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2976                                 # If section or description has changed, update the DB
2977                                 if( 
2978                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2979                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2980                                 ) {
2981                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2982                                 } else {
2983                                         # package is already present in database. cache this knowledge for later use
2984                                         @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2985                                 }
2986                         } else {
2987                                 # Insert a non-existing entry to db
2988                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2989                         }
2990                 } elsif ($statement =~ /^UPDATE/i) {
2991                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2992                         /^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;
2993                         foreach my $distribution (keys %{$hash}) {
2994                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2995                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2996                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2997                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2998                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2999                                                 my $section;
3000                                                 my $description;
3001                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
3002                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
3003                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
3004                                                 }
3005                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3006                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
3007                                                 }
3008                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3009                                         }
3010                                 }
3011                         }
3012                 }
3013         }
3015         # Check for orphaned entries
3016         foreach my $existing_entry (@existing_entries) {
3017                 my $distribution= @{$existing_entry}[0];
3018                 my $package= @{$existing_entry}[1];
3019                 my $version= @{$existing_entry}[2];
3020                 my $section= @{$existing_entry}[3];
3022                 if(
3023                         exists($insert_hash->{$distribution}->{$package}->{$version}) ||
3024                         exists($update_hash->{$distribution}->{$package}->{$version}) ||
3025                         exists($known_packages_hash->{$distribution}->{$package}->{$version})
3026                 ) {
3027                         next;
3028                 } else {
3029                         # Insert entry to delete hash
3030                         @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
3031                 }
3032         }
3034         # unroll the insert hash
3035         foreach my $distribution (keys %{$insert_hash}) {
3036                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
3037                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
3038                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
3039                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
3040                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
3041                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
3042                                 ."'$local_timestamp')";
3043                         }
3044                 }
3045         }
3047         # unroll the update hash
3048         foreach my $distribution (keys %{$update_hash}) {
3049                 foreach my $package (keys %{$update_hash->{$distribution}}) {
3050                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
3051                                 my $set = "";
3052                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
3053                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
3054                                 }
3055                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3056                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
3057                                 }
3058                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
3059                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
3060                                 }
3061                                 if(defined($set) and length($set) > 0) {
3062                                         $set .= "timestamp = '$local_timestamp'";
3063                                 } else {
3064                                         next;
3065                                 }
3066                                 push @new_statement_list, 
3067                                 "UPDATE $main::packages_list_tn SET $set WHERE"
3068                                 ." distribution = '$distribution'"
3069                                 ." AND package = '$package'"
3070                                 ." AND version = '$version'";
3071                         }
3072                 }
3073         }
3074         
3075         # unroll the delete hash
3076         foreach my $distribution (keys %{$delete_hash}) {
3077                 foreach my $package (keys %{$delete_hash->{$distribution}}) {
3078                         foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
3079                                 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
3080                                 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
3081                         }
3082                 }
3083         }
3085         unshift(@new_statement_list, "VACUUM");
3087         @packages_list_statements = @new_statement_list;
3091 sub parse_package_info {
3092     my ($baseurl, $dist, $section, $session_id)= @_;
3093     my ($package);
3094     if (not defined $session_id) { $session_id = 0; }
3095     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3096     $repo_dirs{ "${repo_path}/pool" } = 1;
3098     foreach $package ("Packages.gz"){
3099         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
3100         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3101         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3102     }
3103     
3107 sub get_package {
3108     my ($url, $dest, $session_id)= @_;
3109     if (not defined $session_id) { $session_id = 0; }
3111     my $tpath = dirname($dest);
3112     -d "$tpath" || mkpath "$tpath";
3114     # This is ugly, but I've no time to take a look at "how it works in perl"
3115     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3116         system("gunzip -cd '$dest' > '$dest.in'");
3117         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 7);
3118         unlink($dest);
3119         daemon_log("$session_id DEBUG: delete file '$dest'", 7); 
3120     } else {
3121         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3122     }
3123     return 0;
3127 sub parse_package {
3128     my ($path, $dist, $srv_path, $session_id)= @_;
3129     if (not defined $session_id) { $session_id = 0;}
3130     my ($package, $version, $section, $description);
3131     my $PACKAGES;
3132     my $timestamp = &get_time();
3134     if(not stat("$path.in")) {
3135         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3136         return;
3137     }
3139     open($PACKAGES, "<$path.in");
3140     if(not defined($PACKAGES)) {
3141         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
3142         return;
3143     }
3145     # Read lines
3146     while (<$PACKAGES>){
3147         my $line = $_;
3148         # Unify
3149         chop($line);
3151         # Use empty lines as a trigger
3152         if ($line =~ /^\s*$/){
3153             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3154             push(@packages_list_statements, $sql);
3155             $package = "none";
3156             $version = "none";
3157             $section = "none";
3158             $description = "none"; 
3159             next;
3160         }
3162         # Trigger for package name
3163         if ($line =~ /^Package:\s/){
3164             ($package)= ($line =~ /^Package: (.*)$/);
3165             next;
3166         }
3168         # Trigger for version
3169         if ($line =~ /^Version:\s/){
3170             ($version)= ($line =~ /^Version: (.*)$/);
3171             next;
3172         }
3174         # Trigger for description
3175         if ($line =~ /^Description:\s/){
3176             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3177             next;
3178         }
3180         # Trigger for section
3181         if ($line =~ /^Section:\s/){
3182             ($section)= ($line =~ /^Section: (.*)$/);
3183             next;
3184         }
3186         # Trigger for filename
3187         if ($line =~ /^Filename:\s/){
3188             my ($filename) = ($line =~ /^Filename: (.*)$/);
3189             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3190             next;
3191         }
3192     }
3194     close( $PACKAGES );
3195     unlink( "$path.in" );
3199 sub store_fileinfo {
3200     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3202     my %fileinfo = (
3203         'package' => $package,
3204         'dist' => $dist,
3205         'version' => $vers,
3206     );
3208     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3212 sub cleanup_and_extract {
3213         my $fileinfo = $repo_files{ $File::Find::name };
3215         if( defined $fileinfo ) {
3216                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3217                 my $sql;
3218                 my $package = $fileinfo->{ 'package' };
3219                 my $newver = $fileinfo->{ 'version' };
3221                 mkpath($dir);
3222                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3224                 if( -f "$dir/DEBIAN/templates" ) {
3226                         daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3228                         my $tmpl= ""; {
3229                                 local $/=undef;
3230                                 open FILE, "$dir/DEBIAN/templates";
3231                                 $tmpl = &encode_base64(<FILE>);
3232                                 close FILE;
3233                         }
3234                         rmtree("$dir/DEBIAN/templates");
3236                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3237                         push @packages_list_statements, $sql;
3238                 }
3239         }
3241         return;
3245 sub register_at_foreign_servers {   
3246     my ($kernel) = $_[KERNEL];
3248         # Update status and update-time of all si-server with expired update_time and 
3249         # block them for race conditional registration processes of other si-servers.
3250         my $act_time = &get_time();
3251         my $block_statement = "UPDATE $known_server_tn SET status='new_server',update_time='19700101000000' WHERE (CAST(update_time AS UNSIGNED))<$act_time ";
3252         &daemon_log("0 DEBUG: $block_statement", 7);
3253         my $block_res = $known_server_db->exec_statement($block_statement);
3255         # Fetch all si-server from db where update_time is younger than act_time
3256         my $fetch_statement = "SELECT * FROM $known_server_tn WHERE update_time='19700101000000'"; 
3257         &daemon_log("0 DEBUG: $fetch_statement", 7);
3258         my $fetch_res = $known_server_db->exec_statement($fetch_statement);
3260     # Detect already connected clients. Will be added to registration msg later. 
3261     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3262     my $client_res = $known_clients_db->exec_statement($client_sql);
3264         # Send registration messag to all fetched si-server
3265     foreach my $hit (@$fetch_res) {
3266         my $hostname = @$hit[0];
3267         my $hostkey = &create_passwd;
3269         # Add already connected clients to registration message 
3270         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3271         &add_content2xml_hash($myhash, 'key', $hostkey);
3272         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3274         # Add locally loaded gosa-si modules to registration message
3275         my $loaded_modules = {};
3276         while (my ($package, $pck_info) = each %$known_modules) {
3277                         next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3278                         foreach my $act_module (keys(%{@$pck_info[2]})) {
3279                                 $loaded_modules->{$act_module} = ""; 
3280                         }
3281                 }
3282         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3284         # Add macaddress to registration message
3285         my ($host_ip, $host_port) = split(/:/, $hostname);
3286         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3287         my $network_interface= &get_interface_for_ip($local_ip);
3288         my $host_mac = &get_mac_for_interface($network_interface);
3289         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3290         
3291         # Build registration message and send it
3292         my $foreign_server_msg = &create_xml_string($myhash);
3293         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3294     }
3297         # After n sec perform a check of all server registration processes
3298     $kernel->delay_set("control_server_registration", 2); 
3300         return;
3304 sub control_server_registration {
3305         my ($kernel) = $_[KERNEL];
3306         
3307         # Check if all registration processes succeed or not
3308         my $select_statement = "SELECT * FROM $known_server_tn WHERE status='new_server'"; 
3309         &daemon_log("0 DEBUG $select_statement", 7);
3310         my $select_res = $known_server_db->exec_statement($select_statement);
3312         # If at least one registration process failed, maybe in case of a race condition
3313         # with a foreign registration process
3314         if (@$select_res > 0) 
3315         {
3316                 # Release block statement 'new_server' to make the server accessible
3317                 # for foreign registration processes
3318                 my $update_statement = "UPDATE $known_server_tn SET status='waiting' WHERE status='new_server'";        
3319                 &daemon_log("0 DEBUG: $update_statement", 7);
3320                 my $update_res = $known_server_db->exec_statement($update_statement);
3322                 # Set a random delay to avoid the registration race condition
3323                 my $new_foreign_servers_register_delay = int(rand(4))+1;
3324                 $kernel->delay_set("register_at_foreign_servers", $new_foreign_servers_register_delay);
3325         }
3326         # If all registration processes succeed
3327         else
3328         {
3329                 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3330         }
3332         return;
3336 #==== MAIN = main ==============================================================
3337 #  parse commandline options
3338 Getopt::Long::Configure( "bundling" );
3339 GetOptions("h|help" => \&usage,
3340         "c|config=s" => \$cfg_file,
3341         "f|foreground" => \$foreground,
3342         "v|verbose+" => \$verbose,
3343         "no-arp+" => \$no_arp,
3344                 "d=s" => \$debug_parts,
3345            ) or (&usage("", 1)&&(exit(-1))); 
3347 #  read and set config parameters
3348 &check_cmdline_param ;
3349 &read_configfile($cfg_file, %cfg_defaults);
3350 &check_pid;
3352 $SIG{CHLD} = 'IGNORE';
3354 # forward error messages to logfile
3355 if( ! $foreground ) {
3356   open( STDIN,  '+>/dev/null' );
3357   open( STDOUT, '+>&STDIN'    );
3358   open( STDERR, '+>&STDIN'    );
3361 # Just fork, if we are not in foreground mode
3362 if( ! $foreground ) { 
3363     chdir '/'                 or die "Can't chdir to /: $!";
3364     $pid = fork;
3365     setsid                    or die "Can't start a new session: $!";
3366     umask 0;
3367 } else { 
3368     $pid = $$; 
3371 # Do something useful - put our PID into the pid_file
3372 if( 0 != $pid ) {
3373     open( LOCK_FILE, ">$pid_file" );
3374     print LOCK_FILE "$pid\n";
3375     close( LOCK_FILE );
3376     if( !$foreground ) { 
3377         exit( 0 ) 
3378     };
3381 # parse head url and revision from svn
3382 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3383 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3384 $server_headURL = defined $1 ? $1 : 'unknown' ;
3385 $server_revision = defined $2 ? $2 : 'unknown' ;
3386 if ($server_headURL =~ /\/tag\// || 
3387         $server_headURL =~ /\/branches\// ) {
3388     $server_status = "stable"; 
3389 } else {
3390     $server_status = "developmental" ;
3392 # Prepare log file and set permissions
3393 $root_uid = getpwnam('root');
3394 $adm_gid = getgrnam('adm');
3395 open(FH, ">>$log_file");
3396 close FH;
3397 chmod(0440, $log_file);
3398 chown($root_uid, $adm_gid, $log_file);
3399 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3401 daemon_log(" ", 1);
3402 daemon_log("$0 started!", 1);
3403 daemon_log("status: $server_status", 1);
3404 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3406 # Buildup data bases
3408     no strict "refs";
3410     if ($db_module eq "DBmysql") {
3411         # connect to incoming_db
3412         $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3414         # connect to gosa-si job queue
3415         $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3417         # connect to known_clients_db
3418         $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3420         # connect to foreign_clients_db
3421         $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3423         # connect to known_server_db
3424         $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3426         # connect to login_usr_db
3427         $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3429         # connect to fai_server_db 
3430         $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3432         # connect to fai_release_db
3433         $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3435         # connect to packages_list_db
3436         $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3438         # connect to messaging_db
3439         $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3441     } elsif ($db_module eq "DBsqlite") {
3442         # connect to incoming_db
3443         unlink($incoming_file_name);
3444         $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3445         chmod(0640, $incoming_file_name);
3446         chown($root_uid, $adm_gid, $incoming_file_name);
3447         
3448         # connect to gosa-si job queue
3449         $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3450         chmod(0640, $job_queue_file_name);
3451         chown($root_uid, $adm_gid, $job_queue_file_name);
3452         
3453         # connect to known_clients_db
3454         #unlink($known_clients_file_name);
3455         $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3456         chmod(0640, $known_clients_file_name);
3457         chown($root_uid, $adm_gid, $known_clients_file_name);
3458         
3459         # connect to foreign_clients_db
3460         #unlink($foreign_clients_file_name);
3461         $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3462         chmod(0640, $foreign_clients_file_name);
3463         chown($root_uid, $adm_gid, $foreign_clients_file_name);
3464         
3465         # connect to known_server_db
3466         #unlink($known_server_file_name);
3467         $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3468         chmod(0640, $known_server_file_name);
3469         chown($root_uid, $adm_gid, $known_server_file_name);
3470         
3471         # connect to login_usr_db
3472         #unlink($login_users_file_name);
3473         $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3474         chmod(0640, $login_users_file_name);
3475         chown($root_uid, $adm_gid, $login_users_file_name);
3476         
3477         # connect to fai_server_db
3478         unlink($fai_server_file_name);
3479         $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3480         chmod(0640, $fai_server_file_name);
3481         chown($root_uid, $adm_gid, $fai_server_file_name);
3482         
3483         # connect to fai_release_db
3484         unlink($fai_release_file_name);
3485         $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3486         chmod(0640, $fai_release_file_name);
3487         chown($root_uid, $adm_gid, $fai_release_file_name);
3488         
3489         # connect to packages_list_db
3490         unlink($packages_list_under_construction);
3491         $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3492         chmod(0640, $packages_list_file_name);
3493         chown($root_uid, $adm_gid, $packages_list_file_name);
3494         
3495         # connect to messaging_db
3496         #unlink($messaging_file_name);
3497         $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3498         chmod(0640, $messaging_file_name);
3499         chown($root_uid, $adm_gid, $messaging_file_name);
3500     }
3504 # Creating tables
3505 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3506 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3507 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3508 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3509 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3510 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3511 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3512 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3513 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3514 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3516 # create xml object used for en/decrypting
3517 $xml = new XML::Simple();
3520 # foreign servers 
3521 my @foreign_server_list;
3523 # add foreign server from cfg file
3524 if ($foreign_server_string ne "") {
3525     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3526     foreach my $foreign_server (@cfg_foreign_server_list) {
3527         push(@foreign_server_list, $foreign_server);
3528     }
3530     daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3533 # Perform a DNS lookup for server registration if flag is true
3534 if ($dns_lookup eq "true") {
3535     # Add foreign server from dns
3536     my @tmp_servers;
3537     if (not $server_domain) {
3538         # Try our DNS Searchlist
3539         for my $domain(get_dns_domains()) {
3540             chomp($domain);
3541             my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3542             if(@$tmp_domains) {
3543                 for my $tmp_server(@$tmp_domains) {
3544                     push @tmp_servers, $tmp_server;
3545                 }
3546             }
3547         }
3548         if(@tmp_servers && length(@tmp_servers)==0) {
3549             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3550         }
3551     } else {
3552         @tmp_servers = &get_server_addresses($server_domain);
3553         if( 0 == @tmp_servers ) {
3554             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3555         }
3556     }
3558     daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3560     foreach my $server (@tmp_servers) { 
3561         unshift(@foreign_server_list, $server); 
3562     }
3563 } else {
3564     daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3568 # eliminate duplicate entries
3569 @foreign_server_list = &del_doubles(@foreign_server_list);
3570 my $all_foreign_server = join(", ", @foreign_server_list);
3571 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3573 # add all found foreign servers to known_server
3574 my $cur_timestamp = &get_time();
3575 foreach my $foreign_server (@foreign_server_list) {
3577         # do not add myself to known_server_db
3578         if (&is_local($foreign_server)) { next; }
3579         ######################################
3581     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3582             primkey=>['hostname'],
3583             hostname=>$foreign_server,
3584             macaddress=>"",
3585             status=>'not_yet_registered',
3586             hostkey=>"none",
3587             loaded_modules => "none", 
3588             timestamp=>$cur_timestamp,
3589                         update_time=>'19700101000000',
3590             } );
3594 # Import all modules
3595 &import_modules;
3597 # Check wether all modules are gosa-si valid passwd check
3598 &password_check;
3600 # Create functions hash
3601 #print STDERR Dumper $known_modules;
3602 while (my ($module, @mod_info) = each %$known_modules) 
3604 #print STDERR Dumper $module;
3605         while (my ($plugin, $functions) = each %{$mod_info[0][2]})
3606         {
3607 #print STDERR Dumper $functions;
3608                 while (my ($function, $nothing) = each %$functions )
3609                 {
3610                         $known_functions->{$function} = $nothing;
3611                 }
3612         }
3615 # Prepare for using Opsi 
3616 if ($opsi_enabled eq "true") {
3617     use JSON::RPC::Client;
3618     use XML::Quote qw(:all);
3619     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3620     $opsi_client = new JSON::RPC::Client;
3624 POE::Component::Server::TCP->new(
3625         Alias => "TCP_SERVER",
3626         Port => $server_port,
3627         ClientInput => sub {
3628                 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3629         my $session_id = $session->ID;
3630                 if ($input =~ /;([\d\.]+:[\d]+)$/) 
3631                 {
3632                         &daemon_log("$session_id DEBUG: incoming message from '$1'", 11);
3633                 }
3634                 else
3635                 {
3636                         my $remote_ip = $heap->{'remote_ip'};
3637                         &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 11);
3638                 }
3639                 push(@msgs_to_decrypt, $input);
3640                 $kernel->yield("msg_to_decrypt");
3641         },
3642         InlineStates => {
3643                 msg_to_decrypt => \&msg_to_decrypt,
3644                 next_task => \&next_task,
3645                 task_result => \&handle_task_result,
3646                 task_done   => \&handle_task_done,
3647                 task_debug  => \&handle_task_debug,
3648                 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3649         }
3650 );
3652 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3654 # create session for repeatedly checking the job queue for jobs
3655 POE::Session->create(
3656         inline_states => {
3657                 _start => \&session_start,
3658         register_at_foreign_servers => \&register_at_foreign_servers,
3659                 control_server_registration => \&control_server_registration,
3660         sig_handler => \&sig_handler,
3661         next_task => \&next_task,
3662         task_result => \&handle_task_result,
3663         task_done   => \&handle_task_done,
3664         task_debug  => \&handle_task_debug,
3665         watch_for_next_tasks => \&watch_for_next_tasks,
3666         watch_for_new_messages => \&watch_for_new_messages,
3667         watch_for_delivery_messages => \&watch_for_delivery_messages,
3668         watch_for_done_messages => \&watch_for_done_messages,
3669                 watch_for_new_jobs => \&watch_for_new_jobs,
3670         watch_for_modified_jobs => \&watch_for_modified_jobs,
3671         watch_for_done_jobs => \&watch_for_done_jobs,
3672         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3673         watch_for_old_known_clients => \&watch_for_old_known_clients,
3674         create_packages_list_db => \&run_create_packages_list_db,
3675         create_fai_server_db => \&run_create_fai_server_db,
3676         create_fai_release_db => \&run_create_fai_release_db,
3677                 recreate_packages_db => \&run_recreate_packages_db,
3678         session_run_result => \&session_run_result,
3679         session_run_debug => \&session_run_debug,
3680         session_run_done => \&session_run_done,
3681         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3682         }
3683 );
3686 POE::Kernel->run();
3687 exit;