Code

* Fix LDAP search filter escaping
[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 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5  qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
58 my $modules_path = "/usr/lib/gosa-si/modules";
59 use lib "/usr/lib/gosa-si/modules";
61 # revision number of server and program name
62 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
63 my $server_headURL;
64 my $server_revision;
65 my $server_status;
66 our $prg= basename($0);
68 our $global_kernel;
69 my ($foreground, $ping_timeout);
70 my ($server);
71 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
72 my ($messaging_db_loop_delay);
73 my ($procid, $pid);
74 my ($arp_fifo);
75 my ($xml);
76 my $sources_list;
77 my $max_clients;
78 my %repo_files=();
79 my $repo_path;
80 my %repo_dirs=();
82 # Variables declared in config file are always set to 'our'
83 our (%cfg_defaults, $log_file, $pid_file, 
84     $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
85     $arp_activ, $gosa_unit_tag,
86     $GosaPackages_key, $gosa_timeout,
87     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
88     $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
89     $arp_enabled, $arp_interface,
90     $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
91                 $new_systems_ou,
92 );
94 # additional variable which should be globaly accessable
95 our $server_address;
96 our $server_mac_address;
97 our $gosa_address;
98 our $no_arp;
99 our $verbose;
100 our $forground;
101 our $cfg_file;
102 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
103 our $known_modules;
104 our $root_uid;
105 our $adm_gid;
108 # specifies the verbosity of the daemon_log
109 $verbose = 0 ;
111 # if foreground is not null, script will be not forked to background
112 $foreground = 0 ;
114 # specifies the timeout seconds while checking the online status of a registrating client
115 $ping_timeout = 5;
117 $no_arp = 0;
118 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
119 my @packages_list_statements;
120 my $watch_for_new_jobs_in_progress = 0;
122 # holds all incoming decrypted messages
123 our $incoming_db;
124 our $incoming_tn = 'incoming';
125 my $incoming_file_name;
126 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
127         "timestamp DEFAULT 'none'", 
128         "headertag DEFAULT 'none'",
129                 "targettag DEFAULT 'none'",
130         "xmlmessage DEFAULT 'none'",
131         "module DEFAULT 'none'",
132         "sessionid DEFAULT '0'",
133         );
135 # holds all gosa jobs
136 our $job_db;
137 our $job_queue_tn = 'jobs';
138 my $job_queue_file_name;
139 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
140                 "timestamp DEFAULT 'none'", 
141                 "status DEFAULT 'none'", 
142                 "result DEFAULT 'none'", 
143                 "progress DEFAULT 'none'", 
144         "headertag DEFAULT 'none'", 
145                 "targettag DEFAULT 'none'", 
146                 "xmlmessage DEFAULT 'none'", 
147                 "macaddress DEFAULT 'none'",
148                 "plainname DEFAULT 'none'",
149         "siserver DEFAULT 'none'",
150         "modified DEFAULT '0'",
151                 );
153 # holds all other gosa-si-server
154 our $known_server_db;
155 our $known_server_tn = "known_server";
156 my $known_server_file_name;
157 my @known_server_col_names = ("hostname", "macaddress", "status", "hostkey", "loaded_modules", "timestamp");
159 # holds all registrated clients
160 our $known_clients_db;
161 our $known_clients_tn = "known_clients";
162 my $known_clients_file_name;
163 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
165 # holds all registered clients at a foreign server
166 our $foreign_clients_db;
167 our $foreign_clients_tn = "foreign_clients"; 
168 my $foreign_clients_file_name;
169 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
171 # holds all logged in user at each client 
172 our $login_users_db;
173 our $login_users_tn = "login_users";
174 my $login_users_file_name;
175 my @login_users_col_names = ("client", "user", "timestamp", "regserver");
177 # holds all fai server, the debian release and tag
178 our $fai_server_db;
179 our $fai_server_tn = "fai_server"; 
180 my $fai_server_file_name;
181 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
183 our $fai_release_db;
184 our $fai_release_tn = "fai_release"; 
185 my $fai_release_file_name;
186 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
188 # holds all packages available from different repositories
189 our $packages_list_db;
190 our $packages_list_tn = "packages_list";
191 my $packages_list_file_name;
192 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
193 my $outdir = "/tmp/packages_list_db";
194 my $arch = "i386"; 
196 # holds all messages which should be delivered to a user
197 our $messaging_db;
198 our $messaging_tn = "messaging"; 
199 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
200         "flag", "direction", "delivery_time", "message", "timestamp" );
201 my $messaging_file_name;
203 # path to directory to store client install log files
204 our $client_fai_log_dir = "/var/log/fai"; 
206 # queue which stores taskes until one of the $max_children children are ready to process the task
207 my @tasks = qw();
208 my @msgs_to_decrypt = qw();
209 my $max_children = 2;
212 # loop delay for job queue to look for opsi jobs
213 my $job_queue_opsi_delay = 10;
214 our $opsi_client;
215 our $opsi_url;
216  
217 # Lifetime of logged in user information. If no update information comes after n seconds, 
218 # the user is expeceted to be no longer logged in or the host is no longer running. Because
219 # of this, the user is deleted from login_users_db
220 our $logged_in_user_date_of_expiry = 600;
223 %cfg_defaults = (
224 "general" => {
225     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
226     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
227     },
228 "server" => {
229     "ip"                    => [\$server_ip, "0.0.0.0"],
230     "port"                  => [\$server_port, "20081"],
231     "known-clients"         => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
232     "known-servers"         => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
233     "incoming"              => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
234     "login-users"           => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
235     "fai-server"            => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
236     "fai-release"           => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
237     "packages-list"         => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
238     "messaging"             => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
239     "foreign-clients"       => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
240     "source-list"           => [\$sources_list, '/etc/apt/sources.list'],
241     "repo-path"             => [\$repo_path, '/srv/www/repository'],
242     "ldap-uri"              => [\$ldap_uri, ""],
243     "ldap-base"             => [\$ldap_base, ""],
244     "ldap-admin-dn"         => [\$ldap_admin_dn, ""],
245     "ldap-admin-password"   => [\$ldap_admin_password, ""],
246     "gosa-unit-tag"         => [\$gosa_unit_tag, ""],
247     "max-clients"           => [\$max_clients, 10],
248     "wol-password"          => [\$wake_on_lan_passwd, ""],
249     },
250 "GOsaPackages" => {
251     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
252     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
253     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
254     "key" => [\$GosaPackages_key, "none"],
255                 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
256     },
257 "ClientPackages" => {
258     "key" => [\$ClientPackages_key, "none"],
259     "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
260     },
261 "ServerPackages"=> {
262     "address"      => [\$foreign_server_string, ""],
263     "dns-lookup"            => [\$dns_lookup, "true"],
264     "domain"  => [\$server_domain, ""],
265     "key"     => [\$ServerPackages_key, "none"],
266     "key-lifetime" => [\$foreign_servers_register_delay, 120],
267     "job-synchronization-enabled" => [\$job_synchronization, "true"],
268     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
269     },
270 "ArpHandler" => {
271     "enabled"   => [\$arp_enabled, "true"],
272     "interface" => [\$arp_interface, "all"],
273         },
274 "Opsi" => {
275     "enabled"  => [\$opsi_enabled, "false"], 
276     "server"   => [\$opsi_server, "localhost"],
277     "admin"    => [\$opsi_admin, "opsi-admin"],
278     "password" => [\$opsi_password, "secret"],
279    },
281 );
284 #===  FUNCTION  ================================================================
285 #         NAME:  usage
286 #   PARAMETERS:  nothing
287 #      RETURNS:  nothing
288 #  DESCRIPTION:  print out usage text to STDERR
289 #===============================================================================
290 sub usage {
291     print STDERR << "EOF" ;
292 usage: $prg [-hvf] [-c config]
294            -h        : this (help) message
295            -c <file> : config file
296            -f        : foreground, process will not be forked to background
297            -v        : be verbose (multiple to increase verbosity)
298            -no-arp   : starts $prg without connection to arp module
299  
300 EOF
301     print "\n" ;
305 #===  FUNCTION  ================================================================
306 #         NAME:  logging
307 #   PARAMETERS:  level - string - default 'info'
308 #                msg - string -
309 #                facility - string - default 'LOG_DAEMON'
310 #      RETURNS:  nothing
311 #  DESCRIPTION:  function for logging
312 #===============================================================================
313 sub daemon_log {
314     # log into log_file
315     my( $msg, $level ) = @_;
316     if(not defined $msg) { return }
317     if(not defined $level) { $level = 1 }
318     if(defined $log_file){
319         open(LOG_HANDLE, ">>$log_file");
320         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
321             print STDERR "cannot open $log_file: $!";
322             return 
323         }
324         chomp($msg);
325         #$msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
326         if($level <= $verbose){
327             my ($seconds, $minutes, $hours, $monthday, $month,
328                     $year, $weekday, $yearday, $sommertime) = localtime(time);
329             $hours = $hours < 10 ? $hours = "0".$hours : $hours;
330             $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
331             $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
332             my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
333             $month = $monthnames[$month];
334             $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
335             $year+=1900;
336             my $name = $prg;
338             my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
339             print LOG_HANDLE $log_msg;
340             if( $foreground ) { 
341                 print STDERR $log_msg;
342             }
343         }
344         close( LOG_HANDLE );
345     }
349 #===  FUNCTION  ================================================================
350 #         NAME:  check_cmdline_param
351 #   PARAMETERS:  nothing
352 #      RETURNS:  nothing
353 #  DESCRIPTION:  validates commandline parameter
354 #===============================================================================
355 sub check_cmdline_param () {
356     my $err_config;
357     my $err_counter = 0;
358         if(not defined($cfg_file)) {
359                 $cfg_file = "/etc/gosa-si/server.conf";
360                 if(! -r $cfg_file) {
361                         $err_config = "please specify a config file";
362                         $err_counter += 1;
363                 }
364     }
365     if( $err_counter > 0 ) {
366         &usage( "", 1 );
367         if( defined( $err_config)) { print STDERR "$err_config\n"}
368         print STDERR "\n";
369         exit( -1 );
370     }
374 #===  FUNCTION  ================================================================
375 #         NAME:  check_pid
376 #   PARAMETERS:  nothing
377 #      RETURNS:  nothing
378 #  DESCRIPTION:  handels pid processing
379 #===============================================================================
380 sub check_pid {
381     $pid = -1;
382     # Check, if we are already running
383     if( open(LOCK_FILE, "<$pid_file") ) {
384         $pid = <LOCK_FILE>;
385         if( defined $pid ) {
386             chomp( $pid );
387             if( -f "/proc/$pid/stat" ) {
388                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
389                 if( $stat ) {
390                                         daemon_log("ERROR: Already running",1);
391                     close( LOCK_FILE );
392                     exit -1;
393                 }
394             }
395         }
396         close( LOCK_FILE );
397         unlink( $pid_file );
398     }
400     # create a syslog msg if it is not to possible to open PID file
401     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
402         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
403         if (open(LOCK_FILE, '<', $pid_file)
404                 && ($pid = <LOCK_FILE>))
405         {
406             chomp($pid);
407             $msg .= "(PID $pid)\n";
408         } else {
409             $msg .= "(unable to read PID)\n";
410         }
411         if( ! ($foreground) ) {
412             openlog( $0, "cons,pid", "daemon" );
413             syslog( "warning", $msg );
414             closelog();
415         }
416         else {
417             print( STDERR " $msg " );
418         }
419         exit( -1 );
420     }
423 #===  FUNCTION  ================================================================
424 #         NAME:  import_modules
425 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
426 #                are stored
427 #      RETURNS:  nothing
428 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
429 #                state is on is imported by "require 'file';"
430 #===============================================================================
431 sub import_modules {
432     daemon_log(" ", 1);
434     if (not -e $modules_path) {
435         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
436     }
438     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
439     while (defined (my $file = readdir (DIR))) {
440         if (not $file =~ /(\S*?).pm$/) {
441             next;
442         }
443                 my $mod_name = $1;
445         # ArpHandler switch
446         if( $file =~ /ArpHandler.pm/ ) {
447             if( $arp_enabled eq "false" ) { next; }
448         }
449         
450         eval { require $file; };
451         if ($@) {
452             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
453             daemon_log("$@", 1);
454             exit;
455                 } else {
456                         my $info = eval($mod_name.'::get_module_info()');
457                         # Only load module if get_module_info() returns a non-null object
458                         if( $info ) {
459                                 my ($input_address, $input_key, $event_hash) = @{$info};
460                                 $known_modules->{$mod_name} = $info;
461                                 daemon_log("0 INFO: module $mod_name loaded", 5);
462                         }
463                 }
464     }   
466     close (DIR);
469 #===  FUNCTION  ================================================================
470 #         NAME:  password_check
471 #   PARAMETERS:  nothing
472 #      RETURNS:  nothing
473 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
474 #                the same password
475 #===============================================================================
476 sub password_check {
477     my $passwd_hash = {};
478     while (my ($mod_name, $mod_info) = each %$known_modules) {
479         my $mod_passwd = @$mod_info[1];
480         if (not defined $mod_passwd) { next; }
481         if (not exists $passwd_hash->{$mod_passwd}) {
482             $passwd_hash->{$mod_passwd} = $mod_name;
484         # escalates critical error
485         } else {
486             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
487             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
488             exit( -1 );
489         }
490     }
495 #===  FUNCTION  ================================================================
496 #         NAME:  sig_int_handler
497 #   PARAMETERS:  signal - string - signal arose from system
498 #      RETURNS:  nothing
499 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
500 #===============================================================================
501 sub sig_int_handler {
502     my ($signal) = @_;
504 #       if (defined($ldap_handle)) {
505 #               $ldap_handle->disconnect;
506 #       }
507     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
508     
510     daemon_log("shutting down gosa-si-server", 1);
511     system("kill `ps -C gosa-si-server -o pid=`");
513 $SIG{INT} = \&sig_int_handler;
516 sub check_key_and_xml_validity {
517     my ($crypted_msg, $module_key, $session_id) = @_;
518     my $msg;
519     my $msg_hash;
520     my $error_string;
521     eval{
522         $msg = &decrypt_msg($crypted_msg, $module_key);
524         if ($msg =~ /<xml>/i){
525             $msg =~ s/\s+/ /g;  # just for better daemon_log
526             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
527             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
529             ##############
530             # check header
531             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
532             my $header_l = $msg_hash->{'header'};
533             if( 1 > @{$header_l} ) { die 'empty header tag'; }
534             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
535             my $header = @{$header_l}[0];
536             if( 0 == length $header) { die 'empty string in header tag'; }
538             ##############
539             # check source
540             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
541             my $source_l = $msg_hash->{'source'};
542             if( 1 > @{$source_l} ) { die 'empty source tag'; }
543             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
544             my $source = @{$source_l}[0];
545             if( 0 == length $source) { die 'source error'; }
547             ##############
548             # check target
549             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
550             my $target_l = $msg_hash->{'target'};
551             if( 1 > @{$target_l} ) { die 'empty target tag'; }
552         }
553     };
554     if($@) {
555         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
556         $msg = undef;
557         $msg_hash = undef;
558     }
560     return ($msg, $msg_hash);
564 sub check_outgoing_xml_validity {
565     my ($msg, $session_id) = @_;
567     my $msg_hash;
568     eval{
569         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
571         ##############
572         # check header
573         my $header_l = $msg_hash->{'header'};
574         if( 1 != @{$header_l} ) {
575             die 'no or more than one headers specified';
576         }
577         my $header = @{$header_l}[0];
578         if( 0 == length $header) {
579             die 'header has length 0';
580         }
582         ##############
583         # check source
584         my $source_l = $msg_hash->{'source'};
585         if( 1 != @{$source_l} ) {
586             die 'no or more than 1 sources specified';
587         }
588         my $source = @{$source_l}[0];
589         if( 0 == length $source) {
590             die 'source has length 0';
591         }
592         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
593                 $source =~ /^GOSA$/i ) {
594             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
595         }
596         
597         ##############
598         # check target  
599         my $target_l = $msg_hash->{'target'};
600         if( 0 == @{$target_l} ) {
601             die "no targets specified";
602         }
603         foreach my $target (@$target_l) {
604             if( 0 == length $target) {
605                 die "target has length 0";
606             }
607             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
608                     $target =~ /^GOSA$/i ||
609                     $target =~ /^\*$/ ||
610                     $target =~ /KNOWN_SERVER/i ||
611                     $target =~ /JOBDB/i ||
612                     $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 ){
613                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
614             }
615         }
616     };
617     if($@) {
618         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
619         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
620         $msg_hash = undef;
621     }
623     return ($msg_hash);
627 sub input_from_known_server {
628     my ($input, $remote_ip, $session_id) = @_ ;  
629     my ($msg, $msg_hash, $module);
631     my $sql_statement= "SELECT * FROM known_server";
632     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
634     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
635         my $host_name = $hit->{hostname};
636         if( not $host_name =~ "^$remote_ip") {
637             next;
638         }
639         my $host_key = $hit->{hostkey};
640         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
641         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
643         # check if module can open msg envelope with module key
644         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
645         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
646             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
647             daemon_log("$@", 8);
648             next;
649         }
650         else {
651             $msg = $tmp_msg;
652             $msg_hash = $tmp_msg_hash;
653             $module = "ServerPackages";
654             last;
655         }
656     }
658     if( (!$msg) || (!$msg_hash) || (!$module) ) {
659         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
660     }
661   
662     return ($msg, $msg_hash, $module);
666 sub input_from_known_client {
667     my ($input, $remote_ip, $session_id) = @_ ;  
668     my ($msg, $msg_hash, $module);
670     my $sql_statement= "SELECT * FROM known_clients";
671     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
672     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
673         my $host_name = $hit->{hostname};
674         if( not $host_name =~ /^$remote_ip:\d*$/) {
675                 next;
676                 }
677         my $host_key = $hit->{hostkey};
678         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
679         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
681         # check if module can open msg envelope with module key
682         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
684         if( (!$msg) || (!$msg_hash) ) {
685             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
686             &daemon_log("$@", 8);
687             next;
688         }
689         else {
690             $module = "ClientPackages";
691             last;
692         }
693     }
695     if( (!$msg) || (!$msg_hash) || (!$module) ) {
696         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
697     }
699     return ($msg, $msg_hash, $module);
703 sub input_from_unknown_host {
704         no strict "refs";
705         my ($input, $session_id) = @_ ;
706         my ($msg, $msg_hash, $module);
707         my $error_string;
709         my %act_modules = %$known_modules;
711         while( my ($mod, $info) = each(%act_modules)) {
713                 # check a key exists for this module
714                 my $module_key = ${$mod."_key"};
715                 if( not defined $module_key ) {
716                         if( $mod eq 'ArpHandler' ) {
717                                 next;
718                         }
719                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
720                         next;
721                 }
722                 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
724                 # check if module can open msg envelope with module key
725                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
726                 if( (not defined $msg) || (not defined $msg_hash) ) {
727                         next;
728                 } else {
729                         $module = $mod;
730                         last;
731                 }
732         }
734         if( (!$msg) || (!$msg_hash) || (!$module)) {
735                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
736         }
738         return ($msg, $msg_hash, $module);
742 sub create_ciphering {
743     my ($passwd) = @_;
744         if((!defined($passwd)) || length($passwd)==0) {
745                 $passwd = "";
746         }
747     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
748     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
749     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
750     $my_cipher->set_iv($iv);
751     return $my_cipher;
755 sub encrypt_msg {
756     my ($msg, $key) = @_;
757     my $my_cipher = &create_ciphering($key);
758     my $len;
759     {
760             use bytes;
761             $len= 16-length($msg)%16;
762     }
763     $msg = "\0"x($len).$msg;
764     $msg = $my_cipher->encrypt($msg);
765     chomp($msg = &encode_base64($msg));
766     # there are no newlines allowed inside msg
767     $msg=~ s/\n//g;
768     return $msg;
772 sub decrypt_msg {
774     my ($msg, $key) = @_ ;
775     $msg = &decode_base64($msg);
776     my $my_cipher = &create_ciphering($key);
777     $msg = $my_cipher->decrypt($msg); 
778     $msg =~ s/\0*//g;
779     return $msg;
783 sub get_encrypt_key {
784     my ($target) = @_ ;
785     my $encrypt_key;
786     my $error = 0;
788     # target can be in known_server
789     if( not defined $encrypt_key ) {
790         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
791         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
792         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
793             my $host_name = $hit->{hostname};
794             if( $host_name ne $target ) {
795                 next;
796             }
797             $encrypt_key = $hit->{hostkey};
798             last;
799         }
800     }
802     # target can be in known_client
803     if( not defined $encrypt_key ) {
804         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
805         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
806         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
807             my $host_name = $hit->{hostname};
808             if( $host_name ne $target ) {
809                 next;
810             }
811             $encrypt_key = $hit->{hostkey};
812             last;
813         }
814     }
816     return $encrypt_key;
820 #===  FUNCTION  ================================================================
821 #         NAME:  open_socket
822 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
823 #                [PeerPort] string necessary if port not appended by PeerAddr
824 #      RETURNS:  socket IO::Socket::INET
825 #  DESCRIPTION:  open a socket to PeerAddr
826 #===============================================================================
827 sub open_socket {
828     my ($PeerAddr, $PeerPort) = @_ ;
829     if(defined($PeerPort)){
830         $PeerAddr = $PeerAddr.":".$PeerPort;
831     }
832     my $socket;
833     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
834             Porto => "tcp",
835             Type => SOCK_STREAM,
836             Timeout => 5,
837             );
838     if(not defined $socket) {
839         return;
840     }
841 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
842     return $socket;
846 #sub get_local_ip_for_remote_ip {
847 #       my $remote_ip= shift;
848 #       my $result="0.0.0.0";
850 #       if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
851 #               if($remote_ip eq "127.0.0.1") {
852 #                       $result = "127.0.0.1";
853 #               } else {
854 #                       my $PROC_NET_ROUTE= ('/proc/net/route');
856 #                       open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
857 #                               or die "Could not open $PROC_NET_ROUTE";
859 #                       my @ifs = <PROC_NET_ROUTE>;
861 #                       close(PROC_NET_ROUTE);
863 #                       # Eat header line
864 #                       shift @ifs;
865 #                       chomp @ifs;
866 #                       foreach my $line(@ifs) {
867 #                               my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
868 #                               my $destination;
869 #                               my $mask;
870 #                               my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
871 #                               $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
872 #                               ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
873 #                               $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
874 #                               if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
875 #                                       # destination matches route, save mac and exit
876 #                                       $result= &get_ip($Iface);
877 #                                       last;
878 #                               }
879 #                       }
880 #               }
881 #       } else {
882 #               daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
883 #       }
884 #       return $result;
885 #}
888 sub send_msg_to_target {
889     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
890     my $error = 0;
891     my $header;
892     my $timestamp = &get_time();
893     my $new_status;
894     my $act_status;
895     my ($sql_statement, $res);
896   
897     if( $msg_header ) {
898         $header = "'$msg_header'-";
899     } else {
900         $header = "";
901     }
903         # Patch the source ip
904         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
905                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
906                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
907         }
909     # encrypt xml msg
910     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
912     # opensocket
913     my $socket = &open_socket($address);
914     if( !$socket ) {
915         daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
916         $error++;
917     }
918     
919     if( $error == 0 ) {
920         # send xml msg
921         print $socket $crypted_msg."\n";
923         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
924         daemon_log("$session_id DEBUG: message:\n$msg", 9);
925         
926     }
928     # close socket in any case
929     if( $socket ) {
930         close $socket;
931     }
933     if( $error > 0 ) { $new_status = "down"; }
934     else { $new_status = $msg_header; }
937     # known_clients
938     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
939     $res = $known_clients_db->select_dbentry($sql_statement);
940     if( keys(%$res) == 1) {
941         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
942         if ($act_status eq "down" && $new_status eq "down") {
943             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
944             $res = $known_clients_db->del_dbentry($sql_statement);
945             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
946         } else { 
947             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
948             $res = $known_clients_db->update_dbentry($sql_statement);
949             if($new_status eq "down"){
950                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
951             } else {
952                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
953             }
954         }
955     }
957     # known_server
958     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
959     $res = $known_server_db->select_dbentry($sql_statement);
960     if( keys(%$res) == 1) {
961         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
962         if ($act_status eq "down" && $new_status eq "down") {
963             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
964             $res = $known_server_db->del_dbentry($sql_statement);
965             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
966         } 
967         else { 
968             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
969             $res = $known_server_db->update_dbentry($sql_statement);
970             if($new_status eq "down"){
971                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
972             } else {
973                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
974             }
975         }
976     }
977     return $error; 
981 sub update_jobdb_status_for_send_msgs {
982     my ($session_id, $answer, $error) = @_;
983     &daemon_log("$session_id DEBUG: try to update job status", 7);
984     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
985         my $jobdb_id = $1;
986         $answer =~ /<header>(.*)<\/header>/; 
987         my $job_header = $1; 
988         $answer =~ /<target>(.*)<\/target>/; 
989         my $job_target = $1; 
991         # sending msg failed
992         if( $error ) {
994             # set jobs to done, jobs do not need to deliver their message in any case
995             if (($job_header eq "trigger_action_localboot")
996                     ||($job_header eq "trigger_action_lock")
997                     ||($job_header eq "trigger_action_halt")
998                     ) {
999                 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1000                 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1001                 my $res = $job_db->update_dbentry($sql_statement);
1003             # reactivate jobs, jobs need to deliver their message
1004             } elsif (($job_header eq "trigger_action_activate")
1005                     ||($job_header eq "trigger_action_update")
1006                     ||($job_header eq "trigger_action_reinstall")
1007                     ||($job_header eq "trigger_activate_new")
1008                     ) {
1009                 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1011             # for all other messages
1012             } else {
1013                 my $sql_statement = "UPDATE $job_queue_tn ".
1014                     "SET status='error', result='can not deliver msg, please consult log file' ".
1015                     "WHERE id=$jobdb_id";
1016                 my $res = $job_db->update_dbentry($sql_statement);
1017                 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1018     }
1020         # sending msg was successful
1021         } else {
1022             # set jobs localboot, lock, activate, halt, reboot and wake to done
1023             # jobs reinstall, update, inst_update do themself setting to done
1024             if (($job_header eq "trigger_action_localboot")
1025                     ||($job_header eq "trigger_action_lock")
1026                     ||($job_header eq "trigger_action_activate")
1027                     ||($job_header eq "trigger_action_halt")
1028                     ||($job_header eq "trigger_action_reboot")
1029                     ||($job_header eq "trigger_action_wake")
1030                     ||($job_header eq "trigger_wake")
1031                     ) {
1033                 my $sql_statement = "UPDATE $job_queue_tn ".
1034                     "SET status='done' ".
1035                     "WHERE id=$jobdb_id AND status='processed'";
1036                 my $res = $job_db->update_dbentry($sql_statement);
1037                 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1038             } else {
1039                 &daemon_log("$session_id WARNING: sending message succeed but cannot update job status.", 3);
1040             }
1041         }
1042     } else {
1043           &daemon_log("$session_id ERROR: cannot update job status, msg has no jobdb_id-tag: $answer", 1);
1044     }
1047 sub reactivate_job_with_delay {
1048     my ($session_id, $target, $header, $delay) = @_ ;
1049     # 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
1051     if (not defined $delay) { $delay = 30 } ;
1052     my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1054     my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$target' AND headertag='$header')";
1055     my $res = $job_db->update_dbentry($sql);
1056     daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1057             "cause client '$target' is currently not available", 5);
1058     daemon_log("$session_id $sql", 7);
1059     return;
1063 sub sig_handler {
1064         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1065         daemon_log("0 INFO got signal '$signal'", 1); 
1066         $kernel->sig_handled();
1067         return;
1071 sub msg_to_decrypt {
1072     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1073     my $session_id = $session->ID;
1074     my ($msg, $msg_hash, $module);
1075     my $error = 0;
1077     # fetch new msg out of @msgs_to_decrypt
1078     my $tmp_next_msg = shift @msgs_to_decrypt;
1079     my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1080     
1081     # msg is from a new client or gosa
1082     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1083     # msg is from a gosa-si-server
1084     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1085         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1086     }
1087     # msg is from a gosa-si-client
1088     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1089         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1090     }
1091     # an error occurred
1092     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1093         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1094         # could not understand a msg from its server the client cause a re-registering process
1095         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1096         my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1098         daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1099                 "' to cause a re-registering of the client if necessary", 3);
1100         $error++;
1101     }
1103     my $header;
1104     my $target;
1105     my $source;
1106     my $done = 0;
1107     my $sql;
1108     my $res;
1110     # check whether this message should be processed here
1111     if ($error == 0) {
1112         $header = @{$msg_hash->{'header'}}[0];
1113         $target = @{$msg_hash->{'target'}}[0];
1114         $source = @{$msg_hash->{'source'}}[0];
1115                 my $not_found_in_known_clients_db = 0;
1116                 my $not_found_in_known_server_db = 0;
1117                 my $not_found_in_foreign_clients_db = 0;
1118         my $local_address;
1119         my $local_mac;
1120         my ($target_ip, $target_port) = split(':', $target);
1121         
1122         # Determine the local ip address if target is an ip address
1123                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1124                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1125                 } else {
1126             $local_address = $server_address;
1127         }
1129         # Determine the local mac address if target is a mac address
1130         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) {
1131             my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1132             my $network_interface= &get_interface_for_ip($loc_ip);
1133             $local_mac = &get_mac_for_interface($network_interface);
1134         } else {
1135             $local_mac = $server_mac_address;
1136         }
1138         # target and source is equal to GOSA -> process here
1139         if (not $done) {
1140             if ($target eq "GOSA" && $source eq "GOSA") {
1141                 $done = 1;                    
1142                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1143             }
1144         }
1146         # target is own address without forward_to_gosa-tag -> process here
1147         if (not $done) {
1148             #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1149             if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1150                 $done = 1;
1151                 if ($source eq "GOSA") {
1152                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1153                 }
1154                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1155             }
1156         }
1158         # target is a client address in known_clients -> process here
1159                 if (not $done) {
1160                                 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1161                                 $res = $known_clients_db->select_dbentry($sql);
1162                                 if (keys(%$res) > 0) {
1163                                                 $done = 1; 
1164                                                 my $hostname = $res->{1}->{'hostname'};
1165                                                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1166                         my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1167                         if ($source eq "GOSA") {
1168                             $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1169                         }
1170                         &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1172                                 } else {
1173                                                 $not_found_in_known_clients_db = 1;
1174                                 }
1175                 }
1176         
1177         # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1178         if (not $done) {
1179             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1180             my $gosa_at;
1181             my $gosa_session_id;
1182             if (($target eq $local_address) && (defined $forward_to_gosa)){
1183                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1184                 if ($gosa_at ne $local_address) {
1185                     $done = 1;
1186                     &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7); 
1187                 }
1188             }
1189         }
1191         # if message should be processed here -> add message to incoming_db
1192                 if ($done) {
1193                                 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1194                                 # so gosa-si-server knows how to process this kind of messages
1195                                 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1196                                                 $module = "GosaPackages";
1197                                 }
1199                                 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1200                                                                 primkey=>[],
1201                                                                 headertag=>$header,
1202                                                                 targettag=>$target,
1203                                                                 xmlmessage=>&encode_base64($msg),
1204                                                                 timestamp=>&get_time,
1205                                                                 module=>$module,
1206                                                                 sessionid=>$session_id,
1207                                                                 } );
1208                 }
1210         # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1211         if (not $done) {
1212             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1213             my $gosa_at;
1214             my $gosa_session_id;
1215             if (($target eq $local_address) && (defined $forward_to_gosa)){
1216                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1217                 if ($gosa_at eq $local_address) {
1218                     my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1219                     if( defined $session_reference ) {
1220                         $heap = $session_reference->get_heap();
1221                     }
1222                     if(exists $heap->{'client'}) {
1223                         $msg = &encrypt_msg($msg, $GosaPackages_key);
1224                         $heap->{'client'}->put($msg);
1225                         &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5); 
1226                     }
1227                     $done = 1;
1228                     &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1229                 }
1230             }
1232         }
1234         # target is a client address in foreign_clients -> forward to registration server
1235         if (not $done) {
1236             $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1237             $res = $foreign_clients_db->select_dbentry($sql);
1238             if (keys(%$res) > 0) {
1239                     my $hostname = $res->{1}->{'hostname'};
1240                     my ($host_ip, $host_port) = split(/:/, $hostname);
1241                     my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1242                 my $regserver = $res->{1}->{'regserver'};
1243                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1244                 my $res = $known_server_db->select_dbentry($sql);
1245                 if (keys(%$res) > 0) {
1246                     my $regserver_key = $res->{1}->{'hostkey'};
1247                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1248                     $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1249                     if ($source eq "GOSA") {
1250                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1251                     }
1252                     &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1253                 }
1254                 $done = 1;
1255                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1256             } else {
1257                                 $not_found_in_foreign_clients_db = 1;
1258                         }
1259         }
1261         # target is a server address -> forward to server
1262         if (not $done) {
1263             $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1264             $res = $known_server_db->select_dbentry($sql);
1265             if (keys(%$res) > 0) {
1266                 my $hostkey = $res->{1}->{'hostkey'};
1268                 if ($source eq "GOSA") {
1269                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1270                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1272                 }
1274                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1275                 $done = 1;
1276                 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1277             } else {
1278                                 $not_found_in_known_server_db = 1;
1279                         }
1280         }
1282                 
1283                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1284                 if ( $not_found_in_foreign_clients_db 
1285                                                 && $not_found_in_known_server_db
1286                                                 && $not_found_in_known_clients_db) {
1287                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1288                                 $module = "GosaPackages";
1289                         }
1291                                         
1292                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1293                                                                 primkey=>[],
1294                                                                 headertag=>$header,
1295                                                                 targettag=>$target,
1296                                                                 xmlmessage=>&encode_base64($msg),
1297                                                                 timestamp=>&get_time,
1298                                                                 module=>$module,
1299                                                                 sessionid=>$session_id,
1300                                                                 } );
1301                                 $done = 1;
1302                 &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 here", 7);
1303                 }
1306         if (not $done) {
1307             daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1308             if ($source eq "GOSA") {
1309                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1310                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1312                 my $session_reference = $kernel->ID_id_to_session($session_id);
1313                 if( defined $session_reference ) {
1314                     $heap = $session_reference->get_heap();
1315                 }
1316                 if(exists $heap->{'client'}) {
1317                     $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1318                     $heap->{'client'}->put($error_msg);
1319                 }
1320             }
1321         }
1323     }
1325     return;
1329 sub next_task {
1330     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1331     my $running_task = POE::Wheel::Run->new(
1332             Program => sub { process_task($session, $heap, $task) },
1333             StdioFilter => POE::Filter::Reference->new(),
1334             StdoutEvent  => "task_result",
1335             StderrEvent  => "task_debug",
1336             CloseEvent   => "task_done",
1337             );
1338     $heap->{task}->{ $running_task->ID } = $running_task;
1341 sub handle_task_result {
1342     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1343     my $client_answer = $result->{'answer'};
1344     if( $client_answer =~ s/session_id=(\d+)$// ) {
1345         my $session_id = $1;
1346         if( defined $session_id ) {
1347             my $session_reference = $kernel->ID_id_to_session($session_id);
1348             if( defined $session_reference ) {
1349                 $heap = $session_reference->get_heap();
1350             }
1351         }
1353         if(exists $heap->{'client'}) {
1354             $heap->{'client'}->put($client_answer);
1355         }
1356     }
1357     $kernel->sig(CHLD => "child_reap");
1360 sub handle_task_debug {
1361     my $result = $_[ARG0];
1362     print STDERR "$result\n";
1365 sub handle_task_done {
1366     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1367     delete $heap->{task}->{$task_id};
1370 sub process_task {
1371     no strict "refs";
1372     #CHECK: Not @_[...]?
1373     my ($session, $heap, $task) = @_;
1374     my $error = 0;
1375     my $answer_l;
1376     my ($answer_header, @answer_target_l, $answer_source);
1377     my $client_answer = "";
1379     # prepare all variables needed to process message
1380     #my $msg = $task->{'xmlmessage'};
1381     my $msg = &decode_base64($task->{'xmlmessage'});
1382     my $incoming_id = $task->{'id'};
1383     my $module = $task->{'module'};
1384     my $header =  $task->{'headertag'};
1385     my $session_id = $task->{'sessionid'};
1386     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1387     my $source = @{$msg_hash->{'source'}}[0];
1388     
1389     # set timestamp of incoming client uptodate, so client will not 
1390     # be deleted from known_clients because of expiration
1391     my $act_time = &get_time();
1392     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1393     my $res = $known_clients_db->exec_statement($sql);
1395     ######################
1396     # process incoming msg
1397     if( $error == 0) {
1398         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1399         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1400         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1402         if ( 0 < @{$answer_l} ) {
1403             my $answer_str = join("\n", @{$answer_l});
1404             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1405                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1406             }
1407             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1408         } else {
1409             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1410         }
1412     }
1413     if( !$answer_l ) { $error++ };
1415     ########
1416     # answer
1417     if( $error == 0 ) {
1419         foreach my $answer ( @{$answer_l} ) {
1420             # check outgoing msg to xml validity
1421             my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1422             if( not defined $answer_hash ) { next; }
1423             
1424             $answer_header = @{$answer_hash->{'header'}}[0];
1425             @answer_target_l = @{$answer_hash->{'target'}};
1426             $answer_source = @{$answer_hash->{'source'}}[0];
1428             # deliver msg to all targets 
1429             foreach my $answer_target ( @answer_target_l ) {
1431                 # targets of msg are all gosa-si-clients in known_clients_db
1432                 if( $answer_target eq "*" ) {
1433                     # answer is for all clients
1434                     my $sql_statement= "SELECT * FROM known_clients";
1435                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1436                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1437                         my $host_name = $hit->{hostname};
1438                         my $host_key = $hit->{hostkey};
1439                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1440                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1441                     }
1442                 }
1444                 # targets of msg are all gosa-si-server in known_server_db
1445                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1446                     # answer is for all server in known_server
1447                     my $sql_statement= "SELECT * FROM $known_server_tn";
1448                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1449                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1450                         my $host_name = $hit->{hostname};
1451                         my $host_key = $hit->{hostkey};
1452                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1453                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1454                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1455                     }
1456                 }
1458                 # target of msg is GOsa
1459                                 elsif( $answer_target eq "GOSA" ) {
1460                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1461                                         my $add_on = "";
1462                     if( defined $session_id ) {
1463                         $add_on = ".session_id=$session_id";
1464                     }
1465                     # answer is for GOSA and has to returned to connected client
1466                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1467                     $client_answer = $gosa_answer.$add_on;
1468                 }
1470                 # target of msg is job queue at this host
1471                 elsif( $answer_target eq "JOBDB") {
1472                     $answer =~ /<header>(\S+)<\/header>/;   
1473                     my $header;
1474                     if( defined $1 ) { $header = $1; }
1475                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1476                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1477                 }
1479                 # Target of msg is a mac address
1480                 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 ) {
1481                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1482                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1483                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1484                     my $found_ip_flag = 0;
1485                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1486                         my $host_name = $hit->{hostname};
1487                         my $host_key = $hit->{hostkey};
1488                         $answer =~ s/$answer_target/$host_name/g;
1489                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1490                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1491                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1492                         $found_ip_flag++ ;
1493                     }   
1494                     if ($found_ip_flag == 0) {
1495                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1496                         my $res = $foreign_clients_db->select_dbentry($sql);
1497                         while( my ($hit_num, $hit) = each %{ $res } ) {
1498                             my $host_name = $hit->{hostname};
1499                             my $reg_server = $hit->{regserver};
1500                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1501                             
1502                             # Fetch key for reg_server
1503                             my $reg_server_key;
1504                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1505                             my $res = $known_server_db->select_dbentry($sql);
1506                             if (exists $res->{1}) {
1507                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1508                             } else {
1509                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1510                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1511                                 $reg_server_key = undef;
1512                             }
1514                             # Send answer to server where client is registered
1515                             if (defined $reg_server_key) {
1516                                 $answer =~ s/$answer_target/$host_name/g;
1517                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1518                                 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1519                                 $found_ip_flag++ ;
1520                             }
1521                         }
1522                     }
1523                     if( $found_ip_flag == 0) {
1524                         daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1525                         &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1526                     }
1528                 # Answer is for one specific host   
1529                 } else {
1530                     # get encrypt_key
1531                     my $encrypt_key = &get_encrypt_key($answer_target);
1532                     if( not defined $encrypt_key ) {
1533                         # unknown target
1534                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1535                         next;
1536                     }
1537                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1538                     &update_jobdb_status_for_send_msgs($session_id,$answer, $error);
1539                 }
1540             }
1541         }
1542     }
1544     my $filter = POE::Filter::Reference->new();
1545     my %result = ( 
1546             status => "seems ok to me",
1547             answer => $client_answer,
1548             );
1550     my $output = $filter->put( [ \%result ] );
1551     print @$output;
1556 sub session_start {
1557     my ($kernel) = $_[KERNEL];
1558     $global_kernel = $kernel;
1559     $kernel->yield('register_at_foreign_servers');
1560         $kernel->yield('create_fai_server_db', $fai_server_tn );
1561         $kernel->yield('create_fai_release_db', $fai_release_tn );
1562     $kernel->yield('watch_for_next_tasks');
1563         $kernel->sig(USR1 => "sig_handler");
1564         $kernel->sig(USR2 => "recreate_packages_db");
1565         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1566         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1567     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1568         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1569     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1570         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1571     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1573     # Start opsi check
1574     if ($opsi_enabled eq "true") {
1575         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1576     }
1581 sub watch_for_done_jobs {
1582     #CHECK: $heap for what?
1583     my ($kernel,$heap) = @_[KERNEL, HEAP];
1585     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1586         my $res = $job_db->select_dbentry( $sql_statement );
1588     while( my ($id, $hit) = each %{$res} ) {
1589         my $jobdb_id = $hit->{id};
1590         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1591         my $res = $job_db->del_dbentry($sql_statement); 
1592     }
1594     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1598 sub watch_for_opsi_jobs {
1599     my ($kernel) = $_[KERNEL];
1601     # 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 
1602     # opsi install job is to parse the xml message. There is still the correct header.
1603     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1604         my $res = $job_db->select_dbentry( $sql_statement );
1606     # Ask OPSI for an update of the running jobs
1607     while (my ($id, $hit) = each %$res ) {
1608         # Determine current parameters of the job
1609         my $hostId = $hit->{'plainname'};
1610         my $macaddress = $hit->{'macaddress'};
1611         my $progress = $hit->{'progress'};
1613         my $result= {};
1614         
1615         # For hosts, only return the products that are or get installed
1616         my $callobj;
1617         $callobj = {
1618             method  => 'getProductStates_hash',
1619             params  => [ $hostId ],
1620             id  => 1,
1621         };
1622         
1623         my $hres = $opsi_client->call($opsi_url, $callobj);
1624         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1625         if (not &check_opsi_res($hres)) {
1626             my $htmp= $hres->result->{$hostId};
1627         
1628             # Check state != not_installed or action == setup -> load and add
1629             my $products= 0;
1630             my $installed= 0;
1631             my $installing = 0;
1632             my $error= 0;  
1633             my @installed_list;
1634             my @error_list;
1635             my $act_status = "none";
1636             foreach my $product (@{$htmp}){
1638                 if ($product->{'installationStatus'} ne "not_installed" or
1639                         $product->{'actionRequest'} eq "setup"){
1641                     # Increase number of products for this host
1642                     $products++;
1643         
1644                     if ($product->{'installationStatus'} eq "failed"){
1645                         $result->{$product->{'productId'}}= "error";
1646                         unshift(@error_list, $product->{'productId'});
1647                         $error++;
1648                     }
1649                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1650                         $result->{$product->{'productId'}}= "installed";
1651                         unshift(@installed_list, $product->{'productId'});
1652                         $installed++;
1653                     }
1654                     if ($product->{'installationStatus'} eq "installing"){
1655                         $result->{$product->{'productId'}}= "installing";
1656                         $installing++;
1657                         $act_status = "installing - ".$product->{'productId'};
1658                     }
1659                 }
1660             }
1661         
1662             # Estimate "rough" progress, avoid division by zero
1663             if ($products == 0) {
1664                 $result->{'progress'}= 0;
1665             } else {
1666                 $result->{'progress'}= int($installed * 100 / $products);
1667             }
1669             # Set updates in job queue
1670             if ((not $error) && (not $installing) && ($installed)) {
1671                 $act_status = "installed - ".join(", ", @installed_list);
1672             }
1673             if ($error) {
1674                 $act_status = "error - ".join(", ", @error_list);
1675             }
1676             if ($progress ne $result->{'progress'} ) {
1677                 # Updating progress and result 
1678                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1679                 my $update_res = $job_db->update_dbentry($update_statement);
1680             }
1681             if ($progress eq 100) { 
1682                 # Updateing status
1683                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1684                 if ($error) {
1685                     $done_statement .= "status='error'";
1686                 } else {
1687                     $done_statement .= "status='done'";
1688                 }
1689                 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1690                 my $done_res = $job_db->update_dbentry($done_statement);
1691             }
1694         }
1695     }
1697     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1701 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1702 sub watch_for_modified_jobs {
1703     my ($kernel,$heap) = @_[KERNEL, HEAP];
1705     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')"; 
1706     my $res = $job_db->select_dbentry( $sql_statement );
1707     
1708     # if db contains no jobs which should be update, do nothing
1709     if (keys %$res != 0) {
1711         if ($job_synchronization  eq "true") {
1712             # make out of the db result a gosa-si message   
1713             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1714  
1715             # update all other SI-server
1716             &inform_all_other_si_server($update_msg);
1717         }
1719         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1720         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1721         $res = $job_db->update_dbentry($sql_statement);
1722     }
1724     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1728 sub watch_for_new_jobs {
1729         if($watch_for_new_jobs_in_progress == 0) {
1730                 $watch_for_new_jobs_in_progress = 1;
1731                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1733                 # check gosa job quaeue for jobs with executable timestamp
1734                 my $timestamp = &get_time();
1735                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1736                 my $res = $job_db->exec_statement( $sql_statement );
1738                 # Merge all new jobs that would do the same actions
1739                 my @drops;
1740                 my $hits;
1741                 foreach my $hit (reverse @{$res} ) {
1742                         my $macaddress= lc @{$hit}[8];
1743                         my $headertag= @{$hit}[5];
1744                         if(
1745                                 defined($hits->{$macaddress}) &&
1746                                 defined($hits->{$macaddress}->{$headertag}) &&
1747                                 defined($hits->{$macaddress}->{$headertag}[0])
1748                         ) {
1749                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1750                         }
1751                         $hits->{$macaddress}->{$headertag}= $hit;
1752                 }
1754                 # Delete new jobs with a matching job in state 'processing'
1755                 foreach my $macaddress (keys %{$hits}) {
1756                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1757                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1758                                 if(defined($jobdb_id)) {
1759                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1760                                         my $res = $job_db->exec_statement( $sql_statement );
1761                                         foreach my $hit (@{$res}) {
1762                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1763                                         }
1764                                 } else {
1765                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1766                                 }
1767                         }
1768                 }
1770                 # Commit deletion
1771                 $job_db->exec_statementlist(\@drops);
1773                 # Look for new jobs that could be executed
1774                 foreach my $macaddress (keys %{$hits}) {
1776                         # Look if there is an executing job
1777                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1778                         my $res = $job_db->exec_statement( $sql_statement );
1780                         # Skip new jobs for host if there is a processing job
1781                         if(defined($res) and defined @{$res}[0]) {
1782                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1783                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1784                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1785                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1786                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1787                                         if(defined($res_2) and defined @{$res_2}[0]) {
1788                                                 # Set status from goto-activation to 'waiting' and update timestamp
1789                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1790                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&calc_timestamp(&get_time(), 'plus', 30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1791                                         }
1792                                 }
1793                                 next;
1794                         }
1796                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1797                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1798                                 if(defined($jobdb_id)) {
1799                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1801                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1802                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1803                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1805                                         # expect macaddress is unique!!!!!!
1806                                         my $target = $res_hash->{1}->{hostname};
1808                                         # change header
1809                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1811                                         # add sqlite_id
1812                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1814                                         $job_msg =~ /<header>(\S+)<\/header>/;
1815                                         my $header = $1 ;
1816                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1818                                         # update status in job queue to ...
1819                     # ... 'processing', for jobs: 'reinstall', 'update'
1820                     if (($header =~ /gosa_trigger_action_reinstall/) 
1821                             || ($header =~ /gosa_trigger_activate_new/)
1822                             || ($header =~ /gosa_trigger_action_update/)) {
1823                         my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1824                         my $dbres = $job_db->update_dbentry($sql_statement);
1825                     }
1827                     # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1828                     else {
1829                         my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1830                         my $dbres = $job_db->update_dbentry($sql_statement);
1831                     }
1833                                         # We don't want parallel processing
1834                                         last;
1835                                 }
1836                         }
1837                 }
1839                 $watch_for_new_jobs_in_progress = 0;
1840                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1841         }
1845 sub watch_for_new_messages {
1846     my ($kernel,$heap) = @_[KERNEL, HEAP];
1847     my @coll_user_msg;   # collection list of outgoing messages
1848     
1849     # check messaging_db for new incoming messages with executable timestamp
1850     my $timestamp = &get_time();
1851     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1852     my $res = $messaging_db->exec_statement( $sql_statement );
1853         foreach my $hit (@{$res}) {
1855         # create outgoing messages
1856         my $message_to = @{$hit}[3];
1857         # translate message_to to plain login name
1858         my @message_to_l = split(/,/, $message_to);  
1859                 my %receiver_h; 
1860                 foreach my $receiver (@message_to_l) {
1861                         if ($receiver =~ /^u_([\s\S]*)$/) {
1862                                 $receiver_h{$1} = 0;
1863                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1864                                 my $group_name = $1;
1865                                 # fetch all group members from ldap and add them to receiver hash
1866                                 my $ldap_handle = &get_ldap_handle();
1867                                 if (defined $ldap_handle) {
1868                                                 my $mesg = $ldap_handle->search(
1869                                                                                 base => $ldap_base,
1870                                                                                 scope => 'sub',
1871                                                                                 attrs => ['memberUid'],
1872                                                                                 filter => "cn=$group_name",
1873                                                                                 );
1874                                                 if ($mesg->count) {
1875                                                                 my @entries = $mesg->entries;
1876                                                                 foreach my $entry (@entries) {
1877                                                                                 my @receivers= $entry->get_value("memberUid");
1878                                                                                 foreach my $receiver (@receivers) { 
1879                                                                                                 $receiver_h{$receiver} = 0;
1880                                                                                 }
1881                                                                 }
1882                                                 } 
1883                                                 # translating errors ?
1884                                                 if ($mesg->code) {
1885                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1886                                                 }
1887                                 # ldap handle error ?           
1888                                 } else {
1889                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1890                                 }
1891                         } else {
1892                                 my $sbjct = &encode_base64(@{$hit}[1]);
1893                                 my $msg = &encode_base64(@{$hit}[7]);
1894                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1895                         }
1896                 }
1897                 my @receiver_l = keys(%receiver_h);
1899         my $message_id = @{$hit}[0];
1901         #add each outgoing msg to messaging_db
1902         my $receiver;
1903         foreach $receiver (@receiver_l) {
1904             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1905                 "VALUES ('".
1906                 $message_id."', '".    # id
1907                 @{$hit}[1]."', '".     # subject
1908                 @{$hit}[2]."', '".     # message_from
1909                 $receiver."', '".      # message_to
1910                 "none"."', '".         # flag
1911                 "out"."', '".          # direction
1912                 @{$hit}[6]."', '".     # delivery_time
1913                 @{$hit}[7]."', '".     # message
1914                 $timestamp."'".     # timestamp
1915                 ")";
1916             &daemon_log("M DEBUG: $sql_statement", 1);
1917             my $res = $messaging_db->exec_statement($sql_statement);
1918             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1919         }
1921         # set incoming message to flag d=deliverd
1922         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1923         &daemon_log("M DEBUG: $sql_statement", 7);
1924         $res = $messaging_db->update_dbentry($sql_statement);
1925         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1926     }
1928     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1929     return;
1932 sub watch_for_delivery_messages {
1933     my ($kernel, $heap) = @_[KERNEL, HEAP];
1935     # select outgoing messages
1936     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1937     #&daemon_log("0 DEBUG: $sql", 7);
1938     my $res = $messaging_db->exec_statement( $sql_statement );
1939     
1940     # build out msg for each    usr
1941     foreach my $hit (@{$res}) {
1942         my $receiver = @{$hit}[3];
1943         my $msg_id = @{$hit}[0];
1944         my $subject = @{$hit}[1];
1945         my $message = @{$hit}[7];
1947         # resolve usr -> host where usr is logged in
1948         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1949         #&daemon_log("0 DEBUG: $sql", 7);
1950         my $res = $login_users_db->exec_statement($sql);
1952         # reciver is logged in nowhere
1953         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1955                 my $send_succeed = 0;
1956                 foreach my $hit (@$res) {
1957                                 my $receiver_host = @$hit[0];
1958                 my $delivered2host = 0;
1959                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1961                                 # Looking for host in know_clients_db 
1962                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1963                                 my $res = $known_clients_db->exec_statement($sql);
1965                 # Host is known in known_clients_db
1966                 if (ref(@$res[0]) eq "ARRAY") {
1967                     my $receiver_key = @{@{$res}[0]}[2];
1968                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1969                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1970                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1971                     if ($error == 0 ) {
1972                         $send_succeed++ ;
1973                         $delivered2host++ ;
1974                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
1975                     } else {
1976                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
1977                     }
1978                 }
1979                 
1980                 # Message already send, do not need to do anything more, otherwise ...
1981                 if ($delivered2host) { next;}
1982     
1983                 # ...looking for host in foreign_clients_db
1984                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1985                 $res = $foreign_clients_db->exec_statement($sql);
1986   
1987                                 # Host is known in foreign_clients_db 
1988                                 if (ref(@$res[0]) eq "ARRAY") { 
1989                     my $registration_server = @{@{$res}[0]}[2];
1990                     
1991                     # Fetch encryption key for registration server
1992                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1993                     my $res = $known_server_db->exec_statement($sql);
1994                     if (ref(@$res[0]) eq "ARRAY") { 
1995                         my $registration_server_key = @{@{$res}[0]}[3];
1996                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1997                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1998                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
1999                         if ($error == 0 ) {
2000                             $send_succeed++ ;
2001                             $delivered2host++ ;
2002                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
2003                         } else {
2004                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
2005                         }
2007                     } else {
2008                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2009                                 "registrated at server '$registration_server', ".
2010                                 "but no data available in known_server_db ", 1); 
2011                     }
2012                 }
2013                 
2014                 if (not $delivered2host) {
2015                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2016                 }
2017                 }
2019                 if ($send_succeed) {
2020                                 # set outgoing msg at db to deliverd
2021                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
2022                                 my $res = $messaging_db->exec_statement($sql); 
2023                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2024                 } else {
2025             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
2026         }
2027         }
2029     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
2030     return;
2034 sub watch_for_done_messages {
2035     my ($kernel,$heap) = @_[KERNEL, HEAP];
2037     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
2038     #&daemon_log("0 DEBUG: $sql", 7);
2039     my $res = $messaging_db->exec_statement($sql); 
2041     foreach my $hit (@{$res}) {
2042         my $msg_id = @{$hit}[0];
2044         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
2045         #&daemon_log("0 DEBUG: $sql", 7); 
2046         my $res = $messaging_db->exec_statement($sql);
2048         # not all usr msgs have been seen till now
2049         if ( ref(@$res[0]) eq "ARRAY") { next; }
2050         
2051         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
2052         #&daemon_log("0 DEBUG: $sql", 7);
2053         $res = $messaging_db->exec_statement($sql);
2054     
2055     }
2057     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2058     return;
2062 sub watch_for_old_known_clients {
2063     my ($kernel,$heap) = @_[KERNEL, HEAP];
2065     my $sql_statement = "SELECT * FROM $known_clients_tn";
2066     my $res = $known_clients_db->select_dbentry( $sql_statement );
2068     my $act_time = int(&get_time());
2070     while ( my ($hit_num, $hit) = each %$res) {
2071         my $expired_timestamp = int($hit->{'timestamp'});
2072         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2073         my $dt = DateTime->new( year   => $1,
2074                 month  => $2,
2075                 day    => $3,
2076                 hour   => $4,
2077                 minute => $5,
2078                 second => $6,
2079                 );
2081         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2082         $expired_timestamp = $dt->ymd('').$dt->hms('');
2083         if ($act_time > $expired_timestamp) {
2084             my $hostname = $hit->{'hostname'};
2085             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2086             my $del_res = $known_clients_db->exec_statement($del_sql);
2088             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2089         }
2091     }
2093     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2097 sub watch_for_next_tasks {
2098     my ($kernel,$heap) = @_[KERNEL, HEAP];
2100     my $sql = "SELECT * FROM $incoming_tn";
2101     my $res = $incoming_db->select_dbentry($sql);
2103     while ( my ($hit_num, $hit) = each %$res) {
2104         my $headertag = $hit->{'headertag'};
2105         if ($headertag =~ /^answer_(\d+)/) {
2106             # do not start processing, this message is for a still running POE::Wheel
2107             next;
2108         }
2109         my $message_id = $hit->{'id'};
2110         $kernel->yield('next_task', $hit);
2112         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2113         my $res = $incoming_db->exec_statement($sql);
2114     }
2116     $kernel->delay_set('watch_for_next_tasks', 0.1); 
2120 sub get_ldap_handle {
2121         my ($session_id) = @_;
2122         my $heap;
2123         my $ldap_handle;
2125         if (not defined $session_id ) { $session_id = 0 };
2126         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2128         if ($session_id == 0) {
2129                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
2130                 $ldap_handle = Net::LDAP->new( $ldap_uri );
2131                 if (defined $ldap_handle) {
2132                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
2133                 } else {
2134                         daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2135                 }
2137         } else {
2138                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2139                 if( defined $session_reference ) {
2140                         $heap = $session_reference->get_heap();
2141                 }
2143                 if (not defined $heap) {
2144                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
2145                         return;
2146                 }
2148                 # TODO: This "if" is nonsense, because it doesn't prove that the
2149                 #       used handle is still valid - or if we've to reconnect...
2150                 #if (not exists $heap->{ldap_handle}) {
2151                         $ldap_handle = Net::LDAP->new( $ldap_uri );
2152                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!"); 
2153                         $heap->{ldap_handle} = $ldap_handle;
2154                 #}
2155         }
2156         return $ldap_handle;
2160 sub change_fai_state {
2161     my ($st, $targets, $session_id) = @_;
2162     $session_id = 0 if not defined $session_id;
2163     # Set FAI state to localboot
2164     my %mapActions= (
2165         reboot    => '',
2166         update    => 'softupdate',
2167         localboot => 'localboot',
2168         reinstall => 'install',
2169         rescan    => '',
2170         wake      => '',
2171         memcheck  => 'memcheck',
2172         sysinfo   => 'sysinfo',
2173         install   => 'install',
2174     );
2176     # Return if this is unknown
2177     if (!exists $mapActions{ $st }){
2178         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2179       return;
2180     }
2182     my $state= $mapActions{ $st };
2184     my $ldap_handle = &get_ldap_handle($session_id);
2185     if( defined($ldap_handle) ) {
2187       # Build search filter for hosts
2188         my $search= "(&(objectClass=GOhard)";
2189         foreach (@{$targets}){
2190             $search.= "(macAddress=$_)";
2191         }
2192         $search.= ")";
2194       # If there's any host inside of the search string, procress them
2195         if (!($search =~ /macAddress/)){
2196             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2197             return;
2198         }
2200       # Perform search for Unit Tag
2201       my $mesg = $ldap_handle->search(
2202           base   => $ldap_base,
2203           scope  => 'sub',
2204           attrs  => ['dn', 'FAIstate', 'objectClass'],
2205           filter => "$search"
2206           );
2208           if ($mesg->count) {
2209                   my @entries = $mesg->entries;
2210                   if (0 == @entries) {
2211                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2212                   }
2214                   foreach my $entry (@entries) {
2215                           # Only modify entry if it is not set to '$state'
2216                           if ($entry->get_value("FAIstate") ne "$state"){
2217                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2218                                   my $result;
2219                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2220                                   if (exists $tmp{'FAIobject'}){
2221                                           if ($state eq ''){
2222                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2223                                                           delete => [ FAIstate => [] ] ]);
2224                                           } else {
2225                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2226                                                           replace => [ FAIstate => $state ] ]);
2227                                           }
2228                                   } elsif ($state ne ''){
2229                                           $result= $ldap_handle->modify($entry->dn, changes => [
2230                                                   add     => [ objectClass => 'FAIobject' ],
2231                                                   add     => [ FAIstate => $state ] ]);
2232                                   }
2234                                   # Errors?
2235                                   if ($result->code){
2236                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2237                                   }
2238                           } else {
2239                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
2240                           }  
2241                   }
2242           } else {
2243                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2244           }
2246     # if no ldap handle defined
2247     } else {
2248         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
2249     }
2251         return;
2255 sub change_goto_state {
2256     my ($st, $targets, $session_id) = @_;
2257     $session_id = 0  if not defined $session_id;
2259     # Switch on or off?
2260     my $state= $st eq 'active' ? 'active': 'locked';
2262     my $ldap_handle = &get_ldap_handle($session_id);
2263     if( defined($ldap_handle) ) {
2265       # Build search filter for hosts
2266       my $search= "(&(objectClass=GOhard)";
2267       foreach (@{$targets}){
2268         $search.= "(macAddress=$_)";
2269       }
2270       $search.= ")";
2272       # If there's any host inside of the search string, procress them
2273       if (!($search =~ /macAddress/)){
2274         return;
2275       }
2277       # Perform search for Unit Tag
2278       my $mesg = $ldap_handle->search(
2279           base   => $ldap_base,
2280           scope  => 'sub',
2281           attrs  => ['dn', 'gotoMode'],
2282           filter => "$search"
2283           );
2285       if ($mesg->count) {
2286         my @entries = $mesg->entries;
2287         foreach my $entry (@entries) {
2289           # Only modify entry if it is not set to '$state'
2290           if ($entry->get_value("gotoMode") ne $state){
2292             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2293             my $result;
2294             $result= $ldap_handle->modify($entry->dn, changes => [
2295                                                 replace => [ gotoMode => $state ] ]);
2297             # Errors?
2298             if ($result->code){
2299               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2300             }
2302           }
2303         }
2304       } else {
2305                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2306           }
2308     }
2312 sub run_recreate_packages_db {
2313     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2314     my $session_id = $session->ID;
2315         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2316         $kernel->yield('create_fai_release_db', $fai_release_tn);
2317         $kernel->yield('create_fai_server_db', $fai_server_tn);
2318         return;
2322 sub run_create_fai_server_db {
2323     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2324     my $session_id = $session->ID;
2325     my $task = POE::Wheel::Run->new(
2326             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2327             StdoutEvent  => "session_run_result",
2328             StderrEvent  => "session_run_debug",
2329             CloseEvent   => "session_run_done",
2330             );
2332     $heap->{task}->{ $task->ID } = $task;
2333     return;
2337 sub create_fai_server_db {
2338     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2339         my $result;
2341         if (not defined $session_id) { $session_id = 0; }
2342     my $ldap_handle = &get_ldap_handle();
2343         if(defined($ldap_handle)) {
2344                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2345                 my $mesg= $ldap_handle->search(
2346                         base   => $ldap_base,
2347                         scope  => 'sub',
2348                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2349                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2350                 );
2351                 if($mesg->{'resultCode'} == 0 &&
2352                    $mesg->count != 0) {
2353                    foreach my $entry (@{$mesg->{entries}}) {
2354                            if($entry->exists('FAIrepository')) {
2355                                    # Add an entry for each Repository configured for server
2356                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2357                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2358                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2359                                                    $result= $fai_server_db->add_dbentry( { 
2360                                                                    table => $table_name,
2361                                                                    primkey => ['server', 'release', 'tag'],
2362                                                                    server => $tmp_url,
2363                                                                    release => $tmp_release,
2364                                                                    sections => $tmp_sections,
2365                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
2366                                                            } );
2367                                            }
2368                                    }
2369                            }
2370                    }
2371                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2373                 # TODO: Find a way to post the 'create_packages_list_db' event
2374                 if(not defined($dont_create_packages_list)) {
2375                         &create_packages_list_db(undef, undef, $session_id);
2376                 }
2377         }       
2378     
2379     $ldap_handle->disconnect;
2380         return $result;
2384 sub run_create_fai_release_db {
2385     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2386         my $session_id = $session->ID;
2387     my $task = POE::Wheel::Run->new(
2388             Program => sub { &create_fai_release_db($table_name, $session_id) },
2389             StdoutEvent  => "session_run_result",
2390             StderrEvent  => "session_run_debug",
2391             CloseEvent   => "session_run_done",
2392             );
2394     $heap->{task}->{ $task->ID } = $task;
2395     return;
2399 sub create_fai_release_db {
2400         my ($table_name, $session_id) = @_;
2401         my $result;
2403     # used for logging
2404     if (not defined $session_id) { $session_id = 0; }
2406     my $ldap_handle = &get_ldap_handle();
2407         if(defined($ldap_handle)) {
2408                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2409                 my $mesg= $ldap_handle->search(
2410                         base   => $ldap_base,
2411                         scope  => 'sub',
2412                         attrs  => [],
2413                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2414                 );
2415                 if(($mesg->code == 0) && ($mesg->count != 0))
2416                 {
2417                         daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,8);
2419                         # Walk through all possible FAI container ou's
2420                         my @sql_list;
2421                         my $timestamp= &get_time();
2422                         foreach my $ou (@{$mesg->{entries}}) {
2423                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2424                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2425                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2426                                         if(@tmp_array) {
2427                                                 foreach my $entry (@tmp_array) {
2428                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2429                                                                 my $sql= 
2430                                                                 "INSERT INTO $table_name "
2431                                                                 ."(timestamp, release, class, type, state) VALUES ("
2432                                                                 .$timestamp.","
2433                                                                 ."'".$entry->{'release'}."',"
2434                                                                 ."'".$entry->{'class'}."',"
2435                                                                 ."'".$entry->{'type'}."',"
2436                                                                 ."'".$entry->{'state'}."')";
2437                                                                 push @sql_list, $sql;
2438                                                         }
2439                                                 }
2440                                         }
2441                                 }
2442                         }
2444                         daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",8);
2445                         if(@sql_list) {
2446                                 unshift @sql_list, "VACUUM";
2447                                 unshift @sql_list, "DELETE FROM $table_name";
2448                                 $fai_release_db->exec_statementlist(\@sql_list);
2449                         }
2450                         daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",7);
2451                 } else {
2452                         daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code ,5);
2453                 }
2454                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2455         }
2456     $ldap_handle->disconnect;
2457         return $result;
2460 sub get_fai_types {
2461         my $tmp_classes = shift || return undef;
2462         my @result;
2464         foreach my $type(keys %{$tmp_classes}) {
2465                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2466                         my $entry = {
2467                                 type => $type,
2468                                 state => $tmp_classes->{$type}[0],
2469                         };
2470                         push @result, $entry;
2471                 }
2472         }
2474         return @result;
2477 sub get_fai_state {
2478         my $result = "";
2479         my $tmp_classes = shift || return $result;
2481         foreach my $type(keys %{$tmp_classes}) {
2482                 if(defined($tmp_classes->{$type}[0])) {
2483                         $result = $tmp_classes->{$type}[0];
2484                         
2485                 # State is equal for all types in class
2486                         last;
2487                 }
2488         }
2490         return $result;
2493 sub resolve_fai_classes {
2494         my ($fai_base, $ldap_handle, $session_id) = @_;
2495         if (not defined $session_id) { $session_id = 0; }
2496         my $result;
2497         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2498         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2499         my $fai_classes;
2501         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2502         my $mesg= $ldap_handle->search(
2503                 base   => $fai_base,
2504                 scope  => 'sub',
2505                 attrs  => ['cn','objectClass','FAIstate'],
2506                 filter => $fai_filter,
2507         );
2508         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2510         if($mesg->{'resultCode'} == 0 &&
2511                 $mesg->count != 0) {
2512                 foreach my $entry (@{$mesg->{entries}}) {
2513                         if($entry->exists('cn')) {
2514                                 my $tmp_dn= $entry->dn();
2515                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2516                                         - length($fai_base) - 1 );
2518                                 # Skip classname and ou dn parts for class
2519                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2521                                 # Skip classes without releases
2522                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2523                                         next;
2524                                 }
2526                                 my $tmp_cn= $entry->get_value('cn');
2527                                 my $tmp_state= $entry->get_value('FAIstate');
2529                                 my $tmp_type;
2530                                 # Get FAI type
2531                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2532                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2533                                                 $tmp_type= $oclass;
2534                                                 last;
2535                                         }
2536                                 }
2538                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2539                                         # A Subrelease
2540                                         my @sub_releases = split(/,/, $tmp_release);
2542                                         # Walk through subreleases and build hash tree
2543                                         my $hash;
2544                                         while(my $tmp_sub_release = pop @sub_releases) {
2545                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2546                                         }
2547                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2548                                 } else {
2549                                         # A branch, no subrelease
2550                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2551                                 }
2552                         } elsif (!$entry->exists('cn')) {
2553                                 my $tmp_dn= $entry->dn();
2554                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2555                                         - length($fai_base) - 1 );
2556                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2558                                 # Skip classes without releases
2559                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2560                                         next;
2561                                 }
2563                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2564                                         # A Subrelease
2565                                         my @sub_releases= split(/,/, $tmp_release);
2567                                         # Walk through subreleases and build hash tree
2568                                         my $hash;
2569                                         while(my $tmp_sub_release = pop @sub_releases) {
2570                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2571                                         }
2572                                         # Remove the last two characters
2573                                         chop($hash);
2574                                         chop($hash);
2576                                         eval('$fai_classes->'.$hash.'= {}');
2577                                 } else {
2578                                         # A branch, no subrelease
2579                                         if(!exists($fai_classes->{$tmp_release})) {
2580                                                 $fai_classes->{$tmp_release} = {};
2581                                         }
2582                                 }
2583                         }
2584                 }
2586                 # The hash is complete, now we can honor the copy-on-write based missing entries
2587                 foreach my $release (keys %$fai_classes) {
2588                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2589                 }
2590         }
2591         return $result;
2594 sub apply_fai_inheritance {
2595        my $fai_classes = shift || return {};
2596        my $tmp_classes;
2598        # Get the classes from the branch
2599        foreach my $class (keys %{$fai_classes}) {
2600                # Skip subreleases
2601                if($class =~ /^ou=.*$/) {
2602                        next;
2603                } else {
2604                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2605                }
2606        }
2608        # Apply to each subrelease
2609        foreach my $subrelease (keys %{$fai_classes}) {
2610                if($subrelease =~ /ou=/) {
2611                        foreach my $tmp_class (keys %{$tmp_classes}) {
2612                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2613                                        $fai_classes->{$subrelease}->{$tmp_class} =
2614                                        deep_copy($tmp_classes->{$tmp_class});
2615                                } else {
2616                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2617                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2618                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2619                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2620                                                }
2621                                        }
2622                                }
2623                        }
2624                }
2625        }
2627        # Find subreleases in deeper levels
2628        foreach my $subrelease (keys %{$fai_classes}) {
2629                if($subrelease =~ /ou=/) {
2630                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2631                                if($subsubrelease =~ /ou=/) {
2632                                        apply_fai_inheritance($fai_classes->{$subrelease});
2633                                }
2634                        }
2635                }
2636        }
2638        return $fai_classes;
2641 sub get_fai_release_entries {
2642         my $tmp_classes = shift || return;
2643         my $parent = shift || "";
2644         my @result = shift || ();
2646         foreach my $entry (keys %{$tmp_classes}) {
2647                 if(defined($entry)) {
2648                         if($entry =~ /^ou=.*$/) {
2649                                 my $release_name = $entry;
2650                                 $release_name =~ s/ou=//g;
2651                                 if(length($parent)>0) {
2652                                         $release_name = $parent."/".$release_name;
2653                                 }
2654                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2655                                 foreach my $bufentry(@bufentries) {
2656                                         push @result, $bufentry;
2657                                 }
2658                         } else {
2659                                 my @types = get_fai_types($tmp_classes->{$entry});
2660                                 foreach my $type (@types) {
2661                                         push @result, 
2662                                         {
2663                                                 'class' => $entry,
2664                                                 'type' => $type->{'type'},
2665                                                 'release' => $parent,
2666                                                 'state' => $type->{'state'},
2667                                         };
2668                                 }
2669                         }
2670                 }
2671         }
2673         return @result;
2676 sub deep_copy {
2677         my $this = shift;
2678         if (not ref $this) {
2679                 $this;
2680         } elsif (ref $this eq "ARRAY") {
2681                 [map deep_copy($_), @$this];
2682         } elsif (ref $this eq "HASH") {
2683                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2684         } else { die "what type is $_?" }
2688 sub session_run_result {
2689     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2690     $kernel->sig(CHLD => "child_reap");
2693 sub session_run_debug {
2694     my $result = $_[ARG0];
2695     print STDERR "$result\n";
2698 sub session_run_done {
2699     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2700     delete $heap->{task}->{$task_id};
2704 sub create_sources_list {
2705         my $session_id = shift;
2706         my $ldap_handle = &main::get_ldap_handle;
2707         my $result="/tmp/gosa_si_tmp_sources_list";
2709         # Remove old file
2710         if(stat($result)) {
2711                 unlink($result);
2712                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2713         }
2715         my $fh;
2716         open($fh, ">$result");
2717         if (not defined $fh) {
2718                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2719                 return undef;
2720         }
2721         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2722                 my $mesg=$ldap_handle->search(
2723                         base    => $main::ldap_server_dn,
2724                         scope   => 'base',
2725                         attrs   => 'FAIrepository',
2726                         filter  => 'objectClass=FAIrepositoryServer'
2727                 );
2728                 if($mesg->count) {
2729                         foreach my $entry(@{$mesg->{'entries'}}) {
2730                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2731                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2732                                         my $line = "deb $server $release";
2733                                         $sections =~ s/,/ /g;
2734                                         $line.= " $sections";
2735                                         print $fh $line."\n";
2736                                 }
2737                         }
2738                 }
2739         } else {
2740                 if (defined $main::ldap_server_dn){
2741                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2742                 } else {
2743                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2744                 }
2745         }
2746         close($fh);
2748         return $result;
2752 sub run_create_packages_list_db {
2753     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2754         my $session_id = $session->ID;
2756         my $task = POE::Wheel::Run->new(
2757                                         Priority => +20,
2758                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2759                                         StdoutEvent  => "session_run_result",
2760                                         StderrEvent  => "session_run_debug",
2761                                         CloseEvent   => "session_run_done",
2762                                         );
2763         $heap->{task}->{ $task->ID } = $task;
2767 sub create_packages_list_db {
2768         my ($ldap_handle, $sources_file, $session_id) = @_;
2769         
2770         # it should not be possible to trigger a recreation of packages_list_db
2771         # while packages_list_db is under construction, so set flag packages_list_under_construction
2772         # which is tested befor recreation can be started
2773         if (-r $packages_list_under_construction) {
2774                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2775                 return;
2776         } else {
2777                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2778                 # set packages_list_under_construction to true
2779                 system("touch $packages_list_under_construction");
2780                 @packages_list_statements=();
2781         }
2783         if (not defined $session_id) { $session_id = 0; }
2784         if (not defined $ldap_handle) { 
2785                 $ldap_handle= &get_ldap_handle();
2787                 if (not defined $ldap_handle) {
2788                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2789                         unlink($packages_list_under_construction);
2790                         return;
2791                 }
2792         }
2793         if (not defined $sources_file) { 
2794                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2795                 $sources_file = &create_sources_list($session_id);
2796         }
2798         if (not defined $sources_file) {
2799                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2800                 unlink($packages_list_under_construction);
2801                 return;
2802         }
2804         my $line;
2806         open(CONFIG, "<$sources_file") or do {
2807                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2808                 unlink($packages_list_under_construction);
2809                 return;
2810         };
2812         # Read lines
2813         while ($line = <CONFIG>){
2814                 # Unify
2815                 chop($line);
2816                 $line =~ s/^\s+//;
2817                 $line =~ s/^\s+/ /;
2819                 # Strip comments
2820                 $line =~ s/#.*$//g;
2822                 # Skip empty lines
2823                 if ($line =~ /^\s*$/){
2824                         next;
2825                 }
2827                 # Interpret deb line
2828                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2829                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2830                         my $section;
2831                         foreach $section (split(' ', $sections)){
2832                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2833                         }
2834                 }
2835         }
2837         close (CONFIG);
2840         if(keys(%repo_dirs)) {
2841                 find(\&cleanup_and_extract, keys( %repo_dirs ));
2842                 &main::strip_packages_list_statements();
2843                 unshift @packages_list_statements, "VACUUM";
2844                 $packages_list_db->exec_statementlist(\@packages_list_statements);
2845         }
2846         unlink($packages_list_under_construction);
2847         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2848         return;
2851 # This function should do some intensive task to minimize the db-traffic
2852 sub strip_packages_list_statements {
2853     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2854         my @new_statement_list=();
2855         my $hash;
2856         my $insert_hash;
2857         my $update_hash;
2858         my $delete_hash;
2859         my $local_timestamp=get_time();
2861         foreach my $existing_entry (@existing_entries) {
2862                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2863         }
2865         foreach my $statement (@packages_list_statements) {
2866                 if($statement =~ /^INSERT/i) {
2867                         # Assign the values from the insert statement
2868                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2869                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2870                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2871                                 # If section or description has changed, update the DB
2872                                 if( 
2873                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2874                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2875                                 ) {
2876                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2877                                 }
2878                         } else {
2879                                 # Insert a non-existing entry to db
2880                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2881                         }
2882                 } elsif ($statement =~ /^UPDATE/i) {
2883                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2884                         /^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;
2885                         foreach my $distribution (keys %{$hash}) {
2886                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2887                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2888                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2889                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2890                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2891                                                 my $section;
2892                                                 my $description;
2893                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2894                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2895                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2896                                                 }
2897                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2898                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2899                                                 }
2900                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2901                                         }
2902                                 }
2903                         }
2904                 }
2905         }
2907         # TODO: Check for orphaned entries
2909         # unroll the insert_hash
2910         foreach my $distribution (keys %{$insert_hash}) {
2911                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2912                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2913                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2914                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2915                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2916                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2917                                 ."'$local_timestamp')";
2918                         }
2919                 }
2920         }
2922         # unroll the update hash
2923         foreach my $distribution (keys %{$update_hash}) {
2924                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2925                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2926                                 my $set = "";
2927                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2928                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2929                                 }
2930                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2931                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2932                                 }
2933                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2934                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2935                                 }
2936                                 if(defined($set) and length($set) > 0) {
2937                                         $set .= "timestamp = '$local_timestamp'";
2938                                 } else {
2939                                         next;
2940                                 }
2941                                 push @new_statement_list, 
2942                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2943                                         ." distribution = '$distribution'"
2944                                         ." AND package = '$package'"
2945                                         ." AND version = '$version'";
2946                         }
2947                 }
2948         }
2950         @packages_list_statements = @new_statement_list;
2954 sub parse_package_info {
2955     my ($baseurl, $dist, $section, $session_id)= @_;
2956     my ($package);
2957     if (not defined $session_id) { $session_id = 0; }
2958     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2959     $repo_dirs{ "${repo_path}/pool" } = 1;
2961     foreach $package ("Packages.gz"){
2962         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2963         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2964         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2965     }
2966     
2970 sub get_package {
2971     my ($url, $dest, $session_id)= @_;
2972     if (not defined $session_id) { $session_id = 0; }
2974     my $tpath = dirname($dest);
2975     -d "$tpath" || mkpath "$tpath";
2977     # This is ugly, but I've no time to take a look at "how it works in perl"
2978     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2979         system("gunzip -cd '$dest' > '$dest.in'");
2980         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2981         unlink($dest);
2982         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2983     } else {
2984         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
2985     }
2986     return 0;
2990 sub parse_package {
2991     my ($path, $dist, $srv_path, $session_id)= @_;
2992     if (not defined $session_id) { $session_id = 0;}
2993     my ($package, $version, $section, $description);
2994     my $PACKAGES;
2995     my $timestamp = &get_time();
2997     if(not stat("$path.in")) {
2998         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2999         return;
3000     }
3002     open($PACKAGES, "<$path.in");
3003     if(not defined($PACKAGES)) {
3004         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
3005         return;
3006     }
3008     # Read lines
3009     while (<$PACKAGES>){
3010         my $line = $_;
3011         # Unify
3012         chop($line);
3014         # Use empty lines as a trigger
3015         if ($line =~ /^\s*$/){
3016             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3017             push(@packages_list_statements, $sql);
3018             $package = "none";
3019             $version = "none";
3020             $section = "none";
3021             $description = "none"; 
3022             next;
3023         }
3025         # Trigger for package name
3026         if ($line =~ /^Package:\s/){
3027             ($package)= ($line =~ /^Package: (.*)$/);
3028             next;
3029         }
3031         # Trigger for version
3032         if ($line =~ /^Version:\s/){
3033             ($version)= ($line =~ /^Version: (.*)$/);
3034             next;
3035         }
3037         # Trigger for description
3038         if ($line =~ /^Description:\s/){
3039             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3040             next;
3041         }
3043         # Trigger for section
3044         if ($line =~ /^Section:\s/){
3045             ($section)= ($line =~ /^Section: (.*)$/);
3046             next;
3047         }
3049         # Trigger for filename
3050         if ($line =~ /^Filename:\s/){
3051             my ($filename) = ($line =~ /^Filename: (.*)$/);
3052             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3053             next;
3054         }
3055     }
3057     close( $PACKAGES );
3058     unlink( "$path.in" );
3062 sub store_fileinfo {
3063     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3065     my %fileinfo = (
3066         'package' => $package,
3067         'dist' => $dist,
3068         'version' => $vers,
3069     );
3071     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3075 sub cleanup_and_extract {
3076     my $fileinfo = $repo_files{ $File::Find::name };
3078     if( defined $fileinfo ) {
3080         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3081         my $sql;
3082         my $package = $fileinfo->{ 'package' };
3083         my $newver = $fileinfo->{ 'version' };
3085         mkpath($dir);
3086         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3088                 if( -f "$dir/DEBIAN/templates" ) {
3090                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
3092                         my $tmpl= "";
3093                         {
3094                                 local $/=undef;
3095                                 open FILE, "$dir/DEBIAN/templates";
3096                                 $tmpl = &encode_base64(<FILE>);
3097                                 close FILE;
3098                         }
3099                         rmtree("$dir/DEBIAN/templates");
3101                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3102                 push @packages_list_statements, $sql;
3103                 }
3104     }
3106     return;
3110 sub register_at_foreign_servers {   
3111     my ($kernel) = $_[KERNEL];
3113     # hole alle bekannten server aus known_server_db
3114     my $server_sql = "SELECT * FROM $known_server_tn";
3115     my $server_res = $known_server_db->exec_statement($server_sql);
3117     # no entries in known_server_db
3118     if (not ref(@$server_res[0]) eq "ARRAY") { 
3119         # TODO
3120     }
3122     # detect already connected clients
3123     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3124     my $client_res = $known_clients_db->exec_statement($client_sql);
3126     # send my server details to all other gosa-si-server within the network
3127     foreach my $hit (@$server_res) {
3128         my $hostname = @$hit[0];
3129         my $hostkey = &create_passwd;
3131         # add already connected clients to registration message 
3132         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3133         &add_content2xml_hash($myhash, 'key', $hostkey);
3134         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3136         # add locally loaded gosa-si modules to registration message
3137         my $loaded_modules = {};
3138         while (my ($package, $pck_info) = each %$known_modules) {
3139                                                 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3140                                                 foreach my $act_module (keys(%{@$pck_info[2]})) {
3141                                                         $loaded_modules->{$act_module} = ""; 
3142                                                 }
3143         }
3145         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3147         # add macaddress to registration message
3148         my ($host_ip, $host_port) = split(/:/, $hostname);
3149         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3150         my $network_interface= &get_interface_for_ip($local_ip);
3151         my $host_mac = &get_mac_for_interface($network_interface);
3152         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3153         
3154         # build registration message and send it
3155         my $foreign_server_msg = &create_xml_string($myhash);
3156         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3157     }
3158     
3159     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
3160     return;
3164 #==== MAIN = main ==============================================================
3165 #  parse commandline options
3166 Getopt::Long::Configure( "bundling" );
3167 GetOptions("h|help" => \&usage,
3168         "c|config=s" => \$cfg_file,
3169         "f|foreground" => \$foreground,
3170         "v|verbose+" => \$verbose,
3171         "no-arp+" => \$no_arp,
3172            );
3174 #  read and set config parameters
3175 &check_cmdline_param ;
3176 &read_configfile($cfg_file, %cfg_defaults);
3177 &check_pid;
3179 $SIG{CHLD} = 'IGNORE';
3181 # forward error messages to logfile
3182 if( ! $foreground ) {
3183   open( STDIN,  '+>/dev/null' );
3184   open( STDOUT, '+>&STDIN'    );
3185   open( STDERR, '+>&STDIN'    );
3188 # Just fork, if we are not in foreground mode
3189 if( ! $foreground ) { 
3190     chdir '/'                 or die "Can't chdir to /: $!";
3191     $pid = fork;
3192     setsid                    or die "Can't start a new session: $!";
3193     umask 0;
3194 } else { 
3195     $pid = $$; 
3198 # Do something useful - put our PID into the pid_file
3199 if( 0 != $pid ) {
3200     open( LOCK_FILE, ">$pid_file" );
3201     print LOCK_FILE "$pid\n";
3202     close( LOCK_FILE );
3203     if( !$foreground ) { 
3204         exit( 0 ) 
3205     };
3208 # parse head url and revision from svn
3209 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3210 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3211 $server_headURL = defined $1 ? $1 : 'unknown' ;
3212 $server_revision = defined $2 ? $2 : 'unknown' ;
3213 if ($server_headURL =~ /\/tag\// || 
3214         $server_headURL =~ /\/branches\// ) {
3215     $server_status = "stable"; 
3216 } else {
3217     $server_status = "developmental" ;
3220 # Prepare log file
3221 $root_uid = getpwnam('root');
3222 $adm_gid = getgrnam('adm');
3223 chmod(0640, $log_file);
3224 chown($root_uid, $adm_gid, $log_file);
3225 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3227 daemon_log(" ", 1);
3228 daemon_log("$0 started!", 1);
3229 daemon_log("status: $server_status", 1);
3230 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3232 # connect to incoming_db
3233 unlink($incoming_file_name);
3234 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3235 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3237 # connect to gosa-si job queue
3238 unlink($job_queue_file_name);  ## just for debugging
3239 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3240 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3241 chmod(0660, $job_queue_file_name);
3242 chown($root_uid, $adm_gid, $job_queue_file_name);
3244 # connect to known_clients_db
3245 unlink($known_clients_file_name);   ## just for debugging
3246 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3247 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3248 chmod(0660, $known_clients_file_name);
3249 chown($root_uid, $adm_gid, $known_clients_file_name);
3251 # connect to foreign_clients_db
3252 unlink($foreign_clients_file_name);
3253 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3254 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3255 chmod(0660, $foreign_clients_file_name);
3256 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3258 # connect to known_server_db
3259 unlink($known_server_file_name);
3260 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3261 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3262 chmod(0660, $known_server_file_name);
3263 chown($root_uid, $adm_gid, $known_server_file_name);
3265 # connect to login_usr_db
3266 unlink($login_users_file_name);
3267 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3268 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3269 chmod(0660, $login_users_file_name);
3270 chown($root_uid, $adm_gid, $login_users_file_name);
3272 # connect to fai_server_db 
3273 unlink($fai_server_file_name);
3274 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3275 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3276 chmod(0660, $fai_server_file_name);
3277 chown($root_uid, $adm_gid, $fai_server_file_name);
3279 # connect to fai_release_db
3280 unlink($fai_release_file_name);
3281 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3282 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3283 chmod(0660, $fai_release_file_name);
3284 chown($root_uid, $adm_gid, $fai_release_file_name);
3286 # connect to packages_list_db
3287 #unlink($packages_list_file_name);
3288 unlink($packages_list_under_construction);
3289 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3290 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3291 chmod(0660, $packages_list_file_name);
3292 chown($root_uid, $adm_gid, $packages_list_file_name);
3294 # connect to messaging_db
3295 unlink($messaging_file_name);
3296 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3297 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3298 chmod(0660, $messaging_file_name);
3299 chown($root_uid, $adm_gid, $messaging_file_name);
3302 # create xml object used for en/decrypting
3303 $xml = new XML::Simple();
3306 # foreign servers 
3307 my @foreign_server_list;
3309 # add foreign server from cfg file
3310 if ($foreign_server_string ne "") {
3311     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3312     foreach my $foreign_server (@cfg_foreign_server_list) {
3313         push(@foreign_server_list, $foreign_server);
3314     }
3316     daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3319 # Perform a DNS lookup for server registration if flag is true
3320 if ($dns_lookup eq "true") {
3321     # Add foreign server from dns
3322     my @tmp_servers;
3323     if (not $server_domain) {
3324         # Try our DNS Searchlist
3325         for my $domain(get_dns_domains()) {
3326             chomp($domain);
3327             my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3328             if(@$tmp_domains) {
3329                 for my $tmp_server(@$tmp_domains) {
3330                     push @tmp_servers, $tmp_server;
3331                 }
3332             }
3333         }
3334         if(@tmp_servers && length(@tmp_servers)==0) {
3335             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3336         }
3337     } else {
3338         @tmp_servers = &get_server_addresses($server_domain);
3339         if( 0 == @tmp_servers ) {
3340             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3341         }
3342     }
3344     daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3346     foreach my $server (@tmp_servers) { 
3347         unshift(@foreign_server_list, $server); 
3348     }
3349 } else {
3350     daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3354 # eliminate duplicate entries
3355 @foreign_server_list = &del_doubles(@foreign_server_list);
3356 my $all_foreign_server = join(", ", @foreign_server_list);
3357 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3359 # add all found foreign servers to known_server
3360 my $act_timestamp = &get_time();
3361 foreach my $foreign_server (@foreign_server_list) {
3363         # do not add myself to known_server_db
3364         if (&is_local($foreign_server)) { next; }
3365         ######################################
3367     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3368             primkey=>['hostname'],
3369             hostname=>$foreign_server,
3370             macaddress=>"",
3371             status=>'not_jet_registered',
3372             hostkey=>"none",
3373             loaded_modules => "none", 
3374             timestamp=>$act_timestamp,
3375             } );
3379 # Import all modules
3380 &import_modules;
3382 # Check wether all modules are gosa-si valid passwd check
3383 &password_check;
3385 # Prepare for using Opsi 
3386 if ($opsi_enabled eq "true") {
3387     use JSON::RPC::Client;
3388     use XML::Quote qw(:all);
3389     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3390     $opsi_client = new JSON::RPC::Client;
3394 POE::Component::Server::TCP->new(
3395     Alias => "TCP_SERVER",
3396         Port => $server_port,
3397         ClientInput => sub {
3398         my ($kernel, $input) = @_[KERNEL, ARG0];
3399         push(@tasks, $input);
3400         push(@msgs_to_decrypt, $input);
3401         $kernel->yield("msg_to_decrypt");
3402         },
3403     InlineStates => {
3404         msg_to_decrypt => \&msg_to_decrypt,
3405         next_task => \&next_task,
3406         task_result => \&handle_task_result,
3407         task_done   => \&handle_task_done,
3408         task_debug  => \&handle_task_debug,
3409         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3410     }
3411 );
3413 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3415 # create session for repeatedly checking the job queue for jobs
3416 POE::Session->create(
3417         inline_states => {
3418                 _start => \&session_start,
3419         register_at_foreign_servers => \&register_at_foreign_servers,
3420         sig_handler => \&sig_handler,
3421         next_task => \&next_task,
3422         task_result => \&handle_task_result,
3423         task_done   => \&handle_task_done,
3424         task_debug  => \&handle_task_debug,
3425         watch_for_next_tasks => \&watch_for_next_tasks,
3426         watch_for_new_messages => \&watch_for_new_messages,
3427         watch_for_delivery_messages => \&watch_for_delivery_messages,
3428         watch_for_done_messages => \&watch_for_done_messages,
3429                 watch_for_new_jobs => \&watch_for_new_jobs,
3430         watch_for_modified_jobs => \&watch_for_modified_jobs,
3431         watch_for_done_jobs => \&watch_for_done_jobs,
3432         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3433         watch_for_old_known_clients => \&watch_for_old_known_clients,
3434         create_packages_list_db => \&run_create_packages_list_db,
3435         create_fai_server_db => \&run_create_fai_server_db,
3436         create_fai_release_db => \&run_create_fai_release_db,
3437                 recreate_packages_db => \&run_recreate_packages_db,
3438         session_run_result => \&session_run_result,
3439         session_run_debug => \&session_run_debug,
3440         session_run_done => \&session_run_done,
3441         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3442         }
3443 );
3446 POE::Kernel->run();
3447 exit;