Code

f32876de249626da6504a833e860ad5998bb644e
[gosa.git] / gosa-si / gosa-si-server
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 use strict;
25 use warnings;
26 use Getopt::Long;
27 use Config::IniFiles;
28 use POSIX;
30 use Fcntl;
31 use IO::Socket::INET;
32 use IO::Handle;
33 use IO::Select;
34 use Symbol qw(qualify_to_ref);
35 use Crypt::Rijndael;
36 use MIME::Base64;
37 use Digest::MD5  qw(md5 md5_hex md5_base64);
38 use XML::Simple;
39 use Data::Dumper;
40 use Sys::Syslog qw( :DEFAULT setlogsock);
41 use Cwd;
42 use File::Spec;
43 use File::Basename;
44 use File::Find;
45 use File::Copy;
46 use File::Path;
47 use GOSA::DBsqlite;
48 use GOSA::GosaSupportDaemon;
49 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
50 use Net::LDAP;
51 use Net::LDAP::Util qw(:escape);
53 my $modules_path = "/usr/lib/gosa-si/modules";
54 use lib "/usr/lib/gosa-si/modules";
56 # TODO es gibt eine globale funktion get_ldap_handle
57 # - ist in einer session dieses ldap handle schon vorhanden, wird es zurückgegeben
58 # - ist es nicht vorhanden, wird es erzeugt, im heap für spätere ldap anfragen gespeichert und zurückgegeben
59 # - sessions die kein ldap handle brauchen, sollen auch keins haben
60 # - wird eine session geschlossen, muss das ldap verbindung vorher beendet werden
61 our $global_kernel;
63 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
64 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
65 my ($server);
66 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
67 my ($known_modules);
68 my ($pid_file, $procid, $pid, $log_file);
69 my ($arp_activ, $arp_fifo);
70 my ($xml);
71 my $sources_list;
72 my $max_clients;
73 my %repo_files=();
74 my $repo_path;
75 my %repo_dirs=();
76 # variables declared in config file are always set to 'our'
77 our (%cfg_defaults, $log_file, $pid_file, 
78     $server_ip, $server_port, $SIPackages_key, 
79     $arp_activ, $gosa_unit_tag,
80     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
81 );
83 # additional variable which should be globaly accessable
84 our $server_address;
85 our $server_mac_address;
86 our $bus_address;
87 our $gosa_address;
88 our $no_bus;
89 our $no_arp;
90 our $verbose;
91 our $forground;
92 our $cfg_file;
93 #our ($ldap_handle, $ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
94 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
97 # specifies the verbosity of the daemon_log
98 $verbose = 0 ;
100 # if foreground is not null, script will be not forked to background
101 $foreground = 0 ;
103 # specifies the timeout seconds while checking the online status of a registrating client
104 $ping_timeout = 5;
106 $no_bus = 0;
107 $bus_activ = "true";
109 $no_arp = 0;
111 our $prg= basename($0);
113 # holds all gosa jobs
114 our $job_db;
115 our $job_queue_tn = 'jobs';
116 my $job_queue_file_name;
117 my @job_queue_col_names = ("id INTEGER", 
118                 "timestamp", 
119                 "status DEFAULT 'none'", 
120                 "result DEFAULT 'none'", 
121                 "progress DEFAULT 'none'", 
122                 "headertag DEFAULT 'none'", 
123                 "targettag DEFAULT 'none'", 
124                 "xmlmessage DEFAULT 'none'", 
125                 "macaddress DEFAULT 'none'",
126                 );
128 # holds all other gosa-sd as well as the gosa-sd-bus
129 our $known_server_db;
130 our $known_server_tn = "known_server";
131 my $known_server_file_name;
132 my @known_server_col_names = ('hostname', 'status', 'hostkey', 'timestamp');
134 # holds all registrated clients
135 our $known_clients_db;
136 our $known_clients_tn = "known_clients";
137 my $known_clients_file_name;
138 my @known_clients_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'macaddress', 'events');
140 # holds all logged in user at each client 
141 our $login_users_db;
142 our $login_users_tn = "login_users";
143 my $login_users_file_name;
144 my @login_users_col_names = ('client', 'user', 'timestamp');
146 # holds all fai server, the debian release and tag
147 our $fai_server_db;
148 our $fai_server_tn = "fai_server"; 
149 my $fai_server_file_name;
150 our @fai_server_col_names = ('timestamp', 'server', 'release', 'sections', 'tag'); 
151 our $fai_release_tn = "fai_release"; 
152 our @fai_release_col_names = ('timestamp', 'release', 'class', 'type', 'state'); 
154 # holds all packages available from different repositories
155 our $packages_list_db;
156 our $packages_list_tn = "packages_list";
157 my $packages_list_file_name;
158 our @packages_list_col_names = ('distribution', 'package', 'version', 'section', 'description', 'template', 'timestamp');
159 my $outdir = "/tmp/packages_list_db";
160 my $arch = "i386"; 
162 # holds all messages which should be delivered to a user
163 our $messaging_db;
164 our $messaging_tn = "messaging"; 
165 our @messaging_col_names = ('subject', 'from', 'to', 'flag', 'direction', 'delivery_time', 'message', 'timestamp', 'id INTEGER', );
166 my $messaging_file_name;
168 # path to directory to store client install log files
169 our $client_fai_log_dir = "/var/log/fai"; 
171 # queue which stores taskes until one of the $max_children children are ready to process the task
172 my @tasks = qw();
173 my $max_children = 2;
176 %cfg_defaults = (
177 "general" => {
178     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
179     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
180     },
181 "bus" => {
182     "activ" => [\$bus_activ, "true"],
183     },
184 "server" => {
185     "port" => [\$server_port, "20081"],
186     "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
187     "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
188     "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
189     "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai.db'],
190     "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
191     "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
192     "source-list" => [\$sources_list, '/etc/apt/sources.list'],
193     "repo-path" => [\$repo_path, '/srv/www/repository'],
194     "ldap-uri" => [\$ldap_uri, ""],
195     "ldap-base" => [\$ldap_base, ""],
196     "ldap-admin-dn" => [\$ldap_admin_dn, ""],
197     "ldap-admin-password" => [\$ldap_admin_password, ""],
198     "gosa-unit-tag" => [\$gosa_unit_tag, ""],
199     "max-clients" => [\$max_clients, 10],
200     },
201 "GOsaPackages" => {
202     "ip" => [\$gosa_ip, "0.0.0.0"],
203     "port" => [\$gosa_port, "20082"],
204     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
205     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
206     "key" => [\$GosaPackages_key, "none"],
207     },
208 "SIPackages" => {
209     "key" => [\$SIPackages_key, "none"],
210     },
211 );
214 #===  FUNCTION  ================================================================
215 #         NAME:  usage
216 #   PARAMETERS:  nothing
217 #      RETURNS:  nothing
218 #  DESCRIPTION:  print out usage text to STDERR
219 #===============================================================================
220 sub usage {
221     print STDERR << "EOF" ;
222 usage: $prg [-hvf] [-c config]
224            -h        : this (help) message
225            -c <file> : config file
226            -f        : foreground, process will not be forked to background
227            -v        : be verbose (multiple to increase verbosity)
228            -no-bus   : starts $prg without connection to bus
229            -no-arp   : starts $prg without connection to arp module
230  
231 EOF
232     print "\n" ;
236 #===  FUNCTION  ================================================================
237 #         NAME:  read_configfile
238 #   PARAMETERS:  cfg_file - string -
239 #      RETURNS:  nothing
240 #  DESCRIPTION:  read cfg_file and set variables
241 #===============================================================================
242 sub read_configfile {
243     my $cfg;
244     if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
245         if( -r $cfg_file ) {
246             $cfg = Config::IniFiles->new( -file => $cfg_file );
247         } else {
248             print STDERR "Couldn't read config file!\n";
249         }
250     } else {
251         $cfg = Config::IniFiles->new() ;
252     }
253     foreach my $section (keys %cfg_defaults) {
254         foreach my $param (keys %{$cfg_defaults{ $section }}) {
255             my $pinfo = $cfg_defaults{ $section }{ $param };
256             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
257         }
258     }
262 #===  FUNCTION  ================================================================
263 #         NAME:  logging
264 #   PARAMETERS:  level - string - default 'info'
265 #                msg - string -
266 #                facility - string - default 'LOG_DAEMON'
267 #      RETURNS:  nothing
268 #  DESCRIPTION:  function for logging
269 #===============================================================================
270 sub daemon_log {
271     # log into log_file
272     my( $msg, $level ) = @_;
273     if(not defined $msg) { return }
274     if(not defined $level) { $level = 1 }
275     if(defined $log_file){
276         open(LOG_HANDLE, ">>$log_file");
277         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
278             print STDERR "cannot open $log_file: $!";
279             return }
280             chomp($msg);
281             if($level <= $verbose){
282                 my ($seconds, $minutes, $hours, $monthday, $month,
283                         $year, $weekday, $yearday, $sommertime) = localtime(time);
284                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
285                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
286                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
287                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
288                 $month = $monthnames[$month];
289                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
290                 $year+=1900;
291                 my $name = $prg;
293                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
294                 print LOG_HANDLE $log_msg;
295                 if( $foreground ) { 
296                     print STDERR $log_msg;
297                 }
298             }
299         close( LOG_HANDLE );
300     }
304 #===  FUNCTION  ================================================================
305 #         NAME:  check_cmdline_param
306 #   PARAMETERS:  nothing
307 #      RETURNS:  nothing
308 #  DESCRIPTION:  validates commandline parameter
309 #===============================================================================
310 sub check_cmdline_param () {
311     my $err_config;
312     my $err_counter = 0;
313         if(not defined($cfg_file)) {
314                 $cfg_file = "/etc/gosa-si/server.conf";
315                 if(! -r $cfg_file) {
316                         $err_config = "please specify a config file";
317                         $err_counter += 1;
318                 }
319     }
320     if( $err_counter > 0 ) {
321         &usage( "", 1 );
322         if( defined( $err_config)) { print STDERR "$err_config\n"}
323         print STDERR "\n";
324         exit( -1 );
325     }
329 #===  FUNCTION  ================================================================
330 #         NAME:  check_pid
331 #   PARAMETERS:  nothing
332 #      RETURNS:  nothing
333 #  DESCRIPTION:  handels pid processing
334 #===============================================================================
335 sub check_pid {
336     $pid = -1;
337     # Check, if we are already running
338     if( open(LOCK_FILE, "<$pid_file") ) {
339         $pid = <LOCK_FILE>;
340         if( defined $pid ) {
341             chomp( $pid );
342             if( -f "/proc/$pid/stat" ) {
343                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
344                 if( $stat ) {
345                                         daemon_log("ERROR: Already running",1);
346                     close( LOCK_FILE );
347                     exit -1;
348                 }
349             }
350         }
351         close( LOCK_FILE );
352         unlink( $pid_file );
353     }
355     # create a syslog msg if it is not to possible to open PID file
356     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
357         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
358         if (open(LOCK_FILE, '<', $pid_file)
359                 && ($pid = <LOCK_FILE>))
360         {
361             chomp($pid);
362             $msg .= "(PID $pid)\n";
363         } else {
364             $msg .= "(unable to read PID)\n";
365         }
366         if( ! ($foreground) ) {
367             openlog( $0, "cons,pid", "daemon" );
368             syslog( "warning", $msg );
369             closelog();
370         }
371         else {
372             print( STDERR " $msg " );
373         }
374         exit( -1 );
375     }
378 #===  FUNCTION  ================================================================
379 #         NAME:  import_modules
380 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
381 #                are stored
382 #      RETURNS:  nothing
383 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
384 #                state is on is imported by "require 'file';"
385 #===============================================================================
386 sub import_modules {
387     daemon_log(" ", 1);
389     if (not -e $modules_path) {
390         daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
391     }
393     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
394     while (defined (my $file = readdir (DIR))) {
395         if (not $file =~ /(\S*?).pm$/) {
396             next;
397         }
398                 my $mod_name = $1;
400         if( $file =~ /ArpHandler.pm/ ) {
401             if( $no_arp > 0 ) {
402                 next;
403             }
404         }
405         
406         eval { require $file; };
407         if ($@) {
408             daemon_log("ERROR: gosa-si-server could not load module $file", 1);
409             daemon_log("$@", 5);
410                 } else {
411                         my $info = eval($mod_name.'::get_module_info()');
412                         # Only load module if get_module_info() returns a non-null object
413                         if( $info ) {
414                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
415                                 $known_modules->{$mod_name} = $info;
416                                 daemon_log("INFO: module $mod_name loaded", 5);
417                         }
418                 }
419     }   
420     close (DIR);
424 #===  FUNCTION  ================================================================
425 #         NAME:  sig_int_handler
426 #   PARAMETERS:  signal - string - signal arose from system
427 #      RETURNS:  noting
428 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
429 #===============================================================================
430 sub sig_int_handler {
431     my ($signal) = @_;
433 #       if (defined($ldap_handle)) {
434 #               $ldap_handle->disconnect;
435 #       }
436     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
437     
439     daemon_log("shutting down gosa-si-server", 1);
440     system("kill `ps -C gosa-si-server -o pid=`");
442 $SIG{INT} = \&sig_int_handler;
445 sub check_key_and_xml_validity {
446     my ($crypted_msg, $module_key, $session_id) = @_;
447     my $msg;
448     my $msg_hash;
449     my $error_string;
450     eval{
451         $msg = &decrypt_msg($crypted_msg, $module_key);
453         if ($msg =~ /<xml>/i){
454             $msg =~ s/\s+/ /g;  # just for better daemon_log
455             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
456             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
458             ##############
459             # check header
460             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
461             my $header_l = $msg_hash->{'header'};
462             if( 1 > @{$header_l} ) { die 'empty header tag'; }
463             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
464             my $header = @{$header_l}[0];
465             if( 0 == length $header) { die 'empty string in header tag'; }
467             ##############
468             # check source
469             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
470             my $source_l = $msg_hash->{'source'};
471             if( 1 > @{$source_l} ) { die 'empty source tag'; }
472             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
473             my $source = @{$source_l}[0];
474             if( 0 == length $source) { die 'source error'; }
476             ##############
477             # check target
478             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
479             my $target_l = $msg_hash->{'target'};
480             if( 1 > @{$target_l} ) { die 'empty target tag'; }
481         }
482     };
483     if($@) {
484         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
485         $msg = undef;
486         $msg_hash = undef;
487     }
489     return ($msg, $msg_hash);
493 sub check_outgoing_xml_validity {
494     my ($msg) = @_;
496     my $msg_hash;
497     eval{
498         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
500         ##############
501         # check header
502         my $header_l = $msg_hash->{'header'};
503         if( 1 != @{$header_l} ) {
504             die 'no or more than one headers specified';
505         }
506         my $header = @{$header_l}[0];
507         if( 0 == length $header) {
508             die 'header has length 0';
509         }
511         ##############
512         # check source
513         my $source_l = $msg_hash->{'source'};
514         if( 1 != @{$source_l} ) {
515             die 'no or more than 1 sources specified';
516         }
517         my $source = @{$source_l}[0];
518         if( 0 == length $source) {
519             die 'source has length 0';
520         }
521         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
522                 $source =~ /^GOSA$/i ) {
523             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
524         }
525         
526         ##############
527         # check target  
528         my $target_l = $msg_hash->{'target'};
529         if( 0 == @{$target_l} ) {
530             die "no targets specified";
531         }
532         foreach my $target (@$target_l) {
533             if( 0 == length $target) {
534                 die "target has length 0";
535             }
536             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
537                     $target =~ /^GOSA$/i ||
538                     $target =~ /^\*$/ ||
539                     $target =~ /KNOWN_SERVER/i ||
540                     $target =~ /JOBDB/i ||
541                     $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 ){
542                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
543             }
544         }
545     };
546     if($@) {
547         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
548         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
549         $msg_hash = undef;
550     }
552     return ($msg_hash);
556 sub input_from_known_server {
557     my ($input, $remote_ip, $session_id) = @_ ;  
558     my ($msg, $msg_hash, $module);
560     my $sql_statement= "SELECT * FROM known_server";
561     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
563     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
564         my $host_name = $hit->{hostname};
565         if( not $host_name =~ "^$remote_ip") {
566             next;
567         }
568         my $host_key = $hit->{hostkey};
569         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
570         daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
572         # check if module can open msg envelope with module key
573         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
574         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
575             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
576             daemon_log("$@", 8);
577             next;
578         }
579         else {
580             $msg = $tmp_msg;
581             $msg_hash = $tmp_msg_hash;
582             $module = "SIPackages";
583             last;
584         }
585     }
587     if( (!$msg) || (!$msg_hash) || (!$module) ) {
588         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
589     }
590   
591     return ($msg, $msg_hash, $module);
595 sub input_from_known_client {
596     my ($input, $remote_ip, $session_id) = @_ ;  
597     my ($msg, $msg_hash, $module);
599     my $sql_statement= "SELECT * FROM known_clients";
600     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
601     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
602         my $host_name = $hit->{hostname};
603         if( not $host_name =~ /^$remote_ip:\d*$/) {
604                 next;
605                 }
606         my $host_key = $hit->{hostkey};
607         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
608         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
610         # check if module can open msg envelope with module key
611         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
613         if( (!$msg) || (!$msg_hash) ) {
614             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
615             &daemon_log("$@", 8);
616             next;
617         }
618         else {
619             $module = "SIPackages";
620             last;
621         }
622     }
624     if( (!$msg) || (!$msg_hash) || (!$module) ) {
625         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
626     }
628     return ($msg, $msg_hash, $module);
632 sub input_from_unknown_host {
633     no strict "refs";
634     my ($input, $session_id) = @_ ;
635     my ($msg, $msg_hash, $module);
636     my $error_string;
637     
638         my %act_modules = %$known_modules;
640         while( my ($mod, $info) = each(%act_modules)) {
642         # check a key exists for this module
643         my $module_key = ${$mod."_key"};
644         if( not defined $module_key ) {
645             if( $mod eq 'ArpHandler' ) {
646                 next;
647             }
648             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
649             next;
650         }
651         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
653         # check if module can open msg envelope with module key
654         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
655         if( (not defined $msg) || (not defined $msg_hash) ) {
656             next;
657         }
658         else {
659             $module = $mod;
660             last;
661         }
662     }
664     if( (!$msg) || (!$msg_hash) || (!$module)) {
665         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
666     }
668     return ($msg, $msg_hash, $module);
672 sub create_ciphering {
673     my ($passwd) = @_;
674         if((!defined($passwd)) || length($passwd)==0) {
675                 $passwd = "";
676         }
677     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
678     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
679     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
680     $my_cipher->set_iv($iv);
681     return $my_cipher;
685 sub encrypt_msg {
686     my ($msg, $key) = @_;
687     my $my_cipher = &create_ciphering($key);
688     my $len;
689     {
690             use bytes;
691             $len= 16-length($msg)%16;
692     }
693     $msg = "\0"x($len).$msg;
694     $msg = $my_cipher->encrypt($msg);
695     chomp($msg = &encode_base64($msg));
696     # there are no newlines allowed inside msg
697     $msg=~ s/\n//g;
698     return $msg;
702 sub decrypt_msg {
704     my ($msg, $key) = @_ ;
705     $msg = &decode_base64($msg);
706     my $my_cipher = &create_ciphering($key);
707     $msg = $my_cipher->decrypt($msg); 
708     $msg =~ s/\0*//g;
709     return $msg;
713 sub get_encrypt_key {
714     my ($target) = @_ ;
715     my $encrypt_key;
716     my $error = 0;
718     # target can be in known_server
719     if( not defined $encrypt_key ) {
720         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
721         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
722         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
723             my $host_name = $hit->{hostname};
724             if( $host_name ne $target ) {
725                 next;
726             }
727             $encrypt_key = $hit->{hostkey};
728             last;
729         }
730     }
732     # target can be in known_client
733     if( not defined $encrypt_key ) {
734         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
735         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
736         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
737             my $host_name = $hit->{hostname};
738             if( $host_name ne $target ) {
739                 next;
740             }
741             $encrypt_key = $hit->{hostkey};
742             last;
743         }
744     }
746     return $encrypt_key;
750 #===  FUNCTION  ================================================================
751 #         NAME:  open_socket
752 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
753 #                [PeerPort] string necessary if port not appended by PeerAddr
754 #      RETURNS:  socket IO::Socket::INET
755 #  DESCRIPTION:  open a socket to PeerAddr
756 #===============================================================================
757 sub open_socket {
758     my ($PeerAddr, $PeerPort) = @_ ;
759     if(defined($PeerPort)){
760         $PeerAddr = $PeerAddr.":".$PeerPort;
761     }
762     my $socket;
763     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
764             Porto => "tcp",
765             Type => SOCK_STREAM,
766             Timeout => 5,
767             );
768     if(not defined $socket) {
769         return;
770     }
771 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
772     return $socket;
776 #===  FUNCTION  ================================================================
777 #         NAME:  get_ip 
778 #   PARAMETERS:  interface name (i.e. eth0)
779 #      RETURNS:  (ip address) 
780 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
781 #===============================================================================
782 sub get_ip {
783         my $ifreq= shift;
784         my $result= "";
785         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
786         my $proto= getprotobyname('ip');
788         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
789                 or die "socket: $!";
791         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
792                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
793                 my ($port, $addr) = sockaddr_in $sin;
794                 my $ip            = inet_ntoa $addr;
796                 if ($ip && length($ip) > 0) {
797                         $result = $ip;
798                 }
799         }
801         return $result;
805 sub get_local_ip_for_remote_ip {
806         my $remote_ip= shift;
807         my $result="0.0.0.0";
809         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
810                 if($remote_ip eq "127.0.0.1") {
811                         $result = "127.0.0.1";
812                 } else {
813                         my $PROC_NET_ROUTE= ('/proc/net/route');
815                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
816                                 or die "Could not open $PROC_NET_ROUTE";
818                         my @ifs = <PROC_NET_ROUTE>;
820                         close(PROC_NET_ROUTE);
822                         # Eat header line
823                         shift @ifs;
824                         chomp @ifs;
825                         foreach my $line(@ifs) {
826                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
827                                 my $destination;
828                                 my $mask;
829                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
830                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
831                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
832                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
833                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
834                                         # destination matches route, save mac and exit
835                                         $result= &get_ip($Iface);
836                                         last;
837                                 }
838                         }
839                 }
840         } else {
841                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
842         }
843         return $result;
847 sub send_msg_to_target {
848     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
849     my $error = 0;
850     my $header;
851     my $new_status;
852     my $act_status;
853     my ($sql_statement, $res);
854   
855     if( $msg_header ) {
856         $header = "'$msg_header'-";
857     } else {
858         $header = "";
859     }
861         # Patch the source ip
862         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
863                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
864                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
865         }
867     # encrypt xml msg
868     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
870     # opensocket
871     my $socket = &open_socket($address);
872     if( !$socket ) {
873         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
874         $error++;
875     }
876     
877     if( $error == 0 ) {
878         # send xml msg
879         print $socket $crypted_msg."\n";
881         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
882         #daemon_log("DEBUG: message:\n$msg", 9);
883         
884     }
886     # close socket in any case
887     if( $socket ) {
888         close $socket;
889     }
891     if( $error > 0 ) { $new_status = "down"; }
892     else { $new_status = $msg_header; }
895     # known_clients
896     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
897     $res = $known_clients_db->select_dbentry($sql_statement);
898     if( keys(%$res) > 0) {
899         $act_status = $res->{1}->{'status'};
900         if( $act_status eq "down" ) {
901             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
902             $res = $known_clients_db->del_dbentry($sql_statement);
903             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
904         } else { 
905             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
906             $res = $known_clients_db->update_dbentry($sql_statement);
907             if($new_status eq "down"){
908                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
909             } else {
910                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
911             }
912         }
913     }
915     # known_server
916     $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
917     $res = $known_server_db->select_dbentry($sql_statement);
918     if( keys(%$res) > 0 ) {
919         $act_status = $res->{1}->{'status'};
920         if( $act_status eq "down" ) {
921             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
922             $res = $known_server_db->del_dbentry($sql_statement);
923             daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
924         } 
925         else { 
926             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
927             $res = $known_server_db->update_dbentry($sql_statement);
928             if($new_status eq "down"){
929                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
930             }
931             else {
932                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
933             }
934         }
935     }
936     return $error; 
940 sub update_jobdb_status_for_send_msgs {
941     my ($answer, $error) = @_;
942     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
943         my $jobdb_id = $1;
944             
945         # sending msg faild
946         if( $error ) {
947             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
948                 my $sql_statement = "UPDATE $job_queue_tn ".
949                     "SET status='error', result='can not deliver msg, please consult log file' ".
950                     "WHERE id='$jobdb_id'";
951                 my $res = $job_db->update_dbentry($sql_statement);
952             }
954         # sending msg was successful
955         } else {
956             my $sql_statement = "UPDATE $job_queue_tn ".
957                 "SET status='done' ".
958                 "WHERE id='$jobdb_id' AND status='processed'";
959             my $res = $job_db->update_dbentry($sql_statement);
960         }
961     }
964 sub _start {
965     my ($kernel) = $_[KERNEL];
966     &trigger_db_loop($kernel);
967     $global_kernel = $kernel;
968         $kernel->yield('create_fai_server_db', $fai_server_tn );
969         $kernel->yield('create_fai_release_db', $fai_release_tn );
970         $kernel->sig(USR1 => "sig_handler");
973 sub sig_handler {
974         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
975         daemon_log("0 INFO got signal '$signal'", 1); 
976         $kernel->sig_handled();
977         return;
980 sub next_task {
981     my ($session, $heap) = @_[SESSION, HEAP];
983     while ( keys( %{ $heap->{task} } ) < $max_children ) {
984         my $next_task = shift @tasks;
985         last unless defined $next_task;
987         my $task = POE::Wheel::Run->new(
988                 Program => sub { process_task($session, $heap, $next_task) },
989                 StdioFilter => POE::Filter::Reference->new(),
990                 StdoutEvent  => "task_result",
991                 StderrEvent  => "task_debug",
992                 CloseEvent   => "task_done",
993                );
995         $heap->{task}->{ $task->ID } = $task;
996     }
999 sub handle_task_result {
1000     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1001     my $client_answer = $result->{'answer'};
1002     if( $client_answer =~ s/session_id=(\d+)$// ) {
1003         my $session_id = $1;
1004         if( defined $session_id ) {
1005             my $session_reference = $kernel->ID_id_to_session($session_id);
1006             if( defined $session_reference ) {
1007                 $heap = $session_reference->get_heap();
1008             }
1009         }
1011         if(exists $heap->{'client'}) {
1012             $heap->{'client'}->put($client_answer);
1013         }
1014     }
1015     $kernel->sig(CHLD => "child_reap");
1018 sub handle_task_debug {
1019     my $result = $_[ARG0];
1020     print STDERR "$result\n";
1023 sub handle_task_done {
1024     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1025     delete $heap->{task}->{$task_id};
1026     $kernel->yield("next_task");
1029 sub process_task {
1030     no strict "refs";
1031     my ($session, $heap, $input) = @_;
1032     my $session_id = $session->ID;
1033     my ($msg, $msg_hash, $module);
1034     my $error = 0;
1035     my $answer_l;
1036     my ($answer_header, @answer_target_l, $answer_source);
1037     my $client_answer = "";
1039     daemon_log("", 5); 
1040     daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1041     daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1043     ####################
1044     # check incoming msg
1045     # msg is from a new client or gosa
1046     ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1047     # msg is from a gosa-si-server or gosa-si-bus
1048     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1049         ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1050     }
1051     # msg is from a gosa-si-client
1052     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1053         ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1054     }
1055     # an error occurred
1056     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1057         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1058         # could not understand a msg from its server the client cause a re-registering process
1059         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);
1060         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1061         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1062         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1063             my $host_name = $hit->{'hostname'};
1064             my $host_key = $hit->{'hostkey'};
1065             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1066             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1067             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1068         }
1069         $error++;
1070     }
1072     ######################
1073     # process incoming msg
1074     if( $error == 0) {
1075         daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1076                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1077         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1078         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1080         if ( 0 < @{$answer_l} ) {
1081             my $answer_str = join("\n", @{$answer_l});
1082             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1083         }
1084     }
1085     if( !$answer_l ) { $error++ };
1087     ########
1088     # answer
1089     if( $error == 0 ) {
1091         foreach my $answer ( @{$answer_l} ) {
1092             # for each answer in answer list
1093             
1094             # check outgoing msg to xml validity
1095             my $answer_hash = &check_outgoing_xml_validity($answer);
1096             if( not defined $answer_hash ) {
1097                 next;
1098             }
1099             
1100             $answer_header = @{$answer_hash->{'header'}}[0];
1101             @answer_target_l = @{$answer_hash->{'target'}};
1102             $answer_source = @{$answer_hash->{'source'}}[0];
1104             # deliver msg to all targets 
1105             foreach my $answer_target ( @answer_target_l ) {
1107                 # targets of msg are all gosa-si-clients in known_clients_db
1108                 if( $answer_target eq "*" ) {
1109                     # answer is for all clients
1110                     my $sql_statement= "SELECT * FROM known_clients";
1111                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1112                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1113                         my $host_name = $hit->{hostname};
1114                         my $host_key = $hit->{hostkey};
1115                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1116                         &update_jobdb_status_for_send_msgs($answer, $error);
1117                     }
1118                 }
1120                 # targets of msg are all gosa-si-server in known_server_db
1121                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1122                     # answer is for all server in known_server
1123                     my $sql_statement= "SELECT * FROM known_server";
1124                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1125                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1126                         my $host_name = $hit->{hostname};
1127                         my $host_key = $hit->{hostkey};
1128                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1129                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1130                         &update_jobdb_status_for_send_msgs($answer, $error);
1131                     }
1132                 }
1134                 # target of msg is GOsa
1135                                 elsif( $answer_target eq "GOSA" ) {
1136                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1137                                         my $add_on = "";
1138                     if( defined $session_id ) {
1139                         $add_on = ".session_id=$session_id";
1140                     }
1141                     # answer is for GOSA and has to returned to connected client
1142                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1143                     $client_answer = $gosa_answer.$add_on;
1144                 }
1146                 # target of msg is job queue at this host
1147                 elsif( $answer_target eq "JOBDB") {
1148                     $answer =~ /<header>(\S+)<\/header>/;   
1149                     my $header;
1150                     if( defined $1 ) { $header = $1; }
1151                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1152                     &update_jobdb_status_for_send_msgs($answer, $error);
1153                 }
1155                 # target of msg is a mac address
1156                 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 ) {
1157                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1158                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1159                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1160                     my $found_ip_flag = 0;
1161                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1162                         my $host_name = $hit->{hostname};
1163                         my $host_key = $hit->{hostkey};
1164                         $answer =~ s/$answer_target/$host_name/g;
1165                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1166                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1167                         &update_jobdb_status_for_send_msgs($answer, $error);
1168                         $found_ip_flag++ ;
1169                     }   
1170                     if( $found_ip_flag == 0) {
1171                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1172                         if( $bus_activ eq "true" ) { 
1173                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1174                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1175                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1176                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1177                                 my $bus_address = $hit->{hostname};
1178                                 my $bus_key = $hit->{hostkey};
1179                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1180                                 &update_jobdb_status_for_send_msgs($answer, $error);
1181                                 last;
1182                             }
1183                         }
1185                     }
1187                 #  answer is for one specific host   
1188                 } else {
1189                     # get encrypt_key
1190                     my $encrypt_key = &get_encrypt_key($answer_target);
1191                     if( not defined $encrypt_key ) {
1192                         # unknown target, forward msg to bus
1193                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1194                         if( $bus_activ eq "true" ) { 
1195                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1196                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1197                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1198                             my $res_length = keys( %{$query_res} );
1199                             if( $res_length == 0 ){
1200                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1201                                         "no bus found in known_server", 3);
1202                             }
1203                             else {
1204                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1205                                     my $bus_key = $hit->{hostkey};
1206                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1207                                     &update_jobdb_status_for_send_msgs($answer, $error);
1208                                 }
1209                             }
1210                         }
1211                         next;
1212                     }
1213                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1214                     &update_jobdb_status_for_send_msgs($answer, $error);
1215                 }
1216             }
1217         }
1218     }
1220     my $filter = POE::Filter::Reference->new();
1221     my %result = ( 
1222             status => "seems ok to me",
1223             answer => $client_answer,
1224             );
1226     my $output = $filter->put( [ \%result ] );
1227     print @$output;
1233 sub trigger_db_loop {
1234         my ($kernel) = @_ ;
1235         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1236         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1239 sub watch_for_done_jobs {
1240     my ($kernel,$heap) = @_[KERNEL, HEAP];
1242     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1243         " WHERE status='done'";
1244         my $res = $job_db->select_dbentry( $sql_statement );
1246     while( my ($id, $hit) = each %{$res} ) {
1247         my $jobdb_id = $hit->{id};
1248         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'"; 
1249         my $res = $job_db->del_dbentry($sql_statement);
1250     }
1252     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1255 sub watch_for_new_jobs {
1256         my ($kernel,$heap) = @_[KERNEL, HEAP];
1258         # check gosa job queue for jobs with executable timestamp
1259         my $timestamp = &get_time();
1260         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER) + 120) < $timestamp ORDER BY timestamp";
1261         my $res = $job_db->exec_statement( $sql_statement );
1263         # Merge all new jobs that would do the same actions
1264         my @drops;
1265         my $hits;
1266         foreach my $hit (reverse @{$res} ) {
1267                 my $macaddress= lc @{$hit}[8];
1268                 my $headertag= @{$hit}[5];
1269                 if(defined($hits->{$macaddress}->{$headertag})) {
1270                         push @drops, "DELETE FROM $job_queue_tn WHERE id = '$hits->{$macaddress}->{$headertag}[0]'";
1271                 }
1272                 $hits->{$macaddress}->{$headertag}= $hit;
1273         }
1275         # Delete new jobs with a matching job in state 'processing'
1276         foreach my $macaddress (keys %{$hits}) {
1277                 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1278                         my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1279                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1280                         my $res = $job_db->exec_statement( $sql_statement );
1281                         foreach my $hit (@{$res}) {
1282                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$jobdb_id'";
1283                         }
1284                 }
1285         }
1287         # Commit deletion
1288         $job_db->exec_statementlist(\@drops);
1290         # Look for new jobs that could be executed
1291         foreach my $macaddress (keys %{$hits}) {
1293                 # Look if there is an executing job
1294                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1295                 my $res = $job_db->exec_statement( $sql_statement );
1297                 # Skip new jobs for host if there is a processing job
1298                 if(defined($res) and defined @{$res}[0]) {
1299                         next;
1300                 }
1302                 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1303                         my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1304                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1306                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1307                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1308                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1310                         # expect macaddress is unique!!!!!!
1311                         my $target = $res_hash->{1}->{hostname};
1313                         # change header
1314                         $job_msg =~ s/<header>job_/<header>gosa_/;
1316                         # add sqlite_id
1317                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1319                         $job_msg =~ /<header>(\S+)<\/header>/;
1320                         my $header = $1 ;
1321                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1323                         # update status in job queue to 'processing'
1324                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1325                         my $res = $job_db->update_dbentry($sql_statement);
1327                         # We don't want parallel processing
1328                         last;
1329                 }
1330         }
1332         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1336 sub get_ldap_handle {
1337         my ($session_id) = @_;
1338         my $heap;
1339         my $ldap_handle;
1341         if (not defined $session_id ) { $session_id = 0 };
1343         if ($session_id == 0) {
1344                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
1345                 $ldap_handle = Net::LDAP->new( $ldap_uri );
1346                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1348         } else {
1349                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1350                 if( defined $session_reference ) {
1351                         $heap = $session_reference->get_heap();
1352                 }
1354                 if (not defined $heap) {
1355                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
1356                         return;
1357                 }
1359                 # TODO: This "if" is nonsense, because it doesn't prove that the
1360                 #       used handle is still valid - or if we've to reconnect...
1361                 #if (not exists $heap->{ldap_handle}) {
1362                         $ldap_handle = Net::LDAP->new( $ldap_uri );
1363                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password); 
1364                         $heap->{ldap_handle} = $ldap_handle;
1365                 #}
1366         }
1367         return $ldap_handle;
1371 sub change_fai_state {
1372     my ($st, $targets, $session_id) = @_;
1373     $session_id = 0 if not defined $session_id;
1374     # Set FAI state to localboot
1375     my %mapActions= (
1376         reboot    => '',
1377         update    => 'softupdate',
1378         localboot => 'localboot',
1379         reinstall => 'install',
1380         rescan    => '',
1381         wake      => '',
1382         memcheck  => 'memcheck',
1383         sysinfo   => 'sysinfo',
1384         install   => 'install',
1385     );
1387     # Return if this is unknown
1388     if (!exists $mapActions{ $st }){
1389         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1390       return;
1391     }
1393     my $state= $mapActions{ $st };
1395     my $ldap_handle = &get_ldap_handle($session_id);
1396     if( defined($ldap_handle) ) {
1398       # Build search filter for hosts
1399         my $search= "(&(objectClass=GOhard)";
1400         foreach (@{$targets}){
1401             $search.= "(macAddress=$_)";
1402         }
1403         $search.= ")";
1405       # If there's any host inside of the search string, procress them
1406         if (!($search =~ /macAddress/)){
1407             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1408             return;
1409         }
1411       # Perform search for Unit Tag
1412       my $mesg = $ldap_handle->search(
1413           base   => $ldap_base,
1414           scope  => 'sub',
1415           attrs  => ['dn', 'FAIstate', 'objectClass'],
1416           filter => "$search"
1417           );
1419           if ($mesg->count) {
1420                   my @entries = $mesg->entries;
1421                   foreach my $entry (@entries) {
1422                           # Only modify entry if it is not set to '$state'
1423                           if ($entry->get_value("FAIstate") ne "$state"){
1424                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1425                                   my $result;
1426                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1427                                   if (exists $tmp{'FAIobject'}){
1428                                           if ($state eq ''){
1429                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1430                                                           delete => [ FAIstate => [] ] ]);
1431                                           } else {
1432                                                   $result= $ldap_handle->modify($entry->dn, changes => [
1433                                                           replace => [ FAIstate => $state ] ]);
1434                                           }
1435                                   } elsif ($state ne ''){
1436                                           $result= $ldap_handle->modify($entry->dn, changes => [
1437                                                   add     => [ objectClass => 'FAIobject' ],
1438                                                   add     => [ FAIstate => $state ] ]);
1439                                   }
1441                                   # Errors?
1442                                   if ($result->code){
1443                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1444                                   }
1445                           } else {
1446                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
1447                           }  
1448                   }
1449           }
1450     # if no ldap handle defined
1451     } else {
1452         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1453     }
1458 sub change_goto_state {
1459     my ($st, $targets, $session_id) = @_;
1460     $session_id = 0  if not defined $session_id;
1462     # Switch on or off?
1463     my $state= $st eq 'active' ? 'active': 'locked';
1465     my $ldap_handle = &get_ldap_handle($session_id);
1466     if( defined($ldap_handle) ) {
1468       # Build search filter for hosts
1469       my $search= "(&(objectClass=GOhard)";
1470       foreach (@{$targets}){
1471         $search.= "(macAddress=$_)";
1472       }
1473       $search.= ")";
1475       # If there's any host inside of the search string, procress them
1476       if (!($search =~ /macAddress/)){
1477         return;
1478       }
1480       # Perform search for Unit Tag
1481       my $mesg = $ldap_handle->search(
1482           base   => $ldap_base,
1483           scope  => 'sub',
1484           attrs  => ['dn', 'gotoMode'],
1485           filter => "$search"
1486           );
1488       if ($mesg->count) {
1489         my @entries = $mesg->entries;
1490         foreach my $entry (@entries) {
1492           # Only modify entry if it is not set to '$state'
1493           if ($entry->get_value("gotoMode") ne $state){
1495             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1496             my $result;
1497             $result= $ldap_handle->modify($entry->dn, changes => [
1498                                                 replace => [ gotoMode => $state ] ]);
1500             # Errors?
1501             if ($result->code){
1502               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1503             }
1505           }
1506         }
1507       }
1509     }
1513 sub create_fai_server_db {
1514     my ($table_name, $kernel) = @_;
1515         my $result;
1516     my $ldap_handle = &get_ldap_handle();
1517         if(defined($ldap_handle)) {
1518                 daemon_log("INFO: create_fai_server_db: start", 5);
1519                 my $mesg= $ldap_handle->search(
1520                         base   => $ldap_base,
1521                         scope  => 'sub',
1522                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1523                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1524                 );
1525                 if($mesg->{'resultCode'} == 0 &&
1526                    $mesg->count != 0) {
1527                    foreach my $entry (@{$mesg->{entries}}) {
1528                            if($entry->exists('FAIrepository')) {
1529                                    # Add an entry for each Repository configured for server
1530                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1531                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1532                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1533                                                    $result= $fai_server_db->add_dbentry( { 
1534                                                                    table => $table_name,
1535                                                                    primkey => ['server', 'release', 'tag'],
1536                                                                    server => $tmp_url,
1537                                                                    release => $tmp_release,
1538                                                                    sections => $tmp_sections,
1539                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1540                                                            } );
1541                                            }
1542                                    }
1543                            }
1544                    }
1545                 daemon_log("INFO: create_fai_server_db: finished", 5);
1547                 # TODO: Find a way to post the 'create_packages_list_db' event
1548                 &create_packages_list_db($ldap_handle);
1549         }       
1550     
1551     $ldap_handle->disconnect;
1552         return $result;
1555 sub run_create_fai_server_db {
1556     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1557     my $task = POE::Wheel::Run->new(
1558             Program => sub { &create_fai_server_db($table_name,$kernel) },
1559             StdoutEvent  => "session_run_result",
1560             StderrEvent  => "session_run_debug",
1561             CloseEvent   => "session_run_done",
1562             );
1564     $heap->{task}->{ $task->ID } = $task;
1565     return;
1569 sub create_fai_release_db {
1570         my ($table_name) = @_;
1571         my $result;
1573     my $ldap_handle = &get_ldap_handle();
1574         if(defined($ldap_handle)) {
1575                 daemon_log("INFO: create_fai_release_db: start",5);
1576                 my $mesg= $ldap_handle->search(
1577                         base   => $ldap_base,
1578                         scope  => 'sub',
1579                         attrs  => [],
1580                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1581                 );
1582                 if($mesg->{'resultCode'} == 0 &&
1583                         $mesg->count != 0) {
1584                         # Walk through all possible FAI container ou's
1585                         my @sql_list;
1586                         my $timestamp= &get_time();
1587                         foreach my $ou (@{$mesg->{entries}}) {
1588                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle);
1589                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1590                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1591                                         if(@tmp_array) {
1592                                                 foreach my $entry (@tmp_array) {
1593                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1594                                                                 my $sql= 
1595                                                                 "INSERT INTO $table_name "
1596                                                                 ."(timestamp, release, class, type, state) VALUES ("
1597                                                                 .$timestamp.","
1598                                                                 ."'".$entry->{'release'}."',"
1599                                                                 ."'".$entry->{'class'}."',"
1600                                                                 ."'".$entry->{'type'}."',"
1601                                                                 ."'".$entry->{'state'}."')";
1602                                                                 push @sql_list, $sql;
1603                                                         }
1604                                                 }
1605                                         }
1606                                 }
1607                         }
1608                         daemon_log("DEBUG: Inserting ".scalar @sql_list." entries to DB",6);
1609                         if(@sql_list) {
1610                                 unshift @sql_list, "DELETE FROM $table_name";
1611                                 $fai_server_db->exec_statementlist(\@sql_list);
1612                         }
1613                         daemon_log("DEBUG: Done with inserting",6);
1614                 }
1615                 daemon_log("INFO: create_fai_release_db: finished",5);
1616         }
1617     $ldap_handle->disconnect;
1618         return $result;
1620 sub run_create_fai_release_db {
1621     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1622     my $task = POE::Wheel::Run->new(
1623             Program => sub { &create_fai_release_db($table_name) },
1624             StdoutEvent  => "session_run_result",
1625             StderrEvent  => "session_run_debug",
1626             CloseEvent   => "session_run_done",
1627             );
1629     $heap->{task}->{ $task->ID } = $task;
1630     return;
1633 sub get_fai_types {
1634         my $tmp_classes = shift || return undef;
1635         my @result;
1637         foreach my $type(keys %{$tmp_classes}) {
1638                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1639                         my $entry = {
1640                                 type => $type,
1641                                 state => $tmp_classes->{$type}[0],
1642                         };
1643                         push @result, $entry;
1644                 }
1645         }
1647         return @result;
1650 sub get_fai_state {
1651         my $result = "";
1652         my $tmp_classes = shift || return $result;
1654         foreach my $type(keys %{$tmp_classes}) {
1655                 if(defined($tmp_classes->{$type}[0])) {
1656                         $result = $tmp_classes->{$type}[0];
1657                         
1658                 # State is equal for all types in class
1659                         last;
1660                 }
1661         }
1663         return $result;
1666 sub resolve_fai_classes {
1667         my ($fai_base, $ldap_handle) = @_;
1668         my $result;
1669         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1670         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1671         my $fai_classes;
1673         daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1674         my $mesg= $ldap_handle->search(
1675                 base   => $fai_base,
1676                 scope  => 'sub',
1677                 attrs  => ['cn','objectClass','FAIstate'],
1678                 filter => $fai_filter,
1679         );
1680         daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1682         if($mesg->{'resultCode'} == 0 &&
1683                 $mesg->count != 0) {
1684                 foreach my $entry (@{$mesg->{entries}}) {
1685                         if($entry->exists('cn')) {
1686                                 my $tmp_dn= $entry->dn();
1688                                 # Skip classname and ou dn parts for class
1689                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1691                                 # Skip classes without releases
1692                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1693                                         next;
1694                                 }
1696                                 my $tmp_cn= $entry->get_value('cn');
1697                                 my $tmp_state= $entry->get_value('FAIstate');
1699                                 my $tmp_type;
1700                                 # Get FAI type
1701                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1702                                         if(grep $_ eq $oclass, @possible_fai_classes) {
1703                                                 $tmp_type= $oclass;
1704                                                 last;
1705                                         }
1706                                 }
1708                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1709                                         # A Subrelease
1710                                         my @sub_releases = split(/,/, $tmp_release);
1712                                         # Walk through subreleases and build hash tree
1713                                         my $hash;
1714                                         while(my $tmp_sub_release = pop @sub_releases) {
1715                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1716                                         }
1717                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1718                                 } else {
1719                                         # A branch, no subrelease
1720                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1721                                 }
1722                         } elsif (!$entry->exists('cn')) {
1723                                 my $tmp_dn= $entry->dn();
1724                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1726                                 # Skip classes without releases
1727                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1728                                         next;
1729                                 }
1731                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1732                                         # A Subrelease
1733                                         my @sub_releases= split(/,/, $tmp_release);
1735                                         # Walk through subreleases and build hash tree
1736                                         my $hash;
1737                                         while(my $tmp_sub_release = pop @sub_releases) {
1738                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1739                                         }
1740                                         # Remove the last two characters
1741                                         chop($hash);
1742                                         chop($hash);
1744                                         eval('$fai_classes->'.$hash.'= {}');
1745                                 } else {
1746                                         # A branch, no subrelease
1747                                         if(!exists($fai_classes->{$tmp_release})) {
1748                                                 $fai_classes->{$tmp_release} = {};
1749                                         }
1750                                 }
1751                         }
1752                 }
1754                 # The hash is complete, now we can honor the copy-on-write based missing entries
1755                 foreach my $release (keys %$fai_classes) {
1756                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1757                 }
1758         }
1759         return $result;
1762 sub apply_fai_inheritance {
1763        my $fai_classes = shift || return {};
1764        my $tmp_classes;
1766        # Get the classes from the branch
1767        foreach my $class (keys %{$fai_classes}) {
1768                # Skip subreleases
1769                if($class =~ /^ou=.*$/) {
1770                        next;
1771                } else {
1772                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1773                }
1774        }
1776        # Apply to each subrelease
1777        foreach my $subrelease (keys %{$fai_classes}) {
1778                if($subrelease =~ /ou=/) {
1779                        foreach my $tmp_class (keys %{$tmp_classes}) {
1780                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1781                                        $fai_classes->{$subrelease}->{$tmp_class} =
1782                                        deep_copy($tmp_classes->{$tmp_class});
1783                                } else {
1784                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1785                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1786                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1787                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
1788                                                }
1789                                        }
1790                                }
1791                        }
1792                }
1793        }
1795        # Find subreleases in deeper levels
1796        foreach my $subrelease (keys %{$fai_classes}) {
1797                if($subrelease =~ /ou=/) {
1798                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1799                                if($subsubrelease =~ /ou=/) {
1800                                        apply_fai_inheritance($fai_classes->{$subrelease});
1801                                }
1802                        }
1803                }
1804        }
1806        return $fai_classes;
1809 sub get_fai_release_entries {
1810         my $tmp_classes = shift || return;
1811         my $parent = shift || "";
1812         my @result = shift || ();
1814         foreach my $entry (keys %{$tmp_classes}) {
1815                 if(defined($entry)) {
1816                         if($entry =~ /^ou=.*$/) {
1817                                 my $release_name = $entry;
1818                                 $release_name =~ s/ou=//g;
1819                                 if(length($parent)>0) {
1820                                         $release_name = $parent."/".$release_name;
1821                                 }
1822                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1823                                 foreach my $bufentry(@bufentries) {
1824                                         push @result, $bufentry;
1825                                 }
1826                         } else {
1827                                 my @types = get_fai_types($tmp_classes->{$entry});
1828                                 foreach my $type (@types) {
1829                                         push @result, 
1830                                         {
1831                                                 'class' => $entry,
1832                                                 'type' => $type->{'type'},
1833                                                 'release' => $parent,
1834                                                 'state' => $type->{'state'},
1835                                         };
1836                                 }
1837                         }
1838                 }
1839         }
1841         return @result;
1844 sub deep_copy {
1845         my $this = shift;
1846         if (not ref $this) {
1847                 $this;
1848         } elsif (ref $this eq "ARRAY") {
1849                 [map deep_copy($_), @$this];
1850         } elsif (ref $this eq "HASH") {
1851                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1852         } else { die "what type is $_?" }
1856 sub session_run_result {
1857     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
1858     $kernel->sig(CHLD => "child_reap");
1861 sub session_run_debug {
1862     my $result = $_[ARG0];
1863     print STDERR "$result\n";
1866 sub session_run_done {
1867     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1868     delete $heap->{task}->{$task_id};
1871 sub create_sources_list {
1872     my ($ldap_handle) = @_;
1873         my $result="/tmp/gosa_si_tmp_sources_list";
1875         # Remove old file
1876         if(stat($result)) {
1877                 unlink($result);
1878         }
1880         my $fh;
1881         open($fh, ">$result") or return undef;
1882         if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1883                 my $mesg=$ldap_handle->search(
1884                                 base    => $ldap_server_dn,
1885                                 scope   => 'base',
1886                                 attrs   => 'FAIrepository',
1887                                 filter  => 'objectClass=FAIrepositoryServer'
1888                                 );
1889                 if($mesg->count) {
1890                         foreach my $entry(@{$mesg->{'entries'}}) {
1891                                 my ($server, $tag, $release, $sections)= split /\|/, $entry->get_value('FAIrepository');
1892                                 my $line = "deb $server $release";
1893                                 $sections =~ s/,/ /g;
1894                                 $line.= " $sections";
1895                                 print $fh $line."\n";
1896                         }
1897                 }
1898         }
1899         close($fh);
1901         return $result;
1904 sub create_packages_list_db {
1905     my ($ldap_handle, $sources_file) = @_ ;
1907     if (not defined $ldap_handle) { 
1908         daemon_log("0 ERROR: no ldap_handle available to create_packages_list_db", 1);
1909         return;
1910     }
1911     if (not defined $sources_file) { 
1912         $sources_file = &create_sources_list($ldap_handle);
1913     }
1915     my $line;
1916     daemon_log("INFO: create_packages_list_db: start", 5); 
1918     open(CONFIG, "<$sources_file") or do {
1919         daemon_log( "ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
1920         return;
1921     };
1922     
1923     # Read lines
1924     while ($line = <CONFIG>){
1925         # Unify
1926         chop($line);
1927         $line =~ s/^\s+//;
1928         $line =~ s/^\s+/ /;
1930         # Strip comments
1931         $line =~ s/#.*$//g;
1933         # Skip empty lines
1934         if ($line =~ /^\s*$/){
1935             next;
1936         }
1938         # Interpret deb line
1939         if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
1940             my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
1941             my $section;
1942             foreach $section (split(' ', $sections)){
1943                 &parse_package_info( $baseurl, $dist, $section );
1944             }
1945         }
1946     }
1948     close (CONFIG);
1950     daemon_log("INFO: create_packages_list_db: finished", 5); 
1951     return;
1954 sub run_create_packages_list_db {
1955     my ($session, $heap) = @_[SESSION, HEAP];
1956     my $task = POE::Wheel::Run->new(
1957             Program => sub {&create_packages_list_db},
1958             StdoutEvent  => "session_run_result",
1959             StderrEvent  => "session_run_debug",
1960             CloseEvent   => "session_run_done",
1961             );
1962     $heap->{task}->{ $task->ID } = $task;
1965 sub parse_package_info {
1966   my ($baseurl, $dist, $section)= @_;
1967   my ($package);
1969   my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
1970   $repo_dirs{ "${repo_path}/pool" } = 1;
1972   foreach $package ("Packages.gz"){
1973     daemon_log("DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
1974     get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section" );
1975     parse_package( "$outdir/$dist/$section", $dist, $path );
1976   }
1977   find(\&cleanup_and_extract, keys( %repo_dirs ) );
1980 sub get_package {
1981   my ($url, $dest)= @_;
1983   my $tpath = dirname($dest);
1984   -d "$tpath" || mkpath "$tpath";
1986   # This is ugly, but I've no time to take a look at "how it works in perl"
1987   if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
1988       system("gunzip -cd '$dest' > '$dest.in'");
1989       unlink($dest);
1990   } else {
1991       daemon_log("ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
1992   }
1993   return 0;
1996 sub parse_package {
1997     my ($path, $dist, $srv_path)= @_;
1998     my ($package, $version, $section, $description);
1999     my @sql_list;
2000     my $PACKAGES;
2002     if(not stat("$path.in")) {
2003         daemon_log("ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2004         return;
2005     }
2007     open($PACKAGES, "<$path.in");
2008         if(not defined($PACKAGES)) {
2009         daemon_log("ERROR: create_packages_list_db: parse_package: can not open '$path.in'",1); 
2010         return;
2011     }
2013     # Read lines
2014     while (<$PACKAGES>){
2015         my $line = $_;
2016         # Unify
2017         chop($line);
2019         # Use empty lines as a trigger
2020         if ($line =~ /^\s*$/){
2021             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
2022             push(@sql_list, $sql);
2023             $package = "none";
2024             $version = "none";
2025             $section = "none";
2026             $description = "none"; 
2027             next;
2028         }
2030         # Trigger for package name
2031         if ($line =~ /^Package:\s/){
2032             ($package)= ($line =~ /^Package: (.*)$/);
2033             next;
2034         }
2036         # Trigger for version
2037         if ($line =~ /^Version:\s/){
2038             ($version)= ($line =~ /^Version: (.*)$/);
2039             next;
2040         }
2042         # Trigger for description
2043         if ($line =~ /^Description:\s/){
2044             ($description)= ($line =~ /^Description: (.*)$/);
2045             next;
2046         }
2048         # Trigger for section
2049         if ($line =~ /^Section:\s/){
2050             ($section)= ($line =~ /^Section: (.*)$/);
2051             next;
2052         }
2054         # Trigger for filename
2055         if ($line =~ /^Filename:\s/){
2056                 my ($filename) = ($line =~ /^Filename: (.*)$/);
2057                 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2058                 next;
2059         }
2060     }
2062     close( $PACKAGES );
2063     unlink( "$path.in" );
2064     
2065     $packages_list_db->exec_statementlist(\@sql_list);
2068 sub store_fileinfo {
2069   my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2071   my %fileinfo = (
2072     'package' => $package,
2073     'dist' => $dist,
2074     'version' => $vers,
2075   );
2077   $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2080 sub cleanup_and_extract {
2081   my $fileinfo = $repo_files{ $File::Find::name };
2083   if( defined $fileinfo ) {
2085     my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2086     my $sql;
2087     my $package = $fileinfo->{ 'package' };
2088     my $newver = $fileinfo->{ 'version' };
2090     mkpath($dir);
2091     system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2093     if( -f "$dir/DEBIAN/templates" ) {
2095       daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2097       my $tmpl= "";
2098       {
2099           local $/=undef;
2100           open FILE, "$dir/DEBIAN/templates";
2101           $tmpl = &encode_base64(<FILE>);
2102           close FILE;
2103       }
2104       rmtree("$dir/DEBIAN/templates");
2106       $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2108     } else {
2109       $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2110     }
2112     my $res= $main::packages_list_db->update_dbentry($sql);
2113   }
2117 #==== MAIN = main ==============================================================
2118 #  parse commandline options
2119 Getopt::Long::Configure( "bundling" );
2120 GetOptions("h|help" => \&usage,
2121         "c|config=s" => \$cfg_file,
2122         "f|foreground" => \$foreground,
2123         "v|verbose+" => \$verbose,
2124         "no-bus+" => \$no_bus,
2125         "no-arp+" => \$no_arp,
2126            );
2128 #  read and set config parameters
2129 &check_cmdline_param ;
2130 &read_configfile;
2131 &check_pid;
2133 $SIG{CHLD} = 'IGNORE';
2135 # forward error messages to logfile
2136 if( ! $foreground ) {
2137   open( STDIN,  '+>/dev/null' );
2138   open( STDOUT, '+>&STDIN'    );
2139   open( STDERR, '+>&STDIN'    );
2142 # Just fork, if we are not in foreground mode
2143 if( ! $foreground ) { 
2144     chdir '/'                 or die "Can't chdir to /: $!";
2145     $pid = fork;
2146     setsid                    or die "Can't start a new session: $!";
2147     umask 0;
2148 } else { 
2149     $pid = $$; 
2152 # Do something useful - put our PID into the pid_file
2153 if( 0 != $pid ) {
2154     open( LOCK_FILE, ">$pid_file" );
2155     print LOCK_FILE "$pid\n";
2156     close( LOCK_FILE );
2157     if( !$foreground ) { 
2158         exit( 0 ) 
2159     };
2162 daemon_log(" ", 1);
2163 daemon_log("$0 started!", 1);
2165 if ($no_bus > 0) {
2166     $bus_activ = "false"
2169 # connect to gosa-si job queue
2170 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2171 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2173 # connect to known_clients_db
2174 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2175 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2177 # connect to known_server_db
2178 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2179 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2181 # connect to login_usr_db
2182 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2183 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2185 # connect to fai_server_db and fai_release_db
2186 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2187 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2188 $fai_server_db->create_table($fai_release_tn, \@fai_release_col_names);
2190 # connect to packages_list_db
2191 unlink($packages_list_file_name);
2192 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2193 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2195 # connect to messaging_db
2196 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2197 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2200 # create xml object used for en/decrypting
2201 $xml = new XML::Simple();
2203 # create socket for incoming xml messages
2205 POE::Component::Server::TCP->new(
2206         Port => $server_port,
2207         ClientInput => sub {
2208         my ($kernel, $input) = @_[KERNEL, ARG0];
2209         push(@tasks, $input);
2210         $kernel->yield("next_task");
2211         },
2212     InlineStates => {
2213         next_task => \&next_task,
2214         task_result => \&handle_task_result,
2215         task_done   => \&handle_task_done,
2216         task_debug  => \&handle_task_debug,
2217         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2218     }
2219 );
2221 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2223 # create session for repeatedly checking the job queue for jobs
2224 POE::Session->create(
2225         inline_states => {
2226                 _start => \&_start,
2227                 sig_handler => \&sig_handler,
2228                 watch_for_new_jobs => \&watch_for_new_jobs,
2229         watch_for_done_jobs => \&watch_for_done_jobs,
2230         create_packages_list_db => \&run_create_packages_list_db,
2231         create_fai_server_db => \&run_create_fai_server_db,
2232         create_fai_release_db => \&run_create_fai_release_db,
2233         session_run_result => \&session_run_result,
2234         session_run_debug => \&session_run_debug,
2235         session_run_done => \&session_run_done,
2236         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2237         }
2238 );
2241 # import all modules
2242 &import_modules;
2244 # check wether all modules are gosa-si valid passwd check
2246 POE::Kernel->run();
2247 exit;