Code

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