Code

* gosa-si-server-nobus
[gosa.git] / gosa-si / gosa-si-server-nobus
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 (%cfg_defaults, $foreground, $verbose, $ping_timeout);
70 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
71 my ($server);
72 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
73 my ($messaging_db_loop_delay);
74 my ($known_modules);
75 my ($pid_file, $procid, $pid, $log_file);
76 my ($arp_activ, $arp_fifo);
77 my ($xml);
78 my $sources_list;
79 my $max_clients;
80 my %repo_files=();
81 my $repo_path;
82 my %repo_dirs=();
83 # variables declared in config file are always set to 'our'
84 our (%cfg_defaults, $log_file, $pid_file, 
85     $server_ip, $server_port, $ClientPackages_key, 
86     $arp_activ, $gosa_unit_tag,
87     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
88     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
89 );
91 # additional variable which should be globaly accessable
92 our $server_address;
93 our $server_mac_address;
94 our $bus_address;
95 our $gosa_address;
96 our $no_bus;
97 our $no_arp;
98 our $verbose;
99 our $forground;
100 our $cfg_file;
101 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
104 # specifies the verbosity of the daemon_log
105 $verbose = 0 ;
107 # if foreground is not null, script will be not forked to background
108 $foreground = 0 ;
110 # specifies the timeout seconds while checking the online status of a registrating client
111 $ping_timeout = 5;
113 $no_bus = 0;
114 $bus_activ = "true";
115 $no_arp = 0;
116 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
117 my @packages_list_statements;
118 my $watch_for_new_jobs_in_progress = 0;
120 # holds all incoming decrypted messages
121 our $incoming_db;
122 our $incoming_tn = 'incoming';
123 my $incoming_file_name;
124 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
125         "timestamp DEFAULT 'none'", 
126         "headertag DEFAULT 'none'",
127                 "targettag DEFAULT 'none'",
128         "xmlmessage DEFAULT 'none'",
129         "module DEFAULT 'none'",
130         );
132 # holds all gosa jobs
133 our $job_db;
134 our $job_queue_tn = 'jobs';
135 my $job_queue_file_name;
136 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
137                 "timestamp DEFAULT 'none'", 
138                 "status DEFAULT 'none'", 
139                 "result DEFAULT 'none'", 
140                 "progress DEFAULT 'none'", 
141         "headertag DEFAULT 'none'", 
142                 "targettag DEFAULT 'none'", 
143                 "xmlmessage DEFAULT 'none'", 
144                 "macaddress DEFAULT 'none'",
145                 "plainname DEFAULT 'none'",
146                 );
148 # holds all other gosa-sd as well as the gosa-sd-bus
149 our $known_server_db;
150 our $known_server_tn = "known_server";
151 my $known_server_file_name;
152 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
154 # holds all registrated clients
155 our $known_clients_db;
156 our $known_clients_tn = "known_clients";
157 my $known_clients_file_name;
158 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events");
160 # holds all logged in user at each client 
161 our $login_users_db;
162 our $login_users_tn = "login_users";
163 my $login_users_file_name;
164 my @login_users_col_names = ("client", "user", "timestamp");
166 # holds all fai server, the debian release and tag
167 our $fai_server_db;
168 our $fai_server_tn = "fai_server"; 
169 my $fai_server_file_name;
170 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
172 our $fai_release_db;
173 our $fai_release_tn = "fai_release"; 
174 my $fai_release_file_name;
175 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
177 # holds all packages available from different repositories
178 our $packages_list_db;
179 our $packages_list_tn = "packages_list";
180 my $packages_list_file_name;
181 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
182 my $outdir = "/tmp/packages_list_db";
183 my $arch = "i386"; 
185 # holds all messages which should be delivered to a user
186 our $messaging_db;
187 our $messaging_tn = "messaging"; 
188 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
189         "flag", "direction", "delivery_time", "message", "timestamp" );
190 my $messaging_file_name;
192 # path to directory to store client install log files
193 our $client_fai_log_dir = "/var/log/fai"; 
195 # queue which stores taskes until one of the $max_children children are ready to process the task
196 my @tasks = qw();
197 my @msgs_to_decrypt = qw();
198 my $max_children = 2;
201 %cfg_defaults = (
202 "general" => {
203     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
204     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
205     },
206 "bus" => {
207     "activ" => [\$bus_activ, "true"],
208     },
209 "server" => {
210     "port" => [\$server_port, "20081"],
211     "known-clients"        => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
212     "known-servers"        => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
213     "incoming"             => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
214     "login-users"          => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
215     "fai-server"           => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
216     "fai-release"          => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
217     "packages-list"        => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
218     "messaging"            => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
219     "source-list"          => [\$sources_list, '/etc/apt/sources.list'],
220     "repo-path"            => [\$repo_path, '/srv/www/repository'],
221     "ldap-uri"             => [\$ldap_uri, ""],
222     "ldap-base"            => [\$ldap_base, ""],
223     "ldap-admin-dn"        => [\$ldap_admin_dn, ""],
224     "ldap-admin-password"  => [\$ldap_admin_password, ""],
225     "gosa-unit-tag"        => [\$gosa_unit_tag, ""],
226     "max-clients"          => [\$max_clients, 10],
227     },
228 "GOsaPackages" => {
229     "ip" => [\$gosa_ip, "0.0.0.0"],
230     "port" => [\$gosa_port, "20082"],
231     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
232     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
233     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
234     "key" => [\$GosaPackages_key, "none"],
235     },
236 "ClientPackages" => {
237     "key" => [\$ClientPackages_key, "none"],
238     },
239 "ServerPackages"=> {
240     "address"      => [\$foreign_server_string, ""],
241     "domain"  => [\$server_domain, ""],
242     "key"     => [\$ServerPackages_key, "none"],
243     "key-lifetime" => [\$foreign_servers_register_delay, 600],
245 );
248 #===  FUNCTION  ================================================================
249 #         NAME:  usage
250 #   PARAMETERS:  nothing
251 #      RETURNS:  nothing
252 #  DESCRIPTION:  print out usage text to STDERR
253 #===============================================================================
254 sub usage {
255     print STDERR << "EOF" ;
256 usage: $prg [-hvf] [-c config]
258            -h        : this (help) message
259            -c <file> : config file
260            -f        : foreground, process will not be forked to background
261            -v        : be verbose (multiple to increase verbosity)
262            -no-bus   : starts $prg without connection to bus
263            -no-arp   : starts $prg without connection to arp module
264  
265 EOF
266     print "\n" ;
270 #===  FUNCTION  ================================================================
271 #         NAME:  read_configfile
272 #   PARAMETERS:  cfg_file - string -
273 #      RETURNS:  nothing
274 #  DESCRIPTION:  read cfg_file and set variables
275 #===============================================================================
276 sub read_configfile {
277     my $cfg;
278     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
279         if( -r $cfg_file ) {
280             $cfg = Config::IniFiles->new( -file => $cfg_file );
281         } else {
282             print STDERR "Couldn't read config file!\n";
283         }
284     } else {
285         $cfg = Config::IniFiles->new() ;
286     }
287     foreach my $section (keys %cfg_defaults) {
288         foreach my $param (keys %{$cfg_defaults{ $section }}) {
289             my $pinfo = $cfg_defaults{ $section }{ $param };
290             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
291         }
292     }
296 #===  FUNCTION  ================================================================
297 #         NAME:  logging
298 #   PARAMETERS:  level - string - default 'info'
299 #                msg - string -
300 #                facility - string - default 'LOG_DAEMON'
301 #      RETURNS:  nothing
302 #  DESCRIPTION:  function for logging
303 #===============================================================================
304 sub daemon_log {
305     # log into log_file
306     my( $msg, $level ) = @_;
307     if(not defined $msg) { return }
308     if(not defined $level) { $level = 1 }
309     if(defined $log_file){
310         open(LOG_HANDLE, ">>$log_file");
311         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
312             print STDERR "cannot open $log_file: $!";
313             return }
314             chomp($msg);
315                         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
316             if($level <= $verbose){
317                 my ($seconds, $minutes, $hours, $monthday, $month,
318                         $year, $weekday, $yearday, $sommertime) = localtime(time);
319                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
320                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
321                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
322                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
323                 $month = $monthnames[$month];
324                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
325                 $year+=1900;
326                 my $name = $prg;
328                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
329                 print LOG_HANDLE $log_msg;
330                 if( $foreground ) { 
331                     print STDERR $log_msg;
332                 }
333             }
334         close( LOG_HANDLE );
335     }
339 #===  FUNCTION  ================================================================
340 #         NAME:  check_cmdline_param
341 #   PARAMETERS:  nothing
342 #      RETURNS:  nothing
343 #  DESCRIPTION:  validates commandline parameter
344 #===============================================================================
345 sub check_cmdline_param () {
346     my $err_config;
347     my $err_counter = 0;
348         if(not defined($cfg_file)) {
349                 $cfg_file = "/etc/gosa-si/server.conf";
350                 if(! -r $cfg_file) {
351                         $err_config = "please specify a config file";
352                         $err_counter += 1;
353                 }
354     }
355     if( $err_counter > 0 ) {
356         &usage( "", 1 );
357         if( defined( $err_config)) { print STDERR "$err_config\n"}
358         print STDERR "\n";
359         exit( -1 );
360     }
364 #===  FUNCTION  ================================================================
365 #         NAME:  check_pid
366 #   PARAMETERS:  nothing
367 #      RETURNS:  nothing
368 #  DESCRIPTION:  handels pid processing
369 #===============================================================================
370 sub check_pid {
371     $pid = -1;
372     # Check, if we are already running
373     if( open(LOCK_FILE, "<$pid_file") ) {
374         $pid = <LOCK_FILE>;
375         if( defined $pid ) {
376             chomp( $pid );
377             if( -f "/proc/$pid/stat" ) {
378                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
379                 if( $stat ) {
380                                         daemon_log("ERROR: Already running",1);
381                     close( LOCK_FILE );
382                     exit -1;
383                 }
384             }
385         }
386         close( LOCK_FILE );
387         unlink( $pid_file );
388     }
390     # create a syslog msg if it is not to possible to open PID file
391     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
392         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
393         if (open(LOCK_FILE, '<', $pid_file)
394                 && ($pid = <LOCK_FILE>))
395         {
396             chomp($pid);
397             $msg .= "(PID $pid)\n";
398         } else {
399             $msg .= "(unable to read PID)\n";
400         }
401         if( ! ($foreground) ) {
402             openlog( $0, "cons,pid", "daemon" );
403             syslog( "warning", $msg );
404             closelog();
405         }
406         else {
407             print( STDERR " $msg " );
408         }
409         exit( -1 );
410     }
413 #===  FUNCTION  ================================================================
414 #         NAME:  import_modules
415 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
416 #                are stored
417 #      RETURNS:  nothing
418 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
419 #                state is on is imported by "require 'file';"
420 #===============================================================================
421 sub import_modules {
422     daemon_log(" ", 1);
424     if (not -e $modules_path) {
425         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
426     }
428     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
429     while (defined (my $file = readdir (DIR))) {
430         if (not $file =~ /(\S*?).pm$/) {
431             next;
432         }
433                 my $mod_name = $1;
435         if( $file =~ /ArpHandler.pm/ ) {
436             if( $no_arp > 0 ) {
437                 next;
438             }
439         }
440         
441         eval { require $file; };
442         if ($@) {
443             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
444             daemon_log("$@", 5);
445                 } else {
446                         my $info = eval($mod_name.'::get_module_info()');
447                         # Only load module if get_module_info() returns a non-null object
448                         if( $info ) {
449                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
450                                 $known_modules->{$mod_name} = $info;
451                                 daemon_log("0 INFO: module $mod_name loaded", 5);
452                         }
453                 }
454     }   
455     close (DIR);
459 #===  FUNCTION  ================================================================
460 #         NAME:  sig_int_handler
461 #   PARAMETERS:  signal - string - signal arose from system
462 #      RETURNS:  noting
463 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
464 #===============================================================================
465 sub sig_int_handler {
466     my ($signal) = @_;
468 #       if (defined($ldap_handle)) {
469 #               $ldap_handle->disconnect;
470 #       }
471     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
472     
474     daemon_log("shutting down gosa-si-server", 1);
475     system("kill `ps -C gosa-si-server-nobus -o pid=`");
477 $SIG{INT} = \&sig_int_handler;
480 sub check_key_and_xml_validity {
481     my ($crypted_msg, $module_key, $session_id) = @_;
482     my $msg;
483     my $msg_hash;
484     my $error_string;
485     eval{
486         $msg = &decrypt_msg($crypted_msg, $module_key);
488         if ($msg =~ /<xml>/i){
489             $msg =~ s/\s+/ /g;  # just for better daemon_log
490             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
491             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
493             ##############
494             # check header
495             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
496             my $header_l = $msg_hash->{'header'};
497             if( 1 > @{$header_l} ) { die 'empty header tag'; }
498             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
499             my $header = @{$header_l}[0];
500             if( 0 == length $header) { die 'empty string in header tag'; }
502             ##############
503             # check source
504             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
505             my $source_l = $msg_hash->{'source'};
506             if( 1 > @{$source_l} ) { die 'empty source tag'; }
507             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
508             my $source = @{$source_l}[0];
509             if( 0 == length $source) { die 'source error'; }
511             ##############
512             # check target
513             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
514             my $target_l = $msg_hash->{'target'};
515             if( 1 > @{$target_l} ) { die 'empty target tag'; }
516         }
517     };
518     if($@) {
519         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
520         $msg = undef;
521         $msg_hash = undef;
522     }
524     return ($msg, $msg_hash);
528 sub check_outgoing_xml_validity {
529     my ($msg) = @_;
531     my $msg_hash;
532     eval{
533         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
535         ##############
536         # check header
537         my $header_l = $msg_hash->{'header'};
538         if( 1 != @{$header_l} ) {
539             die 'no or more than one headers specified';
540         }
541         my $header = @{$header_l}[0];
542         if( 0 == length $header) {
543             die 'header has length 0';
544         }
546         ##############
547         # check source
548         my $source_l = $msg_hash->{'source'};
549         if( 1 != @{$source_l} ) {
550             die 'no or more than 1 sources specified';
551         }
552         my $source = @{$source_l}[0];
553         if( 0 == length $source) {
554             die 'source has length 0';
555         }
556         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
557                 $source =~ /^GOSA$/i ) {
558             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
559         }
560         
561         ##############
562         # check target  
563         my $target_l = $msg_hash->{'target'};
564         if( 0 == @{$target_l} ) {
565             die "no targets specified";
566         }
567         foreach my $target (@$target_l) {
568             if( 0 == length $target) {
569                 die "target has length 0";
570             }
571             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
572                     $target =~ /^GOSA$/i ||
573                     $target =~ /^\*$/ ||
574                     $target =~ /KNOWN_SERVER/i ||
575                     $target =~ /JOBDB/i ||
576                     $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 ){
577                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
578             }
579         }
580     };
581     if($@) {
582         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
583         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
584         $msg_hash = undef;
585     }
587     return ($msg_hash);
591 sub input_from_known_server {
592     my ($input, $remote_ip, $session_id) = @_ ;  
593     my ($msg, $msg_hash, $module);
595     my $sql_statement= "SELECT * FROM known_server";
596     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
598     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
599         my $host_name = $hit->{hostname};
600         if( not $host_name =~ "^$remote_ip") {
601             next;
602         }
603         my $host_key = $hit->{hostkey};
604         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
605         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
607         # check if module can open msg envelope with module key
608         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
609         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
610             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
611             daemon_log("$@", 8);
612             next;
613         }
614         else {
615             $msg = $tmp_msg;
616             $msg_hash = $tmp_msg_hash;
617             $module = "ServerPackages";
618             last;
619         }
620     }
622     if( (!$msg) || (!$msg_hash) || (!$module) ) {
623         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
624     }
625   
626     return ($msg, $msg_hash, $module);
630 sub input_from_known_client {
631     my ($input, $remote_ip, $session_id) = @_ ;  
632     my ($msg, $msg_hash, $module);
634     my $sql_statement= "SELECT * FROM known_clients";
635     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
636     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
637         my $host_name = $hit->{hostname};
638         if( not $host_name =~ /^$remote_ip:\d*$/) {
639                 next;
640                 }
641         my $host_key = $hit->{hostkey};
642         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
643         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
645         # check if module can open msg envelope with module key
646         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
648         if( (!$msg) || (!$msg_hash) ) {
649             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
650             &daemon_log("$@", 8);
651             next;
652         }
653         else {
654             $module = "ClientPackages";
655             last;
656         }
657     }
659     if( (!$msg) || (!$msg_hash) || (!$module) ) {
660         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
661     }
663     return ($msg, $msg_hash, $module);
667 sub input_from_unknown_host {
668     no strict "refs";
669     my ($input, $session_id) = @_ ;
670     my ($msg, $msg_hash, $module);
671     my $error_string;
672     
673         my %act_modules = %$known_modules;
674         
675     while( my ($mod, $info) = each(%act_modules)) {
677         # check a key exists for this module
678         my $module_key = ${$mod."_key"};
679         if( not defined $module_key ) {
680             if( $mod eq 'ArpHandler' ) {
681                 next;
682             }
683             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
684             next;
685         }
686         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
688         # check if module can open msg envelope with module key
689         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
690         if( (not defined $msg) || (not defined $msg_hash) ) {
691             next;
692         }
693         else {
694             $module = $mod;
695             last;
696         }
697     }
699     if( (!$msg) || (!$msg_hash) || (!$module)) {
700         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
701     }
703     return ($msg, $msg_hash, $module);
707 sub create_ciphering {
708     my ($passwd) = @_;
709         if((!defined($passwd)) || length($passwd)==0) {
710                 $passwd = "";
711         }
712     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
713     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
714     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
715     $my_cipher->set_iv($iv);
716     return $my_cipher;
720 sub encrypt_msg {
721     my ($msg, $key) = @_;
722     my $my_cipher = &create_ciphering($key);
723     my $len;
724     {
725             use bytes;
726             $len= 16-length($msg)%16;
727     }
728     $msg = "\0"x($len).$msg;
729     $msg = $my_cipher->encrypt($msg);
730     chomp($msg = &encode_base64($msg));
731     # there are no newlines allowed inside msg
732     $msg=~ s/\n//g;
733     return $msg;
737 sub decrypt_msg {
739     my ($msg, $key) = @_ ;
740     $msg = &decode_base64($msg);
741     my $my_cipher = &create_ciphering($key);
742     $msg = $my_cipher->decrypt($msg); 
743     $msg =~ s/\0*//g;
744     return $msg;
748 sub get_encrypt_key {
749     my ($target) = @_ ;
750     my $encrypt_key;
751     my $error = 0;
753     # target can be in known_server
754     if( not defined $encrypt_key ) {
755         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
756         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
757         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
758             my $host_name = $hit->{hostname};
759             if( $host_name ne $target ) {
760                 next;
761             }
762             $encrypt_key = $hit->{hostkey};
763             last;
764         }
765     }
767     # target can be in known_client
768     if( not defined $encrypt_key ) {
769         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
770         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
771         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
772             my $host_name = $hit->{hostname};
773             if( $host_name ne $target ) {
774                 next;
775             }
776             $encrypt_key = $hit->{hostkey};
777             last;
778         }
779     }
781     return $encrypt_key;
785 #===  FUNCTION  ================================================================
786 #         NAME:  open_socket
787 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
788 #                [PeerPort] string necessary if port not appended by PeerAddr
789 #      RETURNS:  socket IO::Socket::INET
790 #  DESCRIPTION:  open a socket to PeerAddr
791 #===============================================================================
792 sub open_socket {
793     my ($PeerAddr, $PeerPort) = @_ ;
794     if(defined($PeerPort)){
795         $PeerAddr = $PeerAddr.":".$PeerPort;
796     }
797     my $socket;
798     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
799             Porto => "tcp",
800             Type => SOCK_STREAM,
801             Timeout => 5,
802             );
803     if(not defined $socket) {
804         return;
805     }
806 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
807     return $socket;
811 #===  FUNCTION  ================================================================
812 #         NAME:  get_ip 
813 #   PARAMETERS:  interface name (i.e. eth0)
814 #      RETURNS:  (ip address) 
815 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
816 #===============================================================================
817 sub get_ip {
818         my $ifreq= shift;
819         my $result= "";
820         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
821         my $proto= getprotobyname('ip');
823         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
824                 or die "socket: $!";
826         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
827                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
828                 my ($port, $addr) = sockaddr_in $sin;
829                 my $ip            = inet_ntoa $addr;
831                 if ($ip && length($ip) > 0) {
832                         $result = $ip;
833                 }
834         }
836         return $result;
840 sub get_local_ip_for_remote_ip {
841         my $remote_ip= shift;
842         my $result="0.0.0.0";
844         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
845                 if($remote_ip eq "127.0.0.1") {
846                         $result = "127.0.0.1";
847                 } else {
848                         my $PROC_NET_ROUTE= ('/proc/net/route');
850                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
851                                 or die "Could not open $PROC_NET_ROUTE";
853                         my @ifs = <PROC_NET_ROUTE>;
855                         close(PROC_NET_ROUTE);
857                         # Eat header line
858                         shift @ifs;
859                         chomp @ifs;
860                         foreach my $line(@ifs) {
861                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
862                                 my $destination;
863                                 my $mask;
864                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
865                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
866                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
867                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
868                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
869                                         # destination matches route, save mac and exit
870                                         $result= &get_ip($Iface);
871                                         last;
872                                 }
873                         }
874                 }
875         } else {
876                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
877         }
878         return $result;
882 sub send_msg_to_target {
883     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
884     my $error = 0;
885     my $header;
886     my $new_status;
887     my $act_status;
888     my ($sql_statement, $res);
889   
890     if( $msg_header ) {
891         $header = "'$msg_header'-";
892     } else {
893         $header = "";
894     }
896         # Patch the source ip
897         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
898                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
899                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
900         }
902     # encrypt xml msg
903     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
905     # opensocket
906     my $socket = &open_socket($address);
907     if( !$socket ) {
908         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
909         $error++;
910     }
911     
912     if( $error == 0 ) {
913         # send xml msg
914         print $socket $crypted_msg."\n";
916         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
917         daemon_log("DEBUG: message:\n$msg", 9);
918         
919     }
921     # close socket in any case
922     if( $socket ) {
923         close $socket;
924     }
926     if( $error > 0 ) { $new_status = "down"; }
927     else { $new_status = $msg_header; }
930     # known_clients
931     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
932     $res = $known_clients_db->select_dbentry($sql_statement);
933     if( keys(%$res) > 0) {
934         $act_status = $res->{1}->{'status'};
935         if ($act_status eq "down" && $new_status eq "down") {
936             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
937             $res = $known_clients_db->del_dbentry($sql_statement);
938             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
939         } else { 
940             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
941             $res = $known_clients_db->update_dbentry($sql_statement);
942             if($new_status eq "down"){
943                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
944             } else {
945                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
946             }
947         }
948     }
950     # known_server
951     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
952     $res = $known_server_db->select_dbentry($sql_statement);
953 print STDERR Dumper($res);    
954     if( keys(%$res) > 0 ) {
955         $act_status = $res->{1}->{'status'};
956         if ($act_status eq "down" && $new_status eq "down") {
957             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
958             $res = $known_server_db->del_dbentry($sql_statement);
959             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
960         } 
961         else { 
962             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
963             $res = $known_server_db->update_dbentry($sql_statement);
964             if($new_status eq "down"){
965                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
966             }
967             else {
968                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
969             }
970         }
971     }
972     return $error; 
976 sub update_jobdb_status_for_send_msgs {
977     my ($answer, $error) = @_;
978     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
979         my $jobdb_id = $1;
980             
981         # sending msg faild
982         if( $error ) {
983             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
984                 my $sql_statement = "UPDATE $job_queue_tn ".
985                     "SET status='error', result='can not deliver msg, please consult log file' ".
986                     "WHERE id=$jobdb_id";
987                 my $res = $job_db->update_dbentry($sql_statement);
988             }
990         # sending msg was successful
991         } else {
992             my $sql_statement = "UPDATE $job_queue_tn ".
993                 "SET status='done' ".
994                 "WHERE id=$jobdb_id AND status='processed'";
995             my $res = $job_db->update_dbentry($sql_statement);
996         }
997     }
1000 sub _start {
1001     my ($kernel) = $_[KERNEL];
1002     &trigger_db_loop($kernel);
1003     $global_kernel = $kernel;
1004     $kernel->yield('register_at_foreign_servers');
1005         $kernel->yield('create_fai_server_db', $fai_server_tn );
1006         $kernel->yield('create_fai_release_db', $fai_release_tn );
1007         $kernel->sig(USR1 => "sig_handler");
1008         $kernel->sig(USR2 => "create_packages_list_db");
1011 sub sig_handler {
1012         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1013         daemon_log("0 INFO got signal '$signal'", 1); 
1014         $kernel->sig_handled();
1015         return;
1019 sub msg_to_decrypt {
1020     my ($session, $heap) = @_[SESSION, HEAP];
1021     my $session_id = $session->ID;
1022     my ($msg, $msg_hash, $module);
1023     my $error = 0;
1025     # hole neue msg aus @msgs_to_decrypt
1026     my $next_msg = shift @msgs_to_decrypt;
1027     
1028     # entschlüssle sie
1030     # msg is from a new client or gosa
1031     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1032     # msg is from a gosa-si-server or gosa-si-bus
1033     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1034         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1035     }
1036     # msg is from a gosa-si-client
1037     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1038         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1039     }
1040     # an error occurred
1041     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1042         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1043         # could not understand a msg from its server the client cause a re-registering process
1044         daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}."' to cause a re-registering of the client if necessary", 5);
1045         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1046         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1047         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1048             my $host_name = $hit->{'hostname'};
1049             my $host_key = $hit->{'hostkey'};
1050             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1051             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1052             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1053         }
1054         $error++;
1055     }
1056     
1057     # add message to incoming_db
1058     if( $error == 0) {
1059         my $header = @{$msg_hash->{'header'}}[0];
1060         my $target = @{$msg_hash->{'target'}}[0];
1061         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1062                 primkey=>[],
1063                 headertag=>$header,
1064                                 targettag=>$target,
1065                 xmlmessage=>$msg,
1066                 timestamp=>&get_time,
1067                 module=>$module,
1068                 } );
1069         if ($res != 0) {
1070                         # TODO ist das mit $! so ok???
1071             #&daemon_log("$session_id ERROR: cannot add message to incoming.db: $!", 1); 
1072         }
1073     }
1078 sub next_task {
1079     my ($session, $heap) = @_[SESSION, HEAP];
1080     my $task = POE::Wheel::Run->new(
1081             Program => sub { process_task($session, $heap) },
1082             StdioFilter => POE::Filter::Reference->new(),
1083             StdoutEvent  => "task_result",
1084             StderrEvent  => "task_debug",
1085             CloseEvent   => "task_done",
1086             );
1088     $heap->{task}->{ $task->ID } = $task;
1091 sub handle_task_result {
1092     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1093     my $client_answer = $result->{'answer'};
1094     if( $client_answer =~ s/session_id=(\d+)$// ) {
1095         my $session_id = $1;
1096         if( defined $session_id ) {
1097             my $session_reference = $kernel->ID_id_to_session($session_id);
1098             if( defined $session_reference ) {
1099                 $heap = $session_reference->get_heap();
1100             }
1101         }
1103         if(exists $heap->{'client'}) {
1104             $heap->{'client'}->put($client_answer);
1105         }
1106     }
1107     $kernel->sig(CHLD => "child_reap");
1110 sub handle_task_debug {
1111     my $result = $_[ARG0];
1112     print STDERR "$result\n";
1115 sub handle_task_done {
1116     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1117     delete $heap->{task}->{$task_id};
1120 sub process_task {
1121     no strict "refs";
1122     my ($session, $heap, $input) = @_;
1123     my $session_id = $session->ID;
1124     my $error = 0;
1125     my $answer_l;
1126     my ($answer_header, @answer_target_l, $answer_source);
1127     my $client_answer = "";
1129         ##################################################
1130         # fetch first unprocessed message from incoming_db
1131     # sometimes the program is faster than sqlite, so wait until informations are present at db
1132     my $id_sql;
1133     my $id_res;
1134     my $message_id;
1135 # TODO : das hier ist sehr sehr unschön        
1136 # to be tested: speed enhancement with usleep 100000???
1137     while (1) {
1138         $id_sql = "SELECT min(id) FROM $incoming_tn WHERE (NOT headertag LIKE 'answer%')"; 
1139         $id_res = $incoming_db->exec_statement($id_sql);
1140         $message_id = @{@$id_res[0]}[0];
1141         if (defined $message_id) { last }
1142         usleep(100000);
1143     }
1145     # fetch new message from incoming_db
1146     my $sql = "SELECT * FROM $incoming_tn WHERE id=$message_id"; 
1147     my $res = $incoming_db->exec_statement($sql);
1149     # prepare all variables needed to process message
1150     my $msg = @{@$res[0]}[4];
1151     my $incoming_id = @{@$res[0]}[0];
1152     my $module = @{@$res[0]}[5];
1153     my $header =  @{@$res[0]}[2];
1154     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1156     # messages which are an answer to a still running process should not be processed here
1157     if ($header =~ /^answer_(\d+)/) {
1158         return;
1159     }
1160    
1161     # delete message from db 
1162     my $delete_sql = "DELETE FROM $incoming_tn WHERE id=$incoming_id";
1163     my $delete_res = $incoming_db->exec_statement($delete_sql);
1165     ######################
1166     # process incoming msg
1167     if( $error == 0) {
1168         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0].
1169                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1170         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1171         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1173         if ( 0 < @{$answer_l} ) {
1174             my $answer_str = join("\n", @{$answer_l});
1175             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1176                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1177             }
1178             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1179         } else {
1180             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1181         }
1183     }
1184     if( !$answer_l ) { $error++ };
1186     ########
1187     # answer
1188     if( $error == 0 ) {
1190         foreach my $answer ( @{$answer_l} ) {
1191             # check outgoing msg to xml validity
1192             my $answer_hash = &check_outgoing_xml_validity($answer);
1193             if( not defined $answer_hash ) { next; }
1194             
1195             $answer_header = @{$answer_hash->{'header'}}[0];
1196             @answer_target_l = @{$answer_hash->{'target'}};
1197             $answer_source = @{$answer_hash->{'source'}}[0];
1199             # deliver msg to all targets 
1200             foreach my $answer_target ( @answer_target_l ) {
1202                 # targets of msg are all gosa-si-clients in known_clients_db
1203                 if( $answer_target eq "*" ) {
1204                     # answer is for all clients
1205                     my $sql_statement= "SELECT * FROM known_clients";
1206                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1207                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1208                         my $host_name = $hit->{hostname};
1209                         my $host_key = $hit->{hostkey};
1210                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1211                         &update_jobdb_status_for_send_msgs($answer, $error);
1212                     }
1213                 }
1215                 # targets of msg are all gosa-si-server in known_server_db
1216                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1217                     # answer is for all server in known_server
1218                     my $sql_statement= "SELECT * FROM known_server";
1219                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1220                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1221                         my $host_name = $hit->{hostname};
1222                         my $host_key = $hit->{hostkey};
1223                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1224                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1225                         &update_jobdb_status_for_send_msgs($answer, $error);
1226                     }
1227                 }
1229                 # target of msg is GOsa
1230                                 elsif( $answer_target eq "GOSA" ) {
1231                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1232                                         my $add_on = "";
1233                     if( defined $session_id ) {
1234                         $add_on = ".session_id=$session_id";
1235                     }
1236                     # answer is for GOSA and has to returned to connected client
1237                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1238                     $client_answer = $gosa_answer.$add_on;
1239                 }
1241                 # target of msg is job queue at this host
1242                 elsif( $answer_target eq "JOBDB") {
1243                     $answer =~ /<header>(\S+)<\/header>/;   
1244                     my $header;
1245                     if( defined $1 ) { $header = $1; }
1246                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1247                     &update_jobdb_status_for_send_msgs($answer, $error);
1248                 }
1250                 # target of msg is a mac address
1251                 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 ) {
1252                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1253                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1254                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1255                     my $found_ip_flag = 0;
1256                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1257                         my $host_name = $hit->{hostname};
1258                         my $host_key = $hit->{hostkey};
1259                         $answer =~ s/$answer_target/$host_name/g;
1260                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1261                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1262                         &update_jobdb_status_for_send_msgs($answer, $error);
1263                         $found_ip_flag++ ;
1264                     }   
1265                     if( $found_ip_flag == 0) {
1266                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1267                         if( $bus_activ eq "true" ) { 
1268                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1269                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1270                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1271                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1272                                 my $bus_address = $hit->{hostname};
1273                                 my $bus_key = $hit->{hostkey};
1274                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1275                                 &update_jobdb_status_for_send_msgs($answer, $error);
1276                                 last;
1277                             }
1278                         }
1280                     }
1282                 #  answer is for one specific host   
1283                 } else {
1284                     # get encrypt_key
1285                     my $encrypt_key = &get_encrypt_key($answer_target);
1286                     if( not defined $encrypt_key ) {
1287                         # unknown target, forward msg to bus
1288                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1289                         if( $bus_activ eq "true" ) { 
1290                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1291                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1292                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1293                             my $res_length = keys( %{$query_res} );
1294                             if( $res_length == 0 ){
1295                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1296                                         "no bus found in known_server", 3);
1297                             }
1298                             else {
1299                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1300                                     my $bus_key = $hit->{hostkey};
1301                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1302                                     &update_jobdb_status_for_send_msgs($answer, $error);
1303                                 }
1304                             }
1305                         }
1306                         next;
1307                     }
1308                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1309                     &update_jobdb_status_for_send_msgs($answer, $error);
1310                 }
1311             }
1312         }
1313     }
1315     my $filter = POE::Filter::Reference->new();
1316     my %result = ( 
1317             status => "seems ok to me",
1318             answer => $client_answer,
1319             );
1321     my $output = $filter->put( [ \%result ] );
1322     print @$output;
1328 sub trigger_db_loop {
1329         my ($kernel) = @_ ;
1330         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1331         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1332         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1333     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1334         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1338 sub watch_for_done_jobs {
1339     my ($kernel,$heap) = @_[KERNEL, HEAP];
1341     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1342         " WHERE status='done'";
1343         my $res = $job_db->select_dbentry( $sql_statement );
1345     while( my ($id, $hit) = each %{$res} ) {
1346         my $jobdb_id = $hit->{id};
1347         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1348         my $res = $job_db->del_dbentry($sql_statement); 
1349     }
1351     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1355 sub watch_for_new_jobs {
1356         if($watch_for_new_jobs_in_progress == 0) {
1357                 $watch_for_new_jobs_in_progress = 1;
1358                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1360                 # check gosa job queue for jobs with executable timestamp
1361                 my $timestamp = &get_time();
1362                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1363                 my $res = $job_db->exec_statement( $sql_statement );
1365                 # Merge all new jobs that would do the same actions
1366                 my @drops;
1367                 my $hits;
1368                 foreach my $hit (reverse @{$res} ) {
1369                         my $macaddress= lc @{$hit}[8];
1370                         my $headertag= @{$hit}[5];
1371                         if(
1372                                 defined($hits->{$macaddress}) &&
1373                                 defined($hits->{$macaddress}->{$headertag}) &&
1374                                 defined($hits->{$macaddress}->{$headertag}[0])
1375                         ) {
1376                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1377                         }
1378                         $hits->{$macaddress}->{$headertag}= $hit;
1379                 }
1381                 # Delete new jobs with a matching job in state 'processing'
1382                 foreach my $macaddress (keys %{$hits}) {
1383                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1384                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1385                                 if(defined($jobdb_id)) {
1386                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1387                                         my $res = $job_db->exec_statement( $sql_statement );
1388                                         foreach my $hit (@{$res}) {
1389                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1390                                         }
1391                                 } else {
1392                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1393                                 }
1394                         }
1395                 }
1397                 # Commit deletion
1398                 $job_db->exec_statementlist(\@drops);
1400                 # Look for new jobs that could be executed
1401                 foreach my $macaddress (keys %{$hits}) {
1403                         # Look if there is an executing job
1404                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1405                         my $res = $job_db->exec_statement( $sql_statement );
1407                         # Skip new jobs for host if there is a processing job
1408                         if(defined($res) and defined @{$res}[0]) {
1409                                 next;
1410                         }
1412                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1413                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1414                                 if(defined($jobdb_id)) {
1415                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1417                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1418                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1419                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1421                                         # expect macaddress is unique!!!!!!
1422                                         my $target = $res_hash->{1}->{hostname};
1424                                         # change header
1425                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1427                                         # add sqlite_id
1428                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1430                                         $job_msg =~ /<header>(\S+)<\/header>/;
1431                                         my $header = $1 ;
1432                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1434                                         # update status in job queue to 'processing'
1435                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1436                                         my $res = $job_db->update_dbentry($sql_statement);
1437 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1439                                         # We don't want parallel processing
1440                                         last;
1441                                 }
1442                         }
1443                 }
1445                 $watch_for_new_jobs_in_progress = 0;
1446                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1447         }
1451 sub watch_for_new_messages {
1452     my ($kernel,$heap) = @_[KERNEL, HEAP];
1453     my @coll_user_msg;   # collection list of outgoing messages
1454     
1455     # check messaging_db for new incoming messages with executable timestamp
1456     my $timestamp = &get_time();
1457     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1458     my $res = $messaging_db->exec_statement( $sql_statement );
1459         foreach my $hit (@{$res}) {
1461         # create outgoing messages
1462         my $message_to = @{$hit}[3];
1463         # translate message_to to plain login name
1464         my @message_to_l = split(/,/, $message_to);  
1465                 my %receiver_h; 
1466                 foreach my $receiver (@message_to_l) {
1467                         if ($receiver =~ /^u_([\s\S]*)$/) {
1468                                 $receiver_h{$1} = 0;
1469                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1470                                 my $group_name = $1;
1471                                 # fetch all group members from ldap and add them to receiver hash
1472                                 my $ldap_handle = &get_ldap_handle();
1473                                 if (defined $ldap_handle) {
1474                                                 my $mesg = $ldap_handle->search(
1475                                                                                 base => $ldap_base,
1476                                                                                 scope => 'sub',
1477                                                                                 attrs => ['memberUid'],
1478                                                                                 filter => "cn=$group_name",
1479                                                                                 );
1480                                                 if ($mesg->count) {
1481                                                                 my @entries = $mesg->entries;
1482                                                                 foreach my $entry (@entries) {
1483                                                                                 my @receivers= $entry->get_value("memberUid");
1484                                                                                 foreach my $receiver (@receivers) { 
1485                                                                                                 $receiver_h{$1} = 0;
1486                                                                                 }
1487                                                                 }
1488                                                 } 
1489                                                 # translating errors ?
1490                                                 if ($mesg->code) {
1491                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1492                                                 }
1493                                 # ldap handle error ?           
1494                                 } else {
1495                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1496                                 }
1497                         } else {
1498                                 my $sbjct = &encode_base64(@{$hit}[1]);
1499                                 my $msg = &encode_base64(@{$hit}[7]);
1500                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1501                         }
1502                 }
1503                 my @receiver_l = keys(%receiver_h);
1505         my $message_id = @{$hit}[0];
1507         #add each outgoing msg to messaging_db
1508         my $receiver;
1509         foreach $receiver (@receiver_l) {
1510             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1511                 "VALUES ('".
1512                 $message_id."', '".    # id
1513                 @{$hit}[1]."', '".     # subject
1514                 @{$hit}[2]."', '".     # message_from
1515                 $receiver."', '".      # message_to
1516                 "none"."', '".         # flag
1517                 "out"."', '".          # direction
1518                 @{$hit}[6]."', '".     # delivery_time
1519                 @{$hit}[7]."', '".     # message
1520                 $timestamp."'".     # timestamp
1521                 ")";
1522             &daemon_log("M DEBUG: $sql_statement", 1);
1523             my $res = $messaging_db->exec_statement($sql_statement);
1524             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1525         }
1527         # set incoming message to flag d=deliverd
1528         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1529         &daemon_log("M DEBUG: $sql_statement", 7);
1530         $res = $messaging_db->update_dbentry($sql_statement);
1531         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1532     }
1534     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1535     return;
1538 sub watch_for_delivery_messages {
1539     my ($kernel, $heap) = @_[KERNEL, HEAP];
1541     # select outgoing messages
1542     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1543     #&daemon_log("0 DEBUG: $sql", 7);
1544     my $res = $messaging_db->exec_statement( $sql_statement );
1545     
1546     # build out msg for each    usr
1547     foreach my $hit (@{$res}) {
1548         my $receiver = @{$hit}[3];
1549         my $msg_id = @{$hit}[0];
1550         my $subject = @{$hit}[1];
1551         my $message = @{$hit}[7];
1553         # resolve usr -> host where usr is logged in
1554         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1555         #&daemon_log("0 DEBUG: $sql", 7);
1556         my $res = $login_users_db->exec_statement($sql);
1558         # reciver is logged in nowhere
1559         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1561                 my $send_succeed = 0;
1562                 foreach my $hit (@$res) {
1563                                 my $receiver_host = @$hit[0];
1564                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1566                                 # fetch key to encrypt msg propperly for usr/host
1567                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1568                                 &daemon_log("0 DEBUG: $sql", 7);
1569                                 my $res = $known_clients_db->exec_statement($sql);
1571                                 # host is already down
1572                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1574                                 # host is on
1575                                 my $receiver_key = @{@{$res}[0]}[2];
1576                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1577                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1578                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1579                                 if ($error == 0 ) {
1580                                         $send_succeed++ ;
1581                                 }
1582                 }
1584                 if ($send_succeed) {
1585                                 # set outgoing msg at db to deliverd
1586                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1587                                 &daemon_log("0 DEBUG: $sql", 7);
1588                                 my $res = $messaging_db->exec_statement($sql); 
1589                 }
1590         }
1592     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1593     return;
1597 sub watch_for_done_messages {
1598     my ($kernel,$heap) = @_[KERNEL, HEAP];
1600     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1601     #&daemon_log("0 DEBUG: $sql", 7);
1602     my $res = $messaging_db->exec_statement($sql); 
1604     foreach my $hit (@{$res}) {
1605         my $msg_id = @{$hit}[0];
1607         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1608         #&daemon_log("0 DEBUG: $sql", 7); 
1609         my $res = $messaging_db->exec_statement($sql);
1611         # not all usr msgs have been seen till now
1612         if ( ref(@$res[0]) eq "ARRAY") { next; }
1613         
1614         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1615         #&daemon_log("0 DEBUG: $sql", 7);
1616         $res = $messaging_db->exec_statement($sql);
1617     
1618     }
1620     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1621     return;
1625 sub get_ldap_handle {
1626         my ($session_id) = @_;
1627         my $heap;
1628         my $ldap_handle;
1630         if (not defined $session_id ) { $session_id = 0 };
1631         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1633         if ($session_id == 0) {
1634                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1635                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1636                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1638         } else {
1639                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1640                 if( defined $session_reference ) {
1641                         $heap = $session_reference->get_heap();
1642                 }
1644                 if (not defined $heap) {
1645                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1646                         return;
1647                 }
1649                 # TODO: This "if" is nonsense, because it doesn't prove that the
1650                 #       used handle is still valid - or if we've to reconnect...
1651                 #if (not exists $heap->{ldap_handle}) {
1652                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1653                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1654                         $heap->{ldap_handle} = $ldap_handle;
1655                 #}
1656         }
1657         return $ldap_handle;
1661 sub change_fai_state {
1662     my ($st, $targets, $session_id) = @_;
1663     $session_id = 0 if not defined $session_id;
1664     # Set FAI state to localboot
1665     my %mapActions= (
1666         reboot    => '',
1667         update    => 'softupdate',
1668         localboot => 'localboot',
1669         reinstall => 'install',
1670         rescan    => '',
1671         wake      => '',
1672         memcheck  => 'memcheck',
1673         sysinfo   => 'sysinfo',
1674         install   => 'install',
1675     );
1677     # Return if this is unknown
1678     if (!exists $mapActions{ $st }){
1679         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1680       return;
1681     }
1683     my $state= $mapActions{ $st };
1685     my $ldap_handle = &get_ldap_handle($session_id);
1686     if( defined($ldap_handle) ) {
1688       # Build search filter for hosts
1689         my $search= "(&(objectClass=GOhard)";
1690         foreach (@{$targets}){
1691             $search.= "(macAddress=$_)";
1692         }
1693         $search.= ")";
1695       # If there's any host inside of the search string, procress them
1696         if (!($search =~ /macAddress/)){
1697             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1698             return;
1699         }
1701       # Perform search for Unit Tag
1702       my $mesg = $ldap_handle->search(
1703           base   => $ldap_base,
1704           scope  => 'sub',
1705           attrs  => ['dn', 'FAIstate', 'objectClass'],
1706           filter => "$search"
1707           );
1709           if ($mesg->count) {
1710                   my @entries = $mesg->entries;
1711                   foreach my $entry (@entries) {
1712                           # Only modify entry if it is not set to '$state'
1713                           if ($entry->get_value("FAIstate") ne "$state"){
1714                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1715                                   my $result;
1716                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1717                                   if (exists $tmp{'FAIobject'}){
1718                                           if ($state eq ''){
1719                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1720                                                           delete => [ FAIstate => [] ] ]);
1721                                           } else {
1722                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1723                                                           replace => [ FAIstate => $state ] ]);
1724                                           }
1725                                   } elsif ($state ne ''){
1726                                           $result= $ldap_handle->modify($entry->dn, changes => [
1727                                                   add     => [ objectClass => 'FAIobject' ],
1728                                                   add     => [ FAIstate => $state ] ]);
1729                                   }
1731                                   # Errors?
1732                                   if ($result->code){
1733                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1734                                   }
1735                           } else {
1736                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1737                           }  
1738                   }
1739           }
1740     # if no ldap handle defined
1741     } else {
1742         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1743     }
1748 sub change_goto_state {
1749     my ($st, $targets, $session_id) = @_;
1750     $session_id = 0  if not defined $session_id;
1752     # Switch on or off?
1753     my $state= $st eq 'active' ? 'active': 'locked';
1755     my $ldap_handle = &get_ldap_handle($session_id);
1756     if( defined($ldap_handle) ) {
1758       # Build search filter for hosts
1759       my $search= "(&(objectClass=GOhard)";
1760       foreach (@{$targets}){
1761         $search.= "(macAddress=$_)";
1762       }
1763       $search.= ")";
1765       # If there's any host inside of the search string, procress them
1766       if (!($search =~ /macAddress/)){
1767         return;
1768       }
1770       # Perform search for Unit Tag
1771       my $mesg = $ldap_handle->search(
1772           base   => $ldap_base,
1773           scope  => 'sub',
1774           attrs  => ['dn', 'gotoMode'],
1775           filter => "$search"
1776           );
1778       if ($mesg->count) {
1779         my @entries = $mesg->entries;
1780         foreach my $entry (@entries) {
1782           # Only modify entry if it is not set to '$state'
1783           if ($entry->get_value("gotoMode") ne $state){
1785             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1786             my $result;
1787             $result= $ldap_handle->modify($entry->dn, changes => [
1788                                                 replace => [ gotoMode => $state ] ]);
1790             # Errors?
1791             if ($result->code){
1792               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1793             }
1795           }
1796         }
1797       }
1799     }
1803 sub run_create_fai_server_db {
1804     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1805     my $session_id = $session->ID;
1806     my $task = POE::Wheel::Run->new(
1807             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1808             StdoutEvent  => "session_run_result",
1809             StderrEvent  => "session_run_debug",
1810             CloseEvent   => "session_run_done",
1811             );
1813     $heap->{task}->{ $task->ID } = $task;
1814     return;
1818 sub create_fai_server_db {
1819     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1820         my $result;
1822         if (not defined $session_id) { $session_id = 0; }
1823     my $ldap_handle = &get_ldap_handle();
1824         if(defined($ldap_handle)) {
1825                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1826                 my $mesg= $ldap_handle->search(
1827                         base   => $ldap_base,
1828                         scope  => 'sub',
1829                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1830                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1831                 );
1832                 if($mesg->{'resultCode'} == 0 &&
1833                    $mesg->count != 0) {
1834                    foreach my $entry (@{$mesg->{entries}}) {
1835                            if($entry->exists('FAIrepository')) {
1836                                    # Add an entry for each Repository configured for server
1837                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1838                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1839                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1840                                                    $result= $fai_server_db->add_dbentry( { 
1841                                                                    table => $table_name,
1842                                                                    primkey => ['server', 'release', 'tag'],
1843                                                                    server => $tmp_url,
1844                                                                    release => $tmp_release,
1845                                                                    sections => $tmp_sections,
1846                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1847                                                            } );
1848                                            }
1849                                    }
1850                            }
1851                    }
1852                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1854                 # TODO: Find a way to post the 'create_packages_list_db' event
1855                 if(not defined($dont_create_packages_list)) {
1856                         &create_packages_list_db(undef, undef, $session_id);
1857                 }
1858         }       
1859     
1860     $ldap_handle->disconnect;
1861         return $result;
1865 sub run_create_fai_release_db {
1866     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1867         my $session_id = $session->ID;
1868     my $task = POE::Wheel::Run->new(
1869             Program => sub { &create_fai_release_db($table_name, $session_id) },
1870             StdoutEvent  => "session_run_result",
1871             StderrEvent  => "session_run_debug",
1872             CloseEvent   => "session_run_done",
1873             );
1875     $heap->{task}->{ $task->ID } = $task;
1876     return;
1880 sub create_fai_release_db {
1881         my ($table_name, $session_id) = @_;
1882         my $result;
1884     # used for logging
1885     if (not defined $session_id) { $session_id = 0; }
1887     my $ldap_handle = &get_ldap_handle();
1888         if(defined($ldap_handle)) {
1889                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1890                 my $mesg= $ldap_handle->search(
1891                         base   => $ldap_base,
1892                         scope  => 'sub',
1893                         attrs  => [],
1894                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1895                 );
1896                 if($mesg->{'resultCode'} == 0 &&
1897                         $mesg->count != 0) {
1898                         # Walk through all possible FAI container ou's
1899                         my @sql_list;
1900                         my $timestamp= &get_time();
1901                         foreach my $ou (@{$mesg->{entries}}) {
1902                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1903                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1904                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1905                                         if(@tmp_array) {
1906                                                 foreach my $entry (@tmp_array) {
1907                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1908                                                                 my $sql= 
1909                                                                 "INSERT INTO $table_name "
1910                                                                 ."(timestamp, release, class, type, state) VALUES ("
1911                                                                 .$timestamp.","
1912                                                                 ."'".$entry->{'release'}."',"
1913                                                                 ."'".$entry->{'class'}."',"
1914                                                                 ."'".$entry->{'type'}."',"
1915                                                                 ."'".$entry->{'state'}."')";
1916                                                                 push @sql_list, $sql;
1917                                                         }
1918                                                 }
1919                                         }
1920                                 }
1921                         }
1923                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1924                         if(@sql_list) {
1925                                 unshift @sql_list, "VACUUM";
1926                                 unshift @sql_list, "DELETE FROM $table_name";
1927                                 $fai_release_db->exec_statementlist(\@sql_list);
1928                         }
1929                         daemon_log("$session_id DEBUG: Done with inserting",7);
1930                 }
1931                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1932         }
1933     $ldap_handle->disconnect;
1934         return $result;
1937 sub get_fai_types {
1938         my $tmp_classes = shift || return undef;
1939         my @result;
1941         foreach my $type(keys %{$tmp_classes}) {
1942                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1943                         my $entry = {
1944                                 type => $type,
1945                                 state => $tmp_classes->{$type}[0],
1946                         };
1947                         push @result, $entry;
1948                 }
1949         }
1951         return @result;
1954 sub get_fai_state {
1955         my $result = "";
1956         my $tmp_classes = shift || return $result;
1958         foreach my $type(keys %{$tmp_classes}) {
1959                 if(defined($tmp_classes->{$type}[0])) {
1960                         $result = $tmp_classes->{$type}[0];
1961                         
1962                 # State is equal for all types in class
1963                         last;
1964                 }
1965         }
1967         return $result;
1970 sub resolve_fai_classes {
1971         my ($fai_base, $ldap_handle, $session_id) = @_;
1972         if (not defined $session_id) { $session_id = 0; }
1973         my $result;
1974         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1975         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1976         my $fai_classes;
1978         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
1979         my $mesg= $ldap_handle->search(
1980                 base   => $fai_base,
1981                 scope  => 'sub',
1982                 attrs  => ['cn','objectClass','FAIstate'],
1983                 filter => $fai_filter,
1984         );
1985         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
1987         if($mesg->{'resultCode'} == 0 &&
1988                 $mesg->count != 0) {
1989                 foreach my $entry (@{$mesg->{entries}}) {
1990                         if($entry->exists('cn')) {
1991                                 my $tmp_dn= $entry->dn();
1993                                 # Skip classname and ou dn parts for class
1994                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1996                                 # Skip classes without releases
1997                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1998                                         next;
1999                                 }
2001                                 my $tmp_cn= $entry->get_value('cn');
2002                                 my $tmp_state= $entry->get_value('FAIstate');
2004                                 my $tmp_type;
2005                                 # Get FAI type
2006                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2007                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2008                                                 $tmp_type= $oclass;
2009                                                 last;
2010                                         }
2011                                 }
2013                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2014                                         # A Subrelease
2015                                         my @sub_releases = split(/,/, $tmp_release);
2017                                         # Walk through subreleases and build hash tree
2018                                         my $hash;
2019                                         while(my $tmp_sub_release = pop @sub_releases) {
2020                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2021                                         }
2022                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2023                                 } else {
2024                                         # A branch, no subrelease
2025                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2026                                 }
2027                         } elsif (!$entry->exists('cn')) {
2028                                 my $tmp_dn= $entry->dn();
2029                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2031                                 # Skip classes without releases
2032                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2033                                         next;
2034                                 }
2036                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2037                                         # A Subrelease
2038                                         my @sub_releases= split(/,/, $tmp_release);
2040                                         # Walk through subreleases and build hash tree
2041                                         my $hash;
2042                                         while(my $tmp_sub_release = pop @sub_releases) {
2043                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2044                                         }
2045                                         # Remove the last two characters
2046                                         chop($hash);
2047                                         chop($hash);
2049                                         eval('$fai_classes->'.$hash.'= {}');
2050                                 } else {
2051                                         # A branch, no subrelease
2052                                         if(!exists($fai_classes->{$tmp_release})) {
2053                                                 $fai_classes->{$tmp_release} = {};
2054                                         }
2055                                 }
2056                         }
2057                 }
2059                 # The hash is complete, now we can honor the copy-on-write based missing entries
2060                 foreach my $release (keys %$fai_classes) {
2061                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2062                 }
2063         }
2064         return $result;
2067 sub apply_fai_inheritance {
2068        my $fai_classes = shift || return {};
2069        my $tmp_classes;
2071        # Get the classes from the branch
2072        foreach my $class (keys %{$fai_classes}) {
2073                # Skip subreleases
2074                if($class =~ /^ou=.*$/) {
2075                        next;
2076                } else {
2077                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2078                }
2079        }
2081        # Apply to each subrelease
2082        foreach my $subrelease (keys %{$fai_classes}) {
2083                if($subrelease =~ /ou=/) {
2084                        foreach my $tmp_class (keys %{$tmp_classes}) {
2085                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2086                                        $fai_classes->{$subrelease}->{$tmp_class} =
2087                                        deep_copy($tmp_classes->{$tmp_class});
2088                                } else {
2089                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2090                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2091                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2092                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2093                                                }
2094                                        }
2095                                }
2096                        }
2097                }
2098        }
2100        # Find subreleases in deeper levels
2101        foreach my $subrelease (keys %{$fai_classes}) {
2102                if($subrelease =~ /ou=/) {
2103                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2104                                if($subsubrelease =~ /ou=/) {
2105                                        apply_fai_inheritance($fai_classes->{$subrelease});
2106                                }
2107                        }
2108                }
2109        }
2111        return $fai_classes;
2114 sub get_fai_release_entries {
2115         my $tmp_classes = shift || return;
2116         my $parent = shift || "";
2117         my @result = shift || ();
2119         foreach my $entry (keys %{$tmp_classes}) {
2120                 if(defined($entry)) {
2121                         if($entry =~ /^ou=.*$/) {
2122                                 my $release_name = $entry;
2123                                 $release_name =~ s/ou=//g;
2124                                 if(length($parent)>0) {
2125                                         $release_name = $parent."/".$release_name;
2126                                 }
2127                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2128                                 foreach my $bufentry(@bufentries) {
2129                                         push @result, $bufentry;
2130                                 }
2131                         } else {
2132                                 my @types = get_fai_types($tmp_classes->{$entry});
2133                                 foreach my $type (@types) {
2134                                         push @result, 
2135                                         {
2136                                                 'class' => $entry,
2137                                                 'type' => $type->{'type'},
2138                                                 'release' => $parent,
2139                                                 'state' => $type->{'state'},
2140                                         };
2141                                 }
2142                         }
2143                 }
2144         }
2146         return @result;
2149 sub deep_copy {
2150         my $this = shift;
2151         if (not ref $this) {
2152                 $this;
2153         } elsif (ref $this eq "ARRAY") {
2154                 [map deep_copy($_), @$this];
2155         } elsif (ref $this eq "HASH") {
2156                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2157         } else { die "what type is $_?" }
2161 sub session_run_result {
2162     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2163     $kernel->sig(CHLD => "child_reap");
2166 sub session_run_debug {
2167     my $result = $_[ARG0];
2168     print STDERR "$result\n";
2171 sub session_run_done {
2172     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2173     delete $heap->{task}->{$task_id};
2177 sub create_sources_list {
2178         my $session_id = shift;
2179         my $ldap_handle = &main::get_ldap_handle;
2180         my $result="/tmp/gosa_si_tmp_sources_list";
2182         # Remove old file
2183         if(stat($result)) {
2184                 unlink($result);
2185                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2186         }
2188         my $fh;
2189         open($fh, ">$result");
2190         if (not defined $fh) {
2191                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2192                 return undef;
2193         }
2194         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2195                 my $mesg=$ldap_handle->search(
2196                         base    => $main::ldap_server_dn,
2197                         scope   => 'base',
2198                         attrs   => 'FAIrepository',
2199                         filter  => 'objectClass=FAIrepositoryServer'
2200                 );
2201                 if($mesg->count) {
2202                         foreach my $entry(@{$mesg->{'entries'}}) {
2203                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2204                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2205                                         my $line = "deb $server $release";
2206                                         $sections =~ s/,/ /g;
2207                                         $line.= " $sections";
2208                                         print $fh $line."\n";
2209                                 }
2210                         }
2211                 }
2212         } else {
2213                 if (defined $main::ldap_server_dn){
2214                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2215                 } else {
2216                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2217                 }
2218         }
2219         close($fh);
2221         return $result;
2225 sub run_create_packages_list_db {
2226     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2227         my $session_id = $session->ID;
2229         my $task = POE::Wheel::Run->new(
2230                                         Priority => +20,
2231                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2232                                         StdoutEvent  => "session_run_result",
2233                                         StderrEvent  => "session_run_debug",
2234                                         CloseEvent   => "session_run_done",
2235                                         );
2236         $heap->{task}->{ $task->ID } = $task;
2240 sub create_packages_list_db {
2241         my ($ldap_handle, $sources_file, $session_id) = @_;
2242         
2243         # it should not be possible to trigger a recreation of packages_list_db
2244         # while packages_list_db is under construction, so set flag packages_list_under_construction
2245         # which is tested befor recreation can be started
2246         if (-r $packages_list_under_construction) {
2247                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2248                 return;
2249         } else {
2250                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2251                 # set packages_list_under_construction to true
2252                 system("touch $packages_list_under_construction");
2253                 @packages_list_statements=();
2254         }
2256         if (not defined $session_id) { $session_id = 0; }
2257         if (not defined $ldap_handle) { 
2258                 $ldap_handle= &get_ldap_handle();
2260                 if (not defined $ldap_handle) {
2261                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2262                         unlink($packages_list_under_construction);
2263                         return;
2264                 }
2265         }
2266         if (not defined $sources_file) { 
2267                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2268                 $sources_file = &create_sources_list($session_id);
2269         }
2271         if (not defined $sources_file) {
2272                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2273                 unlink($packages_list_under_construction);
2274                 return;
2275         }
2277         my $line;
2279         open(CONFIG, "<$sources_file") or do {
2280                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2281                 unlink($packages_list_under_construction);
2282                 return;
2283         };
2285         # Read lines
2286         while ($line = <CONFIG>){
2287                 # Unify
2288                 chop($line);
2289                 $line =~ s/^\s+//;
2290                 $line =~ s/^\s+/ /;
2292                 # Strip comments
2293                 $line =~ s/#.*$//g;
2295                 # Skip empty lines
2296                 if ($line =~ /^\s*$/){
2297                         next;
2298                 }
2300                 # Interpret deb line
2301                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2302                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2303                         my $section;
2304                         foreach $section (split(' ', $sections)){
2305                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2306                         }
2307                 }
2308         }
2310         close (CONFIG);
2312         find(\&cleanup_and_extract, keys( %repo_dirs ));
2313         &main::strip_packages_list_statements();
2314         unshift @packages_list_statements, "VACUUM";
2315         $packages_list_db->exec_statementlist(\@packages_list_statements);
2316         unlink($packages_list_under_construction);
2317         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2318         return;
2321 # This function should do some intensive task to minimize the db-traffic
2322 sub strip_packages_list_statements {
2323     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2324         my @new_statement_list=();
2325         my $hash;
2326         my $insert_hash;
2327         my $update_hash;
2328         my $delete_hash;
2329         my $local_timestamp=get_time();
2331         foreach my $existing_entry (@existing_entries) {
2332                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2333         }
2335         foreach my $statement (@packages_list_statements) {
2336                 if($statement =~ /^INSERT/i) {
2337                         # Assign the values from the insert statement
2338                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2339                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2340                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2341                                 # If section or description has changed, update the DB
2342                                 if( 
2343                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2344                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2345                                 ) {
2346                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2347                                 }
2348                         } else {
2349                                 # Insert a non-existing entry to db
2350                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2351                         }
2352                 } elsif ($statement =~ /^UPDATE/i) {
2353                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2354                         /^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;
2355                         foreach my $distribution (keys %{$hash}) {
2356                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2357                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2358                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2359                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2360                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2361                                                 my $section;
2362                                                 my $description;
2363                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2364                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2365                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2366                                                 }
2367                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2368                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2369                                                 }
2370                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2371                                         }
2372                                 }
2373                         }
2374                 }
2375         }
2377         # TODO: Check for orphaned entries
2379         # unroll the insert_hash
2380         foreach my $distribution (keys %{$insert_hash}) {
2381                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2382                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2383                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2384                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2385                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2386                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2387                                 ."'$local_timestamp')";
2388                         }
2389                 }
2390         }
2392         # unroll the update hash
2393         foreach my $distribution (keys %{$update_hash}) {
2394                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2395                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2396                                 my $set = "";
2397                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2398                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2399                                 }
2400                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2401                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2402                                 }
2403                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2404                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2405                                 }
2406                                 if(defined($set) and length($set) > 0) {
2407                                         $set .= "timestamp = '$local_timestamp'";
2408                                 } else {
2409                                         next;
2410                                 }
2411                                 push @new_statement_list, 
2412                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2413                                         ." distribution = '$distribution'"
2414                                         ." AND package = '$package'"
2415                                         ." AND version = '$version'";
2416                         }
2417                 }
2418         }
2420         @packages_list_statements = @new_statement_list;
2424 sub parse_package_info {
2425     my ($baseurl, $dist, $section, $session_id)= @_;
2426     my ($package);
2427     if (not defined $session_id) { $session_id = 0; }
2428     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2429     $repo_dirs{ "${repo_path}/pool" } = 1;
2431     foreach $package ("Packages.gz"){
2432         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2433         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2434         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2435     }
2436     
2440 sub get_package {
2441     my ($url, $dest, $session_id)= @_;
2442     if (not defined $session_id) { $session_id = 0; }
2444     my $tpath = dirname($dest);
2445     -d "$tpath" || mkpath "$tpath";
2447     # This is ugly, but I've no time to take a look at "how it works in perl"
2448     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2449         system("gunzip -cd '$dest' > '$dest.in'");
2450         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2451         unlink($dest);
2452         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2453     } else {
2454         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2455     }
2456     return 0;
2460 sub parse_package {
2461     my ($path, $dist, $srv_path, $session_id)= @_;
2462     if (not defined $session_id) { $session_id = 0;}
2463     my ($package, $version, $section, $description);
2464     my $PACKAGES;
2465     my $timestamp = &get_time();
2467     if(not stat("$path.in")) {
2468         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2469         return;
2470     }
2472     open($PACKAGES, "<$path.in");
2473     if(not defined($PACKAGES)) {
2474         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2475         return;
2476     }
2478     # Read lines
2479     while (<$PACKAGES>){
2480         my $line = $_;
2481         # Unify
2482         chop($line);
2484         # Use empty lines as a trigger
2485         if ($line =~ /^\s*$/){
2486             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2487             push(@packages_list_statements, $sql);
2488             $package = "none";
2489             $version = "none";
2490             $section = "none";
2491             $description = "none"; 
2492             next;
2493         }
2495         # Trigger for package name
2496         if ($line =~ /^Package:\s/){
2497             ($package)= ($line =~ /^Package: (.*)$/);
2498             next;
2499         }
2501         # Trigger for version
2502         if ($line =~ /^Version:\s/){
2503             ($version)= ($line =~ /^Version: (.*)$/);
2504             next;
2505         }
2507         # Trigger for description
2508         if ($line =~ /^Description:\s/){
2509             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2510             next;
2511         }
2513         # Trigger for section
2514         if ($line =~ /^Section:\s/){
2515             ($section)= ($line =~ /^Section: (.*)$/);
2516             next;
2517         }
2519         # Trigger for filename
2520         if ($line =~ /^Filename:\s/){
2521             my ($filename) = ($line =~ /^Filename: (.*)$/);
2522             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2523             next;
2524         }
2525     }
2527     close( $PACKAGES );
2528     unlink( "$path.in" );
2529     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2533 sub store_fileinfo {
2534     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2536     my %fileinfo = (
2537         'package' => $package,
2538         'dist' => $dist,
2539         'version' => $vers,
2540     );
2542     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2546 sub cleanup_and_extract {
2547     my $fileinfo = $repo_files{ $File::Find::name };
2549     if( defined $fileinfo ) {
2551         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2552         my $sql;
2553         my $package = $fileinfo->{ 'package' };
2554         my $newver = $fileinfo->{ 'version' };
2556         mkpath($dir);
2557         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2559                 if( -f "$dir/DEBIAN/templates" ) {
2561                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2563                         my $tmpl= "";
2564                         {
2565                                 local $/=undef;
2566                                 open FILE, "$dir/DEBIAN/templates";
2567                                 $tmpl = &encode_base64(<FILE>);
2568                                 close FILE;
2569                         }
2570                         rmtree("$dir/DEBIAN/templates");
2572                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2573                 push @packages_list_statements, $sql;
2574                 }
2575     }
2577     return;
2581 sub register_at_foreign_servers {   
2582     my ($kernel) = $_[KERNEL];
2584     # hole alle bekannten server aus known_server_db
2585     my $sql = "SELECT * FROM $known_server_tn";
2586     my $res = $known_server_db->exec_statement($sql);
2588     # no entries in known_server_db
2589     if (not ref(@$res[0]) eq "ARRAY") { 
2590         # TODO
2591     }
2593     foreach my $hit (@$res) {
2594         my $hostname = @$hit[0];
2595         my $hostkey = &create_passwd;
2597         my %data= ('known_clients' => "",
2598                 'key' => $hostkey,
2599                 );
2600         my $foreign_server_msg = &build_msg('new_server', $server_address, $hostname, \%data);
2601         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
2603         
2604     }
2605     
2606     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
2607     return;
2611 #==== MAIN = main ==============================================================
2612 #  parse commandline options
2613 Getopt::Long::Configure( "bundling" );
2614 GetOptions("h|help" => \&usage,
2615         "c|config=s" => \$cfg_file,
2616         "f|foreground" => \$foreground,
2617         "v|verbose+" => \$verbose,
2618         "no-bus+" => \$no_bus,
2619         "no-arp+" => \$no_arp,
2620            );
2622 #  read and set config parameters
2623 &check_cmdline_param ;
2624 &read_configfile;
2625 &check_pid;
2627 $SIG{CHLD} = 'IGNORE';
2629 # forward error messages to logfile
2630 if( ! $foreground ) {
2631   open( STDIN,  '+>/dev/null' );
2632   open( STDOUT, '+>&STDIN'    );
2633   open( STDERR, '+>&STDIN'    );
2636 # Just fork, if we are not in foreground mode
2637 if( ! $foreground ) { 
2638     chdir '/'                 or die "Can't chdir to /: $!";
2639     $pid = fork;
2640     setsid                    or die "Can't start a new session: $!";
2641     umask 0;
2642 } else { 
2643     $pid = $$; 
2646 # Do something useful - put our PID into the pid_file
2647 if( 0 != $pid ) {
2648     open( LOCK_FILE, ">$pid_file" );
2649     print LOCK_FILE "$pid\n";
2650     close( LOCK_FILE );
2651     if( !$foreground ) { 
2652         exit( 0 ) 
2653     };
2656 # parse head url and revision from svn
2657 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2658 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2659 $server_headURL = defined $1 ? $1 : 'unknown' ;
2660 $server_revision = defined $2 ? $2 : 'unknown' ;
2661 if ($server_headURL =~ /\/tag\// || 
2662         $server_headURL =~ /\/branches\// ) {
2663     $server_status = "stable"; 
2664 } else {
2665     $server_status = "developmental" ;
2669 daemon_log(" ", 1);
2670 daemon_log("$0 started!", 1);
2671 daemon_log("status: $server_status", 1);
2672 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2674 if ($no_bus > 0) {
2675     $bus_activ = "false"
2678 # connect to incoming_db
2679 unlink($incoming_file_name);
2680 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2681 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2683 # connect to gosa-si job queue
2684 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2685 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2687 # connect to known_clients_db
2688 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2689 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2691 # connect to known_server_db
2692 unlink($known_server_file_name);
2693 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2694 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2696 # connect to login_usr_db
2697 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2698 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2700 # connect to fai_server_db and fai_release_db
2701 unlink($fai_server_file_name);
2702 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2703 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2705 unlink($fai_release_file_name);
2706 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2707 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2709 # connect to packages_list_db
2710 #unlink($packages_list_file_name);
2711 unlink($packages_list_under_construction);
2712 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2713 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2715 # connect to messaging_db
2716 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2717 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2720 # create xml object used for en/decrypting
2721 $xml = new XML::Simple();
2724 # foreign servers 
2725 my @foreign_server_list;
2727 # add foreign server from cfg file
2728 if ($foreign_server_string ne "") {
2729     my @cfg_foreign_server_list = split(",", $foreign_server_string);
2730     foreach my $foreign_server (@cfg_foreign_server_list) {
2731         push(@foreign_server_list, $foreign_server);
2732     }
2735 # add foreign server from dns
2736 my @tmp_servers;
2737 if ( !$server_domain) {
2738     # Try our DNS Searchlist
2739     for my $domain(get_dns_domains()) {
2740         chomp($domain);
2741         my @tmp_domains= &get_server_addresses($domain);
2742         if(@tmp_domains) {
2743             for my $tmp_server(@tmp_domains) {
2744                 push @tmp_servers, $tmp_server;
2745             }
2746         }
2747     }
2748     if(@tmp_servers && length(@tmp_servers)==0) {
2749         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2750     }
2751 } else {
2752     @tmp_servers = &get_server_addresses($server_domain);
2753     if( 0 == @tmp_servers ) {
2754         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2755     }
2757 foreach my $server (@tmp_servers) { 
2758     unshift(@foreign_server_list, $server); 
2760 # eliminate duplicate entries
2761 @foreign_server_list = &del_doubles(@foreign_server_list);
2762 my $all_foreign_server = join(", ", @foreign_server_list);
2763 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2765 # add all found foreign servers to known_server
2766 my $act_timestamp = &get_time();
2767 foreach my $foreign_server (@foreign_server_list) {
2768     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
2769             primkey=>['hostname'],
2770             hostname=>$foreign_server,
2771             status=>'not_jet_registered',
2772             hostkey=>"none",
2773             timestamp=>$act_timestamp,
2774             } );
2778 POE::Component::Server::TCP->new(
2779         Port => $server_port,
2780         ClientInput => sub {
2781         my ($kernel, $input) = @_[KERNEL, ARG0];
2782         push(@tasks, $input);
2783         push(@msgs_to_decrypt, $input);
2784         $kernel->yield("msg_to_decrypt");
2785         $kernel->yield("next_task");
2786         },
2787     InlineStates => {
2788         next_task => \&next_task,
2789         msg_to_decrypt => \&msg_to_decrypt,
2790         task_result => \&handle_task_result,
2791         task_done   => \&handle_task_done,
2792         task_debug  => \&handle_task_debug,
2793         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2794     }
2795 );
2797 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2799 # create session for repeatedly checking the job queue for jobs
2800 POE::Session->create(
2801         inline_states => {
2802                 _start => \&_start,
2803         register_at_foreign_servers => \&register_at_foreign_servers,
2804                 sig_handler => \&sig_handler,
2805         watch_for_new_messages => \&watch_for_new_messages,
2806         watch_for_delivery_messages => \&watch_for_delivery_messages,
2807         watch_for_done_messages => \&watch_for_done_messages,
2808                 watch_for_new_jobs => \&watch_for_new_jobs,
2809         watch_for_done_jobs => \&watch_for_done_jobs,
2810         create_packages_list_db => \&run_create_packages_list_db,
2811         create_fai_server_db => \&run_create_fai_server_db,
2812         create_fai_release_db => \&run_create_fai_release_db,
2813         session_run_result => \&session_run_result,
2814         session_run_debug => \&session_run_debug,
2815         session_run_done => \&session_run_done,
2816         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2817         }
2818 );
2821 # import all modules
2822 &import_modules;
2824 # TODO
2825 # check wether all modules are gosa-si valid passwd check
2829 POE::Kernel->run();
2830 exit;