Code

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