Code

* gosa-si-server without bus
[gosa.git] / gosa-si / gosa-si-server-nobus
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 #         FILE:  gosa-sd
5 #
6 #        USAGE:  ./gosa-sd
7 #
8 #  DESCRIPTION:
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl 
12 #                libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 #                libpoe-perl
14 #         BUGS:  ---
15 #        NOTES:
16 #       AUTHOR:   (Andreas Rettenberger), <rettenberger@gonicus.de>
17 #      COMPANY:
18 #      VERSION:  1.0
19 #      CREATED:  12.09.2007 08:54:41 CEST
20 #     REVISION:  ---
21 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5  qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
58 my $modules_path = "/usr/lib/gosa-si/modules";
59 use lib "/usr/lib/gosa-si/modules";
61 # revision number of server and program name
62 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
63 my $server_headURL;
64 my $server_revision;
65 my $server_status;
66 our $prg= basename($0);
68 our $global_kernel;
69 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
70 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
71 my ($server);
72 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
73 my ($messaging_db_loop_delay);
74 my ($known_modules);
75 my ($pid_file, $procid, $pid, $log_file);
76 my ($arp_activ, $arp_fifo);
77 my ($xml);
78 my $sources_list;
79 my $max_clients;
80 my %repo_files=();
81 my $repo_path;
82 my %repo_dirs=();
83 # variables declared in config file are always set to 'our'
84 our (%cfg_defaults, $log_file, $pid_file, 
85     $server_ip, $server_port, $SIPackages_key, 
86     $arp_activ, $gosa_unit_tag,
87     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
88 );
90 # additional variable which should be globaly accessable
91 our $server_address;
92 our $server_mac_address;
93 our $bus_address;
94 our $gosa_address;
95 our $no_bus;
96 our $no_arp;
97 our $verbose;
98 our $forground;
99 our $cfg_file;
100 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
103 # specifies the verbosity of the daemon_log
104 $verbose = 0 ;
106 # if foreground is not null, script will be not forked to background
107 $foreground = 0 ;
109 # specifies the timeout seconds while checking the online status of a registrating client
110 $ping_timeout = 5;
112 $no_bus = 0;
113 $bus_activ = "true";
114 $no_arp = 0;
115 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
116 my @packages_list_statements;
117 my $watch_for_new_jobs_in_progress = 0;
119 # holds all incoming decrypted messages
120 our $incoming_db;
121 our $incoming_tn = 'incoming';
122 my $incoming_file_name;
123 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
124         "timestamp DEFAULT 'none'", 
125         "headertag DEFAULT 'none'",
126                 "targettag DEFAULT 'none'",
127         "xmlmessage DEFAULT 'none'",
128         "module DEFAULT 'none'",
129         );
131 # holds all gosa jobs
132 our $job_db;
133 our $job_queue_tn = 'jobs';
134 my $job_queue_file_name;
135 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
136                 "timestamp DEFAULT 'none'", 
137                 "status DEFAULT 'none'", 
138                 "result DEFAULT 'none'", 
139                 "progress DEFAULT 'none'", 
140         "headertag DEFAULT 'none'", 
141                 "targettag DEFAULT 'none'", 
142                 "xmlmessage DEFAULT 'none'", 
143                 "macaddress DEFAULT 'none'",
144                 "plainname DEFAULT 'none'",
145                 );
147 # holds all other gosa-sd as well as the gosa-sd-bus
148 our $known_server_db;
149 our $known_server_tn = "known_server";
150 my $known_server_file_name;
151 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
153 # holds all registrated clients
154 our $known_clients_db;
155 our $known_clients_tn = "known_clients";
156 my $known_clients_file_name;
157 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events");
159 # holds all logged in user at each client 
160 our $login_users_db;
161 our $login_users_tn = "login_users";
162 my $login_users_file_name;
163 my @login_users_col_names = ("client", "user", "timestamp");
165 # holds all fai server, the debian release and tag
166 our $fai_server_db;
167 our $fai_server_tn = "fai_server"; 
168 my $fai_server_file_name;
169 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
171 our $fai_release_db;
172 our $fai_release_tn = "fai_release"; 
173 my $fai_release_file_name;
174 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
176 # holds all packages available from different repositories
177 our $packages_list_db;
178 our $packages_list_tn = "packages_list";
179 my $packages_list_file_name;
180 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
181 my $outdir = "/tmp/packages_list_db";
182 my $arch = "i386"; 
184 # holds all messages which should be delivered to a user
185 our $messaging_db;
186 our $messaging_tn = "messaging"; 
187 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
188         "flag", "direction", "delivery_time", "message", "timestamp" );
189 my $messaging_file_name;
191 # path to directory to store client install log files
192 our $client_fai_log_dir = "/var/log/fai"; 
194 # queue which stores taskes until one of the $max_children children are ready to process the task
195 my @tasks = qw();
196 my @msgs_to_decrypt = qw();
197 my $max_children = 2;
200 %cfg_defaults = (
201 "general" => {
202     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
203     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
204     },
205 "bus" => {
206     "activ" => [\$bus_activ, "true"],
207     },
208 "server" => {
209     "port" => [\$server_port, "20081"],
210     "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
211     "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
212     "incoming"      => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
213     "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
214     "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
215     "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
216     "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
217     "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
218     "source-list" => [\$sources_list, '/etc/apt/sources.list'],
219     "repo-path" => [\$repo_path, '/srv/www/repository'],
220     "ldap-uri" => [\$ldap_uri, ""],
221     "ldap-base" => [\$ldap_base, ""],
222     "ldap-admin-dn" => [\$ldap_admin_dn, ""],
223     "ldap-admin-password" => [\$ldap_admin_password, ""],
224     "gosa-unit-tag" => [\$gosa_unit_tag, ""],
225     "max-clients" => [\$max_clients, 10],
226     },
227 "GOsaPackages" => {
228     "ip" => [\$gosa_ip, "0.0.0.0"],
229     "port" => [\$gosa_port, "20082"],
230     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
231     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
232     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
233     "key" => [\$GosaPackages_key, "none"],
234     },
235 "SIPackages" => {
236     "key" => [\$SIPackages_key, "none"],
237     },
238 );
241 #===  FUNCTION  ================================================================
242 #         NAME:  usage
243 #   PARAMETERS:  nothing
244 #      RETURNS:  nothing
245 #  DESCRIPTION:  print out usage text to STDERR
246 #===============================================================================
247 sub usage {
248     print STDERR << "EOF" ;
249 usage: $prg [-hvf] [-c config]
251            -h        : this (help) message
252            -c <file> : config file
253            -f        : foreground, process will not be forked to background
254            -v        : be verbose (multiple to increase verbosity)
255            -no-bus   : starts $prg without connection to bus
256            -no-arp   : starts $prg without connection to arp module
257  
258 EOF
259     print "\n" ;
263 #===  FUNCTION  ================================================================
264 #         NAME:  read_configfile
265 #   PARAMETERS:  cfg_file - string -
266 #      RETURNS:  nothing
267 #  DESCRIPTION:  read cfg_file and set variables
268 #===============================================================================
269 sub read_configfile {
270     my $cfg;
271     if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
272         if( -r $cfg_file ) {
273             $cfg = Config::IniFiles->new( -file => $cfg_file );
274         } else {
275             print STDERR "Couldn't read config file!\n";
276         }
277     } else {
278         $cfg = Config::IniFiles->new() ;
279     }
280     foreach my $section (keys %cfg_defaults) {
281         foreach my $param (keys %{$cfg_defaults{ $section }}) {
282             my $pinfo = $cfg_defaults{ $section }{ $param };
283             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
284         }
285     }
289 #===  FUNCTION  ================================================================
290 #         NAME:  logging
291 #   PARAMETERS:  level - string - default 'info'
292 #                msg - string -
293 #                facility - string - default 'LOG_DAEMON'
294 #      RETURNS:  nothing
295 #  DESCRIPTION:  function for logging
296 #===============================================================================
297 sub daemon_log {
298     # log into log_file
299     my( $msg, $level ) = @_;
300     if(not defined $msg) { return }
301     if(not defined $level) { $level = 1 }
302     if(defined $log_file){
303         open(LOG_HANDLE, ">>$log_file");
304         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
305             print STDERR "cannot open $log_file: $!";
306             return }
307             chomp($msg);
308                         $msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
309             if($level <= $verbose){
310                 my ($seconds, $minutes, $hours, $monthday, $month,
311                         $year, $weekday, $yearday, $sommertime) = localtime(time);
312                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
313                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
314                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
315                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
316                 $month = $monthnames[$month];
317                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
318                 $year+=1900;
319                 my $name = $prg;
321                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
322                 print LOG_HANDLE $log_msg;
323                 if( $foreground ) { 
324                     print STDERR $log_msg;
325                 }
326             }
327         close( LOG_HANDLE );
328     }
332 #===  FUNCTION  ================================================================
333 #         NAME:  check_cmdline_param
334 #   PARAMETERS:  nothing
335 #      RETURNS:  nothing
336 #  DESCRIPTION:  validates commandline parameter
337 #===============================================================================
338 sub check_cmdline_param () {
339     my $err_config;
340     my $err_counter = 0;
341         if(not defined($cfg_file)) {
342                 $cfg_file = "/etc/gosa-si/server.conf";
343                 if(! -r $cfg_file) {
344                         $err_config = "please specify a config file";
345                         $err_counter += 1;
346                 }
347     }
348     if( $err_counter > 0 ) {
349         &usage( "", 1 );
350         if( defined( $err_config)) { print STDERR "$err_config\n"}
351         print STDERR "\n";
352         exit( -1 );
353     }
357 #===  FUNCTION  ================================================================
358 #         NAME:  check_pid
359 #   PARAMETERS:  nothing
360 #      RETURNS:  nothing
361 #  DESCRIPTION:  handels pid processing
362 #===============================================================================
363 sub check_pid {
364     $pid = -1;
365     # Check, if we are already running
366     if( open(LOCK_FILE, "<$pid_file") ) {
367         $pid = <LOCK_FILE>;
368         if( defined $pid ) {
369             chomp( $pid );
370             if( -f "/proc/$pid/stat" ) {
371                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
372                 if( $stat ) {
373                                         daemon_log("ERROR: Already running",1);
374                     close( LOCK_FILE );
375                     exit -1;
376                 }
377             }
378         }
379         close( LOCK_FILE );
380         unlink( $pid_file );
381     }
383     # create a syslog msg if it is not to possible to open PID file
384     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
385         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
386         if (open(LOCK_FILE, '<', $pid_file)
387                 && ($pid = <LOCK_FILE>))
388         {
389             chomp($pid);
390             $msg .= "(PID $pid)\n";
391         } else {
392             $msg .= "(unable to read PID)\n";
393         }
394         if( ! ($foreground) ) {
395             openlog( $0, "cons,pid", "daemon" );
396             syslog( "warning", $msg );
397             closelog();
398         }
399         else {
400             print( STDERR " $msg " );
401         }
402         exit( -1 );
403     }
406 #===  FUNCTION  ================================================================
407 #         NAME:  import_modules
408 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
409 #                are stored
410 #      RETURNS:  nothing
411 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
412 #                state is on is imported by "require 'file';"
413 #===============================================================================
414 sub import_modules {
415     daemon_log(" ", 1);
417     if (not -e $modules_path) {
418         daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
419     }
421     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
422     while (defined (my $file = readdir (DIR))) {
423         if (not $file =~ /(\S*?).pm$/) {
424             next;
425         }
426                 my $mod_name = $1;
428         if( $file =~ /ArpHandler.pm/ ) {
429             if( $no_arp > 0 ) {
430                 next;
431             }
432         }
433         
434         eval { require $file; };
435         if ($@) {
436             daemon_log("ERROR: gosa-si-server could not load module $file", 1);
437             daemon_log("$@", 5);
438                 } else {
439                         my $info = eval($mod_name.'::get_module_info()');
440                         # Only load module if get_module_info() returns a non-null object
441                         if( $info ) {
442                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
443                                 $known_modules->{$mod_name} = $info;
444                                 daemon_log("INFO: module $mod_name loaded", 5);
445                         }
446                 }
447     }   
448     close (DIR);
452 #===  FUNCTION  ================================================================
453 #         NAME:  sig_int_handler
454 #   PARAMETERS:  signal - string - signal arose from system
455 #      RETURNS:  noting
456 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
457 #===============================================================================
458 sub sig_int_handler {
459     my ($signal) = @_;
461 #       if (defined($ldap_handle)) {
462 #               $ldap_handle->disconnect;
463 #       }
464     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
465     
467     daemon_log("shutting down gosa-si-server", 1);
468     system("kill `ps -C gosa-si-server-nobus -o pid=`");
470 $SIG{INT} = \&sig_int_handler;
473 sub check_key_and_xml_validity {
474     my ($crypted_msg, $module_key, $session_id) = @_;
475     my $msg;
476     my $msg_hash;
477     my $error_string;
478     eval{
479         $msg = &decrypt_msg($crypted_msg, $module_key);
481         if ($msg =~ /<xml>/i){
482             $msg =~ s/\s+/ /g;  # just for better daemon_log
483             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
484             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
486             ##############
487             # check header
488             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
489             my $header_l = $msg_hash->{'header'};
490             if( 1 > @{$header_l} ) { die 'empty header tag'; }
491             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
492             my $header = @{$header_l}[0];
493             if( 0 == length $header) { die 'empty string in header tag'; }
495             ##############
496             # check source
497             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
498             my $source_l = $msg_hash->{'source'};
499             if( 1 > @{$source_l} ) { die 'empty source tag'; }
500             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
501             my $source = @{$source_l}[0];
502             if( 0 == length $source) { die 'source error'; }
504             ##############
505             # check target
506             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
507             my $target_l = $msg_hash->{'target'};
508             if( 1 > @{$target_l} ) { die 'empty target tag'; }
509         }
510     };
511     if($@) {
512         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
513         $msg = undef;
514         $msg_hash = undef;
515     }
517     return ($msg, $msg_hash);
521 sub check_outgoing_xml_validity {
522     my ($msg) = @_;
524     my $msg_hash;
525     eval{
526         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
528         ##############
529         # check header
530         my $header_l = $msg_hash->{'header'};
531         if( 1 != @{$header_l} ) {
532             die 'no or more than one headers specified';
533         }
534         my $header = @{$header_l}[0];
535         if( 0 == length $header) {
536             die 'header has length 0';
537         }
539         ##############
540         # check source
541         my $source_l = $msg_hash->{'source'};
542         if( 1 != @{$source_l} ) {
543             die 'no or more than 1 sources specified';
544         }
545         my $source = @{$source_l}[0];
546         if( 0 == length $source) {
547             die 'source has length 0';
548         }
549         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
550                 $source =~ /^GOSA$/i ) {
551             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
552         }
553         
554         ##############
555         # check target  
556         my $target_l = $msg_hash->{'target'};
557         if( 0 == @{$target_l} ) {
558             die "no targets specified";
559         }
560         foreach my $target (@$target_l) {
561             if( 0 == length $target) {
562                 die "target has length 0";
563             }
564             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
565                     $target =~ /^GOSA$/i ||
566                     $target =~ /^\*$/ ||
567                     $target =~ /KNOWN_SERVER/i ||
568                     $target =~ /JOBDB/i ||
569                     $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 ){
570                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
571             }
572         }
573     };
574     if($@) {
575         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
576         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
577         $msg_hash = undef;
578     }
580     return ($msg_hash);
584 sub input_from_known_server {
585     my ($input, $remote_ip, $session_id) = @_ ;  
586     my ($msg, $msg_hash, $module);
588     my $sql_statement= "SELECT * FROM known_server";
589     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
591     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
592         my $host_name = $hit->{hostname};
593         if( not $host_name =~ "^$remote_ip") {
594             next;
595         }
596         my $host_key = $hit->{hostkey};
597         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
598         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
600         # check if module can open msg envelope with module key
601         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
602         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
603             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
604             daemon_log("$@", 8);
605             next;
606         }
607         else {
608             $msg = $tmp_msg;
609             $msg_hash = $tmp_msg_hash;
610             $module = "SIPackages";
611             last;
612         }
613     }
615     if( (!$msg) || (!$msg_hash) || (!$module) ) {
616         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
617     }
618   
619     return ($msg, $msg_hash, $module);
623 sub input_from_known_client {
624     my ($input, $remote_ip, $session_id) = @_ ;  
625     my ($msg, $msg_hash, $module);
627     my $sql_statement= "SELECT * FROM known_clients";
628     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
629     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
630         my $host_name = $hit->{hostname};
631         if( not $host_name =~ /^$remote_ip:\d*$/) {
632                 next;
633                 }
634         my $host_key = $hit->{hostkey};
635         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
636         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
638         # check if module can open msg envelope with module key
639         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
641         if( (!$msg) || (!$msg_hash) ) {
642             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
643             &daemon_log("$@", 8);
644             next;
645         }
646         else {
647             $module = "SIPackages";
648             last;
649         }
650     }
652     if( (!$msg) || (!$msg_hash) || (!$module) ) {
653         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
654     }
656     return ($msg, $msg_hash, $module);
660 sub input_from_unknown_host {
661     no strict "refs";
662     my ($input, $session_id) = @_ ;
663     my ($msg, $msg_hash, $module);
664     my $error_string;
665     
666         my %act_modules = %$known_modules;
668         while( my ($mod, $info) = each(%act_modules)) {
670         # check a key exists for this module
671         my $module_key = ${$mod."_key"};
672         if( not defined $module_key ) {
673             if( $mod eq 'ArpHandler' ) {
674                 next;
675             }
676             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
677             next;
678         }
679         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
681         # check if module can open msg envelope with module key
682         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
683         if( (not defined $msg) || (not defined $msg_hash) ) {
684             next;
685         }
686         else {
687             $module = $mod;
688             last;
689         }
690     }
692     if( (!$msg) || (!$msg_hash) || (!$module)) {
693         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
694     }
696     return ($msg, $msg_hash, $module);
700 sub create_ciphering {
701     my ($passwd) = @_;
702         if((!defined($passwd)) || length($passwd)==0) {
703                 $passwd = "";
704         }
705     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
706     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
707     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
708     $my_cipher->set_iv($iv);
709     return $my_cipher;
713 sub encrypt_msg {
714     my ($msg, $key) = @_;
715     my $my_cipher = &create_ciphering($key);
716     my $len;
717     {
718             use bytes;
719             $len= 16-length($msg)%16;
720     }
721     $msg = "\0"x($len).$msg;
722     $msg = $my_cipher->encrypt($msg);
723     chomp($msg = &encode_base64($msg));
724     # there are no newlines allowed inside msg
725     $msg=~ s/\n//g;
726     return $msg;
730 sub decrypt_msg {
732     my ($msg, $key) = @_ ;
733     $msg = &decode_base64($msg);
734     my $my_cipher = &create_ciphering($key);
735     $msg = $my_cipher->decrypt($msg); 
736     $msg =~ s/\0*//g;
737     return $msg;
741 sub get_encrypt_key {
742     my ($target) = @_ ;
743     my $encrypt_key;
744     my $error = 0;
746     # target can be in known_server
747     if( not defined $encrypt_key ) {
748         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
749         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
750         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
751             my $host_name = $hit->{hostname};
752             if( $host_name ne $target ) {
753                 next;
754             }
755             $encrypt_key = $hit->{hostkey};
756             last;
757         }
758     }
760     # target can be in known_client
761     if( not defined $encrypt_key ) {
762         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
763         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
764         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
765             my $host_name = $hit->{hostname};
766             if( $host_name ne $target ) {
767                 next;
768             }
769             $encrypt_key = $hit->{hostkey};
770             last;
771         }
772     }
774     return $encrypt_key;
778 #===  FUNCTION  ================================================================
779 #         NAME:  open_socket
780 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
781 #                [PeerPort] string necessary if port not appended by PeerAddr
782 #      RETURNS:  socket IO::Socket::INET
783 #  DESCRIPTION:  open a socket to PeerAddr
784 #===============================================================================
785 sub open_socket {
786     my ($PeerAddr, $PeerPort) = @_ ;
787     if(defined($PeerPort)){
788         $PeerAddr = $PeerAddr.":".$PeerPort;
789     }
790     my $socket;
791     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
792             Porto => "tcp",
793             Type => SOCK_STREAM,
794             Timeout => 5,
795             );
796     if(not defined $socket) {
797         return;
798     }
799 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
800     return $socket;
804 #===  FUNCTION  ================================================================
805 #         NAME:  get_ip 
806 #   PARAMETERS:  interface name (i.e. eth0)
807 #      RETURNS:  (ip address) 
808 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
809 #===============================================================================
810 sub get_ip {
811         my $ifreq= shift;
812         my $result= "";
813         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
814         my $proto= getprotobyname('ip');
816         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
817                 or die "socket: $!";
819         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
820                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
821                 my ($port, $addr) = sockaddr_in $sin;
822                 my $ip            = inet_ntoa $addr;
824                 if ($ip && length($ip) > 0) {
825                         $result = $ip;
826                 }
827         }
829         return $result;
833 sub get_local_ip_for_remote_ip {
834         my $remote_ip= shift;
835         my $result="0.0.0.0";
837         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
838                 if($remote_ip eq "127.0.0.1") {
839                         $result = "127.0.0.1";
840                 } else {
841                         my $PROC_NET_ROUTE= ('/proc/net/route');
843                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
844                                 or die "Could not open $PROC_NET_ROUTE";
846                         my @ifs = <PROC_NET_ROUTE>;
848                         close(PROC_NET_ROUTE);
850                         # Eat header line
851                         shift @ifs;
852                         chomp @ifs;
853                         foreach my $line(@ifs) {
854                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
855                                 my $destination;
856                                 my $mask;
857                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
858                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
859                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
860                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
861                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
862                                         # destination matches route, save mac and exit
863                                         $result= &get_ip($Iface);
864                                         last;
865                                 }
866                         }
867                 }
868         } else {
869                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
870         }
871         return $result;
875 sub send_msg_to_target {
876     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
877     my $error = 0;
878     my $header;
879     my $new_status;
880     my $act_status;
881     my ($sql_statement, $res);
882   
883     if( $msg_header ) {
884         $header = "'$msg_header'-";
885     } else {
886         $header = "";
887     }
889         # Patch the source ip
890         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
891                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
892                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
893         }
895     # encrypt xml msg
896     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
898     # opensocket
899     my $socket = &open_socket($address);
900     if( !$socket ) {
901         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
902         $error++;
903     }
904     
905     if( $error == 0 ) {
906         # send xml msg
907         print $socket $crypted_msg."\n";
909         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
910         #daemon_log("DEBUG: message:\n$msg", 9);
911         
912     }
914     # close socket in any case
915     if( $socket ) {
916         close $socket;
917     }
919     if( $error > 0 ) { $new_status = "down"; }
920     else { $new_status = $msg_header; }
923     # known_clients
924     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
925     $res = $known_clients_db->select_dbentry($sql_statement);
926     if( keys(%$res) > 0) {
927         $act_status = $res->{1}->{'status'};
928         if( $act_status eq "down" ) {
929             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
930             $res = $known_clients_db->del_dbentry($sql_statement);
931             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
932         } else { 
933             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
934             $res = $known_clients_db->update_dbentry($sql_statement);
935             if($new_status eq "down"){
936                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
937             } else {
938                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
939             }
940         }
941     }
943     # known_server
944     $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
945     $res = $known_server_db->select_dbentry($sql_statement);
946     if( keys(%$res) > 0 ) {
947         $act_status = $res->{1}->{'status'};
948         if( $act_status eq "down" ) {
949             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
950             $res = $known_server_db->del_dbentry($sql_statement);
951             daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
952         } 
953         else { 
954             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
955             $res = $known_server_db->update_dbentry($sql_statement);
956             if($new_status eq "down"){
957                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
958             }
959             else {
960                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
961             }
962         }
963     }
964     return $error; 
968 sub update_jobdb_status_for_send_msgs {
969     my ($answer, $error) = @_;
970     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
971         my $jobdb_id = $1;
972             
973         # sending msg faild
974         if( $error ) {
975             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
976                 my $sql_statement = "UPDATE $job_queue_tn ".
977                     "SET status='error', result='can not deliver msg, please consult log file' ".
978                     "WHERE id=$jobdb_id";
979                 my $res = $job_db->update_dbentry($sql_statement);
980             }
982         # sending msg was successful
983         } else {
984             my $sql_statement = "UPDATE $job_queue_tn ".
985                 "SET status='done' ".
986                 "WHERE id=$jobdb_id AND status='processed'";
987             my $res = $job_db->update_dbentry($sql_statement);
988         }
989     }
992 sub _start {
993     my ($kernel) = $_[KERNEL];
994     &trigger_db_loop($kernel);
995     $global_kernel = $kernel;
996         $kernel->yield('create_fai_server_db', $fai_server_tn );
997         $kernel->yield('create_fai_release_db', $fai_release_tn );
998         $kernel->sig(USR1 => "sig_handler");
999         $kernel->sig(USR2 => "create_packages_list_db");
1002 sub sig_handler {
1003         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1004         daemon_log("0 INFO got signal '$signal'", 1); 
1005         $kernel->sig_handled();
1006         return;
1010 sub msg_to_decrypt {
1011     my ($session, $heap) = @_[SESSION, HEAP];
1012     my $session_id = $session->ID;
1013     my ($msg, $msg_hash, $module);
1014     my $error = 0;
1016     # hole neue msg aus @msgs_to_decrypt
1017     my $next_msg = shift @msgs_to_decrypt;
1018     
1019     # entschlüssle sie
1021     # msg is from a new client or gosa
1022     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1023     # msg is from a gosa-si-server or gosa-si-bus
1024     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1025         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1026     }
1027     # msg is from a gosa-si-client
1028     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1029         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1030     }
1031     # an error occurred
1032     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1033         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1034         # could not understand a msg from its server the client cause a re-registering process
1035         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);
1036         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1037         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1038         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1039             my $host_name = $hit->{'hostname'};
1040             my $host_key = $hit->{'hostkey'};
1041             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1042             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1043             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1044         }
1045         $error++;
1046     }
1047     
1048     # add message to incoming_db
1049     if( $error == 0) {
1050         my $header = @{$msg_hash->{'header'}}[0];
1051         my $target = @{$msg_hash->{'target'}}[0];
1052         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1053                 primkey=>[],
1054                 headertag=>$header,
1055                                 targettag=>$target,
1056                 xmlmessage=>$msg,
1057                 timestamp=>&get_time,
1058                 module=>$module,
1059                 } );
1060         if ($res != 0) {
1061                         # TODO ist das mit $! so ok???
1062             #&daemon_log("$session_id ERROR: cannot add message to incoming.db: $!", 1); 
1063         }
1064     }
1069 sub next_task {
1070     my ($session, $heap) = @_[SESSION, HEAP];
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;
1082 sub handle_task_result {
1083     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1084     my $client_answer = $result->{'answer'};
1085     if( $client_answer =~ s/session_id=(\d+)$// ) {
1086         my $session_id = $1;
1087         if( defined $session_id ) {
1088             my $session_reference = $kernel->ID_id_to_session($session_id);
1089             if( defined $session_reference ) {
1090                 $heap = $session_reference->get_heap();
1091             }
1092         }
1094         if(exists $heap->{'client'}) {
1095             $heap->{'client'}->put($client_answer);
1096         }
1097     }
1098     $kernel->sig(CHLD => "child_reap");
1101 sub handle_task_debug {
1102     my $result = $_[ARG0];
1103     print STDERR "$result\n";
1106 sub handle_task_done {
1107     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1108     delete $heap->{task}->{$task_id};
1111 sub process_task {
1112     no strict "refs";
1113     my ($session, $heap, $input) = @_;
1114     my $session_id = $session->ID;
1115     my $error = 0;
1116     my $answer_l;
1117     my ($answer_header, @answer_target_l, $answer_source);
1118     my $client_answer = "";
1120         ##################################################
1121         # fetch first unprocessed message from incoming_db
1122     # sometimes the program is faster than sqlite, so wait until informations are present at db
1123     my $id_sql;
1124     my $id_res;
1125     my $message_id;
1126 # TODO : das hier ist sehr sehr unschön        
1127 # to be tested: speed enhancement with usleep 100000???
1128     while (1) {
1129         $id_sql = "SELECT min(id) FROM $incoming_tn WHERE (NOT headertag LIKE 'answer%')"; 
1130         $id_res = $incoming_db->exec_statement($id_sql);
1131         $message_id = @{@$id_res[0]}[0];
1132         if (defined $message_id) { last }
1133         usleep(100000);
1134     }
1136     # fetch new message from incoming_db
1137     my $sql = "SELECT * FROM $incoming_tn WHERE id=$message_id"; 
1138     my $res = $incoming_db->exec_statement($sql);
1140     # prepare all variables needed to process message
1141     my $msg = @{@$res[0]}[4];
1142     my $incoming_id = @{@$res[0]}[0];
1143     my $module = @{@$res[0]}[5];
1144     my $header =  @{@$res[0]}[2];
1145     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1147     # messages which are an answer to a still running process should not be processed here
1148     if ($header =~ /^answer_(\d+)/) {
1149         return;
1150     }
1151    
1152     # delete message from db 
1153     my $delete_sql = "DELETE FROM $incoming_tn WHERE id=$incoming_id";
1154     my $delete_res = $incoming_db->exec_statement($delete_sql);
1156     ######################
1157     # process incoming msg
1158     if( $error == 0) {
1159         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0].
1160                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1161         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1162         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1164         if ( 0 < @{$answer_l} ) {
1165             my $answer_str = join("\n", @{$answer_l});
1166             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1167                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1168             }
1169             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1170         } else {
1171             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1172         }
1174     }
1175     if( !$answer_l ) { $error++ };
1177     ########
1178     # answer
1179     if( $error == 0 ) {
1181         foreach my $answer ( @{$answer_l} ) {
1182             # check outgoing msg to xml validity
1183             my $answer_hash = &check_outgoing_xml_validity($answer);
1184             if( not defined $answer_hash ) { next; }
1185             
1186             $answer_header = @{$answer_hash->{'header'}}[0];
1187             @answer_target_l = @{$answer_hash->{'target'}};
1188             $answer_source = @{$answer_hash->{'source'}}[0];
1190             # deliver msg to all targets 
1191             foreach my $answer_target ( @answer_target_l ) {
1193                 # targets of msg are all gosa-si-clients in known_clients_db
1194                 if( $answer_target eq "*" ) {
1195                     # answer is for all clients
1196                     my $sql_statement= "SELECT * FROM known_clients";
1197                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1198                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1199                         my $host_name = $hit->{hostname};
1200                         my $host_key = $hit->{hostkey};
1201                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1202                         &update_jobdb_status_for_send_msgs($answer, $error);
1203                     }
1204                 }
1206                 # targets of msg are all gosa-si-server in known_server_db
1207                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1208                     # answer is for all server in known_server
1209                     my $sql_statement= "SELECT * FROM known_server";
1210                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1211                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1212                         my $host_name = $hit->{hostname};
1213                         my $host_key = $hit->{hostkey};
1214                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1215                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1216                         &update_jobdb_status_for_send_msgs($answer, $error);
1217                     }
1218                 }
1220                 # target of msg is GOsa
1221                                 elsif( $answer_target eq "GOSA" ) {
1222                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1223                                         my $add_on = "";
1224                     if( defined $session_id ) {
1225                         $add_on = ".session_id=$session_id";
1226                     }
1227                     # answer is for GOSA and has to returned to connected client
1228                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1229                     $client_answer = $gosa_answer.$add_on;
1230                 }
1232                 # target of msg is job queue at this host
1233                 elsif( $answer_target eq "JOBDB") {
1234                     $answer =~ /<header>(\S+)<\/header>/;   
1235                     my $header;
1236                     if( defined $1 ) { $header = $1; }
1237                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1238                     &update_jobdb_status_for_send_msgs($answer, $error);
1239                 }
1241                 # target of msg is a mac address
1242                 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 ) {
1243                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1244                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1245                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1246                     my $found_ip_flag = 0;
1247                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1248                         my $host_name = $hit->{hostname};
1249                         my $host_key = $hit->{hostkey};
1250                         $answer =~ s/$answer_target/$host_name/g;
1251                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1252                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1253                         &update_jobdb_status_for_send_msgs($answer, $error);
1254                         $found_ip_flag++ ;
1255                     }   
1256                     if( $found_ip_flag == 0) {
1257                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1258                         if( $bus_activ eq "true" ) { 
1259                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1260                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1261                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1262                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1263                                 my $bus_address = $hit->{hostname};
1264                                 my $bus_key = $hit->{hostkey};
1265                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1266                                 &update_jobdb_status_for_send_msgs($answer, $error);
1267                                 last;
1268                             }
1269                         }
1271                     }
1273                 #  answer is for one specific host   
1274                 } else {
1275                     # get encrypt_key
1276                     my $encrypt_key = &get_encrypt_key($answer_target);
1277                     if( not defined $encrypt_key ) {
1278                         # unknown target, forward msg to bus
1279                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1280                         if( $bus_activ eq "true" ) { 
1281                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1282                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1283                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1284                             my $res_length = keys( %{$query_res} );
1285                             if( $res_length == 0 ){
1286                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1287                                         "no bus found in known_server", 3);
1288                             }
1289                             else {
1290                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1291                                     my $bus_key = $hit->{hostkey};
1292                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1293                                     &update_jobdb_status_for_send_msgs($answer, $error);
1294                                 }
1295                             }
1296                         }
1297                         next;
1298                     }
1299                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1300                     &update_jobdb_status_for_send_msgs($answer, $error);
1301                 }
1302             }
1303         }
1304     }
1306     my $filter = POE::Filter::Reference->new();
1307     my %result = ( 
1308             status => "seems ok to me",
1309             answer => $client_answer,
1310             );
1312     my $output = $filter->put( [ \%result ] );
1313     print @$output;
1319 sub trigger_db_loop {
1320         my ($kernel) = @_ ;
1321         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1322         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1323         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1324     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1325         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1329 sub watch_for_done_jobs {
1330     my ($kernel,$heap) = @_[KERNEL, HEAP];
1332     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1333         " WHERE status='done'";
1334         my $res = $job_db->select_dbentry( $sql_statement );
1336     while( my ($id, $hit) = each %{$res} ) {
1337         my $jobdb_id = $hit->{id};
1338         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1339         my $res = $job_db->del_dbentry($sql_statement); 
1340     }
1342     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1346 sub watch_for_new_jobs {
1347         if($watch_for_new_jobs_in_progress == 0) {
1348                 $watch_for_new_jobs_in_progress = 1;
1349                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1351                 # check gosa job queue for jobs with executable timestamp
1352                 my $timestamp = &get_time();
1353                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1354                 my $res = $job_db->exec_statement( $sql_statement );
1356                 # Merge all new jobs that would do the same actions
1357                 my @drops;
1358                 my $hits;
1359                 foreach my $hit (reverse @{$res} ) {
1360                         my $macaddress= lc @{$hit}[8];
1361                         my $headertag= @{$hit}[5];
1362                         if(
1363                                 defined($hits->{$macaddress}) &&
1364                                 defined($hits->{$macaddress}->{$headertag}) &&
1365                                 defined($hits->{$macaddress}->{$headertag}[0])
1366                         ) {
1367                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1368                         }
1369                         $hits->{$macaddress}->{$headertag}= $hit;
1370                 }
1372                 # Delete new jobs with a matching job in state 'processing'
1373                 foreach my $macaddress (keys %{$hits}) {
1374                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1375                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1376                                 if(defined($jobdb_id)) {
1377                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1378                                         my $res = $job_db->exec_statement( $sql_statement );
1379                                         foreach my $hit (@{$res}) {
1380                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1381                                         }
1382                                 } else {
1383                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1384                                 }
1385                         }
1386                 }
1388                 # Commit deletion
1389                 $job_db->exec_statementlist(\@drops);
1391                 # Look for new jobs that could be executed
1392                 foreach my $macaddress (keys %{$hits}) {
1394                         # Look if there is an executing job
1395                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1396                         my $res = $job_db->exec_statement( $sql_statement );
1398                         # Skip new jobs for host if there is a processing job
1399                         if(defined($res) and defined @{$res}[0]) {
1400                                 next;
1401                         }
1403                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1404                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1405                                 if(defined($jobdb_id)) {
1406                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1408                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1409                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1410                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1412                                         # expect macaddress is unique!!!!!!
1413                                         my $target = $res_hash->{1}->{hostname};
1415                                         # change header
1416                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1418                                         # add sqlite_id
1419                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1421                                         $job_msg =~ /<header>(\S+)<\/header>/;
1422                                         my $header = $1 ;
1423                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1425                                         # update status in job queue to 'processing'
1426                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1427                                         my $res = $job_db->update_dbentry($sql_statement);
1428 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1430                                         # We don't want parallel processing
1431                                         last;
1432                                 }
1433                         }
1434                 }
1436                 $watch_for_new_jobs_in_progress = 0;
1437                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1438         }
1442 sub watch_for_new_messages {
1443     my ($kernel,$heap) = @_[KERNEL, HEAP];
1444     my @coll_user_msg;   # collection list of outgoing messages
1445     
1446     # check messaging_db for new incoming messages with executable timestamp
1447     my $timestamp = &get_time();
1448     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1449     my $res = $messaging_db->exec_statement( $sql_statement );
1450         foreach my $hit (@{$res}) {
1452         # create outgoing messages
1453         my $message_to = @{$hit}[3];
1454         # translate message_to to plain login name
1455         my @message_to_l = split(/,/, $message_to);  
1456                 my %receiver_h; 
1457                 foreach my $receiver (@message_to_l) {
1458                         if ($receiver =~ /^u_([\s\S]*)$/) {
1459                                 $receiver_h{$1} = 0;
1460                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1461                                 my $group_name = $1;
1462                                 # fetch all group members from ldap and add them to receiver hash
1463                                 my $ldap_handle = &get_ldap_handle();
1464                                 if (defined $ldap_handle) {
1465                                                 my $mesg = $ldap_handle->search(
1466                                                                                 base => $ldap_base,
1467                                                                                 scope => 'sub',
1468                                                                                 attrs => ['memberUid'],
1469                                                                                 filter => "cn=$group_name",
1470                                                                                 );
1471                                                 if ($mesg->count) {
1472                                                                 my @entries = $mesg->entries;
1473                                                                 foreach my $entry (@entries) {
1474                                                                                 my @receivers= $entry->get_value("memberUid");
1475                                                                                 foreach my $receiver (@receivers) { 
1476                                                                                                 $receiver_h{$1} = 0;
1477                                                                                 }
1478                                                                 }
1479                                                 } 
1480                                                 # translating errors ?
1481                                                 if ($mesg->code) {
1482                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1483                                                 }
1484                                 # ldap handle error ?           
1485                                 } else {
1486                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1487                                 }
1488                         } else {
1489                                 my $sbjct = &encode_base64(@{$hit}[1]);
1490                                 my $msg = &encode_base64(@{$hit}[7]);
1491                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1492                         }
1493                 }
1494                 my @receiver_l = keys(%receiver_h);
1496         my $message_id = @{$hit}[0];
1498         #add each outgoing msg to messaging_db
1499         my $receiver;
1500         foreach $receiver (@receiver_l) {
1501             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1502                 "VALUES ('".
1503                 $message_id."', '".    # id
1504                 @{$hit}[1]."', '".     # subject
1505                 @{$hit}[2]."', '".     # message_from
1506                 $receiver."', '".      # message_to
1507                 "none"."', '".         # flag
1508                 "out"."', '".          # direction
1509                 @{$hit}[6]."', '".     # delivery_time
1510                 @{$hit}[7]."', '".     # message
1511                 $timestamp."'".     # timestamp
1512                 ")";
1513             &daemon_log("M DEBUG: $sql_statement", 1);
1514             my $res = $messaging_db->exec_statement($sql_statement);
1515             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1516         }
1518         # set incoming message to flag d=deliverd
1519         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1520         &daemon_log("M DEBUG: $sql_statement", 7);
1521         $res = $messaging_db->update_dbentry($sql_statement);
1522         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1523     }
1525     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1526     return;
1529 sub watch_for_delivery_messages {
1530     my ($kernel, $heap) = @_[KERNEL, HEAP];
1532     # select outgoing messages
1533     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1534     #&daemon_log("0 DEBUG: $sql", 7);
1535     my $res = $messaging_db->exec_statement( $sql_statement );
1536     
1537     # build out msg for each    usr
1538     foreach my $hit (@{$res}) {
1539         my $receiver = @{$hit}[3];
1540         my $msg_id = @{$hit}[0];
1541         my $subject = @{$hit}[1];
1542         my $message = @{$hit}[7];
1544         # resolve usr -> host where usr is logged in
1545         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1546         #&daemon_log("0 DEBUG: $sql", 7);
1547         my $res = $login_users_db->exec_statement($sql);
1549         # reciver is logged in nowhere
1550         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1552                 my $send_succeed = 0;
1553                 foreach my $hit (@$res) {
1554                                 my $receiver_host = @$hit[0];
1555                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1557                                 # fetch key to encrypt msg propperly for usr/host
1558                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1559                                 &daemon_log("0 DEBUG: $sql", 7);
1560                                 my $res = $known_clients_db->exec_statement($sql);
1562                                 # host is already down
1563                                 if (not ref(@$res[0]) eq "ARRAY") { next; }
1565                                 # host is on
1566                                 my $receiver_key = @{@{$res}[0]}[2];
1567                                 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1568                                 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1569                                 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1570                                 if ($error == 0 ) {
1571                                         $send_succeed++ ;
1572                                 }
1573                 }
1575                 if ($send_succeed) {
1576                                 # set outgoing msg at db to deliverd
1577                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1578                                 &daemon_log("0 DEBUG: $sql", 7);
1579                                 my $res = $messaging_db->exec_statement($sql); 
1580                 }
1581         }
1583     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1584     return;
1588 sub watch_for_done_messages {
1589     my ($kernel,$heap) = @_[KERNEL, HEAP];
1591     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1592     #&daemon_log("0 DEBUG: $sql", 7);
1593     my $res = $messaging_db->exec_statement($sql); 
1595     foreach my $hit (@{$res}) {
1596         my $msg_id = @{$hit}[0];
1598         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1599         #&daemon_log("0 DEBUG: $sql", 7); 
1600         my $res = $messaging_db->exec_statement($sql);
1602         # not all usr msgs have been seen till now
1603         if ( ref(@$res[0]) eq "ARRAY") { next; }
1604         
1605         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1606         #&daemon_log("0 DEBUG: $sql", 7);
1607         $res = $messaging_db->exec_statement($sql);
1608     
1609     }
1611     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1612     return;
1616 sub get_ldap_handle {
1617         my ($session_id) = @_;
1618         my $heap;
1619         my $ldap_handle;
1621         if (not defined $session_id ) { $session_id = 0 };
1622         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1624         if ($session_id == 0) {
1625                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1626                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1627                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1629         } else {
1630                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1631                 if( defined $session_reference ) {
1632                         $heap = $session_reference->get_heap();
1633                 }
1635                 if (not defined $heap) {
1636                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1637                         return;
1638                 }
1640                 # TODO: This "if" is nonsense, because it doesn't prove that the
1641                 #       used handle is still valid - or if we've to reconnect...
1642                 #if (not exists $heap->{ldap_handle}) {
1643                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1644                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1645                         $heap->{ldap_handle} = $ldap_handle;
1646                 #}
1647         }
1648         return $ldap_handle;
1652 sub change_fai_state {
1653     my ($st, $targets, $session_id) = @_;
1654     $session_id = 0 if not defined $session_id;
1655     # Set FAI state to localboot
1656     my %mapActions= (
1657         reboot    => '',
1658         update    => 'softupdate',
1659         localboot => 'localboot',
1660         reinstall => 'install',
1661         rescan    => '',
1662         wake      => '',
1663         memcheck  => 'memcheck',
1664         sysinfo   => 'sysinfo',
1665         install   => 'install',
1666     );
1668     # Return if this is unknown
1669     if (!exists $mapActions{ $st }){
1670         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1671       return;
1672     }
1674     my $state= $mapActions{ $st };
1676     my $ldap_handle = &get_ldap_handle($session_id);
1677     if( defined($ldap_handle) ) {
1679       # Build search filter for hosts
1680         my $search= "(&(objectClass=GOhard)";
1681         foreach (@{$targets}){
1682             $search.= "(macAddress=$_)";
1683         }
1684         $search.= ")";
1686       # If there's any host inside of the search string, procress them
1687         if (!($search =~ /macAddress/)){
1688             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1689             return;
1690         }
1692       # Perform search for Unit Tag
1693       my $mesg = $ldap_handle->search(
1694           base   => $ldap_base,
1695           scope  => 'sub',
1696           attrs  => ['dn', 'FAIstate', 'objectClass'],
1697           filter => "$search"
1698           );
1700           if ($mesg->count) {
1701                   my @entries = $mesg->entries;
1702                   foreach my $entry (@entries) {
1703                           # Only modify entry if it is not set to '$state'
1704                           if ($entry->get_value("FAIstate") ne "$state"){
1705                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1706                                   my $result;
1707                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1708                                   if (exists $tmp{'FAIobject'}){
1709                                           if ($state eq ''){
1710                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1711                                                           delete => [ FAIstate => [] ] ]);
1712                                           } else {
1713                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1714                                                           replace => [ FAIstate => $state ] ]);
1715                                           }
1716                                   } elsif ($state ne ''){
1717                                           $result= $ldap_handle->modify($entry->dn, changes => [
1718                                                   add     => [ objectClass => 'FAIobject' ],
1719                                                   add     => [ FAIstate => $state ] ]);
1720                                   }
1722                                   # Errors?
1723                                   if ($result->code){
1724                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1725                                   }
1726                           } else {
1727                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1728                           }  
1729                   }
1730           }
1731     # if no ldap handle defined
1732     } else {
1733         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1734     }
1739 sub change_goto_state {
1740     my ($st, $targets, $session_id) = @_;
1741     $session_id = 0  if not defined $session_id;
1743     # Switch on or off?
1744     my $state= $st eq 'active' ? 'active': 'locked';
1746     my $ldap_handle = &get_ldap_handle($session_id);
1747     if( defined($ldap_handle) ) {
1749       # Build search filter for hosts
1750       my $search= "(&(objectClass=GOhard)";
1751       foreach (@{$targets}){
1752         $search.= "(macAddress=$_)";
1753       }
1754       $search.= ")";
1756       # If there's any host inside of the search string, procress them
1757       if (!($search =~ /macAddress/)){
1758         return;
1759       }
1761       # Perform search for Unit Tag
1762       my $mesg = $ldap_handle->search(
1763           base   => $ldap_base,
1764           scope  => 'sub',
1765           attrs  => ['dn', 'gotoMode'],
1766           filter => "$search"
1767           );
1769       if ($mesg->count) {
1770         my @entries = $mesg->entries;
1771         foreach my $entry (@entries) {
1773           # Only modify entry if it is not set to '$state'
1774           if ($entry->get_value("gotoMode") ne $state){
1776             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1777             my $result;
1778             $result= $ldap_handle->modify($entry->dn, changes => [
1779                                                 replace => [ gotoMode => $state ] ]);
1781             # Errors?
1782             if ($result->code){
1783               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1784             }
1786           }
1787         }
1788       }
1790     }
1794 sub run_create_fai_server_db {
1795     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1796     my $session_id = $session->ID;
1797     my $task = POE::Wheel::Run->new(
1798             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1799             StdoutEvent  => "session_run_result",
1800             StderrEvent  => "session_run_debug",
1801             CloseEvent   => "session_run_done",
1802             );
1804     $heap->{task}->{ $task->ID } = $task;
1805     return;
1809 sub create_fai_server_db {
1810     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1811         my $result;
1813         if (not defined $session_id) { $session_id = 0; }
1814     my $ldap_handle = &get_ldap_handle();
1815         if(defined($ldap_handle)) {
1816                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1817                 my $mesg= $ldap_handle->search(
1818                         base   => $ldap_base,
1819                         scope  => 'sub',
1820                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1821                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1822                 );
1823                 if($mesg->{'resultCode'} == 0 &&
1824                    $mesg->count != 0) {
1825                    foreach my $entry (@{$mesg->{entries}}) {
1826                            if($entry->exists('FAIrepository')) {
1827                                    # Add an entry for each Repository configured for server
1828                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1829                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1830                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1831                                                    $result= $fai_server_db->add_dbentry( { 
1832                                                                    table => $table_name,
1833                                                                    primkey => ['server', 'release', 'tag'],
1834                                                                    server => $tmp_url,
1835                                                                    release => $tmp_release,
1836                                                                    sections => $tmp_sections,
1837                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1838                                                            } );
1839                                            }
1840                                    }
1841                            }
1842                    }
1843                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1845                 # TODO: Find a way to post the 'create_packages_list_db' event
1846                 if(not defined($dont_create_packages_list)) {
1847                         &create_packages_list_db(undef, undef, $session_id);
1848                 }
1849         }       
1850     
1851     $ldap_handle->disconnect;
1852         return $result;
1856 sub run_create_fai_release_db {
1857     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1858         my $session_id = $session->ID;
1859     my $task = POE::Wheel::Run->new(
1860             Program => sub { &create_fai_release_db($table_name, $session_id) },
1861             StdoutEvent  => "session_run_result",
1862             StderrEvent  => "session_run_debug",
1863             CloseEvent   => "session_run_done",
1864             );
1866     $heap->{task}->{ $task->ID } = $task;
1867     return;
1871 sub create_fai_release_db {
1872         my ($table_name, $session_id) = @_;
1873         my $result;
1875     # used for logging
1876     if (not defined $session_id) { $session_id = 0; }
1878     my $ldap_handle = &get_ldap_handle();
1879         if(defined($ldap_handle)) {
1880                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1881                 my $mesg= $ldap_handle->search(
1882                         base   => $ldap_base,
1883                         scope  => 'sub',
1884                         attrs  => [],
1885                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1886                 );
1887                 if($mesg->{'resultCode'} == 0 &&
1888                         $mesg->count != 0) {
1889                         # Walk through all possible FAI container ou's
1890                         my @sql_list;
1891                         my $timestamp= &get_time();
1892                         foreach my $ou (@{$mesg->{entries}}) {
1893                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1894                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1895                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1896                                         if(@tmp_array) {
1897                                                 foreach my $entry (@tmp_array) {
1898                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1899                                                                 my $sql= 
1900                                                                 "INSERT INTO $table_name "
1901                                                                 ."(timestamp, release, class, type, state) VALUES ("
1902                                                                 .$timestamp.","
1903                                                                 ."'".$entry->{'release'}."',"
1904                                                                 ."'".$entry->{'class'}."',"
1905                                                                 ."'".$entry->{'type'}."',"
1906                                                                 ."'".$entry->{'state'}."')";
1907                                                                 push @sql_list, $sql;
1908                                                         }
1909                                                 }
1910                                         }
1911                                 }
1912                         }
1914                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1915                         if(@sql_list) {
1916                                 unshift @sql_list, "VACUUM";
1917                                 unshift @sql_list, "DELETE FROM $table_name";
1918                                 $fai_release_db->exec_statementlist(\@sql_list);
1919                         }
1920                         daemon_log("$session_id DEBUG: Done with inserting",7);
1921                 }
1922                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1923         }
1924     $ldap_handle->disconnect;
1925         return $result;
1928 sub get_fai_types {
1929         my $tmp_classes = shift || return undef;
1930         my @result;
1932         foreach my $type(keys %{$tmp_classes}) {
1933                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1934                         my $entry = {
1935                                 type => $type,
1936                                 state => $tmp_classes->{$type}[0],
1937                         };
1938                         push @result, $entry;
1939                 }
1940         }
1942         return @result;
1945 sub get_fai_state {
1946         my $result = "";
1947         my $tmp_classes = shift || return $result;
1949         foreach my $type(keys %{$tmp_classes}) {
1950                 if(defined($tmp_classes->{$type}[0])) {
1951                         $result = $tmp_classes->{$type}[0];
1952                         
1953                 # State is equal for all types in class
1954                         last;
1955                 }
1956         }
1958         return $result;
1961 sub resolve_fai_classes {
1962         my ($fai_base, $ldap_handle, $session_id) = @_;
1963         if (not defined $session_id) { $session_id = 0; }
1964         my $result;
1965         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1966         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1967         my $fai_classes;
1969         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
1970         my $mesg= $ldap_handle->search(
1971                 base   => $fai_base,
1972                 scope  => 'sub',
1973                 attrs  => ['cn','objectClass','FAIstate'],
1974                 filter => $fai_filter,
1975         );
1976         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
1978         if($mesg->{'resultCode'} == 0 &&
1979                 $mesg->count != 0) {
1980                 foreach my $entry (@{$mesg->{entries}}) {
1981                         if($entry->exists('cn')) {
1982                                 my $tmp_dn= $entry->dn();
1984                                 # Skip classname and ou dn parts for class
1985                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1987                                 # Skip classes without releases
1988                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1989                                         next;
1990                                 }
1992                                 my $tmp_cn= $entry->get_value('cn');
1993                                 my $tmp_state= $entry->get_value('FAIstate');
1995                                 my $tmp_type;
1996                                 # Get FAI type
1997                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1998                                         if(grep $_ eq $oclass, @possible_fai_classes) {
1999                                                 $tmp_type= $oclass;
2000                                                 last;
2001                                         }
2002                                 }
2004                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2005                                         # A Subrelease
2006                                         my @sub_releases = split(/,/, $tmp_release);
2008                                         # Walk through subreleases and build hash tree
2009                                         my $hash;
2010                                         while(my $tmp_sub_release = pop @sub_releases) {
2011                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2012                                         }
2013                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2014                                 } else {
2015                                         # A branch, no subrelease
2016                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2017                                 }
2018                         } elsif (!$entry->exists('cn')) {
2019                                 my $tmp_dn= $entry->dn();
2020                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2022                                 # Skip classes without releases
2023                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2024                                         next;
2025                                 }
2027                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2028                                         # A Subrelease
2029                                         my @sub_releases= split(/,/, $tmp_release);
2031                                         # Walk through subreleases and build hash tree
2032                                         my $hash;
2033                                         while(my $tmp_sub_release = pop @sub_releases) {
2034                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2035                                         }
2036                                         # Remove the last two characters
2037                                         chop($hash);
2038                                         chop($hash);
2040                                         eval('$fai_classes->'.$hash.'= {}');
2041                                 } else {
2042                                         # A branch, no subrelease
2043                                         if(!exists($fai_classes->{$tmp_release})) {
2044                                                 $fai_classes->{$tmp_release} = {};
2045                                         }
2046                                 }
2047                         }
2048                 }
2050                 # The hash is complete, now we can honor the copy-on-write based missing entries
2051                 foreach my $release (keys %$fai_classes) {
2052                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2053                 }
2054         }
2055         return $result;
2058 sub apply_fai_inheritance {
2059        my $fai_classes = shift || return {};
2060        my $tmp_classes;
2062        # Get the classes from the branch
2063        foreach my $class (keys %{$fai_classes}) {
2064                # Skip subreleases
2065                if($class =~ /^ou=.*$/) {
2066                        next;
2067                } else {
2068                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2069                }
2070        }
2072        # Apply to each subrelease
2073        foreach my $subrelease (keys %{$fai_classes}) {
2074                if($subrelease =~ /ou=/) {
2075                        foreach my $tmp_class (keys %{$tmp_classes}) {
2076                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2077                                        $fai_classes->{$subrelease}->{$tmp_class} =
2078                                        deep_copy($tmp_classes->{$tmp_class});
2079                                } else {
2080                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2081                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2082                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2083                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2084                                                }
2085                                        }
2086                                }
2087                        }
2088                }
2089        }
2091        # Find subreleases in deeper levels
2092        foreach my $subrelease (keys %{$fai_classes}) {
2093                if($subrelease =~ /ou=/) {
2094                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2095                                if($subsubrelease =~ /ou=/) {
2096                                        apply_fai_inheritance($fai_classes->{$subrelease});
2097                                }
2098                        }
2099                }
2100        }
2102        return $fai_classes;
2105 sub get_fai_release_entries {
2106         my $tmp_classes = shift || return;
2107         my $parent = shift || "";
2108         my @result = shift || ();
2110         foreach my $entry (keys %{$tmp_classes}) {
2111                 if(defined($entry)) {
2112                         if($entry =~ /^ou=.*$/) {
2113                                 my $release_name = $entry;
2114                                 $release_name =~ s/ou=//g;
2115                                 if(length($parent)>0) {
2116                                         $release_name = $parent."/".$release_name;
2117                                 }
2118                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2119                                 foreach my $bufentry(@bufentries) {
2120                                         push @result, $bufentry;
2121                                 }
2122                         } else {
2123                                 my @types = get_fai_types($tmp_classes->{$entry});
2124                                 foreach my $type (@types) {
2125                                         push @result, 
2126                                         {
2127                                                 'class' => $entry,
2128                                                 'type' => $type->{'type'},
2129                                                 'release' => $parent,
2130                                                 'state' => $type->{'state'},
2131                                         };
2132                                 }
2133                         }
2134                 }
2135         }
2137         return @result;
2140 sub deep_copy {
2141         my $this = shift;
2142         if (not ref $this) {
2143                 $this;
2144         } elsif (ref $this eq "ARRAY") {
2145                 [map deep_copy($_), @$this];
2146         } elsif (ref $this eq "HASH") {
2147                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2148         } else { die "what type is $_?" }
2152 sub session_run_result {
2153     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2154     $kernel->sig(CHLD => "child_reap");
2157 sub session_run_debug {
2158     my $result = $_[ARG0];
2159     print STDERR "$result\n";
2162 sub session_run_done {
2163     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2164     delete $heap->{task}->{$task_id};
2168 sub create_sources_list {
2169         my $session_id = shift;
2170         my $ldap_handle = &main::get_ldap_handle;
2171         my $result="/tmp/gosa_si_tmp_sources_list";
2173         # Remove old file
2174         if(stat($result)) {
2175                 unlink($result);
2176                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2177         }
2179         my $fh;
2180         open($fh, ">$result");
2181         if (not defined $fh) {
2182                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2183                 return undef;
2184         }
2185         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2186                 my $mesg=$ldap_handle->search(
2187                         base    => $main::ldap_server_dn,
2188                         scope   => 'base',
2189                         attrs   => 'FAIrepository',
2190                         filter  => 'objectClass=FAIrepositoryServer'
2191                 );
2192                 if($mesg->count) {
2193                         foreach my $entry(@{$mesg->{'entries'}}) {
2194                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2195                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2196                                         my $line = "deb $server $release";
2197                                         $sections =~ s/,/ /g;
2198                                         $line.= " $sections";
2199                                         print $fh $line."\n";
2200                                 }
2201                         }
2202                 }
2203         } else {
2204                 if (defined $main::ldap_server_dn){
2205                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2206                 } else {
2207                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2208                 }
2209         }
2210         close($fh);
2212         return $result;
2216 sub run_create_packages_list_db {
2217     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2218         my $session_id = $session->ID;
2220         my $task = POE::Wheel::Run->new(
2221                                         Priority => +20,
2222                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2223                                         StdoutEvent  => "session_run_result",
2224                                         StderrEvent  => "session_run_debug",
2225                                         CloseEvent   => "session_run_done",
2226                                         );
2227         $heap->{task}->{ $task->ID } = $task;
2231 sub create_packages_list_db {
2232         my ($ldap_handle, $sources_file, $session_id) = @_;
2233         
2234         # it should not be possible to trigger a recreation of packages_list_db
2235         # while packages_list_db is under construction, so set flag packages_list_under_construction
2236         # which is tested befor recreation can be started
2237         if (-r $packages_list_under_construction) {
2238                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2239                 return;
2240         } else {
2241                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2242                 # set packages_list_under_construction to true
2243                 system("touch $packages_list_under_construction");
2244                 @packages_list_statements=();
2245         }
2247         if (not defined $session_id) { $session_id = 0; }
2248         if (not defined $ldap_handle) { 
2249                 $ldap_handle= &get_ldap_handle();
2251                 if (not defined $ldap_handle) {
2252                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2253                         unlink($packages_list_under_construction);
2254                         return;
2255                 }
2256         }
2257         if (not defined $sources_file) { 
2258                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2259                 $sources_file = &create_sources_list($session_id);
2260         }
2262         if (not defined $sources_file) {
2263                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2264                 unlink($packages_list_under_construction);
2265                 return;
2266         }
2268         my $line;
2270         open(CONFIG, "<$sources_file") or do {
2271                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2272                 unlink($packages_list_under_construction);
2273                 return;
2274         };
2276         # Read lines
2277         while ($line = <CONFIG>){
2278                 # Unify
2279                 chop($line);
2280                 $line =~ s/^\s+//;
2281                 $line =~ s/^\s+/ /;
2283                 # Strip comments
2284                 $line =~ s/#.*$//g;
2286                 # Skip empty lines
2287                 if ($line =~ /^\s*$/){
2288                         next;
2289                 }
2291                 # Interpret deb line
2292                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2293                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2294                         my $section;
2295                         foreach $section (split(' ', $sections)){
2296                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2297                         }
2298                 }
2299         }
2301         close (CONFIG);
2303         find(\&cleanup_and_extract, keys( %repo_dirs ));
2304         &main::strip_packages_list_statements();
2305         unshift @packages_list_statements, "VACUUM";
2306         $packages_list_db->exec_statementlist(\@packages_list_statements);
2307         unlink($packages_list_under_construction);
2308         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2309         return;
2312 # This function should do some intensive task to minimize the db-traffic
2313 sub strip_packages_list_statements {
2314     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2315         my @new_statement_list=();
2316         my $hash;
2317         my $insert_hash;
2318         my $update_hash;
2319         my $delete_hash;
2320         my $local_timestamp=get_time();
2322         foreach my $existing_entry (@existing_entries) {
2323                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2324         }
2326         foreach my $statement (@packages_list_statements) {
2327                 if($statement =~ /^INSERT/i) {
2328                         # Assign the values from the insert statement
2329                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2330                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2331                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2332                                 # If section or description has changed, update the DB
2333                                 if( 
2334                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2335                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2336                                 ) {
2337                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2338                                 }
2339                         } else {
2340                                 # Insert a non-existing entry to db
2341                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2342                         }
2343                 } elsif ($statement =~ /^UPDATE/i) {
2344                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2345                         /^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;
2346                         foreach my $distribution (keys %{$hash}) {
2347                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2348                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2349                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2350                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2351                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2352                                                 my $section;
2353                                                 my $description;
2354                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2355                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2356                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2357                                                 }
2358                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2359                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2360                                                 }
2361                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2362                                         }
2363                                 }
2364                         }
2365                 }
2366         }
2368         # TODO: Check for orphaned entries
2370         # unroll the insert_hash
2371         foreach my $distribution (keys %{$insert_hash}) {
2372                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2373                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2374                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2375                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2376                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2377                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2378                                 ."'$local_timestamp')";
2379                         }
2380                 }
2381         }
2383         # unroll the update hash
2384         foreach my $distribution (keys %{$update_hash}) {
2385                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2386                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2387                                 my $set = "";
2388                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2389                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2390                                 }
2391                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2392                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2393                                 }
2394                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2395                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2396                                 }
2397                                 if(defined($set) and length($set) > 0) {
2398                                         $set .= "timestamp = '$local_timestamp'";
2399                                 } else {
2400                                         next;
2401                                 }
2402                                 push @new_statement_list, 
2403                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2404                                         ." distribution = '$distribution'"
2405                                         ." AND package = '$package'"
2406                                         ." AND version = '$version'";
2407                         }
2408                 }
2409         }
2411         @packages_list_statements = @new_statement_list;
2415 sub parse_package_info {
2416     my ($baseurl, $dist, $section, $session_id)= @_;
2417     my ($package);
2418     if (not defined $session_id) { $session_id = 0; }
2419     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2420     $repo_dirs{ "${repo_path}/pool" } = 1;
2422     foreach $package ("Packages.gz"){
2423         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2424         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2425         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2426     }
2427     
2431 sub get_package {
2432     my ($url, $dest, $session_id)= @_;
2433     if (not defined $session_id) { $session_id = 0; }
2435     my $tpath = dirname($dest);
2436     -d "$tpath" || mkpath "$tpath";
2438     # This is ugly, but I've no time to take a look at "how it works in perl"
2439     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2440         system("gunzip -cd '$dest' > '$dest.in'");
2441         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2442         unlink($dest);
2443         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2444     } else {
2445         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2446     }
2447     return 0;
2451 sub parse_package {
2452     my ($path, $dist, $srv_path, $session_id)= @_;
2453     if (not defined $session_id) { $session_id = 0;}
2454     my ($package, $version, $section, $description);
2455     my $PACKAGES;
2456     my $timestamp = &get_time();
2458     if(not stat("$path.in")) {
2459         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2460         return;
2461     }
2463     open($PACKAGES, "<$path.in");
2464     if(not defined($PACKAGES)) {
2465         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2466         return;
2467     }
2469     # Read lines
2470     while (<$PACKAGES>){
2471         my $line = $_;
2472         # Unify
2473         chop($line);
2475         # Use empty lines as a trigger
2476         if ($line =~ /^\s*$/){
2477             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2478             push(@packages_list_statements, $sql);
2479             $package = "none";
2480             $version = "none";
2481             $section = "none";
2482             $description = "none"; 
2483             next;
2484         }
2486         # Trigger for package name
2487         if ($line =~ /^Package:\s/){
2488             ($package)= ($line =~ /^Package: (.*)$/);
2489             next;
2490         }
2492         # Trigger for version
2493         if ($line =~ /^Version:\s/){
2494             ($version)= ($line =~ /^Version: (.*)$/);
2495             next;
2496         }
2498         # Trigger for description
2499         if ($line =~ /^Description:\s/){
2500             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2501             next;
2502         }
2504         # Trigger for section
2505         if ($line =~ /^Section:\s/){
2506             ($section)= ($line =~ /^Section: (.*)$/);
2507             next;
2508         }
2510         # Trigger for filename
2511         if ($line =~ /^Filename:\s/){
2512             my ($filename) = ($line =~ /^Filename: (.*)$/);
2513             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2514             next;
2515         }
2516     }
2518     close( $PACKAGES );
2519     unlink( "$path.in" );
2520     &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1); 
2524 sub store_fileinfo {
2525     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2527     my %fileinfo = (
2528         'package' => $package,
2529         'dist' => $dist,
2530         'version' => $vers,
2531     );
2533     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2537 sub cleanup_and_extract {
2538     my $fileinfo = $repo_files{ $File::Find::name };
2540     if( defined $fileinfo ) {
2542         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2543         my $sql;
2544         my $package = $fileinfo->{ 'package' };
2545         my $newver = $fileinfo->{ 'version' };
2547         mkpath($dir);
2548         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2550                 if( -f "$dir/DEBIAN/templates" ) {
2552                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2554                         my $tmpl= "";
2555                         {
2556                                 local $/=undef;
2557                                 open FILE, "$dir/DEBIAN/templates";
2558                                 $tmpl = &encode_base64(<FILE>);
2559                                 close FILE;
2560                         }
2561                         rmtree("$dir/DEBIAN/templates");
2563                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2564                 push @packages_list_statements, $sql;
2565                 }
2566     }
2568     return;
2572 #==== MAIN = main ==============================================================
2573 #  parse commandline options
2574 Getopt::Long::Configure( "bundling" );
2575 GetOptions("h|help" => \&usage,
2576         "c|config=s" => \$cfg_file,
2577         "f|foreground" => \$foreground,
2578         "v|verbose+" => \$verbose,
2579         "no-bus+" => \$no_bus,
2580         "no-arp+" => \$no_arp,
2581            );
2583 #  read and set config parameters
2584 &check_cmdline_param ;
2585 &read_configfile;
2586 &check_pid;
2588 $SIG{CHLD} = 'IGNORE';
2590 # forward error messages to logfile
2591 if( ! $foreground ) {
2592   open( STDIN,  '+>/dev/null' );
2593   open( STDOUT, '+>&STDIN'    );
2594   open( STDERR, '+>&STDIN'    );
2597 # Just fork, if we are not in foreground mode
2598 if( ! $foreground ) { 
2599     chdir '/'                 or die "Can't chdir to /: $!";
2600     $pid = fork;
2601     setsid                    or die "Can't start a new session: $!";
2602     umask 0;
2603 } else { 
2604     $pid = $$; 
2607 # Do something useful - put our PID into the pid_file
2608 if( 0 != $pid ) {
2609     open( LOCK_FILE, ">$pid_file" );
2610     print LOCK_FILE "$pid\n";
2611     close( LOCK_FILE );
2612     if( !$foreground ) { 
2613         exit( 0 ) 
2614     };
2617 # parse head url and revision from svn
2618 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2619 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2620 $server_headURL = defined $1 ? $1 : 'unknown' ;
2621 $server_revision = defined $2 ? $2 : 'unknown' ;
2622 if ($server_headURL =~ /\/tag\// || 
2623         $server_headURL =~ /\/branches\// ) {
2624     $server_status = "stable"; 
2625 } else {
2626     $server_status = "developmental" ;
2630 daemon_log(" ", 1);
2631 daemon_log("$0 started!", 1);
2632 daemon_log("status: $server_status", 1);
2633 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
2635 if ($no_bus > 0) {
2636     $bus_activ = "false"
2639 # connect to incoming_db
2640 unlink($incoming_file_name);
2641 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2642 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2644 # connect to gosa-si job queue
2645 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2646 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2648 # connect to known_clients_db
2649 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2650 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2652 # connect to known_server_db
2653 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2654 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2656 # connect to login_usr_db
2657 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2658 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2660 # connect to fai_server_db and fai_release_db
2661 unlink($fai_server_file_name);
2662 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2663 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2665 unlink($fai_release_file_name);
2666 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2667 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2669 # connect to packages_list_db
2670 #unlink($packages_list_file_name);
2671 unlink($packages_list_under_construction);
2672 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2673 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2675 # connect to messaging_db
2676 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2677 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2680 # create xml object used for en/decrypting
2681 $xml = new XML::Simple();
2683 # create socket for incoming xml messages
2685 POE::Component::Server::TCP->new(
2686         Port => $server_port,
2687         ClientInput => sub {
2688         my ($kernel, $input) = @_[KERNEL, ARG0];
2689         push(@tasks, $input);
2690         push(@msgs_to_decrypt, $input);
2691         $kernel->yield("msg_to_decrypt");
2692         $kernel->yield("next_task");
2693         },
2694     InlineStates => {
2695         next_task => \&next_task,
2696         msg_to_decrypt => \&msg_to_decrypt,
2697         task_result => \&handle_task_result,
2698         task_done   => \&handle_task_done,
2699         task_debug  => \&handle_task_debug,
2700         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2701     }
2702 );
2704 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2706 # create session for repeatedly checking the job queue for jobs
2707 POE::Session->create(
2708         inline_states => {
2709                 _start => \&_start,
2710                 sig_handler => \&sig_handler,
2711         watch_for_new_messages => \&watch_for_new_messages,
2712         watch_for_delivery_messages => \&watch_for_delivery_messages,
2713         watch_for_done_messages => \&watch_for_done_messages,
2714                 watch_for_new_jobs => \&watch_for_new_jobs,
2715         watch_for_done_jobs => \&watch_for_done_jobs,
2716         create_packages_list_db => \&run_create_packages_list_db,
2717         create_fai_server_db => \&run_create_fai_server_db,
2718         create_fai_release_db => \&run_create_fai_release_db,
2719         session_run_result => \&session_run_result,
2720         session_run_debug => \&session_run_debug,
2721         session_run_done => \&session_run_done,
2722         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2723         }
2724 );
2727 # import all modules
2728 &import_modules;
2730 # check wether all modules are gosa-si valid passwd check
2732 POE::Kernel->run();
2733 exit;