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, $SIPackages_key, 
86     $arp_activ, $gosa_unit_tag,
87     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
88     $foreign_server_string, $server_domain, $foreign_server_key
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 "SIPackages" => {
237     "key" => [\$SIPackages_key, "none"],
238     },
239 "foreign-server"=> {
240     "address"      => [\$foreign_server_string, ""],
241     "domain"  => [\$server_domain, ""],
242     "key"     => [\$foreign_server_key, "none"],
244 );
247 #===  FUNCTION  ================================================================
248 #         NAME:  usage
249 #   PARAMETERS:  nothing
250 #      RETURNS:  nothing
251 #  DESCRIPTION:  print out usage text to STDERR
252 #===============================================================================
253 sub usage {
254     print STDERR << "EOF" ;
255 usage: $prg [-hvf] [-c config]
257            -h        : this (help) message
258            -c <file> : config file
259            -f        : foreground, process will not be forked to background
260            -v        : be verbose (multiple to increase verbosity)
261            -no-bus   : starts $prg without connection to bus
262            -no-arp   : starts $prg without connection to arp module
263  
264 EOF
265     print "\n" ;
269 #===  FUNCTION  ================================================================
270 #         NAME:  read_configfile
271 #   PARAMETERS:  cfg_file - string -
272 #      RETURNS:  nothing
273 #  DESCRIPTION:  read cfg_file and set variables
274 #===============================================================================
275 sub read_configfile {
276     my $cfg;
277     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
278         if( -r $cfg_file ) {
279             $cfg = Config::IniFiles->new( -file => $cfg_file );
280         } else {
281             print STDERR "Couldn't read config file!\n";
282         }
283     } else {
284         $cfg = Config::IniFiles->new() ;
285     }
286     foreach my $section (keys %cfg_defaults) {
287         foreach my $param (keys %{$cfg_defaults{ $section }}) {
288             my $pinfo = $cfg_defaults{ $section }{ $param };
289             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
290         }
291     }
295 #===  FUNCTION  ================================================================
296 #         NAME:  logging
297 #   PARAMETERS:  level - string - default 'info'
298 #                msg - string -
299 #                facility - string - default 'LOG_DAEMON'
300 #      RETURNS:  nothing
301 #  DESCRIPTION:  function for logging
302 #===============================================================================
303 sub daemon_log {
304     # log into log_file
305     my( $msg, $level ) = @_;
306     if(not defined $msg) { return }
307     if(not defined $level) { $level = 1 }
308     if(defined $log_file){
309         open(LOG_HANDLE, ">>$log_file");
310         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
311             print STDERR "cannot open $log_file: $!";
312             return }
313             chomp($msg);
314                         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
315             if($level <= $verbose){
316                 my ($seconds, $minutes, $hours, $monthday, $month,
317                         $year, $weekday, $yearday, $sommertime) = localtime(time);
318                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
319                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
320                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
321                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
322                 $month = $monthnames[$month];
323                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
324                 $year+=1900;
325                 my $name = $prg;
327                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
328                 print LOG_HANDLE $log_msg;
329                 if( $foreground ) { 
330                     print STDERR $log_msg;
331                 }
332             }
333         close( LOG_HANDLE );
334     }
338 #===  FUNCTION  ================================================================
339 #         NAME:  check_cmdline_param
340 #   PARAMETERS:  nothing
341 #      RETURNS:  nothing
342 #  DESCRIPTION:  validates commandline parameter
343 #===============================================================================
344 sub check_cmdline_param () {
345     my $err_config;
346     my $err_counter = 0;
347         if(not defined($cfg_file)) {
348                 $cfg_file = "/etc/gosa-si/server.conf";
349                 if(! -r $cfg_file) {
350                         $err_config = "please specify a config file";
351                         $err_counter += 1;
352                 }
353     }
354     if( $err_counter > 0 ) {
355         &usage( "", 1 );
356         if( defined( $err_config)) { print STDERR "$err_config\n"}
357         print STDERR "\n";
358         exit( -1 );
359     }
363 #===  FUNCTION  ================================================================
364 #         NAME:  check_pid
365 #   PARAMETERS:  nothing
366 #      RETURNS:  nothing
367 #  DESCRIPTION:  handels pid processing
368 #===============================================================================
369 sub check_pid {
370     $pid = -1;
371     # Check, if we are already running
372     if( open(LOCK_FILE, "<$pid_file") ) {
373         $pid = <LOCK_FILE>;
374         if( defined $pid ) {
375             chomp( $pid );
376             if( -f "/proc/$pid/stat" ) {
377                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
378                 if( $stat ) {
379                                         daemon_log("ERROR: Already running",1);
380                     close( LOCK_FILE );
381                     exit -1;
382                 }
383             }
384         }
385         close( LOCK_FILE );
386         unlink( $pid_file );
387     }
389     # create a syslog msg if it is not to possible to open PID file
390     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
391         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
392         if (open(LOCK_FILE, '<', $pid_file)
393                 && ($pid = <LOCK_FILE>))
394         {
395             chomp($pid);
396             $msg .= "(PID $pid)\n";
397         } else {
398             $msg .= "(unable to read PID)\n";
399         }
400         if( ! ($foreground) ) {
401             openlog( $0, "cons,pid", "daemon" );
402             syslog( "warning", $msg );
403             closelog();
404         }
405         else {
406             print( STDERR " $msg " );
407         }
408         exit( -1 );
409     }
412 #===  FUNCTION  ================================================================
413 #         NAME:  import_modules
414 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
415 #                are stored
416 #      RETURNS:  nothing
417 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
418 #                state is on is imported by "require 'file';"
419 #===============================================================================
420 sub import_modules {
421     daemon_log(" ", 1);
423     if (not -e $modules_path) {
424         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
425     }
427     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
428     while (defined (my $file = readdir (DIR))) {
429         if (not $file =~ /(\S*?).pm$/) {
430             next;
431         }
432                 my $mod_name = $1;
434         if( $file =~ /ArpHandler.pm/ ) {
435             if( $no_arp > 0 ) {
436                 next;
437             }
438         }
439         
440         eval { require $file; };
441         if ($@) {
442             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
443             daemon_log("$@", 5);
444                 } else {
445                         my $info = eval($mod_name.'::get_module_info()');
446                         # Only load module if get_module_info() returns a non-null object
447                         if( $info ) {
448                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
449                                 $known_modules->{$mod_name} = $info;
450                                 daemon_log("0 INFO: module $mod_name loaded", 5);
451                         }
452                 }
453     }   
454     close (DIR);
458 #===  FUNCTION  ================================================================
459 #         NAME:  sig_int_handler
460 #   PARAMETERS:  signal - string - signal arose from system
461 #      RETURNS:  noting
462 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
463 #===============================================================================
464 sub sig_int_handler {
465     my ($signal) = @_;
467 #       if (defined($ldap_handle)) {
468 #               $ldap_handle->disconnect;
469 #       }
470     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
471     
473     daemon_log("shutting down gosa-si-server", 1);
474     system("kill `ps -C gosa-si-server-nobus -o pid=`");
476 $SIG{INT} = \&sig_int_handler;
479 sub check_key_and_xml_validity {
480     my ($crypted_msg, $module_key, $session_id) = @_;
481     my $msg;
482     my $msg_hash;
483     my $error_string;
484     eval{
485         $msg = &decrypt_msg($crypted_msg, $module_key);
487         if ($msg =~ /<xml>/i){
488             $msg =~ s/\s+/ /g;  # just for better daemon_log
489             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
490             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
492             ##############
493             # check header
494             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
495             my $header_l = $msg_hash->{'header'};
496             if( 1 > @{$header_l} ) { die 'empty header tag'; }
497             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
498             my $header = @{$header_l}[0];
499             if( 0 == length $header) { die 'empty string in header tag'; }
501             ##############
502             # check source
503             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
504             my $source_l = $msg_hash->{'source'};
505             if( 1 > @{$source_l} ) { die 'empty source tag'; }
506             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
507             my $source = @{$source_l}[0];
508             if( 0 == length $source) { die 'source error'; }
510             ##############
511             # check target
512             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
513             my $target_l = $msg_hash->{'target'};
514             if( 1 > @{$target_l} ) { die 'empty target tag'; }
515         }
516     };
517     if($@) {
518         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
519         $msg = undef;
520         $msg_hash = undef;
521     }
523     return ($msg, $msg_hash);
527 sub check_outgoing_xml_validity {
528     my ($msg) = @_;
530     my $msg_hash;
531     eval{
532         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
534         ##############
535         # check header
536         my $header_l = $msg_hash->{'header'};
537         if( 1 != @{$header_l} ) {
538             die 'no or more than one headers specified';
539         }
540         my $header = @{$header_l}[0];
541         if( 0 == length $header) {
542             die 'header has length 0';
543         }
545         ##############
546         # check source
547         my $source_l = $msg_hash->{'source'};
548         if( 1 != @{$source_l} ) {
549             die 'no or more than 1 sources specified';
550         }
551         my $source = @{$source_l}[0];
552         if( 0 == length $source) {
553             die 'source has length 0';
554         }
555         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
556                 $source =~ /^GOSA$/i ) {
557             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
558         }
559         
560         ##############
561         # check target  
562         my $target_l = $msg_hash->{'target'};
563         if( 0 == @{$target_l} ) {
564             die "no targets specified";
565         }
566         foreach my $target (@$target_l) {
567             if( 0 == length $target) {
568                 die "target has length 0";
569             }
570             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
571                     $target =~ /^GOSA$/i ||
572                     $target =~ /^\*$/ ||
573                     $target =~ /KNOWN_SERVER/i ||
574                     $target =~ /JOBDB/i ||
575                     $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 ){
576                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
577             }
578         }
579     };
580     if($@) {
581         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
582         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
583         $msg_hash = undef;
584     }
586     return ($msg_hash);
590 sub input_from_known_server {
591     my ($input, $remote_ip, $session_id) = @_ ;  
592     my ($msg, $msg_hash, $module);
594     my $sql_statement= "SELECT * FROM known_server";
595     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
597     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
598         my $host_name = $hit->{hostname};
599         if( not $host_name =~ "^$remote_ip") {
600             next;
601         }
602         my $host_key = $hit->{hostkey};
603         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
604         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
606         # check if module can open msg envelope with module key
607         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
608         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
609             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
610             daemon_log("$@", 8);
611             next;
612         }
613         else {
614             $msg = $tmp_msg;
615             $msg_hash = $tmp_msg_hash;
616             $module = "SIPackages";
617             last;
618         }
619     }
621     if( (!$msg) || (!$msg_hash) || (!$module) ) {
622         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
623     }
624   
625     return ($msg, $msg_hash, $module);
629 sub input_from_known_client {
630     my ($input, $remote_ip, $session_id) = @_ ;  
631     my ($msg, $msg_hash, $module);
633     my $sql_statement= "SELECT * FROM known_clients";
634     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
635     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
636         my $host_name = $hit->{hostname};
637         if( not $host_name =~ /^$remote_ip:\d*$/) {
638                 next;
639                 }
640         my $host_key = $hit->{hostkey};
641         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
642         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
644         # check if module can open msg envelope with module key
645         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
647         if( (!$msg) || (!$msg_hash) ) {
648             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
649             &daemon_log("$@", 8);
650             next;
651         }
652         else {
653             $module = "SIPackages";
654             last;
655         }
656     }
658     if( (!$msg) || (!$msg_hash) || (!$module) ) {
659         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
660     }
662     return ($msg, $msg_hash, $module);
666 sub input_from_unknown_host {
667     no strict "refs";
668     my ($input, $session_id) = @_ ;
669     my ($msg, $msg_hash, $module);
670     my $error_string;
671     
672         my %act_modules = %$known_modules;
674         while( my ($mod, $info) = each(%act_modules)) {
676         # check a key exists for this module
677         my $module_key = ${$mod."_key"};
678         if( not defined $module_key ) {
679             if( $mod eq 'ArpHandler' ) {
680                 next;
681             }
682             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
683             next;
684         }
685         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
687         # check if module can open msg envelope with module key
688         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
689         if( (not defined $msg) || (not defined $msg_hash) ) {
690             next;
691         }
692         else {
693             $module = $mod;
694             last;
695         }
696     }
698     if( (!$msg) || (!$msg_hash) || (!$module)) {
699         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
700     }
702     return ($msg, $msg_hash, $module);
706 sub create_ciphering {
707     my ($passwd) = @_;
708         if((!defined($passwd)) || length($passwd)==0) {
709                 $passwd = "";
710         }
711     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
712     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
713     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
714     $my_cipher->set_iv($iv);
715     return $my_cipher;
719 sub encrypt_msg {
720     my ($msg, $key) = @_;
721     my $my_cipher = &create_ciphering($key);
722     my $len;
723     {
724             use bytes;
725             $len= 16-length($msg)%16;
726     }
727     $msg = "\0"x($len).$msg;
728     $msg = $my_cipher->encrypt($msg);
729     chomp($msg = &encode_base64($msg));
730     # there are no newlines allowed inside msg
731     $msg=~ s/\n//g;
732     return $msg;
736 sub decrypt_msg {
738     my ($msg, $key) = @_ ;
739     $msg = &decode_base64($msg);
740     my $my_cipher = &create_ciphering($key);
741     $msg = $my_cipher->decrypt($msg); 
742     $msg =~ s/\0*//g;
743     return $msg;
747 sub get_encrypt_key {
748     my ($target) = @_ ;
749     my $encrypt_key;
750     my $error = 0;
752     # target can be in known_server
753     if( not defined $encrypt_key ) {
754         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
755         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
756         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
757             my $host_name = $hit->{hostname};
758             if( $host_name ne $target ) {
759                 next;
760             }
761             $encrypt_key = $hit->{hostkey};
762             last;
763         }
764     }
766     # target can be in known_client
767     if( not defined $encrypt_key ) {
768         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
769         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
770         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
771             my $host_name = $hit->{hostname};
772             if( $host_name ne $target ) {
773                 next;
774             }
775             $encrypt_key = $hit->{hostkey};
776             last;
777         }
778     }
780     return $encrypt_key;
784 #===  FUNCTION  ================================================================
785 #         NAME:  open_socket
786 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
787 #                [PeerPort] string necessary if port not appended by PeerAddr
788 #      RETURNS:  socket IO::Socket::INET
789 #  DESCRIPTION:  open a socket to PeerAddr
790 #===============================================================================
791 sub open_socket {
792     my ($PeerAddr, $PeerPort) = @_ ;
793     if(defined($PeerPort)){
794         $PeerAddr = $PeerAddr.":".$PeerPort;
795     }
796     my $socket;
797     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
798             Porto => "tcp",
799             Type => SOCK_STREAM,
800             Timeout => 5,
801             );
802     if(not defined $socket) {
803         return;
804     }
805 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
806     return $socket;
810 #===  FUNCTION  ================================================================
811 #         NAME:  get_ip 
812 #   PARAMETERS:  interface name (i.e. eth0)
813 #      RETURNS:  (ip address) 
814 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
815 #===============================================================================
816 sub get_ip {
817         my $ifreq= shift;
818         my $result= "";
819         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
820         my $proto= getprotobyname('ip');
822         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
823                 or die "socket: $!";
825         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
826                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
827                 my ($port, $addr) = sockaddr_in $sin;
828                 my $ip            = inet_ntoa $addr;
830                 if ($ip && length($ip) > 0) {
831                         $result = $ip;
832                 }
833         }
835         return $result;
839 sub get_local_ip_for_remote_ip {
840         my $remote_ip= shift;
841         my $result="0.0.0.0";
843         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
844                 if($remote_ip eq "127.0.0.1") {
845                         $result = "127.0.0.1";
846                 } else {
847                         my $PROC_NET_ROUTE= ('/proc/net/route');
849                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
850                                 or die "Could not open $PROC_NET_ROUTE";
852                         my @ifs = <PROC_NET_ROUTE>;
854                         close(PROC_NET_ROUTE);
856                         # Eat header line
857                         shift @ifs;
858                         chomp @ifs;
859                         foreach my $line(@ifs) {
860                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
861                                 my $destination;
862                                 my $mask;
863                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
864                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
865                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
866                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
867                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
868                                         # destination matches route, save mac and exit
869                                         $result= &get_ip($Iface);
870                                         last;
871                                 }
872                         }
873                 }
874         } else {
875                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
876         }
877         return $result;
881 sub send_msg_to_target {
882     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
883     my $error = 0;
884     my $header;
885     my $new_status;
886     my $act_status;
887     my ($sql_statement, $res);
888   
889     if( $msg_header ) {
890         $header = "'$msg_header'-";
891     } else {
892         $header = "";
893     }
895         # Patch the source ip
896         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
897                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
898                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
899         }
901     # encrypt xml msg
902     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
904     # opensocket
905     my $socket = &open_socket($address);
906     if( !$socket ) {
907         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
908         $error++;
909     }
910     
911     if( $error == 0 ) {
912         # send xml msg
913         print $socket $crypted_msg."\n";
915         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
916         #daemon_log("DEBUG: message:\n$msg", 9);
917         
918     }
920     # close socket in any case
921     if( $socket ) {
922         close $socket;
923     }
925     if( $error > 0 ) { $new_status = "down"; }
926     else { $new_status = $msg_header; }
929     # known_clients
930     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
931     $res = $known_clients_db->select_dbentry($sql_statement);
932     if( keys(%$res) > 0) {
933         $act_status = $res->{1}->{'status'};
934         if( $act_status eq "down" ) {
935             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
936             $res = $known_clients_db->del_dbentry($sql_statement);
937             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
938         } else { 
939             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
940             $res = $known_clients_db->update_dbentry($sql_statement);
941             if($new_status eq "down"){
942                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
943             } else {
944                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
945             }
946         }
947     }
949     # known_server
950     $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
951     $res = $known_server_db->select_dbentry($sql_statement);
952     if( keys(%$res) > 0 ) {
953         $act_status = $res->{1}->{'status'};
954         if( $act_status eq "down" ) {
955             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
956             $res = $known_server_db->del_dbentry($sql_statement);
957             daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
958         } 
959         else { 
960             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
961             $res = $known_server_db->update_dbentry($sql_statement);
962             if($new_status eq "down"){
963                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
964             }
965             else {
966                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
967             }
968         }
969     }
970     return $error; 
974 sub update_jobdb_status_for_send_msgs {
975     my ($answer, $error) = @_;
976     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
977         my $jobdb_id = $1;
978             
979         # sending msg faild
980         if( $error ) {
981             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
982                 my $sql_statement = "UPDATE $job_queue_tn ".
983                     "SET status='error', result='can not deliver msg, please consult log file' ".
984                     "WHERE id=$jobdb_id";
985                 my $res = $job_db->update_dbentry($sql_statement);
986             }
988         # sending msg was successful
989         } else {
990             my $sql_statement = "UPDATE $job_queue_tn ".
991                 "SET status='done' ".
992                 "WHERE id=$jobdb_id AND status='processed'";
993             my $res = $job_db->update_dbentry($sql_statement);
994         }
995     }
998 sub _start {
999     my ($kernel) = $_[KERNEL];
1000     &trigger_db_loop($kernel);
1001     $global_kernel = $kernel;
1002         $kernel->yield('create_fai_server_db', $fai_server_tn );
1003         $kernel->yield('create_fai_release_db', $fai_release_tn );
1004         $kernel->sig(USR1 => "sig_handler");
1005         $kernel->sig(USR2 => "create_packages_list_db");
1008 sub sig_handler {
1009         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1010         daemon_log("0 INFO got signal '$signal'", 1); 
1011         $kernel->sig_handled();
1012         return;
1016 sub msg_to_decrypt {
1017     my ($session, $heap) = @_[SESSION, HEAP];
1018     my $session_id = $session->ID;
1019     my ($msg, $msg_hash, $module);
1020     my $error = 0;
1022     # hole neue msg aus @msgs_to_decrypt
1023     my $next_msg = shift @msgs_to_decrypt;
1024     
1025     # entschlüssle sie
1027     # msg is from a new client or gosa
1028     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1029     # msg is from a gosa-si-server or gosa-si-bus
1030     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1031         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1032     }
1033     # msg is from a gosa-si-client
1034     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1035         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1036     }
1037     # an error occurred
1038     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1039         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1040         # could not understand a msg from its server the client cause a re-registering process
1041         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);
1042         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1043         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1044         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1045             my $host_name = $hit->{'hostname'};
1046             my $host_key = $hit->{'hostkey'};
1047             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1048             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1049             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1050         }
1051         $error++;
1052     }
1053     
1054     # add message to incoming_db
1055     if( $error == 0) {
1056         my $header = @{$msg_hash->{'header'}}[0];
1057         my $target = @{$msg_hash->{'target'}}[0];
1058         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1059                 primkey=>[],
1060                 headertag=>$header,
1061                                 targettag=>$target,
1062                 xmlmessage=>$msg,
1063                 timestamp=>&get_time,
1064                 module=>$module,
1065                 } );
1066         if ($res != 0) {
1067                         # TODO ist das mit $! so ok???
1068             #&daemon_log("$session_id ERROR: cannot add message to incoming.db: $!", 1); 
1069         }
1070     }
1075 sub next_task {
1076     my ($session, $heap) = @_[SESSION, HEAP];
1077     my $task = POE::Wheel::Run->new(
1078             Program => sub { process_task($session, $heap) },
1079             StdioFilter => POE::Filter::Reference->new(),
1080             StdoutEvent  => "task_result",
1081             StderrEvent  => "task_debug",
1082             CloseEvent   => "task_done",
1083             );
1085     $heap->{task}->{ $task->ID } = $task;
1088 sub handle_task_result {
1089     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1090     my $client_answer = $result->{'answer'};
1091     if( $client_answer =~ s/session_id=(\d+)$// ) {
1092         my $session_id = $1;
1093         if( defined $session_id ) {
1094             my $session_reference = $kernel->ID_id_to_session($session_id);
1095             if( defined $session_reference ) {
1096                 $heap = $session_reference->get_heap();
1097             }
1098         }
1100         if(exists $heap->{'client'}) {
1101             $heap->{'client'}->put($client_answer);
1102         }
1103     }
1104     $kernel->sig(CHLD => "child_reap");
1107 sub handle_task_debug {
1108     my $result = $_[ARG0];
1109     print STDERR "$result\n";
1112 sub handle_task_done {
1113     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1114     delete $heap->{task}->{$task_id};
1117 sub process_task {
1118     no strict "refs";
1119     my ($session, $heap, $input) = @_;
1120     my $session_id = $session->ID;
1121     my $error = 0;
1122     my $answer_l;
1123     my ($answer_header, @answer_target_l, $answer_source);
1124     my $client_answer = "";
1126         ##################################################
1127         # fetch first unprocessed message from incoming_db
1128     # sometimes the program is faster than sqlite, so wait until informations are present at db
1129     my $id_sql;
1130     my $id_res;
1131     my $message_id;
1132 # TODO : das hier ist sehr sehr unschön        
1133 # to be tested: speed enhancement with usleep 100000???
1134     while (1) {
1135         $id_sql = "SELECT min(id) FROM $incoming_tn WHERE (NOT headertag LIKE 'answer%')"; 
1136         $id_res = $incoming_db->exec_statement($id_sql);
1137         $message_id = @{@$id_res[0]}[0];
1138         if (defined $message_id) { last }
1139         usleep(100000);
1140     }
1142     # fetch new message from incoming_db
1143     my $sql = "SELECT * FROM $incoming_tn WHERE id=$message_id"; 
1144     my $res = $incoming_db->exec_statement($sql);
1146     # prepare all variables needed to process message
1147     my $msg = @{@$res[0]}[4];
1148     my $incoming_id = @{@$res[0]}[0];
1149     my $module = @{@$res[0]}[5];
1150     my $header =  @{@$res[0]}[2];
1151     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1153     # messages which are an answer to a still running process should not be processed here
1154     if ($header =~ /^answer_(\d+)/) {
1155         return;
1156     }
1157    
1158     # delete message from db 
1159     my $delete_sql = "DELETE FROM $incoming_tn WHERE id=$incoming_id";
1160     my $delete_res = $incoming_db->exec_statement($delete_sql);
1162     ######################
1163     # process incoming msg
1164     if( $error == 0) {
1165         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0].
1166                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1167         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1168         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1170         if ( 0 < @{$answer_l} ) {
1171             my $answer_str = join("\n", @{$answer_l});
1172             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1173                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1174             }
1175             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1176         } else {
1177             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1178         }
1180     }
1181     if( !$answer_l ) { $error++ };
1183     ########
1184     # answer
1185     if( $error == 0 ) {
1187         foreach my $answer ( @{$answer_l} ) {
1188             # check outgoing msg to xml validity
1189             my $answer_hash = &check_outgoing_xml_validity($answer);
1190             if( not defined $answer_hash ) { next; }
1191             
1192             $answer_header = @{$answer_hash->{'header'}}[0];
1193             @answer_target_l = @{$answer_hash->{'target'}};
1194             $answer_source = @{$answer_hash->{'source'}}[0];
1196             # deliver msg to all targets 
1197             foreach my $answer_target ( @answer_target_l ) {
1199                 # targets of msg are all gosa-si-clients in known_clients_db
1200                 if( $answer_target eq "*" ) {
1201                     # answer is for all clients
1202                     my $sql_statement= "SELECT * FROM known_clients";
1203                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1204                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1205                         my $host_name = $hit->{hostname};
1206                         my $host_key = $hit->{hostkey};
1207                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1208                         &update_jobdb_status_for_send_msgs($answer, $error);
1209                     }
1210                 }
1212                 # targets of msg are all gosa-si-server in known_server_db
1213                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1214                     # answer is for all server in known_server
1215                     my $sql_statement= "SELECT * FROM known_server";
1216                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1217                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1218                         my $host_name = $hit->{hostname};
1219                         my $host_key = $hit->{hostkey};
1220                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1221                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1222                         &update_jobdb_status_for_send_msgs($answer, $error);
1223                     }
1224                 }
1226                 # target of msg is GOsa
1227                                 elsif( $answer_target eq "GOSA" ) {
1228                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1229                                         my $add_on = "";
1230                     if( defined $session_id ) {
1231                         $add_on = ".session_id=$session_id";
1232                     }
1233                     # answer is for GOSA and has to returned to connected client
1234                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1235                     $client_answer = $gosa_answer.$add_on;
1236                 }
1238                 # target of msg is job queue at this host
1239                 elsif( $answer_target eq "JOBDB") {
1240                     $answer =~ /<header>(\S+)<\/header>/;   
1241                     my $header;
1242                     if( defined $1 ) { $header = $1; }
1243                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1244                     &update_jobdb_status_for_send_msgs($answer, $error);
1245                 }
1247                 # target of msg is a mac address
1248                 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 ) {
1249                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1250                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1251                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1252                     my $found_ip_flag = 0;
1253                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1254                         my $host_name = $hit->{hostname};
1255                         my $host_key = $hit->{hostkey};
1256                         $answer =~ s/$answer_target/$host_name/g;
1257                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1258                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1259                         &update_jobdb_status_for_send_msgs($answer, $error);
1260                         $found_ip_flag++ ;
1261                     }   
1262                     if( $found_ip_flag == 0) {
1263                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1264                         if( $bus_activ eq "true" ) { 
1265                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1266                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1267                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1268                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1269                                 my $bus_address = $hit->{hostname};
1270                                 my $bus_key = $hit->{hostkey};
1271                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1272                                 &update_jobdb_status_for_send_msgs($answer, $error);
1273                                 last;
1274                             }
1275                         }
1277                     }
1279                 #  answer is for one specific host   
1280                 } else {
1281                     # get encrypt_key
1282                     my $encrypt_key = &get_encrypt_key($answer_target);
1283                     if( not defined $encrypt_key ) {
1284                         # unknown target, forward msg to bus
1285                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1286                         if( $bus_activ eq "true" ) { 
1287                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1288                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1289                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1290                             my $res_length = keys( %{$query_res} );
1291                             if( $res_length == 0 ){
1292                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1293                                         "no bus found in known_server", 3);
1294                             }
1295                             else {
1296                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1297                                     my $bus_key = $hit->{hostkey};
1298                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1299                                     &update_jobdb_status_for_send_msgs($answer, $error);
1300                                 }
1301                             }
1302                         }
1303                         next;
1304                     }
1305                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1306                     &update_jobdb_status_for_send_msgs($answer, $error);
1307                 }
1308             }
1309         }
1310     }
1312     my $filter = POE::Filter::Reference->new();
1313     my %result = ( 
1314             status => "seems ok to me",
1315             answer => $client_answer,
1316             );
1318     my $output = $filter->put( [ \%result ] );
1319     print @$output;
1325 sub trigger_db_loop {
1326         my ($kernel) = @_ ;
1327         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1328         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1329         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1330     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1331         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1335 sub watch_for_done_jobs {
1336     my ($kernel,$heap) = @_[KERNEL, HEAP];
1338     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1339         " WHERE status='done'";
1340         my $res = $job_db->select_dbentry( $sql_statement );
1342     while( my ($id, $hit) = each %{$res} ) {
1343         my $jobdb_id = $hit->{id};
1344         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1345         my $res = $job_db->del_dbentry($sql_statement); 
1346     }
1348     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1352 sub watch_for_new_jobs {
1353         if($watch_for_new_jobs_in_progress == 0) {
1354                 $watch_for_new_jobs_in_progress = 1;
1355                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1357                 # check gosa job queue for jobs with executable timestamp
1358                 my $timestamp = &get_time();
1359                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1360                 my $res = $job_db->exec_statement( $sql_statement );
1362                 # Merge all new jobs that would do the same actions
1363                 my @drops;
1364                 my $hits;
1365                 foreach my $hit (reverse @{$res} ) {
1366                         my $macaddress= lc @{$hit}[8];
1367                         my $headertag= @{$hit}[5];
1368                         if(
1369                                 defined($hits->{$macaddress}) &&
1370                                 defined($hits->{$macaddress}->{$headertag}) &&
1371                                 defined($hits->{$macaddress}->{$headertag}[0])
1372                         ) {
1373                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1374                         }
1375                         $hits->{$macaddress}->{$headertag}= $hit;
1376                 }
1378                 # Delete new jobs with a matching job in state 'processing'
1379                 foreach my $macaddress (keys %{$hits}) {
1380                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1381                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1382                                 if(defined($jobdb_id)) {
1383                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1384                                         my $res = $job_db->exec_statement( $sql_statement );
1385                                         foreach my $hit (@{$res}) {
1386                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1387                                         }
1388                                 } else {
1389                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1390                                 }
1391                         }
1392                 }
1394                 # Commit deletion
1395                 $job_db->exec_statementlist(\@drops);
1397                 # Look for new jobs that could be executed
1398                 foreach my $macaddress (keys %{$hits}) {
1400                         # Look if there is an executing job
1401                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1402                         my $res = $job_db->exec_statement( $sql_statement );
1404                         # Skip new jobs for host if there is a processing job
1405                         if(defined($res) and defined @{$res}[0]) {
1406                                 next;
1407                         }
1409                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1410                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1411                                 if(defined($jobdb_id)) {
1412                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1414                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1415                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1416                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1418                                         # expect macaddress is unique!!!!!!
1419                                         my $target = $res_hash->{1}->{hostname};
1421                                         # change header
1422                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1424                                         # add sqlite_id
1425                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1427                                         $job_msg =~ /<header>(\S+)<\/header>/;
1428                                         my $header = $1 ;
1429                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1431                                         # update status in job queue to 'processing'
1432                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1433                                         my $res = $job_db->update_dbentry($sql_statement);
1434 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1436                                         # We don't want parallel processing
1437                                         last;
1438                                 }
1439                         }
1440                 }
1442                 $watch_for_new_jobs_in_progress = 0;
1443                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1444         }
1448 sub watch_for_new_messages {
1449     my ($kernel,$heap) = @_[KERNEL, HEAP];
1450     my @coll_user_msg;   # collection list of outgoing messages
1451     
1452     # check messaging_db for new incoming messages with executable timestamp
1453     my $timestamp = &get_time();
1454     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1455     my $res = $messaging_db->exec_statement( $sql_statement );
1456         foreach my $hit (@{$res}) {
1458         # create outgoing messages
1459         my $message_to = @{$hit}[3];
1460         # translate message_to to plain login name
1461         my @message_to_l = split(/,/, $message_to);  
1462                 my %receiver_h; 
1463                 foreach my $receiver (@message_to_l) {
1464                         if ($receiver =~ /^u_([\s\S]*)$/) {
1465                                 $receiver_h{$1} = 0;
1466                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1467                                 my $group_name = $1;
1468                                 # fetch all group members from ldap and add them to receiver hash
1469                                 my $ldap_handle = &get_ldap_handle();
1470                                 if (defined $ldap_handle) {
1471                                                 my $mesg = $ldap_handle->search(
1472                                                                                 base => $ldap_base,
1473                                                                                 scope => 'sub',
1474                                                                                 attrs => ['memberUid'],
1475                                                                                 filter => "cn=$group_name",
1476                                                                                 );
1477                                                 if ($mesg->count) {
1478                                                                 my @entries = $mesg->entries;
1479                                                                 foreach my $entry (@entries) {
1480                                                                                 my @receivers= $entry->get_value("memberUid");
1481                                                                                 foreach my $receiver (@receivers) { 
1482                                                                                                 $receiver_h{$1} = 0;
1483                                                                                 }
1484                                                                 }
1485                                                 } 
1486                                                 # translating errors ?
1487                                                 if ($mesg->code) {
1488                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1489                                                 }
1490                                 # ldap handle error ?           
1491                                 } else {
1492                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1493                                 }
1494                         } else {
1495                                 my $sbjct = &encode_base64(@{$hit}[1]);
1496                                 my $msg = &encode_base64(@{$hit}[7]);
1497                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1498                         }
1499                 }
1500                 my @receiver_l = keys(%receiver_h);
1502         my $message_id = @{$hit}[0];
1504         #add each outgoing msg to messaging_db
1505         my $receiver;
1506         foreach $receiver (@receiver_l) {
1507             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1508                 "VALUES ('".
1509                 $message_id."', '".    # id
1510                 @{$hit}[1]."', '".     # subject
1511                 @{$hit}[2]."', '".     # message_from
1512                 $receiver."', '".      # message_to
1513                 "none"."', '".         # flag
1514                 "out"."', '".          # direction
1515                 @{$hit}[6]."', '".     # delivery_time
1516                 @{$hit}[7]."', '".     # message
1517                 $timestamp."'".     # timestamp
1518                 ")";
1519             &daemon_log("M DEBUG: $sql_statement", 1);
1520             my $res = $messaging_db->exec_statement($sql_statement);
1521             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1522         }
1524         # set incoming message to flag d=deliverd
1525         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1526         &daemon_log("M DEBUG: $sql_statement", 7);
1527         $res = $messaging_db->update_dbentry($sql_statement);
1528         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1529     }
1531     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1532     return;
1535 sub watch_for_delivery_messages {
1536     my ($kernel, $heap) = @_[KERNEL, HEAP];
1538     # select outgoing messages
1539     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1540     #&daemon_log("0 DEBUG: $sql", 7);
1541     my $res = $messaging_db->exec_statement( $sql_statement );
1542     
1543     # build out msg for each    usr
1544     foreach my $hit (@{$res}) {
1545         my $receiver = @{$hit}[3];
1546         my $msg_id = @{$hit}[0];
1547         my $subject = @{$hit}[1];
1548         my $message = @{$hit}[7];
1550         # resolve usr -> host where usr is logged in
1551         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1552         #&daemon_log("0 DEBUG: $sql", 7);
1553         my $res = $login_users_db->exec_statement($sql);
1555         # reciver is logged in nowhere
1556         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1558                 my $send_succeed = 0;
1559                 foreach my $hit (@$res) {
1560                                 my $receiver_host = @$hit[0];
1561                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1563                                 # fetch key to encrypt msg propperly for usr/host
1564                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1565                                 &daemon_log("0 DEBUG: $sql", 7);
1566                                 my $res = $known_clients_db->exec_statement($sql);
1568                                 # host is already down
1569                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1571                                 # host is on
1572                                 my $receiver_key = @{@{$res}[0]}[2];
1573                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1574                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1575                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1576                                 if ($error == 0 ) {
1577                                         $send_succeed++ ;
1578                                 }
1579                 }
1581                 if ($send_succeed) {
1582                                 # set outgoing msg at db to deliverd
1583                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1584                                 &daemon_log("0 DEBUG: $sql", 7);
1585                                 my $res = $messaging_db->exec_statement($sql); 
1586                 }
1587         }
1589     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1590     return;
1594 sub watch_for_done_messages {
1595     my ($kernel,$heap) = @_[KERNEL, HEAP];
1597     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1598     #&daemon_log("0 DEBUG: $sql", 7);
1599     my $res = $messaging_db->exec_statement($sql); 
1601     foreach my $hit (@{$res}) {
1602         my $msg_id = @{$hit}[0];
1604         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1605         #&daemon_log("0 DEBUG: $sql", 7); 
1606         my $res = $messaging_db->exec_statement($sql);
1608         # not all usr msgs have been seen till now
1609         if ( ref(@$res[0]) eq "ARRAY") { next; }
1610         
1611         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1612         #&daemon_log("0 DEBUG: $sql", 7);
1613         $res = $messaging_db->exec_statement($sql);
1614     
1615     }
1617     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1618     return;
1622 sub get_ldap_handle {
1623         my ($session_id) = @_;
1624         my $heap;
1625         my $ldap_handle;
1627         if (not defined $session_id ) { $session_id = 0 };
1628         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1630         if ($session_id == 0) {
1631                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1632                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1633                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1635         } else {
1636                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1637                 if( defined $session_reference ) {
1638                         $heap = $session_reference->get_heap();
1639                 }
1641                 if (not defined $heap) {
1642                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1643                         return;
1644                 }
1646                 # TODO: This "if" is nonsense, because it doesn't prove that the
1647                 #       used handle is still valid - or if we've to reconnect...
1648                 #if (not exists $heap->{ldap_handle}) {
1649                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1650                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1651                         $heap->{ldap_handle} = $ldap_handle;
1652                 #}
1653         }
1654         return $ldap_handle;
1658 sub change_fai_state {
1659     my ($st, $targets, $session_id) = @_;
1660     $session_id = 0 if not defined $session_id;
1661     # Set FAI state to localboot
1662     my %mapActions= (
1663         reboot    => '',
1664         update    => 'softupdate',
1665         localboot => 'localboot',
1666         reinstall => 'install',
1667         rescan    => '',
1668         wake      => '',
1669         memcheck  => 'memcheck',
1670         sysinfo   => 'sysinfo',
1671         install   => 'install',
1672     );
1674     # Return if this is unknown
1675     if (!exists $mapActions{ $st }){
1676         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1677       return;
1678     }
1680     my $state= $mapActions{ $st };
1682     my $ldap_handle = &get_ldap_handle($session_id);
1683     if( defined($ldap_handle) ) {
1685       # Build search filter for hosts
1686         my $search= "(&(objectClass=GOhard)";
1687         foreach (@{$targets}){
1688             $search.= "(macAddress=$_)";
1689         }
1690         $search.= ")";
1692       # If there's any host inside of the search string, procress them
1693         if (!($search =~ /macAddress/)){
1694             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1695             return;
1696         }
1698       # Perform search for Unit Tag
1699       my $mesg = $ldap_handle->search(
1700           base   => $ldap_base,
1701           scope  => 'sub',
1702           attrs  => ['dn', 'FAIstate', 'objectClass'],
1703           filter => "$search"
1704           );
1706           if ($mesg->count) {
1707                   my @entries = $mesg->entries;
1708                   foreach my $entry (@entries) {
1709                           # Only modify entry if it is not set to '$state'
1710                           if ($entry->get_value("FAIstate") ne "$state"){
1711                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1712                                   my $result;
1713                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1714                                   if (exists $tmp{'FAIobject'}){
1715                                           if ($state eq ''){
1716                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1717                                                           delete => [ FAIstate => [] ] ]);
1718                                           } else {
1719                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1720                                                           replace => [ FAIstate => $state ] ]);
1721                                           }
1722                                   } elsif ($state ne ''){
1723                                           $result= $ldap_handle->modify($entry->dn, changes => [
1724                                                   add     => [ objectClass => 'FAIobject' ],
1725                                                   add     => [ FAIstate => $state ] ]);
1726                                   }
1728                                   # Errors?
1729                                   if ($result->code){
1730                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1731                                   }
1732                           } else {
1733                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1734                           }  
1735                   }
1736           }
1737     # if no ldap handle defined
1738     } else {
1739         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1740     }
1745 sub change_goto_state {
1746     my ($st, $targets, $session_id) = @_;
1747     $session_id = 0  if not defined $session_id;
1749     # Switch on or off?
1750     my $state= $st eq 'active' ? 'active': 'locked';
1752     my $ldap_handle = &get_ldap_handle($session_id);
1753     if( defined($ldap_handle) ) {
1755       # Build search filter for hosts
1756       my $search= "(&(objectClass=GOhard)";
1757       foreach (@{$targets}){
1758         $search.= "(macAddress=$_)";
1759       }
1760       $search.= ")";
1762       # If there's any host inside of the search string, procress them
1763       if (!($search =~ /macAddress/)){
1764         return;
1765       }
1767       # Perform search for Unit Tag
1768       my $mesg = $ldap_handle->search(
1769           base   => $ldap_base,
1770           scope  => 'sub',
1771           attrs  => ['dn', 'gotoMode'],
1772           filter => "$search"
1773           );
1775       if ($mesg->count) {
1776         my @entries = $mesg->entries;
1777         foreach my $entry (@entries) {
1779           # Only modify entry if it is not set to '$state'
1780           if ($entry->get_value("gotoMode") ne $state){
1782             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1783             my $result;
1784             $result= $ldap_handle->modify($entry->dn, changes => [
1785                                                 replace => [ gotoMode => $state ] ]);
1787             # Errors?
1788             if ($result->code){
1789               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1790             }
1792           }
1793         }
1794       }
1796     }
1800 sub run_create_fai_server_db {
1801     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1802     my $session_id = $session->ID;
1803     my $task = POE::Wheel::Run->new(
1804             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1805             StdoutEvent  => "session_run_result",
1806             StderrEvent  => "session_run_debug",
1807             CloseEvent   => "session_run_done",
1808             );
1810     $heap->{task}->{ $task->ID } = $task;
1811     return;
1815 sub create_fai_server_db {
1816     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1817         my $result;
1819         if (not defined $session_id) { $session_id = 0; }
1820     my $ldap_handle = &get_ldap_handle();
1821         if(defined($ldap_handle)) {
1822                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1823                 my $mesg= $ldap_handle->search(
1824                         base   => $ldap_base,
1825                         scope  => 'sub',
1826                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1827                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1828                 );
1829                 if($mesg->{'resultCode'} == 0 &&
1830                    $mesg->count != 0) {
1831                    foreach my $entry (@{$mesg->{entries}}) {
1832                            if($entry->exists('FAIrepository')) {
1833                                    # Add an entry for each Repository configured for server
1834                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1835                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1836                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1837                                                    $result= $fai_server_db->add_dbentry( { 
1838                                                                    table => $table_name,
1839                                                                    primkey => ['server', 'release', 'tag'],
1840                                                                    server => $tmp_url,
1841                                                                    release => $tmp_release,
1842                                                                    sections => $tmp_sections,
1843                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1844                                                            } );
1845                                            }
1846                                    }
1847                            }
1848                    }
1849                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1851                 # TODO: Find a way to post the 'create_packages_list_db' event
1852                 if(not defined($dont_create_packages_list)) {
1853                         &create_packages_list_db(undef, undef, $session_id);
1854                 }
1855         }       
1856     
1857     $ldap_handle->disconnect;
1858         return $result;
1862 sub run_create_fai_release_db {
1863     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1864         my $session_id = $session->ID;
1865     my $task = POE::Wheel::Run->new(
1866             Program => sub { &create_fai_release_db($table_name, $session_id) },
1867             StdoutEvent  => "session_run_result",
1868             StderrEvent  => "session_run_debug",
1869             CloseEvent   => "session_run_done",
1870             );
1872     $heap->{task}->{ $task->ID } = $task;
1873     return;
1877 sub create_fai_release_db {
1878         my ($table_name, $session_id) = @_;
1879         my $result;
1881     # used for logging
1882     if (not defined $session_id) { $session_id = 0; }
1884     my $ldap_handle = &get_ldap_handle();
1885         if(defined($ldap_handle)) {
1886                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1887                 my $mesg= $ldap_handle->search(
1888                         base   => $ldap_base,
1889                         scope  => 'sub',
1890                         attrs  => [],
1891                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1892                 );
1893                 if($mesg->{'resultCode'} == 0 &&
1894                         $mesg->count != 0) {
1895                         # Walk through all possible FAI container ou's
1896                         my @sql_list;
1897                         my $timestamp= &get_time();
1898                         foreach my $ou (@{$mesg->{entries}}) {
1899                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1900                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1901                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1902                                         if(@tmp_array) {
1903                                                 foreach my $entry (@tmp_array) {
1904                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1905                                                                 my $sql= 
1906                                                                 "INSERT INTO $table_name "
1907                                                                 ."(timestamp, release, class, type, state) VALUES ("
1908                                                                 .$timestamp.","
1909                                                                 ."'".$entry->{'release'}."',"
1910                                                                 ."'".$entry->{'class'}."',"
1911                                                                 ."'".$entry->{'type'}."',"
1912                                                                 ."'".$entry->{'state'}."')";
1913                                                                 push @sql_list, $sql;
1914                                                         }
1915                                                 }
1916                                         }
1917                                 }
1918                         }
1920                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1921                         if(@sql_list) {
1922                                 unshift @sql_list, "VACUUM";
1923                                 unshift @sql_list, "DELETE FROM $table_name";
1924                                 $fai_release_db->exec_statementlist(\@sql_list);
1925                         }
1926                         daemon_log("$session_id DEBUG: Done with inserting",7);
1927                 }
1928                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1929         }
1930     $ldap_handle->disconnect;
1931         return $result;
1934 sub get_fai_types {
1935         my $tmp_classes = shift || return undef;
1936         my @result;
1938         foreach my $type(keys %{$tmp_classes}) {
1939                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1940                         my $entry = {
1941                                 type => $type,
1942                                 state => $tmp_classes->{$type}[0],
1943                         };
1944                         push @result, $entry;
1945                 }
1946         }
1948         return @result;
1951 sub get_fai_state {
1952         my $result = "";
1953         my $tmp_classes = shift || return $result;
1955         foreach my $type(keys %{$tmp_classes}) {
1956                 if(defined($tmp_classes->{$type}[0])) {
1957                         $result = $tmp_classes->{$type}[0];
1958                         
1959                 # State is equal for all types in class
1960                         last;
1961                 }
1962         }
1964         return $result;
1967 sub resolve_fai_classes {
1968         my ($fai_base, $ldap_handle, $session_id) = @_;
1969         if (not defined $session_id) { $session_id = 0; }
1970         my $result;
1971         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1972         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1973         my $fai_classes;
1975         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
1976         my $mesg= $ldap_handle->search(
1977                 base   => $fai_base,
1978                 scope  => 'sub',
1979                 attrs  => ['cn','objectClass','FAIstate'],
1980                 filter => $fai_filter,
1981         );
1982         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
1984         if($mesg->{'resultCode'} == 0 &&
1985                 $mesg->count != 0) {
1986                 foreach my $entry (@{$mesg->{entries}}) {
1987                         if($entry->exists('cn')) {
1988                                 my $tmp_dn= $entry->dn();
1990                                 # Skip classname and ou dn parts for class
1991                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1993                                 # Skip classes without releases
1994                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1995                                         next;
1996                                 }
1998                                 my $tmp_cn= $entry->get_value('cn');
1999                                 my $tmp_state= $entry->get_value('FAIstate');
2001                                 my $tmp_type;
2002                                 # Get FAI type
2003                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2004                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2005                                                 $tmp_type= $oclass;
2006                                                 last;
2007                                         }
2008                                 }
2010                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2011                                         # A Subrelease
2012                                         my @sub_releases = split(/,/, $tmp_release);
2014                                         # Walk through subreleases and build hash tree
2015                                         my $hash;
2016                                         while(my $tmp_sub_release = pop @sub_releases) {
2017                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2018                                         }
2019                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2020                                 } else {
2021                                         # A branch, no subrelease
2022                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2023                                 }
2024                         } elsif (!$entry->exists('cn')) {
2025                                 my $tmp_dn= $entry->dn();
2026                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2028                                 # Skip classes without releases
2029                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2030                                         next;
2031                                 }
2033                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2034                                         # A Subrelease
2035                                         my @sub_releases= split(/,/, $tmp_release);
2037                                         # Walk through subreleases and build hash tree
2038                                         my $hash;
2039                                         while(my $tmp_sub_release = pop @sub_releases) {
2040                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2041                                         }
2042                                         # Remove the last two characters
2043                                         chop($hash);
2044                                         chop($hash);
2046                                         eval('$fai_classes->'.$hash.'= {}');
2047                                 } else {
2048                                         # A branch, no subrelease
2049                                         if(!exists($fai_classes->{$tmp_release})) {
2050                                                 $fai_classes->{$tmp_release} = {};
2051                                         }
2052                                 }
2053                         }
2054                 }
2056                 # The hash is complete, now we can honor the copy-on-write based missing entries
2057                 foreach my $release (keys %$fai_classes) {
2058                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2059                 }
2060         }
2061         return $result;
2064 sub apply_fai_inheritance {
2065        my $fai_classes = shift || return {};
2066        my $tmp_classes;
2068        # Get the classes from the branch
2069        foreach my $class (keys %{$fai_classes}) {
2070                # Skip subreleases
2071                if($class =~ /^ou=.*$/) {
2072                        next;
2073                } else {
2074                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2075                }
2076        }
2078        # Apply to each subrelease
2079        foreach my $subrelease (keys %{$fai_classes}) {
2080                if($subrelease =~ /ou=/) {
2081                        foreach my $tmp_class (keys %{$tmp_classes}) {
2082                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2083                                        $fai_classes->{$subrelease}->{$tmp_class} =
2084                                        deep_copy($tmp_classes->{$tmp_class});
2085                                } else {
2086                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2087                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2088                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2089                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2090                                                }
2091                                        }
2092                                }
2093                        }
2094                }
2095        }
2097        # Find subreleases in deeper levels
2098        foreach my $subrelease (keys %{$fai_classes}) {
2099                if($subrelease =~ /ou=/) {
2100                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2101                                if($subsubrelease =~ /ou=/) {
2102                                        apply_fai_inheritance($fai_classes->{$subrelease});
2103                                }
2104                        }
2105                }
2106        }
2108        return $fai_classes;
2111 sub get_fai_release_entries {
2112         my $tmp_classes = shift || return;
2113         my $parent = shift || "";
2114         my @result = shift || ();
2116         foreach my $entry (keys %{$tmp_classes}) {
2117                 if(defined($entry)) {
2118                         if($entry =~ /^ou=.*$/) {
2119                                 my $release_name = $entry;
2120                                 $release_name =~ s/ou=//g;
2121                                 if(length($parent)>0) {
2122                                         $release_name = $parent."/".$release_name;
2123                                 }
2124                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2125                                 foreach my $bufentry(@bufentries) {
2126                                         push @result, $bufentry;
2127                                 }
2128                         } else {
2129                                 my @types = get_fai_types($tmp_classes->{$entry});
2130                                 foreach my $type (@types) {
2131                                         push @result, 
2132                                         {
2133                                                 'class' => $entry,
2134                                                 'type' => $type->{'type'},
2135                                                 'release' => $parent,
2136                                                 'state' => $type->{'state'},
2137                                         };
2138                                 }
2139                         }
2140                 }
2141         }
2143         return @result;
2146 sub deep_copy {
2147         my $this = shift;
2148         if (not ref $this) {
2149                 $this;
2150         } elsif (ref $this eq "ARRAY") {
2151                 [map deep_copy($_), @$this];
2152         } elsif (ref $this eq "HASH") {
2153                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2154         } else { die "what type is $_?" }
2158 sub session_run_result {
2159     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2160     $kernel->sig(CHLD => "child_reap");
2163 sub session_run_debug {
2164     my $result = $_[ARG0];
2165     print STDERR "$result\n";
2168 sub session_run_done {
2169     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2170     delete $heap->{task}->{$task_id};
2174 sub create_sources_list {
2175         my $session_id = shift;
2176         my $ldap_handle = &main::get_ldap_handle;
2177         my $result="/tmp/gosa_si_tmp_sources_list";
2179         # Remove old file
2180         if(stat($result)) {
2181                 unlink($result);
2182                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2183         }
2185         my $fh;
2186         open($fh, ">$result");
2187         if (not defined $fh) {
2188                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2189                 return undef;
2190         }
2191         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2192                 my $mesg=$ldap_handle->search(
2193                         base    => $main::ldap_server_dn,
2194                         scope   => 'base',
2195                         attrs   => 'FAIrepository',
2196                         filter  => 'objectClass=FAIrepositoryServer'
2197                 );
2198                 if($mesg->count) {
2199                         foreach my $entry(@{$mesg->{'entries'}}) {
2200                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2201                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2202                                         my $line = "deb $server $release";
2203                                         $sections =~ s/,/ /g;
2204                                         $line.= " $sections";
2205                                         print $fh $line."\n";
2206                                 }
2207                         }
2208                 }
2209         } else {
2210                 if (defined $main::ldap_server_dn){
2211                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2212                 } else {
2213                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2214                 }
2215         }
2216         close($fh);
2218         return $result;
2222 sub run_create_packages_list_db {
2223     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2224         my $session_id = $session->ID;
2226         my $task = POE::Wheel::Run->new(
2227                                         Priority => +20,
2228                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2229                                         StdoutEvent  => "session_run_result",
2230                                         StderrEvent  => "session_run_debug",
2231                                         CloseEvent   => "session_run_done",
2232                                         );
2233         $heap->{task}->{ $task->ID } = $task;
2237 sub create_packages_list_db {
2238         my ($ldap_handle, $sources_file, $session_id) = @_;
2239         
2240         # it should not be possible to trigger a recreation of packages_list_db
2241         # while packages_list_db is under construction, so set flag packages_list_under_construction
2242         # which is tested befor recreation can be started
2243         if (-r $packages_list_under_construction) {
2244                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2245                 return;
2246         } else {
2247                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2248                 # set packages_list_under_construction to true
2249                 system("touch $packages_list_under_construction");
2250                 @packages_list_statements=();
2251         }
2253         if (not defined $session_id) { $session_id = 0; }
2254         if (not defined $ldap_handle) { 
2255                 $ldap_handle= &get_ldap_handle();
2257                 if (not defined $ldap_handle) {
2258                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2259                         unlink($packages_list_under_construction);
2260                         return;
2261                 }
2262         }
2263         if (not defined $sources_file) { 
2264                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2265                 $sources_file = &create_sources_list($session_id);
2266         }
2268         if (not defined $sources_file) {
2269                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2270                 unlink($packages_list_under_construction);
2271                 return;
2272         }
2274         my $line;
2276         open(CONFIG, "<$sources_file") or do {
2277                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2278                 unlink($packages_list_under_construction);
2279                 return;
2280         };
2282         # Read lines
2283         while ($line = <CONFIG>){
2284                 # Unify
2285                 chop($line);
2286                 $line =~ s/^\s+//;
2287                 $line =~ s/^\s+/ /;
2289                 # Strip comments
2290                 $line =~ s/#.*$//g;
2292                 # Skip empty lines
2293                 if ($line =~ /^\s*$/){
2294                         next;
2295                 }
2297                 # Interpret deb line
2298                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2299                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2300                         my $section;
2301                         foreach $section (split(' ', $sections)){
2302                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2303                         }
2304                 }
2305         }
2307         close (CONFIG);
2309         find(\&cleanup_and_extract, keys( %repo_dirs ));
2310         &main::strip_packages_list_statements();
2311         unshift @packages_list_statements, "VACUUM";
2312         $packages_list_db->exec_statementlist(\@packages_list_statements);
2313         unlink($packages_list_under_construction);
2314         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2315         return;
2318 # This function should do some intensive task to minimize the db-traffic
2319 sub strip_packages_list_statements {
2320     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2321         my @new_statement_list=();
2322         my $hash;
2323         my $insert_hash;
2324         my $update_hash;
2325         my $delete_hash;
2326         my $local_timestamp=get_time();
2328         foreach my $existing_entry (@existing_entries) {
2329                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2330         }
2332         foreach my $statement (@packages_list_statements) {
2333                 if($statement =~ /^INSERT/i) {
2334                         # Assign the values from the insert statement
2335                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2336                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2337                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2338                                 # If section or description has changed, update the DB
2339                                 if( 
2340                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2341                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2342                                 ) {
2343                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2344                                 }
2345                         } else {
2346                                 # Insert a non-existing entry to db
2347                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2348                         }
2349                 } elsif ($statement =~ /^UPDATE/i) {
2350                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2351                         /^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;
2352                         foreach my $distribution (keys %{$hash}) {
2353                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2354                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2355                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2356                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2357                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2358                                                 my $section;
2359                                                 my $description;
2360                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2361                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2362                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2363                                                 }
2364                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2365                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2366                                                 }
2367                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2368                                         }
2369                                 }
2370                         }
2371                 }
2372         }
2374         # TODO: Check for orphaned entries
2376         # unroll the insert_hash
2377         foreach my $distribution (keys %{$insert_hash}) {
2378                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2379                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2380                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2381                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2382                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2383                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2384                                 ."'$local_timestamp')";
2385                         }
2386                 }
2387         }
2389         # unroll the update hash
2390         foreach my $distribution (keys %{$update_hash}) {
2391                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2392                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2393                                 my $set = "";
2394                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2395                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2396                                 }
2397                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2398                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2399                                 }
2400                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2401                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2402                                 }
2403                                 if(defined($set) and length($set) > 0) {
2404                                         $set .= "timestamp = '$local_timestamp'";
2405                                 } else {
2406                                         next;
2407                                 }
2408                                 push @new_statement_list, 
2409                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2410                                         ." distribution = '$distribution'"
2411                                         ." AND package = '$package'"
2412                                         ." AND version = '$version'";
2413                         }
2414                 }
2415         }
2417         @packages_list_statements = @new_statement_list;
2421 sub parse_package_info {
2422     my ($baseurl, $dist, $section, $session_id)= @_;
2423     my ($package);
2424     if (not defined $session_id) { $session_id = 0; }
2425     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2426     $repo_dirs{ "${repo_path}/pool" } = 1;
2428     foreach $package ("Packages.gz"){
2429         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2430         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2431         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2432     }
2433     
2437 sub get_package {
2438     my ($url, $dest, $session_id)= @_;
2439     if (not defined $session_id) { $session_id = 0; }
2441     my $tpath = dirname($dest);
2442     -d "$tpath" || mkpath "$tpath";
2444     # This is ugly, but I've no time to take a look at "how it works in perl"
2445     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2446         system("gunzip -cd '$dest' > '$dest.in'");
2447         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2448         unlink($dest);
2449         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2450     } else {
2451         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2452     }
2453     return 0;
2457 sub parse_package {
2458     my ($path, $dist, $srv_path, $session_id)= @_;
2459     if (not defined $session_id) { $session_id = 0;}
2460     my ($package, $version, $section, $description);
2461     my $PACKAGES;
2462     my $timestamp = &get_time();
2464     if(not stat("$path.in")) {
2465         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2466         return;
2467     }
2469     open($PACKAGES, "<$path.in");
2470     if(not defined($PACKAGES)) {
2471         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2472         return;
2473     }
2475     # Read lines
2476     while (<$PACKAGES>){
2477         my $line = $_;
2478         # Unify
2479         chop($line);
2481         # Use empty lines as a trigger
2482         if ($line =~ /^\s*$/){
2483             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2484             push(@packages_list_statements, $sql);
2485             $package = "none";
2486             $version = "none";
2487             $section = "none";
2488             $description = "none"; 
2489             next;
2490         }
2492         # Trigger for package name
2493         if ($line =~ /^Package:\s/){
2494             ($package)= ($line =~ /^Package: (.*)$/);
2495             next;
2496         }
2498         # Trigger for version
2499         if ($line =~ /^Version:\s/){
2500             ($version)= ($line =~ /^Version: (.*)$/);
2501             next;
2502         }
2504         # Trigger for description
2505         if ($line =~ /^Description:\s/){
2506             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2507             next;
2508         }
2510         # Trigger for section
2511         if ($line =~ /^Section:\s/){
2512             ($section)= ($line =~ /^Section: (.*)$/);
2513             next;
2514         }
2516         # Trigger for filename
2517         if ($line =~ /^Filename:\s/){
2518             my ($filename) = ($line =~ /^Filename: (.*)$/);
2519             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2520             next;
2521         }
2522     }
2524     close( $PACKAGES );
2525     unlink( "$path.in" );
2526     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2530 sub store_fileinfo {
2531     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2533     my %fileinfo = (
2534         'package' => $package,
2535         'dist' => $dist,
2536         'version' => $vers,
2537     );
2539     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2543 sub cleanup_and_extract {
2544     my $fileinfo = $repo_files{ $File::Find::name };
2546     if( defined $fileinfo ) {
2548         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2549         my $sql;
2550         my $package = $fileinfo->{ 'package' };
2551         my $newver = $fileinfo->{ 'version' };
2553         mkpath($dir);
2554         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2556                 if( -f "$dir/DEBIAN/templates" ) {
2558                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2560                         my $tmpl= "";
2561                         {
2562                                 local $/=undef;
2563                                 open FILE, "$dir/DEBIAN/templates";
2564                                 $tmpl = &encode_base64(<FILE>);
2565                                 close FILE;
2566                         }
2567                         rmtree("$dir/DEBIAN/templates");
2569                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2570                 push @packages_list_statements, $sql;
2571                 }
2572     }
2574     return;
2578 #==== MAIN = main ==============================================================
2579 #  parse commandline options
2580 Getopt::Long::Configure( "bundling" );
2581 GetOptions("h|help" => \&usage,
2582         "c|config=s" => \$cfg_file,
2583         "f|foreground" => \$foreground,
2584         "v|verbose+" => \$verbose,
2585         "no-bus+" => \$no_bus,
2586         "no-arp+" => \$no_arp,
2587            );
2589 #  read and set config parameters
2590 &check_cmdline_param ;
2591 &read_configfile;
2592 &check_pid;
2594 $SIG{CHLD} = 'IGNORE';
2596 # forward error messages to logfile
2597 if( ! $foreground ) {
2598   open( STDIN,  '+>/dev/null' );
2599   open( STDOUT, '+>&STDIN'    );
2600   open( STDERR, '+>&STDIN'    );
2603 # Just fork, if we are not in foreground mode
2604 if( ! $foreground ) { 
2605     chdir '/'                 or die "Can't chdir to /: $!";
2606     $pid = fork;
2607     setsid                    or die "Can't start a new session: $!";
2608     umask 0;
2609 } else { 
2610     $pid = $$; 
2613 # Do something useful - put our PID into the pid_file
2614 if( 0 != $pid ) {
2615     open( LOCK_FILE, ">$pid_file" );
2616     print LOCK_FILE "$pid\n";
2617     close( LOCK_FILE );
2618     if( !$foreground ) { 
2619         exit( 0 ) 
2620     };
2623 # parse head url and revision from svn
2624 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2625 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2626 $server_headURL = defined $1 ? $1 : 'unknown' ;
2627 $server_revision = defined $2 ? $2 : 'unknown' ;
2628 if ($server_headURL =~ /\/tag\// || 
2629         $server_headURL =~ /\/branches\// ) {
2630     $server_status = "stable"; 
2631 } else {
2632     $server_status = "developmental" ;
2636 daemon_log(" ", 1);
2637 daemon_log("$0 started!", 1);
2638 daemon_log("status: $server_status", 1);
2639 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2641 if ($no_bus > 0) {
2642     $bus_activ = "false"
2645 # connect to incoming_db
2646 unlink($incoming_file_name);
2647 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2648 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2650 # connect to gosa-si job queue
2651 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2652 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2654 # connect to known_clients_db
2655 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2656 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2658 # connect to known_server_db
2659 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2660 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2662 # connect to login_usr_db
2663 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2664 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2666 # connect to fai_server_db and fai_release_db
2667 unlink($fai_server_file_name);
2668 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2669 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2671 unlink($fai_release_file_name);
2672 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2673 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2675 # connect to packages_list_db
2676 #unlink($packages_list_file_name);
2677 unlink($packages_list_under_construction);
2678 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2679 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2681 # connect to messaging_db
2682 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2683 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2686 # create xml object used for en/decrypting
2687 $xml = new XML::Simple();
2689 # create socket for incoming xml messages
2691 POE::Component::Server::TCP->new(
2692         Port => $server_port,
2693         ClientInput => sub {
2694         my ($kernel, $input) = @_[KERNEL, ARG0];
2695         push(@tasks, $input);
2696         push(@msgs_to_decrypt, $input);
2697         $kernel->yield("msg_to_decrypt");
2698         $kernel->yield("next_task");
2699         },
2700     InlineStates => {
2701         next_task => \&next_task,
2702         msg_to_decrypt => \&msg_to_decrypt,
2703         task_result => \&handle_task_result,
2704         task_done   => \&handle_task_done,
2705         task_debug  => \&handle_task_debug,
2706         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2707     }
2708 );
2710 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2712 # create session for repeatedly checking the job queue for jobs
2713 POE::Session->create(
2714         inline_states => {
2715                 _start => \&_start,
2716                 sig_handler => \&sig_handler,
2717         watch_for_new_messages => \&watch_for_new_messages,
2718         watch_for_delivery_messages => \&watch_for_delivery_messages,
2719         watch_for_done_messages => \&watch_for_done_messages,
2720                 watch_for_new_jobs => \&watch_for_new_jobs,
2721         watch_for_done_jobs => \&watch_for_done_jobs,
2722         create_packages_list_db => \&run_create_packages_list_db,
2723         create_fai_server_db => \&run_create_fai_server_db,
2724         create_fai_release_db => \&run_create_fai_release_db,
2725         session_run_result => \&session_run_result,
2726         session_run_debug => \&session_run_debug,
2727         session_run_done => \&session_run_done,
2728         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2729         }
2730 );
2733 # import all modules
2734 &import_modules;
2736 # TODO
2737 # check wether all modules are gosa-si valid passwd check
2739 #############################################
2740 # send registration message to foreign server
2741 my @foreign_server_list;
2742 # add foreign server from cfg file
2743 if ($foreign_server_string ne "") {
2744     my @cfg_foreign_server_list = split(",", $foreign_server_string);
2745     foreach my $foreign_server (@cfg_foreign_server_list) {
2746         push(@foreign_server_list, $foreign_server);
2747     }
2750 # add foreign server from dns
2751 my @tmp_servers;
2752 if ( !$server_domain) {
2753     # Try our DNS Searchlist
2754     for my $domain(get_dns_domains()) {
2755         chomp($domain);
2756         my @tmp_domains= &get_server_addresses($domain);
2757         if(@tmp_domains) {
2758             for my $tmp_server(@tmp_domains) {
2759                 push @tmp_servers, $tmp_server;
2760             }
2761         }
2762     }
2763     if(@tmp_servers && length(@tmp_servers)==0) {
2764         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2765     }
2766 } else {
2767     @tmp_servers = &get_server_addresses($server_domain);
2768     if( 0 == @tmp_servers ) {
2769         daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2770     }
2772 foreach my $server (@tmp_servers) { 
2773     unshift(@foreign_server_list, $server); 
2775 # eliminate duplicate entries
2776 @foreign_server_list = &del_doubles(@foreign_server_list);
2778 my $all_foreign_server = join(", ", @foreign_server_list);
2779 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2781 # build messages
2782 foreach my $foreign_server (@foreign_server_list) {
2783     # do not send myself a 'new_server' registration!!!
2784     if ($foreign_server eq $server_address) { next;}
2786     print STDERR "foreign_server: $foreign_server\n"; 
2787     my %data= ('known_clients' => "",
2788             'key' => "",
2789             );
2790     my $foreign_server_msg = &build_msg('new_server', $server_address, $foreign_server, \%data);
2791     my $error = &send_msg_to_target($foreign_server_msg, $foreign_server, $foreign_server_key, "new_server", 0); 
2792     if ($error != 0 ) {
2793         daemon_log("0 WARNING: sending of 'new_server'-message to $foreign_server failed!", 3); 
2794     }
2798 POE::Kernel->run();
2799 exit;