Code

bugfixing user messaging function, use goto_notify suffix instead of prefix
[gosa.git] / gosa-si / gosa-si-server
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 #         FILE:  gosa-sd
5 #
6 #        USAGE:  ./gosa-sd
7 #
8 #  DESCRIPTION:
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl 
12 #                libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 #                libpoe-perl
14 #         BUGS:  ---
15 #        NOTES:
16 #       AUTHOR:   (Andreas Rettenberger), <rettenberger@gonicus.de>
17 #      COMPANY:
18 #      VERSION:  1.0
19 #      CREATED:  12.09.2007 08:54:41 CEST
20 #     REVISION:  ---
21 #===============================================================================
23 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev$';
25 use strict;
26 use warnings;
27 use Getopt::Long;
28 use Config::IniFiles;
29 use POSIX;
31 use Fcntl qw/:flock/;
32 use IO::Socket::INET;
33 use IO::Handle;
34 use IO::Select;
35 use Symbol qw(qualify_to_ref);
36 use Crypt::Rijndael;
37 use MIME::Base64;
38 use Digest::MD5  qw(md5 md5_hex md5_base64);
39 use XML::Simple;
40 use Data::Dumper;
41 use Sys::Syslog qw( :DEFAULT setlogsock);
42 use Time::HiRes qw( usleep);
43 use Cwd;
44 use File::Spec;
45 use File::Basename;
46 use File::Find;
47 use File::Copy;
48 use File::Path;
49 use GOSA::GosaSupportDaemon;
50 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
51 use Net::LDAP;
52 use Net::LDAP::Util qw(:escape);
54 # revision number of server and program name
55 my $server_headURL;
56 my $server_revision;
57 my $server_status;
58 our $prg= basename($0);
59 our $verbose= 0;
61 my $db_module = "DBsqlite";
62 {
63 no strict "refs";
64 require ("GOSA/".$db_module.".pm");
65 ("GOSA/".$db_module)->import;
66 #daemon_log("0 INFO: importing database module '$db_module'", 1);
67 }
69 my $modules_path = "/usr/lib/gosa-si/modules";
70 use lib "/usr/lib/gosa-si/modules";
72 our $global_kernel;
73 my ($foreground, $ping_timeout);
74 my ($server);
75 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
76 my ($messaging_db_loop_delay);
77 my ($procid, $pid);
78 my $arp_fifo;
79 my $debug_parts = 0;
80 my $debug_parts_bitstring;
81 my ($xml);
82 my $sources_list;
83 my $max_clients;
84 my %repo_files=();
85 my $repo_path;
86 my %repo_dirs=();
88 # Variables declared in config file are always set to 'our'
89 our (%cfg_defaults, $log_file, $pid_file, 
90     $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
91     $arp_activ, $gosa_unit_tag,
92     $GosaPackages_key, $gosa_timeout,
93     $serverPackages_enabled, $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
94     $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
95     $arp_enabled, $arp_interface,
96     $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
97                 $new_systems_ou,
98 );
100 # additional variable which should be globaly accessable
101 our $server_address;
102 our $server_mac_address;
103 our $gosa_address;
104 our $no_arp;
105 our $forground;
106 our $cfg_file;
107 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn, $ldap_version);
108 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
109 our $known_modules;
110 our $known_functions;
111 our $root_uid;
112 our $adm_gid;
114 # if foreground is not null, script will be not forked to background
115 $foreground = 0 ;
117 # specifies the timeout seconds while checking the online status of a registrating client
118 $ping_timeout = 5;
120 $no_arp = 0;
121 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
122 my @packages_list_statements;
123 my $watch_for_new_jobs_in_progress = 0;
125 # holds all incoming decrypted messages
126 our $incoming_db;
127 our $incoming_tn = 'incoming';
128 my $incoming_file_name;
129 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
130         "timestamp VARCHAR(14) DEFAULT 'none'", 
131         "headertag VARCHAR(255) DEFAULT 'none'",
132         "targettag VARCHAR(255) DEFAULT 'none'",
133         "xmlmessage TEXT",
134         "module VARCHAR(255) DEFAULT 'none'",
135         "sessionid VARCHAR(255) DEFAULT '0'",
136 );
138 # holds all gosa jobs
139 our $job_db;
140 our $job_queue_tn = 'jobs';
141 my $job_queue_file_name;
142 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
143         "timestamp VARCHAR(14) DEFAULT 'none'", 
144         "status VARCHAR(255) DEFAULT 'none'", 
145         "result TEXT",
146         "progress VARCHAR(255) DEFAULT 'none'",
147         "headertag VARCHAR(255) DEFAULT 'none'",
148         "targettag VARCHAR(255) DEFAULT 'none'", 
149         "xmlmessage TEXT", 
150         "macaddress VARCHAR(17) DEFAULT 'none'",
151         "plainname VARCHAR(255) DEFAULT 'none'",
152         "siserver VARCHAR(255) DEFAULT 'none'",
153         "modified INTEGER DEFAULT '0'",
154 );
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         "enabled" => [\$serverPackages_enabled, "true"],
273     "address"      => [\$foreign_server_string, ""],
274     "dns-lookup"            => [\$dns_lookup, "true"],
275     "domain"  => [\$server_domain, ""],
276     "key"     => [\$ServerPackages_key, "none"],
277     "key-lifetime" => [\$foreign_servers_register_delay, 120],
278     "job-synchronization-enabled" => [\$job_synchronization, "true"],
279     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
280     },
281 "ArpHandler" => {
282     "enabled"   => [\$arp_enabled, "true"],
283     "interface" => [\$arp_interface, "all"],
284         },
285 "Opsi" => {
286     "enabled"  => [\$opsi_enabled, "false"], 
287     "server"   => [\$opsi_server, "localhost"],
288     "admin"    => [\$opsi_admin, "opsi-admin"],
289     "password" => [\$opsi_password, "secret"],
290    },
292 );
295 #===  FUNCTION  ================================================================
296 #         NAME:  usage
297 #   PARAMETERS:  nothing
298 #      RETURNS:  nothing
299 #  DESCRIPTION:  print out usage text to STDERR
300 #===============================================================================
301 sub usage {
302     print STDERR << "EOF" ;
303 usage: $prg [-hvf] [-c config] [-d number]
305            -h        : this (help) message
306            -c <file> : config file
307            -f        : foreground, process will not be forked to background
308            -v        : be verbose (multiple to increase verbosity)
309                               'v': error logs
310                             'vvv': warning plus error logs                                              
311                           'vvvvv': info plus warning logs
312                         'vvvvvvv': debug plus info logs
313            -no-arp   : starts $prg without connection to arp module
314            -d <int>  : if verbose level is higher than 7x 'v' specified parts can be debugged
315                            1 : receiving messages
316                            2 : sending messages
317                            4 : encrypting/decrypting messages
318                            8 : verification if a message complies gosa-si requirements
319                           16 : message processing
320                           32 : ldap connectivity
321                           64 : database status and connectivity
322                          128 : main process 
323 EOF
324         exit(0);
328 #===  FUNCTION  ================================================================
329 #         NAME:  logging
330 #   PARAMETERS:  level - string - default 'info'
331 #                msg - string -
332 #                facility - string - default 'LOG_DAEMON'
333 #      RETURNS:  nothing
334 #  DESCRIPTION:  function for logging
335 #===============================================================================
336 sub daemon_log {
337     my( $msg, $level ) = @_;
338     if (not defined $msg) { return }
339     if (not defined $level) { $level = 1 }
340         my $to_be_logged = 0;
342         # Write log line if line level is lower than verbosity given in commandline
343         if ($level <= $verbose) 
344         { 
345                 $to_be_logged = 1 ;
346         }
348         # Write if debug flag is set and bitstring matches
349         if ($debug_parts > 0)
350         {
351                 my $tmp_level = ($level - 10 >= 0) ? $level - 10 : 0 ;
352                 my $tmp_level_bitstring = unpack("B32", pack("N", $tmp_level));
353                 if (int($debug_parts_bitstring & $tmp_level_bitstring)) 
354                 {
355                         $to_be_logged = 1;
356                 }
357         }
359         if ($to_be_logged) 
360         {
361                 if(defined $log_file){
362                         my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
363                         if(not $open_log_fh) {
364                                 print STDERR "cannot open $log_file: $!";
365                                 return;
366                         }
367                         # Check owner and group of log_file and update settings if necessary
368                         my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
369                         if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
370                                 chown($root_uid, $adm_gid, $log_file);
371                         }
373                         # Prepare time string for log message
374                         my ($seconds,$minutes,$hours,$monthday,$month,$year,$weekday,$yearday,$sommertime) = localtime(time);
375                         $hours = $hours < 10 ? $hours = "0".$hours : $hours;
376                         $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
377                         $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
378                         $month = $monthnames[$month];
379                         $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
380                         $year+=1900;
382                         
383                         # Build log message and write it to log file and commandline
384                         chomp($msg);
385                         my $log_msg = "$month $monthday $hours:$minutes:$seconds $prg $msg\n";
386                         flock(LOG_HANDLE, LOCK_EX);
387                         seek(LOG_HANDLE, 0, 2);
388                         print LOG_HANDLE $log_msg;
389                         flock(LOG_HANDLE, LOCK_UN);
390                         if( $foreground ) 
391                         { 
392                                 print STDERR $log_msg;
393                         }
394                         close( LOG_HANDLE );
395                 }
396         }
400 #===  FUNCTION  ================================================================
401 #         NAME:  check_cmdline_param
402 #   PARAMETERS:  nothing
403 #      RETURNS:  nothing
404 #  DESCRIPTION:  validates commandline parameter
405 #===============================================================================
406 sub check_cmdline_param () {
407     my $err_counter = 0;
409         # Check configuration file
410         if(not defined($cfg_file)) {
411                 $cfg_file = "/etc/gosa-si/server.conf";
412                 if(! -r $cfg_file) {
413                         print STDERR "Please specify a config file.\n";
414                         $err_counter++;
415                 }
416     }
418         # Prepare identification which gosa-si parts should be debugged and which not
419         if (defined $debug_parts) 
420         {
421                 if ($debug_parts =~ /^\d+$/)
422                 {
423                         $debug_parts_bitstring = unpack("B32", pack("N", $debug_parts));
424                 }
425                 else
426                 {
427                         print STDERR "Value '$debug_parts' invalid for option d (number expected)\n";
428                         $err_counter++;
429                 }
430         }
432         # Exit if an error occour
433     if( $err_counter > 0 ) { &usage( "", 1 ); }
437 #===  FUNCTION  ================================================================
438 #         NAME:  check_pid
439 #   PARAMETERS:  nothing
440 #      RETURNS:  nothing
441 #  DESCRIPTION:  handels pid processing
442 #===============================================================================
443 sub check_pid {
444     $pid = -1;
445     # Check, if we are already running
446     if( open(LOCK_FILE, "<$pid_file") ) {
447         $pid = <LOCK_FILE>;
448         if( defined $pid ) {
449             chomp( $pid );
450             if( -f "/proc/$pid/stat" ) {
451                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
452                 if( $stat ) {
453                                         print STDERR "\nERROR: Already running!\n";
454                     close( LOCK_FILE );
455                     exit -1;
456                 }
457             }
458         }
459         close( LOCK_FILE );
460         unlink( $pid_file );
461     }
463     # create a syslog msg if it is not to possible to open PID file
464     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
465         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
466         if (open(LOCK_FILE, '<', $pid_file)
467                 && ($pid = <LOCK_FILE>))
468         {
469             chomp($pid);
470             $msg .= "(PID $pid)\n";
471         } else {
472             $msg .= "(unable to read PID)\n";
473         }
474         if( ! ($foreground) ) {
475             openlog( $0, "cons,pid", "daemon" );
476             syslog( "warning", $msg );
477             closelog();
478         }
479         else {
480             print( STDERR " $msg " );
481         }
482         exit( -1 );
483     }
486 #===  FUNCTION  ================================================================
487 #         NAME:  import_modules
488 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
489 #                are stored
490 #      RETURNS:  nothing
491 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
492 #                state is on is imported by "require 'file';"
493 #===============================================================================
494 sub import_modules {
495     if (not -e $modules_path) {
496         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
497     }
499     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
501     while (defined (my $file = readdir (DIR))) {
502         if (not $file =~ /(\S*?).pm$/) {
503             next;
504         }
505                 my $mod_name = $1;
507         # ArpHandler switch
508         if( $file =~ /ArpHandler.pm/ ) {
509             if( $arp_enabled eq "false" ) { next; }
510         }
512                 # ServerPackages switch
513                 if ($file eq "ServerPackages.pm" && $serverPackages_enabled eq "false") 
514                 {
515                         $dns_lookup = "false";
516                         next; 
517                 }
518         
519         eval { require $file; };
520         if ($@) {
521             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
522             daemon_log("$@", 1);
523             exit;
524                 } else {
525                         my $info = eval($mod_name.'::get_module_info()');
526                         # Only load module if get_module_info() returns a non-null object
527                         if( $info ) {
528                                 my ($input_address, $input_key, $event_hash) = @{$info};
529                                 $known_modules->{$mod_name} = $info;
530                                 daemon_log("0 INFO: module $mod_name loaded", 5);
531                         }
532                 }
533     }   
534     close (DIR);
537 #===  FUNCTION  ================================================================
538 #         NAME:  password_check
539 #   PARAMETERS:  nothing
540 #      RETURNS:  nothing
541 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
542 #                the same password
543 #===============================================================================
544 sub password_check {
545     my $passwd_hash = {};
546     while (my ($mod_name, $mod_info) = each %$known_modules) {
547         my $mod_passwd = @$mod_info[1];
548         if (not defined $mod_passwd) { next; }
549         if (not exists $passwd_hash->{$mod_passwd}) {
550             $passwd_hash->{$mod_passwd} = $mod_name;
552         # escalates critical error
553         } else {
554             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
555             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
556             exit( -1 );
557         }
558     }
563 #===  FUNCTION  ================================================================
564 #         NAME:  sig_int_handler
565 #   PARAMETERS:  signal - string - signal arose from system
566 #      RETURNS:  nothing
567 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
568 #===============================================================================
569 sub sig_int_handler {
570     my ($signal) = @_;
572 #       if (defined($ldap_handle)) {
573 #               $ldap_handle->disconnect;
574 #       }
575     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
576     
578     daemon_log("shutting down gosa-si-server", 1);
579     system("kill `ps -C gosa-si-server -o pid=`");
581 $SIG{INT} = \&sig_int_handler;
584 sub check_key_and_xml_validity {
585     my ($crypted_msg, $module_key, $session_id) = @_;
586     my $msg;
587     my $msg_hash;
588     my $error_string;
589     eval{
590         $msg = &decrypt_msg($crypted_msg, $module_key);
592         if ($msg =~ /<xml>/i){
593             $msg =~ s/\s+/ /g;  # just for better daemon_log
594             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 18);
595             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
597             ##############
598             # check header
599             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
600             my $header_l = $msg_hash->{'header'};
601             if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
602             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
603             my $header = @{$header_l}[0];
604             if( 0 == length $header) { die 'empty string in header tag'; }
606             ##############
607             # check source
608             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
609             my $source_l = $msg_hash->{'source'};
610             if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
611             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
612             my $source = @{$source_l}[0];
613             if( 0 == length $source) { die 'source error'; }
615             ##############
616             # check target
617             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
618             my $target_l = $msg_hash->{'target'};
619             if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
620         }
621     };
622     if($@) {
623         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
624         $msg = undef;
625         $msg_hash = undef;
626     }
628     return ($msg, $msg_hash);
632 sub check_outgoing_xml_validity {
633     my ($msg, $session_id) = @_;
635     my $msg_hash;
636     eval{
637         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
639         ##############
640         # check header
641         my $header_l = $msg_hash->{'header'};
642         if( 1 != @{$header_l} ) {
643             die 'no or more than one headers specified';
644         }
645         my $header = @{$header_l}[0];
646         if( 0 == length $header) {
647             die 'header has length 0';
648         }
650         ##############
651         # check source
652         my $source_l = $msg_hash->{'source'};
653         if( 1 != @{$source_l} ) {
654             die 'no or more than 1 sources specified';
655         }
656         my $source = @{$source_l}[0];
657         if( 0 == length $source) {
658             die 'source has length 0';
659         }
661                 # Check if source contains hostname instead of ip address
662                 if($source =~ /^[a-z][\w\-\.]+:\d+$/i) {
663                         my ($hostname,$port) = split(/:/, $source);
664                         my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
665                         if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
666                                 # Write ip address to $source variable
667                                 $source = "$ip_address:$port";
668                                 $msg_hash->{source}[0] = $source ;
669                                 $msg =~ s/<source>.*<\/source>/<source>$source<\/source>/; 
670                         }
671                 }
672         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
673                 $source =~ /^GOSA$/i) {
674             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
675         }
676         
677         ##############
678         # check target  
679         my $target_l = $msg_hash->{'target'};
680         if( 0 == @{$target_l} ) {
681             die "no targets specified";
682         }
683         foreach my $target (@$target_l) {
684             if( 0 == length $target) {
685                 die "target has length 0";
686             }
687             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
688                     $target =~ /^GOSA$/i ||
689                     $target =~ /^\*$/ ||
690                     $target =~ /KNOWN_SERVER/i ||
691                     $target =~ /JOBDB/i ||
692                     $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 ){
693                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
694             }
695         }
696     };
697     if($@) {
698         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
699         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
700         $msg_hash = undef;
701     }
703     return ($msg, $msg_hash);
707 sub input_from_known_server {
708     my ($input, $remote_ip, $session_id) = @_ ;  
709     my ($msg, $msg_hash, $module);
711     my $sql_statement= "SELECT * FROM known_server";
712     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
714     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
715         my $host_name = $hit->{hostname};
716         if( not $host_name =~ "^$remote_ip") {
717             next;
718         }
719         my $host_key = $hit->{hostkey};
720         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 14);
721         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 14);
723         # check if module can open msg envelope with module key
724         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
725         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
726             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 14);
727             daemon_log("$@", 14);
728             next;
729         }
730         else {
731             $msg = $tmp_msg;
732             $msg_hash = $tmp_msg_hash;
733             $module = "ServerPackages";
734             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
735             last;
736         }
737     }
739     if( (!$msg) || (!$msg_hash) || (!$module) ) {
740         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 14);
741     }
742   
743     return ($msg, $msg_hash, $module);
747 sub input_from_known_client {
748     my ($input, $remote_ip, $session_id) = @_ ;  
749     my ($msg, $msg_hash, $module);
751     my $sql_statement= "SELECT * FROM known_clients";
752     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
753     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
754         my $host_name = $hit->{hostname};
755         if( not $host_name =~ /^$remote_ip/) {
756                 next;
757                 }
758         my $host_key = $hit->{hostkey};
759         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 14);
760         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 14);
762         # check if module can open msg envelope with module key
763         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
765         if( (!$msg) || (!$msg_hash) ) {
766             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 14);
767             next;
768         }
769         else {
770             $module = "ClientPackages";
771             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
772             last;
773         }
774     }
776     if( (!$msg) || (!$msg_hash) || (!$module) ) {
777         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 14);
778     }
780     return ($msg, $msg_hash, $module);
784 sub input_from_unknown_host {
785         no strict "refs";
786         my ($input, $session_id) = @_ ;
787         my ($msg, $msg_hash, $module);
788         my $error_string;
790         my %act_modules = %$known_modules;
792         while( my ($mod, $info) = each(%act_modules)) {
794                 # check a key exists for this module
795                 my $module_key = ${$mod."_key"};
796                 if( not defined $module_key ) {
797                         if( $mod eq 'ArpHandler' ) {
798                                 next;
799                         }
800                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
801                         next;
802                 }
803                 daemon_log("$session_id DEBUG: $mod: $module_key", 14);
805                 # check if module can open msg envelope with module key
806                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
807                 if( (not defined $msg) || (not defined $msg_hash) ) {
808                         next;
809                 } else {
810                         $module = $mod;
811             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 18);
812                         last;
813                 }
814         }
816         if( (!$msg) || (!$msg_hash) || (!$module)) {
817                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 14);
818         }
820         return ($msg, $msg_hash, $module);
824 sub create_ciphering {
825     my ($passwd) = @_;
826         if((!defined($passwd)) || length($passwd)==0) {
827                 $passwd = "";
828         }
829     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
830     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
831     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
832     $my_cipher->set_iv($iv);
833     return $my_cipher;
837 sub encrypt_msg {
838     my ($msg, $key) = @_;
839     my $my_cipher = &create_ciphering($key);
840     my $len;
841     {
842             use bytes;
843             $len= 16-length($msg)%16;
844     }
845     $msg = "\0"x($len).$msg;
846     $msg = $my_cipher->encrypt($msg);
847     chomp($msg = &encode_base64($msg));
848     # there are no newlines allowed inside msg
849     $msg=~ s/\n//g;
850     return $msg;
854 sub decrypt_msg {
856     my ($msg, $key) = @_ ;
857     $msg = &decode_base64($msg);
858     my $my_cipher = &create_ciphering($key);
859     $msg = $my_cipher->decrypt($msg); 
860     $msg =~ s/\0*//g;
861     return $msg;
865 sub get_encrypt_key {
866     my ($target) = @_ ;
867     my $encrypt_key;
868     my $error = 0;
870     # target can be in known_server
871     if( not defined $encrypt_key ) {
872         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
873         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
874         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
875             my $host_name = $hit->{hostname};
876             if( $host_name ne $target ) {
877                 next;
878             }
879             $encrypt_key = $hit->{hostkey};
880             last;
881         }
882     }
884     # target can be in known_client
885     if( not defined $encrypt_key ) {
886         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
887         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
888         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
889             my $host_name = $hit->{hostname};
890             if( $host_name ne $target ) {
891                 next;
892             }
893             $encrypt_key = $hit->{hostkey};
894             last;
895         }
896     }
898     return $encrypt_key;
902 #===  FUNCTION  ================================================================
903 #         NAME:  open_socket
904 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
905 #                [PeerPort] string necessary if port not appended by PeerAddr
906 #      RETURNS:  socket IO::Socket::INET
907 #  DESCRIPTION:  open a socket to PeerAddr
908 #===============================================================================
909 sub open_socket {
910     my ($PeerAddr, $PeerPort) = @_ ;
911     if(defined($PeerPort)){
912         $PeerAddr = $PeerAddr.":".$PeerPort;
913     }
914     my $socket;
915     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
916             Porto => "tcp",
917             Type => SOCK_STREAM,
918             Timeout => 5,
919             );
920     if(not defined $socket) {
921         return;
922     }
923 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
924     return $socket;
928 sub send_msg_to_target {
929     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
930     my $error = 0;
931     my $header;
932     my $timestamp = &get_time();
933     my $new_status;
934     my $act_status;
935     my ($sql_statement, $res);
936   
937     if( $msg_header ) {
938         $header = "'$msg_header'-";
939     } else {
940         $header = "";
941     }
943         # Memorize own source address
944         my $own_source_address = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
945         $own_source_address .= ":".$server_port;
947         # Patch 0.0.0.0 source to real address
948         $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$own_source_address<\/source>/s;
949         # Patch GOSA source to real address and add forward_to_gosa tag
950         $msg =~ s/<source>GOSA<\/source>/<source>$own_source_address<\/source> <forward_to_gosa>$own_source_address,$session_id<\/forward_to_gosa>/ ;
952     # encrypt xml msg
953     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
955     # opensocket
956     my $socket = &open_socket($address);
957     if( !$socket ) {
958         daemon_log("$session_id ERROR: Cannot open socket to host '$address'. Message processing aborted!", 1);
959         $error++;
960     }
961     
962     if( $error == 0 ) {
963         # send xml msg
964         print $socket $crypted_msg.";$own_source_address\n";
965         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
966         daemon_log("$session_id DEBUG: message:\n$msg", 12);
967         
968     }
970     # close socket in any case
971     if( $socket ) {
972         close $socket;
973     }
975     if( $error > 0 ) { $new_status = "down"; }
976     else { $new_status = $msg_header; }
979     # known_clients
980     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
981     $res = $known_clients_db->select_dbentry($sql_statement);
982     if( keys(%$res) == 1) {
983         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
984         if ($act_status eq "down" && $new_status eq "down") {
985             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
986             $res = $known_clients_db->del_dbentry($sql_statement);
987             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
988         } else { 
989             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
990             $res = $known_clients_db->update_dbentry($sql_statement);
991             if($new_status eq "down"){
992                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
993             } else {
994                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
995             }
996         }
997     }
999     # known_server
1000     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
1001     $res = $known_server_db->select_dbentry($sql_statement);
1002     if( keys(%$res) == 1) {
1003         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1004         if ($act_status eq "down" && $new_status eq "down") {
1005             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
1006             $res = $known_server_db->del_dbentry($sql_statement);
1007             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
1008         } 
1009         else { 
1010             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1011             $res = $known_server_db->update_dbentry($sql_statement);
1012             if($new_status eq "down"){
1013                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1014             } else {
1015                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
1016             }
1017         }
1018     }
1019     return $error; 
1023 sub update_jobdb_status_for_send_msgs {
1024     my ($session_id, $answer, $error) = @_;
1025     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1026                 &daemon_log("$session_id DEBUG: try to update job status", 138); 
1027         my $jobdb_id = $1;
1028     
1029         $answer =~ /<header>(.*)<\/header>/;
1030         my $job_header = $1;
1032         $answer =~ /<target>(.*)<\/target>/;
1033         my $job_target = $1;
1034             
1035         # Sending msg failed
1036         if( $error ) {
1038             # Set jobs to done, jobs do not need to deliver their message in any case
1039             if (($job_header eq "trigger_action_localboot")
1040                     ||($job_header eq "trigger_action_lock")
1041                     ||($job_header eq "trigger_action_halt") 
1042                     ) {
1043                 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1044                 my $res = $job_db->update_dbentry($sql_statement);
1045                 
1046             # Reactivate jobs, jobs need to deliver their message
1047             } elsif (($job_header eq "trigger_action_activate")
1048                     ||($job_header eq "trigger_action_update")
1049                     ||($job_header eq "trigger_action_reinstall") 
1050                     ||($job_header eq "trigger_activate_new")
1051                     ) {
1052                 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1054             # For all other messages
1055             } else {
1056                 my $sql_statement = "UPDATE $job_queue_tn ".
1057                     "SET status='error', result='can not deliver msg, please consult log file' ".
1058                     "WHERE id=$jobdb_id";
1059                 my $res = $job_db->update_dbentry($sql_statement);
1060             }
1062         # Sending msg was successful
1063         } else {
1064             # Set jobs localboot, lock, activate, halt, reboot and wake to done
1065             # jobs reinstall, update, inst_update do themself setting to done
1066             if (($job_header eq "trigger_action_localboot")
1067                     ||($job_header eq "trigger_action_lock")
1068                     ||($job_header eq "trigger_action_activate")
1069                     ||($job_header eq "trigger_action_halt") 
1070                     ||($job_header eq "trigger_action_reboot")
1071                     ||($job_header eq "trigger_action_wake")
1072                     ||($job_header eq "trigger_wake")
1073                     ) {
1075                 my $sql_statement = "UPDATE $job_queue_tn ".
1076                     "SET status='done' ".
1077                     "WHERE id=$jobdb_id AND status='processed'";
1078                 my $res = $job_db->update_dbentry($sql_statement);
1079             } else { 
1080                 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 138); 
1081             } 
1082         } 
1083     } else { 
1084         &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag.", 138); 
1085     }
1088 sub reactivate_job_with_delay {
1089     my ($session_id, $target, $header, $delay) = @_ ;
1090     # 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
1091     
1092     if (not defined $delay) { $delay = 30 } ;
1093     my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1095     my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress LIKE 'target' AND headertag='$header')"; 
1096     my $res = $job_db->update_dbentry($sql);
1097     daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1098             "cause client '$target' is currently not available", 5);
1099     return;
1103 sub sig_handler {
1104         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1105         daemon_log("0 INFO got signal '$signal'", 1); 
1106         $kernel->sig_handled();
1107         return;
1111 sub msg_to_decrypt {
1112         my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1113         my $session_id = $session->ID;
1114         my ($msg, $msg_hash, $module);
1115         my $error = 0;
1117         # fetch new msg out of @msgs_to_decrypt
1118         my $tmp_next_msg = shift @msgs_to_decrypt;
1119     my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1121         # msg is from a new client or gosa
1122         ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1124         # msg is from a gosa-si-server
1125         if(((!$msg) || (!$msg_hash) || (!$module)) && ($serverPackages_enabled eq "true")){
1126                 if (not defined $msg_source) 
1127                 {
1128                         # Only needed, to be compatible with older gosa-si-server versions
1129                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1130                 }
1131                 else
1132                 {
1133                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $msg_source, $session_id);
1134                 }
1135         }
1136         # msg is from a gosa-si-client
1137         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1138                 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $msg_source, $session_id);
1139         }
1140         # an error occurred
1141         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1142                 # If an incoming msg could not be decrypted (maybe a wrong key), decide if msg comes from a client 
1143                 # or a server.  In case of a client, send a ping. If the client could not understand a msg from its 
1144                 # server the client cause a re-registering process. In case of a server, decrease update_time in kown_server_db
1145                 # and trigger a re-registering process for servers
1146                 if (defined $msg_source && $msg_source =~ /:$server_port$/ && $serverPackages_enabled eq "true")
1147                 {
1148                         daemon_log("$session_id WARNING: Cannot understand incoming msg from server '$msg_source'. Cause re-registration process for servers.", 3);
1149                         my $update_statement = "UPDATE $known_server_tn SET update_time='19700101000000' WHERE hostname='$msg_source'"; 
1150                         daemon_log("$session_id DEBUG: $update_statement", 7);
1151                         my $upadte_res = $known_server_db->exec_statement($update_statement);
1152                         $kernel->yield("register_at_foreign_servers");
1153                 }
1154                 elsif ((defined $msg_source) && (not $msg_source =~ /:$server_port$/))
1155                 {
1156                         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);
1157                         #my $remote_ip = $heap->{'remote_ip'};
1158                         #my $remote_port = $heap->{'remote_port'};
1159                         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1160                         my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1161                         daemon_log("$session_id WARNING: Sending msg to cause re-registering: $ping_msg", 3);
1162                 }
1163                 else
1164                 {
1165                         my $foreign_host = defined $msg_source ? $msg_source : $heap->{'remote_ip'};
1166                         daemon_log("$session_id ERROR: Incoming message from host '$foreign_host' cannot be understood. Processing aborted!", 1);
1167                         daemon_log("$session_id DEBUG: Aborted message: $tmp_next_msg", 11);
1168                 }
1170                 $error++
1171         }
1174         my $header;
1175         my $target;
1176         my $source;
1177         my $done = 0;
1178         my $sql;
1179         my $res;
1181         # check whether this message should be processed here
1182         if ($error == 0) {
1183                 $header = @{$msg_hash->{'header'}}[0];
1184                 $target = @{$msg_hash->{'target'}}[0];
1185                 $source = @{$msg_hash->{'source'}}[0];
1186                 my $not_found_in_known_clients_db = 0;
1187                 my $not_found_in_known_server_db = 0;
1188                 my $not_found_in_foreign_clients_db = 0;
1189                 my $local_address;
1190                 my $local_mac;
1191                 my ($target_ip, $target_port) = split(':', $target);
1193                 # Determine the local ip address if target is an ip address
1194                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1195                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1196                 } else {
1197                         $local_address = $server_address;
1198                 }
1200                 # Determine the local mac address if target is a mac address
1201                 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) {
1202                         my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1203                         my $network_interface= &get_interface_for_ip($loc_ip);
1204                         $local_mac = &get_mac_for_interface($network_interface);
1205                 } else {
1206                         $local_mac = $server_mac_address;
1207                 }
1209                 # target and source is equal to GOSA -> process here
1210                 if (not $done) {
1211                         if ($target eq "GOSA" && $source eq "GOSA") {
1212                                 $done = 1;                    
1213                                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process '$header' here", 11);
1214                         }
1215                 }
1217                 # target is own address without forward_to_gosa-tag -> process here
1218                 if (not $done) {
1219                         #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1220                         if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1221                                 $done = 1;
1222                                 if ($source eq "GOSA") {
1223                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1224                                 }
1225                                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process '$header' here", 11);
1226                         }
1227                 }
1229                 # target is own address with forward_to_gosa-tag not pointing to myself -> process here
1230                 if (not $done) {
1231                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1232                         my $gosa_at;
1233                         my $gosa_session_id;
1234                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1235                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1236                                 if ($gosa_at ne $local_address) {
1237                                         $done = 1;
1238                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process '$header' here", 11); 
1239                                 }
1240                         }
1241                 }
1243                 # Target is a client address and there is a processing function within a plugin -> process loaclly
1244                 if (not $done)
1245                 {
1246                         # Check if target is a client address
1247                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1248                         $res = $known_clients_db->select_dbentry($sql);
1249                         if ((keys(%$res) > 0) ) 
1250                         {
1251                                 my $hostname = $res->{1}->{'hostname'};
1252                                 my $reduced_header = $header;
1253                                 $reduced_header =~ s/gosa_//;
1254                                 # Check if there is a processing function within a plugin
1255                                 if (exists $known_functions->{$reduced_header}) 
1256                                 {
1257                                         $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1258                                         $done = 1;
1259                                         &daemon_log("$session_id DEBUG: Target is client address with processing function within a plugin -> process '$header' here", 11);
1260                                 }
1261                         }
1262                 }
1264                 # If header has a 'job_' prefix, do always process message locally
1265                 # which means put it into job queue
1266                 if ((not $done) && ($header =~ /job_/))
1267                 {
1268                         $done = 1;
1269                         &daemon_log("$session_id DEBUG: Header has a 'job_' prefix. Put it into job queue. -> process '$header' here", 11);
1270                 }
1272                 # if message should be processed here -> add message to incoming_db
1273                 if ($done) {
1274                         # if a 'job_' or a 'gosa_' message comes from a foreign server, fake module from
1275                         # ServerPackages to GosaPackages so gosa-si-server knows how to process this kind of messages
1276                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1277                                 $module = "GosaPackages";
1278                         }
1280                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1281                                         primkey=>[],
1282                                         headertag=>$header,
1283                                         targettag=>$target,
1284                                         xmlmessage=>&encode_base64($msg),
1285                                         timestamp=>&get_time,
1286                                         module=>$module,
1287                                         sessionid=>$session_id,
1288                                 } );
1290                 }
1292                 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1293                 if (not $done) {
1294                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1295                         my $gosa_at;
1296                         my $gosa_session_id;
1297                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1298                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1299                                 if ($gosa_at eq $local_address) {
1300                                         my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1301                                         if( defined $session_reference ) {
1302                                                 $heap = $session_reference->get_heap();
1303                                         }
1304                                         if(exists $heap->{'client'}) {
1305                                                 $msg = &encrypt_msg($msg, $GosaPackages_key);
1306                                                 $heap->{'client'}->put($msg);
1307                                                 &daemon_log("$session_id DEBUG: incoming '$header' message forwarded to GOsa", 11); 
1308                                         }
1309                                         $done = 1;
1310                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward '$header' to gosa", 11);
1311                                 }
1312                         }
1314                 }
1316                 # target is a client address in known_clients -> forward to client
1317                 if (not $done) {
1318                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1319                         $res = $known_clients_db->select_dbentry($sql);
1320                         if (keys(%$res) > 0) 
1321                         {
1322                                 $done = 1; 
1323                                 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> forward '$header' to client", 11);
1324                                 my $hostkey = $res->{1}->{'hostkey'};
1325                                 my $hostname = $res->{1}->{'hostname'};
1326                                 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1327                                 $msg =~ s/<header>gosa_/<header>/;
1328                                 my $error= &send_msg_to_target($msg, $hostname, $hostkey, $header, $session_id);
1329                                 if ($error) {
1330                                         &daemon_log("$session_id ERROR: Some problems occurred while trying to send msg to client '$hostkey': $msg", 1);
1331                                 }
1332                         } 
1333                         else 
1334                         {
1335                                 $not_found_in_known_clients_db = 1;
1336                         }
1337                 }
1339                 # target is a client address in foreign_clients -> forward to registration server
1340                 if (not $done) {
1341                         $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1342                         $res = $foreign_clients_db->select_dbentry($sql);
1343                         if (keys(%$res) > 0) {
1344                                 my $hostname = $res->{1}->{'hostname'};
1345                                 my ($host_ip, $host_port) = split(/:/, $hostname);
1346                                 my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1347                                 my $regserver = $res->{1}->{'regserver'};
1348                                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1349                                 my $res = $known_server_db->select_dbentry($sql);
1350                                 if (keys(%$res) > 0) {
1351                                         my $regserver_key = $res->{1}->{'hostkey'};
1352                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1353                                         $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1354                                         if ($source eq "GOSA") {
1355                                                 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1356                                         }
1357                                         my $error= &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1358                                         if ($error) {
1359                                                 &daemon_log("$session_id ERROR: some problems occurred while trying to send msg to registration server: $msg", 1); 
1360                                         }
1361                                 }
1362                                 $done = 1;
1363                                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward '$header' to registration server", 11);
1364                         } else {
1365                                 $not_found_in_foreign_clients_db = 1;
1366                         }
1367                 }
1369                 # target is a server address -> forward to server
1370                 if (not $done) {
1371                         $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1372                         $res = $known_server_db->select_dbentry($sql);
1373                         if (keys(%$res) > 0) {
1374                                 my $hostkey = $res->{1}->{'hostkey'};
1376                                 if ($source eq "GOSA") {
1377                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1378                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1380                                 }
1382                                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1383                                 $done = 1;
1384                                 &daemon_log("$session_id DEBUG: target is a server address -> forward '$header' to server", 11);
1385                         } else {
1386                                 $not_found_in_known_server_db = 1;
1387                         }
1388                 }
1391                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1392                 if ( $not_found_in_foreign_clients_db 
1393                         && $not_found_in_known_server_db
1394                         && $not_found_in_known_clients_db) {
1395                         &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);
1396             if ($header =~ /^gosa_/ || $header =~ /^job_/) { 
1397                 $module = "GosaPackages"; 
1398             }
1399                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1400                                         primkey=>[],
1401                                         headertag=>$header,
1402                                         targettag=>$target,
1403                                         xmlmessage=>&encode_base64($msg),
1404                                         timestamp=>&get_time,
1405                                         module=>$module,
1406                                         sessionid=>$session_id,
1407                                 } );
1408                         $done = 1;
1409                 }
1412                 if (not $done) {
1413                         daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1414                         if ($source eq "GOSA") {
1415                                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1416                                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1418                                 my $session_reference = $kernel->ID_id_to_session($session_id);
1419                                 if( defined $session_reference ) {
1420                                         $heap = $session_reference->get_heap();
1421                                 }
1422                                 if(exists $heap->{'client'}) {
1423                                         $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1424                                         $heap->{'client'}->put($error_msg);
1425                                 }
1426                         }
1427                 }
1429         }
1431         return;
1435 sub next_task {
1436     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0, ARG1];
1437     my $running_task = POE::Wheel::Run->new(
1438             Program => sub { process_task($session, $heap, $task) },
1439             StdioFilter => POE::Filter::Reference->new(),
1440             StdoutEvent  => "task_result",
1441             StderrEvent  => "task_debug",
1442             CloseEvent   => "task_done",
1443             );
1444     $heap->{task}->{ $running_task->ID } = $running_task;
1447 sub handle_task_result {
1448     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1449     my $client_answer = $result->{'answer'};
1450     if( $client_answer =~ s/session_id=(\d+)$// ) {
1451         my $session_id = $1;
1452         if( defined $session_id ) {
1453             my $session_reference = $kernel->ID_id_to_session($session_id);
1454             if( defined $session_reference ) {
1455                 $heap = $session_reference->get_heap();
1456             }
1457         }
1459         if(exists $heap->{'client'}) {
1460             $heap->{'client'}->put($client_answer);
1461         }
1462     }
1463     $kernel->sig(CHLD => "child_reap");
1466 sub handle_task_debug {
1467     my $result = $_[ARG0];
1468     print STDERR "$result\n";
1471 sub handle_task_done {
1472     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1473     delete $heap->{task}->{$task_id};
1474         if (exists $heap->{ldap_handle}->{$task_id}) {
1475                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
1476         }
1479 sub process_task {
1480     no strict "refs";
1481     #CHECK: Not @_[...]?
1482     my ($session, $heap, $task) = @_;
1483     my $error = 0;
1484     my $answer_l;
1485     my ($answer_header, @answer_target_l, $answer_source);
1486     my $client_answer = "";
1488     # prepare all variables needed to process message
1489     #my $msg = $task->{'xmlmessage'};
1490     my $msg = &decode_base64($task->{'xmlmessage'});
1491     my $incoming_id = $task->{'id'};
1492     my $module = $task->{'module'};
1493     my $header =  $task->{'headertag'};
1494     my $session_id = $task->{'sessionid'};
1495                 my $msg_hash;
1496                 eval {
1497         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1498                 }; 
1499                 daemon_log("ERROR: XML failure '$@'") if ($@);
1500     my $source = @{$msg_hash->{'source'}}[0];
1501     
1502     # set timestamp of incoming client uptodate, so client will not 
1503     # be deleted from known_clients because of expiration
1504     my $cur_time = &get_time();
1505     my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'"; 
1506     my $res = $known_clients_db->exec_statement($sql);
1508     ######################
1509     # process incoming msg
1510     if( $error == 0) {
1511         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1512         daemon_log("$session_id DEBUG: Processing module ".$module, 26);
1513         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1515         if ( 0 < @{$answer_l} ) {
1516             my $answer_str = join("\n", @{$answer_l});
1517                         my @headers; 
1518             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1519                                 push(@headers, $1);
1520             }
1521                         daemon_log("$session_id INFO: got answer message(s) with header: '".join("', '", @headers)."'", 5);
1522             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,26);
1523         } else {
1524             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,26);
1525         }
1527     }
1528     if( !$answer_l ) { $error++ };
1530     ########
1531     # answer
1532     if( $error == 0 ) {
1534         foreach my $answer ( @{$answer_l} ) {
1535             # check outgoing msg to xml validity
1536             my ($answer, $answer_hash) = &check_outgoing_xml_validity($answer, $session_id);
1537             if( not defined $answer_hash ) { next; }
1538             
1539             $answer_header = @{$answer_hash->{'header'}}[0];
1540             @answer_target_l = @{$answer_hash->{'target'}};
1541             $answer_source = @{$answer_hash->{'source'}}[0];
1543             # deliver msg to all targets 
1544             foreach my $answer_target ( @answer_target_l ) {
1546                 # targets of msg are all gosa-si-clients in known_clients_db
1547                 if( $answer_target eq "*" ) {
1548                     # answer is for all clients
1549                     my $sql_statement= "SELECT * FROM known_clients";
1550                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1551                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1552                         my $host_name = $hit->{hostname};
1553                         my $host_key = $hit->{hostkey};
1554                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1555                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1556                     }
1557                 }
1559                 # targets of msg are all gosa-si-server in known_server_db
1560                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1561                     # answer is for all server in known_server
1562                     my $sql_statement= "SELECT * FROM $known_server_tn";
1563                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1564                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1565                         my $host_name = $hit->{hostname};
1566                         my $host_key = $hit->{hostkey};
1567                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1568                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1569                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1570                     }
1571                 }
1573                 # target of msg is GOsa
1574                                 elsif( $answer_target eq "GOSA" ) {
1575                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1576                                         my $add_on = "";
1577                     if( defined $session_id ) {
1578                         $add_on = ".session_id=$session_id";
1579                     }
1580                     # answer is for GOSA and has to returned to connected client
1581                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1582                     $client_answer = $gosa_answer.$add_on;
1583                 }
1585                 # target of msg is job queue at this host
1586                 elsif( $answer_target eq "JOBDB") {
1587                     $answer =~ /<header>(\S+)<\/header>/;   
1588                     my $header;
1589                     if( defined $1 ) { $header = $1; }
1590                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1591                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1592                 }
1594                 # Target of msg is a mac address
1595                 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 ) {
1596                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1598                     # Looking for macaddress in known_clients
1599                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1600                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1601                     my $found_ip_flag = 0;
1602                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1603                         my $host_name = $hit->{hostname};
1604                         my $host_key = $hit->{hostkey};
1605                         $answer =~ s/$answer_target/$host_name/g;
1606                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1607                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1608                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1609                         $found_ip_flag++ ;
1610                     }   
1612                     # Looking for macaddress in foreign_clients
1613                     if ($found_ip_flag == 0) {
1614                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1615                         my $res = $foreign_clients_db->select_dbentry($sql);
1616                         while( my ($hit_num, $hit) = each %{ $res } ) {
1617                             my $host_name = $hit->{hostname};
1618                             my $reg_server = $hit->{regserver};
1619                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1620                             
1621                             # Fetch key for reg_server
1622                             my $reg_server_key;
1623                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1624                             my $res = $known_server_db->select_dbentry($sql);
1625                             if (exists $res->{1}) {
1626                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1627                             } else {
1628                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1629                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1630                                 $reg_server_key = undef;
1631                             }
1633                             # Send answer to server where client is registered
1634                             if (defined $reg_server_key) {
1635                                 $answer =~ s/$answer_target/$host_name/g;
1636                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1637                                 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1638                                 $found_ip_flag++ ;
1639                             }
1640                         }
1641                     }
1643                     # No mac to ip matching found
1644                     if( $found_ip_flag == 0) {
1645                         daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1646                         &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1647                     }
1649                 # Answer is for one specific host   
1650                 } else {
1651                     # get encrypt_key
1652                     my $encrypt_key = &get_encrypt_key($answer_target);
1653                     if( not defined $encrypt_key ) {
1654                         # unknown target
1655                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1656                         next;
1657                     }
1658                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1659                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1660                 }
1661             }
1662         }
1663     }
1665     my $filter = POE::Filter::Reference->new();
1666     my %result = ( 
1667             status => "seems ok to me",
1668             answer => $client_answer,
1669             );
1671     my $output = $filter->put( [ \%result ] );
1672     print @$output;
1677 sub session_start {
1678     my ($kernel) = $_[KERNEL];
1679     $global_kernel = $kernel;
1680     $kernel->yield('register_at_foreign_servers');
1681         $kernel->yield('create_fai_server_db', $fai_server_tn );
1682         $kernel->yield('create_fai_release_db', $fai_release_tn );
1683     $kernel->yield('watch_for_next_tasks');
1684         $kernel->sig(USR1 => "sig_handler");
1685         $kernel->sig(USR2 => "recreate_packages_db");
1686         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1687         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1688     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1689         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1690     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1691         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1692     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1694     # Start opsi check
1695     if ($opsi_enabled eq "true") {
1696         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1697     }
1702 sub watch_for_done_jobs {
1703         #CHECK: $heap for what?
1704         my ($kernel,$heap) = @_[KERNEL, HEAP];
1706         my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1707         my $res = $job_db->select_dbentry( $sql_statement );
1709         while( my ($id, $hit) = each %{$res} ) {
1710                 my $jobdb_id = $hit->{id};
1711                 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1712                 my $res = $job_db->del_dbentry($sql_statement); 
1713         }
1715         $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1719 sub watch_for_opsi_jobs {
1720     my ($kernel) = $_[KERNEL];
1722     # 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 
1723     # opsi install job is to parse the xml message. There is still the correct header.
1724     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1725         my $res = $job_db->select_dbentry( $sql_statement );
1727     # Ask OPSI for an update of the running jobs
1728     while (my ($id, $hit) = each %$res ) {
1729         # Determine current parameters of the job
1730         my $hostId = $hit->{'plainname'};
1731         my $macaddress = $hit->{'macaddress'};
1732         my $progress = $hit->{'progress'};
1734         my $result= {};
1735         
1736         # For hosts, only return the products that are or get installed
1737         my $callobj;
1738         $callobj = {
1739             method  => 'getProductStates_hash',
1740             params  => [ $hostId ],
1741             id  => 1,
1742         };
1743         
1744         my $hres = $opsi_client->call($opsi_url, $callobj);
1745         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1746         if (not &check_opsi_res($hres)) {
1747             my $htmp= $hres->result->{$hostId};
1748         
1749             # Check state != not_installed or action == setup -> load and add
1750             my $products= 0;
1751             my $installed= 0;
1752             my $installing = 0;
1753             my $error= 0;  
1754             my @installed_list;
1755             my @error_list;
1756             my $act_status = "none";
1757             foreach my $product (@{$htmp}){
1759                 if ($product->{'installationStatus'} ne "not_installed" or
1760                         $product->{'actionRequest'} eq "setup"){
1762                     # Increase number of products for this host
1763                     $products++;
1764         
1765                     if ($product->{'installationStatus'} eq "failed"){
1766                         $result->{$product->{'productId'}}= "error";
1767                         unshift(@error_list, $product->{'productId'});
1768                         $error++;
1769                     }
1770                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1771                         $result->{$product->{'productId'}}= "installed";
1772                         unshift(@installed_list, $product->{'productId'});
1773                         $installed++;
1774                     }
1775                     if ($product->{'installationStatus'} eq "installing"){
1776                         $result->{$product->{'productId'}}= "installing";
1777                         $installing++;
1778                         $act_status = "installing - ".$product->{'productId'};
1779                     }
1780                 }
1781             }
1782         
1783             # Estimate "rough" progress, avoid division by zero
1784             if ($products == 0) {
1785                 $result->{'progress'}= 0;
1786             } else {
1787                 $result->{'progress'}= int($installed * 100 / $products);
1788             }
1790             # Set updates in job queue
1791             if ((not $error) && (not $installing) && ($installed)) {
1792                 $act_status = "installed - ".join(", ", @installed_list);
1793             }
1794             if ($error) {
1795                 $act_status = "error - ".join(", ", @error_list);
1796             }
1797             if ($progress ne $result->{'progress'} ) {
1798                 # Updating progress and result 
1799                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1800                 my $update_res = $job_db->update_dbentry($update_statement);
1801             }
1802             if ($progress eq 100) { 
1803                 # Updateing status
1804                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1805                 if ($error) {
1806                     $done_statement .= "status='error'";
1807                 } else {
1808                     $done_statement .= "status='done'";
1809                 }
1810                 $done_statement .= " WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1811                 my $done_res = $job_db->update_dbentry($done_statement);
1812             }
1815         }
1816     }
1818     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1822 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1823 sub watch_for_modified_jobs {
1824     my ($kernel,$heap) = @_[KERNEL, HEAP];
1826     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')"; 
1827     my $res = $job_db->select_dbentry( $sql_statement );
1828     
1829     # if db contains no jobs which should be update, do nothing
1830     if (keys %$res != 0) {
1832         if ($job_synchronization  eq "true") {
1833             # make out of the db result a gosa-si message   
1834             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1835  
1836             # update all other SI-server
1837             &inform_all_other_si_server($update_msg);
1838         }
1840         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1841         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1842         $res = $job_db->update_dbentry($sql_statement);
1843     }
1845     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1849 sub watch_for_new_jobs {
1850         if($watch_for_new_jobs_in_progress == 0) {
1851                 $watch_for_new_jobs_in_progress = 1;
1852                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1854                 # check gosa job queue for jobs with executable timestamp
1855                 my $timestamp = &get_time();
1856                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE siserver='localhost' AND status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1857                 my $res = $job_db->exec_statement( $sql_statement );
1859                 # Merge all new jobs that would do the same actions
1860                 my @drops;
1861                 my $hits;
1862                 foreach my $hit (reverse @{$res} ) {
1863                         my $macaddress= lc @{$hit}[8];
1864                         my $headertag= @{$hit}[5];
1865                         if(
1866                                 defined($hits->{$macaddress}) &&
1867                                 defined($hits->{$macaddress}->{$headertag}) &&
1868                                 defined($hits->{$macaddress}->{$headertag}[0])
1869                         ) {
1870                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1871                         }
1872                         $hits->{$macaddress}->{$headertag}= $hit;
1873                 }
1875                 # Delete new jobs with a matching job in state 'processing'
1876                 foreach my $macaddress (keys %{$hits}) {
1877                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1878                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1879                                 if(defined($jobdb_id)) {
1880                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1881                                         my $res = $job_db->exec_statement( $sql_statement );
1882                                         foreach my $hit (@{$res}) {
1883                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1884                                         }
1885                                 } else {
1886                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1887                                 }
1888                         }
1889                 }
1891                 # Commit deletion
1892                 $job_db->exec_statementlist(\@drops);
1894                 # Look for new jobs that could be executed
1895                 foreach my $macaddress (keys %{$hits}) {
1897                         # Look if there is an executing job
1898                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1899                         my $res = $job_db->exec_statement( $sql_statement );
1901                         # Skip new jobs for host if there is a processing job
1902                         if(defined($res) and defined @{$res}[0]) {
1903                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1904                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1905                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1906                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1907                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1908                                         if(defined($res_2) and defined @{$res_2}[0]) {
1909                                                 # Set status from goto-activation to 'waiting' and update timestamp
1910                                                 $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'");
1911                                         }
1912                                 }
1913                                 next;
1914                         }
1916                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1917                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1918                                 if(defined($jobdb_id)) {
1919                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1921                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1922                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1923                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1925                                         # expect macaddress is unique!!!!!!
1926                                         my $target = $res_hash->{1}->{hostname};
1928                                         # change header
1929                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1931                                         # add sqlite_id
1932                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1934                                         $job_msg =~ /<header>(\S+)<\/header>/;
1935                                         my $header = $1 ;
1936                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");                    
1938                                         # update status in job queue to ...
1939                     # ... 'processing', for jobs: 'reinstall', 'update'
1940                     if (($header =~ /gosa_trigger_action_reinstall/) 
1941                             || ($header =~ /gosa_trigger_activate_new/)
1942                             || ($header =~ /gosa_trigger_action_update/)) {
1943                         my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1944                         my $dbres = $job_db->update_dbentry($sql_statement);
1945                     }
1947                     # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1948                     else {
1949                         my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1950                         my $dbres = $job_db->update_dbentry($sql_statement);
1951                     }
1952                 
1954                                         # We don't want parallel processing
1955                                         last;
1956                                 }
1957                         }
1958                 }
1960                 $watch_for_new_jobs_in_progress = 0;
1961                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1962         }
1966 sub watch_for_new_messages {
1967     my ($kernel,$heap) = @_[KERNEL, HEAP];
1968     my @coll_user_msg;   # collection list of outgoing messages
1969     
1970     # check messaging_db for new incoming messages with executable timestamp
1971     my $timestamp = &get_time();
1972     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1973     my $res = $messaging_db->exec_statement( $sql_statement );
1974         foreach my $hit (@{$res}) {
1976         # create outgoing messages
1977         my $message_to = @{$hit}[3];
1978         # translate message_to to plain login name
1979         my @message_to_l = split(/,/, $message_to);  
1980                 my %receiver_h; 
1981                 foreach my $receiver (@message_to_l) {
1982                         if ($receiver =~ /^u_([\s\S]*)$/) {
1983                                 $receiver_h{$1} = 0;
1984                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1985                                 my $group_name = $1;
1986                                 # fetch all group members from ldap and add them to receiver hash
1987                                 my $ldap_handle = &get_ldap_handle();
1988                                 if (defined $ldap_handle) {
1989                                                 my $mesg = $ldap_handle->search(
1990                                                                                 base => $ldap_base,
1991                                                                                 scope => 'sub',
1992                                                                                 attrs => ['memberUid'],
1993                                                                                 filter => "cn=$group_name",
1994                                                                                 );
1995                                                 if ($mesg->count) {
1996                                                                 my @entries = $mesg->entries;
1997                                                                 foreach my $entry (@entries) {
1998                                                                                 my @receivers= $entry->get_value("memberUid");
1999                                                                                 foreach my $receiver (@receivers) { 
2000                                                                                                 $receiver_h{$receiver} = 0;
2001                                                                                 }
2002                                                                 }
2003                                                 } 
2004                                                 # translating errors ?
2005                                                 if ($mesg->code) {
2006                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
2007                                                 }
2008                                                 &release_ldap_handle($ldap_handle);
2009                                 # ldap handle error ?           
2010                                 } else {
2011                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
2012                                 }
2013                         } else {
2014                                 my $sbjct = &encode_base64(@{$hit}[1]);
2015                                 my $msg = &encode_base64(@{$hit}[7]);
2016                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
2017                         }
2018                 }
2019                 my @receiver_l = keys(%receiver_h);
2021         my $message_id = @{$hit}[0];
2023         #add each outgoing msg to messaging_db
2024         my $receiver;
2025         foreach $receiver (@receiver_l) {
2026             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
2027                 "VALUES ('".
2028                 $message_id."', '".    # id
2029                 @{$hit}[1]."', '".     # subject
2030                 @{$hit}[2]."', '".     # message_from
2031                 $receiver."', '".      # message_to
2032                 "none"."', '".         # flag
2033                 "out"."', '".          # direction
2034                 @{$hit}[6]."', '".     # delivery_time
2035                 @{$hit}[7]."', '".     # message
2036                 $timestamp."'".     # timestamp
2037                 ")";
2038             &daemon_log("M DEBUG: $sql_statement", 1);
2039             my $res = $messaging_db->exec_statement($sql_statement);
2040             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
2041         }
2043         # set incoming message to flag d=deliverd
2044         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
2045         &daemon_log("M DEBUG: $sql_statement", 7);
2046         $res = $messaging_db->update_dbentry($sql_statement);
2047         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
2048     }
2050     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
2051     return;
2054 sub watch_for_delivery_messages {
2055     my ($kernel, $heap) = @_[KERNEL, HEAP];
2057     # select outgoing messages
2058     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
2059     my $res = $messaging_db->exec_statement( $sql_statement );
2060     
2061     # build out msg for each    usr
2062     foreach my $hit (@{$res}) {
2063         my $receiver = @{$hit}[3];
2064         my $msg_id = @{$hit}[0];
2065         my $subject = @{$hit}[1];
2066         my $message = @{$hit}[7];
2068         # resolve usr -> host where usr is logged in
2069         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
2070         my $res = $login_users_db->exec_statement($sql);
2072         # receiver is logged in nowhere
2073         if (not ref(@$res[0]) eq "ARRAY") { next; }    
2075         # receiver ist logged in at a client registered at local server
2076                 my $send_succeed = 0;
2077                 foreach my $hit (@$res) {
2078                                 my $receiver_host = @$hit[0];
2079                 my $delivered2host = 0;
2080                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
2082                                 # Looking for host in know_clients_db 
2083                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
2084                                 my $res = $known_clients_db->exec_statement($sql);
2086                 # Host is known in known_clients_db
2087                 if (ref(@$res[0]) eq "ARRAY") {
2088                     my $receiver_key = @{@{$res}[0]}[2];
2089                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2090                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2091                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
2092                     if ($error == 0 ) {
2093                         $send_succeed++ ;
2094                         $delivered2host++ ;
2095                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
2096                     } else {
2097                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
2098                     }
2099                 }
2100                 
2101                 # Message already send, do not need to do anything more, otherwise ...
2102                 if ($delivered2host) { next;}
2103     
2104                 # ...looking for host in foreign_clients_db
2105                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2106                 $res = $foreign_clients_db->exec_statement($sql);
2107   
2108                                 # Host is known in foreign_clients_db 
2109                                 if (ref(@$res[0]) eq "ARRAY") { 
2110                     my $registration_server = @{@{$res}[0]}[2];
2111                     
2112                     # Fetch encryption key for registration server
2113                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2114                     my $res = $known_server_db->exec_statement($sql);
2115                     if (ref(@$res[0]) eq "ARRAY") { 
2116                         my $registration_server_key = @{@{$res}[0]}[3];
2117                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2118                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2119                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
2120                         if ($error == 0 ) {
2121                             $send_succeed++ ;
2122                             $delivered2host++ ;
2123                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
2124                         } else {
2125                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
2126                         }
2128                     } else {
2129                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2130                                 "registrated at server '$registration_server', ".
2131                                 "but no data available in known_server_db ", 1); 
2132                     }
2133                 }
2134                 
2135                 if (not $delivered2host) {
2136                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2137                 }
2138                 }
2140                 if ($send_succeed) {
2141                                 # set outgoing msg at db to deliverd
2142                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
2143                                 my $res = $messaging_db->exec_statement($sql); 
2144                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2145                 } else {
2146             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
2147         }
2148         }
2150     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
2151     return;
2155 sub watch_for_done_messages {
2156     my ($kernel,$heap) = @_[KERNEL, HEAP];
2158     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
2159     my $res = $messaging_db->exec_statement($sql); 
2161     foreach my $hit (@{$res}) {
2162         my $msg_id = @{$hit}[0];
2164         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
2165         my $res = $messaging_db->exec_statement($sql);
2167         # not all usr msgs have been seen till now
2168         if ( ref(@$res[0]) eq "ARRAY") { next; }
2169         
2170         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
2171         $res = $messaging_db->exec_statement($sql);
2172     
2173     }
2175     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2176     return;
2180 sub watch_for_old_known_clients {
2181     my ($kernel,$heap) = @_[KERNEL, HEAP];
2183     my $sql_statement = "SELECT * FROM $known_clients_tn";
2184     my $res = $known_clients_db->select_dbentry( $sql_statement );
2186     my $cur_time = int(&get_time());
2188     while ( my ($hit_num, $hit) = each %$res) {
2189         my $expired_timestamp = int($hit->{'timestamp'});
2190         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2191         my $dt = DateTime->new( year   => $1,
2192                 month  => $2,
2193                 day    => $3,
2194                 hour   => $4,
2195                 minute => $5,
2196                 second => $6,
2197                 );
2199         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2200         $expired_timestamp = $dt->ymd('').$dt->hms('');
2201         if ($cur_time > $expired_timestamp) {
2202             my $hostname = $hit->{'hostname'};
2203             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2204             my $del_res = $known_clients_db->exec_statement($del_sql);
2206             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2207         }
2209     }
2211     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2215 sub watch_for_next_tasks {
2216     my ($kernel,$heap) = @_[KERNEL, HEAP];
2218     my $sql = "SELECT * FROM $incoming_tn";
2219     my $res = $incoming_db->select_dbentry($sql);
2220     
2221     while ( my ($hit_num, $hit) = each %$res) {
2222         my $headertag = $hit->{'headertag'};
2223         if ($headertag =~ /^answer_(\d+)/) {
2224             # do not start processing, this message is for a still running POE::Wheel
2225             next;
2226         }
2227         my $message_id = $hit->{'id'};
2228         my $session_id = $hit->{'sessionid'};
2229         &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 11);
2231         $kernel->yield('next_task', $hit);
2233         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2234         my $res = $incoming_db->exec_statement($sql);
2235     }
2237     $kernel->delay_set('watch_for_next_tasks', 1); 
2241 sub get_ldap_handle {
2242         my ($session_id) = @_;
2243         my $heap;
2245         if (not defined $session_id ) { $session_id = 0 };
2246         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2248         my ($package, $file, $row, $subroutine, $hasArgs, $wantArray, $evalText, $isRequire) = caller(1);
2249         my $caller_text = "subroutine $subroutine";
2250         if ($subroutine eq "(eval)") {
2251                 $caller_text = "eval block within file '$file' for '$evalText'"; 
2252         }
2253         daemon_log("$session_id DEBUG: new ldap handle for '$caller_text' required!", 42);
2255 get_handle:
2256         my $ldap_handle = Net::LDAP->new( $ldap_uri );
2257         if (not ref $ldap_handle) {
2258                 daemon_log("$session_id ERROR: Connection to LDAP URI '$ldap_uri' failed! Retrying!", 1);
2259                 usleep(100000);
2260                 goto get_handle;
2261         } else {
2262                 daemon_log("$session_id DEBUG: Connection to LDAP URI '$ldap_uri' established.", 42);
2263         }
2265         $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);
2266         return $ldap_handle;
2270 sub release_ldap_handle {
2271         my ($ldap_handle, $session_id) = @_ ;
2272         if (not defined $session_id ) { $session_id = 0 };
2274         if(ref $ldap_handle) {
2275           $ldap_handle->disconnect();
2276   }
2277         &main::daemon_log("$session_id DEBUG: Released a ldap handle!", 42);
2278         return;
2282 sub change_fai_state {
2283         my ($st, $targets, $session_id) = @_;
2284         $session_id = 0 if not defined $session_id;
2285         # Set FAI state to localboot
2286         my %mapActions= (
2287                 reboot    => '',
2288                 update    => 'softupdate',
2289                 localboot => 'localboot',
2290                 reinstall => 'install',
2291                 rescan    => '',
2292                 wake      => '',
2293                 memcheck  => 'memcheck',
2294                 sysinfo   => 'sysinfo',
2295                 install   => 'install',
2296         );
2298         # Return if this is unknown
2299         if (!exists $mapActions{ $st }){
2300                 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2301                 return;
2302         }
2304         my $state= $mapActions{ $st };
2306         # Build search filter for hosts
2307         my $search= "(&(objectClass=GOhard)";
2308         foreach (@{$targets}){
2309                 $search.= "(macAddress=$_)";
2310         }
2311         $search.= ")";
2313         # If there's any host inside of the search string, procress them
2314         if (!($search =~ /macAddress/)){
2315                 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2316                 return;
2317         }
2319         my $ldap_handle = &get_ldap_handle($session_id);
2320         # Perform search for Unit Tag
2321         my $mesg = $ldap_handle->search(
2322                 base   => $ldap_base,
2323                 scope  => 'sub',
2324                 attrs  => ['dn', 'FAIstate', 'objectClass'],
2325                 filter => "$search"
2326         );
2328         if ($mesg->count) {
2329                 my @entries = $mesg->entries;
2330                 if (0 == @entries) {
2331                         daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2332                 }
2334                 foreach my $entry (@entries) {
2335                         # Only modify entry if it is not set to '$state'
2336                         if ($entry->get_value("FAIstate") ne "$state"){
2337                                 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2338                                 my $result;
2339                                 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2340                                 if (exists $tmp{'FAIobject'}){
2341                                         if ($state eq ''){
2342                                                 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2343                                         } else {
2344                                                 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2345                                         }
2346                                 } elsif ($state ne ''){
2347                                         $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2348                                 }
2350                                 # Errors?
2351                                 if ($result->code){
2352                                         daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2353                                 }
2354                         } else {
2355                                 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 42); 
2356                         }  
2357                 }
2358         } else {
2359                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2360         }
2361         &release_ldap_handle($ldap_handle, $session_id);                  
2363         return;
2367 sub change_goto_state {
2368     my ($st, $targets, $session_id) = @_;
2369     $session_id = 0  if not defined $session_id;
2371     # Switch on or off?
2372     my $state= $st eq 'active' ? 'active': 'locked';
2374     my $ldap_handle = &get_ldap_handle($session_id);
2375     if( defined($ldap_handle) ) {
2377       # Build search filter for hosts
2378       my $search= "(&(objectClass=GOhard)";
2379       foreach (@{$targets}){
2380         $search.= "(macAddress=$_)";
2381       }
2382       $search.= ")";
2384       # If there's any host inside of the search string, procress them
2385       if (!($search =~ /macAddress/)){
2386               &release_ldap_handle($ldap_handle);
2387         return;
2388       }
2390       # Perform search for Unit Tag
2391       my $mesg = $ldap_handle->search(
2392           base   => $ldap_base,
2393           scope  => 'sub',
2394           attrs  => ['dn', 'gotoMode'],
2395           filter => "$search"
2396           );
2398       if ($mesg->count) {
2399         my @entries = $mesg->entries;
2400         foreach my $entry (@entries) {
2402           # Only modify entry if it is not set to '$state'
2403           if ($entry->get_value("gotoMode") ne $state){
2405             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2406             my $result;
2407             $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2409             # Errors?
2410             if ($result->code){
2411               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2412             }
2414           }
2415         }
2416       } else {
2417                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2418           }
2420     }
2421         &release_ldap_handle($ldap_handle, $session_id);
2422         return;
2426 sub run_recreate_packages_db {
2427     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2428     my $session_id = $session->ID;
2429         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2430         $kernel->yield('create_fai_release_db', $fai_release_tn);
2431         $kernel->yield('create_fai_server_db', $fai_server_tn);
2432         return;
2436 sub run_create_fai_server_db {
2437     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2438     my $session_id = $session->ID;
2439     my $task = POE::Wheel::Run->new(
2440             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2441             StdoutEvent  => "session_run_result",
2442             StderrEvent  => "session_run_debug",
2443             CloseEvent   => "session_run_done",
2444             );
2446     $heap->{task}->{ $task->ID } = $task;
2447     return;
2451 sub create_fai_server_db {
2452         my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2453         my $result;
2455         if (not defined $session_id) { $session_id = 0; }
2456         my $ldap_handle = &get_ldap_handle($session_id);
2457         if(defined($ldap_handle)) {
2458                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2459                 my $mesg= $ldap_handle->search(
2460                         base   => $ldap_base,
2461                         scope  => 'sub',
2462                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2463                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2464                 );
2465                 if($mesg->{'resultCode'} == 0 &&
2466                         $mesg->count != 0) {
2467                         foreach my $entry (@{$mesg->{entries}}) {
2468                                 if($entry->exists('FAIrepository')) {
2469                                         # Add an entry for each Repository configured for server
2470                                         foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2471                                                 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2472                                                 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2473                                                 $result= $fai_server_db->add_dbentry( { 
2474                                                                 table => $table_name,
2475                                                                 primkey => ['server', 'fai_release', 'tag'],
2476                                                                 server => $tmp_url,
2477                                                                 fai_release => $tmp_release,
2478                                                                 sections => $tmp_sections,
2479                                                                 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2480                                                         } );
2481                                         }
2482                                 }
2483                         }
2484                 }
2485                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2486                 &release_ldap_handle($ldap_handle);
2488                 # TODO: Find a way to post the 'create_packages_list_db' event
2489                 if(not defined($dont_create_packages_list)) {
2490                         &create_packages_list_db(undef, $session_id);
2491                 }
2492         }       
2494         return $result;
2498 sub run_create_fai_release_db {
2499         my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2500         my $session_id = $session->ID;
2501         my $task = POE::Wheel::Run->new(
2502                 Program => sub { &create_fai_release_db($table_name, $session_id) },
2503                 StdoutEvent  => "session_run_result",
2504                 StderrEvent  => "session_run_debug",
2505                 CloseEvent   => "session_run_done",
2506         );
2508         $heap->{task}->{ $task->ID } = $task;
2509         return;
2513 sub create_fai_release_db {
2514         my ($table_name, $session_id) = @_;
2515         my $result;
2517         # used for logging
2518         if (not defined $session_id) { $session_id = 0; }
2520         my $ldap_handle = &get_ldap_handle($session_id);
2521         if(defined($ldap_handle)) {
2522                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2523                 my $mesg= $ldap_handle->search(
2524                         base   => $ldap_base,
2525                         scope  => 'sub',
2526                         attrs  => [],
2527                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2528                 );
2529                 if(($mesg->code == 0) && ($mesg->count != 0))
2530                 {
2531                         daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,138);
2533                         # Walk through all possible FAI container ou's
2534                         my @sql_list;
2535                         my $timestamp= &get_time();
2536                         foreach my $ou (@{$mesg->{entries}}) {
2537                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2538                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2539                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2540                                         if(@tmp_array) {
2541                                                 foreach my $entry (@tmp_array) {
2542                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2543                                                                 my $sql= 
2544                                                                 "INSERT INTO $table_name "
2545                                                                 ."(timestamp, fai_release, class, type, state) VALUES ("
2546                                                                 .$timestamp.","
2547                                                                 ."'".$entry->{'release'}."',"
2548                                                                 ."'".$entry->{'class'}."',"
2549                                                                 ."'".$entry->{'type'}."',"
2550                                                                 ."'".$entry->{'state'}."')";
2551                                                                 push @sql_list, $sql;
2552                                                         }
2553                                                 }
2554                                         }
2555                                 }
2556                         }
2558                         daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",138);
2559             &release_ldap_handle($ldap_handle);
2560                         if(@sql_list) {
2561                                 unshift @sql_list, "VACUUM";
2562                                 unshift @sql_list, "DELETE FROM $table_name";
2563                                 $fai_release_db->exec_statementlist(\@sql_list);
2564                         }
2565                         daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",138);
2566                 } else {
2567                         daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2568                 }
2569                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2570         }
2571         return $result;
2574 sub get_fai_types {
2575         my $tmp_classes = shift || return undef;
2576         my @result;
2578         foreach my $type(keys %{$tmp_classes}) {
2579                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2580                         my $entry = {
2581                                 type => $type,
2582                                 state => $tmp_classes->{$type}[0],
2583                         };
2584                         push @result, $entry;
2585                 }
2586         }
2588         return @result;
2591 sub get_fai_state {
2592         my $result = "";
2593         my $tmp_classes = shift || return $result;
2595         foreach my $type(keys %{$tmp_classes}) {
2596                 if(defined($tmp_classes->{$type}[0])) {
2597                         $result = $tmp_classes->{$type}[0];
2598                         
2599                 # State is equal for all types in class
2600                         last;
2601                 }
2602         }
2604         return $result;
2607 sub resolve_fai_classes {
2608         my ($fai_base, $ldap_handle, $session_id) = @_;
2609         if (not defined $session_id) { $session_id = 0; }
2610         my $result;
2611         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2612         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2613         my $fai_classes;
2615         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base", 138);
2616         my $mesg= $ldap_handle->search(
2617                 base   => $fai_base,
2618                 scope  => 'sub',
2619                 attrs  => ['cn','objectClass','FAIstate'],
2620                 filter => $fai_filter,
2621         );
2622         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries", 138);
2624         if($mesg->{'resultCode'} == 0 &&
2625                 $mesg->count != 0) {
2626                 foreach my $entry (@{$mesg->{entries}}) {
2627                         if($entry->exists('cn')) {
2628                                 my $tmp_dn= $entry->dn();
2629                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2630                                         - length($fai_base) - 1 );
2632                                 # Skip classname and ou dn parts for class
2633                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2635                                 # Skip classes without releases
2636                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2637                                         next;
2638                                 }
2640                                 my $tmp_cn= $entry->get_value('cn');
2641                                 my $tmp_state= $entry->get_value('FAIstate');
2643                                 my $tmp_type;
2644                                 # Get FAI type
2645                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2646                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2647                                                 $tmp_type= $oclass;
2648                                                 last;
2649                                         }
2650                                 }
2652                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2653                                         # A Subrelease
2654                                         my @sub_releases = split(/,/, $tmp_release);
2656                                         # Walk through subreleases and build hash tree
2657                                         my $hash;
2658                                         while(my $tmp_sub_release = pop @sub_releases) {
2659                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2660                                         }
2661                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2662                                 } else {
2663                                         # A branch, no subrelease
2664                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2665                                 }
2666                         } elsif (!$entry->exists('cn')) {
2667                                 my $tmp_dn= $entry->dn();
2668                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2669                                         - length($fai_base) - 1 );
2670                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2672                                 # Skip classes without releases
2673                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2674                                         next;
2675                                 }
2677                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2678                                         # A Subrelease
2679                                         my @sub_releases= split(/,/, $tmp_release);
2681                                         # Walk through subreleases and build hash tree
2682                                         my $hash;
2683                                         while(my $tmp_sub_release = pop @sub_releases) {
2684                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2685                                         }
2686                                         # Remove the last two characters
2687                                         chop($hash);
2688                                         chop($hash);
2690                                         eval('$fai_classes->'.$hash.'= {}');
2691                                 } else {
2692                                         # A branch, no subrelease
2693                                         if(!exists($fai_classes->{$tmp_release})) {
2694                                                 $fai_classes->{$tmp_release} = {};
2695                                         }
2696                                 }
2697                         }
2698                 }
2700                 # The hash is complete, now we can honor the copy-on-write based missing entries
2701                 foreach my $release (keys %$fai_classes) {
2702                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2703                 }
2704         }
2705         return $result;
2708 sub apply_fai_inheritance {
2709        my $fai_classes = shift || return {};
2710        my $tmp_classes;
2712        # Get the classes from the branch
2713        foreach my $class (keys %{$fai_classes}) {
2714                # Skip subreleases
2715                if($class =~ /^ou=.*$/) {
2716                        next;
2717                } else {
2718                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2719                }
2720        }
2722        # Apply to each subrelease
2723        foreach my $subrelease (keys %{$fai_classes}) {
2724                if($subrelease =~ /ou=/) {
2725                        foreach my $tmp_class (keys %{$tmp_classes}) {
2726                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2727                                        $fai_classes->{$subrelease}->{$tmp_class} =
2728                                        deep_copy($tmp_classes->{$tmp_class});
2729                                } else {
2730                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2731                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2732                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2733                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2734                                                }
2735                                        }
2736                                }
2737                        }
2738                }
2739        }
2741        # Find subreleases in deeper levels
2742        foreach my $subrelease (keys %{$fai_classes}) {
2743                if($subrelease =~ /ou=/) {
2744                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2745                                if($subsubrelease =~ /ou=/) {
2746                                        apply_fai_inheritance($fai_classes->{$subrelease});
2747                                }
2748                        }
2749                }
2750        }
2752        return $fai_classes;
2755 sub get_fai_release_entries {
2756         my $tmp_classes = shift || return;
2757         my $parent = shift || "";
2758         my @result = shift || ();
2760         foreach my $entry (keys %{$tmp_classes}) {
2761                 if(defined($entry)) {
2762                         if($entry =~ /^ou=.*$/) {
2763                                 my $release_name = $entry;
2764                                 $release_name =~ s/ou=//g;
2765                                 if(length($parent)>0) {
2766                                         $release_name = $parent."/".$release_name;
2767                                 }
2768                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2769                                 foreach my $bufentry(@bufentries) {
2770                                         push @result, $bufentry;
2771                                 }
2772                         } else {
2773                                 my @types = get_fai_types($tmp_classes->{$entry});
2774                                 foreach my $type (@types) {
2775                                         push @result, 
2776                                         {
2777                                                 'class' => $entry,
2778                                                 'type' => $type->{'type'},
2779                                                 'release' => $parent,
2780                                                 'state' => $type->{'state'},
2781                                         };
2782                                 }
2783                         }
2784                 }
2785         }
2787         return @result;
2790 sub deep_copy {
2791         my $this = shift;
2792         if (not ref $this) {
2793                 $this;
2794         } elsif (ref $this eq "ARRAY") {
2795                 [map deep_copy($_), @$this];
2796         } elsif (ref $this eq "HASH") {
2797                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2798         } else { die "what type is $_?" }
2802 sub session_run_result {
2803     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2804     $kernel->sig(CHLD => "child_reap");
2807 sub session_run_debug {
2808     my $result = $_[ARG0];
2809     print STDERR "$result\n";
2812 sub session_run_done {
2813     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2814     delete $heap->{task}->{$task_id};
2815         if (exists $heap->{ldap_handle}->{$task_id}) {
2816                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
2817         }
2818         delete $heap->{ldap_handle}->{$task_id};
2822 sub create_sources_list {
2823         my $session_id = shift || 0;
2824         my $result="/tmp/gosa_si_tmp_sources_list";
2826         # Remove old file
2827         if(stat($result)) {
2828                 unlink($result);
2829                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2830         }
2832         my $fh;
2833         open($fh, ">$result");
2834         if (not defined $fh) {
2835                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2836                 return undef;
2837         }
2838         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2839                 my $ldap_handle = &get_ldap_handle($session_id);
2840                 my $mesg=$ldap_handle->search(
2841                         base    => $main::ldap_server_dn,
2842                         scope   => 'base',
2843                         attrs   => 'FAIrepository',
2844                         filter  => 'objectClass=FAIrepositoryServer'
2845                 );
2846                 if($mesg->count) {
2847                         foreach my $entry(@{$mesg->{'entries'}}) {
2848                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2849                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2850                                         my $line = "deb $server $release";
2851                                         $sections =~ s/,/ /g;
2852                                         $line.= " $sections";
2853                                         print $fh $line."\n";
2854                                 }
2855                         }
2856                 }
2857                 &release_ldap_handle($ldap_handle);
2858         } else {
2859                 if (defined $main::ldap_server_dn){
2860                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2861                 } else {
2862                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2863                 }
2864         }
2865         close($fh);
2867         return $result;
2871 sub run_create_packages_list_db {
2872     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2873         my $session_id = $session->ID;
2874         my $task = POE::Wheel::Run->new(
2875                                         Priority => +20,
2876                                         Program => sub {&create_packages_list_db(undef, $session_id)},
2877                                         StdoutEvent  => "session_run_result",
2878                                         StderrEvent  => "session_run_debug",
2879                                         CloseEvent   => "session_run_done",
2880                                         );
2881         $heap->{task}->{ $task->ID } = $task;
2885 sub create_packages_list_db {
2886         my ($sources_file, $session_id) = @_;
2887         
2888         # it should not be possible to trigger a recreation of packages_list_db
2889         # while packages_list_db is under construction, so set flag packages_list_under_construction
2890         # which is tested befor recreation can be started
2891         if (-r $packages_list_under_construction) {
2892                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2893                 return;
2894         } else {
2895                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2896                 # set packages_list_under_construction to true
2897                 system("touch $packages_list_under_construction");
2898                 @packages_list_statements=();
2899         }
2901         if (not defined $session_id) { $session_id = 0; }
2903         if (not defined $sources_file) { 
2904                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2905                 $sources_file = &create_sources_list($session_id);
2906         }
2908         if (not defined $sources_file) {
2909                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2910                 unlink($packages_list_under_construction);
2911                 return;
2912         }
2914         my $line;
2916         open(CONFIG, "<$sources_file") or do {
2917                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2918                 unlink($packages_list_under_construction);
2919                 return;
2920         };
2922         # Read lines
2923         while ($line = <CONFIG>){
2924                 # Unify
2925                 chop($line);
2926                 $line =~ s/^\s+//;
2927                 $line =~ s/^\s+/ /;
2929                 # Strip comments
2930                 $line =~ s/#.*$//g;
2932                 # Skip empty lines
2933                 if ($line =~ /^\s*$/){
2934                         next;
2935                 }
2937                 # Interpret deb line
2938                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2939                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2940                         my $section;
2941                         foreach $section (split(' ', $sections)){
2942                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2943                         }
2944                 }
2945         }
2947         close (CONFIG);
2949         if(keys(%repo_dirs)) {
2950                 find(\&cleanup_and_extract, keys( %repo_dirs ));
2951                 &main::strip_packages_list_statements();
2952                 $packages_list_db->exec_statementlist(\@packages_list_statements);
2953         }
2954         unlink($packages_list_under_construction);
2955         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2956         return;
2959 # This function should do some intensive task to minimize the db-traffic
2960 sub strip_packages_list_statements {
2961         my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2962         my @new_statement_list=();
2963         my $hash;
2964         my $insert_hash;
2965         my $update_hash;
2966         my $delete_hash;
2967         my $known_packages_hash;
2968         my $local_timestamp=get_time();
2970         foreach my $existing_entry (@existing_entries) {
2971                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2972         }
2974         foreach my $statement (@packages_list_statements) {
2975                 if($statement =~ /^INSERT/i) {
2976                         # Assign the values from the insert statement
2977                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2978                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2979                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2980                                 # If section or description has changed, update the DB
2981                                 if( 
2982                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2983                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2984                                 ) {
2985                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2986                                 } else {
2987                                         # package is already present in database. cache this knowledge for later use
2988                                         @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2989                                 }
2990                         } else {
2991                                 # Insert a non-existing entry to db
2992                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2993                         }
2994                 } elsif ($statement =~ /^UPDATE/i) {
2995                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2996                         /^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;
2997                         foreach my $distribution (keys %{$hash}) {
2998                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2999                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
3000                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
3001                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
3002                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
3003                                                 my $section;
3004                                                 my $description;
3005                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
3006                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
3007                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
3008                                                 }
3009                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3010                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
3011                                                 }
3012                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3013                                         }
3014                                 }
3015                         }
3016                 }
3017         }
3019         # Check for orphaned entries
3020         foreach my $existing_entry (@existing_entries) {
3021                 my $distribution= @{$existing_entry}[0];
3022                 my $package= @{$existing_entry}[1];
3023                 my $version= @{$existing_entry}[2];
3024                 my $section= @{$existing_entry}[3];
3026                 if(
3027                         exists($insert_hash->{$distribution}->{$package}->{$version}) ||
3028                         exists($update_hash->{$distribution}->{$package}->{$version}) ||
3029                         exists($known_packages_hash->{$distribution}->{$package}->{$version})
3030                 ) {
3031                         next;
3032                 } else {
3033                         # Insert entry to delete hash
3034                         @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
3035                 }
3036         }
3038         # unroll the insert hash
3039         foreach my $distribution (keys %{$insert_hash}) {
3040                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
3041                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
3042                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
3043                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
3044                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
3045                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
3046                                 ."'$local_timestamp')";
3047                         }
3048                 }
3049         }
3051         # unroll the update hash
3052         foreach my $distribution (keys %{$update_hash}) {
3053                 foreach my $package (keys %{$update_hash->{$distribution}}) {
3054                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
3055                                 my $set = "";
3056                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
3057                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
3058                                 }
3059                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3060                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
3061                                 }
3062                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
3063                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
3064                                 }
3065                                 if(defined($set) and length($set) > 0) {
3066                                         $set .= "timestamp = '$local_timestamp'";
3067                                 } else {
3068                                         next;
3069                                 }
3070                                 push @new_statement_list, 
3071                                 "UPDATE $main::packages_list_tn SET $set WHERE"
3072                                 ." distribution = '$distribution'"
3073                                 ." AND package = '$package'"
3074                                 ." AND version = '$version'";
3075                         }
3076                 }
3077         }
3078         
3079         # unroll the delete hash
3080         foreach my $distribution (keys %{$delete_hash}) {
3081                 foreach my $package (keys %{$delete_hash->{$distribution}}) {
3082                         foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
3083                                 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
3084                                 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
3085                         }
3086                 }
3087         }
3089         unshift(@new_statement_list, "VACUUM");
3091         @packages_list_statements = @new_statement_list;
3095 sub parse_package_info {
3096     my ($baseurl, $dist, $section, $session_id)= @_;
3097     my ($package);
3098     if (not defined $session_id) { $session_id = 0; }
3099     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3100     $repo_dirs{ "${repo_path}/pool" } = 1;
3102     foreach $package ("Packages.gz"){
3103         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
3104         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3105         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3106     }
3107     
3111 sub get_package {
3112     my ($url, $dest, $session_id)= @_;
3113     if (not defined $session_id) { $session_id = 0; }
3115     my $tpath = dirname($dest);
3116     -d "$tpath" || mkpath "$tpath";
3118     # This is ugly, but I've no time to take a look at "how it works in perl"
3119     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3120         system("gunzip -cd '$dest' > '$dest.in'");
3121         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 7);
3122         unlink($dest);
3123         daemon_log("$session_id DEBUG: delete file '$dest'", 7); 
3124     } else {
3125         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3126     }
3127     return 0;
3131 sub parse_package {
3132     my ($path, $dist, $srv_path, $session_id)= @_;
3133     if (not defined $session_id) { $session_id = 0;}
3134     my ($package, $version, $section, $description);
3135     my $PACKAGES;
3136     my $timestamp = &get_time();
3138     if(not stat("$path.in")) {
3139         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3140         return;
3141     }
3143     open($PACKAGES, "<$path.in");
3144     if(not defined($PACKAGES)) {
3145         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
3146         return;
3147     }
3149     # Read lines
3150     while (<$PACKAGES>){
3151         my $line = $_;
3152         # Unify
3153         chop($line);
3155         # Use empty lines as a trigger
3156         if ($line =~ /^\s*$/){
3157             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3158             push(@packages_list_statements, $sql);
3159             $package = "none";
3160             $version = "none";
3161             $section = "none";
3162             $description = "none"; 
3163             next;
3164         }
3166         # Trigger for package name
3167         if ($line =~ /^Package:\s/){
3168             ($package)= ($line =~ /^Package: (.*)$/);
3169             next;
3170         }
3172         # Trigger for version
3173         if ($line =~ /^Version:\s/){
3174             ($version)= ($line =~ /^Version: (.*)$/);
3175             next;
3176         }
3178         # Trigger for description
3179         if ($line =~ /^Description:\s/){
3180             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3181             next;
3182         }
3184         # Trigger for section
3185         if ($line =~ /^Section:\s/){
3186             ($section)= ($line =~ /^Section: (.*)$/);
3187             next;
3188         }
3190         # Trigger for filename
3191         if ($line =~ /^Filename:\s/){
3192             my ($filename) = ($line =~ /^Filename: (.*)$/);
3193             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3194             next;
3195         }
3196     }
3198     close( $PACKAGES );
3199     unlink( "$path.in" );
3203 sub store_fileinfo {
3204     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3206     my %fileinfo = (
3207         'package' => $package,
3208         'dist' => $dist,
3209         'version' => $vers,
3210     );
3212     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3216 sub cleanup_and_extract {
3217         my $fileinfo = $repo_files{ $File::Find::name };
3219         if( defined $fileinfo ) {
3220                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3221                 my $sql;
3222                 my $package = $fileinfo->{ 'package' };
3223                 my $newver = $fileinfo->{ 'version' };
3225                 mkpath($dir);
3226                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3228                 if( -f "$dir/DEBIAN/templates" ) {
3230                         daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3232                         my $tmpl= ""; {
3233                                 local $/=undef;
3234                                 open FILE, "$dir/DEBIAN/templates";
3235                                 $tmpl = &encode_base64(<FILE>);
3236                                 close FILE;
3237                         }
3238                         rmtree("$dir/DEBIAN/templates");
3240                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3241                         push @packages_list_statements, $sql;
3242                 }
3243         }
3245         return;
3249 sub prepare_server_registration 
3251         # Add foreign server from cfg file
3252         my @foreign_server_list;
3253         if ($foreign_server_string ne "") {
3254             my @cfg_foreign_server_list = split(",", $foreign_server_string);
3255             foreach my $foreign_server (@cfg_foreign_server_list) {
3256                 push(@foreign_server_list, $foreign_server);
3257             }
3258         
3259             daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3260         }
3261         
3262         # Perform a DNS lookup for server registration if flag is true
3263         if ($dns_lookup eq "true") {
3264             # Add foreign server from dns
3265             my @tmp_servers;
3266             if (not $server_domain) {
3267                 # Try our DNS Searchlist
3268                 for my $domain(get_dns_domains()) {
3269                     chomp($domain);
3270                     my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3271                     if(@$tmp_domains) {
3272                         for my $tmp_server(@$tmp_domains) {
3273                             push @tmp_servers, $tmp_server;
3274                         }
3275                     }
3276                 }
3277                 if(@tmp_servers && length(@tmp_servers)==0) {
3278                     daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3279                 }
3280             } else {
3281                 @tmp_servers = &get_server_addresses($server_domain);
3282                 if( 0 == @tmp_servers ) {
3283                     daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3284                 }
3285             }
3286         
3287             daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3288         
3289             foreach my $server (@tmp_servers) { 
3290                 unshift(@foreign_server_list, $server); 
3291             }
3292         } else {
3293             daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3294         }
3295         
3296         # eliminate duplicate entries
3297         @foreign_server_list = &del_doubles(@foreign_server_list);
3298         my $all_foreign_server = join(", ", @foreign_server_list);
3299         daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3300         
3301         # add all found foreign servers to known_server
3302         my $cur_timestamp = &get_time();
3303         foreach my $foreign_server (@foreign_server_list) {
3304         
3305                 # do not add myself to known_server_db
3306                 if (&is_local($foreign_server)) { next; }
3307                 ######################################
3308         
3309             my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3310                     primkey=>['hostname'],
3311                     hostname=>$foreign_server,
3312                     macaddress=>"",
3313                     status=>'not_yet_registered',
3314                     hostkey=>"none",
3315                     loaded_modules => "none", 
3316                     timestamp=>$cur_timestamp,
3317                                 update_time=>'19700101000000',
3318                     } );
3319         }
3322 sub register_at_foreign_servers {   
3323     my ($kernel) = $_[KERNEL];
3325         # Update status and update-time of all si-server with expired update_time and 
3326         # block them for race conditional registration processes of other si-servers.
3327         my $act_time = &get_time();
3328         my $block_statement = "UPDATE $known_server_tn SET status='new_server',update_time='19700101000000' WHERE (CAST(update_time AS UNSIGNED))<$act_time ";
3329         my $block_res = $known_server_db->exec_statement($block_statement);
3331         # Fetch all si-server from db where update_time is younger than act_time
3332         my $fetch_statement = "SELECT * FROM $known_server_tn WHERE update_time='19700101000000'"; 
3333         my $fetch_res = $known_server_db->exec_statement($fetch_statement);
3335     # Detect already connected clients. Will be added to registration msg later. 
3336     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3337     my $client_res = $known_clients_db->exec_statement($client_sql);
3339         # Send registration messag to all fetched si-server
3340     foreach my $hit (@$fetch_res) {
3341         my $hostname = @$hit[0];
3342         my $hostkey = &create_passwd;
3344         # Add already connected clients to registration message 
3345         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3346         &add_content2xml_hash($myhash, 'key', $hostkey);
3347         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3349         # Add locally loaded gosa-si modules to registration message
3350         my $loaded_modules = {};
3351         while (my ($package, $pck_info) = each %$known_modules) {
3352                         next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3353                         foreach my $act_module (keys(%{@$pck_info[2]})) {
3354                                 $loaded_modules->{$act_module} = ""; 
3355                         }
3356                 }
3357         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3359         # Add macaddress to registration message
3360         my ($host_ip, $host_port) = split(/:/, $hostname);
3361         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3362         my $network_interface= &get_interface_for_ip($local_ip);
3363         my $host_mac = &get_mac_for_interface($network_interface);
3364         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3365         
3366         # Build registration message and send it
3367         my $foreign_server_msg = &create_xml_string($myhash);
3368         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3369     }
3372         # After n sec perform a check of all server registration processes
3373     $kernel->delay_set("control_server_registration", 2); 
3375         return;
3379 sub control_server_registration {
3380         my ($kernel) = $_[KERNEL];
3381         
3382         # Check if all registration processes succeed or not
3383         my $select_statement = "SELECT * FROM $known_server_tn WHERE status='new_server'"; 
3384         my $select_res = $known_server_db->exec_statement($select_statement);
3386         # If at least one registration process failed, maybe in case of a race condition
3387         # with a foreign registration process
3388         if (@$select_res > 0) 
3389         {
3390                 # Release block statement 'new_server' to make the server accessible
3391                 # for foreign registration processes
3392                 my $update_statement = "UPDATE $known_server_tn SET status='waiting' WHERE status='new_server'";        
3393                 my $update_res = $known_server_db->exec_statement($update_statement);
3395                 # Set a random delay to avoid the registration race condition
3396                 my $new_foreign_servers_register_delay = int(rand(4))+1;
3397                 $kernel->delay_set("register_at_foreign_servers", $new_foreign_servers_register_delay);
3398         }
3399         # If all registration processes succeed
3400         else
3401         {
3402                 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3403         }
3405         return;
3409 #==== MAIN = main ==============================================================
3410 #  parse commandline options
3411 Getopt::Long::Configure( "bundling" );
3412 GetOptions("h|help" => \&usage,
3413         "c|config=s" => \$cfg_file,
3414         "f|foreground" => \$foreground,
3415         "v|verbose+" => \$verbose,
3416         "no-arp+" => \$no_arp,
3417                 "d=s" => \$debug_parts,
3418            ) or &usage("", 1); 
3420 #  read and set config parameters
3421 &check_cmdline_param ;
3422 &read_configfile($cfg_file, %cfg_defaults);
3423 &check_pid;
3425 $SIG{CHLD} = 'IGNORE';
3427 # forward error messages to logfile
3428 if( ! $foreground ) {
3429   open( STDIN,  '+>/dev/null' );
3430   open( STDOUT, '+>&STDIN'    );
3431   open( STDERR, '+>&STDIN'    );
3434 # Just fork, if we are not in foreground mode
3435 if( ! $foreground ) { 
3436     chdir '/'                 or die "Can't chdir to /: $!";
3437     $pid = fork;
3438     setsid                    or die "Can't start a new session: $!";
3439     umask 0;
3440 } else { 
3441     $pid = $$; 
3444 # Do something useful - put our PID into the pid_file
3445 if( 0 != $pid ) {
3446     open( LOCK_FILE, ">$pid_file" );
3447     print LOCK_FILE "$pid\n";
3448     close( LOCK_FILE );
3449     if( !$foreground ) { 
3450         exit( 0 ) 
3451     };
3454 # parse head url and revision from svn
3455 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3456 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3457 $server_headURL = defined $1 ? $1 : 'unknown' ;
3458 $server_revision = defined $2 ? $2 : 'unknown' ;
3459 if ($server_headURL =~ /\/tag\// || 
3460         $server_headURL =~ /\/branches\// ) {
3461     $server_status = "stable"; 
3462 } else {
3463     $server_status = "developmental" ;
3465 # Prepare log file and set permissions
3466 $root_uid = getpwnam('root');
3467 $adm_gid = getgrnam('adm');
3468 open(FH, ">>$log_file");
3469 close FH;
3470 chmod(0440, $log_file);
3471 chown($root_uid, $adm_gid, $log_file);
3472 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3474 daemon_log(" ", 1);
3475 daemon_log("$0 started!", 1);
3476 daemon_log("status: $server_status", 1);
3477 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3479 # Buildup data bases
3481     no strict "refs";
3483     if ($db_module eq "DBmysql") {
3484         # connect to incoming_db
3485         $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3487         # connect to gosa-si job queue
3488         $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3490         # connect to known_clients_db
3491         $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3493         # connect to foreign_clients_db
3494         $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3496         # connect to known_server_db
3497         $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3499         # connect to login_usr_db
3500         $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3502         # connect to fai_server_db 
3503         $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3505         # connect to fai_release_db
3506         $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3508         # connect to packages_list_db
3509         $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3511         # connect to messaging_db
3512         $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3514     } elsif ($db_module eq "DBsqlite") {
3515         # connect to incoming_db
3516         unlink($incoming_file_name);
3517         $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3518         chmod(0640, $incoming_file_name);
3519         chown($root_uid, $adm_gid, $incoming_file_name);
3520         
3521         # connect to gosa-si job queue
3522         $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3523         chmod(0640, $job_queue_file_name);
3524         chown($root_uid, $adm_gid, $job_queue_file_name);
3525         
3526         # connect to known_clients_db
3527         #unlink($known_clients_file_name);
3528         $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3529         chmod(0640, $known_clients_file_name);
3530         chown($root_uid, $adm_gid, $known_clients_file_name);
3531         
3532         # connect to foreign_clients_db
3533         #unlink($foreign_clients_file_name);
3534         $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3535         chmod(0640, $foreign_clients_file_name);
3536         chown($root_uid, $adm_gid, $foreign_clients_file_name);
3537         
3538         # connect to known_server_db
3539         unlink($known_server_file_name);   # do not delete, gosa-si-server should be forced to check config file and dns at each start
3540         $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3541         chmod(0640, $known_server_file_name);
3542         chown($root_uid, $adm_gid, $known_server_file_name);
3543         
3544         # connect to login_usr_db
3545         #unlink($login_users_file_name);
3546         $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3547         chmod(0640, $login_users_file_name);
3548         chown($root_uid, $adm_gid, $login_users_file_name);
3549         
3550         # connect to fai_server_db
3551         unlink($fai_server_file_name);
3552         $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3553         chmod(0640, $fai_server_file_name);
3554         chown($root_uid, $adm_gid, $fai_server_file_name);
3555         
3556         # connect to fai_release_db
3557         unlink($fai_release_file_name);
3558         $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3559         chmod(0640, $fai_release_file_name);
3560         chown($root_uid, $adm_gid, $fai_release_file_name);
3561         
3562         # connect to packages_list_db
3563         unlink($packages_list_under_construction);
3564         $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3565         chmod(0640, $packages_list_file_name);
3566         chown($root_uid, $adm_gid, $packages_list_file_name);
3567         
3568         # connect to messaging_db
3569         #unlink($messaging_file_name);
3570         $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3571         chmod(0640, $messaging_file_name);
3572         chown($root_uid, $adm_gid, $messaging_file_name);
3573     }
3576 # Creating tables
3577 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3578 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3579 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3580 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3581 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3582 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3583 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3584 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3585 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3586 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3588 # create xml object used for en/decrypting
3589 $xml = new XML::Simple();
3591 # Import all modules
3592 &import_modules;
3594 # Check wether all modules are gosa-si valid passwd check
3595 &password_check;
3597 # Check DNS and config file for server registration
3598 if ($serverPackages_enabled eq "true") { &prepare_server_registration; }
3600 # Create functions hash
3601 while (my ($module, @mod_info) = each %$known_modules) 
3603         while (my ($plugin, $functions) = each %{$mod_info[0][2]})
3604         {
3605                 while (my ($function, $nothing) = each %$functions )
3606                 {
3607                         $known_functions->{$function} = $nothing;
3608                 }
3609         }
3612 # Prepare for using Opsi 
3613 if ($opsi_enabled eq "true") {
3614     use JSON::RPC::Client;
3615     use XML::Quote qw(:all);
3616     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3617     $opsi_client = new JSON::RPC::Client;
3621 POE::Component::Server::TCP->new(
3622         Alias => "TCP_SERVER",
3623         Port => $server_port,
3624         ClientInput => sub {
3625                 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3626         my $session_id = $session->ID;
3627                 if ($input =~ /;([\d\.]+):([\d]+)$/) 
3628                 {
3629                         # Messages from other servers should be blocked if config option is set
3630                         if (($2 eq $server_port) && ($serverPackages_enabled eq "false"))
3631                         {
3632                                 return;
3633                         }
3634                         &daemon_log("$session_id DEBUG: incoming message from '$1:$2'", 11);
3635                 }
3636                 else
3637                 {
3638                         my $remote_ip = $heap->{'remote_ip'};
3639                         &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 11);
3640                 }
3641                 push(@msgs_to_decrypt, $input);
3642                 $kernel->yield("msg_to_decrypt");
3643         },
3644         InlineStates => {
3645                 msg_to_decrypt => \&msg_to_decrypt,
3646                 next_task => \&next_task,
3647                 task_result => \&handle_task_result,
3648                 task_done   => \&handle_task_done,
3649                 task_debug  => \&handle_task_debug,
3650                 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3651         }
3652 );
3654 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3656 # create session for repeatedly checking the job queue for jobs
3657 POE::Session->create(
3658         inline_states => {
3659                 _start => \&session_start,
3660         register_at_foreign_servers => \&register_at_foreign_servers,
3661                 control_server_registration => \&control_server_registration,
3662         sig_handler => \&sig_handler,
3663         next_task => \&next_task,
3664         task_result => \&handle_task_result,
3665         task_done   => \&handle_task_done,
3666         task_debug  => \&handle_task_debug,
3667         watch_for_next_tasks => \&watch_for_next_tasks,
3668         watch_for_new_messages => \&watch_for_new_messages,
3669         watch_for_delivery_messages => \&watch_for_delivery_messages,
3670         watch_for_done_messages => \&watch_for_done_messages,
3671                 watch_for_new_jobs => \&watch_for_new_jobs,
3672         watch_for_modified_jobs => \&watch_for_modified_jobs,
3673         watch_for_done_jobs => \&watch_for_done_jobs,
3674         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3675         watch_for_old_known_clients => \&watch_for_old_known_clients,
3676         create_packages_list_db => \&run_create_packages_list_db,
3677         create_fai_server_db => \&run_create_fai_server_db,
3678         create_fai_release_db => \&run_create_fai_release_db,
3679                 recreate_packages_db => \&run_recreate_packages_db,
3680         session_run_result => \&session_run_result,
3681         session_run_debug => \&session_run_debug,
3682         session_run_done => \&session_run_done,
3683         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3684         }
3685 );
3688 POE::Kernel->run();
3689 exit;