Code

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