Code

Display a warning if ACL checks are disabled
[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);
57 my $modules_path = "/usr/lib/gosa-si/modules";
58 use lib "/usr/lib/gosa-si/modules";
60 # revision number of server and program name
61 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
62 my $server_headURL;
63 my $server_revision;
64 my $server_status;
65 our $prg= basename($0);
67 our $global_kernel;
68 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
69 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
70 my ($server);
71 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
72 my ($messaging_db_loop_delay);
73 my ($known_modules);
74 my ($pid_file, $procid, $pid, $log_file);
75 my ($arp_activ, $arp_fifo);
76 my ($xml);
77 my $sources_list;
78 my $max_clients;
79 my %repo_files=();
80 my $repo_path;
81 my %repo_dirs=();
82 # variables declared in config file are always set to 'our'
83 our (%cfg_defaults, $log_file, $pid_file, 
84     $server_ip, $server_port, $SIPackages_key, 
85     $arp_activ, $gosa_unit_tag,
86     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
87 );
89 # additional variable which should be globaly accessable
90 our $server_address;
91 our $server_mac_address;
92 our $bus_address;
93 our $gosa_address;
94 our $no_bus;
95 our $no_arp;
96 our $verbose;
97 our $forground;
98 our $cfg_file;
99 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
102 # specifies the verbosity of the daemon_log
103 $verbose = 0 ;
105 # if foreground is not null, script will be not forked to background
106 $foreground = 0 ;
108 # specifies the timeout seconds while checking the online status of a registrating client
109 $ping_timeout = 5;
111 $no_bus = 0;
112 $bus_activ = "true";
113 $no_arp = 0;
114 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
115 my @packages_list_statements;
116 my $watch_for_new_jobs_in_progress = 0;
118 # holds all incoming decrypted messages
119 our $incoming_db;
120 our $incoming_tn = 'incoming';
121 my $incoming_file_name;
122 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
123         "timestamp DEFAULT 'none'", 
124         "headertag DEFAULT 'none'",
125         "xmlmessage DEFAULT 'none'",
126         "module DEFAULT 'none'",
127         );
129 # holds all gosa jobs
130 our $job_db;
131 our $job_queue_tn = 'jobs';
132 my $job_queue_file_name;
133 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
134                 "timestamp DEFAULT 'none'", 
135                 "status DEFAULT 'none'", 
136                 "result DEFAULT 'none'", 
137                 "progress DEFAULT 'none'", 
138         "headertag DEFAULT 'none'", 
139                 "targettag DEFAULT 'none'", 
140                 "xmlmessage DEFAULT 'none'", 
141                 "macaddress DEFAULT 'none'",
142                 "plainname DEFAULT 'none'",
143                 );
145 # holds all other gosa-sd as well as the gosa-sd-bus
146 our $known_server_db;
147 our $known_server_tn = "known_server";
148 my $known_server_file_name;
149 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
151 # holds all registrated clients
152 our $known_clients_db;
153 our $known_clients_tn = "known_clients";
154 my $known_clients_file_name;
155 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events");
157 # holds all logged in user at each client 
158 our $login_users_db;
159 our $login_users_tn = "login_users";
160 my $login_users_file_name;
161 my @login_users_col_names = ("client", "user", "timestamp");
163 # holds all fai server, the debian release and tag
164 our $fai_server_db;
165 our $fai_server_tn = "fai_server"; 
166 my $fai_server_file_name;
167 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
169 our $fai_release_db;
170 our $fai_release_tn = "fai_release"; 
171 my $fai_release_file_name;
172 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
174 # holds all packages available from different repositories
175 our $packages_list_db;
176 our $packages_list_tn = "packages_list";
177 my $packages_list_file_name;
178 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
179 my $outdir = "/tmp/packages_list_db";
180 my $arch = "i386"; 
182 # holds all messages which should be delivered to a user
183 our $messaging_db;
184 our $messaging_tn = "messaging"; 
185 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
186         "flag", "direction", "delivery_time", "message", "timestamp" );
187 my $messaging_file_name;
189 # path to directory to store client install log files
190 our $client_fai_log_dir = "/var/log/fai"; 
192 # queue which stores taskes until one of the $max_children children are ready to process the task
193 my @tasks = qw();
194 my @msgs_to_decrypt = qw();
195 my $max_children = 2;
198 %cfg_defaults = (
199 "general" => {
200     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
201     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
202     },
203 "bus" => {
204     "activ" => [\$bus_activ, "true"],
205     },
206 "server" => {
207     "port" => [\$server_port, "20081"],
208     "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
209     "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
210     "incoming"      => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
211     "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
212     "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
213     "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
214     "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
215     "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
216     "source-list" => [\$sources_list, '/etc/apt/sources.list'],
217     "repo-path" => [\$repo_path, '/srv/www/repository'],
218     "ldap-uri" => [\$ldap_uri, ""],
219     "ldap-base" => [\$ldap_base, ""],
220     "ldap-admin-dn" => [\$ldap_admin_dn, ""],
221     "ldap-admin-password" => [\$ldap_admin_password, ""],
222     "gosa-unit-tag" => [\$gosa_unit_tag, ""],
223     "max-clients" => [\$max_clients, 10],
224     },
225 "GOsaPackages" => {
226     "ip" => [\$gosa_ip, "0.0.0.0"],
227     "port" => [\$gosa_port, "20082"],
228     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
229     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
230     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
231     "key" => [\$GosaPackages_key, "none"],
232     },
233 "SIPackages" => {
234     "key" => [\$SIPackages_key, "none"],
235     },
236 );
239 #===  FUNCTION  ================================================================
240 #         NAME:  usage
241 #   PARAMETERS:  nothing
242 #      RETURNS:  nothing
243 #  DESCRIPTION:  print out usage text to STDERR
244 #===============================================================================
245 sub usage {
246     print STDERR << "EOF" ;
247 usage: $prg [-hvf] [-c config]
249            -h        : this (help) message
250            -c <file> : config file
251            -f        : foreground, process will not be forked to background
252            -v        : be verbose (multiple to increase verbosity)
253            -no-bus   : starts $prg without connection to bus
254            -no-arp   : starts $prg without connection to arp module
255  
256 EOF
257     print "\n" ;
261 #===  FUNCTION  ================================================================
262 #         NAME:  read_configfile
263 #   PARAMETERS:  cfg_file - string -
264 #      RETURNS:  nothing
265 #  DESCRIPTION:  read cfg_file and set variables
266 #===============================================================================
267 sub read_configfile {
268     my $cfg;
269     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
270         if( -r $cfg_file ) {
271             $cfg = Config::IniFiles->new( -file => $cfg_file );
272         } else {
273             print STDERR "Couldn't read config file!\n";
274         }
275     } else {
276         $cfg = Config::IniFiles->new() ;
277     }
278     foreach my $section (keys %cfg_defaults) {
279         foreach my $param (keys %{$cfg_defaults{ $section }}) {
280             my $pinfo = $cfg_defaults{ $section }{ $param };
281             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
282         }
283     }
287 #===  FUNCTION  ================================================================
288 #         NAME:  logging
289 #   PARAMETERS:  level - string - default 'info'
290 #                msg - string -
291 #                facility - string - default 'LOG_DAEMON'
292 #      RETURNS:  nothing
293 #  DESCRIPTION:  function for logging
294 #===============================================================================
295 sub daemon_log {
296     # log into log_file
297     my( $msg, $level ) = @_;
298     if(not defined $msg) { return }
299     if(not defined $level) { $level = 1 }
300     if(defined $log_file){
301         open(LOG_HANDLE, ">>$log_file");
302         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
303             print STDERR "cannot open $log_file: $!";
304             return }
305             chomp($msg);
306                         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
307             if($level <= $verbose){
308                 my ($seconds, $minutes, $hours, $monthday, $month,
309                         $year, $weekday, $yearday, $sommertime) = localtime(time);
310                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
311                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
312                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
313                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
314                 $month = $monthnames[$month];
315                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
316                 $year+=1900;
317                 my $name = $prg;
319                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
320                 print LOG_HANDLE $log_msg;
321                 if( $foreground ) { 
322                     print STDERR $log_msg;
323                 }
324             }
325         close( LOG_HANDLE );
326     }
330 #===  FUNCTION  ================================================================
331 #         NAME:  check_cmdline_param
332 #   PARAMETERS:  nothing
333 #      RETURNS:  nothing
334 #  DESCRIPTION:  validates commandline parameter
335 #===============================================================================
336 sub check_cmdline_param () {
337     my $err_config;
338     my $err_counter = 0;
339         if(not defined($cfg_file)) {
340                 $cfg_file = "/etc/gosa-si/server.conf";
341                 if(! -r $cfg_file) {
342                         $err_config = "please specify a config file";
343                         $err_counter += 1;
344                 }
345     }
346     if( $err_counter > 0 ) {
347         &usage( "", 1 );
348         if( defined( $err_config)) { print STDERR "$err_config\n"}
349         print STDERR "\n";
350         exit( -1 );
351     }
355 #===  FUNCTION  ================================================================
356 #         NAME:  check_pid
357 #   PARAMETERS:  nothing
358 #      RETURNS:  nothing
359 #  DESCRIPTION:  handels pid processing
360 #===============================================================================
361 sub check_pid {
362     $pid = -1;
363     # Check, if we are already running
364     if( open(LOCK_FILE, "<$pid_file") ) {
365         $pid = <LOCK_FILE>;
366         if( defined $pid ) {
367             chomp( $pid );
368             if( -f "/proc/$pid/stat" ) {
369                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
370                 if( $stat ) {
371                                         daemon_log("ERROR: Already running",1);
372                     close( LOCK_FILE );
373                     exit -1;
374                 }
375             }
376         }
377         close( LOCK_FILE );
378         unlink( $pid_file );
379     }
381     # create a syslog msg if it is not to possible to open PID file
382     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
383         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
384         if (open(LOCK_FILE, '<', $pid_file)
385                 && ($pid = <LOCK_FILE>))
386         {
387             chomp($pid);
388             $msg .= "(PID $pid)\n";
389         } else {
390             $msg .= "(unable to read PID)\n";
391         }
392         if( ! ($foreground) ) {
393             openlog( $0, "cons,pid", "daemon" );
394             syslog( "warning", $msg );
395             closelog();
396         }
397         else {
398             print( STDERR " $msg " );
399         }
400         exit( -1 );
401     }
404 #===  FUNCTION  ================================================================
405 #         NAME:  import_modules
406 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
407 #                are stored
408 #      RETURNS:  nothing
409 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
410 #                state is on is imported by "require 'file';"
411 #===============================================================================
412 sub import_modules {
413     daemon_log(" ", 1);
415     if (not -e $modules_path) {
416         daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
417     }
419     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
420     while (defined (my $file = readdir (DIR))) {
421         if (not $file =~ /(\S*?).pm$/) {
422             next;
423         }
424                 my $mod_name = $1;
426         if( $file =~ /ArpHandler.pm/ ) {
427             if( $no_arp > 0 ) {
428                 next;
429             }
430         }
431         
432         eval { require $file; };
433         if ($@) {
434             daemon_log("ERROR: gosa-si-server could not load module $file", 1);
435             daemon_log("$@", 5);
436                 } else {
437                         my $info = eval($mod_name.'::get_module_info()');
438                         # Only load module if get_module_info() returns a non-null object
439                         if( $info ) {
440                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
441                                 $known_modules->{$mod_name} = $info;
442                                 daemon_log("INFO: module $mod_name loaded", 5);
443                         }
444                 }
445     }   
446     close (DIR);
450 #===  FUNCTION  ================================================================
451 #         NAME:  sig_int_handler
452 #   PARAMETERS:  signal - string - signal arose from system
453 #      RETURNS:  noting
454 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
455 #===============================================================================
456 sub sig_int_handler {
457     my ($signal) = @_;
459 #       if (defined($ldap_handle)) {
460 #               $ldap_handle->disconnect;
461 #       }
462     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
463     
465     daemon_log("shutting down gosa-si-server", 1);
466     system("kill `ps -C gosa-si-server -o pid=`");
468 $SIG{INT} = \&sig_int_handler;
471 sub check_key_and_xml_validity {
472     my ($crypted_msg, $module_key, $session_id) = @_;
473     my $msg;
474     my $msg_hash;
475     my $error_string;
476     eval{
477         $msg = &decrypt_msg($crypted_msg, $module_key);
479         if ($msg =~ /<xml>/i){
480             $msg =~ s/\s+/ /g;  # just for better daemon_log
481             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
482             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
484             ##############
485             # check header
486             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
487             my $header_l = $msg_hash->{'header'};
488             if( 1 > @{$header_l} ) { die 'empty header tag'; }
489             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
490             my $header = @{$header_l}[0];
491             if( 0 == length $header) { die 'empty string in header tag'; }
493             ##############
494             # check source
495             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
496             my $source_l = $msg_hash->{'source'};
497             if( 1 > @{$source_l} ) { die 'empty source tag'; }
498             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
499             my $source = @{$source_l}[0];
500             if( 0 == length $source) { die 'source error'; }
502             ##############
503             # check target
504             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
505             my $target_l = $msg_hash->{'target'};
506             if( 1 > @{$target_l} ) { die 'empty target tag'; }
507         }
508     };
509     if($@) {
510         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
511         $msg = undef;
512         $msg_hash = undef;
513     }
515     return ($msg, $msg_hash);
519 sub check_outgoing_xml_validity {
520     my ($msg) = @_;
522     my $msg_hash;
523     eval{
524         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
526         ##############
527         # check header
528         my $header_l = $msg_hash->{'header'};
529         if( 1 != @{$header_l} ) {
530             die 'no or more than one headers specified';
531         }
532         my $header = @{$header_l}[0];
533         if( 0 == length $header) {
534             die 'header has length 0';
535         }
537         ##############
538         # check source
539         my $source_l = $msg_hash->{'source'};
540         if( 1 != @{$source_l} ) {
541             die 'no or more than 1 sources specified';
542         }
543         my $source = @{$source_l}[0];
544         if( 0 == length $source) {
545             die 'source has length 0';
546         }
547         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
548                 $source =~ /^GOSA$/i ) {
549             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
550         }
551         
552         ##############
553         # check target  
554         my $target_l = $msg_hash->{'target'};
555         if( 0 == @{$target_l} ) {
556             die "no targets specified";
557         }
558         foreach my $target (@$target_l) {
559             if( 0 == length $target) {
560                 die "target has length 0";
561             }
562             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
563                     $target =~ /^GOSA$/i ||
564                     $target =~ /^\*$/ ||
565                     $target =~ /KNOWN_SERVER/i ||
566                     $target =~ /JOBDB/i ||
567                     $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 ){
568                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
569             }
570         }
571     };
572     if($@) {
573         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
574         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
575         $msg_hash = undef;
576     }
578     return ($msg_hash);
582 sub input_from_known_server {
583     my ($input, $remote_ip, $session_id) = @_ ;  
584     my ($msg, $msg_hash, $module);
586     my $sql_statement= "SELECT * FROM known_server";
587     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
589     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
590         my $host_name = $hit->{hostname};
591         if( not $host_name =~ "^$remote_ip") {
592             next;
593         }
594         my $host_key = $hit->{hostkey};
595         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
596         daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
598         # check if module can open msg envelope with module key
599         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
600         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
601             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
602             daemon_log("$@", 8);
603             next;
604         }
605         else {
606             $msg = $tmp_msg;
607             $msg_hash = $tmp_msg_hash;
608             $module = "SIPackages";
609             last;
610         }
611     }
613     if( (!$msg) || (!$msg_hash) || (!$module) ) {
614         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
615     }
616   
617     return ($msg, $msg_hash, $module);
621 sub input_from_known_client {
622     my ($input, $remote_ip, $session_id) = @_ ;  
623     my ($msg, $msg_hash, $module);
625     my $sql_statement= "SELECT * FROM known_clients";
626     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
627     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
628         my $host_name = $hit->{hostname};
629         if( not $host_name =~ /^$remote_ip:\d*$/) {
630                 next;
631                 }
632         my $host_key = $hit->{hostkey};
633         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
634         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
636         # check if module can open msg envelope with module key
637         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
639         if( (!$msg) || (!$msg_hash) ) {
640             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
641             &daemon_log("$@", 8);
642             next;
643         }
644         else {
645             $module = "SIPackages";
646             last;
647         }
648     }
650     if( (!$msg) || (!$msg_hash) || (!$module) ) {
651         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
652     }
654     return ($msg, $msg_hash, $module);
658 sub input_from_unknown_host {
659     no strict "refs";
660     my ($input, $session_id) = @_ ;
661     my ($msg, $msg_hash, $module);
662     my $error_string;
663     
664         my %act_modules = %$known_modules;
666         while( my ($mod, $info) = each(%act_modules)) {
668         # check a key exists for this module
669         my $module_key = ${$mod."_key"};
670         if( not defined $module_key ) {
671             if( $mod eq 'ArpHandler' ) {
672                 next;
673             }
674             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
675             next;
676         }
677         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
679         # check if module can open msg envelope with module key
680         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
681         if( (not defined $msg) || (not defined $msg_hash) ) {
682             next;
683         }
684         else {
685             $module = $mod;
686             last;
687         }
688     }
690     if( (!$msg) || (!$msg_hash) || (!$module)) {
691         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
692     }
694     return ($msg, $msg_hash, $module);
698 sub create_ciphering {
699     my ($passwd) = @_;
700         if((!defined($passwd)) || length($passwd)==0) {
701                 $passwd = "";
702         }
703     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
704     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
705     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
706     $my_cipher->set_iv($iv);
707     return $my_cipher;
711 sub encrypt_msg {
712     my ($msg, $key) = @_;
713     my $my_cipher = &create_ciphering($key);
714     my $len;
715     {
716             use bytes;
717             $len= 16-length($msg)%16;
718     }
719     $msg = "\0"x($len).$msg;
720     $msg = $my_cipher->encrypt($msg);
721     chomp($msg = &encode_base64($msg));
722     # there are no newlines allowed inside msg
723     $msg=~ s/\n//g;
724     return $msg;
728 sub decrypt_msg {
730     my ($msg, $key) = @_ ;
731     $msg = &decode_base64($msg);
732     my $my_cipher = &create_ciphering($key);
733     $msg = $my_cipher->decrypt($msg); 
734     $msg =~ s/\0*//g;
735     return $msg;
739 sub get_encrypt_key {
740     my ($target) = @_ ;
741     my $encrypt_key;
742     my $error = 0;
744     # target can be in known_server
745     if( not defined $encrypt_key ) {
746         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
747         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
748         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
749             my $host_name = $hit->{hostname};
750             if( $host_name ne $target ) {
751                 next;
752             }
753             $encrypt_key = $hit->{hostkey};
754             last;
755         }
756     }
758     # target can be in known_client
759     if( not defined $encrypt_key ) {
760         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
761         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
762         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
763             my $host_name = $hit->{hostname};
764             if( $host_name ne $target ) {
765                 next;
766             }
767             $encrypt_key = $hit->{hostkey};
768             last;
769         }
770     }
772     return $encrypt_key;
776 #===  FUNCTION  ================================================================
777 #         NAME:  open_socket
778 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
779 #                [PeerPort] string necessary if port not appended by PeerAddr
780 #      RETURNS:  socket IO::Socket::INET
781 #  DESCRIPTION:  open a socket to PeerAddr
782 #===============================================================================
783 sub open_socket {
784     my ($PeerAddr, $PeerPort) = @_ ;
785     if(defined($PeerPort)){
786         $PeerAddr = $PeerAddr.":".$PeerPort;
787     }
788     my $socket;
789     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
790             Porto => "tcp",
791             Type => SOCK_STREAM,
792             Timeout => 5,
793             );
794     if(not defined $socket) {
795         return;
796     }
797 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
798     return $socket;
802 #===  FUNCTION  ================================================================
803 #         NAME:  get_ip 
804 #   PARAMETERS:  interface name (i.e. eth0)
805 #      RETURNS:  (ip address) 
806 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
807 #===============================================================================
808 sub get_ip {
809         my $ifreq= shift;
810         my $result= "";
811         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
812         my $proto= getprotobyname('ip');
814         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
815                 or die "socket: $!";
817         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
818                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
819                 my ($port, $addr) = sockaddr_in $sin;
820                 my $ip            = inet_ntoa $addr;
822                 if ($ip && length($ip) > 0) {
823                         $result = $ip;
824                 }
825         }
827         return $result;
831 sub get_local_ip_for_remote_ip {
832         my $remote_ip= shift;
833         my $result="0.0.0.0";
835         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
836                 if($remote_ip eq "127.0.0.1") {
837                         $result = "127.0.0.1";
838                 } else {
839                         my $PROC_NET_ROUTE= ('/proc/net/route');
841                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
842                                 or die "Could not open $PROC_NET_ROUTE";
844                         my @ifs = <PROC_NET_ROUTE>;
846                         close(PROC_NET_ROUTE);
848                         # Eat header line
849                         shift @ifs;
850                         chomp @ifs;
851                         foreach my $line(@ifs) {
852                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
853                                 my $destination;
854                                 my $mask;
855                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
856                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
857                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
858                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
859                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
860                                         # destination matches route, save mac and exit
861                                         $result= &get_ip($Iface);
862                                         last;
863                                 }
864                         }
865                 }
866         } else {
867                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
868         }
869         return $result;
873 sub send_msg_to_target {
874     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
875     my $error = 0;
876     my $header;
877     my $new_status;
878     my $act_status;
879     my ($sql_statement, $res);
880   
881     if( $msg_header ) {
882         $header = "'$msg_header'-";
883     } else {
884         $header = "";
885     }
887         # Patch the source ip
888         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
889                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
890                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
891         }
893     # encrypt xml msg
894     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
896     # opensocket
897     my $socket = &open_socket($address);
898     if( !$socket ) {
899         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
900         $error++;
901     }
902     
903     if( $error == 0 ) {
904         # send xml msg
905         print $socket $crypted_msg."\n";
907         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
908         #daemon_log("DEBUG: message:\n$msg", 9);
909         
910     }
912     # close socket in any case
913     if( $socket ) {
914         close $socket;
915     }
917     if( $error > 0 ) { $new_status = "down"; }
918     else { $new_status = $msg_header; }
921     # known_clients
922     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
923     $res = $known_clients_db->select_dbentry($sql_statement);
924     if( keys(%$res) > 0) {
925         $act_status = $res->{1}->{'status'};
926         if( $act_status eq "down" ) {
927             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
928             $res = $known_clients_db->del_dbentry($sql_statement);
929             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
930         } else { 
931             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
932             $res = $known_clients_db->update_dbentry($sql_statement);
933             if($new_status eq "down"){
934                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
935             } else {
936                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
937             }
938         }
939     }
941     # known_server
942     $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
943     $res = $known_server_db->select_dbentry($sql_statement);
944     if( keys(%$res) > 0 ) {
945         $act_status = $res->{1}->{'status'};
946         if( $act_status eq "down" ) {
947             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
948             $res = $known_server_db->del_dbentry($sql_statement);
949             daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
950         } 
951         else { 
952             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
953             $res = $known_server_db->update_dbentry($sql_statement);
954             if($new_status eq "down"){
955                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
956             }
957             else {
958                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
959             }
960         }
961     }
962     return $error; 
966 sub update_jobdb_status_for_send_msgs {
967     my ($answer, $error) = @_;
968     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
969         my $jobdb_id = $1;
970             
971         # sending msg faild
972         if( $error ) {
973             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
974                 my $sql_statement = "UPDATE $job_queue_tn ".
975                     "SET status='error', result='can not deliver msg, please consult log file' ".
976                     "WHERE id=$jobdb_id";
977                 my $res = $job_db->update_dbentry($sql_statement);
978             }
980         # sending msg was successful
981         } else {
982             my $sql_statement = "UPDATE $job_queue_tn ".
983                 "SET status='done' ".
984                 "WHERE id=$jobdb_id AND status='processed'";
985             my $res = $job_db->update_dbentry($sql_statement);
986         }
987     }
990 sub _start {
991     my ($kernel) = $_[KERNEL];
992     &trigger_db_loop($kernel);
993     $global_kernel = $kernel;
994         $kernel->yield('create_fai_server_db', $fai_server_tn );
995         $kernel->yield('create_fai_release_db', $fai_release_tn );
996         $kernel->sig(USR1 => "sig_handler");
997         $kernel->sig(USR2 => "create_packages_list_db");
1000 sub sig_handler {
1001         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1002         daemon_log("0 INFO got signal '$signal'", 1); 
1003         $kernel->sig_handled();
1004         return;
1008 sub msg_to_decrypt {
1009     my ($session, $heap) = @_[SESSION, HEAP];
1010     my $session_id = $session->ID;
1011     my ($msg, $msg_hash, $module);
1012     my $error = 0;
1014     # hole neue msg aus @msgs_to_decrypt
1015     my $next_msg = shift @msgs_to_decrypt;
1016     
1017     # entschlüssle sie
1019     # msg is from a new client or gosa
1020     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1021     # msg is from a gosa-si-server or gosa-si-bus
1022     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1023         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1024     }
1025     # msg is from a gosa-si-client
1026     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1027         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1028     }
1029     # an error occurred
1030     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1031         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1032         # could not understand a msg from its server the client cause a re-registering process
1033         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);
1034         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1035         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1036         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1037             my $host_name = $hit->{'hostname'};
1038             my $host_key = $hit->{'hostkey'};
1039             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1040             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1041             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1042         }
1043         $error++;
1044     }
1045     
1046     # schiebe sie in incoming_db
1047     if( $error == 0) {
1048         my $header = @{$msg_hash->{'header'}}[0];
1049         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1050                 primkey=>[],
1051                 headertag=>$header,
1052                 xmlmessage=>$msg,
1053                 timestamp=>&get_time,
1054                 module=>$module,
1055                 } );
1056         if ($res != 0) {
1057             &daemon_log(); 
1058         }
1059     }
1064 sub next_task {
1065     my ($session, $heap) = @_[SESSION, HEAP];
1066 #    if (keys( %{ $heap->{task} } ) < $max_children ) {
1067         my $task = POE::Wheel::Run->new(
1068                 Program => sub { process_task($session, $heap) },
1069                 StdioFilter => POE::Filter::Reference->new(),
1070                 StdoutEvent  => "task_result",
1071                 StderrEvent  => "task_debug",
1072                 CloseEvent   => "task_done",
1073                );
1075         $heap->{task}->{ $task->ID } = $task;
1076 #    }
1079 sub handle_task_result {
1080     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1081     my $client_answer = $result->{'answer'};
1082     if( $client_answer =~ s/session_id=(\d+)$// ) {
1083         my $session_id = $1;
1084         if( defined $session_id ) {
1085             my $session_reference = $kernel->ID_id_to_session($session_id);
1086             if( defined $session_reference ) {
1087                 $heap = $session_reference->get_heap();
1088             }
1089         }
1091         if(exists $heap->{'client'}) {
1092             $heap->{'client'}->put($client_answer);
1093         }
1094     }
1095     $kernel->sig(CHLD => "child_reap");
1098 sub handle_task_debug {
1099     my $result = $_[ARG0];
1100     print STDERR "$result\n";
1103 sub handle_task_done {
1104     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1105     delete $heap->{task}->{$task_id};
1108 sub process_task {
1109     no strict "refs";
1110     my ($session, $heap, $input) = @_;
1111     my $session_id = $session->ID;
1112     my $error = 0;
1113     my $answer_l;
1114     my ($answer_header, @answer_target_l, $answer_source);
1115     my $client_answer = "";
1117     daemon_log("", 5); 
1118     daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1119     #daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1121 #    ####################
1122 #    # check incoming msg
1123 #    # msg is from a new client or gosa
1124 #    ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1125 #    # msg is from a gosa-si-server or gosa-si-bus
1126 #    if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1127 #        ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1128 #    }
1129 #    # msg is from a gosa-si-client
1130 #    if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1131 #        ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1132 #    }
1133 #    # an error occurred
1134 #    if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1135 #        # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1136 #        # could not understand a msg from its server the client cause a re-registering process
1137 #        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);
1138 #        my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1139 #        my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1140 #        while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1141 #            my $host_name = $hit->{'hostname'};
1142 #            my $host_key = $hit->{'hostkey'};
1143 #            my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1144 #            my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1145 #            &update_jobdb_status_for_send_msgs($ping_msg, $error);
1146 #        }
1147 #        $error++;
1148 #    }
1151     # sometimes the program is faster than sqlite, so wait until informations are present at db
1152     my $id_sql;
1153     my $id_res;
1154     my $message_id;
1155     while (1) {
1156         $id_sql = "SELECT min(id) FROM $incoming_tn WHERE (NOT headertag LIKE 'answer%')"; 
1157         $id_res = $incoming_db->exec_statement($id_sql);
1158         $message_id = @{@$id_res[0]}[0];
1159         if (defined $message_id) { last }
1160     }
1162 print STDERR "min(id): $message_id\n"; 
1165     # fetch new message from incoming_db
1166     my $sql = "SELECT * FROM $incoming_tn WHERE id=$message_id"; 
1167     my $res = $incoming_db->exec_statement($sql);
1169     # prepare all variables needed to process message
1170     my $msg = @{@$res[0]}[3];
1171     my $incoming_id = @{@$res[0]}[0];
1172     my $module = @{@$res[0]}[4];
1173     my $header =  @{@$res[0]}[2];
1174     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1176     # messages which are an answer to a still running process should not be processed here
1177     if ($header =~ /^answer_(\d+)/) {
1178         return;
1179     }
1180    
1181     # delete message from db 
1182     my $delete_sql = "DELETE FROM $incoming_tn WHERE id=$incoming_id";
1183     my $delete_res = $incoming_db->exec_statement($delete_sql);
1185     ######################
1186     # process incoming msg
1187     if( $error == 0) {
1188         daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1189                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1190         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1191         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1193         if ( 0 < @{$answer_l} ) {
1194             my $answer_str = join("\n", @{$answer_l});
1195             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1196         } else {
1197             daemon_log("$session_id DEBUG: $module: Got no answer from module!" ,8);
1198         }
1200     }
1201     if( !$answer_l ) { $error++ };
1203     ########
1204     # answer
1205     if( $error == 0 ) {
1207         foreach my $answer ( @{$answer_l} ) {
1208             # for each answer in answer list
1209             
1210             # check outgoing msg to xml validity
1211             my $answer_hash = &check_outgoing_xml_validity($answer);
1212             if( not defined $answer_hash ) {
1213                 next;
1214             }
1215             
1216             $answer_header = @{$answer_hash->{'header'}}[0];
1217             @answer_target_l = @{$answer_hash->{'target'}};
1218             $answer_source = @{$answer_hash->{'source'}}[0];
1220             # deliver msg to all targets 
1221             foreach my $answer_target ( @answer_target_l ) {
1223                 # targets of msg are all gosa-si-clients in known_clients_db
1224                 if( $answer_target eq "*" ) {
1225                     # answer is for all clients
1226                     my $sql_statement= "SELECT * FROM known_clients";
1227                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1228                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1229                         my $host_name = $hit->{hostname};
1230                         my $host_key = $hit->{hostkey};
1231                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1232                         &update_jobdb_status_for_send_msgs($answer, $error);
1233                     }
1234                 }
1236                 # targets of msg are all gosa-si-server in known_server_db
1237                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1238                     # answer is for all server in known_server
1239                     my $sql_statement= "SELECT * FROM known_server";
1240                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1241                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1242                         my $host_name = $hit->{hostname};
1243                         my $host_key = $hit->{hostkey};
1244                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1245                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1246                         &update_jobdb_status_for_send_msgs($answer, $error);
1247                     }
1248                 }
1250                 # target of msg is GOsa
1251                                 elsif( $answer_target eq "GOSA" ) {
1252                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1253                                         my $add_on = "";
1254                     if( defined $session_id ) {
1255                         $add_on = ".session_id=$session_id";
1256                     }
1257                     # answer is for GOSA and has to returned to connected client
1258                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1259                     $client_answer = $gosa_answer.$add_on;
1260                 }
1262                 # target of msg is job queue at this host
1263                 elsif( $answer_target eq "JOBDB") {
1264                     $answer =~ /<header>(\S+)<\/header>/;   
1265                     my $header;
1266                     if( defined $1 ) { $header = $1; }
1267                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1268                     &update_jobdb_status_for_send_msgs($answer, $error);
1269                 }
1271                 # target of msg is a mac address
1272                 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 ) {
1273                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1274                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1275                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1276                     my $found_ip_flag = 0;
1277                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1278                         my $host_name = $hit->{hostname};
1279                         my $host_key = $hit->{hostkey};
1280                         $answer =~ s/$answer_target/$host_name/g;
1281                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1282                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1283                         &update_jobdb_status_for_send_msgs($answer, $error);
1284                         $found_ip_flag++ ;
1285                     }   
1286                     if( $found_ip_flag == 0) {
1287                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$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                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1293                                 my $bus_address = $hit->{hostname};
1294                                 my $bus_key = $hit->{hostkey};
1295                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1296                                 &update_jobdb_status_for_send_msgs($answer, $error);
1297                                 last;
1298                             }
1299                         }
1301                     }
1303                 #  answer is for one specific host   
1304                 } else {
1305                     # get encrypt_key
1306                     my $encrypt_key = &get_encrypt_key($answer_target);
1307                     if( not defined $encrypt_key ) {
1308                         # unknown target, forward msg to bus
1309                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1310                         if( $bus_activ eq "true" ) { 
1311                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1312                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1313                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1314                             my $res_length = keys( %{$query_res} );
1315                             if( $res_length == 0 ){
1316                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1317                                         "no bus found in known_server", 3);
1318                             }
1319                             else {
1320                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1321                                     my $bus_key = $hit->{hostkey};
1322                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1323                                     &update_jobdb_status_for_send_msgs($answer, $error);
1324                                 }
1325                             }
1326                         }
1327                         next;
1328                     }
1329                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1330                     &update_jobdb_status_for_send_msgs($answer, $error);
1331                 }
1332             }
1333         }
1334     }
1336     my $filter = POE::Filter::Reference->new();
1337     my %result = ( 
1338             status => "seems ok to me",
1339             answer => $client_answer,
1340             );
1342     my $output = $filter->put( [ \%result ] );
1343     print @$output;
1349 sub trigger_db_loop {
1350         my ($kernel) = @_ ;
1351         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1352         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1353         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1354     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1355         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1359 sub watch_for_done_jobs {
1360     my ($kernel,$heap) = @_[KERNEL, HEAP];
1362     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1363         " WHERE status='done'";
1364         my $res = $job_db->select_dbentry( $sql_statement );
1366     while( my ($id, $hit) = each %{$res} ) {
1367         my $jobdb_id = $hit->{id};
1368         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1369         my $res = $job_db->del_dbentry($sql_statement); 
1370     }
1372     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1376 sub watch_for_new_jobs {
1377         if($watch_for_new_jobs_in_progress == 0) {
1378                 $watch_for_new_jobs_in_progress = 1;
1379                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1381                 # check gosa job queue for jobs with executable timestamp
1382                 my $timestamp = &get_time();
1383                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1384                 my $res = $job_db->exec_statement( $sql_statement );
1386                 # Merge all new jobs that would do the same actions
1387                 my @drops;
1388                 my $hits;
1389                 foreach my $hit (reverse @{$res} ) {
1390                         my $macaddress= lc @{$hit}[8];
1391                         my $headertag= @{$hit}[5];
1392                         if(
1393                                 defined($hits->{$macaddress}) &&
1394                                 defined($hits->{$macaddress}->{$headertag}) &&
1395                                 defined($hits->{$macaddress}->{$headertag}[0])
1396                         ) {
1397                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1398                         }
1399                         $hits->{$macaddress}->{$headertag}= $hit;
1400                 }
1402                 # Delete new jobs with a matching job in state 'processing'
1403                 foreach my $macaddress (keys %{$hits}) {
1404                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1405                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1406                                 if(defined($jobdb_id)) {
1407                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1408                                         my $res = $job_db->exec_statement( $sql_statement );
1409                                         foreach my $hit (@{$res}) {
1410                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1411                                         }
1412                                 } else {
1413                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1414                                 }
1415                         }
1416                 }
1418                 # Commit deletion
1419                 $job_db->exec_statementlist(\@drops);
1421                 # Look for new jobs that could be executed
1422                 foreach my $macaddress (keys %{$hits}) {
1424                         # Look if there is an executing job
1425                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1426                         my $res = $job_db->exec_statement( $sql_statement );
1428                         # Skip new jobs for host if there is a processing job
1429                         if(defined($res) and defined @{$res}[0]) {
1430                                 next;
1431                         }
1433                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1434                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1435                                 if(defined($jobdb_id)) {
1436                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1438                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1439                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1440                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1442                                         # expect macaddress is unique!!!!!!
1443                                         my $target = $res_hash->{1}->{hostname};
1445                                         # change header
1446                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1448                                         # add sqlite_id
1449                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1451                                         $job_msg =~ /<header>(\S+)<\/header>/;
1452                                         my $header = $1 ;
1453                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1455                                         # update status in job queue to 'processing'
1456                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1457                                         my $res = $job_db->update_dbentry($sql_statement);
1458 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1460                                         # We don't want parallel processing
1461                                         last;
1462                                 }
1463                         }
1464                 }
1466                 $watch_for_new_jobs_in_progress = 0;
1467                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1468         }
1472 sub watch_for_new_messages {
1473     my ($kernel,$heap) = @_[KERNEL, HEAP];
1474     my @coll_user_msg;   # collection list of outgoing messages
1475     
1476     # check messaging_db for new incoming messages with executable timestamp
1477     my $timestamp = &get_time();
1478     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1479     my $res = $messaging_db->exec_statement( $sql_statement );
1480         foreach my $hit (@{$res}) {
1482         # create outgoing messages
1483         my $message_to = @{$hit}[3];
1484         # translate message_to to plain login name
1485         my @message_to_l = split(/,/, $message_to);  
1486                 my %receiver_h; 
1487                 foreach my $receiver (@message_to_l) {
1488                         if ($receiver =~ /^u_([\s\S]*)$/) {
1489                                 $receiver_h{$1} = 0;
1490                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1491 # TODO implement receiver translation
1492                         } else {
1493                                 my $sbjct = &encode_base64(@{$hit}[1]);
1494                                 my $msg = &encode_base64(@{$hit}[7]);
1495                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message 'sbjct - msg'", 3); 
1496                         }
1497                 }
1498                 my @receiver_l = keys(%receiver_h);
1500         my $message_id = @{$hit}[0];
1502         #add each outgoing msg to messaging_db
1503         my $receiver;
1504         foreach $receiver (@receiver_l) {
1505             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1506                 "VALUES ('".
1507                 $message_id."', '".    # id
1508                 @{$hit}[1]."', '".     # subject
1509                 @{$hit}[2]."', '".     # message_from
1510                 $receiver."', '".      # message_to
1511                 "none"."', '".         # flag
1512                 "out"."', '".          # direction
1513                 @{$hit}[6]."', '".     # delivery_time
1514                 @{$hit}[7]."', '".     # message
1515                 $timestamp."'".     # timestamp
1516                 ")";
1517             &daemon_log("M DEBUG: $sql_statement", 1);
1518             my $res = $messaging_db->exec_statement($sql_statement);
1519             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1520         }
1522         # set incoming message to flag d=deliverd
1523         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1524         &daemon_log("M DEBUG: $sql_statement", 7);
1525         $res = $messaging_db->update_dbentry($sql_statement);
1526         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1527     }
1529     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1530     return;
1533 sub watch_for_delivery_messages {
1534     my ($kernel, $heap) = @_[KERNEL, HEAP];
1536     # select outgoing messages
1537     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1538     #&daemon_log("0 DEBUG: $sql", 7);
1539     my $res = $messaging_db->exec_statement( $sql_statement );
1540     
1541     # build out msg for each    usr
1542     foreach my $hit (@{$res}) {
1543         my $receiver = @{$hit}[3];
1544         my $msg_id = @{$hit}[0];
1545         my $subject = @{$hit}[1];
1546         my $message = @{$hit}[7];
1548         # resolve usr -> host where usr is logged in
1549         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1550         #&daemon_log("0 DEBUG: $sql", 7);
1551         my $res = $login_users_db->exec_statement($sql);
1553         # reciver is logged in nowhere
1554         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1556                 my $send_succeed = 0;
1557                 foreach my $hit (@$res) {
1558                                 my $receiver_host = @$hit[0];
1559                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1561                                 # fetch key to encrypt msg propperly for usr/host
1562                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1563                                 &daemon_log("0 DEBUG: $sql", 7);
1564                                 my $res = $known_clients_db->exec_statement($sql);
1566                                 # host is already down
1567                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1569                                 # host is on
1570                                 my $receiver_key = @{@{$res}[0]}[2];
1571                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1572                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1573                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1574                                 if ($error == 0 ) {
1575                                         $send_succeed++ ;
1576                                 }
1577                 }
1579                 if ($send_succeed) {
1580                                 # set outgoing msg at db to deliverd
1581                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1582                                 &daemon_log("0 DEBUG: $sql", 7);
1583                                 my $res = $messaging_db->exec_statement($sql); 
1584                 }
1585         }
1587     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1588     return;
1592 sub watch_for_done_messages {
1593     my ($kernel,$heap) = @_[KERNEL, HEAP];
1595     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1596     #&daemon_log("0 DEBUG: $sql", 7);
1597     my $res = $messaging_db->exec_statement($sql); 
1599     foreach my $hit (@{$res}) {
1600         my $msg_id = @{$hit}[0];
1602         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1603         #&daemon_log("0 DEBUG: $sql", 7); 
1604         my $res = $messaging_db->exec_statement($sql);
1606         # not all usr msgs have been seen till now
1607         if ( ref(@$res[0]) eq "ARRAY") { next; }
1608         
1609         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1610         #&daemon_log("0 DEBUG: $sql", 7);
1611         $res = $messaging_db->exec_statement($sql);
1612     
1613     }
1615     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1616     return;
1620 sub get_ldap_handle {
1621         my ($session_id) = @_;
1622         my $heap;
1623         my $ldap_handle;
1625         if (not defined $session_id ) { $session_id = 0 };
1626         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1628         if ($session_id == 0) {
1629                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1630                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1631                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1633         } else {
1634                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1635                 if( defined $session_reference ) {
1636                         $heap = $session_reference->get_heap();
1637                 }
1639                 if (not defined $heap) {
1640                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1641                         return;
1642                 }
1644                 # TODO: This "if" is nonsense, because it doesn't prove that the
1645                 #       used handle is still valid - or if we've to reconnect...
1646                 #if (not exists $heap->{ldap_handle}) {
1647                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1648                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1649                         $heap->{ldap_handle} = $ldap_handle;
1650                 #}
1651         }
1652         return $ldap_handle;
1656 sub change_fai_state {
1657     my ($st, $targets, $session_id) = @_;
1658     $session_id = 0 if not defined $session_id;
1659     # Set FAI state to localboot
1660     my %mapActions= (
1661         reboot    => '',
1662         update    => 'softupdate',
1663         localboot => 'localboot',
1664         reinstall => 'install',
1665         rescan    => '',
1666         wake      => '',
1667         memcheck  => 'memcheck',
1668         sysinfo   => 'sysinfo',
1669         install   => 'install',
1670     );
1672     # Return if this is unknown
1673     if (!exists $mapActions{ $st }){
1674         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1675       return;
1676     }
1678     my $state= $mapActions{ $st };
1680     my $ldap_handle = &get_ldap_handle($session_id);
1681     if( defined($ldap_handle) ) {
1683       # Build search filter for hosts
1684         my $search= "(&(objectClass=GOhard)";
1685         foreach (@{$targets}){
1686             $search.= "(macAddress=$_)";
1687         }
1688         $search.= ")";
1690       # If there's any host inside of the search string, procress them
1691         if (!($search =~ /macAddress/)){
1692             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1693             return;
1694         }
1696       # Perform search for Unit Tag
1697       my $mesg = $ldap_handle->search(
1698           base   => $ldap_base,
1699           scope  => 'sub',
1700           attrs  => ['dn', 'FAIstate', 'objectClass'],
1701           filter => "$search"
1702           );
1704           if ($mesg->count) {
1705                   my @entries = $mesg->entries;
1706                   foreach my $entry (@entries) {
1707                           # Only modify entry if it is not set to '$state'
1708                           if ($entry->get_value("FAIstate") ne "$state"){
1709                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1710                                   my $result;
1711                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1712                                   if (exists $tmp{'FAIobject'}){
1713                                           if ($state eq ''){
1714                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1715                                                           delete => [ FAIstate => [] ] ]);
1716                                           } else {
1717                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1718                                                           replace => [ FAIstate => $state ] ]);
1719                                           }
1720                                   } elsif ($state ne ''){
1721                                           $result= $ldap_handle->modify($entry->dn, changes => [
1722                                                   add     => [ objectClass => 'FAIobject' ],
1723                                                   add     => [ FAIstate => $state ] ]);
1724                                   }
1726                                   # Errors?
1727                                   if ($result->code){
1728                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1729                                   }
1730                           } else {
1731                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1732                           }  
1733                   }
1734           }
1735     # if no ldap handle defined
1736     } else {
1737         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1738     }
1743 sub change_goto_state {
1744     my ($st, $targets, $session_id) = @_;
1745     $session_id = 0  if not defined $session_id;
1747     # Switch on or off?
1748     my $state= $st eq 'active' ? 'active': 'locked';
1750     my $ldap_handle = &get_ldap_handle($session_id);
1751     if( defined($ldap_handle) ) {
1753       # Build search filter for hosts
1754       my $search= "(&(objectClass=GOhard)";
1755       foreach (@{$targets}){
1756         $search.= "(macAddress=$_)";
1757       }
1758       $search.= ")";
1760       # If there's any host inside of the search string, procress them
1761       if (!($search =~ /macAddress/)){
1762         return;
1763       }
1765       # Perform search for Unit Tag
1766       my $mesg = $ldap_handle->search(
1767           base   => $ldap_base,
1768           scope  => 'sub',
1769           attrs  => ['dn', 'gotoMode'],
1770           filter => "$search"
1771           );
1773       if ($mesg->count) {
1774         my @entries = $mesg->entries;
1775         foreach my $entry (@entries) {
1777           # Only modify entry if it is not set to '$state'
1778           if ($entry->get_value("gotoMode") ne $state){
1780             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1781             my $result;
1782             $result= $ldap_handle->modify($entry->dn, changes => [
1783                                                 replace => [ gotoMode => $state ] ]);
1785             # Errors?
1786             if ($result->code){
1787               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1788             }
1790           }
1791         }
1792       }
1794     }
1798 sub run_create_fai_server_db {
1799     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1800     my $session_id = $session->ID;
1801     my $task = POE::Wheel::Run->new(
1802             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1803             StdoutEvent  => "session_run_result",
1804             StderrEvent  => "session_run_debug",
1805             CloseEvent   => "session_run_done",
1806             );
1808     $heap->{task}->{ $task->ID } = $task;
1809     return;
1813 sub create_fai_server_db {
1814     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1815         my $result;
1817         if (not defined $session_id) { $session_id = 0; }
1818     my $ldap_handle = &get_ldap_handle();
1819         if(defined($ldap_handle)) {
1820                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1821                 my $mesg= $ldap_handle->search(
1822                         base   => $ldap_base,
1823                         scope  => 'sub',
1824                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1825                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1826                 );
1827                 if($mesg->{'resultCode'} == 0 &&
1828                    $mesg->count != 0) {
1829                    foreach my $entry (@{$mesg->{entries}}) {
1830                            if($entry->exists('FAIrepository')) {
1831                                    # Add an entry for each Repository configured for server
1832                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1833                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1834                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1835                                                    $result= $fai_server_db->add_dbentry( { 
1836                                                                    table => $table_name,
1837                                                                    primkey => ['server', 'release', 'tag'],
1838                                                                    server => $tmp_url,
1839                                                                    release => $tmp_release,
1840                                                                    sections => $tmp_sections,
1841                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1842                                                            } );
1843                                            }
1844                                    }
1845                            }
1846                    }
1847                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1849                 # TODO: Find a way to post the 'create_packages_list_db' event
1850                 if(not defined($dont_create_packages_list)) {
1851                         &create_packages_list_db(undef, undef, $session_id);
1852                 }
1853         }       
1854     
1855     $ldap_handle->disconnect;
1856         return $result;
1860 sub run_create_fai_release_db {
1861     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1862         my $session_id = $session->ID;
1863     my $task = POE::Wheel::Run->new(
1864             Program => sub { &create_fai_release_db($table_name, $session_id) },
1865             StdoutEvent  => "session_run_result",
1866             StderrEvent  => "session_run_debug",
1867             CloseEvent   => "session_run_done",
1868             );
1870     $heap->{task}->{ $task->ID } = $task;
1871     return;
1875 sub create_fai_release_db {
1876         my ($table_name, $session_id) = @_;
1877         my $result;
1879     # used for logging
1880     if (not defined $session_id) { $session_id = 0; }
1882     my $ldap_handle = &get_ldap_handle();
1883         if(defined($ldap_handle)) {
1884                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1885                 my $mesg= $ldap_handle->search(
1886                         base   => $ldap_base,
1887                         scope  => 'sub',
1888                         attrs  => [],
1889                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1890                 );
1891                 if($mesg->{'resultCode'} == 0 &&
1892                         $mesg->count != 0) {
1893                         # Walk through all possible FAI container ou's
1894                         my @sql_list;
1895                         my $timestamp= &get_time();
1896                         foreach my $ou (@{$mesg->{entries}}) {
1897                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1898                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1899                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1900                                         if(@tmp_array) {
1901                                                 foreach my $entry (@tmp_array) {
1902                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1903                                                                 my $sql= 
1904                                                                 "INSERT INTO $table_name "
1905                                                                 ."(timestamp, release, class, type, state) VALUES ("
1906                                                                 .$timestamp.","
1907                                                                 ."'".$entry->{'release'}."',"
1908                                                                 ."'".$entry->{'class'}."',"
1909                                                                 ."'".$entry->{'type'}."',"
1910                                                                 ."'".$entry->{'state'}."')";
1911                                                                 push @sql_list, $sql;
1912                                                         }
1913                                                 }
1914                                         }
1915                                 }
1916                         }
1918                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1919                         if(@sql_list) {
1920                                 unshift @sql_list, "VACUUM";
1921                                 unshift @sql_list, "DELETE FROM $table_name";
1922                                 $fai_release_db->exec_statementlist(\@sql_list);
1923                         }
1924                         daemon_log("$session_id DEBUG: Done with inserting",7);
1925                 }
1926                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1927         }
1928     $ldap_handle->disconnect;
1929         return $result;
1932 sub get_fai_types {
1933         my $tmp_classes = shift || return undef;
1934         my @result;
1936         foreach my $type(keys %{$tmp_classes}) {
1937                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1938                         my $entry = {
1939                                 type => $type,
1940                                 state => $tmp_classes->{$type}[0],
1941                         };
1942                         push @result, $entry;
1943                 }
1944         }
1946         return @result;
1949 sub get_fai_state {
1950         my $result = "";
1951         my $tmp_classes = shift || return $result;
1953         foreach my $type(keys %{$tmp_classes}) {
1954                 if(defined($tmp_classes->{$type}[0])) {
1955                         $result = $tmp_classes->{$type}[0];
1956                         
1957                 # State is equal for all types in class
1958                         last;
1959                 }
1960         }
1962         return $result;
1965 sub resolve_fai_classes {
1966         my ($fai_base, $ldap_handle, $session_id) = @_;
1967         if (not defined $session_id) { $session_id = 0; }
1968         my $result;
1969         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1970         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1971         my $fai_classes;
1973         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
1974         my $mesg= $ldap_handle->search(
1975                 base   => $fai_base,
1976                 scope  => 'sub',
1977                 attrs  => ['cn','objectClass','FAIstate'],
1978                 filter => $fai_filter,
1979         );
1980         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
1982         if($mesg->{'resultCode'} == 0 &&
1983                 $mesg->count != 0) {
1984                 foreach my $entry (@{$mesg->{entries}}) {
1985                         if($entry->exists('cn')) {
1986                                 my $tmp_dn= $entry->dn();
1988                                 # Skip classname and ou dn parts for class
1989                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1991                                 # Skip classes without releases
1992                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1993                                         next;
1994                                 }
1996                                 my $tmp_cn= $entry->get_value('cn');
1997                                 my $tmp_state= $entry->get_value('FAIstate');
1999                                 my $tmp_type;
2000                                 # Get FAI type
2001                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2002                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2003                                                 $tmp_type= $oclass;
2004                                                 last;
2005                                         }
2006                                 }
2008                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2009                                         # A Subrelease
2010                                         my @sub_releases = split(/,/, $tmp_release);
2012                                         # Walk through subreleases and build hash tree
2013                                         my $hash;
2014                                         while(my $tmp_sub_release = pop @sub_releases) {
2015                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2016                                         }
2017                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2018                                 } else {
2019                                         # A branch, no subrelease
2020                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2021                                 }
2022                         } elsif (!$entry->exists('cn')) {
2023                                 my $tmp_dn= $entry->dn();
2024                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2026                                 # Skip classes without releases
2027                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2028                                         next;
2029                                 }
2031                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2032                                         # A Subrelease
2033                                         my @sub_releases= split(/,/, $tmp_release);
2035                                         # Walk through subreleases and build hash tree
2036                                         my $hash;
2037                                         while(my $tmp_sub_release = pop @sub_releases) {
2038                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2039                                         }
2040                                         # Remove the last two characters
2041                                         chop($hash);
2042                                         chop($hash);
2044                                         eval('$fai_classes->'.$hash.'= {}');
2045                                 } else {
2046                                         # A branch, no subrelease
2047                                         if(!exists($fai_classes->{$tmp_release})) {
2048                                                 $fai_classes->{$tmp_release} = {};
2049                                         }
2050                                 }
2051                         }
2052                 }
2054                 # The hash is complete, now we can honor the copy-on-write based missing entries
2055                 foreach my $release (keys %$fai_classes) {
2056                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2057                 }
2058         }
2059         return $result;
2062 sub apply_fai_inheritance {
2063        my $fai_classes = shift || return {};
2064        my $tmp_classes;
2066        # Get the classes from the branch
2067        foreach my $class (keys %{$fai_classes}) {
2068                # Skip subreleases
2069                if($class =~ /^ou=.*$/) {
2070                        next;
2071                } else {
2072                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2073                }
2074        }
2076        # Apply to each subrelease
2077        foreach my $subrelease (keys %{$fai_classes}) {
2078                if($subrelease =~ /ou=/) {
2079                        foreach my $tmp_class (keys %{$tmp_classes}) {
2080                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2081                                        $fai_classes->{$subrelease}->{$tmp_class} =
2082                                        deep_copy($tmp_classes->{$tmp_class});
2083                                } else {
2084                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2085                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2086                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2087                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2088                                                }
2089                                        }
2090                                }
2091                        }
2092                }
2093        }
2095        # Find subreleases in deeper levels
2096        foreach my $subrelease (keys %{$fai_classes}) {
2097                if($subrelease =~ /ou=/) {
2098                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2099                                if($subsubrelease =~ /ou=/) {
2100                                        apply_fai_inheritance($fai_classes->{$subrelease});
2101                                }
2102                        }
2103                }
2104        }
2106        return $fai_classes;
2109 sub get_fai_release_entries {
2110         my $tmp_classes = shift || return;
2111         my $parent = shift || "";
2112         my @result = shift || ();
2114         foreach my $entry (keys %{$tmp_classes}) {
2115                 if(defined($entry)) {
2116                         if($entry =~ /^ou=.*$/) {
2117                                 my $release_name = $entry;
2118                                 $release_name =~ s/ou=//g;
2119                                 if(length($parent)>0) {
2120                                         $release_name = $parent."/".$release_name;
2121                                 }
2122                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2123                                 foreach my $bufentry(@bufentries) {
2124                                         push @result, $bufentry;
2125                                 }
2126                         } else {
2127                                 my @types = get_fai_types($tmp_classes->{$entry});
2128                                 foreach my $type (@types) {
2129                                         push @result, 
2130                                         {
2131                                                 'class' => $entry,
2132                                                 'type' => $type->{'type'},
2133                                                 'release' => $parent,
2134                                                 'state' => $type->{'state'},
2135                                         };
2136                                 }
2137                         }
2138                 }
2139         }
2141         return @result;
2144 sub deep_copy {
2145         my $this = shift;
2146         if (not ref $this) {
2147                 $this;
2148         } elsif (ref $this eq "ARRAY") {
2149                 [map deep_copy($_), @$this];
2150         } elsif (ref $this eq "HASH") {
2151                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2152         } else { die "what type is $_?" }
2156 sub session_run_result {
2157     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2158     $kernel->sig(CHLD => "child_reap");
2161 sub session_run_debug {
2162     my $result = $_[ARG0];
2163     print STDERR "$result\n";
2166 sub session_run_done {
2167     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2168     delete $heap->{task}->{$task_id};
2172 sub create_sources_list {
2173         my $session_id = shift;
2174         my $ldap_handle = &main::get_ldap_handle;
2175         my $result="/tmp/gosa_si_tmp_sources_list";
2177         # Remove old file
2178         if(stat($result)) {
2179                 unlink($result);
2180                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2181         }
2183         my $fh;
2184         open($fh, ">$result");
2185         if (not defined $fh) {
2186                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2187                 return undef;
2188         }
2189         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2190                 my $mesg=$ldap_handle->search(
2191                         base    => $main::ldap_server_dn,
2192                         scope   => 'base',
2193                         attrs   => 'FAIrepository',
2194                         filter  => 'objectClass=FAIrepositoryServer'
2195                 );
2196                 if($mesg->count) {
2197                         foreach my $entry(@{$mesg->{'entries'}}) {
2198                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2199                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2200                                         my $line = "deb $server $release";
2201                                         $sections =~ s/,/ /g;
2202                                         $line.= " $sections";
2203                                         print $fh $line."\n";
2204                                 }
2205                         }
2206                 }
2207         } else {
2208                 if (defined $main::ldap_server_dn){
2209                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2210                 } else {
2211                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2212                 }
2213         }
2214         close($fh);
2216         return $result;
2220 sub run_create_packages_list_db {
2221     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2222         my $session_id = $session->ID;
2224         my $task = POE::Wheel::Run->new(
2225                                         Priority => +20,
2226                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2227                                         StdoutEvent  => "session_run_result",
2228                                         StderrEvent  => "session_run_debug",
2229                                         CloseEvent   => "session_run_done",
2230                                         );
2231         $heap->{task}->{ $task->ID } = $task;
2235 sub create_packages_list_db {
2236         my ($ldap_handle, $sources_file, $session_id) = @_;
2237         
2238         # it should not be possible to trigger a recreation of packages_list_db
2239         # while packages_list_db is under construction, so set flag packages_list_under_construction
2240         # which is tested befor recreation can be started
2241         if (-r $packages_list_under_construction) {
2242                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2243                 return;
2244         } else {
2245                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2246                 # set packages_list_under_construction to true
2247                 system("touch $packages_list_under_construction");
2248                 @packages_list_statements=();
2249         }
2251         if (not defined $session_id) { $session_id = 0; }
2252         if (not defined $ldap_handle) { 
2253                 $ldap_handle= &get_ldap_handle();
2255                 if (not defined $ldap_handle) {
2256                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2257                         unlink($packages_list_under_construction);
2258                         return;
2259                 }
2260         }
2261         if (not defined $sources_file) { 
2262                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2263                 $sources_file = &create_sources_list($session_id);
2264         }
2266         if (not defined $sources_file) {
2267                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2268                 unlink($packages_list_under_construction);
2269                 return;
2270         }
2272         my $line;
2274         open(CONFIG, "<$sources_file") or do {
2275                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2276                 unlink($packages_list_under_construction);
2277                 return;
2278         };
2280         # Read lines
2281         while ($line = <CONFIG>){
2282                 # Unify
2283                 chop($line);
2284                 $line =~ s/^\s+//;
2285                 $line =~ s/^\s+/ /;
2287                 # Strip comments
2288                 $line =~ s/#.*$//g;
2290                 # Skip empty lines
2291                 if ($line =~ /^\s*$/){
2292                         next;
2293                 }
2295                 # Interpret deb line
2296                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2297                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2298                         my $section;
2299                         foreach $section (split(' ', $sections)){
2300                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2301                         }
2302                 }
2303         }
2305         close (CONFIG);
2307         find(\&cleanup_and_extract, keys( %repo_dirs ));
2308         &main::strip_packages_list_statements();
2309         unshift @packages_list_statements, "VACUUM";
2310         $packages_list_db->exec_statementlist(\@packages_list_statements);
2311         unlink($packages_list_under_construction);
2312         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2313         return;
2316 # This function should do some intensive task to minimize the db-traffic
2317 sub strip_packages_list_statements {
2318     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2319         my @new_statement_list=();
2320         my $hash;
2321         my $insert_hash;
2322         my $update_hash;
2323         my $delete_hash;
2324         my $local_timestamp=get_time();
2326         foreach my $existing_entry (@existing_entries) {
2327                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2328         }
2330         foreach my $statement (@packages_list_statements) {
2331                 if($statement =~ /^INSERT/i) {
2332                         # Assign the values from the insert statement
2333                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2334                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2335                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2336                                 # If section or description has changed, update the DB
2337                                 if( 
2338                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2339                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2340                                 ) {
2341                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2342                                 }
2343                         } else {
2344                                 # Insert a non-existing entry to db
2345                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2346                         }
2347                 } elsif ($statement =~ /^UPDATE/i) {
2348                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2349                         /^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;
2350                         foreach my $distribution (keys %{$hash}) {
2351                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2352                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2353                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2354                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2355                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2356                                                 my $section;
2357                                                 my $description;
2358                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2359                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2360                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2361                                                 }
2362                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2363                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2364                                                 }
2365                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2366                                         }
2367                                 }
2368                         }
2369                 }
2370         }
2372         # TODO: Check for orphaned entries
2374         # unroll the insert_hash
2375         foreach my $distribution (keys %{$insert_hash}) {
2376                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2377                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2378                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2379                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2380                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2381                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2382                                 ."'$local_timestamp')";
2383                         }
2384                 }
2385         }
2387         # unroll the update hash
2388         foreach my $distribution (keys %{$update_hash}) {
2389                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2390                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2391                                 my $set = "";
2392                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2393                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2394                                 }
2395                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2396                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2397                                 }
2398                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2399                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2400                                 }
2401                                 if(defined($set) and length($set) > 0) {
2402                                         $set .= "timestamp = '$local_timestamp'";
2403                                 } else {
2404                                         next;
2405                                 }
2406                                 push @new_statement_list, 
2407                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2408                                         ." distribution = '$distribution'"
2409                                         ." AND package = '$package'"
2410                                         ." AND version = '$version'";
2411                         }
2412                 }
2413         }
2415         @packages_list_statements = @new_statement_list;
2419 sub parse_package_info {
2420     my ($baseurl, $dist, $section, $session_id)= @_;
2421     my ($package);
2422     if (not defined $session_id) { $session_id = 0; }
2423     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2424     $repo_dirs{ "${repo_path}/pool" } = 1;
2426     foreach $package ("Packages.gz"){
2427         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2428         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2429         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2430     }
2431     
2435 sub get_package {
2436     my ($url, $dest, $session_id)= @_;
2437     if (not defined $session_id) { $session_id = 0; }
2439     my $tpath = dirname($dest);
2440     -d "$tpath" || mkpath "$tpath";
2442     # This is ugly, but I've no time to take a look at "how it works in perl"
2443     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2444         system("gunzip -cd '$dest' > '$dest.in'");
2445         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2446         unlink($dest);
2447         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2448     } else {
2449         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2450     }
2451     return 0;
2455 sub parse_package {
2456     my ($path, $dist, $srv_path, $session_id)= @_;
2457     if (not defined $session_id) { $session_id = 0;}
2458     my ($package, $version, $section, $description);
2459     my $PACKAGES;
2460     my $timestamp = &get_time();
2462     if(not stat("$path.in")) {
2463         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2464         return;
2465     }
2467     open($PACKAGES, "<$path.in");
2468     if(not defined($PACKAGES)) {
2469         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2470         return;
2471     }
2473     # Read lines
2474     while (<$PACKAGES>){
2475         my $line = $_;
2476         # Unify
2477         chop($line);
2479         # Use empty lines as a trigger
2480         if ($line =~ /^\s*$/){
2481             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2482             push(@packages_list_statements, $sql);
2483             $package = "none";
2484             $version = "none";
2485             $section = "none";
2486             $description = "none"; 
2487             next;
2488         }
2490         # Trigger for package name
2491         if ($line =~ /^Package:\s/){
2492             ($package)= ($line =~ /^Package: (.*)$/);
2493             next;
2494         }
2496         # Trigger for version
2497         if ($line =~ /^Version:\s/){
2498             ($version)= ($line =~ /^Version: (.*)$/);
2499             next;
2500         }
2502         # Trigger for description
2503         if ($line =~ /^Description:\s/){
2504             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2505             next;
2506         }
2508         # Trigger for section
2509         if ($line =~ /^Section:\s/){
2510             ($section)= ($line =~ /^Section: (.*)$/);
2511             next;
2512         }
2514         # Trigger for filename
2515         if ($line =~ /^Filename:\s/){
2516             my ($filename) = ($line =~ /^Filename: (.*)$/);
2517             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2518             next;
2519         }
2520     }
2522     close( $PACKAGES );
2523     unlink( "$path.in" );
2524     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2528 sub store_fileinfo {
2529     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2531     my %fileinfo = (
2532         'package' => $package,
2533         'dist' => $dist,
2534         'version' => $vers,
2535     );
2537     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2541 sub cleanup_and_extract {
2542     my $fileinfo = $repo_files{ $File::Find::name };
2544     if( defined $fileinfo ) {
2546         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2547         my $sql;
2548         my $package = $fileinfo->{ 'package' };
2549         my $newver = $fileinfo->{ 'version' };
2551         mkpath($dir);
2552         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2554                 if( -f "$dir/DEBIAN/templates" ) {
2556                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2558                         my $tmpl= "";
2559                         {
2560                                 local $/=undef;
2561                                 open FILE, "$dir/DEBIAN/templates";
2562                                 $tmpl = &encode_base64(<FILE>);
2563                                 close FILE;
2564                         }
2565                         rmtree("$dir/DEBIAN/templates");
2567                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2568                 push @packages_list_statements, $sql;
2569                 }
2570     }
2572     return;
2576 #==== MAIN = main ==============================================================
2577 #  parse commandline options
2578 Getopt::Long::Configure( "bundling" );
2579 GetOptions("h|help" => \&usage,
2580         "c|config=s" => \$cfg_file,
2581         "f|foreground" => \$foreground,
2582         "v|verbose+" => \$verbose,
2583         "no-bus+" => \$no_bus,
2584         "no-arp+" => \$no_arp,
2585            );
2587 #  read and set config parameters
2588 &check_cmdline_param ;
2589 &read_configfile;
2590 &check_pid;
2592 $SIG{CHLD} = 'IGNORE';
2594 # forward error messages to logfile
2595 if( ! $foreground ) {
2596   open( STDIN,  '+>/dev/null' );
2597   open( STDOUT, '+>&STDIN'    );
2598   open( STDERR, '+>&STDIN'    );
2601 # Just fork, if we are not in foreground mode
2602 if( ! $foreground ) { 
2603     chdir '/'                 or die "Can't chdir to /: $!";
2604     $pid = fork;
2605     setsid                    or die "Can't start a new session: $!";
2606     umask 0;
2607 } else { 
2608     $pid = $$; 
2611 # Do something useful - put our PID into the pid_file
2612 if( 0 != $pid ) {
2613     open( LOCK_FILE, ">$pid_file" );
2614     print LOCK_FILE "$pid\n";
2615     close( LOCK_FILE );
2616     if( !$foreground ) { 
2617         exit( 0 ) 
2618     };
2621 # parse head url and revision from svn
2622 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2623 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2624 $server_headURL = defined $1 ? $1 : 'unknown' ;
2625 $server_revision = defined $2 ? $2 : 'unknown' ;
2626 if ($server_headURL =~ /\/tag\// || 
2627         $server_headURL =~ /\/branches\// ) {
2628     $server_status = "stable"; 
2629 } else {
2630     $server_status = "developmental" ;
2634 daemon_log(" ", 1);
2635 daemon_log("$0 started!", 1);
2636 daemon_log("status: $server_status", 1);
2637 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2639 if ($no_bus > 0) {
2640     $bus_activ = "false"
2643 # connect to incoming_db
2644 unlink($incoming_file_name);
2645 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2646 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2648 # connect to gosa-si job queue
2649 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2650 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2652 # connect to known_clients_db
2653 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2654 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2656 # connect to known_server_db
2657 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2658 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2660 # connect to login_usr_db
2661 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2662 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2664 # connect to fai_server_db and fai_release_db
2665 unlink($fai_server_file_name);
2666 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2667 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2669 unlink($fai_release_file_name);
2670 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2671 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2673 # connect to packages_list_db
2674 #unlink($packages_list_file_name);
2675 unlink($packages_list_under_construction);
2676 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2677 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2679 # connect to messaging_db
2680 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2681 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2684 # create xml object used for en/decrypting
2685 $xml = new XML::Simple();
2687 # create socket for incoming xml messages
2689 POE::Component::Server::TCP->new(
2690         Port => $server_port,
2691         ClientInput => sub {
2692         my ($kernel, $input) = @_[KERNEL, ARG0];
2693         push(@tasks, $input);
2694         push(@msgs_to_decrypt, $input);
2695         $kernel->yield("next_task");
2696         $kernel->yield("msg_to_decrypt");
2697         },
2698     InlineStates => {
2699         next_task => \&next_task,
2700         msg_to_decrypt => \&msg_to_decrypt,
2701         task_result => \&handle_task_result,
2702         task_done   => \&handle_task_done,
2703         task_debug  => \&handle_task_debug,
2704         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2705     }
2706 );
2708 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2710 # create session for repeatedly checking the job queue for jobs
2711 POE::Session->create(
2712         inline_states => {
2713                 _start => \&_start,
2714                 sig_handler => \&sig_handler,
2715         watch_for_new_messages => \&watch_for_new_messages,
2716         watch_for_delivery_messages => \&watch_for_delivery_messages,
2717         watch_for_done_messages => \&watch_for_done_messages,
2718                 watch_for_new_jobs => \&watch_for_new_jobs,
2719         watch_for_done_jobs => \&watch_for_done_jobs,
2720         create_packages_list_db => \&run_create_packages_list_db,
2721         create_fai_server_db => \&run_create_fai_server_db,
2722         create_fai_release_db => \&run_create_fai_release_db,
2723         session_run_result => \&session_run_result,
2724         session_run_debug => \&session_run_debug,
2725         session_run_done => \&session_run_done,
2726         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2727         }
2728 );
2731 # import all modules
2732 &import_modules;
2734 # check wether all modules are gosa-si valid passwd check
2736 POE::Kernel->run();
2737 exit;